diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/media/gmp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-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')
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, ¬es); +} + +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') |