summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/media/gmp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/media/gmp')
-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/GMPCDMCallbackProxy.cpp251
-rw-r--r--dom/media/gmp/GMPCDMCallbackProxy.h72
-rw-r--r--dom/media/gmp/GMPCDMProxy.cpp799
-rw-r--r--dom/media/gmp/GMPCDMProxy.h266
-rw-r--r--dom/media/gmp/GMPCallbackBase.h21
-rw-r--r--dom/media/gmp/GMPChild.cpp646
-rw-r--r--dom/media/gmp/GMPChild.h99
-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.cpp508
-rw-r--r--dom/media/gmp/GMPDecryptorParent.h129
-rw-r--r--dom/media/gmp/GMPDecryptorProxy.h63
-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.cpp222
-rw-r--r--dom/media/gmp/GMPLoader.h113
-rw-r--r--dom/media/gmp/GMPMemoryStorage.cpp95
-rw-r--r--dom/media/gmp/GMPMessageUtils.h253
-rw-r--r--dom/media/gmp/GMPParent.cpp1167
-rw-r--r--dom/media/gmp/GMPParent.h260
-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.cpp118
-rw-r--r--dom/media/gmp/GMPProcessParent.h52
-rw-r--r--dom/media/gmp/GMPService.cpp568
-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.cpp2135
-rw-r--r--dom/media/gmp/GMPServiceParent.h279
-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.cpp229
-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.ipdl44
-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.build156
-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.cpp209
-rw-r--r--dom/media/gmp/rlz/GMPDeviceBinding.h22
-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.build42
-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/gmp/widevine-adapter/WidevineAdapter.cpp168
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineAdapter.h59
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp541
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineDecryptor.h134
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.cpp97
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.h46
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.cpp95
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.h69
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp400
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h80
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp126
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.h50
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module.h1199
-rw-r--r--dom/media/gmp/widevine-adapter/moz.build25
135 files changed, 24554 insertions, 0 deletions
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/GMPCDMCallbackProxy.cpp b/dom/media/gmp/GMPCDMCallbackProxy.cpp
new file mode 100644
index 000000000..0cbc89fff
--- /dev/null
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPCDMCallbackProxy.h"
+#include "mozilla/CDMProxy.h"
+#include "nsString.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "MainThreadUtils.h"
+#include "mozilla/EMEUtils.h"
+
+namespace mozilla {
+
+GMPCDMCallbackProxy::GMPCDMCallbackProxy(CDMProxy* aProxy)
+ : mProxy(aProxy)
+{}
+
+void
+GMPCDMCallbackProxy::SetDecryptorId(uint32_t aId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, aId] ()
+ {
+ proxy->OnSetDecryptorId(aId);
+ })
+ );}
+
+void
+GMPCDMCallbackProxy::SetSessionId(uint32_t aToken,
+ const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ aToken,
+ sid] ()
+ {
+ proxy->OnSetSessionId(aToken, sid);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, aPromiseId, aSuccess] ()
+ {
+ proxy->OnResolveLoadSessionPromise(aPromiseId, aSuccess);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ // Note: CDMProxy proxies this from non-main threads to main thread.
+ mProxy->ResolvePromise(aPromiseId);
+}
+
+void
+GMPCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ aPromiseId,
+ aException,
+ aMessage] ()
+ {
+ proxy->OnRejectPromise(aPromiseId, aException, aMessage);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ nsTArray<uint8_t> msg(aMessage);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aMessageType,
+ msg] () mutable
+ {
+ proxy->OnSessionMessage(sid, aMessageType, msg);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+ GMPTimestamp aExpiryTime)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aExpiryTime] ()
+ {
+ proxy->OnExpirationChange(sid, aExpiryTime);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ bool keyStatusesChange = false;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
+ }
+ if (keyStatusesChange) {
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnKeyStatusesChange(sid);
+ })
+ );
+ }
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnSessionClosed(sid);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ auto msg = NS_ConvertUTF8toUTF16(aMessage);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy,
+ sid,
+ aException,
+ aSystemCode,
+ msg] ()
+ {
+ proxy->OnSessionError(sid,
+ aException,
+ aSystemCode,
+ msg);
+ })
+ );
+}
+
+void
+GMPCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+ BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
+}
+
+void
+GMPCDMCallbackProxy::BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ bool keyStatusesChange = false;
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ for (size_t i = 0; i < aKeyInfos.Length(); i++) {
+ keyStatusesChange |=
+ caps.SetKeyStatus(aKeyInfos[i].mKeyId,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ aKeyInfos[i].mStatus);
+ }
+ }
+ if (keyStatusesChange) {
+ RefPtr<CDMProxy> proxy = mProxy;
+ auto sid = NS_ConvertUTF8toUTF16(aSessionId);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy, sid] ()
+ {
+ proxy->OnKeyStatusesChange(sid);
+ })
+ );
+ }
+}
+
+void
+GMPCDMCallbackProxy::Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ mProxy->OnDecrypted(aId, aResult, aDecryptedData);
+}
+
+void
+GMPCDMCallbackProxy::Terminated()
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+
+ RefPtr<CDMProxy> proxy = mProxy;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction([proxy] ()
+ {
+ proxy->Terminated();
+ })
+ );
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCDMCallbackProxy.h b/dom/media/gmp/GMPCDMCallbackProxy.h
new file mode 100644
index 000000000..3f396f597
--- /dev/null
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPCDMCallbackProxy_h_
+#define GMPCDMCallbackProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "gmp-decryption.h"
+#include "GMPDecryptorProxy.h"
+
+namespace mozilla {
+
+// Proxies call backs from the CDM on the GMP thread back to the MediaKeys
+// object on the main thread.
+class GMPCDMCallbackProxy : public GMPDecryptorProxyCallback {
+public:
+
+ void SetDecryptorId(uint32_t aId) override;
+
+ void SetSessionId(uint32_t aCreateSessionToken,
+ const nsCString& aSessionId) override;
+
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) override;
+
+ void ResolvePromise(uint32_t aPromiseId) override;
+
+ void RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aSessionId) override;
+
+ void SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) override;
+
+ void ExpirationChange(const nsCString& aSessionId,
+ UnixTime aExpiryTime) override;
+
+ void SessionClosed(const nsCString& aSessionId) override;
+
+ void SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage) override;
+
+ void Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override;
+
+ void Terminated() override;
+
+ ~GMPCDMCallbackProxy() {}
+
+private:
+ friend class GMPCDMProxy;
+ explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
+
+ void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos);
+ // Warning: Weak ref.
+ CDMProxy* mProxy;
+};
+
+} // namespace mozilla
+
+#endif // GMPCDMCallbackProxy_h_
diff --git a/dom/media/gmp/GMPCDMProxy.cpp b/dom/media/gmp/GMPCDMProxy.cpp
new file mode 100644
index 000000000..58c5596ee
--- /dev/null
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -0,0 +1,799 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPCDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/PodOperations.h"
+
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsIConsoleService.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "prenv.h"
+#include "GMPCDMCallbackProxy.h"
+#include "GMPService.h"
+#include "MainThreadUtils.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+GMPCDMProxy::GMPCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys,
+ aKeySystem,
+ aDistinctiveIdentifierRequired,
+ aPersistentStateRequired)
+ , mCrashHelper(aCrashHelper)
+ , mCDM(nullptr)
+ , mDecryptionJobCount(0)
+ , mShutdownCalled(false)
+ , mDecryptorId(0)
+ , mCreatePromiseId(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(GMPCDMProxy);
+}
+
+GMPCDMProxy::~GMPCDMProxy()
+{
+ MOZ_COUNT_DTOR(GMPCDMProxy);
+}
+
+void
+GMPCDMProxy::Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("GMPCDMProxy::Init (%s, %s) %s",
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+ nsCString pluginVersion;
+ if (!mOwnerThread) {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::Init"));
+ return;
+ }
+ mps->GetThread(getter_AddRefs(mOwnerThread));
+ if (!mOwnerThread) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get GMP thread GMPCDMProxy::Init"));
+ return;
+ }
+ }
+
+ if (aGMPName.IsEmpty()) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ nsPrintfCString("Unknown GMP for keysystem '%s'", NS_ConvertUTF16toUTF8(mKeySystem).get()));
+ return;
+ }
+
+ nsAutoPtr<InitData> data(new InitData());
+ data->mPromiseId = aPromiseId;
+ data->mOrigin = aOrigin;
+ data->mTopLevelOrigin = aTopLevelOrigin;
+ data->mGMPName = aGMPName;
+ data->mInPrivateBrowsing = aInPrivateBrowsing;
+ data->mCrashHelper = mCrashHelper;
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<InitData>>(this,
+ &GMPCDMProxy::gmp_Init,
+ Move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+#ifdef DEBUG
+bool
+GMPCDMProxy::IsOnOwnerThread()
+{
+ return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+void
+GMPCDMProxy::gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData)
+{
+ EME_LOG("GMPCDMProxy::gmp_InitDone");
+ if (mShutdownCalled) {
+ if (aCDM) {
+ aCDM->Close();
+ }
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GMPCDMProxy was shut down before init could complete"));
+ return;
+ }
+ if (!aCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GetGMPDecryptor failed to return a CDM"));
+ return;
+ }
+
+ mCDM = aCDM;
+ mCallback = new GMPCDMCallbackProxy(this);
+ mCDM->Init(mCallback,
+ mDistinctiveIdentifierRequired,
+ mPersistentStateRequired);
+
+ // Await the OnSetDecryptorId callback.
+ mCreatePromiseId = aData->mPromiseId;
+}
+
+void GMPCDMProxy::OnSetDecryptorId(uint32_t aId)
+{
+ MOZ_ASSERT(mCreatePromiseId);
+ mDecryptorId = aId;
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>(this,
+ &GMPCDMProxy::OnCDMCreated,
+ mCreatePromiseId));
+ NS_DispatchToMainThread(task);
+}
+
+class gmp_InitDoneCallback : public GetGMPDecryptorCallback
+{
+public:
+ gmp_InitDoneCallback(GMPCDMProxy* aGMPCDMProxy,
+ nsAutoPtr<GMPCDMProxy::InitData>&& aData)
+ : mGMPCDMProxy(aGMPCDMProxy),
+ mData(Move(aData))
+ {
+ }
+
+ void Done(GMPDecryptorProxy* aCDM)
+ {
+ mGMPCDMProxy->gmp_InitDone(aCDM, Move(mData));
+ }
+
+private:
+ RefPtr<GMPCDMProxy> mGMPCDMProxy;
+ nsAutoPtr<GMPCDMProxy::InitData> mData;
+};
+
+class gmp_InitGetGMPDecryptorCallback : public GetNodeIdCallback
+{
+public:
+ gmp_InitGetGMPDecryptorCallback(GMPCDMProxy* aGMPCDMProxy,
+ nsAutoPtr<GMPCDMProxy::InitData>&& aData)
+ : mGMPCDMProxy(aGMPCDMProxy),
+ mData(aData)
+ {
+ }
+
+ void Done(nsresult aResult, const nsACString& aNodeId)
+ {
+ mGMPCDMProxy->gmp_InitGetGMPDecryptor(aResult, aNodeId, Move(mData));
+ }
+
+private:
+ RefPtr<GMPCDMProxy> mGMPCDMProxy;
+ nsAutoPtr<GMPCDMProxy::InitData> mData;
+};
+
+void
+GMPCDMProxy::gmp_Init(nsAutoPtr<InitData>&& aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::gmp_Init"));
+ return;
+ }
+
+ // Make a copy before we transfer ownership of aData to the
+ // gmp_InitGetGMPDecryptorCallback.
+ InitData data(*aData);
+ UniquePtr<GetNodeIdCallback> callback(
+ new gmp_InitGetGMPDecryptorCallback(this, Move(aData)));
+ nsresult rv = mps->GetNodeId(data.mOrigin,
+ data.mTopLevelOrigin,
+ data.mGMPName,
+ data.mInPrivateBrowsing,
+ Move(callback));
+ if (NS_FAILED(rv)) {
+ RejectPromise(data.mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Call to GetNodeId() failed early"));
+ }
+}
+
+void
+GMPCDMProxy::gmp_InitGetGMPDecryptor(nsresult aResult,
+ const nsACString& aNodeId,
+ nsAutoPtr<InitData>&& aData)
+{
+ uint32_t promiseID = aData->mPromiseId;
+ if (NS_FAILED(aResult)) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("GetNodeId() called back, but with a failure result"));
+ return;
+ }
+
+ mNodeId = aNodeId;
+ MOZ_ASSERT(!GetNodeId().IsEmpty());
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ if (!mps) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::gmp_InitGetGMPDecryptor"));
+ return;
+ }
+
+ EME_LOG("GMPCDMProxy::gmp_Init (%s, %s) %s NodeId=%s",
+ NS_ConvertUTF16toUTF8(aData->mOrigin).get(),
+ NS_ConvertUTF16toUTF8(aData->mTopLevelOrigin).get(),
+ (aData->mInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"),
+ GetNodeId().get());
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
+
+ // Note: must capture helper refptr here, before the Move()
+ // when we create the GetGMPDecryptorCallback below.
+ RefPtr<GMPCrashHelper> crashHelper = Move(aData->mCrashHelper);
+ UniquePtr<GetGMPDecryptorCallback> callback(new gmp_InitDoneCallback(this,
+ Move(aData)));
+ nsresult rv = mps->GetGMPDecryptor(crashHelper,
+ &tags,
+ GetNodeId(),
+ Move(callback));
+ if (NS_FAILED(rv)) {
+ RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Call to GetGMPDecryptor() failed early"));
+ }
+}
+
+void
+GMPCDMProxy::OnCDMCreated(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ MOZ_ASSERT(!GetNodeId().IsEmpty());
+ if (mCDM) {
+ mKeys->OnCDMCreated(aPromiseId, GetNodeId(), mCDM->GetPluginId());
+ } else {
+ // No CDM? Just reject the promise.
+ mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
+ }
+}
+
+void
+GMPCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<CreateSessionData> data(new CreateSessionData());
+ data->mSessionType = aSessionType;
+ data->mCreateSessionToken = aCreateSessionToken;
+ data->mPromiseId = aPromiseId;
+ data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
+ data->mInitData = Move(aInitData);
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<CreateSessionData>>(this, &GMPCDMProxy::gmp_CreateSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+GMPSessionType
+ToGMPSessionType(dom::MediaKeySessionType aSessionType) {
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary: return kGMPTemporySession;
+ case dom::MediaKeySessionType::Persistent_license: return kGMPPersistentSession;
+ default: return kGMPTemporySession;
+ };
+};
+
+void
+GMPCDMProxy::gmp_CreateSession(nsAutoPtr<CreateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_CreateSession"));
+ return;
+ }
+ mCDM->CreateSession(aData->mCreateSessionToken,
+ aData->mPromiseId,
+ aData->mInitDataType,
+ aData->mInitData,
+ ToGMPSessionType(aData->mSessionType));
+}
+
+void
+GMPCDMProxy::LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_LoadSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_LoadSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_LoadSession"));
+ return;
+ }
+ mCDM->LoadSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ nsAutoPtr<SetServerCertificateData> data(new SetServerCertificateData());
+ data->mPromiseId = aPromiseId;
+ data->mCert = Move(aCert);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SetServerCertificateData>>(this, &GMPCDMProxy::gmp_SetServerCertificate, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_SetServerCertificate"));
+ return;
+ }
+ mCDM->SetServerCertificate(aData->mPromiseId, aData->mCert);
+}
+
+void
+GMPCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<UpdateSessionData> data(new UpdateSessionData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ data->mResponse = Move(aResponse);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<UpdateSessionData>>(this, &GMPCDMProxy::gmp_UpdateSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_UpdateSession"));
+ return;
+ }
+ mCDM->UpdateSession(aData->mPromiseId,
+ aData->mSessionId,
+ aData->mResponse);
+}
+
+void
+GMPCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_CloseSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_CloseSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_CloseSession"));
+ return;
+ }
+ mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this, &GMPCDMProxy::gmp_RemoveSession, data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+GMPCDMProxy::gmp_RemoveSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in gmp_RemoveSession"));
+ return;
+ }
+ mCDM->RemoveSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+GMPCDMProxy::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mKeys.Clear();
+ // Note: This may end up being the last owning reference to the GMPCDMProxy.
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(this, &GMPCDMProxy::gmp_Shutdown));
+ if (mOwnerThread) {
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+}
+
+void
+GMPCDMProxy::gmp_Shutdown()
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ mShutdownCalled = true;
+
+ // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
+ for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
+ DecryptJob* job = mDecryptionJobs[i];
+ job->PostResult(AbortedErr);
+ }
+ mDecryptionJobs.Clear();
+
+ if (mCDM) {
+ mCDM->Close();
+ mCDM = nullptr;
+ }
+}
+
+void
+GMPCDMProxy::RejectPromise(PromiseId aId, nsresult aCode,
+ const nsCString& aReason)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, aCode, aReason);
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
+ aReason));
+ NS_DispatchToMainThread(task);
+ }
+}
+
+void
+GMPCDMProxy::ResolvePromise(PromiseId aId)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("GMPCDMProxy unable to resolve promise!");
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<PromiseId>(this,
+ &GMPCDMProxy::ResolvePromise,
+ aId);
+ NS_DispatchToMainThread(task);
+ }
+}
+
+const nsCString&
+GMPCDMProxy::GetNodeId() const
+{
+ return mNodeId;
+}
+
+void
+GMPCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ RefPtr<dom::MediaKeySession> session(mKeys->GetPendingSession(aCreateSessionToken));
+ if (session) {
+ session->SetSessionId(aSessionId);
+ }
+}
+
+void
+GMPCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void
+GMPCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void
+GMPCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void
+GMPCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ GMPTimestamp aExpiryTime)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->SetExpiration(static_cast<double>(aExpiryTime));
+ }
+}
+
+void
+GMPCDMProxy::OnSessionClosed(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void
+GMPCDMProxy::OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ gmp_Decrypted(aId, aResult, aDecryptedData);
+}
+
+static void
+LogToConsole(const nsAString& aMsg)
+{
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+ nsAutoString msg(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+void
+GMPCDMProxy::OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyError(aSystemCode);
+ }
+ LogToConsole(aMsg);
+}
+
+void
+GMPCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, aDOMException, aMsg);
+}
+
+const nsString&
+GMPCDMProxy::KeySystem() const
+{
+ return mKeySystem;
+}
+
+CDMCaps&
+GMPCDMProxy::Capabilites() {
+ return mCapabilites;
+}
+
+RefPtr<GMPCDMProxy::DecryptPromise>
+GMPCDMProxy::Decrypt(MediaRawData* aSample)
+{
+ RefPtr<DecryptJob> job(new DecryptJob(aSample));
+ RefPtr<DecryptPromise> promise(job->Ensure());
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<RefPtr<DecryptJob>>(this, &GMPCDMProxy::gmp_Decrypt, job));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ return promise;
+}
+
+void
+GMPCDMProxy::gmp_Decrypt(RefPtr<DecryptJob> aJob)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ aJob->PostResult(AbortedErr);
+ return;
+ }
+
+ aJob->mId = ++mDecryptionJobCount;
+ nsTArray<uint8_t> data;
+ data.AppendElements(aJob->mSample->Data(), aJob->mSample->Size());
+ mCDM->Decrypt(aJob->mId, aJob->mSample->mCrypto, data);
+ mDecryptionJobs.AppendElement(aJob.forget());
+}
+
+void
+GMPCDMProxy::gmp_Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+#ifdef DEBUG
+ bool jobIdFound = false;
+#endif
+ for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
+ DecryptJob* job = mDecryptionJobs[i];
+ if (job->mId == aId) {
+#ifdef DEBUG
+ jobIdFound = true;
+#endif
+ job->PostResult(aResult, aDecryptedData);
+ mDecryptionJobs.RemoveElementAt(i);
+ }
+ }
+#ifdef DEBUG
+ if (!jobIdFound) {
+ NS_WARNING("GMPDecryptorChild returned incorrect job ID");
+ }
+#endif
+}
+
+void
+GMPCDMProxy::DecryptJob::PostResult(DecryptStatus aResult)
+{
+ nsTArray<uint8_t> empty;
+ PostResult(aResult, empty);
+}
+
+void
+GMPCDMProxy::DecryptJob::PostResult(DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ if (aDecryptedData.Length() != mSample->Size()) {
+ NS_WARNING("CDM returned incorrect number of decrypted bytes");
+ }
+ if (aResult == Ok) {
+ nsAutoPtr<MediaRawDataWriter> writer(mSample->CreateWriter());
+ PodCopy(writer->Data(),
+ aDecryptedData.Elements(),
+ std::min<size_t>(aDecryptedData.Length(), mSample->Size()));
+ } else if (aResult == NoKeyErr) {
+ NS_WARNING("CDM returned NoKeyErr");
+ // We still have the encrypted sample, so we can re-enqueue it to be
+ // decrypted again once the key is usable again.
+ } else {
+ nsAutoCString str("CDM returned decode failure DecryptStatus=");
+ str.AppendInt(aResult);
+ NS_WARNING(str.get());
+ }
+ mPromise.Resolve(DecryptResult(aResult, mSample), __func__);
+}
+
+void
+GMPCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds)
+{
+ CDMCaps::AutoLock caps(Capabilites());
+ caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+void
+GMPCDMProxy::Terminated()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_WARNING("CDM terminated");
+ if (mCreatePromiseId) {
+ RejectPromise(mCreatePromiseId,
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ NS_LITERAL_CSTRING("Crashed waiting for CDM to initialize"));
+ mCreatePromiseId = 0;
+ }
+ if (!mKeys.IsNull()) {
+ mKeys->Terminated();
+ }
+}
+
+uint32_t
+GMPCDMProxy::GetDecryptorId()
+{
+ return mDecryptorId;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCDMProxy.h b/dom/media/gmp/GMPCDMProxy.h
new file mode 100644
index 000000000..84b59d9e4
--- /dev/null
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPCDMProxy_h_
+#define GMPCDMProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "GMPCDMCallbackProxy.h"
+#include "GMPDecryptorProxy.h"
+
+namespace mozilla {
+class MediaRawData;
+
+// Implementation of CDMProxy which is based on GMP architecture.
+class GMPCDMProxy : public CDMProxy {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPCDMProxy, override)
+
+ typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
+
+ GMPCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ void Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing) override;
+
+ void OnSetDecryptorId(uint32_t aId) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) override;
+
+ void LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId) override;
+
+ void SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) override;
+
+ void UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) override;
+
+ void CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void Shutdown() override;
+
+ void Terminated() override;
+
+ const nsCString& GetNodeId() const override;
+
+ void OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) override;
+
+ void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
+
+ void OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage) override;
+
+ void OnExpirationChange(const nsAString& aSessionId,
+ GMPTimestamp aExpiryTime) override;
+
+ void OnSessionClosed(const nsAString& aSessionId) override;
+
+ void OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg) override;
+
+ void OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg) override;
+
+ RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override;
+
+ void OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void RejectPromise(PromiseId aId, nsresult aExceptionCode,
+ const nsCString& aReason) override;
+
+ void ResolvePromise(PromiseId aId) override;
+
+ const nsString& KeySystem() const override;
+
+ CDMCaps& Capabilites() override;
+
+ void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+ void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds) override;
+
+#ifdef DEBUG
+ bool IsOnOwnerThread() override;
+#endif
+
+ uint32_t GetDecryptorId() override;
+
+private:
+ friend class gmp_InitDoneCallback;
+ friend class gmp_InitGetGMPDecryptorCallback;
+
+ struct InitData {
+ uint32_t mPromiseId;
+ nsString mOrigin;
+ nsString mTopLevelOrigin;
+ nsString mGMPName;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+ bool mInPrivateBrowsing;
+ };
+
+ // GMP thread only.
+ void gmp_Init(nsAutoPtr<InitData>&& aData);
+ void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
+ void gmp_InitGetGMPDecryptor(nsresult aResult,
+ const nsACString& aNodeId,
+ nsAutoPtr<InitData>&& aData);
+
+ // GMP thread only.
+ void gmp_Shutdown();
+
+ // Main thread only.
+ void OnCDMCreated(uint32_t aPromiseId);
+
+ struct CreateSessionData {
+ dom::MediaKeySessionType mSessionType;
+ uint32_t mCreateSessionToken;
+ PromiseId mPromiseId;
+ nsCString mInitDataType;
+ nsTArray<uint8_t> mInitData;
+ };
+ // GMP thread only.
+ void gmp_CreateSession(nsAutoPtr<CreateSessionData> aData);
+
+ struct SessionOpData {
+ PromiseId mPromiseId;
+ nsCString mSessionId;
+ };
+ // GMP thread only.
+ void gmp_LoadSession(nsAutoPtr<SessionOpData> aData);
+
+ struct SetServerCertificateData {
+ PromiseId mPromiseId;
+ nsTArray<uint8_t> mCert;
+ };
+ // GMP thread only.
+ void gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData);
+
+ struct UpdateSessionData {
+ PromiseId mPromiseId;
+ nsCString mSessionId;
+ nsTArray<uint8_t> mResponse;
+ };
+ // GMP thread only.
+ void gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData);
+
+ // GMP thread only.
+ void gmp_CloseSession(nsAutoPtr<SessionOpData> aData);
+
+ // GMP thread only.
+ void gmp_RemoveSession(nsAutoPtr<SessionOpData> aData);
+
+ class DecryptJob {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecryptJob)
+
+ explicit DecryptJob(MediaRawData* aSample)
+ : mId(0)
+ , mSample(aSample)
+ {
+ }
+
+ void PostResult(DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData);
+ void PostResult(DecryptStatus aResult);
+
+ RefPtr<DecryptPromise> Ensure() {
+ return mPromise.Ensure(__func__);
+ }
+
+ uint32_t mId;
+ RefPtr<MediaRawData> mSample;
+ private:
+ ~DecryptJob() {}
+ MozPromiseHolder<DecryptPromise> mPromise;
+ };
+ // GMP thread only.
+ void gmp_Decrypt(RefPtr<DecryptJob> aJob);
+
+ // GMP thread only.
+ void gmp_Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData);
+
+ class RejectPromiseTask : public Runnable {
+ public:
+ RejectPromiseTask(GMPCDMProxy* aProxy,
+ PromiseId aId,
+ nsresult aCode,
+ const nsCString& aReason)
+ : mProxy(aProxy)
+ , mId(aId)
+ , mCode(aCode)
+ , mReason(aReason)
+ {
+ }
+ NS_IMETHOD Run() override {
+ mProxy->RejectPromise(mId, mCode, mReason);
+ return NS_OK;
+ }
+ private:
+ RefPtr<GMPCDMProxy> mProxy;
+ PromiseId mId;
+ nsresult mCode;
+ nsCString mReason;
+ };
+
+ ~GMPCDMProxy();
+
+ GMPCrashHelper* mCrashHelper;
+
+ GMPDecryptorProxy* mCDM;
+
+ nsAutoPtr<GMPCDMCallbackProxy> mCallback;
+
+ // Decryption jobs sent to CDM, awaiting result.
+ // GMP thread only.
+ nsTArray<RefPtr<DecryptJob>> mDecryptionJobs;
+
+ // Number of buffers we've decrypted. Used to uniquely identify
+ // decryption jobs sent to CDM. Note we can't just use the length of
+ // mDecryptionJobs as that shrinks as jobs are completed and removed
+ // from it.
+ // GMP thread only.
+ uint32_t mDecryptionJobCount;
+
+ // True if GMPCDMProxy::gmp_Shutdown was called.
+ // GMP thread only.
+ bool mShutdownCalled;
+
+ uint32_t mDecryptorId;
+
+ PromiseId mCreatePromiseId;
+};
+
+
+} // namespace mozilla
+
+#endif // GMPCDMProxy_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..953dae3c6
--- /dev/null
+++ b/dom/media/gmp/GMPChild.cpp
@@ -0,0 +1,646 @@
+/* -*- 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/dom/CrashReporterChild.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPUtils.h"
+#include "prio.h"
+#include "base/task.h"
+#include "widevine-adapter/WidevineAdapter.h"
+
+using namespace mozilla::ipc;
+using mozilla::dom::CrashReporterChild;
+
+static const int MAX_VOUCHER_LENGTH = 500000;
+
+#ifdef XP_WIN
+#include <stdlib.h> // for _exit()
+#else
+#include <unistd.h> // for _exit()
+#endif
+
+#if defined(MOZ_GMP_SANDBOX)
+#if defined(XP_MACOSX)
+#include "mozilla/Sandbox.h"
+#endif
+#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(XP_MACOSX)
+ nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".dylib");
+#elif 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 not defined
+#endif
+ aLibFile->AppendRelativePath(binaryName);
+ return true;
+}
+
+#if !defined(XP_MACOSX) || !defined(MOZ_GMP_SANDBOX)
+static bool
+GetPluginFile(const nsAString& aPluginPath,
+ nsCOMPtr<nsIFile>& aLibFile)
+{
+ nsCOMPtr<nsIFile> unusedlibDir;
+ return GetPluginFile(aPluginPath, unusedlibDir, aLibFile);
+}
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+static nsCString
+GetNativeTarget(nsIFile* aFile)
+{
+ bool isLink;
+ nsCString path;
+ aFile->IsSymlink(&isLink);
+ if (isLink) {
+ aFile->GetNativeTarget(path);
+ } else {
+ aFile->GetNativePath(path);
+ }
+ return path;
+}
+
+static bool
+GetPluginPaths(const nsAString& aPluginPath,
+ nsCString &aPluginDirectoryPath,
+ nsCString &aPluginFilePath)
+{
+ nsCOMPtr<nsIFile> libDirectory, libFile;
+ if (!GetPluginFile(aPluginPath, libDirectory, libFile)) {
+ return false;
+ }
+
+ // Mac sandbox rules expect paths to actual files and directories -- not
+ // soft links.
+ libDirectory->Normalize();
+ aPluginDirectoryPath = GetNativeTarget(libDirectory);
+
+ libFile->Normalize();
+ aPluginFilePath = GetNativeTarget(libFile);
+
+ return true;
+}
+
+static bool
+GetAppPaths(nsCString &aAppPath, nsCString &aAppBinaryPath)
+{
+ nsAutoCString appPath;
+ nsAutoCString appBinaryPath(
+ (CommandLine::ForCurrentProcess()->argv()[0]).c_str());
+
+ nsAutoCString::const_iterator start, end;
+ appBinaryPath.BeginReading(start);
+ appBinaryPath.EndReading(end);
+ if (RFindInReadable(NS_LITERAL_CSTRING(".app/Contents/MacOS/"), start, end)) {
+ end = start;
+ ++end; ++end; ++end; ++end;
+ appBinaryPath.BeginReading(start);
+ appPath.Assign(Substring(start, end));
+ } else {
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> app, appBinary;
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath),
+ true, getter_AddRefs(app));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appBinaryPath),
+ true, getter_AddRefs(appBinary));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // Mac sandbox rules expect paths to actual files and directories -- not
+ // soft links.
+ aAppPath = GetNativeTarget(app);
+ appBinaryPath = GetNativeTarget(appBinary);
+
+ return true;
+}
+
+bool
+GMPChild::SetMacSandboxInfo(MacSandboxPluginType aPluginType)
+{
+ if (!mGMPLoader) {
+ return false;
+ }
+ nsAutoCString pluginDirectoryPath, pluginFilePath;
+ if (!GetPluginPaths(mPluginPath, pluginDirectoryPath, pluginFilePath)) {
+ return false;
+ }
+ nsAutoCString appPath, appBinaryPath;
+ if (!GetAppPaths(appPath, appBinaryPath)) {
+ return false;
+ }
+
+ MacSandboxInfo info;
+ info.type = MacSandboxType_Plugin;
+ info.pluginInfo.type = aPluginType;
+ info.pluginInfo.pluginPath.assign(pluginDirectoryPath.get());
+ info.pluginInfo.pluginBinaryPath.assign(pluginFilePath.get());
+ info.appPath.assign(appPath.get());
+ info.appBinaryPath.assign(appBinaryPath.get());
+
+ mGMPLoader->SetSandboxInfo(&info);
+ return true;
+}
+#endif // XP_MACOSX && MOZ_GMP_SANDBOX
+
+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;
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ SendPCrashReporterConstructor(CrashReporter::CurrentThreadId());
+#endif
+
+ 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)
+{
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+ nsAutoCString pluginDirectoryPath, pluginFilePath;
+ if (!GetPluginPaths(mPluginPath, pluginDirectoryPath, pluginFilePath)) {
+ MOZ_CRASH("Error scanning plugin path");
+ }
+ aOutLibPath.Assign(pluginFilePath);
+ return true;
+#else
+ 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;
+#endif
+}
+
+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;
+ }
+
+ bool isWidevine = aAdapter.EqualsLiteral("widevine");
+#if defined(MOZ_GMP_SANDBOX) && defined(XP_MACOSX)
+ MacSandboxPluginType pluginType = MacSandboxPluginType_GMPlugin_Default;
+ if (isWidevine) {
+ pluginType = MacSandboxPluginType_GMPlugin_EME_Widevine;
+ }
+ if (!SetMacSandboxInfo(pluginType)) {
+ NS_WARNING("Failed to set Mac GMP sandbox info");
+ delete platformAPI;
+ return false;
+ }
+#endif
+
+ GMPAdapter* adapter = (isWidevine) ? new WidevineAdapter() : 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");
+ }
+}
+
+mozilla::dom::PCrashReporterChild*
+GMPChild::AllocPCrashReporterChild(const NativeThreadId& aThread)
+{
+ return new CrashReporterChild();
+}
+
+bool
+GMPChild::DeallocPCrashReporterChild(PCrashReporterChild* aCrashReporter)
+{
+ delete aCrashReporter;
+ return true;
+}
+
+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..d5314cf96
--- /dev/null
+++ b/dom/media/gmp/GMPChild.h
@@ -0,0 +1,99 @@
+/* -*- 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;
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+ bool SetMacSandboxInfo(MacSandboxPluginType aPluginType);
+#endif
+
+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;
+
+ PCrashReporterChild* AllocPCrashReporterChild(const NativeThreadId& aThread) override;
+ bool DeallocPCrashReporterChild(PCrashReporterChild*) 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..1f8b9a7a8
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -0,0 +1,508 @@
+/* -*- 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 "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)
+{
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SetDecryptorId(aId);
+ 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)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvSetSessionId(token=%u, sessionId='%s')",
+ this, aCreateSessionId, aSessionId.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SetSessionId(aCreateSessionId, aSessionId);
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+ const bool& aSuccess)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvResolveLoadSessionPromise(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ResolveLoadSessionPromise(aPromiseId, aSuccess);
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvResolvePromise(promiseId=%u)",
+ this, aPromiseId));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ResolvePromise(aPromiseId);
+ 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)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')",
+ this, aPromiseId, aException, aMessage.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->RejectPromise(aPromiseId, GMPExToNsresult(aException), aMessage);
+ return true;
+}
+
+
+static dom::MediaKeyMessageType
+ToMediaKeyMessageType(GMPSessionMessageType aMessageType) {
+ switch (aMessageType) {
+ case kGMPLicenseRequest: return dom::MediaKeyMessageType::License_request;
+ case kGMPLicenseRenewal: return dom::MediaKeyMessageType::License_renewal;
+ case kGMPLicenseRelease: return dom::MediaKeyMessageType::License_release;
+ case kGMPIndividualizationRequest: return dom::MediaKeyMessageType::Individualization_request;
+ default: return dom::MediaKeyMessageType::License_request;
+ };
+};
+
+bool
+GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId,
+ const GMPSessionMessageType& aMessageType,
+ nsTArray<uint8_t>&& aMessage)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionMessage(sessionId='%s', type=%d, msg='%s')",
+ this, aSessionId.get(), aMessageType, ToBase64(aMessage).get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionMessage(aSessionId, ToMediaKeyMessageType(aMessageType), aMessage);
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId,
+ const double& aExpiryTime)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvExpirationChange(sessionId='%s', expiry=%lf)",
+ this, aSessionId.get(), aExpiryTime));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->ExpirationChange(aSessionId, aExpiryTime);
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionClosed(sessionId='%s')",
+ this, aSessionId.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionClosed(aSessionId);
+ return true;
+}
+
+bool
+GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId,
+ const GMPDOMException& aException,
+ const uint32_t& aSystemCode,
+ const nsCString& aMessage)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvSessionError(sessionId='%s', exception=%d, sysCode=%d, msg='%s')",
+ this, aSessionId.get(),
+ aException, aSystemCode, aMessage.get()));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->SessionError(aSessionId,
+ GMPExToNsresult(aException),
+ aSystemCode,
+ aMessage);
+ return true;
+}
+
+static dom::MediaKeyStatus
+ToMediaKeyStatus(GMPMediaKeyStatus aStatus) {
+ switch (aStatus) {
+ case kGMPUsable: return dom::MediaKeyStatus::Usable;
+ case kGMPExpired: return dom::MediaKeyStatus::Expired;
+ case kGMPOutputDownscaled: return dom::MediaKeyStatus::Output_downscaled;
+ case kGMPOutputRestricted: return dom::MediaKeyStatus::Output_restricted;
+ case kGMPInternalError: return dom::MediaKeyStatus::Internal_error;
+ case kGMPReleased: return dom::MediaKeyStatus::Released;
+ case kGMPStatusPending: return dom::MediaKeyStatus::Status_pending;
+ default: return dom::MediaKeyStatus::Internal_error;
+ }
+}
+
+bool
+GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%d')",
+ this, aSessionId.get(), aKeyInfos.Length()));
+
+ if (mIsOpen) {
+ nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
+ for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(keyId=%s, gmp-status=%d)",
+ this, ToBase64(aKeyInfos[i].keyId()).get(), aKeyInfos[i].status()));
+ // If the status is kGMPUnknown, we're going to forget(remove) that key info.
+ if (aKeyInfos[i].status() != kGMPUnknown) {
+ auto status = ToMediaKeyStatus(aKeyInfos[i].status());
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
+ dom::Optional<dom::MediaKeyStatus>(status)));
+ } else {
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
+ }
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
+ }
+ return true;
+}
+
+DecryptStatus
+ToDecryptStatus(GMPErr aError)
+{
+ switch (aError) {
+ case GMPNoErr: return Ok;
+ case GMPNoKeyErr: return NoKeyErr;
+ case GMPAbortedErr: return AbortedErr;
+ default: return GenericErr;
+ }
+}
+
+bool
+GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
+ const GMPErr& aErr,
+ InfallibleTArray<uint8_t>&& aBuffer)
+{
+ LOGV(("GMPDecryptorParent[%p]::RecvDecrypted(id=%d, err=%d)",
+ this, aId, aErr));
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use a dead GMP decrypter!");
+ return false;
+ }
+ mCallback->Decrypted(aId, ToDecryptStatus(aErr), aBuffer);
+ 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..0ef31fd92
--- /dev/null
+++ b/dom/media/gmp/GMPDecryptorProxy.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 GMPDecryptorProxy_h_
+#define GMPDecryptorProxy_h_
+
+#include "mozilla/DecryptorProxyCallback.h"
+#include "GMPCallbackBase.h"
+#include "gmp-decryption.h"
+#include "nsString.h"
+
+namespace mozilla {
+class CryptoSample;
+} // namespace mozilla
+
+class GMPDecryptorProxyCallback : public DecryptorProxyCallback,
+ 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..c10208a49
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -0,0 +1,222 @@
+/* -*- 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;
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+ void SetSandboxInfo(MacSandboxInfo* aSandboxInfo) override;
+#endif
+
+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();
+ }
+}
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+void
+GMPLoaderImpl::SetSandboxInfo(MacSandboxInfo* aSandboxInfo)
+{
+ if (mSandboxStarter) {
+ mSandboxStarter->SetSandboxInfo(aSandboxInfo);
+ }
+}
+#endif
+} // namespace gmp
+} // namespace mozilla
+
diff --git a/dom/media/gmp/GMPLoader.h b/dom/media/gmp/GMPLoader.h
new file mode 100644
index 000000000..60581be2d
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.h
@@ -0,0 +1,113 @@
+/* -*- 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"
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+#include "mozilla/Sandbox.h"
+#endif
+
+namespace mozilla {
+namespace gmp {
+
+class SandboxStarter {
+public:
+ virtual ~SandboxStarter() {}
+ virtual bool Start(const char* aLibPath) = 0;
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+ // On OS X we need to set Mac-specific sandbox info just before we start the
+ // sandbox, which we don't yet know when the GMPLoader and SandboxStarter
+ // objects are created.
+ virtual void SetSandboxInfo(MacSandboxInfo* aSandboxInfo) = 0;
+#endif
+};
+
+// 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;
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+ // On OS X we need to set Mac-specific sandbox info just before we start the
+ // sandbox, which we don't yet know when the GMPLoader and SandboxStarter
+ // objects are created.
+ virtual void SetSandboxInfo(MacSandboxInfo* aSandboxInfo) = 0;
+#endif
+};
+
+// 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..75468ea9a
--- /dev/null
+++ b/dom/media/gmp/GMPParent.cpp
@@ -0,0 +1,1167 @@
+/* -*- 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"
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+#include "mozilla/SandboxInfo.h"
+#endif
+#include "GMPContentParent.h"
+#include "MediaPrefs.h"
+#include "VideoUtils.h"
+
+#include "mozilla/dom/CrashReporterParent.h"
+using mozilla::dom::CrashReporterParent;
+using mozilla::ipc::GeckoChildProcessHost;
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsPrintfCString.h"
+using CrashReporter::AnnotationTable;
+using CrashReporter::GetIDFromMinidump;
+#endif
+
+#include "mozilla/Telemetry.h"
+
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
+
+#include "mozilla/dom/WidevineCDMManifestBinding.h"
+#include "widevine-adapter/WidevineAdapter.h"
+
+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);
+#if defined(MOZ_CRASHREPORTER)
+ parent->mService->SetAsyncShutdownPluginState(parent, 'G',
+ NS_LITERAL_CSTRING("Timed out waiting for async shutdown"));
+#endif
+ 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()) {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'E',
+ NS_LITERAL_CSTRING("Last content child destroyed"));
+ }
+#endif
+ CloseIfUnused();
+ }
+#if defined(MOZ_CRASHREPORTER)
+ else {
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'F',
+ nsPrintfCString("Content child destroyed, remaining: %u", mGMPContentChildCount));
+ }
+ }
+#endif
+ 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__);
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'H',
+ NS_LITERAL_CSTRING("Sent BeginAsyncShutdown"));
+ }
+#endif
+ mAsyncShutdownInProgress = true;
+ if (!SendBeginAsyncShutdown()) {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'I',
+ NS_LITERAL_CSTRING("Could not send BeginAsyncShutdown - Aborting async shutdown"));
+ }
+#endif
+ AbortAsyncShutdown();
+ } else if (NS_FAILED(EnsureAsyncShutdownTimeoutSet())) {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'J',
+ NS_LITERAL_CSTRING("Could not start timer after sending BeginAsyncShutdown - Aborting async shutdown"));
+ }
+#endif
+ AbortAsyncShutdown();
+ }
+ }
+ } else {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'K',
+ NS_LITERAL_CSTRING("No (more) async-shutdown required"));
+ }
+#endif
+ // 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 defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'A',
+ nsPrintfCString("Sent CloseActive, content children to close: %u", mGMPContentChildCount));
+ }
+#endif
+ if (!SendCloseActive()) {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'B',
+ NS_LITERAL_CSTRING("Could not send CloseActive - Aborting async shutdown"));
+ }
+#endif
+ AbortAsyncShutdown();
+ } else if (IsUsed()) {
+ // We're expecting RecvPGMPContentChildDestroyed's -> Start async-shutdown timer now if needed.
+ if (mAsyncShutdownRequired && NS_FAILED(EnsureAsyncShutdownTimeoutSet())) {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'C',
+ NS_LITERAL_CSTRING("Could not start timer after sending CloseActive - Aborting async shutdown"));
+ }
+#endif
+ 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.
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'D',
+ NS_LITERAL_CSTRING("Content children already destroyed"));
+ }
+#endif
+ 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 XP, and on some Vista, 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);
+}
+
+#ifdef MOZ_CRASHREPORTER
+void
+GMPParent::WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes)
+{
+ notes.Put(NS_LITERAL_CSTRING("GMPPlugin"), NS_LITERAL_CSTRING("1"));
+ notes.Put(NS_LITERAL_CSTRING("PluginFilename"),
+ NS_ConvertUTF16toUTF8(mName));
+ notes.Put(NS_LITERAL_CSTRING("PluginName"), mDisplayName);
+ notes.Put(NS_LITERAL_CSTRING("PluginVersion"), mVersion);
+}
+
+void
+GMPParent::GetCrashID(nsString& aResult)
+{
+ CrashReporterParent* cr =
+ static_cast<CrashReporterParent*>(LoneManagedOrNullAsserts(ManagedPCrashReporterParent()));
+ if (NS_WARN_IF(!cr)) {
+ return;
+ }
+
+ AnnotationTable notes(4);
+ WriteExtraDataForMinidump(notes);
+ nsCOMPtr<nsIFile> dumpFile;
+ TakeMinidump(getter_AddRefs(dumpFile), nullptr);
+ if (!dumpFile) {
+ NS_WARNING("GMP crash without crash report");
+ aResult = mName;
+ aResult += '-';
+ AppendUTF8toUTF16(mVersion, aResult);
+ return;
+ }
+ GetIDFromMinidump(dumpFile, aResult);
+ cr->GenerateCrashReportForMinidump(dumpFile, &notes);
+}
+
+static void
+GMPNotifyObservers(const uint32_t aPluginID, const nsACString& aPluginName, const nsAString& aPluginDumpID)
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ nsCOMPtr<nsIWritablePropertyBag2> propbag =
+ do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ if (obs && propbag) {
+ propbag->SetPropertyAsUint32(NS_LITERAL_STRING("pluginID"), aPluginID);
+ propbag->SetPropertyAsACString(NS_LITERAL_STRING("pluginName"), aPluginName);
+ propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), aPluginDumpID);
+ obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr);
+ }
+
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ if (service) {
+ service->RunPluginCrashCallbacks(aPluginID, aPluginName);
+ }
+}
+#endif
+void
+GMPParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
+#ifdef MOZ_CRASHREPORTER
+ if (AbnormalShutdown == aWhy) {
+ Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT,
+ NS_LITERAL_CSTRING("gmplugin"), 1);
+ nsString dumpID;
+ GetCrashID(dumpID);
+
+ // NotifyObservers is mainthread-only
+ NS_DispatchToMainThread(WrapRunnableNM(&GMPNotifyObservers,
+ mPluginId, mDisplayName, dumpID),
+ NS_DISPATCH_NORMAL);
+ }
+#endif
+ // 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) {
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'M',
+ NS_LITERAL_CSTRING("Actor destroyed"));
+ }
+#endif
+ 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);
+ }
+}
+
+mozilla::dom::PCrashReporterParent*
+GMPParent::AllocPCrashReporterParent(const NativeThreadId& aThread)
+{
+#ifndef MOZ_CRASHREPORTER
+ MOZ_ASSERT(false, "Should only be sent if crash reporting is enabled.");
+#endif
+ CrashReporterParent* cr = new CrashReporterParent();
+ cr->SetChildData(aThread, GeckoProcessType_GMPlugin);
+ return cr;
+}
+
+bool
+GMPParent::DeallocPCrashReporterParent(PCrashReporterParent* aCrashReporter)
+{
+ delete aCrashReporter;
+ return true;
+}
+
+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);
+ }
+
+ // Maybe this is the Widevine adapted plugin?
+ nsCOMPtr<nsIFile> manifestFile;
+ rv = mDirectory->Clone(getter_AddRefs(manifestFile));
+ if (NS_FAILED(rv)) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json"));
+ return ReadChromiumManifestFile(manifestFile);
+}
+
+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;
+
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+ if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
+ printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
+ " but this system can't sandbox it; not loading.\n",
+ mDisplayName.get());
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+#endif
+#ifdef XP_WIN
+ // Adobe GMP doesn't work without SSE2. Check the tags to see if
+ // the decryptor is for the Adobe GMP, and refuse to load it if
+ // SSE2 isn't supported.
+ if (cap.mAPITags.Contains(kEMEKeySystemPrimetime) &&
+ !mozilla::supports_sse2()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+#endif // XP_WIN
+ }
+
+ 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)
+{
+ LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
+
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::WidevineCDMManifest m;
+ if (!m.Init(aJSON)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsresult ignored; // Note: ToInteger returns 0 on failure.
+ if (!WidevineAdapter::Supports(m.mX_cdm_module_versions.ToInteger(&ignored),
+ m.mX_cdm_interface_versions.ToInteger(&ignored),
+ m.mX_cdm_host_versions.ToInteger(&ignored))) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
+ mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
+ mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
+
+ GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER));
+ video.mAPITags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ video.mAPITags.AppendElement(kEMEKeySystemWidevine);
+ mCapabilities.AppendElement(Move(video));
+
+ GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
+ decrypt.mAPITags.AppendElement(kEMEKeySystemWidevine);
+ mCapabilities.AppendElement(Move(decrypt));
+
+ MOZ_ASSERT(mName.EqualsLiteral("widevinecdm"));
+ mAdapter = NS_LITERAL_STRING("widevine");
+#ifdef XP_WIN
+ mLibs = NS_LITERAL_CSTRING("dxva2.dll");
+#endif
+
+ return GenericPromise::CreateAndResolve(true, __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);
+#if defined(MOZ_CRASHREPORTER)
+ if (mService) {
+ mService->SetAsyncShutdownPluginState(this, 'L',
+ NS_LITERAL_CSTRING("Received AsyncShutdownComplete"));
+ }
+#endif
+ 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..91a6fb429
--- /dev/null
+++ b/dom/media/gmp/GMPParent.h
@@ -0,0 +1,260 @@
+/* -*- 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;
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+
+namespace mozilla {
+namespace dom {
+class PCrashReporterParent;
+class CrashReporterParent;
+}
+}
+#endif
+
+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.
+#ifdef MOZ_CRASHREPORTER
+ void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
+ void GetCrashID(nsString& aResult);
+#endif
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ PCrashReporterParent* AllocPCrashReporterParent(const NativeThreadId& aThread) override;
+ bool DeallocPCrashReporterParent(PCrashReporterParent* aCrashReporter) 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..2fe7306a4
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.cpp
@@ -0,0 +1,118 @@
+/* -*- 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"
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+#include "WinUtils.h"
+#endif
+
+#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;
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ std::wstring wGMPPath = UTF8ToWide(mGMPPath.c_str());
+
+ // The sandbox doesn't allow file system rules where the paths contain
+ // symbolic links or junction points. Sometimes the Users folder has been
+ // moved to another drive using a junction point, so allow for this specific
+ // case. See bug 1236680 for details.
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(wGMPPath)) {
+ GMP_LOG("ResolveJunctionPointsAndSymLinks failed for GMP path=%S",
+ wGMPPath.c_str());
+ NS_WARNING("ResolveJunctionPointsAndSymLinks failed for GMP path.");
+ return false;
+ }
+ GMP_LOG("GMPProcessParent::Launch() resolved path to %S", wGMPPath.c_str());
+
+ // If the GMP path is a network path that is not mapped to a drive letter,
+ // then we need to fix the path format for the sandbox rule.
+ wchar_t volPath[MAX_PATH];
+ if (::GetVolumePathNameW(wGMPPath.c_str(), volPath, MAX_PATH) &&
+ ::GetDriveTypeW(volPath) == DRIVE_REMOTE &&
+ wGMPPath.compare(0, 2, L"\\\\") == 0) {
+ std::wstring sandboxGMPPath(wGMPPath);
+ sandboxGMPPath.insert(1, L"??\\UNC");
+ mAllowedFilesRead.push_back(sandboxGMPPath + L"\\*");
+ } else {
+ mAllowedFilesRead.push_back(wGMPPath + L"\\*");
+ }
+
+ args.push_back(WideToUTF8(wGMPPath));
+#else
+ args.push_back(mGMPPath);
+#endif
+
+ 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..65f4037ee
--- /dev/null
+++ b/dom/media/gmp/GMPService.cpp
@@ -0,0 +1,568 @@
+/* -*- 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"
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+#include "mozilla/SandboxInfo.h"
+#endif
+#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)
+{
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+ if (!SandboxInfo::Get().CanSandboxMedia()) {
+ NS_WARNING("GeckoMediaPluginService::GetGMPDecryptor: "
+ "EME decryption not available without sandboxing support.");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+#endif
+
+ 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..8741989e3
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -0,0 +1,2135 @@
+/* -*- 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"
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+#include "mozilla/SandboxInfo.h"
+#endif
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#include "nsPrintfCString.h"
+#endif
+#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)
+#ifdef MOZ_CRASHREPORTER
+ , mAsyncShutdownPluginStatesMutex("GeckoMediaPluginService::mAsyncShutdownPluginStatesMutex")
+#endif
+ , 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 void
+MoveAndOverwrite(nsIFile* aOldParentDir,
+ nsIFile* aNewParentDir,
+ const nsAString& aSubDir)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> srcDir(CloneAndAppend(aOldParentDir, aSubDir));
+ if (NS_WARN_IF(!srcDir)) {
+ return;
+ }
+
+ if (!FileExists(srcDir)) {
+ // No sub-directory to be migrated.
+ return;
+ }
+
+ // Ensure destination parent directory exists.
+ rv = aNewParentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIFile> dstDir(CloneAndAppend(aNewParentDir, aSubDir));
+ if (FileExists(dstDir)) {
+ // We must have migrated before already, and then ran an old version
+ // of Gecko again which created storage at the old location. Overwrite
+ // the previously migrated storage.
+ rv = dstDir->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // MoveTo will fail.
+ return;
+ }
+ }
+
+ rv = srcDir->MoveTo(aNewParentDir, EmptyString());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+static void
+MigratePreGecko42StorageDir(nsIFile* aOldStorageDir,
+ nsIFile* aNewStorageDir)
+{
+ MoveAndOverwrite(aOldStorageDir, aNewStorageDir, NS_LITERAL_STRING("id"));
+ MoveAndOverwrite(aOldStorageDir, aNewStorageDir, NS_LITERAL_STRING("storage"));
+}
+
+static void
+MigratePreGecko45StorageDir(nsIFile* aStorageDirBase)
+{
+ nsCOMPtr<nsIFile> adobeStorageDir(CloneAndAppend(aStorageDirBase, NS_LITERAL_STRING("gmp-eme-adobe")));
+ if (NS_WARN_IF(!adobeStorageDir)) {
+ return;
+ }
+
+ // The base storage dir in pre-45 contained "id" and "storage" subdirs.
+ // We assume all storage in the base storage dir that aren't known to GMP
+ // storage are records for the Adobe GMP.
+ MoveAndOverwrite(aStorageDirBase, adobeStorageDir, NS_LITERAL_STRING("id"));
+ MoveAndOverwrite(aStorageDirBase, adobeStorageDir, NS_LITERAL_STRING("storage"));
+}
+
+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.
+#ifdef MOZ_WIDGET_GONK
+ nsresult rv = NS_NewLocalFile(NS_LITERAL_STRING("/data/b2g/mozilla"), false, getter_AddRefs(mStorageBaseDir));
+#else
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mStorageBaseDir));
+#endif
+
+ 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;
+ }
+
+ // Prior to 42, GMP storage was stored in $profileDir/gmp/. After 42, it's
+ // stored in $profileDir/gmp/$platform/. So we must migrate any old records
+ // from the old location to the new location, for forwards compatibility.
+ MigratePreGecko42StorageDir(gmpDirWithoutPlatform, mStorageBaseDir);
+
+ // Prior to 45, GMP storage was not separated by plugin. In 45 and after,
+ // it's stored in $profile/gmp/$platform/$gmpName. So we must migrate old
+ // records from the old location to the new location, for forwards
+ // compatibility. We assume all directories in the base storage dir that
+ // aren't known to GMP storage are records for the Adobe GMP, since it
+ // was first.
+ MigratePreGecko45StorageDir(mStorageBaseDir);
+
+ 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__));
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(nullptr, '0',
+ NS_LITERAL_CSTRING("Dispatching UnloadPlugins"));
+#endif
+ gmpThread->Dispatch(
+ NewRunnableMethod(this,
+ &GeckoMediaPluginServiceParent::UnloadPlugins),
+ NS_DISPATCH_NORMAL);
+
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(nullptr, '1',
+ NS_LITERAL_CSTRING("Waiting for sync shutdown"));
+#endif
+ // Wait for UnloadPlugins() to do initial sync shutdown...
+ while (mWaitingForPluginsSyncShutdown) {
+ NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(nullptr, '4',
+ NS_LITERAL_CSTRING("Waiting for async shutdown"));
+#endif
+ // 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__));
+#if defined(MOZ_CRASHREPORTER)
+ CrashReporter::RemoveCrashReportAnnotation(
+ NS_LITERAL_CSTRING("AsyncPluginShutdown"));
+#endif
+ 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));
+#if defined(MOZ_CRASHREPORTER)
+ nsAutoCString names;
+ for (const auto& plugin : mAsyncShutdownPlugins) {
+ if (!names.IsEmpty()) { names.Append(NS_LITERAL_CSTRING(", ")); }
+ names.Append(plugin->GetDisplayName());
+ }
+ CrashReporter::AnnotateCrashReport(
+ NS_LITERAL_CSTRING("AsyncPluginShutdown"),
+ names);
+#endif
+ }
+ }
+ NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+ }
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(nullptr, '5',
+ NS_LITERAL_CSTRING("Async shutdown complete"));
+#endif
+ } 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);
+ }
+}
+
+#ifdef MOZ_CRASHREPORTER
+void
+GeckoMediaPluginServiceParent::SetAsyncShutdownPluginState(GMPParent* aGMPParent,
+ char aId,
+ const nsCString& aState)
+{
+ MutexAutoLock lock(mAsyncShutdownPluginStatesMutex);
+ if (!aGMPParent) {
+ mAsyncShutdownPluginStates.Update(NS_LITERAL_CSTRING("-"),
+ NS_LITERAL_CSTRING("-"),
+ aId,
+ aState);
+ return;
+ }
+ mAsyncShutdownPluginStates.Update(aGMPParent->GetDisplayName(),
+ nsPrintfCString("%p", aGMPParent),
+ aId,
+ aState);
+}
+
+void
+GeckoMediaPluginServiceParent::AsyncShutdownPluginStates::Update(const nsCString& aPlugin,
+ const nsCString& aInstance,
+ char aId,
+ const nsCString& aState)
+{
+ nsCString note;
+ StatesByInstance* instances = mStates.LookupOrAdd(aPlugin);
+ if (!instances) { return; }
+ State* state = instances->LookupOrAdd(aInstance);
+ if (!state) { return; }
+ state->mStateSequence += aId;
+ state->mLastStateDescription = aState;
+ note += '{';
+ bool firstPlugin = true;
+ for (auto pluginIt = mStates.ConstIter(); !pluginIt.Done(); pluginIt.Next()) {
+ if (!firstPlugin) { note += ','; } else { firstPlugin = false; }
+ note += pluginIt.Key();
+ note += ":{";
+ bool firstInstance = true;
+ for (auto instanceIt = pluginIt.UserData()->ConstIter(); !instanceIt.Done(); instanceIt.Next()) {
+ if (!firstInstance) { note += ','; } else { firstInstance = false; }
+ note += instanceIt.Key();
+ note += ":\"";
+ note += instanceIt.UserData()->mStateSequence;
+ note += '=';
+ note += instanceIt.UserData()->mLastStateDescription;
+ note += '"';
+ }
+ note += '}';
+ }
+ note += '}';
+ LOGD(("%s::%s states[%s][%s]='%c'/'%s' -> %s", __CLASS__, __FUNCTION__,
+ aPlugin.get(), aInstance.get(), aId, aState.get(), note.get()));
+ CrashReporter::AnnotateCrashReport(
+ NS_LITERAL_CSTRING("AsyncPluginShutdownStates"),
+ note);
+}
+#endif // MOZ_CRASHREPORTER
+
+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;
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(nullptr, '2',
+ NS_LITERAL_CSTRING("Starting to unload plugins"));
+#endif
+
+ 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) {
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(plugin, 'S',
+ NS_LITERAL_CSTRING("CloseActive"));
+#endif
+ plugin->CloseActive(true);
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ SetAsyncShutdownPluginState(nullptr, '3',
+ NS_LITERAL_CSTRING("Dispatching sync-shutdown-complete"));
+#endif
+ 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()
+{
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+ if (!SandboxInfo::Get().CanSandboxMedia()) {
+ if (!MediaPrefs::GMPAllowInsecure()) {
+ NS_WARNING("Denying media plugin load due to lack of sandboxing.");
+ return nullptr;
+ }
+ NS_WARNING("Loading media plugin despite lack of sandboxing.");
+ }
+#endif
+ 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..f3f43e215
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -0,0 +1,279 @@
+/* -*- 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();
+#ifdef MOZ_CRASHREPORTER
+ void SetAsyncShutdownPluginState(GMPParent* aGMPParent, char aId, const nsCString& aState);
+#endif // MOZ_CRASHREPORTER
+ 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;
+
+#ifdef MOZ_CRASHREPORTER
+ Mutex mAsyncShutdownPluginStatesMutex; // Protects mAsyncShutdownPluginStates.
+ class AsyncShutdownPluginStates
+ {
+ public:
+ void Update(const nsCString& aPlugin, const nsCString& aInstance,
+ char aId, const nsCString& aState);
+ private:
+ struct State { nsCString mStateSequence; nsCString mLastStateDescription; };
+ typedef nsClassHashtable<nsCStringHashKey, State> StatesByInstance;
+ typedef nsClassHashtable<nsCStringHashKey, StatesByInstance> StateInstancesByPlugin;
+ StateInstancesByPlugin mStates;
+ } mAsyncShutdownPluginStates;
+#endif // MOZ_CRASHREPORTER
+
+ // 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..d27523760
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsLiteralString.h"
+#include "nsCRTGlue.h"
+#include "mozilla/Base64.h"
+#include "nsISimpleEnumerator.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..b421f0280
--- /dev/null
+++ b/dom/media/gmp/PGMP.ipdl
@@ -0,0 +1,44 @@
+/* -*- 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 PCrashReporter;
+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 PCrashReporter;
+ manages PGMPTimer;
+ manages PGMPStorage;
+
+parent:
+ async PCrashReporter(NativeThreadId tid);
+ 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..3b67fb5c9
--- /dev/null
+++ b/dom/media/gmp/moz.build
@@ -0,0 +1,156 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+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',
+ 'GMPCDMCallbackProxy.h',
+ 'GMPCDMProxy.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',
+]
+
+# We link GMPLoader into xul on B2G/Fennec as its code does not need to be
+# covered by a DRM vendor's voucher.
+if CONFIG['OS_TARGET'] == 'Android':
+ SOURCES += [
+ 'GMPLoader.cpp',
+ ]
+ USE_LIBS += [
+ 'rlz',
+ ]
+
+UNIFIED_SOURCES += [
+ 'GMPAudioDecoderChild.cpp',
+ 'GMPAudioDecoderParent.cpp',
+ 'GMPAudioHost.cpp',
+ 'GMPCDMCallbackProxy.cpp',
+ 'GMPCDMProxy.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',
+ 'widevine-adapter',
+]
+
+IPDL_SOURCES += [
+ 'GMPTypes.ipdlh',
+ 'PGMP.ipdl',
+ 'PGMPAudioDecoder.ipdl',
+ 'PGMPContent.ipdl',
+ 'PGMPDecryptor.ipdl',
+ 'PGMPService.ipdl',
+ 'PGMPStorage.ipdl',
+ 'PGMPTimer.ipdl',
+ 'PGMPVideoDecoder.ipdl',
+ 'PGMPVideoEncoder.ipdl',
+]
+
+# comment this out to use Unsafe Shmem for more performance
+DEFINES['GMP_SAFE_SHMEM'] = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+# media/mtransport so we work with --disable-webrtc
+LOCAL_INCLUDES += [
+ '/media/mtransport',
+ '/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..c8aee2bcd
--- /dev/null
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDeviceBinding.h"
+#include "mozilla/Attributes.h"
+#include "prenv.h"
+
+#include <string>
+
+#ifdef XP_WIN
+#include "windows.h"
+#ifdef MOZ_SANDBOX
+#include <intrin.h>
+#include <assert.h>
+#endif
+#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"
+#ifdef MOZ_SANDBOX
+#include <intrin.h>
+#include <assert.h>
+#endif
+#endif
+
+#ifdef XP_MACOSX
+#include <assert.h>
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+#include <unistd.h>
+#include <mach/mach.h>
+#include <mach/mach_vm.h>
+#endif
+#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
+
+#if defined(XP_MACOSX) && defined(HASH_NODE_ID_WITH_DEVICE_ID)
+static mach_vm_address_t
+RegionContainingAddress(mach_vm_address_t aAddress)
+{
+ mach_port_t task;
+ kern_return_t kr = task_for_pid(mach_task_self(), getpid(), &task);
+ if (kr != KERN_SUCCESS) {
+ return 0;
+ }
+
+ mach_vm_address_t address = aAddress;
+ mach_vm_size_t size;
+ vm_region_basic_info_data_64_t info;
+ mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
+ mach_port_t object_name;
+ kr = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO_64,
+ reinterpret_cast<vm_region_info_t>(&info), &count,
+ &object_name);
+ if (kr != KERN_SUCCESS || size == 0
+ || address > aAddress || address + size <= aAddress) {
+ // mach_vm_region failed, or couldn't find region at given address.
+ return 0;
+ }
+
+ return address;
+}
+
+MOZ_NEVER_INLINE
+static bool
+GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
+{
+ mach_vm_address_t stackFrame =
+ reinterpret_cast<mach_vm_address_t>(__builtin_frame_address(0));
+ *aOutTop = reinterpret_cast<uint8_t*>(stackFrame);
+ // Kernel code shows that stack is always a single region.
+ *aOutBottom = reinterpret_cast<uint8_t*>(RegionContainingAddress(stackFrame));
+ return *aOutBottom && (*aOutBottom < *aOutTop);
+}
+#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..835704054
--- /dev/null
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..f366c2b5d
--- /dev/null
+++ b/dom/media/gmp/rlz/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# 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['MOZ_SANDBOX'] and CONFIG['OS_TARGET'] in ['WINNT', 'Darwin']:
+ DEFINES['HASH_NODE_ID_WITH_DEVICE_ID'] = 1;
+ UNIFIED_SOURCES += [
+ 'lib/string_utils.cc',
+ 'sha256.c',
+ ]
+
+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/gmp/widevine-adapter/WidevineAdapter.cpp b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
new file mode 100644
index 000000000..74b5c38e8
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineAdapter.h"
+#include "content_decryption_module.h"
+#include "VideoUtils.h"
+#include "WidevineDecryptor.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoDecoder.h"
+#include "gmp-api/gmp-entrypoints.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-video-codec.h"
+#include "gmp-api/gmp-platform.h"
+
+static const GMPPlatformAPI* sPlatform = nullptr;
+
+namespace mozilla {
+
+GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) {
+ return sPlatform->getcurrenttime(aOutTime);
+}
+
+// Call on main thread only.
+GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) {
+ return sPlatform->settimer(aTask, aTimeoutMS);
+}
+
+GMPErr GMPCreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient)
+{
+ return sPlatform->createrecord(aRecordName, aRecordNameSize, aOutRecord, aClient);
+}
+
+void
+WidevineAdapter::SetAdaptee(PRLibrary* aLib)
+{
+ mLib = aLib;
+}
+
+void* GetCdmHost(int aHostInterfaceVersion, void* aUserData)
+{
+ Log("GetCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
+ WidevineDecryptor* decryptor = reinterpret_cast<WidevineDecryptor*>(aUserData);
+ MOZ_ASSERT(decryptor);
+ return static_cast<cdm::Host_8*>(decryptor);
+}
+
+#define STRINGIFY(s) _STRINGIFY(s)
+#define _STRINGIFY(s) #s
+
+GMPErr
+WidevineAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
+{
+#ifdef ENABLE_WIDEVINE_LOG
+ if (getenv("GMP_LOG_FILE")) {
+ // Clear log file.
+ FILE* f = fopen(getenv("GMP_LOG_FILE"), "w");
+ if (f) {
+ fclose(f);
+ }
+ }
+#endif
+
+ sPlatform = aPlatformAPI;
+ if (!mLib) {
+ return GMPGenericErr;
+ }
+
+ auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
+ PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
+ if (!init) {
+ return GMPGenericErr;
+ }
+
+ Log(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
+ init();
+
+ return GMPNoErr;
+}
+
+GMPErr
+WidevineAdapter::GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId)
+{
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) {
+ if (WidevineDecryptor::GetInstance(aDecryptorId)) {
+ // We only support one CDM instance per PGMPDecryptor. Fail!
+ Log("WidevineAdapter::GMPGetAPI() Tried to create more than once CDM per IPDL actor! FAIL!");
+ return GMPQuotaExceededErr;
+ }
+ auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
+ PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
+ if (!create) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to find CreateCdmInstance",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ return GMPGenericErr;
+ }
+
+ WidevineDecryptor* decryptor = new WidevineDecryptor();
+
+ auto cdm = reinterpret_cast<cdm::ContentDecryptionModule*>(
+ create(cdm::ContentDecryptionModule::kVersion,
+ kEMEKeySystemWidevine.get(),
+ kEMEKeySystemWidevine.Length(),
+ &GetCdmHost,
+ decryptor));
+ if (!cdm) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to create cdm",
+ aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
+ return GMPGenericErr;
+ }
+ Log("cdm: 0x%x", cdm);
+ RefPtr<CDMWrapper> wrapper(new CDMWrapper(cdm, decryptor));
+ decryptor->SetCDM(wrapper, aDecryptorId);
+ *aPluginAPI = decryptor;
+
+ } else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
+ RefPtr<CDMWrapper> wrapper = WidevineDecryptor::GetInstance(aDecryptorId);
+ if (!wrapper) {
+ Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p No cdm for video decoder",
+ aAPIName, aHostAPI, aPluginAPI, thiss, aDecryptorId);
+ return GMPGenericErr;
+ }
+ *aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI),
+ wrapper);
+ }
+ return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
+}
+
+void
+WidevineAdapter::GMPShutdown()
+{
+ Log("WidevineAdapter::GMPShutdown()");
+
+ decltype(::DeinitializeCdmModule)* deinit;
+ deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
+ if (deinit) {
+ Log("DeinitializeCdmModule()");
+ deinit();
+ }
+}
+
+void
+WidevineAdapter::GMPSetNodeId(const char* aNodeId, uint32_t aLength)
+{
+
+}
+
+/* static */
+bool
+WidevineAdapter::Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion)
+{
+ return aModuleVersion == CDM_MODULE_VERSION &&
+ aInterfaceVersion == cdm::ContentDecryptionModule::kVersion &&
+ aHostVersion == cdm::Host_8::kVersion;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.h b/dom/media/gmp/widevine-adapter/WidevineAdapter.h
new file mode 100644
index 000000000..714e041be
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineAdapter_h_
+#define WidevineAdapter_h_
+
+#include "GMPLoader.h"
+#include "prlink.h"
+#include "GMPUtils.h"
+
+struct GMPPlatformAPI;
+
+namespace mozilla {
+
+class WidevineAdapter : public gmp::GMPAdapter {
+public:
+
+ void SetAdaptee(PRLibrary* aLib) override;
+
+ // These are called in place of the corresponding GMP API functions.
+ GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override;
+ GMPErr GMPGetAPI(const char* aAPIName,
+ void* aHostAPI,
+ void** aPluginAPI,
+ uint32_t aDecryptorId) override;
+ void GMPShutdown() override;
+ void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override;
+
+ static bool Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion);
+
+private:
+ PRLibrary* mLib = nullptr;
+};
+
+GMPErr GMPCreateThread(GMPThread** aThread);
+GMPErr GMPRunOnMainThread(GMPTask* aTask);
+GMPErr GMPCreateMutex(GMPMutex** aMutex);
+
+// Call on main thread only.
+GMPErr GMPCreateRecord(const char* aRecordName,
+ uint32_t aRecordNameSize,
+ GMPRecord** aOutRecord,
+ GMPRecordClient* aClient);
+
+// Call on main thread only.
+GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
+
+GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime);
+
+GMPErr GMPCreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
+ void* aUserArg);
+
+} // namespace mozilla
+
+#endif // WidevineAdapter_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
new file mode 100644
index 000000000..149fa1701
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -0,0 +1,541 @@
+/* -*- 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 "WidevineDecryptor.h"
+
+#include "WidevineAdapter.h"
+#include "WidevineUtils.h"
+#include "WidevineFileIO.h"
+#include <mozilla/SizePrintfMacros.h>
+#include <stdarg.h>
+#include "base/time.h"
+
+using namespace cdm;
+using namespace std;
+
+namespace mozilla {
+
+static map<uint32_t, RefPtr<CDMWrapper>> sDecryptors;
+
+/* static */
+RefPtr<CDMWrapper>
+WidevineDecryptor::GetInstance(uint32_t aInstanceId)
+{
+ auto itr = sDecryptors.find(aInstanceId);
+ if (itr != sDecryptors.end()) {
+ return itr->second;
+ }
+ return nullptr;
+}
+
+
+WidevineDecryptor::WidevineDecryptor()
+ : mCallback(nullptr)
+{
+ Log("WidevineDecryptor created this=%p", this);
+ AddRef(); // Released in DecryptingComplete().
+}
+
+WidevineDecryptor::~WidevineDecryptor()
+{
+ Log("WidevineDecryptor destroyed this=%p", this);
+}
+
+void
+WidevineDecryptor::SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aInstanceId)
+{
+ mCDM = aCDM;
+ mInstanceId = aInstanceId;
+ sDecryptors[mInstanceId] = aCDM;
+}
+
+void
+WidevineDecryptor::Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+{
+ Log("WidevineDecryptor::Init() this=%p distinctiveId=%d persistentState=%d",
+ this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
+ MOZ_ASSERT(aCallback);
+ mCallback = aCallback;
+ MOZ_ASSERT(mCDM);
+ mDistinctiveIdentifierRequired = aDistinctiveIdentifierRequired;
+ mPersistentStateRequired = aPersistentStateRequired;
+ if (CDM()) {
+ CDM()->Initialize(aDistinctiveIdentifierRequired,
+ aPersistentStateRequired);
+ }
+}
+
+static SessionType
+ToCDMSessionType(GMPSessionType aSessionType)
+{
+ switch (aSessionType) {
+ case kGMPTemporySession: return kTemporary;
+ case kGMPPersistentSession: return kPersistentLicense;
+ case kGMPSessionInvalid: return kTemporary;
+ // TODO: kPersistentKeyRelease
+ }
+ MOZ_ASSERT(false); // Not supposed to get here.
+ return kTemporary;
+}
+
+void
+WidevineDecryptor::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType)
+{
+ Log("Decryptor::CreateSession(token=%d, pid=%d)", aCreateSessionToken, aPromiseId);
+ InitDataType initDataType;
+ if (!strcmp(aInitDataType, "cenc")) {
+ initDataType = kCenc;
+ } else if (!strcmp(aInitDataType, "webm")) {
+ initDataType = kWebM;
+ } else if (!strcmp(aInitDataType, "keyids")) {
+ initDataType = kKeyIds;
+ } else {
+ // Invalid init data type
+ const char* errorMsg = "Invalid init data type when creating session.";
+ OnRejectPromise(aPromiseId, kNotSupportedError, 0, errorMsg, sizeof(errorMsg));
+ return;
+ }
+ mPromiseIdToNewSessionTokens[aPromiseId] = aCreateSessionToken;
+ CDM()->CreateSessionAndGenerateRequest(aPromiseId,
+ ToCDMSessionType(aSessionType),
+ initDataType,
+ aInitData, aInitDataSize);
+}
+
+void
+WidevineDecryptor::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::LoadSession(pid=%d, %s)", aPromiseId, aSessionId);
+ // TODO: session type??
+ CDM()->LoadSession(aPromiseId, kPersistentLicense, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ Log("Decryptor::UpdateSession(pid=%d, session=%s)", aPromiseId, aSessionId);
+ CDM()->UpdateSession(aPromiseId, aSessionId, aSessionIdLength, aResponse, aResponseSize);
+}
+
+void
+WidevineDecryptor::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::CloseSession(pid=%d, session=%s)", aPromiseId, aSessionId);
+ CDM()->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ Log("Decryptor::RemoveSession(%s)", aSessionId);
+ CDM()->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
+}
+
+void
+WidevineDecryptor::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize)
+{
+ Log("Decryptor::SetServerCertificate()");
+ CDM()->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
+}
+
+class WidevineDecryptedBlock : public cdm::DecryptedBlock {
+public:
+
+ WidevineDecryptedBlock()
+ : mBuffer(nullptr)
+ , mTimestamp(0)
+ {
+ }
+
+ ~WidevineDecryptedBlock() {
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+ }
+
+ void SetDecryptedBuffer(cdm::Buffer* aBuffer) override {
+ mBuffer = aBuffer;
+ }
+
+ cdm::Buffer* DecryptedBuffer() override {
+ return mBuffer;
+ }
+
+ void SetTimestamp(int64_t aTimestamp) override {
+ mTimestamp = aTimestamp;
+ }
+
+ int64_t Timestamp() const override {
+ return mTimestamp;
+ }
+
+private:
+ cdm::Buffer* mBuffer;
+ int64_t mTimestamp;
+};
+
+void
+WidevineDecryptor::Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ if (!mCallback) {
+ Log("WidevineDecryptor::Decrypt() this=%p FAIL; !mCallback", this);
+ return;
+ }
+ const GMPEncryptedBufferMetadata* crypto = aMetadata;
+ InputBuffer sample;
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples);
+ WidevineDecryptedBlock decrypted;
+ Status rv = CDM()->Decrypt(sample, &decrypted);
+ Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d",
+ sample.timestamp, rv, decrypted.DecryptedBuffer()->Size());
+ if (rv == kSuccess) {
+ aBuffer->Resize(decrypted.DecryptedBuffer()->Size());
+ memcpy(aBuffer->Data(),
+ decrypted.DecryptedBuffer()->Data(),
+ decrypted.DecryptedBuffer()->Size());
+ }
+ mCallback->Decrypted(aBuffer, ToGMPErr(rv));
+}
+
+void
+WidevineDecryptor::DecryptingComplete()
+{
+ Log("WidevineDecryptor::DecryptingComplete() this=%p", this);
+ // Drop our references to the CDMWrapper. When any other references
+ // held elsewhere are dropped (for example references held by a
+ // WidevineVideoDecoder, or a runnable), the CDMWrapper destroys
+ // the CDM.
+ mCDM = nullptr;
+ sDecryptors.erase(mInstanceId);
+ mCallback = nullptr;
+ Release();
+}
+
+class WidevineBuffer : public cdm::Buffer {
+public:
+ explicit WidevineBuffer(size_t aSize) {
+ Log("WidevineBuffer(size=" PRIuSIZE ") created", aSize);
+ mBuffer.SetLength(aSize);
+ }
+ ~WidevineBuffer() {
+ Log("WidevineBuffer(size=" PRIuSIZE ") destroyed", Size());
+ }
+ void Destroy() override { delete this; }
+ uint32_t Capacity() const override { return mBuffer.Length(); };
+ uint8_t* Data() override { return mBuffer.Elements(); }
+ void SetSize(uint32_t aSize) override { mBuffer.SetLength(aSize); }
+ uint32_t Size() const override { return mBuffer.Length(); }
+
+private:
+ WidevineBuffer(const WidevineBuffer&);
+ void operator=(const WidevineBuffer&);
+
+ nsTArray<uint8_t> mBuffer;
+};
+
+Buffer*
+WidevineDecryptor::Allocate(uint32_t aCapacity)
+{
+ Log("Decryptor::Allocate(capacity=%u)", aCapacity);
+ return new WidevineBuffer(aCapacity);
+}
+
+class TimerTask : public GMPTask {
+public:
+ TimerTask(WidevineDecryptor* aDecryptor,
+ RefPtr<CDMWrapper> aCDM,
+ void* aContext)
+ : mDecryptor(aDecryptor)
+ , mCDM(aCDM)
+ , mContext(aContext)
+ {
+ }
+ ~TimerTask() override {}
+ void Run() override {
+ mCDM->GetCDM()->TimerExpired(mContext);
+ }
+ void Destroy() override { delete this; }
+private:
+ RefPtr<WidevineDecryptor> mDecryptor;
+ RefPtr<CDMWrapper> mCDM;
+ void* mContext;
+};
+
+void
+WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext)
+{
+ Log("Decryptor::SetTimer(delay_ms=%lld, context=0x%x)", aDelayMs, aContext);
+ if (mCDM) {
+ GMPSetTimerOnMainThread(new TimerTask(this, mCDM, aContext), aDelayMs);
+ }
+}
+
+Time
+WidevineDecryptor::GetCurrentWallTime()
+{
+ return base::Time::Now().ToDoubleT();
+}
+
+void
+WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
+ return;
+ }
+ Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
+ auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
+ if (iter == mPromiseIdToNewSessionTokens.end()) {
+ Log("FAIL: Decryptor::OnResolveNewSessionPromise(aPromiseId=%d) unknown aPromiseId", aPromiseId);
+ return;
+ }
+ mCallback->SetSessionId(iter->second, aSessionId, aSessionIdSize);
+ mCallback->ResolvePromise(aPromiseId);
+ mPromiseIdToNewSessionTokens.erase(iter);
+}
+
+void
+WidevineDecryptor::OnResolvePromise(uint32_t aPromiseId)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnResolvePromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
+ return;
+ }
+ Log("Decryptor::OnResolvePromise(aPromiseId=%d)", aPromiseId);
+ mCallback->ResolvePromise(aPromiseId);
+}
+
+static GMPDOMException
+ToGMPDOMException(cdm::Error aError)
+{
+ switch (aError) {
+ case kNotSupportedError: return kGMPNotSupportedError;
+ case kInvalidStateError: return kGMPInvalidStateError;
+ case kInvalidAccessError:
+ // Note: Chrome converts kInvalidAccessError to TypeError, since the
+ // Chromium CDM API doesn't have a type error enum value. The EME spec
+ // requires TypeError in some places, so we do the same conversion.
+ // See bug 1313202.
+ return kGMPTypeError;
+ case kQuotaExceededError: return kGMPQuotaExceededError;
+ case kUnknownError: return kGMPInvalidModificationError; // Note: Unique placeholder.
+ case kClientError: return kGMPAbortError; // Note: Unique placeholder.
+ case kOutputError: return kGMPSecurityError; // Note: Unique placeholder.
+ };
+ return kGMPTimeoutError; // Note: Unique placeholder.
+}
+
+void
+WidevineDecryptor::OnRejectPromise(uint32_t aPromiseId,
+ Error aError,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s) FAIL; !mCallback",
+ aPromiseId, (int)aError, aSystemCode, aErrorMessage);
+ return;
+ }
+ Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s)",
+ aPromiseId, (int)aError, aSystemCode, aErrorMessage);
+ mCallback->RejectPromise(aPromiseId,
+ ToGMPDOMException(aError),
+ !aErrorMessageSize ? "" : aErrorMessage,
+ aErrorMessageSize);
+}
+
+static GMPSessionMessageType
+ToGMPMessageType(MessageType message_type)
+{
+ switch (message_type) {
+ case kLicenseRequest: return kGMPLicenseRequest;
+ case kLicenseRenewal: return kGMPLicenseRenewal;
+ case kLicenseRelease: return kGMPLicenseRelease;
+ }
+ return kGMPMessageInvalid;
+}
+
+void
+WidevineDecryptor::OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize,
+ const char* aLegacyDestinationUrl,
+ uint32_t aLegacyDestinationUrlLength)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionMessage() FAIL; !mCallback");
+ return;
+ }
+ Log("Decryptor::OnSessionMessage()");
+ mCallback->SessionMessage(aSessionId,
+ aSessionIdSize,
+ ToGMPMessageType(aMessageType),
+ reinterpret_cast<const uint8_t*>(aMessage),
+ aMessageSize);
+}
+
+static GMPMediaKeyStatus
+ToGMPKeyStatus(KeyStatus aStatus)
+{
+ switch (aStatus) {
+ case kUsable: return kGMPUsable;
+ case kInternalError: return kGMPInternalError;
+ case kExpired: return kGMPExpired;
+ case kOutputRestricted: return kGMPOutputRestricted;
+ case kOutputDownscaled: return kGMPOutputDownscaled;
+ case kStatusPending: return kGMPStatusPending;
+ case kReleased: return kGMPReleased;
+ }
+ return kGMPUnknown;
+}
+
+void
+WidevineDecryptor::OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback");
+ return;
+ }
+ Log("Decryptor::OnSessionKeysChange()");
+
+ nsTArray<GMPMediaKeyInfo> key_infos;
+ for (uint32_t i = 0; i < aKeysInfoCount; i++) {
+ key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id,
+ aKeysInfo[i].key_id_size,
+ ToGMPKeyStatus(aKeysInfo[i].status)));
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize,
+ key_infos.Elements(), key_infos.Length());
+}
+
+static GMPTimestamp
+ToGMPTime(Time aCDMTime)
+{
+ return static_cast<GMPTimestamp>(aCDMTime * 1000);
+}
+
+void
+WidevineDecryptor::OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ Time aNewExpiryTime)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback",
+ aSessionId, aNewExpiryTime);
+ return;
+ }
+ Log("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime);
+ GMPTimestamp expiry = ToGMPTime(aNewExpiryTime);
+ if (aNewExpiryTime == 0) {
+ return;
+ }
+ mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry);
+}
+
+void
+WidevineDecryptor::OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId);
+ return;
+ }
+ Log("Decryptor::OnSessionClosed(sid=%s)", aSessionId);
+ mCallback->SessionClosed(aSessionId, aSessionIdSize);
+}
+
+void
+WidevineDecryptor::OnLegacySessionError(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ Error aError,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageLength)
+{
+ if (!mCallback) {
+ Log("Decryptor::OnLegacySessionError(sid=%s, error=%d) FAIL; !mCallback",
+ aSessionId, (int)aError);
+ return;
+ }
+ Log("Decryptor::OnLegacySessionError(sid=%s, error=%d)", aSessionId, (int)aError);
+ mCallback->SessionError(aSessionId,
+ aSessionIdLength,
+ ToGMPDOMException(aError),
+ aSystemCode,
+ aErrorMessage,
+ aErrorMessageLength);
+}
+
+void
+WidevineDecryptor::SendPlatformChallenge(const char* aServiceId,
+ uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize)
+{
+ Log("Decryptor::SendPlatformChallenge(service_id=%s)", aServiceId);
+}
+
+void
+WidevineDecryptor::EnableOutputProtection(uint32_t aDesiredProtectionMask)
+{
+ Log("Decryptor::EnableOutputProtection(mask=0x%x)", aDesiredProtectionMask);
+}
+
+void
+WidevineDecryptor::QueryOutputProtectionStatus()
+{
+ Log("Decryptor::QueryOutputProtectionStatus()");
+}
+
+void
+WidevineDecryptor::OnDeferredInitializationDone(StreamType aStreamType,
+ Status aDecoderStatus)
+{
+ Log("Decryptor::OnDeferredInitializationDone()");
+}
+
+FileIO*
+WidevineDecryptor::CreateFileIO(FileIOClient* aClient)
+{
+ Log("Decryptor::CreateFileIO()");
+ if (!mPersistentStateRequired) {
+ return nullptr;
+ }
+ return new WidevineFileIO(aClient);
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.h b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
new file mode 100644
index 000000000..d5185192b
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
@@ -0,0 +1,134 @@
+/* -*- 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 WidevineDecryptor_h_
+#define WidevineDecryptor_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-decryption.h"
+#include "mozilla/RefPtr.h"
+#include "WidevineUtils.h"
+#include <map>
+
+namespace mozilla {
+
+class WidevineDecryptor : public GMPDecryptor
+ , public cdm::Host_8
+{
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineDecryptor)
+
+ WidevineDecryptor();
+
+ void SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aDecryptorId);
+
+ static RefPtr<CDMWrapper> GetInstance(uint32_t aDecryptorId);
+
+ // GMPDecryptor
+ void Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType) override;
+
+ 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;
+
+
+ // cdm::Host_8
+ cdm::Buffer* Allocate(uint32_t aCapacity) override;
+ void SetTimer(int64_t aDelayMs, void* aContext) override;
+ cdm::Time GetCurrentWallTime() override;
+ void OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void OnResolvePromise(uint32_t aPromiseId) override;
+ void OnRejectPromise(uint32_t aPromiseId,
+ cdm::Error aError,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize) override;
+ void OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize,
+ const char* aLegacyDestinationUrl,
+ uint32_t aLegacyDestinationUrlLength) override;
+ void OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) override;
+ void OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::Time aNewExpiryTime) override;
+ void OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void OnLegacySessionError(const char* aSessionId,
+ uint32_t aSessionId_length,
+ cdm::Error aError,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageLength) override;
+ void SendPlatformChallenge(const char* aServiceId,
+ uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize) override;
+ void EnableOutputProtection(uint32_t aDesiredProtectionMask) override;
+ void QueryOutputProtectionStatus() override;
+ void OnDeferredInitializationDone(cdm::StreamType aStreamType,
+ cdm::Status aDecoderStatus) override;
+ cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
+
+ GMPDecryptorCallback* Callback() const { return mCallback; }
+ RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; }
+private:
+ ~WidevineDecryptor();
+ RefPtr<CDMWrapper> mCDM;
+ cdm::ContentDecryptionModule_8* CDM() { return mCDM->GetCDM(); }
+
+ GMPDecryptorCallback* mCallback;
+ std::map<uint32_t, uint32_t> mPromiseIdToNewSessionTokens;
+ bool mDistinctiveIdentifierRequired = false;
+ bool mPersistentStateRequired = false;
+ uint32_t mInstanceId = 0;
+};
+
+} // namespace mozilla
+
+#endif // WidevineDecryptor_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
new file mode 100644
index 000000000..b5fb1d705
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
@@ -0,0 +1,97 @@
+#include "WidevineFileIO.h"
+#include "WidevineUtils.h"
+#include "WidevineAdapter.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+void
+WidevineFileIO::Open(const char* aFilename, uint32_t aFilenameLength)
+{
+ mName = std::string(aFilename, aFilename + aFilenameLength);
+ GMPRecord* record = nullptr;
+ GMPErr err = GMPCreateRecord(aFilename, aFilenameLength, &record, static_cast<GMPRecordClient*>(this));
+ if (GMP_FAILED(err)) {
+ Log("WidevineFileIO::Open() '%s' GMPCreateRecord failed", mName.c_str());
+ mClient->OnOpenComplete(FileIOClient::kError);
+ return;
+ }
+ if (GMP_FAILED(record->Open())) {
+ Log("WidevineFileIO::Open() '%s' record open failed", mName.c_str());
+ mClient->OnOpenComplete(FileIOClient::kError);
+ return;
+ }
+
+ Log("WidevineFileIO::Open() '%s'", mName.c_str());
+ mRecord = record;
+}
+
+void
+WidevineFileIO::Read()
+{
+ if (!mRecord) {
+ Log("WidevineFileIO::Read() '%s' used uninitialized!", mName.c_str());
+ mClient->OnReadComplete(FileIOClient::kError, nullptr, 0);
+ return;
+ }
+ Log("WidevineFileIO::Read() '%s'", mName.c_str());
+ mRecord->Read();
+}
+
+void
+WidevineFileIO::Write(const uint8_t* aData, uint32_t aDataSize)
+{
+ if (!mRecord) {
+ Log("WidevineFileIO::Write() '%s' used uninitialized!", mName.c_str());
+ mClient->OnWriteComplete(FileIOClient::kError);
+ return;
+ }
+ mRecord->Write(aData, aDataSize);
+}
+
+void
+WidevineFileIO::Close()
+{
+ Log("WidevineFileIO::Close() '%s'", mName.c_str());
+ if (mRecord) {
+ mRecord->Close();
+ mRecord = nullptr;
+ }
+ delete this;
+}
+
+static FileIOClient::Status
+GMPToWidevineFileStatus(GMPErr aStatus)
+{
+ switch (aStatus) {
+ case GMPRecordInUse: return FileIOClient::kInUse;
+ case GMPNoErr: return FileIOClient::kSuccess;
+ default: return FileIOClient::kError;
+ }
+}
+
+void
+WidevineFileIO::OpenComplete(GMPErr aStatus)
+{
+ Log("WidevineFileIO::OpenComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnOpenComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+void
+WidevineFileIO::ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize)
+{
+ Log("WidevineFileIO::OnReadComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnReadComplete(GMPToWidevineFileStatus(aStatus), aData, aDataSize);
+}
+
+void
+WidevineFileIO::WriteComplete(GMPErr aStatus)
+{
+ Log("WidevineFileIO::WriteComplete() '%s' status=%d", mName.c_str(), aStatus);
+ mClient->OnWriteComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.h b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
new file mode 100644
index 000000000..63003d9b6
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineFileIO_h_
+#define WidevineFileIO_h_
+
+#include <stddef.h>
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-storage.h"
+#include <string>
+
+namespace mozilla {
+
+class WidevineFileIO : public cdm::FileIO
+ , public GMPRecordClient
+{
+public:
+ explicit WidevineFileIO(cdm::FileIOClient* aClient)
+ : mClient(aClient)
+ , mRecord(nullptr)
+ {}
+
+ // cdm::FileIO
+ void Open(const char* aFilename, uint32_t aFilenameLength) override;
+ void Read() override;
+ void Write(const uint8_t* aData, uint32_t aDataSize) override;
+ void Close() override;
+
+ // GMPRecordClient
+ void OpenComplete(GMPErr aStatus) override;
+ void ReadComplete(GMPErr aStatus,
+ const uint8_t* aData,
+ uint32_t aDataSize) override;
+ void WriteComplete(GMPErr aStatus) override;
+
+private:
+ cdm::FileIOClient* mClient;
+ GMPRecord* mRecord;
+ std::string mName;
+};
+
+} // namespace mozilla
+
+#endif // WidevineFileIO_h_ \ No newline at end of file
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.cpp b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
new file mode 100644
index 000000000..925dfe1a1
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.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 "WidevineUtils.h"
+#include "WidevineDecryptor.h"
+
+#include "gmp-api/gmp-errors.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace mozilla {
+
+#ifdef ENABLE_WIDEVINE_LOG
+void
+Log(const char* aFormat, ...)
+{
+ va_list ap;
+ va_start(ap, aFormat);
+ const size_t len = 1024;
+ char buf[len];
+ vsnprintf(buf, len, aFormat, ap);
+ va_end(ap);
+ if (getenv("GMP_LOG_FILE")) {
+ FILE* f = fopen(getenv("GMP_LOG_FILE"), "a");
+ if (f) {
+ fprintf(f, "%s\n", buf);
+ fflush(f);
+ fclose(f);
+ f = nullptr;
+ }
+ } else {
+ printf("LOG: %s\n", buf);
+ }
+}
+#endif // ENABLE_WIDEVINE_LOG
+
+GMPErr
+ToGMPErr(cdm::Status aStatus)
+{
+ switch (aStatus) {
+ case cdm::kSuccess: return GMPNoErr;
+ case cdm::kNeedMoreData: return GMPGenericErr;
+ case cdm::kNoKey: return GMPNoKeyErr;
+ case cdm::kSessionError: return GMPGenericErr;
+ case cdm::kDecryptError: return GMPCryptoErr;
+ case cdm::kDecodeError: return GMPDecodeErr;
+ case cdm::kDeferredInitialization: return GMPGenericErr;
+ default: return GMPGenericErr;
+ }
+}
+
+void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
+ int64_t aTimestamp,
+ const uint8_t* aData,
+ size_t aDataSize,
+ cdm::InputBuffer &aInputBuffer,
+ nsTArray<cdm::SubsampleEntry> &aSubsamples)
+{
+ if (aCrypto) {
+ aInputBuffer.key_id = aCrypto->KeyId();
+ aInputBuffer.key_id_size = aCrypto->KeyIdSize();
+ aInputBuffer.iv = aCrypto->IV();
+ aInputBuffer.iv_size = aCrypto->IVSize();
+ aInputBuffer.num_subsamples = aCrypto->NumSubsamples();
+ aSubsamples.SetCapacity(aInputBuffer.num_subsamples);
+ const uint16_t* clear = aCrypto->ClearBytes();
+ const uint32_t* cipher = aCrypto->CipherBytes();
+ for (size_t i = 0; i < aCrypto->NumSubsamples(); i++) {
+ aSubsamples.AppendElement(cdm::SubsampleEntry(clear[i], cipher[i]));
+ }
+ }
+ aInputBuffer.data = aData;
+ aInputBuffer.data_size = aDataSize;
+ aInputBuffer.subsamples = aSubsamples.Elements();
+ aInputBuffer.timestamp = aTimestamp;
+}
+
+CDMWrapper::CDMWrapper(cdm::ContentDecryptionModule_8* aCDM,
+ WidevineDecryptor* aDecryptor)
+ : mCDM(aCDM)
+ , mDecryptor(aDecryptor)
+{
+ MOZ_ASSERT(mCDM);
+}
+
+CDMWrapper::~CDMWrapper()
+{
+ Log("CDMWrapper destroying CDM=%p", mCDM);
+ mCDM->Destroy();
+ mCDM = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.h b/dom/media/gmp/widevine-adapter/WidevineUtils.h
new file mode 100644
index 000000000..57c004a87
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.h
@@ -0,0 +1,69 @@
+/* -*- 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 WidevineUtils_h_
+#define WidevineUtils_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-decryption.h"
+#include "gmp-api/gmp-platform.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+// Uncomment for logging...
+//#define ENABLE_WIDEVINE_LOG 1
+#ifdef ENABLE_WIDEVINE_LOG
+void
+Log(const char* aFormat, ...);
+#else
+#define Log(...)
+#endif // ENABLE_WIDEVINE_LOG
+
+
+#define ENSURE_TRUE(condition, rv) { \
+ if (!(condition)) {\
+ Log("ENSURE_TRUE FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+} \
+
+#define ENSURE_GMP_SUCCESS(err, rv) { \
+ if (GMP_FAILED(err)) {\
+ Log("ENSURE_GMP_SUCCESS FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+} \
+
+GMPErr
+ToGMPErr(cdm::Status aStatus);
+
+class WidevineDecryptor;
+
+class CDMWrapper {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMWrapper)
+
+ explicit CDMWrapper(cdm::ContentDecryptionModule_8* aCDM,
+ WidevineDecryptor* aDecryptor);
+ cdm::ContentDecryptionModule_8* GetCDM() const { return mCDM; }
+private:
+ ~CDMWrapper();
+ cdm::ContentDecryptionModule_8* mCDM;
+ RefPtr<WidevineDecryptor> mDecryptor;
+};
+
+void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto,
+ int64_t aTimestamp,
+ const uint8_t* aData,
+ size_t aDataSize,
+ cdm::InputBuffer &aInputBuffer,
+ nsTArray<cdm::SubsampleEntry> &aSubsamples);
+
+} // namespace mozilla
+
+#endif // WidevineUtils_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
new file mode 100644
index 000000000..70d2fd8e0
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -0,0 +1,400 @@
+/* -*- 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 "WidevineVideoDecoder.h"
+
+#include "mp4_demuxer/AnnexB.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoFrame.h"
+#include "mozilla/Move.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper)
+ : mVideoHost(aVideoHost)
+ , mCDMWrapper(Move(aCDMWrapper))
+ , mExtraData(new MediaByteBuffer())
+ , mSentInput(false)
+ , mCodecType(kGMPVideoCodecInvalid)
+ , mReturnOutputCallDepth(0)
+ , mDrainPending(false)
+ , mResetInProgress(false)
+{
+ // Expect to start with a CDM wrapper, will release it in DecodingComplete().
+ MOZ_ASSERT(mCDMWrapper);
+ Log("WidevineVideoDecoder created this=%p", this);
+
+ // Corresponding Release is in DecodingComplete().
+ AddRef();
+}
+
+WidevineVideoDecoder::~WidevineVideoDecoder()
+{
+ Log("WidevineVideoDecoder destroyed this=%p", this);
+}
+
+static
+VideoDecoderConfig::VideoCodecProfile
+ToCDMH264Profile(uint8_t aProfile)
+{
+ switch (aProfile) {
+ case 66: return VideoDecoderConfig::kH264ProfileBaseline;
+ case 77: return VideoDecoderConfig::kH264ProfileMain;
+ case 88: return VideoDecoderConfig::kH264ProfileExtended;
+ case 100: return VideoDecoderConfig::kH264ProfileHigh;
+ case 110: return VideoDecoderConfig::kH264ProfileHigh10;
+ case 122: return VideoDecoderConfig::kH264ProfileHigh422;
+ case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
+ }
+ return VideoDecoderConfig::kUnknownVideoCodecProfile;
+}
+
+void
+WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount)
+{
+ mCallback = aCallback;
+ VideoDecoderConfig config;
+ mCodecType = aCodecSettings.mCodecType;
+ if (mCodecType == kGMPVideoCodecH264) {
+ config.codec = VideoDecoderConfig::kCodecH264;
+ const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
+ config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
+ } else if (mCodecType == kGMPVideoCodecVP8) {
+ config.codec = VideoDecoderConfig::kCodecVp8;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } else if (mCodecType == kGMPVideoCodecVP9) {
+ config.codec = VideoDecoderConfig::kCodecVp9;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } else {
+ mCallback->Error(GMPInvalidArgErr);
+ return;
+ }
+ config.format = kYv12;
+ config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
+ mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
+ config.extra_data = mExtraData->Elements();
+ config.extra_data_size = mExtraData->Length();
+ Status rv = CDM()->InitializeVideoDecoder(config);
+ if (rv != kSuccess) {
+ mCallback->Error(ToGMPErr(rv));
+ return;
+ }
+ Log("WidevineVideoDecoder::InitDecode() rv=%d", rv);
+ mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
+}
+
+void
+WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs)
+{
+ // We should not be given new input if a drain has been initiated
+ MOZ_ASSERT(!mDrainPending);
+ // We may not get the same out of the CDM decoder as we put in, and there
+ // may be some latency, i.e. we may need to input (say) 30 frames before
+ // we receive output. So we need to store the durations of the frames input,
+ // and retrieve them on output.
+ mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
+
+ mSentInput = true;
+ InputBuffer sample;
+
+ RefPtr<MediaRawData> raw(
+ new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
+ if (aInputFrame->Size() && !raw->Data()) {
+ // OOM.
+ mCallback->Error(GMPAllocErr);
+ return;
+ }
+ raw->mExtraData = mExtraData;
+ raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
+ if (mCodecType == kGMPVideoCodecH264) {
+ // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
+ // Chromium uses internally.
+ mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
+ }
+
+ const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples);
+
+ // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
+ // at the start of the input. So we need to account for that as clear data
+ // in the subsamples.
+ if (raw->mKeyframe && !subsamples.IsEmpty() && mCodecType == kGMPVideoCodecH264) {
+ subsamples[0].clear_bytes += mAnnexB->Length();
+ }
+
+ WidevineVideoFrame frame;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv);
+
+ // Destroy frame, so that the shmem is now free to be used to return
+ // output to the Gecko process.
+ aInputFrame->Destroy();
+ aInputFrame = nullptr;
+
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ mCallback->Error(GMPDecodeErr);
+ return;
+ }
+ // A reset should only be started at most at level mReturnOutputCallDepth 1,
+ // and if it's started it should be finished by that call by the time
+ // the it returns, so it should always be false by this point.
+ MOZ_ASSERT(!mResetInProgress);
+ // Only request more data if we don't have pending samples.
+ if (mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ }
+ } else if (rv == kNeedMoreData) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ } else {
+ mCallback->Error(ToGMPErr(rv));
+ }
+ // Finish a drain if pending and we have no pending ReturnOutput calls on the stack.
+ if (mDrainPending && mReturnOutputCallDepth == 0) {
+ Drain();
+ }
+}
+
+// Util class to assist with counting mReturnOutputCallDepth.
+class CounterHelper {
+public:
+ // RAII, increment counter
+ explicit CounterHelper(int32_t& counter)
+ : mCounter(counter)
+ {
+ mCounter++;
+ }
+
+ // RAII, decrement counter
+ ~CounterHelper()
+ {
+ mCounter--;
+ }
+
+private:
+ int32_t& mCounter;
+};
+
+// Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
+// and will destroy it when the helper is destroyed unless the held frame
+// if forgotten with ForgetFrame.
+class FrameDestroyerHelper {
+public:
+ explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame)
+ : frame(frame)
+ {
+ }
+
+ // RAII, destroy frame if held.
+ ~FrameDestroyerHelper()
+ {
+ if (frame) {
+ frame->Destroy();
+ }
+ frame = nullptr;
+ }
+
+ // Forget the frame without destroying it.
+ void ForgetFrame()
+ {
+ frame = nullptr;
+ }
+
+private:
+ GMPVideoi420Frame* frame;
+};
+
+
+// Special handing is needed around ReturnOutput as it spins the IPC message
+// queue when creating an empty frame and can end up with reentrant calls into
+// the class methods.
+bool
+WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
+{
+ MOZ_ASSERT(mReturnOutputCallDepth >= 0);
+ CounterHelper counterHelper(mReturnOutputCallDepth);
+ mFrameAllocationQueue.push_back(Move(aCDMFrame));
+ if (mReturnOutputCallDepth > 1) {
+ // In a reentrant call.
+ return true;
+ }
+ while (!mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ // If we're at call level 1 a reset should not have been started. A
+ // reset may be received during CreateEmptyFrame below, but we should not
+ // be in a reset at this stage -- this would indicate receiving decode
+ // messages before completing our reset, which we should not.
+ MOZ_ASSERT(!mResetInProgress);
+ WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
+ mFrameAllocationQueue.pop_front();
+ GMPVideoFrame* f = nullptr;
+ auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
+ if (GMP_FAILED(err) || !f) {
+ Log("Failed to create i420 frame!\n");
+ return false;
+ }
+ auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
+ FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
+ Size size = currentCDMFrame.Size();
+ const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
+ const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
+ const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
+ const int32_t halfHeight = size.height / 2;
+ // This call can cause a shmem alloc, during this alloc other calls
+ // may be made to this class and placed on the stack. ***WARNING***:
+ // other IPC calls can happen during this call, resulting in calls
+ // being made to the CDM. After this call state can have changed,
+ // and should be reevaluated.
+ err = gmpFrame->CreateEmptyFrame(size.width,
+ size.height,
+ yStride,
+ uStride,
+ vStride);
+ // Assert possible reentrant calls or resets haven't altered level unexpectedly.
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ // If a reset started we need to dump the current frame and complete the reset.
+ if (mResetInProgress) {
+ MOZ_ASSERT(mCDMWrapper);
+ MOZ_ASSERT(mFrameAllocationQueue.empty());
+ CompleteReset();
+ return true;
+ }
+
+ err = gmpFrame->SetWidth(size.width);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ err = gmpFrame->SetHeight(size.height);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ Buffer* buffer = currentCDMFrame.FrameBuffer();
+ uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
+ yStride * size.height);
+
+ outBuffer = gmpFrame->Buffer(kGMPUPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
+ uStride * halfHeight);
+
+ outBuffer = gmpFrame->Buffer(kGMPVPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
+ vStride * halfHeight);
+
+ gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());
+
+ auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
+ if (d != mFrameDurations.end()) {
+ gmpFrame->SetDuration(d->second);
+ mFrameDurations.erase(d);
+ }
+
+ // Forget frame so it's not deleted, call back taking ownership.
+ frameDestroyerHelper.ForgetFrame();
+ mCallback->Decoded(gmpFrame);
+ }
+
+ return true;
+}
+
+void
+WidevineVideoDecoder::Reset()
+{
+ Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
+ // We shouldn't reset if a drain is pending.
+ MOZ_ASSERT(!mDrainPending);
+ mResetInProgress = true;
+ if (mSentInput) {
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ }
+ // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
+ // ReturnOutput calls unwind and decrement the counter as needed.
+ mFrameAllocationQueue.clear();
+ mFrameDurations.clear();
+ // Only if no ReturnOutput calls are in progress can we complete, otherwise
+ // ReturnOutput needs to finalize the reset.
+ if (mReturnOutputCallDepth == 0) {
+ CompleteReset();
+ }
+}
+
+void
+WidevineVideoDecoder::CompleteReset()
+{
+ mCallback->ResetComplete();
+ mSentInput = false;
+ mResetInProgress = false;
+}
+
+void
+WidevineVideoDecoder::Drain()
+{
+ Log("WidevineVideoDecoder::Drain()");
+ if (mReturnOutputCallDepth > 0) {
+ Log("Drain call is reentrant, postponing drain");
+ mDrainPending = true;
+ return;
+ }
+
+ Status rv = kSuccess;
+ while (rv == kSuccess) {
+ WidevineVideoFrame frame;
+ InputBuffer sample;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv);
+ if (frame.Format() == kUnknownVideoFormat) {
+ break;
+ }
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ }
+ }
+ }
+ // Shouldn't be reset while draining.
+ MOZ_ASSERT(!mResetInProgress);
+
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ mDrainPending = false;
+ mCallback->DrainComplete();
+}
+
+void
+WidevineVideoDecoder::DecodingComplete()
+{
+ Log("WidevineVideoDecoder::DecodingComplete()");
+ if (mCDMWrapper) {
+ CDM()->DeinitializeDecoder(kStreamTypeVideo);
+ mCDMWrapper = nullptr;
+ }
+ // Release that corresponds to AddRef() in constructor.
+ Release();
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
new file mode 100644
index 000000000..b143f75f7
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
@@ -0,0 +1,80 @@
+/* -*- 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 WidevineVideoDecoder_h_
+#define WidevineVideoDecoder_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-video-decode.h"
+#include "gmp-api/gmp-video-host.h"
+#include "MediaData.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "WidevineDecryptor.h"
+#include "WidevineVideoFrame.h"
+#include <map>
+#include <deque>
+
+namespace mozilla {
+
+class WidevineVideoDecoder : public GMPVideoDecoder {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineVideoDecoder)
+
+ WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper);
+ void InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount) override;
+ void Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs = -1) override;
+ void Reset() override;
+ void Drain() override;
+ void DecodingComplete() override;
+
+private:
+
+ ~WidevineVideoDecoder();
+
+ cdm::ContentDecryptionModule_8* CDM() const {
+ // CDM should only be accessed before 'DecodingComplete'.
+ MOZ_ASSERT(mCDMWrapper);
+ // CDMWrapper ensure the CDM is non-null, no need to check again.
+ return mCDMWrapper->GetCDM();
+ }
+
+ bool ReturnOutput(WidevineVideoFrame& aFrame);
+ void CompleteReset();
+
+ GMPVideoHost* mVideoHost;
+ RefPtr<CDMWrapper> mCDMWrapper;
+ RefPtr<MediaByteBuffer> mExtraData;
+ RefPtr<MediaByteBuffer> mAnnexB;
+ GMPVideoDecoderCallback* mCallback;
+ std::map<uint64_t, uint64_t> mFrameDurations;
+ bool mSentInput;
+ GMPVideoCodecType mCodecType;
+ // Frames waiting on allocation
+ std::deque<WidevineVideoFrame> mFrameAllocationQueue;
+ // Number of calls of ReturnOutput currently in progress.
+ int32_t mReturnOutputCallDepth;
+ // If we're waiting to drain. Used to prevent drain completing while
+ // ReturnOutput calls are still on the stack.
+ bool mDrainPending;
+ // If a reset is being performed. Used to track if ReturnOutput should
+ // dump current frame.
+ bool mResetInProgress;
+};
+
+} // namespace mozilla
+
+#endif // WidevineVideoDecoder_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
new file mode 100644
index 000000000..4221bf15b
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
@@ -0,0 +1,126 @@
+/* -*- 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 "WidevineVideoFrame.h"
+
+#include "WidevineUtils.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoFrame::WidevineVideoFrame()
+ : mFormat(kUnknownVideoFormat)
+ , mSize(0,0)
+ , mBuffer(nullptr)
+ , mTimestamp(0)
+{
+ Log("WidevineVideoFrame::WidevineVideoFrame() this=%p", this);
+ memset(mPlaneOffsets, 0, sizeof(mPlaneOffsets));
+ memset(mPlaneStrides, 0, sizeof(mPlaneStrides));
+}
+
+WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&& aOther)
+ : mFormat(aOther.mFormat)
+ , mSize(aOther.mSize)
+ , mBuffer(aOther.mBuffer)
+ , mTimestamp(aOther.mTimestamp)
+{
+ Log("WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&&) this=%p, other=%p",
+ this, &aOther);
+ memcpy(mPlaneOffsets, aOther.mPlaneOffsets, sizeof(mPlaneOffsets));
+ memcpy(mPlaneStrides, aOther.mPlaneStrides, sizeof(mPlaneStrides));
+ aOther.mBuffer = nullptr;
+}
+
+WidevineVideoFrame::~WidevineVideoFrame()
+{
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+}
+
+void
+WidevineVideoFrame::SetFormat(cdm::VideoFormat aFormat)
+{
+ Log("WidevineVideoFrame::SetFormat(%d) this=%p", aFormat, this);
+ mFormat = aFormat;
+}
+
+cdm::VideoFormat
+WidevineVideoFrame::Format() const
+{
+ return mFormat;
+}
+
+void
+WidevineVideoFrame::SetSize(cdm::Size aSize)
+{
+ Log("WidevineVideoFrame::SetSize(%d,%d) this=%p", aSize.width, aSize.height, this);
+ mSize.width = aSize.width;
+ mSize.height = aSize.height;
+}
+
+cdm::Size
+WidevineVideoFrame::Size() const
+{
+ return mSize;
+}
+
+void
+WidevineVideoFrame::SetFrameBuffer(cdm::Buffer* aFrameBuffer)
+{
+ Log("WidevineVideoFrame::SetFrameBuffer(%p) this=%p", aFrameBuffer, this);
+ MOZ_ASSERT(!mBuffer);
+ mBuffer = aFrameBuffer;
+}
+
+cdm::Buffer*
+WidevineVideoFrame::FrameBuffer()
+{
+ return mBuffer;
+}
+
+void
+WidevineVideoFrame::SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset)
+{
+ Log("WidevineVideoFrame::SetPlaneOffset(%d, %d) this=%p", aPlane, aOffset, this);
+ mPlaneOffsets[aPlane] = aOffset;
+}
+
+uint32_t
+WidevineVideoFrame::PlaneOffset(cdm::VideoFrame::VideoPlane aPlane)
+{
+ return mPlaneOffsets[aPlane];
+}
+
+void
+WidevineVideoFrame::SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride)
+{
+ Log("WidevineVideoFrame::SetStride(%d, %d) this=%p", aPlane, aStride, this);
+ mPlaneStrides[aPlane] = aStride;
+}
+
+uint32_t
+WidevineVideoFrame::Stride(cdm::VideoFrame::VideoPlane aPlane)
+{
+ return mPlaneStrides[aPlane];
+}
+
+void
+WidevineVideoFrame::SetTimestamp(int64_t timestamp)
+{
+ Log("WidevineVideoFrame::SetTimestamp(%lld) this=%p", timestamp, this);
+ mTimestamp = timestamp;
+}
+
+int64_t
+WidevineVideoFrame::Timestamp() const
+{
+ return mTimestamp;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
new file mode 100644
index 000000000..96d4f20f8
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
@@ -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/. */
+
+#ifndef WidevineVideoFrame_h_
+#define WidevineVideoFrame_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include <vector>
+
+namespace mozilla {
+
+class WidevineVideoFrame : public cdm::VideoFrame {
+public:
+ WidevineVideoFrame();
+ WidevineVideoFrame(WidevineVideoFrame&& other);
+ ~WidevineVideoFrame();
+
+ void SetFormat(cdm::VideoFormat aFormat) override;
+ cdm::VideoFormat Format() const override;
+
+ void SetSize(cdm::Size aSize) override;
+ cdm::Size Size() const override;
+
+ void SetFrameBuffer(cdm::Buffer* aFrameBuffer) override;
+ cdm::Buffer* FrameBuffer() override;
+
+ void SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset) override;
+ uint32_t PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) override;
+
+ void SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) override;
+ uint32_t Stride(cdm::VideoFrame::VideoPlane aPlane) override;
+
+ void SetTimestamp(int64_t aTimestamp) override;
+ int64_t Timestamp() const override;
+
+protected:
+ cdm::VideoFormat mFormat;
+ cdm::Size mSize;
+ cdm::Buffer* mBuffer;
+ uint32_t mPlaneOffsets[kMaxPlanes];
+ uint32_t mPlaneStrides[kMaxPlanes];
+ int64_t mTimestamp;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module.h b/dom/media/gmp/widevine-adapter/content_decryption_module.h
new file mode 100644
index 000000000..512ca9768
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module.h
@@ -0,0 +1,1199 @@
+// 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.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_H_
+
+#if defined(_MSC_VER)
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+typedef int int32_t;
+typedef __int64 int64_t;
+#else
+#include <stdint.h>
+#endif
+
+// Define CDM_EXPORT so that functionality implemented by the CDM module
+// can be exported to consumers.
+#if defined(WIN32)
+
+#if defined(CDM_IMPLEMENTATION)
+#define CDM_EXPORT __declspec(dllexport)
+#else
+#define CDM_EXPORT __declspec(dllimport)
+#endif // defined(CDM_IMPLEMENTATION)
+
+#else // defined(WIN32)
+
+#if defined(CDM_IMPLEMENTATION)
+#define CDM_EXPORT __attribute__((visibility("default")))
+#else
+#define CDM_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+// The version number must be rolled when the exported functions are updated!
+// If the CDM and the adapter use different versions of these functions, the
+// adapter will fail to load or crash!
+#define CDM_MODULE_VERSION 4
+
+// Build the versioned entrypoint name.
+// The extra macros are necessary to expand version to an actual value.
+#define INITIALIZE_CDM_MODULE \
+ BUILD_ENTRYPOINT(InitializeCdmModule, CDM_MODULE_VERSION)
+#define BUILD_ENTRYPOINT(name, version) \
+ BUILD_ENTRYPOINT_NO_EXPANSION(name, version)
+#define BUILD_ENTRYPOINT_NO_EXPANSION(name, version) name##_##version
+
+extern "C" {
+CDM_EXPORT void INITIALIZE_CDM_MODULE();
+
+CDM_EXPORT void DeinitializeCdmModule();
+
+// Returns a pointer to the requested CDM Host interface upon success.
+// Returns NULL if the requested CDM Host interface is not supported.
+// The caller should cast the returned pointer to the type matching
+// |host_interface_version|.
+typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data);
+
+// Returns a pointer to the requested CDM upon success.
+// Returns NULL if an error occurs or the requested |cdm_interface_version| or
+// |key_system| is not supported or another error occurs.
+// The caller should cast the returned pointer to the type matching
+// |cdm_interface_version|.
+// Caller retains ownership of arguments and must call Destroy() on the returned
+// object.
+CDM_EXPORT void* CreateCdmInstance(
+ int cdm_interface_version,
+ const char* key_system, uint32_t key_system_size,
+ GetCdmHostFunc get_cdm_host_func, void* user_data);
+
+CDM_EXPORT const char* GetCdmVersion();
+}
+
+namespace cdm {
+
+class AudioFrames;
+class DecryptedBlock;
+class VideoFrame;
+
+class Host_7;
+class Host_8;
+
+enum Status {
+ kSuccess = 0,
+ kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample.
+ kNoKey, // The required decryption key is not available.
+ kSessionError, // Session management error.
+ kDecryptError, // Decryption failed.
+ kDecodeError, // Error decoding audio or video.
+ kDeferredInitialization // Decoder is not ready for initialization.
+};
+
+// This must at least contain the exceptions defined in the spec:
+// https://w3c.github.io/encrypted-media/#exceptions
+// The following starts with the list of DOM4 exceptions from:
+// http://www.w3.org/TR/dom/#domexception
+// Some DOM4 exceptions are not included as they are not expected to be used.
+enum Error {
+ kNotSupportedError = 9,
+ kInvalidStateError = 11,
+ kInvalidAccessError = 15,
+ kQuotaExceededError = 22,
+
+ // Additional exceptions that do not have assigned codes.
+ // There are other non-EME-specific values, not included in this list.
+ kUnknownError = 30,
+
+ // Additional values from previous EME versions. They currently have no
+ // matching DOMException.
+ kClientError = 100,
+ kOutputError = 101
+};
+
+// Time is defined as the number of seconds since the
+// Epoch (00:00:00 UTC, January 1, 1970).
+typedef double Time;
+
+// An input buffer can be split into several continuous subsamples.
+// A SubsampleEntry specifies the number of clear and cipher bytes in each
+// subsample. For example, the following buffer has three subsamples:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 |
+//
+// For decryption, all of the cipher bytes in a buffer should be concatenated
+// (in the subsample order) into a single logical stream. The clear bytes should
+// not be considered as part of decryption.
+//
+// Stream to decrypt: | cipher1 | cipher2 | cipher3 |
+// Decrypted stream: | decrypted1| decrypted2 | decrypted3 |
+//
+// After decryption, the decrypted bytes should be copied over the position
+// of the corresponding cipher bytes in the original buffer to form the output
+// buffer. Following the above example, the decrypted buffer should be:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 |
+//
+struct SubsampleEntry {
+ SubsampleEntry(uint32_t clear_bytes, uint32_t cipher_bytes)
+ : clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {}
+
+ uint32_t clear_bytes;
+ uint32_t cipher_bytes;
+};
+
+// Represents an input buffer to be decrypted (and possibly decoded). It does
+// not own any pointers in this struct. If |iv_size| = 0, the data is
+// unencrypted.
+struct InputBuffer {
+ InputBuffer()
+ : data(NULL),
+ data_size(0),
+ key_id(NULL),
+ key_id_size(0),
+ iv(NULL),
+ iv_size(0),
+ subsamples(NULL),
+ num_subsamples(0),
+ timestamp(0) {}
+
+ const uint8_t* data; // Pointer to the beginning of the input data.
+ uint32_t data_size; // Size (in bytes) of |data|.
+
+ const uint8_t* key_id; // Key ID to identify the decryption key.
+ uint32_t key_id_size; // Size (in bytes) of |key_id|.
+
+ const uint8_t* iv; // Initialization vector.
+ uint32_t iv_size; // Size (in bytes) of |iv|.
+
+ const struct SubsampleEntry* subsamples;
+ uint32_t num_subsamples; // Number of subsamples in |subsamples|.
+
+ int64_t timestamp; // Presentation timestamp in microseconds.
+};
+
+struct AudioDecoderConfig {
+ enum AudioCodec {
+ kUnknownAudioCodec = 0,
+ kCodecVorbis,
+ kCodecAac
+ };
+
+ AudioDecoderConfig()
+ : codec(kUnknownAudioCodec),
+ channel_count(0),
+ bits_per_channel(0),
+ samples_per_second(0),
+ extra_data(NULL),
+ extra_data_size(0) {}
+
+ AudioCodec codec;
+ int32_t channel_count;
+ int32_t bits_per_channel;
+ int32_t samples_per_second;
+
+ // Optional byte data required to initialize audio decoders, such as the
+ // vorbis setup header.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+};
+
+// Supported sample formats for AudioFrames.
+enum AudioFormat {
+ kUnknownAudioFormat = 0, // Unknown format value. Used for error reporting.
+ kAudioFormatU8, // Interleaved unsigned 8-bit w/ bias of 128.
+ kAudioFormatS16, // Interleaved signed 16-bit.
+ kAudioFormatS32, // Interleaved signed 32-bit.
+ kAudioFormatF32, // Interleaved float 32-bit.
+ kAudioFormatPlanarS16, // Signed 16-bit planar.
+ kAudioFormatPlanarF32, // Float 32-bit planar.
+};
+
+// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php
+enum VideoFormat {
+ kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting.
+ kYv12, // 12bpp YVU planar 1x1 Y, 2x2 VU samples.
+ kI420 // 12bpp YVU planar 1x1 Y, 2x2 UV samples.
+};
+
+struct Size {
+ Size() : width(0), height(0) {}
+ Size(int32_t width, int32_t height) : width(width), height(height) {}
+
+ int32_t width;
+ int32_t height;
+};
+
+struct VideoDecoderConfig {
+ enum VideoCodec {
+ kUnknownVideoCodec = 0,
+ kCodecVp8,
+ kCodecH264,
+ kCodecVp9
+ };
+
+ enum VideoCodecProfile {
+ kUnknownVideoCodecProfile = 0,
+ kProfileNotNeeded,
+ kH264ProfileBaseline,
+ kH264ProfileMain,
+ kH264ProfileExtended,
+ kH264ProfileHigh,
+ kH264ProfileHigh10,
+ kH264ProfileHigh422,
+ kH264ProfileHigh444Predictive
+ };
+
+ VideoDecoderConfig()
+ : codec(kUnknownVideoCodec),
+ profile(kUnknownVideoCodecProfile),
+ format(kUnknownVideoFormat),
+ extra_data(NULL),
+ extra_data_size(0) {}
+
+ VideoCodec codec;
+ VideoCodecProfile profile;
+ VideoFormat format;
+
+ // Width and height of video frame immediately post-decode. Not all pixels
+ // in this region are valid.
+ Size coded_size;
+
+ // Optional byte data required to initialize video decoders, such as H.264
+ // AAVC data.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+};
+
+enum StreamType {
+ kStreamTypeAudio = 0,
+ kStreamTypeVideo = 1
+};
+
+// Structure provided to ContentDecryptionModule::OnPlatformChallengeResponse()
+// after a platform challenge was initiated via Host::SendPlatformChallenge().
+// All values will be NULL / zero in the event of a challenge failure.
+struct PlatformChallengeResponse {
+ // |challenge| provided during Host::SendPlatformChallenge() combined with
+ // nonce data and signed with the platform's private key.
+ const uint8_t* signed_data;
+ uint32_t signed_data_length;
+
+ // RSASSA-PKCS1-v1_5-SHA256 signature of the |signed_data| block.
+ const uint8_t* signed_data_signature;
+ uint32_t signed_data_signature_length;
+
+ // X.509 device specific certificate for the |service_id| requested.
+ const uint8_t* platform_key_certificate;
+ uint32_t platform_key_certificate_length;
+};
+
+// Used when passing arrays of binary data. Does not own the referenced data.
+struct BinaryData {
+ BinaryData() : data(NULL), length(0) {}
+ const uint8_t* data;
+ uint32_t length;
+};
+
+// The current status of the associated key. The valid types are defined in the
+// spec: https://w3c.github.io/encrypted-media/#idl-def-MediaKeyStatus
+enum KeyStatus {
+ kUsable = 0,
+ kInternalError = 1,
+ kExpired = 2,
+ kOutputRestricted = 3,
+ kOutputDownscaled = 4,
+ kStatusPending = 5,
+ kReleased = 6
+};
+
+// Used when passing arrays of key information. Does not own the referenced
+// data. |system_code| is an additional error code for unusable keys and
+// should be 0 when |status| == kUsable.
+struct KeyInformation {
+ KeyInformation()
+ : key_id(NULL), key_id_size(0), status(kInternalError), system_code(0) {}
+ const uint8_t* key_id;
+ uint32_t key_id_size;
+ KeyStatus status;
+ uint32_t system_code;
+};
+
+// Supported output protection methods for use with EnableOutputProtection() and
+// returned by OnQueryOutputProtectionStatus().
+enum OutputProtectionMethods {
+ kProtectionNone = 0,
+ kProtectionHDCP = 1 << 0
+};
+
+// Connected output link types returned by OnQueryOutputProtectionStatus().
+enum OutputLinkTypes {
+ kLinkTypeNone = 0,
+ kLinkTypeUnknown = 1 << 0,
+ kLinkTypeInternal = 1 << 1,
+ kLinkTypeVGA = 1 << 2,
+ kLinkTypeHDMI = 1 << 3,
+ kLinkTypeDVI = 1 << 4,
+ kLinkTypeDisplayPort = 1 << 5,
+ kLinkTypeNetwork = 1 << 6
+};
+
+// Result of the QueryOutputProtectionStatus() call.
+enum QueryResult {
+ kQuerySucceeded = 0,
+ kQueryFailed
+};
+
+// The Initialization Data Type. The valid types are defined in the spec:
+// http://w3c.github.io/encrypted-media/initdata-format-registry.html#registry
+enum InitDataType {
+ kCenc = 0,
+ kKeyIds = 1,
+ kWebM = 2
+};
+
+// The type of session to create. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#idl-def-SessionType
+enum SessionType {
+ kTemporary = 0,
+ kPersistentLicense = 1,
+ kPersistentKeyRelease = 2
+};
+
+// The type of the message event. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#idl-def-MediaKeyMessageType
+enum MessageType {
+ kLicenseRequest = 0,
+ kLicenseRenewal = 1,
+ kLicenseRelease = 2
+};
+
+// FileIO interface provides a way for the CDM to store data in a file in
+// persistent storage. This interface aims only at providing basic read/write
+// capabilities and should not be used as a full fledged file IO API.
+// Each CDM and origin (e.g. HTTPS, "foo.example.com", 443) combination has
+// its own persistent storage. All instances of a given CDM associated with a
+// given origin share the same persistent storage.
+// Note to implementors of this interface:
+// Per-origin storage and the ability for users to clear it are important.
+// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo.
+class FileIO {
+ public:
+ // Opens the file with |file_name| for read and write.
+ // FileIOClient::OnOpenComplete() will be called after the opening
+ // operation finishes.
+ // - When the file is opened by a CDM instance, it will be classified as "in
+ // use". In this case other CDM instances in the same domain may receive
+ // kInUse status when trying to open it.
+ // - |file_name| must not contain forward slash ('/') or backslash ('\'), and
+ // must not start with an underscore ('_').
+ virtual void Open(const char* file_name, uint32_t file_name_size) = 0;
+
+ // Reads the contents of the file. FileIOClient::OnReadComplete() will be
+ // called with the read status. Read() should not be called if a previous
+ // Read() or Write() call is still pending; otherwise OnReadComplete() will
+ // be called with kInUse.
+ virtual void Read() = 0;
+
+ // Writes |data_size| bytes of |data| into the file.
+ // FileIOClient::OnWriteComplete() will be called with the write status.
+ // All existing contents in the file will be overwritten. Calling Write() with
+ // NULL |data| will clear all contents in the file. Write() should not be
+ // called if a previous Write() or Read() call is still pending; otherwise
+ // OnWriteComplete() will be called with kInUse.
+ virtual void Write(const uint8_t* data, uint32_t data_size) = 0;
+
+ // Closes the file if opened, destroys this FileIO object and releases any
+ // resources allocated. The CDM must call this method when it finished using
+ // this object. A FileIO object must not be used after Close() is called.
+ virtual void Close() = 0;
+
+ protected:
+ FileIO() {}
+ virtual ~FileIO() {}
+};
+
+// Responses to FileIO calls. All responses will be called asynchronously.
+// When kError is returned, the FileIO object could be in an error state. All
+// following calls (other than Close()) could return kError. The CDM should
+// still call Close() to destroy the FileIO object.
+class FileIOClient {
+ public:
+ enum Status {
+ kSuccess = 0,
+ kInUse,
+ kError
+ };
+
+ // Response to a FileIO::Open() call with the open |status|.
+ virtual void OnOpenComplete(Status status) = 0;
+
+ // Response to a FileIO::Read() call to provide |data_size| bytes of |data|
+ // read from the file.
+ // - kSuccess indicates that all contents of the file has been successfully
+ // read. In this case, 0 |data_size| means that the file is empty.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates read failure, e.g. the storage is not open or cannot be
+ // fully read.
+ virtual void OnReadComplete(Status status,
+ const uint8_t* data, uint32_t data_size) = 0;
+
+ // Response to a FileIO::Write() call.
+ // - kSuccess indicates that all the data has been written into the file
+ // successfully.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates write failure, e.g. the storage is not open or cannot be
+ // fully written. Upon write failure, the contents of the file should be
+ // regarded as corrupt and should not used.
+ virtual void OnWriteComplete(Status status) = 0;
+
+ protected:
+ FileIOClient() {}
+ virtual ~FileIOClient() {}
+};
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class ContentDecryptionModule_7 {
+ public:
+ static const int kVersion = 7;
+ typedef Host_7 Host;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ const char* init_data_type,
+ uint32_t init_data_type_size,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id,
+ SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size,
+ const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kSessionError if |audio_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kSessionError if |video_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |video_frame| (|format| == kEmptyVideoFrame) is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result,
+ uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_7() {}
+ virtual ~ContentDecryptionModule_7() {}
+};
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class ContentDecryptionModule_8 {
+ public:
+ static const int kVersion = 8;
+ typedef Host_8 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id,
+ SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size,
+ const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kSessionError if |audio_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kSessionError if |video_decoder_config| is not supported. The CDM
+ // may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |video_frame| (|format| == kEmptyVideoFrame) is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result,
+ uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_8() {}
+ virtual ~ContentDecryptionModule_8() {}
+};
+
+typedef ContentDecryptionModule_8 ContentDecryptionModule;
+
+// Represents a buffer created by Allocator implementations.
+class Buffer {
+ public:
+ // Destroys the buffer in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ virtual uint32_t Capacity() const = 0;
+ virtual uint8_t* Data() = 0;
+ virtual void SetSize(uint32_t size) = 0;
+ virtual uint32_t Size() const = 0;
+
+ protected:
+ Buffer() {}
+ virtual ~Buffer() {}
+
+ private:
+ Buffer(const Buffer&);
+ void operator=(const Buffer&);
+};
+
+class Host_7 {
+ public:
+ static const int kVersion = 7;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time in seconds.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |error| must be specified, |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id,
+ Error error,
+ uint32_t system_code,
+ const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ // |legacy_destination_url| is only for supporting the prefixed EME API and
+ // is ignored by unprefixed EME. It should only be non-null if |message_type|
+ // is kLicenseRenewal.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type,
+ const char* message,
+ uint32_t message_size,
+ const char* legacy_destination_url,
+ uint32_t legacy_destination_url_length) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |key_ids| is the list of key ids for this session along with their
+ // current status. |key_ids_count| is the number of entries in |key_ids|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // can be 0 to represent "undefined". Size parameter should not include
+ // null termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when an error occurs in session |session_id|
+ // unrelated to one of the ContentDecryptionModule calls that accept a
+ // |promise_id|. |error| must be specified, |error_message| and
+ // |system_code| are optional. Length parameters should not include null
+ // termination.
+ // Note:
+ // - This method is only for supporting prefixed EME API.
+ // - This method will be ignored by unprefixed EME. All errors reported
+ // in this method should probably also be reported by one of other methods.
+ virtual void OnLegacySessionError(
+ const char* session_id, uint32_t session_id_length,
+ Error error,
+ uint32_t system_code,
+ const char* error_message, uint32_t error_message_length) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ protected:
+ Host_7() {}
+ virtual ~Host_7() {}
+};
+
+class Host_8 {
+ public:
+ static const int kVersion = 8;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time in seconds.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |error| must be specified, |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id,
+ Error error,
+ uint32_t system_code,
+ const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ // |legacy_destination_url| is only for supporting the prefixed EME API and
+ // is ignored by unprefixed EME. It should only be non-null if |message_type|
+ // is kLicenseRenewal.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type,
+ const char* message,
+ uint32_t message_size,
+ const char* legacy_destination_url,
+ uint32_t legacy_destination_url_length) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |key_ids| is the list of key ids for this session along with their
+ // current status. |key_ids_count| is the number of entries in |key_ids|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // can be 0 to represent "undefined". Size parameter should not include
+ // null termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when an error occurs in session |session_id|
+ // unrelated to one of the ContentDecryptionModule calls that accept a
+ // |promise_id|. |error| must be specified, |error_message| and
+ // |system_code| are optional. Length parameters should not include null
+ // termination.
+ // Note:
+ // - This method is only for supporting prefixed EME API.
+ // - This method will be ignored by unprefixed EME. All errors reported
+ // in this method should probably also be reported by one of other methods.
+ virtual void OnLegacySessionError(
+ const char* session_id, uint32_t session_id_length,
+ Error error,
+ uint32_t system_code,
+ const char* error_message, uint32_t error_message_length) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ protected:
+ Host_8() {}
+ virtual ~Host_8() {}
+};
+
+// Represents a decrypted block that has not been decoded.
+class DecryptedBlock {
+ public:
+ virtual void SetDecryptedBuffer(Buffer* buffer) = 0;
+ virtual Buffer* DecryptedBuffer() = 0;
+
+ // TODO(tomfinegan): Figure out if timestamp is really needed. If it is not,
+ // we can just pass Buffer pointers around.
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ DecryptedBlock() {}
+ virtual ~DecryptedBlock() {}
+};
+
+class VideoFrame {
+ public:
+ enum VideoPlane {
+ kYPlane = 0,
+ kUPlane = 1,
+ kVPlane = 2,
+ kMaxPlanes = 3,
+ };
+
+ virtual void SetFormat(VideoFormat format) = 0;
+ virtual VideoFormat Format() const = 0;
+
+ virtual void SetSize(cdm::Size size) = 0;
+ virtual cdm::Size Size() const = 0;
+
+ virtual void SetFrameBuffer(Buffer* frame_buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0;
+ virtual uint32_t PlaneOffset(VideoPlane plane) = 0;
+
+ virtual void SetStride(VideoPlane plane, uint32_t stride) = 0;
+ virtual uint32_t Stride(VideoPlane plane) = 0;
+
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ VideoFrame() {}
+ virtual ~VideoFrame() {}
+};
+
+// Represents decrypted and decoded audio frames. AudioFrames can contain
+// multiple audio output buffers, which are serialized into this format:
+//
+// |<------------------- serialized audio buffer ------------------->|
+// | int64_t timestamp | int64_t length | length bytes of audio data |
+//
+// For example, with three audio output buffers, the AudioFrames will look
+// like this:
+//
+// |<----------------- AudioFrames ------------------>|
+// | audio buffer 0 | audio buffer 1 | audio buffer 2 |
+class AudioFrames {
+ public:
+ virtual void SetFrameBuffer(Buffer* buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ // The CDM must call this method, providing a valid format, when providing
+ // frame buffers. Planar data should be stored end to end; e.g.,
+ // |ch1 sample1||ch1 sample2|....|ch1 sample_last||ch2 sample1|...
+ virtual void SetFormat(AudioFormat format) = 0;
+ virtual AudioFormat Format() const = 0;
+
+ protected:
+ AudioFrames() {}
+ virtual ~AudioFrames() {}
+};
+
+} // namespace cdm
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_H_
diff --git a/dom/media/gmp/widevine-adapter/moz.build b/dom/media/gmp/widevine-adapter/moz.build
new file mode 100644
index 000000000..a689a6393
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'WidevineAdapter.cpp',
+ 'WidevineDecryptor.cpp',
+ 'WidevineFileIO.cpp',
+ 'WidevineUtils.cpp',
+ 'WidevineVideoDecoder.cpp',
+ 'WidevineVideoFrame.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/media/gmp',
+]
+
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+include('/ipc/chromium/chromium-config.mozbuild')