summaryrefslogtreecommitdiff
path: root/dom
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2022-04-20 11:45:55 -0500
committerMatt A. Tobin <email@mattatobin.com>2022-04-20 11:45:55 -0500
commit87dabd4e0b724bb81db3eaeefa09cfd2c5545a36 (patch)
treef91a4de96d859747a5028abf252e87d3361006cb /dom
parent1e640f82104e57d63e268785f75914ca7d3953ce (diff)
downloadaura-central-87dabd4e0b724bb81db3eaeefa09cfd2c5545a36.tar.gz
Issue #1 - Restore Gecko Media Plugins
Diffstat (limited to 'dom')
-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
164 files changed, 22541 insertions, 19 deletions
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']