summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/addons/content/OpenH264-license.txt59
-rw-r--r--components/addons/content/gmpPrefs.xul8
-rw-r--r--components/addons/extensions.manifest1
-rw-r--r--components/addons/jar.mn2
-rw-r--r--components/addons/moz.build4
-rw-r--r--components/addons/src/GMPInstallManager.jsm917
-rw-r--r--components/addons/src/GMPProvider.jsm605
-rw-r--r--components/addons/src/GMPUtils.jsm187
-rw-r--r--components/addons/src/ProductAddonChecker.jsm464
-rw-r--r--components/global/content/gmp-sources/openh264.json57
-rw-r--r--components/global/content/gmp-sources/widevinecdm.json49
-rw-r--r--components/global/content/process-content.js7
-rw-r--r--components/global/jar.mn2
-rw-r--r--dom/ipc/ContentChild.cpp17
-rw-r--r--dom/ipc/ContentChild.h7
-rw-r--r--dom/ipc/ContentParent.cpp18
-rw-r--r--dom/ipc/ContentParent.h6
-rw-r--r--dom/ipc/PContent.ipdl27
-rw-r--r--dom/media/AbstractMediaDecoder.h4
-rw-r--r--dom/media/DecoderDoctorDiagnostics.cpp7
-rw-r--r--dom/media/DecoderDoctorDiagnostics.h7
-rw-r--r--dom/media/MediaDecoder.cpp29
-rw-r--r--dom/media/MediaDecoder.h2
-rw-r--r--dom/media/MediaFormatReader.cpp6
-rw-r--r--dom/media/MediaFormatReader.h2
-rw-r--r--dom/media/MediaPrefs.h6
-rw-r--r--dom/media/gmp-plugin-openh264/fakeopenh264.info4
-rw-r--r--dom/media/gmp-plugin-openh264/fakeopenh264.voucher1
-rw-r--r--dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp429
-rw-r--r--dom/media/gmp-plugin-openh264/moz.build29
-rw-r--r--dom/media/gmp-plugin/fake.info5
-rw-r--r--dom/media/gmp-plugin/fake.voucher1
-rw-r--r--dom/media/gmp-plugin/gmp-fake.cpp98
-rw-r--r--dom/media/gmp-plugin/gmp-test-decryptor.cpp608
-rw-r--r--dom/media/gmp-plugin/gmp-test-decryptor.h102
-rw-r--r--dom/media/gmp-plugin/gmp-test-output-protection.h129
-rw-r--r--dom/media/gmp-plugin/gmp-test-storage.cpp233
-rw-r--r--dom/media/gmp-plugin/gmp-test-storage.h63
-rw-r--r--dom/media/gmp-plugin/moz.build31
-rw-r--r--dom/media/gmp/GMPAudioDecoderChild.cpp172
-rw-r--r--dom/media/gmp/GMPAudioDecoderChild.h51
-rw-r--r--dom/media/gmp/GMPAudioDecoderParent.cpp369
-rw-r--r--dom/media/gmp/GMPAudioDecoderParent.h72
-rw-r--r--dom/media/gmp/GMPAudioDecoderProxy.h48
-rw-r--r--dom/media/gmp/GMPAudioHost.cpp162
-rw-r--r--dom/media/gmp/GMPAudioHost.h71
-rw-r--r--dom/media/gmp/GMPCallbackBase.h21
-rw-r--r--dom/media/gmp/GMPChild.cpp491
-rw-r--r--dom/media/gmp/GMPChild.h92
-rw-r--r--dom/media/gmp/GMPContentChild.cpp320
-rw-r--r--dom/media/gmp/GMPContentChild.h58
-rw-r--r--dom/media/gmp/GMPContentParent.cpp298
-rw-r--r--dom/media/gmp/GMPContentParent.h103
-rw-r--r--dom/media/gmp/GMPCrashHelperHolder.h81
-rw-r--r--dom/media/gmp/GMPDecryptorChild.cpp403
-rw-r--r--dom/media/gmp/GMPDecryptorChild.h142
-rw-r--r--dom/media/gmp/GMPDecryptorParent.cpp384
-rw-r--r--dom/media/gmp/GMPDecryptorParent.h129
-rw-r--r--dom/media/gmp/GMPDecryptorProxy.h62
-rw-r--r--dom/media/gmp/GMPDiskStorage.cpp499
-rw-r--r--dom/media/gmp/GMPEncryptedBufferDataImpl.cpp132
-rw-r--r--dom/media/gmp/GMPEncryptedBufferDataImpl.h92
-rw-r--r--dom/media/gmp/GMPLoader.cpp209
-rw-r--r--dom/media/gmp/GMPLoader.h96
-rw-r--r--dom/media/gmp/GMPMemoryStorage.cpp95
-rw-r--r--dom/media/gmp/GMPMessageUtils.h253
-rw-r--r--dom/media/gmp/GMPParent.cpp927
-rw-r--r--dom/media/gmp/GMPParent.h242
-rw-r--r--dom/media/gmp/GMPPlatform.cpp300
-rw-r--r--dom/media/gmp/GMPPlatform.h56
-rw-r--r--dom/media/gmp/GMPProcessChild.cpp82
-rw-r--r--dom/media/gmp/GMPProcessChild.h43
-rw-r--r--dom/media/gmp/GMPProcessParent.cpp84
-rw-r--r--dom/media/gmp/GMPProcessParent.h52
-rw-r--r--dom/media/gmp/GMPService.cpp557
-rw-r--r--dom/media/gmp/GMPService.h142
-rw-r--r--dom/media/gmp/GMPServiceChild.cpp478
-rw-r--r--dom/media/gmp/GMPServiceChild.h105
-rw-r--r--dom/media/gmp/GMPServiceParent.cpp1933
-rw-r--r--dom/media/gmp/GMPServiceParent.h261
-rw-r--r--dom/media/gmp/GMPSharedMemManager.cpp98
-rw-r--r--dom/media/gmp/GMPSharedMemManager.h82
-rw-r--r--dom/media/gmp/GMPStorage.h41
-rw-r--r--dom/media/gmp/GMPStorageChild.cpp380
-rw-r--r--dom/media/gmp/GMPStorageChild.h118
-rw-r--r--dom/media/gmp/GMPStorageParent.cpp220
-rw-r--r--dom/media/gmp/GMPStorageParent.h49
-rw-r--r--dom/media/gmp/GMPTimerChild.cpp67
-rw-r--r--dom/media/gmp/GMPTimerChild.h46
-rw-r--r--dom/media/gmp/GMPTimerParent.cpp123
-rw-r--r--dom/media/gmp/GMPTimerParent.h61
-rw-r--r--dom/media/gmp/GMPTypes.ipdlh86
-rw-r--r--dom/media/gmp/GMPUtils.cpp230
-rw-r--r--dom/media/gmp/GMPUtils.h89
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.cpp254
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.h75
-rw-r--r--dom/media/gmp/GMPVideoDecoderParent.cpp513
-rw-r--r--dom/media/gmp/GMPVideoDecoderParent.h104
-rw-r--r--dom/media/gmp/GMPVideoDecoderProxy.h56
-rw-r--r--dom/media/gmp/GMPVideoEncodedFrameImpl.cpp324
-rw-r--r--dom/media/gmp/GMPVideoEncodedFrameImpl.h122
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.cpp235
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.h75
-rw-r--r--dom/media/gmp/GMPVideoEncoderParent.cpp382
-rw-r--r--dom/media/gmp/GMPVideoEncoderParent.h93
-rw-r--r--dom/media/gmp/GMPVideoEncoderProxy.h56
-rw-r--r--dom/media/gmp/GMPVideoHost.cpp120
-rw-r--r--dom/media/gmp/GMPVideoHost.h57
-rw-r--r--dom/media/gmp/GMPVideoPlaneImpl.cpp225
-rw-r--r--dom/media/gmp/GMPVideoPlaneImpl.h66
-rw-r--r--dom/media/gmp/GMPVideoi420FrameImpl.cpp365
-rw-r--r--dom/media/gmp/GMPVideoi420FrameImpl.h84
-rw-r--r--dom/media/gmp/PGMP.ipdl41
-rw-r--r--dom/media/gmp/PGMPAudioDecoder.ipdl37
-rw-r--r--dom/media/gmp/PGMPContent.ipdl33
-rw-r--r--dom/media/gmp/PGMPDecryptor.ipdl92
-rw-r--r--dom/media/gmp/PGMPService.ipdl32
-rw-r--r--dom/media/gmp/PGMPStorage.ipdl36
-rw-r--r--dom/media/gmp/PGMPTimer.ipdl22
-rw-r--r--dom/media/gmp/PGMPVideoDecoder.ipdl50
-rw-r--r--dom/media/gmp/PGMPVideoEncoder.ipdl48
-rw-r--r--dom/media/gmp/README.txt1
-rw-r--r--dom/media/gmp/gmp-api/gmp-async-shutdown.h54
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-codec.h43
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-decode.h84
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-host.h32
-rw-r--r--dom/media/gmp/gmp-api/gmp-audio-samples.h74
-rw-r--r--dom/media/gmp/gmp-api/gmp-decryption.h459
-rw-r--r--dom/media/gmp/gmp-api/gmp-entrypoints.h75
-rw-r--r--dom/media/gmp/gmp-api/gmp-errors.h58
-rw-r--r--dom/media/gmp/gmp-api/gmp-platform.h118
-rw-r--r--dom/media/gmp/gmp-api/gmp-storage.h141
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-codec.h226
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-decode.h127
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-encode.h135
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame-encoded.h99
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame-i420.h132
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame.h51
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-host.h53
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-plane.h94
-rw-r--r--dom/media/gmp/moz.build140
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginChromeService.idl51
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginService.idl169
-rw-r--r--dom/media/gmp/rlz/COPYING14
-rw-r--r--dom/media/gmp/rlz/GMPDeviceBinding.cpp152
-rw-r--r--dom/media/gmp/rlz/GMPDeviceBinding.h21
-rw-r--r--dom/media/gmp/rlz/README.mozilla6
-rw-r--r--dom/media/gmp/rlz/lib/assert.h14
-rw-r--r--dom/media/gmp/rlz/lib/machine_id.h19
-rw-r--r--dom/media/gmp/rlz/lib/string_utils.cc34
-rw-r--r--dom/media/gmp/rlz/lib/string_utils.h20
-rw-r--r--dom/media/gmp/rlz/mac/lib/machine_id_mac.cc320
-rw-r--r--dom/media/gmp/rlz/moz.build34
-rw-r--r--dom/media/gmp/rlz/sha256.c469
-rw-r--r--dom/media/gmp/rlz/sha256.h46
-rw-r--r--dom/media/gmp/rlz/win/lib/machine_id_win.cc134
-rw-r--r--dom/media/moz.build6
-rw-r--r--dom/media/platforms/PDMFactory.cpp14
-rw-r--r--dom/media/platforms/PDMFactory.h1
-rw-r--r--dom/media/platforms/PlatformDecoderModule.h4
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp306
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h112
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp170
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.h57
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp388
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h122
-rw-r--r--dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp90
-rw-r--r--dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h181
-rw-r--r--dom/media/platforms/agnostic/gmp/moz.build23
-rw-r--r--dom/media/platforms/moz.build1
-rw-r--r--dom/media/platforms/wmf/WMFVideoMFTManager.cpp16
-rw-r--r--dom/media/platforms/wrappers/H264Converter.cpp4
-rw-r--r--dom/media/platforms/wrappers/H264Converter.h1
-rw-r--r--dom/media/webaudio/BufferDecoder.cpp10
-rw-r--r--dom/media/webaudio/BufferDecoder.h5
-rw-r--r--dom/media/webaudio/MediaBufferDecoder.cpp22
-rw-r--r--dom/media/webaudio/moz.build3
-rw-r--r--ipc/app/moz.build9
-rw-r--r--ipc/contentproc/plugin-container.cpp16
-rw-r--r--layout/build/nsLayoutModule.cpp7
-rw-r--r--modules/ForgetAboutSite.jsm9
-rw-r--r--python/mozbuild/mozbuild/mach_commands.py10
-rw-r--r--system/graphics/layers/ipc/PAPZCTreeManager.ipdl4
-rw-r--r--system/platform-prefs.js46
-rw-r--r--system/runtime/nsAppRunner.cpp1
-rw-r--r--system/runtime/nsEmbedFunctions.cpp17
-rw-r--r--xpcom/build/XREChildData.h7
-rw-r--r--xpcom/build/nsXULAppAPI.h9
-rw-r--r--xpcom/system/nsIXULRuntime.idl3
189 files changed, 25036 insertions, 24 deletions
diff --git a/components/addons/content/OpenH264-license.txt b/components/addons/content/OpenH264-license.txt
new file mode 100644
index 000000000..ad37989b8
--- /dev/null
+++ b/components/addons/content/OpenH264-license.txt
@@ -0,0 +1,59 @@
+-------------------------------------------------------
+About The Cisco-Provided Binary of OpenH264 Video Codec
+-------------------------------------------------------
+
+Cisco provides this program under the terms of the BSD license.
+
+Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met.
+
+As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice.
+
+For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary
+
+A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org
+
+-----------
+BSD License
+-----------
+
+Copyright © 2014 Cisco Systems, Inc.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----------------------------------------
+AVC/H.264 Patent Portfolio License Notice
+-----------------------------------------
+
+The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software:
+
+THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEO”) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM
+
+Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla
+
+---------------------------------------------
+AVC/H.264 Patent Portfolio License Conditions
+---------------------------------------------
+
+In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met:
+
+1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device;
+
+2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary;
+
+3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text:
+
+ "OpenH264 Video Codec provided by Cisco Systems, Inc."
+
+4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user.
+
+
+
+ v1.0
diff --git a/components/addons/content/gmpPrefs.xul b/components/addons/content/gmpPrefs.xul
new file mode 100644
index 000000000..ea7ee92fa
--- /dev/null
+++ b/components/addons/content/gmpPrefs.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+- License, v. 2.0. If a copy of the MPL was not distributed with this file,
+- You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- This is intentionally empty and a dummy to let the GMPProvider
+ have a preferences button in the list view. -->
diff --git a/components/addons/extensions.manifest b/components/addons/extensions.manifest
index 35d605575..b56152e10 100644
--- a/components/addons/extensions.manifest
+++ b/components/addons/extensions.manifest
@@ -13,3 +13,4 @@ contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fd
category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
+category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm
diff --git a/components/addons/jar.mn b/components/addons/jar.mn
index 0f4bfce26..878be4df1 100644
--- a/components/addons/jar.mn
+++ b/components/addons/jar.mn
@@ -29,6 +29,8 @@ toolkit.jar:
content/mozapps/extensions/newaddon.js (content/newaddon.js)
content/mozapps/extensions/setting.xml (content/setting.xml)
content/mozapps/extensions/pluginPrefs.xul (content/pluginPrefs.xul)
+ content/mozapps/extensions/gmpPrefs.xul (content/gmpPrefs.xul)
+ content/mozapps/extensions/OpenH264-license.txt (content/OpenH264-license.txt)
content/mozapps/xpinstall/xpinstallConfirm.xul (content/xpinstallConfirm.xul)
content/mozapps/xpinstall/xpinstallConfirm.js (content/xpinstallConfirm.js)
content/mozapps/xpinstall/xpinstallConfirm.css (content/xpinstallConfirm.css)
diff --git a/components/addons/moz.build b/components/addons/moz.build
index 22193c1e7..6b1e2fe93 100644
--- a/components/addons/moz.build
+++ b/components/addons/moz.build
@@ -39,6 +39,8 @@ EXTRA_JS_MODULES += [
EXTRA_PP_JS_MODULES += [
'src/AddonManager.jsm',
+ 'src/GMPInstallManager.jsm',
+ 'src/GMPUtils.jsm',
]
EXTRA_JS_MODULES.addons += [
@@ -46,8 +48,10 @@ EXTRA_JS_MODULES.addons += [
'src/AddonRepository.jsm',
'src/AddonRepository_SQLiteMigrator.jsm',
'src/Content.js',
+ 'src/GMPProvider.jsm',
'src/LightweightThemeImageOptimizer.jsm',
'src/PluginProvider.jsm',
+ 'src/ProductAddonChecker.jsm',
'src/SpellCheckDictionaryBootstrap.js',
]
diff --git a/components/addons/src/GMPInstallManager.jsm b/components/addons/src/GMPInstallManager.jsm
new file mode 100644
index 000000000..fe4e2de10
--- /dev/null
+++ b/components/addons/src/GMPInstallManager.jsm
@@ -0,0 +1,917 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
+ Components;
+// Chunk size for the incremental downloader
+const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
+// Incremental downloader interval
+const DOWNLOAD_INTERVAL = 0;
+// 1 day default
+const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/GMPUtils.jsm");
+
+this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader",
+ "GMPAddon"];
+
+var gLocale = null;
+
+// Shared code for suppressing bad cert dialogs
+XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
+ let temp = { };
+ Cu.import("resource://gre/modules/CertUtils.jsm", temp);
+ return temp;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+/**
+ * Number of milliseconds after which we need to cancel `checkForAddons`.
+ *
+ * Bug 1087674 suggests that the XHR we use in `checkForAddons` may
+ * never terminate in presence of network nuisances (e.g. strange
+ * antivirus behavior). This timeout is a defensive measure to ensure
+ * that we fail cleanly in such case.
+ */
+const CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS = 20000;
+
+function getScopedLogger(prefix) {
+ // `PARENT_LOGGER_ID.` being passed here effectively links this logger
+ // to the parentLogger.
+ return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " ");
+}
+
+// This is copied directly from nsUpdateService.js
+// It is used for calculating the URL string w/ var replacement.
+// TODO: refactor this out somewhere else
+XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
+ let osVersion;
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ try {
+ osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+ }
+ catch (e) {
+ LOG("gOSVersion - OS Version unknown: updates are not possible.");
+ }
+
+ if (osVersion) {
+#ifdef XP_WIN
+ const BYTE = ctypes.uint8_t;
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const WCHAR = ctypes.char16_t;
+ const BOOL = ctypes.int;
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+ const SZCSDVERSIONLENGTH = 128;
+ const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
+ [
+ {dwOSVersionInfoSize: DWORD},
+ {dwMajorVersion: DWORD},
+ {dwMinorVersion: DWORD},
+ {dwBuildNumber: DWORD},
+ {dwPlatformId: DWORD},
+ {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
+ {wServicePackMajor: WORD},
+ {wServicePackMinor: WORD},
+ {wSuiteMask: WORD},
+ {wProductType: BYTE},
+ {wReserved: BYTE}
+ ]);
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+ const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+ [
+ {wProcessorArchitecture: WORD},
+ {wReserved: WORD},
+ {dwPageSize: DWORD},
+ {lpMinimumApplicationAddress: ctypes.voidptr_t},
+ {lpMaximumApplicationAddress: ctypes.voidptr_t},
+ {dwActiveProcessorMask: DWORD.ptr},
+ {dwNumberOfProcessors: DWORD},
+ {dwProcessorType: DWORD},
+ {dwAllocationGranularity: DWORD},
+ {wProcessorLevel: WORD},
+ {wProcessorRevision: WORD}
+ ]);
+
+ let kernel32 = false;
+ try {
+ kernel32 = ctypes.open("Kernel32");
+ } catch (e) {
+ LOG("gOSVersion - Unable to open kernel32! " + e);
+ osVersion += ".unknown (unknown)";
+ }
+
+ if(kernel32) {
+ try {
+ // Get Service pack info
+ try {
+ let GetVersionEx = kernel32.declare("GetVersionExW",
+ ctypes.default_abi,
+ BOOL,
+ OSVERSIONINFOEXW.ptr);
+ let winVer = OSVERSIONINFOEXW();
+ winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+ if(0 !== GetVersionEx(winVer.address())) {
+ osVersion += "." + winVer.wServicePackMajor
+ + "." + winVer.wServicePackMinor;
+ } else {
+ LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
+ osVersion += ".unknown";
+ }
+ } catch (e) {
+ LOG("gOSVersion - error getting service pack information. Exception: " + e);
+ osVersion += ".unknown";
+ }
+
+ // Get processor architecture
+ let arch = "unknown";
+ try {
+ let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
+ ctypes.default_abi,
+ ctypes.void_t,
+ SYSTEM_INFO.ptr);
+ let sysInfo = SYSTEM_INFO();
+ // Default to unknown
+ sysInfo.wProcessorArchitecture = 0xffff;
+
+ GetNativeSystemInfo(sysInfo.address());
+ switch(sysInfo.wProcessorArchitecture) {
+ case 9:
+ arch = "x64";
+ break;
+ case 6:
+ arch = "IA64";
+ break;
+ case 0:
+ arch = "x86";
+ break;
+ }
+ } catch (e) {
+ LOG("gOSVersion - error getting processor architecture. Exception: " + e);
+ } finally {
+ osVersion += " (" + arch + ")";
+ }
+ } finally {
+ kernel32.close();
+ }
+ }
+#endif
+
+ try {
+ osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+ }
+ catch (e) {
+ // Not all platforms have a secondary widget library, so an error is nothing to worry about.
+ }
+ osVersion = encodeURIComponent(osVersion);
+ }
+ return osVersion;
+});
+
+/**
+ * Provides an easy API for downloading and installing GMP Addons
+ */
+function GMPInstallManager() {
+}
+/**
+ * Temp file name used for downloading
+ */
+GMPInstallManager.prototype = {
+ /**
+ * Obtains a URL with replacement of vars
+ */
+ _getURL: function() {
+ let log = getScopedLogger("GMPInstallManager._getURL");
+ // Use the override URL if it is specified. The override URL is just like
+ // the normal URL but it does not check the cert.
+ let url = GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE);
+ if (url) {
+ log.info("Using override url: " + url);
+ } else {
+ url = GMPPrefs.get(GMPPrefs.KEY_URL);
+ log.info("Using url: " + url);
+ }
+
+ url = UpdateUtils.formatUpdateURL(url);
+ log.info("Using url (with replacement): " + url);
+ return url;
+ },
+ /**
+ * Performs an addon check.
+ * @return a promise which will be resolved or rejected.
+ * The promise is resolved with an array of GMPAddons
+ * The promise is rejected with an object with properties:
+ * target: The XHR request object
+ * status: The HTTP status code
+ * type: Sometimes specifies type of rejection
+ */
+ checkForAddons: function() {
+ let log = getScopedLogger("GMPInstallManager.checkForAddons");
+ if (this._deferred) {
+ log.error("checkForAddons already called");
+ return Promise.reject({type: "alreadycalled"});
+ }
+ this._deferred = Promise.defer();
+ let url = this._getURL();
+
+ this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsISupports);
+ // This is here to let unit test code override XHR
+ if (this._request.wrappedJSObject) {
+ this._request = this._request.wrappedJSObject;
+ }
+ this._request.open("GET", url, true);
+ let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true);
+ this._request.channel.notificationCallbacks =
+ new gCertUtils.BadCertHandler(allowNonBuiltIn);
+ // Prevent the request from reading from the cache.
+ this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+
+ this._request.overrideMimeType("text/xml");
+ // The Cache-Control header is only interpreted by proxies and the
+ // final destination. It does not help if a resource is already
+ // cached locally.
+ this._request.setRequestHeader("Cache-Control", "no-cache");
+ // HTTP/1.0 servers might not implement Cache-Control and
+ // might only implement Pragma: no-cache
+ this._request.setRequestHeader("Pragma", "no-cache");
+
+ this._request.timeout = CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS;
+ this._request.addEventListener("error", event => this.onFailXML("onErrorXML", event), false);
+ this._request.addEventListener("abort", event => this.onFailXML("onAbortXML", event), false);
+ this._request.addEventListener("timeout", event => this.onFailXML("onTimeoutXML", event), false);
+ this._request.addEventListener("load", event => this.onLoadXML(event), false);
+
+ log.info("sending request to: " + url);
+ this._request.send(null);
+
+ return this._deferred.promise;
+ },
+ /**
+ * Installs the specified addon and calls a callback when done.
+ * @param gmpAddon The GMPAddon object to install
+ * @return a promise which will be resolved or rejected
+ * The promise will resolve with an array of paths that were extracted
+ * The promise will reject with an error object:
+ * target: The XHR request object
+ * status: The HTTP status code
+ * type: A string to represent the type of error
+ * downloaderr, verifyerr or previouserrorencountered
+ */
+ installAddon: function(gmpAddon) {
+ if (this._deferred) {
+ log.error("previous error encountered");
+ return Promise.reject({type: "previouserrorencountered"});
+ }
+ this.gmpDownloader = new GMPDownloader(gmpAddon);
+ return this.gmpDownloader.start();
+ },
+ _getTimeSinceLastCheck: function() {
+ let now = Math.round(Date.now() / 1000);
+ // Default to 0 here because `now - 0` will be returned later if that case
+ // is hit. We want a large value so a check will occur.
+ let lastCheck = GMPPrefs.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
+ // Handle clock jumps, return now since we want it to represent
+ // a lot of time has passed since the last check.
+ if (now < lastCheck) {
+ return now;
+ }
+ return now - lastCheck;
+ },
+ get _isEMEEnabled() {
+ return GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true);
+ },
+ _isAddonUpdateEnabled: function(aAddon) {
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, aAddon) &&
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, aAddon);
+ },
+ _updateLastCheck: function() {
+ let now = Math.round(Date.now() / 1000);
+ GMPPrefs.set(GMPPrefs.KEY_UPDATE_LAST_CHECK, now);
+ },
+ _versionchangeOccurred: function() {
+ let savedBuildID = GMPPrefs.get(GMPPrefs.KEY_BUILDID, null);
+ let buildID = Services.appinfo.platformBuildID;
+ if (savedBuildID == buildID) {
+ return false;
+ }
+ GMPPrefs.set(GMPPrefs.KEY_BUILDID, buildID);
+ return true;
+ },
+ /**
+ * Wrapper for checkForAddons and installAddon.
+ * Will only install if not already installed and will log the results.
+ * This will only install/update the OpenH264 and EME plugins
+ * @return a promise which will be resolved if all addons could be installed
+ * successfully, rejected otherwise.
+ */
+ simpleCheckAndInstall: Task.async(function*() {
+ let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall");
+
+ if (this._versionchangeOccurred()) {
+ log.info("A version change occurred. Ignoring " +
+ "media.gmp-manager.lastCheck to check immediately for " +
+ "new or updated GMPs.");
+ } else {
+ let secondsBetweenChecks =
+ GMPPrefs.get(GMPPrefs.KEY_SECONDS_BETWEEN_CHECKS,
+ DEFAULT_SECONDS_BETWEEN_CHECKS)
+ let secondsSinceLast = this._getTimeSinceLastCheck();
+ log.info("Last check was: " + secondsSinceLast +
+ " seconds ago, minimum seconds: " + secondsBetweenChecks);
+ if (secondsBetweenChecks > secondsSinceLast) {
+ log.info("Will not check for updates.");
+ return {status: "too-frequent-no-check"};
+ }
+ }
+
+ try {
+ let gmpAddons = yield this.checkForAddons();
+ this._updateLastCheck();
+ log.info("Found " + gmpAddons.length + " addons advertised.");
+ let addonsToInstall = gmpAddons.filter(function(gmpAddon) {
+ log.info("Found addon: " + gmpAddon.toString());
+
+ if (!gmpAddon.isValid || GMPUtils.isPluginHidden(gmpAddon) ||
+ gmpAddon.isInstalled) {
+ log.info("Addon invalid, hidden or already installed.");
+ return false;
+ }
+
+ let addonUpdateEnabled = false;
+ if (GMP_PLUGIN_IDS.indexOf(gmpAddon.id) >= 0) {
+ addonUpdateEnabled = this._isAddonUpdateEnabled(gmpAddon.id);
+ if (!addonUpdateEnabled) {
+ log.info("Auto-update is off for " + gmpAddon.id +
+ ", skipping check.");
+ }
+ } else {
+ // Currently, we only support installs of OpenH264 and EME plugins.
+ log.info("Auto-update is off for unknown plugin '" + gmpAddon.id +
+ "', skipping check.");
+ }
+
+ return addonUpdateEnabled;
+ }, this);
+
+ if (!addonsToInstall.length) {
+ log.info("No new addons to install, returning");
+ return {status: "nothing-new-to-install"};
+ }
+
+ let installResults = [];
+ let failureEncountered = false;
+ for (let addon of addonsToInstall) {
+ try {
+ yield this.installAddon(addon);
+ installResults.push({
+ id: addon.id,
+ result: "succeeded",
+ });
+ } catch (e) {
+ failureEncountered = true;
+ installResults.push({
+ id: addon.id,
+ result: "failed",
+ });
+ }
+ }
+ if (failureEncountered) {
+ throw {status: "failed",
+ results: installResults};
+ }
+ return {status: "succeeded",
+ results: installResults};
+ } catch(e) {
+ log.error("Could not check for addons", e);
+ throw e;
+ }
+ }),
+
+ /**
+ * Makes sure everything is cleaned up
+ */
+ uninit: function() {
+ let log = getScopedLogger("GMPInstallManager.uninit");
+ if (this._request) {
+ log.info("Aborting request");
+ this._request.abort();
+ }
+ if (this._deferred) {
+ log.info("Rejecting deferred");
+ this._deferred.reject({type: "uninitialized"});
+ }
+ log.info("Done cleanup");
+ },
+
+ /**
+ * If set to true, specifies to leave the temporary downloaded zip file.
+ * This is useful for tests.
+ */
+ overrideLeaveDownloadedZip: false,
+
+ /**
+ * The XMLHttpRequest succeeded and the document was loaded.
+ * @param event The nsIDOMEvent for the load
+ */
+ onLoadXML: function(event) {
+ let log = getScopedLogger("GMPInstallManager.onLoadXML");
+ try {
+ log.info("request completed downloading document");
+ let certs = null;
+ if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
+ GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
+ certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
+ }
+
+ let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
+ true);
+ log.info("allowNonBuiltIn: " + allowNonBuiltIn);
+
+ gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
+
+ this.parseResponseXML();
+ } catch (ex) {
+ log.error("could not load xml: " + ex);
+ this._deferred.reject({
+ target: event.target,
+ status: this._getChannelStatus(event.target),
+ message: "" + ex,
+ });
+ delete this._deferred;
+ }
+ },
+
+ /**
+ * Returns the status code for the XMLHttpRequest
+ */
+ _getChannelStatus: function(request) {
+ let log = getScopedLogger("GMPInstallManager._getChannelStatus");
+ let status = null;
+ try {
+ status = request.status;
+ log.info("request.status is: " + request.status);
+ }
+ catch (e) {
+ }
+
+ if (status == null) {
+ status = request.channel.QueryInterface(Ci.nsIRequest).status;
+ }
+ return status;
+ },
+
+ /**
+ * There was an error of some kind during the XMLHttpRequest. This
+ * error may have been caused by external factors (e.g. network
+ * issues) or internally (by a timeout).
+ *
+ * @param event The nsIDOMEvent for the error
+ */
+ onFailXML: function(failure, event) {
+ let log = getScopedLogger("GMPInstallManager.onFailXML " + failure);
+ let request = event.target;
+ let status = this._getChannelStatus(request);
+ let message = "request.status: " + status + " (" + event.type + ")";
+ log.warn(message);
+ this._deferred.reject({
+ target: request,
+ status: status,
+ message: message
+ });
+ delete this._deferred;
+ },
+
+ /**
+ * Returns an array of GMPAddon objects discovered by the update check.
+ * Or returns an empty array if there were any problems with parsing.
+ * If there's an error, it will be logged if logging is enabled.
+ */
+ parseResponseXML: function() {
+ try {
+ let log = getScopedLogger("GMPInstallManager.parseResponseXML");
+ let updatesElement = this._request.responseXML.documentElement;
+ if (!updatesElement) {
+ let message = "empty updates document";
+ log.warn(message);
+ this._deferred.reject({
+ target: this._request,
+ message: message
+ });
+ delete this._deferred;
+ return;
+ }
+
+ if (updatesElement.nodeName != "updates") {
+ let message = "got node name: " + updatesElement.nodeName +
+ ", expected: updates";
+ log.warn(message);
+ this._deferred.reject({
+ target: this._request,
+ message: message
+ });
+ delete this._deferred;
+ return;
+ }
+
+ const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
+ let gmpResults = [];
+ for (let i = 0; i < updatesElement.childNodes.length; ++i) {
+ let updatesChildElement = updatesElement.childNodes.item(i);
+ if (updatesChildElement.nodeType != ELEMENT_NODE) {
+ continue;
+ }
+ if (updatesChildElement.localName == "addons") {
+ gmpResults = GMPAddon.parseGMPAddonsNode(updatesChildElement);
+ }
+ }
+ this._deferred.resolve(gmpResults);
+ delete this._deferred;
+ } catch (e) {
+ this._deferred.reject({
+ target: this._request,
+ message: e
+ });
+ delete this._deferred;
+ }
+ },
+};
+
+/**
+ * Used to construct a single GMP addon
+ * GMPAddon objects are returns from GMPInstallManager.checkForAddons
+ * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon
+ *
+ * @param gmpAddon The AUS response XML's DOM element `addon`
+ */
+function GMPAddon(gmpAddon) {
+ let log = getScopedLogger("GMPAddon.constructor");
+ gmpAddon.QueryInterface(Ci.nsIDOMElement);
+ ["id", "URL", "hashFunction",
+ "hashValue", "version", "size"].forEach(name => {
+ if (gmpAddon.hasAttribute(name)) {
+ this[name] = gmpAddon.getAttribute(name);
+ }
+ });
+ this.size = Number(this.size) || undefined;
+ log.info ("Created new addon: " + this.toString());
+}
+/**
+ * Parses an XML GMP addons node from AUS into an array
+ * @param addonsElement An nsIDOMElement compatible node with XML from AUS
+ * @return An array of GMPAddon results
+ */
+GMPAddon.parseGMPAddonsNode = function(addonsElement) {
+ let log = getScopedLogger("GMPAddon.parseGMPAddonsNode");
+ let gmpResults = [];
+ if (addonsElement.localName !== "addons") {
+ return;
+ }
+
+ addonsElement.QueryInterface(Ci.nsIDOMElement);
+ let addonCount = addonsElement.childNodes.length;
+ for (let i = 0; i < addonCount; ++i) {
+ let addonElement = addonsElement.childNodes.item(i);
+ if (addonElement.localName !== "addon") {
+ continue;
+ }
+ addonElement.QueryInterface(Ci.nsIDOMElement);
+ try {
+ gmpResults.push(new GMPAddon(addonElement));
+ } catch (e) {
+ log.warn("invalid addon: " + e);
+ continue;
+ }
+ }
+ return gmpResults;
+};
+GMPAddon.prototype = {
+ /**
+ * Returns a string representation of the addon
+ */
+ toString: function() {
+ return this.id + " (" +
+ "isValid: " + this.isValid +
+ ", isInstalled: " + this.isInstalled +
+ ", hashFunction: " + this.hashFunction+
+ ", hashValue: " + this.hashValue +
+ (this.size !== undefined ? ", size: " + this.size : "" ) +
+ ")";
+ },
+ /**
+ * If all the fields aren't specified don't consider this addon valid
+ * @return true if the addon is parsed and valid
+ */
+ get isValid() {
+ return this.id && this.URL && this.version &&
+ this.hashFunction && !!this.hashValue;
+ },
+ get isInstalled() {
+ return this.version &&
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) === this.version;
+ },
+ get isEME() {
+ return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0;
+ },
+};
+/**
+ * Constructs a GMPExtractor object which is used to extract a GMP zip
+ * into the specified location. (Which typically leties per platform)
+ * @param zipPath The path on disk of the zip file to extract
+ */
+function GMPExtractor(zipPath, installToDirPath) {
+ this.zipPath = zipPath;
+ this.installToDirPath = installToDirPath;
+}
+GMPExtractor.prototype = {
+ /**
+ * Obtains a list of all the entries in a zipfile in the format of *.*.
+ * This also includes files inside directories.
+ *
+ * @param zipReader the nsIZipReader to check
+ * @return An array of string name entries which can be used
+ * in nsIZipReader.extract
+ */
+ _getZipEntries: function(zipReader) {
+ let entries = [];
+ let enumerator = zipReader.findEntries("*.*");
+ while (enumerator.hasMore()) {
+ entries.push(enumerator.getNext());
+ }
+ return entries;
+ },
+ /**
+ * Installs the this.zipPath contents into the directory used to store GMP
+ * addons for the current platform.
+ *
+ * @return a promise which will be resolved or rejected
+ * See GMPInstallManager.installAddon for resolve/rejected info
+ */
+ install: function() {
+ try {
+ let log = getScopedLogger("GMPExtractor.install");
+ this._deferred = Promise.defer();
+ log.info("Installing " + this.zipPath + "...");
+ // Get the input zip file
+ let zipFile = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ zipFile.initWithPath(this.zipPath);
+
+ // Initialize a zipReader and obtain the entries
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipReader.open(zipFile)
+ let entries = this._getZipEntries(zipReader);
+ let extractedPaths = [];
+
+ // Extract each of the entries
+ entries.forEach(entry => {
+ // We don't need these types of files
+ if (entry.includes("__MACOSX")) {
+ return;
+ }
+ let outFile = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ outFile.initWithPath(this.installToDirPath);
+ outFile.appendRelativePath(entry);
+
+ // Make sure the directory hierarchy exists
+ if(!outFile.parent.exists()) {
+ outFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+ }
+ zipReader.extract(entry, outFile);
+ extractedPaths.push(outFile.path);
+ log.info(entry + " was successfully extracted to: " +
+ outFile.path);
+ });
+ zipReader.close();
+ if (!GMPInstallManager.overrideLeaveDownloadedZip) {
+ zipFile.remove(false);
+ }
+
+ log.info(this.zipPath + " was installed successfully");
+ this._deferred.resolve(extractedPaths);
+ } catch (e) {
+ if (zipReader) {
+ zipReader.close();
+ }
+ this._deferred.reject({
+ target: this,
+ status: e,
+ type: "exception"
+ });
+ }
+ return this._deferred.promise;
+ }
+};
+
+
+/**
+ * Constructs an object which downloads and initiates an install of
+ * the specified GMPAddon object.
+ * @param gmpAddon The addon to install.
+ */
+function GMPDownloader(gmpAddon)
+{
+ this._gmpAddon = gmpAddon;
+}
+/**
+ * Computes the file hash of fileToHash with the specified hash function
+ * @param hashFunctionName A hash function name such as sha512
+ * @param fileToHash An nsIFile to hash
+ * @return a promise which resolve to a digest in binary hex format
+ */
+GMPDownloader.computeHash = function(hashFunctionName, fileToHash) {
+ let log = getScopedLogger("GMPDownloader.computeHash");
+ let digest;
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fileStream.init(fileToHash, FileUtils.MODE_RDONLY,
+ FileUtils.PERMS_FILE, 0);
+ try {
+ let hash = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ let hashFunction =
+ Ci.nsICryptoHash[hashFunctionName.toUpperCase()];
+ if (!hashFunction) {
+ log.error("could not get hash function");
+ return Promise.reject();
+ }
+ hash.init(hashFunction);
+ hash.updateFromStream(fileStream, -1);
+ digest = binaryToHex(hash.finish(false));
+ } catch (e) {
+ log.warn("failed to compute hash: " + e);
+ digest = "";
+ }
+ fileStream.close();
+ return Promise.resolve(digest);
+},
+GMPDownloader.prototype = {
+ /**
+ * Starts the download process for an addon.
+ * @return a promise which will be resolved or rejected
+ * See GMPInstallManager.installAddon for resolve/rejected info
+ */
+ start: function() {
+ let log = getScopedLogger("GMPDownloader.start");
+ this._deferred = Promise.defer();
+ if (!this._gmpAddon.isValid) {
+ log.info("gmpAddon is not valid, will not continue");
+ return Promise.reject({
+ target: this,
+ status: status,
+ type: "downloaderr"
+ });
+ }
+
+ let uri = Services.io.newURI(this._gmpAddon.URL, null, null);
+ this._request = Cc["@mozilla.org/network/incremental-download;1"].
+ createInstance(Ci.nsIIncrementalDownload);
+ let gmpFile = FileUtils.getFile("TmpD", [this._gmpAddon.id + ".zip"]);
+ if (gmpFile.exists()) {
+ gmpFile.remove(false);
+ }
+
+ log.info("downloading from " + uri.spec + " to " + gmpFile.path);
+ this._request.init(uri, gmpFile, DOWNLOAD_CHUNK_BYTES_SIZE,
+ DOWNLOAD_INTERVAL);
+ this._request.start(this, null);
+ return this._deferred.promise;
+ },
+ // For nsIRequestObserver
+ onStartRequest: function(request, context) {
+ },
+ // For nsIRequestObserver
+ // Called when the GMP addon zip file is downloaded
+ onStopRequest: function(request, context, status) {
+ let log = getScopedLogger("GMPDownloader.onStopRequest");
+ log.info("onStopRequest called");
+ if (!Components.isSuccessCode(status)) {
+ log.info("status failed: " + status);
+ this._deferred.reject({
+ target: this,
+ status: status,
+ type: "downloaderr"
+ });
+ return;
+ }
+
+ let promise = this._verifyDownload();
+ promise.then(() => {
+ log.info("GMP file is ready to unzip");
+ let destination = this._request.destination;
+
+ let zipPath = destination.path;
+ let gmpAddon = this._gmpAddon;
+ let installToDirPath = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ let path = OS.Path.join(OS.Constants.Path.profileDir,
+ gmpAddon.id,
+ gmpAddon.version);
+ installToDirPath.initWithPath(path);
+ log.info("install to directory path: " + installToDirPath.path);
+ let gmpInstaller = new GMPExtractor(zipPath, installToDirPath.path);
+ let installPromise = gmpInstaller.install();
+ installPromise.then(extractedPaths => {
+ // Success, set the prefs
+ let now = Math.round(Date.now() / 1000);
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
+ // Setting the version pref signals installation completion to consumers,
+ // if you need to set other prefs etc. do it before this.
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_VERSION, gmpAddon.version,
+ gmpAddon.id);
+ this._deferred.resolve(extractedPaths);
+ }, err => {
+ this._deferred.reject(err);
+ });
+ }, err => {
+ log.warn("verifyDownload check failed");
+ this._deferred.reject({
+ target: this,
+ status: 200,
+ type: "verifyerr"
+ });
+ });
+ },
+ /**
+ * Verifies that the downloaded zip file's hash matches the GMPAddon hash.
+ * @return a promise which resolves if the download verifies
+ */
+ _verifyDownload: function() {
+ let verifyDownloadDeferred = Promise.defer();
+ let log = getScopedLogger("GMPDownloader._verifyDownload");
+ log.info("_verifyDownload called");
+ if (!this._request) {
+ return Promise.reject();
+ }
+
+ let destination = this._request.destination;
+ log.info("for path: " + destination.path);
+
+ // Ensure that the file size matches the expected file size.
+ if (this._gmpAddon.size !== undefined &&
+ destination.fileSize != this._gmpAddon.size) {
+ log.warn("Downloader:_verifyDownload downloaded size " +
+ destination.fileSize + " != expected size " +
+ this._gmpAddon.size + ".");
+ return Promise.reject();
+ }
+
+ let promise = GMPDownloader.computeHash(this._gmpAddon.hashFunction, destination);
+ promise.then(digest => {
+ let expectedDigest = this._gmpAddon.hashValue.toLowerCase();
+ if (digest !== expectedDigest) {
+ log.warn("hashes do not match! Got: `" +
+ digest + "`, expected: `" + expectedDigest + "`");
+ this._deferred.reject();
+ return;
+ }
+
+ log.info("hashes match!");
+ verifyDownloadDeferred.resolve();
+ }, err => {
+ verifyDownloadDeferred.reject();
+ });
+ return verifyDownloadDeferred.promise;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver])
+};
+
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+ let result = "";
+ for (let i = 0; i < input.length; ++i) {
+ let hex = input.charCodeAt(i).toString(16);
+ if (hex.length == 1)
+ hex = "0" + hex;
+ result += hex;
+ }
+ return result;
+}
diff --git a/components/addons/src/GMPProvider.jsm b/components/addons/src/GMPProvider.jsm
new file mode 100644
index 000000000..c89427101
--- /dev/null
+++ b/components/addons/src/GMPProvider.jsm
@@ -0,0 +1,605 @@
+/* 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";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/GMPUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(
+ this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(
+ this, "setTimeout", "resource://gre/modules/Timer.jsm");
+
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME = "type.%ID%.name";
+
+const SEC_IN_A_DAY = 24 * 60 * 60;
+// How long to wait after a user enabled EME before attempting to download CDMs.
+const GMP_CHECK_DELAY = 10 * 1000; // milliseconds
+
+const NS_GRE_DIR = "GreD";
+const CLEARKEY_PLUGIN_ID = "gmp-clearkey";
+const CLEARKEY_VERSION = "0.1";
+
+const GMP_LICENSE_INFO = "gmp_license_info";
+
+const GMP_PLUGINS = [
+ {
+ id: OPEN_H264_ID,
+ name: "openH264_name",
+ description: "openH264_description2",
+ // The following licenseURL is part of an awful hack to include the OpenH264
+ // license without having bug 624602 fixed yet, and intentionally ignores
+ // localisation.
+ licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt",
+ homepageURL: "http://www.openh264.org/",
+ optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul"
+ },
+ {
+ id: WIDEVINE_ID,
+ name: "widevine_name",
+ // Describe the purpose of both CDMs in the same way.
+ description: "widevine_description2",
+ licenseURL: "https://www.google.com/policies/privacy/",
+ homepageURL: "https://www.widevine.com/",
+ optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul",
+ isEME: true
+ }];
+XPCOMUtils.defineConstant(this, "GMP_PLUGINS", GMP_PLUGINS);
+
+XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
+ () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
+XPCOMUtils.defineLazyGetter(this, "gmpService",
+ () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));
+
+var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+var gLogger;
+var gLogAppenderDump = null;
+
+function configureLogging() {
+ if (!gLogger) {
+ gLogger = Log.repository.getLogger("Toolkit.GMP");
+ gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
+ }
+ gLogger.level = GMPPrefs.get(GMPPrefs.KEY_LOGGING_LEVEL, Log.Level.Warn);
+
+ let logDumping = GMPPrefs.get(GMPPrefs.KEY_LOGGING_DUMP, false);
+ if (logDumping != !!gLogAppenderDump) {
+ if (logDumping) {
+ gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
+ gLogger.addAppender(gLogAppenderDump);
+ } else {
+ gLogger.removeAppender(gLogAppenderDump);
+ gLogAppenderDump = null;
+ }
+ }
+}
+
+
+
+/**
+ * The GMPWrapper provides the info for the various GMP plugins to public
+ * callers through the API.
+ */
+function GMPWrapper(aPluginInfo) {
+ this._plugin = aPluginInfo;
+ this._log =
+ Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+ "GMPWrapper(" +
+ this._plugin.id + ") ");
+ Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
+ this._plugin.id),
+ this.onPrefEnabledChanged, this);
+ Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
+ this._plugin.id),
+ this.onPrefVersionChanged, this);
+ if (this._plugin.isEME) {
+ Preferences.observe(GMPPrefs.KEY_EME_ENABLED,
+ this.onPrefEMEGlobalEnabledChanged, this);
+ messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", this);
+ }
+}
+
+GMPWrapper.prototype = {
+ // An active task that checks for plugin updates and installs them.
+ _updateTask: null,
+ _gmpPath: null,
+ _isUpdateCheckPending: false,
+
+ optionsType: AddonManager.OPTIONS_TYPE_INLINE,
+ get optionsURL() { return this._plugin.optionsURL; },
+
+ set gmpPath(aPath) { this._gmpPath = aPath; },
+ get gmpPath() {
+ if (!this._gmpPath && this.isInstalled) {
+ this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
+ this._plugin.id,
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
+ null, this._plugin.id));
+ }
+ return this._gmpPath;
+ },
+
+ get id() { return this._plugin.id; },
+ get type() { return "plugin"; },
+ get isGMPlugin() { return true; },
+ get name() { return this._plugin.name; },
+ get creator() { return null; },
+ get homepageURL() { return this._plugin.homepageURL; },
+
+ get description() { return this._plugin.description; },
+ get fullDescription() { return this._plugin.fullDescription; },
+
+ get version() { return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, null,
+ this._plugin.id); },
+
+ get isActive() { return !this.appDisabled && !this.userDisabled; },
+ get appDisabled() {
+ if (this._plugin.isEME && !GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
+ // If "media.eme.enabled" is false, all EME plugins are disabled.
+ return true;
+ }
+ return false;
+ },
+
+ get userDisabled() {
+ return !GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, this._plugin.id);
+ },
+ set userDisabled(aVal) { GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ENABLED,
+ aVal === false,
+ this._plugin.id); },
+
+ get blocklistState() { return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; },
+ get size() { return 0; },
+ get scope() { return AddonManager.SCOPE_APPLICATION; },
+ get pendingOperations() { return AddonManager.PENDING_NONE; },
+
+ get operationsRequiringRestart() { return AddonManager.OP_NEEDS_RESTART_NONE },
+
+ get permissions() {
+ let permissions = 0;
+ if (!this.appDisabled) {
+ permissions |= AddonManager.PERM_CAN_UPGRADE;
+ permissions |= this.userDisabled ? AddonManager.PERM_CAN_ENABLE :
+ AddonManager.PERM_CAN_DISABLE;
+ }
+ return permissions;
+ },
+
+ get updateDate() {
+ let time = Number(GMPPrefs.get(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, null,
+ this._plugin.id));
+ if (time !== NaN && this.isInstalled) {
+ return new Date(time * 1000)
+ }
+ return null;
+ },
+
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ get foreignInstall() {
+ return false;
+ },
+
+ isCompatibleWith: function(aAppVersion, aPlatformVersion) {
+ return true;
+ },
+
+ get applyBackgroundUpdates() {
+ if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
+ return AddonManager.AUTOUPDATE_DEFAULT;
+ }
+
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id) ?
+ AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
+ },
+
+ set applyBackgroundUpdates(aVal) {
+ if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
+ GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
+ } else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
+ } else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
+ GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
+ }
+ },
+
+ findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
+ this._log.trace("findUpdates() - " + this._plugin.id + " - reason=" +
+ aReason);
+
+ AddonManagerPrivate.callNoUpdateListeners(this, aListener);
+
+ if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
+ if (!AddonManager.shouldAutoUpdate(this)) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - no autoupdate");
+ return Promise.resolve(false);
+ }
+
+ let secSinceLastCheck =
+ Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
+ if (secSinceLastCheck <= SEC_IN_A_DAY) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - last check was less then a day ago");
+ return Promise.resolve(false);
+ }
+ } else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - the given reason to update is not supported");
+ return Promise.resolve(false);
+ }
+
+ if (this._updateTask !== null) {
+ this._log.trace("findUpdates() - " + this._plugin.id +
+ " - update task already running");
+ return this._updateTask;
+ }
+
+ this._updateTask = Task.spawn(function* GMPProvider_updateTask() {
+ this._log.trace("findUpdates() - updateTask");
+ try {
+ let installManager = new GMPInstallManager();
+ let gmpAddons = yield installManager.checkForAddons();
+ let update = gmpAddons.find(function(aAddon) {
+ return aAddon.id === this._plugin.id;
+ }, this);
+ if (update && update.isValid && !update.isInstalled) {
+ this._log.trace("findUpdates() - found update for " +
+ this._plugin.id + ", installing");
+ yield installManager.installAddon(update);
+ } else {
+ this._log.trace("findUpdates() - no updates for " + this._plugin.id);
+ }
+ this._log.info("findUpdates() - updateTask succeeded for " +
+ this._plugin.id);
+ } catch (e) {
+ this._log.error("findUpdates() - updateTask for " + this._plugin.id +
+ " threw", e);
+ throw e;
+ } finally {
+ this._updateTask = null;
+ return true;
+ }
+ }.bind(this));
+
+ return this._updateTask;
+ },
+
+ get pluginMimeTypes() { return []; },
+ get pluginLibraries() {
+ if (this.isInstalled) {
+ let path = this.version;
+ return [path];
+ }
+ return [];
+ },
+ get pluginFullpath() {
+ if (this.isInstalled) {
+ let path = OS.Path.join(OS.Constants.Path.profileDir,
+ this._plugin.id,
+ this.version);
+ return [path];
+ }
+ return [];
+ },
+
+ get isInstalled() {
+ return this.version && this.version.length > 0;
+ },
+
+ _handleEnabledChanged: function() {
+ AddonManagerPrivate.callAddonListeners(this.isActive ?
+ "onEnabling" : "onDisabling",
+ this, false);
+ if (this._gmpPath) {
+ if (this.isActive) {
+ this._log.info("onPrefEnabledChanged() - adding gmp directory " +
+ this._gmpPath);
+ gmpService.addPluginDirectory(this._gmpPath);
+ } else {
+ this._log.info("onPrefEnabledChanged() - removing gmp directory " +
+ this._gmpPath);
+ gmpService.removePluginDirectory(this._gmpPath);
+ }
+ }
+ AddonManagerPrivate.callAddonListeners(this.isActive ?
+ "onEnabled" : "onDisabled",
+ this);
+ },
+
+ onPrefEMEGlobalEnabledChanged: function() {
+ AddonManagerPrivate.callAddonListeners("onPropertyChanged", this,
+ ["appDisabled"]);
+ if (this.appDisabled) {
+ this.uninstallPlugin();
+ } else {
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
+ null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
+ AddonManagerPrivate.callAddonListeners("onInstalled", this);
+ this.checkForUpdates(GMP_CHECK_DELAY);
+ }
+ if (!this.userDisabled) {
+ this._handleEnabledChanged();
+ }
+ },
+
+ checkForUpdates: function(delay) {
+ if (this._isUpdateCheckPending) {
+ return;
+ }
+ this._isUpdateCheckPending = true;
+ GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
+ // Delay this in case the user changes his mind and doesn't want to
+ // enable EME after all.
+ setTimeout(() => {
+ if (!this.appDisabled) {
+ let gmpInstallManager = new GMPInstallManager();
+ // We don't really care about the results, if someone is interested
+ // they can check the log.
+ gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
+ }
+ this._isUpdateCheckPending = false;
+ }, delay);
+ },
+
+ receiveMessage: function({target: browser, data: data}) {
+ this._log.trace("receiveMessage() data=" + data);
+ let parsedData;
+ try {
+ parsedData = JSON.parse(data);
+ } catch(ex) {
+ this._log.error("Malformed EME video message with data: " + data);
+ return;
+ }
+ let {status: status, keySystem: keySystem} = parsedData;
+ if (status == "cdm-not-installed" || status == "cdm-insufficient-version") {
+ this.checkForUpdates(0);
+ }
+ },
+
+ onPrefEnabledChanged: function() {
+ if (!this._plugin.isEME || !this.appDisabled) {
+ this._handleEnabledChanged();
+ }
+ },
+
+ onPrefVersionChanged: function() {
+ AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
+ if (this._gmpPath) {
+ this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
+ this._gmpPath);
+ gmpService.removeAndDeletePluginDirectory(this._gmpPath, true /* can defer */);
+ }
+ AddonManagerPrivate.callAddonListeners("onUninstalled", this);
+
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
+ null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
+ this._gmpPath = null;
+ if (this.isInstalled) {
+ this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
+ this._plugin.id,
+ GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
+ null, this._plugin.id));
+ }
+ if (this._gmpPath && this.isActive) {
+ this._log.info("onPrefVersionChanged() - registering gmp directory " +
+ this._gmpPath);
+ gmpService.addPluginDirectory(this._gmpPath);
+ }
+ AddonManagerPrivate.callAddonListeners("onInstalled", this);
+ },
+
+ uninstallPlugin: function() {
+ AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
+ if (this.gmpPath) {
+ this._log.info("uninstallPlugin() - unregistering gmp directory " +
+ this.gmpPath);
+ gmpService.removeAndDeletePluginDirectory(this.gmpPath);
+ }
+ GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
+ AddonManagerPrivate.callAddonListeners("onUninstalled", this);
+ },
+
+ shutdown: function() {
+ Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
+ this._plugin.id),
+ this.onPrefEnabledChanged, this);
+ Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
+ this._plugin.id),
+ this.onPrefVersionChanged, this);
+ if (this._plugin.isEME) {
+ Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
+ this.onPrefEMEGlobalEnabledChanged, this);
+ messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
+ }
+ return this._updateTask;
+ },
+};
+
+var GMPProvider = {
+ get name() { return "GMPProvider"; },
+
+ _plugins: null,
+
+ startup: function() {
+ configureLogging();
+ this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+ "GMPProvider.");
+ this.buildPluginList();
+ this.ensureProperCDMInstallState();
+
+ Preferences.observe(GMPPrefs.KEY_LOG_BASE, configureLogging);
+
+ for (let [id, plugin] of this._plugins) {
+ let wrapper = plugin.wrapper;
+ let gmpPath = wrapper.gmpPath;
+ let isEnabled = wrapper.isActive;
+ this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
+ gmpPath);
+
+ if (gmpPath && isEnabled) {
+ this._log.info("startup - adding gmp directory " + gmpPath);
+ try {
+ gmpService.addPluginDirectory(gmpPath);
+ } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+ this._log.warn("startup - adding gmp directory failed with " +
+ e.name + " - sandboxing not available?", e);
+ }
+ }
+ }
+
+ if (Preferences.get(GMPPrefs.KEY_EME_ENABLED, false)) {
+ try {
+ let greDir = Services.dirsvc.get(NS_GRE_DIR,
+ Ci.nsILocalFile);
+ let clearkeyPath = OS.Path.join(greDir.path,
+ CLEARKEY_PLUGIN_ID,
+ CLEARKEY_VERSION);
+ this._log.info("startup - adding clearkey CDM directory " +
+ clearkeyPath);
+ gmpService.addPluginDirectory(clearkeyPath);
+ } catch (e) {
+ this._log.warn("startup - adding clearkey CDM failed", e);
+ }
+ }
+ },
+
+ shutdown: function() {
+ this._log.trace("shutdown");
+ Preferences.ignore(GMPPrefs.KEY_LOG_BASE, configureLogging);
+
+ let shutdownTask = Task.spawn(function* GMPProvider_shutdownTask() {
+ this._log.trace("shutdown - shutdownTask");
+ let shutdownSucceeded = true;
+
+ for (let plugin of this._plugins.values()) {
+ try {
+ yield plugin.wrapper.shutdown();
+ } catch (e) {
+ shutdownSucceeded = false;
+ }
+ }
+
+ this._plugins = null;
+
+ if (!shutdownSucceeded) {
+ throw new Error("Shutdown failed");
+ }
+ }.bind(this));
+
+ return shutdownTask;
+ },
+
+ getAddonByID: function(aId, aCallback) {
+ if (!this.isEnabled) {
+ aCallback(null);
+ return;
+ }
+
+ let plugin = this._plugins.get(aId);
+ if (plugin && !GMPUtils.isPluginHidden(plugin)) {
+ aCallback(plugin.wrapper);
+ } else {
+ aCallback(null);
+ }
+ },
+
+ getAddonsByTypes: function(aTypes, aCallback) {
+ if (!this.isEnabled ||
+ (aTypes && aTypes.indexOf("plugin") < 0)) {
+ aCallback([]);
+ return;
+ }
+
+ // Tycho:
+ // let results = [p.wrapper for ([id, p] of this._plugins)
+ // if (!GMPUtils.isPluginHidden(p))];
+ let results = [];
+ for (let [id, p] of this._plugins) {
+ if (!GMPUtils.isPluginHidden(p)) {
+ results.push(p.wrapper);
+ }
+ }
+
+ aCallback(results);
+ },
+
+ get isEnabled() {
+ return GMPPrefs.get(GMPPrefs.KEY_PROVIDER_ENABLED, false);
+ },
+
+ generateFullDescription: function(aLicenseURL, aLicenseInfo) {
+ return "<xhtml:a href=\"" + aLicenseURL + "\" target=\"_blank\">" +
+ aLicenseInfo + "</xhtml:a>."
+ },
+
+ buildPluginList: function() {
+ let licenseInfo = pluginsBundle.GetStringFromName(GMP_LICENSE_INFO);
+
+ this._plugins = new Map();
+ for (let aPlugin of GMP_PLUGINS) {
+ let plugin = {
+ id: aPlugin.id,
+ name: pluginsBundle.GetStringFromName(aPlugin.name),
+ description: pluginsBundle.GetStringFromName(aPlugin.description),
+ homepageURL: aPlugin.homepageURL,
+ optionsURL: aPlugin.optionsURL,
+ wrapper: null,
+ isEME: aPlugin.isEME,
+ };
+ if (aPlugin.licenseURL) {
+ plugin.fullDescription =
+ this.generateFullDescription(aPlugin.licenseURL, licenseInfo);
+ }
+ plugin.wrapper = new GMPWrapper(plugin);
+ this._plugins.set(plugin.id, plugin);
+ }
+ },
+
+ ensureProperCDMInstallState: function() {
+ if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
+ for (let [id, plugin] of this._plugins) {
+ if (plugin.isEME && plugin.wrapper.isInstalled) {
+ gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
+ plugin.wrapper.uninstallPlugin();
+ }
+ }
+ }
+ },
+};
+
+AddonManagerPrivate.registerProvider(GMPProvider, [
+ new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 6000,
+ AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
+]);
diff --git a/components/addons/src/GMPUtils.jsm b/components/addons/src/GMPUtils.jsm
new file mode 100644
index 000000000..593fc3c8d
--- /dev/null
+++ b/components/addons/src/GMPUtils.jsm
@@ -0,0 +1,187 @@
+/* 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 {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
+ Components;
+
+this.EXPORTED_SYMBOLS = [ "GMP_PLUGIN_IDS",
+ "GMPPrefs",
+ "GMPUtils",
+ "OPEN_H264_ID",
+ "WIDEVINE_ID" ];
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// GMP IDs
+const OPEN_H264_ID = "gmp-gmpopenh264";
+const WIDEVINE_ID = "gmp-widevinecdm";
+const GMP_PLUGIN_IDS = [ OPEN_H264_ID, WIDEVINE_ID ];
+
+var GMPPluginUnsupportedReason = {
+ NOT_WINDOWS: 1,
+ WINDOWS_VERSION: 2,
+};
+
+var GMPPluginHiddenReason = {
+ UNSUPPORTED: 1,
+ EME_DISABLED: 2,
+};
+
+this.GMPUtils = {
+ /**
+ * Checks whether or not a given plugin is hidden. Hidden plugins are neither
+ * downloaded nor displayed in the addons manager.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ isPluginHidden: function(aPlugin) {
+ if (!aPlugin.isEME) {
+ return false;
+ }
+
+ if (!this._isPluginSupported(aPlugin) ||
+ !this._isPluginVisible(aPlugin)) {
+ return true;
+ }
+
+ if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Checks whether or not a given plugin is supported by the current OS.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ _isPluginSupported: function(aPlugin) {
+ if (this._isPluginForceSupported(aPlugin)) {
+ return true;
+ }
+ if (aPlugin.id == WIDEVINE_ID) {
+
+#if defined(XP_WIN) || defined(XP_LINUX)
+ // The Widevine plugin is available for Windows versions Vista and later,
+ // Mac OSX, and Linux.
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ return true;
+ },
+
+ /**
+ * Checks whether or not a given plugin is visible in the addons manager
+ * UI and the "enable DRM" notification box. This can be used to test
+ * plugins that aren't yet turned on in the mozconfig.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ _isPluginVisible: function(aPlugin) {
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VISIBLE, false, aPlugin.id);
+ },
+
+ /**
+ * Checks whether or not a given plugin is forced-supported. This is used
+ * in automated tests to override the checks that prevent GMPs running on an
+ * unsupported platform.
+ * @param aPlugin
+ * The plugin to check.
+ */
+ _isPluginForceSupported: function(aPlugin) {
+ return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED, false, aPlugin.id);
+ },
+};
+
+/**
+ * Manages preferences for GMP addons
+ */
+this.GMPPrefs = {
+ KEY_EME_ENABLED: "media.eme.enabled",
+ KEY_PLUGIN_ENABLED: "media.{0}.enabled",
+ KEY_PLUGIN_LAST_UPDATE: "media.{0}.lastUpdate",
+ KEY_PLUGIN_VERSION: "media.{0}.version",
+ KEY_PLUGIN_AUTOUPDATE: "media.{0}.autoupdate",
+ KEY_PLUGIN_VISIBLE: "media.{0}.visible",
+ KEY_PLUGIN_ABI: "media.{0}.abi",
+ KEY_PLUGIN_FORCE_SUPPORTED: "media.{0}.forceSupported",
+ KEY_URL: "media.gmp-manager.url",
+ KEY_URL_OVERRIDE: "media.gmp-manager.url.override",
+ KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes",
+ KEY_CERT_REQUIREBUILTIN: "media.gmp-manager.cert.requireBuiltIn",
+ KEY_UPDATE_LAST_CHECK: "media.gmp-manager.lastCheck",
+ KEY_SECONDS_BETWEEN_CHECKS: "media.gmp-manager.secondsBetweenChecks",
+ KEY_UPDATE_ENABLED: "media.gmp-manager.updateEnabled",
+ KEY_APP_DISTRIBUTION: "distribution.id",
+ KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
+ KEY_BUILDID: "media.gmp-manager.buildID",
+ KEY_CERTS_BRANCH: "media.gmp-manager.certs.",
+ KEY_PROVIDER_ENABLED: "media.gmp-provider.enabled",
+ KEY_LOG_BASE: "media.gmp.log.",
+ KEY_LOGGING_LEVEL: "media.gmp.log.level",
+ KEY_LOGGING_DUMP: "media.gmp.log.dump",
+
+ /**
+ * Obtains the specified preference in relation to the specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aDefaultValue The default value if no preference exists.
+ * @param aPlugin The plugin to scope the preference to.
+ * @return The obtained preference value, or the defaultValue if none exists.
+ */
+ get: function(aKey, aDefaultValue, aPlugin) {
+ if (aKey === this.KEY_APP_DISTRIBUTION ||
+ aKey === this.KEY_APP_DISTRIBUTION_VERSION) {
+ return Services.prefs.getDefaultBranch(null).getCharPref(aKey, "default");
+ }
+ return Preferences.get(this.getPrefKey(aKey, aPlugin), aDefaultValue);
+ },
+
+ /**
+ * Sets the specified preference in relation to the specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aVal The value to set.
+ * @param aPlugin The plugin to scope the preference to.
+ */
+ set: function(aKey, aVal, aPlugin) {
+ Preferences.set(this.getPrefKey(aKey, aPlugin), aVal);
+ },
+
+ /**
+ * Checks whether or not the specified preference is set in relation to the
+ * specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aPlugin The plugin to scope the preference to.
+ * @return true if the preference is set, false otherwise.
+ */
+ isSet: function(aKey, aPlugin) {
+ return Preferences.isSet(this.getPrefKey(aKey, aPlugin));
+ },
+
+ /**
+ * Resets the specified preference in relation to the specified plugin to its
+ * default.
+ * @param aKey The preference key value to use.
+ * @param aPlugin The plugin to scope the preference to.
+ */
+ reset: function(aKey, aPlugin) {
+ Preferences.reset(this.getPrefKey(aKey, aPlugin));
+ },
+
+ /**
+ * Scopes the specified preference key to the specified plugin.
+ * @param aKey The preference key value to use.
+ * @param aPlugin The plugin to scope the preference to.
+ * @return A preference key scoped to the specified plugin.
+ */
+ getPrefKey: function(aKey, aPlugin) {
+ return aKey.replace("{0}", aPlugin || "");
+ },
+};
diff --git a/components/addons/src/ProductAddonChecker.jsm b/components/addons/src/ProductAddonChecker.jsm
new file mode 100644
index 000000000..c6324da0a
--- /dev/null
+++ b/components/addons/src/ProductAddonChecker.jsm
@@ -0,0 +1,464 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const LOCAL_EME_SOURCES = [{
+ "id": "gmp-gmpopenh264",
+ "src": "chrome://global/content/gmp-sources/openh264.json"
+}, {
+ "id": "gmp-widevinecdm",
+ "src": "chrome://global/content/gmp-sources/widevinecdm.json"
+}];
+
+this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/CertUtils.jsm");
+/* globals checkCert, BadCertHandler*/
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+/* globals GMPPrefs */
+XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs",
+ "resource://gre/modules/GMPUtils.jsm");
+
+/* globals OS */
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
+ "resource://gre/modules/ServiceRequest.jsm");
+
+// This exists so that tests can override the XHR behaviour for downloading
+// the addon update XML file.
+var CreateXHR = function() {
+ return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsISupports);
+}
+
+var logger = Log.repository.getLogger("addons.productaddons");
+
+/**
+ * Number of milliseconds after which we need to cancel `downloadXML`.
+ *
+ * Bug 1087674 suggests that the XHR we use in `downloadXML` may
+ * never terminate in presence of network nuisances (e.g. strange
+ * antivirus behavior). This timeout is a defensive measure to ensure
+ * that we fail cleanly in such case.
+ */
+const TIMEOUT_DELAY_MS = 20000;
+// Chunk size for the incremental downloader
+const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
+// Incremental downloader interval
+const DOWNLOAD_INTERVAL = 0;
+// How much of a file to read into memory at a time for hashing
+const HASH_CHUNK_SIZE = 8192;
+
+/**
+ * Gets the status of an XMLHttpRequest either directly or from its underlying
+ * channel.
+ *
+ * @param request
+ * The XMLHttpRequest.
+ * @return an integer status value.
+ */
+function getRequestStatus(request) {
+ let status = null;
+ try {
+ status = request.status;
+ }
+ catch (e) {
+ }
+
+ if (status != null) {
+ return status;
+ }
+
+ return request.channel.QueryInterface(Ci.nsIRequest).status;
+}
+
+/**
+ * Downloads an XML document from a URL optionally testing the SSL certificate
+ * for certain attributes.
+ *
+ * @param url
+ * The url to download from.
+ * @param allowNonBuiltIn
+ * Whether to trust SSL certificates without a built-in CA issuer.
+ * @param allowedCerts
+ * The list of certificate attributes to match the SSL certificate
+ * against or null to skip checks.
+ * @return a promise that resolves to the DOM document downloaded or rejects
+ * with a JS exception in case of error.
+ */
+function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
+ return new Promise((resolve, reject) => {
+ let request = CreateXHR();
+ // This is here to let unit test code override XHR
+ if (request.wrappedJSObject) {
+ request = request.wrappedJSObject;
+ }
+ request.open("GET", url, true);
+ request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
+ // Prevent the request from reading from the cache.
+ request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ // Use conservative TLS settings. See bug 1325501.
+ // TODO move to ServiceRequest.
+ if (request.channel instanceof Ci.nsIHttpChannelInternal) {
+ request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
+ }
+ request.timeout = TIMEOUT_DELAY_MS;
+
+ request.overrideMimeType("text/xml");
+ // The Cache-Control header is only interpreted by proxies and the
+ // final destination. It does not help if a resource is already
+ // cached locally.
+ request.setRequestHeader("Cache-Control", "no-cache");
+ // HTTP/1.0 servers might not implement Cache-Control and
+ // might only implement Pragma: no-cache
+ request.setRequestHeader("Pragma", "no-cache");
+
+ let fail = (event) => {
+ let request = event.target;
+ let status = getRequestStatus(request);
+ let message = "Failed downloading XML, status: " + status + ", reason: " + event.type;
+ logger.warn(message);
+ let ex = new Error(message);
+ ex.status = status;
+ reject(ex);
+ };
+
+ let success = (event) => {
+ logger.info("Completed downloading document");
+ let request = event.target;
+
+ try {
+ checkCert(request.channel, allowNonBuiltIn, allowedCerts);
+ } catch (ex) {
+ logger.error("Request failed certificate checks: " + ex);
+ ex.status = getRequestStatus(request);
+ reject(ex);
+ return;
+ }
+
+ resolve(request.responseXML);
+ };
+
+ request.addEventListener("error", fail, false);
+ request.addEventListener("abort", fail, false);
+ request.addEventListener("timeout", fail, false);
+ request.addEventListener("load", success, false);
+
+ logger.info("sending request to: " + url);
+ request.send(null);
+ });
+}
+
+function downloadJSON(uri) {
+ logger.info("fetching config from: " + uri);
+ return new Promise((resolve, reject) => {
+ let xmlHttp = new ServiceRequest({mozAnon: true});
+
+ xmlHttp.onload = function(aResponse) {
+ resolve(JSON.parse(this.responseText));
+ };
+
+ xmlHttp.onerror = function(e) {
+ reject("Fetching " + uri + " results in error code: " + e.target.status);
+ };
+
+ xmlHttp.open("GET", uri);
+ xmlHttp.overrideMimeType("application/json");
+ xmlHttp.send();
+ });
+}
+
+
+/**
+ * Parses a list of add-ons from a DOM document.
+ *
+ * @param document
+ * The DOM document to parse.
+ * @return null if there is no <addons> element otherwise an object containing
+ * an array of the addons listed and a field notifying whether the
+ * fallback was used.
+ */
+function parseXML(document) {
+ // Check that the root element is correct
+ if (document.documentElement.localName != "updates") {
+ throw new Error("got node name: " + document.documentElement.localName +
+ ", expected: updates");
+ }
+
+ // Check if there are any addons elements in the updates element
+ let addons = document.querySelector("updates:root > addons");
+ if (!addons) {
+ return null;
+ }
+
+ let results = [];
+ let addonList = document.querySelectorAll("updates:root > addons > addon");
+ for (let addonElement of addonList) {
+ let addon = {};
+
+ for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
+ if (addonElement.hasAttribute(name)) {
+ addon[name] = addonElement.getAttribute(name);
+ }
+ }
+ addon.size = Number(addon.size) || undefined;
+
+ results.push(addon);
+ }
+
+ return {
+ usedFallback: false,
+ gmpAddons: results
+ };
+}
+
+/**
+ * If downloading from the network fails (AUS server is down),
+ * load the sources from local build configuration.
+ */
+function downloadLocalConfig() {
+
+ if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
+ logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
+ return Promise.resolve({usedFallback: true, gmpAddons: []});
+ }
+
+ return Promise.all(LOCAL_EME_SOURCES.map(conf => {
+ return downloadJSON(conf.src).then(addons => {
+
+ let platforms = addons.vendors[conf.id].platforms;
+ let target = Services.appinfo.OS + "_" + UpdateUtils.ABI;
+ let details = null;
+
+ while (!details) {
+ if (!(target in platforms)) {
+ // There was no matching platform so return false, this addon
+ // will be filtered from the results below
+ logger.info("no details found for: " + target);
+ return false;
+ }
+ // Field either has the details of the binary or is an alias
+ // to another build target key that does
+ if (platforms[target].alias) {
+ target = platforms[target].alias;
+ } else {
+ details = platforms[target];
+ }
+ }
+
+ logger.info("found plugin: " + conf.id);
+ return {
+ "id": conf.id,
+ "URL": details.fileUrl,
+ "hashFunction": addons.hashFunction,
+ "hashValue": details.hashValue,
+ "version": addons.vendors[conf.id].version,
+ "size": details.filesize
+ };
+ });
+ })).then(addons => {
+
+ // Some filters may not match this platform so
+ // filter those out
+ addons = addons.filter(x => x !== false);
+
+ return {
+ usedFallback: true,
+ gmpAddons: addons
+ };
+ });
+}
+
+/**
+ * Downloads file from a URL using XHR.
+ *
+ * @param url
+ * The url to download from.
+ * @return a promise that resolves to the path of a temporary file or rejects
+ * with a JS exception in case of error.
+ */
+function downloadFile(url) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.onload = function(response) {
+ logger.info("downloadXHR File download. status=" + xhr.status);
+ if (xhr.status != 200 && xhr.status != 206) {
+ reject(Components.Exception("File download failed", xhr.status));
+ return;
+ }
+ Task.spawn(function* () {
+ let f = yield OS.File.openUnique(OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"));
+ let path = f.path;
+ logger.info(`Downloaded file will be saved to ${path}`);
+ yield f.file.close();
+ yield OS.File.writeAtomic(path, new Uint8Array(xhr.response));
+ return path;
+ }).then(resolve, reject);
+ };
+
+ let fail = (event) => {
+ let request = event.target;
+ let status = getRequestStatus(request);
+ let message = "Failed downloading via XHR, status: " + status + ", reason: " + event.type;
+ logger.warn(message);
+ let ex = new Error(message);
+ ex.status = status;
+ reject(ex);
+ };
+ xhr.addEventListener("error", fail);
+ xhr.addEventListener("abort", fail);
+
+ xhr.responseType = "arraybuffer";
+ try {
+ xhr.open("GET", url);
+ // Use conservative TLS settings. See bug 1325501.
+ // TODO move to ServiceRequest.
+ if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
+ xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
+ }
+ xhr.send(null);
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+}
+
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+ let result = "";
+ for (let i = 0; i < input.length; ++i) {
+ let hex = input.charCodeAt(i).toString(16);
+ if (hex.length == 1) {
+ hex = "0" + hex;
+ }
+ result += hex;
+ }
+ return result;
+}
+
+/**
+ * Calculates the hash of a file.
+ *
+ * @param hashFunction
+ * The type of hash function to use, must be supported by nsICryptoHash.
+ * @param path
+ * The path of the file to hash.
+ * @return a promise that resolves to hash of the file or rejects with a JS
+ * exception in case of error.
+ */
+var computeHash = Task.async(function*(hashFunction, path) {
+ let file = yield OS.File.open(path, { existing: true, read: true });
+ try {
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.initWithString(hashFunction);
+
+ let bytes;
+ do {
+ bytes = yield file.read(HASH_CHUNK_SIZE);
+ hasher.update(bytes, bytes.length);
+ } while (bytes.length == HASH_CHUNK_SIZE);
+
+ return binaryToHex(hasher.finish(false));
+ }
+ finally {
+ yield file.close();
+ }
+});
+
+/**
+ * Verifies that a downloaded file matches what was expected.
+ *
+ * @param properties
+ * The properties to check, `size` and `hashFunction` with `hashValue`
+ * are supported. Any properties missing won't be checked.
+ * @param path
+ * The path of the file to check.
+ * @return a promise that resolves if the file matched or rejects with a JS
+ * exception in case of error.
+ */
+var verifyFile = Task.async(function*(properties, path) {
+ if (properties.size !== undefined) {
+ let stat = yield OS.File.stat(path);
+ if (stat.size != properties.size) {
+ throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
+ }
+ }
+
+ if (properties.hashFunction !== undefined) {
+ let expectedDigest = properties.hashValue.toLowerCase();
+ let digest = yield computeHash(properties.hashFunction, path);
+ if (digest != expectedDigest) {
+ throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`.");
+ }
+ }
+});
+
+const ProductAddonChecker = {
+ /**
+ * Downloads a list of add-ons from a URL optionally testing the SSL
+ * certificate for certain attributes.
+ *
+ * @param url
+ * The url to download from.
+ * @param allowNonBuiltIn
+ * Whether to trust SSL certificates without a built-in CA issuer.
+ * @param allowedCerts
+ * The list of certificate attributes to match the SSL certificate
+ * against or null to skip checks.
+ * @return a promise that resolves to an object containing the list of add-ons
+ * and whether the local fallback was used, or rejects with a JS
+ * exception in case of error.
+ */
+ getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
+ if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
+ logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
+ return Promise.resolve({usedFallback: true, gmpAddons: []});
+ }
+
+ return downloadXML(url, allowNonBuiltIn, allowedCerts)
+ .then(parseXML)
+ .catch(downloadLocalConfig);
+ },
+
+ /**
+ * Downloads an add-on to a local file and checks that it matches the expected
+ * file. The caller is responsible for deleting the temporary file returned.
+ *
+ * @param addon
+ * The addon to download.
+ * @return a promise that resolves to the temporary file downloaded or rejects
+ * with a JS exception in case of error.
+ */
+ downloadAddon: Task.async(function*(addon) {
+ let path = yield downloadFile(addon.URL);
+ try {
+ yield verifyFile(addon, path);
+ return path;
+ }
+ catch (e) {
+ yield OS.File.remove(path);
+ throw e;
+ }
+ })
+}
diff --git a/components/global/content/gmp-sources/openh264.json b/components/global/content/gmp-sources/openh264.json
new file mode 100644
index 000000000..7c6eb0197
--- /dev/null
+++ b/components/global/content/gmp-sources/openh264.json
@@ -0,0 +1,57 @@
+{
+ "vendors": {
+ "gmp-gmpopenh264": {
+ "platforms": {
+ "WINNT_x86-msvc-x64": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "WINNT_x86-msvc": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-win32-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "991e01c3b95fa13fac52e0512e1936f1edae42ecbbbcc55447a36915eb3ca8f836546cc780343751691e0188872e5bc56fe3ad5f23f3243e90b96a637561b89e",
+ "filesize": 356940
+ },
+ "WINNT_x86-msvc-x86": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "Linux_x86_64-gcc3": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "e1086ee6e4fb60a1aa11b5626594b97695533a8e269d776877cebd5cf29088619e2c164e7bd1eba5486f772c943f2efec723f69cc48478ec84a11d7b61ca1865",
+ "filesize": 515722
+ },
+ "Darwin_x86-gcc3-u-i386-x86_64": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx32-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "64b0e13e6319b7a31ed35a46bea5abcfe6af04ba59a277db07677236cfb685813763731ff6b44b85e03e1489f3b15f8df0128a299a36720531b9f4ba6e1c1f58",
+ "filesize": 382435
+ },
+ "Darwin_x86_64-gcc3": {
+ "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
+ },
+ "Linux_x86-gcc3": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-linux32-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "19084f0230218c584715861f4723e072b1af02e26995762f368105f670f60ecb4082531bc4e33065a4675dd1296f6872a6cb101547ef2d19ef3e25e2e16d4dc0",
+ "filesize": 515857
+ },
+ "Darwin_x86_64-gcc3-u-i386-x86_64": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "3b52343070a2f75e91b7b0d3bb33935352237c7e1d2fdc6a467d039ffbbda6a72087f9e0a369fe95e6c4c789ff3052f0c134af721d7273db9ba66d077d85b327",
+ "filesize": 390308
+ },
+ "Darwin_x86-gcc3": {
+ "alias": "Darwin_x86-gcc3-u-i386-x86_64"
+ },
+ "WINNT_x86_64-msvc-x64": {
+ "alias": "WINNT_x86_64-msvc"
+ },
+ "WINNT_x86_64-msvc": {
+ "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-0410d336bb748149a4f560eb6108090f078254b1.zip",
+ "hashValue": "5030b47065e817db5c40bca9c62ac27292bbf636e24698f45dc67f03fa6420b97bd2f792c1cb39df65776c1e7597c70122ac7abf36fb2ad0603734e9e8ec4ef3",
+ "filesize": 404355
+ }
+ },
+ "version": "1.6"
+ }
+ },
+ "hashFunction": "sha512",
+ "name": "OpenH264-1.6",
+ "schema_version": 1000
+}
diff --git a/components/global/content/gmp-sources/widevinecdm.json b/components/global/content/gmp-sources/widevinecdm.json
new file mode 100644
index 000000000..02ef7fee5
--- /dev/null
+++ b/components/global/content/gmp-sources/widevinecdm.json
@@ -0,0 +1,49 @@
+{
+ "vendors": {
+ "gmp-widevinecdm": {
+ "platforms": {
+ "WINNT_x86-msvc-x64": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "WINNT_x86-msvc": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-ia32.zip",
+ "hashValue": "d7e10d09c87a157af865f8388ba70ae672bd9e38987bdd94077af52d6b1abaa745b3db92e9f93f607af6420c68210f7cfd518a9d2c99fecf79aed3385cbcbc0b",
+ "filesize": 2884452
+ },
+ "WINNT_x86-msvc-x86": {
+ "alias": "WINNT_x86-msvc"
+ },
+ "Linux_x86_64-gcc3": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-x64.zip",
+ "hashValue": "1edfb58a44792d2a53694f46fcc698161edafb2a1fe0e5c31b50c1d52408b5e8918d9f33271c62a19a65017694ebeacb4f390fe914688ca7b1952cdb84ed55ec",
+ "filesize": 2975492
+ },
+ "Darwin_x86_64-gcc3-u-i386-x86_64": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-mac-x64.zip",
+ "hashValue": "1916805e84a49e04748204f4e4c48ae52c8312f7c04afedacacd7dfab2de424412bc988a8c3e5bcb0865f8844b569c0eb9589dae51e74d9bdfe46792c9d1631f",
+ "filesize": 2155607
+ },
+ "Darwin_x86_64-gcc3": {
+ "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
+ },
+ "Linux_x86-gcc3": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-ia32.zip",
+ "hashValue": "5c4beb72ea693740a013b60bc5491a042bb82fa5ca6845a2f450579e2e1e465263f19e7ab6d08d91deb8219b30f092ab6e6745300d5adda627f270c95e5a66e0",
+ "filesize": 3084582
+ },
+ "WINNT_x86_64-msvc-x64": {
+ "alias": "WINNT_x86_64-msvc"
+ },
+ "WINNT_x86_64-msvc": {
+ "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-x64.zip",
+ "hashValue": "33497f3458846e11fa52413f6477bfe1a7f502da262c3a2ce9fe6d773a4a2d023c54228596eb162444b55c87fb126de01f60fa729d897ef5e6eec73b2dfbdc7a",
+ "filesize": 2853777
+ }
+ },
+ "version": "1.4.8.903"
+ }
+ },
+ "hashFunction": "sha512",
+ "name": "Widevine-1.4.8.903",
+ "schema_version": 1000
+}
diff --git a/components/global/content/process-content.js b/components/global/content/process-content.js
index 9d7e37100..2ff8f908a 100644
--- a/components/global/content/process-content.js
+++ b/components/global/content/process-content.js
@@ -14,6 +14,13 @@ Cu.import("resource://gre/modules/Services.jsm");
const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+Services.cpmm.addMessageListener("gmp-plugin-crash", msg => {
+ let gmpservice = Cc["@mozilla.org/gecko-media-plugin-service;1"]
+ .getService(Ci.mozIGeckoMediaPluginService);
+
+ gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName);
+});
+
if (gInContentProcess) {
let ProcessObserver = {
TOPICS: [
diff --git a/components/global/jar.mn b/components/global/jar.mn
index b02587ec4..5086663c2 100644
--- a/components/global/jar.mn
+++ b/components/global/jar.mn
@@ -1,6 +1,8 @@
toolkit.jar:
% content global %content/global/ contentaccessible=yes
% content global-platform %content/global-platform/ platform
+ content/global/gmp-sources/openh264.json (content/gmp-sources/openh264.json)
+ content/global/gmp-sources/widevinecdm.json (content/gmp-sources/widevinecdm.json)
content/global/XPCNativeWrapper.js (content/XPCNativeWrapper.js)
content/global/minimal-xul.css (content/minimal-xul.css)
* content/global/xul.css (content/xul.css)
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index f1e6c9bc1..8780fbf36 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -59,6 +59,7 @@
#include "mozilla/BasePrincipal.h"
#include "mozilla/WebBrowserPersistDocumentChild.h"
#include "imgLoader.h"
+#include "GMPServiceChild.h"
#include "mozilla/Unused.h"
@@ -154,6 +155,7 @@
#include "mozilla/net/NeckoMessageUtils.h"
#include "mozilla/widget/PuppetBidiKeyboard.h"
#include "mozilla/RemoteSpellCheckEngineChild.h"
+#include "GMPServiceChild.h"
#include "gfxPlatform.h"
#include "nscore.h" // for NS_FREE_PERMANENT_DATA
@@ -163,6 +165,7 @@ using namespace mozilla::dom::ipc;
using namespace mozilla::dom::workers;
using namespace mozilla::media;
using namespace mozilla::embedding;
+using namespace mozilla::gmp;
using namespace mozilla::hal_sandbox;
using namespace mozilla::ipc;
using namespace mozilla::layers;
@@ -1114,6 +1117,20 @@ ContentChild::AllocPContentBridgeParent(mozilla::ipc::Transport* aTransport,
return mLastBridge;
}
+PGMPServiceChild*
+ContentChild::AllocPGMPServiceChild(mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherProcess)
+{
+ return GMPServiceChild::Create(aTransport, aOtherProcess);
+}
+
+bool
+ContentChild::RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities)
+{
+ GeckoMediaPluginServiceChild::UpdateGMPCapabilities(Move(capabilities));
+ return true;
+}
+
bool
ContentChild::RecvInitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor,
Endpoint<PImageBridgeChild>&& aImageBridge,
diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h
index 5ca7a053b..7b2298feb 100644
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -140,6 +140,13 @@ public:
AllocPContentBridgeChild(mozilla::ipc::Transport* transport,
base::ProcessId otherProcess) override;
+ PGMPServiceChild*
+ AllocPGMPServiceChild(mozilla::ipc::Transport* transport,
+ base::ProcessId otherProcess) override;
+
+ bool
+ RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) override;
+
bool
RecvInitRendering(
Endpoint<PCompositorBridgeChild>&& aCompositor,
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index 76ffc5ba6..f20312807 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -21,6 +21,7 @@
#include "AppProcessChecker.h"
#include "AudioChannelService.h"
#include "BlobParent.h"
+#include "GMPServiceParent.h"
#include "HandlerServiceParent.h"
#include "IHistory.h"
#include "imgIContainer.h"
@@ -219,6 +220,7 @@ using namespace mozilla::dom::power;
using namespace mozilla::media;
using namespace mozilla::embedding;
using namespace mozilla::gfx;
+using namespace mozilla::gmp;
using namespace mozilla::hal;
using namespace mozilla::ipc;
using namespace mozilla::layers;
@@ -855,6 +857,12 @@ static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement)
}
bool
+ContentParent::RecvCreateGMPService()
+{
+ return PGMPService::Open(this);
+}
+
+bool
ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
{
*aRv = NS_OK;
@@ -1249,6 +1257,9 @@ ContentParent::Init()
#endif
}
#endif
+
+ RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton());
+ gmps->UpdateContentProcessGMPCapabilities();
}
void
@@ -2515,6 +2526,13 @@ ContentParent::Observe(nsISupports* aSubject,
return NS_OK;
}
+PGMPServiceParent*
+ContentParent::AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherProcess)
+{
+ return GMPServiceParent::Create(aTransport, aOtherProcess);
+}
+
PBackgroundParent*
ContentParent::AllocPBackgroundParent(Transport* aTransport,
ProcessId aOtherProcess)
diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
index ab3258e4f..7a4d3d55a 100644
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -237,6 +237,8 @@ public:
virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) override;
+ virtual bool RecvCreateGMPService() override;
+
virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
uint32_t* aRunID) override;
@@ -668,6 +670,10 @@ private:
TabParent* aTopLevel, const TabId& aTabId,
uint64_t* aId);
+ PGMPServiceParent*
+ AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherProcess) override;
+
PBackgroundParent*
AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess)
override;
diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl
index 991e90c23..70ef084cd 100644
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -21,7 +21,15 @@ include protocol PImageBridge;
include protocol PMedia;
include protocol PMemoryReportRequest;
include protocol PNecko;
+// FIXME This is pretty ridiculous, but we have to keep the order of the
+// following 4 includes, or the parser is confused about PGMPContent
+// bridging PContent and PGMP. As soon as it registers the bridge between
+// PContent and PPluginModule it seems to think that PContent's parent and
+// child live in the same process!
+include protocol PGMPContent;
+include protocol PGMPService;
include protocol PPluginModule;
+include protocol PGMP;
include protocol PPrinting;
include protocol PSendStream;
include protocol POfflineCacheUpdate;
@@ -202,11 +210,25 @@ struct BlobURLRegistrationData
Principal principal;
};
+struct GMPAPITags
+{
+ nsCString api;
+ nsCString[] tags;
+};
+
+struct GMPCapabilityData
+{
+ nsCString name;
+ nsCString version;
+ GMPAPITags[] capabilities;
+};
+
nested(upto inside_cpow) sync protocol PContent
{
parent spawns PPluginModule;
parent opens PProcessHangMonitor;
+ parent opens PGMPService;
child opens PBackground;
manages PBlob;
@@ -492,6 +514,9 @@ child:
async BlobURLUnregistration(nsCString aURI);
+
+ async GMPsChanged(GMPCapabilityData[] capabilities);
+
parent:
/**
* Tell the content process some attributes of itself. This is
@@ -523,6 +548,8 @@ parent:
returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
sync BridgeToChildProcess(ContentParentId cpId);
+ async CreateGMPService();
+
/**
* This call connects the content process to a plugin process. While this
* call runs, a new PluginModuleParent will be created in the ContentChild
diff --git a/dom/media/AbstractMediaDecoder.h b/dom/media/AbstractMediaDecoder.h
index 119e38bfb..01722f8b1 100644
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -16,6 +16,8 @@
#include "nsDataHashtable.h"
#include "nsThreadUtils.h"
+class GMPCrashHelper;
+
namespace mozilla
{
@@ -107,6 +109,8 @@ public:
// Set by Reader if the current audio track can be offloaded
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) {}
+ virtual already_AddRefed<GMPCrashHelper> GetCrashHelper() { return nullptr; }
+
// Stack based class to assist in notifying the frame statistics of
// parsed and decoded frames. Use inside video demux & decode functions
// to ensure all parsed and decoded frames are reported on all return paths.
diff --git a/dom/media/DecoderDoctorDiagnostics.cpp b/dom/media/DecoderDoctorDiagnostics.cpp
index 1ca33e14b..778e8c4c5 100644
--- a/dom/media/DecoderDoctorDiagnostics.cpp
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -839,6 +839,13 @@ DecoderDoctorDiagnostics::GetDescription() const
if (mFFmpegFailedToLoad) {
s += ", Linux platform decoder failed to load";
}
+ if (mGMPPDMFailedToStartup) {
+ s += ", GMP PDM failed to startup";
+ } else if (!mGMP.IsEmpty()) {
+ s += ", Using GMP '";
+ s += mGMP;
+ s += "'";
+ }
break;
case eMediaKeySystemAccessRequest:
s = "key system='";
diff --git a/dom/media/DecoderDoctorDiagnostics.h b/dom/media/DecoderDoctorDiagnostics.h
index 51f7664b1..6ed04cfe3 100644
--- a/dom/media/DecoderDoctorDiagnostics.h
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -79,9 +79,15 @@ public:
void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; }
bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; }
+ void SetGMPPDMFailedToStartup() { mGMPPDMFailedToStartup = true; }
+ bool DidGMPPDMFailToStartup() const { return mGMPPDMFailedToStartup; }
+
void SetVideoNotSupported() { mVideoNotSupported = true; }
void SetAudioNotSupported() { mAudioNotSupported = true; }
+ void SetGMP(const nsACString& aGMP) { mGMP = aGMP; }
+ const nsACString& GMP() const { return mGMP; }
+
const nsAString& KeySystem() const { return mKeySystem; }
bool IsKeySystemSupported() const { return mIsKeySystemSupported; }
enum KeySystemIssue {
@@ -114,6 +120,7 @@ private:
bool mWMFFailedToLoad = false;
bool mFFmpegFailedToLoad = false;
+ bool mGMPPDMFailedToStartup = false;
bool mVideoNotSupported = false;
bool mAudioNotSupported = false;
nsCString mGMP;
diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp
index 3988d37b1..ebc694b47 100644
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -29,6 +29,7 @@
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h"
#include "nsPrintfCString.h"
+#include "GMPService.h"
#include "Layers.h"
#include "mozilla/layers/ShadowLayers.h"
@@ -925,6 +926,34 @@ MediaDecoder::OwnerHasError() const
return mOwner->HasError();
}
+class MediaElementGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
+ : mElement(aElement)
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ }
+ already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ if (!mElement) {
+ return nullptr;
+ }
+ return do_AddRef(mElement->OwnerDoc()->GetInnerWindow());
+ }
+private:
+ WeakPtr<HTMLMediaElement> mElement;
+};
+
+already_AddRefed<GMPCrashHelper>
+MediaDecoder::GetCrashHelper()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mOwner->GetMediaElement() ?
+ MakeAndAddRef<MediaElementGMPCrashHelper>(mOwner->GetMediaElement()) : nullptr;
+}
+
bool
MediaDecoder::IsEnded() const
{
diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h
index a0b14b6da..5bc9e5000 100644
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -242,6 +242,8 @@ public:
// Must be called before Shutdown().
bool OwnerHasError() const;
+ already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
+
protected:
// Updates the media duration. This is called while the media is being
// played, calls before the media has reached loaded metadata are ignored.
diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp
index 08505f306..7775f0d9b 100644
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -359,6 +359,7 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack)
: *ownerData.mOriginalInfo->GetAsAudioInfo(),
ownerData.mTaskQueue,
ownerData.mCallback.get(),
+ mOwner->mCrashHelper,
ownerData.mIsBlankDecode,
&result
});
@@ -376,6 +377,7 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack)
ownerData.mCallback.get(),
mOwner->mKnowsCompositor,
mOwner->GetImageContainer(),
+ mOwner->mCrashHelper,
ownerData.mIsBlankDecode,
&result
});
@@ -568,6 +570,10 @@ MediaFormatReader::InitInternal()
mVideo.mTaskQueue =
new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER));
+ // Note: GMPCrashHelper must be created on main thread, as it may use
+ // weak references, which aren't threadsafe.
+ mCrashHelper = mDecoder->GetCrashHelper();
+
return NS_OK;
}
diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h
index 4e084f415..3da080450 100644
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -579,6 +579,8 @@ private:
RefPtr<VideoFrameContainer> mVideoFrameContainer;
layers::ImageContainer* GetImageContainer();
+ RefPtr<GMPCrashHelper> mCrashHelper;
+
void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode);
class DecoderFactory;
diff --git a/dom/media/MediaPrefs.h b/dom/media/MediaPrefs.h
index 179711b5f..84163353a 100644
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -100,6 +100,9 @@ private:
// PlatformDecoderModule
DECL_MEDIA_PREF("media.apple.forcevda", AppleForceVDA, bool, false);
+ DECL_MEDIA_PREF("media.gmp.insecure.allow", GMPAllowInsecure, bool, false);
+ DECL_MEDIA_PREF("media.gmp.async-shutdown-timeout", GMPAsyncShutdownTimeout, uint32_t, GMP_DEFAULT_ASYNC_SHUTDOWN_TIMEOUT);
+ DECL_MEDIA_PREF("media.eme.enabled", EMEEnabled, bool, false);
DECL_MEDIA_PREF("media.use-blank-decoder", PDMUseBlankDecoder, bool, false);
DECL_MEDIA_PREF("media.gpu-process-decoder", PDMUseGPUDecoder, bool, false);
#ifdef MOZ_FFMPEG
@@ -122,6 +125,9 @@ private:
DECL_MEDIA_PREF("media.decoder.fuzzing.enabled", PDMFuzzingEnabled, bool, false);
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.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);
// MediaDecoderStateMachine
DECL_MEDIA_PREF("media.suspend-bkgnd-video.enabled", MDSMSuspendBackgroundVideoEnabled, bool, false);
diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.info b/dom/media/gmp-plugin-openh264/fakeopenh264.info
new file mode 100644
index 000000000..814a96be3
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/fakeopenh264.info
@@ -0,0 +1,4 @@
+Name: fakeopenh264
+Description: Fake GMP Plugin
+Version: 1.0
+APIs: encode-video[h264:fake], decode-video[h264:fake]
diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.voucher b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher
new file mode 100644
index 000000000..2104d73c7
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher
@@ -0,0 +1 @@
+gmp-fakeopenh264 placeholder voucher
diff --git a/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
new file mode 100644
index 000000000..7dbc85251
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
@@ -0,0 +1,429 @@
+/*!
+ * \copy
+ * Copyright (c) 2009-2014, Cisco Systems
+ * Copyright (c) 2014, Mozilla
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ *************************************************************************************
+ */
+
+#include <stdint.h>
+#include <time.h>
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+#include <string>
+#include <memory>
+#include <assert.h>
+#include <limits.h>
+
+#include "gmp-platform.h"
+#include "gmp-video-host.h"
+#include "gmp-video-encode.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+#include "gmp-decryption.h"
+#include "gmp-test-decryptor.h"
+#include "gmp-test-storage.h"
+#endif
+
+#if defined(_MSC_VER)
+#define PUBLIC_FUNC __declspec(dllexport)
+#else
+#define PUBLIC_FUNC
+#endif
+
+#define BIG_FRAME 10000
+
+static int g_log_level = 0;
+
+#define GMPLOG(l, x) do { \
+ if (l <= g_log_level) { \
+ const char *log_string = "unknown"; \
+ if ((l >= 0) && (l <= 3)) { \
+ log_string = kLogStrings[l]; \
+ } \
+ std::cerr << log_string << ": " << x << std::endl; \
+ } \
+ } while(0)
+
+#define GL_CRIT 0
+#define GL_ERROR 1
+#define GL_INFO 2
+#define GL_DEBUG 3
+
+const char* kLogStrings[] = {
+ "Critical",
+ "Error",
+ "Info",
+ "Debug"
+};
+
+
+GMPPlatformAPI* g_platform_api = NULL;
+
+class FakeVideoEncoder;
+class FakeVideoDecoder;
+
+struct EncodedFrame {
+ uint32_t length_;
+ uint8_t h264_compat_;
+ uint32_t magic_;
+ uint32_t width_;
+ uint32_t height_;
+ uint8_t y_;
+ uint8_t u_;
+ uint8_t v_;
+ uint32_t timestamp_;
+};
+
+#define ENCODED_FRAME_MAGIC 0x4652414d
+
+class FakeEncoderTask : public GMPTask {
+ public:
+ FakeEncoderTask(FakeVideoEncoder* encoder,
+ GMPVideoi420Frame* frame,
+ GMPVideoFrameType type)
+ : encoder_(encoder), frame_(frame), type_(type) {}
+
+ virtual void Run();
+ virtual void Destroy() { delete this; }
+
+ FakeVideoEncoder* encoder_;
+ GMPVideoi420Frame* frame_;
+ GMPVideoFrameType type_;
+};
+
+class FakeVideoEncoder : public GMPVideoEncoder {
+ public:
+ explicit FakeVideoEncoder (GMPVideoHost* hostAPI) :
+ host_ (hostAPI),
+ callback_ (NULL) {}
+
+ virtual void InitEncode (const GMPVideoCodec& codecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificSize,
+ GMPVideoEncoderCallback* callback,
+ int32_t numberOfCores,
+ uint32_t maxPayloadSize) {
+ callback_ = callback;
+
+ GMPLOG (GL_INFO, "Initialized encoder");
+ }
+
+ virtual void Encode (GMPVideoi420Frame* inputImage,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ const GMPVideoFrameType* aFrameTypes,
+ uint32_t aFrameTypesLength) {
+ GMPLOG (GL_DEBUG,
+ __FUNCTION__
+ << " size="
+ << inputImage->Width() << "x" << inputImage->Height());
+
+ assert (aFrameTypesLength != 0);
+
+ g_platform_api->runonmainthread(new FakeEncoderTask(this,
+ inputImage,
+ aFrameTypes[0]));
+ }
+
+ void Encode_m (GMPVideoi420Frame* inputImage,
+ GMPVideoFrameType frame_type) {
+ if (frame_type == kGMPKeyFrame) {
+ if (!inputImage)
+ return;
+ }
+ if (!inputImage) {
+ GMPLOG (GL_ERROR, "no input image");
+ return;
+ }
+
+ // Now return the encoded data back to the parent.
+ GMPVideoFrame* ftmp;
+ GMPErr err = host_->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Error creating encoded frame");
+ return;
+ }
+
+ GMPVideoEncodedFrame* f = static_cast<GMPVideoEncodedFrame*> (ftmp);
+
+ // Encode this in a frame that looks a little bit like H.264.
+ // Note that we don't do PPS or SPS.
+ // Copy the data. This really should convert this to network byte order.
+ EncodedFrame eframe;
+ eframe.length_ = sizeof(eframe) - sizeof(uint32_t);
+ eframe.h264_compat_ = 5; // Emulate a H.264 IDR NAL.
+ eframe.magic_ = ENCODED_FRAME_MAGIC;
+ eframe.width_ = inputImage->Width();
+ eframe.height_ = inputImage->Height();
+ eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
+ inputImage->AllocatedSize(kGMPYPlane));
+ eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
+ inputImage->AllocatedSize(kGMPUPlane));
+ eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
+ inputImage->AllocatedSize(kGMPVPlane));
+
+ eframe.timestamp_ = inputImage->Timestamp();
+
+ err = f->CreateEmptyFrame (sizeof(eframe) +
+ (frame_type == kGMPKeyFrame ? sizeof(uint32_t) + BIG_FRAME : 0));
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Error allocating frame data");
+ f->Destroy();
+ return;
+ }
+ memcpy(f->Buffer(), &eframe, sizeof(eframe));
+ if (frame_type == kGMPKeyFrame) {
+ *((uint32_t*) f->Buffer() + sizeof(eframe)) = BIG_FRAME;
+ }
+
+ f->SetEncodedWidth (inputImage->Width());
+ f->SetEncodedHeight (inputImage->Height());
+ f->SetTimeStamp (inputImage->Timestamp());
+ f->SetFrameType (frame_type);
+ f->SetCompleteFrame (true);
+ f->SetBufferType(GMP_BufferLength32);
+
+ GMPLOG (GL_DEBUG, "Encoding complete. type= "
+ << f->FrameType()
+ << " length="
+ << f->Size()
+ << " timestamp="
+ << f->TimeStamp());
+
+ // Return the encoded frame.
+ GMPCodecSpecificInfo info;
+ memset (&info, 0, sizeof (info));
+ info.mCodecType = kGMPVideoCodecH264;
+ info.mBufferType = GMP_BufferLength32;
+ info.mCodecSpecific.mH264.mSimulcastIdx = 0;
+ GMPLOG (GL_DEBUG, "Calling callback");
+ callback_->Encoded (f, reinterpret_cast<uint8_t*> (&info), sizeof(info));
+ GMPLOG (GL_DEBUG, "Callback called");
+ }
+
+ virtual void SetChannelParameters (uint32_t aPacketLoss, uint32_t aRTT) {
+ }
+
+ virtual void SetRates (uint32_t aNewBitRate, uint32_t aFrameRate) {
+ }
+
+ virtual void SetPeriodicKeyFrames (bool aEnable) {
+ }
+
+ virtual void EncodingComplete() {
+ delete this;
+ }
+
+ private:
+ uint8_t AveragePlane(uint8_t* ptr, size_t len) {
+ uint64_t val = 0;
+
+ for (size_t i=0; i<len; ++i) {
+ val += ptr[i];
+ }
+
+ return (val / len) % 0xff;
+ }
+
+ GMPVideoHost* host_;
+ GMPVideoEncoderCallback* callback_;
+};
+
+void FakeEncoderTask::Run() {
+ encoder_->Encode_m(frame_, type_);
+ frame_->Destroy();
+}
+
+class FakeDecoderTask : public GMPTask {
+ public:
+ FakeDecoderTask(FakeVideoDecoder* decoder,
+ GMPVideoEncodedFrame* frame,
+ int64_t time)
+ : decoder_(decoder), frame_(frame), time_(time) {}
+
+ virtual void Run();
+ virtual void Destroy() { delete this; }
+
+ FakeVideoDecoder* decoder_;
+ GMPVideoEncodedFrame* frame_;
+ int64_t time_;
+};
+
+class FakeVideoDecoder : public GMPVideoDecoder {
+ public:
+ explicit FakeVideoDecoder (GMPVideoHost* hostAPI) :
+ host_ (hostAPI),
+ callback_ (NULL) {}
+
+ virtual ~FakeVideoDecoder() {
+ }
+
+ virtual void InitDecode (const GMPVideoCodec& codecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificSize,
+ GMPVideoDecoderCallback* callback,
+ int32_t coreCount) {
+ GMPLOG (GL_INFO, "InitDecode");
+
+ callback_ = callback;
+ }
+
+ virtual void Decode (GMPVideoEncodedFrame* inputFrame,
+ bool missingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t renderTimeMs = -1) {
+ GMPLOG (GL_DEBUG, __FUNCTION__
+ << "Decoding frame size=" << inputFrame->Size()
+ << " timestamp=" << inputFrame->TimeStamp());
+ g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs));
+ }
+
+ virtual void Reset() {
+ }
+
+ virtual void Drain() {
+ }
+
+ virtual void DecodingComplete() {
+ delete this;
+ }
+
+ // Return the decoded data back to the parent.
+ void Decode_m (GMPVideoEncodedFrame* inputFrame,
+ int64_t renderTimeMs) {
+ EncodedFrame *eframe;
+ if (inputFrame->Size() != (sizeof(*eframe))) {
+ GMPLOG (GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size());
+ return;
+ }
+ eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
+
+ if (eframe->magic_ != ENCODED_FRAME_MAGIC) {
+ GMPLOG (GL_ERROR, "Couldn't decode frame. Magic=" << eframe->magic_);
+ return;
+ }
+
+ int width = eframe->width_;
+ int height = eframe->height_;
+ int ystride = eframe->width_;
+ int uvstride = eframe->width_/2;
+
+ GMPLOG (GL_DEBUG, "Video frame ready for display "
+ << width
+ << "x"
+ << height
+ << " timestamp="
+ << inputFrame->TimeStamp());
+
+ GMPVideoFrame* ftmp = NULL;
+
+ // Translate the image.
+ GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp);
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame");
+ return;
+ }
+
+ GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp);
+ err = frame->CreateEmptyFrame (
+ width, height,
+ ystride, uvstride, uvstride);
+ if (err != GMPNoErr) {
+ GMPLOG (GL_ERROR, "Couldn't make decoded frame");
+ return;
+ }
+
+ memset(frame->Buffer(kGMPYPlane),
+ eframe->y_,
+ frame->AllocatedSize(kGMPYPlane));
+ memset(frame->Buffer(kGMPUPlane),
+ eframe->u_,
+ frame->AllocatedSize(kGMPUPlane));
+ memset(frame->Buffer(kGMPVPlane),
+ eframe->v_,
+ frame->AllocatedSize(kGMPVPlane));
+
+ GMPLOG (GL_DEBUG, "Allocated size = "
+ << frame->AllocatedSize (kGMPYPlane));
+ frame->SetTimestamp (inputFrame->TimeStamp());
+ frame->SetDuration (inputFrame->Duration());
+ callback_->Decoded (frame);
+
+ }
+
+ GMPVideoHost* host_;
+ GMPVideoDecoderCallback* callback_;
+};
+
+void FakeDecoderTask::Run() {
+ decoder_->Decode_m(frame_, time_);
+ frame_->Destroy();
+}
+
+extern "C" {
+
+ PUBLIC_FUNC GMPErr
+ GMPInit (GMPPlatformAPI* aPlatformAPI) {
+ g_platform_api = aPlatformAPI;
+ return GMPNoErr;
+ }
+
+ PUBLIC_FUNC GMPErr
+ GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
+ if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
+ *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
+ return GMPNoErr;
+ } else if (!strcmp (aApiName, GMP_API_VIDEO_ENCODER)) {
+ *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
+ return GMPNoErr;
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+ } else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
+ *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
+ return GMPNoErr;
+ } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
+ *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
+ return GMPNoErr;
+#endif
+ }
+ return GMPGenericErr;
+ }
+
+ PUBLIC_FUNC void
+ GMPShutdown (void) {
+ g_platform_api = NULL;
+ }
+
+} // extern "C"
diff --git a/dom/media/gmp-plugin-openh264/moz.build b/dom/media/gmp-plugin-openh264/moz.build
new file mode 100644
index 000000000..d47a1d197
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# largely a copy of dom/media/gmp-fake/moz.build
+
+FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0'
+
+FINAL_TARGET_FILES += [
+ 'fakeopenh264.info',
+ 'fakeopenh264.voucher',
+]
+
+SOURCES += [
+ 'gmp-fake-openh264.cpp',
+]
+
+SharedLibrary("fakeopenh264")
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'ole32',
+ ]
+
+USE_STATIC_LIBS = True
+NO_VISIBILITY_FLAGS = True
+# Don't use STL wrappers; this isn't Gecko code
+DISABLE_STL_WRAPPING = True
diff --git a/dom/media/gmp-plugin/fake.info b/dom/media/gmp-plugin/fake.info
new file mode 100644
index 000000000..b0592ff63
--- /dev/null
+++ b/dom/media/gmp-plugin/fake.info
@@ -0,0 +1,5 @@
+Name: fake
+Description: Fake GMP Plugin, which deliberately uses GMP_API_DECRYPTOR_BACKWARDS_COMPAT for its decryptor.
+Version: 1.0
+APIs: decode-video[h264:broken], eme-decrypt-v7[fake]
+Libraries: dxva2.dll
diff --git a/dom/media/gmp-plugin/fake.voucher b/dom/media/gmp-plugin/fake.voucher
new file mode 100644
index 000000000..bb133701c
--- /dev/null
+++ b/dom/media/gmp-plugin/fake.voucher
@@ -0,0 +1 @@
+gmp-fake placeholder voucher \ No newline at end of file
diff --git a/dom/media/gmp-plugin/gmp-fake.cpp b/dom/media/gmp-plugin/gmp-fake.cpp
new file mode 100644
index 000000000..b67efe251
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-fake.cpp
@@ -0,0 +1,98 @@
+/*!
+ * \copy
+ * Copyright (c) 2009-2014, Cisco Systems
+ * Copyright (c) 2014, Mozilla
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ *************************************************************************************
+ */
+
+#include <stdint.h>
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <memory>
+
+#include "gmp-platform.h"
+#include "gmp-video-decode.h"
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+#include "gmp-decryption.h"
+#include "gmp-test-decryptor.h"
+#include "gmp-test-storage.h"
+#endif
+
+#if defined(_MSC_VER)
+#define PUBLIC_FUNC __declspec(dllexport)
+#else
+#define PUBLIC_FUNC
+#endif
+
+GMPPlatformAPI* g_platform_api = NULL;
+
+extern "C" {
+
+ PUBLIC_FUNC GMPErr
+ GMPInit (GMPPlatformAPI* aPlatformAPI) {
+ g_platform_api = aPlatformAPI;
+ return GMPNoErr;
+ }
+
+ PUBLIC_FUNC GMPErr
+ GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
+ if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
+ // Note: Deliberately advertise in our .info file that we support
+ // video-decode, but we fail the "get" call here to simulate what
+ // happens when decoder init fails.
+ return GMPGenericErr;
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+ } else if (!strcmp (aApiName, GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
+ *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
+ return GMPNoErr;
+ } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
+ *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
+ return GMPNoErr;
+#endif
+ }
+ return GMPGenericErr;
+ }
+
+ PUBLIC_FUNC void
+ GMPShutdown (void) {
+ g_platform_api = NULL;
+ }
+
+#if defined(GMP_FAKE_SUPPORT_DECRYPT)
+ PUBLIC_FUNC void
+ GMPSetNodeId(const char* aNodeId, uint32_t aLength) {
+ FakeDecryptor::SetNodeId(aNodeId, aLength);
+ }
+#endif
+
+} // extern "C"
diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.cpp b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
new file mode 100644
index 000000000..afa809602
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -0,0 +1,608 @@
+/* -*- 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 "gmp-test-decryptor.h"
+#include "gmp-test-storage.h"
+#include "gmp-test-output-protection.h"
+
+#include <string>
+#include <vector>
+#include <iostream>
+#include <istream>
+#include <iterator>
+#include <sstream>
+#include <set>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+using namespace std;
+
+std::string FakeDecryptor::sNodeId;
+
+FakeDecryptor* FakeDecryptor::sInstance = nullptr;
+extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp
+
+class GMPMutexAutoLock
+{
+public:
+ explicit GMPMutexAutoLock(GMPMutex* aMutex) : mMutex(aMutex) {
+ mMutex->Acquire();
+ }
+ ~GMPMutexAutoLock() {
+ mMutex->Release();
+ }
+private:
+ GMPMutex* const mMutex;
+};
+
+class TestManager {
+public:
+ TestManager() : mMutex(CreateMutex()) {}
+
+ // Register a test with the test manager.
+ void BeginTest(const string& aTestID) {
+ GMPMutexAutoLock lock(mMutex);
+ auto found = mTestIDs.find(aTestID);
+ if (found == mTestIDs.end()) {
+ mTestIDs.insert(aTestID);
+ } else {
+ Error("FAIL BeginTest test already existed: " + aTestID);
+ }
+ }
+
+ // Notify the test manager that the test is finished. If all tests are done,
+ // test manager will send "test-storage complete" to notify the parent that
+ // all tests are finished and also delete itself.
+ void EndTest(const string& aTestID) {
+ bool isEmpty = false;
+ {
+ GMPMutexAutoLock lock(mMutex);
+ auto found = mTestIDs.find(aTestID);
+ if (found != mTestIDs.end()) {
+ mTestIDs.erase(aTestID);
+ isEmpty = mTestIDs.empty();
+ } else {
+ Error("FAIL EndTest test not existed: " + aTestID);
+ return;
+ }
+ }
+ if (isEmpty) {
+ Finish();
+ delete this;
+ }
+ }
+
+private:
+ ~TestManager() {
+ mMutex->Destroy();
+ }
+
+ static void Error(const string& msg) {
+ FakeDecryptor::Message(msg);
+ }
+
+ static void Finish() {
+ FakeDecryptor::Message("test-storage complete");
+ }
+
+ static GMPMutex* CreateMutex() {
+ GMPMutex* mutex = nullptr;
+ g_platform_api->createmutex(&mutex);
+ return mutex;
+ }
+
+ GMPMutex* const mMutex;
+ set<string> mTestIDs;
+};
+
+FakeDecryptor::FakeDecryptor(GMPDecryptorHost* aHost)
+ : mCallback(nullptr)
+ , mHost(aHost)
+{
+ MOZ_ASSERT(!sInstance);
+ sInstance = this;
+}
+
+void FakeDecryptor::DecryptingComplete()
+{
+ sInstance = nullptr;
+ delete this;
+}
+
+void
+FakeDecryptor::Message(const std::string& aMessage)
+{
+ MOZ_ASSERT(sInstance);
+ const static std::string sid("fake-session-id");
+ sInstance->mCallback->SessionMessage(sid.c_str(), sid.size(),
+ kGMPLicenseRequest,
+ (const uint8_t*)aMessage.c_str(), aMessage.size());
+}
+
+std::vector<std::string>
+Tokenize(const std::string& aString)
+{
+ std::stringstream strstr(aString);
+ std::istream_iterator<std::string> it(strstr), end;
+ return std::vector<std::string>(it, end);
+}
+
+static const string TruncateRecordId = "truncate-record-id";
+static const string TruncateRecordData = "I will soon be truncated";
+
+class ReadThenTask : public GMPTask {
+public:
+ ReadThenTask(string aId, ReadContinuation* aThen)
+ : mId(aId)
+ , mThen(aThen)
+ {}
+ void Run() override {
+ ReadRecord(mId, mThen);
+ }
+ void Destroy() override {
+ delete this;
+ }
+ string mId;
+ ReadContinuation* mThen;
+};
+
+class SendMessageTask : public GMPTask {
+public:
+ explicit SendMessageTask(const string& aMessage,
+ TestManager* aTestManager = nullptr,
+ const string& aTestID = "")
+ : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void Run() override {
+ FakeDecryptor::Message(mMessage);
+ if (mTestmanager) {
+ mTestmanager->EndTest(mTestID);
+ }
+ }
+
+ void Destroy() override {
+ delete this;
+ }
+
+private:
+ string mMessage;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class TestEmptyContinuation : public ReadContinuation {
+public:
+ TestEmptyContinuation(TestManager* aTestManager, const string& aTestID)
+ : mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != "") {
+ FakeDecryptor::Message("FAIL TestEmptyContinuation record was not truncated");
+ }
+ mTestmanager->EndTest(mTestID);
+ delete this;
+ }
+
+private:
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class TruncateContinuation : public ReadContinuation {
+public:
+ TruncateContinuation(const string& aID,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != TruncateRecordData) {
+ FakeDecryptor::Message("FAIL TruncateContinuation read data doesn't match written data");
+ }
+ auto cont = new TestEmptyContinuation(mTestmanager, mTestID);
+ auto msg = "FAIL in TruncateContinuation write.";
+ auto failTask = new SendMessageTask(msg, mTestmanager, mTestID);
+ WriteRecord(mID, nullptr, 0, new ReadThenTask(mID, cont), failTask);
+ delete this;
+ }
+
+private:
+ const string mID;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class VerifyAndFinishContinuation : public ReadContinuation {
+public:
+ explicit VerifyAndFinishContinuation(string aValue,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mValue(aValue), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != mValue) {
+ FakeDecryptor::Message("FAIL VerifyAndFinishContinuation read data doesn't match expected data");
+ }
+ mTestmanager->EndTest(mTestID);
+ delete this;
+ }
+
+private:
+ string mValue;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class VerifyAndOverwriteContinuation : public ReadContinuation {
+public:
+ VerifyAndOverwriteContinuation(string aId, string aValue, string aOverwrite,
+ TestManager* aTestManager, const string& aTestID)
+ : mId(aId)
+ , mValue(aValue)
+ , mOverwrite(aOverwrite)
+ , mTestmanager(aTestManager)
+ , mTestID(aTestID)
+ {}
+
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (aData != mValue) {
+ FakeDecryptor::Message("FAIL VerifyAndOverwriteContinuation read data doesn't match expected data");
+ }
+ auto cont = new VerifyAndFinishContinuation(mOverwrite, mTestmanager, mTestID);
+ auto msg = "FAIL in VerifyAndOverwriteContinuation write.";
+ auto failTask = new SendMessageTask(msg, mTestmanager, mTestID);
+ WriteRecord(mId, mOverwrite, new ReadThenTask(mId, cont), failTask);
+ delete this;
+ }
+
+private:
+ string mId;
+ string mValue;
+ string mOverwrite;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+static const string OpenAgainRecordId = "open-again-record-id";
+
+class OpenedSecondTimeContinuation : public OpenContinuation {
+public:
+ explicit OpenedSecondTimeContinuation(GMPRecord* aRecord,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mRecord(aRecord), mTestmanager(aTestManager), mTestID(aTestID) {
+ MOZ_ASSERT(aRecord);
+ }
+
+ virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override {
+ if (GMP_SUCCEEDED(aStatus)) {
+ FakeDecryptor::Message("FAIL OpenSecondTimeContinuation should not be able to re-open record.");
+ }
+ if (aRecord) {
+ aRecord->Close();
+ }
+ // Succeeded, open should have failed.
+ mTestmanager->EndTest(mTestID);
+ mRecord->Close();
+ }
+
+private:
+ GMPRecord* mRecord;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+class OpenedFirstTimeContinuation : public OpenContinuation {
+public:
+ OpenedFirstTimeContinuation(const string& aID,
+ TestManager* aTestManager,
+ const string& aTestID)
+ : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override {
+ if (GMP_FAILED(aStatus)) {
+ FakeDecryptor::Message("FAIL OpenAgainContinuation to open record initially.");
+ mTestmanager->EndTest(mTestID);
+ if (aRecord) {
+ aRecord->Close();
+ }
+ return;
+ }
+
+ auto cont = new OpenedSecondTimeContinuation(aRecord, mTestmanager, mTestID);
+ GMPOpenRecord(mID, cont);
+ }
+
+private:
+ const string mID;
+ TestManager* const mTestmanager;
+ const string mTestID;
+};
+
+static void
+DoTestStorage(const string& aPrefix, TestManager* aTestManager)
+{
+ // Basic I/O tests. We run three cases concurrently. The tests, like
+ // GMPStorage run asynchronously. When they've all passed, we send
+ // a message back to the parent process, or a failure message if not.
+
+ // Test 1: Basic I/O test, and test that writing 0 bytes in a record
+ // deletes record.
+ //
+ // Write data to truncate record, then
+ // read data, verify that we read what we wrote, then
+ // write 0 bytes to truncate record, then
+ // read data, verify that 0 bytes was read
+ const string id1 = aPrefix + TruncateRecordId;
+ const string testID1 = aPrefix + "write-test-1";
+ aTestManager->BeginTest(testID1);
+ auto cont1 = new TruncateContinuation(id1, aTestManager, testID1);
+ auto msg1 = "FAIL in TestStorage writing TruncateRecord.";
+ auto failTask1 = new SendMessageTask(msg1, aTestManager, testID1);
+ WriteRecord(id1, TruncateRecordData,
+ new ReadThenTask(id1, cont1), failTask1);
+
+ // Test 2: Test that overwriting a record with a shorter record truncates
+ // the record to the shorter record.
+ //
+ // Write record, then
+ // read and verify record, then
+ // write a shorter record to same record.
+ // read and verify
+ string id2 = aPrefix + "record1";
+ string record1 = "This is the first write to a record.";
+ string overwrite = "A shorter record";
+ const string testID2 = aPrefix + "write-test-2";
+ aTestManager->BeginTest(testID2);
+ auto task2 = new VerifyAndOverwriteContinuation(id2, record1, overwrite,
+ aTestManager, testID2);
+ auto msg2 = "FAIL in TestStorage writing record1.";
+ auto failTask2 = new SendMessageTask(msg2, aTestManager, testID2);
+ WriteRecord(id2, record1, new ReadThenTask(id2, task2), failTask2);
+
+ // Test 3: Test that opening a record while it's already open fails.
+ //
+ // Open record1, then
+ // open record1, should fail.
+ // close record1
+ const string id3 = aPrefix + OpenAgainRecordId;
+ const string testID3 = aPrefix + "open-test-1";
+ aTestManager->BeginTest(testID3);
+ auto task3 = new OpenedFirstTimeContinuation(id3, aTestManager, testID3);
+ GMPOpenRecord(id3, task3);
+}
+
+class TestStorageTask : public GMPTask {
+public:
+ TestStorageTask(const string& aPrefix, TestManager* aTestManager)
+ : mPrefix(aPrefix), mTestManager(aTestManager) {}
+ virtual void Destroy() { delete this; }
+ virtual void Run() {
+ DoTestStorage(mPrefix, mTestManager);
+ }
+private:
+ const string mPrefix;
+ TestManager* const mTestManager;
+};
+
+void
+FakeDecryptor::TestStorage()
+{
+ TestManager* testManager = new TestManager();
+ GMPThread* thread1 = nullptr;
+ GMPThread* thread2 = nullptr;
+
+ // Main thread tests.
+ DoTestStorage("mt1-", testManager);
+ DoTestStorage("mt2-", testManager);
+
+ // Off-main-thread tests.
+ if (GMP_SUCCEEDED(g_platform_api->createthread(&thread1))) {
+ thread1->Post(new TestStorageTask("thread1-", testManager));
+ } else {
+ FakeDecryptor::Message("FAIL to create thread1 for storage tests");
+ }
+
+ if (GMP_SUCCEEDED(g_platform_api->createthread(&thread2))) {
+ thread2->Post(new TestStorageTask("thread2-", testManager));
+ } else {
+ FakeDecryptor::Message("FAIL to create thread2 for storage tests");
+ }
+
+ if (thread1) {
+ thread1->Join();
+ }
+
+ if (thread2) {
+ thread2->Join();
+ }
+
+ // Note: Once all tests finish, TestManager will dispatch "test-pass" message,
+ // which ends the test for the parent.
+}
+
+class ReportWritten : public GMPTask {
+public:
+ ReportWritten(const string& aRecordId, const string& aValue)
+ : mRecordId(aRecordId)
+ , mValue(aValue)
+ {}
+ void Run() override {
+ FakeDecryptor::Message("stored " + mRecordId + " " + mValue);
+ }
+ void Destroy() override {
+ delete this;
+ }
+ const string mRecordId;
+ const string mValue;
+};
+
+class ReportReadStatusContinuation : public ReadContinuation {
+public:
+ explicit ReportReadStatusContinuation(const string& aRecordId)
+ : mRecordId(aRecordId)
+ {}
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (GMP_FAILED(aErr)) {
+ FakeDecryptor::Message("retrieve " + mRecordId + " failed");
+ } else {
+ stringstream ss;
+ ss << aData.size();
+ string len;
+ ss >> len;
+ FakeDecryptor::Message("retrieve " + mRecordId + " succeeded (length " +
+ len + " bytes)");
+ }
+ delete this;
+ }
+ string mRecordId;
+};
+
+class ReportReadRecordContinuation : public ReadContinuation {
+public:
+ explicit ReportReadRecordContinuation(const string& aRecordId)
+ : mRecordId(aRecordId)
+ {}
+ void ReadComplete(GMPErr aErr, const std::string& aData) override {
+ if (GMP_FAILED(aErr)) {
+ FakeDecryptor::Message("retrieved " + mRecordId + " failed");
+ } else {
+ FakeDecryptor::Message("retrieved " + mRecordId + " " + aData);
+ }
+ delete this;
+ }
+ string mRecordId;
+};
+
+static void
+RecvGMPRecordIterator(GMPRecordIterator* aRecordIterator,
+ void* aUserArg,
+ GMPErr aStatus)
+{
+ FakeDecryptor* decryptor = reinterpret_cast<FakeDecryptor*>(aUserArg);
+ decryptor->ProcessRecordNames(aRecordIterator, aStatus);
+}
+
+void
+FakeDecryptor::ProcessRecordNames(GMPRecordIterator* aRecordIterator,
+ GMPErr aStatus)
+{
+ if (sInstance != this) {
+ FakeDecryptor::Message("Error aUserArg was not passed through GetRecordIterator");
+ return;
+ }
+ if (GMP_FAILED(aStatus)) {
+ FakeDecryptor::Message("Error GetRecordIterator failed");
+ return;
+ }
+ std::string response("record-names ");
+ bool first = true;
+ const char* name = nullptr;
+ uint32_t len = 0;
+ while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
+ std::string s(name, name+len);
+ if (!first) {
+ response += ",";
+ } else {
+ first = false;
+ }
+ response += s;
+ aRecordIterator->NextRecord();
+ }
+ aRecordIterator->Close();
+ FakeDecryptor::Message(response);
+}
+
+enum ShutdownMode {
+ ShutdownNormal,
+ ShutdownTimeout,
+ ShutdownStoreToken
+};
+
+static ShutdownMode sShutdownMode = ShutdownNormal;
+static string sShutdownToken = "";
+
+void
+FakeDecryptor::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ std::string response((const char*)aResponse, (const char*)(aResponse)+aResponseSize);
+ std::vector<std::string> tokens = Tokenize(response);
+ const string& task = tokens[0];
+ if (task == "test-storage") {
+ TestStorage();
+ } else if (task == "store") {
+ // send "stored record" message on complete.
+ const string& id = tokens[1];
+ const string& value = tokens[2];
+ WriteRecord(id,
+ value,
+ new ReportWritten(id, value),
+ new SendMessageTask("FAIL in writing record."));
+ } else if (task == "retrieve") {
+ const string& id = tokens[1];
+ ReadRecord(id, new ReportReadStatusContinuation(id));
+ } else if (task == "shutdown-mode") {
+ const string& mode = tokens[1];
+ if (mode == "timeout") {
+ sShutdownMode = ShutdownTimeout;
+ } else if (mode == "token") {
+ sShutdownMode = ShutdownStoreToken;
+ sShutdownToken = tokens[2];
+ Message("shutdown-token received " + sShutdownToken);
+ }
+ } else if (task == "retrieve-shutdown-token") {
+ ReadRecord("shutdown-token", new ReportReadRecordContinuation("shutdown-token"));
+ } else if (task == "test-op-apis") {
+ mozilla::gmptest::TestOuputProtectionAPIs();
+ } else if (task == "retrieve-plugin-voucher") {
+ const uint8_t* rawVoucher = nullptr;
+ uint32_t length = 0;
+ mHost->GetPluginVoucher(&rawVoucher, &length);
+ std::string voucher((const char*)rawVoucher, (const char*)(rawVoucher + length));
+ Message("retrieved plugin-voucher: " + voucher);
+ } else if (task == "retrieve-record-names") {
+ GMPEnumRecordNames(&RecvGMPRecordIterator, this);
+ } else if (task == "retrieve-node-id") {
+ Message("node-id " + sNodeId);
+ }
+}
+
+class CompleteShutdownTask : public GMPTask {
+public:
+ explicit CompleteShutdownTask(GMPAsyncShutdownHost* aHost)
+ : mHost(aHost)
+ {
+ }
+ virtual void Run() {
+ mHost->ShutdownComplete();
+ }
+ virtual void Destroy() { delete this; }
+ GMPAsyncShutdownHost* mHost;
+};
+
+void
+TestAsyncShutdown::BeginShutdown() {
+ switch (sShutdownMode) {
+ case ShutdownNormal:
+ mHost->ShutdownComplete();
+ break;
+ case ShutdownTimeout:
+ // Don't do anything; wait for timeout, Gecko should kill
+ // the plugin and recover.
+ break;
+ case ShutdownStoreToken:
+ // Store message, then shutdown.
+ WriteRecord("shutdown-token",
+ sShutdownToken,
+ new CompleteShutdownTask(mHost),
+ new SendMessageTask("FAIL writing shutdown-token."));
+ break;
+ }
+}
diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.h b/dom/media/gmp-plugin/gmp-test-decryptor.h
new file mode 100644
index 000000000..3b17e42c5
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.h
@@ -0,0 +1,102 @@
+/* -*- 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 FAKE_DECRYPTOR_H__
+#define FAKE_DECRYPTOR_H__
+
+#include "gmp-decryption.h"
+#include "gmp-async-shutdown.h"
+#include <string>
+#include "mozilla/Attributes.h"
+
+class FakeDecryptor : public GMPDecryptor7 {
+public:
+
+ explicit FakeDecryptor(GMPDecryptorHost* aHost);
+
+ void Init(GMPDecryptorCallback* aCallback) override {
+ mCallback = aCallback;
+ }
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) override
+ {
+ }
+
+ void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ }
+
+ void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override;
+
+ void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ }
+
+ void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ }
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override
+ {
+ }
+
+ void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override
+ {
+ }
+
+ void DecryptingComplete() override;
+
+ static void Message(const std::string& aMessage);
+
+ void ProcessRecordNames(GMPRecordIterator* aRecordIterator,
+ GMPErr aStatus);
+
+ static void SetNodeId(const char* aNodeId, uint32_t aLength) {
+ sNodeId = std::string(aNodeId, aNodeId + aLength);
+ }
+
+private:
+
+ virtual ~FakeDecryptor() {}
+ static FakeDecryptor* sInstance;
+ static std::string sNodeId;
+
+ void TestStorage();
+
+ GMPDecryptorCallback* mCallback;
+ GMPDecryptorHost* mHost;
+};
+
+class TestAsyncShutdown : public GMPAsyncShutdown {
+public:
+ explicit TestAsyncShutdown(GMPAsyncShutdownHost* aHost)
+ : mHost(aHost)
+ {
+ }
+ void BeginShutdown() override;
+private:
+ GMPAsyncShutdownHost* mHost;
+};
+
+#endif
diff --git a/dom/media/gmp-plugin/gmp-test-output-protection.h b/dom/media/gmp-plugin/gmp-test-output-protection.h
new file mode 100644
index 000000000..93adf299c
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-output-protection.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#if defined(XP_WIN)
+#include <d3d9.h> // needed to prevent re-definition of enums
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include <windows.h>
+
+#include "opmapi.h"
+#endif
+
+namespace mozilla {
+namespace gmptest {
+
+#if defined(XP_WIN)
+typedef HRESULT(STDAPICALLTYPE * OPMGetVideoOutputsFromHMONITORProc)
+ (HMONITOR, OPM_VIDEO_OUTPUT_SEMANTICS, ULONG*, IOPMVideoOutput***);
+
+static OPMGetVideoOutputsFromHMONITORProc sOPMGetVideoOutputsFromHMONITORProc = nullptr;
+
+static BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc,
+ LPRECT lprc, LPARAM pData)
+{
+ std::vector<std::string>* failureMsgs = (std::vector<std::string>*)pData;
+
+ MONITORINFOEXA miex;
+ ZeroMemory(&miex, sizeof(miex));
+ miex.cbSize = sizeof(miex);
+ if (!GetMonitorInfoA(hMonitor, &miex)) {
+ failureMsgs->push_back("FAIL GetMonitorInfoA call failed");
+ }
+
+ ULONG numVideoOutputs = 0;
+ IOPMVideoOutput** opmVideoOutputArray = nullptr;
+ HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(hMonitor,
+ OPM_VOS_OPM_SEMANTICS,
+ &numVideoOutputs,
+ &opmVideoOutputArray);
+ if (S_OK != hr) {
+ if (0x8007001f != hr && 0x80070032 != hr && 0xc02625e5 != hr) {
+ char msg[100];
+ sprintf(msg, "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08x", hr);
+ failureMsgs->push_back(msg);
+ }
+ return true;
+ }
+
+ DISPLAY_DEVICEA dd;
+ ZeroMemory(&dd, sizeof(dd));
+ dd.cb = sizeof(dd);
+ if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) {
+ failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed");
+ }
+
+ for (ULONG i = 0; i < numVideoOutputs; ++i) {
+ OPM_RANDOM_NUMBER opmRandomNumber;
+ BYTE* certificate = nullptr;
+ ULONG certificateLength = 0;
+ hr = opmVideoOutputArray[i]->StartInitialization(&opmRandomNumber,
+ &certificate,
+ &certificateLength);
+ if (S_OK != hr) {
+ char msg[100];
+ sprintf(msg, "FAIL StartInitialization call failed: HRESULT=0x%08x", hr);
+ failureMsgs->push_back(msg);
+ }
+
+ if (certificate) {
+ CoTaskMemFree(certificate);
+ }
+
+ opmVideoOutputArray[i]->Release();
+ }
+
+ if (opmVideoOutputArray) {
+ CoTaskMemFree(opmVideoOutputArray);
+ }
+
+ return true;
+}
+#endif
+
+static void
+RunOutputProtectionAPITests()
+{
+#if defined(XP_WIN)
+ // Get hold of OPMGetVideoOutputsFromHMONITOR function.
+ HMODULE hDvax2DLL = GetModuleHandleW(L"dxva2.dll");
+ if (!hDvax2DLL) {
+ FakeDecryptor::Message("FAIL GetModuleHandleW call failed for dxva2.dll");
+ return;
+ }
+
+ sOPMGetVideoOutputsFromHMONITORProc = (OPMGetVideoOutputsFromHMONITORProc)
+ GetProcAddress(hDvax2DLL, "OPMGetVideoOutputsFromHMONITOR");
+ if (!sOPMGetVideoOutputsFromHMONITORProc) {
+ FakeDecryptor::Message("FAIL GetProcAddress call failed for OPMGetVideoOutputsFromHMONITOR");
+ return;
+ }
+
+ // Test EnumDisplayMonitors.
+ // Other APIs are tested in the callback function.
+ std::vector<std::string> failureMsgs;
+ if (!EnumDisplayMonitors(NULL, NULL, EnumDisplayMonitorsCallback,
+ (LPARAM) &failureMsgs)) {
+ FakeDecryptor::Message("FAIL EnumDisplayMonitors call failed");
+ }
+
+ // Report any failures in the callback function.
+ for (size_t i = 0; i < failureMsgs.size(); i++) {
+ FakeDecryptor::Message(failureMsgs[i]);
+ }
+#endif
+}
+
+static void
+TestOuputProtectionAPIs()
+{
+ RunOutputProtectionAPITests();
+ FakeDecryptor::Message("OP tests completed");
+ return;
+}
+
+} // namespace gmptest
+} // namespace mozilla
diff --git a/dom/media/gmp-plugin/gmp-test-storage.cpp b/dom/media/gmp-plugin/gmp-test-storage.cpp
new file mode 100644
index 000000000..e75b49f42
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-storage.cpp
@@ -0,0 +1,233 @@
+/* -*- 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 "gmp-test-storage.h"
+#include <vector>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+class WriteRecordClient : public GMPRecordClient {
+public:
+ GMPErr Init(GMPRecord* aRecord,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure,
+ const uint8_t* aData,
+ uint32_t aDataSize) {
+ mRecord = aRecord;
+ mOnSuccess = aOnSuccess;
+ mOnFailure = aOnFailure;
+ mData.insert(mData.end(), aData, aData + aDataSize);
+ return mRecord->Open();
+ }
+
+ void OpenComplete(GMPErr aStatus) override {
+ if (GMP_SUCCEEDED(aStatus)) {
+ mRecord->Write(mData.size() ? &mData.front() : nullptr, mData.size());
+ } else {
+ GMPRunOnMainThread(mOnFailure);
+ mOnSuccess->Destroy();
+ }
+ }
+
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {}
+
+ void WriteComplete(GMPErr aStatus) override {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ mRecord->Close();
+ if (GMP_SUCCEEDED(aStatus)) {
+ GMPRunOnMainThread(mOnSuccess);
+ mOnFailure->Destroy();
+ } else {
+ GMPRunOnMainThread(mOnFailure);
+ mOnSuccess->Destroy();
+ }
+ delete this;
+ }
+
+private:
+ GMPRecord* mRecord;
+ GMPTask* mOnSuccess;
+ GMPTask* mOnFailure;
+ std::vector<uint8_t> mData;
+};
+
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const uint8_t* aData,
+ uint32_t aNumBytes,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure)
+{
+ GMPRecord* record;
+ WriteRecordClient* client = new WriteRecordClient();
+ auto err = GMPOpenRecord(aRecordName.c_str(),
+ aRecordName.size(),
+ &record,
+ client);
+ if (GMP_FAILED(err)) {
+ GMPRunOnMainThread(aOnFailure);
+ aOnSuccess->Destroy();
+ return err;
+ }
+ return client->Init(record, aOnSuccess, aOnFailure, aData, aNumBytes);
+}
+
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const std::string& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure)
+{
+ return WriteRecord(aRecordName,
+ (const uint8_t*)aData.c_str(),
+ aData.size(),
+ aOnSuccess,
+ aOnFailure);
+}
+
+class ReadRecordClient : public GMPRecordClient {
+public:
+ GMPErr Init(GMPRecord* aRecord,
+ ReadContinuation* aContinuation) {
+ mRecord = aRecord;
+ mContinuation = aContinuation;
+ return mRecord->Open();
+ }
+
+ void OpenComplete(GMPErr aStatus) override {
+ auto err = mRecord->Read();
+ if (GMP_FAILED(err)) {
+ mContinuation->ReadComplete(err, "");
+ delete this;
+ }
+ }
+
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ mRecord->Close();
+ std::string data((const char*)aData, aDataSize);
+ mContinuation->ReadComplete(GMPNoErr, data);
+ delete this;
+ }
+
+ void WriteComplete(GMPErr aStatus) override {
+ }
+
+private:
+ GMPRecord* mRecord;
+ ReadContinuation* mContinuation;
+};
+
+GMPErr
+ReadRecord(const std::string& aRecordName,
+ ReadContinuation* aContinuation)
+{
+ MOZ_ASSERT(aContinuation);
+ GMPRecord* record;
+ ReadRecordClient* client = new ReadRecordClient();
+ auto err = GMPOpenRecord(aRecordName.c_str(),
+ aRecordName.size(),
+ &record,
+ client);
+ if (GMP_FAILED(err)) {
+ return err;
+ }
+ return client->Init(record, aContinuation);
+}
+
+extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp
+
+GMPErr
+GMPOpenRecord(const char* aName,
+ uint32_t aNameLength,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ MOZ_ASSERT(g_platform_api);
+ return g_platform_api->createrecord(aName, aNameLength, aOutRecord, aClient);
+}
+
+GMPErr
+GMPRunOnMainThread(GMPTask* aTask)
+{
+ MOZ_ASSERT(g_platform_api);
+ return g_platform_api->runonmainthread(aTask);
+}
+
+class OpenRecordClient : public GMPRecordClient {
+public:
+ /*
+ * This function will take the memory ownership of the parameters and
+ * delete them when done.
+ */
+ static void Open(const std::string& aRecordName,
+ OpenContinuation* aContinuation) {
+ MOZ_ASSERT(aContinuation);
+ (new OpenRecordClient(aContinuation))->Do(aRecordName);
+ }
+
+ void OpenComplete(GMPErr aStatus) override {
+ Done(aStatus);
+ }
+
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override {
+ MOZ_CRASH("Should not reach here.");
+ }
+
+ void WriteComplete(GMPErr aStatus) override {
+ MOZ_CRASH("Should not reach here.");
+ }
+
+private:
+ explicit OpenRecordClient(OpenContinuation* aContinuation)
+ : mRecord(nullptr), mContinuation(aContinuation) {}
+
+ void Do(const std::string& aName) {
+ auto err = GMPOpenRecord(aName.c_str(), aName.size(), &mRecord, this);
+ if (GMP_FAILED(err) ||
+ GMP_FAILED(err = mRecord->Open())) {
+ Done(err);
+ }
+ }
+
+ void Done(GMPErr err) {
+ // mContinuation is responsible for closing mRecord.
+ mContinuation->OpenComplete(err, mRecord);
+ delete mContinuation;
+ delete this;
+ }
+
+ GMPRecord* mRecord;
+ OpenContinuation* mContinuation;
+};
+
+void
+GMPOpenRecord(const std::string& aRecordName,
+ OpenContinuation* aContinuation)
+{
+ OpenRecordClient::Open(aRecordName, aContinuation);
+}
+
+GMPErr
+GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg)
+{
+ return g_platform_api->getrecordenumerator(aRecvIteratorFunc, aUserArg);
+}
diff --git a/dom/media/gmp-plugin/gmp-test-storage.h b/dom/media/gmp-plugin/gmp-test-storage.h
new file mode 100644
index 000000000..d77f8ebb3
--- /dev/null
+++ b/dom/media/gmp-plugin/gmp-test-storage.h
@@ -0,0 +1,63 @@
+/* -*- 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 TEST_GMP_STORAGE_H__
+#define TEST_GMP_STORAGE_H__
+
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+#include <string>
+
+class ReadContinuation {
+public:
+ virtual ~ReadContinuation() {}
+ virtual void ReadComplete(GMPErr aErr, const std::string& aData) = 0;
+};
+
+// Reads a record to storage using GMPRecord.
+// Calls ReadContinuation with read data.
+GMPErr
+ReadRecord(const std::string& aRecordName,
+ ReadContinuation* aContinuation);
+
+// Writes a record to storage using GMPRecord.
+// Runs continuation when data is written.
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const std::string& aData,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure);
+
+GMPErr
+WriteRecord(const std::string& aRecordName,
+ const uint8_t* aData,
+ uint32_t aNumBytes,
+ GMPTask* aOnSuccess,
+ GMPTask* aOnFailure);
+
+GMPErr
+GMPOpenRecord(const char* aName,
+ uint32_t aNameLength,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+GMPErr
+GMPRunOnMainThread(GMPTask* aTask);
+
+class OpenContinuation {
+public:
+ virtual ~OpenContinuation() {}
+ virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) = 0;
+};
+
+void
+GMPOpenRecord(const std::string& aRecordName,
+ OpenContinuation* aContinuation);
+
+GMPErr
+GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+#endif // TEST_GMP_STORAGE_H__
diff --git a/dom/media/gmp-plugin/moz.build b/dom/media/gmp-plugin/moz.build
new file mode 100644
index 000000000..39061d0c9
--- /dev/null
+++ b/dom/media/gmp-plugin/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET = 'dist/bin/gmp-fake/1.0'
+
+FINAL_TARGET_FILES += [
+ 'fake.info',
+ 'fake.voucher',
+]
+
+SOURCES += [
+ 'gmp-fake.cpp',
+ 'gmp-test-decryptor.cpp',
+ 'gmp-test-storage.cpp',
+]
+
+DEFINES['GMP_FAKE_SUPPORT_DECRYPT'] = True
+
+SharedLibrary("fake")
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'ole32',
+ ]
+
+USE_STATIC_LIBS = True
+NO_VISIBILITY_FLAGS = True
+# Don't use STL wrappers; this isn't Gecko code
+DISABLE_STL_WRAPPING = True
diff --git a/dom/media/gmp/GMPAudioDecoderChild.cpp b/dom/media/gmp/GMPAudioDecoderChild.cpp
new file mode 100644
index 000000000..53550d4a1
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderChild.cpp
@@ -0,0 +1,172 @@
+/* -*- 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 "GMPAudioDecoderChild.h"
+#include "GMPContentChild.h"
+#include "GMPAudioHost.h"
+#include "mozilla/Unused.h"
+#include <stdio.h>
+
+namespace mozilla {
+namespace gmp {
+
+GMPAudioDecoderChild::GMPAudioDecoderChild(GMPContentChild* aPlugin)
+ : mPlugin(aPlugin)
+ , mAudioDecoder(nullptr)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPAudioDecoderChild::~GMPAudioDecoderChild()
+{
+}
+
+void
+GMPAudioDecoderChild::Init(GMPAudioDecoder* aDecoder)
+{
+ MOZ_ASSERT(aDecoder, "Cannot initialize Audio decoder child without a Audio decoder!");
+ mAudioDecoder = aDecoder;
+}
+
+GMPAudioHostImpl&
+GMPAudioDecoderChild::Host()
+{
+ return mAudioHost;
+}
+
+void
+GMPAudioDecoderChild::Decoded(GMPAudioSamples* aDecodedSamples)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (!aDecodedSamples) {
+ MOZ_CRASH("Not given decoded audio samples!");
+ }
+
+ GMPAudioDecodedSampleData samples;
+ samples.mData().AppendElements((int16_t*)aDecodedSamples->Buffer(),
+ aDecodedSamples->Size() / sizeof(int16_t));
+ samples.mTimeStamp() = aDecodedSamples->TimeStamp();
+ samples.mChannelCount() = aDecodedSamples->Channels();
+ samples.mSamplesPerSecond() = aDecodedSamples->Rate();
+
+ Unused << SendDecoded(samples);
+
+ aDecodedSamples->Destroy();
+}
+
+void
+GMPAudioDecoderChild::InputDataExhausted()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendInputDataExhausted();
+}
+
+void
+GMPAudioDecoderChild::DrainComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendDrainComplete();
+}
+
+void
+GMPAudioDecoderChild::ResetComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendResetComplete();
+}
+
+void
+GMPAudioDecoderChild::Error(GMPErr aError)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ Unused << SendError(aError);
+}
+
+bool
+GMPAudioDecoderChild::RecvInitDecode(const GMPAudioCodecData& a)
+{
+ MOZ_ASSERT(mAudioDecoder);
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ GMPAudioCodec codec;
+ codec.mCodecType = a.mCodecType();
+ codec.mChannelCount = a.mChannelCount();
+ codec.mBitsPerChannel = a.mBitsPerChannel();
+ codec.mSamplesPerSecond = a.mSamplesPerSecond();
+ codec.mExtraData = a.mExtraData().Elements();
+ codec.mExtraDataLen = a.mExtraData().Length();
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->InitDecode(codec, this);
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvDecode(const GMPAudioEncodedSampleData& aEncodedSamples)
+{
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ GMPAudioSamples* samples = new GMPAudioSamplesImpl(aEncodedSamples);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->Decode(samples);
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvReset()
+{
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->Reset();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvDrain()
+{
+ if (!mAudioDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->Drain();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderChild::RecvDecodingComplete()
+{
+ if (mAudioDecoder) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mAudioDecoder->DecodingComplete();
+ mAudioDecoder = nullptr;
+ }
+
+ mPlugin = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPAudioDecoderChild.h b/dom/media/gmp/GMPAudioDecoderChild.h
new file mode 100644
index 000000000..0393fa573
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderChild.h
@@ -0,0 +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/. */
+
+#ifndef GMPAudioDecoderChild_h_
+#define GMPAudioDecoderChild_h_
+
+#include "mozilla/gmp/PGMPAudioDecoderChild.h"
+#include "gmp-audio-decode.h"
+#include "GMPAudioHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPAudioDecoderChild : public PGMPAudioDecoderChild,
+ public GMPAudioDecoderCallback
+{
+public:
+ explicit GMPAudioDecoderChild(GMPContentChild* aPlugin);
+ virtual ~GMPAudioDecoderChild();
+
+ void Init(GMPAudioDecoder* aDecoder);
+ GMPAudioHostImpl& Host();
+
+ // GMPAudioDecoderCallback
+ void Decoded(GMPAudioSamples* aEncodedSamples) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aError) override;
+
+private:
+ // PGMPAudioDecoderChild
+ bool RecvInitDecode(const GMPAudioCodecData& codecSettings) override;
+ bool RecvDecode(const GMPAudioEncodedSampleData& input) override;
+ bool RecvReset() override;
+ bool RecvDrain() override;
+ bool RecvDecodingComplete() override;
+
+ GMPContentChild* mPlugin;
+ GMPAudioDecoder* mAudioDecoder;
+ GMPAudioHostImpl mAudioHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPAudioDecoderChild_h_
diff --git a/dom/media/gmp/GMPAudioDecoderParent.cpp b/dom/media/gmp/GMPAudioDecoderParent.cpp
new file mode 100644
index 000000000..592c7719b
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderParent.cpp
@@ -0,0 +1,369 @@
+/* -*- 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 "GMPAudioDecoderParent.h"
+#include "GMPContentParent.h"
+#include <stdio.h>
+#include "mozilla/Unused.h"
+#include "GMPMessageUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+GMPAudioDecoderParent::GMPAudioDecoderParent(GMPContentParent* aPlugin)
+ : mIsOpen(false)
+ , mShuttingDown(false)
+ , mActorDestroyed(false)
+ , mIsAwaitingResetComplete(false)
+ , mIsAwaitingDrainComplete(false)
+ , mPlugin(aPlugin)
+ , mCallback(nullptr)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPAudioDecoderParent::~GMPAudioDecoderParent()
+{
+}
+
+nsresult
+GMPAudioDecoderParent::InitDecode(GMPAudioCodecType aCodecType,
+ uint32_t aChannelCount,
+ uint32_t aBitsPerChannel,
+ uint32_t aSamplesPerSecond,
+ nsTArray<uint8_t>& aExtraData,
+ GMPAudioDecoderCallbackProxy* aCallback)
+{
+ LOGD(("GMPAudioDecoderParent[%p]::InitDecode()", this));
+
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-init an in-use GMP audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aCallback;
+
+ GMPAudioCodecData data;
+ data.mCodecType() = aCodecType;
+ data.mChannelCount() = aChannelCount;
+ data.mBitsPerChannel() = aBitsPerChannel;
+ data.mSamplesPerSecond() = aSamplesPerSecond;
+ data.mExtraData() = aExtraData;
+ if (!SendInitDecode(data)) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsOpen = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPAudioDecoderParent::Decode(GMPAudioSamplesImpl& aEncodedSamples)
+{
+ LOGV(("GMPAudioDecoderParent[%p]::Decode() timestamp=%lld",
+ this, aEncodedSamples.TimeStamp()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP Audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ GMPAudioEncodedSampleData samples;
+ aEncodedSamples.RelinquishData(samples);
+
+ if (!SendDecode(samples)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPAudioDecoderParent::Reset()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Reset()", this));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP Audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendReset()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingResetComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPAudioDecoderParent::Drain()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Drain()", this));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP Audio decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendDrain()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingDrainComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult
+GMPAudioDecoderParent::Close()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Close()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ // Ensure if we've received a Close while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the close. This seems unlikely to happen, but better to be careful.
+ UnblockResetAndDrain();
+
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPAudioDecoderParent> kungfudeathgrip(this);
+ Release();
+ Shutdown();
+
+ return NS_OK;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult
+GMPAudioDecoderParent::Shutdown()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Shutdown()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+ mShuttingDown = true;
+
+ // Ensure if we've received a shutdown while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the shutdown.
+ UnblockResetAndDrain();
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendDecodingComplete();
+ }
+
+ return NS_OK;
+}
+
+// Note: Keep this sync'd up with DecodingComplete
+void
+GMPAudioDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPAudioDecoderParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+
+ mIsOpen = false;
+ mActorDestroyed = true;
+
+ // Ensure if we've received a destroy while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->AudioDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+bool
+GMPAudioDecoderParent::RecvDecoded(const GMPAudioDecodedSampleData& aDecoded)
+{
+ LOGV(("GMPAudioDecoderParent[%p]::RecvDecoded() timestamp=%lld",
+ this, aDecoded.mTimeStamp()));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ mCallback->Decoded(aDecoded.mData(),
+ aDecoded.mTimeStamp(),
+ aDecoded.mChannelCount(),
+ aDecoded.mSamplesPerSecond());
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvInputDataExhausted()
+{
+ LOGV(("GMPAudioDecoderParent[%p]::RecvInputDataExhausted()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->InputDataExhausted();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvDrainComplete()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvDrainComplete()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingDrainComplete) {
+ return true;
+ }
+ mIsAwaitingDrainComplete = false;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->DrainComplete();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvResetComplete()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvResetComplete()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingResetComplete) {
+ return true;
+ }
+ mIsAwaitingResetComplete = false;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ResetComplete();
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvError(const GMPErr& aError)
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvError(error=%d)", this, aError));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ensure if we've received an error while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Error(aError);
+
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::RecvShutdown()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::RecvShutdown()", this));
+
+ Shutdown();
+ return true;
+}
+
+bool
+GMPAudioDecoderParent::Recv__delete__()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::Recv__delete__()", this));
+
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->AudioDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+
+ return true;
+}
+
+void
+GMPAudioDecoderParent::UnblockResetAndDrain()
+{
+ LOGD(("GMPAudioDecoderParent[%p]::UnblockResetAndDrain()", this));
+
+ if (!mCallback) {
+ MOZ_ASSERT(!mIsAwaitingResetComplete);
+ MOZ_ASSERT(!mIsAwaitingDrainComplete);
+ return;
+ }
+ if (mIsAwaitingResetComplete) {
+ mIsAwaitingResetComplete = false;
+ mCallback->ResetComplete();
+ }
+ if (mIsAwaitingDrainComplete) {
+ mIsAwaitingDrainComplete = false;
+ mCallback->DrainComplete();
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPAudioDecoderParent.h b/dom/media/gmp/GMPAudioDecoderParent.h
new file mode 100644
index 000000000..5ced5ca61
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderParent.h
@@ -0,0 +1,72 @@
+/* -*- 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 GMPAudioDecoderParent_h_
+#define GMPAudioDecoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-audio-decode.h"
+#include "gmp-audio-codec.h"
+#include "mozilla/gmp/PGMPAudioDecoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPAudioDecoderProxy.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPAudioDecoderParent final : public GMPAudioDecoderProxy
+ , public PGMPAudioDecoderParent
+ , public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPAudioDecoderParent)
+
+ explicit GMPAudioDecoderParent(GMPContentParent *aPlugin);
+
+ nsresult Shutdown();
+
+ // GMPAudioDecoderProxy
+ nsresult InitDecode(GMPAudioCodecType aCodecType,
+ uint32_t aChannelCount,
+ uint32_t aBitsPerChannel,
+ uint32_t aSamplesPerSecond,
+ nsTArray<uint8_t>& aExtraData,
+ GMPAudioDecoderCallbackProxy* aCallback) override;
+ nsresult Decode(GMPAudioSamplesImpl& aInput) override;
+ nsresult Reset() override;
+ nsresult Drain() override;
+ nsresult Close() override;
+
+private:
+ ~GMPAudioDecoderParent();
+
+ // PGMPAudioDecoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvDecoded(const GMPAudioDecodedSampleData& aDecoded) override;
+ bool RecvInputDataExhausted() override;
+ bool RecvDrainComplete() override;
+ bool RecvResetComplete() override;
+ bool RecvError(const GMPErr& aError) override;
+ bool RecvShutdown() override;
+ bool Recv__delete__() override;
+
+ void UnblockResetAndDrain();
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ bool mIsAwaitingResetComplete;
+ bool mIsAwaitingDrainComplete;
+ RefPtr<GMPContentParent> mPlugin;
+ GMPAudioDecoderCallbackProxy* mCallback;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPAudioDecoderParent_h_
diff --git a/dom/media/gmp/GMPAudioDecoderProxy.h b/dom/media/gmp/GMPAudioDecoderProxy.h
new file mode 100644
index 000000000..6d71ba089
--- /dev/null
+++ b/dom/media/gmp/GMPAudioDecoderProxy.h
@@ -0,0 +1,48 @@
+/* -*- 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 GMPAudioDecoderProxy_h_
+#define GMPAudioDecoderProxy_h_
+
+#include "GMPCallbackBase.h"
+#include "gmp-audio-codec.h"
+#include "GMPAudioHost.h"
+#include "nsTArray.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+class GMPAudioDecoderCallbackProxy : public GMPCallbackBase {
+public:
+ virtual ~GMPAudioDecoderCallbackProxy() {}
+ // Note: aChannelCount and aSamplesPerSecond may not be consistent from
+ // one invocation to the next.
+ virtual void Decoded(const nsTArray<int16_t>& aPCM,
+ uint64_t aTimeStamp,
+ uint32_t aChannelCount,
+ uint32_t aSamplesPerSecond) = 0;
+ virtual void InputDataExhausted() = 0;
+ virtual void DrainComplete() = 0;
+ virtual void ResetComplete() = 0;
+ virtual void Error(GMPErr aError) = 0;
+};
+
+class GMPAudioDecoderProxy {
+public:
+ virtual ~GMPAudioDecoderProxy() {}
+
+ virtual nsresult InitDecode(GMPAudioCodecType aCodecType,
+ uint32_t aChannelCount,
+ uint32_t aBitsPerChannel,
+ uint32_t aSamplesPerSecond,
+ nsTArray<uint8_t>& aExtraData,
+ GMPAudioDecoderCallbackProxy* aCallback) = 0;
+ virtual nsresult Decode(mozilla::gmp::GMPAudioSamplesImpl& aSamples) = 0;
+ virtual nsresult Reset() = 0;
+ virtual nsresult Drain() = 0;
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual nsresult Close() = 0;
+};
+
+#endif // GMPAudioDecoderProxy_h_
diff --git a/dom/media/gmp/GMPAudioHost.cpp b/dom/media/gmp/GMPAudioHost.cpp
new file mode 100644
index 000000000..4e14fed0b
--- /dev/null
+++ b/dom/media/gmp/GMPAudioHost.cpp
@@ -0,0 +1,162 @@
+/* -*- 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 "GMPAudioHost.h"
+#include "gmp-audio-samples.h"
+#include "gmp-errors.h"
+#include "GMPEncryptedBufferDataImpl.h"
+#include "MediaData.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPAudioSamplesImpl::GMPAudioSamplesImpl(GMPAudioFormat aFormat)
+ : mFormat(aFormat)
+ , mTimeStamp(0)
+ , mChannels(0)
+ , mRate(0)
+{
+}
+
+GMPAudioSamplesImpl::GMPAudioSamplesImpl(const GMPAudioEncodedSampleData& aData)
+ : mFormat(kGMPAudioEncodedSamples)
+ , mBuffer(aData.mData())
+ , mTimeStamp(aData.mTimeStamp())
+ , mChannels(aData.mChannelCount())
+ , mRate(aData.mSamplesPerSecond())
+{
+ if (aData.mDecryptionData().mKeyId().Length() > 0) {
+ mCrypto = new GMPEncryptedBufferDataImpl(aData.mDecryptionData());
+ }
+}
+
+GMPAudioSamplesImpl::GMPAudioSamplesImpl(MediaRawData* aSample,
+ uint32_t aChannels,
+ uint32_t aRate)
+ : mFormat(kGMPAudioEncodedSamples)
+ , mTimeStamp(aSample->mTime)
+ , mChannels(aChannels)
+ , mRate(aRate)
+{
+ mBuffer.AppendElements(aSample->Data(), aSample->Size());
+ if (aSample->mCrypto.mValid) {
+ mCrypto = new GMPEncryptedBufferDataImpl(aSample->mCrypto);
+ }
+}
+
+GMPAudioSamplesImpl::~GMPAudioSamplesImpl()
+{
+}
+
+GMPAudioFormat
+GMPAudioSamplesImpl::GetFormat()
+{
+ return mFormat;
+}
+
+void
+GMPAudioSamplesImpl::Destroy()
+{
+ delete this;
+}
+
+GMPErr
+GMPAudioSamplesImpl::SetBufferSize(uint32_t aSize)
+{
+ mBuffer.SetLength(aSize);
+ return GMPNoErr;
+}
+
+uint32_t
+GMPAudioSamplesImpl::Size()
+{
+ return mBuffer.Length();
+}
+
+void
+GMPAudioSamplesImpl::SetTimeStamp(uint64_t aTimeStamp)
+{
+ mTimeStamp = aTimeStamp;
+}
+
+uint64_t
+GMPAudioSamplesImpl::TimeStamp()
+{
+ return mTimeStamp;
+}
+
+const uint8_t*
+GMPAudioSamplesImpl::Buffer() const
+{
+ return mBuffer.Elements();
+}
+
+uint8_t*
+GMPAudioSamplesImpl::Buffer()
+{
+ return mBuffer.Elements();
+}
+
+const GMPEncryptedBufferMetadata*
+GMPAudioSamplesImpl::GetDecryptionData() const
+{
+ return mCrypto;
+}
+
+void
+GMPAudioSamplesImpl::InitCrypto(const CryptoSample& aCrypto)
+{
+ if (!aCrypto.mValid) {
+ return;
+ }
+ mCrypto = new GMPEncryptedBufferDataImpl(aCrypto);
+}
+
+void
+GMPAudioSamplesImpl::RelinquishData(GMPAudioEncodedSampleData& aData)
+{
+ aData.mData() = Move(mBuffer);
+ aData.mTimeStamp() = TimeStamp();
+ if (mCrypto) {
+ mCrypto->RelinquishData(aData.mDecryptionData());
+ }
+}
+
+uint32_t
+GMPAudioSamplesImpl::Channels() const
+{
+ return mChannels;
+}
+
+void
+GMPAudioSamplesImpl::SetChannels(uint32_t aChannels)
+{
+ mChannels = aChannels;
+}
+
+uint32_t
+GMPAudioSamplesImpl::Rate() const
+{
+ return mRate;
+}
+
+void
+GMPAudioSamplesImpl::SetRate(uint32_t aRate)
+{
+ mRate = aRate;
+}
+
+
+GMPErr
+GMPAudioHostImpl::CreateSamples(GMPAudioFormat aFormat,
+ GMPAudioSamples** aSamples)
+{
+
+ *aSamples = new GMPAudioSamplesImpl(aFormat);
+ return GMPNoErr;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPAudioHost.h b/dom/media/gmp/GMPAudioHost.h
new file mode 100644
index 000000000..ed829b9c5
--- /dev/null
+++ b/dom/media/gmp/GMPAudioHost.h
@@ -0,0 +1,71 @@
+/* -*- 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 GMPAudioHost_h_
+#define GMPAudioHost_h_
+
+#include "gmp-audio-host.h"
+#include "gmp-audio-samples.h"
+#include "nsTArray.h"
+#include "gmp-decryption.h"
+#include "nsAutoPtr.h"
+#include "GMPEncryptedBufferDataImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+class CryptoSample;
+class MediaRawData;
+
+namespace gmp {
+
+class GMPAudioSamplesImpl : public GMPAudioSamples {
+public:
+ explicit GMPAudioSamplesImpl(GMPAudioFormat aFormat);
+ explicit GMPAudioSamplesImpl(const GMPAudioEncodedSampleData& aData);
+ GMPAudioSamplesImpl(MediaRawData* aSample,
+ uint32_t aChannels,
+ uint32_t aRate);
+ virtual ~GMPAudioSamplesImpl();
+
+ GMPAudioFormat GetFormat() override;
+ void Destroy() override;
+ GMPErr SetBufferSize(uint32_t aSize) override;
+ uint32_t Size() override;
+ void SetTimeStamp(uint64_t aTimeStamp) override;
+ uint64_t TimeStamp() override;
+ const uint8_t* Buffer() const override;
+ uint8_t* Buffer() override;
+ const GMPEncryptedBufferMetadata* GetDecryptionData() const override;
+
+ void InitCrypto(const CryptoSample& aCrypto);
+
+ void RelinquishData(GMPAudioEncodedSampleData& aData);
+
+ uint32_t Channels() const override;
+ void SetChannels(uint32_t aChannels) override;
+ uint32_t Rate() const override;
+ void SetRate(uint32_t aRate) override;
+
+private:
+ GMPAudioFormat mFormat;
+ nsTArray<uint8_t> mBuffer;
+ int64_t mTimeStamp;
+ nsAutoPtr<GMPEncryptedBufferDataImpl> mCrypto;
+ uint32_t mChannels;
+ uint32_t mRate;
+};
+
+class GMPAudioHostImpl : public GMPAudioHost
+{
+public:
+ GMPErr CreateSamples(GMPAudioFormat aFormat,
+ GMPAudioSamples** aSamples) override;
+private:
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPAudioHost_h_
diff --git a/dom/media/gmp/GMPCallbackBase.h b/dom/media/gmp/GMPCallbackBase.h
new file mode 100644
index 000000000..3d96629ef
--- /dev/null
+++ b/dom/media/gmp/GMPCallbackBase.h
@@ -0,0 +1,21 @@
+/* -*- 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 GMPCallbackBase_h_
+#define GMPCallbackBase_h_
+
+class GMPCallbackBase
+{
+public:
+ virtual ~GMPCallbackBase() {}
+
+ // The GMP code will call this if the codec crashes or shuts down. It's
+ // expected that the consumer (destination of this callback) will respond
+ // by dropping their reference to the proxy, allowing the proxy/parent to
+ // be destroyed.
+ virtual void Terminated() = 0;
+};
+
+#endif
diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp
new file mode 100644
index 000000000..7941183b5
--- /dev/null
+++ b/dom/media/gmp/GMPChild.cpp
@@ -0,0 +1,491 @@
+/* -*- 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 "GMPChild.h"
+#include "GMPContentChild.h"
+#include "GMPProcessChild.h"
+#include "GMPLoader.h"
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "GMPAudioDecoderChild.h"
+#include "GMPDecryptorChild.h"
+#include "GMPVideoHost.h"
+#include "nsDebugImpl.h"
+#include "nsIFile.h"
+#include "nsXULAppAPI.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-encode.h"
+#include "GMPPlatform.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPUtils.h"
+#include "prio.h"
+#include "base/task.h"
+
+using namespace mozilla::ipc;
+
+static const int MAX_VOUCHER_LENGTH = 500000;
+
+#ifdef XP_WIN
+#include <stdlib.h> // for _exit()
+#else
+#include <unistd.h> // for _exit()
+#endif
+
+namespace mozilla {
+
+#undef LOG
+#undef LOGD
+
+extern LogModule* GetGMPLog();
+#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
+#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPChild[pid=%d] " x, (int)base::GetCurrentProcId(), ##__VA_ARGS__)
+
+namespace gmp {
+
+GMPChild::GMPChild()
+ : mAsyncShutdown(nullptr)
+ , mGMPMessageLoop(MessageLoop::current())
+ , mGMPLoader(nullptr)
+{
+ LOGD("GMPChild ctor");
+ nsDebugImpl::SetMultiprocessMode("GMP");
+}
+
+GMPChild::~GMPChild()
+{
+ LOGD("GMPChild dtor");
+}
+
+static bool
+GetFileBase(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibDirectory,
+ nsCOMPtr<nsIFile>& aFileBase,
+ nsAutoString& aBaseName)
+{
+ nsresult rv = NS_NewLocalFile(aPluginPath,
+ true, getter_AddRefs(aFileBase));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (NS_FAILED(aFileBase->Clone(getter_AddRefs(aLibDirectory)))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> parent;
+ rv = aFileBase->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoString parentLeafName;
+ rv = parent->GetLeafName(parentLeafName);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ aBaseName = Substring(parentLeafName,
+ 4,
+ parentLeafName.Length() - 1);
+ return true;
+}
+
+static bool
+GetFileBase(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aFileBase,
+ nsAutoString& aBaseName)
+{
+ nsCOMPtr<nsIFile> unusedLibDir;
+ return GetFileBase(aPluginPath, unusedLibDir, aFileBase, aBaseName);
+}
+
+static bool
+GetPluginFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibDirectory,
+ nsCOMPtr<nsIFile>& aLibFile)
+{
+ nsAutoString baseName;
+ GetFileBase(aPluginPath, aLibDirectory, aLibFile, baseName);
+
+#if defined(OS_POSIX)
+ nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
+#elif defined(XP_WIN)
+ nsAutoString binaryName = baseName + NS_LITERAL_STRING(".dll");
+#else
+#error Unsupported O.S.
+#endif
+ aLibFile->AppendRelativePath(binaryName);
+ return true;
+}
+
+static bool
+GetPluginFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibFile)
+{
+ nsCOMPtr<nsIFile> unusedlibDir;
+ return GetPluginFile(aPluginPath, unusedlibDir, aLibFile);
+}
+
+bool
+GMPChild::Init(const nsAString& aPluginPath,
+ const nsAString& aVoucherPath,
+ base::ProcessId aParentPid,
+ MessageLoop* aIOLoop,
+ IPC::Channel* aChannel)
+{
+ LOGD("%s pluginPath=%s", __FUNCTION__, NS_ConvertUTF16toUTF8(aPluginPath).get());
+
+ if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) {
+ return false;
+ }
+
+ mPluginPath = aPluginPath;
+ mSandboxVoucherPath = aVoucherPath;
+
+ return true;
+}
+
+bool
+GMPChild::RecvSetNodeId(const nsCString& aNodeId)
+{
+ LOGD("%s nodeId=%s", __FUNCTION__, aNodeId.Data());
+
+ // Store the per origin salt for the node id. Note: we do this in a
+ // separate message than RecvStartPlugin() so that the string is not
+ // sitting in a string on the IPC code's call stack.
+ mNodeId = aNodeId;
+ return true;
+}
+
+GMPErr
+GMPChild::GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ if (!mGMPLoader) {
+ return GMPGenericErr;
+ }
+ return mGMPLoader->GetAPI(aAPIName, aHostAPI, aPluginAPI, aDecryptorId);
+}
+
+bool
+GMPChild::RecvPreloadLibs(const nsCString& aLibs)
+{
+#ifdef XP_WIN
+ // Pre-load DLLs that need to be used by the EME plugin but that can't be
+ // loaded after the sandbox has started
+ // Items in this must be lowercase!
+ static const char* whitelist[] = {
+ "d3d9.dll", // Create an `IDirect3D9` to get adapter information
+ "dxva2.dll", // Get monitor information
+ "evr.dll", // MFGetStrideForBitmapInfoHeader
+ "mfh264dec.dll", // H.264 decoder (on Windows Vista)
+ "mfheaacdec.dll", // AAC decoder (on Windows Vista)
+ "mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer, MFCreateMediaType
+ "msauddecmft.dll", // AAC decoder (on Windows 8)
+ "msmpeg2adec.dll", // AAC decoder (on Windows 7)
+ "msmpeg2vdec.dll", // H.264 decoder
+ };
+
+ nsTArray<nsCString> libs;
+ SplitAt(", ", aLibs, libs);
+ for (nsCString lib : libs) {
+ ToLowerCase(lib);
+ for (const char* whiteListedLib : whitelist) {
+ if (lib.EqualsASCII(whiteListedLib)) {
+ LoadLibraryA(lib.get());
+ break;
+ }
+ }
+ }
+#endif
+ return true;
+}
+
+bool
+GMPChild::GetUTF8LibPath(nsACString& aOutLibPath)
+{
+ nsCOMPtr<nsIFile> libFile;
+ if (!GetPluginFile(mPluginPath, libFile)) {
+ return false;
+ }
+
+ if (!FileExists(libFile)) {
+ NS_WARNING("Can't find GMP library file!");
+ return false;
+ }
+
+ nsAutoString path;
+ libFile->GetPath(path);
+ aOutLibPath = NS_ConvertUTF16toUTF8(path);
+
+ return true;
+}
+
+bool
+GMPChild::AnswerStartPlugin(const nsString& aAdapter)
+{
+ LOGD("%s", __FUNCTION__);
+
+ if (!PreLoadPluginVoucher()) {
+ NS_WARNING("Plugin voucher failed to load!");
+ return false;
+ }
+ PreLoadSandboxVoucher();
+
+ nsCString libPath;
+ if (!GetUTF8LibPath(libPath)) {
+ return false;
+ }
+
+ auto platformAPI = new GMPPlatformAPI();
+ InitPlatformAPI(*platformAPI, this);
+
+ mGMPLoader = GMPProcessChild::GetGMPLoader();
+ if (!mGMPLoader) {
+ NS_WARNING("Failed to get GMPLoader");
+ delete platformAPI;
+ return false;
+ }
+
+ GMPAdapter* adapter = nullptr;
+ if (!mGMPLoader->Load(libPath.get(),
+ libPath.Length(),
+ mNodeId.BeginWriting(),
+ mNodeId.Length(),
+ platformAPI,
+ adapter)) {
+ NS_WARNING("Failed to load GMP");
+ delete platformAPI;
+ return false;
+ }
+
+ void* sh = nullptr;
+ GMPAsyncShutdownHost* host = static_cast<GMPAsyncShutdownHost*>(this);
+ GMPErr err = GetAPI(GMP_API_ASYNC_SHUTDOWN, host, &sh);
+ if (err == GMPNoErr && sh) {
+ mAsyncShutdown = reinterpret_cast<GMPAsyncShutdown*>(sh);
+ SendAsyncShutdownRequired();
+ }
+
+ return true;
+}
+
+MessageLoop*
+GMPChild::GMPMessageLoop()
+{
+ return mGMPMessageLoop;
+}
+
+void
+GMPChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD("%s reason=%d", __FUNCTION__, aWhy);
+
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ MOZ_ASSERT_IF(aWhy == NormalShutdown, !mGMPContentChildren[i - 1]->IsUsed());
+ mGMPContentChildren[i - 1]->Close();
+ }
+
+ if (mGMPLoader) {
+ mGMPLoader->Shutdown();
+ }
+ if (AbnormalShutdown == aWhy) {
+ NS_WARNING("Abnormal shutdown of GMP process!");
+ ProcessChild::QuickExit();
+ }
+
+ XRE_ShutdownChildProcess();
+}
+
+void
+GMPChild::ProcessingError(Result aCode, const char* aReason)
+{
+ switch (aCode) {
+ case MsgDropped:
+ _exit(0); // Don't trigger a crash report.
+ case MsgNotKnown:
+ MOZ_CRASH("aborting because of MsgNotKnown");
+ case MsgNotAllowed:
+ MOZ_CRASH("aborting because of MsgNotAllowed");
+ case MsgPayloadError:
+ MOZ_CRASH("aborting because of MsgPayloadError");
+ case MsgProcessingError:
+ MOZ_CRASH("aborting because of MsgProcessingError");
+ case MsgRouteError:
+ MOZ_CRASH("aborting because of MsgRouteError");
+ case MsgValueError:
+ MOZ_CRASH("aborting because of MsgValueError");
+ default:
+ MOZ_CRASH("not reached");
+ }
+}
+
+PGMPTimerChild*
+GMPChild::AllocPGMPTimerChild()
+{
+ return new GMPTimerChild(this);
+}
+
+bool
+GMPChild::DeallocPGMPTimerChild(PGMPTimerChild* aActor)
+{
+ MOZ_ASSERT(mTimerChild == static_cast<GMPTimerChild*>(aActor));
+ mTimerChild = nullptr;
+ return true;
+}
+
+GMPTimerChild*
+GMPChild::GetGMPTimers()
+{
+ if (!mTimerChild) {
+ PGMPTimerChild* sc = SendPGMPTimerConstructor();
+ if (!sc) {
+ return nullptr;
+ }
+ mTimerChild = static_cast<GMPTimerChild*>(sc);
+ }
+ return mTimerChild;
+}
+
+PGMPStorageChild*
+GMPChild::AllocPGMPStorageChild()
+{
+ return new GMPStorageChild(this);
+}
+
+bool
+GMPChild::DeallocPGMPStorageChild(PGMPStorageChild* aActor)
+{
+ mStorage = nullptr;
+ return true;
+}
+
+GMPStorageChild*
+GMPChild::GetGMPStorage()
+{
+ if (!mStorage) {
+ PGMPStorageChild* sc = SendPGMPStorageConstructor();
+ if (!sc) {
+ return nullptr;
+ }
+ mStorage = static_cast<GMPStorageChild*>(sc);
+ }
+ return mStorage;
+}
+
+bool
+GMPChild::RecvCrashPluginNow()
+{
+ MOZ_CRASH();
+ return true;
+}
+
+bool
+GMPChild::RecvBeginAsyncShutdown()
+{
+ LOGD("%s AsyncShutdown=%d", __FUNCTION__, mAsyncShutdown!=nullptr);
+
+ MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
+ if (mAsyncShutdown) {
+ mAsyncShutdown->BeginShutdown();
+ } else {
+ ShutdownComplete();
+ }
+ return true;
+}
+
+bool
+GMPChild::RecvCloseActive()
+{
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ mGMPContentChildren[i - 1]->CloseActive();
+ }
+ return true;
+}
+
+void
+GMPChild::ShutdownComplete()
+{
+ LOGD("%s", __FUNCTION__);
+ MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
+ mAsyncShutdown = nullptr;
+ SendAsyncShutdownComplete();
+}
+
+static void
+GetPluginVoucherFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aOutVoucherFile)
+{
+ nsAutoString baseName;
+ GetFileBase(aPluginPath, aOutVoucherFile, baseName);
+ nsAutoString infoFileName = baseName + NS_LITERAL_STRING(".voucher");
+ aOutVoucherFile->AppendRelativePath(infoFileName);
+}
+
+bool
+GMPChild::PreLoadPluginVoucher()
+{
+ nsCOMPtr<nsIFile> voucherFile;
+ GetPluginVoucherFile(mPluginPath, voucherFile);
+ if (!FileExists(voucherFile)) {
+ // Assume missing file is not fatal; that would break OpenH264.
+ return true;
+ }
+ return ReadIntoArray(voucherFile, mPluginVoucher, MAX_VOUCHER_LENGTH);
+}
+
+void
+GMPChild::PreLoadSandboxVoucher()
+{
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = NS_NewLocalFile(mSandboxVoucherPath, true, getter_AddRefs(f));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create nsIFile for sandbox voucher");
+ return;
+ }
+ if (!FileExists(f)) {
+ // Assume missing file is not fatal; that would break OpenH264.
+ return;
+ }
+
+ if (!ReadIntoArray(f, mSandboxVoucher, MAX_VOUCHER_LENGTH)) {
+ NS_WARNING("Failed to read sandbox voucher");
+ }
+}
+
+PGMPContentChild*
+GMPChild::AllocPGMPContentChild(Transport* aTransport,
+ ProcessId aOtherPid)
+{
+ GMPContentChild* child =
+ mGMPContentChildren.AppendElement(new GMPContentChild(this))->get();
+ child->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), ipc::ChildSide);
+
+ return child;
+}
+
+void
+GMPChild::GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild)
+{
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ UniquePtr<GMPContentChild>& toDestroy = mGMPContentChildren[i - 1];
+ if (toDestroy.get() == aGMPContentChild) {
+ SendPGMPContentChildDestroyed();
+ RefPtr<DeleteTask<GMPContentChild>> task =
+ new DeleteTask<GMPContentChild>(toDestroy.release());
+ MessageLoop::current()->PostTask(task.forget());
+ mGMPContentChildren.RemoveElementAt(i - 1);
+ break;
+ }
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+#undef LOG
+#undef LOGD
diff --git a/dom/media/gmp/GMPChild.h b/dom/media/gmp/GMPChild.h
new file mode 100644
index 000000000..722e4c7a9
--- /dev/null
+++ b/dom/media/gmp/GMPChild.h
@@ -0,0 +1,92 @@
+/* -*- 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 GMPChild_h_
+#define GMPChild_h_
+
+#include "mozilla/gmp/PGMPChild.h"
+#include "GMPTimerChild.h"
+#include "GMPStorageChild.h"
+#include "GMPLoader.h"
+#include "gmp-async-shutdown.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPChild : public PGMPChild
+ , public GMPAsyncShutdownHost
+{
+public:
+ GMPChild();
+ virtual ~GMPChild();
+
+ bool Init(const nsAString& aPluginPath,
+ const nsAString& aVoucherPath,
+ base::ProcessId aParentPid,
+ MessageLoop* aIOLoop,
+ IPC::Channel* aChannel);
+ MessageLoop* GMPMessageLoop();
+
+ // Main thread only.
+ GMPTimerChild* GetGMPTimers();
+ GMPStorageChild* GetGMPStorage();
+
+ // GMPAsyncShutdownHost
+ void ShutdownComplete() override;
+
+private:
+ friend class GMPContentChild;
+
+ bool PreLoadPluginVoucher();
+ void PreLoadSandboxVoucher();
+
+ bool GetUTF8LibPath(nsACString& aOutLibPath);
+
+ bool RecvSetNodeId(const nsCString& aNodeId) override;
+ bool AnswerStartPlugin(const nsString& aAdapter) override;
+ bool RecvPreloadLibs(const nsCString& aLibs) override;
+
+ PGMPTimerChild* AllocPGMPTimerChild() override;
+ bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) override;
+
+ PGMPStorageChild* AllocPGMPStorageChild() override;
+ bool DeallocPGMPStorageChild(PGMPStorageChild* aActor) override;
+
+ PGMPContentChild* AllocPGMPContentChild(Transport* aTransport,
+ ProcessId aOtherPid) override;
+ void GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild);
+
+ bool RecvCrashPluginNow() override;
+ bool RecvBeginAsyncShutdown() override;
+ bool RecvCloseActive() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI, uint32_t aDecryptorId = 0);
+
+ nsTArray<UniquePtr<GMPContentChild>> mGMPContentChildren;
+
+ GMPAsyncShutdown* mAsyncShutdown;
+ RefPtr<GMPTimerChild> mTimerChild;
+ RefPtr<GMPStorageChild> mStorage;
+
+ MessageLoop* mGMPMessageLoop;
+ nsString mPluginPath;
+ nsString mSandboxVoucherPath;
+ nsCString mNodeId;
+ GMPLoader* mGMPLoader;
+ nsTArray<uint8_t> mPluginVoucher;
+ nsTArray<uint8_t> mSandboxVoucher;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPChild_h_
diff --git a/dom/media/gmp/GMPContentChild.cpp b/dom/media/gmp/GMPContentChild.cpp
new file mode 100644
index 000000000..415736e11
--- /dev/null
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -0,0 +1,320 @@
+/* -*- 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 "GMPContentChild.h"
+#include "GMPChild.h"
+#include "GMPAudioDecoderChild.h"
+#include "GMPDecryptorChild.h"
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "base/task.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPContentChild::GMPContentChild(GMPChild* aChild)
+ : mGMPChild(aChild)
+{
+ MOZ_COUNT_CTOR(GMPContentChild);
+}
+
+GMPContentChild::~GMPContentChild()
+{
+ MOZ_COUNT_DTOR(GMPContentChild);
+}
+
+MessageLoop*
+GMPContentChild::GMPMessageLoop()
+{
+ return mGMPChild->GMPMessageLoop();
+}
+
+void
+GMPContentChild::CheckThread()
+{
+ MOZ_ASSERT(mGMPChild->mGMPMessageLoop == MessageLoop::current());
+}
+
+void
+GMPContentChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ mGMPChild->GMPContentChildActorDestroy(this);
+}
+
+void
+GMPContentChild::ProcessingError(Result aCode, const char* aReason)
+{
+ mGMPChild->ProcessingError(aCode, aReason);
+}
+
+PGMPAudioDecoderChild*
+GMPContentChild::AllocPGMPAudioDecoderChild()
+{
+ return new GMPAudioDecoderChild(this);
+}
+
+bool
+GMPContentChild::DeallocPGMPAudioDecoderChild(PGMPAudioDecoderChild* aActor)
+{
+ delete aActor;
+ return true;
+}
+
+PGMPDecryptorChild*
+GMPContentChild::AllocPGMPDecryptorChild()
+{
+ GMPDecryptorChild* actor = new GMPDecryptorChild(this,
+ mGMPChild->mPluginVoucher,
+ mGMPChild->mSandboxVoucher);
+ actor->AddRef();
+ return actor;
+}
+
+bool
+GMPContentChild::DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor)
+{
+ static_cast<GMPDecryptorChild*>(aActor)->Release();
+ return true;
+}
+
+PGMPVideoDecoderChild*
+GMPContentChild::AllocPGMPVideoDecoderChild(const uint32_t& aDecryptorId)
+{
+ GMPVideoDecoderChild* actor = new GMPVideoDecoderChild(this);
+ actor->AddRef();
+ return actor;
+}
+
+bool
+GMPContentChild::DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor)
+{
+ static_cast<GMPVideoDecoderChild*>(aActor)->Release();
+ return true;
+}
+
+PGMPVideoEncoderChild*
+GMPContentChild::AllocPGMPVideoEncoderChild()
+{
+ GMPVideoEncoderChild* actor = new GMPVideoEncoderChild(this);
+ actor->AddRef();
+ return actor;
+}
+
+bool
+GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
+{
+ static_cast<GMPVideoEncoderChild*>(aActor)->Release();
+ return true;
+}
+
+// Adapts GMPDecryptor7 to the current GMPDecryptor version.
+class GMPDecryptor7BackwardsCompat : public GMPDecryptor {
+public:
+ explicit GMPDecryptor7BackwardsCompat(GMPDecryptor7* aDecryptorV7)
+ : mDecryptorV7(aDecryptorV7)
+ {
+ }
+
+ void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override
+ {
+ // Distinctive identifier and persistent state arguments not present
+ // in v7 interface.
+ mDecryptorV7->Init(aCallback);
+ }
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) override
+ {
+ mDecryptorV7->CreateSession(aCreateSessionToken,
+ aPromiseId,
+ aInitDataType,
+ aInitDataTypeSize,
+ aInitData,
+ aInitDataSize,
+ aSessionType);
+ }
+
+ void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ mDecryptorV7->LoadSession(aPromiseId, aSessionId, aSessionIdLength);
+ }
+
+ void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) override
+ {
+ mDecryptorV7->UpdateSession(aPromiseId,
+ aSessionId,
+ aSessionIdLength,
+ aResponse,
+ aResponseSize);
+ }
+
+ void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ mDecryptorV7->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
+ }
+
+ void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override
+ {
+ mDecryptorV7->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
+ }
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) override
+ {
+ mDecryptorV7->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
+ }
+
+ void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) override
+ {
+ mDecryptorV7->Decrypt(aBuffer, aMetadata);
+ }
+
+ void DecryptingComplete() override
+ {
+ mDecryptorV7->DecryptingComplete();
+ delete this;
+ }
+private:
+ GMPDecryptor7* mDecryptorV7;
+};
+
+bool
+GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
+{
+ GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
+ GMPDecryptorHost* host = static_cast<GMPDecryptorHost*>(child);
+
+ void* ptr = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &ptr, aActor->Id());
+ GMPDecryptor* decryptor = nullptr;
+ if (GMP_SUCCEEDED(err) && ptr) {
+ decryptor = static_cast<GMPDecryptor*>(ptr);
+ } else if (err != GMPNoErr) {
+ // We Adapt the previous GMPDecryptor version to the current, so that
+ // Gecko thinks it's only talking to the current version. v7 differs
+ // from v9 in its Init() function arguments, and v9 has extra enumeration
+ // members at the end of the key status enumerations.
+ err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_BACKWARDS_COMPAT, host, &ptr);
+ if (err != GMPNoErr || !ptr) {
+ return false;
+ }
+ decryptor = new GMPDecryptor7BackwardsCompat(static_cast<GMPDecryptor7*>(ptr));
+ }
+
+ child->Init(decryptor);
+
+ return true;
+}
+
+bool
+GMPContentChild::RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor)
+{
+ auto vdc = static_cast<GMPAudioDecoderChild*>(aActor);
+
+ void* vd = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_AUDIO_DECODER, &vdc->Host(), &vd);
+ if (err != GMPNoErr || !vd) {
+ return false;
+ }
+
+ vdc->Init(static_cast<GMPAudioDecoder*>(vd));
+
+ return true;
+}
+
+bool
+GMPContentChild::RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor,
+ const uint32_t& aDecryptorId)
+{
+ auto vdc = static_cast<GMPVideoDecoderChild*>(aActor);
+
+ void* vd = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_DECODER, &vdc->Host(), &vd, aDecryptorId);
+ if (err != GMPNoErr || !vd) {
+ NS_WARNING("GMPGetAPI call failed trying to construct decoder.");
+ return false;
+ }
+
+ vdc->Init(static_cast<GMPVideoDecoder*>(vd));
+
+ return true;
+}
+
+bool
+GMPContentChild::RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor)
+{
+ auto vec = static_cast<GMPVideoEncoderChild*>(aActor);
+
+ void* ve = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_ENCODER, &vec->Host(), &ve);
+ if (err != GMPNoErr || !ve) {
+ NS_WARNING("GMPGetAPI call failed trying to construct encoder.");
+ return false;
+ }
+
+ vec->Init(static_cast<GMPVideoEncoder*>(ve));
+
+ return true;
+}
+
+void
+GMPContentChild::CloseActive()
+{
+ // Invalidate and remove any remaining API objects.
+ const ManagedContainer<PGMPAudioDecoderChild>& audioDecoders =
+ ManagedPGMPAudioDecoderChild();
+ for (auto iter = audioDecoders.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPDecryptorChild>& decryptors =
+ ManagedPGMPDecryptorChild();
+ for (auto iter = decryptors.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPVideoDecoderChild>& videoDecoders =
+ ManagedPGMPVideoDecoderChild();
+ for (auto iter = videoDecoders.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPVideoEncoderChild>& videoEncoders =
+ ManagedPGMPVideoEncoderChild();
+ for (auto iter = videoEncoders.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->SendShutdown();
+ }
+}
+
+bool
+GMPContentChild::IsUsed()
+{
+ return !ManagedPGMPAudioDecoderChild().IsEmpty() ||
+ !ManagedPGMPDecryptorChild().IsEmpty() ||
+ !ManagedPGMPVideoDecoderChild().IsEmpty() ||
+ !ManagedPGMPVideoEncoderChild().IsEmpty();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPContentChild.h b/dom/media/gmp/GMPContentChild.h
new file mode 100644
index 000000000..714207608
--- /dev/null
+++ b/dom/media/gmp/GMPContentChild.h
@@ -0,0 +1,58 @@
+/* -*- 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 GMPContentChild_h_
+#define GMPContentChild_h_
+
+#include "mozilla/gmp/PGMPContentChild.h"
+#include "GMPSharedMemManager.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+class GMPContentChild : public PGMPContentChild
+ , public GMPSharedMem
+{
+public:
+ explicit GMPContentChild(GMPChild* aChild);
+ virtual ~GMPContentChild();
+
+ MessageLoop* GMPMessageLoop();
+
+ bool RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor) override;
+ bool RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor) override;
+ bool RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor, const uint32_t& aDecryptorId) override;
+ bool RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) override;
+
+ PGMPAudioDecoderChild* AllocPGMPAudioDecoderChild() override;
+ bool DeallocPGMPAudioDecoderChild(PGMPAudioDecoderChild* 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;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ // GMPSharedMem
+ void CheckThread() override;
+
+ void CloseActive();
+ bool IsUsed();
+
+ GMPChild* mGMPChild;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPContentChild_h_
diff --git a/dom/media/gmp/GMPContentParent.cpp b/dom/media/gmp/GMPContentParent.cpp
new file mode 100644
index 000000000..12f6f4c48
--- /dev/null
+++ b/dom/media/gmp/GMPContentParent.cpp
@@ -0,0 +1,298 @@
+/* -*- 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 "GMPContentParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "GMPDecryptorParent.h"
+#include "GMPParent.h"
+#include "GMPServiceChild.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Unused.h"
+#include "base/task.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPContentParent"
+
+namespace gmp {
+
+GMPContentParent::GMPContentParent(GMPParent* aParent)
+ : mParent(aParent)
+{
+ if (mParent) {
+ SetDisplayName(mParent->GetDisplayName());
+ SetPluginId(mParent->GetPluginId());
+ }
+}
+
+GMPContentParent::~GMPContentParent()
+{
+}
+
+class ReleaseGMPContentParent : public Runnable
+{
+public:
+ explicit ReleaseGMPContentParent(GMPContentParent* aToRelease)
+ : mToRelease(aToRelease)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ return NS_OK;
+ }
+
+private:
+ RefPtr<GMPContentParent> mToRelease;
+};
+
+void
+GMPContentParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ MOZ_ASSERT(mAudioDecoders.IsEmpty() &&
+ mDecryptors.IsEmpty() &&
+ mVideoDecoders.IsEmpty() &&
+ mVideoEncoders.IsEmpty());
+ NS_DispatchToCurrentThread(new ReleaseGMPContentParent(this));
+}
+
+void
+GMPContentParent::CheckThread()
+{
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+}
+
+void
+GMPContentParent::AudioDecoderDestroyed(GMPAudioDecoderParent* aDecoder)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ MOZ_ALWAYS_TRUE(mAudioDecoders.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();
+}
+
+void
+GMPContentParent::VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ // If the constructor fails, we'll get called before it's added
+ Unused << NS_WARN_IF(!mVideoEncoders.RemoveElement(aEncoder));
+ CloseIfUnused();
+}
+
+void
+GMPContentParent::DecryptorDestroyed(GMPDecryptorParent* aSession)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ MOZ_ALWAYS_TRUE(mDecryptors.RemoveElement(aSession));
+ CloseIfUnused();
+}
+
+void
+GMPContentParent::CloseIfUnused()
+{
+ if (mAudioDecoders.IsEmpty() &&
+ mDecryptors.IsEmpty() &&
+ mVideoDecoders.IsEmpty() &&
+ mVideoEncoders.IsEmpty()) {
+ RefPtr<GMPContentParent> toClose;
+ if (mParent) {
+ toClose = mParent->ForgetGMPContentParent();
+ } else {
+ toClose = this;
+ RefPtr<GeckoMediaPluginServiceChild> gmp(
+ GeckoMediaPluginServiceChild::GetSingleton());
+ gmp->RemoveGMPContentParent(toClose);
+ }
+ NS_DispatchToCurrentThread(NewRunnableMethod(toClose,
+ &GMPContentParent::Close));
+ }
+}
+
+nsresult
+GMPContentParent::GetGMPDecryptor(GMPDecryptorParent** aGMPDP)
+{
+ PGMPDecryptorParent* pdp = SendPGMPDecryptorConstructor();
+ if (!pdp) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPDecryptorParent* dp = static_cast<GMPDecryptorParent*>(pdp);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(dp);
+ mDecryptors.AppendElement(dp);
+ *aGMPDP = dp;
+
+ return NS_OK;
+}
+
+nsIThread*
+GMPContentParent::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);
+ }
+
+ return mGMPThread;
+}
+
+nsresult
+GMPContentParent::GetGMPAudioDecoder(GMPAudioDecoderParent** aGMPAD)
+{
+ PGMPAudioDecoderParent* pvap = SendPGMPAudioDecoderConstructor();
+ if (!pvap) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPAudioDecoderParent* vap = static_cast<GMPAudioDecoderParent*>(pvap);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(vap);
+ *aGMPAD = vap;
+ mAudioDecoders.AppendElement(vap);
+
+ return NS_OK;
+}
+
+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;
+ }
+ GMPVideoDecoderParent *vdp = static_cast<GMPVideoDecoderParent*>(pvdp);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(vdp);
+ *aGMPVD = vdp;
+ mVideoDecoders.AppendElement(vdp);
+
+ return NS_OK;
+}
+
+nsresult
+GMPContentParent::GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE)
+{
+ // returned with one anonymous AddRef that locks it until Destroy
+ PGMPVideoEncoderParent* pvep = SendPGMPVideoEncoderConstructor();
+ if (!pvep) {
+ return NS_ERROR_FAILURE;
+ }
+ GMPVideoEncoderParent *vep = static_cast<GMPVideoEncoderParent*>(pvep);
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ NS_ADDREF(vep);
+ *aGMPVE = vep;
+ mVideoEncoders.AppendElement(vep);
+
+ return NS_OK;
+}
+
+PGMPVideoDecoderParent*
+GMPContentParent::AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId)
+{
+ GMPVideoDecoderParent* vdp = new GMPVideoDecoderParent(this);
+ NS_ADDREF(vdp);
+ return vdp;
+}
+
+bool
+GMPContentParent::DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor)
+{
+ GMPVideoDecoderParent* vdp = static_cast<GMPVideoDecoderParent*>(aActor);
+ NS_RELEASE(vdp);
+ return true;
+}
+
+PGMPVideoEncoderParent*
+GMPContentParent::AllocPGMPVideoEncoderParent()
+{
+ GMPVideoEncoderParent* vep = new GMPVideoEncoderParent(this);
+ NS_ADDREF(vep);
+ return vep;
+}
+
+bool
+GMPContentParent::DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor)
+{
+ GMPVideoEncoderParent* vep = static_cast<GMPVideoEncoderParent*>(aActor);
+ NS_RELEASE(vep);
+ return true;
+}
+
+PGMPDecryptorParent*
+GMPContentParent::AllocPGMPDecryptorParent()
+{
+ GMPDecryptorParent* ksp = new GMPDecryptorParent(this);
+ NS_ADDREF(ksp);
+ return ksp;
+}
+
+bool
+GMPContentParent::DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor)
+{
+ GMPDecryptorParent* ksp = static_cast<GMPDecryptorParent*>(aActor);
+ NS_RELEASE(ksp);
+ return true;
+}
+
+PGMPAudioDecoderParent*
+GMPContentParent::AllocPGMPAudioDecoderParent()
+{
+ GMPAudioDecoderParent* vdp = new GMPAudioDecoderParent(this);
+ NS_ADDREF(vdp);
+ return vdp;
+}
+
+bool
+GMPContentParent::DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor)
+{
+ GMPAudioDecoderParent* vdp = static_cast<GMPAudioDecoderParent*>(aActor);
+ NS_RELEASE(vdp);
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPContentParent.h b/dom/media/gmp/GMPContentParent.h
new file mode 100644
index 000000000..81f79bc73
--- /dev/null
+++ b/dom/media/gmp/GMPContentParent.h
@@ -0,0 +1,103 @@
+/* -*- 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 GMPContentParent_h_
+#define GMPContentParent_h_
+
+#include "mozilla/gmp/PGMPContentParent.h"
+#include "GMPSharedMemManager.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPAudioDecoderParent;
+class GMPDecryptorParent;
+class GMPParent;
+class GMPVideoDecoderParent;
+class GMPVideoEncoderParent;
+
+class GMPContentParent final : public PGMPContentParent,
+ public GMPSharedMem
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPContentParent)
+
+ explicit GMPContentParent(GMPParent* aParent = nullptr);
+
+ nsresult GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD,
+ uint32_t aDecryptorId);
+ void VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder);
+
+ nsresult GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE);
+ void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder);
+
+ nsresult GetGMPDecryptor(GMPDecryptorParent** aGMPKS);
+ void DecryptorDestroyed(GMPDecryptorParent* aSession);
+
+ nsresult GetGMPAudioDecoder(GMPAudioDecoderParent** aGMPAD);
+ void AudioDecoderDestroyed(GMPAudioDecoderParent* aDecoder);
+
+ nsIThread* GMPThread();
+
+ // GMPSharedMem
+ void CheckThread() override;
+
+ void SetDisplayName(const nsCString& aDisplayName)
+ {
+ mDisplayName = aDisplayName;
+ }
+ const nsCString& GetDisplayName()
+ {
+ return mDisplayName;
+ }
+ void SetPluginId(const uint32_t aPluginId)
+ {
+ mPluginId = aPluginId;
+ }
+ uint32_t GetPluginId() const
+ {
+ return mPluginId;
+ }
+
+private:
+ ~GMPContentParent();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ PGMPVideoDecoderParent* AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId) override;
+ bool DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) override;
+
+ PGMPVideoEncoderParent* AllocPGMPVideoEncoderParent() override;
+ bool DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor) override;
+
+ PGMPDecryptorParent* AllocPGMPDecryptorParent() override;
+ bool DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor) override;
+
+ PGMPAudioDecoderParent* AllocPGMPAudioDecoderParent() override;
+ bool DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* 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<GMPAudioDecoderParent>> mAudioDecoders;
+ nsCOMPtr<nsIThread> mGMPThread;
+ RefPtr<GMPParent> mParent;
+ nsCString mDisplayName;
+ uint32_t mPluginId;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPParent_h_
diff --git a/dom/media/gmp/GMPCrashHelperHolder.h b/dom/media/gmp/GMPCrashHelperHolder.h
new file mode 100644
index 000000000..ea6f36c83
--- /dev/null
+++ b/dom/media/gmp/GMPCrashHelperHolder.h
@@ -0,0 +1,81 @@
+/* -*- 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 GMPCrashHelperHolder_h_
+#define GMPCrashHelperHolder_h_
+
+#include "GMPService.h"
+#include "mozilla/RefPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+class GMPCrashHelper;
+
+namespace mozilla {
+
+// Disconnecting the GMPCrashHelpers at the right time is hard. We need to
+// ensure that in the crashing case that we stay connected until the
+// "gmp-plugin-crashed" message is processed in the content process.
+//
+// We have two channels connecting to the GMP; PGMP which connects from
+// chrome to GMP process, and PGMPContent, which bridges between the content
+// and GMP process. If the GMP crashes both PGMP and PGMPContent receive
+// ActorDestroy messages and begin to shutdown at the same time.
+//
+// However the crash report mini dump must be generated in the chrome
+// process' ActorDestroy, before the "gmp-plugin-crashed" message can be sent
+// to the content process. We fire the "PluginCrashed" event when we handle
+// the "gmp-plugin-crashed" message in the content process, and we need the
+// crash helpers to do that.
+//
+// The PGMPContent's managed actors' ActorDestroy messages are the only shutdown
+// notification we get in the content process, but we can't disconnect the
+// crash handlers there in the crashing case, as ActorDestroy happens before
+// we've received the "gmp-plugin-crashed" message and had a chance for the
+// crash helpers to generate the window to dispatch PluginCrashed to initiate
+// the crash report submission notification box.
+//
+// So we need to ensure that in the content process, the GMPCrashHelpers stay
+// connected to the GMPService until after ActorDestroy messages are received
+// if there's an abnormal shutdown. In the case where the GMP doesn't crash,
+// we do actually want to disconnect GMPCrashHandlers in ActorDestroy, since
+// we don't have any other signal that a GMP actor is shutting down. If we don't
+// disconnect the crash helper there in the normal shutdown case, the helper
+// will stick around forever and leak.
+//
+// In the crashing case, the GMPCrashHelpers are deallocated when the crash
+// report is processed in GeckoMediaPluginService::RunPluginCrashCallbacks().
+//
+// It's a bit yuck that we have to have two paths for disconnecting the crash
+// helpers, but there aren't really any better options.
+class GMPCrashHelperHolder
+{
+public:
+
+ void SetCrashHelper(GMPCrashHelper* aHelper)
+ {
+ mCrashHelper = aHelper;
+ }
+
+ GMPCrashHelper* GetCrashHelper()
+ {
+ return mCrashHelper;
+ }
+
+ void MaybeDisconnect(bool aAbnormalShutdown)
+ {
+ if (!aAbnormalShutdown) {
+ RefPtr<gmp::GeckoMediaPluginService> service(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ service->DisconnectCrashHelper(GetCrashHelper());
+ }
+ }
+
+private:
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+}
+
+#endif // GMPCrashHelperHolder_h_
diff --git a/dom/media/gmp/GMPDecryptorChild.cpp b/dom/media/gmp/GMPDecryptorChild.cpp
new file mode 100644
index 000000000..6da3c6c43
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -0,0 +1,403 @@
+/* -*- 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 "GMPDecryptorChild.h"
+#include "GMPContentChild.h"
+#include "GMPChild.h"
+#include "base/task.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "runnable_utils.h"
+#include <ctime>
+
+#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current())
+
+#define CALL_ON_GMP_THREAD(_func, ...) \
+ CallOnGMPThread(&GMPDecryptorChild::_func, __VA_ARGS__)
+
+namespace mozilla {
+namespace gmp {
+
+GMPDecryptorChild::GMPDecryptorChild(GMPContentChild* aPlugin,
+ const nsTArray<uint8_t>& aPluginVoucher,
+ const nsTArray<uint8_t>& aSandboxVoucher)
+ : mSession(nullptr)
+ , mPlugin(aPlugin)
+ , mPluginVoucher(aPluginVoucher)
+ , mSandboxVoucher(aSandboxVoucher)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPDecryptorChild::~GMPDecryptorChild()
+{
+}
+
+template <typename MethodType, typename... ParamType>
+void
+GMPDecryptorChild::CallMethod(MethodType aMethod, ParamType&&... aParams)
+{
+ MOZ_ASSERT(ON_GMP_THREAD());
+ // Don't send IPC messages after tear-down.
+ if (mSession) {
+ (this->*aMethod)(Forward<ParamType>(aParams)...);
+ }
+}
+
+template<typename T>
+struct AddConstReference {
+ typedef const typename RemoveReference<T>::Type& Type;
+};
+
+template<typename MethodType, typename... ParamType>
+void
+GMPDecryptorChild::CallOnGMPThread(MethodType aMethod, ParamType&&... aParams)
+{
+ if (ON_GMP_THREAD()) {
+ // Use forwarding reference when we can.
+ CallMethod(aMethod, Forward<ParamType>(aParams)...);
+ } else {
+ // Use const reference when we have to.
+ auto m = &GMPDecryptorChild::CallMethod<
+ decltype(aMethod), typename AddConstReference<ParamType>::Type...>;
+ RefPtr<mozilla::Runnable> t =
+ dont_add_new_uses_of_this::NewRunnableMethod(this, m, aMethod, Forward<ParamType>(aParams)...);
+ mPlugin->GMPMessageLoop()->PostTask(t.forget());
+ }
+}
+
+void
+GMPDecryptorChild::Init(GMPDecryptor* aSession)
+{
+ MOZ_ASSERT(aSession);
+ mSession = aSession;
+ // The ID of this decryptor is the IPDL actor ID. Note it's unique inside
+ // the child process, but not necessarily across all gecko processes. However,
+ // since GMPDecryptors are segregated by node ID/origin, we shouldn't end up
+ // with clashes in the content process.
+ SendSetDecryptorId(Id());
+}
+
+void
+GMPDecryptorChild::SetSessionId(uint32_t aCreateSessionToken,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CALL_ON_GMP_THREAD(SendSetSessionId,
+ aCreateSessionToken, nsCString(aSessionId, aSessionIdLength));
+}
+
+void
+GMPDecryptorChild::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
+{
+ CALL_ON_GMP_THREAD(SendResolveLoadSessionPromise, aPromiseId, aSuccess);
+}
+
+void
+GMPDecryptorChild::ResolvePromise(uint32_t aPromiseId)
+{
+ CALL_ON_GMP_THREAD(SendResolvePromise, aPromiseId);
+}
+
+void
+GMPDecryptorChild::RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aException,
+ const char* aMessage,
+ uint32_t aMessageLength)
+{
+ CALL_ON_GMP_THREAD(SendRejectPromise,
+ aPromiseId, aException, nsCString(aMessage, aMessageLength));
+}
+
+void
+GMPDecryptorChild::SessionMessage(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPSessionMessageType aMessageType,
+ const uint8_t* aMessage,
+ uint32_t aMessageLength)
+{
+ nsTArray<uint8_t> msg;
+ msg.AppendElements(aMessage, aMessageLength);
+ CALL_ON_GMP_THREAD(SendSessionMessage,
+ nsCString(aSessionId, aSessionIdLength),
+ aMessageType, Move(msg));
+}
+
+void
+GMPDecryptorChild::ExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPTimestamp aExpiryTime)
+{
+ CALL_ON_GMP_THREAD(SendExpirationChange,
+ nsCString(aSessionId, aSessionIdLength), aExpiryTime);
+}
+
+void
+GMPDecryptorChild::SessionClosed(const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CALL_ON_GMP_THREAD(SendSessionClosed,
+ nsCString(aSessionId, aSessionIdLength));
+}
+
+void
+GMPDecryptorChild::SessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPDOMException aException,
+ uint32_t aSystemCode,
+ const char* aMessage,
+ uint32_t aMessageLength)
+{
+ CALL_ON_GMP_THREAD(SendSessionError,
+ nsCString(aSessionId, aSessionIdLength),
+ aException, aSystemCode,
+ nsCString(aMessage, aMessageLength));
+}
+
+void
+GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aKeyId,
+ uint32_t aKeyIdLength,
+ GMPMediaKeyStatus aStatus)
+{
+ AutoTArray<uint8_t, 16> kid;
+ kid.AppendElements(aKeyId, aKeyIdLength);
+
+ nsTArray<GMPKeyInformation> keyInfos;
+ keyInfos.AppendElement(GMPKeyInformation(kid, aStatus));
+ CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+ nsCString(aSessionId, aSessionIdLength),
+ keyInfos);
+}
+
+void
+GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength)
+{
+ nsTArray<GMPKeyInformation> keyInfos;
+ for (uint32_t i = 0; i < aKeyInfosLength; i++) {
+ nsTArray<uint8_t> keyId;
+ keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size);
+ keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status));
+ }
+ CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+ nsCString(aSessionId, aSessionIdLength),
+ keyInfos);
+}
+
+void
+GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult)
+{
+ if (!ON_GMP_THREAD()) {
+ // We should run this whole method on the GMP thread since the buffer needs
+ // to be deleted after the SendDecrypted call.
+ mPlugin->GMPMessageLoop()->PostTask(NewRunnableMethod
+ <GMPBuffer*, GMPErr>(this,
+ &GMPDecryptorChild::Decrypted,
+ aBuffer, aResult));
+ return;
+ }
+
+ if (!aBuffer) {
+ NS_WARNING("GMPDecryptorCallback passed bull GMPBuffer");
+ return;
+ }
+
+ auto buffer = static_cast<GMPBufferImpl*>(aBuffer);
+ if (mSession) {
+ SendDecrypted(buffer->mId, aResult, buffer->mData);
+ }
+ delete buffer;
+}
+
+void
+GMPDecryptorChild::SetCapabilities(uint64_t aCaps)
+{
+ // Deprecated.
+}
+
+void
+GMPDecryptorChild::GetSandboxVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength)
+{
+ if (!aVoucher || !aVoucherLength) {
+ return;
+ }
+ *aVoucher = mSandboxVoucher.Elements();
+ *aVoucherLength = mSandboxVoucher.Length();
+}
+
+void
+GMPDecryptorChild::GetPluginVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength)
+{
+ if (!aVoucher || !aVoucherLength) {
+ return;
+ }
+ *aVoucher = mPluginVoucher.Elements();
+ *aVoucherLength = mPluginVoucher.Length();
+}
+
+bool
+GMPDecryptorChild::RecvInit(const bool& aDistinctiveIdentifierRequired,
+ const bool& aPersistentStateRequired)
+{
+ if (!mSession) {
+ return false;
+ }
+ mSession->Init(this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvCreateSession(const uint32_t& aCreateSessionToken,
+ const uint32_t& aPromiseId,
+ const nsCString& aInitDataType,
+ InfallibleTArray<uint8_t>&& aInitData,
+ const GMPSessionType& aSessionType)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->CreateSession(aCreateSessionToken,
+ aPromiseId,
+ aInitDataType.get(),
+ aInitDataType.Length(),
+ aInitData.Elements(),
+ aInitData.Length(),
+ aSessionType);
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvLoadSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->LoadSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvUpdateSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId,
+ InfallibleTArray<uint8_t>&& aResponse)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->UpdateSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length(),
+ aResponse.Elements(),
+ aResponse.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvCloseSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->CloseSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvRemoveSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->RemoveSession(aPromiseId,
+ aSessionId.get(),
+ aSessionId.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
+ InfallibleTArray<uint8_t>&& aServerCert)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ mSession->SetServerCertificate(aPromiseId,
+ aServerCert.Elements(),
+ aServerCert.Length());
+
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvDecrypt(const uint32_t& aId,
+ InfallibleTArray<uint8_t>&& aBuffer,
+ const GMPDecryptionData& aMetadata)
+{
+ if (!mSession) {
+ return false;
+ }
+
+ // Note: the GMPBufferImpl created here is deleted when the GMP passes
+ // it back in the Decrypted() callback above.
+ GMPBufferImpl* buffer = new GMPBufferImpl(aId, aBuffer);
+
+ // |metadata| lifetime is managed by |buffer|.
+ GMPEncryptedBufferDataImpl* metadata = new GMPEncryptedBufferDataImpl(aMetadata);
+ buffer->SetMetadata(metadata);
+
+ mSession->Decrypt(buffer, metadata);
+ return true;
+}
+
+bool
+GMPDecryptorChild::RecvDecryptingComplete()
+{
+ // Reset |mSession| before calling DecryptingComplete(). We should not send
+ // any IPC messages during tear-down.
+ auto session = mSession;
+ mSession = nullptr;
+
+ if (!session) {
+ return false;
+ }
+
+ session->DecryptingComplete();
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+// avoid redefined macro in unified build
+#undef ON_GMP_THREAD
+#undef CALL_ON_GMP_THREAD
diff --git a/dom/media/gmp/GMPDecryptorChild.h b/dom/media/gmp/GMPDecryptorChild.h
new file mode 100644
index 000000000..434da774f
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -0,0 +1,142 @@
+/* -*- 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 GMPDecryptorChild_h_
+#define GMPDecryptorChild_h_
+
+#include "mozilla/gmp/PGMPDecryptorChild.h"
+#include "gmp-decryption.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPEncryptedBufferDataImpl.h"
+#include <string>
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPDecryptorChild : public GMPDecryptorCallback
+ , public GMPDecryptorHost
+ , public PGMPDecryptorChild
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPDecryptorChild);
+
+ explicit GMPDecryptorChild(GMPContentChild* aPlugin,
+ const nsTArray<uint8_t>& aPluginVoucher,
+ const nsTArray<uint8_t>& aSandboxVoucher);
+
+ void Init(GMPDecryptor* aSession);
+
+ // GMPDecryptorCallback
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override;
+ void ResolvePromise(uint32_t aPromiseId) override;
+
+ void RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aException,
+ const char* aMessage,
+ uint32_t aMessageLength) override;
+
+ void SessionMessage(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPSessionMessageType aMessageType,
+ const uint8_t* aMessage,
+ uint32_t aMessageLength) override;
+
+ void ExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPTimestamp aExpiryTime) override;
+
+ void SessionClosed(const char* aSessionId,
+ uint32_t aSessionIdLength) override;
+
+ void SessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPDOMException aException,
+ uint32_t aSystemCode,
+ const char* aMessage,
+ uint32_t aMessageLength) override;
+
+ void KeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aKeyId,
+ uint32_t aKeyIdLength,
+ GMPMediaKeyStatus aStatus) override;
+
+ void SetCapabilities(uint64_t aCaps) override;
+
+ void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override;
+
+ void BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength) override;
+
+ // GMPDecryptorHost
+ void GetSandboxVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) override;
+
+ void GetPluginVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) override;
+private:
+ ~GMPDecryptorChild();
+
+ // GMPDecryptorChild
+ bool RecvInit(const bool& aDistinctiveIdentifierRequired,
+ const bool& aPersistentStateRequired) override;
+
+ bool RecvCreateSession(const uint32_t& aCreateSessionToken,
+ const uint32_t& aPromiseId,
+ const nsCString& aInitDataType,
+ InfallibleTArray<uint8_t>&& aInitData,
+ const GMPSessionType& aSessionType) override;
+
+ bool RecvLoadSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId) override;
+
+ bool RecvUpdateSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId,
+ InfallibleTArray<uint8_t>&& aResponse) override;
+
+ bool RecvCloseSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId) override;
+
+ bool RecvRemoveSession(const uint32_t& aPromiseId,
+ const nsCString& aSessionId) override;
+
+ bool RecvDecrypt(const uint32_t& aId,
+ InfallibleTArray<uint8_t>&& aBuffer,
+ const GMPDecryptionData& aMetadata) override;
+
+ // Resolve/reject promise on completion.
+ bool RecvSetServerCertificate(const uint32_t& aPromiseId,
+ InfallibleTArray<uint8_t>&& aServerCert) override;
+
+ bool RecvDecryptingComplete() override;
+
+ template <typename MethodType, typename... ParamType>
+ void CallMethod(MethodType, ParamType&&...);
+
+ template<typename MethodType, typename... ParamType>
+ void CallOnGMPThread(MethodType, ParamType&&...);
+
+ // GMP's GMPDecryptor implementation.
+ // Only call into this on the (GMP process) main thread.
+ GMPDecryptor* mSession;
+ GMPContentChild* mPlugin;
+
+ // Reference to the vouchers owned by the GMPChild.
+ const nsTArray<uint8_t>& mPluginVoucher;
+ const nsTArray<uint8_t>& mSandboxVoucher;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPDecryptorChild_h_
diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp
new file mode 100644
index 000000000..f2a5d2431
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -0,0 +1,384 @@
+/* -*- 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 "GMPDecryptorParent.h"
+
+#include "GMPContentParent.h"
+#include "GMPUtils.h"
+#include "MediaData.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin)
+ : mIsOpen(false)
+ , mShuttingDown(false)
+ , mActorDestroyed(false)
+ , mPlugin(aPlugin)
+ , mPluginId(aPlugin->GetPluginId())
+ , mCallback(nullptr)
+#ifdef DEBUG
+ , mGMPThread(aPlugin->GMPThread())
+#endif
+{
+ MOZ_ASSERT(mPlugin && mGMPThread);
+}
+
+GMPDecryptorParent::~GMPDecryptorParent()
+{
+}
+
+bool
+GMPDecryptorParent::RecvSetDecryptorId(const uint32_t& aId)
+{
+ /* STUB */
+ return true;
+}
+
+nsresult
+GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+{
+ LOGD(("GMPDecryptorParent[%p]::Init()", this));
+
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-use an in-use GMP decrypter!");
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aCallback;
+ if (!SendInit(aDistinctiveIdentifierRequired, aPersistentStateRequired)) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsOpen = true;
+ return NS_OK;
+}
+
+void
+GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ GMPSessionType aSessionType)
+{
+ LOGD(("GMPDecryptorParent[%p]::CreateSession(token=%u, promiseId=%u, aInitData='%s')",
+ this, aCreateSessionToken, aPromiseId, ToBase64(aInitData).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aInitDataType.IsEmpty() && !aInitData.IsEmpty());
+ Unused << SendCreateSession(aCreateSessionToken, aPromiseId, aInitDataType, aInitData, aSessionType);
+}
+
+void
+GMPDecryptorParent::LoadSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::LoadSession(sessionId='%s', promiseId=%u)",
+ this, aSessionId.get(), aPromiseId));
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty());
+ Unused << SendLoadSession(aPromiseId, aSessionId);
+}
+
+void
+GMPDecryptorParent::UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse)
+{
+ LOGD(("GMPDecryptorParent[%p]::UpdateSession(sessionId='%s', promiseId=%u response='%s')",
+ this, aSessionId.get(), aPromiseId, ToBase64(aResponse).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty() && !aResponse.IsEmpty());
+ Unused << SendUpdateSession(aPromiseId, aSessionId, aResponse);
+}
+
+void
+GMPDecryptorParent::CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::CloseSession(sessionId='%s', promiseId=%u)",
+ this, aSessionId.get(), aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty());
+ Unused << SendCloseSession(aPromiseId, aSessionId);
+}
+
+void
+GMPDecryptorParent::RemoveSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::RemoveSession(sessionId='%s', promiseId=%u)",
+ this, aSessionId.get(), aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aSessionId.IsEmpty());
+ Unused << SendRemoveSession(aPromiseId, aSessionId);
+}
+
+void
+GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aServerCert)
+{
+ LOGD(("GMPDecryptorParent[%p]::SetServerCertificate(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+ // Caller should ensure parameters passed in from JS are valid.
+ MOZ_ASSERT(!aServerCert.IsEmpty());
+ Unused << SendSetServerCertificate(aPromiseId, aServerCert);
+}
+
+void
+GMPDecryptorParent::Decrypt(uint32_t aId,
+ const CryptoSample& aCrypto,
+ const nsTArray<uint8_t>& aBuffer)
+{
+ LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return;
+ }
+
+ // Caller should ensure parameters passed in are valid.
+ MOZ_ASSERT(!aBuffer.IsEmpty());
+
+ if (aCrypto.mValid) {
+ GMPDecryptionData data(aCrypto.mKeyId,
+ aCrypto.mIV,
+ aCrypto.mPlainSizes,
+ aCrypto.mEncryptedSizes,
+ aCrypto.mSessionIds);
+
+ Unused << SendDecrypt(aId, aBuffer, data);
+ } else {
+ GMPDecryptionData data;
+ Unused << SendDecrypt(aId, aBuffer, data);
+ }
+}
+
+bool
+GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId,
+ const nsCString& aSessionId)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+ const bool& aSuccess)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId)
+{
+ /* STUB */
+ return true;
+}
+
+nsresult
+GMPExToNsresult(GMPDOMException aDomException) {
+ switch (aDomException) {
+ case kGMPNoModificationAllowedError: return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+ case kGMPNotFoundError: return NS_ERROR_DOM_NOT_FOUND_ERR;
+ case kGMPNotSupportedError: return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ case kGMPInvalidStateError: return NS_ERROR_DOM_INVALID_STATE_ERR;
+ case kGMPSyntaxError: return NS_ERROR_DOM_SYNTAX_ERR;
+ case kGMPInvalidModificationError: return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
+ case kGMPInvalidAccessError: return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ case kGMPSecurityError: return NS_ERROR_DOM_SECURITY_ERR;
+ case kGMPAbortError: return NS_ERROR_DOM_ABORT_ERR;
+ case kGMPQuotaExceededError: return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
+ case kGMPTimeoutError: return NS_ERROR_DOM_TIMEOUT_ERR;
+ case kGMPTypeError: return NS_ERROR_DOM_TYPE_ERR;
+ default: return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+}
+
+bool
+GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId,
+ const GMPDOMException& aException,
+ const nsCString& aMessage)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId,
+ const GMPSessionMessageType& aMessageType,
+ nsTArray<uint8_t>&& aMessage)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId,
+ const double& aExpiryTime)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId,
+ const GMPDOMException& aException,
+ const uint32_t& aSystemCode,
+ const nsCString& aMessage)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
+ const GMPErr& aErr,
+ InfallibleTArray<uint8_t>&& aBuffer)
+{
+ /* STUB */
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvShutdown()
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvShutdown()", this));
+
+ Shutdown();
+ return true;
+}
+
+// Note: may be called via Terminated()
+void
+GMPDecryptorParent::Close()
+{
+ LOGD(("GMPDecryptorParent[%p]::Close()", this));
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPDecryptorParent> kungfudeathgrip(this);
+ this->Release();
+ Shutdown();
+}
+
+void
+GMPDecryptorParent::Shutdown()
+{
+ LOGD(("GMPDecryptorParent[%p]::Shutdown()", this));
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return;
+ }
+ mShuttingDown = true;
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendDecryptingComplete();
+ }
+}
+
+// Note: Keep this sync'd up with Shutdown
+void
+GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPDecryptorParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+
+ mIsOpen = false;
+ mActorDestroyed = true;
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ if (mPlugin) {
+ mPlugin->DecryptorDestroyed(this);
+ mPlugin = nullptr;
+ }
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+bool
+GMPDecryptorParent::Recv__delete__()
+{
+ LOGD(("GMPDecryptorParent[%p]::Recv__delete__()", this));
+
+ if (mPlugin) {
+ mPlugin->DecryptorDestroyed(this);
+ mPlugin = nullptr;
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h
new file mode 100644
index 000000000..30ff24690
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -0,0 +1,129 @@
+/* -*- 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 GMPDecryptorParent_h_
+#define GMPDecryptorParent_h_
+
+#include "mozilla/gmp/PGMPDecryptorParent.h"
+#include "mozilla/RefPtr.h"
+#include "gmp-decryption.h"
+#include "GMPDecryptorProxy.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+
+class CryptoSample;
+
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPDecryptorParent final : public GMPDecryptorProxy
+ , public PGMPDecryptorParent
+ , public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPDecryptorParent)
+
+ explicit GMPDecryptorParent(GMPContentParent *aPlugin);
+
+ // GMPDecryptorProxy
+
+ uint32_t GetPluginId() const override { return mPluginId; }
+
+ nsresult Init(GMPDecryptorProxyCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ GMPSessionType aSessionType) override;
+
+ void LoadSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) override;
+
+ void UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse) override;
+
+ void CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) override;
+
+ void RemoveSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) override;
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aServerCert) override;
+
+ void Decrypt(uint32_t aId,
+ const CryptoSample& aCrypto,
+ const nsTArray<uint8_t>& aBuffer) override;
+
+ void Close() override;
+
+ void Shutdown();
+
+private:
+ ~GMPDecryptorParent();
+
+ // PGMPDecryptorParent
+
+ bool RecvSetDecryptorId(const uint32_t& aId) override;
+
+ bool RecvSetSessionId(const uint32_t& aCreateSessionToken,
+ const nsCString& aSessionId) override;
+
+ bool RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+ const bool& aSuccess) override;
+
+ bool RecvResolvePromise(const uint32_t& aPromiseId) override;
+
+ bool RecvRejectPromise(const uint32_t& aPromiseId,
+ const GMPDOMException& aException,
+ const nsCString& aMessage) override;
+
+ bool RecvSessionMessage(const nsCString& aSessionId,
+ const GMPSessionMessageType& aMessageType,
+ nsTArray<uint8_t>&& aMessage) override;
+
+ bool RecvExpirationChange(const nsCString& aSessionId,
+ const double& aExpiryTime) override;
+
+ bool RecvSessionClosed(const nsCString& aSessionId) override;
+
+ bool RecvSessionError(const nsCString& aSessionId,
+ const GMPDOMException& aException,
+ const uint32_t& aSystemCode,
+ const nsCString& aMessage) override;
+
+ bool RecvDecrypted(const uint32_t& aId,
+ const GMPErr& aErr,
+ InfallibleTArray<uint8_t>&& aBuffer) override;
+
+ bool RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos) override;
+
+ bool RecvShutdown() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool Recv__delete__() override;
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ RefPtr<GMPContentParent> mPlugin;
+ uint32_t mPluginId;
+ GMPDecryptorProxyCallback* mCallback;
+#ifdef DEBUG
+ nsIThread* const mGMPThread;
+#endif
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPDecryptorChild_h_
diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h
new file mode 100644
index 000000000..f9e34a45f
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -0,0 +1,62 @@
+/* -*- 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 GMPDecryptorProxy_h_
+#define GMPDecryptorProxy_h_
+
+#include "GMPCallbackBase.h"
+#include "gmp-decryption.h"
+#include "nsString.h"
+
+namespace mozilla {
+class CryptoSample;
+} // namespace mozilla
+
+class GMPDecryptorProxyCallback : public GMPCallbackBase {
+
+public:
+ virtual ~GMPDecryptorProxyCallback() {}
+};
+
+class GMPDecryptorProxy {
+public:
+ ~GMPDecryptorProxy() {}
+
+ virtual uint32_t GetPluginId() const = 0;
+
+ virtual nsresult Init(GMPDecryptorProxyCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) = 0;
+
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ GMPSessionType aSessionType) = 0;
+
+ virtual void LoadSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse) = 0;
+
+ virtual void CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aServerCert) = 0;
+
+ virtual void Decrypt(uint32_t aId,
+ const mozilla::CryptoSample& aCrypto,
+ const nsTArray<uint8_t>& aBuffer) = 0;
+
+ virtual void Close() = 0;
+};
+
+#endif // GMPDecryptorProxy_h_
diff --git a/dom/media/gmp/GMPDiskStorage.cpp b/dom/media/gmp/GMPDiskStorage.cpp
new file mode 100644
index 000000000..11f49c8fe
--- /dev/null
+++ b/dom/media/gmp/GMPDiskStorage.cpp
@@ -0,0 +1,499 @@
+/* -*- 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 "plhash.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "GMPParent.h"
+#include "gmp-storage.h"
+#include "mozilla/Unused.h"
+#include "mozilla/EndianUtils.h"
+#include "nsClassHashtable.h"
+#include "prio.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+// We store the records for a given GMP as files in the profile dir.
+// $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
+static nsresult
+GetGMPStorageDir(nsIFile** aTempDir,
+ const nsString& aGMPName,
+ const nsCString& aNodeId)
+{
+ if (NS_WARN_IF(!aTempDir)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (NS_WARN_IF(!mps)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = mps->GetStorageDir(getter_AddRefs(tmpFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Append(aGMPName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("storage"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->AppendNative(aNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ tmpFile.forget(aTempDir);
+
+ return NS_OK;
+}
+
+// Disk-backed GMP storage. Records are stored in files on disk in
+// the profile directory. The record name is a hash of the filename,
+// and we resolve hash collisions by just adding 1 to the hash code.
+// The format of records on disk is:
+// 4 byte, uint32_t $recordNameLength, in little-endian byte order,
+// record name (i.e. $recordNameLength bytes, no null terminator)
+// record bytes (entire remainder of file)
+class GMPDiskStorage : public GMPStorage {
+public:
+ explicit GMPDiskStorage(const nsCString& aNodeId,
+ const nsString& aGMPName)
+ : mNodeId(aNodeId)
+ , mGMPName(aGMPName)
+ {
+ }
+
+ ~GMPDiskStorage() {
+ // Close all open file handles.
+ for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ Record* record = iter.UserData();
+ if (record->mFileDesc) {
+ PR_Close(record->mFileDesc);
+ record->mFileDesc = nullptr;
+ }
+ }
+ }
+
+ nsresult Init() {
+ // Build our index of records on disk.
+ nsCOMPtr<nsIFile> storageDir;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DirectoryEnumerator iter(storageDir, DirectoryEnumerator::FilesAndDirs);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ PRFileDesc* fd = nullptr;
+ if (NS_FAILED(dirEntry->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
+ continue;
+ }
+ int32_t recordLength = 0;
+ nsCString recordName;
+ nsresult err = ReadRecordMetadata(fd, recordLength, recordName);
+ PR_Close(fd);
+ if (NS_FAILED(err)) {
+ // File is not a valid storage file. Don't index it. Delete the file,
+ // to make our indexing faster in future.
+ dirEntry->Remove(false);
+ continue;
+ }
+
+ nsAutoString filename;
+ rv = dirEntry->GetLeafName(filename);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ mRecords.Put(recordName, new Record(filename, recordName));
+ }
+
+ return NS_OK;
+ }
+
+ GMPErr Open(const nsCString& aRecordName) override
+ {
+ MOZ_ASSERT(!IsOpen(aRecordName));
+ nsresult rv;
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ // New file.
+ nsAutoString filename;
+ rv = GetUnusedFilename(aRecordName, filename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GMPGenericErr;
+ }
+ record = new Record(filename, aRecordName);
+ mRecords.Put(aRecordName, record);
+ }
+
+ MOZ_ASSERT(record);
+ if (record->mFileDesc) {
+ NS_WARNING("Tried to open already open record");
+ return GMPRecordInUse;
+ }
+
+ rv = OpenStorageFile(record->mFilename, ReadWrite, &record->mFileDesc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(IsOpen(aRecordName));
+
+ return GMPNoErr;
+ }
+
+ bool IsOpen(const nsCString& aRecordName) const override {
+ // We are open if we have a record indexed, and it has a valid
+ // file descriptor.
+ const Record* record = mRecords.Get(aRecordName);
+ return record && !!record->mFileDesc;
+ }
+
+ GMPErr Read(const nsCString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) override
+ {
+ if (!IsOpen(aRecordName)) {
+ return GMPClosedErr;
+ }
+
+ Record* record = nullptr;
+ mRecords.Get(aRecordName, &record);
+ MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
+
+ // Our error strategy is to report records with invalid contents as
+ // containing 0 bytes. Zero length records are considered "deleted" by
+ // the GMPStorage API.
+ aOutBytes.SetLength(0);
+
+ int32_t recordLength = 0;
+ nsCString recordName;
+ nsresult err = ReadRecordMetadata(record->mFileDesc,
+ recordLength,
+ recordName);
+ if (NS_FAILED(err) || recordLength == 0) {
+ // We failed to read the record metadata. Or the record is 0 length.
+ // Treat damaged records as empty.
+ // ReadRecordMetadata() could fail if the GMP opened a new record and
+ // tried to read it before anything was written to it..
+ return GMPNoErr;
+ }
+
+ if (!aRecordName.Equals(recordName)) {
+ NS_WARNING("Record file contains some other record's contents!");
+ return GMPRecordCorrupted;
+ }
+
+ // After calling ReadRecordMetadata, we should be ready to read the
+ // record data.
+ if (PR_Available(record->mFileDesc) != recordLength) {
+ NS_WARNING("Record file length mismatch!");
+ return GMPRecordCorrupted;
+ }
+
+ aOutBytes.SetLength(recordLength);
+ int32_t bytesRead = PR_Read(record->mFileDesc, aOutBytes.Elements(), recordLength);
+ return (bytesRead == recordLength) ? GMPNoErr : GMPRecordCorrupted;
+ }
+
+ GMPErr Write(const nsCString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) override
+ {
+ if (!IsOpen(aRecordName)) {
+ return GMPClosedErr;
+ }
+
+ Record* record = nullptr;
+ mRecords.Get(aRecordName, &record);
+ MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
+
+ // Write operations overwrite the entire record. So close it now.
+ PR_Close(record->mFileDesc);
+ record->mFileDesc = nullptr;
+
+ // Writing 0 bytes means removing (deleting) the file.
+ if (aBytes.Length() == 0) {
+ nsresult rv = RemoveStorageFile(record->mFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Could not delete file -> Continue with trying to erase the contents.
+ } else {
+ return GMPNoErr;
+ }
+ }
+
+ // Write operations overwrite the entire record. So re-open the file
+ // in truncate mode, to clear its contents.
+ if (NS_FAILED(OpenStorageFile(record->mFilename,
+ Truncate,
+ &record->mFileDesc))) {
+ return GMPGenericErr;
+ }
+
+ // Store the length of the record name followed by the record name
+ // at the start of the file.
+ int32_t bytesWritten = 0;
+ char buf[sizeof(uint32_t)] = {0};
+ LittleEndian::writeUint32(buf, aRecordName.Length());
+ bytesWritten = PR_Write(record->mFileDesc, buf, MOZ_ARRAY_LENGTH(buf));
+ if (bytesWritten != MOZ_ARRAY_LENGTH(buf)) {
+ NS_WARNING("Failed to write GMPStorage record name length.");
+ return GMPRecordCorrupted;
+ }
+ bytesWritten = PR_Write(record->mFileDesc,
+ aRecordName.get(),
+ aRecordName.Length());
+ if (bytesWritten != (int32_t)aRecordName.Length()) {
+ NS_WARNING("Failed to write GMPStorage record name.");
+ return GMPRecordCorrupted;
+ }
+
+ bytesWritten = PR_Write(record->mFileDesc, aBytes.Elements(), aBytes.Length());
+ if (bytesWritten != (int32_t)aBytes.Length()) {
+ NS_WARNING("Failed to write GMPStorage record data.");
+ return GMPRecordCorrupted;
+ }
+
+ // Try to sync the file to disk, so that in the event of a crash,
+ // the record is less likely to be corrupted.
+ PR_Sync(record->mFileDesc);
+
+ return GMPNoErr;
+ }
+
+ GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const override
+ {
+ for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ aOutRecordNames.AppendElement(iter.UserData()->mRecordName);
+ }
+ return GMPNoErr;
+ }
+
+ void Close(const nsCString& aRecordName) override
+ {
+ Record* record = nullptr;
+ mRecords.Get(aRecordName, &record);
+ if (record && !!record->mFileDesc) {
+ PR_Close(record->mFileDesc);
+ record->mFileDesc = nullptr;
+ }
+ MOZ_ASSERT(!IsOpen(aRecordName));
+ }
+
+private:
+
+ // We store records in a file which is a hash of the record name.
+ // If there is a hash collision, we just keep adding 1 to the hash
+ // code, until we find a free slot.
+ nsresult GetUnusedFilename(const nsACString& aRecordName,
+ nsString& aOutFilename)
+ {
+ nsCOMPtr<nsIFile> storageDir;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint64_t recordNameHash = HashString(PromiseFlatCString(aRecordName).get());
+ for (int i = 0; i < 1000000; i++) {
+ nsCOMPtr<nsIFile> f;
+ rv = storageDir->Clone(getter_AddRefs(f));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsAutoString hashStr;
+ hashStr.AppendInt(recordNameHash);
+ rv = f->Append(hashStr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ bool exists = false;
+ f->Exists(&exists);
+ if (!exists) {
+ // Filename not in use, we can write into this file.
+ aOutFilename = hashStr;
+ return NS_OK;
+ } else {
+ // Hash collision; just increment the hash name and try that again.
+ ++recordNameHash;
+ continue;
+ }
+ }
+ // Somehow, we've managed to completely fail to find a vacant file name.
+ // Give up.
+ NS_WARNING("GetUnusedFilename had extreme hash collision!");
+ return NS_ERROR_FAILURE;
+ }
+
+ enum OpenFileMode { ReadWrite, Truncate };
+
+ nsresult OpenStorageFile(const nsAString& aFileLeafName,
+ const OpenFileMode aMode,
+ PRFileDesc** aOutFD)
+ {
+ MOZ_ASSERT(aOutFD);
+
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ f->Append(aFileLeafName);
+
+ auto mode = PR_RDWR | PR_CREATE_FILE;
+ if (aMode == Truncate) {
+ mode |= PR_TRUNCATE;
+ }
+
+ return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
+ }
+
+ nsresult ReadRecordMetadata(PRFileDesc* aFd,
+ int32_t& aOutRecordLength,
+ nsACString& aOutRecordName)
+ {
+ int32_t offset = PR_Seek(aFd, 0, PR_SEEK_END);
+ PR_Seek(aFd, 0, PR_SEEK_SET);
+
+ if (offset < 0 || offset > GMP_MAX_RECORD_SIZE) {
+ // Refuse to read big records, or records where we can't get a length.
+ return NS_ERROR_FAILURE;
+ }
+ const uint32_t fileLength = static_cast<uint32_t>(offset);
+
+ // At the start of the file the length of the record name is stored in a
+ // uint32_t (little endian byte order) followed by the record name at the
+ // start of the file. The record name is not null terminated. The remainder
+ // of the file is the record's data.
+
+ if (fileLength < sizeof(uint32_t)) {
+ // Record file doesn't have enough contents to store the record name
+ // length. Fail.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Read length, and convert to host byte order.
+ uint32_t recordNameLength = 0;
+ char buf[sizeof(recordNameLength)] = { 0 };
+ int32_t bytesRead = PR_Read(aFd, &buf, sizeof(recordNameLength));
+ recordNameLength = LittleEndian::readUint32(buf);
+ if (sizeof(recordNameLength) != bytesRead ||
+ recordNameLength == 0 ||
+ recordNameLength + sizeof(recordNameLength) > fileLength ||
+ recordNameLength > GMP_MAX_RECORD_NAME_SIZE) {
+ // Record file has invalid contents. Fail.
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString recordName;
+ recordName.SetLength(recordNameLength);
+ bytesRead = PR_Read(aFd, recordName.BeginWriting(), recordNameLength);
+ if ((uint32_t)bytesRead != recordNameLength) {
+ // Read failed.
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(fileLength >= sizeof(recordNameLength) + recordNameLength);
+ int32_t recordLength = fileLength - (sizeof(recordNameLength) + recordNameLength);
+
+ aOutRecordLength = recordLength;
+ aOutRecordName = recordName;
+
+ // Read cursor should be positioned after the record name, before the record contents.
+ if (PR_Seek(aFd, 0, PR_SEEK_CUR) != (int32_t)(sizeof(recordNameLength) + recordNameLength)) {
+ NS_WARNING("Read cursor mismatch after ReadRecordMetadata()");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ nsresult RemoveStorageFile(const nsString& aFilename)
+ {
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = f->Append(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return f->Remove(/* bool recursive= */ false);
+ }
+
+ struct Record {
+ Record(const nsAString& aFilename,
+ const nsACString& aRecordName)
+ : mFilename(aFilename)
+ , mRecordName(aRecordName)
+ , mFileDesc(0)
+ {}
+ ~Record() {
+ MOZ_ASSERT(!mFileDesc);
+ }
+ nsString mFilename;
+ nsCString mRecordName;
+ PRFileDesc* mFileDesc;
+ };
+
+ // Hash record name to record data.
+ nsClassHashtable<nsCStringHashKey, Record> mRecords;
+ const nsCString mNodeId;
+ const nsString mGMPName;
+};
+
+already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId,
+ const nsString& aGMPName)
+{
+ RefPtr<GMPDiskStorage> storage(new GMPDiskStorage(aNodeId, aGMPName));
+ if (NS_FAILED(storage->Init())) {
+ NS_WARNING("Failed to initialize on disk GMP storage");
+ return nullptr;
+ }
+ return storage.forget();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp
new file mode 100644
index 000000000..206d7b42a
--- /dev/null
+++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp
@@ -0,0 +1,132 @@
+/* -*- 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 "GMPEncryptedBufferDataImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "MediaData.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto)
+ : mKeyId(aCrypto.mKeyId)
+ , mIV(aCrypto.mIV)
+ , mClearBytes(aCrypto.mPlainSizes)
+ , mCipherBytes(aCrypto.mEncryptedSizes)
+ , mSessionIdList(aCrypto.mSessionIds)
+{
+}
+
+GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData)
+ : mKeyId(aData.mKeyId())
+ , mIV(aData.mIV())
+ , mClearBytes(aData.mClearBytes())
+ , mCipherBytes(aData.mCipherBytes())
+ , mSessionIdList(aData.mSessionIds())
+{
+ MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length());
+}
+
+GMPEncryptedBufferDataImpl::~GMPEncryptedBufferDataImpl()
+{
+}
+
+void
+GMPEncryptedBufferDataImpl::RelinquishData(GMPDecryptionData& aData)
+{
+ aData.mKeyId() = Move(mKeyId);
+ aData.mIV() = Move(mIV);
+ aData.mClearBytes() = Move(mClearBytes);
+ aData.mCipherBytes() = Move(mCipherBytes);
+ mSessionIdList.RelinquishData(aData.mSessionIds());
+}
+
+const uint8_t*
+GMPEncryptedBufferDataImpl::KeyId() const
+{
+ return mKeyId.Elements();
+}
+
+uint32_t
+GMPEncryptedBufferDataImpl::KeyIdSize() const
+{
+ return mKeyId.Length();
+}
+
+const uint8_t*
+GMPEncryptedBufferDataImpl::IV() const
+{
+ return mIV.Elements();
+}
+
+uint32_t
+GMPEncryptedBufferDataImpl::IVSize() const
+{
+ return mIV.Length();
+}
+
+const uint16_t*
+GMPEncryptedBufferDataImpl::ClearBytes() const
+{
+ return mClearBytes.Elements();
+}
+
+const uint32_t*
+GMPEncryptedBufferDataImpl::CipherBytes() const
+{
+ return mCipherBytes.Elements();
+}
+
+const GMPStringList*
+GMPEncryptedBufferDataImpl::SessionIds() const
+{
+ return &mSessionIdList;
+}
+
+uint32_t
+GMPEncryptedBufferDataImpl::NumSubsamples() const
+{
+ MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length());
+ // Return the min of the two, to ensure there's not chance of array index
+ // out-of-bounds shenanigans.
+ return std::min<uint32_t>(mClearBytes.Length(), mCipherBytes.Length());
+}
+
+GMPStringListImpl::GMPStringListImpl(const nsTArray<nsCString>& aStrings)
+ : mStrings(aStrings)
+{
+}
+
+uint32_t
+GMPStringListImpl::Size() const
+{
+ return mStrings.Length();
+}
+
+void
+GMPStringListImpl::StringAt(uint32_t aIndex,
+ const char** aOutString,
+ uint32_t *aOutLength) const
+{
+ if (NS_WARN_IF(aIndex >= Size())) {
+ return;
+ }
+
+ *aOutString = mStrings[aIndex].BeginReading();
+ *aOutLength = mStrings[aIndex].Length();
+}
+
+void
+GMPStringListImpl::RelinquishData(nsTArray<nsCString>& aStrings)
+{
+ aStrings = Move(mStrings);
+}
+
+GMPStringListImpl::~GMPStringListImpl()
+{
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.h b/dom/media/gmp/GMPEncryptedBufferDataImpl.h
new file mode 100644
index 000000000..f0d5728df
--- /dev/null
+++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.h
@@ -0,0 +1,92 @@
+/* -*- 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 GMPEncryptedBufferDataImpl_h_
+#define GMPEncryptedBufferDataImpl_h_
+
+#include "gmp-decryption.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+class CryptoSample;
+
+namespace gmp {
+
+class GMPStringListImpl : public GMPStringList
+{
+public:
+ explicit GMPStringListImpl(const nsTArray<nsCString>& aStrings);
+ uint32_t Size() const override;
+ void StringAt(uint32_t aIndex,
+ const char** aOutString, uint32_t *aOutLength) const override;
+ virtual ~GMPStringListImpl() override;
+ void RelinquishData(nsTArray<nsCString>& aStrings);
+
+private:
+ nsTArray<nsCString> mStrings;
+};
+
+class GMPEncryptedBufferDataImpl : public GMPEncryptedBufferMetadata {
+public:
+ explicit GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto);
+ explicit GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData);
+ virtual ~GMPEncryptedBufferDataImpl();
+
+ void RelinquishData(GMPDecryptionData& aData);
+
+ const uint8_t* KeyId() const override;
+ uint32_t KeyIdSize() const override;
+ const uint8_t* IV() const override;
+ uint32_t IVSize() const override;
+ uint32_t NumSubsamples() const override;
+ const uint16_t* ClearBytes() const override;
+ const uint32_t* CipherBytes() const override;
+ const GMPStringList* SessionIds() const override;
+
+private:
+ nsTArray<uint8_t> mKeyId;
+ nsTArray<uint8_t> mIV;
+ nsTArray<uint16_t> mClearBytes;
+ nsTArray<uint32_t> mCipherBytes;
+
+ GMPStringListImpl mSessionIdList;
+};
+
+class GMPBufferImpl : public GMPBuffer {
+public:
+ GMPBufferImpl(uint32_t aId, const nsTArray<uint8_t>& aData)
+ : mId(aId)
+ , mData(aData)
+ {
+ }
+ uint32_t Id() const override {
+ return mId;
+ }
+ uint8_t* Data() override {
+ return mData.Elements();
+ }
+ uint32_t Size() const override {
+ return mData.Length();
+ }
+ void Resize(uint32_t aSize) override {
+ mData.SetLength(aSize);
+ }
+
+ // Set metadata object to be freed when this buffer is destroyed.
+ void SetMetadata(GMPEncryptedBufferDataImpl* aMetadata) {
+ mMetadata = aMetadata;
+ }
+
+ uint32_t mId;
+ nsTArray<uint8_t> mData;
+ nsAutoPtr<GMPEncryptedBufferDataImpl> mMetadata;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPEncryptedBufferDataImpl_h_
diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp
new file mode 100644
index 000000000..0bccdd0b1
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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 "GMPLoader.h"
+#include <stdio.h>
+#include "mozilla/Attributes.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+#include "prenv.h"
+
+#include <string>
+
+#ifdef XP_WIN
+#include "windows.h"
+#endif
+
+#include "GMPDeviceBinding.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPLoaderImpl : public GMPLoader {
+public:
+ explicit GMPLoaderImpl(SandboxStarter* aStarter)
+ : mSandboxStarter(aStarter)
+ , mAdapter(nullptr)
+ {}
+ virtual ~GMPLoaderImpl() {}
+
+ bool Load(const char* aUTF8LibPath,
+ uint32_t aUTF8LibPathLen,
+ char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ const GMPPlatformAPI* aPlatformAPI,
+ GMPAdapter* aAdapter) override;
+
+ GMPErr GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override;
+
+ void Shutdown() override;
+
+private:
+ SandboxStarter* mSandboxStarter;
+ UniquePtr<GMPAdapter> mAdapter;
+};
+
+UniquePtr<GMPLoader> CreateGMPLoader(SandboxStarter* aStarter) {
+ return MakeUnique<GMPLoaderImpl>(aStarter);
+}
+
+class PassThroughGMPAdapter : public GMPAdapter {
+public:
+ ~PassThroughGMPAdapter() {
+ // Ensure we're always shutdown, even if caller forgets to call GMPShutdown().
+ GMPShutdown();
+ }
+
+ void SetAdaptee(PRLibrary* aLib) override
+ {
+ mLib = aLib;
+ }
+
+ GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override
+ {
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+ GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
+ if (!initFunc) {
+ return GMPNotImplementedErr;
+ }
+ return initFunc(aPlatformAPI);
+ }
+
+ GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override
+ {
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+ GMPGetAPIFunc getapiFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI"));
+ if (!getapiFunc) {
+ return GMPNotImplementedErr;
+ }
+ return getapiFunc(aAPIName, aHostAPI, aPluginAPI);
+ }
+
+ void GMPShutdown() override
+ {
+ if (mLib) {
+ GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown"));
+ if (shutdownFunc) {
+ shutdownFunc();
+ }
+ PR_UnloadLibrary(mLib);
+ mLib = nullptr;
+ }
+ }
+
+ void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override
+ {
+ if (!mLib) {
+ return;
+ }
+ GMPSetNodeIdFunc setNodeIdFunc = reinterpret_cast<GMPSetNodeIdFunc>(PR_FindFunctionSymbol(mLib, "GMPSetNodeId"));
+ if (setNodeIdFunc) {
+ setNodeIdFunc(aNodeId, aLength);
+ }
+ }
+
+private:
+ PRLibrary* mLib = nullptr;
+};
+
+bool
+GMPLoaderImpl::Load(const char* aUTF8LibPath,
+ uint32_t aUTF8LibPathLen,
+ char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ const GMPPlatformAPI* aPlatformAPI,
+ GMPAdapter* aAdapter)
+{
+ std::string nodeId;
+ if (!CalculateGMPDeviceId(aOriginSalt, aOriginSaltLen, nodeId)) {
+ return false;
+ }
+
+ // Start the sandbox now that we've generated the device bound node id.
+ // This must happen after the node id is bound to the device id, as
+ // generating the device id requires privileges.
+ if (mSandboxStarter && !mSandboxStarter->Start(aUTF8LibPath)) {
+ return false;
+ }
+
+ // Load the GMP.
+ PRLibSpec libSpec;
+#ifdef XP_WIN
+ int pathLen = MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, nullptr, 0);
+ if (pathLen == 0) {
+ return false;
+ }
+
+ auto widePath = MakeUnique<wchar_t[]>(pathLen);
+ if (MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, widePath.get(), pathLen) == 0) {
+ return false;
+ }
+
+ libSpec.value.pathname_u = widePath.get();
+ libSpec.type = PR_LibSpec_PathnameU;
+#else
+ libSpec.value.pathname = aUTF8LibPath;
+ libSpec.type = PR_LibSpec_Pathname;
+#endif
+ PRLibrary* lib = PR_LoadLibraryWithFlags(libSpec, 0);
+ if (!lib) {
+ return false;
+ }
+
+ GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(lib, "GMPInit"));
+ if ((initFunc && aAdapter) ||
+ (!initFunc && !aAdapter)) {
+ // Ensure that if we're dealing with a GMP we do *not* use an adapter
+ // provided from the outside world. This is important as it means we
+ // don't call code not covered by Adobe's plugin-container voucher
+ // before we pass the node Id to Adobe's GMP.
+ return false;
+ }
+
+ // Note: PassThroughGMPAdapter's code must remain in this file so that it's
+ // covered by Adobe's plugin-container voucher.
+ mAdapter.reset((!aAdapter) ? new PassThroughGMPAdapter() : aAdapter);
+ mAdapter->SetAdaptee(lib);
+
+ if (mAdapter->GMPInit(aPlatformAPI) != GMPNoErr) {
+ return false;
+ }
+
+ mAdapter->GMPSetNodeId(nodeId.c_str(), nodeId.size());
+
+ return true;
+}
+
+GMPErr
+GMPLoaderImpl::GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ return mAdapter->GMPGetAPI(aAPIName, aHostAPI, aPluginAPI, aDecryptorId);
+}
+
+void
+GMPLoaderImpl::Shutdown()
+{
+ if (mAdapter) {
+ mAdapter->GMPShutdown();
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
diff --git a/dom/media/gmp/GMPLoader.h b/dom/media/gmp/GMPLoader.h
new file mode 100644
index 000000000..8e6b3cfac
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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_LOADER_H__
+#define GMP_LOADER_H__
+
+#include <stdint.h>
+#include "prlink.h"
+#include "gmp-entrypoints.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace gmp {
+
+class SandboxStarter {
+public:
+ virtual ~SandboxStarter() {}
+ virtual bool Start(const char* aLibPath) = 0;
+};
+
+// Interface that adapts a plugin to the GMP API.
+class GMPAdapter {
+public:
+ virtual ~GMPAdapter() {}
+ // Sets the adapted to plugin library module.
+ // Note: the GMPAdapter is responsible for calling PR_UnloadLibrary on aLib
+ // when it's finished with it.
+ virtual void SetAdaptee(PRLibrary* aLib) = 0;
+
+ // These are called in place of the corresponding GMP API functions.
+ virtual GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) = 0;
+ virtual GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) = 0;
+ virtual void GMPShutdown() = 0;
+ virtual void GMPSetNodeId(const char* aNodeId, uint32_t aLength) = 0;
+};
+
+// Encapsulates generating the device-bound node id, activating the sandbox,
+// loading the GMP, and passing the node id to the GMP (in that order).
+//
+// In Desktop Gecko, the implementation of this lives in plugin-container,
+// and is passed into XUL code from on startup. The GMP IPC child protocol actor
+// uses this interface to load and retrieve interfaces from the GMPs.
+//
+// In Desktop Gecko the implementation lives in the plugin-container so that
+// it can be covered by DRM vendor's voucher.
+//
+// On Android the GMPLoader implementation lives in libxul (because for the time
+// being GMPLoader relies upon NSPR, which we can't use in plugin-container
+// on Android).
+//
+// There is exactly one GMPLoader per GMP child process, and only one GMP
+// per child process (so the GMPLoader only loads one GMP).
+//
+// Load() takes an optional GMPAdapter which can be used to adapt non-GMPs
+// to adhere to the GMP API.
+class GMPLoader {
+public:
+ virtual ~GMPLoader() {}
+
+ // Calculates the device-bound node id, then activates the sandbox,
+ // then loads the GMP library and (if applicable) passes the bound node id
+ // to the GMP. If aAdapter is non-null, the lib path is assumed to be
+ // a non-GMP, and the adapter is initialized with the lib and the adapter
+ // is used to interact with the plugin.
+ virtual bool Load(const char* aUTF8LibPath,
+ uint32_t aLibPathLen,
+ char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ const GMPPlatformAPI* aPlatformAPI,
+ GMPAdapter* aAdapter = nullptr) = 0;
+
+ // Retrieves an interface pointer from the GMP.
+ virtual GMPErr GetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) = 0;
+
+ // Calls the GMPShutdown function exported by the GMP lib, and unloads the
+ // plugin library.
+ virtual void Shutdown() = 0;
+};
+
+// On Desktop, this function resides in plugin-container.
+// On Mobile, this function resides in XUL.
+UniquePtr<GMPLoader> CreateGMPLoader(SandboxStarter* aStarter);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMP_LOADER_H__
diff --git a/dom/media/gmp/GMPMemoryStorage.cpp b/dom/media/gmp/GMPMemoryStorage.cpp
new file mode 100644
index 000000000..dc799caa1
--- /dev/null
+++ b/dom/media/gmp/GMPMemoryStorage.cpp
@@ -0,0 +1,95 @@
+/* -*- 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 "GMPStorage.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPMemoryStorage : public GMPStorage {
+public:
+ GMPErr Open(const nsCString& aRecordName) override
+ {
+ MOZ_ASSERT(!IsOpen(aRecordName));
+
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ record = new Record();
+ mRecords.Put(aRecordName, record);
+ }
+ record->mIsOpen = true;
+ return GMPNoErr;
+ }
+
+ bool IsOpen(const nsCString& aRecordName) const override {
+ const Record* record = mRecords.Get(aRecordName);
+ if (!record) {
+ return false;
+ }
+ return record->mIsOpen;
+ }
+
+ GMPErr Read(const nsCString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) override
+ {
+ const Record* record = mRecords.Get(aRecordName);
+ if (!record) {
+ return GMPGenericErr;
+ }
+ aOutBytes = record->mData;
+ return GMPNoErr;
+ }
+
+ GMPErr Write(const nsCString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) override
+ {
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ return GMPClosedErr;
+ }
+ record->mData = aBytes;
+ return GMPNoErr;
+ }
+
+ GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const override
+ {
+ for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ aOutRecordNames.AppendElement(iter.Key());
+ }
+ return GMPNoErr;
+ }
+
+ void Close(const nsCString& aRecordName) override
+ {
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ return;
+ }
+ if (!record->mData.Length()) {
+ // Record is empty, delete.
+ mRecords.Remove(aRecordName);
+ } else {
+ record->mIsOpen = false;
+ }
+ }
+
+private:
+
+ struct Record {
+ nsTArray<uint8_t> mData;
+ bool mIsOpen = false;
+ };
+
+ nsClassHashtable<nsCStringHashKey, Record> mRecords;
+};
+
+already_AddRefed<GMPStorage> CreateGMPMemoryStorage()
+{
+ return RefPtr<GMPStorage>(new GMPMemoryStorage()).forget();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPMessageUtils.h b/dom/media/gmp/GMPMessageUtils.h
new file mode 100644
index 000000000..13f6127f3
--- /dev/null
+++ b/dom/media/gmp/GMPMessageUtils.h
@@ -0,0 +1,253 @@
+/* -*- 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 GMPMessageUtils_h_
+#define GMPMessageUtils_h_
+
+#include "gmp-video-codec.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-audio-codec.h"
+#include "gmp-decryption.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<GMPErr>
+: public ContiguousEnumSerializer<GMPErr,
+ GMPNoErr,
+ GMPLastErr>
+{};
+
+struct GMPDomExceptionValidator {
+ static bool IsLegalValue(GMPDOMException aValue) {
+ switch (aValue) {
+ case kGMPNoModificationAllowedError:
+ case kGMPNotFoundError:
+ case kGMPNotSupportedError:
+ case kGMPInvalidStateError:
+ case kGMPSyntaxError:
+ case kGMPInvalidModificationError:
+ case kGMPInvalidAccessError:
+ case kGMPSecurityError:
+ case kGMPAbortError:
+ case kGMPQuotaExceededError:
+ case kGMPTimeoutError:
+ case kGMPTypeError:
+ return true;
+ default:
+ return false;
+ }
+ }
+};
+
+template <>
+struct ParamTraits<GMPVideoFrameType>
+: public ContiguousEnumSerializer<GMPVideoFrameType,
+ kGMPKeyFrame,
+ kGMPVideoFrameInvalid>
+{};
+
+template<>
+struct ParamTraits<GMPDOMException>
+: public EnumSerializer<GMPDOMException, GMPDomExceptionValidator>
+{};
+
+template <>
+struct ParamTraits<GMPSessionMessageType>
+: public ContiguousEnumSerializer<GMPSessionMessageType,
+ kGMPLicenseRequest,
+ kGMPMessageInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPMediaKeyStatus>
+: public ContiguousEnumSerializer<GMPMediaKeyStatus,
+ kGMPUsable,
+ kGMPMediaKeyStatusInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPSessionType>
+: public ContiguousEnumSerializer<GMPSessionType,
+ kGMPTemporySession,
+ kGMPSessionInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPAudioCodecType>
+: public ContiguousEnumSerializer<GMPAudioCodecType,
+ kGMPAudioCodecAAC,
+ kGMPAudioCodecInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecComplexity>
+: public ContiguousEnumSerializer<GMPVideoCodecComplexity,
+ kGMPComplexityNormal,
+ kGMPComplexityInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVP8ResilienceMode>
+: public ContiguousEnumSerializer<GMPVP8ResilienceMode,
+ kResilienceOff,
+ kResilienceInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecType>
+: public ContiguousEnumSerializer<GMPVideoCodecType,
+ kGMPVideoCodecVP8,
+ kGMPVideoCodecInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecMode>
+: public ContiguousEnumSerializer<GMPVideoCodecMode,
+ kGMPRealtimeVideo,
+ kGMPCodecModeInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPBufferType>
+: public ContiguousEnumSerializer<GMPBufferType,
+ GMP_BufferSingle,
+ GMP_BufferInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPSimulcastStream>
+{
+ typedef GMPSimulcastStream paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ WriteParam(aMsg, aParam.mNumberOfTemporalLayers);
+ WriteParam(aMsg, aParam.mMaxBitrate);
+ WriteParam(aMsg, aParam.mTargetBitrate);
+ WriteParam(aMsg, aParam.mMinBitrate);
+ WriteParam(aMsg, aParam.mQPMax);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ if (ReadParam(aMsg, aIter, &(aResult->mWidth)) &&
+ ReadParam(aMsg, aIter, &(aResult->mHeight)) &&
+ ReadParam(aMsg, aIter, &(aResult->mNumberOfTemporalLayers)) &&
+ ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) &&
+ ReadParam(aMsg, aIter, &(aResult->mTargetBitrate)) &&
+ ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) &&
+ ReadParam(aMsg, aIter, &(aResult->mQPMax))) {
+ return true;
+ }
+ return false;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ aLog->append(StringPrintf(L"[%u, %u, %u, %u, %u, %u, %u]", aParam.mWidth, aParam.mHeight,
+ aParam.mNumberOfTemporalLayers, aParam.mMaxBitrate,
+ aParam.mTargetBitrate, aParam.mMinBitrate, aParam.mQPMax));
+ }
+};
+
+template <>
+struct ParamTraits<GMPVideoCodec>
+{
+ typedef GMPVideoCodec paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mGMPApiVersion);
+ WriteParam(aMsg, aParam.mCodecType);
+ WriteParam(aMsg, static_cast<const nsCString&>(nsDependentCString(aParam.mPLName)));
+ WriteParam(aMsg, aParam.mPLType);
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ WriteParam(aMsg, aParam.mStartBitrate);
+ WriteParam(aMsg, aParam.mMaxBitrate);
+ WriteParam(aMsg, aParam.mMinBitrate);
+ WriteParam(aMsg, aParam.mMaxFramerate);
+ WriteParam(aMsg, aParam.mFrameDroppingOn);
+ WriteParam(aMsg, aParam.mKeyFrameInterval);
+ WriteParam(aMsg, aParam.mQPMax);
+ WriteParam(aMsg, aParam.mNumberOfSimulcastStreams);
+ for (uint32_t i = 0; i < aParam.mNumberOfSimulcastStreams; i++) {
+ WriteParam(aMsg, aParam.mSimulcastStream[i]);
+ }
+ WriteParam(aMsg, aParam.mMode);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ // NOTE: make sure this matches any versions supported
+ if (!ReadParam(aMsg, aIter, &(aResult->mGMPApiVersion)) ||
+ aResult->mGMPApiVersion != kGMPVersion33) {
+ return false;
+ }
+ if (!ReadParam(aMsg, aIter, &(aResult->mCodecType))) {
+ return false;
+ }
+
+ nsAutoCString plName;
+ if (!ReadParam(aMsg, aIter, &plName) ||
+ plName.Length() > kGMPPayloadNameSize - 1) {
+ return false;
+ }
+ memcpy(aResult->mPLName, plName.get(), plName.Length());
+ memset(aResult->mPLName + plName.Length(), 0, kGMPPayloadNameSize - plName.Length());
+
+ if (!ReadParam(aMsg, aIter, &(aResult->mPLType)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mWidth)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mHeight)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mStartBitrate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mMaxFramerate)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mFrameDroppingOn)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mKeyFrameInterval))) {
+ return false;
+ }
+
+ if (!ReadParam(aMsg, aIter, &(aResult->mQPMax)) ||
+ !ReadParam(aMsg, aIter, &(aResult->mNumberOfSimulcastStreams))) {
+ return false;
+ }
+
+ if (aResult->mNumberOfSimulcastStreams > kGMPMaxSimulcastStreams) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < aResult->mNumberOfSimulcastStreams; i++) {
+ if (!ReadParam(aMsg, aIter, &(aResult->mSimulcastStream[i]))) {
+ return false;
+ }
+ }
+
+ if (!ReadParam(aMsg, aIter, &(aResult->mMode))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ const char* codecName = nullptr;
+ if (aParam.mCodecType == kGMPVideoCodecVP8) {
+ codecName = "VP8";
+ }
+ aLog->append(StringPrintf(L"[%s, %u, %u]",
+ codecName,
+ aParam.mWidth,
+ aParam.mHeight));
+ }
+};
+
+} // namespace IPC
+
+#endif // GMPMessageUtils_h_
diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp
new file mode 100644
index 000000000..11dbc6b96
--- /dev/null
+++ b/dom/media/gmp/GMPParent.cpp
@@ -0,0 +1,927 @@
+/* -*- 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 "GMPParent.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIRunnable.h"
+#include "nsIWritablePropertyBag2.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/SSE.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
+#include "nsIObserverService.h"
+#include "GMPTimerParent.h"
+#include "runnable_utils.h"
+#include "GMPContentParent.h"
+#include "MediaPrefs.h"
+#include "VideoUtils.h"
+
+using mozilla::ipc::GeckoChildProcessHost;
+
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla {
+
+#undef LOG
+#undef LOGD
+
+extern LogModule* GetGMPLog();
+#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
+#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPParent"
+
+namespace gmp {
+
+GMPParent::GMPParent()
+ : mState(GMPStateNotLoaded)
+ , mProcess(nullptr)
+ , mDeleteProcessOnlyOnUnload(false)
+ , mAbnormalShutdownInProgress(false)
+ , mIsBlockingDeletion(false)
+ , mCanDecrypt(false)
+ , mGMPContentChildCount(0)
+ , mAsyncShutdownRequired(false)
+ , mAsyncShutdownInProgress(false)
+ , mChildPid(0)
+ , mHoldingSelfRef(false)
+{
+ mPluginId = GeckoChildProcessHost::GetUniqueID();
+ LOGD("GMPParent ctor id=%u", mPluginId);
+}
+
+GMPParent::~GMPParent()
+{
+ LOGD("GMPParent dtor id=%u", mPluginId);
+ MOZ_ASSERT(!mProcess);
+}
+
+nsresult
+GMPParent::CloneFrom(const GMPParent* aOther)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
+
+ mService = aOther->mService;
+ mDirectory = aOther->mDirectory;
+ mName = aOther->mName;
+ mVersion = aOther->mVersion;
+ mDescription = aOther->mDescription;
+ mDisplayName = aOther->mDisplayName;
+#ifdef XP_WIN
+ mLibs = aOther->mLibs;
+#endif
+ for (const GMPCapability& cap : aOther->mCapabilities) {
+ mCapabilities.AppendElement(cap);
+ }
+ mAdapter = aOther->mAdapter;
+ return NS_OK;
+}
+
+RefPtr<GenericPromise>
+GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir)
+{
+ MOZ_ASSERT(aPluginDir);
+ MOZ_ASSERT(aService);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ mService = aService;
+ mDirectory = aPluginDir;
+
+ // aPluginDir is <profile-dir>/<gmp-plugin-id>/<version>
+ // where <gmp-plugin-id> should be gmp-gmpopenh264
+ nsCOMPtr<nsIFile> parent;
+ nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ nsAutoString parentLeafName;
+ rv = parent->GetLeafName(parentLeafName);
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get());
+
+ MOZ_ASSERT(parentLeafName.Length() > 4);
+ mName = Substring(parentLeafName, 4);
+
+ return ReadGMPMetaData();
+}
+
+void
+GMPParent::Crash()
+{
+ if (mState != GMPStateNotLoaded) {
+ Unused << SendCrashPluginNow();
+ }
+}
+
+nsresult
+GMPParent::LoadProcess()
+{
+ MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(mState == GMPStateNotLoaded);
+
+ nsAutoString path;
+ if (NS_FAILED(mDirectory->GetPath(path))) {
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get());
+
+ if (!mProcess) {
+ mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
+ if (!mProcess->Launch(30 * 1000)) {
+ LOGD("%s: Failed to launch new child process", __FUNCTION__);
+ mProcess->Delete();
+ mProcess = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mChildPid = base::GetProcId(mProcess->GetChildProcessHandle());
+ LOGD("%s: Launched new child process", __FUNCTION__);
+
+ bool opened = Open(mProcess->GetChannel(),
+ base::GetProcId(mProcess->GetChildProcessHandle()));
+ if (!opened) {
+ LOGD("%s: Failed to open channel to new child process", __FUNCTION__);
+ mProcess->Delete();
+ mProcess = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Opened channel to new child process", __FUNCTION__);
+
+ bool ok = SendSetNodeId(mNodeId);
+ if (!ok) {
+ LOGD("%s: Failed to send node id to child process", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Sent node id to child process", __FUNCTION__);
+
+#ifdef XP_WIN
+ if (!mLibs.IsEmpty()) {
+ bool ok = SendPreloadLibs(mLibs);
+ if (!ok) {
+ LOGD("%s: Failed to send preload-libs to child process", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Sent preload-libs ('%s') to child process", __FUNCTION__, mLibs.get());
+ }
+#endif
+
+ // Intr call to block initialization on plugin load.
+ ok = CallStartPlugin(mAdapter);
+ if (!ok) {
+ LOGD("%s: Failed to send start to child process", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ LOGD("%s: Sent StartPlugin to child process", __FUNCTION__);
+ }
+
+ mState = GMPStateLoaded;
+
+ // Hold a self ref while the child process is alive. This ensures that
+ // during shutdown the GMPParent stays alive long enough to
+ // terminate the child process.
+ MOZ_ASSERT(!mHoldingSelfRef);
+ mHoldingSelfRef = true;
+ AddRef();
+
+ return NS_OK;
+}
+
+// static
+void
+GMPParent::AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure)
+{
+ NS_WARNING("Timed out waiting for GMP async shutdown!");
+ GMPParent* parent = reinterpret_cast<GMPParent*>(aClosure);
+ MOZ_ASSERT(parent->mService);
+ parent->mService->AsyncShutdownComplete(parent);
+}
+
+nsresult
+GMPParent::EnsureAsyncShutdownTimeoutSet()
+{
+ MOZ_ASSERT(mAsyncShutdownRequired);
+ if (mAsyncShutdownTimeout) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ mAsyncShutdownTimeout = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Set timer to abort waiting for plugin to shutdown if it takes
+ // too long.
+ rv = mAsyncShutdownTimeout->SetTarget(mGMPThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t timeout = MediaPrefs::GMPAsyncShutdownTimeout();
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ GeckoMediaPluginServiceParent::GetSingleton();
+ if (service) {
+ timeout = service->AsyncShutdownTimeoutMs();
+ }
+ rv = mAsyncShutdownTimeout->InitWithFuncCallback(
+ &AbortWaitingForGMPAsyncShutdown, this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ return rv;
+}
+
+bool
+GMPParent::RecvPGMPContentChildDestroyed()
+{
+ --mGMPContentChildCount;
+ if (!IsUsed()) {
+ CloseIfUnused();
+ }
+ return true;
+}
+
+void
+GMPParent::CloseIfUnused()
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ LOGD("%s: mAsyncShutdownRequired=%d", __FUNCTION__, mAsyncShutdownRequired);
+
+ if ((mDeleteProcessOnlyOnUnload ||
+ mState == GMPStateLoaded ||
+ mState == GMPStateUnloading) &&
+ !IsUsed()) {
+ // Ensure all timers are killed.
+ for (uint32_t i = mTimers.Length(); i > 0; i--) {
+ mTimers[i - 1]->Shutdown();
+ }
+
+ if (mAsyncShutdownRequired) {
+ if (!mAsyncShutdownInProgress) {
+ LOGD("%s: sending async shutdown notification", __FUNCTION__);
+ mAsyncShutdownInProgress = true;
+ if (!SendBeginAsyncShutdown()) {
+ AbortAsyncShutdown();
+ } else if (NS_FAILED(EnsureAsyncShutdownTimeoutSet())) {
+ AbortAsyncShutdown();
+ }
+ }
+ } else {
+ // No async-shutdown, kill async-shutdown timer started in CloseActive().
+ AbortAsyncShutdown();
+ // Any async shutdown must be complete. Shutdown GMPStorage.
+ for (size_t i = mStorage.Length(); i > 0; i--) {
+ mStorage[i - 1]->Shutdown();
+ }
+ Shutdown();
+ }
+ }
+}
+
+void
+GMPParent::AbortAsyncShutdown()
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ LOGD("%s", __FUNCTION__);
+
+ if (mAsyncShutdownTimeout) {
+ mAsyncShutdownTimeout->Cancel();
+ mAsyncShutdownTimeout = nullptr;
+ }
+
+ if (!mAsyncShutdownRequired || !mAsyncShutdownInProgress) {
+ return;
+ }
+
+ RefPtr<GMPParent> kungFuDeathGrip(this);
+ mService->AsyncShutdownComplete(this);
+ mAsyncShutdownRequired = false;
+ mAsyncShutdownInProgress = false;
+ CloseIfUnused();
+}
+
+void
+GMPParent::CloseActive(bool aDieWhenUnloaded)
+{
+ LOGD("%s: state %d", __FUNCTION__, mState);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ if (aDieWhenUnloaded) {
+ mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
+ }
+ if (mState == GMPStateLoaded) {
+ mState = GMPStateUnloading;
+ }
+ if (mState != GMPStateNotLoaded && IsUsed()) {
+ if (!SendCloseActive()) {
+ AbortAsyncShutdown();
+ } else if (IsUsed()) {
+ // We're expecting RecvPGMPContentChildDestroyed's -> Start async-shutdown timer now if needed.
+ if (mAsyncShutdownRequired && NS_FAILED(EnsureAsyncShutdownTimeoutSet())) {
+ AbortAsyncShutdown();
+ }
+ } else {
+ // We're not expecting any RecvPGMPContentChildDestroyed
+ // -> Call CloseIfUnused() now, to run async shutdown if necessary.
+ // Note that CloseIfUnused() may have already been called from a prior
+ // RecvPGMPContentChildDestroyed(), however depending on the state at
+ // that time, it might not have proceeded with shutdown; And calling it
+ // again after shutdown is fine because after the first one we'll be in
+ // GMPStateNotLoaded.
+ CloseIfUnused();
+ }
+ }
+}
+
+void
+GMPParent::MarkForDeletion()
+{
+ mDeleteProcessOnlyOnUnload = true;
+ mIsBlockingDeletion = true;
+}
+
+bool
+GMPParent::IsMarkedForDeletion()
+{
+ return mIsBlockingDeletion;
+}
+
+void
+GMPParent::Shutdown()
+{
+ LOGD("%s", __FUNCTION__);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ MOZ_ASSERT(!mAsyncShutdownTimeout, "Should have canceled shutdown timeout");
+
+ if (mAbnormalShutdownInProgress) {
+ return;
+ }
+
+ MOZ_ASSERT(!IsUsed());
+ if (mState == GMPStateNotLoaded || mState == GMPStateClosing) {
+ return;
+ }
+
+ RefPtr<GMPParent> self(this);
+ DeleteProcess();
+
+ // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
+ // Bug 1043671 is fixed
+ if (!mDeleteProcessOnlyOnUnload) {
+ // Destroy ourselves and rise from the fire to save memory
+ mService->ReAddOnGMPThread(self);
+ } // else we've been asked to die and stay dead
+ MOZ_ASSERT(mState == GMPStateNotLoaded);
+}
+
+class NotifyGMPShutdownTask : public Runnable {
+public:
+ explicit NotifyGMPShutdownTask(const nsAString& aNodeId)
+ : mNodeId(aNodeId)
+ {
+ }
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get());
+ }
+ return NS_OK;
+ }
+ nsString mNodeId;
+};
+
+void
+GMPParent::ChildTerminated()
+{
+ RefPtr<GMPParent> self(this);
+ 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 {
+ gmpThread->Dispatch(NewRunnableMethod<RefPtr<GMPParent>>(
+ mService,
+ &GeckoMediaPluginServiceParent::PluginTerminated,
+ self),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+void
+GMPParent::DeleteProcess()
+{
+ LOGD("%s", __FUNCTION__);
+
+ if (mState != GMPStateClosing) {
+ // Don't Close() twice!
+ // Probably remove when bug 1043671 is resolved
+ mState = GMPStateClosing;
+ Close();
+ }
+ mProcess->Delete(NewRunnableMethod(this, &GMPParent::ChildTerminated));
+ LOGD("%s: Shut down process", __FUNCTION__);
+ mProcess = nullptr;
+ mState = GMPStateNotLoaded;
+
+ NS_DispatchToMainThread(
+ new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)),
+ NS_DISPATCH_NORMAL);
+
+ if (mHoldingSelfRef) {
+ Release();
+ mHoldingSelfRef = false;
+ }
+}
+
+GMPState
+GMPParent::State() const
+{
+ return mState;
+}
+
+// Not changing to use mService since we'll be removing it
+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);
+ }
+
+ return mGMPThread;
+}
+
+/* static */
+bool
+GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags)
+{
+ for (const nsCString& tag : aTags) {
+ if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */
+bool
+GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsCString& aTag)
+{
+ for (const GMPCapability& capabilities : aCapabilities) {
+ if (!capabilities.mAPIName.Equals(aAPI)) {
+ continue;
+ }
+ for (const nsCString& tag : capabilities.mAPITags) {
+ if (tag.Equals(aTag)) {
+#ifdef XP_WIN
+ // Clearkey on Windows advertises that it can decode in its GMP info
+ // file, but uses Windows Media Foundation to decode. That's not present
+ // on Windows N and KN variants without certain services packs.
+ if (tag.Equals(kEMEKeySystemClearkey)) {
+ if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) {
+ if (!WMFDecoderModule::HasH264()) {
+ continue;
+ }
+ } else if (capabilities.mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER)) {
+ if (!WMFDecoderModule::HasAAC()) {
+ continue;
+ }
+ }
+ }
+#endif
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+GMPParent::EnsureProcessLoaded()
+{
+ if (mState == GMPStateLoaded) {
+ return true;
+ }
+ if (mState == GMPStateClosing ||
+ mState == GMPStateUnloading) {
+ return false;
+ }
+
+ nsresult rv = LoadProcess();
+
+ return NS_SUCCEEDED(rv);
+}
+
+void
+GMPParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
+ // warn us off trying to close again
+ mState = GMPStateClosing;
+ mAbnormalShutdownInProgress = true;
+ CloseActive(false);
+
+ // Normal Shutdown() will delete the process on unwind.
+ if (AbnormalShutdown == aWhy) {
+ RefPtr<GMPParent> self(this);
+ if (mAsyncShutdownRequired) {
+ mService->AsyncShutdownComplete(this);
+ mAsyncShutdownRequired = false;
+ }
+ // Must not call Close() again in DeleteProcess(), as we'll recurse
+ // infinitely if we do.
+ MOZ_ASSERT(mState == GMPStateClosing);
+ DeleteProcess();
+ // Note: final destruction will be Dispatched to ourself
+ mService->ReAddOnGMPThread(self);
+ }
+}
+
+PGMPStorageParent*
+GMPParent::AllocPGMPStorageParent()
+{
+ GMPStorageParent* p = new GMPStorageParent(mNodeId, this);
+ mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
+ return p;
+}
+
+bool
+GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
+{
+ GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
+ p->Shutdown();
+ mStorage.RemoveElement(p);
+ return true;
+}
+
+bool
+GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor)
+{
+ GMPStorageParent* p = (GMPStorageParent*)aActor;
+ if (NS_WARN_IF(NS_FAILED(p->Init()))) {
+ return false;
+ }
+ return true;
+}
+
+bool
+GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
+{
+ return true;
+}
+
+PGMPTimerParent*
+GMPParent::AllocPGMPTimerParent()
+{
+ GMPTimerParent* p = new GMPTimerParent(GMPThread());
+ mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
+ return p;
+}
+
+bool
+GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
+{
+ GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
+ p->Shutdown();
+ mTimers.RemoveElement(p);
+ return true;
+}
+
+bool
+ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOutValue)
+{
+ if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) {
+ return false;
+ }
+ aOutValue = aParser.Get(aKey);
+ return true;
+}
+
+RefPtr<GenericPromise>
+GMPParent::ReadGMPMetaData()
+{
+ MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+ MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!");
+
+ nsCOMPtr<nsIFile> infoFile;
+ nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info"));
+
+ if (FileExists(infoFile)) {
+ return ReadGMPInfoFile(infoFile);
+ }
+
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+}
+
+RefPtr<GenericPromise>
+GMPParent::ReadGMPInfoFile(nsIFile* aFile)
+{
+ GMPInfoFileParser parser;
+ if (!parser.Init(aFile)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsAutoCString apis;
+ if (!ReadInfoField(parser, NS_LITERAL_CSTRING("name"), mDisplayName) ||
+ !ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) ||
+ !ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) ||
+ !ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+#ifdef XP_WIN
+ // "Libraries" field is optional.
+ ReadInfoField(parser, NS_LITERAL_CSTRING("libraries"), mLibs);
+#endif
+
+ nsTArray<nsCString> apiTokens;
+ SplitAt(", ", apis, apiTokens);
+ for (nsCString api : apiTokens) {
+ int32_t tagsStart = api.FindChar('[');
+ if (tagsStart == 0) {
+ // Not allowed to be the first character.
+ // API name must be at least one character.
+ continue;
+ }
+
+ GMPCapability cap;
+ if (tagsStart == -1) {
+ // No tags.
+ cap.mAPIName.Assign(api);
+ } else {
+ auto tagsEnd = api.FindChar(']');
+ if (tagsEnd == -1 || tagsEnd < tagsStart) {
+ // Invalid syntax, skip whole capability.
+ continue;
+ }
+
+ cap.mAPIName.Assign(Substring(api, 0, tagsStart));
+
+ if ((tagsEnd - tagsStart) > 1) {
+ const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
+ nsTArray<nsCString> tagTokens;
+ SplitAt(":", ts, tagTokens);
+ for (nsCString tag : tagTokens) {
+ cap.mAPITags.AppendElement(tag);
+ }
+ }
+ }
+
+ // We support the current GMPDecryptor version, and the previous.
+ // We Adapt the previous to the current in the GMPContentChild.
+ if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
+ cap.mAPIName.AssignLiteral(GMP_API_DECRYPTOR);
+ }
+
+ if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
+ mCanDecrypt = true;
+ }
+
+ mCapabilities.AppendElement(Move(cap));
+ }
+
+ if (mCapabilities.IsEmpty()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+RefPtr<GenericPromise>
+GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
+{
+ nsAutoCString json;
+ if (!ReadIntoString(aFile, json, 5 * 1024)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ // DOM JSON parsing needs to run on the main thread.
+ return InvokeAsync(AbstractThread::MainThread(), this, __func__,
+ &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
+}
+
+RefPtr<GenericPromise>
+GMPParent::ParseChromiumManifest(nsString aJSON)
+{
+ // TODO: Remove This.
+ MOZ_ASSERT_UNREACHABLE("don't call me if EME isn't enabled");
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+}
+
+bool
+GMPParent::CanBeSharedCrossNodeIds() const
+{
+ return !mAsyncShutdownInProgress &&
+ mNodeId.IsEmpty() &&
+ // XXX bug 1159300 hack -- maybe remove after openh264 1.4
+ // We don't want to use CDM decoders for non-encrypted playback
+ // just yet; especially not for WebRTC. Don't allow CDMs to be used
+ // without a node ID.
+ !mCanDecrypt;
+}
+
+bool
+GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const
+{
+ return !mAsyncShutdownInProgress && mNodeId == aNodeId;
+}
+
+void
+GMPParent::SetNodeId(const nsACString& aNodeId)
+{
+ MOZ_ASSERT(!aNodeId.IsEmpty());
+ mNodeId = aNodeId;
+}
+
+const nsCString&
+GMPParent::GetDisplayName() const
+{
+ return mDisplayName;
+}
+
+const nsCString&
+GMPParent::GetVersion() const
+{
+ return mVersion;
+}
+
+uint32_t
+GMPParent::GetPluginId() const
+{
+ return mPluginId;
+}
+
+bool
+GMPParent::RecvAsyncShutdownRequired()
+{
+ LOGD("%s", __FUNCTION__);
+ if (mAsyncShutdownRequired) {
+ NS_WARNING("Received AsyncShutdownRequired message more than once!");
+ return true;
+ }
+ mAsyncShutdownRequired = true;
+ mService->AsyncShutdownNeeded(this);
+ return true;
+}
+
+bool
+GMPParent::RecvAsyncShutdownComplete()
+{
+ LOGD("%s", __FUNCTION__);
+
+ MOZ_ASSERT(mAsyncShutdownRequired);
+ AbortAsyncShutdown();
+ return true;
+}
+
+class RunCreateContentParentCallbacks : public Runnable
+{
+public:
+ explicit RunCreateContentParentCallbacks(GMPContentParent* aGMPContentParent)
+ : mGMPContentParent(aGMPContentParent)
+ {
+ }
+
+ void TakeCallbacks(nsTArray<UniquePtr<GetGMPContentParentCallback>>& aCallbacks)
+ {
+ mCallbacks.SwapElements(aCallbacks);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ for (uint32_t i = 0, length = mCallbacks.Length(); i < length; ++i) {
+ mCallbacks[i]->Done(mGMPContentParent);
+ }
+ return NS_OK;
+ }
+
+private:
+ RefPtr<GMPContentParent> mGMPContentParent;
+ nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks;
+};
+
+PGMPContentParent*
+GMPParent::AllocPGMPContentParent(Transport* aTransport, ProcessId aOtherPid)
+{
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(!mGMPContentParent);
+
+ mGMPContentParent = new GMPContentParent(this);
+ mGMPContentParent->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(),
+ ipc::ParentSide);
+
+ RefPtr<RunCreateContentParentCallbacks> runCallbacks =
+ new RunCreateContentParentCallbacks(mGMPContentParent);
+ runCallbacks->TakeCallbacks(mCallbacks);
+ NS_DispatchToCurrentThread(runCallbacks);
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+
+ return mGMPContentParent;
+}
+
+bool
+GMPParent::GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback)
+{
+ LOGD("%s %p", __FUNCTION__, this);
+ MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+ if (mGMPContentParent) {
+ aCallback->Done(mGMPContentParent);
+ } else {
+ mCallbacks.AppendElement(Move(aCallback));
+ // If we don't have a GMPContentParent and we try to get one for the first
+ // time (mCallbacks.Length() == 1) then call PGMPContent::Open. If more
+ // calls to GetGMPContentParent happen before mGMPContentParent has been
+ // set then we should just store them, so that they get called when we set
+ // mGMPContentParent as a result of the PGMPContent::Open call.
+ if (mCallbacks.Length() == 1) {
+ if (!EnsureProcessLoaded() || !PGMPContent::Open(this)) {
+ return false;
+ }
+ // We want to increment this as soon as possible, to avoid that we'd try
+ // to shut down the GMP process while we're still trying to get a
+ // PGMPContentParent actor.
+ ++mGMPContentChildCount;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<GMPContentParent>
+GMPParent::ForgetGMPContentParent()
+{
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+ return Move(mGMPContentParent.forget());
+}
+
+bool
+GMPParent::EnsureProcessLoaded(base::ProcessId* aID)
+{
+ if (!EnsureProcessLoaded()) {
+ return false;
+ }
+ *aID = OtherPid();
+ return true;
+}
+
+bool
+GMPParent::Bridge(GMPServiceParent* aGMPServiceParent)
+{
+ if (NS_FAILED(PGMPContent::Bridge(aGMPServiceParent, this))) {
+ return false;
+ }
+ ++mGMPContentChildCount;
+ return true;
+}
+
+nsString
+GMPParent::GetPluginBaseName() const
+{
+ return NS_LITERAL_STRING("gmp-") + mName;
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+#undef LOG
+#undef LOGD
diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h
new file mode 100644
index 000000000..dacd6feeb
--- /dev/null
+++ b/dom/media/gmp/GMPParent.h
@@ -0,0 +1,242 @@
+/* -*- 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 GMPParent_h_
+#define GMPParent_h_
+
+#include "GMPProcessParent.h"
+#include "GMPServiceParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "GMPDecryptorParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "GMPTimerParent.h"
+#include "GMPStorageParent.h"
+#include "mozilla/gmp/PGMPParent.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIFile.h"
+#include "mozilla/MozPromise.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPCapability
+{
+public:
+ explicit GMPCapability() {}
+ GMPCapability(GMPCapability&& aOther)
+ : mAPIName(Move(aOther.mAPIName))
+ , mAPITags(Move(aOther.mAPITags))
+ {
+ }
+ explicit GMPCapability(const nsCString& aAPIName)
+ : mAPIName(aAPIName)
+ {}
+ explicit GMPCapability(const GMPCapability& aOther) = default;
+ nsCString mAPIName;
+ nsTArray<nsCString> mAPITags;
+
+ static bool Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags);
+
+ static bool Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsCString& aAPI,
+ const nsCString& aTag);
+};
+
+enum GMPState {
+ GMPStateNotLoaded,
+ GMPStateLoaded,
+ GMPStateUnloading,
+ GMPStateClosing
+};
+
+class GMPContentParent;
+
+class GetGMPContentParentCallback
+{
+public:
+ GetGMPContentParentCallback()
+ {
+ MOZ_COUNT_CTOR(GetGMPContentParentCallback);
+ };
+ virtual ~GetGMPContentParentCallback()
+ {
+ MOZ_COUNT_DTOR(GetGMPContentParentCallback);
+ };
+ virtual void Done(GMPContentParent* aGMPContentParent) = 0;
+};
+
+class GMPParent final : public PGMPParent
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPParent)
+
+ GMPParent();
+
+ RefPtr<GenericPromise> Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir);
+ nsresult CloneFrom(const GMPParent* aOther);
+
+ void Crash();
+
+ nsresult LoadProcess();
+
+ // Called internally to close this if we don't need it
+ void CloseIfUnused();
+
+ // Notify all active de/encoders that we are closing, either because of
+ // normal shutdown or unexpected shutdown/crash.
+ void CloseActive(bool aDieWhenUnloaded);
+
+ // Tell the plugin to die after shutdown.
+ void MarkForDeletion();
+ bool IsMarkedForDeletion();
+
+ // 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();
+
+ // 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
+ // loading.
+ //
+ // If a plugin has no NodeId specified and it is loaded, it is assumed to
+ // be shared across NodeIds.
+
+ // Specifies that a GMP can only work with the specified NodeIds.
+ void SetNodeId(const nsACString& aNodeId);
+ const nsACString& GetNodeId() const { return mNodeId; }
+
+ const nsCString& GetDisplayName() const;
+ const nsCString& GetVersion() const;
+ uint32_t GetPluginId() const;
+ nsString GetPluginBaseName() const;
+
+ // Returns true if a plugin can be or is being used across multiple NodeIds.
+ bool CanBeSharedCrossNodeIds() const;
+
+ // A GMP can be used from a NodeId if it's already been set to work with
+ // that NodeId, or if it's not been set to work with any NodeId and has
+ // not yet been loaded (i.e. it's not shared across NodeIds).
+ bool CanBeUsedFrom(const nsACString& aNodeId) const;
+
+ already_AddRefed<nsIFile> GetDirectory() {
+ return nsCOMPtr<nsIFile>(mDirectory).forget();
+ }
+
+ void AbortAsyncShutdown();
+
+ // Called when the child process has died.
+ void ChildTerminated();
+
+ bool GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback);
+ already_AddRefed<GMPContentParent> ForgetGMPContentParent();
+
+ bool EnsureProcessLoaded(base::ProcessId* aID);
+
+ bool Bridge(GMPServiceParent* aGMPServiceParent);
+
+ const nsTArray<GMPCapability>& GetCapabilities() const { return mCapabilities; }
+
+private:
+ ~GMPParent();
+
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+ bool EnsureProcessLoaded();
+ RefPtr<GenericPromise> ReadGMPMetaData();
+ RefPtr<GenericPromise> ReadGMPInfoFile(nsIFile* aFile);
+ RefPtr<GenericPromise> ParseChromiumManifest(nsString aJSON); // Main thread.
+ RefPtr<GenericPromise> ReadChromiumManifestFile(nsIFile* aFile); // GMP thread.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ bool RecvPGMPStorageConstructor(PGMPStorageParent* actor) override;
+ PGMPStorageParent* AllocPGMPStorageParent() override;
+ bool DeallocPGMPStorageParent(PGMPStorageParent* aActor) override;
+
+ PGMPContentParent* AllocPGMPContentParent(Transport* aTransport,
+ ProcessId aOtherPid) override;
+
+ bool RecvPGMPTimerConstructor(PGMPTimerParent* actor) override;
+ PGMPTimerParent* AllocPGMPTimerParent() override;
+ bool DeallocPGMPTimerParent(PGMPTimerParent* aActor) override;
+
+ bool RecvAsyncShutdownComplete() override;
+ bool RecvAsyncShutdownRequired() override;
+
+ bool RecvPGMPContentChildDestroyed() override;
+ bool IsUsed()
+ {
+ return mGMPContentChildCount > 0;
+ }
+
+
+ static void AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure);
+ nsresult EnsureAsyncShutdownTimeoutSet();
+
+ GMPState mState;
+ nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
+ nsString mName; // base name of plugin on disk, UTF-16 because used for paths
+ nsCString mDisplayName; // name of plugin displayed to users
+ nsCString mDescription; // description of plugin for display to users
+ nsCString mVersion;
+#ifdef XP_WIN
+ nsCString mLibs;
+#endif
+ nsString mAdapter;
+ uint32_t mPluginId;
+ nsTArray<GMPCapability> mCapabilities;
+ GMPProcessParent* mProcess;
+ bool mDeleteProcessOnlyOnUnload;
+ bool mAbnormalShutdownInProgress;
+ bool mIsBlockingDeletion;
+
+ bool mCanDecrypt;
+
+ nsTArray<RefPtr<GMPTimerParent>> mTimers;
+ nsTArray<RefPtr<GMPStorageParent>> mStorage;
+ nsCOMPtr<nsIThread> mGMPThread;
+ nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
+ // 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<GetGMPContentParentCallback>> mCallbacks;
+ uint32_t mGMPContentChildCount;
+
+ bool mAsyncShutdownRequired;
+ bool mAsyncShutdownInProgress;
+
+ int mChildPid;
+
+ // We hold a self reference to ourself while the child process is alive.
+ // This ensures that if the GMPService tries to shut us down and drops
+ // its reference to us, we stay alive long enough for the child process
+ // to terminate gracefully.
+ bool mHoldingSelfRef;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPParent_h_
diff --git a/dom/media/gmp/GMPPlatform.cpp b/dom/media/gmp/GMPPlatform.cpp
new file mode 100644
index 000000000..71fa03468
--- /dev/null
+++ b/dom/media/gmp/GMPPlatform.cpp
@@ -0,0 +1,300 @@
+/* -*- 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 "GMPPlatform.h"
+#include "GMPStorageChild.h"
+#include "GMPTimerChild.h"
+#include "mozilla/Monitor.h"
+#include "GMPChild.h"
+#include <ctime>
+
+namespace mozilla {
+namespace gmp {
+
+static MessageLoop* sMainLoop = nullptr;
+static GMPChild* sChild = nullptr;
+
+static bool
+IsOnChildMainThread()
+{
+ return sMainLoop && sMainLoop == MessageLoop::current();
+}
+
+// We just need a refcounted wrapper for GMPTask objects.
+class GMPRunnable final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRunnable)
+
+ explicit GMPRunnable(GMPTask* aTask)
+ : mTask(aTask)
+ {
+ MOZ_ASSERT(mTask);
+ }
+
+ void Run()
+ {
+ mTask->Run();
+ mTask->Destroy();
+ mTask = nullptr;
+ }
+
+private:
+ ~GMPRunnable()
+ {
+ }
+
+ GMPTask* mTask;
+};
+
+class GMPSyncRunnable final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPSyncRunnable)
+
+ GMPSyncRunnable(GMPTask* aTask, MessageLoop* aMessageLoop)
+ : mDone(false)
+ , mTask(aTask)
+ , mMessageLoop(aMessageLoop)
+ , mMonitor("GMPSyncRunnable")
+ {
+ MOZ_ASSERT(mTask);
+ MOZ_ASSERT(mMessageLoop);
+ }
+
+ void Post()
+ {
+ // We assert here for two reasons.
+ // 1) Nobody should be blocking the main thread.
+ // 2) This prevents deadlocks when doing sync calls to main which if the
+ // main thread tries to do a sync call back to the calling thread.
+ MOZ_ASSERT(!IsOnChildMainThread());
+
+ mMessageLoop->PostTask(NewRunnableMethod(this, &GMPSyncRunnable::Run));
+ MonitorAutoLock lock(mMonitor);
+ while (!mDone) {
+ lock.Wait();
+ }
+ }
+
+ void Run()
+ {
+ mTask->Run();
+ mTask->Destroy();
+ mTask = nullptr;
+ MonitorAutoLock lock(mMonitor);
+ mDone = true;
+ lock.Notify();
+ }
+
+private:
+ ~GMPSyncRunnable()
+ {
+ }
+
+ bool mDone;
+ GMPTask* mTask;
+ MessageLoop* mMessageLoop;
+ Monitor mMonitor;
+};
+
+GMPErr
+CreateThread(GMPThread** aThread)
+{
+ if (!aThread) {
+ return GMPGenericErr;
+ }
+
+ *aThread = new GMPThreadImpl();
+
+ return GMPNoErr;
+}
+
+GMPErr
+RunOnMainThread(GMPTask* aTask)
+{
+ if (!aTask || !sMainLoop) {
+ return GMPGenericErr;
+ }
+
+ RefPtr<GMPRunnable> r = new GMPRunnable(aTask);
+ sMainLoop->PostTask(NewRunnableMethod(r, &GMPRunnable::Run));
+
+ return GMPNoErr;
+}
+
+GMPErr
+SyncRunOnMainThread(GMPTask* aTask)
+{
+ if (!aTask || !sMainLoop || IsOnChildMainThread()) {
+ return GMPGenericErr;
+ }
+
+ RefPtr<GMPSyncRunnable> r = new GMPSyncRunnable(aTask, sMainLoop);
+
+ r->Post();
+
+ return GMPNoErr;
+}
+
+GMPErr
+CreateMutex(GMPMutex** aMutex)
+{
+ if (!aMutex) {
+ return GMPGenericErr;
+ }
+
+ *aMutex = new GMPMutexImpl();
+
+ return GMPNoErr;
+}
+
+GMPErr
+CreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE ||
+ aRecordNameSize == 0) {
+ NS_WARNING("GMP tried to CreateRecord with too long or 0 record name");
+ return GMPGenericErr;
+ }
+ GMPStorageChild* storage = sChild->GetGMPStorage();
+ if (!storage) {
+ return GMPGenericErr;
+ }
+ MOZ_ASSERT(storage);
+ return storage->CreateRecord(nsDependentCString(aRecordName, aRecordNameSize),
+ aOutRecord,
+ aClient);
+}
+
+GMPErr
+SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS)
+{
+ if (!aTask || !sMainLoop || !IsOnChildMainThread()) {
+ return GMPGenericErr;
+ }
+ GMPTimerChild* timers = sChild->GetGMPTimers();
+ NS_ENSURE_TRUE(timers, GMPGenericErr);
+ return timers->SetTimer(aTask, aTimeoutMS);
+}
+
+GMPErr
+GetClock(GMPTimestamp* aOutTime)
+{
+ *aOutTime = time(0) * 1000;
+ return GMPNoErr;
+}
+
+GMPErr
+CreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg)
+{
+ if (!aRecvIteratorFunc) {
+ return GMPInvalidArgErr;
+ }
+ GMPStorageChild* storage = sChild->GetGMPStorage();
+ if (!storage) {
+ return GMPGenericErr;
+ }
+ MOZ_ASSERT(storage);
+ return storage->EnumerateRecords(aRecvIteratorFunc, aUserArg);
+}
+
+void
+InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
+{
+ if (!sMainLoop) {
+ sMainLoop = MessageLoop::current();
+ }
+ if (!sChild) {
+ sChild = aChild;
+ }
+
+ aPlatformAPI.version = 0;
+ aPlatformAPI.createthread = &CreateThread;
+ aPlatformAPI.runonmainthread = &RunOnMainThread;
+ aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread;
+ aPlatformAPI.createmutex = &CreateMutex;
+ aPlatformAPI.createrecord = &CreateRecord;
+ aPlatformAPI.settimer = &SetTimerOnMainThread;
+ aPlatformAPI.getcurrenttime = &GetClock;
+ aPlatformAPI.getrecordenumerator = &CreateRecordIterator;
+}
+
+GMPThreadImpl::GMPThreadImpl()
+: mMutex("GMPThreadImpl"),
+ mThread("GMPThread")
+{
+ MOZ_COUNT_CTOR(GMPThread);
+}
+
+GMPThreadImpl::~GMPThreadImpl()
+{
+ MOZ_COUNT_DTOR(GMPThread);
+}
+
+void
+GMPThreadImpl::Post(GMPTask* aTask)
+{
+ MutexAutoLock lock(mMutex);
+
+ if (!mThread.IsRunning()) {
+ bool started = mThread.Start();
+ if (!started) {
+ NS_WARNING("Unable to start GMPThread!");
+ return;
+ }
+ }
+
+ RefPtr<GMPRunnable> r = new GMPRunnable(aTask);
+ mThread.message_loop()->PostTask(NewRunnableMethod(r.get(), &GMPRunnable::Run));
+}
+
+void
+GMPThreadImpl::Join()
+{
+ {
+ MutexAutoLock lock(mMutex);
+ if (mThread.IsRunning()) {
+ mThread.Stop();
+ }
+ }
+ delete this;
+}
+
+GMPMutexImpl::GMPMutexImpl()
+: mMonitor("gmp-mutex")
+{
+ MOZ_COUNT_CTOR(GMPMutexImpl);
+}
+
+GMPMutexImpl::~GMPMutexImpl()
+{
+ MOZ_COUNT_DTOR(GMPMutexImpl);
+}
+
+void
+GMPMutexImpl::Destroy()
+{
+ delete this;
+}
+
+void
+GMPMutexImpl::Acquire()
+{
+ mMonitor.Enter();
+}
+
+void
+GMPMutexImpl::Release()
+{
+ mMonitor.Exit();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPPlatform.h b/dom/media/gmp/GMPPlatform.h
new file mode 100644
index 000000000..79079c327
--- /dev/null
+++ b/dom/media/gmp/GMPPlatform.h
@@ -0,0 +1,56 @@
+/* -*- 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 GMPPlatform_h_
+#define GMPPlatform_h_
+
+#include "mozilla/Mutex.h"
+#include "gmp-platform.h"
+#include "base/thread.h"
+#include "mozilla/ReentrantMonitor.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+void InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild);
+
+GMPErr RunOnMainThread(GMPTask* aTask);
+
+class GMPThreadImpl : public GMPThread
+{
+public:
+ GMPThreadImpl();
+ virtual ~GMPThreadImpl();
+
+ // GMPThread
+ void Post(GMPTask* aTask) override;
+ void Join() override;
+
+private:
+ Mutex mMutex;
+ base::Thread mThread;
+};
+
+class GMPMutexImpl : public GMPMutex
+{
+public:
+ GMPMutexImpl();
+ virtual ~GMPMutexImpl();
+
+ // GMPMutex
+ void Acquire() override;
+ void Release() override;
+ void Destroy() override;
+
+private:
+ ReentrantMonitor mMonitor;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPPlatform_h_
diff --git a/dom/media/gmp/GMPProcessChild.cpp b/dom/media/gmp/GMPProcessChild.cpp
new file mode 100644
index 000000000..294cabb42
--- /dev/null
+++ b/dom/media/gmp/GMPProcessChild.cpp
@@ -0,0 +1,82 @@
+/* -*- 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 "GMPProcessChild.h"
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/BackgroundHangMonitor.h"
+
+using mozilla::ipc::IOThreadChild;
+
+namespace mozilla {
+namespace gmp {
+
+GMPProcessChild::GMPProcessChild(ProcessId aParentPid)
+: ProcessChild(aParentPid)
+{
+}
+
+GMPProcessChild::~GMPProcessChild()
+{
+}
+
+bool
+GMPProcessChild::Init()
+{
+ nsAutoString pluginFilename;
+ nsAutoString voucherFilename;
+
+#if defined(OS_POSIX)
+ // NB: need to be very careful in ensuring that the first arg
+ // (after the binary name) here is indeed the plugin module path.
+ // Keep in sync with dom/plugins/PluginModuleParent.
+ std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv();
+ MOZ_ASSERT(values.size() >= 3, "not enough args");
+ pluginFilename = NS_ConvertUTF8toUTF16(nsDependentCString(values[1].c_str()));
+ voucherFilename = NS_ConvertUTF8toUTF16(nsDependentCString(values[2].c_str()));
+#elif defined(OS_WIN)
+ std::vector<std::wstring> values = CommandLine::ForCurrentProcess()->GetLooseValues();
+ MOZ_ASSERT(values.size() >= 2, "not enough loose args");
+ pluginFilename = nsDependentString(values[0].c_str());
+ voucherFilename = nsDependentString(values[1].c_str());
+#else
+#error Not implemented
+#endif
+
+ BackgroundHangMonitor::Startup();
+
+ return mPlugin.Init(pluginFilename,
+ voucherFilename,
+ ParentPid(),
+ IOThreadChild::message_loop(),
+ IOThreadChild::channel());
+}
+
+void
+GMPProcessChild::CleanUp()
+{
+ BackgroundHangMonitor::Shutdown();
+}
+
+GMPLoader* GMPProcessChild::mLoader = nullptr;
+
+/* static */
+void
+GMPProcessChild::SetGMPLoader(GMPLoader* aLoader)
+{
+ mLoader = aLoader;
+}
+
+/* static */
+GMPLoader*
+GMPProcessChild::GetGMPLoader()
+{
+ return mLoader;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPProcessChild.h b/dom/media/gmp/GMPProcessChild.h
new file mode 100644
index 000000000..1a8df7653
--- /dev/null
+++ b/dom/media/gmp/GMPProcessChild.h
@@ -0,0 +1,43 @@
+/* -*- 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 GMPProcessChild_h_
+#define GMPProcessChild_h_
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPChild.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPLoader;
+
+class GMPProcessChild final : public mozilla::ipc::ProcessChild {
+protected:
+ typedef mozilla::ipc::ProcessChild ProcessChild;
+
+public:
+ explicit GMPProcessChild(ProcessId aParentPid);
+ ~GMPProcessChild();
+
+ bool Init() override;
+ void CleanUp() override;
+
+ // Set/get the GMPLoader singleton for this child process.
+ // Note: The GMPLoader is not deleted by this object, the caller of
+ // SetGMPLoader() must manage the GMPLoader's lifecycle.
+ static void SetGMPLoader(GMPLoader* aHost);
+ static GMPLoader* GetGMPLoader();
+
+private:
+ GMPChild mPlugin;
+ static GMPLoader* mLoader;
+ DISALLOW_COPY_AND_ASSIGN(GMPProcessChild);
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPProcessChild_h_
diff --git a/dom/media/gmp/GMPProcessParent.cpp b/dom/media/gmp/GMPProcessParent.cpp
new file mode 100644
index 000000000..ef58175e8
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et :
+ * 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 "GMPProcessParent.h"
+#include "GMPUtils.h"
+#include "nsIFile.h"
+#include "nsIRunnable.h"
+
+#include "base/string_util.h"
+#include "base/process_util.h"
+
+#include <string>
+
+using std::vector;
+using std::string;
+
+using mozilla::gmp::GMPProcessParent;
+using mozilla::ipc::GeckoChildProcessHost;
+using base::ProcessArchitecture;
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+#define GMP_LOG(msg, ...) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace gmp {
+
+GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
+: GeckoChildProcessHost(GeckoProcessType_GMPlugin),
+ mGMPPath(aGMPPath)
+{
+ MOZ_COUNT_CTOR(GMPProcessParent);
+}
+
+GMPProcessParent::~GMPProcessParent()
+{
+ MOZ_COUNT_DTOR(GMPProcessParent);
+}
+
+bool
+GMPProcessParent::Launch(int32_t aTimeoutMs)
+{
+ nsCOMPtr<nsIFile> path;
+ if (!GetEMEVoucherPath(getter_AddRefs(path))) {
+ NS_WARNING("GMPProcessParent can't get EME voucher path!");
+ return false;
+ }
+ nsAutoCString voucherPath;
+ path->GetNativePath(voucherPath);
+
+ vector<string> args;
+
+ args.push_back(mGMPPath);
+
+ args.push_back(string(voucherPath.BeginReading(), voucherPath.EndReading()));
+
+ return SyncLaunch(args, aTimeoutMs, base::GetCurrentProcessArchitecture());
+}
+
+void
+GMPProcessParent::Delete(nsCOMPtr<nsIRunnable> aCallback)
+{
+ mDeletedCallback = aCallback;
+ XRE_GetIOMessageLoop()->PostTask(NewNonOwningRunnableMethod(this, &GMPProcessParent::DoDelete));
+}
+
+void
+GMPProcessParent::DoDelete()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ Join();
+
+ if (mDeletedCallback) {
+ mDeletedCallback->Run();
+ }
+
+ delete this;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPProcessParent.h b/dom/media/gmp/GMPProcessParent.h
new file mode 100644
index 000000000..b94f203cf
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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 GMPProcessParent_h
+#define GMPProcessParent_h 1
+
+#include "mozilla/Attributes.h"
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/thread.h"
+#include "chrome/common/child_process_host.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPProcessParent final : public mozilla::ipc::GeckoChildProcessHost
+{
+public:
+ explicit GMPProcessParent(const std::string& aGMPPath);
+ ~GMPProcessParent();
+
+ // Synchronously launch the plugin process. If the process fails to launch
+ // after timeoutMs, this method will return false.
+ bool Launch(int32_t aTimeoutMs);
+
+ void Delete(nsCOMPtr<nsIRunnable> aCallback = nullptr);
+
+ bool CanShutdown() override { return true; }
+ const std::string& GetPluginFilePath() { return mGMPPath; }
+
+ using mozilla::ipc::GeckoChildProcessHost::GetChannel;
+ using mozilla::ipc::GeckoChildProcessHost::GetChildProcessHandle;
+
+private:
+ void DoDelete();
+
+ std::string mGMPPath;
+ nsCOMPtr<nsIRunnable> mDeletedCallback;
+
+ DISALLOW_COPY_AND_ASSIGN(GMPProcessParent);
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // ifndef GMPProcessParent_h
diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp
new file mode 100644
index 000000000..1901210da
--- /dev/null
+++ b/dom/media/gmp/GMPService.cpp
@@ -0,0 +1,557 @@
+/* -*- 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 "GMPService.h"
+#include "GMPServiceParent.h"
+#include "GMPServiceChild.h"
+#include "GMPContentParent.h"
+#include "prio.h"
+#include "mozilla/Logging.h"
+#include "GMPParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "nsIObserverService.h"
+#include "GeckoChildProcessHost.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/Services.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Unused.h"
+#include "GMPDecryptorParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "nsComponentManagerUtils.h"
+#include "runnable_utils.h"
+#include "VideoUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+LogModule*
+GetGMPLog()
+{
+ static LazyLogModule sLog("GMP");
+ return sLog;
+}
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPService"
+
+namespace gmp {
+
+static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
+
+class GMPServiceCreateHelper final : public mozilla::Runnable
+{
+ RefPtr<GeckoMediaPluginService> mService;
+
+public:
+ static already_AddRefed<GeckoMediaPluginService>
+ GetOrCreate()
+ {
+ RefPtr<GeckoMediaPluginService> service;
+
+ if (NS_IsMainThread()) {
+ service = GetOrCreateOnMainThread();
+ } else {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+
+ RefPtr<GMPServiceCreateHelper> createHelper = new GMPServiceCreateHelper();
+
+ mozilla::SyncRunnable::DispatchToThread(mainThread, createHelper, true);
+
+ service = createHelper->mService.forget();
+ }
+
+ return service.forget();
+ }
+
+private:
+ GMPServiceCreateHelper()
+ {
+ }
+
+ ~GMPServiceCreateHelper()
+ {
+ MOZ_ASSERT(!mService);
+ }
+
+ static already_AddRefed<GeckoMediaPluginService>
+ GetOrCreateOnMainThread()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingletonService) {
+ if (XRE_IsParentProcess()) {
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ new GeckoMediaPluginServiceParent();
+ service->Init();
+ sSingletonService = service;
+ } else {
+ RefPtr<GeckoMediaPluginServiceChild> service =
+ new GeckoMediaPluginServiceChild();
+ service->Init();
+ sSingletonService = service;
+ }
+
+ ClearOnShutdown(&sSingletonService);
+ }
+
+ RefPtr<GeckoMediaPluginService> service = sSingletonService.get();
+ return service.forget();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mService = GetOrCreateOnMainThread();
+ return NS_OK;
+ }
+};
+
+already_AddRefed<GeckoMediaPluginService>
+GeckoMediaPluginService::GetGeckoMediaPluginService()
+{
+ return GMPServiceCreateHelper::GetOrCreate();
+}
+
+NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, nsIObserver)
+
+GeckoMediaPluginService::GeckoMediaPluginService()
+ : mMutex("GeckoMediaPluginService::mMutex")
+ , mGMPThreadShutdown(false)
+ , mShuttingDownOnGMPThread(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+GeckoMediaPluginService::~GeckoMediaPluginService()
+{
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::RunPluginCrashCallbacks(uint32_t aPluginId,
+ const nsACString& aPluginName)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
+
+ nsAutoPtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers;
+ {
+ MutexAutoLock lock(mMutex);
+ mPluginCrashHelpers.RemoveAndForget(aPluginId, helpers);
+ }
+ if (!helpers) {
+ LOGD(("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__, __FUNCTION__, aPluginId));
+ return NS_OK;
+ }
+
+ for (const auto& helper : *helpers) {
+ nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget();
+ if (NS_WARN_IF(!window)) {
+ continue;
+ }
+ nsCOMPtr<nsIDocument> document(window->GetExtantDoc());
+ if (NS_WARN_IF(!document)) {
+ continue;
+ }
+
+ dom::PluginCrashedEventInit init;
+ init.mPluginID = aPluginId;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mGmpPlugin = true;
+ CopyUTF8toUTF16(aPluginName, init.mPluginName);
+ init.mSubmittedCrashReport = false;
+ RefPtr<dom::PluginCrashedEvent> event =
+ dom::PluginCrashedEvent::Constructor(document,
+ NS_LITERAL_STRING("PluginCrashed"),
+ init);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginService::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ 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));
+}
+
+void
+GeckoMediaPluginService::ShutdownGMPThread()
+{
+ LOGD(("%s::%s", __CLASS__, __FUNCTION__));
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ mGMPThreadShutdown = true;
+ mGMPThread.swap(gmpThread);
+ mAbstractGMPThread = nullptr;
+ }
+
+ if (gmpThread) {
+ gmpThread->Shutdown();
+ }
+}
+
+nsresult
+GeckoMediaPluginService::GMPDispatch(nsIRunnable* event,
+ uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> r(event);
+ return GMPDispatch(r.forget());
+}
+
+nsresult
+GeckoMediaPluginService::GMPDispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags)
+{
+ nsCOMPtr<nsIRunnable> r(event);
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = GetThread(getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return thread->Dispatch(r, flags);
+}
+
+// always call with getter_AddRefs, because it does
+NS_IMETHODIMP
+GeckoMediaPluginService::GetThread(nsIThread** aThread)
+{
+ MOZ_ASSERT(aThread);
+
+ // This can be called from any thread.
+ MutexAutoLock lock(mMutex);
+
+ if (!mGMPThread) {
+ // Don't allow the thread to be created after shutdown has started.
+ if (mGMPThreadShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mAbstractGMPThread = AbstractThread::CreateXPCOMThreadWrapper(mGMPThread, false);
+
+ // Tell the thread to initialize plugins
+ InitializePlugins(mAbstractGMPThread.get());
+ }
+
+ nsCOMPtr<nsIThread> copy = mGMPThread;
+ copy.forget(aThread);
+
+ return NS_OK;
+}
+
+RefPtr<AbstractThread>
+GeckoMediaPluginService::GetAbstractGMPThread()
+{
+ MutexAutoLock lock(mMutex);
+ return mAbstractGMPThread;
+}
+
+class GetGMPContentParentForAudioDecoderDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForAudioDecoderDone(UniquePtr<GetGMPAudioDecoderCallback>&& aCallback,
+ GMPCrashHelper* aHelper)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPAudioDecoderParent* gmpADP = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPAudioDecoder(&gmpADP))) {
+ gmpADP->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(gmpADP);
+ }
+
+private:
+ UniquePtr<GetGMPAudioDecoderCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPAudioDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForAudioDecoderDone(Move(aCallback), aHelper));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GetGMPContentParentForVideoDecoderDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForVideoDecoderDone(UniquePtr<GetGMPVideoDecoderCallback>&& aCallback,
+ GMPCrashHelper* aHelper,
+ uint32_t aDecryptorId)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ , mDecryptorId(aDecryptorId)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPVideoDecoderParent* gmpVDP = nullptr;
+ GMPVideoHostImpl* videoHost = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPVideoDecoder(&gmpVDP, mDecryptorId))) {
+ videoHost = &gmpVDP->Host();
+ gmpVDP->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(gmpVDP, videoHost);
+ }
+
+private:
+ UniquePtr<GetGMPVideoDecoderCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+ const uint32_t mDecryptorId;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback,
+ uint32_t aDecryptorId)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForVideoDecoderDone(Move(aCallback), aHelper, aDecryptorId));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GetGMPContentParentForVideoEncoderDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForVideoEncoderDone(UniquePtr<GetGMPVideoEncoderCallback>&& aCallback,
+ GMPCrashHelper* aHelper)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPVideoEncoderParent* gmpVEP = nullptr;
+ GMPVideoHostImpl* videoHost = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPVideoEncoder(&gmpVEP))) {
+ videoHost = &gmpVEP->Host();
+ gmpVEP->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(gmpVEP, videoHost);
+ }
+
+private:
+ UniquePtr<GetGMPVideoEncoderCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPVideoEncoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForVideoEncoderDone(Move(aCallback), aHelper));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GetGMPContentParentForDecryptorDone : public GetGMPContentParentCallback
+{
+public:
+ explicit GetGMPContentParentForDecryptorDone(UniquePtr<GetGMPDecryptorCallback>&& aCallback,
+ GMPCrashHelper* aHelper)
+ : mCallback(Move(aCallback))
+ , mHelper(aHelper)
+ {
+ }
+
+ void Done(GMPContentParent* aGMPParent) override
+ {
+ GMPDecryptorParent* ksp = nullptr;
+ if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPDecryptor(&ksp))) {
+ ksp->SetCrashHelper(mHelper);
+ }
+ mCallback->Done(ksp);
+ }
+
+private:
+ UniquePtr<GetGMPDecryptorCallback> mCallback;
+ RefPtr<GMPCrashHelper> mHelper;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPDecryptor(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPDecryptorCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<GetGMPContentParentCallback> callback(
+ new GetGMPContentParentForDecryptorDone(Move(aCallback), aHelper));
+ if (!GetContentParentFrom(aHelper,
+ aNodeId,
+ NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
+ *aTags,
+ Move(callback))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper)
+{
+ if (!aHelper) {
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ nsTArray<RefPtr<GMPCrashHelper>>* helpers;
+ if (!mPluginCrashHelpers.Get(aPluginId, &helpers)) {
+ helpers = new nsTArray<RefPtr<GMPCrashHelper>>();
+ mPluginCrashHelpers.Put(aPluginId, helpers);
+ } else if (helpers->Contains(aHelper)) {
+ return;
+ }
+ helpers->AppendElement(aHelper);
+}
+
+void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper)
+{
+ if (!aHelper) {
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) {
+ nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.Data();
+ if (!helpers->Contains(aHelper)) {
+ continue;
+ }
+ helpers->RemoveElement(aHelper);
+ MOZ_ASSERT(!helpers->Contains(aHelper)); // Ensure there aren't duplicates.
+ if (helpers->IsEmpty()) {
+ iter.Remove();
+ }
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+NS_IMPL_ADDREF(GMPCrashHelper)
+NS_IMPL_RELEASE_WITH_DESTROY(GMPCrashHelper, Destroy())
+
+void
+GMPCrashHelper::Destroy()
+{
+ if (NS_IsMainThread()) {
+ delete this;
+ } else {
+ // Don't addref, as then we'd end up releasing after the detele runs!
+ NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this, &GMPCrashHelper::Destroy));
+ }
+}
diff --git a/dom/media/gmp/GMPService.h b/dom/media/gmp/GMPService.h
new file mode 100644
index 000000000..7ed318a25
--- /dev/null
+++ b/dom/media/gmp/GMPService.h
@@ -0,0 +1,142 @@
+/* -*- 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 GMPService_h_
+#define GMPService_h_
+
+#include "nsString.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Monitor.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsIWeakReference.h"
+#include "mozilla/AbstractThread.h"
+#include "nsClassHashtable.h"
+#include "nsISupportsImpl.h"
+
+template <class> struct already_AddRefed;
+
+// For every GMP actor requested, the caller can specify a crash helper,
+// which is an object which supplies the nsPIDOMWindowInner to which we'll
+// dispatch the PluginCrashed event if the GMP crashes.
+// GMPCrashHelper has threadsafe refcounting. Its release method ensures
+// that instances are destroyed on the main thread.
+class GMPCrashHelper
+{
+public:
+ NS_METHOD_(MozExternalRefCountType) AddRef(void);
+ NS_METHOD_(MozExternalRefCountType) Release(void);
+
+ // Called on the main thread.
+ virtual already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() = 0;
+
+protected:
+ virtual ~GMPCrashHelper()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ void Destroy();
+ mozilla::ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+
+namespace gmp {
+
+class GetGMPContentParentCallback;
+
+class GeckoMediaPluginService : public mozIGeckoMediaPluginService
+ , public nsIObserver
+{
+public:
+ static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
+
+ virtual nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // 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;
+ NS_IMETHOD GetGMPVideoEncoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoEncoderCallback>&& aCallback)
+ override;
+ NS_IMETHOD GetGMPAudioDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPAudioDecoderCallback>&& aCallback)
+ override;
+ NS_IMETHOD GetGMPDecryptor(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPDecryptorCallback>&& aCallback)
+ override;
+
+ // Helper for backwards compatibility with WebRTC/tests.
+ NS_IMETHOD
+ GetGMPVideoDecoder(GMPCrashHelper* aHelper,
+ nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) override
+ {
+ return GetDecryptingGMPVideoDecoder(aHelper, aTags, aNodeId, Move(aCallback), 0);
+ }
+
+ int32_t AsyncShutdownTimeoutMs();
+
+ NS_IMETHOD RunPluginCrashCallbacks(uint32_t aPluginId,
+ const nsACString& aPluginName) override;
+
+ RefPtr<AbstractThread> GetAbstractGMPThread();
+
+ void ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper);
+ void DisconnectCrashHelper(GMPCrashHelper* aHelper);
+
+protected:
+ GeckoMediaPluginService();
+ virtual ~GeckoMediaPluginService();
+
+ virtual void InitializePlugins(AbstractThread* aAbstractGMPThread) = 0;
+ virtual bool GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback) = 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;
+ RefPtr<AbstractThread> mAbstractGMPThread;
+ bool mGMPThreadShutdown;
+ bool mShuttingDownOnGMPThread;
+
+ nsClassHashtable<nsUint32HashKey, nsTArray<RefPtr<GMPCrashHelper>>> mPluginCrashHelpers;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPService_h_
diff --git a/dom/media/gmp/GMPServiceChild.cpp b/dom/media/gmp/GMPServiceChild.cpp
new file mode 100644
index 000000000..08599039f
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -0,0 +1,478 @@
+/* -*- 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 "GMPServiceChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "nsCOMPtr.h"
+#include "GMPParent.h"
+#include "GMPContentParent.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/StaticMutex.h"
+#include "runnable_utils.h"
+#include "base/task.h"
+#include "nsIObserverService.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPService"
+
+namespace gmp {
+
+already_AddRefed<GeckoMediaPluginServiceChild>
+GeckoMediaPluginServiceChild::GetSingleton()
+{
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ RefPtr<GeckoMediaPluginService> service(
+ GeckoMediaPluginService::GetGeckoMediaPluginService());
+#ifdef DEBUG
+ if (service) {
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
+ CallQueryInterface(service.get(), getter_AddRefs(chromeService));
+ MOZ_ASSERT(!chromeService);
+ }
+#endif
+ return service.forget().downcast<GeckoMediaPluginServiceChild>();
+}
+
+class GetContentParentFromDone : public GetServiceChildCallback
+{
+public:
+ GetContentParentFromDone(GMPCrashHelper* aHelper, const nsACString& aNodeId, const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+ : mHelper(aHelper),
+ mNodeId(aNodeId),
+ mAPI(aAPI),
+ mTags(aTags),
+ mCallback(Move(aCallback))
+ {
+ }
+
+ void Done(GMPServiceChild* aGMPServiceChild) override
+ {
+ if (!aGMPServiceChild) {
+ mCallback->Done(nullptr);
+ return;
+ }
+
+ uint32_t pluginId;
+ nsresult rv;
+ bool ok = aGMPServiceChild->SendSelectGMP(mNodeId, mAPI, mTags, &pluginId, &rv);
+ if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) {
+ mCallback->Done(nullptr);
+ return;
+ }
+
+ if (mHelper) {
+ RefPtr<GeckoMediaPluginService> gmps(GeckoMediaPluginService::GetGeckoMediaPluginService());
+ gmps->ConnectCrashHelper(pluginId, mHelper);
+ }
+
+ nsTArray<base::ProcessId> alreadyBridgedTo;
+ aGMPServiceChild->GetAlreadyBridgedTo(alreadyBridgedTo);
+
+ base::ProcessId otherProcess;
+ nsCString displayName;
+ ok = aGMPServiceChild->SendLaunchGMP(pluginId, alreadyBridgedTo, &otherProcess,
+ &displayName, &rv);
+ if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) {
+ mCallback->Done(nullptr);
+ return;
+ }
+
+ RefPtr<GMPContentParent> parent;
+ aGMPServiceChild->GetBridgedGMPContentParent(otherProcess,
+ getter_AddRefs(parent));
+ if (!alreadyBridgedTo.Contains(otherProcess)) {
+ parent->SetDisplayName(displayName);
+ parent->SetPluginId(pluginId);
+ }
+
+ mCallback->Done(parent);
+ }
+
+private:
+ RefPtr<GMPCrashHelper> mHelper;
+ nsCString mNodeId;
+ nsCString mAPI;
+ const nsTArray<nsCString> mTags;
+ UniquePtr<GetGMPContentParentCallback> mCallback;
+};
+
+bool
+GeckoMediaPluginServiceChild::GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ UniquePtr<GetServiceChildCallback> callback(
+ new GetContentParentFromDone(aHelper, aNodeId, aAPI, aTags, Move(aCallback)));
+ GetServiceChild(Move(callback));
+
+ return true;
+}
+
+typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+typedef mozilla::dom::GMPAPITags GMPAPITags;
+
+struct GMPCapabilityAndVersion
+{
+ explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities)
+ : mName(aCapabilities.name())
+ , mVersion(aCapabilities.version())
+ {
+ for (const GMPAPITags& tags : aCapabilities.capabilities()) {
+ GMPCapability cap;
+ cap.mAPIName = tags.api();
+ for (const nsCString& tag : tags.tags()) {
+ cap.mAPITags.AppendElement(tag);
+ }
+ mCapabilities.AppendElement(Move(cap));
+ }
+ }
+
+ nsCString ToString() const
+ {
+ nsCString s;
+ s.Append(mName);
+ s.Append(" version=");
+ s.Append(mVersion);
+ s.Append(" tags=[");
+ nsCString tags;
+ for (const GMPCapability& cap : mCapabilities) {
+ if (!tags.IsEmpty()) {
+ tags.Append(" ");
+ }
+ tags.Append(cap.mAPIName);
+ for (const nsCString& tag : cap.mAPITags) {
+ tags.Append(":");
+ tags.Append(tag);
+ }
+ }
+ s.Append(tags);
+ s.Append("]");
+ return s;
+ }
+
+ nsCString mName;
+ nsCString mVersion;
+ nsTArray<GMPCapability> mCapabilities;
+};
+
+StaticMutex sGMPCapabilitiesMutex;
+StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities;
+
+static nsCString
+GMPCapabilitiesToString()
+{
+ nsCString s;
+ for (const GMPCapabilityAndVersion& gmp : *sGMPCapabilities) {
+ if (!s.IsEmpty()) {
+ s.Append(", ");
+ }
+ s.Append(gmp.ToString());
+ }
+ return s;
+}
+
+/* static */
+void
+GeckoMediaPluginServiceChild::UpdateGMPCapabilities(nsTArray<GMPCapabilityData>&& aCapabilities)
+{
+ {
+ // The mutex should unlock before sending the "gmp-changed" observer service notification.
+ StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
+ if (!sGMPCapabilities) {
+ sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>();
+ ClearOnShutdown(&sGMPCapabilities);
+ }
+ sGMPCapabilities->Clear();
+ for (const GMPCapabilityData& plugin : aCapabilities) {
+ sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin));
+ }
+
+ LOGD(("UpdateGMPCapabilities {%s}", GMPCapabilitiesToString().get()));
+ }
+
+ // Fire a notification so that any MediaKeySystemAccess
+ // requests waiting on a CDM to download will retry.
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
+ }
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool* aHasPlugin)
+{
+ StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
+ if (!sGMPCapabilities) {
+ *aHasPlugin = false;
+ return NS_OK;
+ }
+
+ nsCString api(aAPI);
+ for (const GMPCapabilityAndVersion& plugin : *sGMPCapabilities) {
+ if (GMPCapability::Supports(plugin.mCapabilities, api, *aTags)) {
+ *aHasPlugin = true;
+ return NS_OK;
+ }
+ }
+
+ *aHasPlugin = false;
+ return NS_OK;
+}
+
+class GetNodeIdDone : public GetServiceChildCallback
+{
+public:
+ GetNodeIdDone(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback)
+ : mOrigin(aOrigin),
+ mTopLevelOrigin(aTopLevelOrigin),
+ mGMPName(aGMPName),
+ mInPrivateBrowsing(aInPrivateBrowsing),
+ mCallback(Move(aCallback))
+ {
+ }
+
+ void Done(GMPServiceChild* aGMPServiceChild) override
+ {
+ if (!aGMPServiceChild) {
+ mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
+ return;
+ }
+
+ nsCString outId;
+ if (!aGMPServiceChild->SendGetGMPNodeId(mOrigin, mTopLevelOrigin,
+ mGMPName,
+ mInPrivateBrowsing, &outId)) {
+ mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
+ return;
+ }
+
+ mCallback->Done(NS_OK, outId);
+ }
+
+private:
+ nsString mOrigin;
+ nsString mTopLevelOrigin;
+ nsString mGMPName;
+ bool mInPrivateBrowsing;
+ UniquePtr<GetNodeIdCallback> mCallback;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing,
+ UniquePtr<GetNodeIdCallback>&& aCallback)
+{
+ UniquePtr<GetServiceChildCallback> callback(
+ new GetNodeIdDone(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, Move(aCallback)));
+ GetServiceChild(Move(callback));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData)
+{
+ LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic));
+ if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+ if (mServiceChild) {
+ mozilla::SyncRunnable::DispatchToThread(mGMPThread,
+ WrapRunnable(mServiceChild.get(),
+ &PGMPServiceChild::Close));
+ mServiceChild = nullptr;
+ }
+ ShutdownGMPThread();
+ }
+
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginServiceChild::GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (!mServiceChild) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (!contentChild) {
+ return;
+ }
+ mGetServiceChildCallbacks.AppendElement(Move(aCallback));
+ if (mGetServiceChildCallbacks.Length() == 1) {
+ NS_DispatchToMainThread(WrapRunnable(contentChild,
+ &dom::ContentChild::SendCreateGMPService));
+ }
+ return;
+ }
+
+ aCallback->Done(mServiceChild.get());
+}
+
+void
+GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild)
+{
+ mServiceChild = Move(aServiceChild);
+ nsTArray<UniquePtr<GetServiceChildCallback>> getServiceChildCallbacks;
+ getServiceChildCallbacks.SwapElements(mGetServiceChildCallbacks);
+ for (uint32_t i = 0, length = getServiceChildCallbacks.Length(); i < length; ++i) {
+ getServiceChildCallbacks[i]->Done(mServiceChild.get());
+ }
+}
+
+void
+GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
+{
+ if (mServiceChild) {
+ mServiceChild->RemoveGMPContentParent(aGMPContentParent);
+ }
+}
+
+GMPServiceChild::GMPServiceChild()
+{
+}
+
+GMPServiceChild::~GMPServiceChild()
+{
+}
+
+PGMPContentParent*
+GMPServiceChild::AllocPGMPContentParent(Transport* aTransport,
+ ProcessId aOtherPid)
+{
+ MOZ_ASSERT(!mContentParents.GetWeak(aOtherPid));
+
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+
+ RefPtr<GMPContentParent> parent = new GMPContentParent();
+
+ DebugOnly<bool> ok = parent->Open(aTransport, aOtherPid,
+ XRE_GetIOMessageLoop(),
+ mozilla::ipc::ParentSide);
+ MOZ_ASSERT(ok);
+
+ mContentParents.Put(aOtherPid, parent);
+ return parent;
+}
+
+void
+GMPServiceChild::GetBridgedGMPContentParent(ProcessId aOtherPid,
+ GMPContentParent** aGMPContentParent)
+{
+ mContentParents.Get(aOtherPid, aGMPContentParent);
+}
+
+void
+GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
+{
+ for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<GMPContentParent>& parent = iter.Data();
+ if (parent == aGMPContentParent) {
+ iter.Remove();
+ break;
+ }
+ }
+}
+
+void
+GMPServiceChild::GetAlreadyBridgedTo(nsTArray<base::ProcessId>& aAlreadyBridgedTo)
+{
+ aAlreadyBridgedTo.SetCapacity(mContentParents.Count());
+ for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
+ const uint64_t& id = iter.Key();
+ aAlreadyBridgedTo.AppendElement(id);
+ }
+}
+
+class OpenPGMPServiceChild : public mozilla::Runnable
+{
+public:
+ OpenPGMPServiceChild(UniquePtr<GMPServiceChild>&& aGMPServiceChild,
+ mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherPid)
+ : mGMPServiceChild(Move(aGMPServiceChild)),
+ mTransport(aTransport),
+ mOtherPid(aOtherPid)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(!gmp->mServiceChild);
+ if (mGMPServiceChild->Open(mTransport, mOtherPid, XRE_GetIOMessageLoop(),
+ ipc::ChildSide)) {
+ gmp->SetServiceChild(Move(mGMPServiceChild));
+ } else {
+ gmp->SetServiceChild(nullptr);
+ }
+ return NS_OK;
+ }
+
+private:
+ UniquePtr<GMPServiceChild> mGMPServiceChild;
+ mozilla::ipc::Transport* mTransport;
+ base::ProcessId mOtherPid;
+};
+
+/* static */
+PGMPServiceChild*
+GMPServiceChild::Create(Transport* aTransport, ProcessId aOtherPid)
+{
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(!gmp->mServiceChild);
+
+ UniquePtr<GMPServiceChild> serviceChild(new GMPServiceChild());
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ GMPServiceChild* result = serviceChild.get();
+ rv = gmpThread->Dispatch(new OpenPGMPServiceChild(Move(serviceChild),
+ aTransport,
+ aOtherPid),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPServiceChild.h b/dom/media/gmp/GMPServiceChild.h
new file mode 100644
index 000000000..63b1325bb
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -0,0 +1,105 @@
+/* -*- 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 GMPServiceChild_h_
+#define GMPServiceChild_h_
+
+#include "GMPService.h"
+#include "base/process.h"
+#include "mozilla/ipc/Transport.h"
+#include "mozilla/gmp/PGMPServiceChild.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+class GMPServiceChild;
+
+class GetServiceChildCallback
+{
+public:
+ GetServiceChildCallback()
+ {
+ MOZ_COUNT_CTOR(GetServiceChildCallback);
+ }
+ virtual ~GetServiceChildCallback()
+ {
+ MOZ_COUNT_DTOR(GetServiceChildCallback);
+ }
+ virtual void Done(GMPServiceChild* aGMPServiceChild) = 0;
+};
+
+class GeckoMediaPluginServiceChild : public GeckoMediaPluginService
+{
+ friend class GMPServiceChild;
+
+public:
+ static already_AddRefed<GeckoMediaPluginServiceChild> GetSingleton();
+
+ NS_IMETHOD HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool *aRetVal) override;
+ NS_IMETHOD GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsingMode,
+ UniquePtr<GetNodeIdCallback>&& aCallback) override;
+
+ NS_DECL_NSIOBSERVER
+
+ void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild);
+
+ void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
+
+ static void UpdateGMPCapabilities(nsTArray<mozilla::dom::GMPCapabilityData>&& aCapabilities);
+
+protected:
+ void InitializePlugins(AbstractThread*) override
+ {
+ // Nothing to do here.
+ }
+ bool GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+ override;
+
+private:
+ friend class OpenPGMPServiceChild;
+
+ void GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback);
+
+ UniquePtr<GMPServiceChild> mServiceChild;
+ nsTArray<UniquePtr<GetServiceChildCallback>> mGetServiceChildCallbacks;
+};
+
+class GMPServiceChild : public PGMPServiceChild
+{
+public:
+ explicit GMPServiceChild();
+ virtual ~GMPServiceChild();
+
+ PGMPContentParent* AllocPGMPContentParent(Transport* aTransport,
+ ProcessId aOtherPid) override;
+
+ void GetBridgedGMPContentParent(ProcessId aOtherPid,
+ GMPContentParent** aGMPContentParent);
+ void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
+
+ void GetAlreadyBridgedTo(nsTArray<ProcessId>& aAlreadyBridgedTo);
+
+ static PGMPServiceChild* Create(Transport* aTransport, ProcessId aOtherPid);
+
+private:
+ nsRefPtrHashtable<nsUint64HashKey, GMPContentParent> mContentParents;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPServiceChild_h_
diff --git a/dom/media/gmp/GMPServiceParent.cpp b/dom/media/gmp/GMPServiceParent.cpp
new file mode 100644
index 000000000..a4afbdad4
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -0,0 +1,1933 @@
+/* -*- 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 "GMPServiceParent.h"
+#include "GMPService.h"
+#include "prio.h"
+#include "base/task.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/ContentParent.h"
+#include "GMPParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "nsAutoPtr.h"
+#include "nsIObserverService.h"
+#include "GeckoChildProcessHost.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/Services.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Unused.h"
+#include "GMPDecryptorParent.h"
+#include "GMPAudioDecoderParent.h"
+#include "nsComponentManagerUtils.h"
+#include "runnable_utils.h"
+#include "VideoUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIXULRuntime.h"
+#include "GMPDecoderModule.h"
+#include <limits>
+#include "MediaPrefs.h"
+
+using mozilla::ipc::Transport;
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPService"
+
+namespace gmp {
+
+static const uint32_t NodeIdSaltLength = 32;
+
+already_AddRefed<GeckoMediaPluginServiceParent>
+GeckoMediaPluginServiceParent::GetSingleton()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ RefPtr<GeckoMediaPluginService> service(
+ GeckoMediaPluginServiceParent::GetGeckoMediaPluginService());
+#ifdef DEBUG
+ if (service) {
+ nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
+ CallQueryInterface(service.get(), getter_AddRefs(chromeService));
+ MOZ_ASSERT(chromeService);
+ }
+#endif
+ return service.forget().downcast<GeckoMediaPluginServiceParent>();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceParent,
+ GeckoMediaPluginService,
+ mozIGeckoMediaPluginChromeService,
+ nsIAsyncShutdownBlocker)
+
+GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
+ : mShuttingDown(false)
+ , mScannedPluginOnDisk(false)
+ , mWaitingForPluginsSyncShutdown(false)
+ , mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor")
+ , mLoadPluginsFromDiskComplete(false)
+ , mServiceUserCount(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mInitPromise.SetMonitor(&mInitPromiseMonitor);
+}
+
+GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent()
+{
+ MOZ_ASSERT(mPlugins.IsEmpty());
+ MOZ_ASSERT(mAsyncShutdownPlugins.IsEmpty());
+}
+
+int32_t
+GeckoMediaPluginServiceParent::AsyncShutdownTimeoutMs()
+{
+ return MediaPrefs::GMPAsyncShutdownTimeout();
+}
+
+nsresult
+GeckoMediaPluginServiceParent::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "profile-change-teardown", false));
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "last-pb-context-exited", false));
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "browser:purge-session-history", false));
+
+#ifdef DEBUG
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "mediakeys-request", false));
+#endif
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->AddObserver("media.gmp.plugin.crash", this, false);
+ }
+
+ nsresult rv = InitStorage();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Kick off scanning for plugins
+ nsCOMPtr<nsIThread> thread;
+ rv = GetThread(getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Detect if GMP storage has an incompatible version, and if so nuke it.
+ int32_t version = Preferences::GetInt("media.gmp.storage.version.observed", 0);
+ int32_t expected = Preferences::GetInt("media.gmp.storage.version.expected", 0);
+ if (version != expected) {
+ Preferences::SetInt("media.gmp.storage.version.observed", expected);
+ return GMPDispatch(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::ClearStorage));
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+CloneAndAppend(nsIFile* aFile, const nsAString& aDir)
+{
+ nsCOMPtr<nsIFile> f;
+ nsresult rv = aFile->Clone(getter_AddRefs(f));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ rv = f->Append(aDir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return f.forget();
+}
+
+static nsresult
+GMPPlatformString(nsAString& aOutPlatform)
+{
+ // Append the OS and arch so that we don't reuse the storage if the profile is
+ // copied or used under a different bit-ness, or copied to another platform.
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ if (!runtime) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString OS;
+ nsresult rv = runtime->GetOS(OS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString arch;
+ rv = runtime->GetXPCOMABI(arch);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString platform;
+ platform.Append(OS);
+ platform.AppendLiteral("_");
+ platform.Append(arch);
+
+ aOutPlatform = NS_ConvertUTF8toUTF16(platform);
+
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginServiceParent::InitStorage()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // GMP storage should be used in the chrome process only.
+ if (!XRE_IsParentProcess()) {
+ return NS_OK;
+ }
+
+ // Directory service is main thread only, so cache the profile dir here
+ // so that we can use it off main thread.
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mStorageBaseDir));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->AppendNative(NS_LITERAL_CSTRING("gmp"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> gmpDirWithoutPlatform;
+ rv = mStorageBaseDir->Clone(getter_AddRefs(gmpDirWithoutPlatform));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString platform;
+ rv = GMPPlatformString(platform);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Append(platform);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
+ return rv;
+ }
+
+ return GeckoMediaPluginService::Init();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData)
+{
+ LOGD(("%s::%s topic='%s' data='%s'", __CLASS__, __FUNCTION__,
+ aTopic, NS_ConvertUTF16toUTF8(aSomeData).get()));
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
+ if (branch) {
+ bool crashNow = false;
+ if (NS_LITERAL_STRING("media.gmp.plugin.crash").Equals(aSomeData)) {
+ branch->GetBoolPref("media.gmp.plugin.crash", &crashNow);
+ }
+ if (crashNow) {
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ gmpThread = mGMPThread;
+ }
+ if (gmpThread) {
+ gmpThread->Dispatch(WrapRunnable(this,
+ &GeckoMediaPluginServiceParent::CrashPlugins),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ }
+ } else if (!strcmp("profile-change-teardown", aTopic)) {
+
+ // How shutdown works:
+ //
+ // Some GMPs require time to do bookkeeping upon shutdown. These GMPs
+ // need to be given time to access storage during shutdown. To signal
+ // that time to shutdown is required, those GMPs implement the
+ // GMPAsyncShutdown interface.
+ //
+ // When we startup the child process, we query the GMP for the
+ // GMPAsyncShutdown interface, and if it's present, we send a message
+ // back to the GMPParent, which then registers the GMPParent by calling
+ // GMPService::AsyncShutdownNeeded().
+ //
+ // On shutdown, we set mWaitingForPluginsSyncShutdown to true, and then
+ // call UnloadPlugins on the GMPThread, and process events on the main
+ // thread until 1. An event sets mWaitingForPluginsSyncShutdown=false on
+ // the main thread; then 2. All async-shutdown plugins have indicated
+ // they have completed shutdown.
+ //
+ // UnloadPlugins() sends close messages for all plugins' API objects to
+ // the GMP interfaces in the child process, and then sends the async
+ // shutdown notifications to child GMPs. When a GMP has completed its
+ // shutdown, it calls GMPAsyncShutdownHost::ShutdownComplete(), which
+ // sends a message back to the parent, which calls
+ // GMPService::AsyncShutdownComplete(). If all plugins requiring async
+ // shutdown have called AsyncShutdownComplete() we stick a dummy event on
+ // the main thread, where the list of pending plugins is checked. We must
+ // use an event to do this, as we must ensure the main thread processes an
+ // event to run its loop. This will unblock the main thread, and shutdown
+ // of other components will proceed.
+ //
+ // During shutdown, each GMPParent starts a timer, and pretends shutdown
+ // is complete if it is taking too long.
+ //
+ // We shutdown in "profile-change-teardown", as the profile dir is
+ // still writable then, and it's required for GMPStorage. We block the
+ // shutdown process by spinning the main thread event loop until all GMPs
+ // have shutdown, or timeout has occurred.
+ //
+ // GMPStorage needs to work up until the shutdown-complete notification
+ // arrives from the GMP process.
+
+ mWaitingForPluginsSyncShutdown = true;
+
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mShuttingDown);
+ mShuttingDown = true;
+ gmpThread = mGMPThread;
+ }
+
+ if (gmpThread) {
+ LOGD(("%s::%s Starting to unload plugins, waiting for first sync shutdown..."
+ , __CLASS__, __FUNCTION__));
+ gmpThread->Dispatch(
+ NewRunnableMethod(this,
+ &GeckoMediaPluginServiceParent::UnloadPlugins),
+ NS_DISPATCH_NORMAL);
+
+ // Wait for UnloadPlugins() to do initial sync shutdown...
+ while (mWaitingForPluginsSyncShutdown) {
+ NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+ }
+
+ // Wait for other plugins (if any) to do async shutdown...
+ auto syncShutdownPluginsRemaining =
+ std::numeric_limits<decltype(mAsyncShutdownPlugins.Length())>::max();
+ for (;;) {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mAsyncShutdownPlugins.IsEmpty()) {
+ LOGD(("%s::%s Finished unloading all plugins"
+ , __CLASS__, __FUNCTION__));
+ break;
+ } else if (mAsyncShutdownPlugins.Length() < syncShutdownPluginsRemaining) {
+ // First time here, or number of pending plugins has decreased.
+ // -> Update list of pending plugins in crash report.
+ syncShutdownPluginsRemaining = mAsyncShutdownPlugins.Length();
+ LOGD(("%s::%s Still waiting for %d plugins to shutdown..."
+ , __CLASS__, __FUNCTION__, (int)syncShutdownPluginsRemaining));
+ }
+ }
+ NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+ }
+ } else {
+ // GMP thread has already shutdown.
+ MOZ_ASSERT(mPlugins.IsEmpty());
+ mWaitingForPluginsSyncShutdown = false;
+ }
+
+ } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+ MOZ_ASSERT(mShuttingDown);
+ ShutdownGMPThread();
+ } else if (!strcmp("last-pb-context-exited", aTopic)) {
+ // When Private Browsing mode exits, all we need to do is clear
+ // mTempNodeIds. This drops all the node ids we've cached in memory
+ // for PB origin-pairs. If we try to open an origin-pair for non-PB
+ // mode, we'll get the NodeId salt stored on-disk, and if we try to
+ // open a PB mode origin-pair, we'll re-generate new salt.
+ mTempNodeIds.Clear();
+ } else if (!strcmp("browser:purge-session-history", aTopic)) {
+ // Clear everything!
+ if (!aSomeData || nsDependentString(aSomeData).IsEmpty()) {
+ return GMPDispatch(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::ClearStorage));
+ }
+
+ // Clear nodeIds/records modified after |t|.
+ nsresult rv;
+ PRTime t = nsDependentString(aSomeData).ToInteger64(&rv, 10);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return GMPDispatch(NewRunnableMethod<PRTime>(
+ this, &GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread,
+ t));
+ }
+
+ return NS_OK;
+}
+
+RefPtr<GenericPromise>
+GeckoMediaPluginServiceParent::EnsureInitialized() {
+ MonitorAutoLock lock(mInitPromiseMonitor);
+ if (mLoadPluginsFromDiskComplete) {
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }
+ // We should have an init promise in flight.
+ MOZ_ASSERT(!mInitPromise.IsEmpty());
+ return mInitPromise.Ensure(__func__);
+}
+
+bool
+GeckoMediaPluginServiceParent::GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+{
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ return false;
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ nsCString nodeId(aNodeId);
+ nsTArray<nsCString> tags(aTags);
+ nsCString api(aAPI);
+ GetGMPContentParentCallback* rawCallback = aCallback.release();
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ EnsureInitialized()->Then(thread, __func__,
+ [self, tags, api, nodeId, rawCallback, helper]() -> void {
+ UniquePtr<GetGMPContentParentCallback> callback(rawCallback);
+ RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeId, api, tags);
+ LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)self, (void *)gmp, api.get()));
+ if (!gmp) {
+ NS_WARNING("GeckoMediaPluginServiceParent::GetContentParentFrom failed");
+ callback->Done(nullptr);
+ return;
+ }
+ self->ConnectCrashHelper(gmp->GetPluginId(), helper);
+ gmp->GetGMPContentParent(Move(callback));
+ },
+ [rawCallback]() -> void {
+ UniquePtr<GetGMPContentParentCallback> callback(rawCallback);
+ NS_WARNING("GMPService::EnsureInitialized failed.");
+ callback->Done(nullptr);
+ });
+ return true;
+}
+
+void
+GeckoMediaPluginServiceParent::InitializePlugins(
+ AbstractThread* aAbstractGMPThread)
+{
+ MOZ_ASSERT(aAbstractGMPThread);
+ MonitorAutoLock lock(mInitPromiseMonitor);
+ if (mLoadPluginsFromDiskComplete) {
+ return;
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__);
+ InvokeAsync(aAbstractGMPThread, this, __func__,
+ &GeckoMediaPluginServiceParent::LoadFromEnvironment)
+ ->Then(aAbstractGMPThread, __func__,
+ [self]() -> void {
+ MonitorAutoLock lock(self->mInitPromiseMonitor);
+ self->mLoadPluginsFromDiskComplete = true;
+ self->mInitPromise.Resolve(true, __func__);
+ },
+ [self]() -> void {
+ MonitorAutoLock lock(self->mInitPromiseMonitor);
+ self->mLoadPluginsFromDiskComplete = true;
+ self->mInitPromise.Reject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+void
+GeckoMediaPluginServiceParent::AsyncShutdownNeeded(GMPParent* aParent)
+{
+ LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mAsyncShutdownPlugins.Contains(aParent));
+ mAsyncShutdownPlugins.AppendElement(aParent);
+}
+
+void
+GeckoMediaPluginServiceParent::AsyncShutdownComplete(GMPParent* aParent)
+{
+ LOGD(("%s::%s %p '%s'", __CLASS__, __FUNCTION__,
+ aParent, aParent->GetDisplayName().get()));
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mAsyncShutdownPlugins.RemoveElement(aParent);
+ }
+
+ if (mShuttingDownOnGMPThread) {
+ // The main thread may be waiting for async shutdown of plugins,
+ // one of which has completed. Wake up the main thread by sending a task.
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete));
+ NS_DispatchToMainThread(task);
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // Nothing to do, this task is just used to wake up the event loop in Observe().
+}
+
+void
+GeckoMediaPluginServiceParent::NotifySyncShutdownComplete()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mWaitingForPluginsSyncShutdown = false;
+}
+
+bool
+GeckoMediaPluginServiceParent::IsShuttingDown()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ return mShuttingDownOnGMPThread;
+}
+
+void
+GeckoMediaPluginServiceParent::UnloadPlugins()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ MOZ_ASSERT(!mShuttingDownOnGMPThread);
+ mShuttingDownOnGMPThread = true;
+
+ nsTArray<RefPtr<GMPParent>> plugins;
+ {
+ MutexAutoLock lock(mMutex);
+ // Move all plugins references to a local array. This way mMutex won't be
+ // locked when calling CloseActive (to avoid inter-locking).
+ Swap(plugins, mPlugins);
+ }
+
+ LOGD(("%s::%s plugins:%u including async:%u", __CLASS__, __FUNCTION__,
+ plugins.Length(), mAsyncShutdownPlugins.Length()));
+#ifdef DEBUG
+ for (const auto& plugin : plugins) {
+ LOGD(("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__,
+ plugin->GetDisplayName().get()));
+ }
+ for (const auto& plugin : mAsyncShutdownPlugins) {
+ LOGD(("%s::%s async plugin: '%s'", __CLASS__, __FUNCTION__,
+ plugin->GetDisplayName().get()));
+ }
+#endif
+ // Note: CloseActive may be async; it could actually finish
+ // shutting down when all the plugins have unloaded.
+ for (const auto& plugin : plugins) {
+ plugin->CloseActive(true);
+ }
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
+ this, &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete));
+ NS_DispatchToMainThread(task);
+}
+
+void
+GeckoMediaPluginServiceParent::CrashPlugins()
+{
+ LOGD(("%s::%s", __CLASS__, __FUNCTION__));
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ MutexAutoLock lock(mMutex);
+ for (size_t i = 0; i < mPlugins.Length(); i++) {
+ mPlugins[i]->Crash();
+ }
+}
+
+RefPtr<GenericPromise::AllPromiseType>
+GeckoMediaPluginServiceParent::LoadFromEnvironment()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ const char* env = PR_GetEnv("MOZ_GMP_PATH");
+ if (!env || !*env) {
+ return GenericPromise::AllPromiseType::CreateAndResolve(true, __func__);
+ }
+
+ nsString allpaths;
+ if (NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) {
+ return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsTArray<RefPtr<GenericPromise>> promises;
+ uint32_t pos = 0;
+ while (pos < allpaths.Length()) {
+ // Loop over multiple path entries separated by colons (*nix) or
+ // semicolons (Windows)
+ int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos);
+ if (next == -1) {
+ promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos))));
+ break;
+ } else {
+ promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos, next - pos))));
+ pos = next + 1;
+ }
+ }
+
+ mScannedPluginOnDisk = true;
+ return GenericPromise::All(thread, promises);
+}
+
+class NotifyObserversTask final : public mozilla::Runnable {
+public:
+ explicit NotifyObserversTask(const char* aTopic, nsString aData = EmptyString())
+ : mTopic(aTopic)
+ , mData(aData)
+ {}
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, mTopic, mData.get());
+ }
+ return NS_OK;
+ }
+private:
+ ~NotifyObserversTask() {}
+ const char* mTopic;
+ const nsString mData;
+};
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::PathRunnable::Run()
+{
+ mService->RemoveOnGMPThread(mPath,
+ mOperation == REMOVE_AND_DELETE_FROM_DISK,
+ mDefer);
+
+ mService->UpdateContentProcessGMPCapabilities();
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities()
+{
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task =
+ NewRunnableMethod(this, &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities);
+ NS_DispatchToMainThread(task);
+ return;
+ }
+
+ typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+ typedef mozilla::dom::GMPAPITags GMPAPITags;
+ typedef mozilla::dom::ContentParent ContentParent;
+
+ nsTArray<GMPCapabilityData> caps;
+ {
+ MutexAutoLock lock(mMutex);
+ for (const RefPtr<GMPParent>& gmp : mPlugins) {
+ // We have multiple instances of a GMPParent for a given GMP in the
+ // list, one per origin. So filter the list so that we don't include
+ // the same GMP's capabilities twice.
+ NS_ConvertUTF16toUTF8 name(gmp->GetPluginBaseName());
+ bool found = false;
+ for (const GMPCapabilityData& cap : caps) {
+ if (cap.name().Equals(name)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ continue;
+ }
+ GMPCapabilityData x;
+ x.name() = name;
+ x.version() = gmp->GetVersion();
+ for (const GMPCapability& tag : gmp->GetCapabilities()) {
+ x.capabilities().AppendElement(GMPAPITags(tag.mAPIName, tag.mAPITags));
+ }
+ caps.AppendElement(Move(x));
+ }
+ }
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendGMPsChanged(caps);
+ }
+
+ // For non-e10s, we must fire a notification so that any MediaKeySystemAccess
+ // requests waiting on a CDM to download will retry.
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsService);
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
+ }
+}
+
+RefPtr<GenericPromise>
+GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(const nsAString& aDirectory)
+{
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsString dir(aDirectory);
+ RefPtr<GeckoMediaPluginServiceParent> self = this;
+ return InvokeAsync(thread, this, __func__, &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
+ ->Then(AbstractThread::MainThread(), __func__,
+ [dir, self]() -> void {
+ LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s succeeded",
+ NS_ConvertUTF16toUTF8(dir).get()));
+ MOZ_ASSERT(NS_IsMainThread());
+ self->UpdateContentProcessGMPCapabilities();
+ },
+ [dir]() -> void {
+ LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s failed",
+ NS_ConvertUTF16toUTF8(dir).get()));
+ })
+ ->CompletionPromise();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<GenericPromise> p = AsyncAddPluginDirectory(aDirectory);
+ Unused << p;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::RemovePluginDirectory(const nsAString& aDirectory)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return GMPDispatch(new PathRunnable(this, aDirectory,
+ PathRunnable::EOperation::REMOVE));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::RemoveAndDeletePluginDirectory(
+ const nsAString& aDirectory, const bool aDefer)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return GMPDispatch(
+ new PathRunnable(this, aDirectory,
+ PathRunnable::EOperation::REMOVE_AND_DELETE_FROM_DISK,
+ aDefer));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool* aHasPlugin)
+{
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aHasPlugin);
+
+ nsresult rv = EnsurePluginsOnDiskScanned();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to load GMPs from disk.");
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ nsCString api(aAPI);
+ size_t index = 0;
+ RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, *aTags, &index);
+ *aHasPlugin = !!gmp;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned()
+{
+ const char* env = nullptr;
+ if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) {
+ // We have a MOZ_GMP_PATH environment variable which may specify the
+ // location of plugins to load, and we haven't yet scanned the disk to
+ // see if there are plugins there. Get the GMP thread, which will
+ // cause an event to be dispatched to which scans for plugins. We
+ // dispatch a sync event to the GMP thread here in order to wait until
+ // after the GMP thread has scanned any paths in MOZ_GMP_PATH.
+ nsresult rv = GMPDispatch(new mozilla::Runnable(), NS_DISPATCH_SYNC);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now");
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::FindPluginForAPIFrom(size_t aSearchStartIndex,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ size_t* aOutPluginIndex)
+{
+ mMutex.AssertCurrentThreadOwns();
+ for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) {
+ RefPtr<GMPParent> gmp = mPlugins[i];
+ if (!GMPCapability::Supports(gmp->GetCapabilities(), aAPI, aTags)) {
+ continue;
+ }
+ if (aOutPluginIndex) {
+ *aOutPluginIndex = i;
+ }
+ return gmp.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::SelectPluginForAPI(const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread,
+ "Can't clone GMP plugins on non-GMP threads.");
+
+ GMPParent* gmpToClone = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ size_t index = 0;
+ RefPtr<GMPParent> gmp;
+ while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) {
+ if (aNodeId.IsEmpty()) {
+ if (gmp->CanBeSharedCrossNodeIds()) {
+ return gmp.forget();
+ }
+ } else if (gmp->CanBeUsedFrom(aNodeId)) {
+ return gmp.forget();
+ }
+
+ if (!gmpToClone ||
+ (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) {
+ // This GMP has the correct type but has the wrong nodeId; hold on to it
+ // in case we need to clone it.
+ // Prefer GMPs in-use for the case where an upgraded plugin version is
+ // waiting for the old one to die. If the old plugin is in use, we
+ // should continue using it so that any persistent state remains
+ // consistent. Otherwise, just check that the plugin isn't scheduled
+ // for deletion.
+ gmpToClone = gmp;
+ }
+ // Loop around and try the next plugin; it may be usable from aNodeId.
+ index++;
+ }
+ }
+
+ // Plugin exists, but we can't use it due to cross-origin separation. Create a
+ // new one.
+ if (gmpToClone) {
+ RefPtr<GMPParent> clone = ClonePlugin(gmpToClone);
+ {
+ MutexAutoLock lock(mMutex);
+ mPlugins.AppendElement(clone);
+ }
+ if (!aNodeId.IsEmpty()) {
+ clone->SetNodeId(aNodeId);
+ }
+ return clone.forget();
+ }
+
+ return nullptr;
+}
+
+RefPtr<GMPParent>
+CreateGMPParent()
+{
+ return new GMPParent();
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::ClonePlugin(const GMPParent* aOriginal)
+{
+ MOZ_ASSERT(aOriginal);
+
+ RefPtr<GMPParent> gmp = CreateGMPParent();
+ nsresult rv = gmp ? gmp->CloneFrom(aOriginal) : NS_ERROR_NOT_AVAILABLE;
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't Create GMPParent");
+ return nullptr;
+ }
+
+ return gmp.forget();
+}
+
+RefPtr<GenericPromise>
+GeckoMediaPluginServiceParent::AddOnGMPThread(nsString aDirectory)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ nsCString dir = NS_ConvertUTF16toUTF8(aDirectory);
+ RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+ if (!thread) {
+ LOGD(("%s::%s: %s No GMP Thread", __CLASS__, __FUNCTION__, dir.get()));
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+ LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, dir.get()));
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ RefPtr<GMPParent> gmp = CreateGMPParent();
+ if (!gmp) {
+ NS_WARNING("Can't Create GMPParent");
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ return gmp->Init(this, directory)->Then(thread, __func__,
+ [gmp, self, dir]() -> void {
+ LOGD(("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__, dir.get()));
+ {
+ MutexAutoLock lock(self->mMutex);
+ self->mPlugins.AppendElement(gmp);
+ }
+ },
+ [dir]() -> void {
+ LOGD(("%s::%s: %s Failed", __CLASS__, __FUNCTION__, dir.get()));
+ })
+ ->CompletionPromise();
+}
+
+void
+GeckoMediaPluginServiceParent::RemoveOnGMPThread(const nsAString& aDirectory,
+ const bool aDeleteFromDisk,
+ const bool aCanDefer)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Plugin destruction can modify |mPlugins|. Put them aside for now and
+ // destroy them once we're done with |mPlugins|.
+ nsTArray<RefPtr<GMPParent>> deadPlugins;
+
+ bool inUse = false;
+ MutexAutoLock lock(mMutex);
+ for (size_t i = mPlugins.Length(); i-- > 0; ) {
+ nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
+ bool equals;
+ if (NS_FAILED(directory->Equals(pluginpath, &equals)) || !equals) {
+ continue;
+ }
+
+ RefPtr<GMPParent> gmp = mPlugins[i];
+ if (aDeleteFromDisk && gmp->State() != GMPStateNotLoaded) {
+ // We have to wait for the child process to release its lib handle
+ // before we can delete the GMP.
+ inUse = true;
+ gmp->MarkForDeletion();
+
+ if (!mPluginsWaitingForDeletion.Contains(aDirectory)) {
+ mPluginsWaitingForDeletion.AppendElement(aDirectory);
+ }
+ }
+
+ if (gmp->State() == GMPStateNotLoaded || !aCanDefer) {
+ // GMP not in use or shutdown is being forced; can shut it down now.
+ deadPlugins.AppendElement(gmp);
+ mPlugins.RemoveElementAt(i);
+ }
+ }
+
+ {
+ MutexAutoUnlock unlock(mMutex);
+ for (auto& gmp : deadPlugins) {
+ gmp->AbortAsyncShutdown();
+ gmp->CloseActive(true);
+ }
+ }
+
+ if (aDeleteFromDisk && !inUse) {
+ // Ensure the GMP dir and all files in it are writable, so we have
+ // permission to delete them.
+ directory->SetPermissions(0700);
+ DirectoryEnumerator iter(directory, DirectoryEnumerator::FilesAndDirs);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ dirEntry->SetPermissions(0700);
+ }
+ if (NS_SUCCEEDED(directory->Remove(true))) {
+ mPluginsWaitingForDeletion.RemoveElement(aDirectory);
+ NS_DispatchToMainThread(new NotifyObserversTask("gmp-directory-deleted",
+ nsString(aDirectory)),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+// May remove when Bug 1043671 is fixed
+static void Dummy(RefPtr<GMPParent>& aOnDeathsDoor)
+{
+ // exists solely to do nothing and let the Runnable kill the GMPParent
+ // when done.
+}
+
+void
+GeckoMediaPluginServiceParent::PluginTerminated(const RefPtr<GMPParent>& aPlugin)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+ if (aPlugin->IsMarkedForDeletion()) {
+ nsCString path8;
+ RefPtr<nsIFile> dir = aPlugin->GetDirectory();
+ nsresult rv = dir->GetNativePath(path8);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsString path = NS_ConvertUTF8toUTF16(path8);
+ if (mPluginsWaitingForDeletion.Contains(path)) {
+ RemoveOnGMPThread(path, true /* delete */, true /* can defer */);
+ }
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ReAddOnGMPThread(const RefPtr<GMPParent>& aOld)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, (void*) aOld));
+
+ RefPtr<GMPParent> gmp;
+ if (!mShuttingDownOnGMPThread) {
+ // We're not shutting down, so replace the old plugin in the list with a
+ // clone which is in a pristine state. Note: We place the plugin in
+ // the same slot in the array as a hack to ensure if we re-request with
+ // the same capabilities we get an instance of the same plugin.
+ gmp = ClonePlugin(aOld);
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mPlugins.Contains(aOld));
+ if (mPlugins.Contains(aOld)) {
+ mPlugins[mPlugins.IndexOf(aOld)] = gmp;
+ }
+ } else {
+ // We're shutting down; don't re-add plugin, let the old plugin die.
+ MutexAutoLock lock(mMutex);
+ mPlugins.RemoveElement(aOld);
+ }
+ // Schedule aOld to be destroyed. We can't destroy it from here since we
+ // may be inside ActorDestroyed() for it.
+ NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetStorageDir(nsIFile** aOutFile)
+{
+ if (NS_WARN_IF(!mStorageBaseDir)) {
+ return NS_ERROR_FAILURE;
+ }
+ return mStorageBaseDir->Clone(aOutFile);
+}
+
+static nsresult
+WriteToFile(nsIFile* aPath,
+ const nsCString& aFileName,
+ const nsCString& aData)
+{
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aPath->Clone(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->AppendNative(aFileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ PRFileDesc* f = nullptr;
+ rv = path->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, PR_IRWXU, &f);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t len = PR_Write(f, aData.get(), aData.Length());
+ PR_Close(f);
+ if (NS_WARN_IF(len < 0 || (size_t)len != aData.Length())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+ReadFromFile(nsIFile* aPath,
+ const nsACString& aFileName,
+ nsACString& aOutData,
+ int32_t aMaxLength)
+{
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = aPath->Clone(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->AppendNative(aFileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ PRFileDesc* f = nullptr;
+ rv = path->OpenNSPRFileDesc(PR_RDONLY | PR_CREATE_FILE, PR_IRWXU, &f);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ auto size = PR_Seek(f, 0, PR_SEEK_END);
+ PR_Seek(f, 0, PR_SEEK_SET);
+
+ if (size > aMaxLength) {
+ return NS_ERROR_FAILURE;
+ }
+ aOutData.SetLength(size);
+
+ auto len = PR_Read(f, aOutData.BeginWriting(), size);
+ PR_Close(f);
+ if (NS_WARN_IF(len != size)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ReadSalt(nsIFile* aPath, nsACString& aOutData)
+{
+ return ReadFromFile(aPath, NS_LITERAL_CSTRING("salt"),
+ aOutData, NodeIdSaltLength);
+
+}
+
+already_AddRefed<GMPStorage>
+GeckoMediaPluginServiceParent::GetMemoryStorageFor(const nsACString& aNodeId)
+{
+ RefPtr<GMPStorage> s;
+ if (!mTempGMPStorage.Get(aNodeId, getter_AddRefs(s))) {
+ s = CreateGMPMemoryStorage();
+ mTempGMPStorage.Put(aNodeId, s);
+ }
+ return s.forget();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::IsPersistentStorageAllowed(const nsACString& aNodeId,
+ bool* aOutAllowed)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ NS_ENSURE_ARG(aOutAllowed);
+ // We disallow persistent storage for the NodeId used for shared GMP
+ // decoding, to prevent GMP decoding being used to track what a user
+ // watches somehow.
+ *aOutAllowed = !aNodeId.Equals(SHARED_GMP_DECODING_NODE_ID) &&
+ mPersistentStorageAllowed.Get(aNodeId);
+ return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing,
+ nsACString& aOutId)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
+
+ nsresult rv;
+
+ if (aOrigin.EqualsLiteral("null") ||
+ aOrigin.IsEmpty() ||
+ aTopLevelOrigin.EqualsLiteral("null") ||
+ aTopLevelOrigin.IsEmpty()) {
+ // (origin, topLevelOrigin) is null or empty; this is for an anonymous
+ // origin, probably a local file, for which we don't provide persistent storage.
+ // Generate a random node id, and don't store it so that the GMP's storage
+ // is temporary and the process for this GMP is not shared with GMP
+ // instances that have the same nodeId.
+ nsAutoCString salt;
+ rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ aOutId = salt;
+ mPersistentStorageAllowed.Put(salt, false);
+ return NS_OK;
+ }
+
+ const uint32_t hash = AddToHash(HashString(aOrigin),
+ HashString(aTopLevelOrigin));
+
+ if (aInPrivateBrowsing) {
+ // For PB mode, we store the node id, indexed by the origin pair and GMP name,
+ // so that if the same origin pair is opened for the same GMP in this session,
+ // it gets the same node id.
+ const uint32_t pbHash = AddToHash(HashString(aGMPName), hash);
+ nsCString* salt = nullptr;
+ if (!(salt = mTempNodeIds.Get(pbHash))) {
+ // No salt stored, generate and temporarily store some for this id.
+ nsAutoCString newSalt;
+ rv = GenerateRandomPathName(newSalt, NodeIdSaltLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ salt = new nsCString(newSalt);
+ mTempNodeIds.Put(pbHash, salt);
+ mPersistentStorageAllowed.Put(*salt, false);
+ }
+ aOutId = *salt;
+ return NS_OK;
+ }
+
+ // Otherwise, try to see if we've previously generated and stored salt
+ // for this origin pair.
+ nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
+ rv = GetStorageDir(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->Append(aGMPName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->AppendNative(NS_LITERAL_CSTRING("id"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString hashStr;
+ hashStr.AppendInt((int64_t)hash);
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash
+ rv = path->AppendNative(hashStr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> saltFile;
+ rv = path->Clone(getter_AddRefs(saltFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = saltFile->AppendNative(NS_LITERAL_CSTRING("salt"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString salt;
+ bool exists = false;
+ rv = saltFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (!exists) {
+ // No stored salt for this origin. Generate salt, and store it and
+ // the origin on disk.
+ nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(salt.Length() == NodeIdSaltLength);
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/salt
+ rv = WriteToFile(path, NS_LITERAL_CSTRING("salt"), salt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/origin
+ rv = WriteToFile(path,
+ NS_LITERAL_CSTRING("origin"),
+ NS_ConvertUTF16toUTF8(aOrigin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin
+ rv = WriteToFile(path,
+ NS_LITERAL_CSTRING("topLevelOrigin"),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ } else {
+ rv = ReadSalt(path, salt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ aOutId = salt;
+ mPersistentStorageAllowed.Put(salt, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing,
+ UniquePtr<GetNodeIdCallback>&& aCallback)
+{
+ nsCString nodeId;
+ nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, nodeId);
+ aCallback->Done(rv, nodeId);
+ return rv;
+}
+
+static bool
+ExtractHostName(const nsACString& aOrigin, nsACString& aOutData)
+{
+ nsCString str;
+ str.Assign(aOrigin);
+ int begin = str.Find("://");
+ // The scheme is missing!
+ if (begin == -1) {
+ return false;
+ }
+
+ int end = str.RFind(":");
+ // Remove the port number
+ if (end != begin) {
+ str.SetLength(end);
+ }
+
+ nsDependentCSubstring host(str, begin + 3);
+ aOutData.Assign(host);
+ return true;
+}
+
+bool
+MatchOrigin(nsIFile* aPath,
+ const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+{
+ // http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax
+ static const uint32_t MaxDomainLength = 253;
+
+ nsresult rv;
+ nsCString str;
+ nsCString originNoSuffix;
+ mozilla::PrincipalOriginAttributes originAttributes;
+
+ rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("origin"), str, MaxDomainLength);
+ if (!originAttributes.PopulateFromOrigin(str, originNoSuffix)) {
+ // Fails on parsing the originAttributes, treat this as a non-match.
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) && str.Equals(aSite) &&
+ aPattern.Matches(originAttributes)) {
+ return true;
+ }
+
+ mozilla::PrincipalOriginAttributes topLevelOriginAttributes;
+ rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("topLevelOrigin"), str, MaxDomainLength);
+ if (!topLevelOriginAttributes.PopulateFromOrigin(str, originNoSuffix)) {
+ // Fails on paring the originAttributes, treat this as a non-match.
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) && str.Equals(aSite) &&
+ aPattern.Matches(topLevelOriginAttributes)) {
+ return true;
+ }
+ return false;
+}
+
+template<typename T> static void
+KillPlugins(const nsTArray<RefPtr<GMPParent>>& aPlugins,
+ Mutex& aMutex, T&& aFilter)
+{
+ // Shutdown the plugins when |aFilter| evaluates to true.
+ // After we clear storage data, node IDs will become invalid and shouldn't be
+ // used anymore. We need to kill plugins with such nodeIDs.
+ // Note: we can't shut them down while holding the lock,
+ // as the lock is not re-entrant and shutdown requires taking the lock.
+ // The plugin list is only edited on the GMP thread, so this should be OK.
+ nsTArray<RefPtr<GMPParent>> pluginsToKill;
+ {
+ MutexAutoLock lock(aMutex);
+ for (size_t i = 0; i < aPlugins.Length(); i++) {
+ RefPtr<GMPParent> parent(aPlugins[i]);
+ if (aFilter(parent)) {
+ pluginsToKill.AppendElement(parent);
+ }
+ }
+ }
+
+ for (size_t i = 0; i < pluginsToKill.Length(); i++) {
+ pluginsToKill[i]->CloseActive(false);
+ // Abort async shutdown because we're going to wipe the plugin's storage,
+ // so we don't want it writing more data in its async shutdown path.
+ pluginsToKill[i]->AbortAsyncShutdown();
+ }
+}
+
+static nsresult
+DeleteDir(nsIFile* aPath)
+{
+ bool exists = false;
+ nsresult rv = aPath->Exists(&exists);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (exists) {
+ return aPath->Remove(true);
+ }
+ return NS_OK;
+}
+
+struct NodeFilter {
+ explicit NodeFilter(const nsTArray<nsCString>& nodeIDs) : mNodeIDs(nodeIDs) {}
+ bool operator()(GMPParent* aParent) {
+ return mNodeIDs.Contains(aParent->GetNodeId());
+ }
+private:
+ const nsTArray<nsCString>& mNodeIDs;
+};
+
+void
+GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(DirectoryFilter& aFilter)
+{
+ // $profileDir/gmp/$platform/
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = GetStorageDir(getter_AddRefs(path));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Iterate all sub-folders of $profileDir/gmp/$platform/, i.e. the dirs in which
+ // specific GMPs store their data.
+ DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
+ for (nsCOMPtr<nsIFile> pluginDir; (pluginDir = iter.Next()) != nullptr;) {
+ ClearNodeIdAndPlugin(pluginDir, aFilter);
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+ DirectoryFilter& aFilter)
+{
+ // $profileDir/gmp/$platform/$gmpName/id/
+ nsCOMPtr<nsIFile> path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("id"));
+ if (!path) {
+ return;
+ }
+
+ // Iterate all sub-folders of $profileDir/gmp/$platform/$gmpName/id/
+ nsTArray<nsCString> nodeIDsToClear;
+ DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ // dirEntry is the hash of origins, i.e.:
+ // $profileDir/gmp/$platform/$gmpName/id/$originHash/
+ if (!aFilter(dirEntry)) {
+ continue;
+ }
+ nsAutoCString salt;
+ if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) {
+ // Keep node IDs to clear data/plugins associated with them later.
+ nodeIDsToClear.AppendElement(salt);
+ // Also remove node IDs from the table.
+ mPersistentStorageAllowed.Remove(salt);
+ }
+ // Now we can remove the directory for the origin pair.
+ if (NS_FAILED(dirEntry->Remove(true))) {
+ NS_WARNING("Failed to delete the directory for the origin pair");
+ }
+ }
+
+ // Kill plugin instances that have node IDs being cleared.
+ KillPlugins(mPlugins, mMutex, NodeFilter(nodeIDsToClear));
+
+ // Clear all storage in $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
+ path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("storage"));
+ if (!path) {
+ return;
+ }
+
+ for (const nsCString& nodeId : nodeIDsToClear) {
+ nsCOMPtr<nsIFile> dirEntry;
+ nsresult rv = path->Clone(getter_AddRefs(dirEntry));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ rv = dirEntry->AppendNative(nodeId);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (NS_FAILED(DeleteDir(dirEntry))) {
+ NS_WARNING("Failed to delete GMP storage directory for the node");
+ }
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread(const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: origin=%s", __CLASS__, __FUNCTION__, aSite.Data()));
+
+ struct OriginFilter : public DirectoryFilter {
+ explicit OriginFilter(const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+ : mSite(aSite)
+ , mPattern(aPattern)
+ { }
+ bool operator()(nsIFile* aPath) override {
+ return MatchOrigin(aPath, mSite, mPattern);
+ }
+ private:
+ const nsACString& mSite;
+ const mozilla::OriginAttributesPattern& mPattern;
+ } filter(aSite, aPattern);
+
+ ClearNodeIdAndPlugin(filter);
+}
+
+void
+GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread(PRTime aSince)
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s: since=%lld", __CLASS__, __FUNCTION__, (int64_t)aSince));
+
+ struct MTimeFilter : public DirectoryFilter {
+ explicit MTimeFilter(PRTime aSince)
+ : mSince(aSince) {}
+
+ // Return true if any files under aPath is modified after |mSince|.
+ bool IsModifiedAfter(nsIFile* aPath) {
+ PRTime lastModified;
+ nsresult rv = aPath->GetLastModifiedTime(&lastModified);
+ if (NS_SUCCEEDED(rv) && lastModified >= mSince) {
+ return true;
+ }
+ DirectoryEnumerator iter(aPath, DirectoryEnumerator::FilesAndDirs);
+ for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+ if (IsModifiedAfter(dirEntry)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // |aPath| is $profileDir/gmp/$platform/$gmpName/id/$originHash/
+ bool operator()(nsIFile* aPath) override {
+ if (IsModifiedAfter(aPath)) {
+ return true;
+ }
+
+ nsAutoCString salt;
+ if (NS_FAILED(ReadSalt(aPath, salt))) {
+ return false;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/
+ nsCOMPtr<nsIFile> idDir;
+ if (NS_FAILED(aPath->GetParent(getter_AddRefs(idDir)))) {
+ return false;
+ }
+ // $profileDir/gmp/$platform/$gmpName/
+ nsCOMPtr<nsIFile> temp;
+ if (NS_FAILED(idDir->GetParent(getter_AddRefs(temp)))) {
+ return false;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/storage/
+ if (NS_FAILED(temp->Append(NS_LITERAL_STRING("storage")))) {
+ return false;
+ }
+ // $profileDir/gmp/$platform/$gmpName/storage/$originSalt
+ return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp);
+ }
+ private:
+ const PRTime mSince;
+ } filter(aSince);
+
+ ClearNodeIdAndPlugin(filter);
+
+ NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::ForgetThisSite(const nsAString& aSite,
+ const nsAString& aPattern)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mozilla::OriginAttributesPattern pattern;
+
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return ForgetThisSiteNative(aSite, pattern);
+}
+
+nsresult
+GeckoMediaPluginServiceParent::ForgetThisSiteNative(const nsAString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return GMPDispatch(NewRunnableMethod<nsCString, mozilla::OriginAttributesPattern>(
+ this, &GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread,
+ NS_ConvertUTF16toUTF8(aSite), aPattern));
+}
+
+static bool IsNodeIdValid(GMPParent* aParent) {
+ return !aParent->GetNodeId().IsEmpty();
+}
+
+static nsCOMPtr<nsIAsyncShutdownClient>
+GetShutdownBarrier()
+{
+ nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
+ MOZ_RELEASE_ASSERT(svc);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier;
+ nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
+
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_RELEASE_ASSERT(barrier);
+ return barrier.forget();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetName(nsAString& aName)
+{
+ aName = NS_LITERAL_STRING("GeckoMediaPluginServiceParent: shutdown");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetState(nsIPropertyBag**)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::BlockShutdown(nsIAsyncShutdownClient*)
+{
+ return NS_OK;
+}
+
+void
+GeckoMediaPluginServiceParent::ServiceUserCreated()
+{
+ MOZ_ASSERT(mServiceUserCount >= 0);
+ if (++mServiceUserCount == 1) {
+ nsresult rv = GetShutdownBarrier()->AddBlocker(
+ this, NS_LITERAL_STRING(__FILE__), __LINE__,
+ NS_LITERAL_STRING("GeckoMediaPluginServiceParent shutdown"));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ServiceUserDestroyed()
+{
+ MOZ_ASSERT(mServiceUserCount > 0);
+ if (--mServiceUserCount == 0) {
+ nsresult rv = GetShutdownBarrier()->RemoveBlocker(this);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void
+GeckoMediaPluginServiceParent::ClearStorage()
+{
+ MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+ LOGD(("%s::%s", __CLASS__, __FUNCTION__));
+
+ // Kill plugins with valid nodeIDs.
+ KillPlugins(mPlugins, mMutex, &IsNodeIdValid);
+
+ nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
+ nsresult rv = GetStorageDir(getter_AddRefs(path));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (NS_FAILED(DeleteDir(path))) {
+ NS_WARNING("Failed to delete GMP storage directory");
+ }
+
+ // Clear private-browsing storage.
+ mTempGMPStorage.Clear();
+
+ NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL);
+}
+
+already_AddRefed<GMPParent>
+GeckoMediaPluginServiceParent::GetById(uint32_t aPluginId)
+{
+ MutexAutoLock lock(mMutex);
+ for (const RefPtr<GMPParent>& gmp : mPlugins) {
+ if (gmp->GetPluginId() == aPluginId) {
+ return do_AddRef(gmp);
+ }
+ }
+ return nullptr;
+}
+
+GMPServiceParent::~GMPServiceParent()
+{
+ NS_DispatchToMainThread(
+ NewRunnableMethod(mService.get(),
+ &GeckoMediaPluginServiceParent::ServiceUserDestroyed));
+}
+
+bool
+GMPServiceParent::RecvSelectGMP(const nsCString& aNodeId,
+ const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags,
+ uint32_t* aOutPluginId,
+ nsresult* aOutRv)
+{
+ if (mService->IsShuttingDown()) {
+ *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ return true;
+ }
+
+ RefPtr<GMPParent> gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags);
+ if (gmp) {
+ *aOutPluginId = gmp->GetPluginId();
+ *aOutRv = NS_OK;
+ } else {
+ *aOutRv = NS_ERROR_FAILURE;
+ }
+
+ nsCString api = aTags[0];
+ LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get()));
+
+ return true;
+}
+
+bool
+GMPServiceParent::RecvLaunchGMP(const uint32_t& aPluginId,
+ nsTArray<ProcessId>&& aAlreadyBridgedTo,
+ ProcessId* aOutProcessId,
+ nsCString* aOutDisplayName,
+ nsresult* aOutRv)
+{
+ *aOutRv = NS_OK;
+ if (mService->IsShuttingDown()) {
+ *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ return true;
+ }
+
+ RefPtr<GMPParent> gmp(mService->GetById(aPluginId));
+ if (!gmp) {
+ *aOutRv = NS_ERROR_FAILURE;
+ return true;
+ }
+
+ if (!gmp->EnsureProcessLoaded(aOutProcessId)) {
+ return false;
+ }
+
+ *aOutDisplayName = gmp->GetDisplayName();
+
+ return aAlreadyBridgedTo.Contains(*aOutProcessId) || gmp->Bridge(this);
+}
+
+bool
+GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
+ const nsString& aTopLevelOrigin,
+ const nsString& aGMPName,
+ const bool& aInPrivateBrowsing,
+ nsCString* aID)
+{
+ nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName,
+ aInPrivateBrowsing, *aID);
+ return NS_SUCCEEDED(rv);
+}
+
+class DeleteGMPServiceParent : public mozilla::Runnable
+{
+public:
+ explicit DeleteGMPServiceParent(GMPServiceParent* aToDelete)
+ : mToDelete(aToDelete)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ return NS_OK;
+ }
+
+private:
+ nsAutoPtr<GMPServiceParent> mToDelete;
+};
+
+void GMPServiceParent::CloseTransport(Monitor* aSyncMonitor, bool* aCompleted)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ MonitorAutoLock lock(*aSyncMonitor);
+
+ // This deletes the transport.
+ SetTransport(nullptr);
+
+ *aCompleted = true;
+ lock.NotifyAll();
+}
+
+void
+GMPServiceParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ Monitor monitor("DeleteGMPServiceParent");
+ bool completed = false;
+
+ // Make sure the IPC channel is closed before destroying mToDelete.
+ MonitorAutoLock lock(monitor);
+ RefPtr<Runnable> task =
+ NewNonOwningRunnableMethod<Monitor*, bool*>(this,
+ &GMPServiceParent::CloseTransport,
+ &monitor,
+ &completed);
+ XRE_GetIOMessageLoop()->PostTask(Move(task.forget()));
+
+ while (!completed) {
+ lock.Wait();
+ }
+
+ NS_DispatchToCurrentThread(new DeleteGMPServiceParent(this));
+}
+
+class OpenPGMPServiceParent : public mozilla::Runnable
+{
+public:
+ OpenPGMPServiceParent(GMPServiceParent* aGMPServiceParent,
+ mozilla::ipc::Transport* aTransport,
+ base::ProcessId aOtherPid,
+ bool* aResult)
+ : mGMPServiceParent(aGMPServiceParent),
+ mTransport(aTransport),
+ mOtherPid(aOtherPid),
+ mResult(aResult)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ *mResult = mGMPServiceParent->Open(mTransport, mOtherPid,
+ XRE_GetIOMessageLoop(), ipc::ParentSide);
+ return NS_OK;
+ }
+
+private:
+ GMPServiceParent* mGMPServiceParent;
+ mozilla::ipc::Transport* mTransport;
+ base::ProcessId mOtherPid;
+ bool* mResult;
+};
+
+/* static */
+PGMPServiceParent*
+GMPServiceParent::Create(Transport* aTransport, ProcessId aOtherPid)
+{
+ RefPtr<GeckoMediaPluginServiceParent> gmp =
+ GeckoMediaPluginServiceParent::GetSingleton();
+
+ if (gmp->mShuttingDown) {
+ // Shutdown is initiated. There is no point creating a new actor.
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsAutoPtr<GMPServiceParent> serviceParent(new GMPServiceParent(gmp));
+
+ bool ok;
+ rv = gmpThread->Dispatch(new OpenPGMPServiceParent(serviceParent,
+ aTransport,
+ aOtherPid, &ok),
+ NS_DISPATCH_SYNC);
+ if (NS_FAILED(rv) || !ok) {
+ return nullptr;
+ }
+
+ return serviceParent.forget();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPServiceParent.h b/dom/media/gmp/GMPServiceParent.h
new file mode 100644
index 000000000..49d81055b
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -0,0 +1,261 @@
+/* -*- 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 GMPServiceParent_h_
+#define GMPServiceParent_h_
+
+#include "GMPService.h"
+#include "mozilla/gmp/PGMPServiceParent.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Atomics.h"
+#include "nsIAsyncShutdown.h"
+#include "nsThreadUtils.h"
+#include "mozilla/MozPromise.h"
+#include "GMPStorage.h"
+
+template <class> struct already_AddRefed;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GeckoMediaPluginServiceParent final : public GeckoMediaPluginService
+ , public mozIGeckoMediaPluginChromeService
+ , public nsIAsyncShutdownBlocker
+{
+public:
+ static already_AddRefed<GeckoMediaPluginServiceParent> GetSingleton();
+
+ GeckoMediaPluginServiceParent();
+ nsresult Init() override;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCSHUTDOWNBLOCKER
+
+ // mozIGeckoMediaPluginService
+ NS_IMETHOD HasPluginForAPI(const nsACString& aAPI,
+ nsTArray<nsCString>* aTags,
+ bool *aRetVal) override;
+ NS_IMETHOD GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsingMode,
+ UniquePtr<GetNodeIdCallback>&& aCallback) override;
+
+ NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
+ NS_DECL_NSIOBSERVER
+
+ void AsyncShutdownNeeded(GMPParent* aParent);
+ void AsyncShutdownComplete(GMPParent* aParent);
+
+ int32_t AsyncShutdownTimeoutMs();
+ RefPtr<GenericPromise> EnsureInitialized();
+ RefPtr<GenericPromise> AsyncAddPluginDirectory(const nsAString& aDirectory);
+
+ // GMP thread access only
+ bool IsShuttingDown();
+
+ already_AddRefed<GMPStorage> GetMemoryStorageFor(const nsACString& aNodeId);
+ nsresult ForgetThisSiteNative(const nsAString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern);
+
+ // Notifies that some user of this class is created/destroyed.
+ void ServiceUserCreated();
+ void ServiceUserDestroyed();
+
+ void UpdateContentProcessGMPCapabilities();
+
+private:
+ friend class GMPServiceParent;
+
+ virtual ~GeckoMediaPluginServiceParent();
+
+ void ClearStorage();
+
+ already_AddRefed<GMPParent> SelectPluginForAPI(const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags);
+
+ already_AddRefed<GMPParent> FindPluginForAPIFrom(size_t aSearchStartIndex,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ size_t* aOutPluginIndex);
+
+ nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing, nsACString& aOutId);
+
+ void UnloadPlugins();
+ void CrashPlugins();
+ void NotifySyncShutdownComplete();
+ void NotifyAsyncShutdownComplete();
+
+ void ProcessPossiblePlugin(nsIFile* aDir);
+
+ void RemoveOnGMPThread(const nsAString& aDirectory,
+ const bool aDeleteFromDisk,
+ const bool aCanDefer);
+
+ nsresult SetAsyncShutdownTimeout();
+
+ struct DirectoryFilter {
+ virtual bool operator()(nsIFile* aPath) = 0;
+ ~DirectoryFilter() {}
+ };
+ void ClearNodeIdAndPlugin(DirectoryFilter& aFilter);
+ void ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+ DirectoryFilter& aFilter);
+ void ForgetThisSiteOnGMPThread(const nsACString& aOrigin,
+ const mozilla::OriginAttributesPattern& aPattern);
+ void ClearRecentHistoryOnGMPThread(PRTime aSince);
+
+ already_AddRefed<GMPParent> GetById(uint32_t aPluginId);
+
+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);
+ bool GetContentParentFrom(GMPCrashHelper* aHelper,
+ const nsACString& aNodeId,
+ const nsCString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ UniquePtr<GetGMPContentParentCallback>&& aCallback)
+ 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();
+
+ class PathRunnable : public Runnable
+ {
+ public:
+ enum EOperation {
+ REMOVE,
+ REMOVE_AND_DELETE_FROM_DISK,
+ };
+
+ PathRunnable(GeckoMediaPluginServiceParent* aService, const nsAString& aPath,
+ EOperation aOperation, bool aDefer = false)
+ : mService(aService)
+ , mPath(aPath)
+ , mOperation(aOperation)
+ , mDefer(aDefer)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+ nsString mPath;
+ EOperation mOperation;
+ bool mDefer;
+ };
+
+ // Protected by mMutex from the base class.
+ nsTArray<RefPtr<GMPParent>> mPlugins;
+ bool mShuttingDown;
+ nsTArray<RefPtr<GMPParent>> mAsyncShutdownPlugins;
+
+ // True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
+ // plugins found there into mPlugins.
+ Atomic<bool> mScannedPluginOnDisk;
+
+ template<typename T>
+ class MainThreadOnly {
+ public:
+ MOZ_IMPLICIT MainThreadOnly(T aValue)
+ : mValue(aValue)
+ {}
+ operator T&() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mValue;
+ }
+
+ private:
+ T mValue;
+ };
+
+ MainThreadOnly<bool> mWaitingForPluginsSyncShutdown;
+
+ nsTArray<nsString> mPluginsWaitingForDeletion;
+
+ nsCOMPtr<nsIFile> mStorageBaseDir;
+
+ // Hashes of (origin,topLevelOrigin) to the node id for
+ // non-persistent sessions.
+ nsClassHashtable<nsUint32HashKey, nsCString> mTempNodeIds;
+
+ // Hashes node id to whether that node id is allowed to store data
+ // persistently on disk.
+ nsDataHashtable<nsCStringHashKey, bool> mPersistentStorageAllowed;
+
+ // Synchronization for barrier that ensures we've loaded GMPs from
+ // MOZ_GMP_PATH before allowing GetContentParentFrom() to proceed.
+ Monitor mInitPromiseMonitor;
+ MozPromiseHolder<GenericPromise> mInitPromise;
+ bool mLoadPluginsFromDiskComplete;
+
+ // Hashes nodeId to the hashtable of storage for that nodeId.
+ nsRefPtrHashtable<nsCStringHashKey, GMPStorage> mTempGMPStorage;
+
+ // Tracks how many users are running (on the GMP thread). Only when this count
+ // drops to 0 can we safely shut down the thread.
+ MainThreadOnly<int32_t> mServiceUserCount;
+};
+
+nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData);
+bool MatchOrigin(nsIFile* aPath,
+ const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern);
+
+class GMPServiceParent final : public PGMPServiceParent
+{
+public:
+ explicit GMPServiceParent(GeckoMediaPluginServiceParent* aService)
+ : mService(aService)
+ {
+ mService->ServiceUserCreated();
+ }
+ virtual ~GMPServiceParent();
+
+ bool RecvGetGMPNodeId(const nsString& aOrigin,
+ const nsString& aTopLevelOrigin,
+ const nsString& aGMPName,
+ const bool& aInPrivateBrowsing,
+ nsCString* aID) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ static PGMPServiceParent* Create(Transport* aTransport, ProcessId aOtherPid);
+
+ bool RecvSelectGMP(const nsCString& aNodeId,
+ const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags,
+ uint32_t* aOutPluginId,
+ nsresult* aOutRv) override;
+
+ bool RecvLaunchGMP(const uint32_t& aPluginId,
+ nsTArray<ProcessId>&& aAlreadyBridgedTo,
+ ProcessId* aOutID,
+ nsCString* aOutDisplayName,
+ nsresult* aOutRv) override;
+
+private:
+ void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted);
+
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPServiceParent_h_
diff --git a/dom/media/gmp/GMPSharedMemManager.cpp b/dom/media/gmp/GMPSharedMemManager.cpp
new file mode 100644
index 000000000..86b5f9810
--- /dev/null
+++ b/dom/media/gmp/GMPSharedMemManager.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "GMPSharedMemManager.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace gmp {
+
+// Really one set of pools on each side of the plugin API.
+
+// YUV buffers go from Encoder parent to child; pool there, and then return
+// with Decoded() frames to the Decoder parent and goes into the parent pool.
+// Compressed (encoded) data goes from the Decoder parent to the child;
+// pool there, and then return with Encoded() frames and goes into the parent
+// pool.
+bool
+GMPSharedMemManager::MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass, size_t aSize,
+ ipc::Shmem::SharedMemory::SharedMemoryType aType,
+ ipc::Shmem* aMem)
+{
+ mData->CheckThread();
+
+ // first look to see if we have a free buffer large enough
+ for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) {
+ MOZ_ASSERT(GetGmpFreelist(aClass)[i].IsWritable());
+ if (aSize <= GetGmpFreelist(aClass)[i].Size<uint8_t>()) {
+ *aMem = GetGmpFreelist(aClass)[i];
+ GetGmpFreelist(aClass).RemoveElementAt(i);
+ return true;
+ }
+ }
+
+ // Didn't find a buffer free with enough space; allocate one
+ size_t pagesize = ipc::SharedMemory::SystemPageSize();
+ aSize = (aSize + (pagesize-1)) & ~(pagesize-1); // round up to page size
+ bool retval = Alloc(aSize, aType, aMem);
+ if (retval) {
+ // The allocator (or NeedsShmem call) should never return less than we ask for...
+ MOZ_ASSERT(aMem->Size<uint8_t>() >= aSize);
+ mData->mGmpAllocated[aClass]++;
+ }
+ return retval;
+}
+
+bool
+GMPSharedMemManager::MgrDeallocShmem(GMPSharedMem::GMPMemoryClasses aClass, ipc::Shmem& aMem)
+{
+ mData->CheckThread();
+
+ size_t size = aMem.Size<uint8_t>();
+ size_t total = 0;
+
+ // XXX Bug NNNNNNN Until we put better guards on ipc::shmem, verify we
+ // weren't fed an shmem we already had.
+ for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) {
+ if (NS_WARN_IF(aMem == GetGmpFreelist(aClass)[i])) {
+ // Safest to crash in this case; should never happen in normal
+ // operation.
+ MOZ_CRASH("Deallocating Shmem we already have in our cache!");
+ //return true;
+ }
+ }
+
+ // XXX This works; there are better pool algorithms. We need to avoid
+ // "falling off a cliff" with too low a number
+ if (GetGmpFreelist(aClass).Length() > 10) {
+ Dealloc(GetGmpFreelist(aClass)[0]);
+ GetGmpFreelist(aClass).RemoveElementAt(0);
+ // The allocation numbers will be fubar on the Child!
+ mData->mGmpAllocated[aClass]--;
+ }
+ for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) {
+ MOZ_ASSERT(GetGmpFreelist(aClass)[i].IsWritable());
+ total += GetGmpFreelist(aClass)[i].Size<uint8_t>();
+ if (size < GetGmpFreelist(aClass)[i].Size<uint8_t>()) {
+ GetGmpFreelist(aClass).InsertElementAt(i, aMem);
+ return true;
+ }
+ }
+ GetGmpFreelist(aClass).AppendElement(aMem);
+
+ return true;
+}
+
+uint32_t
+GMPSharedMemManager::NumInUse(GMPSharedMem::GMPMemoryClasses aClass)
+{
+ return mData->mGmpAllocated[aClass] - GetGmpFreelist(aClass).Length();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPSharedMemManager.h b/dom/media/gmp/GMPSharedMemManager.h
new file mode 100644
index 000000000..cc36f3fc0
--- /dev/null
+++ b/dom/media/gmp/GMPSharedMemManager.h
@@ -0,0 +1,82 @@
+/* -*- 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 GMPSharedMemManager_h_
+#define GMPSharedMemManager_h_
+
+#include "mozilla/ipc/Shmem.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPSharedMemManager;
+
+class GMPSharedMem
+{
+public:
+ typedef enum {
+ kGMPFrameData = 0,
+ kGMPEncodedData,
+ kGMPNumTypes
+ } GMPMemoryClasses;
+
+ // This is a heuristic - max of 10 free in the Child pool, plus those
+ // in-use for the encoder and decoder at the given moment and not yet
+ // returned to the parent pool (which is not included). If more than
+ // this are needed, we presume the client has either crashed or hung
+ // (perhaps temporarily).
+ static const uint32_t kGMPBufLimit = 20;
+
+ GMPSharedMem()
+ {
+ for (size_t i = 0; i < sizeof(mGmpAllocated)/sizeof(mGmpAllocated[0]); i++) {
+ mGmpAllocated[i] = 0;
+ }
+ }
+ virtual ~GMPSharedMem() {}
+
+ // Parent and child impls will differ here
+ virtual void CheckThread() = 0;
+
+protected:
+ friend class GMPSharedMemManager;
+
+ nsTArray<ipc::Shmem> mGmpFreelist[GMPSharedMem::kGMPNumTypes];
+ uint32_t mGmpAllocated[GMPSharedMem::kGMPNumTypes];
+};
+
+class GMPSharedMemManager
+{
+public:
+ explicit GMPSharedMemManager(GMPSharedMem *aData) : mData(aData) {}
+ virtual ~GMPSharedMemManager() {}
+
+ virtual bool MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass, size_t aSize,
+ ipc::Shmem::SharedMemory::SharedMemoryType aType,
+ ipc::Shmem* aMem);
+ virtual bool MgrDeallocShmem(GMPSharedMem::GMPMemoryClasses aClass, ipc::Shmem& aMem);
+
+ // So we can know if data is "piling up" for the plugin - I.e. it's hung or crashed
+ virtual uint32_t NumInUse(GMPSharedMem::GMPMemoryClasses aClass);
+
+ // These have to be implemented using the AllocShmem/etc provided by the IPDL-generated interfaces,
+ // so have the Parent/Child implement them.
+ virtual bool Alloc(size_t aSize, ipc::Shmem::SharedMemory::SharedMemoryType aType, ipc::Shmem* aMem) = 0;
+ virtual void Dealloc(ipc::Shmem& aMem) = 0;
+
+private:
+ nsTArray<ipc::Shmem>& GetGmpFreelist(GMPSharedMem::GMPMemoryClasses aTypes)
+ {
+ return mData->mGmpFreelist[aTypes];
+ }
+
+ GMPSharedMem *mData;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPSharedMemManager_h_
diff --git a/dom/media/gmp/GMPStorage.h b/dom/media/gmp/GMPStorage.h
new file mode 100644
index 000000000..db3aebc8c
--- /dev/null
+++ b/dom/media/gmp/GMPStorage.h
@@ -0,0 +1,41 @@
+/* -*- 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 GMPStorage_h_
+#define GMPStorage_h_
+
+#include "gmp-storage.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPStorage {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorage)
+
+ virtual GMPErr Open(const nsCString& aRecordName) = 0;
+ virtual bool IsOpen(const nsCString& aRecordName) const = 0;
+ virtual GMPErr Read(const nsCString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) = 0;
+ virtual GMPErr Write(const nsCString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) = 0;
+ virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const = 0;
+ virtual void Close(const nsCString& aRecordName) = 0;
+protected:
+ virtual ~GMPStorage() {}
+};
+
+already_AddRefed<GMPStorage> CreateGMPMemoryStorage();
+already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId,
+ const nsString& aGMPName);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif \ No newline at end of file
diff --git a/dom/media/gmp/GMPStorageChild.cpp b/dom/media/gmp/GMPStorageChild.cpp
new file mode 100644
index 000000000..052b56d1b
--- /dev/null
+++ b/dom/media/gmp/GMPStorageChild.cpp
@@ -0,0 +1,380 @@
+/* -*- 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 "GMPStorageChild.h"
+#include "GMPChild.h"
+#include "gmp-storage.h"
+#include "base/task.h"
+
+#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current())
+
+#define CALL_ON_GMP_THREAD(_func, ...) \
+ do { \
+ if (ON_GMP_THREAD()) { \
+ _func(__VA_ARGS__); \
+ } else { \
+ mPlugin->GMPMessageLoop()->PostTask( \
+ dont_add_new_uses_of_this::NewRunnableMethod(this, &GMPStorageChild::_func, ##__VA_ARGS__) \
+ ); \
+ } \
+ } while(false)
+
+static nsTArray<uint8_t>
+ToArray(const uint8_t* aData, uint32_t aDataSize)
+{
+ nsTArray<uint8_t> data;
+ data.AppendElements(aData, aDataSize);
+ return mozilla::Move(data);
+}
+
+namespace mozilla {
+namespace gmp {
+
+GMPRecordImpl::GMPRecordImpl(GMPStorageChild* aOwner,
+ const nsCString& aName,
+ GMPRecordClient* aClient)
+ : mName(aName)
+ , mClient(aClient)
+ , mOwner(aOwner)
+{
+}
+
+GMPErr
+GMPRecordImpl::Open()
+{
+ return mOwner->Open(this);
+}
+
+void
+GMPRecordImpl::OpenComplete(GMPErr aStatus)
+{
+ mClient->OpenComplete(aStatus);
+}
+
+GMPErr
+GMPRecordImpl::Read()
+{
+ return mOwner->Read(this);
+}
+
+void
+GMPRecordImpl::ReadComplete(GMPErr aStatus,
+ const uint8_t* aBytes,
+ uint32_t aLength)
+{
+ mClient->ReadComplete(aStatus, aBytes, aLength);
+}
+
+GMPErr
+GMPRecordImpl::Write(const uint8_t* aData, uint32_t aDataSize)
+{
+ return mOwner->Write(this, aData, aDataSize);
+}
+
+void
+GMPRecordImpl::WriteComplete(GMPErr aStatus)
+{
+ mClient->WriteComplete(aStatus);
+}
+
+GMPErr
+GMPRecordImpl::Close()
+{
+ RefPtr<GMPRecordImpl> kungfuDeathGrip(this);
+ // Delete our self reference.
+ Release();
+ mOwner->Close(this->Name());
+ return GMPNoErr;
+}
+
+GMPStorageChild::GMPStorageChild(GMPChild* aPlugin)
+ : mMonitor("GMPStorageChild")
+ , mPlugin(aPlugin)
+ , mShutdown(false)
+{
+ MOZ_ASSERT(ON_GMP_THREAD());
+}
+
+GMPErr
+GMPStorageChild::CreateRecord(const nsCString& aRecordName,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ MOZ_ASSERT(aRecordName.Length() && aOutRecord);
+
+ if (HasRecord(aRecordName)) {
+ return GMPRecordInUse;
+ }
+
+ RefPtr<GMPRecordImpl> record(new GMPRecordImpl(this, aRecordName, aClient));
+ mRecords.Put(aRecordName, record); // Addrefs
+
+ // The GMPRecord holds a self reference until the GMP calls Close() on
+ // it. This means the object is always valid (even if neutered) while
+ // the GMP expects it to be.
+ record.forget(aOutRecord);
+
+ return GMPNoErr;
+}
+
+bool
+GMPStorageChild::HasRecord(const nsCString& aRecordName)
+{
+ mMonitor.AssertCurrentThreadOwns();
+ return mRecords.Contains(aRecordName);
+}
+
+already_AddRefed<GMPRecordImpl>
+GMPStorageChild::GetRecord(const nsCString& aRecordName)
+{
+ MonitorAutoLock lock(mMonitor);
+ RefPtr<GMPRecordImpl> record;
+ mRecords.Get(aRecordName, getter_AddRefs(record));
+ return record.forget();
+}
+
+GMPErr
+GMPStorageChild::Open(GMPRecordImpl* aRecord)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ if (!HasRecord(aRecord->Name())) {
+ // Trying to re-open a record that has already been closed.
+ return GMPClosedErr;
+ }
+
+ CALL_ON_GMP_THREAD(SendOpen, aRecord->Name());
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPStorageChild::Read(GMPRecordImpl* aRecord)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ if (!HasRecord(aRecord->Name())) {
+ // Record not opened.
+ return GMPClosedErr;
+ }
+
+ CALL_ON_GMP_THREAD(SendRead, aRecord->Name());
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPStorageChild::Write(GMPRecordImpl* aRecord,
+ const uint8_t* aData,
+ uint32_t aDataSize)
+{
+ if (aDataSize > GMP_MAX_RECORD_SIZE) {
+ return GMPQuotaExceededErr;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ if (!HasRecord(aRecord->Name())) {
+ // Record not opened.
+ return GMPClosedErr;
+ }
+
+ CALL_ON_GMP_THREAD(SendWrite, aRecord->Name(), ToArray(aData, aDataSize));
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPStorageChild::Close(const nsCString& aRecordName)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (!HasRecord(aRecordName)) {
+ // Already closed.
+ return GMPClosedErr;
+ }
+
+ mRecords.Remove(aRecordName);
+
+ if (!mShutdown) {
+ CALL_ON_GMP_THREAD(SendClose, aRecordName);
+ }
+
+ return GMPNoErr;
+}
+
+bool
+GMPStorageChild::RecvOpenComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus)
+{
+ // We don't need a lock to read |mShutdown| since it is only changed in
+ // the GMP thread.
+ if (mShutdown) {
+ return true;
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return true;
+ }
+ record->OpenComplete(aStatus);
+ return true;
+}
+
+bool
+GMPStorageChild::RecvReadComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus,
+ InfallibleTArray<uint8_t>&& aBytes)
+{
+ if (mShutdown) {
+ return true;
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return true;
+ }
+ record->ReadComplete(aStatus, aBytes.Elements(), aBytes.Length());
+ return true;
+}
+
+bool
+GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus)
+{
+ if (mShutdown) {
+ return true;
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return true;
+ }
+ record->WriteComplete(aStatus);
+ return true;
+}
+
+GMPErr
+GMPStorageChild::EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg)
+{
+ MonitorAutoLock lock(mMonitor);
+
+ if (mShutdown) {
+ NS_WARNING("GMPStorage used after it's been shutdown!");
+ return GMPClosedErr;
+ }
+
+ MOZ_ASSERT(aRecvIteratorFunc);
+ mPendingRecordIterators.push(RecordIteratorContext(aRecvIteratorFunc, aUserArg));
+
+ CALL_ON_GMP_THREAD(SendGetRecordNames);
+
+ return GMPNoErr;
+}
+
+class GMPRecordIteratorImpl : public GMPRecordIterator {
+public:
+ explicit GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames)
+ : mRecordNames(aRecordNames)
+ , mIndex(0)
+ {
+ mRecordNames.Sort();
+ }
+
+ GMPErr GetName(const char** aOutName, uint32_t* aOutNameLength) override {
+ if (!aOutName || !aOutNameLength) {
+ return GMPInvalidArgErr;
+ }
+ if (mIndex == mRecordNames.Length()) {
+ return GMPEndOfEnumeration;
+ }
+ *aOutName = mRecordNames[mIndex].get();
+ *aOutNameLength = mRecordNames[mIndex].Length();
+ return GMPNoErr;
+ }
+
+ GMPErr NextRecord() override {
+ if (mIndex < mRecordNames.Length()) {
+ mIndex++;
+ }
+ return (mIndex < mRecordNames.Length()) ? GMPNoErr
+ : GMPEndOfEnumeration;
+ }
+
+ void Close() override {
+ delete this;
+ }
+
+private:
+ nsTArray<nsCString> mRecordNames;
+ size_t mIndex;
+};
+
+bool
+GMPStorageChild::RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames,
+ const GMPErr& aStatus)
+{
+ RecordIteratorContext ctx;
+ {
+ MonitorAutoLock lock(mMonitor);
+ if (mShutdown || mPendingRecordIterators.empty()) {
+ return true;
+ }
+ ctx = mPendingRecordIterators.front();
+ mPendingRecordIterators.pop();
+ }
+
+ if (GMP_FAILED(aStatus)) {
+ ctx.mFunc(nullptr, ctx.mUserArg, aStatus);
+ } else {
+ ctx.mFunc(new GMPRecordIteratorImpl(aRecordNames), ctx.mUserArg, GMPNoErr);
+ }
+
+ return true;
+}
+
+bool
+GMPStorageChild::RecvShutdown()
+{
+ // Block any new storage requests, and thus any messages back to the
+ // parent. We don't delete any objects here, as that may invalidate
+ // GMPRecord pointers held by the GMP.
+ MonitorAutoLock lock(mMonitor);
+ mShutdown = true;
+ while (!mPendingRecordIterators.empty()) {
+ mPendingRecordIterators.pop();
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+// avoid redefined macro in unified build
+#undef ON_GMP_THREAD
+#undef CALL_ON_GMP_THREAD
diff --git a/dom/media/gmp/GMPStorageChild.h b/dom/media/gmp/GMPStorageChild.h
new file mode 100644
index 000000000..3c5948a30
--- /dev/null
+++ b/dom/media/gmp/GMPStorageChild.h
@@ -0,0 +1,118 @@
+/* -*- 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 GMPStorageChild_h_
+#define GMPStorageChild_h_
+
+#include "mozilla/gmp/PGMPStorageChild.h"
+#include "gmp-storage.h"
+#include "nsTHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "gmp-platform.h"
+
+#include <queue>
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+class GMPStorageChild;
+
+class GMPRecordImpl : public GMPRecord
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRecordImpl)
+
+ GMPRecordImpl(GMPStorageChild* aOwner,
+ const nsCString& aName,
+ GMPRecordClient* aClient);
+
+ // GMPRecord.
+ GMPErr Open() override;
+ GMPErr Read() override;
+ GMPErr Write(const uint8_t* aData,
+ uint32_t aDataSize) override;
+ GMPErr Close() override;
+
+ const nsCString& Name() const { return mName; }
+
+ void OpenComplete(GMPErr aStatus);
+ void ReadComplete(GMPErr aStatus, const uint8_t* aBytes, uint32_t aLength);
+ void WriteComplete(GMPErr aStatus);
+
+private:
+ ~GMPRecordImpl() {}
+ const nsCString mName;
+ GMPRecordClient* const mClient;
+ GMPStorageChild* const mOwner;
+};
+
+class GMPStorageChild : public PGMPStorageChild
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageChild)
+
+ explicit GMPStorageChild(GMPChild* aPlugin);
+
+ GMPErr CreateRecord(const nsCString& aRecordName,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+ GMPErr Open(GMPRecordImpl* aRecord);
+
+ GMPErr Read(GMPRecordImpl* aRecord);
+
+ GMPErr Write(GMPRecordImpl* aRecord,
+ const uint8_t* aData,
+ uint32_t aDataSize);
+
+ GMPErr Close(const nsCString& aRecordName);
+
+ GMPErr EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+private:
+ bool HasRecord(const nsCString& aRecordName);
+ already_AddRefed<GMPRecordImpl> GetRecord(const nsCString& aRecordName);
+
+protected:
+ ~GMPStorageChild() {}
+
+ // PGMPStorageChild
+ bool RecvOpenComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus) override;
+ bool RecvReadComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus,
+ InfallibleTArray<uint8_t>&& aBytes) override;
+ bool RecvWriteComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus) override;
+ bool RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames,
+ const GMPErr& aStatus) override;
+ bool RecvShutdown() override;
+
+private:
+ Monitor mMonitor;
+ nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
+ GMPChild* mPlugin;
+
+ struct RecordIteratorContext {
+ explicit RecordIteratorContext(RecvGMPRecordIteratorPtr aFunc,
+ void* aUserArg)
+ : mFunc(aFunc)
+ , mUserArg(aUserArg)
+ {}
+ RecordIteratorContext() {}
+ RecvGMPRecordIteratorPtr mFunc;
+ void* mUserArg;
+ };
+
+ std::queue<RecordIteratorContext> mPendingRecordIterators;
+ bool mShutdown;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPStorageChild_h_
diff --git a/dom/media/gmp/GMPStorageParent.cpp b/dom/media/gmp/GMPStorageParent.cpp
new file mode 100644
index 000000000..e3eadeccf
--- /dev/null
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 "GMPStorageParent.h"
+#include "GMPParent.h"
+#include "gmp-storage.h"
+#include "mozilla/Unused.h"
+#include "mozIGeckoMediaPluginService.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
+ GMPParent* aPlugin)
+ : mNodeId(aNodeId)
+ , mPlugin(aPlugin)
+ , mShutdown(true)
+{
+}
+
+nsresult
+GMPStorageParent::Init()
+{
+ LOGD(("GMPStorageParent[%p]::Init()", this));
+
+ if (NS_WARN_IF(mNodeId.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<GeckoMediaPluginServiceParent> mps(GeckoMediaPluginServiceParent::GetSingleton());
+ if (NS_WARN_IF(!mps)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool persistent = false;
+ if (NS_WARN_IF(NS_FAILED(mps->IsPersistentStorageAllowed(mNodeId, &persistent)))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (persistent) {
+ mStorage = CreateGMPDiskStorage(mNodeId, mPlugin->GetPluginBaseName());
+ } else {
+ mStorage = mps->GetMemoryStorageFor(mNodeId);
+ }
+ if (!mStorage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mShutdown = false;
+ return NS_OK;
+}
+
+bool
+GMPStorageParent::RecvOpen(const nsCString& aRecordName)
+{
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')",
+ this, aRecordName.get()));
+
+ if (mShutdown) {
+ return false;
+ }
+
+ if (mNodeId.EqualsLiteral("null")) {
+ // Refuse to open storage if the page is opened from local disk,
+ // or shared across origin.
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; null nodeId",
+ this, aRecordName.get()));
+ Unused << SendOpenComplete(aRecordName, GMPGenericErr);
+ return true;
+ }
+
+ if (aRecordName.IsEmpty()) {
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty",
+ this, aRecordName.get()));
+ Unused << SendOpenComplete(aRecordName, GMPGenericErr);
+ return true;
+ }
+
+ if (mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use",
+ this, aRecordName.get()));
+ Unused << SendOpenComplete(aRecordName, GMPRecordInUse);
+ return true;
+ }
+
+ auto err = mStorage->Open(aRecordName);
+ MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName));
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d",
+ this, aRecordName.get(), err));
+ Unused << SendOpenComplete(aRecordName, err);
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvRead(const nsCString& aRecordName)
+{
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s')",
+ this, aRecordName.get()));
+
+ if (mShutdown) {
+ return false;
+ }
+
+ nsTArray<uint8_t> data;
+ if (!mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') failed; record not open",
+ this, aRecordName.get()));
+ Unused << SendReadComplete(aRecordName, GMPClosedErr, data);
+ } else {
+ GMPErr rv = mStorage->Read(aRecordName, data);
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') read %d bytes rv=%d",
+ this, aRecordName.get(), data.Length(), rv));
+ Unused << SendReadComplete(aRecordName, rv, data);
+ }
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvWrite(const nsCString& aRecordName,
+ InfallibleTArray<uint8_t>&& aBytes)
+{
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') %d bytes",
+ this, aRecordName.get(), aBytes.Length()));
+
+ if (mShutdown) {
+ return false;
+ }
+
+ if (!mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record not open",
+ this, aRecordName.get()));
+ Unused << SendWriteComplete(aRecordName, GMPClosedErr);
+ return true;
+ }
+
+ if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record too big",
+ this, aRecordName.get()));
+ Unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
+ return true;
+ }
+
+ GMPErr rv = mStorage->Write(aRecordName, aBytes);
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') write complete rv=%d",
+ this, aRecordName.get(), rv));
+
+ Unused << SendWriteComplete(aRecordName, rv);
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvGetRecordNames()
+{
+ if (mShutdown) {
+ return true;
+ }
+
+ nsTArray<nsCString> recordNames;
+ GMPErr status = mStorage->GetRecordNames(recordNames);
+
+ LOGD(("GMPStorageParent[%p]::RecvGetRecordNames() status=%d numRecords=%d",
+ this, status, recordNames.Length()));
+
+ Unused << SendRecordNames(recordNames, status);
+
+ return true;
+}
+
+bool
+GMPStorageParent::RecvClose(const nsCString& aRecordName)
+{
+ LOGD(("GMPStorageParent[%p]::RecvClose(record='%s')",
+ this, aRecordName.get()));
+
+ if (mShutdown) {
+ return true;
+ }
+
+ mStorage->Close(aRecordName);
+
+ return true;
+}
+
+void
+GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPStorageParent[%p]::ActorDestroy(reason=%d)", this, aWhy));
+ Shutdown();
+}
+
+void
+GMPStorageParent::Shutdown()
+{
+ LOGD(("GMPStorageParent[%p]::Shutdown()", this));
+
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+ Unused << SendShutdown();
+
+ mStorage = nullptr;
+
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPStorageParent.h b/dom/media/gmp/GMPStorageParent.h
new file mode 100644
index 000000000..3d2ea69ac
--- /dev/null
+++ b/dom/media/gmp/GMPStorageParent.h
@@ -0,0 +1,49 @@
+/* -*- 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 GMPStorageParent_h_
+#define GMPStorageParent_h_
+
+#include "mozilla/gmp/PGMPStorageParent.h"
+#include "GMPStorage.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GMPStorageParent : public PGMPStorageParent {
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
+ GMPStorageParent(const nsCString& aNodeId,
+ GMPParent* aPlugin);
+
+ nsresult Init();
+ void Shutdown();
+
+protected:
+ bool RecvOpen(const nsCString& aRecordName) override;
+ bool RecvRead(const nsCString& aRecordName) override;
+ bool RecvWrite(const nsCString& aRecordName,
+ InfallibleTArray<uint8_t>&& aBytes) override;
+ bool RecvGetRecordNames() override;
+ bool RecvClose(const nsCString& aRecordName) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ ~GMPStorageParent() {}
+
+ RefPtr<GMPStorage> mStorage;
+
+ const nsCString mNodeId;
+ RefPtr<GMPParent> mPlugin;
+ // True after Shutdown(), or if Init() has not completed successfully.
+ bool mShutdown;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPStorageParent_h_
diff --git a/dom/media/gmp/GMPTimerChild.cpp b/dom/media/gmp/GMPTimerChild.cpp
new file mode 100644
index 000000000..0b2b55c79
--- /dev/null
+++ b/dom/media/gmp/GMPTimerChild.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "GMPTimerChild.h"
+#include "GMPPlatform.h"
+#include "GMPChild.h"
+
+#define MAX_NUM_TIMERS 1000
+
+namespace mozilla {
+namespace gmp {
+
+GMPTimerChild::GMPTimerChild(GMPChild* aPlugin)
+ : mTimerCount(1)
+ , mPlugin(aPlugin)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+}
+
+GMPTimerChild::~GMPTimerChild()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+}
+
+GMPErr
+GMPTimerChild::SetTimer(GMPTask* aTask, int64_t aTimeoutMS)
+{
+ if (!aTask) {
+ NS_WARNING("Tried to set timer with null task!");
+ return GMPGenericErr;
+ }
+
+ if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
+ NS_WARNING("Tried to set GMP timer on non-main thread.");
+ return GMPGenericErr;
+ }
+
+ if (mTimers.Count() > MAX_NUM_TIMERS) {
+ return GMPQuotaExceededErr;
+ }
+ uint32_t timerId = mTimerCount;
+ mTimers.Put(timerId, aTask);
+ mTimerCount++;
+
+ if (!SendSetTimer(timerId, aTimeoutMS)) {
+ return GMPGenericErr;
+ }
+ return GMPNoErr;
+}
+
+bool
+GMPTimerChild::RecvTimerExpired(const uint32_t& aTimerId)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ GMPTask* task = mTimers.Get(aTimerId);
+ mTimers.Remove(aTimerId);
+ if (task) {
+ RunOnMainThread(task);
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPTimerChild.h b/dom/media/gmp/GMPTimerChild.h
new file mode 100644
index 000000000..c63a49480
--- /dev/null
+++ b/dom/media/gmp/GMPTimerChild.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPTimerChild_h_
+#define GMPTimerChild_h_
+
+#include "mozilla/gmp/PGMPTimerChild.h"
+#include "mozilla/Monitor.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+class GMPTimerChild : public PGMPTimerChild
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPTimerChild)
+
+ explicit GMPTimerChild(GMPChild* aPlugin);
+
+ GMPErr SetTimer(GMPTask* aTask, int64_t aTimeoutMS);
+
+protected:
+ // GMPTimerChild
+ bool RecvTimerExpired(const uint32_t& aTimerId) override;
+
+private:
+ ~GMPTimerChild();
+
+ nsDataHashtable<nsUint32HashKey, GMPTask*> mTimers;
+ uint32_t mTimerCount;
+
+ GMPChild* mPlugin;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPTimerChild_h_
diff --git a/dom/media/gmp/GMPTimerParent.cpp b/dom/media/gmp/GMPTimerParent.cpp
new file mode 100644
index 000000000..50861b97f
--- /dev/null
+++ b/dom/media/gmp/GMPTimerParent.cpp
@@ -0,0 +1,123 @@
+/* -*- 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 "GMPTimerParent.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Unused.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPParent"
+
+namespace gmp {
+
+GMPTimerParent::GMPTimerParent(nsIThread* aGMPThread)
+ : mGMPThread(aGMPThread)
+ , mIsOpen(true)
+{
+}
+
+bool
+GMPTimerParent::RecvSetTimer(const uint32_t& aTimerId,
+ const uint32_t& aTimeoutMs)
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ if (!mIsOpen) {
+ return true;
+ }
+
+ nsresult rv;
+ nsAutoPtr<Context> ctx(new Context());
+ ctx->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ ctx->mId = aTimerId;
+ rv = ctx->mTimer->SetTarget(mGMPThread);
+ NS_ENSURE_SUCCESS(rv, true);
+ ctx->mParent = this;
+
+ rv = ctx->mTimer->InitWithFuncCallback(&GMPTimerParent::GMPTimerExpired,
+ ctx,
+ aTimeoutMs,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ mTimers.PutEntry(ctx.forget());
+
+ return true;
+}
+
+void
+GMPTimerParent::Shutdown()
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ for (auto iter = mTimers.Iter(); !iter.Done(); iter.Next()) {
+ Context* context = iter.Get()->GetKey();
+ context->mTimer->Cancel();
+ delete context;
+ }
+
+ mTimers.Clear();
+ mIsOpen = false;
+}
+
+void
+GMPTimerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+
+ Shutdown();
+}
+
+/* static */
+void
+GMPTimerParent::GMPTimerExpired(nsITimer *aTimer, void *aClosure)
+{
+ MOZ_ASSERT(aClosure);
+ nsAutoPtr<Context> ctx(static_cast<Context*>(aClosure));
+ MOZ_ASSERT(ctx->mParent);
+ if (ctx->mParent) {
+ ctx->mParent->TimerExpired(ctx);
+ }
+}
+
+void
+GMPTimerParent::TimerExpired(Context* aContext)
+{
+ LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen));
+ MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
+
+ if (!mIsOpen) {
+ return;
+ }
+
+ uint32_t id = aContext->mId;
+ mTimers.RemoveEntry(aContext);
+ if (id) {
+ Unused << SendTimerExpired(id);
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPTimerParent.h b/dom/media/gmp/GMPTimerParent.h
new file mode 100644
index 000000000..0aad36121
--- /dev/null
+++ b/dom/media/gmp/GMPTimerParent.h
@@ -0,0 +1,61 @@
+/* -*- 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 GMPTimerParent_h_
+#define GMPTimerParent_h_
+
+#include "mozilla/gmp/PGMPTimerParent.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Monitor.h"
+#include "nsIThread.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPTimerParent : public PGMPTimerParent {
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPTimerParent)
+ explicit GMPTimerParent(nsIThread* aGMPThread);
+
+ void Shutdown();
+
+protected:
+ bool RecvSetTimer(const uint32_t& aTimerId,
+ const uint32_t& aTimeoutMs) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+ ~GMPTimerParent() {}
+
+ static void GMPTimerExpired(nsITimer *aTimer, void *aClosure);
+
+ struct Context {
+ Context() {
+ MOZ_COUNT_CTOR(Context);
+ }
+ ~Context() {
+ MOZ_COUNT_DTOR(Context);
+ }
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<GMPTimerParent> mParent; // Note: live timers keep the GMPTimerParent alive.
+ uint32_t mId;
+ };
+
+ void TimerExpired(Context* aContext);
+
+ nsTHashtable<nsPtrHashKey<Context>> mTimers;
+
+ nsCOMPtr<nsIThread> mGMPThread;
+
+ bool mIsOpen;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPTimerParent_h_
diff --git a/dom/media/gmp/GMPTypes.ipdlh b/dom/media/gmp/GMPTypes.ipdlh
new file mode 100644
index 000000000..44df5fc3d
--- /dev/null
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+using GMPBufferType from "gmp-video-codec.h";
+using GMPAudioCodecType from "gmp-audio-codec.h";
+using GMPMediaKeyStatus from "gmp-decryption.h";
+
+namespace mozilla {
+namespace gmp {
+
+struct GMPDecryptionData {
+ uint8_t[] mKeyId;
+ uint8_t[] mIV;
+ uint16_t[] mClearBytes;
+ uint32_t[] mCipherBytes;
+ nsCString[] mSessionIds;
+};
+
+struct GMPVideoEncodedFrameData
+{
+ uint32_t mEncodedWidth;
+ uint32_t mEncodedHeight;
+ uint64_t mTimestamp; // microseconds
+ uint64_t mDuration; // microseconds
+ uint32_t mFrameType;
+ uint32_t mSize;
+ GMPBufferType mBufferType;
+ Shmem mBuffer;
+ bool mCompleteFrame;
+ GMPDecryptionData mDecryptionData;
+};
+
+struct GMPPlaneData
+{
+ int32_t mSize;
+ int32_t mStride;
+ Shmem mBuffer;
+};
+
+struct GMPVideoi420FrameData
+{
+ GMPPlaneData mYPlane;
+ GMPPlaneData mUPlane;
+ GMPPlaneData mVPlane;
+ int32_t mWidth;
+ int32_t mHeight;
+ uint64_t mTimestamp; // microseconds
+ uint64_t mDuration; // microseconds
+};
+
+struct GMPAudioCodecData
+{
+ GMPAudioCodecType mCodecType;
+ uint32_t mChannelCount;
+ uint32_t mBitsPerChannel;
+ uint32_t mSamplesPerSecond;
+
+ uint8_t[] mExtraData;
+};
+
+struct GMPAudioEncodedSampleData
+{
+ uint8_t[] mData;
+ uint64_t mTimeStamp; // microseconds.
+ GMPDecryptionData mDecryptionData;
+ uint32_t mChannelCount;
+ uint32_t mSamplesPerSecond;
+};
+
+struct GMPAudioDecodedSampleData
+{
+ int16_t[] mData;
+ uint64_t mTimeStamp; // microseconds.
+ uint32_t mChannelCount;
+ uint32_t mSamplesPerSecond;
+};
+
+struct GMPKeyInformation {
+ uint8_t[] keyId;
+ GMPMediaKeyStatus status;
+};
+
+}
+}
diff --git a/dom/media/gmp/GMPUtils.cpp b/dom/media/gmp/GMPUtils.cpp
new file mode 100644
index 000000000..d5863516a
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 8; 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 "GMPUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsLiteralString.h"
+#include "nsCRTGlue.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Base64.h"
+#include "nsISimpleEnumerator.h"
+#include "prio.h"
+
+namespace mozilla {
+
+bool
+GetEMEVoucherPath(nsIFile** aPath)
+{
+ nsCOMPtr<nsIFile> path;
+ NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(path));
+ if (!path) {
+ NS_WARNING("GetEMEVoucherPath can't get NS_GRE_DIR!");
+ return false;
+ }
+ path->AppendNative(NS_LITERAL_CSTRING("voucher.bin"));
+ path.forget(aPath);
+ return true;
+}
+
+bool
+EMEVoucherFileExists()
+{
+ nsCOMPtr<nsIFile> path;
+ bool exists;
+ return GetEMEVoucherPath(getter_AddRefs(path)) &&
+ NS_SUCCEEDED(path->Exists(&exists)) &&
+ exists;
+}
+
+void
+SplitAt(const char* aDelims,
+ const nsACString& aInput,
+ nsTArray<nsCString>& aOutTokens)
+{
+ nsAutoCString str(aInput);
+ char* end = str.BeginWriting();
+ const char* start = nullptr;
+ while (!!(start = NS_strtok(aDelims, &end))) {
+ aOutTokens.AppendElement(nsCString(start));
+ }
+}
+
+nsCString
+ToBase64(const nsTArray<uint8_t>& aBytes)
+{
+ nsAutoCString base64;
+ nsDependentCSubstring raw(reinterpret_cast<const char*>(aBytes.Elements()),
+ aBytes.Length());
+ nsresult rv = Base64Encode(raw, base64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_LITERAL_CSTRING("[Base64EncodeFailed]");
+ }
+ return base64;
+}
+
+bool
+FileExists(nsIFile* aFile)
+{
+ bool exists = false;
+ return aFile && NS_SUCCEEDED(aFile->Exists(&exists)) && exists;
+}
+
+DirectoryEnumerator::DirectoryEnumerator(nsIFile* aPath, Mode aMode)
+ : mMode(aMode)
+{
+ aPath->GetDirectoryEntries(getter_AddRefs(mIter));
+}
+
+already_AddRefed<nsIFile>
+DirectoryEnumerator::Next()
+{
+ if (!mIter) {
+ return nullptr;
+ }
+ bool hasMore = false;
+ while (NS_SUCCEEDED(mIter->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv = mIter->GetNext(getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (mMode == DirsOnly) {
+ bool isDirectory = false;
+ rv = path->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) {
+ continue;
+ }
+ }
+ return path.forget();
+ }
+ return nullptr;
+}
+
+bool
+ReadIntoArray(nsIFile* aFile,
+ nsTArray<uint8_t>& aOutDst,
+ size_t aMaxLength)
+{
+ if (!FileExists(aFile)) {
+ return false;
+ }
+
+ PRFileDesc* fd = nullptr;
+ nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ int32_t length = PR_Seek(fd, 0, PR_SEEK_END);
+ PR_Seek(fd, 0, PR_SEEK_SET);
+
+ if (length < 0 || (size_t)length > aMaxLength) {
+ NS_WARNING("EME file is longer than maximum allowed length");
+ PR_Close(fd);
+ return false;
+ }
+ aOutDst.SetLength(length);
+ int32_t bytesRead = PR_Read(fd, aOutDst.Elements(), length);
+ PR_Close(fd);
+ return (bytesRead == length);
+}
+
+bool
+ReadIntoString(nsIFile* aFile,
+ nsCString& aOutDst,
+ size_t aMaxLength)
+{
+ nsTArray<uint8_t> buf;
+ bool rv = ReadIntoArray(aFile, buf, aMaxLength);
+ if (rv) {
+ buf.AppendElement(0); // Append null terminator, required by nsC*String.
+ aOutDst = nsDependentCString((const char*)buf.Elements(), buf.Length() - 1);
+ }
+ return rv;
+}
+
+bool
+GMPInfoFileParser::Init(nsIFile* aInfoFile)
+{
+ nsTArray<nsCString> lines;
+ static const size_t MAX_GMP_INFO_FILE_LENGTH = 5 * 1024;
+
+ nsAutoCString info;
+ if (!ReadIntoString(aInfoFile, info, MAX_GMP_INFO_FILE_LENGTH)) {
+ NS_WARNING("Failed to read info file in GMP process.");
+ return false;
+ }
+
+ // Note: we pass "\r\n" to SplitAt so that we'll split lines delimited
+ // by \n (Unix), \r\n (Windows) and \r (old MacOSX).
+ SplitAt("\r\n", info, lines);
+
+ for (nsCString line : lines) {
+ // Field name is the string up to but not including the first ':'
+ // character on the line.
+ int32_t colon = line.FindChar(':');
+ if (colon <= 0) {
+ // Not allowed to be the first character.
+ // Info field name must be at least one character.
+ continue;
+ }
+ nsAutoCString key(Substring(line, 0, colon));
+ ToLowerCase(key);
+ key.Trim(" ");
+
+ nsCString* value = new nsCString(Substring(line, colon + 1));
+ value->Trim(" ");
+ mValues.Put(key, value); // Hashtable assumes ownership of value.
+ }
+
+ return true;
+}
+
+bool
+GMPInfoFileParser::Contains(const nsCString& aKey) const {
+ nsCString key(aKey);
+ ToLowerCase(key);
+ return mValues.Contains(key);
+}
+
+nsCString
+GMPInfoFileParser::Get(const nsCString& aKey) const {
+ MOZ_ASSERT(Contains(aKey));
+ nsCString key(aKey);
+ ToLowerCase(key);
+ nsCString* p = nullptr;
+ if (mValues.Get(key, &p)) {
+ return nsCString(*p);
+ }
+ return EmptyCString();
+}
+
+bool
+HaveGMPFor(const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags)
+{
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (NS_WARN_IF(!mps)) {
+ return false;
+ }
+
+ bool hasPlugin = false;
+ if (NS_FAILED(mps->HasPluginForAPI(aAPI, &aTags, &hasPlugin))) {
+ return false;
+ }
+ return hasPlugin;
+}
+
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPUtils.h b/dom/media/gmp/GMPUtils.h
new file mode 100644
index 000000000..7e7b9f26f
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.h
@@ -0,0 +1,89 @@
+/* -*- 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 GMPUtils_h_
+#define GMPUtils_h_
+
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+
+class nsIFile;
+class nsCString;
+class nsISimpleEnumerator;
+
+namespace mozilla {
+
+template<typename T>
+struct DestroyPolicy
+{
+ void operator()(T* aGMPObject) const {
+ aGMPObject->Destroy();
+ }
+};
+
+template<typename T>
+using GMPUniquePtr = mozilla::UniquePtr<T, DestroyPolicy<T>>;
+
+bool GetEMEVoucherPath(nsIFile** aPath);
+
+bool EMEVoucherFileExists();
+
+void
+SplitAt(const char* aDelims,
+ const nsACString& aInput,
+ nsTArray<nsCString>& aOutTokens);
+
+nsCString
+ToBase64(const nsTArray<uint8_t>& aBytes);
+
+bool
+FileExists(nsIFile* aFile);
+
+// Enumerate directory entries for a specified path.
+class DirectoryEnumerator {
+public:
+
+ enum Mode {
+ DirsOnly, // Enumeration only includes directories.
+ FilesAndDirs // Enumeration includes directories and non-directory files.
+ };
+
+ DirectoryEnumerator(nsIFile* aPath, Mode aMode);
+
+ already_AddRefed<nsIFile> Next();
+
+private:
+ Mode mMode;
+ nsCOMPtr<nsISimpleEnumerator> mIter;
+};
+
+class GMPInfoFileParser {
+public:
+ bool Init(nsIFile* aFile);
+ bool Contains(const nsCString& aKey) const;
+ nsCString Get(const nsCString& aKey) const;
+private:
+ nsClassHashtable<nsCStringHashKey, nsCString> mValues;
+};
+
+bool
+ReadIntoArray(nsIFile* aFile,
+ nsTArray<uint8_t>& aOutDst,
+ size_t aMaxLength);
+
+bool
+ReadIntoString(nsIFile* aFile,
+ nsCString& aOutDst,
+ size_t aMaxLength);
+
+bool
+HaveGMPFor(const nsCString& aAPI,
+ nsTArray<nsCString>&& aTags);
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/GMPVideoDecoderChild.cpp b/dom/media/gmp/GMPVideoDecoderChild.cpp
new file mode 100644
index 000000000..f9c1956e7
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderChild.cpp
@@ -0,0 +1,254 @@
+/* -*- 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 "GMPVideoDecoderChild.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPContentChild.h"
+#include <stdio.h>
+#include "mozilla/Unused.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "runnable_utils.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoDecoderChild::GMPVideoDecoderChild(GMPContentChild* aPlugin)
+ : GMPSharedMemManager(aPlugin)
+ , mPlugin(aPlugin)
+ , mVideoDecoder(nullptr)
+ , mVideoHost(this)
+ , mNeedShmemIntrCount(0)
+ , mPendingDecodeComplete(false)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderChild::~GMPVideoDecoderChild()
+{
+ MOZ_ASSERT(!mNeedShmemIntrCount);
+}
+
+void
+GMPVideoDecoderChild::Init(GMPVideoDecoder* aDecoder)
+{
+ MOZ_ASSERT(aDecoder, "Cannot initialize video decoder child without a video decoder!");
+ mVideoDecoder = aDecoder;
+}
+
+GMPVideoHostImpl&
+GMPVideoDecoderChild::Host()
+{
+ return mVideoHost;
+}
+
+void
+GMPVideoDecoderChild::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (!aDecodedFrame) {
+ MOZ_CRASH("Not given a decoded frame!");
+ }
+
+ auto df = static_cast<GMPVideoi420FrameImpl*>(aDecodedFrame);
+
+ GMPVideoi420FrameData frameData;
+ df->InitFrameData(frameData);
+ SendDecoded(frameData);
+
+ aDecodedFrame->Destroy();
+}
+
+void
+GMPVideoDecoderChild::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendReceivedDecodedReferenceFrame(aPictureId);
+}
+
+void
+GMPVideoDecoderChild::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendReceivedDecodedFrame(aPictureId);
+}
+
+void
+GMPVideoDecoderChild::InputDataExhausted()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendInputDataExhausted();
+}
+
+void
+GMPVideoDecoderChild::DrainComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendDrainComplete();
+}
+
+void
+GMPVideoDecoderChild::ResetComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendResetComplete();
+}
+
+void
+GMPVideoDecoderChild::Error(GMPErr aError)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendError(aError);
+}
+
+bool
+GMPVideoDecoderChild::RecvInitDecode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aCoreCount)
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->InitDecode(aCodecSettings,
+ aCodecSpecific.Elements(),
+ aCodecSpecific.Length(),
+ this,
+ aCoreCount);
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDecode(const GMPVideoEncodedFrameData& aInputFrame,
+ const bool& aMissingFrames,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ const int64_t& aRenderTimeMs)
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ auto f = new GMPVideoEncodedFrameImpl(aInputFrame, &mVideoHost);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->Decode(f,
+ aMissingFrames,
+ aCodecSpecificInfo.Elements(),
+ aCodecSpecificInfo.Length(),
+ aRenderTimeMs);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvChildShmemForPool(Shmem&& aFrameBuffer)
+{
+ if (aFrameBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBuffer);
+ }
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvReset()
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->Reset();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDrain()
+{
+ if (!mVideoDecoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->Drain();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDecodingComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (mNeedShmemIntrCount) {
+ // There's a GMP blocked in Alloc() waiting for the CallNeedShem() to
+ // return a frame they can use. Don't call the GMP's DecodingComplete()
+ // now and don't delete the GMPVideoDecoderChild, defer processing the
+ // DecodingComplete() until once the Alloc() finishes.
+ mPendingDecodeComplete = true;
+ return true;
+ }
+ if (mVideoDecoder) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoDecoder->DecodingComplete();
+ mVideoDecoder = nullptr;
+ }
+
+ mVideoHost.DoneWithAPI();
+
+ mPlugin = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderChild::Alloc(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aMem)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ bool rv;
+#ifndef SHMEM_ALLOC_IN_CHILD
+ ++mNeedShmemIntrCount;
+ rv = CallNeedShmem(aSize, aMem);
+ --mNeedShmemIntrCount;
+ if (mPendingDecodeComplete && mNeedShmemIntrCount == 0) {
+ mPendingDecodeComplete = false;
+ mPlugin->GMPMessageLoop()->PostTask(
+ NewRunnableMethod(this, &GMPVideoDecoderChild::RecvDecodingComplete));
+ }
+#else
+#ifdef GMP_SAFE_SHMEM
+ rv = AllocShmem(aSize, aType, aMem);
+#else
+ rv = AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+#endif
+ return rv;
+}
+
+void
+GMPVideoDecoderChild::Dealloc(Shmem& aMem)
+{
+#ifndef SHMEM_ALLOC_IN_CHILD
+ SendParentShmemForPool(aMem);
+#else
+ DeallocShmem(aMem);
+#endif
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoDecoderChild.h b/dom/media/gmp/GMPVideoDecoderChild.h
new file mode 100644
index 000000000..2271a70a1
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderChild.h
@@ -0,0 +1,75 @@
+/* -*- 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 GMPVideoDecoderChild_h_
+#define GMPVideoDecoderChild_h_
+
+#include "nsString.h"
+#include "mozilla/gmp/PGMPVideoDecoderChild.h"
+#include "gmp-video-decode.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPVideoDecoderChild : public PGMPVideoDecoderChild,
+ public GMPVideoDecoderCallback,
+ public GMPSharedMemManager
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoDecoderChild);
+
+ explicit GMPVideoDecoderChild(GMPContentChild* aPlugin);
+
+ void Init(GMPVideoDecoder* aDecoder);
+ GMPVideoHostImpl& Host();
+
+ // GMPVideoDecoderCallback
+ void Decoded(GMPVideoi420Frame* decodedFrame) override;
+ void ReceivedDecodedReferenceFrame(const uint64_t pictureId) override;
+ void ReceivedDecodedFrame(const uint64_t pictureId) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aError) override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override;
+ void Dealloc(Shmem& aMem) override;
+
+private:
+ virtual ~GMPVideoDecoderChild();
+
+ // PGMPVideoDecoderChild
+ bool RecvInitDecode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aCoreCount) override;
+ bool RecvDecode(const GMPVideoEncodedFrameData& aInputFrame,
+ const bool& aMissingFrames,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ const int64_t& aRenderTimeMs) override;
+ bool RecvChildShmemForPool(Shmem&& aFrameBuffer) override;
+ bool RecvReset() override;
+ bool RecvDrain() override;
+ bool RecvDecodingComplete() override;
+
+ GMPContentChild* mPlugin;
+ GMPVideoDecoder* mVideoDecoder;
+ GMPVideoHostImpl mVideoHost;
+
+ // Non-zero when a GMP is blocked spinning the IPC message loop while
+ // waiting on an NeedShmem to complete.
+ int mNeedShmemIntrCount;
+ bool mPendingDecodeComplete;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoDecoderChild_h_
diff --git a/dom/media/gmp/GMPVideoDecoderParent.cpp b/dom/media/gmp/GMPVideoDecoderParent.cpp
new file mode 100644
index 000000000..bd5408adb
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -0,0 +1,513 @@
+/* -*- 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 "GMPVideoDecoderParent.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Unused.h"
+#include "nsAutoRef.h"
+#include "nsThreadUtils.h"
+#include "GMPUtils.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPContentParent.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOGE(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Error, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+namespace gmp {
+
+// States:
+// Initial: mIsOpen == false
+// on InitDecode success -> Open
+// on Shutdown -> Dead
+// Open: mIsOpen == true
+// on Close -> Dead
+// on ActorDestroy -> Dead
+// on Shutdown -> Dead
+// Dead: mIsOpen == false
+
+GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin)
+ : GMPSharedMemManager(aPlugin)
+ , mIsOpen(false)
+ , mShuttingDown(false)
+ , mActorDestroyed(false)
+ , mIsAwaitingResetComplete(false)
+ , mIsAwaitingDrainComplete(false)
+ , mPlugin(aPlugin)
+ , mCallback(nullptr)
+ , mVideoHost(this)
+ , mPluginId(aPlugin->GetPluginId())
+ , mFrameCount(0)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderParent::~GMPVideoDecoderParent()
+{
+}
+
+GMPVideoHostImpl&
+GMPVideoDecoderParent::Host()
+{
+ return mVideoHost;
+}
+
+// Note: may be called via Terminated()
+void
+GMPVideoDecoderParent::Close()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Close()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ // Ensure if we've received a Close while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the close. This seems unlikely to happen, but better to be careful.
+ UnblockResetAndDrain();
+
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPVideoDecoderParent> kungfudeathgrip(this);
+ Release();
+ Shutdown();
+}
+
+nsresult
+GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoDecoderCallbackProxy* aCallback,
+ int32_t aCoreCount)
+{
+ LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this));
+
+ if (mActorDestroyed) {
+ NS_WARNING("Trying to use a destroyed GMP video decoder!");
+ return NS_ERROR_FAILURE;
+ }
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-init an in-use GMP video decoder!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aCallback;
+
+ if (!SendInitDecode(aCodecSettings, aCodecSpecific, aCoreCount)) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsOpen = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPVideoDecoderParent::Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
+ bool aMissingFrames,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ int64_t aRenderTimeMs)
+{
+ LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%lld keyframe=%d",
+ this, aInputFrame->TimeStamp(),
+ aInputFrame->FrameType() == kGMPKeyFrame));
+
+ if (!mIsOpen) {
+ LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; dead GMPVideoDecoder", this));
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ GMPUniquePtr<GMPVideoEncodedFrameImpl> inputFrameImpl(
+ static_cast<GMPVideoEncodedFrameImpl*>(aInputFrame.release()));
+
+ // Very rough kill-switch if the plugin stops processing. If it's merely
+ // hung and continues, we'll come back to life eventually.
+ // 3* is because we're using 3 buffers per frame for i420 data for now.
+ if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) ||
+ (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) {
+ LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; shmem buffer limit hit frame=%d encoded=%d",
+ this, NumInUse(GMPSharedMem::kGMPFrameData), NumInUse(GMPSharedMem::kGMPEncodedData)));
+ return NS_ERROR_FAILURE;
+ }
+
+ GMPVideoEncodedFrameData frameData;
+ inputFrameImpl->RelinquishFrameData(frameData);
+
+ if (!SendDecode(frameData,
+ aMissingFrames,
+ aCodecSpecificInfo,
+ aRenderTimeMs)) {
+ LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; SendDecode() failure.", this));
+ return NS_ERROR_FAILURE;
+ }
+ mFrameCount++;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsresult
+GMPVideoDecoderParent::Reset()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Reset()", this));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendReset()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingResetComplete = true;
+
+ 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());
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+void
+GMPVideoDecoderParent::CancelResetCompleteTimeout()
+{
+ if (mResetCompleteTimeout) {
+ mResetCompleteTimeout->Cancel();
+ mResetCompleteTimeout = nullptr;
+ }
+}
+
+nsresult
+GMPVideoDecoderParent::Drain()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Drain() frameCount=%d", this, mFrameCount));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendDrain()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingDrainComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+const nsCString&
+GMPVideoDecoderParent::GetDisplayName() const
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ return mPlugin->GetDisplayName();
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult
+GMPVideoDecoderParent::Shutdown()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this));
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+ mShuttingDown = true;
+
+ // Ensure if we've received a shutdown while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the shutdown.
+ UnblockResetAndDrain();
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendDecodingComplete();
+ }
+
+ return NS_OK;
+}
+
+// Note: Keep this sync'd up with Shutdown
+void
+GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy));
+
+ mIsOpen = false;
+ mActorDestroyed = true;
+
+ // Ensure if we've received a destroy while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+ mVideoHost.ActorDestroyed();
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+bool
+GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame)
+{
+ --mFrameCount;
+ LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%lld frameCount=%d",
+ this, aDecodedFrame.mTimestamp(), mFrameCount));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) {
+ LOGE(("GMPVideoDecoderParent[%p]::RecvDecoded() "
+ "timestamp=%lld decoded frame corrupt, ignoring"));
+ return false;
+ }
+ auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Decoded(f);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ReceivedDecodedReferenceFrame(aPictureId);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ReceivedDecodedFrame(aPictureId);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvInputDataExhausted()
+{
+ LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->InputDataExhausted();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvDrainComplete()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete() frameCount=%d", this, mFrameCount));
+ nsAutoString msg;
+ msg.AppendLiteral("GMPVideoDecoderParent::RecvDrainComplete() outstanding frames=");
+ msg.AppendInt(mFrameCount);
+ LogToBrowserConsole(msg);
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingDrainComplete) {
+ return true;
+ }
+ mIsAwaitingDrainComplete = false;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->DrainComplete();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvResetComplete()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this));
+
+ CancelResetCompleteTimeout();
+
+ if (!mCallback) {
+ return false;
+ }
+
+ if (!mIsAwaitingResetComplete) {
+ return true;
+ }
+ mIsAwaitingResetComplete = false;
+ mFrameCount = 0;
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->ResetComplete();
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvError(const GMPErr& aError)
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError));
+
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ensure if we've received an error while waiting for a ResetComplete
+ // or DrainComplete notification, we'll unblock the caller before processing
+ // the error.
+ UnblockResetAndDrain();
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Error(aError);
+
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvShutdown()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this));
+
+ Shutdown();
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer)
+{
+ if (aEncodedBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBuffer);
+ }
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::AnswerNeedShmem(const uint32_t& aFrameBufferSize,
+ Shmem* aMem)
+{
+ ipc::Shmem mem;
+
+ if (!mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBufferSize,
+ ipc::SharedMemory::TYPE_BASIC, &mem))
+ {
+ LOGE(("%s: Failed to get a shared mem buffer for Child! size %u",
+ __FUNCTION__, aFrameBufferSize));
+ return false;
+ }
+ *aMem = mem;
+ mem = ipc::Shmem();
+ return true;
+}
+
+bool
+GMPVideoDecoderParent::Recv__delete__()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this));
+
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoDecoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+
+ return true;
+}
+
+void
+GMPVideoDecoderParent::UnblockResetAndDrain()
+{
+ LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain() "
+ "awaitingResetComplete=%d awaitingDrainComplete=%d",
+ this, mIsAwaitingResetComplete, mIsAwaitingDrainComplete));
+
+ if (!mCallback) {
+ MOZ_ASSERT(!mIsAwaitingResetComplete);
+ MOZ_ASSERT(!mIsAwaitingDrainComplete);
+ return;
+ }
+ if (mIsAwaitingResetComplete) {
+ mIsAwaitingResetComplete = false;
+ mCallback->ResetComplete();
+ }
+ if (mIsAwaitingDrainComplete) {
+ mIsAwaitingDrainComplete = false;
+ mCallback->DrainComplete();
+ }
+ CancelResetCompleteTimeout();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoDecoderParent.h b/dom/media/gmp/GMPVideoDecoderParent.h
new file mode 100644
index 000000000..215c784c1
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderParent.h
@@ -0,0 +1,104 @@
+/* -*- 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 GMPVideoDecoderParent_h_
+#define GMPVideoDecoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-video-decode.h"
+#include "mozilla/gmp/PGMPVideoDecoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPSharedMemManager.h"
+#include "GMPUtils.h"
+#include "GMPVideoHost.h"
+#include "GMPVideoDecoderProxy.h"
+#include "VideoUtils.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPVideoDecoderParent final : public PGMPVideoDecoderParent
+ , public GMPVideoDecoderProxy
+ , public GMPSharedMemManager
+ , public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPVideoDecoderParent)
+
+ explicit GMPVideoDecoderParent(GMPContentParent *aPlugin);
+
+ GMPVideoHostImpl& Host();
+ nsresult Shutdown();
+
+ // GMPVideoDecoder
+ void Close() override;
+ nsresult InitDecode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoDecoderCallbackProxy* aCallback,
+ int32_t aCoreCount) override;
+ nsresult Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
+ bool aMissingFrames,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ int64_t aRenderTimeMs = -1) override;
+ nsresult Reset() override;
+ nsresult Drain() override;
+ uint32_t GetPluginId() const override { return mPluginId; }
+ const nsCString& GetDisplayName() const override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
+ {
+#ifdef GMP_SAFE_SHMEM
+ return AllocShmem(aSize, aType, aMem);
+#else
+ return AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+ }
+ void Dealloc(Shmem& aMem) override
+ {
+ DeallocShmem(aMem);
+ }
+
+private:
+ ~GMPVideoDecoderParent();
+
+ // PGMPVideoDecoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame) override;
+ bool RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId) override;
+ bool RecvReceivedDecodedFrame(const uint64_t& aPictureId) override;
+ bool RecvInputDataExhausted() override;
+ bool RecvDrainComplete() override;
+ bool RecvResetComplete() override;
+ bool RecvError(const GMPErr& aError) override;
+ bool RecvShutdown() override;
+ bool RecvParentShmemForPool(Shmem&& aEncodedBuffer) override;
+ bool AnswerNeedShmem(const uint32_t& aFrameBufferSize,
+ Shmem* aMem) override;
+ bool Recv__delete__() override;
+
+ void UnblockResetAndDrain();
+ void CancelResetCompleteTimeout();
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ bool mIsAwaitingResetComplete;
+ bool mIsAwaitingDrainComplete;
+ RefPtr<GMPContentParent> mPlugin;
+ GMPVideoDecoderCallbackProxy* mCallback;
+ GMPVideoHostImpl mVideoHost;
+ const uint32_t mPluginId;
+ int32_t mFrameCount;
+ RefPtr<SimpleTimer> mResetCompleteTimeout;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoDecoderParent_h_
diff --git a/dom/media/gmp/GMPVideoDecoderProxy.h b/dom/media/gmp/GMPVideoDecoderProxy.h
new file mode 100644
index 000000000..b9596fd21
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderProxy.h
@@ -0,0 +1,56 @@
+/* -*- 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 GMPVideoDecoderProxy_h_
+#define GMPVideoDecoderProxy_h_
+
+#include "nsTArray.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#include "GMPCallbackBase.h"
+#include "GMPUtils.h"
+
+class GMPVideoDecoderCallbackProxy : public GMPCallbackBase,
+ public GMPVideoDecoderCallback
+{
+public:
+ virtual ~GMPVideoDecoderCallbackProxy() {}
+};
+
+// A proxy to GMPVideoDecoder in the child process.
+// GMPVideoDecoderParent exposes this to users the GMP.
+// This enables Gecko to pass nsTArrays to the child GMP and avoid
+// an extra copy when doing so.
+
+// The consumer must call Close() when done with the codec, or when
+// Terminated() is called by the GMP plugin indicating an abnormal shutdown
+// of the underlying plugin. After calling Close(), the consumer must
+// not access this again.
+
+// This interface is not thread-safe and must only be used from GMPThread.
+class GMPVideoDecoderProxy {
+public:
+ virtual nsresult InitDecode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoDecoderCallbackProxy* aCallback,
+ int32_t aCoreCount) = 0;
+ virtual nsresult Decode(mozilla::GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
+ bool aMissingFrames,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ int64_t aRenderTimeMs = -1) = 0;
+ virtual nsresult Reset() = 0;
+ virtual nsresult Drain() = 0;
+ virtual uint32_t GetPluginId() const = 0;
+
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual void Close() = 0;
+
+ virtual const nsCString& GetDisplayName() const = 0;
+};
+
+#endif
diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp
new file mode 100644
index 000000000..7725c5521
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp
@@ -0,0 +1,324 @@
+/* -*- 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 "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoHost.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPSharedMemManager.h"
+#include "GMPEncryptedBufferDataImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost)
+: mEncodedWidth(0),
+ mEncodedHeight(0),
+ mTimeStamp(0ll),
+ mDuration(0ll),
+ mFrameType(kGMPDeltaFrame),
+ mSize(0),
+ mCompleteFrame(false),
+ mHost(aHost),
+ mBufferType(GMP_BufferSingle)
+{
+ MOZ_ASSERT(aHost);
+ aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData,
+ GMPVideoHostImpl* aHost)
+: mEncodedWidth(aFrameData.mEncodedWidth()),
+ mEncodedHeight(aFrameData.mEncodedHeight()),
+ mTimeStamp(aFrameData.mTimestamp()),
+ mDuration(aFrameData.mDuration()),
+ mFrameType(static_cast<GMPVideoFrameType>(aFrameData.mFrameType())),
+ mSize(aFrameData.mSize()),
+ mCompleteFrame(aFrameData.mCompleteFrame()),
+ mHost(aHost),
+ mBuffer(aFrameData.mBuffer()),
+ mBufferType(aFrameData.mBufferType())
+{
+ MOZ_ASSERT(aHost);
+ if (aFrameData.mDecryptionData().mKeyId().Length() > 0) {
+ mCrypto = new GMPEncryptedBufferDataImpl(aFrameData.mDecryptionData());
+ }
+ aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::~GMPVideoEncodedFrameImpl()
+{
+ DestroyBuffer();
+ if (mHost) {
+ mHost->EncodedFrameDestroyed(this);
+ }
+}
+
+void
+GMPVideoEncodedFrameImpl::InitCrypto(const CryptoSample& aCrypto)
+{
+ mCrypto = new GMPEncryptedBufferDataImpl(aCrypto);
+}
+
+const GMPEncryptedBufferMetadata*
+GMPVideoEncodedFrameImpl::GetDecryptionData() const
+{
+ return mCrypto;
+}
+
+GMPVideoFrameFormat
+GMPVideoEncodedFrameImpl::GetFrameFormat()
+{
+ return kGMPEncodedVideoFrame;
+}
+
+void
+GMPVideoEncodedFrameImpl::DoneWithAPI()
+{
+ DestroyBuffer();
+
+ // Do this after destroying the buffer because destruction
+ // involves deallocation, which requires a host.
+ mHost = nullptr;
+}
+
+void
+GMPVideoEncodedFrameImpl::ActorDestroyed()
+{
+ // Simply clear out Shmem reference, do not attempt to
+ // properly free it. It has already been freed.
+ mBuffer = ipc::Shmem();
+ // No more host.
+ mHost = nullptr;
+}
+
+bool
+GMPVideoEncodedFrameImpl::RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData)
+{
+ aFrameData.mEncodedWidth() = mEncodedWidth;
+ aFrameData.mEncodedHeight() = mEncodedHeight;
+ aFrameData.mTimestamp() = mTimeStamp;
+ aFrameData.mDuration() = mDuration;
+ aFrameData.mFrameType() = mFrameType;
+ aFrameData.mSize() = mSize;
+ aFrameData.mCompleteFrame() = mCompleteFrame;
+ aFrameData.mBuffer() = mBuffer;
+ aFrameData.mBufferType() = mBufferType;
+ if (mCrypto) {
+ mCrypto->RelinquishData(aFrameData.mDecryptionData());
+ }
+
+ // This method is called right before Shmem is sent to another process.
+ // We need to effectively zero out our member copy so that we don't
+ // try to delete Shmem we don't own later.
+ mBuffer = ipc::Shmem();
+
+ return true;
+}
+
+void
+GMPVideoEncodedFrameImpl::DestroyBuffer()
+{
+ if (mHost && mBuffer.IsWritable()) {
+ mHost->SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, mBuffer);
+ }
+ mBuffer = ipc::Shmem();
+}
+
+GMPErr
+GMPVideoEncodedFrameImpl::CreateEmptyFrame(uint32_t aSize)
+{
+ if (aSize == 0) {
+ DestroyBuffer();
+ } else if (aSize > AllocatedSize()) {
+ DestroyBuffer();
+ if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, aSize,
+ ipc::SharedMemory::TYPE_BASIC, &mBuffer) ||
+ !Buffer()) {
+ return GMPAllocErr;
+ }
+ }
+ mSize = aSize;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncodedFrameImpl::CopyFrame(const GMPVideoEncodedFrame& aFrame)
+{
+ auto& f = static_cast<const GMPVideoEncodedFrameImpl&>(aFrame);
+
+ if (f.mSize != 0) {
+ GMPErr err = CreateEmptyFrame(f.mSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ memcpy(Buffer(), f.Buffer(), f.mSize);
+ }
+ mEncodedWidth = f.mEncodedWidth;
+ mEncodedHeight = f.mEncodedHeight;
+ mTimeStamp = f.mTimeStamp;
+ mDuration = f.mDuration;
+ mFrameType = f.mFrameType;
+ mSize = f.mSize; // already set...
+ mCompleteFrame = f.mCompleteFrame;
+ mBufferType = f.mBufferType;
+ mCrypto = new GMPEncryptedBufferDataImpl(*(f.mCrypto));
+ // Don't copy host, that should have been set properly on object creation via host.
+
+ return GMPNoErr;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetEncodedWidth(uint32_t aEncodedWidth)
+{
+ mEncodedWidth = aEncodedWidth;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::EncodedWidth()
+{
+ return mEncodedWidth;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetEncodedHeight(uint32_t aEncodedHeight)
+{
+ mEncodedHeight = aEncodedHeight;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::EncodedHeight()
+{
+ return mEncodedHeight;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetTimeStamp(uint64_t aTimeStamp)
+{
+ mTimeStamp = aTimeStamp;
+}
+
+uint64_t
+GMPVideoEncodedFrameImpl::TimeStamp()
+{
+ return mTimeStamp;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetDuration(uint64_t aDuration)
+{
+ mDuration = aDuration;
+}
+
+uint64_t
+GMPVideoEncodedFrameImpl::Duration() const
+{
+ return mDuration;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetFrameType(GMPVideoFrameType aFrameType)
+{
+ mFrameType = aFrameType;
+}
+
+GMPVideoFrameType
+GMPVideoEncodedFrameImpl::FrameType()
+{
+ return mFrameType;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetAllocatedSize(uint32_t aNewSize)
+{
+ if (aNewSize <= AllocatedSize()) {
+ return;
+ }
+
+ if (!mHost) {
+ return;
+ }
+
+ ipc::Shmem new_mem;
+ if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, aNewSize,
+ ipc::SharedMemory::TYPE_BASIC, &new_mem) ||
+ !new_mem.get<uint8_t>()) {
+ return;
+ }
+
+ if (mBuffer.IsReadable()) {
+ memcpy(new_mem.get<uint8_t>(), Buffer(), mSize);
+ }
+
+ DestroyBuffer();
+
+ mBuffer = new_mem;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::AllocatedSize()
+{
+ if (mBuffer.IsWritable()) {
+ return mBuffer.Size<uint8_t>();
+ }
+ return 0;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetSize(uint32_t aSize)
+{
+ mSize = aSize;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::Size()
+{
+ return mSize;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetCompleteFrame(bool aCompleteFrame)
+{
+ mCompleteFrame = aCompleteFrame;
+}
+
+bool
+GMPVideoEncodedFrameImpl::CompleteFrame()
+{
+ return mCompleteFrame;
+}
+
+const uint8_t*
+GMPVideoEncodedFrameImpl::Buffer() const
+{
+ return mBuffer.get<uint8_t>();
+}
+
+uint8_t*
+GMPVideoEncodedFrameImpl::Buffer()
+{
+ return mBuffer.get<uint8_t>();
+}
+
+void
+GMPVideoEncodedFrameImpl::Destroy()
+{
+ delete this;
+}
+
+GMPBufferType
+GMPVideoEncodedFrameImpl::BufferType() const
+{
+ return mBufferType;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetBufferType(GMPBufferType aBufferType)
+{
+ mBufferType = aBufferType;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.h b/dom/media/gmp/GMPVideoEncodedFrameImpl.h
new file mode 100644
index 000000000..69858f1c6
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMPVideoEncodedFrameImpl_h_
+#define GMPVideoEncodedFrameImpl_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-decryption.h"
+#include "mozilla/ipc/Shmem.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+class CryptoSample;
+
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPVideoEncodedFrameData;
+class GMPEncryptedBufferDataImpl;
+
+class GMPVideoEncodedFrameImpl: public GMPVideoEncodedFrame
+{
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoEncodedFrameImpl>;
+public:
+ explicit GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost);
+ GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData, GMPVideoHostImpl* aHost);
+ virtual ~GMPVideoEncodedFrameImpl();
+
+ void InitCrypto(const CryptoSample& aCrypto);
+
+ // This is called during a normal destroy sequence, which is
+ // when a consumer is finished or during XPCOM shutdown.
+ void DoneWithAPI();
+ // Does not attempt to release Shmem, as the Shmem has already been released.
+ void ActorDestroyed();
+
+ bool RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData);
+
+ // GMPVideoFrame
+ GMPVideoFrameFormat GetFrameFormat() override;
+ void Destroy() override;
+
+ // GMPVideoEncodedFrame
+ GMPErr CreateEmptyFrame(uint32_t aSize) override;
+ GMPErr CopyFrame(const GMPVideoEncodedFrame& aFrame) override;
+ void SetEncodedWidth(uint32_t aEncodedWidth) override;
+ uint32_t EncodedWidth() override;
+ void SetEncodedHeight(uint32_t aEncodedHeight) override;
+ uint32_t EncodedHeight() override;
+ // Microseconds
+ void SetTimeStamp(uint64_t aTimeStamp) override;
+ uint64_t TimeStamp() override;
+ // Set frame duration (microseconds)
+ // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration()
+ // depending on rounding to avoid having to track roundoff errors
+ // and dropped/missing frames(!) (which may leave a large gap)
+ void SetDuration(uint64_t aDuration) override;
+ uint64_t Duration() const override;
+ void SetFrameType(GMPVideoFrameType aFrameType) override;
+ GMPVideoFrameType FrameType() override;
+ void SetAllocatedSize(uint32_t aNewSize) override;
+ uint32_t AllocatedSize() override;
+ void SetSize(uint32_t aSize) override;
+ uint32_t Size() override;
+ void SetCompleteFrame(bool aCompleteFrame) override;
+ bool CompleteFrame() override;
+ const uint8_t* Buffer() const override;
+ uint8_t* Buffer() override;
+ GMPBufferType BufferType() const override;
+ void SetBufferType(GMPBufferType aBufferType) override;
+ const GMPEncryptedBufferMetadata* GetDecryptionData() const override;
+
+private:
+ void DestroyBuffer();
+
+ uint32_t mEncodedWidth;
+ uint32_t mEncodedHeight;
+ uint64_t mTimeStamp;
+ uint64_t mDuration;
+ GMPVideoFrameType mFrameType;
+ uint32_t mSize;
+ bool mCompleteFrame;
+ GMPVideoHostImpl* mHost;
+ ipc::Shmem mBuffer;
+ GMPBufferType mBufferType;
+ nsAutoPtr<GMPEncryptedBufferDataImpl> mCrypto;
+};
+
+} // namespace gmp
+
+} // namespace mozilla
+
+#endif // GMPVideoEncodedFrameImpl_h_
diff --git a/dom/media/gmp/GMPVideoEncoderChild.cpp b/dom/media/gmp/GMPVideoEncoderChild.cpp
new file mode 100644
index 000000000..f5c3dda95
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderChild.cpp
@@ -0,0 +1,235 @@
+/* -*- 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 "GMPVideoEncoderChild.h"
+#include "GMPContentChild.h"
+#include <stdio.h>
+#include "mozilla/Unused.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "runnable_utils.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncoderChild::GMPVideoEncoderChild(GMPContentChild* aPlugin)
+ : GMPSharedMemManager(aPlugin)
+ , mPlugin(aPlugin)
+ , mVideoEncoder(nullptr)
+ , mVideoHost(this)
+ , mNeedShmemIntrCount(0)
+ , mPendingEncodeComplete(false)
+{
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoEncoderChild::~GMPVideoEncoderChild()
+{
+ MOZ_ASSERT(!mNeedShmemIntrCount);
+}
+
+void
+GMPVideoEncoderChild::Init(GMPVideoEncoder* aEncoder)
+{
+ MOZ_ASSERT(aEncoder, "Cannot initialize video encoder child without a video encoder!");
+ mVideoEncoder = aEncoder;
+}
+
+GMPVideoHostImpl&
+GMPVideoEncoderChild::Host()
+{
+ return mVideoHost;
+}
+
+void
+GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ auto ef = static_cast<GMPVideoEncodedFrameImpl*>(aEncodedFrame);
+
+ GMPVideoEncodedFrameData frameData;
+ ef->RelinquishFrameData(frameData);
+
+ nsTArray<uint8_t> codecSpecific;
+ codecSpecific.AppendElements(aCodecSpecificInfo, aCodecSpecificInfoLength);
+ SendEncoded(frameData, codecSpecific);
+
+ aEncodedFrame->Destroy();
+}
+
+void
+GMPVideoEncoderChild::Error(GMPErr aError)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ SendError(aError);
+}
+
+bool
+GMPVideoEncoderChild::RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aNumberOfCores,
+ const uint32_t& aMaxPayloadSize)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->InitEncode(aCodecSettings,
+ aCodecSpecific.Elements(),
+ aCodecSpecific.Length(),
+ this,
+ aNumberOfCores,
+ aMaxPayloadSize);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ InfallibleTArray<GMPVideoFrameType>&& aFrameTypes)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ auto f = new GMPVideoi420FrameImpl(aInputFrame, &mVideoHost);
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->Encode(f,
+ aCodecSpecificInfo.Elements(),
+ aCodecSpecificInfo.Length(),
+ aFrameTypes.Elements(),
+ aFrameTypes.Length());
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvChildShmemForPool(Shmem&& aEncodedBuffer)
+{
+ if (aEncodedBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBuffer);
+ }
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetChannelParameters(const uint32_t& aPacketLoss,
+ const uint32_t& aRTT)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->SetChannelParameters(aPacketLoss, aRTT);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetRates(const uint32_t& aNewBitRate,
+ const uint32_t& aFrameRate)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->SetRates(aNewBitRate, aFrameRate);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(const bool& aEnable)
+{
+ if (!mVideoEncoder) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->SetPeriodicKeyFrames(aEnable);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvEncodingComplete()
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ if (mNeedShmemIntrCount) {
+ // There's a GMP blocked in Alloc() waiting for the CallNeedShem() to
+ // return a frame they can use. Don't call the GMP's EncodingComplete()
+ // now and don't delete the GMPVideoEncoderChild, defer processing the
+ // EncodingComplete() until once the Alloc() finishes.
+ mPendingEncodeComplete = true;
+ return true;
+ }
+
+ if (!mVideoEncoder) {
+ Unused << Send__delete__(this);
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mVideoEncoder->EncodingComplete();
+
+ mVideoHost.DoneWithAPI();
+
+ mPlugin = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderChild::Alloc(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aMem)
+{
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ bool rv;
+#ifndef SHMEM_ALLOC_IN_CHILD
+ ++mNeedShmemIntrCount;
+ rv = CallNeedShmem(aSize, aMem);
+ --mNeedShmemIntrCount;
+ if (mPendingEncodeComplete && mNeedShmemIntrCount == 0) {
+ mPendingEncodeComplete = false;
+ mPlugin->GMPMessageLoop()->PostTask(
+ NewRunnableMethod(this, &GMPVideoEncoderChild::RecvEncodingComplete));
+ }
+#else
+#ifdef GMP_SAFE_SHMEM
+ rv = AllocShmem(aSize, aType, aMem);
+#else
+ rv = AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+#endif
+ return rv;
+}
+
+void
+GMPVideoEncoderChild::Dealloc(Shmem& aMem)
+{
+#ifndef SHMEM_ALLOC_IN_CHILD
+ SendParentShmemForPool(aMem);
+#else
+ DeallocShmem(aMem);
+#endif
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h
new file mode 100644
index 000000000..44e2fe605
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderChild.h
@@ -0,0 +1,75 @@
+/* -*- 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 GMPVideoEncoderChild_h_
+#define GMPVideoEncoderChild_h_
+
+#include "nsString.h"
+#include "mozilla/gmp/PGMPVideoEncoderChild.h"
+#include "gmp-video-encode.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPVideoEncoderChild : public PGMPVideoEncoderChild,
+ public GMPVideoEncoderCallback,
+ public GMPSharedMemManager
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoEncoderChild);
+
+ explicit GMPVideoEncoderChild(GMPContentChild* aPlugin);
+
+ void Init(GMPVideoEncoder* aEncoder);
+ GMPVideoHostImpl& Host();
+
+ // GMPVideoEncoderCallback
+ void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength) override;
+ void Error(GMPErr aError) override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aMem) override;
+ void Dealloc(Shmem& aMem) override;
+
+private:
+ virtual ~GMPVideoEncoderChild();
+
+ // PGMPVideoEncoderChild
+ bool RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+ InfallibleTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aNumberOfCores,
+ const uint32_t& aMaxPayloadSize) override;
+ bool RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo,
+ InfallibleTArray<GMPVideoFrameType>&& aFrameTypes) override;
+ bool RecvChildShmemForPool(Shmem&& aEncodedBuffer) override;
+ bool RecvSetChannelParameters(const uint32_t& aPacketLoss,
+ const uint32_t& aRTT) override;
+ bool RecvSetRates(const uint32_t& aNewBitRate,
+ const uint32_t& aFrameRate) override;
+ bool RecvSetPeriodicKeyFrames(const bool& aEnable) override;
+ bool RecvEncodingComplete() override;
+
+ GMPContentChild* mPlugin;
+ GMPVideoEncoder* mVideoEncoder;
+ GMPVideoHostImpl mVideoHost;
+
+ // Non-zero when a GMP is blocked spinning the IPC message loop while
+ // waiting on an NeedShmem to complete.
+ int mNeedShmemIntrCount;
+ bool mPendingEncodeComplete;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncoderChild_h_
diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp
new file mode 100644
index 000000000..95583cd6e
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -0,0 +1,382 @@
+/* -*- 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 "GMPVideoEncoderParent.h"
+#include "mozilla/Logging.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "mozilla/Unused.h"
+#include "GMPMessageUtils.h"
+#include "nsAutoRef.h"
+#include "GMPContentParent.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "runnable_utils.h"
+#include "GMPUtils.h"
+
+namespace mozilla {
+
+#ifdef LOG
+#undef LOG
+#endif
+
+extern LogModule* GetGMPLog();
+
+#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
+#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
+
+#ifdef __CLASS__
+#undef __CLASS__
+#endif
+#define __CLASS__ "GMPVideoEncoderParent"
+
+namespace gmp {
+
+// States:
+// Initial: mIsOpen == false
+// on InitDecode success -> Open
+// on Shutdown -> Dead
+// Open: mIsOpen == true
+// on Close -> Dead
+// on ActorDestroy -> Dead
+// on Shutdown -> Dead
+// Dead: mIsOpen == false
+
+GMPVideoEncoderParent::GMPVideoEncoderParent(GMPContentParent *aPlugin)
+: GMPSharedMemManager(aPlugin),
+ mIsOpen(false),
+ mShuttingDown(false),
+ mActorDestroyed(false),
+ mPlugin(aPlugin),
+ mCallback(nullptr),
+ mVideoHost(this),
+ mPluginId(aPlugin->GetPluginId())
+{
+ MOZ_ASSERT(mPlugin);
+
+ nsresult rv = NS_NewNamedThread("GMPEncoded", getter_AddRefs(mEncodedThread));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ }
+}
+
+GMPVideoEncoderParent::~GMPVideoEncoderParent()
+{
+ if (mEncodedThread) {
+ mEncodedThread->Shutdown();
+ }
+}
+
+GMPVideoHostImpl&
+GMPVideoEncoderParent::Host()
+{
+ return mVideoHost;
+}
+
+// Note: may be called via Terminated()
+void
+GMPVideoEncoderParent::Close()
+{
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+ // Consumer is done with us; we can shut down. No more callbacks should
+ // be made to mCallback. Note: do this before Shutdown()!
+ mCallback = nullptr;
+ // Let Shutdown mark us as dead so it knows if we had been alive
+
+ // In case this is the last reference
+ RefPtr<GMPVideoEncoderParent> kungfudeathgrip(this);
+ Release();
+ Shutdown();
+}
+
+GMPErr
+GMPVideoEncoderParent::InitEncode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoEncoderCallbackProxy* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize)
+{
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+ if (mIsOpen) {
+ NS_WARNING("Trying to re-init an in-use GMP video encoder!");
+ return GMPGenericErr;;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!aCallback) {
+ return GMPGenericErr;
+ }
+ mCallback = aCallback;
+
+ if (!SendInitEncode(aCodecSettings, aCodecSpecific, aNumberOfCores, aMaxPayloadSize)) {
+ return GMPGenericErr;
+ }
+ mIsOpen = true;
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ const nsTArray<GMPVideoFrameType>& aFrameTypes)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video encoder");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ GMPUniquePtr<GMPVideoi420FrameImpl> inputFrameImpl(
+ static_cast<GMPVideoi420FrameImpl*>(aInputFrame.release()));
+
+ // Very rough kill-switch if the plugin stops processing. If it's merely
+ // hung and continues, we'll come back to life eventually.
+ // 3* is because we're using 3 buffers per frame for i420 data for now.
+ if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) ||
+ (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) {
+ return GMPGenericErr;
+ }
+
+ GMPVideoi420FrameData frameData;
+ inputFrameImpl->InitFrameData(frameData);
+
+ if (!SendEncode(frameData,
+ aCodecSpecificInfo,
+ aFrameTypes)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an invalid GMP video encoder!");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendSetChannelParameters(aPacketLoss, aRTT)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::SetRates(uint32_t aNewBitRate, uint32_t aFrameRate)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendSetRates(aNewBitRate, aFrameRate)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an invalid GMP video encoder!");
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (!SendSetPeriodicKeyFrames(aEnable)) {
+ return GMPGenericErr;
+ }
+
+ // Async IPC, we don't have access to a return value.
+ return GMPNoErr;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+void
+GMPVideoEncoderParent::Shutdown()
+{
+ LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
+ MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+ if (mShuttingDown) {
+ return;
+ }
+ mShuttingDown = true;
+
+ // Notify client we're gone! Won't occur after Close()
+ if (mCallback) {
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+
+ mIsOpen = false;
+ if (!mActorDestroyed) {
+ Unused << SendEncodingComplete();
+ }
+}
+
+static void
+ShutdownEncodedThread(nsCOMPtr<nsIThread>& aThread)
+{
+ aThread->Shutdown();
+}
+
+// Note: Keep this sync'd up with Shutdown
+void
+GMPVideoEncoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD(("%s::%s: %p (%d)", __CLASS__, __FUNCTION__, this, (int) aWhy));
+ mIsOpen = false;
+ mActorDestroyed = true;
+ if (mCallback) {
+ // May call Close() (and Shutdown()) immediately or with a delay
+ mCallback->Terminated();
+ mCallback = nullptr;
+ }
+ // Must be shut down before VideoEncoderDestroyed(), since this can recurse
+ // the GMPThread event loop. See bug 1049501
+ if (mEncodedThread) {
+ // Can't get it to allow me to use WrapRunnable with a nsCOMPtr<nsIThread>()
+ NS_DispatchToMainThread(
+ WrapRunnableNM<decltype(&ShutdownEncodedThread),
+ nsCOMPtr<nsIThread> >(&ShutdownEncodedThread, mEncodedThread));
+ mEncodedThread = nullptr;
+ }
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoEncoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+ mVideoHost.ActorDestroyed(); // same as DoneWithAPI
+ MaybeDisconnect(aWhy == AbnormalShutdown);
+}
+
+static void
+EncodedCallback(GMPVideoEncoderCallbackProxy* aCallback,
+ GMPVideoEncodedFrame* aEncodedFrame,
+ nsTArray<uint8_t>* aCodecSpecificInfo,
+ nsCOMPtr<nsIThread> aThread)
+{
+ aCallback->Encoded(aEncodedFrame, *aCodecSpecificInfo);
+ delete aCodecSpecificInfo;
+ // Ugh. Must destroy the frame on GMPThread.
+ // XXX add locks to the ShmemManager instead?
+ aThread->Dispatch(WrapRunnable(aEncodedFrame,
+ &GMPVideoEncodedFrame::Destroy),
+ NS_DISPATCH_NORMAL);
+}
+
+bool
+GMPVideoEncoderParent::RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ auto f = new GMPVideoEncodedFrameImpl(aEncodedFrame, &mVideoHost);
+ nsTArray<uint8_t> *codecSpecificInfo = new nsTArray<uint8_t>;
+ codecSpecificInfo->AppendElements((uint8_t*)aCodecSpecificInfo.Elements(), aCodecSpecificInfo.Length());
+ nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+
+ mEncodedThread->Dispatch(WrapRunnableNM(&EncodedCallback,
+ mCallback, f, codecSpecificInfo, thread),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::RecvError(const GMPErr& aError)
+{
+ if (!mCallback) {
+ return false;
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mCallback->Error(aError);
+
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::RecvShutdown()
+{
+ Shutdown();
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::RecvParentShmemForPool(Shmem&& aFrameBuffer)
+{
+ if (aFrameBuffer.IsWritable()) {
+ // This test may be paranoia now that we don't shut down the VideoHost
+ // in ::Shutdown, but doesn't hurt
+ if (mVideoHost.SharedMemMgr()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBuffer);
+ } else {
+ LOGD(("%s::%s: %p Called in shutdown, ignoring and freeing directly", __CLASS__, __FUNCTION__, this));
+ DeallocShmem(aFrameBuffer);
+ }
+ }
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::AnswerNeedShmem(const uint32_t& aEncodedBufferSize,
+ Shmem* aMem)
+{
+ ipc::Shmem mem;
+
+ // This test may be paranoia now that we don't shut down the VideoHost
+ // in ::Shutdown, but doesn't hurt
+ if (!mVideoHost.SharedMemMgr() ||
+ !mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBufferSize,
+ ipc::SharedMemory::TYPE_BASIC, &mem))
+ {
+ LOG(LogLevel::Error, ("%s::%s: Failed to get a shared mem buffer for Child! size %u",
+ __CLASS__, __FUNCTION__, aEncodedBufferSize));
+ return false;
+ }
+ *aMem = mem;
+ mem = ipc::Shmem();
+ return true;
+}
+
+bool
+GMPVideoEncoderParent::Recv__delete__()
+{
+ if (mPlugin) {
+ // Ignore any return code. It is OK for this to fail without killing the process.
+ mPlugin->VideoEncoderDestroyed(this);
+ mPlugin = nullptr;
+ }
+
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h
new file mode 100644
index 000000000..e7dade692
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderParent.h
@@ -0,0 +1,93 @@
+/* -*- 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 GMPVideoEncoderParent_h_
+#define GMPVideoEncoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-video-encode.h"
+#include "mozilla/gmp/PGMPVideoEncoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPSharedMemManager.h"
+#include "GMPUtils.h"
+#include "GMPVideoHost.h"
+#include "GMPVideoEncoderProxy.h"
+#include "GMPCrashHelperHolder.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentParent;
+
+class GMPVideoEncoderParent : public GMPVideoEncoderProxy,
+ public PGMPVideoEncoderParent,
+ public GMPSharedMemManager,
+ public GMPCrashHelperHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GMPVideoEncoderParent)
+
+ explicit GMPVideoEncoderParent(GMPContentParent *aPlugin);
+
+ GMPVideoHostImpl& Host();
+ void Shutdown();
+
+ // GMPVideoEncoderProxy
+ void Close() override;
+ GMPErr InitEncode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoEncoderCallbackProxy* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize) override;
+ GMPErr Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ const nsTArray<GMPVideoFrameType>& aFrameTypes) override;
+ GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
+ GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
+ GMPErr SetPeriodicKeyFrames(bool aEnable) override;
+ uint32_t GetPluginId() const override { return mPluginId; }
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
+ {
+#ifdef GMP_SAFE_SHMEM
+ return AllocShmem(aSize, aType, aMem);
+#else
+ return AllocUnsafeShmem(aSize, aType, aMem);
+#endif
+ }
+ void Dealloc(Shmem& aMem) override
+ {
+ DeallocShmem(aMem);
+ }
+
+private:
+ virtual ~GMPVideoEncoderParent();
+
+ // PGMPVideoEncoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame,
+ InfallibleTArray<uint8_t>&& aCodecSpecificInfo) override;
+ bool RecvError(const GMPErr& aError) override;
+ bool RecvShutdown() override;
+ bool RecvParentShmemForPool(Shmem&& aFrameBuffer) override;
+ bool AnswerNeedShmem(const uint32_t& aEncodedBufferSize,
+ Shmem* aMem) override;
+ bool Recv__delete__() override;
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ RefPtr<GMPContentParent> mPlugin;
+ GMPVideoEncoderCallbackProxy* mCallback;
+ GMPVideoHostImpl mVideoHost;
+ nsCOMPtr<nsIThread> mEncodedThread;
+ const uint32_t mPluginId;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncoderParent_h_
diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h
new file mode 100644
index 000000000..655b1e9ae
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderProxy.h
@@ -0,0 +1,56 @@
+/* -*- 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 GMPVideoEncoderProxy_h_
+#define GMPVideoEncoderProxy_h_
+
+#include "nsTArray.h"
+#include "gmp-video-encode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#include "GMPCallbackBase.h"
+#include "GMPUtils.h"
+
+class GMPVideoEncoderCallbackProxy : public GMPCallbackBase {
+public:
+ virtual ~GMPVideoEncoderCallbackProxy() {}
+ virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo) = 0;
+ virtual void Error(GMPErr aError) = 0;
+};
+
+// A proxy to GMPVideoEncoder in the child process.
+// GMPVideoEncoderParent exposes this to users the GMP.
+// This enables Gecko to pass nsTArrays to the child GMP and avoid
+// an extra copy when doing so.
+
+// The consumer must call Close() when done with the codec, or when
+// Terminated() is called by the GMP plugin indicating an abnormal shutdown
+// of the underlying plugin. After calling Close(), the consumer must
+// not access this again.
+
+// This interface is not thread-safe and must only be used from GMPThread.
+class GMPVideoEncoderProxy {
+public:
+ virtual GMPErr InitEncode(const GMPVideoCodec& aCodecSettings,
+ const nsTArray<uint8_t>& aCodecSpecific,
+ GMPVideoEncoderCallbackProxy* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize) = 0;
+ virtual GMPErr Encode(mozilla::GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo,
+ const nsTArray<GMPVideoFrameType>& aFrameTypes) = 0;
+ virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
+ virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
+ virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0;
+ virtual uint32_t GetPluginId() const = 0;
+
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual void Close() = 0;
+};
+
+#endif // GMPVideoEncoderProxy_h_
diff --git a/dom/media/gmp/GMPVideoHost.cpp b/dom/media/gmp/GMPVideoHost.cpp
new file mode 100644
index 000000000..db40ebdae
--- /dev/null
+++ b/dom/media/gmp/GMPVideoHost.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 "GMPVideoHost.h"
+#include "mozilla/Assertions.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPVideoEncodedFrameImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoHostImpl::GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr)
+: mSharedMemMgr(aSharedMemMgr)
+{
+}
+
+GMPVideoHostImpl::~GMPVideoHostImpl()
+{
+}
+
+GMPErr
+GMPVideoHostImpl::CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame)
+{
+ if (!mSharedMemMgr) {
+ return GMPGenericErr;
+ }
+
+ if (!aFrame) {
+ return GMPGenericErr;
+ }
+ *aFrame = nullptr;
+
+ switch (aFormat) {
+ case kGMPI420VideoFrame:
+ *aFrame = new GMPVideoi420FrameImpl(this);
+ return GMPNoErr;
+ case kGMPEncodedVideoFrame:
+ *aFrame = new GMPVideoEncodedFrameImpl(this);
+ return GMPNoErr;
+ default:
+ NS_NOTREACHED("Unknown frame format!");
+ }
+
+ return GMPGenericErr;
+}
+
+GMPErr
+GMPVideoHostImpl::CreatePlane(GMPPlane** aPlane)
+{
+ if (!mSharedMemMgr) {
+ return GMPGenericErr;
+ }
+
+ if (!aPlane) {
+ return GMPGenericErr;
+ }
+ *aPlane = nullptr;
+
+ auto p = new GMPPlaneImpl(this);
+
+ *aPlane = p;
+
+ return GMPNoErr;
+}
+
+GMPSharedMemManager*
+GMPVideoHostImpl::SharedMemMgr()
+{
+ return mSharedMemMgr;
+}
+
+// XXX This should merge with ActorDestroyed
+void
+GMPVideoHostImpl::DoneWithAPI()
+{
+ ActorDestroyed();
+}
+
+void
+GMPVideoHostImpl::ActorDestroyed()
+{
+ for (uint32_t i = mPlanes.Length(); i > 0; i--) {
+ mPlanes[i - 1]->DoneWithAPI();
+ mPlanes.RemoveElementAt(i - 1);
+ }
+ for (uint32_t i = mEncodedFrames.Length(); i > 0; i--) {
+ mEncodedFrames[i - 1]->DoneWithAPI();
+ mEncodedFrames.RemoveElementAt(i - 1);
+ }
+ mSharedMemMgr = nullptr;
+}
+
+void
+GMPVideoHostImpl::PlaneCreated(GMPPlaneImpl* aPlane)
+{
+ mPlanes.AppendElement(aPlane);
+}
+
+void
+GMPVideoHostImpl::PlaneDestroyed(GMPPlaneImpl* aPlane)
+{
+ MOZ_ALWAYS_TRUE(mPlanes.RemoveElement(aPlane));
+}
+
+void
+GMPVideoHostImpl::EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame)
+{
+ mEncodedFrames.AppendElement(aEncodedFrame);
+}
+
+void
+GMPVideoHostImpl::EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame)
+{
+ MOZ_ALWAYS_TRUE(mEncodedFrames.RemoveElement(aFrame));
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoHost.h b/dom/media/gmp/GMPVideoHost.h
new file mode 100644
index 000000000..b3e42f08e
--- /dev/null
+++ b/dom/media/gmp/GMPVideoHost.h
@@ -0,0 +1,57 @@
+/* -*- 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 GMPVideoHost_h_
+#define GMPVideoHost_h_
+
+#include "gmp-video-host.h"
+#include "gmp-video-plane.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-host.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPSharedMemManager;
+class GMPPlaneImpl;
+class GMPVideoEncodedFrameImpl;
+
+class GMPVideoHostImpl : public GMPVideoHost
+{
+public:
+ explicit GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr);
+ virtual ~GMPVideoHostImpl();
+
+ // Used for shared memory allocation and deallocation.
+ GMPSharedMemManager* SharedMemMgr();
+ void DoneWithAPI();
+ void ActorDestroyed();
+ void PlaneCreated(GMPPlaneImpl* aPlane);
+ void PlaneDestroyed(GMPPlaneImpl* aPlane);
+ void EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame);
+ void EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame);
+
+ // GMPVideoHost
+ GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) override;
+ GMPErr CreatePlane(GMPPlane** aPlane) override;
+
+private:
+ // All shared memory allocations have to be made by an IPDL actor.
+ // This is a reference to the owning actor. If this reference is
+ // null then the actor has died and all allocations must fail.
+ GMPSharedMemManager* mSharedMemMgr;
+
+ // We track all of these things because they need to handle further
+ // allocations through us and we need to notify them when they
+ // can't use us any more.
+ nsTArray<GMPPlaneImpl*> mPlanes;
+ nsTArray<GMPVideoEncodedFrameImpl*> mEncodedFrames;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoHost_h_
diff --git a/dom/media/gmp/GMPVideoPlaneImpl.cpp b/dom/media/gmp/GMPVideoPlaneImpl.cpp
new file mode 100644
index 000000000..074a965e8
--- /dev/null
+++ b/dom/media/gmp/GMPVideoPlaneImpl.cpp
@@ -0,0 +1,225 @@
+/* -*- 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 "GMPVideoPlaneImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPVideoHost.h"
+#include "GMPSharedMemManager.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPPlaneImpl::GMPPlaneImpl(GMPVideoHostImpl* aHost)
+: mSize(0),
+ mStride(0),
+ mHost(aHost)
+{
+ MOZ_ASSERT(mHost);
+ mHost->PlaneCreated(this);
+}
+
+GMPPlaneImpl::GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost)
+: mBuffer(aPlaneData.mBuffer()),
+ mSize(aPlaneData.mSize()),
+ mStride(aPlaneData.mStride()),
+ mHost(aHost)
+{
+ MOZ_ASSERT(mHost);
+ mHost->PlaneCreated(this);
+}
+
+GMPPlaneImpl::~GMPPlaneImpl()
+{
+ DestroyBuffer();
+ if (mHost) {
+ mHost->PlaneDestroyed(this);
+ }
+}
+
+void
+GMPPlaneImpl::DoneWithAPI()
+{
+ DestroyBuffer();
+
+ // Do this after destroying the buffer because destruction
+ // involves deallocation, which requires a host.
+ mHost = nullptr;
+}
+
+void
+GMPPlaneImpl::ActorDestroyed()
+{
+ // Simply clear out Shmem reference, do not attempt to
+ // properly free it. It has already been freed.
+ mBuffer = ipc::Shmem();
+ // No more host.
+ mHost = nullptr;
+}
+
+bool
+GMPPlaneImpl::InitPlaneData(GMPPlaneData& aPlaneData)
+{
+ aPlaneData.mBuffer() = mBuffer;
+ aPlaneData.mSize() = mSize;
+ aPlaneData.mStride() = mStride;
+
+ // This method is called right before Shmem is sent to another process.
+ // We need to effectively zero out our member copy so that we don't
+ // try to delete memory we don't own later.
+ mBuffer = ipc::Shmem();
+
+ return true;
+}
+
+GMPErr
+GMPPlaneImpl::MaybeResize(int32_t aNewSize) {
+ if (aNewSize <= AllocatedSize()) {
+ return GMPNoErr;
+ }
+
+ if (!mHost) {
+ return GMPGenericErr;
+ }
+
+ ipc::Shmem new_mem;
+ if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData, aNewSize,
+ ipc::SharedMemory::TYPE_BASIC, &new_mem) ||
+ !new_mem.get<uint8_t>()) {
+ return GMPAllocErr;
+ }
+
+ if (mBuffer.IsReadable()) {
+ memcpy(new_mem.get<uint8_t>(), Buffer(), mSize);
+ }
+
+ DestroyBuffer();
+
+ mBuffer = new_mem;
+
+ return GMPNoErr;
+}
+
+void
+GMPPlaneImpl::DestroyBuffer()
+{
+ if (mHost && mBuffer.IsWritable()) {
+ mHost->SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, mBuffer);
+ }
+ mBuffer = ipc::Shmem();
+}
+
+GMPErr
+GMPPlaneImpl::CreateEmptyPlane(int32_t aAllocatedSize, int32_t aStride, int32_t aPlaneSize)
+{
+ if (aAllocatedSize < 1 || aStride < 1 || aPlaneSize < 1) {
+ return GMPGenericErr;
+ }
+
+ GMPErr err = MaybeResize(aAllocatedSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mSize = aPlaneSize;
+ mStride = aStride;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPPlaneImpl::Copy(const GMPPlane& aPlane)
+{
+ auto& planeimpl = static_cast<const GMPPlaneImpl&>(aPlane);
+
+ GMPErr err = MaybeResize(planeimpl.mSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ if (planeimpl.Buffer() && planeimpl.mSize > 0) {
+ memcpy(Buffer(), planeimpl.Buffer(), mSize);
+ }
+
+ mSize = planeimpl.mSize;
+ mStride = planeimpl.mStride;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPPlaneImpl::Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer)
+{
+ GMPErr err = MaybeResize(aSize);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ if (aBuffer && aSize > 0) {
+ memcpy(Buffer(), aBuffer, aSize);
+ }
+
+ mSize = aSize;
+ mStride = aStride;
+
+ return GMPNoErr;
+}
+
+void
+GMPPlaneImpl::Swap(GMPPlane& aPlane)
+{
+ auto& planeimpl = static_cast<GMPPlaneImpl&>(aPlane);
+
+ std::swap(mStride, planeimpl.mStride);
+ std::swap(mSize, planeimpl.mSize);
+ std::swap(mBuffer, planeimpl.mBuffer);
+}
+
+int32_t
+GMPPlaneImpl::AllocatedSize() const
+{
+ if (mBuffer.IsWritable()) {
+ return mBuffer.Size<uint8_t>();
+ }
+ return 0;
+}
+
+void
+GMPPlaneImpl::ResetSize()
+{
+ mSize = 0;
+}
+
+bool
+GMPPlaneImpl::IsZeroSize() const
+{
+ return (mSize == 0);
+}
+
+int32_t
+GMPPlaneImpl::Stride() const
+{
+ return mStride;
+}
+
+const uint8_t*
+GMPPlaneImpl::Buffer() const
+{
+ return mBuffer.get<uint8_t>();
+}
+
+uint8_t*
+GMPPlaneImpl::Buffer()
+{
+ return mBuffer.get<uint8_t>();
+}
+
+void
+GMPPlaneImpl::Destroy()
+{
+ delete this;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoPlaneImpl.h b/dom/media/gmp/GMPVideoPlaneImpl.h
new file mode 100644
index 000000000..d3a0d753a
--- /dev/null
+++ b/dom/media/gmp/GMPVideoPlaneImpl.h
@@ -0,0 +1,66 @@
+/* -*- 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 GMPVideoPlaneImpl_h_
+#define GMPVideoPlaneImpl_h_
+
+#include "gmp-video-plane.h"
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPPlaneData;
+
+class GMPPlaneImpl : public GMPPlane
+{
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPPlaneImpl>;
+public:
+ explicit GMPPlaneImpl(GMPVideoHostImpl* aHost);
+ GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost);
+ virtual ~GMPPlaneImpl();
+
+ // This is called during a normal destroy sequence, which is
+ // when a consumer is finished or during XPCOM shutdown.
+ void DoneWithAPI();
+ // This is called when something has gone wrong - specicifically,
+ // a child process has crashed. Does not attempt to release Shmem,
+ // as the Shmem has already been released.
+ void ActorDestroyed();
+
+ bool InitPlaneData(GMPPlaneData& aPlaneData);
+
+ // GMPPlane
+ GMPErr CreateEmptyPlane(int32_t aAllocatedSize,
+ int32_t aStride,
+ int32_t aPlaneSize) override;
+ GMPErr Copy(const GMPPlane& aPlane) override;
+ GMPErr Copy(int32_t aSize,
+ int32_t aStride,
+ const uint8_t* aBuffer) override;
+ void Swap(GMPPlane& aPlane) override;
+ int32_t AllocatedSize() const override;
+ void ResetSize() override;
+ bool IsZeroSize() const override;
+ int32_t Stride() const override;
+ const uint8_t* Buffer() const override;
+ uint8_t* Buffer() override;
+ void Destroy() override;
+
+private:
+ GMPErr MaybeResize(int32_t aNewSize);
+ void DestroyBuffer();
+
+ ipc::Shmem mBuffer;
+ int32_t mSize;
+ int32_t mStride;
+ GMPVideoHostImpl* mHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoPlaneImpl_h_
diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.cpp b/dom/media/gmp/GMPVideoi420FrameImpl.cpp
new file mode 100644
index 000000000..fdbb9a962
--- /dev/null
+++ b/dom/media/gmp/GMPVideoi420FrameImpl.cpp
@@ -0,0 +1,365 @@
+/* -*- 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 "GMPVideoi420FrameImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/CheckedInt.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost)
+: mYPlane(aHost),
+ mUPlane(aHost),
+ mVPlane(aHost),
+ mWidth(0),
+ mHeight(0),
+ mTimestamp(0ll),
+ mDuration(0ll)
+{
+ MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData,
+ GMPVideoHostImpl* aHost)
+: mYPlane(aFrameData.mYPlane(), aHost),
+ mUPlane(aFrameData.mUPlane(), aHost),
+ mVPlane(aFrameData.mVPlane(), aHost),
+ mWidth(aFrameData.mWidth()),
+ mHeight(aFrameData.mHeight()),
+ mTimestamp(aFrameData.mTimestamp()),
+ mDuration(aFrameData.mDuration())
+{
+ MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::~GMPVideoi420FrameImpl()
+{
+}
+
+bool
+GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData)
+{
+ mYPlane.InitPlaneData(aFrameData.mYPlane());
+ mUPlane.InitPlaneData(aFrameData.mUPlane());
+ mVPlane.InitPlaneData(aFrameData.mVPlane());
+ aFrameData.mWidth() = mWidth;
+ aFrameData.mHeight() = mHeight;
+ aFrameData.mTimestamp() = mTimestamp;
+ aFrameData.mDuration() = mDuration;
+ return true;
+}
+
+GMPVideoFrameFormat
+GMPVideoi420FrameImpl::GetFrameFormat()
+{
+ return kGMPI420VideoFrame;
+}
+
+void
+GMPVideoi420FrameImpl::Destroy()
+{
+ delete this;
+}
+
+/* static */ bool
+GMPVideoi420FrameImpl::CheckFrameData(const GMPVideoi420FrameData& aFrameData)
+{
+ // We may be passed the "wrong" shmem (one smaller than the actual size).
+ // This implies a bug or serious error on the child size. Ignore this frame if so.
+ // Note: Size() greater than expected is also an error, but with no negative consequences
+ int32_t half_width = (aFrameData.mWidth() + 1) / 2;
+ if ((aFrameData.mYPlane().mStride() <= 0) || (aFrameData.mYPlane().mSize() <= 0) ||
+ (aFrameData.mUPlane().mStride() <= 0) || (aFrameData.mUPlane().mSize() <= 0) ||
+ (aFrameData.mVPlane().mStride() <= 0) || (aFrameData.mVPlane().mSize() <= 0) ||
+ (aFrameData.mYPlane().mSize() > (int32_t) aFrameData.mYPlane().mBuffer().Size<uint8_t>()) ||
+ (aFrameData.mUPlane().mSize() > (int32_t) aFrameData.mUPlane().mBuffer().Size<uint8_t>()) ||
+ (aFrameData.mVPlane().mSize() > (int32_t) aFrameData.mVPlane().mBuffer().Size<uint8_t>()) ||
+ (aFrameData.mYPlane().mStride() < aFrameData.mWidth()) ||
+ (aFrameData.mUPlane().mStride() < half_width) ||
+ (aFrameData.mVPlane().mStride() < half_width) ||
+ (aFrameData.mYPlane().mSize() < aFrameData.mYPlane().mStride() * aFrameData.mHeight()) ||
+ (aFrameData.mUPlane().mSize() < aFrameData.mUPlane().mStride() * ((aFrameData.mHeight()+1)/2)) ||
+ (aFrameData.mVPlane().mSize() < aFrameData.mVPlane().mStride() * ((aFrameData.mHeight()+1)/2)))
+ {
+ return false;
+ }
+ return true;
+}
+
+bool
+GMPVideoi420FrameImpl::CheckDimensions(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+ int32_t half_width = (aWidth + 1) / 2;
+ if (aWidth < 1 || aHeight < 1 ||
+ aStride_y < aWidth || aStride_u < half_width || aStride_v < half_width ||
+ !(CheckedInt<int32_t>(aHeight) * aStride_y
+ + ((CheckedInt<int32_t>(aHeight) + 1) / 2)
+ * (CheckedInt<int32_t>(aStride_u) + aStride_v)).isValid()) {
+ return false;
+ }
+ return true;
+}
+
+const GMPPlaneImpl*
+GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) const {
+ switch (aType) {
+ case kGMPYPlane:
+ return &mYPlane;
+ case kGMPUPlane:
+ return &mUPlane;
+ case kGMPVPlane:
+ return &mVPlane;
+ default:
+ MOZ_CRASH("Unknown plane type!");
+ }
+ return nullptr;
+}
+
+GMPPlaneImpl*
+GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) {
+ switch (aType) {
+ case kGMPYPlane :
+ return &mYPlane;
+ case kGMPUPlane :
+ return &mUPlane;
+ case kGMPVPlane :
+ return &mVPlane;
+ default:
+ MOZ_CRASH("Unknown plane type!");
+ }
+ return nullptr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::CreateEmptyFrame(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+ if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) {
+ return GMPGenericErr;
+ }
+
+ int32_t size_y = aStride_y * aHeight;
+ int32_t half_height = (aHeight + 1) / 2;
+ int32_t size_u = aStride_u * half_height;
+ int32_t size_v = aStride_v * half_height;
+
+ GMPErr err = mYPlane.CreateEmptyPlane(size_y, aStride_y, size_y);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mUPlane.CreateEmptyPlane(size_u, aStride_u, size_u);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mVPlane.CreateEmptyPlane(size_v, aStride_v, size_v);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mTimestamp = 0ll;
+ mDuration = 0ll;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+ int32_t aSize_u, const uint8_t* aBuffer_u,
+ int32_t aSize_v, const uint8_t* aBuffer_v,
+ int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+ MOZ_ASSERT(aBuffer_y);
+ MOZ_ASSERT(aBuffer_u);
+ MOZ_ASSERT(aBuffer_v);
+
+ if (aSize_y < 1 || aSize_u < 1 || aSize_v < 1) {
+ return GMPGenericErr;
+ }
+
+ if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) {
+ return GMPGenericErr;
+ }
+
+ GMPErr err = mYPlane.Copy(aSize_y, aStride_y, aBuffer_y);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mUPlane.Copy(aSize_u, aStride_u, aBuffer_u);
+ if (err != GMPNoErr) {
+ return err;
+ }
+ err = mVPlane.Copy(aSize_v, aStride_v, aBuffer_v);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::CopyFrame(const GMPVideoi420Frame& aFrame)
+{
+ auto& f = static_cast<const GMPVideoi420FrameImpl&>(aFrame);
+
+ GMPErr err = mYPlane.Copy(f.mYPlane);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ err = mUPlane.Copy(f.mUPlane);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ err = mVPlane.Copy(f.mVPlane);
+ if (err != GMPNoErr) {
+ return err;
+ }
+
+ mWidth = f.mWidth;
+ mHeight = f.mHeight;
+ mTimestamp = f.mTimestamp;
+ mDuration = f.mDuration;
+
+ return GMPNoErr;
+}
+
+void
+GMPVideoi420FrameImpl::SwapFrame(GMPVideoi420Frame* aFrame)
+{
+ auto f = static_cast<GMPVideoi420FrameImpl*>(aFrame);
+ mYPlane.Swap(f->mYPlane);
+ mUPlane.Swap(f->mUPlane);
+ mVPlane.Swap(f->mVPlane);
+ std::swap(mWidth, f->mWidth);
+ std::swap(mHeight, f->mHeight);
+ std::swap(mTimestamp, f->mTimestamp);
+ std::swap(mDuration, f->mDuration);
+}
+
+uint8_t*
+GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType)
+{
+ GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->Buffer();
+ }
+ return nullptr;
+}
+
+const uint8_t*
+GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->Buffer();
+ }
+ return nullptr;
+}
+
+int32_t
+GMPVideoi420FrameImpl::AllocatedSize(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->AllocatedSize();
+ }
+ return -1;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Stride(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+ if (p) {
+ return p->Stride();
+ }
+ return -1;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::SetWidth(int32_t aWidth)
+{
+ if (!CheckDimensions(aWidth, mHeight,
+ mYPlane.Stride(), mUPlane.Stride(),
+ mVPlane.Stride())) {
+ return GMPGenericErr;
+ }
+ mWidth = aWidth;
+ return GMPNoErr;
+}
+
+GMPErr
+GMPVideoi420FrameImpl::SetHeight(int32_t aHeight)
+{
+ if (!CheckDimensions(mWidth, aHeight,
+ mYPlane.Stride(), mUPlane.Stride(),
+ mVPlane.Stride())) {
+ return GMPGenericErr;
+ }
+ mHeight = aHeight;
+ return GMPNoErr;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Width() const
+{
+ return mWidth;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Height() const
+{
+ return mHeight;
+}
+
+void
+GMPVideoi420FrameImpl::SetTimestamp(uint64_t aTimestamp)
+{
+ mTimestamp = aTimestamp;
+}
+
+uint64_t
+GMPVideoi420FrameImpl::Timestamp() const
+{
+ return mTimestamp;
+}
+
+void
+GMPVideoi420FrameImpl::SetDuration(uint64_t aDuration)
+{
+ mDuration = aDuration;
+}
+
+uint64_t
+GMPVideoi420FrameImpl::Duration() const
+{
+ return mDuration;
+}
+
+bool
+GMPVideoi420FrameImpl::IsZeroSize() const
+{
+ return (mYPlane.IsZeroSize() && mUPlane.IsZeroSize() && mVPlane.IsZeroSize());
+}
+
+void
+GMPVideoi420FrameImpl::ResetSize()
+{
+ mYPlane.ResetSize();
+ mUPlane.ResetSize();
+ mVPlane.ResetSize();
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.h b/dom/media/gmp/GMPVideoi420FrameImpl.h
new file mode 100644
index 000000000..f5cb0254b
--- /dev/null
+++ b/dom/media/gmp/GMPVideoi420FrameImpl.h
@@ -0,0 +1,84 @@
+/* -*- 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 GMPVideoi420FrameImpl_h_
+#define GMPVideoi420FrameImpl_h_
+
+#include "gmp-video-frame-i420.h"
+#include "mozilla/ipc/Shmem.h"
+#include "GMPVideoPlaneImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoi420FrameData;
+
+class GMPVideoi420FrameImpl : public GMPVideoi420Frame
+{
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoi420FrameImpl>;
+public:
+ explicit GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost);
+ GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, GMPVideoHostImpl* aHost);
+ virtual ~GMPVideoi420FrameImpl();
+
+ static bool CheckFrameData(const GMPVideoi420FrameData& aFrameData);
+
+ bool InitFrameData(GMPVideoi420FrameData& aFrameData);
+ const GMPPlaneImpl* GetPlane(GMPPlaneType aType) const;
+ GMPPlaneImpl* GetPlane(GMPPlaneType aType);
+
+ // GMPVideoFrame
+ GMPVideoFrameFormat GetFrameFormat() override;
+ void Destroy() override;
+
+ // GMPVideoi420Frame
+ GMPErr CreateEmptyFrame(int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride_y,
+ int32_t aStride_u,
+ int32_t aStride_v) override;
+ GMPErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+ int32_t aSize_u, const uint8_t* aBuffer_u,
+ int32_t aSize_v, const uint8_t* aBuffer_v,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride_y,
+ int32_t aStride_u,
+ int32_t aStride_v) override;
+ GMPErr CopyFrame(const GMPVideoi420Frame& aFrame) override;
+ void SwapFrame(GMPVideoi420Frame* aFrame) override;
+ uint8_t* Buffer(GMPPlaneType aType) override;
+ const uint8_t* Buffer(GMPPlaneType aType) const override;
+ int32_t AllocatedSize(GMPPlaneType aType) const override;
+ int32_t Stride(GMPPlaneType aType) const override;
+ GMPErr SetWidth(int32_t aWidth) override;
+ GMPErr SetHeight(int32_t aHeight) override;
+ int32_t Width() const override;
+ int32_t Height() const override;
+ void SetTimestamp(uint64_t aTimestamp) override;
+ uint64_t Timestamp() const override;
+ void SetDuration(uint64_t aDuration) override;
+ uint64_t Duration() const override;
+ bool IsZeroSize() const override;
+ void ResetSize() override;
+
+private:
+ bool CheckDimensions(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v);
+
+ GMPPlaneImpl mYPlane;
+ GMPPlaneImpl mUPlane;
+ GMPPlaneImpl mVPlane;
+ int32_t mWidth;
+ int32_t mHeight;
+ uint64_t mTimestamp;
+ uint64_t mDuration;
+};
+
+} // namespace gmp
+
+} // namespace mozilla
+
+#endif // GMPVideoi420FrameImpl_h_
diff --git a/dom/media/gmp/PGMP.ipdl b/dom/media/gmp/PGMP.ipdl
new file mode 100644
index 000000000..e1738d010
--- /dev/null
+++ b/dom/media/gmp/PGMP.ipdl
@@ -0,0 +1,41 @@
+/* -*- 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 protocol PGMPTimer;
+include protocol PGMPStorage;
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMP
+{
+ parent opens PGMPContent;
+
+ manages PGMPTimer;
+ manages PGMPStorage;
+
+parent:
+ async PGMPTimer();
+ async PGMPStorage();
+
+ async PGMPContentChildDestroyed();
+
+ async AsyncShutdownComplete();
+ async AsyncShutdownRequired();
+
+child:
+ async BeginAsyncShutdown();
+ async CrashPluginNow();
+ intr StartPlugin(nsString adapter);
+ async SetNodeId(nsCString nodeId);
+ async PreloadLibs(nsCString libs);
+ async CloseActive();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPAudioDecoder.ipdl b/dom/media/gmp/PGMPAudioDecoder.ipdl
new file mode 100644
index 000000000..9af9415c8
--- /dev/null
+++ b/dom/media/gmp/PGMPAudioDecoder.ipdl
@@ -0,0 +1,37 @@
+/* -*- 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 GMPCodecSpecificInfo from "gmp-audio-codec.h";
+using GMPErr from "gmp-errors.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPAudioDecoder
+{
+ manager PGMPContent;
+child:
+ async InitDecode(GMPAudioCodecData aCodecSettings);
+ async Decode(GMPAudioEncodedSampleData aInput);
+ async Reset();
+ async Drain();
+ async DecodingComplete();
+parent:
+ async __delete__();
+ async Decoded(GMPAudioDecodedSampleData aDecoded);
+ async InputDataExhausted();
+ async DrainComplete();
+ async ResetComplete();
+ async Error(GMPErr aErr);
+ async Shutdown();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPContent.ipdl b/dom/media/gmp/PGMPContent.ipdl
new file mode 100644
index 000000000..00e16c02f
--- /dev/null
+++ b/dom/media/gmp/PGMPContent.ipdl
@@ -0,0 +1,33 @@
+/* -*- 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 PGMP;
+include protocol PGMPService;
+include protocol PGMPVideoDecoder;
+include protocol PGMPVideoEncoder;
+include protocol PGMPDecryptor;
+include protocol PGMPAudioDecoder;
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMPContent
+{
+ bridges PGMPService, PGMP;
+
+ manages PGMPAudioDecoder;
+ manages PGMPDecryptor;
+ manages PGMPVideoDecoder;
+ manages PGMPVideoEncoder;
+
+child:
+ async PGMPAudioDecoder();
+ async PGMPDecryptor();
+ async PGMPVideoDecoder(uint32_t aDecryptorId);
+ async PGMPVideoEncoder();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPDecryptor.ipdl b/dom/media/gmp/PGMPDecryptor.ipdl
new file mode 100644
index 000000000..06b9b9cb6
--- /dev/null
+++ b/dom/media/gmp/PGMPDecryptor.ipdl
@@ -0,0 +1,92 @@
+/* -*- 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 GMPSessionMessageType from "gmp-decryption.h";
+using GMPSessionType from "gmp-decryption.h";
+using GMPDOMException from "gmp-decryption.h";
+using GMPErr from "gmp-errors.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPDecryptor
+{
+ manager PGMPContent;
+child:
+
+ async Init(bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ async CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ nsCString aInitDataType,
+ uint8_t[] aInitData,
+ GMPSessionType aSessionType);
+
+ async LoadSession(uint32_t aPromiseId,
+ 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 SetServerCertificate(uint32_t aPromiseId,
+ uint8_t[] aServerCert);
+
+ async Decrypt(uint32_t aId,
+ uint8_t[] aBuffer,
+ GMPDecryptionData aMetadata);
+
+ async DecryptingComplete();
+
+parent:
+ async __delete__();
+
+ async SetDecryptorId(uint32_t aId);
+
+ async SetSessionId(uint32_t aCreateSessionToken,
+ nsCString aSessionId);
+
+ async ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess);
+
+ async ResolvePromise(uint32_t aPromiseId);
+
+ async RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aDOMExceptionCode,
+ nsCString aMessage);
+
+ async SessionMessage(nsCString aSessionId,
+ GMPSessionMessageType aMessageType,
+ uint8_t[] aMessage);
+
+ async ExpirationChange(nsCString aSessionId, double aExpiryTime);
+
+ async SessionClosed(nsCString aSessionId);
+
+ async SessionError(nsCString aSessionId,
+ GMPDOMException aDOMExceptionCode,
+ uint32_t aSystemCode,
+ nsCString aMessage);
+
+ async Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer);
+
+ async Shutdown();
+
+ async BatchedKeyStatusChanged(nsCString aSessionId,
+ GMPKeyInformation[] aKeyInfos);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPService.ipdl b/dom/media/gmp/PGMPService.ipdl
new file mode 100644
index 000000000..db3fb6388
--- /dev/null
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -0,0 +1,32 @@
+/* -*- 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 PGMP;
+
+using base::ProcessId from "base/process.h";
+
+namespace mozilla {
+namespace gmp {
+
+sync protocol PGMPService
+{
+ parent spawns PGMP as child;
+
+parent:
+
+ sync SelectGMP(nsCString nodeId, nsCString api, nsCString[] tags)
+ returns (uint32_t pluginId, nsresult aResult);
+
+ sync LaunchGMP(uint32_t pluginId, ProcessId[] alreadyBridgedTo)
+ returns (ProcessId id, nsCString displayName, nsresult aResult);
+
+ sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
+ nsString gmpName,
+ bool inPrivateBrowsing)
+ returns (nsCString id);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPStorage.ipdl b/dom/media/gmp/PGMPStorage.ipdl
new file mode 100644
index 000000000..4d858312a
--- /dev/null
+++ b/dom/media/gmp/PGMPStorage.ipdl
@@ -0,0 +1,36 @@
+/* -*- 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 PGMP;
+include GMPTypes;
+
+using GMPErr from "gmp-errors.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPStorage
+{
+ manager PGMP;
+
+child:
+ async OpenComplete(nsCString aRecordName, GMPErr aStatus);
+ async ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes);
+ async WriteComplete(nsCString aRecordName, GMPErr aStatus);
+ async RecordNames(nsCString[] aRecordNames, GMPErr aStatus);
+ async Shutdown();
+
+parent:
+ async Open(nsCString aRecordName);
+ async Read(nsCString aRecordName);
+ async Write(nsCString aRecordName, uint8_t[] aBytes);
+ async Close(nsCString aRecordName);
+ async GetRecordNames();
+ async __delete__();
+
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPTimer.ipdl b/dom/media/gmp/PGMPTimer.ipdl
new file mode 100644
index 000000000..7a486bc3a
--- /dev/null
+++ b/dom/media/gmp/PGMPTimer.ipdl
@@ -0,0 +1,22 @@
+/* -*- 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 PGMP;
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPTimer
+{
+ manager PGMP;
+child:
+ async TimerExpired(uint32_t aTimerId);
+parent:
+ async SetTimer(uint32_t aTimerId, uint32_t aTimeoutMs);
+ async __delete__();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPVideoDecoder.ipdl b/dom/media/gmp/PGMPVideoDecoder.ipdl
new file mode 100644
index 000000000..83ad8f700
--- /dev/null
+++ b/dom/media/gmp/PGMPVideoDecoder.ipdl
@@ -0,0 +1,50 @@
+/* -*- 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 GMPVideoCodec from "gmp-video-codec.h";
+using GMPErr from "gmp-errors.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMPVideoDecoder
+{
+ manager PGMPContent;
+child:
+ async InitDecode(GMPVideoCodec aCodecSettings,
+ uint8_t[] aCodecSpecific,
+ int32_t aCoreCount);
+ async Decode(GMPVideoEncodedFrameData aInputFrame,
+ bool aMissingFrames,
+ uint8_t[] aCodecSpecificInfo,
+ int64_t aRenderTimeMs);
+ async Reset();
+ async Drain();
+ async DecodingComplete();
+ async ChildShmemForPool(Shmem aFrameBuffer);
+
+parent:
+ async __delete__();
+ async Decoded(GMPVideoi420FrameData aDecodedFrame);
+ async ReceivedDecodedReferenceFrame(uint64_t aPictureId);
+ async ReceivedDecodedFrame(uint64_t aPictureId);
+ async InputDataExhausted();
+ async DrainComplete();
+ async ResetComplete();
+ async Error(GMPErr aErr);
+ async Shutdown();
+ async ParentShmemForPool(Shmem aEncodedBuffer);
+ // MUST be intr - if sync and we create a new Shmem, when the returned
+ // Shmem is received in the Child it will fail to Deserialize
+ intr NeedShmem(uint32_t aFrameBufferSize) returns (Shmem aMem);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPVideoEncoder.ipdl b/dom/media/gmp/PGMPVideoEncoder.ipdl
new file mode 100644
index 000000000..ef013352a
--- /dev/null
+++ b/dom/media/gmp/PGMPVideoEncoder.ipdl
@@ -0,0 +1,48 @@
+/* -*- 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 GMPVideoCodec from "gmp-video-codec.h";
+using GMPVideoFrameType from "gmp-video-frame-encoded.h";
+using GMPErr from "gmp-errors.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+intr protocol PGMPVideoEncoder
+{
+ manager PGMPContent;
+child:
+ async InitEncode(GMPVideoCodec aCodecSettings,
+ uint8_t[] aCodecSpecific,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize);
+ async Encode(GMPVideoi420FrameData aInputFrame,
+ uint8_t[] aCodecSpecificInfo,
+ GMPVideoFrameType[] aFrameTypes);
+ async SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT);
+ async SetRates(uint32_t aNewBitRate, uint32_t aFrameRate);
+ async SetPeriodicKeyFrames(bool aEnable);
+ async EncodingComplete();
+ async ChildShmemForPool(Shmem aEncodedBuffer);
+
+parent:
+ async __delete__();
+ async Encoded(GMPVideoEncodedFrameData aEncodedFrame,
+ uint8_t[] aCodecSpecificInfo);
+ async Error(GMPErr aErr);
+ async Shutdown();
+ async ParentShmemForPool(Shmem aFrameBuffer);
+ // MUST be intr - if sync and we create a new Shmem, when the returned
+ // Shmem is received in the Child it will fail to Deserialize
+ intr NeedShmem(uint32_t aEncodedBufferSize) returns (Shmem aMem);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/README.txt b/dom/media/gmp/README.txt
new file mode 100644
index 000000000..189cf3b30
--- /dev/null
+++ b/dom/media/gmp/README.txt
@@ -0,0 +1 @@
+This directory contains code supporting Gecko Media Plugins (GMPs). The GMP API is not the same thing as the Media Plugin API (MPAPI).
diff --git a/dom/media/gmp/gmp-api/gmp-async-shutdown.h b/dom/media/gmp/gmp-api/gmp-async-shutdown.h
new file mode 100644
index 000000000..42268668f
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-async-shutdown.h
@@ -0,0 +1,54 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_ASYNC_SHUTDOWN_H_
+#define GMP_ASYNC_SHUTDOWN_H_
+
+#define GMP_API_ASYNC_SHUTDOWN "async-shutdown"
+
+// API exposed by the plugin library to manage asynchronous shutdown.
+// Some plugins require special cleanup which may need to make calls
+// to host services and wait for async responses.
+//
+// To enable a plugins to block shutdown until its async shutdown is
+// complete, implement the GMPAsyncShutdown interface and return it when
+// your plugin's GMPGetAPI function is called with "async-shutdown".
+// When your GMPAsyncShutdown's BeginShutdown() implementation is called
+// by the GMP host, you should initate your async shutdown process.
+// Once you have completed shutdown, call the ShutdownComplete() function
+// of the GMPAsyncShutdownHost that is passed as the host argument to the
+// GMPGetAPI() call.
+//
+// Note: Your GMP's GMPShutdown function will still be called after your
+// call to ShutdownComplete().
+//
+// API name macro: GMP_API_ASYNC_SHUTDOWN
+// Host API: GMPAsyncShutdownHost
+class GMPAsyncShutdown {
+public:
+ virtual ~GMPAsyncShutdown() {}
+
+ virtual void BeginShutdown() = 0;
+};
+
+class GMPAsyncShutdownHost {
+public:
+ virtual ~GMPAsyncShutdownHost() {}
+
+ virtual void ShutdownComplete() = 0;
+};
+
+#endif // GMP_ASYNC_SHUTDOWN_H_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-codec.h b/dom/media/gmp/gmp-api/gmp-audio-codec.h
new file mode 100644
index 000000000..5a5c17bb9
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-codec.h
@@ -0,0 +1,43 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_AUDIO_CODEC_h_
+#define GMP_AUDIO_CODEC_h_
+
+#include <stdint.h>
+
+enum GMPAudioCodecType
+{
+ kGMPAudioCodecAAC,
+ kGMPAudioCodecVorbis,
+ kGMPAudioCodecInvalid // Should always be last.
+};
+
+struct GMPAudioCodec
+{
+ GMPAudioCodecType mCodecType;
+ uint32_t mChannelCount;
+ uint32_t mBitsPerChannel;
+ uint32_t mSamplesPerSecond;
+
+ // Codec extra data, such as vorbis setup header, or
+ // AAC AudioSpecificConfig.
+ // These are null/0 if not externally negotiated
+ const uint8_t* mExtraData;
+ uint32_t mExtraDataLen;
+};
+
+#endif // GMP_AUDIO_CODEC_h_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-decode.h b/dom/media/gmp/gmp-api/gmp-audio-decode.h
new file mode 100644
index 000000000..8b017c0af
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-decode.h
@@ -0,0 +1,84 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_AUDIO_DECODE_h_
+#define GMP_AUDIO_DECODE_h_
+
+#include "gmp-errors.h"
+#include "gmp-audio-samples.h"
+#include "gmp-audio-codec.h"
+#include <stdint.h>
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPAudioDecoderCallback
+{
+public:
+ virtual ~GMPAudioDecoderCallback() {}
+
+ virtual void Decoded(GMPAudioSamples* aDecodedSamples) = 0;
+
+ virtual void InputDataExhausted() = 0;
+
+ virtual void DrainComplete() = 0;
+
+ virtual void ResetComplete() = 0;
+
+ // Called when the decoder encounters a catestrophic error and cannot
+ // continue. Gecko will not send any more input for decoding.
+ virtual void Error(GMPErr aError) = 0;
+};
+
+#define GMP_API_AUDIO_DECODER "decode-audio"
+
+// Audio decoding for a single stream. A GMP may be asked to create multiple
+// decoders concurrently.
+//
+// API name macro: GMP_API_AUDIO_DECODER
+// Host API: GMPAudioHost
+//
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPAudioDecoder
+{
+public:
+ virtual ~GMPAudioDecoder() {}
+
+ // aCallback: Subclass should retain reference to it until DecodingComplete
+ // is called. Do not attempt to delete it, host retains ownership.
+ // TODO: Pass AudioHost so decoder can create GMPAudioEncodedFrame objects?
+ virtual void InitDecode(const GMPAudioCodec& aCodecSettings,
+ GMPAudioDecoderCallback* aCallback) = 0;
+
+ // Decode encoded audio frames (as a part of an audio stream). The decoded
+ // frames must be returned to the user through the decode complete callback.
+ virtual void Decode(GMPAudioSamples* aEncodedSamples) = 0;
+
+ // Reset decoder state and prepare for a new call to Decode(...).
+ // Flushes the decoder pipeline.
+ // The decoder should enqueue a task to run ResetComplete() on the main
+ // thread once the reset has finished.
+ virtual void Reset() = 0;
+
+ // Output decoded frames for any data in the pipeline, regardless of ordering.
+ // All remaining decoded frames should be immediately returned via callback.
+ // The decoder should enqueue a task to run DrainComplete() on the main
+ // thread once the reset has finished.
+ virtual void Drain() = 0;
+
+ // May free decoder memory.
+ virtual void DecodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_DECODE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-host.h b/dom/media/gmp/gmp-api/gmp-audio-host.h
new file mode 100644
index 000000000..fe3641938
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-host.h
@@ -0,0 +1,32 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_AUDIO_HOST_h_
+#define GMP_AUDIO_HOST_h_
+
+#include "gmp-errors.h"
+#include "gmp-audio-samples.h"
+
+class GMPAudioHost
+{
+public:
+ // Construct various Audio API objects. Host does not retain reference,
+ // caller is owner and responsible for deleting.
+ virtual GMPErr CreateSamples(GMPAudioFormat aFormat,
+ GMPAudioSamples** aSamples) = 0;
+};
+
+#endif // GMP_AUDIO_HOST_h_
diff --git a/dom/media/gmp/gmp-api/gmp-audio-samples.h b/dom/media/gmp/gmp-api/gmp-audio-samples.h
new file mode 100644
index 000000000..a47fc74b9
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-audio-samples.h
@@ -0,0 +1,74 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_AUDIO_FRAME_h_
+#define GMP_AUDIO_FRAME_h_
+
+#include <stdint.h>
+#include "gmp-errors.h"
+#include "gmp-decryption.h"
+
+enum GMPAudioFormat
+{
+ kGMPAudioEncodedSamples, // Raw compressed data, i.e. an AAC/Vorbis packet.
+ kGMPAudioIS16Samples, // Interleaved int16_t PCM samples.
+ kGMPAudioSamplesFormatInvalid // Should always be last.
+};
+
+class GMPAudioSamples {
+public:
+ // The format of the buffer.
+ virtual GMPAudioFormat GetFormat() = 0;
+ virtual void Destroy() = 0;
+
+ // MAIN THREAD ONLY
+ // Buffer size must be exactly what's required to contain all samples in
+ // the buffer; every byte is assumed to be part of a sample.
+ virtual GMPErr SetBufferSize(uint32_t aSize) = 0;
+
+ // Size of the buffer in bytes.
+ virtual uint32_t Size() = 0;
+
+ // Timestamps are in microseconds, and are the playback start time of the
+ // first sample in the buffer.
+ virtual void SetTimeStamp(uint64_t aTimeStamp) = 0;
+ virtual uint64_t TimeStamp() = 0;
+ virtual const uint8_t* Buffer() const = 0;
+ virtual uint8_t* Buffer() = 0;
+
+ // Get metadata describing how this frame is encrypted, or nullptr if the
+ // buffer is not encrypted.
+ virtual const GMPEncryptedBufferMetadata* GetDecryptionData() const = 0;
+
+ virtual uint32_t Channels() const = 0;
+ virtual void SetChannels(uint32_t aChannels) = 0;
+
+ // Rate; the number of frames per second, where a "frame" is one sample for
+ // each channel.
+ //
+ // For IS16 samples, the number of samples should be:
+ // Size() / (Channels() * sizeof(int16_t)).
+ //
+ // Note: Channels() and Rate() may not be constant across a decoding
+ // session. For example the rate for decoded samples may be different
+ // than the rate advertised by the MP4 container for encoded samples
+ // for HE-AAC streams with SBR/PS, and an EME-GMP may need to downsample
+ // to satisfy DRM requirements.
+ virtual uint32_t Rate() const = 0;
+ virtual void SetRate(uint32_t aRate) = 0;
+};
+
+#endif // GMP_AUDIO_FRAME_h_
diff --git a/dom/media/gmp/gmp-api/gmp-decryption.h b/dom/media/gmp/gmp-api/gmp-decryption.h
new file mode 100644
index 000000000..046a05759
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -0,0 +1,459 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_DECRYPTION_h_
+#define GMP_DECRYPTION_h_
+
+#include "gmp-platform.h"
+
+class GMPStringList {
+public:
+ virtual uint32_t Size() const = 0;
+
+ virtual void StringAt(uint32_t aIndex,
+ const char** aOutString, uint32_t* aOutLength) const = 0;
+
+ virtual ~GMPStringList() { }
+};
+
+class GMPEncryptedBufferMetadata {
+public:
+ // Key ID to identify the decryption key.
+ virtual const uint8_t* KeyId() const = 0;
+
+ // Size (in bytes) of |KeyId()|.
+ virtual uint32_t KeyIdSize() const = 0;
+
+ // Initialization vector.
+ virtual const uint8_t* IV() const = 0;
+
+ // Size (in bytes) of |IV|.
+ virtual uint32_t IVSize() const = 0;
+
+ // Number of entries returned by ClearBytes() and CipherBytes().
+ virtual uint32_t NumSubsamples() const = 0;
+
+ virtual const uint16_t* ClearBytes() const = 0;
+
+ virtual const uint32_t* CipherBytes() const = 0;
+
+ virtual ~GMPEncryptedBufferMetadata() {}
+
+ // The set of MediaKeySession IDs associated with this decryption key in
+ // the current stream.
+ virtual const GMPStringList* SessionIds() const = 0;
+};
+
+class GMPBuffer {
+public:
+ virtual uint32_t Id() const = 0;
+ virtual uint8_t* Data() = 0;
+ virtual uint32_t Size() const = 0;
+ virtual void Resize(uint32_t aSize) = 0;
+ virtual ~GMPBuffer() {}
+};
+
+// These match to the DOMException codes as per:
+// http://www.w3.org/TR/dom/#domexception
+enum GMPDOMException {
+ kGMPNoModificationAllowedError = 7,
+ kGMPNotFoundError = 8,
+ kGMPNotSupportedError = 9,
+ kGMPInvalidStateError = 11,
+ kGMPSyntaxError = 12,
+ kGMPInvalidModificationError = 13,
+ kGMPInvalidAccessError = 15,
+ kGMPSecurityError = 18,
+ kGMPAbortError = 20,
+ kGMPQuotaExceededError = 22,
+ kGMPTimeoutError = 23,
+ kGMPTypeError = 52
+};
+
+enum GMPSessionMessageType {
+ kGMPLicenseRequest = 0,
+ kGMPLicenseRenewal = 1,
+ kGMPLicenseRelease = 2,
+ kGMPIndividualizationRequest = 3,
+ kGMPMessageInvalid = 4 // Must always be last.
+};
+
+enum GMPMediaKeyStatus {
+ kGMPUsable = 0,
+ kGMPExpired = 1,
+ kGMPOutputDownscaled = 2,
+ kGMPOutputRestricted = 3,
+ kGMPInternalError = 4,
+ kGMPUnknown = 5, // Removes key from MediaKeyStatusMap
+ kGMPReleased = 6,
+ kGMPStatusPending = 7,
+ kGMPMediaKeyStatusInvalid = 8 // Must always be last.
+};
+
+struct GMPMediaKeyInfo {
+ GMPMediaKeyInfo() {}
+ GMPMediaKeyInfo(const uint8_t* aKeyId,
+ uint32_t aKeyIdSize,
+ GMPMediaKeyStatus aStatus)
+ : keyid(aKeyId)
+ , keyid_size(aKeyIdSize)
+ , status(aStatus)
+ {}
+ const uint8_t* keyid;
+ uint32_t keyid_size;
+ GMPMediaKeyStatus status;
+};
+
+// Time in milliseconds, as offset from epoch, 1 Jan 1970.
+typedef int64_t GMPTimestamp;
+
+// Callbacks to be called from the CDM. Threadsafe.
+class GMPDecryptorCallback {
+public:
+
+ // The GMPDecryptor should call this in response to a call to
+ // GMPDecryptor::CreateSession(). The GMP host calls CreateSession() when
+ // MediaKeySession.generateRequest() is called by JavaScript.
+ // After CreateSession() is called, the GMPDecryptor should call
+ // GMPDecryptorCallback::SetSessionId() to set the sessionId exposed to
+ // JavaScript on the MediaKeySession on which the generateRequest() was
+ // called. SetSessionId() must be called before
+ // GMPDecryptorCallback::SessionMessage() will work.
+ // aSessionId must be null terminated.
+ // Note: pass the aCreateSessionToken from the CreateSession() call,
+ // and then once the session has sent any messages required for the
+ // license request to be sent, then resolve the aPromiseId that was passed
+ // to GMPDecryptor::CreateSession().
+ // Note: GMPDecryptor::LoadSession() does *not* need to call SetSessionId()
+ // for GMPDecryptorCallback::SessionMessage() to work.
+ virtual void SetSessionId(uint32_t aCreateSessionToken,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Resolves a promise for a session loaded.
+ // Resolves to false if we don't have any session data stored for the given
+ // session ID.
+ // Must be called before SessionMessage().
+ virtual void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) = 0;
+
+ // Called to resolve a specified promise with "undefined".
+ virtual void ResolvePromise(uint32_t aPromiseId) = 0;
+
+ // Called to reject a promise with a DOMException.
+ // aMessage is logged to the WebConsole.
+ // aMessage is optional, but if present must be null terminated.
+ virtual void RejectPromise(uint32_t aPromiseId,
+ GMPDOMException aException,
+ const char* aMessage,
+ uint32_t aMessageLength) = 0;
+
+ // Called by the CDM when it has a message for a session.
+ // Length parameters should not include null termination.
+ // aSessionId must be null terminated.
+ virtual void SessionMessage(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPSessionMessageType aMessageType,
+ const uint8_t* aMessage,
+ uint32_t aMessageLength) = 0;
+
+ // aSessionId must be null terminated.
+ virtual void ExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPTimestamp aExpiryTime) = 0;
+
+ // Called by the GMP when a session is closed. All file IO
+ // that a session requires should be complete before calling this.
+ // aSessionId must be null terminated.
+ virtual void SessionClosed(const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Called by the GMP when an error occurs in a session.
+ // aSessionId must be null terminated.
+ // aMessage is logged to the WebConsole.
+ // aMessage is optional, but if present must be null terminated.
+ virtual void SessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ GMPDOMException aException,
+ uint32_t aSystemCode,
+ const char* aMessage,
+ uint32_t aMessageLength) = 0;
+
+ // Notifies the status of a key. Gecko will not call into the CDM to decrypt
+ // or decode content encrypted with a key unless the CDM has marked it
+ // usable first. So a CDM *MUST* mark its usable keys as usable!
+ virtual void KeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aKeyId,
+ uint32_t aKeyIdLength,
+ GMPMediaKeyStatus aStatus) = 0;
+
+ // DEPRECATED; this function has no affect.
+ virtual void SetCapabilities(uint64_t aCaps) = 0;
+
+ // Returns decrypted buffer to Gecko, or reports failure.
+ virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) = 0;
+
+ // To aggregate KeyStatusChanged into single callback per session id.
+ virtual void BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength) = 0;
+
+ virtual ~GMPDecryptorCallback() {}
+};
+
+// Host interface, passed to GetAPIFunc(), with "decrypt".
+class GMPDecryptorHost {
+public:
+ virtual void GetSandboxVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) = 0;
+
+ virtual void GetPluginVoucher(const uint8_t** aVoucher,
+ uint32_t* aVoucherLength) = 0;
+
+ virtual ~GMPDecryptorHost() {}
+};
+
+enum GMPSessionType {
+ kGMPTemporySession = 0,
+ kGMPPersistentSession = 1,
+ kGMPSessionInvalid = 2 // Must always be last.
+};
+
+// Gecko supports the current GMPDecryptor version, and the obsolete
+// version that the Adobe GMP still uses.
+#define GMP_API_DECRYPTOR "eme-decrypt-v9"
+#define GMP_API_DECRYPTOR_BACKWARDS_COMPAT "eme-decrypt-v7"
+
+// API exposed by plugin library to manage decryption sessions.
+// When the Host requests this by calling GMPGetAPIFunc().
+//
+// API name macro: GMP_API_DECRYPTOR
+// Host API: GMPDecryptorHost
+class GMPDecryptor {
+public:
+
+ // Sets the callback to use with the decryptor to return results
+ // to Gecko.
+ virtual void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) = 0;
+
+ // Initiates the creation of a session given |aType| and |aInitData|, and
+ // the generation of a license request message.
+ //
+ // This corresponds to a MediaKeySession.generateRequest() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Generate a sessionId to expose to JS, and call
+ // GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...)
+ // with the sessionId to be exposed to JS/EME on the MediaKeySession
+ // object on which generateRequest() was called, and then
+ // 2. send any messages to JS/EME required to generate a license request
+ // given the supplied initData, and then
+ // 3. generate a license request message, and send it to JS/EME, and then
+ // 4. call GMPDecryptorCallback::ResolvePromise().
+ //
+ // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...)
+ // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...)
+ // will work.
+ //
+ // If generating the request fails, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) = 0;
+
+ // Loads a previously loaded persistent session.
+ //
+ // This corresponds to a MediaKeySession.load() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Send any messages to JS/EME, or read from storage, whatever is
+ // required to load the session, and then
+ // 2. if there is no session with the given sessionId loadable, call
+ // ResolveLoadSessionPromise(aPromiseId, false), otherwise
+ // 2. mark the session's keys as usable, and then
+ // 3. update the session's expiration, and then
+ // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true).
+ //
+ // If loading the session fails due to error, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Updates the session with |aResponse|.
+ // This corresponds to a MediaKeySession.update() call in JS.
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) = 0;
+
+ // Releases the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.close() call in JS.
+ virtual void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Removes the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.remove() call in JS.
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Resolve/reject promise on completion.
+ // This corresponds to a MediaKeySession.setServerCertificate() call in JS.
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) = 0;
+
+ // Asynchronously decrypts aBuffer in place. When the decryption is
+ // complete, GMPDecryptor should write the decrypted data back into the
+ // same GMPBuffer object and return it to Gecko by calling Decrypted(),
+ // with the GMPNoErr successcode. If decryption fails, call Decrypted()
+ // with a failure code, and an error event will fire on the media element.
+ // Note: When Decrypted() is called and aBuffer is passed back, aBuffer
+ // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's
+ // memory will leak!
+ virtual void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) = 0;
+
+ // Called when the decryption operations are complete.
+ // Do not call the GMPDecryptorCallback's functions after this is called.
+ virtual void DecryptingComplete() = 0;
+
+ virtual ~GMPDecryptor() {}
+};
+
+// v7 is the latest decryptor version supported by the Adobe GMP.
+//
+// API name macro: GMP_API_DECRYPTOR_BACKWARDS_COMPAT
+// Host API: GMPDecryptorHost
+class GMPDecryptor7 {
+public:
+
+ // Sets the callback to use with the decryptor to return results
+ // to Gecko.
+ virtual void Init(GMPDecryptorCallback* aCallback) = 0;
+
+ // Initiates the creation of a session given |aType| and |aInitData|, and
+ // the generation of a license request message.
+ //
+ // This corresponds to a MediaKeySession.generateRequest() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Generate a sessionId to expose to JS, and call
+ // GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...)
+ // with the sessionId to be exposed to JS/EME on the MediaKeySession
+ // object on which generateRequest() was called, and then
+ // 2. send any messages to JS/EME required to generate a license request
+ // given the supplied initData, and then
+ // 3. generate a license request message, and send it to JS/EME, and then
+ // 4. call GMPDecryptorCallback::ResolvePromise().
+ //
+ // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...)
+ // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...)
+ // will work.
+ //
+ // If generating the request fails, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) = 0;
+
+ // Loads a previously loaded persistent session.
+ //
+ // This corresponds to a MediaKeySession.load() call in JS.
+ //
+ // The GMPDecryptor must do the following, in order, upon this method
+ // being called:
+ //
+ // 1. Send any messages to JS/EME, or read from storage, whatever is
+ // required to load the session, and then
+ // 2. if there is no session with the given sessionId loadable, call
+ // ResolveLoadSessionPromise(aPromiseId, false), otherwise
+ // 2. mark the session's keys as usable, and then
+ // 3. update the session's expiration, and then
+ // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true).
+ //
+ // If loading the session fails due to error, reject aPromiseId by calling
+ // GMPDecryptorCallback::RejectPromise().
+ virtual void LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Updates the session with |aResponse|.
+ // This corresponds to a MediaKeySession.update() call in JS.
+ virtual void UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) = 0;
+
+ // Releases the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.close() call in JS.
+ virtual void CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Removes the resources (keys) for the specified session.
+ // This corresponds to a MediaKeySession.remove() call in JS.
+ virtual void RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) = 0;
+
+ // Resolve/reject promise on completion.
+ // This corresponds to a MediaKeySession.setServerCertificate() call in JS.
+ virtual void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) = 0;
+
+ // Asynchronously decrypts aBuffer in place. When the decryption is
+ // complete, GMPDecryptor should write the decrypted data back into the
+ // same GMPBuffer object and return it to Gecko by calling Decrypted(),
+ // with the GMPNoErr successcode. If decryption fails, call Decrypted()
+ // with a failure code, and an error event will fire on the media element.
+ // Note: When Decrypted() is called and aBuffer is passed back, aBuffer
+ // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's
+ // memory will leak!
+ virtual void Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata) = 0;
+
+ // Called when the decryption operations are complete.
+ // Do not call the GMPDecryptorCallback's functions after this is called.
+ virtual void DecryptingComplete() = 0;
+
+ virtual ~GMPDecryptor7() {}
+};
+
+#endif // GMP_DECRYPTION_h_
diff --git a/dom/media/gmp/gmp-api/gmp-entrypoints.h b/dom/media/gmp/gmp-api/gmp-entrypoints.h
new file mode 100644
index 000000000..214c9dbfc
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-entrypoints.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_ENTRYPOINTS_h_
+#define GMP_ENTRYPOINTS_h_
+
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+
+/* C functions exposed by Gecko Media Plugin shared library. */
+
+// GMPInit
+// - Called once after plugin library is loaded, before GMPGetAPI or GMPShutdown are called.
+// - Called on main thread.
+// - 'aPlatformAPI' is a structure containing platform-provided APIs. It is valid until
+// 'GMPShutdown' is called. Owned and must be deleted by plugin.
+typedef GMPErr (*GMPInitFunc)(const GMPPlatformAPI* aPlatformAPI);
+
+// GMPGetAPI
+// - Called when host wants to use an API.
+// - Called on main thread.
+// - 'aAPIName' is a string indicating the API being requested. This should
+// match one of the GMP_API_* macros. Subsequent iterations of the GMP_APIs
+// may change the value of the GMP_API_* macros when ABI changes occur. So
+// make sure you compare aAPIName against the corresponding GMP_API_* macro!
+// - 'aHostAPI' is the host API which is specific to the API being requested
+// from the plugin. It is valid so long as the API object requested from the
+// plugin is valid. It is owned by the host, plugin should not attempt to delete.
+// May be null.
+// - 'aPluginAPI' is for returning the requested API. Destruction of the requsted
+// API object is defined by the API.
+typedef GMPErr (*GMPGetAPIFunc)(const char* aAPIName, void* aHostAPI, void** aPluginAPI);
+
+// GMPShutdown
+// - Called once before exiting process (unloading library).
+// - Called on main thread.
+typedef void (*GMPShutdownFunc)(void);
+
+// GMPSetNodeId
+// - Optional, not required to be implemented. Only useful for EME plugins.
+// - Called after GMPInit to set the device-bound origin-specific node id
+// that this GMP instance is running under.
+typedef void (*GMPSetNodeIdFunc)(const char* aNodeId, uint32_t aLength);
+
+#endif // GMP_ENTRYPOINTS_h_
diff --git a/dom/media/gmp/gmp-api/gmp-errors.h b/dom/media/gmp/gmp-api/gmp-errors.h
new file mode 100644
index 000000000..7f20e2a79
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-errors.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_ERRORS_h_
+#define GMP_ERRORS_h_
+
+typedef enum {
+ GMPNoErr = 0,
+ GMPGenericErr = 1,
+ GMPClosedErr = 2,
+ GMPAllocErr = 3,
+ GMPNotImplementedErr = 4,
+ GMPRecordInUse = 5,
+ GMPQuotaExceededErr = 6,
+ GMPDecodeErr = 7,
+ GMPEncodeErr = 8,
+ GMPNoKeyErr = 9,
+ GMPCryptoErr = 10,
+ GMPEndOfEnumeration = 11,
+ GMPInvalidArgErr = 12,
+ GMPAbortedErr = 13,
+ GMPRecordCorrupted = 14,
+ GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
+} GMPErr;
+
+#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
+#define GMP_FAILED(x) ((x) != GMPNoErr)
+
+#endif // GMP_ERRORS_h_
diff --git a/dom/media/gmp/gmp-api/gmp-platform.h b/dom/media/gmp/gmp-api/gmp-platform.h
new file mode 100644
index 000000000..f915050b3
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-platform.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_PLATFORM_h_
+#define GMP_PLATFORM_h_
+
+#include "gmp-errors.h"
+#include "gmp-storage.h"
+#include <stdint.h>
+
+/* Platform helper API. */
+
+class GMPTask {
+public:
+ virtual void Destroy() = 0; // Deletes object.
+ virtual ~GMPTask() {}
+ virtual void Run() = 0;
+};
+
+class GMPThread {
+public:
+ virtual ~GMPThread() {}
+ virtual void Post(GMPTask* aTask) = 0;
+ virtual void Join() = 0; // Deletes object after join completes.
+};
+
+// A re-entrant monitor; can be locked from the same thread multiple times.
+// Must be unlocked the same number of times it's locked.
+class GMPMutex {
+public:
+ virtual ~GMPMutex() {}
+ virtual void Acquire() = 0;
+ virtual void Release() = 0;
+ virtual void Destroy() = 0; // Deletes object.
+};
+
+// Time is defined as the number of milliseconds since the
+// Epoch (00:00:00 UTC, January 1, 1970).
+typedef int64_t GMPTimestamp;
+
+typedef GMPErr (*GMPCreateThreadPtr)(GMPThread** aThread);
+typedef GMPErr (*GMPRunOnMainThreadPtr)(GMPTask* aTask);
+typedef GMPErr (*GMPSyncRunOnMainThreadPtr)(GMPTask* aTask);
+typedef GMPErr (*GMPCreateMutexPtr)(GMPMutex** aMutex);
+
+// Call on main thread only.
+typedef GMPErr (*GMPCreateRecordPtr)(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+// Call on main thread only.
+typedef GMPErr (*GMPSetTimerOnMainThreadPtr)(GMPTask* aTask, int64_t aTimeoutMS);
+typedef GMPErr (*GMPGetCurrentTimePtr)(GMPTimestamp* aOutTime);
+
+typedef void (*RecvGMPRecordIteratorPtr)(GMPRecordIterator* aRecordIterator,
+ void* aUserArg,
+ GMPErr aStatus);
+
+// Creates a GMPCreateRecordIterator to enumerate the records in storage.
+// When the iterator is ready, the function at aRecvIteratorFunc
+// is called with the GMPRecordIterator as an argument. If the operation
+// fails, RecvGMPRecordIteratorPtr is called with a failure aStatus code.
+// The list that the iterator is covering is fixed when
+// GMPCreateRecordIterator is called, it is *not* updated when changes are
+// made to storage.
+// Iterator begins pointing at first record.
+// aUserArg is passed to the aRecvIteratorFunc upon completion.
+typedef GMPErr (*GMPCreateRecordIteratorPtr)(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+struct GMPPlatformAPI {
+ // Increment the version when things change. Can only add to the struct,
+ // do not change what already exists. Pointers to functions may be NULL
+ // when passed to plugins, but beware backwards compat implications of
+ // doing that.
+ uint16_t version; // Currently version 0
+
+ GMPCreateThreadPtr createthread;
+ GMPRunOnMainThreadPtr runonmainthread;
+ GMPSyncRunOnMainThreadPtr syncrunonmainthread;
+ GMPCreateMutexPtr createmutex;
+ GMPCreateRecordPtr createrecord;
+ GMPSetTimerOnMainThreadPtr settimer;
+ GMPGetCurrentTimePtr getcurrenttime;
+ GMPCreateRecordIteratorPtr getrecordenumerator;
+};
+
+#endif // GMP_PLATFORM_h_
diff --git a/dom/media/gmp/gmp-api/gmp-storage.h b/dom/media/gmp/gmp-api/gmp-storage.h
new file mode 100644
index 000000000..43ad12b01
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-storage.h
@@ -0,0 +1,141 @@
+/*
+* Copyright 2013, Mozilla Foundation and contributors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef GMP_STORAGE_h_
+#define GMP_STORAGE_h_
+
+#include "gmp-errors.h"
+#include <stdint.h>
+
+// Maximum size of a record, in bytes; 10 megabytes.
+#define GMP_MAX_RECORD_SIZE (10 * 1024 * 1024)
+
+// Maximum length of a record name in bytes.
+#define GMP_MAX_RECORD_NAME_SIZE 2000
+
+// Provides basic per-origin storage for CDMs. GMPRecord instances can be
+// retrieved by calling GMPPlatformAPI->openstorage. Multiple GMPRecords
+// with different names can be open at once, but a single record can only
+// be opened by one client at a time. This interface is asynchronous, with
+// results being returned via callbacks to the GMPRecordClient pointer
+// provided to the GMPPlatformAPI->openstorage call, on the main thread.
+//
+// Lifecycle: Once opened, the GMPRecord object remains allocated until
+// GMPRecord::Close() is called. If any GMPRecord function, either
+// synchronously or asynchronously through a GMPRecordClient callback,
+// returns an error, the GMP is responsible for calling Close() on the
+// GMPRecord to delete the GMPRecord object's memory. If your GMP does not
+// call Close(), the GMPRecord's memory will leak.
+class GMPRecord {
+public:
+
+ // Opens the record. Calls OpenComplete() once the record is open.
+ // Note: Only work when GMP is loading content from a webserver.
+ // Does not work for web pages on loaded from disk.
+ // Note: OpenComplete() is only called if this returns GMPNoErr.
+ virtual GMPErr Open() = 0;
+
+ // Reads the entire contents of the record, and calls
+ // GMPRecordClient::ReadComplete() once the operation is complete.
+ // Note: ReadComplete() is only called if this returns GMPNoErr.
+ virtual GMPErr Read() = 0;
+
+ // Writes aDataSize bytes of aData into the record, overwriting the
+ // contents of the record, truncating it to aDataSize length.
+ // Overwriting with 0 bytes "deletes" the record.
+ // Note: WriteComplete is only called if this returns GMPNoErr.
+ virtual GMPErr Write(const uint8_t* aData, uint32_t aDataSize) = 0;
+
+ // Closes a record, deletes the GMPRecord object. The GMPRecord object
+ // must not be used after this is called, request a new one with
+ // GMPPlatformAPI->openstorage to re-open this record. Cancels all
+ // callbacks.
+ virtual GMPErr Close() = 0;
+
+ virtual ~GMPRecord() {}
+};
+
+// Callback object that receives the results of GMPRecord calls. Callbacks
+// run asynchronously to the GMPRecord call, on the main thread.
+class GMPRecordClient {
+ public:
+
+ // Response to a GMPRecord::Open() call with the open |status|.
+ // aStatus values:
+ // - GMPNoErr - Record opened successfully. Record may be empty.
+ // - GMPRecordInUse - This record is in use by another client.
+ // - GMPGenericErr - Unspecified error.
+ // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
+ // call Close() on the GMPRecord to dispose of it.
+ virtual void OpenComplete(GMPErr aStatus) = 0;
+
+ // Response to a GMPRecord::Read() call, where aData is the record contents,
+ // of length aDataSize.
+ // aData is only valid for the duration of the call to ReadComplete.
+ // Copy it if you want to hang onto it!
+ // aStatus values:
+ // - GMPNoErr - Record contents read successfully, aDataSize 0 means record
+ // is empty.
+ // - GMPRecordInUse - There are other operations or clients in use on
+ // this record.
+ // - GMPGenericErr - Unspecified error.
+ // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
+ // call Close() on the GMPRecord to dispose of it.
+ virtual void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) = 0;
+
+ // Response to a GMPRecord::Write() call.
+ // - GMPNoErr - File contents written successfully.
+ // - GMPRecordInUse - There are other operations or clients in use on
+ // this record.
+ // - GMPGenericErr - Unspecified error.
+ // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
+ // call Close() on the GMPRecord to dispose of it.
+ virtual void WriteComplete(GMPErr aStatus) = 0;
+
+ virtual ~GMPRecordClient() {}
+};
+
+// Iterates over the records that are available. Note: this list maintains
+// a snapshot of the records that were present when the iterator was created.
+// Create by calling the GMPCreateRecordIteratorPtr function on the
+// GMPPlatformAPI struct.
+// Iteration is in alphabetical order.
+class GMPRecordIterator {
+public:
+ // Retrieve the name for the current record.
+ // aOutName is null terminated at character at index (*aOutNameLength).
+ // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
+ // reached the end.
+ virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0;
+
+ // Advance iteration to the next record.
+ // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
+ // reached the end.
+ virtual GMPErr NextRecord() = 0;
+
+ // Signals to the GMP host that the GMP is finished with the
+ // GMPRecordIterator. GMPs must call this to release memory held by
+ // the GMPRecordIterator. Do not access the GMPRecordIterator pointer
+ // after calling this!
+ // Memory retrieved by GetName is *not* valid after calling Close()!
+ virtual void Close() = 0;
+
+ virtual ~GMPRecordIterator() {}
+};
+
+#endif // GMP_STORAGE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-codec.h b/dom/media/gmp/gmp-api/gmp-video-codec.h
new file mode 100644
index 000000000..e068ab43d
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-codec.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_CODEC_h_
+#define GMP_VIDEO_CODEC_h_
+
+#include <stdint.h>
+#include <stddef.h>
+
+enum { kGMPPayloadNameSize = 32};
+enum { kGMPMaxSimulcastStreams = 4};
+
+enum GMPVideoCodecComplexity
+{
+ kGMPComplexityNormal = 0,
+ kGMPComplexityHigh = 1,
+ kGMPComplexityHigher = 2,
+ kGMPComplexityMax = 3,
+ kGMPComplexityInvalid // Should always be last
+};
+
+enum GMPVP8ResilienceMode {
+ kResilienceOff, // The stream produced by the encoder requires a
+ // recovery frame (typically a key frame) to be
+ // decodable after a packet loss.
+ kResilientStream, // A stream produced by the encoder is resilient to
+ // packet losses, but packets within a frame subsequent
+ // to a loss can't be decoded.
+ kResilientFrames, // Same as kResilientStream but with added resilience
+ // within a frame.
+ kResilienceInvalid // Should always be last.
+};
+
+// VP8 specific
+struct GMPVideoCodecVP8
+{
+ bool mPictureLossIndicationOn;
+ bool mFeedbackModeOn;
+ GMPVideoCodecComplexity mComplexity;
+ GMPVP8ResilienceMode mResilience;
+ uint32_t mNumberOfTemporalLayers;
+ bool mDenoisingOn;
+ bool mErrorConcealmentOn;
+ bool mAutomaticResizeOn;
+};
+
+// H264 specific
+
+// Needs to match a binary spec for this structure.
+// Note: the mSPS at the end of this structure is variable length.
+struct GMPVideoCodecH264AVCC
+{
+ uint8_t mVersion; // == 0x01
+ uint8_t mProfile; // these 3 are profile_level_id
+ uint8_t mConstraints;
+ uint8_t mLevel;
+ uint8_t mLengthSizeMinusOne; // lower 2 bits (== GMPBufferType-1). Top 6 reserved (1's)
+
+ // SPS/PPS will not generally be present for interactive use unless SDP
+ // parameter-sets are used.
+ uint8_t mNumSPS; // lower 5 bits; top 5 reserved (1's)
+
+ /*** uint8_t mSPS[]; (Not defined due to compiler warnings and warnings-as-errors ...) **/
+ // Following mNumSPS is a variable number of bytes, which is the SPS and PPS.
+ // Each SPS == 16 bit size, ("N"), then "N" bytes,
+ // then uint8_t mNumPPS, then each PPS == 16 bit size ("N"), then "N" bytes.
+};
+
+// Codec specific data for H.264 decoding/encoding.
+// Cast the "aCodecSpecific" parameter of GMPVideoDecoder::InitDecode() and
+// GMPVideoEncoder::InitEncode() to this structure.
+struct GMPVideoCodecH264
+{
+ uint8_t mPacketizationMode; // 0 or 1
+ struct GMPVideoCodecH264AVCC mAVCC; // holds a variable-sized struct GMPVideoCodecH264AVCC mAVCC;
+};
+
+enum GMPVideoCodecType
+{
+ kGMPVideoCodecVP8,
+
+ // Encoded frames are in AVCC format; NAL length field of 4 bytes, followed
+ // by frame data. May be multiple NALUs per sample. Codec specific extra data
+ // is the AVCC extra data (in AVCC format).
+ kGMPVideoCodecH264,
+ kGMPVideoCodecVP9,
+ kGMPVideoCodecInvalid // Should always be last.
+};
+
+// Simulcast is when the same stream is encoded multiple times with different
+// settings such as resolution.
+struct GMPSimulcastStream
+{
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mNumberOfTemporalLayers;
+ uint32_t mMaxBitrate; // kilobits/sec.
+ uint32_t mTargetBitrate; // kilobits/sec.
+ uint32_t mMinBitrate; // kilobits/sec.
+ uint32_t mQPMax; // minimum quality
+};
+
+enum GMPVideoCodecMode {
+ kGMPRealtimeVideo,
+ kGMPScreensharing,
+ kGMPStreamingVideo,
+ kGMPCodecModeInvalid // Should always be last.
+};
+
+enum GMPApiVersion {
+ kGMPVersion32 = 1, // leveraging that V32 had mCodecType first, and only supported H264
+ kGMPVersion33 = 33,
+};
+
+struct GMPVideoCodec
+{
+ uint32_t mGMPApiVersion;
+
+ GMPVideoCodecType mCodecType;
+ char mPLName[kGMPPayloadNameSize]; // Must be NULL-terminated!
+ uint32_t mPLType;
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+
+ uint32_t mStartBitrate; // kilobits/sec.
+ uint32_t mMaxBitrate; // kilobits/sec.
+ uint32_t mMinBitrate; // kilobits/sec.
+ uint32_t mMaxFramerate;
+
+ bool mFrameDroppingOn;
+ int32_t mKeyFrameInterval;
+
+ uint32_t mQPMax;
+ uint32_t mNumberOfSimulcastStreams;
+ GMPSimulcastStream mSimulcastStream[kGMPMaxSimulcastStreams];
+
+ GMPVideoCodecMode mMode;
+};
+
+// Either single encoded unit, or multiple units separated by 8/16/24/32
+// bit lengths, all with the same timestamp. Note there is no final 0-length
+// entry; one should check the overall end-of-buffer against where the next
+// length would be.
+enum GMPBufferType {
+ GMP_BufferSingle = 0,
+ GMP_BufferLength8,
+ GMP_BufferLength16,
+ GMP_BufferLength24,
+ GMP_BufferLength32,
+ GMP_BufferInvalid,
+};
+
+struct GMPCodecSpecificInfoGeneric {
+ uint8_t mSimulcastIdx;
+};
+
+struct GMPCodecSpecificInfoH264 {
+ uint8_t mSimulcastIdx;
+};
+
+// Note: if any pointers are added to this struct, it must be fitted
+// with a copy-constructor. See below.
+struct GMPCodecSpecificInfoVP8
+{
+ bool mHasReceivedSLI;
+ uint8_t mPictureIdSLI;
+ bool mHasReceivedRPSI;
+ uint64_t mPictureIdRPSI;
+ int16_t mPictureId; // negative value to skip pictureId
+ bool mNonReference;
+ uint8_t mSimulcastIdx;
+ uint8_t mTemporalIdx;
+ bool mLayerSync;
+ int32_t mTL0PicIdx; // negative value to skip tl0PicIdx
+ int8_t mKeyIdx; // negative value to skip keyIdx
+};
+
+union GMPCodecSpecificInfoUnion
+{
+ GMPCodecSpecificInfoGeneric mGeneric;
+ GMPCodecSpecificInfoVP8 mVP8;
+ GMPCodecSpecificInfoH264 mH264;
+};
+
+// Note: if any pointers are added to this struct or its sub-structs, it
+// must be fitted with a copy-constructor. This is because it is copied
+// in the copy-constructor of VCMEncodedFrame.
+struct GMPCodecSpecificInfo
+{
+ GMPVideoCodecType mCodecType;
+ GMPBufferType mBufferType;
+ GMPCodecSpecificInfoUnion mCodecSpecific;
+};
+
+#endif // GMP_VIDEO_CODEC_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-decode.h b/dom/media/gmp/gmp-api/gmp-video-decode.h
new file mode 100644
index 000000000..e07a7525e
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-decode.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_DECODE_h_
+#define GMP_VIDEO_DECODE_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+#include <stdint.h>
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoDecoderCallback
+{
+public:
+ virtual ~GMPVideoDecoderCallback() {}
+
+ virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) = 0;
+
+ virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) = 0;
+
+ virtual void ReceivedDecodedFrame(const uint64_t aPictureId) = 0;
+
+ virtual void InputDataExhausted() = 0;
+
+ virtual void DrainComplete() = 0;
+
+ virtual void ResetComplete() = 0;
+
+ // Called when the decoder encounters a catestrophic error and cannot
+ // continue. Gecko will not send any more input for decoding.
+ virtual void Error(GMPErr aError) = 0;
+};
+
+#define GMP_API_VIDEO_DECODER "decode-video"
+
+// Video decoding for a single stream. A GMP may be asked to create multiple
+// decoders concurrently.
+//
+// API name macro: GMP_API_VIDEO_DECODER
+// Host API: GMPVideoHost
+//
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoDecoder
+{
+public:
+ virtual ~GMPVideoDecoder() {}
+
+ // - aCodecSettings: Details of decoder to create.
+ // - aCodecSpecific: codec specific data, cast to a GMPVideoCodecXXX struct
+ // to get codec specific config data.
+ // - aCodecSpecificLength: number of bytes in aCodecSpecific.
+ // - aCallback: Subclass should retain reference to it until DecodingComplete
+ // is called. Do not attempt to delete it, host retains ownership.
+ // aCoreCount: number of CPU cores.
+ virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount) = 0;
+
+ // Decode encoded frame (as a part of a video stream). The decoded frame
+ // will be returned to the user through the decode complete callback.
+ //
+ // - aInputFrame: Frame to decode. Call Destroy() on frame when it's decoded.
+ // - aMissingFrames: True if one or more frames have been lost since the
+ // previous decode call.
+ // - aCodecSpecificInfo : codec specific data, pointer to a
+ // GMPCodecSpecificInfo structure appropriate for
+ // this codec type.
+ // - aCodecSpecificInfoLength : number of bytes in aCodecSpecificInfo
+ // - renderTimeMs : System time to render in milliseconds. Only used by
+ // decoders with internal rendering.
+ virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs = -1) = 0;
+
+ // Reset decoder state and prepare for a new call to Decode(...).
+ // Flushes the decoder pipeline.
+ // The decoder should enqueue a task to run ResetComplete() on the main
+ // thread once the reset has finished.
+ virtual void Reset() = 0;
+
+ // Output decoded frames for any data in the pipeline, regardless of ordering.
+ // All remaining decoded frames should be immediately returned via callback.
+ // The decoder should enqueue a task to run DrainComplete() on the main
+ // thread once the reset has finished.
+ virtual void Drain() = 0;
+
+ // May free decoder memory.
+ virtual void DecodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_DECODE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-encode.h b/dom/media/gmp/gmp-api/gmp-video-encode.h
new file mode 100644
index 000000000..5c9cde39c
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-encode.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_ENCODE_h_
+#define GMP_VIDEO_ENCODE_h_
+
+#include <vector>
+#include <stdint.h>
+
+#include "gmp-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoEncoderCallback
+{
+public:
+ virtual ~GMPVideoEncoderCallback() {}
+
+ virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength) = 0;
+
+ // Called when the encoder encounters a catestrophic error and cannot
+ // continue. Gecko will not send any more input for encoding.
+ virtual void Error(GMPErr aError) = 0;
+};
+
+#define GMP_API_VIDEO_ENCODER "encode-video"
+
+// Video encoding for a single stream. A GMP may be asked to create multiple
+// encoders concurrently.
+//
+// API name macro: GMP_API_VIDEO_ENCODER
+// Host API: GMPVideoHost
+//
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoEncoder
+{
+public:
+ virtual ~GMPVideoEncoder() {}
+
+ // Initialize the encoder with the information from the VideoCodec.
+ //
+ // Input:
+ // - codecSettings : Codec settings
+ // - aCodecSpecific : codec specific data, pointer to a
+ // GMPCodecSpecific structure appropriate for
+ // this codec type.
+ // - aCodecSpecificLength : number of bytes in aCodecSpecific
+ // - aCallback: Subclass should retain reference to it until EncodingComplete
+ // is called. Do not attempt to delete it, host retains ownership.
+ // - aNnumberOfCores : Number of cores available for the encoder
+ // - aMaxPayloadSize : The maximum size each payload is allowed
+ // to have. Usually MTU - overhead.
+ virtual void InitEncode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoEncoderCallback* aCallback,
+ int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize) = 0;
+
+ // Encode an I420 frame (as a part of a video stream). The encoded frame
+ // will be returned to the user through the encode complete callback.
+ //
+ // Input:
+ // - aInputFrame : Frame to be encoded
+ // - aCodecSpecificInfo : codec specific data, pointer to a
+ // GMPCodecSpecificInfo structure appropriate for
+ // this codec type.
+ // - aCodecSpecificInfoLength : number of bytes in aCodecSpecific
+ // - aFrameTypes : The frame type to encode
+ // - aFrameTypesLength : The number of elements in aFrameTypes array.
+ virtual void Encode(GMPVideoi420Frame* aInputFrame,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ const GMPVideoFrameType* aFrameTypes,
+ uint32_t aFrameTypesLength) = 0;
+
+ // Inform the encoder about the packet loss and round trip time on the
+ // network used to decide the best pattern and signaling.
+ //
+ // - packetLoss : Fraction lost (loss rate in percent =
+ // 100 * packetLoss / 255)
+ // - rtt : Round-trip time in milliseconds
+ virtual void SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
+
+ // Inform the encoder about the new target bit rate.
+ //
+ // - newBitRate : New target bit rate
+ // - frameRate : The target frame rate
+ virtual void SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
+
+ // Use this function to enable or disable periodic key frames. Can be useful for codecs
+ // which have other ways of stopping error propagation.
+ //
+ // - enable : Enable or disable periodic key frames
+ virtual void SetPeriodicKeyFrames(bool aEnable) = 0;
+
+ // May free Encoder memory.
+ virtual void EncodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_ENCODE_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h
new file mode 100644
index 000000000..76af7349f
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_ENCODED_h_
+#define GMP_VIDEO_FRAME_ENCODED_h_
+
+#include <stdint.h>
+#include "gmp-decryption.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-codec.h"
+
+enum GMPVideoFrameType
+{
+ kGMPKeyFrame = 0,
+ kGMPDeltaFrame = 1,
+ kGMPGoldenFrame = 2,
+ kGMPAltRefFrame = 3,
+ kGMPSkipFrame = 4,
+ kGMPVideoFrameInvalid = 5 // Must always be last.
+};
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPVideoEncodedFrame : public GMPVideoFrame
+{
+public:
+ // MAIN THREAD ONLY
+ virtual GMPErr CreateEmptyFrame(uint32_t aSize) = 0;
+ // MAIN THREAD ONLY
+ virtual GMPErr CopyFrame(const GMPVideoEncodedFrame& aVideoFrame) = 0;
+ virtual void SetEncodedWidth(uint32_t aEncodedWidth) = 0;
+ virtual uint32_t EncodedWidth() = 0;
+ virtual void SetEncodedHeight(uint32_t aEncodedHeight) = 0;
+ virtual uint32_t EncodedHeight() = 0;
+ // Microseconds
+ virtual void SetTimeStamp(uint64_t aTimeStamp) = 0;
+ virtual uint64_t TimeStamp() = 0;
+ // Set frame duration (microseconds)
+ // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration()
+ // depending on rounding to avoid having to track roundoff errors
+ // and dropped/missing frames(!) (which may leave a large gap)
+ virtual void SetDuration(uint64_t aDuration) = 0;
+ virtual uint64_t Duration() const = 0;
+ virtual void SetFrameType(GMPVideoFrameType aFrameType) = 0;
+ virtual GMPVideoFrameType FrameType() = 0;
+ virtual void SetAllocatedSize(uint32_t aNewSize) = 0;
+ virtual uint32_t AllocatedSize() = 0;
+ virtual void SetSize(uint32_t aSize) = 0;
+ virtual uint32_t Size() = 0;
+ virtual void SetCompleteFrame(bool aCompleteFrame) = 0;
+ virtual bool CompleteFrame() = 0;
+ virtual const uint8_t* Buffer() const = 0;
+ virtual uint8_t* Buffer() = 0;
+ virtual GMPBufferType BufferType() const = 0;
+ virtual void SetBufferType(GMPBufferType aBufferType) = 0;
+
+ // Get metadata describing how this frame is encrypted, or nullptr if the
+ // frame is not encrypted.
+ virtual const GMPEncryptedBufferMetadata* GetDecryptionData() const = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_ENCODED_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-frame-i420.h b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h
new file mode 100644
index 000000000..14c2c33cd
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_I420_h_
+#define GMP_VIDEO_FRAME_I420_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-plane.h"
+
+#include <stdint.h>
+
+enum GMPPlaneType {
+ kGMPYPlane = 0,
+ kGMPUPlane = 1,
+ kGMPVPlane = 2,
+ kGMPNumOfPlanes = 3
+};
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPVideoi420Frame : public GMPVideoFrame {
+public:
+ // MAIN THREAD ONLY
+ // CreateEmptyFrame: Sets frame dimensions and allocates buffers based
+ // on set dimensions - height and plane stride.
+ // If required size is bigger than the allocated one, new buffers of adequate
+ // size will be allocated.
+ virtual GMPErr CreateEmptyFrame(int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0;
+
+ // MAIN THREAD ONLY
+ // CreateFrame: Sets the frame's members and buffers. If required size is
+ // bigger than allocated one, new buffers of adequate size will be allocated.
+ virtual GMPErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+ int32_t aSize_u, const uint8_t* aBuffer_u,
+ int32_t aSize_v, const uint8_t* aBuffer_v,
+ int32_t aWidth, int32_t aHeight,
+ int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0;
+
+ // MAIN THREAD ONLY
+ // Copy frame: If required size is bigger than allocated one, new buffers of
+ // adequate size will be allocated.
+ virtual GMPErr CopyFrame(const GMPVideoi420Frame& aVideoFrame) = 0;
+
+ // Swap Frame.
+ virtual void SwapFrame(GMPVideoi420Frame* aVideoFrame) = 0;
+
+ // Get pointer to buffer per plane.
+ virtual uint8_t* Buffer(GMPPlaneType aType) = 0;
+
+ // Overloading with const.
+ virtual const uint8_t* Buffer(GMPPlaneType aType) const = 0;
+
+ // Get allocated size per plane.
+ virtual int32_t AllocatedSize(GMPPlaneType aType) const = 0;
+
+ // Get allocated stride per plane.
+ virtual int32_t Stride(GMPPlaneType aType) const = 0;
+
+ // Set frame width.
+ virtual GMPErr SetWidth(int32_t aWidth) = 0;
+
+ // Set frame height.
+ virtual GMPErr SetHeight(int32_t aHeight) = 0;
+
+ // Get frame width.
+ virtual int32_t Width() const = 0;
+
+ // Get frame height.
+ virtual int32_t Height() const = 0;
+
+ // Set frame timestamp (microseconds)
+ virtual void SetTimestamp(uint64_t aTimestamp) = 0;
+
+ // Get frame timestamp (microseconds)
+ virtual uint64_t Timestamp() const = 0;
+
+ // Set frame duration (microseconds)
+ // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration()
+ // depending on rounding to avoid having to track roundoff errors
+ // and dropped/missing frames(!) (which may leave a large gap)
+ virtual void SetDuration(uint64_t aDuration) = 0;
+
+ // Get frame duration (microseconds)
+ virtual uint64_t Duration() const = 0;
+
+ // Return true if underlying plane buffers are of zero size, false if not.
+ virtual bool IsZeroSize() const = 0;
+
+ // Reset underlying plane buffers sizes to 0. This function doesn't clear memory.
+ virtual void ResetSize() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_I420_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-frame.h b/dom/media/gmp/gmp-api/gmp-video-frame.h
new file mode 100644
index 000000000..b3c9f53ab
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_h_
+#define GMP_VIDEO_FRAME_h_
+
+#include "gmp-video-plane.h"
+
+enum GMPVideoFrameFormat {
+ kGMPEncodedVideoFrame = 0,
+ kGMPI420VideoFrame = 1
+};
+
+class GMPVideoFrame {
+public:
+ virtual GMPVideoFrameFormat GetFrameFormat() = 0;
+ // MAIN THREAD ONLY IF OWNING PROCESS
+ virtual void Destroy() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-host.h b/dom/media/gmp/gmp-api/gmp-video-host.h
new file mode 100644
index 000000000..cf20e3f46
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-host.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_HOST_h_
+#define GMP_VIDEO_HOST_h_
+
+#include "gmp-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+
+// This interface must be called on the main thread only.
+class GMPVideoHost
+{
+public:
+ // Construct various video API objects. Host does not retain reference,
+ // caller is owner and responsible for deleting.
+ // MAIN THREAD ONLY
+ virtual GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) = 0;
+ virtual GMPErr CreatePlane(GMPPlane** aPlane) = 0;
+};
+
+#endif // GMP_VIDEO_HOST_h_
diff --git a/dom/media/gmp/gmp-api/gmp-video-plane.h b/dom/media/gmp/gmp-api/gmp-video-plane.h
new file mode 100644
index 000000000..777cc2495
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-plane.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_PLANE_h_
+#define GMP_VIDEO_PLANE_h_
+
+#include "gmp-errors.h"
+#include <stdint.h>
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPPlane {
+public:
+ // MAIN THREAD ONLY
+ // CreateEmptyPlane - set allocated size, actual plane size and stride:
+ // If current size is smaller than current size, then a buffer of sufficient
+ // size will be allocated.
+ virtual GMPErr CreateEmptyPlane(int32_t aAllocatedSize,
+ int32_t aStride,
+ int32_t aPlaneSize) = 0;
+
+ // MAIN THREAD ONLY
+ // Copy the entire plane data.
+ virtual GMPErr Copy(const GMPPlane& aPlane) = 0;
+
+ // MAIN THREAD ONLY
+ // Copy buffer: If current size is smaller
+ // than current size, then a buffer of sufficient size will be allocated.
+ virtual GMPErr Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer) = 0;
+
+ // Swap plane data.
+ virtual void Swap(GMPPlane& aPlane) = 0;
+
+ // Get allocated size.
+ virtual int32_t AllocatedSize() const = 0;
+
+ // Set actual size.
+ virtual void ResetSize() = 0;
+
+ // Return true is plane size is zero, false if not.
+ virtual bool IsZeroSize() const = 0;
+
+ // Get stride value.
+ virtual int32_t Stride() const = 0;
+
+ // Return data pointer.
+ virtual const uint8_t* Buffer() const = 0;
+
+ // Overloading with non-const.
+ virtual uint8_t* Buffer() = 0;
+
+ // MAIN THREAD ONLY IF OWNING PROCESS
+ // Call this when done with the object. This may delete it.
+ virtual void Destroy() = 0;
+};
+
+#endif // GMP_VIDEO_PLANE_h_
diff --git a/dom/media/gmp/moz.build b/dom/media/gmp/moz.build
new file mode 100644
index 000000000..3ff90b2c8
--- /dev/null
+++ b/dom/media/gmp/moz.build
@@ -0,0 +1,140 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = 'content_geckomediaplugins'
+
+XPIDL_SOURCES += [
+ 'mozIGeckoMediaPluginChromeService.idl',
+ 'mozIGeckoMediaPluginService.idl',
+]
+
+EXPORTS += [
+ 'gmp-api/gmp-async-shutdown.h',
+ 'gmp-api/gmp-audio-codec.h',
+ 'gmp-api/gmp-audio-decode.h',
+ 'gmp-api/gmp-audio-host.h',
+ 'gmp-api/gmp-audio-samples.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',
+ 'gmp-api/gmp-video-encode.h',
+ 'gmp-api/gmp-video-frame-encoded.h',
+ 'gmp-api/gmp-video-frame-i420.h',
+ 'gmp-api/gmp-video-frame.h',
+ 'gmp-api/gmp-video-host.h',
+ 'gmp-api/gmp-video-plane.h',
+ 'GMPAudioDecoderChild.h',
+ 'GMPAudioDecoderParent.h',
+ 'GMPAudioDecoderProxy.h',
+ 'GMPAudioHost.h',
+ 'GMPCallbackBase.h',
+ 'GMPChild.h',
+ 'GMPContentChild.h',
+ 'GMPContentParent.h',
+ 'GMPCrashHelperHolder.h',
+ 'GMPDecryptorChild.h',
+ 'GMPDecryptorParent.h',
+ 'GMPDecryptorProxy.h',
+ 'GMPEncryptedBufferDataImpl.h',
+ 'GMPLoader.h',
+ 'GMPMessageUtils.h',
+ 'GMPParent.h',
+ 'GMPPlatform.h',
+ 'GMPProcessChild.h',
+ 'GMPProcessParent.h',
+ 'GMPService.h',
+ 'GMPServiceChild.h',
+ 'GMPServiceParent.h',
+ 'GMPSharedMemManager.h',
+ 'GMPStorage.h',
+ 'GMPStorageChild.h',
+ 'GMPStorageParent.h',
+ 'GMPTimerChild.h',
+ 'GMPTimerParent.h',
+ 'GMPUtils.h',
+ 'GMPVideoDecoderChild.h',
+ 'GMPVideoDecoderParent.h',
+ 'GMPVideoDecoderProxy.h',
+ 'GMPVideoEncodedFrameImpl.h',
+ 'GMPVideoEncoderChild.h',
+ 'GMPVideoEncoderParent.h',
+ 'GMPVideoEncoderProxy.h',
+ 'GMPVideoHost.h',
+ 'GMPVideoi420FrameImpl.h',
+ 'GMPVideoPlaneImpl.h',
+]
+
+SOURCES += [
+ 'GMPAudioDecoderChild.cpp',
+ 'GMPAudioDecoderParent.cpp',
+ 'GMPAudioHost.cpp',
+ 'GMPChild.cpp',
+ 'GMPContentChild.cpp',
+ 'GMPContentParent.cpp',
+ 'GMPDecryptorChild.cpp',
+ 'GMPDecryptorParent.cpp',
+ 'GMPDiskStorage.cpp',
+ 'GMPEncryptedBufferDataImpl.cpp',
+ 'GMPMemoryStorage.cpp',
+ 'GMPParent.cpp',
+ 'GMPPlatform.cpp',
+ 'GMPProcessChild.cpp',
+ 'GMPProcessParent.cpp',
+ 'GMPService.cpp',
+ 'GMPServiceChild.cpp',
+ 'GMPServiceParent.cpp',
+ 'GMPSharedMemManager.cpp',
+ 'GMPStorageChild.cpp',
+ 'GMPStorageParent.cpp',
+ 'GMPTimerChild.cpp',
+ 'GMPTimerParent.cpp',
+ 'GMPUtils.cpp',
+ 'GMPVideoDecoderChild.cpp',
+ 'GMPVideoDecoderParent.cpp',
+ 'GMPVideoEncodedFrameImpl.cpp',
+ 'GMPVideoEncoderChild.cpp',
+ 'GMPVideoEncoderParent.cpp',
+ 'GMPVideoHost.cpp',
+ 'GMPVideoi420FrameImpl.cpp',
+ 'GMPVideoPlaneImpl.cpp',
+]
+
+DIRS += ['rlz']
+
+IPDL_SOURCES += [
+ 'GMPTypes.ipdlh',
+ 'PGMP.ipdl',
+ 'PGMPAudioDecoder.ipdl',
+ 'PGMPContent.ipdl',
+ 'PGMPDecryptor.ipdl',
+ 'PGMPService.ipdl',
+ 'PGMPStorage.ipdl',
+ 'PGMPTimer.ipdl',
+ 'PGMPVideoDecoder.ipdl',
+ 'PGMPVideoEncoder.ipdl',
+]
+
+if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
+ NO_VISIBILITY_FLAGS = True
+
+# comment this out to use Unsafe Shmem for more performance
+DEFINES['GMP_SAFE_SHMEM'] = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/xpcom/base',
+ '/xpcom/build',
+ '/xpcom/threads',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl
new file mode 100644
index 000000000..9e3286485
--- /dev/null
+++ b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl
@@ -0,0 +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 "nsISupports.idl"
+#include "nsIFile.idl"
+
+[scriptable, uuid(32d35d21-181f-4630-8caa-a431e2ebad72)]
+interface mozIGeckoMediaPluginChromeService : nsISupports
+{
+ /**
+ * Add a directory to scan for gecko media plugins.
+ * @note Main-thread API.
+ */
+ void addPluginDirectory(in AString directory);
+
+ /**
+ * Remove a directory for gecko media plugins.
+ * @note Main-thread API.
+ */
+ void removePluginDirectory(in AString directory);
+
+ /**
+ * Remove a directory for gecko media plugins and delete it from disk.
+ * If |defer| is true, wait until the plugin is unused before removing.
+ * @note Main-thread API.
+ */
+ void removeAndDeletePluginDirectory(in AString directory,
+ [optional] in bool defer);
+
+ /**
+ * Clears storage data associated with the site and the originAttributes
+ * pattern in JSON format.
+ */
+ void forgetThisSite(in AString site,
+ in DOMString aPattern);
+
+ /**
+ * Returns true if the given node id is allowed to store things
+ * persistently on disk. Private Browsing and local content are not
+ * allowed to store persistent data.
+ */
+ bool isPersistentStorageAllowed(in ACString nodeId);
+
+ /**
+ * Returns the directory to use as the base for storing data about GMPs.
+ */
+ nsIFile getStorageDir();
+
+};
diff --git a/dom/media/gmp/mozIGeckoMediaPluginService.idl b/dom/media/gmp/mozIGeckoMediaPluginService.idl
new file mode 100644
index 000000000..388c58142
--- /dev/null
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -0,0 +1,169 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIThread.idl"
+
+%{C++
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "nsStringGlue.h"
+class GMPAudioDecoderProxy;
+class GMPDecryptorProxy;
+class GMPVideoDecoderProxy;
+class GMPVideoEncoderProxy;
+class GMPVideoHost;
+class GMPCrashHelper;
+
+template<class T>
+class GMPGetterCallback
+{
+public:
+ GMPGetterCallback() { MOZ_COUNT_CTOR(GMPGetterCallback<T>); }
+ virtual ~GMPGetterCallback() { MOZ_COUNT_DTOR(GMPGetterCallback<T>); }
+ virtual void Done(T*) = 0;
+};
+template<class T>
+class GMPVideoGetterCallback
+{
+public:
+ GMPVideoGetterCallback() { MOZ_COUNT_CTOR(GMPVideoGetterCallback<T>); }
+ virtual ~GMPVideoGetterCallback() { MOZ_COUNT_DTOR(GMPVideoGetterCallback<T>); }
+ virtual void Done(T*, GMPVideoHost*) = 0;
+};
+typedef GMPGetterCallback<GMPDecryptorProxy> GetGMPDecryptorCallback;
+typedef GMPGetterCallback<GMPAudioDecoderProxy> GetGMPAudioDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoDecoderProxy> GetGMPVideoDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoEncoderProxy> GetGMPVideoEncoderCallback;
+class GetNodeIdCallback
+{
+public:
+ GetNodeIdCallback() { MOZ_COUNT_CTOR(GetNodeIdCallback); }
+ virtual ~GetNodeIdCallback() { MOZ_COUNT_DTOR(GetNodeIdCallback); }
+ virtual void Done(nsresult aResult, const nsACString& aNodeId) = 0;
+};
+%}
+
+[ptr] native TagArray(nsTArray<nsCString>);
+native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
+native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&);
+native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
+native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
+native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
+native GMPCrashHelperPtr(GMPCrashHelper*);
+
+[scriptable, uuid(44d362ae-937a-4803-bee6-f2512a0149d1)]
+interface mozIGeckoMediaPluginService : nsISupports
+{
+
+ /**
+ * The GMP thread. Callable from any thread.
+ */
+ readonly attribute nsIThread thread;
+
+ /**
+ * Run through windows registered registered for pluginId, sending
+ * 'PluginCrashed' chrome-only event
+ */
+ void RunPluginCrashCallbacks(in unsigned long pluginId, in ACString pluginName);
+
+ /**
+ * Get a plugin that supports the specified tags.
+ * Callable on any thread
+ */
+ [noscript]
+ boolean hasPluginForAPI(in ACString api, in TagArray tags);
+
+ /**
+ * Get a video decoder that supports the specified tags.
+ * The array of tags should at least contain a codec tag, and optionally
+ * other tags such as for EME keysystem.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPVideoDecoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ [optional] in ACString nodeId,
+ in GetGMPVideoDecoderCallback callback);
+
+ /**
+ * Gets a video decoder as per getGMPVideoDecoder, except it is linked to
+ * with a corresponding GMPDecryptor via the decryptor's ID.
+ * This is a temporary measure, until we can implement a Chromium CDM
+ * GMP protocol which does both decryption and decoding.
+ */
+ [noscript]
+ void getDecryptingGMPVideoDecoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ in ACString nodeId,
+ in GetGMPVideoDecoderCallback callback,
+ in uint32_t decryptorId);
+
+ /**
+ * Get a video encoder that supports the specified tags.
+ * The array of tags should at least contain a codec tag, and optionally
+ * other tags.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPVideoEncoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ [optional] in ACString nodeId,
+ in GetGMPVideoEncoderCallback callback);
+
+ /**
+ * Returns an audio decoder that supports the specified tags.
+ * The array of tags should at least contain a codec tag, and optionally
+ * other tags such as for EME keysystem.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPAudioDecoder(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ [optional] in ACString nodeId,
+ in GetGMPAudioDecoderCallback callback);
+
+ /**
+ * Returns a decryption session manager that supports the specified tags.
+ * The array of tags should at least contain a key system tag, and optionally
+ * other tags.
+ * Callable only on GMP thread.
+ * This is an asynchronous operation, the Done method of the callback object
+ * will be called on the GMP thread with the result (which might be null in
+ * the case of failure). This method always takes ownership of the callback
+ * object, but if this method returns an error then the Done method of the
+ * callback object will not be called at all.
+ */
+ [noscript]
+ void getGMPDecryptor(in GMPCrashHelperPtr helper,
+ in TagArray tags,
+ in ACString nodeId,
+ in GetGMPDecryptorCallback callback);
+
+ /**
+ * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
+ */
+ [noscript]
+ void getNodeId(in AString origin,
+ in AString topLevelOrigin,
+ in AString gmpName,
+ in bool inPrivateBrowsingMode,
+ in GetNodeIdCallback callback);
+};
diff --git a/dom/media/gmp/rlz/COPYING b/dom/media/gmp/rlz/COPYING
new file mode 100644
index 000000000..b89042ace
--- /dev/null
+++ b/dom/media/gmp/rlz/COPYING
@@ -0,0 +1,14 @@
+Copyright 2010 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.cpp b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
new file mode 100644
index 000000000..0871d2e4e
--- /dev/null
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; 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 "GMPDeviceBinding.h"
+#include "mozilla/Attributes.h"
+#include "prenv.h"
+
+#include <string>
+
+#ifdef XP_WIN
+#include "windows.h"
+#endif
+
+#if defined(HASH_NODE_ID_WITH_DEVICE_ID)
+
+// In order to provide EME plugins with a "device binding" capability,
+// in the parent we generate and store some random bytes as salt for every
+// (origin, urlBarOrigin) pair that uses EME. We store these bytes so
+// that every time we revisit the same origin we get the same salt.
+// We send this salt to the child on startup. The child collects some
+// device specific data and munges that with the salt to create the
+// "node id" that we expose to EME plugins. It then overwrites the device
+// specific data, and activates the sandbox.
+
+#include "rlz/lib/machine_id.h"
+#include "rlz/lib/string_utils.h"
+#include "sha256.h"
+
+#ifdef XP_WIN
+#include "windows.h"
+#endif
+
+
+#endif // HASH_NODE_ID_WITH_DEVICE_ID
+
+namespace mozilla {
+namespace gmp {
+
+#if defined(XP_WIN) && defined(HASH_NODE_ID_WITH_DEVICE_ID)
+MOZ_NEVER_INLINE
+static bool
+GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
+{
+ // "Top" of the free space on the stack is directly after the memory
+ // holding our return address.
+ uint8_t* top = (uint8_t*)_AddressOfReturnAddress();
+
+ // Look down the stack until we find the guard page...
+ MEMORY_BASIC_INFORMATION memInfo = {0};
+ uint8_t* bottom = top;
+ while (1) {
+ if (!VirtualQuery(bottom, &memInfo, sizeof(memInfo))) {
+ return false;
+ }
+ if ((memInfo.Protect & PAGE_GUARD) == PAGE_GUARD) {
+ bottom = (uint8_t*)memInfo.BaseAddress + memInfo.RegionSize;
+#ifdef DEBUG
+ if (!VirtualQuery(bottom, &memInfo, sizeof(memInfo))) {
+ return false;
+ }
+ assert(!(memInfo.Protect & PAGE_GUARD)); // Should have found boundary.
+#endif
+ break;
+ } else if (memInfo.State != MEM_COMMIT ||
+ (memInfo.AllocationProtect & PAGE_READWRITE) != PAGE_READWRITE) {
+ return false;
+ }
+ bottom = (uint8_t*)memInfo.BaseAddress - 1;
+ }
+ *aOutTop = top;
+ *aOutBottom = bottom;
+ return true;
+}
+#endif
+
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+static void SecureMemset(void* start, uint8_t value, size_t size)
+{
+ // Inline instructions equivalent to RtlSecureZeroMemory().
+ for (size_t i = 0; i < size; ++i) {
+ volatile uint8_t* p = static_cast<volatile uint8_t*>(start) + i;
+ *p = value;
+ }
+}
+#endif
+
+bool
+CalculateGMPDeviceId(char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ std::string& aOutNodeId)
+{
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+ if (aOriginSaltLen > 0) {
+ std::vector<uint8_t> deviceId;
+ int volumeId;
+ if (!rlz_lib::GetRawMachineId(&deviceId, &volumeId)) {
+ return false;
+ }
+
+ SHA256Context ctx;
+ SHA256_Begin(&ctx);
+ SHA256_Update(&ctx, (const uint8_t*)aOriginSalt, aOriginSaltLen);
+ SHA256_Update(&ctx, deviceId.data(), deviceId.size());
+ SHA256_Update(&ctx, (const uint8_t*)&volumeId, sizeof(int));
+ uint8_t digest[SHA256_LENGTH] = {0};
+ unsigned int digestLen = 0;
+ SHA256_End(&ctx, digest, &digestLen, SHA256_LENGTH);
+
+ // Overwrite all data involved in calculation as it could potentially
+ // identify the user, so there's no chance a GMP can read it and use
+ // it for identity tracking.
+ SecureMemset(&ctx, 0, sizeof(ctx));
+ SecureMemset(aOriginSalt, 0, aOriginSaltLen);
+ SecureMemset(&volumeId, 0, sizeof(volumeId));
+ SecureMemset(deviceId.data(), '*', deviceId.size());
+ deviceId.clear();
+
+ if (!rlz_lib::BytesToString(digest, SHA256_LENGTH, &aOutNodeId)) {
+ return false;
+ }
+
+ if (!PR_GetEnv("MOZ_GMP_DISABLE_NODE_ID_CLEANUP")) {
+ // We've successfully bound the origin salt to node id.
+ // rlz_lib::GetRawMachineId and/or the system functions it
+ // called could have left user identifiable data on the stack,
+ // so carefully zero the stack down to the guard page.
+ uint8_t* top;
+ uint8_t* bottom;
+ if (!GetStackAfterCurrentFrame(&top, &bottom)) {
+ return false;
+ }
+ assert(top >= bottom);
+ // Inline instructions equivalent to RtlSecureZeroMemory().
+ // We can't just use RtlSecureZeroMemory here directly, as in debug
+ // builds, RtlSecureZeroMemory() can't be inlined, and the stack
+ // memory it uses would get wiped by itself running, causing crashes.
+ for (volatile uint8_t* p = (volatile uint8_t*)bottom; p < top; p++) {
+ *p = 0;
+ }
+ }
+ } else
+#endif
+ {
+ aOutNodeId = std::string(aOriginSalt, aOriginSalt + aOriginSaltLen);
+ }
+ return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.h b/dom/media/gmp/rlz/GMPDeviceBinding.h
new file mode 100644
index 000000000..ee1664466
--- /dev/null
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; 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_DEVICE_BINDING_h_
+#define GMP_DEVICE_BINDING_h_
+
+#include <string>
+
+namespace mozilla {
+namespace gmp {
+
+bool CalculateGMPDeviceId(char* aOriginSalt,
+ uint32_t aOriginSaltLen,
+ std::string& aOutNodeId);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMP_DEVICE_BINDING_h_
diff --git a/dom/media/gmp/rlz/README.mozilla b/dom/media/gmp/rlz/README.mozilla
new file mode 100644
index 000000000..fffc5deb5
--- /dev/null
+++ b/dom/media/gmp/rlz/README.mozilla
@@ -0,0 +1,6 @@
+Code taken from rlz project: https://code.google.com/p/rlz/
+
+Revision: 134, then with unused code stripped out.
+
+Note: base/ contains wrappers/dummies to provide implementations of the
+Chromium APIs that this code relies upon.
diff --git a/dom/media/gmp/rlz/lib/assert.h b/dom/media/gmp/rlz/lib/assert.h
new file mode 100644
index 000000000..68737b1e2
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/assert.h
@@ -0,0 +1,14 @@
+/* -*- 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 FAKE_ASSERT_H_
+#define FAKE_ASSERT_H_
+
+#include <assert.h>
+
+#define ASSERT_STRING(x) { assert(false); }
+#define VERIFY(x) { assert(x); };
+
+#endif
diff --git a/dom/media/gmp/rlz/lib/machine_id.h b/dom/media/gmp/rlz/lib/machine_id.h
new file mode 100644
index 000000000..67661cfce
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/machine_id.h
@@ -0,0 +1,19 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+
+#ifndef RLZ_LIB_MACHINE_ID_H_
+#define RLZ_LIB_MACHINE_ID_H_
+
+#include <vector>
+
+namespace rlz_lib {
+
+// Retrieves a raw machine identifier string and a machine-specific
+// 4 byte value. GetMachineId() will SHA1 |data|, append |more_data|, compute
+// the Crc8 of that, and return a hex-encoded string of that data.
+bool GetRawMachineId(std::vector<uint8_t>* data, int* more_data);
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_MACHINE_ID_H_
diff --git a/dom/media/gmp/rlz/lib/string_utils.cc b/dom/media/gmp/rlz/lib/string_utils.cc
new file mode 100644
index 000000000..b6a71acdb
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/string_utils.cc
@@ -0,0 +1,34 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+//
+// String manipulation functions used in the RLZ library.
+
+#include "rlz/lib/string_utils.h"
+
+namespace rlz_lib {
+
+bool BytesToString(const unsigned char* data,
+ int data_len,
+ std::string* string) {
+ if (!string)
+ return false;
+
+ string->clear();
+ if (data_len < 1 || !data)
+ return false;
+
+ static const char kHex[] = "0123456789ABCDEF";
+
+ // Fix the buffer size to begin with to avoid repeated re-allocation.
+ string->resize(data_len * 2);
+ int index = data_len;
+ while (index--) {
+ string->at(2 * index) = kHex[data[index] >> 4]; // high digit
+ string->at(2 * index + 1) = kHex[data[index] & 0x0F]; // low digit
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/rlz/lib/string_utils.h b/dom/media/gmp/rlz/lib/string_utils.h
new file mode 100644
index 000000000..294f1b2d9
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/string_utils.h
@@ -0,0 +1,20 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+//
+// String manipulation functions used in the RLZ library.
+
+#ifndef RLZ_LIB_STRING_UTILS_H_
+#define RLZ_LIB_STRING_UTILS_H_
+
+#include <string>
+
+namespace rlz_lib {
+
+bool BytesToString(const unsigned char* data,
+ int data_len,
+ std::string* string);
+
+}; // namespace
+
+#endif // RLZ_LIB_STRING_UTILS_H_
diff --git a/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc
new file mode 100644
index 000000000..2bea0f55f
--- /dev/null
+++ b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc
@@ -0,0 +1,320 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/network/IOEthernetController.h>
+#include <IOKit/network/IOEthernetInterface.h>
+#include <IOKit/network/IONetworkInterface.h>
+#include <vector>
+#include <string>
+
+// Note: The original machine_id_mac.cc code is in namespace rlz_lib below.
+// It depends on some external files, which would bring in a log of Chromium
+// code if imported as well.
+// Instead only the necessary code has been extracted from the relevant files,
+// and further combined and reduced to limit the maintenance burden.
+
+// [Extracted from base/logging.h]
+#define DCHECK assert
+
+namespace base {
+
+// [Extracted from base/mac/scoped_typeref.h and base/mac/scoped_cftyperef.h]
+template<typename T>
+class ScopedCFTypeRef {
+ public:
+ typedef T element_type;
+
+ explicit ScopedCFTypeRef(T object)
+ : object_(object) {
+ }
+
+ ScopedCFTypeRef(const ScopedCFTypeRef<T>& that) = delete;
+ ScopedCFTypeRef(ScopedCFTypeRef<T>&& that) = delete;
+
+ ~ScopedCFTypeRef() {
+ if (object_)
+ CFRelease(object_);
+ }
+
+ ScopedCFTypeRef& operator=(const ScopedCFTypeRef<T>& that) = delete;
+ ScopedCFTypeRef& operator=(ScopedCFTypeRef<T>&& that) = delete;
+
+ operator T() const {
+ return object_;
+ }
+
+ // ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT
+ // a wrapper for CFRelease().
+ T release() {
+ T temp = object_;
+ object_ = NULL;
+ return temp;
+ }
+
+ private:
+ T object_;
+};
+
+namespace mac {
+
+// [Extracted from base/mac/scoped_ioobject.h]
+// Just like ScopedCFTypeRef but for io_object_t and subclasses.
+template<typename IOT>
+class ScopedIOObject {
+ public:
+ typedef IOT element_type;
+
+ explicit ScopedIOObject(IOT object = IO_OBJECT_NULL)
+ : object_(object) {
+ }
+
+ ~ScopedIOObject() {
+ if (object_)
+ IOObjectRelease(object_);
+ }
+
+ ScopedIOObject(const ScopedIOObject&) = delete;
+ void operator=(const ScopedIOObject&) = delete;
+
+ void reset(IOT object = IO_OBJECT_NULL) {
+ if (object_)
+ IOObjectRelease(object_);
+ object_ = object;
+ }
+
+ operator IOT() const {
+ return object_;
+ }
+
+ private:
+ IOT object_;
+};
+
+// [Extracted from base/mac/foundation_util.h]
+template<typename T>
+T CFCast(const CFTypeRef& cf_val);
+
+template<>
+CFDataRef
+CFCast<CFDataRef>(const CFTypeRef& cf_val) {
+ if (cf_val == NULL) {
+ return NULL;
+ }
+ if (CFGetTypeID(cf_val) == CFDataGetTypeID()) {
+ return (CFDataRef)(cf_val);
+ }
+ return NULL;
+}
+
+template<>
+CFStringRef
+CFCast<CFStringRef>(const CFTypeRef& cf_val) {
+ if (cf_val == NULL) {
+ return NULL;
+ }
+ if (CFGetTypeID(cf_val) == CFStringGetTypeID()) {
+ return (CFStringRef)(cf_val);
+ }
+ return NULL;
+}
+
+} // namespace mac
+
+// [Extracted from base/strings/sys_string_conversions_mac.mm]
+static const CFStringEncoding kNarrowStringEncoding = kCFStringEncodingUTF8;
+
+template<typename StringType>
+static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring,
+ CFStringEncoding encoding) {
+ CFIndex length = CFStringGetLength(cfstring);
+ if (length == 0)
+ return StringType();
+
+ CFRange whole_string = CFRangeMake(0, length);
+ CFIndex out_size;
+ CFIndex converted = CFStringGetBytes(cfstring,
+ whole_string,
+ encoding,
+ 0, // lossByte
+ false, // isExternalRepresentation
+ NULL, // buffer
+ 0, // maxBufLen
+ &out_size);
+ if (converted == 0 || out_size == 0)
+ return StringType();
+
+ // out_size is the number of UInt8-sized units needed in the destination.
+ // A buffer allocated as UInt8 units might not be properly aligned to
+ // contain elements of StringType::value_type. Use a container for the
+ // proper value_type, and convert out_size by figuring the number of
+ // value_type elements per UInt8. Leave room for a NUL terminator.
+ typename StringType::size_type elements =
+ out_size * sizeof(UInt8) / sizeof(typename StringType::value_type) + 1;
+
+ std::vector<typename StringType::value_type> out_buffer(elements);
+ converted = CFStringGetBytes(cfstring,
+ whole_string,
+ encoding,
+ 0, // lossByte
+ false, // isExternalRepresentation
+ reinterpret_cast<UInt8*>(&out_buffer[0]),
+ out_size,
+ NULL); // usedBufLen
+ if (converted == 0)
+ return StringType();
+
+ out_buffer[elements - 1] = '\0';
+ return StringType(&out_buffer[0], elements - 1);
+}
+
+std::string SysCFStringRefToUTF8(CFStringRef ref)
+{
+ return CFStringToSTLStringWithEncodingT<std::string>(ref,
+ kNarrowStringEncoding);
+}
+
+} // namespace base
+
+
+namespace rlz_lib {
+
+namespace {
+
+// See http://developer.apple.com/library/mac/#technotes/tn1103/_index.html
+
+// The caller is responsible for freeing |matching_services|.
+bool FindEthernetInterfaces(io_iterator_t* matching_services) {
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> matching_dict(
+ IOServiceMatching(kIOEthernetInterfaceClass));
+ if (!matching_dict)
+ return false;
+
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> primary_interface(
+ CFDictionaryCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ if (!primary_interface)
+ return false;
+
+ CFDictionarySetValue(
+ primary_interface, CFSTR(kIOPrimaryInterface), kCFBooleanTrue);
+ CFDictionarySetValue(
+ matching_dict, CFSTR(kIOPropertyMatchKey), primary_interface);
+
+ kern_return_t kern_result = IOServiceGetMatchingServices(
+ kIOMasterPortDefault, matching_dict.release(), matching_services);
+
+ return kern_result == KERN_SUCCESS;
+}
+
+bool GetMACAddressFromIterator(io_iterator_t primary_interface_iterator,
+ uint8_t* buffer, size_t buffer_size) {
+ if (buffer_size < kIOEthernetAddressSize)
+ return false;
+
+ bool success = false;
+
+ bzero(buffer, buffer_size);
+ base::mac::ScopedIOObject<io_object_t> primary_interface;
+ while (primary_interface.reset(IOIteratorNext(primary_interface_iterator)),
+ primary_interface) {
+ io_object_t primary_interface_parent;
+ kern_return_t kern_result = IORegistryEntryGetParentEntry(
+ primary_interface, kIOServicePlane, &primary_interface_parent);
+ base::mac::ScopedIOObject<io_object_t> primary_interface_parent_deleter(
+ primary_interface_parent);
+ success = kern_result == KERN_SUCCESS;
+
+ if (!success)
+ continue;
+
+ base::ScopedCFTypeRef<CFTypeRef> mac_data(
+ IORegistryEntryCreateCFProperty(primary_interface_parent,
+ CFSTR(kIOMACAddress),
+ kCFAllocatorDefault,
+ 0));
+ CFDataRef mac_data_data = base::mac::CFCast<CFDataRef>(mac_data);
+ if (mac_data_data) {
+ CFDataGetBytes(
+ mac_data_data, CFRangeMake(0, kIOEthernetAddressSize), buffer);
+ }
+ }
+
+ return success;
+}
+
+bool GetMacAddress(unsigned char* buffer, size_t size) {
+ io_iterator_t primary_interface_iterator;
+ if (!FindEthernetInterfaces(&primary_interface_iterator))
+ return false;
+ bool result = GetMACAddressFromIterator(
+ primary_interface_iterator, buffer, size);
+ IOObjectRelease(primary_interface_iterator);
+ return result;
+}
+
+CFStringRef CopySerialNumber() {
+ base::mac::ScopedIOObject<io_service_t> expert_device(
+ IOServiceGetMatchingService(kIOMasterPortDefault,
+ IOServiceMatching("IOPlatformExpertDevice")));
+ if (!expert_device)
+ return NULL;
+
+ base::ScopedCFTypeRef<CFTypeRef> serial_number(
+ IORegistryEntryCreateCFProperty(expert_device,
+ CFSTR(kIOPlatformSerialNumberKey),
+ kCFAllocatorDefault,
+ 0));
+ CFStringRef serial_number_cfstring =
+ base::mac::CFCast<CFStringRef>(serial_number.release());
+ if (!serial_number_cfstring)
+ return NULL;
+
+ return serial_number_cfstring;
+}
+
+} // namespace
+
+bool GetRawMachineId(std::vector<uint8_t>* data, int* more_data) {
+ uint8_t mac_address[kIOEthernetAddressSize];
+
+ std::string id;
+ if (GetMacAddress(mac_address, sizeof(mac_address))) {
+ id += "mac:";
+ static const char hex[] =
+ { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ for (int i = 0; i < kIOEthernetAddressSize; ++i) {
+ uint8_t byte = mac_address[i];
+ id += hex[byte >> 4];
+ id += hex[byte & 0xF];
+ }
+ }
+
+ // A MAC address is enough to uniquely identify a machine, but it's only 6
+ // bytes, 3 of which are manufacturer-determined. To make brute-forcing the
+ // SHA1 of this harder, also append the system's serial number.
+ CFStringRef serial = CopySerialNumber();
+ if (serial) {
+ if (!id.empty()) {
+ id += ' ';
+ }
+ id += "serial:";
+ id += base::SysCFStringRefToUTF8(serial);
+ CFRelease(serial);
+ }
+
+ // Get the contents of the string 'id' as a bunch of bytes.
+ data->assign(&id[0], &id[id.size()]);
+
+ // On windows, this is set to the volume id. Since it's not scrambled before
+ // being sent, just set it to 1.
+ *more_data = 1;
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/rlz/moz.build b/dom/media/gmp/rlz/moz.build
new file mode 100644
index 000000000..aa6f5fece
--- /dev/null
+++ b/dom/media/gmp/rlz/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Note: build rlz in its own moz.build, so it doesn't pickup any of
+# Chromium IPC's headers used in the moz.build of the parent file.
+
+Library('rlz')
+
+UNIFIED_SOURCES += [
+ 'GMPDeviceBinding.cpp',
+]
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'win/lib/machine_id_win.cc',
+ ]
+
+if CONFIG['OS_TARGET'] == 'Darwin':
+ UNIFIED_SOURCES += [
+ 'mac/lib/machine_id_mac.cc',
+ ]
+ OS_LIBS += [
+ '-framework IOKit',
+ ]
+
+LOCAL_INCLUDES += [
+ '..',
+]
+
+EXPORTS += [
+ 'GMPDeviceBinding.h',
+]
diff --git a/dom/media/gmp/rlz/sha256.c b/dom/media/gmp/rlz/sha256.c
new file mode 100644
index 000000000..072de5195
--- /dev/null
+++ b/dom/media/gmp/rlz/sha256.c
@@ -0,0 +1,469 @@
+/* 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/. */
+
+// Stripped down version of security/nss/lib/freebl/sha512.c
+// and related headers.
+
+#include "sha256.h"
+#include "string.h"
+#include "prcpucfg.h"
+#if defined(NSS_X86) || defined(SHA_NO_LONG_LONG)
+#define NOUNROLL512 1
+#undef HAVE_LONG_LONG
+#endif
+
+#define SHA256_BLOCK_LENGTH 64 /* bytes */
+
+typedef enum _SECStatus {
+ SECWouldBlock = -2,
+ SECFailure = -1,
+ SECSuccess = 0
+} SECStatus;
+
+/* ============= Common constants and defines ======================= */
+
+#define W ctx->u.w
+#define B ctx->u.b
+#define H ctx->h
+
+#define SHR(x,n) (x >> n)
+#define SHL(x,n) (x << n)
+#define Ch(x,y,z) ((x & y) ^ (~x & z))
+#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z))
+#define SHA_MIN(a,b) (a < b ? a : b)
+
+/* Padding used with all flavors of SHA */
+static const PRUint8 pad[240] = {
+0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+ /* compiler will fill the rest in with zeros */
+};
+
+/* ============= SHA256 implementation ================================== */
+
+/* SHA-256 constants, K256. */
+static const PRUint32 K256[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+/* SHA-256 initial hash values */
+static const PRUint32 H256[8] = {
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+};
+
+#if defined(_MSC_VER)
+#include <stdlib.h>
+#pragma intrinsic(_byteswap_ulong)
+#define SHA_HTONL(x) _byteswap_ulong(x)
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+#elif defined(__GNUC__) && defined(NSS_X86_OR_X64)
+static __inline__ PRUint32 swap4b(PRUint32 value)
+{
+ __asm__("bswap %0" : "+r" (value));
+ return (value);
+}
+#define SHA_HTONL(x) swap4b(x)
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+
+#elif defined(__GNUC__) && (defined(__thumb2__) || \
+ (!defined(__thumb__) && \
+ (defined(__ARM_ARCH_6__) || \
+ defined(__ARM_ARCH_6J__) || \
+ defined(__ARM_ARCH_6K__) || \
+ defined(__ARM_ARCH_6Z__) || \
+ defined(__ARM_ARCH_6ZK__) || \
+ defined(__ARM_ARCH_6T2__) || \
+ defined(__ARM_ARCH_7__) || \
+ defined(__ARM_ARCH_7A__) || \
+ defined(__ARM_ARCH_7R__))))
+static __inline__ PRUint32 swap4b(PRUint32 value)
+{
+ PRUint32 ret;
+ __asm__("rev %0, %1" : "=r" (ret) : "r"(value));
+ return ret;
+}
+#define SHA_HTONL(x) swap4b(x)
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+
+#else
+#define SWAP4MASK 0x00FF00FF
+#define SHA_HTONL(x) (t1 = (x), t1 = (t1 << 16) | (t1 >> 16), \
+ ((t1 & SWAP4MASK) << 8) | ((t1 >> 8) & SWAP4MASK))
+#define BYTESWAP4(x) x = SHA_HTONL(x)
+#endif
+
+#if defined(_MSC_VER)
+#pragma intrinsic (_lrotr, _lrotl)
+#define ROTR32(x,n) _lrotr(x,n)
+#define ROTL32(x,n) _lrotl(x,n)
+#else
+#define ROTR32(x,n) ((x >> n) | (x << ((8 * sizeof x) - n)))
+#define ROTL32(x,n) ((x << n) | (x >> ((8 * sizeof x) - n)))
+#endif
+
+/* Capitol Sigma and lower case sigma functions */
+#define S0(x) (ROTR32(x, 2) ^ ROTR32(x,13) ^ ROTR32(x,22))
+#define S1(x) (ROTR32(x, 6) ^ ROTR32(x,11) ^ ROTR32(x,25))
+#define s0(x) (t1 = x, ROTR32(t1, 7) ^ ROTR32(t1,18) ^ SHR(t1, 3))
+#define s1(x) (t2 = x, ROTR32(t2,17) ^ ROTR32(t2,19) ^ SHR(t2,10))
+
+void
+SHA256_Begin(SHA256Context *ctx)
+{
+ memset(ctx, 0, sizeof *ctx);
+ memcpy(H, H256, sizeof H256);
+}
+
+static void
+SHA256_Compress(SHA256Context *ctx)
+{
+ {
+ register PRUint32 t1, t2;
+
+#if defined(IS_LITTLE_ENDIAN)
+ BYTESWAP4(W[0]);
+ BYTESWAP4(W[1]);
+ BYTESWAP4(W[2]);
+ BYTESWAP4(W[3]);
+ BYTESWAP4(W[4]);
+ BYTESWAP4(W[5]);
+ BYTESWAP4(W[6]);
+ BYTESWAP4(W[7]);
+ BYTESWAP4(W[8]);
+ BYTESWAP4(W[9]);
+ BYTESWAP4(W[10]);
+ BYTESWAP4(W[11]);
+ BYTESWAP4(W[12]);
+ BYTESWAP4(W[13]);
+ BYTESWAP4(W[14]);
+ BYTESWAP4(W[15]);
+#endif
+
+#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16])
+
+ /* prepare the "message schedule" */
+#ifdef NOUNROLL256
+ {
+ int t;
+ for (t = 16; t < 64; ++t) {
+ INITW(t);
+ }
+ }
+#else
+ INITW(16);
+ INITW(17);
+ INITW(18);
+ INITW(19);
+
+ INITW(20);
+ INITW(21);
+ INITW(22);
+ INITW(23);
+ INITW(24);
+ INITW(25);
+ INITW(26);
+ INITW(27);
+ INITW(28);
+ INITW(29);
+
+ INITW(30);
+ INITW(31);
+ INITW(32);
+ INITW(33);
+ INITW(34);
+ INITW(35);
+ INITW(36);
+ INITW(37);
+ INITW(38);
+ INITW(39);
+
+ INITW(40);
+ INITW(41);
+ INITW(42);
+ INITW(43);
+ INITW(44);
+ INITW(45);
+ INITW(46);
+ INITW(47);
+ INITW(48);
+ INITW(49);
+
+ INITW(50);
+ INITW(51);
+ INITW(52);
+ INITW(53);
+ INITW(54);
+ INITW(55);
+ INITW(56);
+ INITW(57);
+ INITW(58);
+ INITW(59);
+
+ INITW(60);
+ INITW(61);
+ INITW(62);
+ INITW(63);
+
+#endif
+#undef INITW
+ }
+ {
+ PRUint32 a, b, c, d, e, f, g, h;
+
+ a = H[0];
+ b = H[1];
+ c = H[2];
+ d = H[3];
+ e = H[4];
+ f = H[5];
+ g = H[6];
+ h = H[7];
+
+#define ROUND(n,a,b,c,d,e,f,g,h) \
+ h += S1(e) + Ch(e,f,g) + K256[n] + W[n]; \
+ d += h; \
+ h += S0(a) + Maj(a,b,c);
+
+#ifdef NOUNROLL256
+ {
+ int t;
+ for (t = 0; t < 64; t+= 8) {
+ ROUND(t+0,a,b,c,d,e,f,g,h)
+ ROUND(t+1,h,a,b,c,d,e,f,g)
+ ROUND(t+2,g,h,a,b,c,d,e,f)
+ ROUND(t+3,f,g,h,a,b,c,d,e)
+ ROUND(t+4,e,f,g,h,a,b,c,d)
+ ROUND(t+5,d,e,f,g,h,a,b,c)
+ ROUND(t+6,c,d,e,f,g,h,a,b)
+ ROUND(t+7,b,c,d,e,f,g,h,a)
+ }
+ }
+#else
+ ROUND( 0,a,b,c,d,e,f,g,h)
+ ROUND( 1,h,a,b,c,d,e,f,g)
+ ROUND( 2,g,h,a,b,c,d,e,f)
+ ROUND( 3,f,g,h,a,b,c,d,e)
+ ROUND( 4,e,f,g,h,a,b,c,d)
+ ROUND( 5,d,e,f,g,h,a,b,c)
+ ROUND( 6,c,d,e,f,g,h,a,b)
+ ROUND( 7,b,c,d,e,f,g,h,a)
+
+ ROUND( 8,a,b,c,d,e,f,g,h)
+ ROUND( 9,h,a,b,c,d,e,f,g)
+ ROUND(10,g,h,a,b,c,d,e,f)
+ ROUND(11,f,g,h,a,b,c,d,e)
+ ROUND(12,e,f,g,h,a,b,c,d)
+ ROUND(13,d,e,f,g,h,a,b,c)
+ ROUND(14,c,d,e,f,g,h,a,b)
+ ROUND(15,b,c,d,e,f,g,h,a)
+
+ ROUND(16,a,b,c,d,e,f,g,h)
+ ROUND(17,h,a,b,c,d,e,f,g)
+ ROUND(18,g,h,a,b,c,d,e,f)
+ ROUND(19,f,g,h,a,b,c,d,e)
+ ROUND(20,e,f,g,h,a,b,c,d)
+ ROUND(21,d,e,f,g,h,a,b,c)
+ ROUND(22,c,d,e,f,g,h,a,b)
+ ROUND(23,b,c,d,e,f,g,h,a)
+
+ ROUND(24,a,b,c,d,e,f,g,h)
+ ROUND(25,h,a,b,c,d,e,f,g)
+ ROUND(26,g,h,a,b,c,d,e,f)
+ ROUND(27,f,g,h,a,b,c,d,e)
+ ROUND(28,e,f,g,h,a,b,c,d)
+ ROUND(29,d,e,f,g,h,a,b,c)
+ ROUND(30,c,d,e,f,g,h,a,b)
+ ROUND(31,b,c,d,e,f,g,h,a)
+
+ ROUND(32,a,b,c,d,e,f,g,h)
+ ROUND(33,h,a,b,c,d,e,f,g)
+ ROUND(34,g,h,a,b,c,d,e,f)
+ ROUND(35,f,g,h,a,b,c,d,e)
+ ROUND(36,e,f,g,h,a,b,c,d)
+ ROUND(37,d,e,f,g,h,a,b,c)
+ ROUND(38,c,d,e,f,g,h,a,b)
+ ROUND(39,b,c,d,e,f,g,h,a)
+
+ ROUND(40,a,b,c,d,e,f,g,h)
+ ROUND(41,h,a,b,c,d,e,f,g)
+ ROUND(42,g,h,a,b,c,d,e,f)
+ ROUND(43,f,g,h,a,b,c,d,e)
+ ROUND(44,e,f,g,h,a,b,c,d)
+ ROUND(45,d,e,f,g,h,a,b,c)
+ ROUND(46,c,d,e,f,g,h,a,b)
+ ROUND(47,b,c,d,e,f,g,h,a)
+
+ ROUND(48,a,b,c,d,e,f,g,h)
+ ROUND(49,h,a,b,c,d,e,f,g)
+ ROUND(50,g,h,a,b,c,d,e,f)
+ ROUND(51,f,g,h,a,b,c,d,e)
+ ROUND(52,e,f,g,h,a,b,c,d)
+ ROUND(53,d,e,f,g,h,a,b,c)
+ ROUND(54,c,d,e,f,g,h,a,b)
+ ROUND(55,b,c,d,e,f,g,h,a)
+
+ ROUND(56,a,b,c,d,e,f,g,h)
+ ROUND(57,h,a,b,c,d,e,f,g)
+ ROUND(58,g,h,a,b,c,d,e,f)
+ ROUND(59,f,g,h,a,b,c,d,e)
+ ROUND(60,e,f,g,h,a,b,c,d)
+ ROUND(61,d,e,f,g,h,a,b,c)
+ ROUND(62,c,d,e,f,g,h,a,b)
+ ROUND(63,b,c,d,e,f,g,h,a)
+#endif
+
+ H[0] += a;
+ H[1] += b;
+ H[2] += c;
+ H[3] += d;
+ H[4] += e;
+ H[5] += f;
+ H[6] += g;
+ H[7] += h;
+ }
+#undef ROUND
+}
+
+#undef s0
+#undef s1
+#undef S0
+#undef S1
+
+void
+SHA256_Update(SHA256Context *ctx, const unsigned char *input,
+ unsigned int inputLen)
+{
+ unsigned int inBuf = ctx->sizeLo & 0x3f;
+ if (!inputLen)
+ return;
+
+ /* Add inputLen into the count of bytes processed, before processing */
+ if ((ctx->sizeLo += inputLen) < inputLen)
+ ctx->sizeHi++;
+
+ /* if data already in buffer, attemp to fill rest of buffer */
+ if (inBuf) {
+ unsigned int todo = SHA256_BLOCK_LENGTH - inBuf;
+ if (inputLen < todo)
+ todo = inputLen;
+ memcpy(B + inBuf, input, todo);
+ input += todo;
+ inputLen -= todo;
+ if (inBuf + todo == SHA256_BLOCK_LENGTH)
+ SHA256_Compress(ctx);
+ }
+
+ /* if enough data to fill one or more whole buffers, process them. */
+ while (inputLen >= SHA256_BLOCK_LENGTH) {
+ memcpy(B, input, SHA256_BLOCK_LENGTH);
+ input += SHA256_BLOCK_LENGTH;
+ inputLen -= SHA256_BLOCK_LENGTH;
+ SHA256_Compress(ctx);
+ }
+ /* if data left over, fill it into buffer */
+ if (inputLen)
+ memcpy(B, input, inputLen);
+}
+
+void
+SHA256_End(SHA256Context *ctx, unsigned char *digest,
+ unsigned int *digestLen, unsigned int maxDigestLen)
+{
+ unsigned int inBuf = ctx->sizeLo & 0x3f;
+ unsigned int padLen = (inBuf < 56) ? (56 - inBuf) : (56 + 64 - inBuf);
+ PRUint32 hi, lo;
+#ifdef SWAP4MASK
+ PRUint32 t1;
+#endif
+
+ hi = (ctx->sizeHi << 3) | (ctx->sizeLo >> 29);
+ lo = (ctx->sizeLo << 3);
+
+ SHA256_Update(ctx, pad, padLen);
+
+#if defined(IS_LITTLE_ENDIAN)
+ W[14] = SHA_HTONL(hi);
+ W[15] = SHA_HTONL(lo);
+#else
+ W[14] = hi;
+ W[15] = lo;
+#endif
+ SHA256_Compress(ctx);
+
+ /* now output the answer */
+#if defined(IS_LITTLE_ENDIAN)
+ BYTESWAP4(H[0]);
+ BYTESWAP4(H[1]);
+ BYTESWAP4(H[2]);
+ BYTESWAP4(H[3]);
+ BYTESWAP4(H[4]);
+ BYTESWAP4(H[5]);
+ BYTESWAP4(H[6]);
+ BYTESWAP4(H[7]);
+#endif
+ padLen = PR_MIN(SHA256_LENGTH, maxDigestLen);
+ memcpy(digest, H, padLen);
+ if (digestLen)
+ *digestLen = padLen;
+}
+
+void
+SHA256_EndRaw(SHA256Context *ctx, unsigned char *digest,
+ unsigned int *digestLen, unsigned int maxDigestLen)
+{
+ PRUint32 h[8];
+ unsigned int len;
+#ifdef SWAP4MASK
+ PRUint32 t1;
+#endif
+
+ memcpy(h, ctx->h, sizeof(h));
+
+#if defined(IS_LITTLE_ENDIAN)
+ BYTESWAP4(h[0]);
+ BYTESWAP4(h[1]);
+ BYTESWAP4(h[2]);
+ BYTESWAP4(h[3]);
+ BYTESWAP4(h[4]);
+ BYTESWAP4(h[5]);
+ BYTESWAP4(h[6]);
+ BYTESWAP4(h[7]);
+#endif
+
+ len = PR_MIN(SHA256_LENGTH, maxDigestLen);
+ memcpy(digest, h, len);
+ if (digestLen)
+ *digestLen = len;
+}
+
+SECStatus
+SHA256_HashBuf(unsigned char *dest, const unsigned char *src,
+ PRUint32 src_length)
+{
+ SHA256Context ctx;
+ unsigned int outLen;
+
+ SHA256_Begin(&ctx);
+ SHA256_Update(&ctx, src, src_length);
+ SHA256_End(&ctx, dest, &outLen, SHA256_LENGTH);
+ memset(&ctx, 0, sizeof ctx);
+
+ return SECSuccess;
+}
diff --git a/dom/media/gmp/rlz/sha256.h b/dom/media/gmp/rlz/sha256.h
new file mode 100644
index 000000000..6958e382c
--- /dev/null
+++ b/dom/media/gmp/rlz/sha256.h
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Stripped down version of security/nss/lib/freebl/blapi.h
+// and related headers.
+
+#ifndef _SHA256_H_
+#define _SHA256_H_
+
+#define SHA256_LENGTH 32
+
+#include "prtypes.h" /* for PRUintXX */
+#include "prlong.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct SHA256Context {
+ union {
+ PRUint32 w[64]; /* message schedule, input buffer, plus 48 words */
+ PRUint8 b[256];
+ } u;
+ PRUint32 h[8]; /* 8 state variables */
+ PRUint32 sizeHi,sizeLo; /* 64-bit count of hashed bytes. */
+};
+
+typedef struct SHA256Context SHA256Context;
+
+extern void
+SHA256_Begin(SHA256Context *ctx);
+
+extern void
+SHA256_Update(SHA256Context *ctx, const unsigned char *input,
+ unsigned int inputLen);
+
+extern void
+SHA256_End(SHA256Context *ctx, unsigned char *digest,
+ unsigned int *digestLen, unsigned int maxDigestLen);
+
+#ifdef __cplusplus
+} /* extern C */
+#endif
+
+#endif /* _SHA256_H_ */
diff --git a/dom/media/gmp/rlz/win/lib/machine_id_win.cc b/dom/media/gmp/rlz/win/lib/machine_id_win.cc
new file mode 100644
index 000000000..0a636ebd3
--- /dev/null
+++ b/dom/media/gmp/rlz/win/lib/machine_id_win.cc
@@ -0,0 +1,134 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Use of this source code is governed by an Apache-style license that can be
+// found in the COPYING file.
+
+#include <windows.h>
+#include <sddl.h> // For ConvertSidToStringSidW.
+#include <string>
+#include <vector>
+#include "mozilla/ArrayUtils.h"
+
+#include "rlz/lib/assert.h"
+
+namespace rlz_lib {
+
+namespace {
+
+bool GetSystemVolumeSerialNumber(int* number) {
+ if (!number)
+ return false;
+
+ *number = 0;
+
+ // Find the system root path (e.g: C:\).
+ wchar_t system_path[MAX_PATH + 1];
+ if (!GetSystemDirectoryW(system_path, MAX_PATH))
+ return false;
+
+ wchar_t* first_slash = wcspbrk(system_path, L"\\/");
+ if (first_slash != NULL)
+ *(first_slash + 1) = 0;
+
+ DWORD number_local = 0;
+ if (!GetVolumeInformationW(system_path, NULL, 0, &number_local, NULL, NULL,
+ NULL, 0))
+ return false;
+
+ *number = (int)number_local;
+ return true;
+}
+
+bool GetComputerSid(const wchar_t* account_name, SID* sid, DWORD sid_size) {
+ static const DWORD kStartDomainLength = 128; // reasonable to start with
+
+ std::vector<wchar_t> domain_buffer(kStartDomainLength, 0);
+ DWORD domain_size = kStartDomainLength;
+ DWORD sid_dword_size = sid_size;
+ SID_NAME_USE sid_name_use;
+
+ BOOL success = ::LookupAccountNameW(NULL, account_name, sid,
+ &sid_dword_size, &domain_buffer.front(),
+ &domain_size, &sid_name_use);
+ if (!success && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ // We could have gotten the insufficient buffer error because
+ // one or both of sid and szDomain was too small. Check for that
+ // here.
+ if (sid_dword_size > sid_size)
+ return false;
+
+ if (domain_size > kStartDomainLength)
+ domain_buffer.resize(domain_size);
+
+ success = ::LookupAccountNameW(NULL, account_name, sid, &sid_dword_size,
+ &domain_buffer.front(), &domain_size,
+ &sid_name_use);
+ }
+
+ return success != FALSE;
+}
+
+std::vector<uint8_t> ConvertSidToBytes(SID* sid) {
+ std::wstring sid_string;
+#if _WIN32_WINNT >= 0x500
+ wchar_t* sid_buffer = NULL;
+ if (ConvertSidToStringSidW(sid, &sid_buffer)) {
+ sid_string = sid_buffer;
+ LocalFree(sid_buffer);
+ }
+#else
+ SID_IDENTIFIER_AUTHORITY* sia = ::GetSidIdentifierAuthority(sid);
+
+ if(sia->Value[0] || sia->Value[1]) {
+ base::SStringPrintf(
+ &sid_string, L"S-%d-0x%02hx%02hx%02hx%02hx%02hx%02hx",
+ SID_REVISION, (USHORT)sia->Value[0], (USHORT)sia->Value[1],
+ (USHORT)sia->Value[2], (USHORT)sia->Value[3], (USHORT)sia->Value[4],
+ (USHORT)sia->Value[5]);
+ } else {
+ ULONG authority = 0;
+ for (int i = 2; i < 6; ++i) {
+ authority <<= 8;
+ authority |= sia->Value[i];
+ }
+ base::SStringPrintf(&sid_string, L"S-%d-%lu", SID_REVISION, authority);
+ }
+
+ int sub_auth_count = *::GetSidSubAuthorityCount(sid);
+ for(int i = 0; i < sub_auth_count; ++i)
+ base::StringAppendF(&sid_string, L"-%lu", *::GetSidSubAuthority(sid, i));
+#endif
+
+ // Get the contents of the string as a bunch of bytes.
+ return std::vector<uint8_t>(
+ reinterpret_cast<uint8_t*>(&sid_string[0]),
+ reinterpret_cast<uint8_t*>(&sid_string[sid_string.size()]));
+}
+
+} // namespace
+
+bool GetRawMachineId(std::vector<uint8_t>* sid_bytes, int* volume_id) {
+ // Calculate the Windows SID.
+
+ wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {0};
+ DWORD size = mozilla::ArrayLength(computer_name);
+
+ if (!GetComputerNameW(computer_name, &size)) {
+ return false;
+ }
+ char sid_buffer[SECURITY_MAX_SID_SIZE];
+ SID* sid = reinterpret_cast<SID*>(sid_buffer);
+ if (GetComputerSid(computer_name, sid, SECURITY_MAX_SID_SIZE)) {
+ *sid_bytes = ConvertSidToBytes(sid);
+ }
+
+ // Get the system drive volume serial number.
+ *volume_id = 0;
+ if (!GetSystemVolumeSerialNumber(volume_id)) {
+ ASSERT_STRING("GetMachineId: Failed to retrieve volume serial number");
+ *volume_id = 0;
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/moz.build b/dom/media/moz.build
index 6b0efc32b..ec36dfaf2 100644
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -3,9 +3,15 @@
# 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/.
+with Files('*'):
+ BUG_COMPONENT = ('Core', 'Video/Audio')
+
DIRS += [
'encoder',
'flac',
+ 'gmp',
+ 'gmp-plugin',
+ 'gmp-plugin-openh264',
'imagecapture',
'ipc',
'mediasink',
diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp
index ecae512be..b8082d1ff 100644
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -15,6 +15,7 @@
#ifdef MOZ_FFMPEG
#include "FFmpegRuntimeLinker.h"
#endif
+#include "GMPDecoderModule.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/SharedThreadPool.h"
@@ -208,6 +209,9 @@ PDMFactory::CreateDecoder(const CreateDecoderParams& aParams)
if (mFFmpegFailedToLoad) {
diagnostics->SetFFmpegFailedToLoad();
}
+ if (mGMPPDMFailedToStartup) {
+ diagnostics->SetGMPPDMFailedToStartup();
+ }
}
for (auto& current : mCurrentPDMs) {
@@ -363,6 +367,13 @@ PDMFactory::CreatePDMs()
m = new AgnosticDecoderModule();
StartupPDM(m);
+
+ if (MediaPrefs::PDMGMPEnabled()) {
+ m = new GMPDecoderModule();
+ mGMPPDMFailedToStartup = !StartupPDM(m);
+ } else {
+ mGMPPDMFailedToStartup = false;
+ }
}
void
@@ -395,6 +406,9 @@ PDMFactory::GetDecoder(const TrackInfo& aTrackInfo,
if (mFFmpegFailedToLoad) {
aDiagnostics->SetFFmpegFailedToLoad();
}
+ if (mGMPPDMFailedToStartup) {
+ aDiagnostics->SetGMPPDMFailedToStartup();
+ }
}
RefPtr<PlatformDecoderModule> pdm;
diff --git a/dom/media/platforms/PDMFactory.h b/dom/media/platforms/PDMFactory.h
index 129876ac2..a42436477 100644
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -61,6 +61,7 @@ private:
bool mWMFFailedToLoad = false;
bool mFFmpegFailedToLoad = false;
+ bool mGMPPDMFailedToStartup = false;
void EnsureInit() const;
template<class T> friend class StaticAutoPtr;
diff --git a/dom/media/platforms/PlatformDecoderModule.h b/dom/media/platforms/PlatformDecoderModule.h
index e2ef1f752..62855335f 100644
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -16,6 +16,7 @@
#include "mozilla/layers/KnowsCompositor.h"
#include "nsTArray.h"
#include "mozilla/RefPtr.h"
+#include "GMPService.h"
#include <queue>
#include "MediaResult.h"
@@ -37,6 +38,7 @@ class RemoteDecoderModule;
class MediaDataDecoder;
class MediaDataDecoderCallback;
class TaskQueue;
+class CDMProxy;
static LazyLogModule sPDMLog("PlatformDecoderModule");
@@ -79,6 +81,7 @@ struct MOZ_STACK_CLASS CreateDecoderParams final {
layers::ImageContainer* mImageContainer = nullptr;
MediaResult* mError = nullptr;
RefPtr<layers::KnowsCompositor> mKnowsCompositor;
+ RefPtr<GMPCrashHelper> mCrashHelper;
bool mUseBlankDecoder = false;
private:
@@ -87,6 +90,7 @@ private:
void Set(DecoderDoctorDiagnostics* aDiagnostics) { mDiagnostics = aDiagnostics; }
void Set(layers::ImageContainer* aImageContainer) { mImageContainer = aImageContainer; }
void Set(MediaResult* aError) { mError = aError; }
+ void Set(GMPCrashHelper* aCrashHelper) { mCrashHelper = aCrashHelper; }
void Set(bool aUseBlankDecoder) { mUseBlankDecoder = aUseBlankDecoder; }
void Set(layers::KnowsCompositor* aKnowsCompositor) { mKnowsCompositor = aKnowsCompositor; }
template <typename T1, typename T2, typename... Ts>
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
new file mode 100644
index 000000000..d863d44d4
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
@@ -0,0 +1,306 @@
+/* -*- 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 "GMPAudioDecoder.h"
+#include "nsServiceManagerUtils.h"
+#include "MediaInfo.h"
+#include "GMPDecoderModule.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+bool IsOnGMPThread()
+{
+ nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = mps->GetThread(getter_AddRefs(gmpThread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread);
+ return NS_GetCurrentThread() == gmpThread;
+}
+#endif
+
+void
+AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (aRate == 0 || aChannels == 0) {
+ mCallback->Error(MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL(
+ "Invalid rate or num channels returned on GMP audio samples")));
+ return;
+ }
+
+ size_t numFrames = aPCM.Length() / aChannels;
+ MOZ_ASSERT((aPCM.Length() % aChannels) == 0);
+ AlignedAudioBuffer audioData(aPCM.Length());
+ if (!audioData) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Unable to allocate audio buffer")));
+ return;
+ }
+
+ for (size_t i = 0; i < aPCM.Length(); ++i) {
+ audioData[i] = AudioSampleToFloat(aPCM[i]);
+ }
+
+ if (mMustRecaptureAudioPosition) {
+ mAudioFrameSum = 0;
+ auto timestamp = UsecsToFrames(aTimeStamp, aRate);
+ if (!timestamp.isValid()) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid timestamp")));
+ return;
+ }
+ mAudioFrameOffset = timestamp.value();
+ mMustRecaptureAudioPosition = false;
+ }
+
+ auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate);
+ if (!timestamp.isValid()) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid timestamp on audio samples")));
+ return;
+ }
+ mAudioFrameSum += numFrames;
+
+ auto duration = FramesToUsecs(numFrames, aRate);
+ if (!duration.isValid()) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid duration on audio samples")));
+ return;
+ }
+
+ RefPtr<AudioData> audio(new AudioData(mLastStreamOffset,
+ timestamp.value(),
+ duration.value(),
+ numFrames,
+ Move(audioData),
+ aChannels,
+ aRate));
+
+#ifdef LOG_SAMPLE_DECODE
+ LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
+ timestamp, duration, currentLength);
+#endif
+
+ mCallback->Output(audio);
+}
+
+void
+AudioCallbackAdapter::InputDataExhausted()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->InputExhausted();
+}
+
+void
+AudioCallbackAdapter::DrainComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->DrainComplete();
+}
+
+void
+AudioCallbackAdapter::ResetComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mMustRecaptureAudioPosition = true;
+ mCallback->FlushComplete();
+}
+
+void
+AudioCallbackAdapter::Error(GMPErr aErr)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->Error(MediaResult(aErr == GMPDecodeErr
+ ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr)));
+}
+
+void
+AudioCallbackAdapter::Terminated()
+{
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Audio GMP decoder terminated.")));
+}
+
+GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.AudioConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(nullptr)
+ , mAdapter(nullptr)
+ , mCrashHelper(aParams.mCrashHelper)
+{}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+ MOZ_ASSERT(aWrapper);
+ MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+ mCallback = aWrapper->Callback();
+ mAdapter = nullptr;
+ return *this;
+}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter)
+{
+ MOZ_ASSERT(aAdapter);
+ MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+ mCallback = aAdapter->Callback();
+ mAdapter = aAdapter;
+ return *this;
+}
+
+GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams)
+ : mConfig(aParams.mConfig)
+ , mCallback(aParams.mCallback)
+ , mGMP(nullptr)
+ , mAdapter(aParams.mAdapter)
+ , mCrashHelper(aParams.mCrashHelper)
+{
+ MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+ if (!mAdapter) {
+ mAdapter = new AudioCallbackAdapter(mCallback);
+ }
+}
+
+void
+GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+ const Maybe<nsCString> gmp(
+ GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm")));
+ if (gmp.isSome()) {
+ aTags.AppendElement(gmp.value());
+ }
+}
+
+nsCString
+GMPAudioDecoder::GetNodeId()
+{
+ return SHARED_GMP_DECODING_NODE_ID;
+}
+
+void
+GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+ nsTArray<uint8_t> codecSpecific;
+ codecSpecific.AppendElements(mConfig.mCodecSpecificConfig->Elements(),
+ mConfig.mCodecSpecificConfig->Length());
+
+ nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC,
+ mConfig.mChannels,
+ mConfig.mBitDepth,
+ mConfig.mRate,
+ codecSpecific,
+ mAdapter);
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mInitPromise.Resolve(TrackInfo::kAudioTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+GMPAudioDecoder::Init()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+void
+GMPAudioDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")));
+ return;
+ }
+
+ mAdapter->SetLastStreamOffset(sample->mOffset);
+
+ gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate);
+ nsresult rv = mGMP->Decode(samples);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(MediaResult(rv, __func__));
+ }
+}
+
+void
+GMPAudioDecoder::Flush()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mCallback->FlushComplete();
+ }
+}
+
+void
+GMPAudioDecoder::Drain()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mCallback->DrainComplete();
+ }
+}
+
+void
+GMPAudioDecoder::Shutdown()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ if (!mGMP) {
+ return;
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
new file mode 100644
index 000000000..90e3ebdb6
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
@@ -0,0 +1,112 @@
+/* -*- 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/. */
+
+#if !defined(GMPAudioDecoder_h_)
+#define GMPAudioDecoder_h_
+
+#include "GMPAudioDecoderProxy.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy {
+public:
+ explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+ : mCallback(aCallback)
+ , mLastStreamOffset(0)
+ , mAudioFrameSum(0)
+ , mAudioFrameOffset(0)
+ , mMustRecaptureAudioPosition(true)
+ {}
+
+ MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
+ // GMPAudioDecoderCallbackProxy
+ void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ void SetLastStreamOffset(int64_t aStreamOffset) {
+ mLastStreamOffset = aStreamOffset;
+ }
+
+private:
+ MediaDataDecoderCallbackProxy* mCallback;
+ int64_t mLastStreamOffset;
+
+ int64_t mAudioFrameSum;
+ int64_t mAudioFrameOffset;
+ bool mMustRecaptureAudioPosition;
+};
+
+struct GMPAudioDecoderParams {
+ explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams);
+ GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+ GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter);
+
+ const AudioInfo& mConfig;
+ TaskQueue* mTaskQueue;
+ MediaDataDecoderCallbackProxy* mCallback;
+ AudioCallbackAdapter* mAdapter;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+class GMPAudioDecoder : public MediaDataDecoder {
+public:
+ explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "GMP audio decoder";
+ }
+
+protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+
+private:
+
+ class GMPInitDoneCallback : public GetGMPAudioDecoderCallback
+ {
+ public:
+ explicit GMPInitDoneCallback(GMPAudioDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ {
+ }
+
+ void Done(GMPAudioDecoderProxy* aGMP) override
+ {
+ mDecoder->GMPInitDone(aGMP);
+ }
+
+ private:
+ RefPtr<GMPAudioDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPAudioDecoderProxy* aGMP);
+
+ const AudioInfo mConfig;
+ MediaDataDecoderCallbackProxy* mCallback;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPAudioDecoderProxy* mGMP;
+ nsAutoPtr<AudioCallbackAdapter> mAdapter;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPAudioDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
new file mode 100644
index 000000000..50a5097ac
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDecoderModule.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "GMPAudioDecoder.h"
+#include "GMPVideoDecoder.h"
+#include "GMPUtils.h"
+#include "MediaDataDecoderProxy.h"
+#include "MediaPrefs.h"
+#include "VideoUtils.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/StaticMutex.h"
+#include "gmp-audio-decode.h"
+#include "gmp-video-decode.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla {
+
+GMPDecoderModule::GMPDecoderModule()
+{
+}
+
+GMPDecoderModule::~GMPDecoderModule()
+{
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback)
+{
+ RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) {
+ return nullptr;
+ }
+
+ if (aParams.mDiagnostics) {
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
+ if (preferredGMP.isSome()) {
+ aParams.mDiagnostics->SetGMP(preferredGMP.value());
+ }
+ }
+
+ RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+ auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new GMPVideoDecoder(params));
+ return wrapper.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ return nullptr;
+ }
+
+ if (aParams.mDiagnostics) {
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
+ if (preferredGMP.isSome()) {
+ aParams.mDiagnostics->SetGMP(preferredGMP.value());
+ }
+ }
+
+ RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+ auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new GMPAudioDecoder(params));
+ return wrapper.forget();
+}
+
+PlatformDecoderModule::ConversionRequired
+GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
+{
+ // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+}
+
+/* static */
+const Maybe<nsCString>
+GMPDecoderModule::PreferredGMP(const nsACString& aMimeType)
+{
+ Maybe<nsCString> rv;
+ if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ switch (MediaPrefs::GMPAACPreferred()) {
+ case 1: rv.emplace(kEMEKeySystemClearkey); break;
+ default: break;
+ }
+ }
+
+ if (MP4Decoder::IsH264(aMimeType)) {
+ switch (MediaPrefs::GMPH264Preferred()) {
+ case 1: rv.emplace(kEMEKeySystemClearkey); break;
+ default: break;
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+bool
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ const Maybe<nsCString>& aGMP)
+{
+ if (aGMP.isNothing()) {
+ return false;
+ }
+
+ if (MP4Decoder::IsH264(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { 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()});
+ }
+
+ if (VPXDecoder::IsVP8(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("vp8"), aGMP.value()});
+ }
+
+ if (MP4Decoder::IsAAC(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+ { NS_LITERAL_CSTRING("aac"), aGMP.value()});
+ }
+
+ return false;
+}
+
+bool
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType);
+ bool rv = SupportsMimeType(aMimeType, preferredGMP);
+ if (rv && aDiagnostics && preferredGMP.isSome()) {
+ aDiagnostics->SetGMP(preferredGMP.value());
+ }
+ return rv;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
new file mode 100644
index 000000000..b501ecb54
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+#if !defined(GMPDecoderModule_h_)
+#define GMPDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/Maybe.h"
+
+// The special NodeId we use when doing unencrypted decoding using the GMP's
+// decoder. This ensures that each GMP MediaDataDecoder we create doesn't
+// require spinning up a new process, but instead we run all instances of
+// GMP decoders in the one process, to reduce overhead.
+//
+// Note: GMP storage is isolated by NodeId, and non persistent for this
+// special NodeId, and the only way a GMP can communicate with the outside
+// world is through the EME GMP APIs, and we never run EME with this NodeID
+// (because NodeIds are random strings which can't contain the '-' character),
+// so there's no way a malicious GMP can harvest, store, and then report any
+// privacy sensitive data about what users are watching.
+#define SHARED_GMP_DECODING_NODE_ID NS_LITERAL_CSTRING("gmp-shared-decoding")
+
+namespace mozilla {
+
+class GMPDecoderModule : public PlatformDecoderModule {
+public:
+ GMPDecoderModule();
+
+ virtual ~GMPDecoderModule();
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override;
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override;
+
+ bool
+ SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType);
+
+ static bool SupportsMimeType(const nsACString& aMimeType,
+ const Maybe<nsCString>& aGMP);
+};
+
+} // namespace mozilla
+
+#endif // GMPDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
new file mode 100644
index 000000000..26d029da0
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -0,0 +1,388 @@
+/* -*- 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 "GMPVideoDecoder.h"
+#include "GMPVideoHost.h"
+#include "mozilla/EndianUtils.h"
+#include "prsystem.h"
+#include "MediaData.h"
+#include "GMPDecoderModule.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+extern bool IsOnGMPThread();
+#endif
+
+void
+VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
+
+ MOZ_ASSERT(IsOnGMPThread());
+
+ VideoData::YCbCrBuffer b;
+ for (int i = 0; i < kGMPNumOfPlanes; ++i) {
+ b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i));
+ b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i));
+ if (i == kGMPYPlane) {
+ b.mPlanes[i].mWidth = decodedFrame->Width();
+ b.mPlanes[i].mHeight = decodedFrame->Height();
+ } else {
+ b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2;
+ b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2;
+ }
+ b.mPlanes[i].mOffset = 0;
+ b.mPlanes[i].mSkip = 0;
+ }
+
+ gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height());
+ RefPtr<VideoData> v =
+ VideoData::CreateAndCopyData(mVideoInfo,
+ mImageContainer,
+ mLastStreamOffset,
+ decodedFrame->Timestamp(),
+ decodedFrame->Duration(),
+ b,
+ false,
+ -1,
+ pictureRegion);
+ if (v) {
+ mCallback->Output(v);
+ } else {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+ }
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::InputDataExhausted()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->InputExhausted();
+}
+
+void
+VideoCallbackAdapter::DrainComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->DrainComplete();
+}
+
+void
+VideoCallbackAdapter::ResetComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->FlushComplete();
+}
+
+void
+VideoCallbackAdapter::Error(GMPErr aErr)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->Error(MediaResult(aErr == GMPDecodeErr
+ ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr)));
+}
+
+void
+VideoCallbackAdapter::Terminated()
+{
+ // Note that this *may* be called from the proxy thread also.
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Video GMP decoder terminated.")));
+}
+
+GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.VideoConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(nullptr)
+ , mAdapter(nullptr)
+ , mImageContainer(aParams.mImageContainer)
+ , mLayersBackend(aParams.GetLayersBackend())
+ , mCrashHelper(aParams.mCrashHelper)
+{}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+ MOZ_ASSERT(aWrapper);
+ MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+ mCallback = aWrapper->Callback();
+ mAdapter = nullptr;
+ return *this;
+}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter)
+{
+ MOZ_ASSERT(aAdapter);
+ MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+ mCallback = aAdapter->Callback();
+ mAdapter = aAdapter;
+ return *this;
+}
+
+GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
+ : mConfig(aParams.mConfig)
+ , mCallback(aParams.mCallback)
+ , mGMP(nullptr)
+ , mHost(nullptr)
+ , mAdapter(aParams.mAdapter)
+ , mConvertNALUnitLengths(false)
+ , mCrashHelper(aParams.mCrashHelper)
+{
+ MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+ if (!mAdapter) {
+ mAdapter = new VideoCallbackAdapter(mCallback,
+ VideoInfo(mConfig.mDisplay.width,
+ mConfig.mDisplay.height),
+ aParams.mImageContainer);
+ }
+}
+
+void
+GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ const Maybe<nsCString> gmp(
+ GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc")));
+ if (gmp.isSome()) {
+ aTags.AppendElement(gmp.value());
+ }
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ }
+}
+
+nsCString
+GMPVideoDecoder::GetNodeId()
+{
+ return SHARED_GMP_DECODING_NODE_ID;
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+GMPVideoDecoder::CreateFrame(MediaRawData* aSample)
+{
+ GMPVideoFrame* ftmp = nullptr;
+ GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (GMP_FAILED(err)) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Host::CreateFrame:%x", err)));
+ return nullptr;
+ }
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
+ err = frame->CreateEmptyFrame(aSample->Size());
+ if (GMP_FAILED(err)) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("GMPVideoEncodedFrame::CreateEmptyFrame:%x", err)));
+ return nullptr;
+ }
+
+ memcpy(frame->Buffer(), aSample->Data(), frame->Size());
+
+ // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to
+ // suit the GMP API.
+ if (mConvertNALUnitLengths) {
+ const int kNALLengthSize = 4;
+ uint8_t* buf = frame->Buffer();
+ while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) {
+ uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize;
+ *reinterpret_cast<uint32_t *>(buf) = length;
+ buf += length;
+ }
+ }
+
+ frame->SetBufferType(GMP_BufferLength32);
+
+ frame->SetEncodedWidth(mConfig.mDisplay.width);
+ frame->SetEncodedHeight(mConfig.mDisplay.height);
+ frame->SetTimeStamp(aSample->mTime);
+ frame->SetCompleteFrame(true);
+ frame->SetDuration(aSample->mDuration);
+ frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame);
+
+ return frame;
+}
+
+const VideoInfo&
+GMPVideoDecoder::GetConfig() const
+{
+ return mConfig;
+}
+
+void
+GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ MOZ_ASSERT(aHost);
+
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+
+ codec.mGMPApiVersion = kGMPVersion33;
+ nsTArray<uint8_t> codecSpecific;
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecH264;
+ codecSpecific.AppendElement(0); // mPacketizationMode.
+ codecSpecific.AppendElements(mConfig.mExtraData->Elements(),
+ mConfig.mExtraData->Length());
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP8;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP9;
+ } else {
+ // Unrecognized mime type
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ codec.mWidth = mConfig.mImage.width;
+ codec.mHeight = mConfig.mImage.height;
+
+ nsresult rv = aGMP->InitDecode(codec,
+ codecSpecific,
+ mAdapter,
+ PR_GetNumberOfProcessors());
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mHost = aHost;
+
+ // GMP implementations have interpreted the meaning of GMP_BufferLength32
+ // differently. The OpenH264 GMP expects GMP_BufferLength32 to behave as
+ // specified in the GMP API, where each buffer is prefixed by a 32-bit
+ // host-endian buffer length that includes the size of the buffer length
+ // field. Other existing GMPs currently expect GMP_BufferLength32 (when
+ // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
+ // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
+ // and do not include the length of the buffer length field.
+ mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+
+ mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+GMPVideoDecoder::Init()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetDecryptingGMPVideoDecoder(mCrashHelper,
+ &tags,
+ GetNodeId(),
+ Move(callback),
+ DecryptorId()))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+void
+GMPVideoDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")));
+ return;
+ }
+
+ mAdapter->SetLastStreamOffset(sample->mOffset);
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
+ if (!frame) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("CreateFrame returned null")));
+ return;
+ }
+ nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
+ nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("mGMP->Decode:%x", rv)));
+ }
+}
+
+void
+GMPVideoDecoder::Flush()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mCallback->FlushComplete();
+ }
+}
+
+void
+GMPVideoDecoder::Drain()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mCallback->DrainComplete();
+ }
+}
+
+void
+GMPVideoDecoder::Shutdown()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ // Note that this *may* be called from the proxy thread also.
+ if (!mGMP) {
+ return;
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
new file mode 100644
index 000000000..900ef4553
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -0,0 +1,122 @@
+/* -*- 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/. */
+
+#if !defined(GMPVideoDecoder_h_)
+#define GMPVideoDecoder_h_
+
+#include "GMPVideoDecoderProxy.h"
+#include "ImageContainer.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "MediaInfo.h"
+
+namespace mozilla {
+
+class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy {
+public:
+ VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+ VideoInfo aVideoInfo,
+ layers::ImageContainer* aImageContainer)
+ : mCallback(aCallback)
+ , mLastStreamOffset(0)
+ , mVideoInfo(aVideoInfo)
+ , mImageContainer(aImageContainer)
+ {}
+
+ MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
+ // GMPVideoDecoderCallbackProxy
+ void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+ void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override;
+ void ReceivedDecodedFrame(const uint64_t aPictureId) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ void SetLastStreamOffset(int64_t aStreamOffset) {
+ mLastStreamOffset = aStreamOffset;
+ }
+
+private:
+ MediaDataDecoderCallbackProxy* mCallback;
+ int64_t mLastStreamOffset;
+
+ VideoInfo mVideoInfo;
+ RefPtr<layers::ImageContainer> mImageContainer;
+};
+
+struct GMPVideoDecoderParams {
+ explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams);
+ GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+ GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter);
+
+ const VideoInfo& mConfig;
+ TaskQueue* mTaskQueue;
+ MediaDataDecoderCallbackProxy* mCallback;
+ VideoCallbackAdapter* mAdapter;
+ layers::ImageContainer* mImageContainer;
+ layers::LayersBackend mLayersBackend;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+class GMPVideoDecoder : public MediaDataDecoder {
+public:
+ explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "GMP video decoder";
+ }
+
+protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+ virtual uint32_t DecryptorId() const { return 0; }
+ virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample);
+ virtual const VideoInfo& GetConfig() const;
+
+private:
+
+ class GMPInitDoneCallback : public GetGMPVideoDecoderCallback
+ {
+ public:
+ explicit GMPInitDoneCallback(GMPVideoDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ {
+ }
+
+ void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override
+ {
+ mDecoder->GMPInitDone(aGMP, aHost);
+ }
+
+ private:
+ RefPtr<GMPVideoDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost);
+
+ const VideoInfo mConfig;
+ MediaDataDecoderCallbackProxy* mCallback;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPVideoDecoderProxy* mGMP;
+ GMPVideoHost* mHost;
+ nsAutoPtr<VideoCallbackAdapter> mAdapter;
+ bool mConvertNALUnitLengths;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
new file mode 100644
index 000000000..5a196f8e6
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
@@ -0,0 +1,90 @@
+/* -*- 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 "MediaDataDecoderProxy.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+void
+MediaDataDecoderCallbackProxy::Error(const MediaResult& aError)
+{
+ mProxyCallback->Error(aError);
+}
+
+void
+MediaDataDecoderCallbackProxy::FlushComplete()
+{
+ mProxyDecoder->FlushComplete();
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+MediaDataDecoderProxy::InternalInit()
+{
+ return mProxyDecoder->Init();
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+MediaDataDecoderProxy::Init()
+{
+ MOZ_ASSERT(!mIsShutdown);
+
+ return InvokeAsync(mProxyThread, this, __func__,
+ &MediaDataDecoderProxy::InternalInit);
+}
+
+void
+MediaDataDecoderProxy::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ nsCOMPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample));
+ mProxyThread->Dispatch(task.forget());
+}
+
+void
+MediaDataDecoderProxy::Flush()
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ mFlushComplete.Set(false);
+
+ mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush));
+
+ mFlushComplete.WaitUntil(true);
+}
+
+void
+MediaDataDecoderProxy::Drain()
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain));
+}
+
+void
+MediaDataDecoderProxy::Shutdown()
+{
+ // Note that this *may* be called from the proxy thread also.
+ MOZ_ASSERT(!mIsShutdown);
+#if defined(DEBUG)
+ mIsShutdown = true;
+#endif
+ mProxyThread->AsXPCOMThread()->Dispatch(NewRunnableMethod(mProxyDecoder,
+ &MediaDataDecoder::Shutdown),
+ NS_DISPATCH_SYNC);
+}
+
+void
+MediaDataDecoderProxy::FlushComplete()
+{
+ mFlushComplete.Set(true);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
new file mode 100644
index 000000000..735b6126e
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
@@ -0,0 +1,181 @@
+/* -*- 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/. */
+
+#if !defined(MediaDataDecoderProxy_h_)
+#define MediaDataDecoderProxy_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "GMPService.h"
+
+namespace mozilla {
+
+class InputTask : public Runnable {
+public:
+ InputTask(MediaDataDecoder* aDecoder,
+ MediaRawData* aSample)
+ : mDecoder(aDecoder)
+ , mSample(aSample)
+ {}
+
+ NS_IMETHOD Run() override {
+ mDecoder->Input(mSample);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<MediaDataDecoder> mDecoder;
+ RefPtr<MediaRawData> mSample;
+};
+
+template<typename T>
+class Condition {
+public:
+ explicit Condition(T aValue)
+ : mMonitor("Condition")
+ , mCondition(aValue)
+ {}
+
+ void Set(T aValue) {
+ MonitorAutoLock mon(mMonitor);
+ mCondition = aValue;
+ mon.NotifyAll();
+ }
+
+ void WaitUntil(T aValue) {
+ MonitorAutoLock mon(mMonitor);
+ while (mCondition != aValue) {
+ mon.Wait();
+ }
+ }
+
+private:
+ Monitor mMonitor;
+ T mCondition;
+};
+
+class MediaDataDecoderProxy;
+
+class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback {
+public:
+ MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder,
+ MediaDataDecoderCallback* aCallback)
+ : mProxyDecoder(aProxyDecoder)
+ , mProxyCallback(aCallback)
+ {
+ }
+
+ void Output(MediaData* aData) override {
+ mProxyCallback->Output(aData);
+ }
+
+ void Error(const MediaResult& aError) override;
+
+ void InputExhausted() override {
+ mProxyCallback->InputExhausted();
+ }
+
+ void DrainComplete() override {
+ mProxyCallback->DrainComplete();
+ }
+
+ void ReleaseMediaResources() override {
+ mProxyCallback->ReleaseMediaResources();
+ }
+
+ void FlushComplete();
+
+ bool OnReaderTaskQueue() override
+ {
+ return mProxyCallback->OnReaderTaskQueue();
+ }
+
+ void WaitingForKey() override
+ {
+ mProxyCallback->WaitingForKey();
+ }
+
+private:
+ MediaDataDecoderProxy* mProxyDecoder;
+ MediaDataDecoderCallback* mProxyCallback;
+};
+
+class MediaDataDecoderProxy : public MediaDataDecoder {
+public:
+ MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
+ MediaDataDecoderCallback* aCallback)
+ : mProxyThread(aProxyThread)
+ , mProxyCallback(this, aCallback)
+ , mFlushComplete(false)
+#if defined(DEBUG)
+ , mIsShutdown(false)
+#endif
+ {
+ }
+
+ // Ideally, this would return a regular MediaDataDecoderCallback pointer
+ // to retain the clean abstraction, but until MediaDataDecoderCallback
+ // supports the FlushComplete interface, this will have to do. When MDDC
+ // supports FlushComplete, this, the GMP*Decoders, and the
+ // *CallbackAdapters can be reverted to accepting a regular
+ // MediaDataDecoderCallback pointer.
+ MediaDataDecoderCallbackProxy* Callback()
+ {
+ return &mProxyCallback;
+ }
+
+ void SetProxyTarget(MediaDataDecoder* aProxyDecoder)
+ {
+ MOZ_ASSERT(aProxyDecoder);
+ mProxyDecoder = aProxyDecoder;
+ }
+
+ // These are called from the decoder thread pool.
+ // Init and Shutdown run synchronously on the proxy thread, all others are
+ // asynchronously and responded to via the MediaDataDecoderCallback.
+ // Note: the nsresults returned by the proxied decoder are lost.
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+
+ const char* GetDescriptionName() const override
+ {
+ return "GMP proxy data decoder";
+ }
+
+ // Called by MediaDataDecoderCallbackProxy.
+ void FlushComplete();
+
+private:
+ RefPtr<InitPromise> InternalInit();
+
+#ifdef DEBUG
+ bool IsOnProxyThread() {
+ return mProxyThread && mProxyThread->IsCurrentThreadIn();
+ }
+#endif
+
+ friend class InputTask;
+ friend class InitTask;
+
+ RefPtr<MediaDataDecoder> mProxyDecoder;
+ RefPtr<AbstractThread> mProxyThread;
+
+ MediaDataDecoderCallbackProxy mProxyCallback;
+
+ Condition<bool> mFlushComplete;
+#if defined(DEBUG)
+ bool mIsShutdown;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // MediaDataDecoderProxy_h_
diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build
new file mode 100644
index 000000000..64e258620
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'GMPAudioDecoder.h',
+ 'GMPDecoderModule.h',
+ 'GMPVideoDecoder.h',
+ 'MediaDataDecoderProxy.h',
+]
+
+SOURCES += [
+ 'GMPAudioDecoder.cpp',
+ 'GMPDecoderModule.cpp',
+ 'GMPVideoDecoder.cpp',
+ 'MediaDataDecoderProxy.cpp',
+]
+
+# GMPVideoEncodedFrameImpl.h needs IPC
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/platforms/moz.build b/dom/media/platforms/moz.build
index b570eb7d9..5da113e77 100644
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -29,6 +29,7 @@ SOURCES += [
]
DIRS += [
+ 'agnostic/gmp',
'omx'
]
diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
index 840348315..8cf22c241 100644
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -29,6 +29,7 @@
#include "mozilla/WindowsVersion.h"
#include "nsPrintfCString.h"
#include "nsIFile.h"
+#include "GMPUtils.h" // For SplitAt. TODO: Move SplitAt to a central place.
#include "MP4Decoder.h"
#include "VPXDecoder.h"
#include "mozilla/SyncRunnable.h"
@@ -70,21 +71,6 @@ const CLSID CLSID_WebmMfVpxDec =
namespace mozilla {
-// Utility function only used here.
-// XXXMC: Perhaps make this available globally?
-void
-SplitAt(const char* aDelims,
- const nsACString& aInput,
- nsTArray<nsCString>& aOutTokens)
-{
- nsAutoCString str(aInput);
- char* end = str.BeginWriting();
- const char* start = nullptr;
- while (!!(start = NS_strtok(aDelims, &end))) {
- aOutTokens.AppendElement(nsCString(start));
- }
-}
-
LayersBackend
GetCompositorBackendType(layers::KnowsCompositor* aKnowsCompositor)
{
diff --git a/dom/media/platforms/wrappers/H264Converter.cpp b/dom/media/platforms/wrappers/H264Converter.cpp
index 3d9d2e62f..cca03fceb 100644
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -26,6 +26,7 @@ H264Converter::H264Converter(PlatformDecoderModule* aPDM,
, mTaskQueue(aParams.mTaskQueue)
, mCallback(aParams.mCallback)
, mDecoder(nullptr)
+ , mGMPCrashHelper(aParams.mCrashHelper)
, mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig)
== PlatformDecoderModule::ConversionRequired::kNeedAVCC)
, mLastError(NS_OK)
@@ -201,7 +202,8 @@ H264Converter::CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics)
mCallback,
aDiagnostics,
mImageContainer,
- mKnowsCompositor
+ mKnowsCompositor,
+ mGMPCrashHelper
});
if (!mDecoder) {
diff --git a/dom/media/platforms/wrappers/H264Converter.h b/dom/media/platforms/wrappers/H264Converter.h
index 627a56731..6905b1c74 100644
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -63,6 +63,7 @@ private:
MediaDataDecoderCallback* mCallback;
RefPtr<MediaDataDecoder> mDecoder;
MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
+ RefPtr<GMPCrashHelper> mGMPCrashHelper;
bool mNeedAVCC;
nsresult mLastError;
bool mNeedKeyframe = true;
diff --git a/dom/media/webaudio/BufferDecoder.cpp b/dom/media/webaudio/BufferDecoder.cpp
index de0a79ffd..ddd9e7d1b 100644
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -7,13 +7,15 @@
#include "nsISupports.h"
#include "MediaResource.h"
+#include "GMPService.h"
namespace mozilla {
NS_IMPL_ISUPPORTS0(BufferDecoder)
-BufferDecoder::BufferDecoder(MediaResource* aResource)
+BufferDecoder::BufferDecoder(MediaResource* aResource, GMPCrashHelper* aCrashHelper)
: mResource(aResource)
+ , mCrashHelper(aCrashHelper)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_COUNT_CTOR(BufferDecoder);
@@ -65,4 +67,10 @@ BufferDecoder::GetOwner() const
return nullptr;
}
+already_AddRefed<GMPCrashHelper>
+BufferDecoder::GetCrashHelper()
+{
+ return do_AddRef(mCrashHelper);
+}
+
} // namespace mozilla
diff --git a/dom/media/webaudio/BufferDecoder.h b/dom/media/webaudio/BufferDecoder.h
index 5b75d6405..2c6c49454 100644
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -23,7 +23,7 @@ class BufferDecoder final : public AbstractMediaDecoder
public:
// This class holds a weak pointer to MediaResource. It's the responsibility
// of the caller to manage the memory of the MediaResource object.
- explicit BufferDecoder(MediaResource* aResource);
+ explicit BufferDecoder(MediaResource* aResource, GMPCrashHelper* aCrashHelper);
NS_DECL_THREADSAFE_ISUPPORTS
@@ -39,10 +39,13 @@ public:
MediaDecoderOwner* GetOwner() const final override;
+ already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
+
private:
virtual ~BufferDecoder();
RefPtr<TaskQueue> mTaskQueueIdentity;
RefPtr<MediaResource> mResource;
+ RefPtr<GMPCrashHelper> mCrashHelper;
};
} // namespace mozilla
diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp
index c57c9b9b5..f590d2f68 100644
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -25,6 +25,7 @@
#include "WebAudioUtils.h"
#include "mozilla/dom/Promise.h"
#include "nsPrintfCString.h"
+#include "GMPService.h"
namespace mozilla {
@@ -180,6 +181,24 @@ MediaDecodeTask::Run()
return NS_OK;
}
+class BufferDecoderGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit BufferDecoderGMPCrashHelper(nsPIDOMWindowInner* aParent)
+ : mParent(do_GetWeakReference(aParent))
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mParent);
+ return window.forget();
+ }
+private:
+ nsWeakPtr mParent;
+};
+
bool
MediaDecodeTask::CreateReader()
{
@@ -197,7 +216,8 @@ MediaDecodeTask::CreateReader()
mLength, principal, mContentType);
MOZ_ASSERT(!mBufferDecoder);
- mBufferDecoder = new BufferDecoder(resource);
+ mBufferDecoder = new BufferDecoder(resource,
+ new BufferDecoderGMPCrashHelper(mDecodeJob.mContext->GetParentObject()));
// If you change this list to add support for new decoders, please consider
// updating HTMLMediaElement::CreateDecoder as well.
diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build
index aac7985ac..5aba958ab 100644
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -3,6 +3,9 @@
# 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/.
+with Files('*'):
+ BUG_COMPONENT = ('Core', 'Web Audio')
+
DIRS += ['blink']
TEST_DIRS += ['gtest']
diff --git a/ipc/app/moz.build b/ipc/app/moz.build
index d6239c5dc..41eb8ccff 100644
--- a/ipc/app/moz.build
+++ b/ipc/app/moz.build
@@ -16,6 +16,15 @@ LOCAL_INCLUDES += [
'/xpcom/base',
]
+# We link GMPLoader into plugin-container on desktop so that its code is
+# covered by the desktop DRM vendor's voucher.
+SOURCES += [
+ '../../dom/media/gmp/GMPLoader.cpp',
+]
+USE_LIBS += [
+ 'rlz',
+]
+
# DELAYLOAD_DLLS in this block ensures that the DLL blocklist is functional
if CONFIG['OS_ARCH'] == 'WINNT':
DELAYLOAD_DLLS += [
diff --git a/ipc/contentproc/plugin-container.cpp b/ipc/contentproc/plugin-container.cpp
index a85f24d34..0e59e9d7a 100644
--- a/ipc/contentproc/plugin-container.cpp
+++ b/ipc/contentproc/plugin-container.cpp
@@ -20,6 +20,14 @@
#include <unistd.h>
#endif
+#include "GMPLoader.h"
+
+mozilla::gmp::SandboxStarter*
+MakeSandboxStarter()
+{
+ return nullptr;
+}
+
int
content_process_main(int argc, char* argv[])
{
@@ -42,6 +50,14 @@ content_process_main(int argc, char* argv[])
SetDllDirectoryW(L"");
}
#endif
+#ifdef MOZ_PLUGIN_CONTAINER
+ // On desktop, the GMPLoader lives in plugin-container, so that its
+ // code can be covered by an EME/GMP vendor's voucher.
+ nsAutoPtr<mozilla::gmp::SandboxStarter> starter(MakeSandboxStarter());
+ if (XRE_GetProcessType() == GeckoProcessType_GMPlugin) {
+ childData.gmpLoader = mozilla::gmp::CreateGMPLoader(starter);
+ }
+#endif
nsresult rv = XRE_InitChildProcess(argc, argv, &childData);
NS_ENSURE_SUCCESS(rv, 1);
diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp
index 29dafb8c2..207ce6f2f 100644
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -171,6 +171,8 @@ static void Shutdown();
#include "MediaManager.h"
+#include "GMPService.h"
+
#include "nsScriptError.h"
#include "mozilla/TextInputProcessor.h"
@@ -184,6 +186,7 @@ using mozilla::dom::workers::WorkerDebuggerManager;
using mozilla::dom::UDPSocketChild;
using mozilla::dom::time::TimeService;
using mozilla::net::StreamingProtocolControllerService;
+using mozilla::gmp::GeckoMediaPluginService;
#define NS_EDITORCOMMANDTABLE_CID \
{ 0x4f5e62b8, 0xd659, 0x4156, \
@@ -501,6 +504,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsStructuredCloneContainer)
NS_GENERIC_FACTORY_CONSTRUCTOR(OSFileConstantsService)
NS_GENERIC_FACTORY_CONSTRUCTOR(UDPSocketChild)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GeckoMediaPluginService, GeckoMediaPluginService::GetGeckoMediaPluginService)
+
NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptError)
#ifdef ACCESSIBILITY
@@ -875,6 +880,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
{ &kNS_POWERMANAGERSERVICE_CID, false, nullptr, nsIPowerManagerServiceConstructor, Module::ALLOW_IN_GPU_PROCESS },
{ &kOSFILECONSTANTSSERVICE_CID, true, nullptr, OSFileConstantsServiceConstructor },
{ &kUDPSOCKETCHILD_CID, false, nullptr, UDPSocketChildConstructor },
+ { &kGECKO_MEDIA_PLUGIN_SERVICE_CID, true, nullptr, GeckoMediaPluginServiceConstructor },
{ &kNS_TIMESERVICE_CID, false, nullptr, nsITimeServiceConstructor },
{ &kNS_MEDIASTREAMCONTROLLERSERVICE_CID, false, nullptr, nsIStreamingProtocolControllerServiceConstructor },
{ &kNS_MEDIAMANAGERSERVICE_CID, false, nullptr, nsIMediaManagerServiceConstructor },
@@ -992,6 +998,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
{ "@mozilla.org/accessibilityService;1", &kNS_ACCESSIBILITY_SERVICE_CID },
{ "@mozilla.org/accessibleRetrieval;1", &kNS_ACCESSIBILITY_SERVICE_CID },
#endif
+ { "@mozilla.org/gecko-media-plugin-service;1", &kGECKO_MEDIA_PLUGIN_SERVICE_CID },
{ "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID },
{ NS_SCRIPTERROR_CONTRACTID, &kNS_SCRIPTERROR_CID },
{ nullptr }
diff --git a/modules/ForgetAboutSite.jsm b/modules/ForgetAboutSite.jsm
index f2f4cd58e..9d7e512a8 100644
--- a/modules/ForgetAboutSite.jsm
+++ b/modules/ForgetAboutSite.jsm
@@ -84,6 +84,15 @@ this.ForgetAboutSite = {
throw new Error("Exception thrown while clearning cookies: " + ex);
}));
+ // EME
+ promises.push(Task.spawn(function*() {
+ let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+ getService(Ci.mozIGeckoMediaPluginChromeService);
+ mps.forgetThisSite(aDomain, JSON.stringify({}));
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
+ }));
+
// Plugin data
const phInterface = Ci.nsIPluginHost;
const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py
index 55da508e4..876a53a81 100644
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -925,6 +925,16 @@ class GTestCommands(MachCommandBase):
# https://code.google.com/p/googletest/wiki/AdvancedGuide#Running_Test_Programs:_Advanced_Options
gtest_env = {b'GTEST_FILTER': gtest_filter}
+ # Note: we must normalize the path here so that gtest on Windows sees
+ # a MOZ_GMP_PATH which has only Windows dir seperators, because
+ # nsILocalFile cannot open the paths with non-Windows dir seperators.
+ xre_path = os.path.join(os.path.normpath(self.topobjdir), "dist", "bin")
+ gtest_env["MOZ_XRE_DIR"] = xre_path
+ gtest_env["MOZ_GMP_PATH"] = os.pathsep.join(
+ os.path.join(xre_path, p, "1.0")
+ for p in ('gmp-fake', 'gmp-fakeopenh264')
+ )
+
gtest_env[b"MOZ_RUN_GTEST"] = b"True"
if shuffle:
diff --git a/system/graphics/layers/ipc/PAPZCTreeManager.ipdl b/system/graphics/layers/ipc/PAPZCTreeManager.ipdl
index 134a222ad..21d899f91 100644
--- a/system/graphics/layers/ipc/PAPZCTreeManager.ipdl
+++ b/system/graphics/layers/ipc/PAPZCTreeManager.ipdl
@@ -8,10 +8,6 @@ include "ipc/nsGUIEventIPC.h";
include protocol PCompositorBridge;
-// Workaround to prevent error if PContentChild.cpp & PAPZCTreeManagerChild.cpp
-// are put into different UnifiedProtocolsXX.cpp files.
-include "mozilla/dom/TabMessageUtils.h";
-
using CSSRect from "Units.h";
using LayoutDeviceCoord from "Units.h";
using LayoutDeviceIntPoint from "Units.h";
diff --git a/system/platform-prefs.js b/system/platform-prefs.js
index 2a8acb7bc..934796ee6 100644
--- a/system/platform-prefs.js
+++ b/system/platform-prefs.js
@@ -393,6 +393,9 @@ pref("media.libavcodec.allow-obsolete", false);
#if defined(MOZ_FFVPX)
pref("media.ffvpx.enabled", true);
#endif
+pref("media.gmp.decoder.enabled", false);
+pref("media.gmp.decoder.aac", 0);
+pref("media.gmp.decoder.h264", 0);
#ifdef MOZ_RAW
pref("media.raw.enabled", true);
#endif
@@ -401,6 +404,13 @@ pref("media.opus.enabled", true);
pref("media.wave.enabled", true);
pref("media.webm.enabled", true);
+// GMP storage version number. At startup we check the version against
+// media.gmp.storage.version.observed, and if the versions don't match,
+// we clear storage and set media.gmp.storage.version.observed=expected.
+// This provides a mechanism to clear GMP storage when non-compatible
+// changes are made.
+pref("media.gmp.storage.version.expected", 1);
+
// Filter what triggers user notifications.
// See DecoderDoctorDocumentWatcher::ReportAnalysis for details.
pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMFNoSilverlight,MediaCannotInitializePulseAudio,MediaCannotPlayNoDecoders,MediaUnsupportedLibavcodec");
@@ -4325,6 +4335,42 @@ pref("browser.search.reset.whitelist", "");
pref("browser.search.official", true);
#endif
+// GMPInstallManager prefs
+
+// User-settable override to media.gmp-manager.url for testing purposes.
+//pref("media.gmp-manager.url.override", "");
+
+// Update service URL for GMP install/updates:
+pref("media.gmp-manager.url", "https://aus5.mozilla.org/update/3/GMP/60.0/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+
+// When |media.gmp-manager.cert.requireBuiltIn| is true or not specified the
+// final certificate and all certificates the connection is redirected to before
+// the final certificate for the url specified in the |media.gmp-manager.url|
+// preference must be built-in.
+pref("media.gmp-manager.cert.requireBuiltIn", true);
+
+// The |media.gmp-manager.certs.| preference branch contains branches that are
+// sequentially numbered starting at 1 that contain attribute name / value
+// pairs for the certificate used by the server that hosts the update xml file
+// as specified in the |media.gmp-manager.url| preference. When these preferences are
+// present the following conditions apply for a successful update check:
+// 1. the uri scheme must be https
+// 2. the preference name must exist as an attribute name on the certificate and
+// the value for the name must be the same as the value for the attribute name
+// on the certificate.
+// If these conditions aren't met it will be treated the same as when there is
+// no update available. This validation will not be performed when the
+// |media.gmp-manager.url.override| user preference has been set for testing updates or
+// when the |media.gmp-manager.cert.checkAttributes| preference is set to false. Also,
+// the |media.gmp-manager.url.override| preference should ONLY be used for testing.
+// IMPORTANT! app.update.certs.* prefs should also be updated if these
+// are updated.
+pref("media.gmp-manager.cert.checkAttributes", true);
+pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US");
+pref("media.gmp-manager.certs.1.commonName", "aus5.mozilla.org");
+pref("media.gmp-manager.certs.2.issuerName", "CN=thawte SSL CA - G2,O=\"thawte, Inc.\",C=US");
+pref("media.gmp-manager.certs.2.commonName", "aus5.mozilla.org");
+
// Whether or not to perform reader mode article parsing on page load.
// If this pref is disabled, we will never show a reader mode icon in the toolbar.
pref("reader.parse-on-load.enabled", true);
diff --git a/system/runtime/nsAppRunner.cpp b/system/runtime/nsAppRunner.cpp
index 32834580b..4071e7730 100644
--- a/system/runtime/nsAppRunner.cpp
+++ b/system/runtime/nsAppRunner.cpp
@@ -783,6 +783,7 @@ SYNC_ENUMS(DEFAULT, Default)
SYNC_ENUMS(PLUGIN, Plugin)
SYNC_ENUMS(CONTENT, Content)
SYNC_ENUMS(IPDLUNITTEST, IPDLUnitTest)
+SYNC_ENUMS(GMPLUGIN, GMPlugin)
SYNC_ENUMS(GPU, GPU)
// .. and ensure that that is all of them:
diff --git a/system/runtime/nsEmbedFunctions.cpp b/system/runtime/nsEmbedFunctions.cpp
index 9c1aca494..841ea2a2d 100644
--- a/system/runtime/nsEmbedFunctions.cpp
+++ b/system/runtime/nsEmbedFunctions.cpp
@@ -63,6 +63,8 @@
#include "mozilla/ipc/XPCShellEnvironment.h"
#include "mozilla/WindowsDllBlocklist.h"
+#include "GMPProcessChild.h"
+#include "GMPLoader.h"
#include "mozilla/gfx/GPUProcessImpl.h"
#include "GeckoProfiler.h"
@@ -87,6 +89,10 @@ using mozilla::dom::ContentProcess;
using mozilla::dom::ContentParent;
using mozilla::dom::ContentChild;
+using mozilla::gmp::GMPLoader;
+using mozilla::gmp::CreateGMPLoader;
+using mozilla::gmp::GMPProcessChild;
+
using mozilla::ipc::TestShellParent;
using mozilla::ipc::TestShellCommandParent;
using mozilla::ipc::XPCShellEnvironment;
@@ -234,6 +240,10 @@ XRE_InitChildProcess(int aArgc,
NS_ENSURE_ARG_POINTER(aArgv[0]);
MOZ_ASSERT(aChildData);
+ // On non-Fennec Gecko, the GMPLoader code resides in plugin-container,
+ // and we must forward it through to the GMP code here.
+ GMPProcessChild::SetGMPLoader(aChildData->gmpLoader.get());
+
#if defined(XP_WIN)
// From the --attach-console support in nsNativeAppSupportWin.cpp, but
// here we are a content child process, so we always attempt to attach
@@ -347,6 +357,9 @@ XRE_InitChildProcess(int aArgc,
// Content processes need the XPCOM/chromium frankenventloop
uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD;
break;
+ case GeckoProcessType_GMPlugin:
+ uiLoopType = MessageLoop::TYPE_DEFAULT;
+ break;
default:
uiLoopType = MessageLoop::TYPE_UI;
break;
@@ -408,6 +421,10 @@ XRE_InitChildProcess(int aArgc,
#endif
break;
+ case GeckoProcessType_GMPlugin:
+ process = new gmp::GMPProcessChild(parentPID);
+ break;
+
case GeckoProcessType_GPU:
process = new gfx::GPUProcessImpl(parentPID);
break;
diff --git a/xpcom/build/XREChildData.h b/xpcom/build/XREChildData.h
index 98a67a8e4..b4cd9895f 100644
--- a/xpcom/build/XREChildData.h
+++ b/xpcom/build/XREChildData.h
@@ -8,6 +8,12 @@
#include "mozilla/UniquePtr.h"
+namespace mozilla {
+namespace gmp {
+class GMPLoader;
+}
+}
+
/**
* Data needed to start a child process.
*/
@@ -16,6 +22,7 @@ struct XREChildData
/**
* Used to load the GMP binary.
*/
+ mozilla::UniquePtr<mozilla::gmp::GMPLoader> gmpLoader;
};
#endif // XREChildData_h
diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h
index 7efeadf40..18ccca415 100644
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -397,6 +397,8 @@ enum GeckoProcessType
GeckoProcessType_IPDLUnitTest,
+ GeckoProcessType_GMPlugin, // Gecko Media Plugin
+
GeckoProcessType_GPU, // GPU and compositor process
GeckoProcessType_End,
@@ -408,6 +410,7 @@ static const char* const kGeckoProcessTypeString[] = {
"plugin",
"tab",
"ipdlunittest",
+ "geckomediaplugin",
"gpu"
};
@@ -421,6 +424,12 @@ XRE_API(const char*,
XRE_API(void,
XRE_SetProcessType, (const char* aProcessTypeString))
+namespace mozilla {
+namespace gmp {
+class GMPLoader;
+} // namespace gmp
+} // namespace mozilla
+
XRE_API(nsresult,
XRE_InitChildProcess, (int aArgc,
char* aArgv[],
diff --git a/xpcom/system/nsIXULRuntime.idl b/xpcom/system/nsIXULRuntime.idl
index a9f249ee7..fd92cf54a 100644
--- a/xpcom/system/nsIXULRuntime.idl
+++ b/xpcom/system/nsIXULRuntime.idl
@@ -73,7 +73,8 @@ interface nsIXULRuntime : nsISupports
const unsigned long PROCESS_TYPE_PLUGIN = 1;
const unsigned long PROCESS_TYPE_CONTENT = 2;
const unsigned long PROCESS_TYPE_IPDLUNITTEST = 3;
- const unsigned long PROCESS_TYPE_GPU = 4;
+ const unsigned long PROCESS_TYPE_GMPLUGIN = 4;
+ const unsigned long PROCESS_TYPE_GPU = 5;
/**
* The type of the caller's process. Returns one of the values above.