diff options
author | Matt A. Tobin <email@mattatobin.com> | 2022-04-20 11:45:55 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2022-04-20 11:45:55 -0500 |
commit | 87dabd4e0b724bb81db3eaeefa09cfd2c5545a36 (patch) | |
tree | f91a4de96d859747a5028abf252e87d3361006cb /dom | |
parent | 1e640f82104e57d63e268785f75914ca7d3953ce (diff) | |
download | aura-central-87dabd4e0b724bb81db3eaeefa09cfd2c5545a36.tar.gz |
Issue #1 - Restore Gecko Media Plugins
Diffstat (limited to 'dom')
164 files changed, 22541 insertions, 19 deletions
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index f1e6c9bc1..8780fbf36 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -59,6 +59,7 @@ #include "mozilla/BasePrincipal.h" #include "mozilla/WebBrowserPersistDocumentChild.h" #include "imgLoader.h" +#include "GMPServiceChild.h" #include "mozilla/Unused.h" @@ -154,6 +155,7 @@ #include "mozilla/net/NeckoMessageUtils.h" #include "mozilla/widget/PuppetBidiKeyboard.h" #include "mozilla/RemoteSpellCheckEngineChild.h" +#include "GMPServiceChild.h" #include "gfxPlatform.h" #include "nscore.h" // for NS_FREE_PERMANENT_DATA @@ -163,6 +165,7 @@ using namespace mozilla::dom::ipc; using namespace mozilla::dom::workers; using namespace mozilla::media; using namespace mozilla::embedding; +using namespace mozilla::gmp; using namespace mozilla::hal_sandbox; using namespace mozilla::ipc; using namespace mozilla::layers; @@ -1114,6 +1117,20 @@ ContentChild::AllocPContentBridgeParent(mozilla::ipc::Transport* aTransport, return mLastBridge; } +PGMPServiceChild* +ContentChild::AllocPGMPServiceChild(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess) +{ + return GMPServiceChild::Create(aTransport, aOtherProcess); +} + +bool +ContentChild::RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) +{ + GeckoMediaPluginServiceChild::UpdateGMPCapabilities(Move(capabilities)); + return true; +} + bool ContentChild::RecvInitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor, Endpoint<PImageBridgeChild>&& aImageBridge, diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index 5ca7a053b..7b2298feb 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -140,6 +140,13 @@ public: AllocPContentBridgeChild(mozilla::ipc::Transport* transport, base::ProcessId otherProcess) override; + PGMPServiceChild* + AllocPGMPServiceChild(mozilla::ipc::Transport* transport, + base::ProcessId otherProcess) override; + + bool + RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) override; + bool RecvInitRendering( Endpoint<PCompositorBridgeChild>&& aCompositor, diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 76ffc5ba6..f20312807 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -21,6 +21,7 @@ #include "AppProcessChecker.h" #include "AudioChannelService.h" #include "BlobParent.h" +#include "GMPServiceParent.h" #include "HandlerServiceParent.h" #include "IHistory.h" #include "imgIContainer.h" @@ -219,6 +220,7 @@ using namespace mozilla::dom::power; using namespace mozilla::media; using namespace mozilla::embedding; using namespace mozilla::gfx; +using namespace mozilla::gmp; using namespace mozilla::hal; using namespace mozilla::ipc; using namespace mozilla::layers; @@ -855,6 +857,12 @@ static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement) } bool +ContentParent::RecvCreateGMPService() +{ + return PGMPService::Open(this); +} + +bool ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) { *aRv = NS_OK; @@ -1249,6 +1257,9 @@ ContentParent::Init() #endif } #endif + + RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton()); + gmps->UpdateContentProcessGMPCapabilities(); } void @@ -2515,6 +2526,13 @@ ContentParent::Observe(nsISupports* aSubject, return NS_OK; } +PGMPServiceParent* +ContentParent::AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess) +{ + return GMPServiceParent::Create(aTransport, aOtherProcess); +} + PBackgroundParent* ContentParent::AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess) diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index ab3258e4f..7a4d3d55a 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -237,6 +237,8 @@ public: virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) override; + virtual bool RecvCreateGMPService() override; + virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override; @@ -668,6 +670,10 @@ private: TabParent* aTopLevel, const TabId& aTabId, uint64_t* aId); + PGMPServiceParent* + AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess) override; + PBackgroundParent* AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess) override; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 991e90c23..70ef084cd 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -21,7 +21,15 @@ include protocol PImageBridge; include protocol PMedia; include protocol PMemoryReportRequest; include protocol PNecko; +// FIXME This is pretty ridiculous, but we have to keep the order of the +// following 4 includes, or the parser is confused about PGMPContent +// bridging PContent and PGMP. As soon as it registers the bridge between +// PContent and PPluginModule it seems to think that PContent's parent and +// child live in the same process! +include protocol PGMPContent; +include protocol PGMPService; include protocol PPluginModule; +include protocol PGMP; include protocol PPrinting; include protocol PSendStream; include protocol POfflineCacheUpdate; @@ -202,11 +210,25 @@ struct BlobURLRegistrationData Principal principal; }; +struct GMPAPITags +{ + nsCString api; + nsCString[] tags; +}; + +struct GMPCapabilityData +{ + nsCString name; + nsCString version; + GMPAPITags[] capabilities; +}; + nested(upto inside_cpow) sync protocol PContent { parent spawns PPluginModule; parent opens PProcessHangMonitor; + parent opens PGMPService; child opens PBackground; manages PBlob; @@ -492,6 +514,9 @@ child: async BlobURLUnregistration(nsCString aURI); + + async GMPsChanged(GMPCapabilityData[] capabilities); + parent: /** * Tell the content process some attributes of itself. This is @@ -523,6 +548,8 @@ parent: returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId); sync BridgeToChildProcess(ContentParentId cpId); + async CreateGMPService(); + /** * This call connects the content process to a plugin process. While this * call runs, a new PluginModuleParent will be created in the ContentChild diff --git a/dom/media/AbstractMediaDecoder.h b/dom/media/AbstractMediaDecoder.h index 119e38bfb..01722f8b1 100644 --- a/dom/media/AbstractMediaDecoder.h +++ b/dom/media/AbstractMediaDecoder.h @@ -16,6 +16,8 @@ #include "nsDataHashtable.h" #include "nsThreadUtils.h" +class GMPCrashHelper; + namespace mozilla { @@ -107,6 +109,8 @@ public: // Set by Reader if the current audio track can be offloaded virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) {} + virtual already_AddRefed<GMPCrashHelper> GetCrashHelper() { return nullptr; } + // Stack based class to assist in notifying the frame statistics of // parsed and decoded frames. Use inside video demux & decode functions // to ensure all parsed and decoded frames are reported on all return paths. diff --git a/dom/media/DecoderDoctorDiagnostics.cpp b/dom/media/DecoderDoctorDiagnostics.cpp index 1ca33e14b..778e8c4c5 100644 --- a/dom/media/DecoderDoctorDiagnostics.cpp +++ b/dom/media/DecoderDoctorDiagnostics.cpp @@ -839,6 +839,13 @@ DecoderDoctorDiagnostics::GetDescription() const if (mFFmpegFailedToLoad) { s += ", Linux platform decoder failed to load"; } + if (mGMPPDMFailedToStartup) { + s += ", GMP PDM failed to startup"; + } else if (!mGMP.IsEmpty()) { + s += ", Using GMP '"; + s += mGMP; + s += "'"; + } break; case eMediaKeySystemAccessRequest: s = "key system='"; diff --git a/dom/media/DecoderDoctorDiagnostics.h b/dom/media/DecoderDoctorDiagnostics.h index 51f7664b1..6ed04cfe3 100644 --- a/dom/media/DecoderDoctorDiagnostics.h +++ b/dom/media/DecoderDoctorDiagnostics.h @@ -79,9 +79,15 @@ public: void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; } bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; } + void SetGMPPDMFailedToStartup() { mGMPPDMFailedToStartup = true; } + bool DidGMPPDMFailToStartup() const { return mGMPPDMFailedToStartup; } + void SetVideoNotSupported() { mVideoNotSupported = true; } void SetAudioNotSupported() { mAudioNotSupported = true; } + void SetGMP(const nsACString& aGMP) { mGMP = aGMP; } + const nsACString& GMP() const { return mGMP; } + const nsAString& KeySystem() const { return mKeySystem; } bool IsKeySystemSupported() const { return mIsKeySystemSupported; } enum KeySystemIssue { @@ -114,6 +120,7 @@ private: bool mWMFFailedToLoad = false; bool mFFmpegFailedToLoad = false; + bool mGMPPDMFailedToStartup = false; bool mVideoNotSupported = false; bool mAudioNotSupported = false; nsCString mGMP; diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 3988d37b1..ebc694b47 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -29,6 +29,7 @@ #include "mozilla/dom/VideoTrack.h" #include "mozilla/dom/VideoTrackList.h" #include "nsPrintfCString.h" +#include "GMPService.h" #include "Layers.h" #include "mozilla/layers/ShadowLayers.h" @@ -925,6 +926,34 @@ MediaDecoder::OwnerHasError() const return mOwner->HasError(); } +class MediaElementGMPCrashHelper : public GMPCrashHelper +{ +public: + explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement) + : mElement(aElement) + { + MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. + } + already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override + { + MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. + if (!mElement) { + return nullptr; + } + return do_AddRef(mElement->OwnerDoc()->GetInnerWindow()); + } +private: + WeakPtr<HTMLMediaElement> mElement; +}; + +already_AddRefed<GMPCrashHelper> +MediaDecoder::GetCrashHelper() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mOwner->GetMediaElement() ? + MakeAndAddRef<MediaElementGMPCrashHelper>(mOwner->GetMediaElement()) : nullptr; +} + bool MediaDecoder::IsEnded() const { diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index a0b14b6da..5bc9e5000 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -242,6 +242,8 @@ public: // Must be called before Shutdown(). bool OwnerHasError() const; + already_AddRefed<GMPCrashHelper> GetCrashHelper() override; + protected: // Updates the media duration. This is called while the media is being // played, calls before the media has reached loaded metadata are ignored. diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 08505f306..7775f0d9b 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -359,6 +359,7 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack) : *ownerData.mOriginalInfo->GetAsAudioInfo(), ownerData.mTaskQueue, ownerData.mCallback.get(), + mOwner->mCrashHelper, ownerData.mIsBlankDecode, &result }); @@ -376,6 +377,7 @@ MediaFormatReader::DecoderFactory::DoCreateDecoder(TrackType aTrack) ownerData.mCallback.get(), mOwner->mKnowsCompositor, mOwner->GetImageContainer(), + mOwner->mCrashHelper, ownerData.mIsBlankDecode, &result }); @@ -568,6 +570,10 @@ MediaFormatReader::InitInternal() mVideo.mTaskQueue = new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER)); + // Note: GMPCrashHelper must be created on main thread, as it may use + // weak references, which aren't threadsafe. + mCrashHelper = mDecoder->GetCrashHelper(); + return NS_OK; } diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index 4e084f415..3da080450 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -579,6 +579,8 @@ private: RefPtr<VideoFrameContainer> mVideoFrameContainer; layers::ImageContainer* GetImageContainer(); + RefPtr<GMPCrashHelper> mCrashHelper; + void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode); class DecoderFactory; diff --git a/dom/media/MediaPrefs.h b/dom/media/MediaPrefs.h index 179711b5f..84163353a 100644 --- a/dom/media/MediaPrefs.h +++ b/dom/media/MediaPrefs.h @@ -100,6 +100,9 @@ private: // PlatformDecoderModule DECL_MEDIA_PREF("media.apple.forcevda", AppleForceVDA, bool, false); + DECL_MEDIA_PREF("media.gmp.insecure.allow", GMPAllowInsecure, bool, false); + DECL_MEDIA_PREF("media.gmp.async-shutdown-timeout", GMPAsyncShutdownTimeout, uint32_t, GMP_DEFAULT_ASYNC_SHUTDOWN_TIMEOUT); + DECL_MEDIA_PREF("media.eme.enabled", EMEEnabled, bool, false); DECL_MEDIA_PREF("media.use-blank-decoder", PDMUseBlankDecoder, bool, false); DECL_MEDIA_PREF("media.gpu-process-decoder", PDMUseGPUDecoder, bool, false); #ifdef MOZ_FFMPEG @@ -122,6 +125,9 @@ private: DECL_MEDIA_PREF("media.decoder.fuzzing.enabled", PDMFuzzingEnabled, bool, false); DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0); DECL_MEDIA_PREF("media.decoder.fuzzing.dont-delay-inputexhausted", PDMFuzzingDelayInputExhausted, bool, true); + DECL_MEDIA_PREF("media.gmp.decoder.enabled", PDMGMPEnabled, bool, true); + DECL_MEDIA_PREF("media.gmp.decoder.aac", GMPAACPreferred, uint32_t, 0); + DECL_MEDIA_PREF("media.gmp.decoder.h264", GMPH264Preferred, uint32_t, 0); // MediaDecoderStateMachine DECL_MEDIA_PREF("media.suspend-bkgnd-video.enabled", MDSMSuspendBackgroundVideoEnabled, bool, false); diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.info b/dom/media/gmp-plugin-openh264/fakeopenh264.info new file mode 100644 index 000000000..814a96be3 --- /dev/null +++ b/dom/media/gmp-plugin-openh264/fakeopenh264.info @@ -0,0 +1,4 @@ +Name: fakeopenh264 +Description: Fake GMP Plugin +Version: 1.0 +APIs: encode-video[h264:fake], decode-video[h264:fake] diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.voucher b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher new file mode 100644 index 000000000..2104d73c7 --- /dev/null +++ b/dom/media/gmp-plugin-openh264/fakeopenh264.voucher @@ -0,0 +1 @@ +gmp-fakeopenh264 placeholder voucher diff --git a/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp new file mode 100644 index 000000000..7dbc85251 --- /dev/null +++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp @@ -0,0 +1,429 @@ +/*! + * \copy + * Copyright (c) 2009-2014, Cisco Systems + * Copyright (c) 2014, Mozilla + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * + ************************************************************************************* + */ + +#include <stdint.h> +#include <time.h> +#include <cstdio> +#include <cstring> +#include <iostream> +#include <string> +#include <memory> +#include <assert.h> +#include <limits.h> + +#include "gmp-platform.h" +#include "gmp-video-host.h" +#include "gmp-video-encode.h" +#include "gmp-video-decode.h" +#include "gmp-video-frame-i420.h" +#include "gmp-video-frame-encoded.h" + +#if defined(GMP_FAKE_SUPPORT_DECRYPT) +#include "gmp-decryption.h" +#include "gmp-test-decryptor.h" +#include "gmp-test-storage.h" +#endif + +#if defined(_MSC_VER) +#define PUBLIC_FUNC __declspec(dllexport) +#else +#define PUBLIC_FUNC +#endif + +#define BIG_FRAME 10000 + +static int g_log_level = 0; + +#define GMPLOG(l, x) do { \ + if (l <= g_log_level) { \ + const char *log_string = "unknown"; \ + if ((l >= 0) && (l <= 3)) { \ + log_string = kLogStrings[l]; \ + } \ + std::cerr << log_string << ": " << x << std::endl; \ + } \ + } while(0) + +#define GL_CRIT 0 +#define GL_ERROR 1 +#define GL_INFO 2 +#define GL_DEBUG 3 + +const char* kLogStrings[] = { + "Critical", + "Error", + "Info", + "Debug" +}; + + +GMPPlatformAPI* g_platform_api = NULL; + +class FakeVideoEncoder; +class FakeVideoDecoder; + +struct EncodedFrame { + uint32_t length_; + uint8_t h264_compat_; + uint32_t magic_; + uint32_t width_; + uint32_t height_; + uint8_t y_; + uint8_t u_; + uint8_t v_; + uint32_t timestamp_; +}; + +#define ENCODED_FRAME_MAGIC 0x4652414d + +class FakeEncoderTask : public GMPTask { + public: + FakeEncoderTask(FakeVideoEncoder* encoder, + GMPVideoi420Frame* frame, + GMPVideoFrameType type) + : encoder_(encoder), frame_(frame), type_(type) {} + + virtual void Run(); + virtual void Destroy() { delete this; } + + FakeVideoEncoder* encoder_; + GMPVideoi420Frame* frame_; + GMPVideoFrameType type_; +}; + +class FakeVideoEncoder : public GMPVideoEncoder { + public: + explicit FakeVideoEncoder (GMPVideoHost* hostAPI) : + host_ (hostAPI), + callback_ (NULL) {} + + virtual void InitEncode (const GMPVideoCodec& codecSettings, + const uint8_t* aCodecSpecific, + uint32_t aCodecSpecificSize, + GMPVideoEncoderCallback* callback, + int32_t numberOfCores, + uint32_t maxPayloadSize) { + callback_ = callback; + + GMPLOG (GL_INFO, "Initialized encoder"); + } + + virtual void Encode (GMPVideoi420Frame* inputImage, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength, + const GMPVideoFrameType* aFrameTypes, + uint32_t aFrameTypesLength) { + GMPLOG (GL_DEBUG, + __FUNCTION__ + << " size=" + << inputImage->Width() << "x" << inputImage->Height()); + + assert (aFrameTypesLength != 0); + + g_platform_api->runonmainthread(new FakeEncoderTask(this, + inputImage, + aFrameTypes[0])); + } + + void Encode_m (GMPVideoi420Frame* inputImage, + GMPVideoFrameType frame_type) { + if (frame_type == kGMPKeyFrame) { + if (!inputImage) + return; + } + if (!inputImage) { + GMPLOG (GL_ERROR, "no input image"); + return; + } + + // Now return the encoded data back to the parent. + GMPVideoFrame* ftmp; + GMPErr err = host_->CreateFrame(kGMPEncodedVideoFrame, &ftmp); + if (err != GMPNoErr) { + GMPLOG (GL_ERROR, "Error creating encoded frame"); + return; + } + + GMPVideoEncodedFrame* f = static_cast<GMPVideoEncodedFrame*> (ftmp); + + // Encode this in a frame that looks a little bit like H.264. + // Note that we don't do PPS or SPS. + // Copy the data. This really should convert this to network byte order. + EncodedFrame eframe; + eframe.length_ = sizeof(eframe) - sizeof(uint32_t); + eframe.h264_compat_ = 5; // Emulate a H.264 IDR NAL. + eframe.magic_ = ENCODED_FRAME_MAGIC; + eframe.width_ = inputImage->Width(); + eframe.height_ = inputImage->Height(); + eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane), + inputImage->AllocatedSize(kGMPYPlane)); + eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane), + inputImage->AllocatedSize(kGMPUPlane)); + eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane), + inputImage->AllocatedSize(kGMPVPlane)); + + eframe.timestamp_ = inputImage->Timestamp(); + + err = f->CreateEmptyFrame (sizeof(eframe) + + (frame_type == kGMPKeyFrame ? sizeof(uint32_t) + BIG_FRAME : 0)); + if (err != GMPNoErr) { + GMPLOG (GL_ERROR, "Error allocating frame data"); + f->Destroy(); + return; + } + memcpy(f->Buffer(), &eframe, sizeof(eframe)); + if (frame_type == kGMPKeyFrame) { + *((uint32_t*) f->Buffer() + sizeof(eframe)) = BIG_FRAME; + } + + f->SetEncodedWidth (inputImage->Width()); + f->SetEncodedHeight (inputImage->Height()); + f->SetTimeStamp (inputImage->Timestamp()); + f->SetFrameType (frame_type); + f->SetCompleteFrame (true); + f->SetBufferType(GMP_BufferLength32); + + GMPLOG (GL_DEBUG, "Encoding complete. type= " + << f->FrameType() + << " length=" + << f->Size() + << " timestamp=" + << f->TimeStamp()); + + // Return the encoded frame. + GMPCodecSpecificInfo info; + memset (&info, 0, sizeof (info)); + info.mCodecType = kGMPVideoCodecH264; + info.mBufferType = GMP_BufferLength32; + info.mCodecSpecific.mH264.mSimulcastIdx = 0; + GMPLOG (GL_DEBUG, "Calling callback"); + callback_->Encoded (f, reinterpret_cast<uint8_t*> (&info), sizeof(info)); + GMPLOG (GL_DEBUG, "Callback called"); + } + + virtual void SetChannelParameters (uint32_t aPacketLoss, uint32_t aRTT) { + } + + virtual void SetRates (uint32_t aNewBitRate, uint32_t aFrameRate) { + } + + virtual void SetPeriodicKeyFrames (bool aEnable) { + } + + virtual void EncodingComplete() { + delete this; + } + + private: + uint8_t AveragePlane(uint8_t* ptr, size_t len) { + uint64_t val = 0; + + for (size_t i=0; i<len; ++i) { + val += ptr[i]; + } + + return (val / len) % 0xff; + } + + GMPVideoHost* host_; + GMPVideoEncoderCallback* callback_; +}; + +void FakeEncoderTask::Run() { + encoder_->Encode_m(frame_, type_); + frame_->Destroy(); +} + +class FakeDecoderTask : public GMPTask { + public: + FakeDecoderTask(FakeVideoDecoder* decoder, + GMPVideoEncodedFrame* frame, + int64_t time) + : decoder_(decoder), frame_(frame), time_(time) {} + + virtual void Run(); + virtual void Destroy() { delete this; } + + FakeVideoDecoder* decoder_; + GMPVideoEncodedFrame* frame_; + int64_t time_; +}; + +class FakeVideoDecoder : public GMPVideoDecoder { + public: + explicit FakeVideoDecoder (GMPVideoHost* hostAPI) : + host_ (hostAPI), + callback_ (NULL) {} + + virtual ~FakeVideoDecoder() { + } + + virtual void InitDecode (const GMPVideoCodec& codecSettings, + const uint8_t* aCodecSpecific, + uint32_t aCodecSpecificSize, + GMPVideoDecoderCallback* callback, + int32_t coreCount) { + GMPLOG (GL_INFO, "InitDecode"); + + callback_ = callback; + } + + virtual void Decode (GMPVideoEncodedFrame* inputFrame, + bool missingFrames, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength, + int64_t renderTimeMs = -1) { + GMPLOG (GL_DEBUG, __FUNCTION__ + << "Decoding frame size=" << inputFrame->Size() + << " timestamp=" << inputFrame->TimeStamp()); + g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs)); + } + + virtual void Reset() { + } + + virtual void Drain() { + } + + virtual void DecodingComplete() { + delete this; + } + + // Return the decoded data back to the parent. + void Decode_m (GMPVideoEncodedFrame* inputFrame, + int64_t renderTimeMs) { + EncodedFrame *eframe; + if (inputFrame->Size() != (sizeof(*eframe))) { + GMPLOG (GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size()); + return; + } + eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer()); + + if (eframe->magic_ != ENCODED_FRAME_MAGIC) { + GMPLOG (GL_ERROR, "Couldn't decode frame. Magic=" << eframe->magic_); + return; + } + + int width = eframe->width_; + int height = eframe->height_; + int ystride = eframe->width_; + int uvstride = eframe->width_/2; + + GMPLOG (GL_DEBUG, "Video frame ready for display " + << width + << "x" + << height + << " timestamp=" + << inputFrame->TimeStamp()); + + GMPVideoFrame* ftmp = NULL; + + // Translate the image. + GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp); + if (err != GMPNoErr) { + GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame"); + return; + } + + GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp); + err = frame->CreateEmptyFrame ( + width, height, + ystride, uvstride, uvstride); + if (err != GMPNoErr) { + GMPLOG (GL_ERROR, "Couldn't make decoded frame"); + return; + } + + memset(frame->Buffer(kGMPYPlane), + eframe->y_, + frame->AllocatedSize(kGMPYPlane)); + memset(frame->Buffer(kGMPUPlane), + eframe->u_, + frame->AllocatedSize(kGMPUPlane)); + memset(frame->Buffer(kGMPVPlane), + eframe->v_, + frame->AllocatedSize(kGMPVPlane)); + + GMPLOG (GL_DEBUG, "Allocated size = " + << frame->AllocatedSize (kGMPYPlane)); + frame->SetTimestamp (inputFrame->TimeStamp()); + frame->SetDuration (inputFrame->Duration()); + callback_->Decoded (frame); + + } + + GMPVideoHost* host_; + GMPVideoDecoderCallback* callback_; +}; + +void FakeDecoderTask::Run() { + decoder_->Decode_m(frame_, time_); + frame_->Destroy(); +} + +extern "C" { + + PUBLIC_FUNC GMPErr + GMPInit (GMPPlatformAPI* aPlatformAPI) { + g_platform_api = aPlatformAPI; + return GMPNoErr; + } + + PUBLIC_FUNC GMPErr + GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) { + if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) { + *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI)); + return GMPNoErr; + } else if (!strcmp (aApiName, GMP_API_VIDEO_ENCODER)) { + *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI)); + return GMPNoErr; +#if defined(GMP_FAKE_SUPPORT_DECRYPT) + } else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) { + *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI)); + return GMPNoErr; + } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) { + *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI)); + return GMPNoErr; +#endif + } + return GMPGenericErr; + } + + PUBLIC_FUNC void + GMPShutdown (void) { + g_platform_api = NULL; + } + +} // extern "C" diff --git a/dom/media/gmp-plugin-openh264/moz.build b/dom/media/gmp-plugin-openh264/moz.build new file mode 100644 index 000000000..d47a1d197 --- /dev/null +++ b/dom/media/gmp-plugin-openh264/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# largely a copy of dom/media/gmp-fake/moz.build + +FINAL_TARGET = 'dist/bin/gmp-fakeopenh264/1.0' + +FINAL_TARGET_FILES += [ + 'fakeopenh264.info', + 'fakeopenh264.voucher', +] + +SOURCES += [ + 'gmp-fake-openh264.cpp', +] + +SharedLibrary("fakeopenh264") + +if CONFIG['OS_ARCH'] == 'WINNT': + OS_LIBS += [ + 'ole32', + ] + +USE_STATIC_LIBS = True +NO_VISIBILITY_FLAGS = True +# Don't use STL wrappers; this isn't Gecko code +DISABLE_STL_WRAPPING = True diff --git a/dom/media/gmp-plugin/fake.info b/dom/media/gmp-plugin/fake.info new file mode 100644 index 000000000..b0592ff63 --- /dev/null +++ b/dom/media/gmp-plugin/fake.info @@ -0,0 +1,5 @@ +Name: fake +Description: Fake GMP Plugin, which deliberately uses GMP_API_DECRYPTOR_BACKWARDS_COMPAT for its decryptor. +Version: 1.0 +APIs: decode-video[h264:broken], eme-decrypt-v7[fake] +Libraries: dxva2.dll diff --git a/dom/media/gmp-plugin/fake.voucher b/dom/media/gmp-plugin/fake.voucher new file mode 100644 index 000000000..bb133701c --- /dev/null +++ b/dom/media/gmp-plugin/fake.voucher @@ -0,0 +1 @@ +gmp-fake placeholder voucher
\ No newline at end of file diff --git a/dom/media/gmp-plugin/gmp-fake.cpp b/dom/media/gmp-plugin/gmp-fake.cpp new file mode 100644 index 000000000..b67efe251 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-fake.cpp @@ -0,0 +1,98 @@ +/*! + * \copy + * Copyright (c) 2009-2014, Cisco Systems + * Copyright (c) 2014, Mozilla + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * + ************************************************************************************* + */ + +#include <stdint.h> +#include <cstdio> +#include <cstring> +#include <string> +#include <memory> + +#include "gmp-platform.h" +#include "gmp-video-decode.h" + +#if defined(GMP_FAKE_SUPPORT_DECRYPT) +#include "gmp-decryption.h" +#include "gmp-test-decryptor.h" +#include "gmp-test-storage.h" +#endif + +#if defined(_MSC_VER) +#define PUBLIC_FUNC __declspec(dllexport) +#else +#define PUBLIC_FUNC +#endif + +GMPPlatformAPI* g_platform_api = NULL; + +extern "C" { + + PUBLIC_FUNC GMPErr + GMPInit (GMPPlatformAPI* aPlatformAPI) { + g_platform_api = aPlatformAPI; + return GMPNoErr; + } + + PUBLIC_FUNC GMPErr + GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) { + if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) { + // Note: Deliberately advertise in our .info file that we support + // video-decode, but we fail the "get" call here to simulate what + // happens when decoder init fails. + return GMPGenericErr; +#if defined(GMP_FAKE_SUPPORT_DECRYPT) + } else if (!strcmp (aApiName, GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) { + *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI)); + return GMPNoErr; + } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) { + *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI)); + return GMPNoErr; +#endif + } + return GMPGenericErr; + } + + PUBLIC_FUNC void + GMPShutdown (void) { + g_platform_api = NULL; + } + +#if defined(GMP_FAKE_SUPPORT_DECRYPT) + PUBLIC_FUNC void + GMPSetNodeId(const char* aNodeId, uint32_t aLength) { + FakeDecryptor::SetNodeId(aNodeId, aLength); + } +#endif + +} // extern "C" diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.cpp b/dom/media/gmp-plugin/gmp-test-decryptor.cpp new file mode 100644 index 000000000..afa809602 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp @@ -0,0 +1,608 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gmp-test-decryptor.h" +#include "gmp-test-storage.h" +#include "gmp-test-output-protection.h" + +#include <string> +#include <vector> +#include <iostream> +#include <istream> +#include <iterator> +#include <sstream> +#include <set> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +using namespace std; + +std::string FakeDecryptor::sNodeId; + +FakeDecryptor* FakeDecryptor::sInstance = nullptr; +extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp + +class GMPMutexAutoLock +{ +public: + explicit GMPMutexAutoLock(GMPMutex* aMutex) : mMutex(aMutex) { + mMutex->Acquire(); + } + ~GMPMutexAutoLock() { + mMutex->Release(); + } +private: + GMPMutex* const mMutex; +}; + +class TestManager { +public: + TestManager() : mMutex(CreateMutex()) {} + + // Register a test with the test manager. + void BeginTest(const string& aTestID) { + GMPMutexAutoLock lock(mMutex); + auto found = mTestIDs.find(aTestID); + if (found == mTestIDs.end()) { + mTestIDs.insert(aTestID); + } else { + Error("FAIL BeginTest test already existed: " + aTestID); + } + } + + // Notify the test manager that the test is finished. If all tests are done, + // test manager will send "test-storage complete" to notify the parent that + // all tests are finished and also delete itself. + void EndTest(const string& aTestID) { + bool isEmpty = false; + { + GMPMutexAutoLock lock(mMutex); + auto found = mTestIDs.find(aTestID); + if (found != mTestIDs.end()) { + mTestIDs.erase(aTestID); + isEmpty = mTestIDs.empty(); + } else { + Error("FAIL EndTest test not existed: " + aTestID); + return; + } + } + if (isEmpty) { + Finish(); + delete this; + } + } + +private: + ~TestManager() { + mMutex->Destroy(); + } + + static void Error(const string& msg) { + FakeDecryptor::Message(msg); + } + + static void Finish() { + FakeDecryptor::Message("test-storage complete"); + } + + static GMPMutex* CreateMutex() { + GMPMutex* mutex = nullptr; + g_platform_api->createmutex(&mutex); + return mutex; + } + + GMPMutex* const mMutex; + set<string> mTestIDs; +}; + +FakeDecryptor::FakeDecryptor(GMPDecryptorHost* aHost) + : mCallback(nullptr) + , mHost(aHost) +{ + MOZ_ASSERT(!sInstance); + sInstance = this; +} + +void FakeDecryptor::DecryptingComplete() +{ + sInstance = nullptr; + delete this; +} + +void +FakeDecryptor::Message(const std::string& aMessage) +{ + MOZ_ASSERT(sInstance); + const static std::string sid("fake-session-id"); + sInstance->mCallback->SessionMessage(sid.c_str(), sid.size(), + kGMPLicenseRequest, + (const uint8_t*)aMessage.c_str(), aMessage.size()); +} + +std::vector<std::string> +Tokenize(const std::string& aString) +{ + std::stringstream strstr(aString); + std::istream_iterator<std::string> it(strstr), end; + return std::vector<std::string>(it, end); +} + +static const string TruncateRecordId = "truncate-record-id"; +static const string TruncateRecordData = "I will soon be truncated"; + +class ReadThenTask : public GMPTask { +public: + ReadThenTask(string aId, ReadContinuation* aThen) + : mId(aId) + , mThen(aThen) + {} + void Run() override { + ReadRecord(mId, mThen); + } + void Destroy() override { + delete this; + } + string mId; + ReadContinuation* mThen; +}; + +class SendMessageTask : public GMPTask { +public: + explicit SendMessageTask(const string& aMessage, + TestManager* aTestManager = nullptr, + const string& aTestID = "") + : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {} + + void Run() override { + FakeDecryptor::Message(mMessage); + if (mTestmanager) { + mTestmanager->EndTest(mTestID); + } + } + + void Destroy() override { + delete this; + } + +private: + string mMessage; + TestManager* const mTestmanager; + const string mTestID; +}; + +class TestEmptyContinuation : public ReadContinuation { +public: + TestEmptyContinuation(TestManager* aTestManager, const string& aTestID) + : mTestmanager(aTestManager), mTestID(aTestID) {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != "") { + FakeDecryptor::Message("FAIL TestEmptyContinuation record was not truncated"); + } + mTestmanager->EndTest(mTestID); + delete this; + } + +private: + TestManager* const mTestmanager; + const string mTestID; +}; + +class TruncateContinuation : public ReadContinuation { +public: + TruncateContinuation(const string& aID, + TestManager* aTestManager, + const string& aTestID) + : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != TruncateRecordData) { + FakeDecryptor::Message("FAIL TruncateContinuation read data doesn't match written data"); + } + auto cont = new TestEmptyContinuation(mTestmanager, mTestID); + auto msg = "FAIL in TruncateContinuation write."; + auto failTask = new SendMessageTask(msg, mTestmanager, mTestID); + WriteRecord(mID, nullptr, 0, new ReadThenTask(mID, cont), failTask); + delete this; + } + +private: + const string mID; + TestManager* const mTestmanager; + const string mTestID; +}; + +class VerifyAndFinishContinuation : public ReadContinuation { +public: + explicit VerifyAndFinishContinuation(string aValue, + TestManager* aTestManager, + const string& aTestID) + : mValue(aValue), mTestmanager(aTestManager), mTestID(aTestID) {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != mValue) { + FakeDecryptor::Message("FAIL VerifyAndFinishContinuation read data doesn't match expected data"); + } + mTestmanager->EndTest(mTestID); + delete this; + } + +private: + string mValue; + TestManager* const mTestmanager; + const string mTestID; +}; + +class VerifyAndOverwriteContinuation : public ReadContinuation { +public: + VerifyAndOverwriteContinuation(string aId, string aValue, string aOverwrite, + TestManager* aTestManager, const string& aTestID) + : mId(aId) + , mValue(aValue) + , mOverwrite(aOverwrite) + , mTestmanager(aTestManager) + , mTestID(aTestID) + {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != mValue) { + FakeDecryptor::Message("FAIL VerifyAndOverwriteContinuation read data doesn't match expected data"); + } + auto cont = new VerifyAndFinishContinuation(mOverwrite, mTestmanager, mTestID); + auto msg = "FAIL in VerifyAndOverwriteContinuation write."; + auto failTask = new SendMessageTask(msg, mTestmanager, mTestID); + WriteRecord(mId, mOverwrite, new ReadThenTask(mId, cont), failTask); + delete this; + } + +private: + string mId; + string mValue; + string mOverwrite; + TestManager* const mTestmanager; + const string mTestID; +}; + +static const string OpenAgainRecordId = "open-again-record-id"; + +class OpenedSecondTimeContinuation : public OpenContinuation { +public: + explicit OpenedSecondTimeContinuation(GMPRecord* aRecord, + TestManager* aTestManager, + const string& aTestID) + : mRecord(aRecord), mTestmanager(aTestManager), mTestID(aTestID) { + MOZ_ASSERT(aRecord); + } + + virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override { + if (GMP_SUCCEEDED(aStatus)) { + FakeDecryptor::Message("FAIL OpenSecondTimeContinuation should not be able to re-open record."); + } + if (aRecord) { + aRecord->Close(); + } + // Succeeded, open should have failed. + mTestmanager->EndTest(mTestID); + mRecord->Close(); + } + +private: + GMPRecord* mRecord; + TestManager* const mTestmanager; + const string mTestID; +}; + +class OpenedFirstTimeContinuation : public OpenContinuation { +public: + OpenedFirstTimeContinuation(const string& aID, + TestManager* aTestManager, + const string& aTestID) + : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {} + + virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override { + if (GMP_FAILED(aStatus)) { + FakeDecryptor::Message("FAIL OpenAgainContinuation to open record initially."); + mTestmanager->EndTest(mTestID); + if (aRecord) { + aRecord->Close(); + } + return; + } + + auto cont = new OpenedSecondTimeContinuation(aRecord, mTestmanager, mTestID); + GMPOpenRecord(mID, cont); + } + +private: + const string mID; + TestManager* const mTestmanager; + const string mTestID; +}; + +static void +DoTestStorage(const string& aPrefix, TestManager* aTestManager) +{ + // Basic I/O tests. We run three cases concurrently. The tests, like + // GMPStorage run asynchronously. When they've all passed, we send + // a message back to the parent process, or a failure message if not. + + // Test 1: Basic I/O test, and test that writing 0 bytes in a record + // deletes record. + // + // Write data to truncate record, then + // read data, verify that we read what we wrote, then + // write 0 bytes to truncate record, then + // read data, verify that 0 bytes was read + const string id1 = aPrefix + TruncateRecordId; + const string testID1 = aPrefix + "write-test-1"; + aTestManager->BeginTest(testID1); + auto cont1 = new TruncateContinuation(id1, aTestManager, testID1); + auto msg1 = "FAIL in TestStorage writing TruncateRecord."; + auto failTask1 = new SendMessageTask(msg1, aTestManager, testID1); + WriteRecord(id1, TruncateRecordData, + new ReadThenTask(id1, cont1), failTask1); + + // Test 2: Test that overwriting a record with a shorter record truncates + // the record to the shorter record. + // + // Write record, then + // read and verify record, then + // write a shorter record to same record. + // read and verify + string id2 = aPrefix + "record1"; + string record1 = "This is the first write to a record."; + string overwrite = "A shorter record"; + const string testID2 = aPrefix + "write-test-2"; + aTestManager->BeginTest(testID2); + auto task2 = new VerifyAndOverwriteContinuation(id2, record1, overwrite, + aTestManager, testID2); + auto msg2 = "FAIL in TestStorage writing record1."; + auto failTask2 = new SendMessageTask(msg2, aTestManager, testID2); + WriteRecord(id2, record1, new ReadThenTask(id2, task2), failTask2); + + // Test 3: Test that opening a record while it's already open fails. + // + // Open record1, then + // open record1, should fail. + // close record1 + const string id3 = aPrefix + OpenAgainRecordId; + const string testID3 = aPrefix + "open-test-1"; + aTestManager->BeginTest(testID3); + auto task3 = new OpenedFirstTimeContinuation(id3, aTestManager, testID3); + GMPOpenRecord(id3, task3); +} + +class TestStorageTask : public GMPTask { +public: + TestStorageTask(const string& aPrefix, TestManager* aTestManager) + : mPrefix(aPrefix), mTestManager(aTestManager) {} + virtual void Destroy() { delete this; } + virtual void Run() { + DoTestStorage(mPrefix, mTestManager); + } +private: + const string mPrefix; + TestManager* const mTestManager; +}; + +void +FakeDecryptor::TestStorage() +{ + TestManager* testManager = new TestManager(); + GMPThread* thread1 = nullptr; + GMPThread* thread2 = nullptr; + + // Main thread tests. + DoTestStorage("mt1-", testManager); + DoTestStorage("mt2-", testManager); + + // Off-main-thread tests. + if (GMP_SUCCEEDED(g_platform_api->createthread(&thread1))) { + thread1->Post(new TestStorageTask("thread1-", testManager)); + } else { + FakeDecryptor::Message("FAIL to create thread1 for storage tests"); + } + + if (GMP_SUCCEEDED(g_platform_api->createthread(&thread2))) { + thread2->Post(new TestStorageTask("thread2-", testManager)); + } else { + FakeDecryptor::Message("FAIL to create thread2 for storage tests"); + } + + if (thread1) { + thread1->Join(); + } + + if (thread2) { + thread2->Join(); + } + + // Note: Once all tests finish, TestManager will dispatch "test-pass" message, + // which ends the test for the parent. +} + +class ReportWritten : public GMPTask { +public: + ReportWritten(const string& aRecordId, const string& aValue) + : mRecordId(aRecordId) + , mValue(aValue) + {} + void Run() override { + FakeDecryptor::Message("stored " + mRecordId + " " + mValue); + } + void Destroy() override { + delete this; + } + const string mRecordId; + const string mValue; +}; + +class ReportReadStatusContinuation : public ReadContinuation { +public: + explicit ReportReadStatusContinuation(const string& aRecordId) + : mRecordId(aRecordId) + {} + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (GMP_FAILED(aErr)) { + FakeDecryptor::Message("retrieve " + mRecordId + " failed"); + } else { + stringstream ss; + ss << aData.size(); + string len; + ss >> len; + FakeDecryptor::Message("retrieve " + mRecordId + " succeeded (length " + + len + " bytes)"); + } + delete this; + } + string mRecordId; +}; + +class ReportReadRecordContinuation : public ReadContinuation { +public: + explicit ReportReadRecordContinuation(const string& aRecordId) + : mRecordId(aRecordId) + {} + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (GMP_FAILED(aErr)) { + FakeDecryptor::Message("retrieved " + mRecordId + " failed"); + } else { + FakeDecryptor::Message("retrieved " + mRecordId + " " + aData); + } + delete this; + } + string mRecordId; +}; + +static void +RecvGMPRecordIterator(GMPRecordIterator* aRecordIterator, + void* aUserArg, + GMPErr aStatus) +{ + FakeDecryptor* decryptor = reinterpret_cast<FakeDecryptor*>(aUserArg); + decryptor->ProcessRecordNames(aRecordIterator, aStatus); +} + +void +FakeDecryptor::ProcessRecordNames(GMPRecordIterator* aRecordIterator, + GMPErr aStatus) +{ + if (sInstance != this) { + FakeDecryptor::Message("Error aUserArg was not passed through GetRecordIterator"); + return; + } + if (GMP_FAILED(aStatus)) { + FakeDecryptor::Message("Error GetRecordIterator failed"); + return; + } + std::string response("record-names "); + bool first = true; + const char* name = nullptr; + uint32_t len = 0; + while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) { + std::string s(name, name+len); + if (!first) { + response += ","; + } else { + first = false; + } + response += s; + aRecordIterator->NextRecord(); + } + aRecordIterator->Close(); + FakeDecryptor::Message(response); +} + +enum ShutdownMode { + ShutdownNormal, + ShutdownTimeout, + ShutdownStoreToken +}; + +static ShutdownMode sShutdownMode = ShutdownNormal; +static string sShutdownToken = ""; + +void +FakeDecryptor::UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) +{ + std::string response((const char*)aResponse, (const char*)(aResponse)+aResponseSize); + std::vector<std::string> tokens = Tokenize(response); + const string& task = tokens[0]; + if (task == "test-storage") { + TestStorage(); + } else if (task == "store") { + // send "stored record" message on complete. + const string& id = tokens[1]; + const string& value = tokens[2]; + WriteRecord(id, + value, + new ReportWritten(id, value), + new SendMessageTask("FAIL in writing record.")); + } else if (task == "retrieve") { + const string& id = tokens[1]; + ReadRecord(id, new ReportReadStatusContinuation(id)); + } else if (task == "shutdown-mode") { + const string& mode = tokens[1]; + if (mode == "timeout") { + sShutdownMode = ShutdownTimeout; + } else if (mode == "token") { + sShutdownMode = ShutdownStoreToken; + sShutdownToken = tokens[2]; + Message("shutdown-token received " + sShutdownToken); + } + } else if (task == "retrieve-shutdown-token") { + ReadRecord("shutdown-token", new ReportReadRecordContinuation("shutdown-token")); + } else if (task == "test-op-apis") { + mozilla::gmptest::TestOuputProtectionAPIs(); + } else if (task == "retrieve-plugin-voucher") { + const uint8_t* rawVoucher = nullptr; + uint32_t length = 0; + mHost->GetPluginVoucher(&rawVoucher, &length); + std::string voucher((const char*)rawVoucher, (const char*)(rawVoucher + length)); + Message("retrieved plugin-voucher: " + voucher); + } else if (task == "retrieve-record-names") { + GMPEnumRecordNames(&RecvGMPRecordIterator, this); + } else if (task == "retrieve-node-id") { + Message("node-id " + sNodeId); + } +} + +class CompleteShutdownTask : public GMPTask { +public: + explicit CompleteShutdownTask(GMPAsyncShutdownHost* aHost) + : mHost(aHost) + { + } + virtual void Run() { + mHost->ShutdownComplete(); + } + virtual void Destroy() { delete this; } + GMPAsyncShutdownHost* mHost; +}; + +void +TestAsyncShutdown::BeginShutdown() { + switch (sShutdownMode) { + case ShutdownNormal: + mHost->ShutdownComplete(); + break; + case ShutdownTimeout: + // Don't do anything; wait for timeout, Gecko should kill + // the plugin and recover. + break; + case ShutdownStoreToken: + // Store message, then shutdown. + WriteRecord("shutdown-token", + sShutdownToken, + new CompleteShutdownTask(mHost), + new SendMessageTask("FAIL writing shutdown-token.")); + break; + } +} diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.h b/dom/media/gmp-plugin/gmp-test-decryptor.h new file mode 100644 index 000000000..3b17e42c5 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-decryptor.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FAKE_DECRYPTOR_H__ +#define FAKE_DECRYPTOR_H__ + +#include "gmp-decryption.h" +#include "gmp-async-shutdown.h" +#include <string> +#include "mozilla/Attributes.h" + +class FakeDecryptor : public GMPDecryptor7 { +public: + + explicit FakeDecryptor(GMPDecryptorHost* aHost); + + void Init(GMPDecryptorCallback* aCallback) override { + mCallback = aCallback; + } + + void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) override + { + } + + void LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + } + + void UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) override; + + void CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + } + + void RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + } + + void SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) override + { + } + + void Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) override + { + } + + void DecryptingComplete() override; + + static void Message(const std::string& aMessage); + + void ProcessRecordNames(GMPRecordIterator* aRecordIterator, + GMPErr aStatus); + + static void SetNodeId(const char* aNodeId, uint32_t aLength) { + sNodeId = std::string(aNodeId, aNodeId + aLength); + } + +private: + + virtual ~FakeDecryptor() {} + static FakeDecryptor* sInstance; + static std::string sNodeId; + + void TestStorage(); + + GMPDecryptorCallback* mCallback; + GMPDecryptorHost* mHost; +}; + +class TestAsyncShutdown : public GMPAsyncShutdown { +public: + explicit TestAsyncShutdown(GMPAsyncShutdownHost* aHost) + : mHost(aHost) + { + } + void BeginShutdown() override; +private: + GMPAsyncShutdownHost* mHost; +}; + +#endif diff --git a/dom/media/gmp-plugin/gmp-test-output-protection.h b/dom/media/gmp-plugin/gmp-test-output-protection.h new file mode 100644 index 000000000..93adf299c --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-output-protection.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(XP_WIN) +#include <d3d9.h> // needed to prevent re-definition of enums +#include <stdio.h> +#include <string> +#include <vector> +#include <windows.h> + +#include "opmapi.h" +#endif + +namespace mozilla { +namespace gmptest { + +#if defined(XP_WIN) +typedef HRESULT(STDAPICALLTYPE * OPMGetVideoOutputsFromHMONITORProc) + (HMONITOR, OPM_VIDEO_OUTPUT_SEMANTICS, ULONG*, IOPMVideoOutput***); + +static OPMGetVideoOutputsFromHMONITORProc sOPMGetVideoOutputsFromHMONITORProc = nullptr; + +static BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc, + LPRECT lprc, LPARAM pData) +{ + std::vector<std::string>* failureMsgs = (std::vector<std::string>*)pData; + + MONITORINFOEXA miex; + ZeroMemory(&miex, sizeof(miex)); + miex.cbSize = sizeof(miex); + if (!GetMonitorInfoA(hMonitor, &miex)) { + failureMsgs->push_back("FAIL GetMonitorInfoA call failed"); + } + + ULONG numVideoOutputs = 0; + IOPMVideoOutput** opmVideoOutputArray = nullptr; + HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(hMonitor, + OPM_VOS_OPM_SEMANTICS, + &numVideoOutputs, + &opmVideoOutputArray); + if (S_OK != hr) { + if (0x8007001f != hr && 0x80070032 != hr && 0xc02625e5 != hr) { + char msg[100]; + sprintf(msg, "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08x", hr); + failureMsgs->push_back(msg); + } + return true; + } + + DISPLAY_DEVICEA dd; + ZeroMemory(&dd, sizeof(dd)); + dd.cb = sizeof(dd); + if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) { + failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed"); + } + + for (ULONG i = 0; i < numVideoOutputs; ++i) { + OPM_RANDOM_NUMBER opmRandomNumber; + BYTE* certificate = nullptr; + ULONG certificateLength = 0; + hr = opmVideoOutputArray[i]->StartInitialization(&opmRandomNumber, + &certificate, + &certificateLength); + if (S_OK != hr) { + char msg[100]; + sprintf(msg, "FAIL StartInitialization call failed: HRESULT=0x%08x", hr); + failureMsgs->push_back(msg); + } + + if (certificate) { + CoTaskMemFree(certificate); + } + + opmVideoOutputArray[i]->Release(); + } + + if (opmVideoOutputArray) { + CoTaskMemFree(opmVideoOutputArray); + } + + return true; +} +#endif + +static void +RunOutputProtectionAPITests() +{ +#if defined(XP_WIN) + // Get hold of OPMGetVideoOutputsFromHMONITOR function. + HMODULE hDvax2DLL = GetModuleHandleW(L"dxva2.dll"); + if (!hDvax2DLL) { + FakeDecryptor::Message("FAIL GetModuleHandleW call failed for dxva2.dll"); + return; + } + + sOPMGetVideoOutputsFromHMONITORProc = (OPMGetVideoOutputsFromHMONITORProc) + GetProcAddress(hDvax2DLL, "OPMGetVideoOutputsFromHMONITOR"); + if (!sOPMGetVideoOutputsFromHMONITORProc) { + FakeDecryptor::Message("FAIL GetProcAddress call failed for OPMGetVideoOutputsFromHMONITOR"); + return; + } + + // Test EnumDisplayMonitors. + // Other APIs are tested in the callback function. + std::vector<std::string> failureMsgs; + if (!EnumDisplayMonitors(NULL, NULL, EnumDisplayMonitorsCallback, + (LPARAM) &failureMsgs)) { + FakeDecryptor::Message("FAIL EnumDisplayMonitors call failed"); + } + + // Report any failures in the callback function. + for (size_t i = 0; i < failureMsgs.size(); i++) { + FakeDecryptor::Message(failureMsgs[i]); + } +#endif +} + +static void +TestOuputProtectionAPIs() +{ + RunOutputProtectionAPITests(); + FakeDecryptor::Message("OP tests completed"); + return; +} + +} // namespace gmptest +} // namespace mozilla diff --git a/dom/media/gmp-plugin/gmp-test-storage.cpp b/dom/media/gmp-plugin/gmp-test-storage.cpp new file mode 100644 index 000000000..e75b49f42 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-storage.cpp @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gmp-test-storage.h" +#include <vector> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +class WriteRecordClient : public GMPRecordClient { +public: + GMPErr Init(GMPRecord* aRecord, + GMPTask* aOnSuccess, + GMPTask* aOnFailure, + const uint8_t* aData, + uint32_t aDataSize) { + mRecord = aRecord; + mOnSuccess = aOnSuccess; + mOnFailure = aOnFailure; + mData.insert(mData.end(), aData, aData + aDataSize); + return mRecord->Open(); + } + + void OpenComplete(GMPErr aStatus) override { + if (GMP_SUCCEEDED(aStatus)) { + mRecord->Write(mData.size() ? &mData.front() : nullptr, mData.size()); + } else { + GMPRunOnMainThread(mOnFailure); + mOnSuccess->Destroy(); + } + } + + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override {} + + void WriteComplete(GMPErr aStatus) override { + // Note: Call Close() before running continuation, in case the + // continuation tries to open the same record; if we call Close() + // after running the continuation, the Close() call will arrive + // just after the Open() call succeeds, immediately closing the + // record we just opened. + mRecord->Close(); + if (GMP_SUCCEEDED(aStatus)) { + GMPRunOnMainThread(mOnSuccess); + mOnFailure->Destroy(); + } else { + GMPRunOnMainThread(mOnFailure); + mOnSuccess->Destroy(); + } + delete this; + } + +private: + GMPRecord* mRecord; + GMPTask* mOnSuccess; + GMPTask* mOnFailure; + std::vector<uint8_t> mData; +}; + +GMPErr +WriteRecord(const std::string& aRecordName, + const uint8_t* aData, + uint32_t aNumBytes, + GMPTask* aOnSuccess, + GMPTask* aOnFailure) +{ + GMPRecord* record; + WriteRecordClient* client = new WriteRecordClient(); + auto err = GMPOpenRecord(aRecordName.c_str(), + aRecordName.size(), + &record, + client); + if (GMP_FAILED(err)) { + GMPRunOnMainThread(aOnFailure); + aOnSuccess->Destroy(); + return err; + } + return client->Init(record, aOnSuccess, aOnFailure, aData, aNumBytes); +} + +GMPErr +WriteRecord(const std::string& aRecordName, + const std::string& aData, + GMPTask* aOnSuccess, + GMPTask* aOnFailure) +{ + return WriteRecord(aRecordName, + (const uint8_t*)aData.c_str(), + aData.size(), + aOnSuccess, + aOnFailure); +} + +class ReadRecordClient : public GMPRecordClient { +public: + GMPErr Init(GMPRecord* aRecord, + ReadContinuation* aContinuation) { + mRecord = aRecord; + mContinuation = aContinuation; + return mRecord->Open(); + } + + void OpenComplete(GMPErr aStatus) override { + auto err = mRecord->Read(); + if (GMP_FAILED(err)) { + mContinuation->ReadComplete(err, ""); + delete this; + } + } + + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override { + // Note: Call Close() before running continuation, in case the + // continuation tries to open the same record; if we call Close() + // after running the continuation, the Close() call will arrive + // just after the Open() call succeeds, immediately closing the + // record we just opened. + mRecord->Close(); + std::string data((const char*)aData, aDataSize); + mContinuation->ReadComplete(GMPNoErr, data); + delete this; + } + + void WriteComplete(GMPErr aStatus) override { + } + +private: + GMPRecord* mRecord; + ReadContinuation* mContinuation; +}; + +GMPErr +ReadRecord(const std::string& aRecordName, + ReadContinuation* aContinuation) +{ + MOZ_ASSERT(aContinuation); + GMPRecord* record; + ReadRecordClient* client = new ReadRecordClient(); + auto err = GMPOpenRecord(aRecordName.c_str(), + aRecordName.size(), + &record, + client); + if (GMP_FAILED(err)) { + return err; + } + return client->Init(record, aContinuation); +} + +extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp + +GMPErr +GMPOpenRecord(const char* aName, + uint32_t aNameLength, + GMPRecord** aOutRecord, + GMPRecordClient* aClient) +{ + MOZ_ASSERT(g_platform_api); + return g_platform_api->createrecord(aName, aNameLength, aOutRecord, aClient); +} + +GMPErr +GMPRunOnMainThread(GMPTask* aTask) +{ + MOZ_ASSERT(g_platform_api); + return g_platform_api->runonmainthread(aTask); +} + +class OpenRecordClient : public GMPRecordClient { +public: + /* + * This function will take the memory ownership of the parameters and + * delete them when done. + */ + static void Open(const std::string& aRecordName, + OpenContinuation* aContinuation) { + MOZ_ASSERT(aContinuation); + (new OpenRecordClient(aContinuation))->Do(aRecordName); + } + + void OpenComplete(GMPErr aStatus) override { + Done(aStatus); + } + + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override { + MOZ_CRASH("Should not reach here."); + } + + void WriteComplete(GMPErr aStatus) override { + MOZ_CRASH("Should not reach here."); + } + +private: + explicit OpenRecordClient(OpenContinuation* aContinuation) + : mRecord(nullptr), mContinuation(aContinuation) {} + + void Do(const std::string& aName) { + auto err = GMPOpenRecord(aName.c_str(), aName.size(), &mRecord, this); + if (GMP_FAILED(err) || + GMP_FAILED(err = mRecord->Open())) { + Done(err); + } + } + + void Done(GMPErr err) { + // mContinuation is responsible for closing mRecord. + mContinuation->OpenComplete(err, mRecord); + delete mContinuation; + delete this; + } + + GMPRecord* mRecord; + OpenContinuation* mContinuation; +}; + +void +GMPOpenRecord(const std::string& aRecordName, + OpenContinuation* aContinuation) +{ + OpenRecordClient::Open(aRecordName, aContinuation); +} + +GMPErr +GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg) +{ + return g_platform_api->getrecordenumerator(aRecvIteratorFunc, aUserArg); +} diff --git a/dom/media/gmp-plugin/gmp-test-storage.h b/dom/media/gmp-plugin/gmp-test-storage.h new file mode 100644 index 000000000..d77f8ebb3 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-storage.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TEST_GMP_STORAGE_H__ +#define TEST_GMP_STORAGE_H__ + +#include "gmp-errors.h" +#include "gmp-platform.h" +#include <string> + +class ReadContinuation { +public: + virtual ~ReadContinuation() {} + virtual void ReadComplete(GMPErr aErr, const std::string& aData) = 0; +}; + +// Reads a record to storage using GMPRecord. +// Calls ReadContinuation with read data. +GMPErr +ReadRecord(const std::string& aRecordName, + ReadContinuation* aContinuation); + +// Writes a record to storage using GMPRecord. +// Runs continuation when data is written. +GMPErr +WriteRecord(const std::string& aRecordName, + const std::string& aData, + GMPTask* aOnSuccess, + GMPTask* aOnFailure); + +GMPErr +WriteRecord(const std::string& aRecordName, + const uint8_t* aData, + uint32_t aNumBytes, + GMPTask* aOnSuccess, + GMPTask* aOnFailure); + +GMPErr +GMPOpenRecord(const char* aName, + uint32_t aNameLength, + GMPRecord** aOutRecord, + GMPRecordClient* aClient); + +GMPErr +GMPRunOnMainThread(GMPTask* aTask); + +class OpenContinuation { +public: + virtual ~OpenContinuation() {} + virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) = 0; +}; + +void +GMPOpenRecord(const std::string& aRecordName, + OpenContinuation* aContinuation); + +GMPErr +GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg); + +#endif // TEST_GMP_STORAGE_H__ diff --git a/dom/media/gmp-plugin/moz.build b/dom/media/gmp-plugin/moz.build new file mode 100644 index 000000000..39061d0c9 --- /dev/null +++ b/dom/media/gmp-plugin/moz.build @@ -0,0 +1,31 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_TARGET = 'dist/bin/gmp-fake/1.0' + +FINAL_TARGET_FILES += [ + 'fake.info', + 'fake.voucher', +] + +SOURCES += [ + 'gmp-fake.cpp', + 'gmp-test-decryptor.cpp', + 'gmp-test-storage.cpp', +] + +DEFINES['GMP_FAKE_SUPPORT_DECRYPT'] = True + +SharedLibrary("fake") + +if CONFIG['OS_ARCH'] == 'WINNT': + OS_LIBS += [ + 'ole32', + ] + +USE_STATIC_LIBS = True +NO_VISIBILITY_FLAGS = True +# Don't use STL wrappers; this isn't Gecko code +DISABLE_STL_WRAPPING = True diff --git a/dom/media/gmp/GMPAudioDecoderChild.cpp b/dom/media/gmp/GMPAudioDecoderChild.cpp new file mode 100644 index 000000000..53550d4a1 --- /dev/null +++ b/dom/media/gmp/GMPAudioDecoderChild.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPAudioDecoderChild.h" +#include "GMPContentChild.h" +#include "GMPAudioHost.h" +#include "mozilla/Unused.h" +#include <stdio.h> + +namespace mozilla { +namespace gmp { + +GMPAudioDecoderChild::GMPAudioDecoderChild(GMPContentChild* aPlugin) + : mPlugin(aPlugin) + , mAudioDecoder(nullptr) +{ + MOZ_ASSERT(mPlugin); +} + +GMPAudioDecoderChild::~GMPAudioDecoderChild() +{ +} + +void +GMPAudioDecoderChild::Init(GMPAudioDecoder* aDecoder) +{ + MOZ_ASSERT(aDecoder, "Cannot initialize Audio decoder child without a Audio decoder!"); + mAudioDecoder = aDecoder; +} + +GMPAudioHostImpl& +GMPAudioDecoderChild::Host() +{ + return mAudioHost; +} + +void +GMPAudioDecoderChild::Decoded(GMPAudioSamples* aDecodedSamples) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + if (!aDecodedSamples) { + MOZ_CRASH("Not given decoded audio samples!"); + } + + GMPAudioDecodedSampleData samples; + samples.mData().AppendElements((int16_t*)aDecodedSamples->Buffer(), + aDecodedSamples->Size() / sizeof(int16_t)); + samples.mTimeStamp() = aDecodedSamples->TimeStamp(); + samples.mChannelCount() = aDecodedSamples->Channels(); + samples.mSamplesPerSecond() = aDecodedSamples->Rate(); + + Unused << SendDecoded(samples); + + aDecodedSamples->Destroy(); +} + +void +GMPAudioDecoderChild::InputDataExhausted() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + Unused << SendInputDataExhausted(); +} + +void +GMPAudioDecoderChild::DrainComplete() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + Unused << SendDrainComplete(); +} + +void +GMPAudioDecoderChild::ResetComplete() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + Unused << SendResetComplete(); +} + +void +GMPAudioDecoderChild::Error(GMPErr aError) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + Unused << SendError(aError); +} + +bool +GMPAudioDecoderChild::RecvInitDecode(const GMPAudioCodecData& a) +{ + MOZ_ASSERT(mAudioDecoder); + if (!mAudioDecoder) { + return false; + } + + GMPAudioCodec codec; + codec.mCodecType = a.mCodecType(); + codec.mChannelCount = a.mChannelCount(); + codec.mBitsPerChannel = a.mBitsPerChannel(); + codec.mSamplesPerSecond = a.mSamplesPerSecond(); + codec.mExtraData = a.mExtraData().Elements(); + codec.mExtraDataLen = a.mExtraData().Length(); + + // Ignore any return code. It is OK for this to fail without killing the process. + mAudioDecoder->InitDecode(codec, this); + + return true; +} + +bool +GMPAudioDecoderChild::RecvDecode(const GMPAudioEncodedSampleData& aEncodedSamples) +{ + if (!mAudioDecoder) { + return false; + } + + GMPAudioSamples* samples = new GMPAudioSamplesImpl(aEncodedSamples); + + // Ignore any return code. It is OK for this to fail without killing the process. + mAudioDecoder->Decode(samples); + + return true; +} + +bool +GMPAudioDecoderChild::RecvReset() +{ + if (!mAudioDecoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mAudioDecoder->Reset(); + + return true; +} + +bool +GMPAudioDecoderChild::RecvDrain() +{ + if (!mAudioDecoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mAudioDecoder->Drain(); + + return true; +} + +bool +GMPAudioDecoderChild::RecvDecodingComplete() +{ + if (mAudioDecoder) { + // Ignore any return code. It is OK for this to fail without killing the process. + mAudioDecoder->DecodingComplete(); + mAudioDecoder = nullptr; + } + + mPlugin = nullptr; + + Unused << Send__delete__(this); + + return true; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPAudioDecoderChild.h b/dom/media/gmp/GMPAudioDecoderChild.h new file mode 100644 index 000000000..0393fa573 --- /dev/null +++ b/dom/media/gmp/GMPAudioDecoderChild.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPAudioDecoderChild_h_ +#define GMPAudioDecoderChild_h_ + +#include "mozilla/gmp/PGMPAudioDecoderChild.h" +#include "gmp-audio-decode.h" +#include "GMPAudioHost.h" + +namespace mozilla { +namespace gmp { + +class GMPContentChild; + +class GMPAudioDecoderChild : public PGMPAudioDecoderChild, + public GMPAudioDecoderCallback +{ +public: + explicit GMPAudioDecoderChild(GMPContentChild* aPlugin); + virtual ~GMPAudioDecoderChild(); + + void Init(GMPAudioDecoder* aDecoder); + GMPAudioHostImpl& Host(); + + // GMPAudioDecoderCallback + void Decoded(GMPAudioSamples* aEncodedSamples) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aError) override; + +private: + // PGMPAudioDecoderChild + bool RecvInitDecode(const GMPAudioCodecData& codecSettings) override; + bool RecvDecode(const GMPAudioEncodedSampleData& input) override; + bool RecvReset() override; + bool RecvDrain() override; + bool RecvDecodingComplete() override; + + GMPContentChild* mPlugin; + GMPAudioDecoder* mAudioDecoder; + GMPAudioHostImpl mAudioHost; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPAudioDecoderChild_h_ diff --git a/dom/media/gmp/GMPAudioDecoderParent.cpp b/dom/media/gmp/GMPAudioDecoderParent.cpp new file mode 100644 index 000000000..592c7719b --- /dev/null +++ b/dom/media/gmp/GMPAudioDecoderParent.cpp @@ -0,0 +1,369 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPAudioDecoderParent.h" +#include "GMPContentParent.h" +#include <stdio.h> +#include "mozilla/Unused.h" +#include "GMPMessageUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Logging.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg) +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +namespace gmp { + +GMPAudioDecoderParent::GMPAudioDecoderParent(GMPContentParent* aPlugin) + : mIsOpen(false) + , mShuttingDown(false) + , mActorDestroyed(false) + , mIsAwaitingResetComplete(false) + , mIsAwaitingDrainComplete(false) + , mPlugin(aPlugin) + , mCallback(nullptr) +{ + MOZ_ASSERT(mPlugin); +} + +GMPAudioDecoderParent::~GMPAudioDecoderParent() +{ +} + +nsresult +GMPAudioDecoderParent::InitDecode(GMPAudioCodecType aCodecType, + uint32_t aChannelCount, + uint32_t aBitsPerChannel, + uint32_t aSamplesPerSecond, + nsTArray<uint8_t>& aExtraData, + GMPAudioDecoderCallbackProxy* aCallback) +{ + LOGD(("GMPAudioDecoderParent[%p]::InitDecode()", this)); + + if (mIsOpen) { + NS_WARNING("Trying to re-init an in-use GMP audio decoder!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!aCallback) { + return NS_ERROR_FAILURE; + } + mCallback = aCallback; + + GMPAudioCodecData data; + data.mCodecType() = aCodecType; + data.mChannelCount() = aChannelCount; + data.mBitsPerChannel() = aBitsPerChannel; + data.mSamplesPerSecond() = aSamplesPerSecond; + data.mExtraData() = aExtraData; + if (!SendInitDecode(data)) { + return NS_ERROR_FAILURE; + } + mIsOpen = true; + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +nsresult +GMPAudioDecoderParent::Decode(GMPAudioSamplesImpl& aEncodedSamples) +{ + LOGV(("GMPAudioDecoderParent[%p]::Decode() timestamp=%lld", + this, aEncodedSamples.TimeStamp())); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP Audio decoder!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + GMPAudioEncodedSampleData samples; + aEncodedSamples.RelinquishData(samples); + + if (!SendDecode(samples)) { + return NS_ERROR_FAILURE; + } + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +nsresult +GMPAudioDecoderParent::Reset() +{ + LOGD(("GMPAudioDecoderParent[%p]::Reset()", this)); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP Audio decoder!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendReset()) { + return NS_ERROR_FAILURE; + } + + mIsAwaitingResetComplete = true; + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +nsresult +GMPAudioDecoderParent::Drain() +{ + LOGD(("GMPAudioDecoderParent[%p]::Drain()", this)); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP Audio decoder!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendDrain()) { + return NS_ERROR_FAILURE; + } + + mIsAwaitingDrainComplete = true; + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +// Note: Consider keeping ActorDestroy sync'd up when making changes here. +nsresult +GMPAudioDecoderParent::Close() +{ + LOGD(("GMPAudioDecoderParent[%p]::Close()", this)); + MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); + + // Ensure if we've received a Close while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the close. This seems unlikely to happen, but better to be careful. + UnblockResetAndDrain(); + + // Consumer is done with us; we can shut down. No more callbacks should + // be made to mCallback. Note: do this before Shutdown()! + mCallback = nullptr; + // Let Shutdown mark us as dead so it knows if we had been alive + + // In case this is the last reference + RefPtr<GMPAudioDecoderParent> kungfudeathgrip(this); + Release(); + Shutdown(); + + return NS_OK; +} + +// Note: Consider keeping ActorDestroy sync'd up when making changes here. +nsresult +GMPAudioDecoderParent::Shutdown() +{ + LOGD(("GMPAudioDecoderParent[%p]::Shutdown()", this)); + MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (mShuttingDown) { + return NS_OK; + } + mShuttingDown = true; + + // Ensure if we've received a shutdown while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the shutdown. + UnblockResetAndDrain(); + + // Notify client we're gone! Won't occur after Close() + if (mCallback) { + mCallback->Terminated(); + mCallback = nullptr; + } + + mIsOpen = false; + if (!mActorDestroyed) { + Unused << SendDecodingComplete(); + } + + return NS_OK; +} + +// Note: Keep this sync'd up with DecodingComplete +void +GMPAudioDecoderParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD(("GMPAudioDecoderParent[%p]::ActorDestroy(reason=%d)", this, aWhy)); + + mIsOpen = false; + mActorDestroyed = true; + + // Ensure if we've received a destroy while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the error. + UnblockResetAndDrain(); + + if (mCallback) { + // May call Close() (and Shutdown()) immediately or with a delay + mCallback->Terminated(); + mCallback = nullptr; + } + if (mPlugin) { + // Ignore any return code. It is OK for this to fail without killing the process. + mPlugin->AudioDecoderDestroyed(this); + mPlugin = nullptr; + } + MaybeDisconnect(aWhy == AbnormalShutdown); +} + +bool +GMPAudioDecoderParent::RecvDecoded(const GMPAudioDecodedSampleData& aDecoded) +{ + LOGV(("GMPAudioDecoderParent[%p]::RecvDecoded() timestamp=%lld", + this, aDecoded.mTimeStamp())); + + if (!mCallback) { + return false; + } + + mCallback->Decoded(aDecoded.mData(), + aDecoded.mTimeStamp(), + aDecoded.mChannelCount(), + aDecoded.mSamplesPerSecond()); + + return true; +} + +bool +GMPAudioDecoderParent::RecvInputDataExhausted() +{ + LOGV(("GMPAudioDecoderParent[%p]::RecvInputDataExhausted()", this)); + + if (!mCallback) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->InputDataExhausted(); + + return true; +} + +bool +GMPAudioDecoderParent::RecvDrainComplete() +{ + LOGD(("GMPAudioDecoderParent[%p]::RecvDrainComplete()", this)); + + if (!mCallback) { + return false; + } + + if (!mIsAwaitingDrainComplete) { + return true; + } + mIsAwaitingDrainComplete = false; + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->DrainComplete(); + + return true; +} + +bool +GMPAudioDecoderParent::RecvResetComplete() +{ + LOGD(("GMPAudioDecoderParent[%p]::RecvResetComplete()", this)); + + if (!mCallback) { + return false; + } + + if (!mIsAwaitingResetComplete) { + return true; + } + mIsAwaitingResetComplete = false; + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->ResetComplete(); + + return true; +} + +bool +GMPAudioDecoderParent::RecvError(const GMPErr& aError) +{ + LOGD(("GMPAudioDecoderParent[%p]::RecvError(error=%d)", this, aError)); + + if (!mCallback) { + return false; + } + + // Ensure if we've received an error while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the error. + UnblockResetAndDrain(); + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->Error(aError); + + return true; +} + +bool +GMPAudioDecoderParent::RecvShutdown() +{ + LOGD(("GMPAudioDecoderParent[%p]::RecvShutdown()", this)); + + Shutdown(); + return true; +} + +bool +GMPAudioDecoderParent::Recv__delete__() +{ + LOGD(("GMPAudioDecoderParent[%p]::Recv__delete__()", this)); + + if (mPlugin) { + // Ignore any return code. It is OK for this to fail without killing the process. + mPlugin->AudioDecoderDestroyed(this); + mPlugin = nullptr; + } + + return true; +} + +void +GMPAudioDecoderParent::UnblockResetAndDrain() +{ + LOGD(("GMPAudioDecoderParent[%p]::UnblockResetAndDrain()", this)); + + if (!mCallback) { + MOZ_ASSERT(!mIsAwaitingResetComplete); + MOZ_ASSERT(!mIsAwaitingDrainComplete); + return; + } + if (mIsAwaitingResetComplete) { + mIsAwaitingResetComplete = false; + mCallback->ResetComplete(); + } + if (mIsAwaitingDrainComplete) { + mIsAwaitingDrainComplete = false; + mCallback->DrainComplete(); + } +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPAudioDecoderParent.h b/dom/media/gmp/GMPAudioDecoderParent.h new file mode 100644 index 000000000..5ced5ca61 --- /dev/null +++ b/dom/media/gmp/GMPAudioDecoderParent.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPAudioDecoderParent_h_ +#define GMPAudioDecoderParent_h_ + +#include "mozilla/RefPtr.h" +#include "gmp-audio-decode.h" +#include "gmp-audio-codec.h" +#include "mozilla/gmp/PGMPAudioDecoderParent.h" +#include "GMPMessageUtils.h" +#include "GMPAudioDecoderProxy.h" +#include "GMPCrashHelperHolder.h" + +namespace mozilla { +namespace gmp { + +class GMPContentParent; + +class GMPAudioDecoderParent final : public GMPAudioDecoderProxy + , public PGMPAudioDecoderParent + , public GMPCrashHelperHolder +{ +public: + NS_INLINE_DECL_REFCOUNTING(GMPAudioDecoderParent) + + explicit GMPAudioDecoderParent(GMPContentParent *aPlugin); + + nsresult Shutdown(); + + // GMPAudioDecoderProxy + nsresult InitDecode(GMPAudioCodecType aCodecType, + uint32_t aChannelCount, + uint32_t aBitsPerChannel, + uint32_t aSamplesPerSecond, + nsTArray<uint8_t>& aExtraData, + GMPAudioDecoderCallbackProxy* aCallback) override; + nsresult Decode(GMPAudioSamplesImpl& aInput) override; + nsresult Reset() override; + nsresult Drain() override; + nsresult Close() override; + +private: + ~GMPAudioDecoderParent(); + + // PGMPAudioDecoderParent + void ActorDestroy(ActorDestroyReason aWhy) override; + bool RecvDecoded(const GMPAudioDecodedSampleData& aDecoded) override; + bool RecvInputDataExhausted() override; + bool RecvDrainComplete() override; + bool RecvResetComplete() override; + bool RecvError(const GMPErr& aError) override; + bool RecvShutdown() override; + bool Recv__delete__() override; + + void UnblockResetAndDrain(); + + bool mIsOpen; + bool mShuttingDown; + bool mActorDestroyed; + bool mIsAwaitingResetComplete; + bool mIsAwaitingDrainComplete; + RefPtr<GMPContentParent> mPlugin; + GMPAudioDecoderCallbackProxy* mCallback; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPAudioDecoderParent_h_ diff --git a/dom/media/gmp/GMPAudioDecoderProxy.h b/dom/media/gmp/GMPAudioDecoderProxy.h new file mode 100644 index 000000000..6d71ba089 --- /dev/null +++ b/dom/media/gmp/GMPAudioDecoderProxy.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPAudioDecoderProxy_h_ +#define GMPAudioDecoderProxy_h_ + +#include "GMPCallbackBase.h" +#include "gmp-audio-codec.h" +#include "GMPAudioHost.h" +#include "nsTArray.h" +#include "mozilla/gmp/GMPTypes.h" + +class GMPAudioDecoderCallbackProxy : public GMPCallbackBase { +public: + virtual ~GMPAudioDecoderCallbackProxy() {} + // Note: aChannelCount and aSamplesPerSecond may not be consistent from + // one invocation to the next. + virtual void Decoded(const nsTArray<int16_t>& aPCM, + uint64_t aTimeStamp, + uint32_t aChannelCount, + uint32_t aSamplesPerSecond) = 0; + virtual void InputDataExhausted() = 0; + virtual void DrainComplete() = 0; + virtual void ResetComplete() = 0; + virtual void Error(GMPErr aError) = 0; +}; + +class GMPAudioDecoderProxy { +public: + virtual ~GMPAudioDecoderProxy() {} + + virtual nsresult InitDecode(GMPAudioCodecType aCodecType, + uint32_t aChannelCount, + uint32_t aBitsPerChannel, + uint32_t aSamplesPerSecond, + nsTArray<uint8_t>& aExtraData, + GMPAudioDecoderCallbackProxy* aCallback) = 0; + virtual nsresult Decode(mozilla::gmp::GMPAudioSamplesImpl& aSamples) = 0; + virtual nsresult Reset() = 0; + virtual nsresult Drain() = 0; + // Call to tell GMP/plugin the consumer will no longer use this + // interface/codec. + virtual nsresult Close() = 0; +}; + +#endif // GMPAudioDecoderProxy_h_ diff --git a/dom/media/gmp/GMPAudioHost.cpp b/dom/media/gmp/GMPAudioHost.cpp new file mode 100644 index 000000000..4e14fed0b --- /dev/null +++ b/dom/media/gmp/GMPAudioHost.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPAudioHost.h" +#include "gmp-audio-samples.h" +#include "gmp-errors.h" +#include "GMPEncryptedBufferDataImpl.h" +#include "MediaData.h" + +namespace mozilla { +namespace gmp { + +GMPAudioSamplesImpl::GMPAudioSamplesImpl(GMPAudioFormat aFormat) + : mFormat(aFormat) + , mTimeStamp(0) + , mChannels(0) + , mRate(0) +{ +} + +GMPAudioSamplesImpl::GMPAudioSamplesImpl(const GMPAudioEncodedSampleData& aData) + : mFormat(kGMPAudioEncodedSamples) + , mBuffer(aData.mData()) + , mTimeStamp(aData.mTimeStamp()) + , mChannels(aData.mChannelCount()) + , mRate(aData.mSamplesPerSecond()) +{ + if (aData.mDecryptionData().mKeyId().Length() > 0) { + mCrypto = new GMPEncryptedBufferDataImpl(aData.mDecryptionData()); + } +} + +GMPAudioSamplesImpl::GMPAudioSamplesImpl(MediaRawData* aSample, + uint32_t aChannels, + uint32_t aRate) + : mFormat(kGMPAudioEncodedSamples) + , mTimeStamp(aSample->mTime) + , mChannels(aChannels) + , mRate(aRate) +{ + mBuffer.AppendElements(aSample->Data(), aSample->Size()); + if (aSample->mCrypto.mValid) { + mCrypto = new GMPEncryptedBufferDataImpl(aSample->mCrypto); + } +} + +GMPAudioSamplesImpl::~GMPAudioSamplesImpl() +{ +} + +GMPAudioFormat +GMPAudioSamplesImpl::GetFormat() +{ + return mFormat; +} + +void +GMPAudioSamplesImpl::Destroy() +{ + delete this; +} + +GMPErr +GMPAudioSamplesImpl::SetBufferSize(uint32_t aSize) +{ + mBuffer.SetLength(aSize); + return GMPNoErr; +} + +uint32_t +GMPAudioSamplesImpl::Size() +{ + return mBuffer.Length(); +} + +void +GMPAudioSamplesImpl::SetTimeStamp(uint64_t aTimeStamp) +{ + mTimeStamp = aTimeStamp; +} + +uint64_t +GMPAudioSamplesImpl::TimeStamp() +{ + return mTimeStamp; +} + +const uint8_t* +GMPAudioSamplesImpl::Buffer() const +{ + return mBuffer.Elements(); +} + +uint8_t* +GMPAudioSamplesImpl::Buffer() +{ + return mBuffer.Elements(); +} + +const GMPEncryptedBufferMetadata* +GMPAudioSamplesImpl::GetDecryptionData() const +{ + return mCrypto; +} + +void +GMPAudioSamplesImpl::InitCrypto(const CryptoSample& aCrypto) +{ + if (!aCrypto.mValid) { + return; + } + mCrypto = new GMPEncryptedBufferDataImpl(aCrypto); +} + +void +GMPAudioSamplesImpl::RelinquishData(GMPAudioEncodedSampleData& aData) +{ + aData.mData() = Move(mBuffer); + aData.mTimeStamp() = TimeStamp(); + if (mCrypto) { + mCrypto->RelinquishData(aData.mDecryptionData()); + } +} + +uint32_t +GMPAudioSamplesImpl::Channels() const +{ + return mChannels; +} + +void +GMPAudioSamplesImpl::SetChannels(uint32_t aChannels) +{ + mChannels = aChannels; +} + +uint32_t +GMPAudioSamplesImpl::Rate() const +{ + return mRate; +} + +void +GMPAudioSamplesImpl::SetRate(uint32_t aRate) +{ + mRate = aRate; +} + + +GMPErr +GMPAudioHostImpl::CreateSamples(GMPAudioFormat aFormat, + GMPAudioSamples** aSamples) +{ + + *aSamples = new GMPAudioSamplesImpl(aFormat); + return GMPNoErr; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPAudioHost.h b/dom/media/gmp/GMPAudioHost.h new file mode 100644 index 000000000..ed829b9c5 --- /dev/null +++ b/dom/media/gmp/GMPAudioHost.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPAudioHost_h_ +#define GMPAudioHost_h_ + +#include "gmp-audio-host.h" +#include "gmp-audio-samples.h" +#include "nsTArray.h" +#include "gmp-decryption.h" +#include "nsAutoPtr.h" +#include "GMPEncryptedBufferDataImpl.h" +#include "mozilla/gmp/GMPTypes.h" + +namespace mozilla { +class CryptoSample; +class MediaRawData; + +namespace gmp { + +class GMPAudioSamplesImpl : public GMPAudioSamples { +public: + explicit GMPAudioSamplesImpl(GMPAudioFormat aFormat); + explicit GMPAudioSamplesImpl(const GMPAudioEncodedSampleData& aData); + GMPAudioSamplesImpl(MediaRawData* aSample, + uint32_t aChannels, + uint32_t aRate); + virtual ~GMPAudioSamplesImpl(); + + GMPAudioFormat GetFormat() override; + void Destroy() override; + GMPErr SetBufferSize(uint32_t aSize) override; + uint32_t Size() override; + void SetTimeStamp(uint64_t aTimeStamp) override; + uint64_t TimeStamp() override; + const uint8_t* Buffer() const override; + uint8_t* Buffer() override; + const GMPEncryptedBufferMetadata* GetDecryptionData() const override; + + void InitCrypto(const CryptoSample& aCrypto); + + void RelinquishData(GMPAudioEncodedSampleData& aData); + + uint32_t Channels() const override; + void SetChannels(uint32_t aChannels) override; + uint32_t Rate() const override; + void SetRate(uint32_t aRate) override; + +private: + GMPAudioFormat mFormat; + nsTArray<uint8_t> mBuffer; + int64_t mTimeStamp; + nsAutoPtr<GMPEncryptedBufferDataImpl> mCrypto; + uint32_t mChannels; + uint32_t mRate; +}; + +class GMPAudioHostImpl : public GMPAudioHost +{ +public: + GMPErr CreateSamples(GMPAudioFormat aFormat, + GMPAudioSamples** aSamples) override; +private: +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPAudioHost_h_ diff --git a/dom/media/gmp/GMPCallbackBase.h b/dom/media/gmp/GMPCallbackBase.h new file mode 100644 index 000000000..3d96629ef --- /dev/null +++ b/dom/media/gmp/GMPCallbackBase.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPCallbackBase_h_ +#define GMPCallbackBase_h_ + +class GMPCallbackBase +{ +public: + virtual ~GMPCallbackBase() {} + + // The GMP code will call this if the codec crashes or shuts down. It's + // expected that the consumer (destination of this callback) will respond + // by dropping their reference to the proxy, allowing the proxy/parent to + // be destroyed. + virtual void Terminated() = 0; +}; + +#endif diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp new file mode 100644 index 000000000..7941183b5 --- /dev/null +++ b/dom/media/gmp/GMPChild.cpp @@ -0,0 +1,491 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPChild.h" +#include "GMPContentChild.h" +#include "GMPProcessChild.h" +#include "GMPLoader.h" +#include "GMPVideoDecoderChild.h" +#include "GMPVideoEncoderChild.h" +#include "GMPAudioDecoderChild.h" +#include "GMPDecryptorChild.h" +#include "GMPVideoHost.h" +#include "nsDebugImpl.h" +#include "nsIFile.h" +#include "nsXULAppAPI.h" +#include "gmp-video-decode.h" +#include "gmp-video-encode.h" +#include "GMPPlatform.h" +#include "mozilla/ipc/ProcessChild.h" +#include "GMPUtils.h" +#include "prio.h" +#include "base/task.h" + +using namespace mozilla::ipc; + +static const int MAX_VOUCHER_LENGTH = 500000; + +#ifdef XP_WIN +#include <stdlib.h> // for _exit() +#else +#include <unistd.h> // for _exit() +#endif + +namespace mozilla { + +#undef LOG +#undef LOGD + +extern LogModule* GetGMPLog(); +#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__)) +#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPChild[pid=%d] " x, (int)base::GetCurrentProcId(), ##__VA_ARGS__) + +namespace gmp { + +GMPChild::GMPChild() + : mAsyncShutdown(nullptr) + , mGMPMessageLoop(MessageLoop::current()) + , mGMPLoader(nullptr) +{ + LOGD("GMPChild ctor"); + nsDebugImpl::SetMultiprocessMode("GMP"); +} + +GMPChild::~GMPChild() +{ + LOGD("GMPChild dtor"); +} + +static bool +GetFileBase(const nsAString& aPluginPath, + nsCOMPtr<nsIFile>& aLibDirectory, + nsCOMPtr<nsIFile>& aFileBase, + nsAutoString& aBaseName) +{ + nsresult rv = NS_NewLocalFile(aPluginPath, + true, getter_AddRefs(aFileBase)); + if (NS_FAILED(rv)) { + return false; + } + + if (NS_FAILED(aFileBase->Clone(getter_AddRefs(aLibDirectory)))) { + return false; + } + + nsCOMPtr<nsIFile> parent; + rv = aFileBase->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return false; + } + + nsAutoString parentLeafName; + rv = parent->GetLeafName(parentLeafName); + if (NS_FAILED(rv)) { + return false; + } + + aBaseName = Substring(parentLeafName, + 4, + parentLeafName.Length() - 1); + return true; +} + +static bool +GetFileBase(const nsAString& aPluginPath, + nsCOMPtr<nsIFile>& aFileBase, + nsAutoString& aBaseName) +{ + nsCOMPtr<nsIFile> unusedLibDir; + return GetFileBase(aPluginPath, unusedLibDir, aFileBase, aBaseName); +} + +static bool +GetPluginFile(const nsAString& aPluginPath, + nsCOMPtr<nsIFile>& aLibDirectory, + nsCOMPtr<nsIFile>& aLibFile) +{ + nsAutoString baseName; + GetFileBase(aPluginPath, aLibDirectory, aLibFile, baseName); + +#if defined(OS_POSIX) + nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so"); +#elif defined(XP_WIN) + nsAutoString binaryName = baseName + NS_LITERAL_STRING(".dll"); +#else +#error Unsupported O.S. +#endif + aLibFile->AppendRelativePath(binaryName); + return true; +} + +static bool +GetPluginFile(const nsAString& aPluginPath, + nsCOMPtr<nsIFile>& aLibFile) +{ + nsCOMPtr<nsIFile> unusedlibDir; + return GetPluginFile(aPluginPath, unusedlibDir, aLibFile); +} + +bool +GMPChild::Init(const nsAString& aPluginPath, + const nsAString& aVoucherPath, + base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel) +{ + LOGD("%s pluginPath=%s", __FUNCTION__, NS_ConvertUTF16toUTF8(aPluginPath).get()); + + if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) { + return false; + } + + mPluginPath = aPluginPath; + mSandboxVoucherPath = aVoucherPath; + + return true; +} + +bool +GMPChild::RecvSetNodeId(const nsCString& aNodeId) +{ + LOGD("%s nodeId=%s", __FUNCTION__, aNodeId.Data()); + + // Store the per origin salt for the node id. Note: we do this in a + // separate message than RecvStartPlugin() so that the string is not + // sitting in a string on the IPC code's call stack. + mNodeId = aNodeId; + return true; +} + +GMPErr +GMPChild::GetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) +{ + if (!mGMPLoader) { + return GMPGenericErr; + } + return mGMPLoader->GetAPI(aAPIName, aHostAPI, aPluginAPI, aDecryptorId); +} + +bool +GMPChild::RecvPreloadLibs(const nsCString& aLibs) +{ +#ifdef XP_WIN + // Pre-load DLLs that need to be used by the EME plugin but that can't be + // loaded after the sandbox has started + // Items in this must be lowercase! + static const char* whitelist[] = { + "d3d9.dll", // Create an `IDirect3D9` to get adapter information + "dxva2.dll", // Get monitor information + "evr.dll", // MFGetStrideForBitmapInfoHeader + "mfh264dec.dll", // H.264 decoder (on Windows Vista) + "mfheaacdec.dll", // AAC decoder (on Windows Vista) + "mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer, MFCreateMediaType + "msauddecmft.dll", // AAC decoder (on Windows 8) + "msmpeg2adec.dll", // AAC decoder (on Windows 7) + "msmpeg2vdec.dll", // H.264 decoder + }; + + nsTArray<nsCString> libs; + SplitAt(", ", aLibs, libs); + for (nsCString lib : libs) { + ToLowerCase(lib); + for (const char* whiteListedLib : whitelist) { + if (lib.EqualsASCII(whiteListedLib)) { + LoadLibraryA(lib.get()); + break; + } + } + } +#endif + return true; +} + +bool +GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) +{ + nsCOMPtr<nsIFile> libFile; + if (!GetPluginFile(mPluginPath, libFile)) { + return false; + } + + if (!FileExists(libFile)) { + NS_WARNING("Can't find GMP library file!"); + return false; + } + + nsAutoString path; + libFile->GetPath(path); + aOutLibPath = NS_ConvertUTF16toUTF8(path); + + return true; +} + +bool +GMPChild::AnswerStartPlugin(const nsString& aAdapter) +{ + LOGD("%s", __FUNCTION__); + + if (!PreLoadPluginVoucher()) { + NS_WARNING("Plugin voucher failed to load!"); + return false; + } + PreLoadSandboxVoucher(); + + nsCString libPath; + if (!GetUTF8LibPath(libPath)) { + return false; + } + + auto platformAPI = new GMPPlatformAPI(); + InitPlatformAPI(*platformAPI, this); + + mGMPLoader = GMPProcessChild::GetGMPLoader(); + if (!mGMPLoader) { + NS_WARNING("Failed to get GMPLoader"); + delete platformAPI; + return false; + } + + GMPAdapter* adapter = nullptr; + if (!mGMPLoader->Load(libPath.get(), + libPath.Length(), + mNodeId.BeginWriting(), + mNodeId.Length(), + platformAPI, + adapter)) { + NS_WARNING("Failed to load GMP"); + delete platformAPI; + return false; + } + + void* sh = nullptr; + GMPAsyncShutdownHost* host = static_cast<GMPAsyncShutdownHost*>(this); + GMPErr err = GetAPI(GMP_API_ASYNC_SHUTDOWN, host, &sh); + if (err == GMPNoErr && sh) { + mAsyncShutdown = reinterpret_cast<GMPAsyncShutdown*>(sh); + SendAsyncShutdownRequired(); + } + + return true; +} + +MessageLoop* +GMPChild::GMPMessageLoop() +{ + return mGMPMessageLoop; +} + +void +GMPChild::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD("%s reason=%d", __FUNCTION__, aWhy); + + for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) { + MOZ_ASSERT_IF(aWhy == NormalShutdown, !mGMPContentChildren[i - 1]->IsUsed()); + mGMPContentChildren[i - 1]->Close(); + } + + if (mGMPLoader) { + mGMPLoader->Shutdown(); + } + if (AbnormalShutdown == aWhy) { + NS_WARNING("Abnormal shutdown of GMP process!"); + ProcessChild::QuickExit(); + } + + XRE_ShutdownChildProcess(); +} + +void +GMPChild::ProcessingError(Result aCode, const char* aReason) +{ + switch (aCode) { + case MsgDropped: + _exit(0); // Don't trigger a crash report. + case MsgNotKnown: + MOZ_CRASH("aborting because of MsgNotKnown"); + case MsgNotAllowed: + MOZ_CRASH("aborting because of MsgNotAllowed"); + case MsgPayloadError: + MOZ_CRASH("aborting because of MsgPayloadError"); + case MsgProcessingError: + MOZ_CRASH("aborting because of MsgProcessingError"); + case MsgRouteError: + MOZ_CRASH("aborting because of MsgRouteError"); + case MsgValueError: + MOZ_CRASH("aborting because of MsgValueError"); + default: + MOZ_CRASH("not reached"); + } +} + +PGMPTimerChild* +GMPChild::AllocPGMPTimerChild() +{ + return new GMPTimerChild(this); +} + +bool +GMPChild::DeallocPGMPTimerChild(PGMPTimerChild* aActor) +{ + MOZ_ASSERT(mTimerChild == static_cast<GMPTimerChild*>(aActor)); + mTimerChild = nullptr; + return true; +} + +GMPTimerChild* +GMPChild::GetGMPTimers() +{ + if (!mTimerChild) { + PGMPTimerChild* sc = SendPGMPTimerConstructor(); + if (!sc) { + return nullptr; + } + mTimerChild = static_cast<GMPTimerChild*>(sc); + } + return mTimerChild; +} + +PGMPStorageChild* +GMPChild::AllocPGMPStorageChild() +{ + return new GMPStorageChild(this); +} + +bool +GMPChild::DeallocPGMPStorageChild(PGMPStorageChild* aActor) +{ + mStorage = nullptr; + return true; +} + +GMPStorageChild* +GMPChild::GetGMPStorage() +{ + if (!mStorage) { + PGMPStorageChild* sc = SendPGMPStorageConstructor(); + if (!sc) { + return nullptr; + } + mStorage = static_cast<GMPStorageChild*>(sc); + } + return mStorage; +} + +bool +GMPChild::RecvCrashPluginNow() +{ + MOZ_CRASH(); + return true; +} + +bool +GMPChild::RecvBeginAsyncShutdown() +{ + LOGD("%s AsyncShutdown=%d", __FUNCTION__, mAsyncShutdown!=nullptr); + + MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current()); + if (mAsyncShutdown) { + mAsyncShutdown->BeginShutdown(); + } else { + ShutdownComplete(); + } + return true; +} + +bool +GMPChild::RecvCloseActive() +{ + for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) { + mGMPContentChildren[i - 1]->CloseActive(); + } + return true; +} + +void +GMPChild::ShutdownComplete() +{ + LOGD("%s", __FUNCTION__); + MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current()); + mAsyncShutdown = nullptr; + SendAsyncShutdownComplete(); +} + +static void +GetPluginVoucherFile(const nsAString& aPluginPath, + nsCOMPtr<nsIFile>& aOutVoucherFile) +{ + nsAutoString baseName; + GetFileBase(aPluginPath, aOutVoucherFile, baseName); + nsAutoString infoFileName = baseName + NS_LITERAL_STRING(".voucher"); + aOutVoucherFile->AppendRelativePath(infoFileName); +} + +bool +GMPChild::PreLoadPluginVoucher() +{ + nsCOMPtr<nsIFile> voucherFile; + GetPluginVoucherFile(mPluginPath, voucherFile); + if (!FileExists(voucherFile)) { + // Assume missing file is not fatal; that would break OpenH264. + return true; + } + return ReadIntoArray(voucherFile, mPluginVoucher, MAX_VOUCHER_LENGTH); +} + +void +GMPChild::PreLoadSandboxVoucher() +{ + nsCOMPtr<nsIFile> f; + nsresult rv = NS_NewLocalFile(mSandboxVoucherPath, true, getter_AddRefs(f)); + if (NS_FAILED(rv)) { + NS_WARNING("Can't create nsIFile for sandbox voucher"); + return; + } + if (!FileExists(f)) { + // Assume missing file is not fatal; that would break OpenH264. + return; + } + + if (!ReadIntoArray(f, mSandboxVoucher, MAX_VOUCHER_LENGTH)) { + NS_WARNING("Failed to read sandbox voucher"); + } +} + +PGMPContentChild* +GMPChild::AllocPGMPContentChild(Transport* aTransport, + ProcessId aOtherPid) +{ + GMPContentChild* child = + mGMPContentChildren.AppendElement(new GMPContentChild(this))->get(); + child->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), ipc::ChildSide); + + return child; +} + +void +GMPChild::GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild) +{ + for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) { + UniquePtr<GMPContentChild>& toDestroy = mGMPContentChildren[i - 1]; + if (toDestroy.get() == aGMPContentChild) { + SendPGMPContentChildDestroyed(); + RefPtr<DeleteTask<GMPContentChild>> task = + new DeleteTask<GMPContentChild>(toDestroy.release()); + MessageLoop::current()->PostTask(task.forget()); + mGMPContentChildren.RemoveElementAt(i - 1); + break; + } + } +} + +} // namespace gmp +} // namespace mozilla + +#undef LOG +#undef LOGD diff --git a/dom/media/gmp/GMPChild.h b/dom/media/gmp/GMPChild.h new file mode 100644 index 000000000..722e4c7a9 --- /dev/null +++ b/dom/media/gmp/GMPChild.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPChild_h_ +#define GMPChild_h_ + +#include "mozilla/gmp/PGMPChild.h" +#include "GMPTimerChild.h" +#include "GMPStorageChild.h" +#include "GMPLoader.h" +#include "gmp-async-shutdown.h" +#include "gmp-entrypoints.h" +#include "prlink.h" + +namespace mozilla { +namespace gmp { + +class GMPContentChild; + +class GMPChild : public PGMPChild + , public GMPAsyncShutdownHost +{ +public: + GMPChild(); + virtual ~GMPChild(); + + bool Init(const nsAString& aPluginPath, + const nsAString& aVoucherPath, + base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel); + MessageLoop* GMPMessageLoop(); + + // Main thread only. + GMPTimerChild* GetGMPTimers(); + GMPStorageChild* GetGMPStorage(); + + // GMPAsyncShutdownHost + void ShutdownComplete() override; + +private: + friend class GMPContentChild; + + bool PreLoadPluginVoucher(); + void PreLoadSandboxVoucher(); + + bool GetUTF8LibPath(nsACString& aOutLibPath); + + bool RecvSetNodeId(const nsCString& aNodeId) override; + bool AnswerStartPlugin(const nsString& aAdapter) override; + bool RecvPreloadLibs(const nsCString& aLibs) override; + + PGMPTimerChild* AllocPGMPTimerChild() override; + bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) override; + + PGMPStorageChild* AllocPGMPStorageChild() override; + bool DeallocPGMPStorageChild(PGMPStorageChild* aActor) override; + + PGMPContentChild* AllocPGMPContentChild(Transport* aTransport, + ProcessId aOtherPid) override; + void GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild); + + bool RecvCrashPluginNow() override; + bool RecvBeginAsyncShutdown() override; + bool RecvCloseActive() override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + void ProcessingError(Result aCode, const char* aReason) override; + + GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI, uint32_t aDecryptorId = 0); + + nsTArray<UniquePtr<GMPContentChild>> mGMPContentChildren; + + GMPAsyncShutdown* mAsyncShutdown; + RefPtr<GMPTimerChild> mTimerChild; + RefPtr<GMPStorageChild> mStorage; + + MessageLoop* mGMPMessageLoop; + nsString mPluginPath; + nsString mSandboxVoucherPath; + nsCString mNodeId; + GMPLoader* mGMPLoader; + nsTArray<uint8_t> mPluginVoucher; + nsTArray<uint8_t> mSandboxVoucher; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPChild_h_ diff --git a/dom/media/gmp/GMPContentChild.cpp b/dom/media/gmp/GMPContentChild.cpp new file mode 100644 index 000000000..415736e11 --- /dev/null +++ b/dom/media/gmp/GMPContentChild.cpp @@ -0,0 +1,320 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPContentChild.h" +#include "GMPChild.h" +#include "GMPAudioDecoderChild.h" +#include "GMPDecryptorChild.h" +#include "GMPVideoDecoderChild.h" +#include "GMPVideoEncoderChild.h" +#include "base/task.h" + +namespace mozilla { +namespace gmp { + +GMPContentChild::GMPContentChild(GMPChild* aChild) + : mGMPChild(aChild) +{ + MOZ_COUNT_CTOR(GMPContentChild); +} + +GMPContentChild::~GMPContentChild() +{ + MOZ_COUNT_DTOR(GMPContentChild); +} + +MessageLoop* +GMPContentChild::GMPMessageLoop() +{ + return mGMPChild->GMPMessageLoop(); +} + +void +GMPContentChild::CheckThread() +{ + MOZ_ASSERT(mGMPChild->mGMPMessageLoop == MessageLoop::current()); +} + +void +GMPContentChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mGMPChild->GMPContentChildActorDestroy(this); +} + +void +GMPContentChild::ProcessingError(Result aCode, const char* aReason) +{ + mGMPChild->ProcessingError(aCode, aReason); +} + +PGMPAudioDecoderChild* +GMPContentChild::AllocPGMPAudioDecoderChild() +{ + return new GMPAudioDecoderChild(this); +} + +bool +GMPContentChild::DeallocPGMPAudioDecoderChild(PGMPAudioDecoderChild* aActor) +{ + delete aActor; + return true; +} + +PGMPDecryptorChild* +GMPContentChild::AllocPGMPDecryptorChild() +{ + GMPDecryptorChild* actor = new GMPDecryptorChild(this, + mGMPChild->mPluginVoucher, + mGMPChild->mSandboxVoucher); + actor->AddRef(); + return actor; +} + +bool +GMPContentChild::DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor) +{ + static_cast<GMPDecryptorChild*>(aActor)->Release(); + return true; +} + +PGMPVideoDecoderChild* +GMPContentChild::AllocPGMPVideoDecoderChild(const uint32_t& aDecryptorId) +{ + GMPVideoDecoderChild* actor = new GMPVideoDecoderChild(this); + actor->AddRef(); + return actor; +} + +bool +GMPContentChild::DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor) +{ + static_cast<GMPVideoDecoderChild*>(aActor)->Release(); + return true; +} + +PGMPVideoEncoderChild* +GMPContentChild::AllocPGMPVideoEncoderChild() +{ + GMPVideoEncoderChild* actor = new GMPVideoEncoderChild(this); + actor->AddRef(); + return actor; +} + +bool +GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor) +{ + static_cast<GMPVideoEncoderChild*>(aActor)->Release(); + return true; +} + +// Adapts GMPDecryptor7 to the current GMPDecryptor version. +class GMPDecryptor7BackwardsCompat : public GMPDecryptor { +public: + explicit GMPDecryptor7BackwardsCompat(GMPDecryptor7* aDecryptorV7) + : mDecryptorV7(aDecryptorV7) + { + } + + void Init(GMPDecryptorCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) override + { + // Distinctive identifier and persistent state arguments not present + // in v7 interface. + mDecryptorV7->Init(aCallback); + } + + void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) override + { + mDecryptorV7->CreateSession(aCreateSessionToken, + aPromiseId, + aInitDataType, + aInitDataTypeSize, + aInitData, + aInitDataSize, + aSessionType); + } + + void LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + mDecryptorV7->LoadSession(aPromiseId, aSessionId, aSessionIdLength); + } + + void UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) override + { + mDecryptorV7->UpdateSession(aPromiseId, + aSessionId, + aSessionIdLength, + aResponse, + aResponseSize); + } + + void CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + mDecryptorV7->CloseSession(aPromiseId, aSessionId, aSessionIdLength); + } + + void RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + mDecryptorV7->RemoveSession(aPromiseId, aSessionId, aSessionIdLength); + } + + void SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) override + { + mDecryptorV7->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize); + } + + void Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) override + { + mDecryptorV7->Decrypt(aBuffer, aMetadata); + } + + void DecryptingComplete() override + { + mDecryptorV7->DecryptingComplete(); + delete this; + } +private: + GMPDecryptor7* mDecryptorV7; +}; + +bool +GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor) +{ + GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor); + GMPDecryptorHost* host = static_cast<GMPDecryptorHost*>(child); + + void* ptr = nullptr; + GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &ptr, aActor->Id()); + GMPDecryptor* decryptor = nullptr; + if (GMP_SUCCEEDED(err) && ptr) { + decryptor = static_cast<GMPDecryptor*>(ptr); + } else if (err != GMPNoErr) { + // We Adapt the previous GMPDecryptor version to the current, so that + // Gecko thinks it's only talking to the current version. v7 differs + // from v9 in its Init() function arguments, and v9 has extra enumeration + // members at the end of the key status enumerations. + err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_BACKWARDS_COMPAT, host, &ptr); + if (err != GMPNoErr || !ptr) { + return false; + } + decryptor = new GMPDecryptor7BackwardsCompat(static_cast<GMPDecryptor7*>(ptr)); + } + + child->Init(decryptor); + + return true; +} + +bool +GMPContentChild::RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor) +{ + auto vdc = static_cast<GMPAudioDecoderChild*>(aActor); + + void* vd = nullptr; + GMPErr err = mGMPChild->GetAPI(GMP_API_AUDIO_DECODER, &vdc->Host(), &vd); + if (err != GMPNoErr || !vd) { + return false; + } + + vdc->Init(static_cast<GMPAudioDecoder*>(vd)); + + return true; +} + +bool +GMPContentChild::RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor, + const uint32_t& aDecryptorId) +{ + auto vdc = static_cast<GMPVideoDecoderChild*>(aActor); + + void* vd = nullptr; + GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_DECODER, &vdc->Host(), &vd, aDecryptorId); + if (err != GMPNoErr || !vd) { + NS_WARNING("GMPGetAPI call failed trying to construct decoder."); + return false; + } + + vdc->Init(static_cast<GMPVideoDecoder*>(vd)); + + return true; +} + +bool +GMPContentChild::RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) +{ + auto vec = static_cast<GMPVideoEncoderChild*>(aActor); + + void* ve = nullptr; + GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_ENCODER, &vec->Host(), &ve); + if (err != GMPNoErr || !ve) { + NS_WARNING("GMPGetAPI call failed trying to construct encoder."); + return false; + } + + vec->Init(static_cast<GMPVideoEncoder*>(ve)); + + return true; +} + +void +GMPContentChild::CloseActive() +{ + // Invalidate and remove any remaining API objects. + const ManagedContainer<PGMPAudioDecoderChild>& audioDecoders = + ManagedPGMPAudioDecoderChild(); + for (auto iter = audioDecoders.ConstIter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->SendShutdown(); + } + + const ManagedContainer<PGMPDecryptorChild>& decryptors = + ManagedPGMPDecryptorChild(); + for (auto iter = decryptors.ConstIter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->SendShutdown(); + } + + const ManagedContainer<PGMPVideoDecoderChild>& videoDecoders = + ManagedPGMPVideoDecoderChild(); + for (auto iter = videoDecoders.ConstIter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->SendShutdown(); + } + + const ManagedContainer<PGMPVideoEncoderChild>& videoEncoders = + ManagedPGMPVideoEncoderChild(); + for (auto iter = videoEncoders.ConstIter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->SendShutdown(); + } +} + +bool +GMPContentChild::IsUsed() +{ + return !ManagedPGMPAudioDecoderChild().IsEmpty() || + !ManagedPGMPDecryptorChild().IsEmpty() || + !ManagedPGMPVideoDecoderChild().IsEmpty() || + !ManagedPGMPVideoEncoderChild().IsEmpty(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPContentChild.h b/dom/media/gmp/GMPContentChild.h new file mode 100644 index 000000000..714207608 --- /dev/null +++ b/dom/media/gmp/GMPContentChild.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPContentChild_h_ +#define GMPContentChild_h_ + +#include "mozilla/gmp/PGMPContentChild.h" +#include "GMPSharedMemManager.h" + +namespace mozilla { +namespace gmp { + +class GMPChild; + +class GMPContentChild : public PGMPContentChild + , public GMPSharedMem +{ +public: + explicit GMPContentChild(GMPChild* aChild); + virtual ~GMPContentChild(); + + MessageLoop* GMPMessageLoop(); + + bool RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor) override; + bool RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor) override; + bool RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor, const uint32_t& aDecryptorId) override; + bool RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) override; + + PGMPAudioDecoderChild* AllocPGMPAudioDecoderChild() override; + bool DeallocPGMPAudioDecoderChild(PGMPAudioDecoderChild* aActor) override; + + PGMPDecryptorChild* AllocPGMPDecryptorChild() override; + bool DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor) override; + + PGMPVideoDecoderChild* AllocPGMPVideoDecoderChild(const uint32_t& aDecryptorId) override; + bool DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor) override; + + PGMPVideoEncoderChild* AllocPGMPVideoEncoderChild() override; + bool DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + void ProcessingError(Result aCode, const char* aReason) override; + + // GMPSharedMem + void CheckThread() override; + + void CloseActive(); + bool IsUsed(); + + GMPChild* mGMPChild; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPContentChild_h_ diff --git a/dom/media/gmp/GMPContentParent.cpp b/dom/media/gmp/GMPContentParent.cpp new file mode 100644 index 000000000..12f6f4c48 --- /dev/null +++ b/dom/media/gmp/GMPContentParent.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPContentParent.h" +#include "GMPAudioDecoderParent.h" +#include "GMPDecryptorParent.h" +#include "GMPParent.h" +#include "GMPServiceChild.h" +#include "GMPVideoDecoderParent.h" +#include "GMPVideoEncoderParent.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/Logging.h" +#include "mozilla/Unused.h" +#include "base/task.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPContentParent" + +namespace gmp { + +GMPContentParent::GMPContentParent(GMPParent* aParent) + : mParent(aParent) +{ + if (mParent) { + SetDisplayName(mParent->GetDisplayName()); + SetPluginId(mParent->GetPluginId()); + } +} + +GMPContentParent::~GMPContentParent() +{ +} + +class ReleaseGMPContentParent : public Runnable +{ +public: + explicit ReleaseGMPContentParent(GMPContentParent* aToRelease) + : mToRelease(aToRelease) + { + } + + NS_IMETHOD Run() override + { + return NS_OK; + } + +private: + RefPtr<GMPContentParent> mToRelease; +}; + +void +GMPContentParent::ActorDestroy(ActorDestroyReason aWhy) +{ + MOZ_ASSERT(mAudioDecoders.IsEmpty() && + mDecryptors.IsEmpty() && + mVideoDecoders.IsEmpty() && + mVideoEncoders.IsEmpty()); + NS_DispatchToCurrentThread(new ReleaseGMPContentParent(this)); +} + +void +GMPContentParent::CheckThread() +{ + MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); +} + +void +GMPContentParent::AudioDecoderDestroyed(GMPAudioDecoderParent* aDecoder) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + MOZ_ALWAYS_TRUE(mAudioDecoders.RemoveElement(aDecoder)); + CloseIfUnused(); +} + +void +GMPContentParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + // If the constructor fails, we'll get called before it's added + Unused << NS_WARN_IF(!mVideoDecoders.RemoveElement(aDecoder)); + CloseIfUnused(); +} + +void +GMPContentParent::VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + // If the constructor fails, we'll get called before it's added + Unused << NS_WARN_IF(!mVideoEncoders.RemoveElement(aEncoder)); + CloseIfUnused(); +} + +void +GMPContentParent::DecryptorDestroyed(GMPDecryptorParent* aSession) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + MOZ_ALWAYS_TRUE(mDecryptors.RemoveElement(aSession)); + CloseIfUnused(); +} + +void +GMPContentParent::CloseIfUnused() +{ + if (mAudioDecoders.IsEmpty() && + mDecryptors.IsEmpty() && + mVideoDecoders.IsEmpty() && + mVideoEncoders.IsEmpty()) { + RefPtr<GMPContentParent> toClose; + if (mParent) { + toClose = mParent->ForgetGMPContentParent(); + } else { + toClose = this; + RefPtr<GeckoMediaPluginServiceChild> gmp( + GeckoMediaPluginServiceChild::GetSingleton()); + gmp->RemoveGMPContentParent(toClose); + } + NS_DispatchToCurrentThread(NewRunnableMethod(toClose, + &GMPContentParent::Close)); + } +} + +nsresult +GMPContentParent::GetGMPDecryptor(GMPDecryptorParent** aGMPDP) +{ + PGMPDecryptorParent* pdp = SendPGMPDecryptorConstructor(); + if (!pdp) { + return NS_ERROR_FAILURE; + } + GMPDecryptorParent* dp = static_cast<GMPDecryptorParent*>(pdp); + // This addref corresponds to the Proxy pointer the consumer is returned. + // It's dropped by calling Close() on the interface. + NS_ADDREF(dp); + mDecryptors.AppendElement(dp); + *aGMPDP = dp; + + return NS_OK; +} + +nsIThread* +GMPContentParent::GMPThread() +{ + if (!mGMPThread) { + nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + if (!mps) { + return nullptr; + } + // Not really safe if we just grab to the mGMPThread, as we don't know + // what thread we're running on and other threads may be trying to + // access this without locks! However, debug only, and primary failure + // mode outside of compiler-helped TSAN is a leak. But better would be + // to use swap() under a lock. + mps->GetThread(getter_AddRefs(mGMPThread)); + MOZ_ASSERT(mGMPThread); + } + + return mGMPThread; +} + +nsresult +GMPContentParent::GetGMPAudioDecoder(GMPAudioDecoderParent** aGMPAD) +{ + PGMPAudioDecoderParent* pvap = SendPGMPAudioDecoderConstructor(); + if (!pvap) { + return NS_ERROR_FAILURE; + } + GMPAudioDecoderParent* vap = static_cast<GMPAudioDecoderParent*>(pvap); + // This addref corresponds to the Proxy pointer the consumer is returned. + // It's dropped by calling Close() on the interface. + NS_ADDREF(vap); + *aGMPAD = vap; + mAudioDecoders.AppendElement(vap); + + return NS_OK; +} + +nsresult +GMPContentParent::GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD, + uint32_t aDecryptorId) +{ + // returned with one anonymous AddRef that locks it until Destroy + PGMPVideoDecoderParent* pvdp = SendPGMPVideoDecoderConstructor(aDecryptorId); + if (!pvdp) { + return NS_ERROR_FAILURE; + } + GMPVideoDecoderParent *vdp = static_cast<GMPVideoDecoderParent*>(pvdp); + // This addref corresponds to the Proxy pointer the consumer is returned. + // It's dropped by calling Close() on the interface. + NS_ADDREF(vdp); + *aGMPVD = vdp; + mVideoDecoders.AppendElement(vdp); + + return NS_OK; +} + +nsresult +GMPContentParent::GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE) +{ + // returned with one anonymous AddRef that locks it until Destroy + PGMPVideoEncoderParent* pvep = SendPGMPVideoEncoderConstructor(); + if (!pvep) { + return NS_ERROR_FAILURE; + } + GMPVideoEncoderParent *vep = static_cast<GMPVideoEncoderParent*>(pvep); + // This addref corresponds to the Proxy pointer the consumer is returned. + // It's dropped by calling Close() on the interface. + NS_ADDREF(vep); + *aGMPVE = vep; + mVideoEncoders.AppendElement(vep); + + return NS_OK; +} + +PGMPVideoDecoderParent* +GMPContentParent::AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId) +{ + GMPVideoDecoderParent* vdp = new GMPVideoDecoderParent(this); + NS_ADDREF(vdp); + return vdp; +} + +bool +GMPContentParent::DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) +{ + GMPVideoDecoderParent* vdp = static_cast<GMPVideoDecoderParent*>(aActor); + NS_RELEASE(vdp); + return true; +} + +PGMPVideoEncoderParent* +GMPContentParent::AllocPGMPVideoEncoderParent() +{ + GMPVideoEncoderParent* vep = new GMPVideoEncoderParent(this); + NS_ADDREF(vep); + return vep; +} + +bool +GMPContentParent::DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor) +{ + GMPVideoEncoderParent* vep = static_cast<GMPVideoEncoderParent*>(aActor); + NS_RELEASE(vep); + return true; +} + +PGMPDecryptorParent* +GMPContentParent::AllocPGMPDecryptorParent() +{ + GMPDecryptorParent* ksp = new GMPDecryptorParent(this); + NS_ADDREF(ksp); + return ksp; +} + +bool +GMPContentParent::DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor) +{ + GMPDecryptorParent* ksp = static_cast<GMPDecryptorParent*>(aActor); + NS_RELEASE(ksp); + return true; +} + +PGMPAudioDecoderParent* +GMPContentParent::AllocPGMPAudioDecoderParent() +{ + GMPAudioDecoderParent* vdp = new GMPAudioDecoderParent(this); + NS_ADDREF(vdp); + return vdp; +} + +bool +GMPContentParent::DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor) +{ + GMPAudioDecoderParent* vdp = static_cast<GMPAudioDecoderParent*>(aActor); + NS_RELEASE(vdp); + return true; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPContentParent.h b/dom/media/gmp/GMPContentParent.h new file mode 100644 index 000000000..81f79bc73 --- /dev/null +++ b/dom/media/gmp/GMPContentParent.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPContentParent_h_ +#define GMPContentParent_h_ + +#include "mozilla/gmp/PGMPContentParent.h" +#include "GMPSharedMemManager.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace gmp { + +class GMPAudioDecoderParent; +class GMPDecryptorParent; +class GMPParent; +class GMPVideoDecoderParent; +class GMPVideoEncoderParent; + +class GMPContentParent final : public PGMPContentParent, + public GMPSharedMem +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPContentParent) + + explicit GMPContentParent(GMPParent* aParent = nullptr); + + nsresult GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD, + uint32_t aDecryptorId); + void VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder); + + nsresult GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE); + void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder); + + nsresult GetGMPDecryptor(GMPDecryptorParent** aGMPKS); + void DecryptorDestroyed(GMPDecryptorParent* aSession); + + nsresult GetGMPAudioDecoder(GMPAudioDecoderParent** aGMPAD); + void AudioDecoderDestroyed(GMPAudioDecoderParent* aDecoder); + + nsIThread* GMPThread(); + + // GMPSharedMem + void CheckThread() override; + + void SetDisplayName(const nsCString& aDisplayName) + { + mDisplayName = aDisplayName; + } + const nsCString& GetDisplayName() + { + return mDisplayName; + } + void SetPluginId(const uint32_t aPluginId) + { + mPluginId = aPluginId; + } + uint32_t GetPluginId() const + { + return mPluginId; + } + +private: + ~GMPContentParent(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + PGMPVideoDecoderParent* AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId) override; + bool DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) override; + + PGMPVideoEncoderParent* AllocPGMPVideoEncoderParent() override; + bool DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor) override; + + PGMPDecryptorParent* AllocPGMPDecryptorParent() override; + bool DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor) override; + + PGMPAudioDecoderParent* AllocPGMPAudioDecoderParent() override; + bool DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor) override; + + void CloseIfUnused(); + // Needed because NewRunnableMethod tried to use the class that the method + // lives on to store the receiver, but PGMPContentParent isn't refcounted. + void Close() + { + PGMPContentParent::Close(); + } + + nsTArray<RefPtr<GMPVideoDecoderParent>> mVideoDecoders; + nsTArray<RefPtr<GMPVideoEncoderParent>> mVideoEncoders; + nsTArray<RefPtr<GMPDecryptorParent>> mDecryptors; + nsTArray<RefPtr<GMPAudioDecoderParent>> mAudioDecoders; + nsCOMPtr<nsIThread> mGMPThread; + RefPtr<GMPParent> mParent; + nsCString mDisplayName; + uint32_t mPluginId; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPParent_h_ diff --git a/dom/media/gmp/GMPCrashHelperHolder.h b/dom/media/gmp/GMPCrashHelperHolder.h new file mode 100644 index 000000000..ea6f36c83 --- /dev/null +++ b/dom/media/gmp/GMPCrashHelperHolder.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPCrashHelperHolder_h_ +#define GMPCrashHelperHolder_h_ + +#include "GMPService.h" +#include "mozilla/RefPtr.h" +#include "nsPIDOMWindow.h" +#include "mozilla/ipc/ProtocolUtils.h" + +class GMPCrashHelper; + +namespace mozilla { + +// Disconnecting the GMPCrashHelpers at the right time is hard. We need to +// ensure that in the crashing case that we stay connected until the +// "gmp-plugin-crashed" message is processed in the content process. +// +// We have two channels connecting to the GMP; PGMP which connects from +// chrome to GMP process, and PGMPContent, which bridges between the content +// and GMP process. If the GMP crashes both PGMP and PGMPContent receive +// ActorDestroy messages and begin to shutdown at the same time. +// +// However the crash report mini dump must be generated in the chrome +// process' ActorDestroy, before the "gmp-plugin-crashed" message can be sent +// to the content process. We fire the "PluginCrashed" event when we handle +// the "gmp-plugin-crashed" message in the content process, and we need the +// crash helpers to do that. +// +// The PGMPContent's managed actors' ActorDestroy messages are the only shutdown +// notification we get in the content process, but we can't disconnect the +// crash handlers there in the crashing case, as ActorDestroy happens before +// we've received the "gmp-plugin-crashed" message and had a chance for the +// crash helpers to generate the window to dispatch PluginCrashed to initiate +// the crash report submission notification box. +// +// So we need to ensure that in the content process, the GMPCrashHelpers stay +// connected to the GMPService until after ActorDestroy messages are received +// if there's an abnormal shutdown. In the case where the GMP doesn't crash, +// we do actually want to disconnect GMPCrashHandlers in ActorDestroy, since +// we don't have any other signal that a GMP actor is shutting down. If we don't +// disconnect the crash helper there in the normal shutdown case, the helper +// will stick around forever and leak. +// +// In the crashing case, the GMPCrashHelpers are deallocated when the crash +// report is processed in GeckoMediaPluginService::RunPluginCrashCallbacks(). +// +// It's a bit yuck that we have to have two paths for disconnecting the crash +// helpers, but there aren't really any better options. +class GMPCrashHelperHolder +{ +public: + + void SetCrashHelper(GMPCrashHelper* aHelper) + { + mCrashHelper = aHelper; + } + + GMPCrashHelper* GetCrashHelper() + { + return mCrashHelper; + } + + void MaybeDisconnect(bool aAbnormalShutdown) + { + if (!aAbnormalShutdown) { + RefPtr<gmp::GeckoMediaPluginService> service(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); + service->DisconnectCrashHelper(GetCrashHelper()); + } + } + +private: + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} + +#endif // GMPCrashHelperHolder_h_ diff --git a/dom/media/gmp/GMPDecryptorChild.cpp b/dom/media/gmp/GMPDecryptorChild.cpp new file mode 100644 index 000000000..6da3c6c43 --- /dev/null +++ b/dom/media/gmp/GMPDecryptorChild.cpp @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPDecryptorChild.h" +#include "GMPContentChild.h" +#include "GMPChild.h" +#include "base/task.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "runnable_utils.h" +#include <ctime> + +#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current()) + +#define CALL_ON_GMP_THREAD(_func, ...) \ + CallOnGMPThread(&GMPDecryptorChild::_func, __VA_ARGS__) + +namespace mozilla { +namespace gmp { + +GMPDecryptorChild::GMPDecryptorChild(GMPContentChild* aPlugin, + const nsTArray<uint8_t>& aPluginVoucher, + const nsTArray<uint8_t>& aSandboxVoucher) + : mSession(nullptr) + , mPlugin(aPlugin) + , mPluginVoucher(aPluginVoucher) + , mSandboxVoucher(aSandboxVoucher) +{ + MOZ_ASSERT(mPlugin); +} + +GMPDecryptorChild::~GMPDecryptorChild() +{ +} + +template <typename MethodType, typename... ParamType> +void +GMPDecryptorChild::CallMethod(MethodType aMethod, ParamType&&... aParams) +{ + MOZ_ASSERT(ON_GMP_THREAD()); + // Don't send IPC messages after tear-down. + if (mSession) { + (this->*aMethod)(Forward<ParamType>(aParams)...); + } +} + +template<typename T> +struct AddConstReference { + typedef const typename RemoveReference<T>::Type& Type; +}; + +template<typename MethodType, typename... ParamType> +void +GMPDecryptorChild::CallOnGMPThread(MethodType aMethod, ParamType&&... aParams) +{ + if (ON_GMP_THREAD()) { + // Use forwarding reference when we can. + CallMethod(aMethod, Forward<ParamType>(aParams)...); + } else { + // Use const reference when we have to. + auto m = &GMPDecryptorChild::CallMethod< + decltype(aMethod), typename AddConstReference<ParamType>::Type...>; + RefPtr<mozilla::Runnable> t = + dont_add_new_uses_of_this::NewRunnableMethod(this, m, aMethod, Forward<ParamType>(aParams)...); + mPlugin->GMPMessageLoop()->PostTask(t.forget()); + } +} + +void +GMPDecryptorChild::Init(GMPDecryptor* aSession) +{ + MOZ_ASSERT(aSession); + mSession = aSession; + // The ID of this decryptor is the IPDL actor ID. Note it's unique inside + // the child process, but not necessarily across all gecko processes. However, + // since GMPDecryptors are segregated by node ID/origin, we shouldn't end up + // with clashes in the content process. + SendSetDecryptorId(Id()); +} + +void +GMPDecryptorChild::SetSessionId(uint32_t aCreateSessionToken, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + CALL_ON_GMP_THREAD(SendSetSessionId, + aCreateSessionToken, nsCString(aSessionId, aSessionIdLength)); +} + +void +GMPDecryptorChild::ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) +{ + CALL_ON_GMP_THREAD(SendResolveLoadSessionPromise, aPromiseId, aSuccess); +} + +void +GMPDecryptorChild::ResolvePromise(uint32_t aPromiseId) +{ + CALL_ON_GMP_THREAD(SendResolvePromise, aPromiseId); +} + +void +GMPDecryptorChild::RejectPromise(uint32_t aPromiseId, + GMPDOMException aException, + const char* aMessage, + uint32_t aMessageLength) +{ + CALL_ON_GMP_THREAD(SendRejectPromise, + aPromiseId, aException, nsCString(aMessage, aMessageLength)); +} + +void +GMPDecryptorChild::SessionMessage(const char* aSessionId, + uint32_t aSessionIdLength, + GMPSessionMessageType aMessageType, + const uint8_t* aMessage, + uint32_t aMessageLength) +{ + nsTArray<uint8_t> msg; + msg.AppendElements(aMessage, aMessageLength); + CALL_ON_GMP_THREAD(SendSessionMessage, + nsCString(aSessionId, aSessionIdLength), + aMessageType, Move(msg)); +} + +void +GMPDecryptorChild::ExpirationChange(const char* aSessionId, + uint32_t aSessionIdLength, + GMPTimestamp aExpiryTime) +{ + CALL_ON_GMP_THREAD(SendExpirationChange, + nsCString(aSessionId, aSessionIdLength), aExpiryTime); +} + +void +GMPDecryptorChild::SessionClosed(const char* aSessionId, + uint32_t aSessionIdLength) +{ + CALL_ON_GMP_THREAD(SendSessionClosed, + nsCString(aSessionId, aSessionIdLength)); +} + +void +GMPDecryptorChild::SessionError(const char* aSessionId, + uint32_t aSessionIdLength, + GMPDOMException aException, + uint32_t aSystemCode, + const char* aMessage, + uint32_t aMessageLength) +{ + CALL_ON_GMP_THREAD(SendSessionError, + nsCString(aSessionId, aSessionIdLength), + aException, aSystemCode, + nsCString(aMessage, aMessageLength)); +} + +void +GMPDecryptorChild::KeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aKeyId, + uint32_t aKeyIdLength, + GMPMediaKeyStatus aStatus) +{ + AutoTArray<uint8_t, 16> kid; + kid.AppendElements(aKeyId, aKeyIdLength); + + nsTArray<GMPKeyInformation> keyInfos; + keyInfos.AppendElement(GMPKeyInformation(kid, aStatus)); + CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged, + nsCString(aSessionId, aSessionIdLength), + keyInfos); +} + +void +GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const GMPMediaKeyInfo* aKeyInfos, + uint32_t aKeyInfosLength) +{ + nsTArray<GMPKeyInformation> keyInfos; + for (uint32_t i = 0; i < aKeyInfosLength; i++) { + nsTArray<uint8_t> keyId; + keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size); + keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status)); + } + CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged, + nsCString(aSessionId, aSessionIdLength), + keyInfos); +} + +void +GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult) +{ + if (!ON_GMP_THREAD()) { + // We should run this whole method on the GMP thread since the buffer needs + // to be deleted after the SendDecrypted call. + mPlugin->GMPMessageLoop()->PostTask(NewRunnableMethod + <GMPBuffer*, GMPErr>(this, + &GMPDecryptorChild::Decrypted, + aBuffer, aResult)); + return; + } + + if (!aBuffer) { + NS_WARNING("GMPDecryptorCallback passed bull GMPBuffer"); + return; + } + + auto buffer = static_cast<GMPBufferImpl*>(aBuffer); + if (mSession) { + SendDecrypted(buffer->mId, aResult, buffer->mData); + } + delete buffer; +} + +void +GMPDecryptorChild::SetCapabilities(uint64_t aCaps) +{ + // Deprecated. +} + +void +GMPDecryptorChild::GetSandboxVoucher(const uint8_t** aVoucher, + uint32_t* aVoucherLength) +{ + if (!aVoucher || !aVoucherLength) { + return; + } + *aVoucher = mSandboxVoucher.Elements(); + *aVoucherLength = mSandboxVoucher.Length(); +} + +void +GMPDecryptorChild::GetPluginVoucher(const uint8_t** aVoucher, + uint32_t* aVoucherLength) +{ + if (!aVoucher || !aVoucherLength) { + return; + } + *aVoucher = mPluginVoucher.Elements(); + *aVoucherLength = mPluginVoucher.Length(); +} + +bool +GMPDecryptorChild::RecvInit(const bool& aDistinctiveIdentifierRequired, + const bool& aPersistentStateRequired) +{ + if (!mSession) { + return false; + } + mSession->Init(this, aDistinctiveIdentifierRequired, aPersistentStateRequired); + return true; +} + +bool +GMPDecryptorChild::RecvCreateSession(const uint32_t& aCreateSessionToken, + const uint32_t& aPromiseId, + const nsCString& aInitDataType, + InfallibleTArray<uint8_t>&& aInitData, + const GMPSessionType& aSessionType) +{ + if (!mSession) { + return false; + } + + mSession->CreateSession(aCreateSessionToken, + aPromiseId, + aInitDataType.get(), + aInitDataType.Length(), + aInitData.Elements(), + aInitData.Length(), + aSessionType); + + return true; +} + +bool +GMPDecryptorChild::RecvLoadSession(const uint32_t& aPromiseId, + const nsCString& aSessionId) +{ + if (!mSession) { + return false; + } + + mSession->LoadSession(aPromiseId, + aSessionId.get(), + aSessionId.Length()); + + return true; +} + +bool +GMPDecryptorChild::RecvUpdateSession(const uint32_t& aPromiseId, + const nsCString& aSessionId, + InfallibleTArray<uint8_t>&& aResponse) +{ + if (!mSession) { + return false; + } + + mSession->UpdateSession(aPromiseId, + aSessionId.get(), + aSessionId.Length(), + aResponse.Elements(), + aResponse.Length()); + + return true; +} + +bool +GMPDecryptorChild::RecvCloseSession(const uint32_t& aPromiseId, + const nsCString& aSessionId) +{ + if (!mSession) { + return false; + } + + mSession->CloseSession(aPromiseId, + aSessionId.get(), + aSessionId.Length()); + + return true; +} + +bool +GMPDecryptorChild::RecvRemoveSession(const uint32_t& aPromiseId, + const nsCString& aSessionId) +{ + if (!mSession) { + return false; + } + + mSession->RemoveSession(aPromiseId, + aSessionId.get(), + aSessionId.Length()); + + return true; +} + +bool +GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId, + InfallibleTArray<uint8_t>&& aServerCert) +{ + if (!mSession) { + return false; + } + + mSession->SetServerCertificate(aPromiseId, + aServerCert.Elements(), + aServerCert.Length()); + + return true; +} + +bool +GMPDecryptorChild::RecvDecrypt(const uint32_t& aId, + InfallibleTArray<uint8_t>&& aBuffer, + const GMPDecryptionData& aMetadata) +{ + if (!mSession) { + return false; + } + + // Note: the GMPBufferImpl created here is deleted when the GMP passes + // it back in the Decrypted() callback above. + GMPBufferImpl* buffer = new GMPBufferImpl(aId, aBuffer); + + // |metadata| lifetime is managed by |buffer|. + GMPEncryptedBufferDataImpl* metadata = new GMPEncryptedBufferDataImpl(aMetadata); + buffer->SetMetadata(metadata); + + mSession->Decrypt(buffer, metadata); + return true; +} + +bool +GMPDecryptorChild::RecvDecryptingComplete() +{ + // Reset |mSession| before calling DecryptingComplete(). We should not send + // any IPC messages during tear-down. + auto session = mSession; + mSession = nullptr; + + if (!session) { + return false; + } + + session->DecryptingComplete(); + + Unused << Send__delete__(this); + + return true; +} + +} // namespace gmp +} // namespace mozilla + +// avoid redefined macro in unified build +#undef ON_GMP_THREAD +#undef CALL_ON_GMP_THREAD diff --git a/dom/media/gmp/GMPDecryptorChild.h b/dom/media/gmp/GMPDecryptorChild.h new file mode 100644 index 000000000..434da774f --- /dev/null +++ b/dom/media/gmp/GMPDecryptorChild.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPDecryptorChild_h_ +#define GMPDecryptorChild_h_ + +#include "mozilla/gmp/PGMPDecryptorChild.h" +#include "gmp-decryption.h" +#include "mozilla/gmp/GMPTypes.h" +#include "GMPEncryptedBufferDataImpl.h" +#include <string> + +namespace mozilla { +namespace gmp { + +class GMPContentChild; + +class GMPDecryptorChild : public GMPDecryptorCallback + , public GMPDecryptorHost + , public PGMPDecryptorChild +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPDecryptorChild); + + explicit GMPDecryptorChild(GMPContentChild* aPlugin, + const nsTArray<uint8_t>& aPluginVoucher, + const nsTArray<uint8_t>& aSandboxVoucher); + + void Init(GMPDecryptor* aSession); + + // GMPDecryptorCallback + void SetSessionId(uint32_t aCreateSessionToken, + const char* aSessionId, + uint32_t aSessionIdLength) override; + void ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) override; + void ResolvePromise(uint32_t aPromiseId) override; + + void RejectPromise(uint32_t aPromiseId, + GMPDOMException aException, + const char* aMessage, + uint32_t aMessageLength) override; + + void SessionMessage(const char* aSessionId, + uint32_t aSessionIdLength, + GMPSessionMessageType aMessageType, + const uint8_t* aMessage, + uint32_t aMessageLength) override; + + void ExpirationChange(const char* aSessionId, + uint32_t aSessionIdLength, + GMPTimestamp aExpiryTime) override; + + void SessionClosed(const char* aSessionId, + uint32_t aSessionIdLength) override; + + void SessionError(const char* aSessionId, + uint32_t aSessionIdLength, + GMPDOMException aException, + uint32_t aSystemCode, + const char* aMessage, + uint32_t aMessageLength) override; + + void KeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aKeyId, + uint32_t aKeyIdLength, + GMPMediaKeyStatus aStatus) override; + + void SetCapabilities(uint64_t aCaps) override; + + void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override; + + void BatchedKeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const GMPMediaKeyInfo* aKeyInfos, + uint32_t aKeyInfosLength) override; + + // GMPDecryptorHost + void GetSandboxVoucher(const uint8_t** aVoucher, + uint32_t* aVoucherLength) override; + + void GetPluginVoucher(const uint8_t** aVoucher, + uint32_t* aVoucherLength) override; +private: + ~GMPDecryptorChild(); + + // GMPDecryptorChild + bool RecvInit(const bool& aDistinctiveIdentifierRequired, + const bool& aPersistentStateRequired) override; + + bool RecvCreateSession(const uint32_t& aCreateSessionToken, + const uint32_t& aPromiseId, + const nsCString& aInitDataType, + InfallibleTArray<uint8_t>&& aInitData, + const GMPSessionType& aSessionType) override; + + bool RecvLoadSession(const uint32_t& aPromiseId, + const nsCString& aSessionId) override; + + bool RecvUpdateSession(const uint32_t& aPromiseId, + const nsCString& aSessionId, + InfallibleTArray<uint8_t>&& aResponse) override; + + bool RecvCloseSession(const uint32_t& aPromiseId, + const nsCString& aSessionId) override; + + bool RecvRemoveSession(const uint32_t& aPromiseId, + const nsCString& aSessionId) override; + + bool RecvDecrypt(const uint32_t& aId, + InfallibleTArray<uint8_t>&& aBuffer, + const GMPDecryptionData& aMetadata) override; + + // Resolve/reject promise on completion. + bool RecvSetServerCertificate(const uint32_t& aPromiseId, + InfallibleTArray<uint8_t>&& aServerCert) override; + + bool RecvDecryptingComplete() override; + + template <typename MethodType, typename... ParamType> + void CallMethod(MethodType, ParamType&&...); + + template<typename MethodType, typename... ParamType> + void CallOnGMPThread(MethodType, ParamType&&...); + + // GMP's GMPDecryptor implementation. + // Only call into this on the (GMP process) main thread. + GMPDecryptor* mSession; + GMPContentChild* mPlugin; + + // Reference to the vouchers owned by the GMPChild. + const nsTArray<uint8_t>& mPluginVoucher; + const nsTArray<uint8_t>& mSandboxVoucher; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPDecryptorChild_h_ diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp new file mode 100644 index 000000000..f2a5d2431 --- /dev/null +++ b/dom/media/gmp/GMPDecryptorParent.cpp @@ -0,0 +1,384 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPDecryptorParent.h" + +#include "GMPContentParent.h" +#include "GMPUtils.h" +#include "MediaData.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg) +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +namespace gmp { + +GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin) + : mIsOpen(false) + , mShuttingDown(false) + , mActorDestroyed(false) + , mPlugin(aPlugin) + , mPluginId(aPlugin->GetPluginId()) + , mCallback(nullptr) +#ifdef DEBUG + , mGMPThread(aPlugin->GMPThread()) +#endif +{ + MOZ_ASSERT(mPlugin && mGMPThread); +} + +GMPDecryptorParent::~GMPDecryptorParent() +{ +} + +bool +GMPDecryptorParent::RecvSetDecryptorId(const uint32_t& aId) +{ + /* STUB */ + return true; +} + +nsresult +GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) +{ + LOGD(("GMPDecryptorParent[%p]::Init()", this)); + + if (mIsOpen) { + NS_WARNING("Trying to re-use an in-use GMP decrypter!"); + return NS_ERROR_FAILURE; + } + mCallback = aCallback; + if (!SendInit(aDistinctiveIdentifierRequired, aPersistentStateRequired)) { + return NS_ERROR_FAILURE; + } + mIsOpen = true; + return NS_OK; +} + +void +GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const nsCString& aInitDataType, + const nsTArray<uint8_t>& aInitData, + GMPSessionType aSessionType) +{ + LOGD(("GMPDecryptorParent[%p]::CreateSession(token=%u, promiseId=%u, aInitData='%s')", + this, aCreateSessionToken, aPromiseId, ToBase64(aInitData).get())); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + // Caller should ensure parameters passed in from JS are valid. + MOZ_ASSERT(!aInitDataType.IsEmpty() && !aInitData.IsEmpty()); + Unused << SendCreateSession(aCreateSessionToken, aPromiseId, aInitDataType, aInitData, aSessionType); +} + +void +GMPDecryptorParent::LoadSession(uint32_t aPromiseId, + const nsCString& aSessionId) +{ + LOGD(("GMPDecryptorParent[%p]::LoadSession(sessionId='%s', promiseId=%u)", + this, aSessionId.get(), aPromiseId)); + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + // Caller should ensure parameters passed in from JS are valid. + MOZ_ASSERT(!aSessionId.IsEmpty()); + Unused << SendLoadSession(aPromiseId, aSessionId); +} + +void +GMPDecryptorParent::UpdateSession(uint32_t aPromiseId, + const nsCString& aSessionId, + const nsTArray<uint8_t>& aResponse) +{ + LOGD(("GMPDecryptorParent[%p]::UpdateSession(sessionId='%s', promiseId=%u response='%s')", + this, aSessionId.get(), aPromiseId, ToBase64(aResponse).get())); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + // Caller should ensure parameters passed in from JS are valid. + MOZ_ASSERT(!aSessionId.IsEmpty() && !aResponse.IsEmpty()); + Unused << SendUpdateSession(aPromiseId, aSessionId, aResponse); +} + +void +GMPDecryptorParent::CloseSession(uint32_t aPromiseId, + const nsCString& aSessionId) +{ + LOGD(("GMPDecryptorParent[%p]::CloseSession(sessionId='%s', promiseId=%u)", + this, aSessionId.get(), aPromiseId)); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + // Caller should ensure parameters passed in from JS are valid. + MOZ_ASSERT(!aSessionId.IsEmpty()); + Unused << SendCloseSession(aPromiseId, aSessionId); +} + +void +GMPDecryptorParent::RemoveSession(uint32_t aPromiseId, + const nsCString& aSessionId) +{ + LOGD(("GMPDecryptorParent[%p]::RemoveSession(sessionId='%s', promiseId=%u)", + this, aSessionId.get(), aPromiseId)); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + // Caller should ensure parameters passed in from JS are valid. + MOZ_ASSERT(!aSessionId.IsEmpty()); + Unused << SendRemoveSession(aPromiseId, aSessionId); +} + +void +GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId, + const nsTArray<uint8_t>& aServerCert) +{ + LOGD(("GMPDecryptorParent[%p]::SetServerCertificate(promiseId=%u)", + this, aPromiseId)); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + // Caller should ensure parameters passed in from JS are valid. + MOZ_ASSERT(!aServerCert.IsEmpty()); + Unused << SendSetServerCertificate(aPromiseId, aServerCert); +} + +void +GMPDecryptorParent::Decrypt(uint32_t aId, + const CryptoSample& aCrypto, + const nsTArray<uint8_t>& aBuffer) +{ + LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId)); + + if (!mIsOpen) { + NS_WARNING("Trying to use a dead GMP decrypter!"); + return; + } + + // Caller should ensure parameters passed in are valid. + MOZ_ASSERT(!aBuffer.IsEmpty()); + + if (aCrypto.mValid) { + GMPDecryptionData data(aCrypto.mKeyId, + aCrypto.mIV, + aCrypto.mPlainSizes, + aCrypto.mEncryptedSizes, + aCrypto.mSessionIds); + + Unused << SendDecrypt(aId, aBuffer, data); + } else { + GMPDecryptionData data; + Unused << SendDecrypt(aId, aBuffer, data); + } +} + +bool +GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId, + const nsCString& aSessionId) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId, + const bool& aSuccess) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId) +{ + /* STUB */ + return true; +} + +nsresult +GMPExToNsresult(GMPDOMException aDomException) { + switch (aDomException) { + case kGMPNoModificationAllowedError: return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; + case kGMPNotFoundError: return NS_ERROR_DOM_NOT_FOUND_ERR; + case kGMPNotSupportedError: return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + case kGMPInvalidStateError: return NS_ERROR_DOM_INVALID_STATE_ERR; + case kGMPSyntaxError: return NS_ERROR_DOM_SYNTAX_ERR; + case kGMPInvalidModificationError: return NS_ERROR_DOM_INVALID_MODIFICATION_ERR; + case kGMPInvalidAccessError: return NS_ERROR_DOM_INVALID_ACCESS_ERR; + case kGMPSecurityError: return NS_ERROR_DOM_SECURITY_ERR; + case kGMPAbortError: return NS_ERROR_DOM_ABORT_ERR; + case kGMPQuotaExceededError: return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR; + case kGMPTimeoutError: return NS_ERROR_DOM_TIMEOUT_ERR; + case kGMPTypeError: return NS_ERROR_DOM_TYPE_ERR; + default: return NS_ERROR_DOM_UNKNOWN_ERR; + } +} + +bool +GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId, + const GMPDOMException& aException, + const nsCString& aMessage) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId, + const GMPSessionMessageType& aMessageType, + nsTArray<uint8_t>&& aMessage) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId, + const double& aExpiryTime) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId, + const GMPDOMException& aException, + const uint32_t& aSystemCode, + const nsCString& aMessage) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId, + InfallibleTArray<GMPKeyInformation>&& aKeyInfos) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvDecrypted(const uint32_t& aId, + const GMPErr& aErr, + InfallibleTArray<uint8_t>&& aBuffer) +{ + /* STUB */ + return true; +} + +bool +GMPDecryptorParent::RecvShutdown() +{ + LOGD(("GMPDecryptorParent[%p]::RecvShutdown()", this)); + + Shutdown(); + return true; +} + +// Note: may be called via Terminated() +void +GMPDecryptorParent::Close() +{ + LOGD(("GMPDecryptorParent[%p]::Close()", this)); + MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); + + // Consumer is done with us; we can shut down. No more callbacks should + // be made to mCallback. Note: do this before Shutdown()! + mCallback = nullptr; + // Let Shutdown mark us as dead so it knows if we had been alive + + // In case this is the last reference + RefPtr<GMPDecryptorParent> kungfudeathgrip(this); + this->Release(); + Shutdown(); +} + +void +GMPDecryptorParent::Shutdown() +{ + LOGD(("GMPDecryptorParent[%p]::Shutdown()", this)); + MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); + + if (mShuttingDown) { + return; + } + mShuttingDown = true; + + // Notify client we're gone! Won't occur after Close() + if (mCallback) { + mCallback->Terminated(); + mCallback = nullptr; + } + + mIsOpen = false; + if (!mActorDestroyed) { + Unused << SendDecryptingComplete(); + } +} + +// Note: Keep this sync'd up with Shutdown +void +GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD(("GMPDecryptorParent[%p]::ActorDestroy(reason=%d)", this, aWhy)); + + mIsOpen = false; + mActorDestroyed = true; + if (mCallback) { + // May call Close() (and Shutdown()) immediately or with a delay + mCallback->Terminated(); + mCallback = nullptr; + } + if (mPlugin) { + mPlugin->DecryptorDestroyed(this); + mPlugin = nullptr; + } + MaybeDisconnect(aWhy == AbnormalShutdown); +} + +bool +GMPDecryptorParent::Recv__delete__() +{ + LOGD(("GMPDecryptorParent[%p]::Recv__delete__()", this)); + + if (mPlugin) { + mPlugin->DecryptorDestroyed(this); + mPlugin = nullptr; + } + return true; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h new file mode 100644 index 000000000..30ff24690 --- /dev/null +++ b/dom/media/gmp/GMPDecryptorParent.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPDecryptorParent_h_ +#define GMPDecryptorParent_h_ + +#include "mozilla/gmp/PGMPDecryptorParent.h" +#include "mozilla/RefPtr.h" +#include "gmp-decryption.h" +#include "GMPDecryptorProxy.h" +#include "GMPCrashHelperHolder.h" + +namespace mozilla { + +class CryptoSample; + +namespace gmp { + +class GMPContentParent; + +class GMPDecryptorParent final : public GMPDecryptorProxy + , public PGMPDecryptorParent + , public GMPCrashHelperHolder +{ +public: + NS_INLINE_DECL_REFCOUNTING(GMPDecryptorParent) + + explicit GMPDecryptorParent(GMPContentParent *aPlugin); + + // GMPDecryptorProxy + + uint32_t GetPluginId() const override { return mPluginId; } + + nsresult Init(GMPDecryptorProxyCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) override; + + void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const nsCString& aInitDataType, + const nsTArray<uint8_t>& aInitData, + GMPSessionType aSessionType) override; + + void LoadSession(uint32_t aPromiseId, + const nsCString& aSessionId) override; + + void UpdateSession(uint32_t aPromiseId, + const nsCString& aSessionId, + const nsTArray<uint8_t>& aResponse) override; + + void CloseSession(uint32_t aPromiseId, + const nsCString& aSessionId) override; + + void RemoveSession(uint32_t aPromiseId, + const nsCString& aSessionId) override; + + void SetServerCertificate(uint32_t aPromiseId, + const nsTArray<uint8_t>& aServerCert) override; + + void Decrypt(uint32_t aId, + const CryptoSample& aCrypto, + const nsTArray<uint8_t>& aBuffer) override; + + void Close() override; + + void Shutdown(); + +private: + ~GMPDecryptorParent(); + + // PGMPDecryptorParent + + bool RecvSetDecryptorId(const uint32_t& aId) override; + + bool RecvSetSessionId(const uint32_t& aCreateSessionToken, + const nsCString& aSessionId) override; + + bool RecvResolveLoadSessionPromise(const uint32_t& aPromiseId, + const bool& aSuccess) override; + + bool RecvResolvePromise(const uint32_t& aPromiseId) override; + + bool RecvRejectPromise(const uint32_t& aPromiseId, + const GMPDOMException& aException, + const nsCString& aMessage) override; + + bool RecvSessionMessage(const nsCString& aSessionId, + const GMPSessionMessageType& aMessageType, + nsTArray<uint8_t>&& aMessage) override; + + bool RecvExpirationChange(const nsCString& aSessionId, + const double& aExpiryTime) override; + + bool RecvSessionClosed(const nsCString& aSessionId) override; + + bool RecvSessionError(const nsCString& aSessionId, + const GMPDOMException& aException, + const uint32_t& aSystemCode, + const nsCString& aMessage) override; + + bool RecvDecrypted(const uint32_t& aId, + const GMPErr& aErr, + InfallibleTArray<uint8_t>&& aBuffer) override; + + bool RecvBatchedKeyStatusChanged(const nsCString& aSessionId, + InfallibleTArray<GMPKeyInformation>&& aKeyInfos) override; + + bool RecvShutdown() override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + bool Recv__delete__() override; + + bool mIsOpen; + bool mShuttingDown; + bool mActorDestroyed; + RefPtr<GMPContentParent> mPlugin; + uint32_t mPluginId; + GMPDecryptorProxyCallback* mCallback; +#ifdef DEBUG + nsIThread* const mGMPThread; +#endif +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPDecryptorChild_h_ diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h new file mode 100644 index 000000000..f9e34a45f --- /dev/null +++ b/dom/media/gmp/GMPDecryptorProxy.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPDecryptorProxy_h_ +#define GMPDecryptorProxy_h_ + +#include "GMPCallbackBase.h" +#include "gmp-decryption.h" +#include "nsString.h" + +namespace mozilla { +class CryptoSample; +} // namespace mozilla + +class GMPDecryptorProxyCallback : public GMPCallbackBase { + +public: + virtual ~GMPDecryptorProxyCallback() {} +}; + +class GMPDecryptorProxy { +public: + ~GMPDecryptorProxy() {} + + virtual uint32_t GetPluginId() const = 0; + + virtual nsresult Init(GMPDecryptorProxyCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) = 0; + + virtual void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const nsCString& aInitDataType, + const nsTArray<uint8_t>& aInitData, + GMPSessionType aSessionType) = 0; + + virtual void LoadSession(uint32_t aPromiseId, + const nsCString& aSessionId) = 0; + + virtual void UpdateSession(uint32_t aPromiseId, + const nsCString& aSessionId, + const nsTArray<uint8_t>& aResponse) = 0; + + virtual void CloseSession(uint32_t aPromiseId, + const nsCString& aSessionId) = 0; + + virtual void RemoveSession(uint32_t aPromiseId, + const nsCString& aSessionId) = 0; + + virtual void SetServerCertificate(uint32_t aPromiseId, + const nsTArray<uint8_t>& aServerCert) = 0; + + virtual void Decrypt(uint32_t aId, + const mozilla::CryptoSample& aCrypto, + const nsTArray<uint8_t>& aBuffer) = 0; + + virtual void Close() = 0; +}; + +#endif // GMPDecryptorProxy_h_ diff --git a/dom/media/gmp/GMPDiskStorage.cpp b/dom/media/gmp/GMPDiskStorage.cpp new file mode 100644 index 000000000..11f49c8fe --- /dev/null +++ b/dom/media/gmp/GMPDiskStorage.cpp @@ -0,0 +1,499 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "plhash.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "GMPParent.h" +#include "gmp-storage.h" +#include "mozilla/Unused.h" +#include "mozilla/EndianUtils.h" +#include "nsClassHashtable.h" +#include "prio.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsContentCID.h" +#include "nsServiceManagerUtils.h" +#include "nsISimpleEnumerator.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +namespace gmp { + +// We store the records for a given GMP as files in the profile dir. +// $profileDir/gmp/$platform/$gmpName/storage/$nodeId/ +static nsresult +GetGMPStorageDir(nsIFile** aTempDir, + const nsString& aGMPName, + const nsCString& aNodeId) +{ + if (NS_WARN_IF(!aTempDir)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<mozIGeckoMediaPluginChromeService> mps = + do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + if (NS_WARN_IF(!mps)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = mps->GetStorageDir(getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->Append(aGMPName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("storage")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->AppendNative(aNodeId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + tmpFile.forget(aTempDir); + + return NS_OK; +} + +// Disk-backed GMP storage. Records are stored in files on disk in +// the profile directory. The record name is a hash of the filename, +// and we resolve hash collisions by just adding 1 to the hash code. +// The format of records on disk is: +// 4 byte, uint32_t $recordNameLength, in little-endian byte order, +// record name (i.e. $recordNameLength bytes, no null terminator) +// record bytes (entire remainder of file) +class GMPDiskStorage : public GMPStorage { +public: + explicit GMPDiskStorage(const nsCString& aNodeId, + const nsString& aGMPName) + : mNodeId(aNodeId) + , mGMPName(aGMPName) + { + } + + ~GMPDiskStorage() { + // Close all open file handles. + for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) { + Record* record = iter.UserData(); + if (record->mFileDesc) { + PR_Close(record->mFileDesc); + record->mFileDesc = nullptr; + } + } + } + + nsresult Init() { + // Build our index of records on disk. + nsCOMPtr<nsIFile> storageDir; + nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + DirectoryEnumerator iter(storageDir, DirectoryEnumerator::FilesAndDirs); + for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { + PRFileDesc* fd = nullptr; + if (NS_FAILED(dirEntry->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) { + continue; + } + int32_t recordLength = 0; + nsCString recordName; + nsresult err = ReadRecordMetadata(fd, recordLength, recordName); + PR_Close(fd); + if (NS_FAILED(err)) { + // File is not a valid storage file. Don't index it. Delete the file, + // to make our indexing faster in future. + dirEntry->Remove(false); + continue; + } + + nsAutoString filename; + rv = dirEntry->GetLeafName(filename); + if (NS_FAILED(rv)) { + continue; + } + + mRecords.Put(recordName, new Record(filename, recordName)); + } + + return NS_OK; + } + + GMPErr Open(const nsCString& aRecordName) override + { + MOZ_ASSERT(!IsOpen(aRecordName)); + nsresult rv; + Record* record = nullptr; + if (!mRecords.Get(aRecordName, &record)) { + // New file. + nsAutoString filename; + rv = GetUnusedFilename(aRecordName, filename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GMPGenericErr; + } + record = new Record(filename, aRecordName); + mRecords.Put(aRecordName, record); + } + + MOZ_ASSERT(record); + if (record->mFileDesc) { + NS_WARNING("Tried to open already open record"); + return GMPRecordInUse; + } + + rv = OpenStorageFile(record->mFilename, ReadWrite, &record->mFileDesc); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GMPGenericErr; + } + + MOZ_ASSERT(IsOpen(aRecordName)); + + return GMPNoErr; + } + + bool IsOpen(const nsCString& aRecordName) const override { + // We are open if we have a record indexed, and it has a valid + // file descriptor. + const Record* record = mRecords.Get(aRecordName); + return record && !!record->mFileDesc; + } + + GMPErr Read(const nsCString& aRecordName, + nsTArray<uint8_t>& aOutBytes) override + { + if (!IsOpen(aRecordName)) { + return GMPClosedErr; + } + + Record* record = nullptr; + mRecords.Get(aRecordName, &record); + MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this. + + // Our error strategy is to report records with invalid contents as + // containing 0 bytes. Zero length records are considered "deleted" by + // the GMPStorage API. + aOutBytes.SetLength(0); + + int32_t recordLength = 0; + nsCString recordName; + nsresult err = ReadRecordMetadata(record->mFileDesc, + recordLength, + recordName); + if (NS_FAILED(err) || recordLength == 0) { + // We failed to read the record metadata. Or the record is 0 length. + // Treat damaged records as empty. + // ReadRecordMetadata() could fail if the GMP opened a new record and + // tried to read it before anything was written to it.. + return GMPNoErr; + } + + if (!aRecordName.Equals(recordName)) { + NS_WARNING("Record file contains some other record's contents!"); + return GMPRecordCorrupted; + } + + // After calling ReadRecordMetadata, we should be ready to read the + // record data. + if (PR_Available(record->mFileDesc) != recordLength) { + NS_WARNING("Record file length mismatch!"); + return GMPRecordCorrupted; + } + + aOutBytes.SetLength(recordLength); + int32_t bytesRead = PR_Read(record->mFileDesc, aOutBytes.Elements(), recordLength); + return (bytesRead == recordLength) ? GMPNoErr : GMPRecordCorrupted; + } + + GMPErr Write(const nsCString& aRecordName, + const nsTArray<uint8_t>& aBytes) override + { + if (!IsOpen(aRecordName)) { + return GMPClosedErr; + } + + Record* record = nullptr; + mRecords.Get(aRecordName, &record); + MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this. + + // Write operations overwrite the entire record. So close it now. + PR_Close(record->mFileDesc); + record->mFileDesc = nullptr; + + // Writing 0 bytes means removing (deleting) the file. + if (aBytes.Length() == 0) { + nsresult rv = RemoveStorageFile(record->mFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Could not delete file -> Continue with trying to erase the contents. + } else { + return GMPNoErr; + } + } + + // Write operations overwrite the entire record. So re-open the file + // in truncate mode, to clear its contents. + if (NS_FAILED(OpenStorageFile(record->mFilename, + Truncate, + &record->mFileDesc))) { + return GMPGenericErr; + } + + // Store the length of the record name followed by the record name + // at the start of the file. + int32_t bytesWritten = 0; + char buf[sizeof(uint32_t)] = {0}; + LittleEndian::writeUint32(buf, aRecordName.Length()); + bytesWritten = PR_Write(record->mFileDesc, buf, MOZ_ARRAY_LENGTH(buf)); + if (bytesWritten != MOZ_ARRAY_LENGTH(buf)) { + NS_WARNING("Failed to write GMPStorage record name length."); + return GMPRecordCorrupted; + } + bytesWritten = PR_Write(record->mFileDesc, + aRecordName.get(), + aRecordName.Length()); + if (bytesWritten != (int32_t)aRecordName.Length()) { + NS_WARNING("Failed to write GMPStorage record name."); + return GMPRecordCorrupted; + } + + bytesWritten = PR_Write(record->mFileDesc, aBytes.Elements(), aBytes.Length()); + if (bytesWritten != (int32_t)aBytes.Length()) { + NS_WARNING("Failed to write GMPStorage record data."); + return GMPRecordCorrupted; + } + + // Try to sync the file to disk, so that in the event of a crash, + // the record is less likely to be corrupted. + PR_Sync(record->mFileDesc); + + return GMPNoErr; + } + + GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const override + { + for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) { + aOutRecordNames.AppendElement(iter.UserData()->mRecordName); + } + return GMPNoErr; + } + + void Close(const nsCString& aRecordName) override + { + Record* record = nullptr; + mRecords.Get(aRecordName, &record); + if (record && !!record->mFileDesc) { + PR_Close(record->mFileDesc); + record->mFileDesc = nullptr; + } + MOZ_ASSERT(!IsOpen(aRecordName)); + } + +private: + + // We store records in a file which is a hash of the record name. + // If there is a hash collision, we just keep adding 1 to the hash + // code, until we find a free slot. + nsresult GetUnusedFilename(const nsACString& aRecordName, + nsString& aOutFilename) + { + nsCOMPtr<nsIFile> storageDir; + nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint64_t recordNameHash = HashString(PromiseFlatCString(aRecordName).get()); + for (int i = 0; i < 1000000; i++) { + nsCOMPtr<nsIFile> f; + rv = storageDir->Clone(getter_AddRefs(f)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsAutoString hashStr; + hashStr.AppendInt(recordNameHash); + rv = f->Append(hashStr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + bool exists = false; + f->Exists(&exists); + if (!exists) { + // Filename not in use, we can write into this file. + aOutFilename = hashStr; + return NS_OK; + } else { + // Hash collision; just increment the hash name and try that again. + ++recordNameHash; + continue; + } + } + // Somehow, we've managed to completely fail to find a vacant file name. + // Give up. + NS_WARNING("GetUnusedFilename had extreme hash collision!"); + return NS_ERROR_FAILURE; + } + + enum OpenFileMode { ReadWrite, Truncate }; + + nsresult OpenStorageFile(const nsAString& aFileLeafName, + const OpenFileMode aMode, + PRFileDesc** aOutFD) + { + MOZ_ASSERT(aOutFD); + + nsCOMPtr<nsIFile> f; + nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + f->Append(aFileLeafName); + + auto mode = PR_RDWR | PR_CREATE_FILE; + if (aMode == Truncate) { + mode |= PR_TRUNCATE; + } + + return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD); + } + + nsresult ReadRecordMetadata(PRFileDesc* aFd, + int32_t& aOutRecordLength, + nsACString& aOutRecordName) + { + int32_t offset = PR_Seek(aFd, 0, PR_SEEK_END); + PR_Seek(aFd, 0, PR_SEEK_SET); + + if (offset < 0 || offset > GMP_MAX_RECORD_SIZE) { + // Refuse to read big records, or records where we can't get a length. + return NS_ERROR_FAILURE; + } + const uint32_t fileLength = static_cast<uint32_t>(offset); + + // At the start of the file the length of the record name is stored in a + // uint32_t (little endian byte order) followed by the record name at the + // start of the file. The record name is not null terminated. The remainder + // of the file is the record's data. + + if (fileLength < sizeof(uint32_t)) { + // Record file doesn't have enough contents to store the record name + // length. Fail. + return NS_ERROR_FAILURE; + } + + // Read length, and convert to host byte order. + uint32_t recordNameLength = 0; + char buf[sizeof(recordNameLength)] = { 0 }; + int32_t bytesRead = PR_Read(aFd, &buf, sizeof(recordNameLength)); + recordNameLength = LittleEndian::readUint32(buf); + if (sizeof(recordNameLength) != bytesRead || + recordNameLength == 0 || + recordNameLength + sizeof(recordNameLength) > fileLength || + recordNameLength > GMP_MAX_RECORD_NAME_SIZE) { + // Record file has invalid contents. Fail. + return NS_ERROR_FAILURE; + } + + nsCString recordName; + recordName.SetLength(recordNameLength); + bytesRead = PR_Read(aFd, recordName.BeginWriting(), recordNameLength); + if ((uint32_t)bytesRead != recordNameLength) { + // Read failed. + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(fileLength >= sizeof(recordNameLength) + recordNameLength); + int32_t recordLength = fileLength - (sizeof(recordNameLength) + recordNameLength); + + aOutRecordLength = recordLength; + aOutRecordName = recordName; + + // Read cursor should be positioned after the record name, before the record contents. + if (PR_Seek(aFd, 0, PR_SEEK_CUR) != (int32_t)(sizeof(recordNameLength) + recordNameLength)) { + NS_WARNING("Read cursor mismatch after ReadRecordMetadata()"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + nsresult RemoveStorageFile(const nsString& aFilename) + { + nsCOMPtr<nsIFile> f; + nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = f->Append(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return f->Remove(/* bool recursive= */ false); + } + + struct Record { + Record(const nsAString& aFilename, + const nsACString& aRecordName) + : mFilename(aFilename) + , mRecordName(aRecordName) + , mFileDesc(0) + {} + ~Record() { + MOZ_ASSERT(!mFileDesc); + } + nsString mFilename; + nsCString mRecordName; + PRFileDesc* mFileDesc; + }; + + // Hash record name to record data. + nsClassHashtable<nsCStringHashKey, Record> mRecords; + const nsCString mNodeId; + const nsString mGMPName; +}; + +already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId, + const nsString& aGMPName) +{ + RefPtr<GMPDiskStorage> storage(new GMPDiskStorage(aNodeId, aGMPName)); + if (NS_FAILED(storage->Init())) { + NS_WARNING("Failed to initialize on disk GMP storage"); + return nullptr; + } + return storage.forget(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp new file mode 100644 index 000000000..206d7b42a --- /dev/null +++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPEncryptedBufferDataImpl.h" +#include "mozilla/gmp/GMPTypes.h" +#include "MediaData.h" + +namespace mozilla { +namespace gmp { + +GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto) + : mKeyId(aCrypto.mKeyId) + , mIV(aCrypto.mIV) + , mClearBytes(aCrypto.mPlainSizes) + , mCipherBytes(aCrypto.mEncryptedSizes) + , mSessionIdList(aCrypto.mSessionIds) +{ +} + +GMPEncryptedBufferDataImpl::GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData) + : mKeyId(aData.mKeyId()) + , mIV(aData.mIV()) + , mClearBytes(aData.mClearBytes()) + , mCipherBytes(aData.mCipherBytes()) + , mSessionIdList(aData.mSessionIds()) +{ + MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length()); +} + +GMPEncryptedBufferDataImpl::~GMPEncryptedBufferDataImpl() +{ +} + +void +GMPEncryptedBufferDataImpl::RelinquishData(GMPDecryptionData& aData) +{ + aData.mKeyId() = Move(mKeyId); + aData.mIV() = Move(mIV); + aData.mClearBytes() = Move(mClearBytes); + aData.mCipherBytes() = Move(mCipherBytes); + mSessionIdList.RelinquishData(aData.mSessionIds()); +} + +const uint8_t* +GMPEncryptedBufferDataImpl::KeyId() const +{ + return mKeyId.Elements(); +} + +uint32_t +GMPEncryptedBufferDataImpl::KeyIdSize() const +{ + return mKeyId.Length(); +} + +const uint8_t* +GMPEncryptedBufferDataImpl::IV() const +{ + return mIV.Elements(); +} + +uint32_t +GMPEncryptedBufferDataImpl::IVSize() const +{ + return mIV.Length(); +} + +const uint16_t* +GMPEncryptedBufferDataImpl::ClearBytes() const +{ + return mClearBytes.Elements(); +} + +const uint32_t* +GMPEncryptedBufferDataImpl::CipherBytes() const +{ + return mCipherBytes.Elements(); +} + +const GMPStringList* +GMPEncryptedBufferDataImpl::SessionIds() const +{ + return &mSessionIdList; +} + +uint32_t +GMPEncryptedBufferDataImpl::NumSubsamples() const +{ + MOZ_ASSERT(mClearBytes.Length() == mCipherBytes.Length()); + // Return the min of the two, to ensure there's not chance of array index + // out-of-bounds shenanigans. + return std::min<uint32_t>(mClearBytes.Length(), mCipherBytes.Length()); +} + +GMPStringListImpl::GMPStringListImpl(const nsTArray<nsCString>& aStrings) + : mStrings(aStrings) +{ +} + +uint32_t +GMPStringListImpl::Size() const +{ + return mStrings.Length(); +} + +void +GMPStringListImpl::StringAt(uint32_t aIndex, + const char** aOutString, + uint32_t *aOutLength) const +{ + if (NS_WARN_IF(aIndex >= Size())) { + return; + } + + *aOutString = mStrings[aIndex].BeginReading(); + *aOutLength = mStrings[aIndex].Length(); +} + +void +GMPStringListImpl::RelinquishData(nsTArray<nsCString>& aStrings) +{ + aStrings = Move(mStrings); +} + +GMPStringListImpl::~GMPStringListImpl() +{ +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPEncryptedBufferDataImpl.h b/dom/media/gmp/GMPEncryptedBufferDataImpl.h new file mode 100644 index 000000000..f0d5728df --- /dev/null +++ b/dom/media/gmp/GMPEncryptedBufferDataImpl.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPEncryptedBufferDataImpl_h_ +#define GMPEncryptedBufferDataImpl_h_ + +#include "gmp-decryption.h" +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "mozilla/gmp/GMPTypes.h" + +namespace mozilla { +class CryptoSample; + +namespace gmp { + +class GMPStringListImpl : public GMPStringList +{ +public: + explicit GMPStringListImpl(const nsTArray<nsCString>& aStrings); + uint32_t Size() const override; + void StringAt(uint32_t aIndex, + const char** aOutString, uint32_t *aOutLength) const override; + virtual ~GMPStringListImpl() override; + void RelinquishData(nsTArray<nsCString>& aStrings); + +private: + nsTArray<nsCString> mStrings; +}; + +class GMPEncryptedBufferDataImpl : public GMPEncryptedBufferMetadata { +public: + explicit GMPEncryptedBufferDataImpl(const CryptoSample& aCrypto); + explicit GMPEncryptedBufferDataImpl(const GMPDecryptionData& aData); + virtual ~GMPEncryptedBufferDataImpl(); + + void RelinquishData(GMPDecryptionData& aData); + + const uint8_t* KeyId() const override; + uint32_t KeyIdSize() const override; + const uint8_t* IV() const override; + uint32_t IVSize() const override; + uint32_t NumSubsamples() const override; + const uint16_t* ClearBytes() const override; + const uint32_t* CipherBytes() const override; + const GMPStringList* SessionIds() const override; + +private: + nsTArray<uint8_t> mKeyId; + nsTArray<uint8_t> mIV; + nsTArray<uint16_t> mClearBytes; + nsTArray<uint32_t> mCipherBytes; + + GMPStringListImpl mSessionIdList; +}; + +class GMPBufferImpl : public GMPBuffer { +public: + GMPBufferImpl(uint32_t aId, const nsTArray<uint8_t>& aData) + : mId(aId) + , mData(aData) + { + } + uint32_t Id() const override { + return mId; + } + uint8_t* Data() override { + return mData.Elements(); + } + uint32_t Size() const override { + return mData.Length(); + } + void Resize(uint32_t aSize) override { + mData.SetLength(aSize); + } + + // Set metadata object to be freed when this buffer is destroyed. + void SetMetadata(GMPEncryptedBufferDataImpl* aMetadata) { + mMetadata = aMetadata; + } + + uint32_t mId; + nsTArray<uint8_t> mData; + nsAutoPtr<GMPEncryptedBufferDataImpl> mMetadata; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPEncryptedBufferDataImpl_h_ diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp new file mode 100644 index 000000000..0bccdd0b1 --- /dev/null +++ b/dom/media/gmp/GMPLoader.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPLoader.h" +#include <stdio.h> +#include "mozilla/Attributes.h" +#include "gmp-entrypoints.h" +#include "prlink.h" +#include "prenv.h" + +#include <string> + +#ifdef XP_WIN +#include "windows.h" +#endif + +#include "GMPDeviceBinding.h" + +namespace mozilla { +namespace gmp { + +class GMPLoaderImpl : public GMPLoader { +public: + explicit GMPLoaderImpl(SandboxStarter* aStarter) + : mSandboxStarter(aStarter) + , mAdapter(nullptr) + {} + virtual ~GMPLoaderImpl() {} + + bool Load(const char* aUTF8LibPath, + uint32_t aUTF8LibPathLen, + char* aOriginSalt, + uint32_t aOriginSaltLen, + const GMPPlatformAPI* aPlatformAPI, + GMPAdapter* aAdapter) override; + + GMPErr GetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) override; + + void Shutdown() override; + +private: + SandboxStarter* mSandboxStarter; + UniquePtr<GMPAdapter> mAdapter; +}; + +UniquePtr<GMPLoader> CreateGMPLoader(SandboxStarter* aStarter) { + return MakeUnique<GMPLoaderImpl>(aStarter); +} + +class PassThroughGMPAdapter : public GMPAdapter { +public: + ~PassThroughGMPAdapter() { + // Ensure we're always shutdown, even if caller forgets to call GMPShutdown(). + GMPShutdown(); + } + + void SetAdaptee(PRLibrary* aLib) override + { + mLib = aLib; + } + + GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override + { + if (!mLib) { + return GMPGenericErr; + } + GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit")); + if (!initFunc) { + return GMPNotImplementedErr; + } + return initFunc(aPlatformAPI); + } + + GMPErr GMPGetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) override + { + if (!mLib) { + return GMPGenericErr; + } + GMPGetAPIFunc getapiFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI")); + if (!getapiFunc) { + return GMPNotImplementedErr; + } + return getapiFunc(aAPIName, aHostAPI, aPluginAPI); + } + + void GMPShutdown() override + { + if (mLib) { + GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown")); + if (shutdownFunc) { + shutdownFunc(); + } + PR_UnloadLibrary(mLib); + mLib = nullptr; + } + } + + void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override + { + if (!mLib) { + return; + } + GMPSetNodeIdFunc setNodeIdFunc = reinterpret_cast<GMPSetNodeIdFunc>(PR_FindFunctionSymbol(mLib, "GMPSetNodeId")); + if (setNodeIdFunc) { + setNodeIdFunc(aNodeId, aLength); + } + } + +private: + PRLibrary* mLib = nullptr; +}; + +bool +GMPLoaderImpl::Load(const char* aUTF8LibPath, + uint32_t aUTF8LibPathLen, + char* aOriginSalt, + uint32_t aOriginSaltLen, + const GMPPlatformAPI* aPlatformAPI, + GMPAdapter* aAdapter) +{ + std::string nodeId; + if (!CalculateGMPDeviceId(aOriginSalt, aOriginSaltLen, nodeId)) { + return false; + } + + // Start the sandbox now that we've generated the device bound node id. + // This must happen after the node id is bound to the device id, as + // generating the device id requires privileges. + if (mSandboxStarter && !mSandboxStarter->Start(aUTF8LibPath)) { + return false; + } + + // Load the GMP. + PRLibSpec libSpec; +#ifdef XP_WIN + int pathLen = MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, nullptr, 0); + if (pathLen == 0) { + return false; + } + + auto widePath = MakeUnique<wchar_t[]>(pathLen); + if (MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, widePath.get(), pathLen) == 0) { + return false; + } + + libSpec.value.pathname_u = widePath.get(); + libSpec.type = PR_LibSpec_PathnameU; +#else + libSpec.value.pathname = aUTF8LibPath; + libSpec.type = PR_LibSpec_Pathname; +#endif + PRLibrary* lib = PR_LoadLibraryWithFlags(libSpec, 0); + if (!lib) { + return false; + } + + GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(lib, "GMPInit")); + if ((initFunc && aAdapter) || + (!initFunc && !aAdapter)) { + // Ensure that if we're dealing with a GMP we do *not* use an adapter + // provided from the outside world. This is important as it means we + // don't call code not covered by Adobe's plugin-container voucher + // before we pass the node Id to Adobe's GMP. + return false; + } + + // Note: PassThroughGMPAdapter's code must remain in this file so that it's + // covered by Adobe's plugin-container voucher. + mAdapter.reset((!aAdapter) ? new PassThroughGMPAdapter() : aAdapter); + mAdapter->SetAdaptee(lib); + + if (mAdapter->GMPInit(aPlatformAPI) != GMPNoErr) { + return false; + } + + mAdapter->GMPSetNodeId(nodeId.c_str(), nodeId.size()); + + return true; +} + +GMPErr +GMPLoaderImpl::GetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) +{ + return mAdapter->GMPGetAPI(aAPIName, aHostAPI, aPluginAPI, aDecryptorId); +} + +void +GMPLoaderImpl::Shutdown() +{ + if (mAdapter) { + mAdapter->GMPShutdown(); + } +} + +} // namespace gmp +} // namespace mozilla + diff --git a/dom/media/gmp/GMPLoader.h b/dom/media/gmp/GMPLoader.h new file mode 100644 index 000000000..8e6b3cfac --- /dev/null +++ b/dom/media/gmp/GMPLoader.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMP_LOADER_H__ +#define GMP_LOADER_H__ + +#include <stdint.h> +#include "prlink.h" +#include "gmp-entrypoints.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace gmp { + +class SandboxStarter { +public: + virtual ~SandboxStarter() {} + virtual bool Start(const char* aLibPath) = 0; +}; + +// Interface that adapts a plugin to the GMP API. +class GMPAdapter { +public: + virtual ~GMPAdapter() {} + // Sets the adapted to plugin library module. + // Note: the GMPAdapter is responsible for calling PR_UnloadLibrary on aLib + // when it's finished with it. + virtual void SetAdaptee(PRLibrary* aLib) = 0; + + // These are called in place of the corresponding GMP API functions. + virtual GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) = 0; + virtual GMPErr GMPGetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) = 0; + virtual void GMPShutdown() = 0; + virtual void GMPSetNodeId(const char* aNodeId, uint32_t aLength) = 0; +}; + +// Encapsulates generating the device-bound node id, activating the sandbox, +// loading the GMP, and passing the node id to the GMP (in that order). +// +// In Desktop Gecko, the implementation of this lives in plugin-container, +// and is passed into XUL code from on startup. The GMP IPC child protocol actor +// uses this interface to load and retrieve interfaces from the GMPs. +// +// In Desktop Gecko the implementation lives in the plugin-container so that +// it can be covered by DRM vendor's voucher. +// +// On Android the GMPLoader implementation lives in libxul (because for the time +// being GMPLoader relies upon NSPR, which we can't use in plugin-container +// on Android). +// +// There is exactly one GMPLoader per GMP child process, and only one GMP +// per child process (so the GMPLoader only loads one GMP). +// +// Load() takes an optional GMPAdapter which can be used to adapt non-GMPs +// to adhere to the GMP API. +class GMPLoader { +public: + virtual ~GMPLoader() {} + + // Calculates the device-bound node id, then activates the sandbox, + // then loads the GMP library and (if applicable) passes the bound node id + // to the GMP. If aAdapter is non-null, the lib path is assumed to be + // a non-GMP, and the adapter is initialized with the lib and the adapter + // is used to interact with the plugin. + virtual bool Load(const char* aUTF8LibPath, + uint32_t aLibPathLen, + char* aOriginSalt, + uint32_t aOriginSaltLen, + const GMPPlatformAPI* aPlatformAPI, + GMPAdapter* aAdapter = nullptr) = 0; + + // Retrieves an interface pointer from the GMP. + virtual GMPErr GetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) = 0; + + // Calls the GMPShutdown function exported by the GMP lib, and unloads the + // plugin library. + virtual void Shutdown() = 0; +}; + +// On Desktop, this function resides in plugin-container. +// On Mobile, this function resides in XUL. +UniquePtr<GMPLoader> CreateGMPLoader(SandboxStarter* aStarter); + +} // namespace gmp +} // namespace mozilla + +#endif // GMP_LOADER_H__ diff --git a/dom/media/gmp/GMPMemoryStorage.cpp b/dom/media/gmp/GMPMemoryStorage.cpp new file mode 100644 index 000000000..dc799caa1 --- /dev/null +++ b/dom/media/gmp/GMPMemoryStorage.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPStorage.h" +#include "nsClassHashtable.h" + +namespace mozilla { +namespace gmp { + +class GMPMemoryStorage : public GMPStorage { +public: + GMPErr Open(const nsCString& aRecordName) override + { + MOZ_ASSERT(!IsOpen(aRecordName)); + + Record* record = nullptr; + if (!mRecords.Get(aRecordName, &record)) { + record = new Record(); + mRecords.Put(aRecordName, record); + } + record->mIsOpen = true; + return GMPNoErr; + } + + bool IsOpen(const nsCString& aRecordName) const override { + const Record* record = mRecords.Get(aRecordName); + if (!record) { + return false; + } + return record->mIsOpen; + } + + GMPErr Read(const nsCString& aRecordName, + nsTArray<uint8_t>& aOutBytes) override + { + const Record* record = mRecords.Get(aRecordName); + if (!record) { + return GMPGenericErr; + } + aOutBytes = record->mData; + return GMPNoErr; + } + + GMPErr Write(const nsCString& aRecordName, + const nsTArray<uint8_t>& aBytes) override + { + Record* record = nullptr; + if (!mRecords.Get(aRecordName, &record)) { + return GMPClosedErr; + } + record->mData = aBytes; + return GMPNoErr; + } + + GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const override + { + for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) { + aOutRecordNames.AppendElement(iter.Key()); + } + return GMPNoErr; + } + + void Close(const nsCString& aRecordName) override + { + Record* record = nullptr; + if (!mRecords.Get(aRecordName, &record)) { + return; + } + if (!record->mData.Length()) { + // Record is empty, delete. + mRecords.Remove(aRecordName); + } else { + record->mIsOpen = false; + } + } + +private: + + struct Record { + nsTArray<uint8_t> mData; + bool mIsOpen = false; + }; + + nsClassHashtable<nsCStringHashKey, Record> mRecords; +}; + +already_AddRefed<GMPStorage> CreateGMPMemoryStorage() +{ + return RefPtr<GMPStorage>(new GMPMemoryStorage()).forget(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPMessageUtils.h b/dom/media/gmp/GMPMessageUtils.h new file mode 100644 index 000000000..13f6127f3 --- /dev/null +++ b/dom/media/gmp/GMPMessageUtils.h @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPMessageUtils_h_ +#define GMPMessageUtils_h_ + +#include "gmp-video-codec.h" +#include "gmp-video-frame-encoded.h" +#include "gmp-audio-codec.h" +#include "gmp-decryption.h" + +namespace IPC { + +template <> +struct ParamTraits<GMPErr> +: public ContiguousEnumSerializer<GMPErr, + GMPNoErr, + GMPLastErr> +{}; + +struct GMPDomExceptionValidator { + static bool IsLegalValue(GMPDOMException aValue) { + switch (aValue) { + case kGMPNoModificationAllowedError: + case kGMPNotFoundError: + case kGMPNotSupportedError: + case kGMPInvalidStateError: + case kGMPSyntaxError: + case kGMPInvalidModificationError: + case kGMPInvalidAccessError: + case kGMPSecurityError: + case kGMPAbortError: + case kGMPQuotaExceededError: + case kGMPTimeoutError: + case kGMPTypeError: + return true; + default: + return false; + } + } +}; + +template <> +struct ParamTraits<GMPVideoFrameType> +: public ContiguousEnumSerializer<GMPVideoFrameType, + kGMPKeyFrame, + kGMPVideoFrameInvalid> +{}; + +template<> +struct ParamTraits<GMPDOMException> +: public EnumSerializer<GMPDOMException, GMPDomExceptionValidator> +{}; + +template <> +struct ParamTraits<GMPSessionMessageType> +: public ContiguousEnumSerializer<GMPSessionMessageType, + kGMPLicenseRequest, + kGMPMessageInvalid> +{}; + +template <> +struct ParamTraits<GMPMediaKeyStatus> +: public ContiguousEnumSerializer<GMPMediaKeyStatus, + kGMPUsable, + kGMPMediaKeyStatusInvalid> +{}; + +template <> +struct ParamTraits<GMPSessionType> +: public ContiguousEnumSerializer<GMPSessionType, + kGMPTemporySession, + kGMPSessionInvalid> +{}; + +template <> +struct ParamTraits<GMPAudioCodecType> +: public ContiguousEnumSerializer<GMPAudioCodecType, + kGMPAudioCodecAAC, + kGMPAudioCodecInvalid> +{}; + +template <> +struct ParamTraits<GMPVideoCodecComplexity> +: public ContiguousEnumSerializer<GMPVideoCodecComplexity, + kGMPComplexityNormal, + kGMPComplexityInvalid> +{}; + +template <> +struct ParamTraits<GMPVP8ResilienceMode> +: public ContiguousEnumSerializer<GMPVP8ResilienceMode, + kResilienceOff, + kResilienceInvalid> +{}; + +template <> +struct ParamTraits<GMPVideoCodecType> +: public ContiguousEnumSerializer<GMPVideoCodecType, + kGMPVideoCodecVP8, + kGMPVideoCodecInvalid> +{}; + +template <> +struct ParamTraits<GMPVideoCodecMode> +: public ContiguousEnumSerializer<GMPVideoCodecMode, + kGMPRealtimeVideo, + kGMPCodecModeInvalid> +{}; + +template <> +struct ParamTraits<GMPBufferType> +: public ContiguousEnumSerializer<GMPBufferType, + GMP_BufferSingle, + GMP_BufferInvalid> +{}; + +template <> +struct ParamTraits<GMPSimulcastStream> +{ + typedef GMPSimulcastStream paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mWidth); + WriteParam(aMsg, aParam.mHeight); + WriteParam(aMsg, aParam.mNumberOfTemporalLayers); + WriteParam(aMsg, aParam.mMaxBitrate); + WriteParam(aMsg, aParam.mTargetBitrate); + WriteParam(aMsg, aParam.mMinBitrate); + WriteParam(aMsg, aParam.mQPMax); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + if (ReadParam(aMsg, aIter, &(aResult->mWidth)) && + ReadParam(aMsg, aIter, &(aResult->mHeight)) && + ReadParam(aMsg, aIter, &(aResult->mNumberOfTemporalLayers)) && + ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) && + ReadParam(aMsg, aIter, &(aResult->mTargetBitrate)) && + ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) && + ReadParam(aMsg, aIter, &(aResult->mQPMax))) { + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"[%u, %u, %u, %u, %u, %u, %u]", aParam.mWidth, aParam.mHeight, + aParam.mNumberOfTemporalLayers, aParam.mMaxBitrate, + aParam.mTargetBitrate, aParam.mMinBitrate, aParam.mQPMax)); + } +}; + +template <> +struct ParamTraits<GMPVideoCodec> +{ + typedef GMPVideoCodec paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mGMPApiVersion); + WriteParam(aMsg, aParam.mCodecType); + WriteParam(aMsg, static_cast<const nsCString&>(nsDependentCString(aParam.mPLName))); + WriteParam(aMsg, aParam.mPLType); + WriteParam(aMsg, aParam.mWidth); + WriteParam(aMsg, aParam.mHeight); + WriteParam(aMsg, aParam.mStartBitrate); + WriteParam(aMsg, aParam.mMaxBitrate); + WriteParam(aMsg, aParam.mMinBitrate); + WriteParam(aMsg, aParam.mMaxFramerate); + WriteParam(aMsg, aParam.mFrameDroppingOn); + WriteParam(aMsg, aParam.mKeyFrameInterval); + WriteParam(aMsg, aParam.mQPMax); + WriteParam(aMsg, aParam.mNumberOfSimulcastStreams); + for (uint32_t i = 0; i < aParam.mNumberOfSimulcastStreams; i++) { + WriteParam(aMsg, aParam.mSimulcastStream[i]); + } + WriteParam(aMsg, aParam.mMode); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + // NOTE: make sure this matches any versions supported + if (!ReadParam(aMsg, aIter, &(aResult->mGMPApiVersion)) || + aResult->mGMPApiVersion != kGMPVersion33) { + return false; + } + if (!ReadParam(aMsg, aIter, &(aResult->mCodecType))) { + return false; + } + + nsAutoCString plName; + if (!ReadParam(aMsg, aIter, &plName) || + plName.Length() > kGMPPayloadNameSize - 1) { + return false; + } + memcpy(aResult->mPLName, plName.get(), plName.Length()); + memset(aResult->mPLName + plName.Length(), 0, kGMPPayloadNameSize - plName.Length()); + + if (!ReadParam(aMsg, aIter, &(aResult->mPLType)) || + !ReadParam(aMsg, aIter, &(aResult->mWidth)) || + !ReadParam(aMsg, aIter, &(aResult->mHeight)) || + !ReadParam(aMsg, aIter, &(aResult->mStartBitrate)) || + !ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) || + !ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) || + !ReadParam(aMsg, aIter, &(aResult->mMaxFramerate)) || + !ReadParam(aMsg, aIter, &(aResult->mFrameDroppingOn)) || + !ReadParam(aMsg, aIter, &(aResult->mKeyFrameInterval))) { + return false; + } + + if (!ReadParam(aMsg, aIter, &(aResult->mQPMax)) || + !ReadParam(aMsg, aIter, &(aResult->mNumberOfSimulcastStreams))) { + return false; + } + + if (aResult->mNumberOfSimulcastStreams > kGMPMaxSimulcastStreams) { + return false; + } + + for (uint32_t i = 0; i < aResult->mNumberOfSimulcastStreams; i++) { + if (!ReadParam(aMsg, aIter, &(aResult->mSimulcastStream[i]))) { + return false; + } + } + + if (!ReadParam(aMsg, aIter, &(aResult->mMode))) { + return false; + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + const char* codecName = nullptr; + if (aParam.mCodecType == kGMPVideoCodecVP8) { + codecName = "VP8"; + } + aLog->append(StringPrintf(L"[%s, %u, %u]", + codecName, + aParam.mWidth, + aParam.mHeight)); + } +}; + +} // namespace IPC + +#endif // GMPMessageUtils_h_ diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp new file mode 100644 index 000000000..11dbc6b96 --- /dev/null +++ b/dom/media/gmp/GMPParent.cpp @@ -0,0 +1,927 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPParent.h" +#include "mozilla/Logging.h" +#include "nsComponentManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsIRunnable.h" +#include "nsIWritablePropertyBag2.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/SSE.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Unused.h" +#include "nsIObserverService.h" +#include "GMPTimerParent.h" +#include "runnable_utils.h" +#include "GMPContentParent.h" +#include "MediaPrefs.h" +#include "VideoUtils.h" + +using mozilla::ipc::GeckoChildProcessHost; + +#ifdef XP_WIN +#include "WMFDecoderModule.h" +#endif + +namespace mozilla { + +#undef LOG +#undef LOGD + +extern LogModule* GetGMPLog(); +#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__)) +#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPParent" + +namespace gmp { + +GMPParent::GMPParent() + : mState(GMPStateNotLoaded) + , mProcess(nullptr) + , mDeleteProcessOnlyOnUnload(false) + , mAbnormalShutdownInProgress(false) + , mIsBlockingDeletion(false) + , mCanDecrypt(false) + , mGMPContentChildCount(0) + , mAsyncShutdownRequired(false) + , mAsyncShutdownInProgress(false) + , mChildPid(0) + , mHoldingSelfRef(false) +{ + mPluginId = GeckoChildProcessHost::GetUniqueID(); + LOGD("GMPParent ctor id=%u", mPluginId); +} + +GMPParent::~GMPParent() +{ + LOGD("GMPParent dtor id=%u", mPluginId); + MOZ_ASSERT(!mProcess); +} + +nsresult +GMPParent::CloneFrom(const GMPParent* aOther) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory"); + + mService = aOther->mService; + mDirectory = aOther->mDirectory; + mName = aOther->mName; + mVersion = aOther->mVersion; + mDescription = aOther->mDescription; + mDisplayName = aOther->mDisplayName; +#ifdef XP_WIN + mLibs = aOther->mLibs; +#endif + for (const GMPCapability& cap : aOther->mCapabilities) { + mCapabilities.AppendElement(cap); + } + mAdapter = aOther->mAdapter; + return NS_OK; +} + +RefPtr<GenericPromise> +GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir) +{ + MOZ_ASSERT(aPluginDir); + MOZ_ASSERT(aService); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + mService = aService; + mDirectory = aPluginDir; + + // aPluginDir is <profile-dir>/<gmp-plugin-id>/<version> + // where <gmp-plugin-id> should be gmp-gmpopenh264 + nsCOMPtr<nsIFile> parent; + nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + nsAutoString parentLeafName; + rv = parent->GetLeafName(parentLeafName); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get()); + + MOZ_ASSERT(parentLeafName.Length() > 4); + mName = Substring(parentLeafName, 4); + + return ReadGMPMetaData(); +} + +void +GMPParent::Crash() +{ + if (mState != GMPStateNotLoaded) { + Unused << SendCrashPluginNow(); + } +} + +nsresult +GMPParent::LoadProcess() +{ + MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + MOZ_ASSERT(mState == GMPStateNotLoaded); + + nsAutoString path; + if (NS_FAILED(mDirectory->GetPath(path))) { + return NS_ERROR_FAILURE; + } + LOGD("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get()); + + if (!mProcess) { + mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get()); + if (!mProcess->Launch(30 * 1000)) { + LOGD("%s: Failed to launch new child process", __FUNCTION__); + mProcess->Delete(); + mProcess = nullptr; + return NS_ERROR_FAILURE; + } + + mChildPid = base::GetProcId(mProcess->GetChildProcessHandle()); + LOGD("%s: Launched new child process", __FUNCTION__); + + bool opened = Open(mProcess->GetChannel(), + base::GetProcId(mProcess->GetChildProcessHandle())); + if (!opened) { + LOGD("%s: Failed to open channel to new child process", __FUNCTION__); + mProcess->Delete(); + mProcess = nullptr; + return NS_ERROR_FAILURE; + } + LOGD("%s: Opened channel to new child process", __FUNCTION__); + + bool ok = SendSetNodeId(mNodeId); + if (!ok) { + LOGD("%s: Failed to send node id to child process", __FUNCTION__); + return NS_ERROR_FAILURE; + } + LOGD("%s: Sent node id to child process", __FUNCTION__); + +#ifdef XP_WIN + if (!mLibs.IsEmpty()) { + bool ok = SendPreloadLibs(mLibs); + if (!ok) { + LOGD("%s: Failed to send preload-libs to child process", __FUNCTION__); + return NS_ERROR_FAILURE; + } + LOGD("%s: Sent preload-libs ('%s') to child process", __FUNCTION__, mLibs.get()); + } +#endif + + // Intr call to block initialization on plugin load. + ok = CallStartPlugin(mAdapter); + if (!ok) { + LOGD("%s: Failed to send start to child process", __FUNCTION__); + return NS_ERROR_FAILURE; + } + LOGD("%s: Sent StartPlugin to child process", __FUNCTION__); + } + + mState = GMPStateLoaded; + + // Hold a self ref while the child process is alive. This ensures that + // during shutdown the GMPParent stays alive long enough to + // terminate the child process. + MOZ_ASSERT(!mHoldingSelfRef); + mHoldingSelfRef = true; + AddRef(); + + return NS_OK; +} + +// static +void +GMPParent::AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure) +{ + NS_WARNING("Timed out waiting for GMP async shutdown!"); + GMPParent* parent = reinterpret_cast<GMPParent*>(aClosure); + MOZ_ASSERT(parent->mService); + parent->mService->AsyncShutdownComplete(parent); +} + +nsresult +GMPParent::EnsureAsyncShutdownTimeoutSet() +{ + MOZ_ASSERT(mAsyncShutdownRequired); + if (mAsyncShutdownTimeout) { + return NS_OK; + } + + nsresult rv; + mAsyncShutdownTimeout = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set timer to abort waiting for plugin to shutdown if it takes + // too long. + rv = mAsyncShutdownTimeout->SetTarget(mGMPThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int32_t timeout = MediaPrefs::GMPAsyncShutdownTimeout(); + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + if (service) { + timeout = service->AsyncShutdownTimeoutMs(); + } + rv = mAsyncShutdownTimeout->InitWithFuncCallback( + &AbortWaitingForGMPAsyncShutdown, this, timeout, + nsITimer::TYPE_ONE_SHOT); + Unused << NS_WARN_IF(NS_FAILED(rv)); + return rv; +} + +bool +GMPParent::RecvPGMPContentChildDestroyed() +{ + --mGMPContentChildCount; + if (!IsUsed()) { + CloseIfUnused(); + } + return true; +} + +void +GMPParent::CloseIfUnused() +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + LOGD("%s: mAsyncShutdownRequired=%d", __FUNCTION__, mAsyncShutdownRequired); + + if ((mDeleteProcessOnlyOnUnload || + mState == GMPStateLoaded || + mState == GMPStateUnloading) && + !IsUsed()) { + // Ensure all timers are killed. + for (uint32_t i = mTimers.Length(); i > 0; i--) { + mTimers[i - 1]->Shutdown(); + } + + if (mAsyncShutdownRequired) { + if (!mAsyncShutdownInProgress) { + LOGD("%s: sending async shutdown notification", __FUNCTION__); + mAsyncShutdownInProgress = true; + if (!SendBeginAsyncShutdown()) { + AbortAsyncShutdown(); + } else if (NS_FAILED(EnsureAsyncShutdownTimeoutSet())) { + AbortAsyncShutdown(); + } + } + } else { + // No async-shutdown, kill async-shutdown timer started in CloseActive(). + AbortAsyncShutdown(); + // Any async shutdown must be complete. Shutdown GMPStorage. + for (size_t i = mStorage.Length(); i > 0; i--) { + mStorage[i - 1]->Shutdown(); + } + Shutdown(); + } + } +} + +void +GMPParent::AbortAsyncShutdown() +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + LOGD("%s", __FUNCTION__); + + if (mAsyncShutdownTimeout) { + mAsyncShutdownTimeout->Cancel(); + mAsyncShutdownTimeout = nullptr; + } + + if (!mAsyncShutdownRequired || !mAsyncShutdownInProgress) { + return; + } + + RefPtr<GMPParent> kungFuDeathGrip(this); + mService->AsyncShutdownComplete(this); + mAsyncShutdownRequired = false; + mAsyncShutdownInProgress = false; + CloseIfUnused(); +} + +void +GMPParent::CloseActive(bool aDieWhenUnloaded) +{ + LOGD("%s: state %d", __FUNCTION__, mState); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + if (aDieWhenUnloaded) { + mDeleteProcessOnlyOnUnload = true; // don't allow this to go back... + } + if (mState == GMPStateLoaded) { + mState = GMPStateUnloading; + } + if (mState != GMPStateNotLoaded && IsUsed()) { + if (!SendCloseActive()) { + AbortAsyncShutdown(); + } else if (IsUsed()) { + // We're expecting RecvPGMPContentChildDestroyed's -> Start async-shutdown timer now if needed. + if (mAsyncShutdownRequired && NS_FAILED(EnsureAsyncShutdownTimeoutSet())) { + AbortAsyncShutdown(); + } + } else { + // We're not expecting any RecvPGMPContentChildDestroyed + // -> Call CloseIfUnused() now, to run async shutdown if necessary. + // Note that CloseIfUnused() may have already been called from a prior + // RecvPGMPContentChildDestroyed(), however depending on the state at + // that time, it might not have proceeded with shutdown; And calling it + // again after shutdown is fine because after the first one we'll be in + // GMPStateNotLoaded. + CloseIfUnused(); + } + } +} + +void +GMPParent::MarkForDeletion() +{ + mDeleteProcessOnlyOnUnload = true; + mIsBlockingDeletion = true; +} + +bool +GMPParent::IsMarkedForDeletion() +{ + return mIsBlockingDeletion; +} + +void +GMPParent::Shutdown() +{ + LOGD("%s", __FUNCTION__); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + MOZ_ASSERT(!mAsyncShutdownTimeout, "Should have canceled shutdown timeout"); + + if (mAbnormalShutdownInProgress) { + return; + } + + MOZ_ASSERT(!IsUsed()); + if (mState == GMPStateNotLoaded || mState == GMPStateClosing) { + return; + } + + RefPtr<GMPParent> self(this); + DeleteProcess(); + + // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when + // Bug 1043671 is fixed + if (!mDeleteProcessOnlyOnUnload) { + // Destroy ourselves and rise from the fire to save memory + mService->ReAddOnGMPThread(self); + } // else we've been asked to die and stay dead + MOZ_ASSERT(mState == GMPStateNotLoaded); +} + +class NotifyGMPShutdownTask : public Runnable { +public: + explicit NotifyGMPShutdownTask(const nsAString& aNodeId) + : mNodeId(aNodeId) + { + } + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + if (obsService) { + obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get()); + } + return NS_OK; + } + nsString mNodeId; +}; + +void +GMPParent::ChildTerminated() +{ + RefPtr<GMPParent> self(this); + nsIThread* gmpThread = GMPThread(); + + if (!gmpThread) { + // Bug 1163239 - this can happen on shutdown. + // PluginTerminated removes the GMP from the GMPService. + // On shutdown we can have this case where it is already been + // removed so there is no harm in not trying to remove it again. + LOGD("%s::%s: GMPThread() returned nullptr.", __CLASS__, __FUNCTION__); + } else { + gmpThread->Dispatch(NewRunnableMethod<RefPtr<GMPParent>>( + mService, + &GeckoMediaPluginServiceParent::PluginTerminated, + self), + NS_DISPATCH_NORMAL); + } +} + +void +GMPParent::DeleteProcess() +{ + LOGD("%s", __FUNCTION__); + + if (mState != GMPStateClosing) { + // Don't Close() twice! + // Probably remove when bug 1043671 is resolved + mState = GMPStateClosing; + Close(); + } + mProcess->Delete(NewRunnableMethod(this, &GMPParent::ChildTerminated)); + LOGD("%s: Shut down process", __FUNCTION__); + mProcess = nullptr; + mState = GMPStateNotLoaded; + + NS_DispatchToMainThread( + new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)), + NS_DISPATCH_NORMAL); + + if (mHoldingSelfRef) { + Release(); + mHoldingSelfRef = false; + } +} + +GMPState +GMPParent::State() const +{ + return mState; +} + +// Not changing to use mService since we'll be removing it +nsIThread* +GMPParent::GMPThread() +{ + if (!mGMPThread) { + nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + if (!mps) { + return nullptr; + } + // Not really safe if we just grab to the mGMPThread, as we don't know + // what thread we're running on and other threads may be trying to + // access this without locks! However, debug only, and primary failure + // mode outside of compiler-helped TSAN is a leak. But better would be + // to use swap() under a lock. + mps->GetThread(getter_AddRefs(mGMPThread)); + MOZ_ASSERT(mGMPThread); + } + + return mGMPThread; +} + +/* static */ +bool +GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags) +{ + for (const nsCString& tag : aTags) { + if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) { + return false; + } + } + return true; +} + +/* static */ +bool +GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities, + const nsCString& aAPI, + const nsCString& aTag) +{ + for (const GMPCapability& capabilities : aCapabilities) { + if (!capabilities.mAPIName.Equals(aAPI)) { + continue; + } + for (const nsCString& tag : capabilities.mAPITags) { + if (tag.Equals(aTag)) { +#ifdef XP_WIN + // Clearkey on Windows advertises that it can decode in its GMP info + // file, but uses Windows Media Foundation to decode. That's not present + // on Windows N and KN variants without certain services packs. + if (tag.Equals(kEMEKeySystemClearkey)) { + if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) { + if (!WMFDecoderModule::HasH264()) { + continue; + } + } else if (capabilities.mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER)) { + if (!WMFDecoderModule::HasAAC()) { + continue; + } + } + } +#endif + return true; + } + } + } + return false; +} + +bool +GMPParent::EnsureProcessLoaded() +{ + if (mState == GMPStateLoaded) { + return true; + } + if (mState == GMPStateClosing || + mState == GMPStateUnloading) { + return false; + } + + nsresult rv = LoadProcess(); + + return NS_SUCCEEDED(rv); +} + +void +GMPParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD("%s: (%d)", __FUNCTION__, (int)aWhy); + // warn us off trying to close again + mState = GMPStateClosing; + mAbnormalShutdownInProgress = true; + CloseActive(false); + + // Normal Shutdown() will delete the process on unwind. + if (AbnormalShutdown == aWhy) { + RefPtr<GMPParent> self(this); + if (mAsyncShutdownRequired) { + mService->AsyncShutdownComplete(this); + mAsyncShutdownRequired = false; + } + // Must not call Close() again in DeleteProcess(), as we'll recurse + // infinitely if we do. + MOZ_ASSERT(mState == GMPStateClosing); + DeleteProcess(); + // Note: final destruction will be Dispatched to ourself + mService->ReAddOnGMPThread(self); + } +} + +PGMPStorageParent* +GMPParent::AllocPGMPStorageParent() +{ + GMPStorageParent* p = new GMPStorageParent(mNodeId, this); + mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent. + return p; +} + +bool +GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor) +{ + GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor); + p->Shutdown(); + mStorage.RemoveElement(p); + return true; +} + +bool +GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor) +{ + GMPStorageParent* p = (GMPStorageParent*)aActor; + if (NS_WARN_IF(NS_FAILED(p->Init()))) { + return false; + } + return true; +} + +bool +GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor) +{ + return true; +} + +PGMPTimerParent* +GMPParent::AllocPGMPTimerParent() +{ + GMPTimerParent* p = new GMPTimerParent(GMPThread()); + mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown. + return p; +} + +bool +GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor) +{ + GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor); + p->Shutdown(); + mTimers.RemoveElement(p); + return true; +} + +bool +ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOutValue) +{ + if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) { + return false; + } + aOutValue = aParser.Get(aKey); + return true; +} + +RefPtr<GenericPromise> +GMPParent::ReadGMPMetaData() +{ + MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); + MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!"); + + nsCOMPtr<nsIFile> infoFile; + nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile)); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info")); + + if (FileExists(infoFile)) { + return ReadGMPInfoFile(infoFile); + } + + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); +} + +RefPtr<GenericPromise> +GMPParent::ReadGMPInfoFile(nsIFile* aFile) +{ + GMPInfoFileParser parser; + if (!parser.Init(aFile)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + nsAutoCString apis; + if (!ReadInfoField(parser, NS_LITERAL_CSTRING("name"), mDisplayName) || + !ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) || + !ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) || + !ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + +#ifdef XP_WIN + // "Libraries" field is optional. + ReadInfoField(parser, NS_LITERAL_CSTRING("libraries"), mLibs); +#endif + + nsTArray<nsCString> apiTokens; + SplitAt(", ", apis, apiTokens); + for (nsCString api : apiTokens) { + int32_t tagsStart = api.FindChar('['); + if (tagsStart == 0) { + // Not allowed to be the first character. + // API name must be at least one character. + continue; + } + + GMPCapability cap; + if (tagsStart == -1) { + // No tags. + cap.mAPIName.Assign(api); + } else { + auto tagsEnd = api.FindChar(']'); + if (tagsEnd == -1 || tagsEnd < tagsStart) { + // Invalid syntax, skip whole capability. + continue; + } + + cap.mAPIName.Assign(Substring(api, 0, tagsStart)); + + if ((tagsEnd - tagsStart) > 1) { + const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1)); + nsTArray<nsCString> tagTokens; + SplitAt(":", ts, tagTokens); + for (nsCString tag : tagTokens) { + cap.mAPITags.AppendElement(tag); + } + } + } + + // We support the current GMPDecryptor version, and the previous. + // We Adapt the previous to the current in the GMPContentChild. + if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) { + cap.mAPIName.AssignLiteral(GMP_API_DECRYPTOR); + } + + if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) { + mCanDecrypt = true; + } + + mCapabilities.AppendElement(Move(cap)); + } + + if (mCapabilities.IsEmpty()) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + return GenericPromise::CreateAndResolve(true, __func__); +} + +RefPtr<GenericPromise> +GMPParent::ReadChromiumManifestFile(nsIFile* aFile) +{ + nsAutoCString json; + if (!ReadIntoString(aFile, json, 5 * 1024)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // DOM JSON parsing needs to run on the main thread. + return InvokeAsync(AbstractThread::MainThread(), this, __func__, + &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json)); +} + +RefPtr<GenericPromise> +GMPParent::ParseChromiumManifest(nsString aJSON) +{ + // TODO: Remove This. + MOZ_ASSERT_UNREACHABLE("don't call me if EME isn't enabled"); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); +} + +bool +GMPParent::CanBeSharedCrossNodeIds() const +{ + return !mAsyncShutdownInProgress && + mNodeId.IsEmpty() && + // XXX bug 1159300 hack -- maybe remove after openh264 1.4 + // We don't want to use CDM decoders for non-encrypted playback + // just yet; especially not for WebRTC. Don't allow CDMs to be used + // without a node ID. + !mCanDecrypt; +} + +bool +GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const +{ + return !mAsyncShutdownInProgress && mNodeId == aNodeId; +} + +void +GMPParent::SetNodeId(const nsACString& aNodeId) +{ + MOZ_ASSERT(!aNodeId.IsEmpty()); + mNodeId = aNodeId; +} + +const nsCString& +GMPParent::GetDisplayName() const +{ + return mDisplayName; +} + +const nsCString& +GMPParent::GetVersion() const +{ + return mVersion; +} + +uint32_t +GMPParent::GetPluginId() const +{ + return mPluginId; +} + +bool +GMPParent::RecvAsyncShutdownRequired() +{ + LOGD("%s", __FUNCTION__); + if (mAsyncShutdownRequired) { + NS_WARNING("Received AsyncShutdownRequired message more than once!"); + return true; + } + mAsyncShutdownRequired = true; + mService->AsyncShutdownNeeded(this); + return true; +} + +bool +GMPParent::RecvAsyncShutdownComplete() +{ + LOGD("%s", __FUNCTION__); + + MOZ_ASSERT(mAsyncShutdownRequired); + AbortAsyncShutdown(); + return true; +} + +class RunCreateContentParentCallbacks : public Runnable +{ +public: + explicit RunCreateContentParentCallbacks(GMPContentParent* aGMPContentParent) + : mGMPContentParent(aGMPContentParent) + { + } + + void TakeCallbacks(nsTArray<UniquePtr<GetGMPContentParentCallback>>& aCallbacks) + { + mCallbacks.SwapElements(aCallbacks); + } + + NS_IMETHOD + Run() override + { + for (uint32_t i = 0, length = mCallbacks.Length(); i < length; ++i) { + mCallbacks[i]->Done(mGMPContentParent); + } + return NS_OK; + } + +private: + RefPtr<GMPContentParent> mGMPContentParent; + nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks; +}; + +PGMPContentParent* +GMPParent::AllocPGMPContentParent(Transport* aTransport, ProcessId aOtherPid) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + MOZ_ASSERT(!mGMPContentParent); + + mGMPContentParent = new GMPContentParent(this); + mGMPContentParent->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), + ipc::ParentSide); + + RefPtr<RunCreateContentParentCallbacks> runCallbacks = + new RunCreateContentParentCallbacks(mGMPContentParent); + runCallbacks->TakeCallbacks(mCallbacks); + NS_DispatchToCurrentThread(runCallbacks); + MOZ_ASSERT(mCallbacks.IsEmpty()); + + return mGMPContentParent; +} + +bool +GMPParent::GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback) +{ + LOGD("%s %p", __FUNCTION__, this); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + if (mGMPContentParent) { + aCallback->Done(mGMPContentParent); + } else { + mCallbacks.AppendElement(Move(aCallback)); + // If we don't have a GMPContentParent and we try to get one for the first + // time (mCallbacks.Length() == 1) then call PGMPContent::Open. If more + // calls to GetGMPContentParent happen before mGMPContentParent has been + // set then we should just store them, so that they get called when we set + // mGMPContentParent as a result of the PGMPContent::Open call. + if (mCallbacks.Length() == 1) { + if (!EnsureProcessLoaded() || !PGMPContent::Open(this)) { + return false; + } + // We want to increment this as soon as possible, to avoid that we'd try + // to shut down the GMP process while we're still trying to get a + // PGMPContentParent actor. + ++mGMPContentChildCount; + } + } + return true; +} + +already_AddRefed<GMPContentParent> +GMPParent::ForgetGMPContentParent() +{ + MOZ_ASSERT(mCallbacks.IsEmpty()); + return Move(mGMPContentParent.forget()); +} + +bool +GMPParent::EnsureProcessLoaded(base::ProcessId* aID) +{ + if (!EnsureProcessLoaded()) { + return false; + } + *aID = OtherPid(); + return true; +} + +bool +GMPParent::Bridge(GMPServiceParent* aGMPServiceParent) +{ + if (NS_FAILED(PGMPContent::Bridge(aGMPServiceParent, this))) { + return false; + } + ++mGMPContentChildCount; + return true; +} + +nsString +GMPParent::GetPluginBaseName() const +{ + return NS_LITERAL_STRING("gmp-") + mName; +} + +} // namespace gmp +} // namespace mozilla + +#undef LOG +#undef LOGD diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h new file mode 100644 index 000000000..dacd6feeb --- /dev/null +++ b/dom/media/gmp/GMPParent.h @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPParent_h_ +#define GMPParent_h_ + +#include "GMPProcessParent.h" +#include "GMPServiceParent.h" +#include "GMPAudioDecoderParent.h" +#include "GMPDecryptorParent.h" +#include "GMPVideoDecoderParent.h" +#include "GMPVideoEncoderParent.h" +#include "GMPTimerParent.h" +#include "GMPStorageParent.h" +#include "mozilla/gmp/PGMPParent.h" +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsISupports.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsIFile.h" +#include "mozilla/MozPromise.h" + +class nsIThread; + +namespace mozilla { +namespace gmp { + +class GMPCapability +{ +public: + explicit GMPCapability() {} + GMPCapability(GMPCapability&& aOther) + : mAPIName(Move(aOther.mAPIName)) + , mAPITags(Move(aOther.mAPITags)) + { + } + explicit GMPCapability(const nsCString& aAPIName) + : mAPIName(aAPIName) + {} + explicit GMPCapability(const GMPCapability& aOther) = default; + nsCString mAPIName; + nsTArray<nsCString> mAPITags; + + static bool Supports(const nsTArray<GMPCapability>& aCapabilities, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags); + + static bool Supports(const nsTArray<GMPCapability>& aCapabilities, + const nsCString& aAPI, + const nsCString& aTag); +}; + +enum GMPState { + GMPStateNotLoaded, + GMPStateLoaded, + GMPStateUnloading, + GMPStateClosing +}; + +class GMPContentParent; + +class GetGMPContentParentCallback +{ +public: + GetGMPContentParentCallback() + { + MOZ_COUNT_CTOR(GetGMPContentParentCallback); + }; + virtual ~GetGMPContentParentCallback() + { + MOZ_COUNT_DTOR(GetGMPContentParentCallback); + }; + virtual void Done(GMPContentParent* aGMPContentParent) = 0; +}; + +class GMPParent final : public PGMPParent +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPParent) + + GMPParent(); + + RefPtr<GenericPromise> Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir); + nsresult CloneFrom(const GMPParent* aOther); + + void Crash(); + + nsresult LoadProcess(); + + // Called internally to close this if we don't need it + void CloseIfUnused(); + + // Notify all active de/encoders that we are closing, either because of + // normal shutdown or unexpected shutdown/crash. + void CloseActive(bool aDieWhenUnloaded); + + // Tell the plugin to die after shutdown. + void MarkForDeletion(); + bool IsMarkedForDeletion(); + + // Called by the GMPService to forcibly close active de/encoders at shutdown + void Shutdown(); + + // This must not be called while we're in the middle of abnormal ActorDestroy + void DeleteProcess(); + + GMPState State() const; + nsIThread* GMPThread(); + + // A GMP can either be a single instance shared across all NodeIds (like + // in the OpenH264 case), or we can require a new plugin instance for every + // NodeIds running the plugin (as in the EME plugin case). + // + // A NodeId is a hash of the ($urlBarOrigin, $ownerDocOrigin) pair. + // + // Plugins are associated with an NodeIds by calling SetNodeId() before + // loading. + // + // If a plugin has no NodeId specified and it is loaded, it is assumed to + // be shared across NodeIds. + + // Specifies that a GMP can only work with the specified NodeIds. + void SetNodeId(const nsACString& aNodeId); + const nsACString& GetNodeId() const { return mNodeId; } + + const nsCString& GetDisplayName() const; + const nsCString& GetVersion() const; + uint32_t GetPluginId() const; + nsString GetPluginBaseName() const; + + // Returns true if a plugin can be or is being used across multiple NodeIds. + bool CanBeSharedCrossNodeIds() const; + + // A GMP can be used from a NodeId if it's already been set to work with + // that NodeId, or if it's not been set to work with any NodeId and has + // not yet been loaded (i.e. it's not shared across NodeIds). + bool CanBeUsedFrom(const nsACString& aNodeId) const; + + already_AddRefed<nsIFile> GetDirectory() { + return nsCOMPtr<nsIFile>(mDirectory).forget(); + } + + void AbortAsyncShutdown(); + + // Called when the child process has died. + void ChildTerminated(); + + bool GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback); + already_AddRefed<GMPContentParent> ForgetGMPContentParent(); + + bool EnsureProcessLoaded(base::ProcessId* aID); + + bool Bridge(GMPServiceParent* aGMPServiceParent); + + const nsTArray<GMPCapability>& GetCapabilities() const { return mCapabilities; } + +private: + ~GMPParent(); + + RefPtr<GeckoMediaPluginServiceParent> mService; + bool EnsureProcessLoaded(); + RefPtr<GenericPromise> ReadGMPMetaData(); + RefPtr<GenericPromise> ReadGMPInfoFile(nsIFile* aFile); + RefPtr<GenericPromise> ParseChromiumManifest(nsString aJSON); // Main thread. + RefPtr<GenericPromise> ReadChromiumManifestFile(nsIFile* aFile); // GMP thread. + void ActorDestroy(ActorDestroyReason aWhy) override; + + bool RecvPGMPStorageConstructor(PGMPStorageParent* actor) override; + PGMPStorageParent* AllocPGMPStorageParent() override; + bool DeallocPGMPStorageParent(PGMPStorageParent* aActor) override; + + PGMPContentParent* AllocPGMPContentParent(Transport* aTransport, + ProcessId aOtherPid) override; + + bool RecvPGMPTimerConstructor(PGMPTimerParent* actor) override; + PGMPTimerParent* AllocPGMPTimerParent() override; + bool DeallocPGMPTimerParent(PGMPTimerParent* aActor) override; + + bool RecvAsyncShutdownComplete() override; + bool RecvAsyncShutdownRequired() override; + + bool RecvPGMPContentChildDestroyed() override; + bool IsUsed() + { + return mGMPContentChildCount > 0; + } + + + static void AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure); + nsresult EnsureAsyncShutdownTimeoutSet(); + + GMPState mState; + nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk + nsString mName; // base name of plugin on disk, UTF-16 because used for paths + nsCString mDisplayName; // name of plugin displayed to users + nsCString mDescription; // description of plugin for display to users + nsCString mVersion; +#ifdef XP_WIN + nsCString mLibs; +#endif + nsString mAdapter; + uint32_t mPluginId; + nsTArray<GMPCapability> mCapabilities; + GMPProcessParent* mProcess; + bool mDeleteProcessOnlyOnUnload; + bool mAbnormalShutdownInProgress; + bool mIsBlockingDeletion; + + bool mCanDecrypt; + + nsTArray<RefPtr<GMPTimerParent>> mTimers; + nsTArray<RefPtr<GMPStorageParent>> mStorage; + nsCOMPtr<nsIThread> mGMPThread; + nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only. + // NodeId the plugin is assigned to, or empty if the the plugin is not + // assigned to a NodeId. + nsCString mNodeId; + // This is used for GMP content in the parent, there may be more of these in + // the content processes. + RefPtr<GMPContentParent> mGMPContentParent; + nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks; + uint32_t mGMPContentChildCount; + + bool mAsyncShutdownRequired; + bool mAsyncShutdownInProgress; + + int mChildPid; + + // We hold a self reference to ourself while the child process is alive. + // This ensures that if the GMPService tries to shut us down and drops + // its reference to us, we stay alive long enough for the child process + // to terminate gracefully. + bool mHoldingSelfRef; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPParent_h_ diff --git a/dom/media/gmp/GMPPlatform.cpp b/dom/media/gmp/GMPPlatform.cpp new file mode 100644 index 000000000..71fa03468 --- /dev/null +++ b/dom/media/gmp/GMPPlatform.cpp @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPPlatform.h" +#include "GMPStorageChild.h" +#include "GMPTimerChild.h" +#include "mozilla/Monitor.h" +#include "GMPChild.h" +#include <ctime> + +namespace mozilla { +namespace gmp { + +static MessageLoop* sMainLoop = nullptr; +static GMPChild* sChild = nullptr; + +static bool +IsOnChildMainThread() +{ + return sMainLoop && sMainLoop == MessageLoop::current(); +} + +// We just need a refcounted wrapper for GMPTask objects. +class GMPRunnable final +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRunnable) + + explicit GMPRunnable(GMPTask* aTask) + : mTask(aTask) + { + MOZ_ASSERT(mTask); + } + + void Run() + { + mTask->Run(); + mTask->Destroy(); + mTask = nullptr; + } + +private: + ~GMPRunnable() + { + } + + GMPTask* mTask; +}; + +class GMPSyncRunnable final +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPSyncRunnable) + + GMPSyncRunnable(GMPTask* aTask, MessageLoop* aMessageLoop) + : mDone(false) + , mTask(aTask) + , mMessageLoop(aMessageLoop) + , mMonitor("GMPSyncRunnable") + { + MOZ_ASSERT(mTask); + MOZ_ASSERT(mMessageLoop); + } + + void Post() + { + // We assert here for two reasons. + // 1) Nobody should be blocking the main thread. + // 2) This prevents deadlocks when doing sync calls to main which if the + // main thread tries to do a sync call back to the calling thread. + MOZ_ASSERT(!IsOnChildMainThread()); + + mMessageLoop->PostTask(NewRunnableMethod(this, &GMPSyncRunnable::Run)); + MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } + } + + void Run() + { + mTask->Run(); + mTask->Destroy(); + mTask = nullptr; + MonitorAutoLock lock(mMonitor); + mDone = true; + lock.Notify(); + } + +private: + ~GMPSyncRunnable() + { + } + + bool mDone; + GMPTask* mTask; + MessageLoop* mMessageLoop; + Monitor mMonitor; +}; + +GMPErr +CreateThread(GMPThread** aThread) +{ + if (!aThread) { + return GMPGenericErr; + } + + *aThread = new GMPThreadImpl(); + + return GMPNoErr; +} + +GMPErr +RunOnMainThread(GMPTask* aTask) +{ + if (!aTask || !sMainLoop) { + return GMPGenericErr; + } + + RefPtr<GMPRunnable> r = new GMPRunnable(aTask); + sMainLoop->PostTask(NewRunnableMethod(r, &GMPRunnable::Run)); + + return GMPNoErr; +} + +GMPErr +SyncRunOnMainThread(GMPTask* aTask) +{ + if (!aTask || !sMainLoop || IsOnChildMainThread()) { + return GMPGenericErr; + } + + RefPtr<GMPSyncRunnable> r = new GMPSyncRunnable(aTask, sMainLoop); + + r->Post(); + + return GMPNoErr; +} + +GMPErr +CreateMutex(GMPMutex** aMutex) +{ + if (!aMutex) { + return GMPGenericErr; + } + + *aMutex = new GMPMutexImpl(); + + return GMPNoErr; +} + +GMPErr +CreateRecord(const char* aRecordName, + uint32_t aRecordNameSize, + GMPRecord** aOutRecord, + GMPRecordClient* aClient) +{ + if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE || + aRecordNameSize == 0) { + NS_WARNING("GMP tried to CreateRecord with too long or 0 record name"); + return GMPGenericErr; + } + GMPStorageChild* storage = sChild->GetGMPStorage(); + if (!storage) { + return GMPGenericErr; + } + MOZ_ASSERT(storage); + return storage->CreateRecord(nsDependentCString(aRecordName, aRecordNameSize), + aOutRecord, + aClient); +} + +GMPErr +SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) +{ + if (!aTask || !sMainLoop || !IsOnChildMainThread()) { + return GMPGenericErr; + } + GMPTimerChild* timers = sChild->GetGMPTimers(); + NS_ENSURE_TRUE(timers, GMPGenericErr); + return timers->SetTimer(aTask, aTimeoutMS); +} + +GMPErr +GetClock(GMPTimestamp* aOutTime) +{ + *aOutTime = time(0) * 1000; + return GMPNoErr; +} + +GMPErr +CreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg) +{ + if (!aRecvIteratorFunc) { + return GMPInvalidArgErr; + } + GMPStorageChild* storage = sChild->GetGMPStorage(); + if (!storage) { + return GMPGenericErr; + } + MOZ_ASSERT(storage); + return storage->EnumerateRecords(aRecvIteratorFunc, aUserArg); +} + +void +InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild) +{ + if (!sMainLoop) { + sMainLoop = MessageLoop::current(); + } + if (!sChild) { + sChild = aChild; + } + + aPlatformAPI.version = 0; + aPlatformAPI.createthread = &CreateThread; + aPlatformAPI.runonmainthread = &RunOnMainThread; + aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread; + aPlatformAPI.createmutex = &CreateMutex; + aPlatformAPI.createrecord = &CreateRecord; + aPlatformAPI.settimer = &SetTimerOnMainThread; + aPlatformAPI.getcurrenttime = &GetClock; + aPlatformAPI.getrecordenumerator = &CreateRecordIterator; +} + +GMPThreadImpl::GMPThreadImpl() +: mMutex("GMPThreadImpl"), + mThread("GMPThread") +{ + MOZ_COUNT_CTOR(GMPThread); +} + +GMPThreadImpl::~GMPThreadImpl() +{ + MOZ_COUNT_DTOR(GMPThread); +} + +void +GMPThreadImpl::Post(GMPTask* aTask) +{ + MutexAutoLock lock(mMutex); + + if (!mThread.IsRunning()) { + bool started = mThread.Start(); + if (!started) { + NS_WARNING("Unable to start GMPThread!"); + return; + } + } + + RefPtr<GMPRunnable> r = new GMPRunnable(aTask); + mThread.message_loop()->PostTask(NewRunnableMethod(r.get(), &GMPRunnable::Run)); +} + +void +GMPThreadImpl::Join() +{ + { + MutexAutoLock lock(mMutex); + if (mThread.IsRunning()) { + mThread.Stop(); + } + } + delete this; +} + +GMPMutexImpl::GMPMutexImpl() +: mMonitor("gmp-mutex") +{ + MOZ_COUNT_CTOR(GMPMutexImpl); +} + +GMPMutexImpl::~GMPMutexImpl() +{ + MOZ_COUNT_DTOR(GMPMutexImpl); +} + +void +GMPMutexImpl::Destroy() +{ + delete this; +} + +void +GMPMutexImpl::Acquire() +{ + mMonitor.Enter(); +} + +void +GMPMutexImpl::Release() +{ + mMonitor.Exit(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPPlatform.h b/dom/media/gmp/GMPPlatform.h new file mode 100644 index 000000000..79079c327 --- /dev/null +++ b/dom/media/gmp/GMPPlatform.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPPlatform_h_ +#define GMPPlatform_h_ + +#include "mozilla/Mutex.h" +#include "gmp-platform.h" +#include "base/thread.h" +#include "mozilla/ReentrantMonitor.h" + +namespace mozilla { +namespace gmp { + +class GMPChild; + +void InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild); + +GMPErr RunOnMainThread(GMPTask* aTask); + +class GMPThreadImpl : public GMPThread +{ +public: + GMPThreadImpl(); + virtual ~GMPThreadImpl(); + + // GMPThread + void Post(GMPTask* aTask) override; + void Join() override; + +private: + Mutex mMutex; + base::Thread mThread; +}; + +class GMPMutexImpl : public GMPMutex +{ +public: + GMPMutexImpl(); + virtual ~GMPMutexImpl(); + + // GMPMutex + void Acquire() override; + void Release() override; + void Destroy() override; + +private: + ReentrantMonitor mMonitor; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPPlatform_h_ diff --git a/dom/media/gmp/GMPProcessChild.cpp b/dom/media/gmp/GMPProcessChild.cpp new file mode 100644 index 000000000..294cabb42 --- /dev/null +++ b/dom/media/gmp/GMPProcessChild.cpp @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPProcessChild.h" + +#include "base/command_line.h" +#include "base/string_util.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/BackgroundHangMonitor.h" + +using mozilla::ipc::IOThreadChild; + +namespace mozilla { +namespace gmp { + +GMPProcessChild::GMPProcessChild(ProcessId aParentPid) +: ProcessChild(aParentPid) +{ +} + +GMPProcessChild::~GMPProcessChild() +{ +} + +bool +GMPProcessChild::Init() +{ + nsAutoString pluginFilename; + nsAutoString voucherFilename; + +#if defined(OS_POSIX) + // NB: need to be very careful in ensuring that the first arg + // (after the binary name) here is indeed the plugin module path. + // Keep in sync with dom/plugins/PluginModuleParent. + std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv(); + MOZ_ASSERT(values.size() >= 3, "not enough args"); + pluginFilename = NS_ConvertUTF8toUTF16(nsDependentCString(values[1].c_str())); + voucherFilename = NS_ConvertUTF8toUTF16(nsDependentCString(values[2].c_str())); +#elif defined(OS_WIN) + std::vector<std::wstring> values = CommandLine::ForCurrentProcess()->GetLooseValues(); + MOZ_ASSERT(values.size() >= 2, "not enough loose args"); + pluginFilename = nsDependentString(values[0].c_str()); + voucherFilename = nsDependentString(values[1].c_str()); +#else +#error Not implemented +#endif + + BackgroundHangMonitor::Startup(); + + return mPlugin.Init(pluginFilename, + voucherFilename, + ParentPid(), + IOThreadChild::message_loop(), + IOThreadChild::channel()); +} + +void +GMPProcessChild::CleanUp() +{ + BackgroundHangMonitor::Shutdown(); +} + +GMPLoader* GMPProcessChild::mLoader = nullptr; + +/* static */ +void +GMPProcessChild::SetGMPLoader(GMPLoader* aLoader) +{ + mLoader = aLoader; +} + +/* static */ +GMPLoader* +GMPProcessChild::GetGMPLoader() +{ + return mLoader; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPProcessChild.h b/dom/media/gmp/GMPProcessChild.h new file mode 100644 index 000000000..1a8df7653 --- /dev/null +++ b/dom/media/gmp/GMPProcessChild.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPProcessChild_h_ +#define GMPProcessChild_h_ + +#include "mozilla/ipc/ProcessChild.h" +#include "GMPChild.h" + +namespace mozilla { +namespace gmp { + +class GMPLoader; + +class GMPProcessChild final : public mozilla::ipc::ProcessChild { +protected: + typedef mozilla::ipc::ProcessChild ProcessChild; + +public: + explicit GMPProcessChild(ProcessId aParentPid); + ~GMPProcessChild(); + + bool Init() override; + void CleanUp() override; + + // Set/get the GMPLoader singleton for this child process. + // Note: The GMPLoader is not deleted by this object, the caller of + // SetGMPLoader() must manage the GMPLoader's lifecycle. + static void SetGMPLoader(GMPLoader* aHost); + static GMPLoader* GetGMPLoader(); + +private: + GMPChild mPlugin; + static GMPLoader* mLoader; + DISALLOW_COPY_AND_ASSIGN(GMPProcessChild); +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPProcessChild_h_ diff --git a/dom/media/gmp/GMPProcessParent.cpp b/dom/media/gmp/GMPProcessParent.cpp new file mode 100644 index 000000000..ef58175e8 --- /dev/null +++ b/dom/media/gmp/GMPProcessParent.cpp @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPProcessParent.h" +#include "GMPUtils.h" +#include "nsIFile.h" +#include "nsIRunnable.h" + +#include "base/string_util.h" +#include "base/process_util.h" + +#include <string> + +using std::vector; +using std::string; + +using mozilla::gmp::GMPProcessParent; +using mozilla::ipc::GeckoChildProcessHost; +using base::ProcessArchitecture; + +namespace mozilla { + +extern LogModule* GetGMPLog(); +#define GMP_LOG(msg, ...) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace gmp { + +GMPProcessParent::GMPProcessParent(const std::string& aGMPPath) +: GeckoChildProcessHost(GeckoProcessType_GMPlugin), + mGMPPath(aGMPPath) +{ + MOZ_COUNT_CTOR(GMPProcessParent); +} + +GMPProcessParent::~GMPProcessParent() +{ + MOZ_COUNT_DTOR(GMPProcessParent); +} + +bool +GMPProcessParent::Launch(int32_t aTimeoutMs) +{ + nsCOMPtr<nsIFile> path; + if (!GetEMEVoucherPath(getter_AddRefs(path))) { + NS_WARNING("GMPProcessParent can't get EME voucher path!"); + return false; + } + nsAutoCString voucherPath; + path->GetNativePath(voucherPath); + + vector<string> args; + + args.push_back(mGMPPath); + + args.push_back(string(voucherPath.BeginReading(), voucherPath.EndReading())); + + return SyncLaunch(args, aTimeoutMs, base::GetCurrentProcessArchitecture()); +} + +void +GMPProcessParent::Delete(nsCOMPtr<nsIRunnable> aCallback) +{ + mDeletedCallback = aCallback; + XRE_GetIOMessageLoop()->PostTask(NewNonOwningRunnableMethod(this, &GMPProcessParent::DoDelete)); +} + +void +GMPProcessParent::DoDelete() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + Join(); + + if (mDeletedCallback) { + mDeletedCallback->Run(); + } + + delete this; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPProcessParent.h b/dom/media/gmp/GMPProcessParent.h new file mode 100644 index 000000000..b94f203cf --- /dev/null +++ b/dom/media/gmp/GMPProcessParent.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPProcessParent_h +#define GMPProcessParent_h 1 + +#include "mozilla/Attributes.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/thread.h" +#include "chrome/common/child_process_host.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" + +class nsIRunnable; + +namespace mozilla { +namespace gmp { + +class GMPProcessParent final : public mozilla::ipc::GeckoChildProcessHost +{ +public: + explicit GMPProcessParent(const std::string& aGMPPath); + ~GMPProcessParent(); + + // Synchronously launch the plugin process. If the process fails to launch + // after timeoutMs, this method will return false. + bool Launch(int32_t aTimeoutMs); + + void Delete(nsCOMPtr<nsIRunnable> aCallback = nullptr); + + bool CanShutdown() override { return true; } + const std::string& GetPluginFilePath() { return mGMPPath; } + + using mozilla::ipc::GeckoChildProcessHost::GetChannel; + using mozilla::ipc::GeckoChildProcessHost::GetChildProcessHandle; + +private: + void DoDelete(); + + std::string mGMPPath; + nsCOMPtr<nsIRunnable> mDeletedCallback; + + DISALLOW_COPY_AND_ASSIGN(GMPProcessParent); +}; + +} // namespace gmp +} // namespace mozilla + +#endif // ifndef GMPProcessParent_h diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp new file mode 100644 index 000000000..1901210da --- /dev/null +++ b/dom/media/gmp/GMPService.cpp @@ -0,0 +1,557 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPService.h" +#include "GMPServiceParent.h" +#include "GMPServiceChild.h" +#include "GMPContentParent.h" +#include "prio.h" +#include "mozilla/Logging.h" +#include "GMPParent.h" +#include "GMPVideoDecoderParent.h" +#include "nsIObserverService.h" +#include "GeckoChildProcessHost.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/SyncRunnable.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/Services.h" +#include "nsNativeCharsetUtils.h" +#include "nsIConsoleService.h" +#include "mozilla/Unused.h" +#include "GMPDecryptorParent.h" +#include "GMPAudioDecoderParent.h" +#include "nsComponentManagerUtils.h" +#include "runnable_utils.h" +#include "VideoUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsHashKeys.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "nsThreadUtils.h" + +#include "mozilla/dom/PluginCrashedEvent.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/Attributes.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +LogModule* +GetGMPLog() +{ + static LazyLogModule sLog("GMP"); + return sLog; +} + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPService" + +namespace gmp { + +static StaticRefPtr<GeckoMediaPluginService> sSingletonService; + +class GMPServiceCreateHelper final : public mozilla::Runnable +{ + RefPtr<GeckoMediaPluginService> mService; + +public: + static already_AddRefed<GeckoMediaPluginService> + GetOrCreate() + { + RefPtr<GeckoMediaPluginService> service; + + if (NS_IsMainThread()) { + service = GetOrCreateOnMainThread(); + } else { + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + + RefPtr<GMPServiceCreateHelper> createHelper = new GMPServiceCreateHelper(); + + mozilla::SyncRunnable::DispatchToThread(mainThread, createHelper, true); + + service = createHelper->mService.forget(); + } + + return service.forget(); + } + +private: + GMPServiceCreateHelper() + { + } + + ~GMPServiceCreateHelper() + { + MOZ_ASSERT(!mService); + } + + static already_AddRefed<GeckoMediaPluginService> + GetOrCreateOnMainThread() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingletonService) { + if (XRE_IsParentProcess()) { + RefPtr<GeckoMediaPluginServiceParent> service = + new GeckoMediaPluginServiceParent(); + service->Init(); + sSingletonService = service; + } else { + RefPtr<GeckoMediaPluginServiceChild> service = + new GeckoMediaPluginServiceChild(); + service->Init(); + sSingletonService = service; + } + + ClearOnShutdown(&sSingletonService); + } + + RefPtr<GeckoMediaPluginService> service = sSingletonService.get(); + return service.forget(); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + mService = GetOrCreateOnMainThread(); + return NS_OK; + } +}; + +already_AddRefed<GeckoMediaPluginService> +GeckoMediaPluginService::GetGeckoMediaPluginService() +{ + return GMPServiceCreateHelper::GetOrCreate(); +} + +NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, nsIObserver) + +GeckoMediaPluginService::GeckoMediaPluginService() + : mMutex("GeckoMediaPluginService::mMutex") + , mGMPThreadShutdown(false) + , mShuttingDownOnGMPThread(false) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +GeckoMediaPluginService::~GeckoMediaPluginService() +{ +} + +NS_IMETHODIMP +GeckoMediaPluginService::RunPluginCrashCallbacks(uint32_t aPluginId, + const nsACString& aPluginName) +{ + MOZ_ASSERT(NS_IsMainThread()); + LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId)); + + nsAutoPtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers; + { + MutexAutoLock lock(mMutex); + mPluginCrashHelpers.RemoveAndForget(aPluginId, helpers); + } + if (!helpers) { + LOGD(("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__, __FUNCTION__, aPluginId)); + return NS_OK; + } + + for (const auto& helper : *helpers) { + nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget(); + if (NS_WARN_IF(!window)) { + continue; + } + nsCOMPtr<nsIDocument> document(window->GetExtantDoc()); + if (NS_WARN_IF(!document)) { + continue; + } + + dom::PluginCrashedEventInit init; + init.mPluginID = aPluginId; + init.mBubbles = true; + init.mCancelable = true; + init.mGmpPlugin = true; + CopyUTF8toUTF16(aPluginName, init.mPluginName); + init.mSubmittedCrashReport = false; + RefPtr<dom::PluginCrashedEvent> event = + dom::PluginCrashedEvent::Constructor(document, + NS_LITERAL_STRING("PluginCrashed"), + init); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr); + } + + return NS_OK; +} + +nsresult +GeckoMediaPluginService::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)); + + // Kick off scanning for plugins + nsCOMPtr<nsIThread> thread; + return GetThread(getter_AddRefs(thread)); +} + +void +GeckoMediaPluginService::ShutdownGMPThread() +{ + LOGD(("%s::%s", __CLASS__, __FUNCTION__)); + nsCOMPtr<nsIThread> gmpThread; + { + MutexAutoLock lock(mMutex); + mGMPThreadShutdown = true; + mGMPThread.swap(gmpThread); + mAbstractGMPThread = nullptr; + } + + if (gmpThread) { + gmpThread->Shutdown(); + } +} + +nsresult +GeckoMediaPluginService::GMPDispatch(nsIRunnable* event, + uint32_t flags) +{ + nsCOMPtr<nsIRunnable> r(event); + return GMPDispatch(r.forget()); +} + +nsresult +GeckoMediaPluginService::GMPDispatch(already_AddRefed<nsIRunnable> event, + uint32_t flags) +{ + nsCOMPtr<nsIRunnable> r(event); + nsCOMPtr<nsIThread> thread; + nsresult rv = GetThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return rv; + } + return thread->Dispatch(r, flags); +} + +// always call with getter_AddRefs, because it does +NS_IMETHODIMP +GeckoMediaPluginService::GetThread(nsIThread** aThread) +{ + MOZ_ASSERT(aThread); + + // This can be called from any thread. + MutexAutoLock lock(mMutex); + + if (!mGMPThread) { + // Don't allow the thread to be created after shutdown has started. + if (mGMPThreadShutdown) { + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread)); + if (NS_FAILED(rv)) { + return rv; + } + + mAbstractGMPThread = AbstractThread::CreateXPCOMThreadWrapper(mGMPThread, false); + + // Tell the thread to initialize plugins + InitializePlugins(mAbstractGMPThread.get()); + } + + nsCOMPtr<nsIThread> copy = mGMPThread; + copy.forget(aThread); + + return NS_OK; +} + +RefPtr<AbstractThread> +GeckoMediaPluginService::GetAbstractGMPThread() +{ + MutexAutoLock lock(mMutex); + return mAbstractGMPThread; +} + +class GetGMPContentParentForAudioDecoderDone : public GetGMPContentParentCallback +{ +public: + explicit GetGMPContentParentForAudioDecoderDone(UniquePtr<GetGMPAudioDecoderCallback>&& aCallback, + GMPCrashHelper* aHelper) + : mCallback(Move(aCallback)) + , mHelper(aHelper) + { + } + + void Done(GMPContentParent* aGMPParent) override + { + GMPAudioDecoderParent* gmpADP = nullptr; + if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPAudioDecoder(&gmpADP))) { + gmpADP->SetCrashHelper(mHelper); + } + mCallback->Done(gmpADP); + } + +private: + UniquePtr<GetGMPAudioDecoderCallback> mCallback; + RefPtr<GMPCrashHelper> mHelper; +}; + +NS_IMETHODIMP +GeckoMediaPluginService::GetGMPAudioDecoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPAudioDecoderCallback>&& aCallback) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aCallback); + + if (mShuttingDownOnGMPThread) { + return NS_ERROR_FAILURE; + } + + UniquePtr<GetGMPContentParentCallback> callback( + new GetGMPContentParentForAudioDecoderDone(Move(aCallback), aHelper)); + if (!GetContentParentFrom(aHelper, + aNodeId, + NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER), + *aTags, + Move(callback))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +class GetGMPContentParentForVideoDecoderDone : public GetGMPContentParentCallback +{ +public: + explicit GetGMPContentParentForVideoDecoderDone(UniquePtr<GetGMPVideoDecoderCallback>&& aCallback, + GMPCrashHelper* aHelper, + uint32_t aDecryptorId) + : mCallback(Move(aCallback)) + , mHelper(aHelper) + , mDecryptorId(aDecryptorId) + { + } + + void Done(GMPContentParent* aGMPParent) override + { + GMPVideoDecoderParent* gmpVDP = nullptr; + GMPVideoHostImpl* videoHost = nullptr; + if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPVideoDecoder(&gmpVDP, mDecryptorId))) { + videoHost = &gmpVDP->Host(); + gmpVDP->SetCrashHelper(mHelper); + } + mCallback->Done(gmpVDP, videoHost); + } + +private: + UniquePtr<GetGMPVideoDecoderCallback> mCallback; + RefPtr<GMPCrashHelper> mHelper; + const uint32_t mDecryptorId; +}; + +NS_IMETHODIMP +GeckoMediaPluginService::GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoDecoderCallback>&& aCallback, + uint32_t aDecryptorId) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aCallback); + + if (mShuttingDownOnGMPThread) { + return NS_ERROR_FAILURE; + } + + UniquePtr<GetGMPContentParentCallback> callback( + new GetGMPContentParentForVideoDecoderDone(Move(aCallback), aHelper, aDecryptorId)); + if (!GetContentParentFrom(aHelper, + aNodeId, + NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + *aTags, + Move(callback))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +class GetGMPContentParentForVideoEncoderDone : public GetGMPContentParentCallback +{ +public: + explicit GetGMPContentParentForVideoEncoderDone(UniquePtr<GetGMPVideoEncoderCallback>&& aCallback, + GMPCrashHelper* aHelper) + : mCallback(Move(aCallback)) + , mHelper(aHelper) + { + } + + void Done(GMPContentParent* aGMPParent) override + { + GMPVideoEncoderParent* gmpVEP = nullptr; + GMPVideoHostImpl* videoHost = nullptr; + if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPVideoEncoder(&gmpVEP))) { + videoHost = &gmpVEP->Host(); + gmpVEP->SetCrashHelper(mHelper); + } + mCallback->Done(gmpVEP, videoHost); + } + +private: + UniquePtr<GetGMPVideoEncoderCallback> mCallback; + RefPtr<GMPCrashHelper> mHelper; +}; + +NS_IMETHODIMP +GeckoMediaPluginService::GetGMPVideoEncoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aCallback); + + if (mShuttingDownOnGMPThread) { + return NS_ERROR_FAILURE; + } + + UniquePtr<GetGMPContentParentCallback> callback( + new GetGMPContentParentForVideoEncoderDone(Move(aCallback), aHelper)); + if (!GetContentParentFrom(aHelper, + aNodeId, + NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER), + *aTags, + Move(callback))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +class GetGMPContentParentForDecryptorDone : public GetGMPContentParentCallback +{ +public: + explicit GetGMPContentParentForDecryptorDone(UniquePtr<GetGMPDecryptorCallback>&& aCallback, + GMPCrashHelper* aHelper) + : mCallback(Move(aCallback)) + , mHelper(aHelper) + { + } + + void Done(GMPContentParent* aGMPParent) override + { + GMPDecryptorParent* ksp = nullptr; + if (aGMPParent && NS_SUCCEEDED(aGMPParent->GetGMPDecryptor(&ksp))) { + ksp->SetCrashHelper(mHelper); + } + mCallback->Done(ksp); + } + +private: + UniquePtr<GetGMPDecryptorCallback> mCallback; + RefPtr<GMPCrashHelper> mHelper; +}; + +NS_IMETHODIMP +GeckoMediaPluginService::GetGMPDecryptor(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPDecryptorCallback>&& aCallback) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aCallback); + + if (mShuttingDownOnGMPThread) { + return NS_ERROR_FAILURE; + } + + UniquePtr<GetGMPContentParentCallback> callback( + new GetGMPContentParentForDecryptorDone(Move(aCallback), aHelper)); + if (!GetContentParentFrom(aHelper, + aNodeId, + NS_LITERAL_CSTRING(GMP_API_DECRYPTOR), + *aTags, + Move(callback))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper) +{ + if (!aHelper) { + return; + } + MutexAutoLock lock(mMutex); + nsTArray<RefPtr<GMPCrashHelper>>* helpers; + if (!mPluginCrashHelpers.Get(aPluginId, &helpers)) { + helpers = new nsTArray<RefPtr<GMPCrashHelper>>(); + mPluginCrashHelpers.Put(aPluginId, helpers); + } else if (helpers->Contains(aHelper)) { + return; + } + helpers->AppendElement(aHelper); +} + +void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper) +{ + if (!aHelper) { + return; + } + MutexAutoLock lock(mMutex); + for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) { + nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.Data(); + if (!helpers->Contains(aHelper)) { + continue; + } + helpers->RemoveElement(aHelper); + MOZ_ASSERT(!helpers->Contains(aHelper)); // Ensure there aren't duplicates. + if (helpers->IsEmpty()) { + iter.Remove(); + } + } +} + +} // namespace gmp +} // namespace mozilla + +NS_IMPL_ADDREF(GMPCrashHelper) +NS_IMPL_RELEASE_WITH_DESTROY(GMPCrashHelper, Destroy()) + +void +GMPCrashHelper::Destroy() +{ + if (NS_IsMainThread()) { + delete this; + } else { + // Don't addref, as then we'd end up releasing after the detele runs! + NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this, &GMPCrashHelper::Destroy)); + } +} diff --git a/dom/media/gmp/GMPService.h b/dom/media/gmp/GMPService.h new file mode 100644 index 000000000..7ed318a25 --- /dev/null +++ b/dom/media/gmp/GMPService.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPService_h_ +#define GMPService_h_ + +#include "nsString.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/Monitor.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIDocument.h" +#include "nsIWeakReference.h" +#include "mozilla/AbstractThread.h" +#include "nsClassHashtable.h" +#include "nsISupportsImpl.h" + +template <class> struct already_AddRefed; + +// For every GMP actor requested, the caller can specify a crash helper, +// which is an object which supplies the nsPIDOMWindowInner to which we'll +// dispatch the PluginCrashed event if the GMP crashes. +// GMPCrashHelper has threadsafe refcounting. Its release method ensures +// that instances are destroyed on the main thread. +class GMPCrashHelper +{ +public: + NS_METHOD_(MozExternalRefCountType) AddRef(void); + NS_METHOD_(MozExternalRefCountType) Release(void); + + // Called on the main thread. + virtual already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() = 0; + +protected: + virtual ~GMPCrashHelper() + { + MOZ_ASSERT(NS_IsMainThread()); + } + void Destroy(); + mozilla::ThreadSafeAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD +}; + +namespace mozilla { + +extern LogModule* GetGMPLog(); + +namespace gmp { + +class GetGMPContentParentCallback; + +class GeckoMediaPluginService : public mozIGeckoMediaPluginService + , public nsIObserver +{ +public: + static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService(); + + virtual nsresult Init(); + + NS_DECL_THREADSAFE_ISUPPORTS + + // mozIGeckoMediaPluginService + NS_IMETHOD GetThread(nsIThread** aThread) override; + NS_IMETHOD GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoDecoderCallback>&& aCallback, + uint32_t aDecryptorId) + override; + NS_IMETHOD GetGMPVideoEncoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) + override; + NS_IMETHOD GetGMPAudioDecoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPAudioDecoderCallback>&& aCallback) + override; + NS_IMETHOD GetGMPDecryptor(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPDecryptorCallback>&& aCallback) + override; + + // Helper for backwards compatibility with WebRTC/tests. + NS_IMETHOD + GetGMPVideoDecoder(GMPCrashHelper* aHelper, + nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) override + { + return GetDecryptingGMPVideoDecoder(aHelper, aTags, aNodeId, Move(aCallback), 0); + } + + int32_t AsyncShutdownTimeoutMs(); + + NS_IMETHOD RunPluginCrashCallbacks(uint32_t aPluginId, + const nsACString& aPluginName) override; + + RefPtr<AbstractThread> GetAbstractGMPThread(); + + void ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper); + void DisconnectCrashHelper(GMPCrashHelper* aHelper); + +protected: + GeckoMediaPluginService(); + virtual ~GeckoMediaPluginService(); + + virtual void InitializePlugins(AbstractThread* aAbstractGMPThread) = 0; + virtual bool GetContentParentFrom(GMPCrashHelper* aHelper, + const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + UniquePtr<GetGMPContentParentCallback>&& aCallback) = 0; + + nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL); + nsresult GMPDispatch(already_AddRefed<nsIRunnable> event, uint32_t flags = NS_DISPATCH_NORMAL); + void ShutdownGMPThread(); + + Mutex mMutex; // Protects mGMPThread, mAbstractGMPThread, mPluginCrashHelpers, + // mGMPThreadShutdown and some members in derived classes. + nsCOMPtr<nsIThread> mGMPThread; + RefPtr<AbstractThread> mAbstractGMPThread; + bool mGMPThreadShutdown; + bool mShuttingDownOnGMPThread; + + nsClassHashtable<nsUint32HashKey, nsTArray<RefPtr<GMPCrashHelper>>> mPluginCrashHelpers; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPService_h_ diff --git a/dom/media/gmp/GMPServiceChild.cpp b/dom/media/gmp/GMPServiceChild.cpp new file mode 100644 index 000000000..08599039f --- /dev/null +++ b/dom/media/gmp/GMPServiceChild.cpp @@ -0,0 +1,478 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPServiceChild.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozIGeckoMediaPluginChromeService.h" +#include "nsCOMPtr.h" +#include "GMPParent.h" +#include "GMPContentParent.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/StaticMutex.h" +#include "runnable_utils.h" +#include "base/task.h" +#include "nsIObserverService.h" +#include "nsComponentManagerUtils.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPService" + +namespace gmp { + +already_AddRefed<GeckoMediaPluginServiceChild> +GeckoMediaPluginServiceChild::GetSingleton() +{ + MOZ_ASSERT(!XRE_IsParentProcess()); + RefPtr<GeckoMediaPluginService> service( + GeckoMediaPluginService::GetGeckoMediaPluginService()); +#ifdef DEBUG + if (service) { + nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService; + CallQueryInterface(service.get(), getter_AddRefs(chromeService)); + MOZ_ASSERT(!chromeService); + } +#endif + return service.forget().downcast<GeckoMediaPluginServiceChild>(); +} + +class GetContentParentFromDone : public GetServiceChildCallback +{ +public: + GetContentParentFromDone(GMPCrashHelper* aHelper, const nsACString& aNodeId, const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + UniquePtr<GetGMPContentParentCallback>&& aCallback) + : mHelper(aHelper), + mNodeId(aNodeId), + mAPI(aAPI), + mTags(aTags), + mCallback(Move(aCallback)) + { + } + + void Done(GMPServiceChild* aGMPServiceChild) override + { + if (!aGMPServiceChild) { + mCallback->Done(nullptr); + return; + } + + uint32_t pluginId; + nsresult rv; + bool ok = aGMPServiceChild->SendSelectGMP(mNodeId, mAPI, mTags, &pluginId, &rv); + if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) { + mCallback->Done(nullptr); + return; + } + + if (mHelper) { + RefPtr<GeckoMediaPluginService> gmps(GeckoMediaPluginService::GetGeckoMediaPluginService()); + gmps->ConnectCrashHelper(pluginId, mHelper); + } + + nsTArray<base::ProcessId> alreadyBridgedTo; + aGMPServiceChild->GetAlreadyBridgedTo(alreadyBridgedTo); + + base::ProcessId otherProcess; + nsCString displayName; + ok = aGMPServiceChild->SendLaunchGMP(pluginId, alreadyBridgedTo, &otherProcess, + &displayName, &rv); + if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) { + mCallback->Done(nullptr); + return; + } + + RefPtr<GMPContentParent> parent; + aGMPServiceChild->GetBridgedGMPContentParent(otherProcess, + getter_AddRefs(parent)); + if (!alreadyBridgedTo.Contains(otherProcess)) { + parent->SetDisplayName(displayName); + parent->SetPluginId(pluginId); + } + + mCallback->Done(parent); + } + +private: + RefPtr<GMPCrashHelper> mHelper; + nsCString mNodeId; + nsCString mAPI; + const nsTArray<nsCString> mTags; + UniquePtr<GetGMPContentParentCallback> mCallback; +}; + +bool +GeckoMediaPluginServiceChild::GetContentParentFrom(GMPCrashHelper* aHelper, + const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + UniquePtr<GetGMPContentParentCallback>&& aCallback) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + + UniquePtr<GetServiceChildCallback> callback( + new GetContentParentFromDone(aHelper, aNodeId, aAPI, aTags, Move(aCallback))); + GetServiceChild(Move(callback)); + + return true; +} + +typedef mozilla::dom::GMPCapabilityData GMPCapabilityData; +typedef mozilla::dom::GMPAPITags GMPAPITags; + +struct GMPCapabilityAndVersion +{ + explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities) + : mName(aCapabilities.name()) + , mVersion(aCapabilities.version()) + { + for (const GMPAPITags& tags : aCapabilities.capabilities()) { + GMPCapability cap; + cap.mAPIName = tags.api(); + for (const nsCString& tag : tags.tags()) { + cap.mAPITags.AppendElement(tag); + } + mCapabilities.AppendElement(Move(cap)); + } + } + + nsCString ToString() const + { + nsCString s; + s.Append(mName); + s.Append(" version="); + s.Append(mVersion); + s.Append(" tags=["); + nsCString tags; + for (const GMPCapability& cap : mCapabilities) { + if (!tags.IsEmpty()) { + tags.Append(" "); + } + tags.Append(cap.mAPIName); + for (const nsCString& tag : cap.mAPITags) { + tags.Append(":"); + tags.Append(tag); + } + } + s.Append(tags); + s.Append("]"); + return s; + } + + nsCString mName; + nsCString mVersion; + nsTArray<GMPCapability> mCapabilities; +}; + +StaticMutex sGMPCapabilitiesMutex; +StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities; + +static nsCString +GMPCapabilitiesToString() +{ + nsCString s; + for (const GMPCapabilityAndVersion& gmp : *sGMPCapabilities) { + if (!s.IsEmpty()) { + s.Append(", "); + } + s.Append(gmp.ToString()); + } + return s; +} + +/* static */ +void +GeckoMediaPluginServiceChild::UpdateGMPCapabilities(nsTArray<GMPCapabilityData>&& aCapabilities) +{ + { + // The mutex should unlock before sending the "gmp-changed" observer service notification. + StaticMutexAutoLock lock(sGMPCapabilitiesMutex); + if (!sGMPCapabilities) { + sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>(); + ClearOnShutdown(&sGMPCapabilities); + } + sGMPCapabilities->Clear(); + for (const GMPCapabilityData& plugin : aCapabilities) { + sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin)); + } + + LOGD(("UpdateGMPCapabilities {%s}", GMPCapabilitiesToString().get())); + } + + // Fire a notification so that any MediaKeySystemAccess + // requests waiting on a CDM to download will retry. + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + if (obsService) { + obsService->NotifyObservers(nullptr, "gmp-changed", nullptr); + } +} + +NS_IMETHODIMP +GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI, + nsTArray<nsCString>* aTags, + bool* aHasPlugin) +{ + StaticMutexAutoLock lock(sGMPCapabilitiesMutex); + if (!sGMPCapabilities) { + *aHasPlugin = false; + return NS_OK; + } + + nsCString api(aAPI); + for (const GMPCapabilityAndVersion& plugin : *sGMPCapabilities) { + if (GMPCapability::Supports(plugin.mCapabilities, api, *aTags)) { + *aHasPlugin = true; + return NS_OK; + } + } + + *aHasPlugin = false; + return NS_OK; +} + +class GetNodeIdDone : public GetServiceChildCallback +{ +public: + GetNodeIdDone(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback) + : mOrigin(aOrigin), + mTopLevelOrigin(aTopLevelOrigin), + mGMPName(aGMPName), + mInPrivateBrowsing(aInPrivateBrowsing), + mCallback(Move(aCallback)) + { + } + + void Done(GMPServiceChild* aGMPServiceChild) override + { + if (!aGMPServiceChild) { + mCallback->Done(NS_ERROR_FAILURE, EmptyCString()); + return; + } + + nsCString outId; + if (!aGMPServiceChild->SendGetGMPNodeId(mOrigin, mTopLevelOrigin, + mGMPName, + mInPrivateBrowsing, &outId)) { + mCallback->Done(NS_ERROR_FAILURE, EmptyCString()); + return; + } + + mCallback->Done(NS_OK, outId); + } + +private: + nsString mOrigin; + nsString mTopLevelOrigin; + nsString mGMPName; + bool mInPrivateBrowsing; + UniquePtr<GetNodeIdCallback> mCallback; +}; + +NS_IMETHODIMP +GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsing, + UniquePtr<GetNodeIdCallback>&& aCallback) +{ + UniquePtr<GetServiceChildCallback> callback( + new GetNodeIdDone(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, Move(aCallback))); + GetServiceChild(Move(callback)); + return NS_OK; +} + +NS_IMETHODIMP +GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) +{ + LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic)); + if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) { + if (mServiceChild) { + mozilla::SyncRunnable::DispatchToThread(mGMPThread, + WrapRunnable(mServiceChild.get(), + &PGMPServiceChild::Close)); + mServiceChild = nullptr; + } + ShutdownGMPThread(); + } + + return NS_OK; +} + +void +GeckoMediaPluginServiceChild::GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + if (!mServiceChild) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (!contentChild) { + return; + } + mGetServiceChildCallbacks.AppendElement(Move(aCallback)); + if (mGetServiceChildCallbacks.Length() == 1) { + NS_DispatchToMainThread(WrapRunnable(contentChild, + &dom::ContentChild::SendCreateGMPService)); + } + return; + } + + aCallback->Done(mServiceChild.get()); +} + +void +GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild) +{ + mServiceChild = Move(aServiceChild); + nsTArray<UniquePtr<GetServiceChildCallback>> getServiceChildCallbacks; + getServiceChildCallbacks.SwapElements(mGetServiceChildCallbacks); + for (uint32_t i = 0, length = getServiceChildCallbacks.Length(); i < length; ++i) { + getServiceChildCallbacks[i]->Done(mServiceChild.get()); + } +} + +void +GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent) +{ + if (mServiceChild) { + mServiceChild->RemoveGMPContentParent(aGMPContentParent); + } +} + +GMPServiceChild::GMPServiceChild() +{ +} + +GMPServiceChild::~GMPServiceChild() +{ +} + +PGMPContentParent* +GMPServiceChild::AllocPGMPContentParent(Transport* aTransport, + ProcessId aOtherPid) +{ + MOZ_ASSERT(!mContentParents.GetWeak(aOtherPid)); + + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + + RefPtr<GMPContentParent> parent = new GMPContentParent(); + + DebugOnly<bool> ok = parent->Open(aTransport, aOtherPid, + XRE_GetIOMessageLoop(), + mozilla::ipc::ParentSide); + MOZ_ASSERT(ok); + + mContentParents.Put(aOtherPid, parent); + return parent; +} + +void +GMPServiceChild::GetBridgedGMPContentParent(ProcessId aOtherPid, + GMPContentParent** aGMPContentParent) +{ + mContentParents.Get(aOtherPid, aGMPContentParent); +} + +void +GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent) +{ + for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) { + RefPtr<GMPContentParent>& parent = iter.Data(); + if (parent == aGMPContentParent) { + iter.Remove(); + break; + } + } +} + +void +GMPServiceChild::GetAlreadyBridgedTo(nsTArray<base::ProcessId>& aAlreadyBridgedTo) +{ + aAlreadyBridgedTo.SetCapacity(mContentParents.Count()); + for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) { + const uint64_t& id = iter.Key(); + aAlreadyBridgedTo.AppendElement(id); + } +} + +class OpenPGMPServiceChild : public mozilla::Runnable +{ +public: + OpenPGMPServiceChild(UniquePtr<GMPServiceChild>&& aGMPServiceChild, + mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid) + : mGMPServiceChild(Move(aGMPServiceChild)), + mTransport(aTransport), + mOtherPid(aOtherPid) + { + } + + NS_IMETHOD Run() override + { + RefPtr<GeckoMediaPluginServiceChild> gmp = + GeckoMediaPluginServiceChild::GetSingleton(); + MOZ_ASSERT(!gmp->mServiceChild); + if (mGMPServiceChild->Open(mTransport, mOtherPid, XRE_GetIOMessageLoop(), + ipc::ChildSide)) { + gmp->SetServiceChild(Move(mGMPServiceChild)); + } else { + gmp->SetServiceChild(nullptr); + } + return NS_OK; + } + +private: + UniquePtr<GMPServiceChild> mGMPServiceChild; + mozilla::ipc::Transport* mTransport; + base::ProcessId mOtherPid; +}; + +/* static */ +PGMPServiceChild* +GMPServiceChild::Create(Transport* aTransport, ProcessId aOtherPid) +{ + RefPtr<GeckoMediaPluginServiceChild> gmp = + GeckoMediaPluginServiceChild::GetSingleton(); + MOZ_ASSERT(!gmp->mServiceChild); + + UniquePtr<GMPServiceChild> serviceChild(new GMPServiceChild()); + + nsCOMPtr<nsIThread> gmpThread; + nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread)); + NS_ENSURE_SUCCESS(rv, nullptr); + + GMPServiceChild* result = serviceChild.get(); + rv = gmpThread->Dispatch(new OpenPGMPServiceChild(Move(serviceChild), + aTransport, + aOtherPid), + NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + return nullptr; + } + + return result; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPServiceChild.h b/dom/media/gmp/GMPServiceChild.h new file mode 100644 index 000000000..63b1325bb --- /dev/null +++ b/dom/media/gmp/GMPServiceChild.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPServiceChild_h_ +#define GMPServiceChild_h_ + +#include "GMPService.h" +#include "base/process.h" +#include "mozilla/ipc/Transport.h" +#include "mozilla/gmp/PGMPServiceChild.h" +#include "nsRefPtrHashtable.h" +#include "mozilla/dom/ContentChild.h" + +namespace mozilla { +namespace gmp { + +class GMPContentParent; +class GMPServiceChild; + +class GetServiceChildCallback +{ +public: + GetServiceChildCallback() + { + MOZ_COUNT_CTOR(GetServiceChildCallback); + } + virtual ~GetServiceChildCallback() + { + MOZ_COUNT_DTOR(GetServiceChildCallback); + } + virtual void Done(GMPServiceChild* aGMPServiceChild) = 0; +}; + +class GeckoMediaPluginServiceChild : public GeckoMediaPluginService +{ + friend class GMPServiceChild; + +public: + static already_AddRefed<GeckoMediaPluginServiceChild> GetSingleton(); + + NS_IMETHOD HasPluginForAPI(const nsACString& aAPI, + nsTArray<nsCString>* aTags, + bool *aRetVal) override; + NS_IMETHOD GetNodeId(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsingMode, + UniquePtr<GetNodeIdCallback>&& aCallback) override; + + NS_DECL_NSIOBSERVER + + void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild); + + void RemoveGMPContentParent(GMPContentParent* aGMPContentParent); + + static void UpdateGMPCapabilities(nsTArray<mozilla::dom::GMPCapabilityData>&& aCapabilities); + +protected: + void InitializePlugins(AbstractThread*) override + { + // Nothing to do here. + } + bool GetContentParentFrom(GMPCrashHelper* aHelper, + const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + UniquePtr<GetGMPContentParentCallback>&& aCallback) + override; + +private: + friend class OpenPGMPServiceChild; + + void GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback); + + UniquePtr<GMPServiceChild> mServiceChild; + nsTArray<UniquePtr<GetServiceChildCallback>> mGetServiceChildCallbacks; +}; + +class GMPServiceChild : public PGMPServiceChild +{ +public: + explicit GMPServiceChild(); + virtual ~GMPServiceChild(); + + PGMPContentParent* AllocPGMPContentParent(Transport* aTransport, + ProcessId aOtherPid) override; + + void GetBridgedGMPContentParent(ProcessId aOtherPid, + GMPContentParent** aGMPContentParent); + void RemoveGMPContentParent(GMPContentParent* aGMPContentParent); + + void GetAlreadyBridgedTo(nsTArray<ProcessId>& aAlreadyBridgedTo); + + static PGMPServiceChild* Create(Transport* aTransport, ProcessId aOtherPid); + +private: + nsRefPtrHashtable<nsUint64HashKey, GMPContentParent> mContentParents; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPServiceChild_h_ diff --git a/dom/media/gmp/GMPServiceParent.cpp b/dom/media/gmp/GMPServiceParent.cpp new file mode 100644 index 000000000..a4afbdad4 --- /dev/null +++ b/dom/media/gmp/GMPServiceParent.cpp @@ -0,0 +1,1933 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPServiceParent.h" +#include "GMPService.h" +#include "prio.h" +#include "base/task.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/ContentParent.h" +#include "GMPParent.h" +#include "GMPVideoDecoderParent.h" +#include "nsAutoPtr.h" +#include "nsIObserverService.h" +#include "GeckoChildProcessHost.h" +#include "mozilla/Preferences.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/SyncRunnable.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/Services.h" +#include "nsNativeCharsetUtils.h" +#include "nsIConsoleService.h" +#include "mozilla/Unused.h" +#include "GMPDecryptorParent.h" +#include "GMPAudioDecoderParent.h" +#include "nsComponentManagerUtils.h" +#include "runnable_utils.h" +#include "VideoUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsHashKeys.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "nsIXULRuntime.h" +#include "GMPDecoderModule.h" +#include <limits> +#include "MediaPrefs.h" + +using mozilla::ipc::Transport; + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPService" + +namespace gmp { + +static const uint32_t NodeIdSaltLength = 32; + +already_AddRefed<GeckoMediaPluginServiceParent> +GeckoMediaPluginServiceParent::GetSingleton() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + RefPtr<GeckoMediaPluginService> service( + GeckoMediaPluginServiceParent::GetGeckoMediaPluginService()); +#ifdef DEBUG + if (service) { + nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService; + CallQueryInterface(service.get(), getter_AddRefs(chromeService)); + MOZ_ASSERT(chromeService); + } +#endif + return service.forget().downcast<GeckoMediaPluginServiceParent>(); +} + +NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceParent, + GeckoMediaPluginService, + mozIGeckoMediaPluginChromeService, + nsIAsyncShutdownBlocker) + +GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent() + : mShuttingDown(false) + , mScannedPluginOnDisk(false) + , mWaitingForPluginsSyncShutdown(false) + , mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor") + , mLoadPluginsFromDiskComplete(false) + , mServiceUserCount(0) +{ + MOZ_ASSERT(NS_IsMainThread()); + mInitPromise.SetMonitor(&mInitPromiseMonitor); +} + +GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent() +{ + MOZ_ASSERT(mPlugins.IsEmpty()); + MOZ_ASSERT(mAsyncShutdownPlugins.IsEmpty()); +} + +int32_t +GeckoMediaPluginServiceParent::AsyncShutdownTimeoutMs() +{ + return MediaPrefs::GMPAsyncShutdownTimeout(); +} + +nsresult +GeckoMediaPluginServiceParent::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "profile-change-teardown", false)); + MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "last-pb-context-exited", false)); + MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "browser:purge-session-history", false)); + +#ifdef DEBUG + MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "mediakeys-request", false)); +#endif + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + prefs->AddObserver("media.gmp.plugin.crash", this, false); + } + + nsresult rv = InitStorage(); + if (NS_FAILED(rv)) { + return rv; + } + + // Kick off scanning for plugins + nsCOMPtr<nsIThread> thread; + rv = GetThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return rv; + } + + // Detect if GMP storage has an incompatible version, and if so nuke it. + int32_t version = Preferences::GetInt("media.gmp.storage.version.observed", 0); + int32_t expected = Preferences::GetInt("media.gmp.storage.version.expected", 0); + if (version != expected) { + Preferences::SetInt("media.gmp.storage.version.observed", expected); + return GMPDispatch(NewRunnableMethod( + this, &GeckoMediaPluginServiceParent::ClearStorage)); + } + return NS_OK; +} + +already_AddRefed<nsIFile> +CloneAndAppend(nsIFile* aFile, const nsAString& aDir) +{ + nsCOMPtr<nsIFile> f; + nsresult rv = aFile->Clone(getter_AddRefs(f)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + rv = f->Append(aDir); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + return f.forget(); +} + +static nsresult +GMPPlatformString(nsAString& aOutPlatform) +{ + // Append the OS and arch so that we don't reuse the storage if the profile is + // copied or used under a different bit-ness, or copied to another platform. + nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); + if (!runtime) { + return NS_ERROR_FAILURE; + } + + nsAutoCString OS; + nsresult rv = runtime->GetOS(OS); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString arch; + rv = runtime->GetXPCOMABI(arch); + if (NS_FAILED(rv)) { + return rv; + } + + nsCString platform; + platform.Append(OS); + platform.AppendLiteral("_"); + platform.Append(arch); + + aOutPlatform = NS_ConvertUTF8toUTF16(platform); + + return NS_OK; +} + +nsresult +GeckoMediaPluginServiceParent::InitStorage() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // GMP storage should be used in the chrome process only. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + + // Directory service is main thread only, so cache the profile dir here + // so that we can use it off main thread. + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mStorageBaseDir)); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mStorageBaseDir->AppendNative(NS_LITERAL_CSTRING("gmp")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) { + return rv; + } + + nsCOMPtr<nsIFile> gmpDirWithoutPlatform; + rv = mStorageBaseDir->Clone(getter_AddRefs(gmpDirWithoutPlatform)); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString platform; + rv = GMPPlatformString(platform); + if (NS_FAILED(rv)) { + return rv; + } + + rv = mStorageBaseDir->Append(platform); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) { + return rv; + } + + return GeckoMediaPluginService::Init(); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) +{ + LOGD(("%s::%s topic='%s' data='%s'", __CLASS__, __FUNCTION__, + aTopic, NS_ConvertUTF16toUTF8(aSomeData).get())); + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) ); + if (branch) { + bool crashNow = false; + if (NS_LITERAL_STRING("media.gmp.plugin.crash").Equals(aSomeData)) { + branch->GetBoolPref("media.gmp.plugin.crash", &crashNow); + } + if (crashNow) { + nsCOMPtr<nsIThread> gmpThread; + { + MutexAutoLock lock(mMutex); + gmpThread = mGMPThread; + } + if (gmpThread) { + gmpThread->Dispatch(WrapRunnable(this, + &GeckoMediaPluginServiceParent::CrashPlugins), + NS_DISPATCH_NORMAL); + } + } + } + } else if (!strcmp("profile-change-teardown", aTopic)) { + + // How shutdown works: + // + // Some GMPs require time to do bookkeeping upon shutdown. These GMPs + // need to be given time to access storage during shutdown. To signal + // that time to shutdown is required, those GMPs implement the + // GMPAsyncShutdown interface. + // + // When we startup the child process, we query the GMP for the + // GMPAsyncShutdown interface, and if it's present, we send a message + // back to the GMPParent, which then registers the GMPParent by calling + // GMPService::AsyncShutdownNeeded(). + // + // On shutdown, we set mWaitingForPluginsSyncShutdown to true, and then + // call UnloadPlugins on the GMPThread, and process events on the main + // thread until 1. An event sets mWaitingForPluginsSyncShutdown=false on + // the main thread; then 2. All async-shutdown plugins have indicated + // they have completed shutdown. + // + // UnloadPlugins() sends close messages for all plugins' API objects to + // the GMP interfaces in the child process, and then sends the async + // shutdown notifications to child GMPs. When a GMP has completed its + // shutdown, it calls GMPAsyncShutdownHost::ShutdownComplete(), which + // sends a message back to the parent, which calls + // GMPService::AsyncShutdownComplete(). If all plugins requiring async + // shutdown have called AsyncShutdownComplete() we stick a dummy event on + // the main thread, where the list of pending plugins is checked. We must + // use an event to do this, as we must ensure the main thread processes an + // event to run its loop. This will unblock the main thread, and shutdown + // of other components will proceed. + // + // During shutdown, each GMPParent starts a timer, and pretends shutdown + // is complete if it is taking too long. + // + // We shutdown in "profile-change-teardown", as the profile dir is + // still writable then, and it's required for GMPStorage. We block the + // shutdown process by spinning the main thread event loop until all GMPs + // have shutdown, or timeout has occurred. + // + // GMPStorage needs to work up until the shutdown-complete notification + // arrives from the GMP process. + + mWaitingForPluginsSyncShutdown = true; + + nsCOMPtr<nsIThread> gmpThread; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mShuttingDown); + mShuttingDown = true; + gmpThread = mGMPThread; + } + + if (gmpThread) { + LOGD(("%s::%s Starting to unload plugins, waiting for first sync shutdown..." + , __CLASS__, __FUNCTION__)); + gmpThread->Dispatch( + NewRunnableMethod(this, + &GeckoMediaPluginServiceParent::UnloadPlugins), + NS_DISPATCH_NORMAL); + + // Wait for UnloadPlugins() to do initial sync shutdown... + while (mWaitingForPluginsSyncShutdown) { + NS_ProcessNextEvent(NS_GetCurrentThread(), true); + } + + // Wait for other plugins (if any) to do async shutdown... + auto syncShutdownPluginsRemaining = + std::numeric_limits<decltype(mAsyncShutdownPlugins.Length())>::max(); + for (;;) { + { + MutexAutoLock lock(mMutex); + if (mAsyncShutdownPlugins.IsEmpty()) { + LOGD(("%s::%s Finished unloading all plugins" + , __CLASS__, __FUNCTION__)); + break; + } else if (mAsyncShutdownPlugins.Length() < syncShutdownPluginsRemaining) { + // First time here, or number of pending plugins has decreased. + // -> Update list of pending plugins in crash report. + syncShutdownPluginsRemaining = mAsyncShutdownPlugins.Length(); + LOGD(("%s::%s Still waiting for %d plugins to shutdown..." + , __CLASS__, __FUNCTION__, (int)syncShutdownPluginsRemaining)); + } + } + NS_ProcessNextEvent(NS_GetCurrentThread(), true); + } + } else { + // GMP thread has already shutdown. + MOZ_ASSERT(mPlugins.IsEmpty()); + mWaitingForPluginsSyncShutdown = false; + } + + } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) { + MOZ_ASSERT(mShuttingDown); + ShutdownGMPThread(); + } else if (!strcmp("last-pb-context-exited", aTopic)) { + // When Private Browsing mode exits, all we need to do is clear + // mTempNodeIds. This drops all the node ids we've cached in memory + // for PB origin-pairs. If we try to open an origin-pair for non-PB + // mode, we'll get the NodeId salt stored on-disk, and if we try to + // open a PB mode origin-pair, we'll re-generate new salt. + mTempNodeIds.Clear(); + } else if (!strcmp("browser:purge-session-history", aTopic)) { + // Clear everything! + if (!aSomeData || nsDependentString(aSomeData).IsEmpty()) { + return GMPDispatch(NewRunnableMethod( + this, &GeckoMediaPluginServiceParent::ClearStorage)); + } + + // Clear nodeIds/records modified after |t|. + nsresult rv; + PRTime t = nsDependentString(aSomeData).ToInteger64(&rv, 10); + if (NS_FAILED(rv)) { + return rv; + } + return GMPDispatch(NewRunnableMethod<PRTime>( + this, &GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread, + t)); + } + + return NS_OK; +} + +RefPtr<GenericPromise> +GeckoMediaPluginServiceParent::EnsureInitialized() { + MonitorAutoLock lock(mInitPromiseMonitor); + if (mLoadPluginsFromDiskComplete) { + return GenericPromise::CreateAndResolve(true, __func__); + } + // We should have an init promise in flight. + MOZ_ASSERT(!mInitPromise.IsEmpty()); + return mInitPromise.Ensure(__func__); +} + +bool +GeckoMediaPluginServiceParent::GetContentParentFrom(GMPCrashHelper* aHelper, + const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + UniquePtr<GetGMPContentParentCallback>&& aCallback) +{ + RefPtr<AbstractThread> thread(GetAbstractGMPThread()); + if (!thread) { + return false; + } + + RefPtr<GeckoMediaPluginServiceParent> self(this); + nsCString nodeId(aNodeId); + nsTArray<nsCString> tags(aTags); + nsCString api(aAPI); + GetGMPContentParentCallback* rawCallback = aCallback.release(); + RefPtr<GMPCrashHelper> helper(aHelper); + EnsureInitialized()->Then(thread, __func__, + [self, tags, api, nodeId, rawCallback, helper]() -> void { + UniquePtr<GetGMPContentParentCallback> callback(rawCallback); + RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeId, api, tags); + LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)self, (void *)gmp, api.get())); + if (!gmp) { + NS_WARNING("GeckoMediaPluginServiceParent::GetContentParentFrom failed"); + callback->Done(nullptr); + return; + } + self->ConnectCrashHelper(gmp->GetPluginId(), helper); + gmp->GetGMPContentParent(Move(callback)); + }, + [rawCallback]() -> void { + UniquePtr<GetGMPContentParentCallback> callback(rawCallback); + NS_WARNING("GMPService::EnsureInitialized failed."); + callback->Done(nullptr); + }); + return true; +} + +void +GeckoMediaPluginServiceParent::InitializePlugins( + AbstractThread* aAbstractGMPThread) +{ + MOZ_ASSERT(aAbstractGMPThread); + MonitorAutoLock lock(mInitPromiseMonitor); + if (mLoadPluginsFromDiskComplete) { + return; + } + + RefPtr<GeckoMediaPluginServiceParent> self(this); + RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__); + InvokeAsync(aAbstractGMPThread, this, __func__, + &GeckoMediaPluginServiceParent::LoadFromEnvironment) + ->Then(aAbstractGMPThread, __func__, + [self]() -> void { + MonitorAutoLock lock(self->mInitPromiseMonitor); + self->mLoadPluginsFromDiskComplete = true; + self->mInitPromise.Resolve(true, __func__); + }, + [self]() -> void { + MonitorAutoLock lock(self->mInitPromiseMonitor); + self->mLoadPluginsFromDiskComplete = true; + self->mInitPromise.Reject(NS_ERROR_FAILURE, __func__); + }); +} + +void +GeckoMediaPluginServiceParent::AsyncShutdownNeeded(GMPParent* aParent) +{ + LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent)); + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mAsyncShutdownPlugins.Contains(aParent)); + mAsyncShutdownPlugins.AppendElement(aParent); +} + +void +GeckoMediaPluginServiceParent::AsyncShutdownComplete(GMPParent* aParent) +{ + LOGD(("%s::%s %p '%s'", __CLASS__, __FUNCTION__, + aParent, aParent->GetDisplayName().get())); + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + + { + MutexAutoLock lock(mMutex); + mAsyncShutdownPlugins.RemoveElement(aParent); + } + + if (mShuttingDownOnGMPThread) { + // The main thread may be waiting for async shutdown of plugins, + // one of which has completed. Wake up the main thread by sending a task. + nsCOMPtr<nsIRunnable> task(NewRunnableMethod( + this, &GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete)); + NS_DispatchToMainThread(task); + } +} + +void +GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete() +{ + MOZ_ASSERT(NS_IsMainThread()); + // Nothing to do, this task is just used to wake up the event loop in Observe(). +} + +void +GeckoMediaPluginServiceParent::NotifySyncShutdownComplete() +{ + MOZ_ASSERT(NS_IsMainThread()); + mWaitingForPluginsSyncShutdown = false; +} + +bool +GeckoMediaPluginServiceParent::IsShuttingDown() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + return mShuttingDownOnGMPThread; +} + +void +GeckoMediaPluginServiceParent::UnloadPlugins() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + MOZ_ASSERT(!mShuttingDownOnGMPThread); + mShuttingDownOnGMPThread = true; + + nsTArray<RefPtr<GMPParent>> plugins; + { + MutexAutoLock lock(mMutex); + // Move all plugins references to a local array. This way mMutex won't be + // locked when calling CloseActive (to avoid inter-locking). + Swap(plugins, mPlugins); + } + + LOGD(("%s::%s plugins:%u including async:%u", __CLASS__, __FUNCTION__, + plugins.Length(), mAsyncShutdownPlugins.Length())); +#ifdef DEBUG + for (const auto& plugin : plugins) { + LOGD(("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__, + plugin->GetDisplayName().get())); + } + for (const auto& plugin : mAsyncShutdownPlugins) { + LOGD(("%s::%s async plugin: '%s'", __CLASS__, __FUNCTION__, + plugin->GetDisplayName().get())); + } +#endif + // Note: CloseActive may be async; it could actually finish + // shutting down when all the plugins have unloaded. + for (const auto& plugin : plugins) { + plugin->CloseActive(true); + } + + nsCOMPtr<nsIRunnable> task(NewRunnableMethod( + this, &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete)); + NS_DispatchToMainThread(task); +} + +void +GeckoMediaPluginServiceParent::CrashPlugins() +{ + LOGD(("%s::%s", __CLASS__, __FUNCTION__)); + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + + MutexAutoLock lock(mMutex); + for (size_t i = 0; i < mPlugins.Length(); i++) { + mPlugins[i]->Crash(); + } +} + +RefPtr<GenericPromise::AllPromiseType> +GeckoMediaPluginServiceParent::LoadFromEnvironment() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + RefPtr<AbstractThread> thread(GetAbstractGMPThread()); + if (!thread) { + return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + const char* env = PR_GetEnv("MOZ_GMP_PATH"); + if (!env || !*env) { + return GenericPromise::AllPromiseType::CreateAndResolve(true, __func__); + } + + nsString allpaths; + if (NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) { + return GenericPromise::AllPromiseType::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + nsTArray<RefPtr<GenericPromise>> promises; + uint32_t pos = 0; + while (pos < allpaths.Length()) { + // Loop over multiple path entries separated by colons (*nix) or + // semicolons (Windows) + int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos); + if (next == -1) { + promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos)))); + break; + } else { + promises.AppendElement(AddOnGMPThread(nsString(Substring(allpaths, pos, next - pos)))); + pos = next + 1; + } + } + + mScannedPluginOnDisk = true; + return GenericPromise::All(thread, promises); +} + +class NotifyObserversTask final : public mozilla::Runnable { +public: + explicit NotifyObserversTask(const char* aTopic, nsString aData = EmptyString()) + : mTopic(aTopic) + , mData(aData) + {} + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + if (obsService) { + obsService->NotifyObservers(nullptr, mTopic, mData.get()); + } + return NS_OK; + } +private: + ~NotifyObserversTask() {} + const char* mTopic; + const nsString mData; +}; + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::PathRunnable::Run() +{ + mService->RemoveOnGMPThread(mPath, + mOperation == REMOVE_AND_DELETE_FROM_DISK, + mDefer); + + mService->UpdateContentProcessGMPCapabilities(); + return NS_OK; +} + +void +GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities() +{ + if (!NS_IsMainThread()) { + nsCOMPtr<nsIRunnable> task = + NewRunnableMethod(this, &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities); + NS_DispatchToMainThread(task); + return; + } + + typedef mozilla::dom::GMPCapabilityData GMPCapabilityData; + typedef mozilla::dom::GMPAPITags GMPAPITags; + typedef mozilla::dom::ContentParent ContentParent; + + nsTArray<GMPCapabilityData> caps; + { + MutexAutoLock lock(mMutex); + for (const RefPtr<GMPParent>& gmp : mPlugins) { + // We have multiple instances of a GMPParent for a given GMP in the + // list, one per origin. So filter the list so that we don't include + // the same GMP's capabilities twice. + NS_ConvertUTF16toUTF8 name(gmp->GetPluginBaseName()); + bool found = false; + for (const GMPCapabilityData& cap : caps) { + if (cap.name().Equals(name)) { + found = true; + break; + } + } + if (found) { + continue; + } + GMPCapabilityData x; + x.name() = name; + x.version() = gmp->GetVersion(); + for (const GMPCapability& tag : gmp->GetCapabilities()) { + x.capabilities().AppendElement(GMPAPITags(tag.mAPIName, tag.mAPITags)); + } + caps.AppendElement(Move(x)); + } + } + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendGMPsChanged(caps); + } + + // For non-e10s, we must fire a notification so that any MediaKeySystemAccess + // requests waiting on a CDM to download will retry. + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + if (obsService) { + obsService->NotifyObservers(nullptr, "gmp-changed", nullptr); + } +} + +RefPtr<GenericPromise> +GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(const nsAString& aDirectory) +{ + RefPtr<AbstractThread> thread(GetAbstractGMPThread()); + if (!thread) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + nsString dir(aDirectory); + RefPtr<GeckoMediaPluginServiceParent> self = this; + return InvokeAsync(thread, this, __func__, &GeckoMediaPluginServiceParent::AddOnGMPThread, dir) + ->Then(AbstractThread::MainThread(), __func__, + [dir, self]() -> void { + LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s succeeded", + NS_ConvertUTF16toUTF8(dir).get())); + MOZ_ASSERT(NS_IsMainThread()); + self->UpdateContentProcessGMPCapabilities(); + }, + [dir]() -> void { + LOGD(("GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s failed", + NS_ConvertUTF16toUTF8(dir).get())); + }) + ->CompletionPromise(); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory) +{ + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<GenericPromise> p = AsyncAddPluginDirectory(aDirectory); + Unused << p; + return NS_OK; +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::RemovePluginDirectory(const nsAString& aDirectory) +{ + MOZ_ASSERT(NS_IsMainThread()); + return GMPDispatch(new PathRunnable(this, aDirectory, + PathRunnable::EOperation::REMOVE)); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::RemoveAndDeletePluginDirectory( + const nsAString& aDirectory, const bool aDefer) +{ + MOZ_ASSERT(NS_IsMainThread()); + return GMPDispatch( + new PathRunnable(this, aDirectory, + PathRunnable::EOperation::REMOVE_AND_DELETE_FROM_DISK, + aDefer)); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::HasPluginForAPI(const nsACString& aAPI, + nsTArray<nsCString>* aTags, + bool* aHasPlugin) +{ + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aHasPlugin); + + nsresult rv = EnsurePluginsOnDiskScanned(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to load GMPs from disk."); + return rv; + } + + { + MutexAutoLock lock(mMutex); + nsCString api(aAPI); + size_t index = 0; + RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, *aTags, &index); + *aHasPlugin = !!gmp; + } + + return NS_OK; +} + +nsresult +GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned() +{ + const char* env = nullptr; + if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) { + // We have a MOZ_GMP_PATH environment variable which may specify the + // location of plugins to load, and we haven't yet scanned the disk to + // see if there are plugins there. Get the GMP thread, which will + // cause an event to be dispatched to which scans for plugins. We + // dispatch a sync event to the GMP thread here in order to wait until + // after the GMP thread has scanned any paths in MOZ_GMP_PATH. + nsresult rv = GMPDispatch(new mozilla::Runnable(), NS_DISPATCH_SYNC); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now"); + } + + return NS_OK; +} + +already_AddRefed<GMPParent> +GeckoMediaPluginServiceParent::FindPluginForAPIFrom(size_t aSearchStartIndex, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + size_t* aOutPluginIndex) +{ + mMutex.AssertCurrentThreadOwns(); + for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) { + RefPtr<GMPParent> gmp = mPlugins[i]; + if (!GMPCapability::Supports(gmp->GetCapabilities(), aAPI, aTags)) { + continue; + } + if (aOutPluginIndex) { + *aOutPluginIndex = i; + } + return gmp.forget(); + } + return nullptr; +} + +already_AddRefed<GMPParent> +GeckoMediaPluginServiceParent::SelectPluginForAPI(const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread, + "Can't clone GMP plugins on non-GMP threads."); + + GMPParent* gmpToClone = nullptr; + { + MutexAutoLock lock(mMutex); + size_t index = 0; + RefPtr<GMPParent> gmp; + while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) { + if (aNodeId.IsEmpty()) { + if (gmp->CanBeSharedCrossNodeIds()) { + return gmp.forget(); + } + } else if (gmp->CanBeUsedFrom(aNodeId)) { + return gmp.forget(); + } + + if (!gmpToClone || + (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) { + // This GMP has the correct type but has the wrong nodeId; hold on to it + // in case we need to clone it. + // Prefer GMPs in-use for the case where an upgraded plugin version is + // waiting for the old one to die. If the old plugin is in use, we + // should continue using it so that any persistent state remains + // consistent. Otherwise, just check that the plugin isn't scheduled + // for deletion. + gmpToClone = gmp; + } + // Loop around and try the next plugin; it may be usable from aNodeId. + index++; + } + } + + // Plugin exists, but we can't use it due to cross-origin separation. Create a + // new one. + if (gmpToClone) { + RefPtr<GMPParent> clone = ClonePlugin(gmpToClone); + { + MutexAutoLock lock(mMutex); + mPlugins.AppendElement(clone); + } + if (!aNodeId.IsEmpty()) { + clone->SetNodeId(aNodeId); + } + return clone.forget(); + } + + return nullptr; +} + +RefPtr<GMPParent> +CreateGMPParent() +{ + return new GMPParent(); +} + +already_AddRefed<GMPParent> +GeckoMediaPluginServiceParent::ClonePlugin(const GMPParent* aOriginal) +{ + MOZ_ASSERT(aOriginal); + + RefPtr<GMPParent> gmp = CreateGMPParent(); + nsresult rv = gmp ? gmp->CloneFrom(aOriginal) : NS_ERROR_NOT_AVAILABLE; + + if (NS_FAILED(rv)) { + NS_WARNING("Can't Create GMPParent"); + return nullptr; + } + + return gmp.forget(); +} + +RefPtr<GenericPromise> +GeckoMediaPluginServiceParent::AddOnGMPThread(nsString aDirectory) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + nsCString dir = NS_ConvertUTF16toUTF8(aDirectory); + RefPtr<AbstractThread> thread(GetAbstractGMPThread()); + if (!thread) { + LOGD(("%s::%s: %s No GMP Thread", __CLASS__, __FUNCTION__, dir.get())); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, dir.get())); + + nsCOMPtr<nsIFile> directory; + nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + RefPtr<GMPParent> gmp = CreateGMPParent(); + if (!gmp) { + NS_WARNING("Can't Create GMPParent"); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + RefPtr<GeckoMediaPluginServiceParent> self(this); + return gmp->Init(this, directory)->Then(thread, __func__, + [gmp, self, dir]() -> void { + LOGD(("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__, dir.get())); + { + MutexAutoLock lock(self->mMutex); + self->mPlugins.AppendElement(gmp); + } + }, + [dir]() -> void { + LOGD(("%s::%s: %s Failed", __CLASS__, __FUNCTION__, dir.get())); + }) + ->CompletionPromise(); +} + +void +GeckoMediaPluginServiceParent::RemoveOnGMPThread(const nsAString& aDirectory, + const bool aDeleteFromDisk, + const bool aCanDefer) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get())); + + nsCOMPtr<nsIFile> directory; + nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Plugin destruction can modify |mPlugins|. Put them aside for now and + // destroy them once we're done with |mPlugins|. + nsTArray<RefPtr<GMPParent>> deadPlugins; + + bool inUse = false; + MutexAutoLock lock(mMutex); + for (size_t i = mPlugins.Length(); i-- > 0; ) { + nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory(); + bool equals; + if (NS_FAILED(directory->Equals(pluginpath, &equals)) || !equals) { + continue; + } + + RefPtr<GMPParent> gmp = mPlugins[i]; + if (aDeleteFromDisk && gmp->State() != GMPStateNotLoaded) { + // We have to wait for the child process to release its lib handle + // before we can delete the GMP. + inUse = true; + gmp->MarkForDeletion(); + + if (!mPluginsWaitingForDeletion.Contains(aDirectory)) { + mPluginsWaitingForDeletion.AppendElement(aDirectory); + } + } + + if (gmp->State() == GMPStateNotLoaded || !aCanDefer) { + // GMP not in use or shutdown is being forced; can shut it down now. + deadPlugins.AppendElement(gmp); + mPlugins.RemoveElementAt(i); + } + } + + { + MutexAutoUnlock unlock(mMutex); + for (auto& gmp : deadPlugins) { + gmp->AbortAsyncShutdown(); + gmp->CloseActive(true); + } + } + + if (aDeleteFromDisk && !inUse) { + // Ensure the GMP dir and all files in it are writable, so we have + // permission to delete them. + directory->SetPermissions(0700); + DirectoryEnumerator iter(directory, DirectoryEnumerator::FilesAndDirs); + for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { + dirEntry->SetPermissions(0700); + } + if (NS_SUCCEEDED(directory->Remove(true))) { + mPluginsWaitingForDeletion.RemoveElement(aDirectory); + NS_DispatchToMainThread(new NotifyObserversTask("gmp-directory-deleted", + nsString(aDirectory)), + NS_DISPATCH_NORMAL); + } + } +} + +// May remove when Bug 1043671 is fixed +static void Dummy(RefPtr<GMPParent>& aOnDeathsDoor) +{ + // exists solely to do nothing and let the Runnable kill the GMPParent + // when done. +} + +void +GeckoMediaPluginServiceParent::PluginTerminated(const RefPtr<GMPParent>& aPlugin) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + + if (aPlugin->IsMarkedForDeletion()) { + nsCString path8; + RefPtr<nsIFile> dir = aPlugin->GetDirectory(); + nsresult rv = dir->GetNativePath(path8); + NS_ENSURE_SUCCESS_VOID(rv); + + nsString path = NS_ConvertUTF8toUTF16(path8); + if (mPluginsWaitingForDeletion.Contains(path)) { + RemoveOnGMPThread(path, true /* delete */, true /* can defer */); + } + } +} + +void +GeckoMediaPluginServiceParent::ReAddOnGMPThread(const RefPtr<GMPParent>& aOld) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, (void*) aOld)); + + RefPtr<GMPParent> gmp; + if (!mShuttingDownOnGMPThread) { + // We're not shutting down, so replace the old plugin in the list with a + // clone which is in a pristine state. Note: We place the plugin in + // the same slot in the array as a hack to ensure if we re-request with + // the same capabilities we get an instance of the same plugin. + gmp = ClonePlugin(aOld); + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mPlugins.Contains(aOld)); + if (mPlugins.Contains(aOld)) { + mPlugins[mPlugins.IndexOf(aOld)] = gmp; + } + } else { + // We're shutting down; don't re-add plugin, let the old plugin die. + MutexAutoLock lock(mMutex); + mPlugins.RemoveElement(aOld); + } + // Schedule aOld to be destroyed. We can't destroy it from here since we + // may be inside ActorDestroyed() for it. + NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld)); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::GetStorageDir(nsIFile** aOutFile) +{ + if (NS_WARN_IF(!mStorageBaseDir)) { + return NS_ERROR_FAILURE; + } + return mStorageBaseDir->Clone(aOutFile); +} + +static nsresult +WriteToFile(nsIFile* aPath, + const nsCString& aFileName, + const nsCString& aData) +{ + nsCOMPtr<nsIFile> path; + nsresult rv = aPath->Clone(getter_AddRefs(path)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = path->AppendNative(aFileName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PRFileDesc* f = nullptr; + rv = path->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, PR_IRWXU, &f); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int32_t len = PR_Write(f, aData.get(), aData.Length()); + PR_Close(f); + if (NS_WARN_IF(len < 0 || (size_t)len != aData.Length())) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static nsresult +ReadFromFile(nsIFile* aPath, + const nsACString& aFileName, + nsACString& aOutData, + int32_t aMaxLength) +{ + nsCOMPtr<nsIFile> path; + nsresult rv = aPath->Clone(getter_AddRefs(path)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = path->AppendNative(aFileName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PRFileDesc* f = nullptr; + rv = path->OpenNSPRFileDesc(PR_RDONLY | PR_CREATE_FILE, PR_IRWXU, &f); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + auto size = PR_Seek(f, 0, PR_SEEK_END); + PR_Seek(f, 0, PR_SEEK_SET); + + if (size > aMaxLength) { + return NS_ERROR_FAILURE; + } + aOutData.SetLength(size); + + auto len = PR_Read(f, aOutData.BeginWriting(), size); + PR_Close(f); + if (NS_WARN_IF(len != size)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +ReadSalt(nsIFile* aPath, nsACString& aOutData) +{ + return ReadFromFile(aPath, NS_LITERAL_CSTRING("salt"), + aOutData, NodeIdSaltLength); + +} + +already_AddRefed<GMPStorage> +GeckoMediaPluginServiceParent::GetMemoryStorageFor(const nsACString& aNodeId) +{ + RefPtr<GMPStorage> s; + if (!mTempGMPStorage.Get(aNodeId, getter_AddRefs(s))) { + s = CreateGMPMemoryStorage(); + mTempGMPStorage.Put(aNodeId, s); + } + return s.forget(); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::IsPersistentStorageAllowed(const nsACString& aNodeId, + bool* aOutAllowed) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + NS_ENSURE_ARG(aOutAllowed); + // We disallow persistent storage for the NodeId used for shared GMP + // decoding, to prevent GMP decoding being used to track what a user + // watches somehow. + *aOutAllowed = !aNodeId.Equals(SHARED_GMP_DECODING_NODE_ID) && + mPersistentStorageAllowed.Get(aNodeId); + return NS_OK; +} + +nsresult +GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsing, + nsACString& aOutId) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__, + NS_ConvertUTF16toUTF8(aOrigin).get(), + NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(), + (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"))); + + nsresult rv; + + if (aOrigin.EqualsLiteral("null") || + aOrigin.IsEmpty() || + aTopLevelOrigin.EqualsLiteral("null") || + aTopLevelOrigin.IsEmpty()) { + // (origin, topLevelOrigin) is null or empty; this is for an anonymous + // origin, probably a local file, for which we don't provide persistent storage. + // Generate a random node id, and don't store it so that the GMP's storage + // is temporary and the process for this GMP is not shared with GMP + // instances that have the same nodeId. + nsAutoCString salt; + rv = GenerateRandomPathName(salt, NodeIdSaltLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + aOutId = salt; + mPersistentStorageAllowed.Put(salt, false); + return NS_OK; + } + + const uint32_t hash = AddToHash(HashString(aOrigin), + HashString(aTopLevelOrigin)); + + if (aInPrivateBrowsing) { + // For PB mode, we store the node id, indexed by the origin pair and GMP name, + // so that if the same origin pair is opened for the same GMP in this session, + // it gets the same node id. + const uint32_t pbHash = AddToHash(HashString(aGMPName), hash); + nsCString* salt = nullptr; + if (!(salt = mTempNodeIds.Get(pbHash))) { + // No salt stored, generate and temporarily store some for this id. + nsAutoCString newSalt; + rv = GenerateRandomPathName(newSalt, NodeIdSaltLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + salt = new nsCString(newSalt); + mTempNodeIds.Put(pbHash, salt); + mPersistentStorageAllowed.Put(*salt, false); + } + aOutId = *salt; + return NS_OK; + } + + // Otherwise, try to see if we've previously generated and stored salt + // for this origin pair. + nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/ + rv = GetStorageDir(getter_AddRefs(path)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = path->Append(aGMPName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // $profileDir/gmp/$platform/$gmpName/ + rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = path->AppendNative(NS_LITERAL_CSTRING("id")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // $profileDir/gmp/$platform/$gmpName/id/ + rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString hashStr; + hashStr.AppendInt((int64_t)hash); + + // $profileDir/gmp/$platform/$gmpName/id/$hash + rv = path->AppendNative(hashStr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> saltFile; + rv = path->Clone(getter_AddRefs(saltFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = saltFile->AppendNative(NS_LITERAL_CSTRING("salt")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString salt; + bool exists = false; + rv = saltFile->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!exists) { + // No stored salt for this origin. Generate salt, and store it and + // the origin on disk. + nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(salt.Length() == NodeIdSaltLength); + + // $profileDir/gmp/$platform/$gmpName/id/$hash/salt + rv = WriteToFile(path, NS_LITERAL_CSTRING("salt"), salt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // $profileDir/gmp/$platform/$gmpName/id/$hash/origin + rv = WriteToFile(path, + NS_LITERAL_CSTRING("origin"), + NS_ConvertUTF16toUTF8(aOrigin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin + rv = WriteToFile(path, + NS_LITERAL_CSTRING("topLevelOrigin"), + NS_ConvertUTF16toUTF8(aTopLevelOrigin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + } else { + rv = ReadSalt(path, salt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + aOutId = salt; + mPersistentStorageAllowed.Put(salt, true); + + return NS_OK; +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsing, + UniquePtr<GetNodeIdCallback>&& aCallback) +{ + nsCString nodeId; + nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, nodeId); + aCallback->Done(rv, nodeId); + return rv; +} + +static bool +ExtractHostName(const nsACString& aOrigin, nsACString& aOutData) +{ + nsCString str; + str.Assign(aOrigin); + int begin = str.Find("://"); + // The scheme is missing! + if (begin == -1) { + return false; + } + + int end = str.RFind(":"); + // Remove the port number + if (end != begin) { + str.SetLength(end); + } + + nsDependentCSubstring host(str, begin + 3); + aOutData.Assign(host); + return true; +} + +bool +MatchOrigin(nsIFile* aPath, + const nsACString& aSite, + const mozilla::OriginAttributesPattern& aPattern) +{ + // http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax + static const uint32_t MaxDomainLength = 253; + + nsresult rv; + nsCString str; + nsCString originNoSuffix; + mozilla::PrincipalOriginAttributes originAttributes; + + rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("origin"), str, MaxDomainLength); + if (!originAttributes.PopulateFromOrigin(str, originNoSuffix)) { + // Fails on parsing the originAttributes, treat this as a non-match. + return false; + } + + if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) && str.Equals(aSite) && + aPattern.Matches(originAttributes)) { + return true; + } + + mozilla::PrincipalOriginAttributes topLevelOriginAttributes; + rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("topLevelOrigin"), str, MaxDomainLength); + if (!topLevelOriginAttributes.PopulateFromOrigin(str, originNoSuffix)) { + // Fails on paring the originAttributes, treat this as a non-match. + return false; + } + + if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) && str.Equals(aSite) && + aPattern.Matches(topLevelOriginAttributes)) { + return true; + } + return false; +} + +template<typename T> static void +KillPlugins(const nsTArray<RefPtr<GMPParent>>& aPlugins, + Mutex& aMutex, T&& aFilter) +{ + // Shutdown the plugins when |aFilter| evaluates to true. + // After we clear storage data, node IDs will become invalid and shouldn't be + // used anymore. We need to kill plugins with such nodeIDs. + // Note: we can't shut them down while holding the lock, + // as the lock is not re-entrant and shutdown requires taking the lock. + // The plugin list is only edited on the GMP thread, so this should be OK. + nsTArray<RefPtr<GMPParent>> pluginsToKill; + { + MutexAutoLock lock(aMutex); + for (size_t i = 0; i < aPlugins.Length(); i++) { + RefPtr<GMPParent> parent(aPlugins[i]); + if (aFilter(parent)) { + pluginsToKill.AppendElement(parent); + } + } + } + + for (size_t i = 0; i < pluginsToKill.Length(); i++) { + pluginsToKill[i]->CloseActive(false); + // Abort async shutdown because we're going to wipe the plugin's storage, + // so we don't want it writing more data in its async shutdown path. + pluginsToKill[i]->AbortAsyncShutdown(); + } +} + +static nsresult +DeleteDir(nsIFile* aPath) +{ + bool exists = false; + nsresult rv = aPath->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + if (exists) { + return aPath->Remove(true); + } + return NS_OK; +} + +struct NodeFilter { + explicit NodeFilter(const nsTArray<nsCString>& nodeIDs) : mNodeIDs(nodeIDs) {} + bool operator()(GMPParent* aParent) { + return mNodeIDs.Contains(aParent->GetNodeId()); + } +private: + const nsTArray<nsCString>& mNodeIDs; +}; + +void +GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(DirectoryFilter& aFilter) +{ + // $profileDir/gmp/$platform/ + nsCOMPtr<nsIFile> path; + nsresult rv = GetStorageDir(getter_AddRefs(path)); + if (NS_FAILED(rv)) { + return; + } + + // Iterate all sub-folders of $profileDir/gmp/$platform/, i.e. the dirs in which + // specific GMPs store their data. + DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly); + for (nsCOMPtr<nsIFile> pluginDir; (pluginDir = iter.Next()) != nullptr;) { + ClearNodeIdAndPlugin(pluginDir, aFilter); + } +} + +void +GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir, + DirectoryFilter& aFilter) +{ + // $profileDir/gmp/$platform/$gmpName/id/ + nsCOMPtr<nsIFile> path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("id")); + if (!path) { + return; + } + + // Iterate all sub-folders of $profileDir/gmp/$platform/$gmpName/id/ + nsTArray<nsCString> nodeIDsToClear; + DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly); + for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { + // dirEntry is the hash of origins, i.e.: + // $profileDir/gmp/$platform/$gmpName/id/$originHash/ + if (!aFilter(dirEntry)) { + continue; + } + nsAutoCString salt; + if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) { + // Keep node IDs to clear data/plugins associated with them later. + nodeIDsToClear.AppendElement(salt); + // Also remove node IDs from the table. + mPersistentStorageAllowed.Remove(salt); + } + // Now we can remove the directory for the origin pair. + if (NS_FAILED(dirEntry->Remove(true))) { + NS_WARNING("Failed to delete the directory for the origin pair"); + } + } + + // Kill plugin instances that have node IDs being cleared. + KillPlugins(mPlugins, mMutex, NodeFilter(nodeIDsToClear)); + + // Clear all storage in $profileDir/gmp/$platform/$gmpName/storage/$nodeId/ + path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("storage")); + if (!path) { + return; + } + + for (const nsCString& nodeId : nodeIDsToClear) { + nsCOMPtr<nsIFile> dirEntry; + nsresult rv = path->Clone(getter_AddRefs(dirEntry)); + if (NS_FAILED(rv)) { + continue; + } + + rv = dirEntry->AppendNative(nodeId); + if (NS_FAILED(rv)) { + continue; + } + + if (NS_FAILED(DeleteDir(dirEntry))) { + NS_WARNING("Failed to delete GMP storage directory for the node"); + } + } +} + +void +GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread(const nsACString& aSite, + const mozilla::OriginAttributesPattern& aPattern) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + LOGD(("%s::%s: origin=%s", __CLASS__, __FUNCTION__, aSite.Data())); + + struct OriginFilter : public DirectoryFilter { + explicit OriginFilter(const nsACString& aSite, + const mozilla::OriginAttributesPattern& aPattern) + : mSite(aSite) + , mPattern(aPattern) + { } + bool operator()(nsIFile* aPath) override { + return MatchOrigin(aPath, mSite, mPattern); + } + private: + const nsACString& mSite; + const mozilla::OriginAttributesPattern& mPattern; + } filter(aSite, aPattern); + + ClearNodeIdAndPlugin(filter); +} + +void +GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread(PRTime aSince) +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + LOGD(("%s::%s: since=%lld", __CLASS__, __FUNCTION__, (int64_t)aSince)); + + struct MTimeFilter : public DirectoryFilter { + explicit MTimeFilter(PRTime aSince) + : mSince(aSince) {} + + // Return true if any files under aPath is modified after |mSince|. + bool IsModifiedAfter(nsIFile* aPath) { + PRTime lastModified; + nsresult rv = aPath->GetLastModifiedTime(&lastModified); + if (NS_SUCCEEDED(rv) && lastModified >= mSince) { + return true; + } + DirectoryEnumerator iter(aPath, DirectoryEnumerator::FilesAndDirs); + for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { + if (IsModifiedAfter(dirEntry)) { + return true; + } + } + return false; + } + + // |aPath| is $profileDir/gmp/$platform/$gmpName/id/$originHash/ + bool operator()(nsIFile* aPath) override { + if (IsModifiedAfter(aPath)) { + return true; + } + + nsAutoCString salt; + if (NS_FAILED(ReadSalt(aPath, salt))) { + return false; + } + + // $profileDir/gmp/$platform/$gmpName/id/ + nsCOMPtr<nsIFile> idDir; + if (NS_FAILED(aPath->GetParent(getter_AddRefs(idDir)))) { + return false; + } + // $profileDir/gmp/$platform/$gmpName/ + nsCOMPtr<nsIFile> temp; + if (NS_FAILED(idDir->GetParent(getter_AddRefs(temp)))) { + return false; + } + + // $profileDir/gmp/$platform/$gmpName/storage/ + if (NS_FAILED(temp->Append(NS_LITERAL_STRING("storage")))) { + return false; + } + // $profileDir/gmp/$platform/$gmpName/storage/$originSalt + return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp); + } + private: + const PRTime mSince; + } filter(aSince); + + ClearNodeIdAndPlugin(filter); + + NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::ForgetThisSite(const nsAString& aSite, + const nsAString& aPattern) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mozilla::OriginAttributesPattern pattern; + + if (!pattern.Init(aPattern)) { + return NS_ERROR_INVALID_ARG; + } + + return ForgetThisSiteNative(aSite, pattern); +} + +nsresult +GeckoMediaPluginServiceParent::ForgetThisSiteNative(const nsAString& aSite, + const mozilla::OriginAttributesPattern& aPattern) +{ + MOZ_ASSERT(NS_IsMainThread()); + + return GMPDispatch(NewRunnableMethod<nsCString, mozilla::OriginAttributesPattern>( + this, &GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread, + NS_ConvertUTF16toUTF8(aSite), aPattern)); +} + +static bool IsNodeIdValid(GMPParent* aParent) { + return !aParent->GetNodeId().IsEmpty(); +} + +static nsCOMPtr<nsIAsyncShutdownClient> +GetShutdownBarrier() +{ + nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown(); + MOZ_RELEASE_ASSERT(svc); + + nsCOMPtr<nsIAsyncShutdownClient> barrier; + nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier)); + + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + MOZ_RELEASE_ASSERT(barrier); + return barrier.forget(); +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::GetName(nsAString& aName) +{ + aName = NS_LITERAL_STRING("GeckoMediaPluginServiceParent: shutdown"); + return NS_OK; +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::GetState(nsIPropertyBag**) +{ + return NS_OK; +} + +NS_IMETHODIMP +GeckoMediaPluginServiceParent::BlockShutdown(nsIAsyncShutdownClient*) +{ + return NS_OK; +} + +void +GeckoMediaPluginServiceParent::ServiceUserCreated() +{ + MOZ_ASSERT(mServiceUserCount >= 0); + if (++mServiceUserCount == 1) { + nsresult rv = GetShutdownBarrier()->AddBlocker( + this, NS_LITERAL_STRING(__FILE__), __LINE__, + NS_LITERAL_STRING("GeckoMediaPluginServiceParent shutdown")); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + } +} + +void +GeckoMediaPluginServiceParent::ServiceUserDestroyed() +{ + MOZ_ASSERT(mServiceUserCount > 0); + if (--mServiceUserCount == 0) { + nsresult rv = GetShutdownBarrier()->RemoveBlocker(this); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + } +} + +void +GeckoMediaPluginServiceParent::ClearStorage() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); + LOGD(("%s::%s", __CLASS__, __FUNCTION__)); + + // Kill plugins with valid nodeIDs. + KillPlugins(mPlugins, mMutex, &IsNodeIdValid); + + nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/ + nsresult rv = GetStorageDir(getter_AddRefs(path)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (NS_FAILED(DeleteDir(path))) { + NS_WARNING("Failed to delete GMP storage directory"); + } + + // Clear private-browsing storage. + mTempGMPStorage.Clear(); + + NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL); +} + +already_AddRefed<GMPParent> +GeckoMediaPluginServiceParent::GetById(uint32_t aPluginId) +{ + MutexAutoLock lock(mMutex); + for (const RefPtr<GMPParent>& gmp : mPlugins) { + if (gmp->GetPluginId() == aPluginId) { + return do_AddRef(gmp); + } + } + return nullptr; +} + +GMPServiceParent::~GMPServiceParent() +{ + NS_DispatchToMainThread( + NewRunnableMethod(mService.get(), + &GeckoMediaPluginServiceParent::ServiceUserDestroyed)); +} + +bool +GMPServiceParent::RecvSelectGMP(const nsCString& aNodeId, + const nsCString& aAPI, + nsTArray<nsCString>&& aTags, + uint32_t* aOutPluginId, + nsresult* aOutRv) +{ + if (mService->IsShuttingDown()) { + *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + return true; + } + + RefPtr<GMPParent> gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags); + if (gmp) { + *aOutPluginId = gmp->GetPluginId(); + *aOutRv = NS_OK; + } else { + *aOutRv = NS_ERROR_FAILURE; + } + + nsCString api = aTags[0]; + LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get())); + + return true; +} + +bool +GMPServiceParent::RecvLaunchGMP(const uint32_t& aPluginId, + nsTArray<ProcessId>&& aAlreadyBridgedTo, + ProcessId* aOutProcessId, + nsCString* aOutDisplayName, + nsresult* aOutRv) +{ + *aOutRv = NS_OK; + if (mService->IsShuttingDown()) { + *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + return true; + } + + RefPtr<GMPParent> gmp(mService->GetById(aPluginId)); + if (!gmp) { + *aOutRv = NS_ERROR_FAILURE; + return true; + } + + if (!gmp->EnsureProcessLoaded(aOutProcessId)) { + return false; + } + + *aOutDisplayName = gmp->GetDisplayName(); + + return aAlreadyBridgedTo.Contains(*aOutProcessId) || gmp->Bridge(this); +} + +bool +GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin, + const nsString& aTopLevelOrigin, + const nsString& aGMPName, + const bool& aInPrivateBrowsing, + nsCString* aID) +{ + nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, + aInPrivateBrowsing, *aID); + return NS_SUCCEEDED(rv); +} + +class DeleteGMPServiceParent : public mozilla::Runnable +{ +public: + explicit DeleteGMPServiceParent(GMPServiceParent* aToDelete) + : mToDelete(aToDelete) + { + } + + NS_IMETHOD Run() override + { + return NS_OK; + } + +private: + nsAutoPtr<GMPServiceParent> mToDelete; +}; + +void GMPServiceParent::CloseTransport(Monitor* aSyncMonitor, bool* aCompleted) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + MonitorAutoLock lock(*aSyncMonitor); + + // This deletes the transport. + SetTransport(nullptr); + + *aCompleted = true; + lock.NotifyAll(); +} + +void +GMPServiceParent::ActorDestroy(ActorDestroyReason aWhy) +{ + Monitor monitor("DeleteGMPServiceParent"); + bool completed = false; + + // Make sure the IPC channel is closed before destroying mToDelete. + MonitorAutoLock lock(monitor); + RefPtr<Runnable> task = + NewNonOwningRunnableMethod<Monitor*, bool*>(this, + &GMPServiceParent::CloseTransport, + &monitor, + &completed); + XRE_GetIOMessageLoop()->PostTask(Move(task.forget())); + + while (!completed) { + lock.Wait(); + } + + NS_DispatchToCurrentThread(new DeleteGMPServiceParent(this)); +} + +class OpenPGMPServiceParent : public mozilla::Runnable +{ +public: + OpenPGMPServiceParent(GMPServiceParent* aGMPServiceParent, + mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid, + bool* aResult) + : mGMPServiceParent(aGMPServiceParent), + mTransport(aTransport), + mOtherPid(aOtherPid), + mResult(aResult) + { + } + + NS_IMETHOD Run() override + { + *mResult = mGMPServiceParent->Open(mTransport, mOtherPid, + XRE_GetIOMessageLoop(), ipc::ParentSide); + return NS_OK; + } + +private: + GMPServiceParent* mGMPServiceParent; + mozilla::ipc::Transport* mTransport; + base::ProcessId mOtherPid; + bool* mResult; +}; + +/* static */ +PGMPServiceParent* +GMPServiceParent::Create(Transport* aTransport, ProcessId aOtherPid) +{ + RefPtr<GeckoMediaPluginServiceParent> gmp = + GeckoMediaPluginServiceParent::GetSingleton(); + + if (gmp->mShuttingDown) { + // Shutdown is initiated. There is no point creating a new actor. + return nullptr; + } + + nsCOMPtr<nsIThread> gmpThread; + nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsAutoPtr<GMPServiceParent> serviceParent(new GMPServiceParent(gmp)); + + bool ok; + rv = gmpThread->Dispatch(new OpenPGMPServiceParent(serviceParent, + aTransport, + aOtherPid, &ok), + NS_DISPATCH_SYNC); + if (NS_FAILED(rv) || !ok) { + return nullptr; + } + + return serviceParent.forget(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPServiceParent.h b/dom/media/gmp/GMPServiceParent.h new file mode 100644 index 000000000..49d81055b --- /dev/null +++ b/dom/media/gmp/GMPServiceParent.h @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPServiceParent_h_ +#define GMPServiceParent_h_ + +#include "GMPService.h" +#include "mozilla/gmp/PGMPServiceParent.h" +#include "mozIGeckoMediaPluginChromeService.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "mozilla/Atomics.h" +#include "nsIAsyncShutdown.h" +#include "nsThreadUtils.h" +#include "mozilla/MozPromise.h" +#include "GMPStorage.h" + +template <class> struct already_AddRefed; + +namespace mozilla { +namespace gmp { + +class GMPParent; + +class GeckoMediaPluginServiceParent final : public GeckoMediaPluginService + , public mozIGeckoMediaPluginChromeService + , public nsIAsyncShutdownBlocker +{ +public: + static already_AddRefed<GeckoMediaPluginServiceParent> GetSingleton(); + + GeckoMediaPluginServiceParent(); + nsresult Init() override; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIASYNCSHUTDOWNBLOCKER + + // mozIGeckoMediaPluginService + NS_IMETHOD HasPluginForAPI(const nsACString& aAPI, + nsTArray<nsCString>* aTags, + bool *aRetVal) override; + NS_IMETHOD GetNodeId(const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsingMode, + UniquePtr<GetNodeIdCallback>&& aCallback) override; + + NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE + NS_DECL_NSIOBSERVER + + void AsyncShutdownNeeded(GMPParent* aParent); + void AsyncShutdownComplete(GMPParent* aParent); + + int32_t AsyncShutdownTimeoutMs(); + RefPtr<GenericPromise> EnsureInitialized(); + RefPtr<GenericPromise> AsyncAddPluginDirectory(const nsAString& aDirectory); + + // GMP thread access only + bool IsShuttingDown(); + + already_AddRefed<GMPStorage> GetMemoryStorageFor(const nsACString& aNodeId); + nsresult ForgetThisSiteNative(const nsAString& aSite, + const mozilla::OriginAttributesPattern& aPattern); + + // Notifies that some user of this class is created/destroyed. + void ServiceUserCreated(); + void ServiceUserDestroyed(); + + void UpdateContentProcessGMPCapabilities(); + +private: + friend class GMPServiceParent; + + virtual ~GeckoMediaPluginServiceParent(); + + void ClearStorage(); + + already_AddRefed<GMPParent> SelectPluginForAPI(const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags); + + already_AddRefed<GMPParent> FindPluginForAPIFrom(size_t aSearchStartIndex, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + size_t* aOutPluginIndex); + + nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, + const nsAString& aGMPName, + bool aInPrivateBrowsing, nsACString& aOutId); + + void UnloadPlugins(); + void CrashPlugins(); + void NotifySyncShutdownComplete(); + void NotifyAsyncShutdownComplete(); + + void ProcessPossiblePlugin(nsIFile* aDir); + + void RemoveOnGMPThread(const nsAString& aDirectory, + const bool aDeleteFromDisk, + const bool aCanDefer); + + nsresult SetAsyncShutdownTimeout(); + + struct DirectoryFilter { + virtual bool operator()(nsIFile* aPath) = 0; + ~DirectoryFilter() {} + }; + void ClearNodeIdAndPlugin(DirectoryFilter& aFilter); + void ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir, + DirectoryFilter& aFilter); + void ForgetThisSiteOnGMPThread(const nsACString& aOrigin, + const mozilla::OriginAttributesPattern& aPattern); + void ClearRecentHistoryOnGMPThread(PRTime aSince); + + already_AddRefed<GMPParent> GetById(uint32_t aPluginId); + +protected: + friend class GMPParent; + void ReAddOnGMPThread(const RefPtr<GMPParent>& aOld); + void PluginTerminated(const RefPtr<GMPParent>& aOld); + void InitializePlugins(AbstractThread* aAbstractGMPThread) override; + RefPtr<GenericPromise::AllPromiseType> LoadFromEnvironment(); + RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory); + bool GetContentParentFrom(GMPCrashHelper* aHelper, + const nsACString& aNodeId, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags, + UniquePtr<GetGMPContentParentCallback>&& aCallback) + override; +private: + // Creates a copy of aOriginal. Note that the caller is responsible for + // adding this to GeckoMediaPluginServiceParent::mPlugins. + already_AddRefed<GMPParent> ClonePlugin(const GMPParent* aOriginal); + nsresult EnsurePluginsOnDiskScanned(); + nsresult InitStorage(); + + class PathRunnable : public Runnable + { + public: + enum EOperation { + REMOVE, + REMOVE_AND_DELETE_FROM_DISK, + }; + + PathRunnable(GeckoMediaPluginServiceParent* aService, const nsAString& aPath, + EOperation aOperation, bool aDefer = false) + : mService(aService) + , mPath(aPath) + , mOperation(aOperation) + , mDefer(aDefer) + { } + + NS_DECL_NSIRUNNABLE + + private: + RefPtr<GeckoMediaPluginServiceParent> mService; + nsString mPath; + EOperation mOperation; + bool mDefer; + }; + + // Protected by mMutex from the base class. + nsTArray<RefPtr<GMPParent>> mPlugins; + bool mShuttingDown; + nsTArray<RefPtr<GMPParent>> mAsyncShutdownPlugins; + + // True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any + // plugins found there into mPlugins. + Atomic<bool> mScannedPluginOnDisk; + + template<typename T> + class MainThreadOnly { + public: + MOZ_IMPLICIT MainThreadOnly(T aValue) + : mValue(aValue) + {} + operator T&() { + MOZ_ASSERT(NS_IsMainThread()); + return mValue; + } + + private: + T mValue; + }; + + MainThreadOnly<bool> mWaitingForPluginsSyncShutdown; + + nsTArray<nsString> mPluginsWaitingForDeletion; + + nsCOMPtr<nsIFile> mStorageBaseDir; + + // Hashes of (origin,topLevelOrigin) to the node id for + // non-persistent sessions. + nsClassHashtable<nsUint32HashKey, nsCString> mTempNodeIds; + + // Hashes node id to whether that node id is allowed to store data + // persistently on disk. + nsDataHashtable<nsCStringHashKey, bool> mPersistentStorageAllowed; + + // Synchronization for barrier that ensures we've loaded GMPs from + // MOZ_GMP_PATH before allowing GetContentParentFrom() to proceed. + Monitor mInitPromiseMonitor; + MozPromiseHolder<GenericPromise> mInitPromise; + bool mLoadPluginsFromDiskComplete; + + // Hashes nodeId to the hashtable of storage for that nodeId. + nsRefPtrHashtable<nsCStringHashKey, GMPStorage> mTempGMPStorage; + + // Tracks how many users are running (on the GMP thread). Only when this count + // drops to 0 can we safely shut down the thread. + MainThreadOnly<int32_t> mServiceUserCount; +}; + +nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData); +bool MatchOrigin(nsIFile* aPath, + const nsACString& aSite, + const mozilla::OriginAttributesPattern& aPattern); + +class GMPServiceParent final : public PGMPServiceParent +{ +public: + explicit GMPServiceParent(GeckoMediaPluginServiceParent* aService) + : mService(aService) + { + mService->ServiceUserCreated(); + } + virtual ~GMPServiceParent(); + + bool RecvGetGMPNodeId(const nsString& aOrigin, + const nsString& aTopLevelOrigin, + const nsString& aGMPName, + const bool& aInPrivateBrowsing, + nsCString* aID) override; + void ActorDestroy(ActorDestroyReason aWhy) override; + + static PGMPServiceParent* Create(Transport* aTransport, ProcessId aOtherPid); + + bool RecvSelectGMP(const nsCString& aNodeId, + const nsCString& aAPI, + nsTArray<nsCString>&& aTags, + uint32_t* aOutPluginId, + nsresult* aOutRv) override; + + bool RecvLaunchGMP(const uint32_t& aPluginId, + nsTArray<ProcessId>&& aAlreadyBridgedTo, + ProcessId* aOutID, + nsCString* aOutDisplayName, + nsresult* aOutRv) override; + +private: + void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted); + + RefPtr<GeckoMediaPluginServiceParent> mService; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPServiceParent_h_ diff --git a/dom/media/gmp/GMPSharedMemManager.cpp b/dom/media/gmp/GMPSharedMemManager.cpp new file mode 100644 index 000000000..86b5f9810 --- /dev/null +++ b/dom/media/gmp/GMPSharedMemManager.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPSharedMemManager.h" +#include "GMPMessageUtils.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ClearOnShutdown.h" + +namespace mozilla { +namespace gmp { + +// Really one set of pools on each side of the plugin API. + +// YUV buffers go from Encoder parent to child; pool there, and then return +// with Decoded() frames to the Decoder parent and goes into the parent pool. +// Compressed (encoded) data goes from the Decoder parent to the child; +// pool there, and then return with Encoded() frames and goes into the parent +// pool. +bool +GMPSharedMemManager::MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass, size_t aSize, + ipc::Shmem::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aMem) +{ + mData->CheckThread(); + + // first look to see if we have a free buffer large enough + for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) { + MOZ_ASSERT(GetGmpFreelist(aClass)[i].IsWritable()); + if (aSize <= GetGmpFreelist(aClass)[i].Size<uint8_t>()) { + *aMem = GetGmpFreelist(aClass)[i]; + GetGmpFreelist(aClass).RemoveElementAt(i); + return true; + } + } + + // Didn't find a buffer free with enough space; allocate one + size_t pagesize = ipc::SharedMemory::SystemPageSize(); + aSize = (aSize + (pagesize-1)) & ~(pagesize-1); // round up to page size + bool retval = Alloc(aSize, aType, aMem); + if (retval) { + // The allocator (or NeedsShmem call) should never return less than we ask for... + MOZ_ASSERT(aMem->Size<uint8_t>() >= aSize); + mData->mGmpAllocated[aClass]++; + } + return retval; +} + +bool +GMPSharedMemManager::MgrDeallocShmem(GMPSharedMem::GMPMemoryClasses aClass, ipc::Shmem& aMem) +{ + mData->CheckThread(); + + size_t size = aMem.Size<uint8_t>(); + size_t total = 0; + + // XXX Bug NNNNNNN Until we put better guards on ipc::shmem, verify we + // weren't fed an shmem we already had. + for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) { + if (NS_WARN_IF(aMem == GetGmpFreelist(aClass)[i])) { + // Safest to crash in this case; should never happen in normal + // operation. + MOZ_CRASH("Deallocating Shmem we already have in our cache!"); + //return true; + } + } + + // XXX This works; there are better pool algorithms. We need to avoid + // "falling off a cliff" with too low a number + if (GetGmpFreelist(aClass).Length() > 10) { + Dealloc(GetGmpFreelist(aClass)[0]); + GetGmpFreelist(aClass).RemoveElementAt(0); + // The allocation numbers will be fubar on the Child! + mData->mGmpAllocated[aClass]--; + } + for (uint32_t i = 0; i < GetGmpFreelist(aClass).Length(); i++) { + MOZ_ASSERT(GetGmpFreelist(aClass)[i].IsWritable()); + total += GetGmpFreelist(aClass)[i].Size<uint8_t>(); + if (size < GetGmpFreelist(aClass)[i].Size<uint8_t>()) { + GetGmpFreelist(aClass).InsertElementAt(i, aMem); + return true; + } + } + GetGmpFreelist(aClass).AppendElement(aMem); + + return true; +} + +uint32_t +GMPSharedMemManager::NumInUse(GMPSharedMem::GMPMemoryClasses aClass) +{ + return mData->mGmpAllocated[aClass] - GetGmpFreelist(aClass).Length(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPSharedMemManager.h b/dom/media/gmp/GMPSharedMemManager.h new file mode 100644 index 000000000..cc36f3fc0 --- /dev/null +++ b/dom/media/gmp/GMPSharedMemManager.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPSharedMemManager_h_ +#define GMPSharedMemManager_h_ + +#include "mozilla/ipc/Shmem.h" +#include "nsTArray.h" + +namespace mozilla { +namespace gmp { + +class GMPSharedMemManager; + +class GMPSharedMem +{ +public: + typedef enum { + kGMPFrameData = 0, + kGMPEncodedData, + kGMPNumTypes + } GMPMemoryClasses; + + // This is a heuristic - max of 10 free in the Child pool, plus those + // in-use for the encoder and decoder at the given moment and not yet + // returned to the parent pool (which is not included). If more than + // this are needed, we presume the client has either crashed or hung + // (perhaps temporarily). + static const uint32_t kGMPBufLimit = 20; + + GMPSharedMem() + { + for (size_t i = 0; i < sizeof(mGmpAllocated)/sizeof(mGmpAllocated[0]); i++) { + mGmpAllocated[i] = 0; + } + } + virtual ~GMPSharedMem() {} + + // Parent and child impls will differ here + virtual void CheckThread() = 0; + +protected: + friend class GMPSharedMemManager; + + nsTArray<ipc::Shmem> mGmpFreelist[GMPSharedMem::kGMPNumTypes]; + uint32_t mGmpAllocated[GMPSharedMem::kGMPNumTypes]; +}; + +class GMPSharedMemManager +{ +public: + explicit GMPSharedMemManager(GMPSharedMem *aData) : mData(aData) {} + virtual ~GMPSharedMemManager() {} + + virtual bool MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass, size_t aSize, + ipc::Shmem::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aMem); + virtual bool MgrDeallocShmem(GMPSharedMem::GMPMemoryClasses aClass, ipc::Shmem& aMem); + + // So we can know if data is "piling up" for the plugin - I.e. it's hung or crashed + virtual uint32_t NumInUse(GMPSharedMem::GMPMemoryClasses aClass); + + // These have to be implemented using the AllocShmem/etc provided by the IPDL-generated interfaces, + // so have the Parent/Child implement them. + virtual bool Alloc(size_t aSize, ipc::Shmem::SharedMemory::SharedMemoryType aType, ipc::Shmem* aMem) = 0; + virtual void Dealloc(ipc::Shmem& aMem) = 0; + +private: + nsTArray<ipc::Shmem>& GetGmpFreelist(GMPSharedMem::GMPMemoryClasses aTypes) + { + return mData->mGmpFreelist[aTypes]; + } + + GMPSharedMem *mData; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPSharedMemManager_h_ diff --git a/dom/media/gmp/GMPStorage.h b/dom/media/gmp/GMPStorage.h new file mode 100644 index 000000000..db3aebc8c --- /dev/null +++ b/dom/media/gmp/GMPStorage.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPStorage_h_ +#define GMPStorage_h_ + +#include "gmp-storage.h" +#include "mozilla/AlreadyAddRefed.h" +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace gmp { + +class GMPStorage { +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorage) + + virtual GMPErr Open(const nsCString& aRecordName) = 0; + virtual bool IsOpen(const nsCString& aRecordName) const = 0; + virtual GMPErr Read(const nsCString& aRecordName, + nsTArray<uint8_t>& aOutBytes) = 0; + virtual GMPErr Write(const nsCString& aRecordName, + const nsTArray<uint8_t>& aBytes) = 0; + virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) const = 0; + virtual void Close(const nsCString& aRecordName) = 0; +protected: + virtual ~GMPStorage() {} +}; + +already_AddRefed<GMPStorage> CreateGMPMemoryStorage(); +already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId, + const nsString& aGMPName); + +} // namespace gmp +} // namespace mozilla + +#endif
\ No newline at end of file diff --git a/dom/media/gmp/GMPStorageChild.cpp b/dom/media/gmp/GMPStorageChild.cpp new file mode 100644 index 000000000..052b56d1b --- /dev/null +++ b/dom/media/gmp/GMPStorageChild.cpp @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPStorageChild.h" +#include "GMPChild.h" +#include "gmp-storage.h" +#include "base/task.h" + +#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current()) + +#define CALL_ON_GMP_THREAD(_func, ...) \ + do { \ + if (ON_GMP_THREAD()) { \ + _func(__VA_ARGS__); \ + } else { \ + mPlugin->GMPMessageLoop()->PostTask( \ + dont_add_new_uses_of_this::NewRunnableMethod(this, &GMPStorageChild::_func, ##__VA_ARGS__) \ + ); \ + } \ + } while(false) + +static nsTArray<uint8_t> +ToArray(const uint8_t* aData, uint32_t aDataSize) +{ + nsTArray<uint8_t> data; + data.AppendElements(aData, aDataSize); + return mozilla::Move(data); +} + +namespace mozilla { +namespace gmp { + +GMPRecordImpl::GMPRecordImpl(GMPStorageChild* aOwner, + const nsCString& aName, + GMPRecordClient* aClient) + : mName(aName) + , mClient(aClient) + , mOwner(aOwner) +{ +} + +GMPErr +GMPRecordImpl::Open() +{ + return mOwner->Open(this); +} + +void +GMPRecordImpl::OpenComplete(GMPErr aStatus) +{ + mClient->OpenComplete(aStatus); +} + +GMPErr +GMPRecordImpl::Read() +{ + return mOwner->Read(this); +} + +void +GMPRecordImpl::ReadComplete(GMPErr aStatus, + const uint8_t* aBytes, + uint32_t aLength) +{ + mClient->ReadComplete(aStatus, aBytes, aLength); +} + +GMPErr +GMPRecordImpl::Write(const uint8_t* aData, uint32_t aDataSize) +{ + return mOwner->Write(this, aData, aDataSize); +} + +void +GMPRecordImpl::WriteComplete(GMPErr aStatus) +{ + mClient->WriteComplete(aStatus); +} + +GMPErr +GMPRecordImpl::Close() +{ + RefPtr<GMPRecordImpl> kungfuDeathGrip(this); + // Delete our self reference. + Release(); + mOwner->Close(this->Name()); + return GMPNoErr; +} + +GMPStorageChild::GMPStorageChild(GMPChild* aPlugin) + : mMonitor("GMPStorageChild") + , mPlugin(aPlugin) + , mShutdown(false) +{ + MOZ_ASSERT(ON_GMP_THREAD()); +} + +GMPErr +GMPStorageChild::CreateRecord(const nsCString& aRecordName, + GMPRecord** aOutRecord, + GMPRecordClient* aClient) +{ + MonitorAutoLock lock(mMonitor); + + if (mShutdown) { + NS_WARNING("GMPStorage used after it's been shutdown!"); + return GMPClosedErr; + } + + MOZ_ASSERT(aRecordName.Length() && aOutRecord); + + if (HasRecord(aRecordName)) { + return GMPRecordInUse; + } + + RefPtr<GMPRecordImpl> record(new GMPRecordImpl(this, aRecordName, aClient)); + mRecords.Put(aRecordName, record); // Addrefs + + // The GMPRecord holds a self reference until the GMP calls Close() on + // it. This means the object is always valid (even if neutered) while + // the GMP expects it to be. + record.forget(aOutRecord); + + return GMPNoErr; +} + +bool +GMPStorageChild::HasRecord(const nsCString& aRecordName) +{ + mMonitor.AssertCurrentThreadOwns(); + return mRecords.Contains(aRecordName); +} + +already_AddRefed<GMPRecordImpl> +GMPStorageChild::GetRecord(const nsCString& aRecordName) +{ + MonitorAutoLock lock(mMonitor); + RefPtr<GMPRecordImpl> record; + mRecords.Get(aRecordName, getter_AddRefs(record)); + return record.forget(); +} + +GMPErr +GMPStorageChild::Open(GMPRecordImpl* aRecord) +{ + MonitorAutoLock lock(mMonitor); + + if (mShutdown) { + NS_WARNING("GMPStorage used after it's been shutdown!"); + return GMPClosedErr; + } + + if (!HasRecord(aRecord->Name())) { + // Trying to re-open a record that has already been closed. + return GMPClosedErr; + } + + CALL_ON_GMP_THREAD(SendOpen, aRecord->Name()); + + return GMPNoErr; +} + +GMPErr +GMPStorageChild::Read(GMPRecordImpl* aRecord) +{ + MonitorAutoLock lock(mMonitor); + + if (mShutdown) { + NS_WARNING("GMPStorage used after it's been shutdown!"); + return GMPClosedErr; + } + + if (!HasRecord(aRecord->Name())) { + // Record not opened. + return GMPClosedErr; + } + + CALL_ON_GMP_THREAD(SendRead, aRecord->Name()); + + return GMPNoErr; +} + +GMPErr +GMPStorageChild::Write(GMPRecordImpl* aRecord, + const uint8_t* aData, + uint32_t aDataSize) +{ + if (aDataSize > GMP_MAX_RECORD_SIZE) { + return GMPQuotaExceededErr; + } + + MonitorAutoLock lock(mMonitor); + + if (mShutdown) { + NS_WARNING("GMPStorage used after it's been shutdown!"); + return GMPClosedErr; + } + + if (!HasRecord(aRecord->Name())) { + // Record not opened. + return GMPClosedErr; + } + + CALL_ON_GMP_THREAD(SendWrite, aRecord->Name(), ToArray(aData, aDataSize)); + + return GMPNoErr; +} + +GMPErr +GMPStorageChild::Close(const nsCString& aRecordName) +{ + MonitorAutoLock lock(mMonitor); + + if (!HasRecord(aRecordName)) { + // Already closed. + return GMPClosedErr; + } + + mRecords.Remove(aRecordName); + + if (!mShutdown) { + CALL_ON_GMP_THREAD(SendClose, aRecordName); + } + + return GMPNoErr; +} + +bool +GMPStorageChild::RecvOpenComplete(const nsCString& aRecordName, + const GMPErr& aStatus) +{ + // We don't need a lock to read |mShutdown| since it is only changed in + // the GMP thread. + if (mShutdown) { + return true; + } + RefPtr<GMPRecordImpl> record = GetRecord(aRecordName); + if (!record) { + // Not fatal. + return true; + } + record->OpenComplete(aStatus); + return true; +} + +bool +GMPStorageChild::RecvReadComplete(const nsCString& aRecordName, + const GMPErr& aStatus, + InfallibleTArray<uint8_t>&& aBytes) +{ + if (mShutdown) { + return true; + } + RefPtr<GMPRecordImpl> record = GetRecord(aRecordName); + if (!record) { + // Not fatal. + return true; + } + record->ReadComplete(aStatus, aBytes.Elements(), aBytes.Length()); + return true; +} + +bool +GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName, + const GMPErr& aStatus) +{ + if (mShutdown) { + return true; + } + RefPtr<GMPRecordImpl> record = GetRecord(aRecordName); + if (!record) { + // Not fatal. + return true; + } + record->WriteComplete(aStatus); + return true; +} + +GMPErr +GMPStorageChild::EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg) +{ + MonitorAutoLock lock(mMonitor); + + if (mShutdown) { + NS_WARNING("GMPStorage used after it's been shutdown!"); + return GMPClosedErr; + } + + MOZ_ASSERT(aRecvIteratorFunc); + mPendingRecordIterators.push(RecordIteratorContext(aRecvIteratorFunc, aUserArg)); + + CALL_ON_GMP_THREAD(SendGetRecordNames); + + return GMPNoErr; +} + +class GMPRecordIteratorImpl : public GMPRecordIterator { +public: + explicit GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames) + : mRecordNames(aRecordNames) + , mIndex(0) + { + mRecordNames.Sort(); + } + + GMPErr GetName(const char** aOutName, uint32_t* aOutNameLength) override { + if (!aOutName || !aOutNameLength) { + return GMPInvalidArgErr; + } + if (mIndex == mRecordNames.Length()) { + return GMPEndOfEnumeration; + } + *aOutName = mRecordNames[mIndex].get(); + *aOutNameLength = mRecordNames[mIndex].Length(); + return GMPNoErr; + } + + GMPErr NextRecord() override { + if (mIndex < mRecordNames.Length()) { + mIndex++; + } + return (mIndex < mRecordNames.Length()) ? GMPNoErr + : GMPEndOfEnumeration; + } + + void Close() override { + delete this; + } + +private: + nsTArray<nsCString> mRecordNames; + size_t mIndex; +}; + +bool +GMPStorageChild::RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames, + const GMPErr& aStatus) +{ + RecordIteratorContext ctx; + { + MonitorAutoLock lock(mMonitor); + if (mShutdown || mPendingRecordIterators.empty()) { + return true; + } + ctx = mPendingRecordIterators.front(); + mPendingRecordIterators.pop(); + } + + if (GMP_FAILED(aStatus)) { + ctx.mFunc(nullptr, ctx.mUserArg, aStatus); + } else { + ctx.mFunc(new GMPRecordIteratorImpl(aRecordNames), ctx.mUserArg, GMPNoErr); + } + + return true; +} + +bool +GMPStorageChild::RecvShutdown() +{ + // Block any new storage requests, and thus any messages back to the + // parent. We don't delete any objects here, as that may invalidate + // GMPRecord pointers held by the GMP. + MonitorAutoLock lock(mMonitor); + mShutdown = true; + while (!mPendingRecordIterators.empty()) { + mPendingRecordIterators.pop(); + } + return true; +} + +} // namespace gmp +} // namespace mozilla + +// avoid redefined macro in unified build +#undef ON_GMP_THREAD +#undef CALL_ON_GMP_THREAD diff --git a/dom/media/gmp/GMPStorageChild.h b/dom/media/gmp/GMPStorageChild.h new file mode 100644 index 000000000..3c5948a30 --- /dev/null +++ b/dom/media/gmp/GMPStorageChild.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPStorageChild_h_ +#define GMPStorageChild_h_ + +#include "mozilla/gmp/PGMPStorageChild.h" +#include "gmp-storage.h" +#include "nsTHashtable.h" +#include "nsRefPtrHashtable.h" +#include "gmp-platform.h" + +#include <queue> + +namespace mozilla { +namespace gmp { + +class GMPChild; +class GMPStorageChild; + +class GMPRecordImpl : public GMPRecord +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRecordImpl) + + GMPRecordImpl(GMPStorageChild* aOwner, + const nsCString& aName, + GMPRecordClient* aClient); + + // GMPRecord. + GMPErr Open() override; + GMPErr Read() override; + GMPErr Write(const uint8_t* aData, + uint32_t aDataSize) override; + GMPErr Close() override; + + const nsCString& Name() const { return mName; } + + void OpenComplete(GMPErr aStatus); + void ReadComplete(GMPErr aStatus, const uint8_t* aBytes, uint32_t aLength); + void WriteComplete(GMPErr aStatus); + +private: + ~GMPRecordImpl() {} + const nsCString mName; + GMPRecordClient* const mClient; + GMPStorageChild* const mOwner; +}; + +class GMPStorageChild : public PGMPStorageChild +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorageChild) + + explicit GMPStorageChild(GMPChild* aPlugin); + + GMPErr CreateRecord(const nsCString& aRecordName, + GMPRecord** aOutRecord, + GMPRecordClient* aClient); + + GMPErr Open(GMPRecordImpl* aRecord); + + GMPErr Read(GMPRecordImpl* aRecord); + + GMPErr Write(GMPRecordImpl* aRecord, + const uint8_t* aData, + uint32_t aDataSize); + + GMPErr Close(const nsCString& aRecordName); + + GMPErr EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg); + +private: + bool HasRecord(const nsCString& aRecordName); + already_AddRefed<GMPRecordImpl> GetRecord(const nsCString& aRecordName); + +protected: + ~GMPStorageChild() {} + + // PGMPStorageChild + bool RecvOpenComplete(const nsCString& aRecordName, + const GMPErr& aStatus) override; + bool RecvReadComplete(const nsCString& aRecordName, + const GMPErr& aStatus, + InfallibleTArray<uint8_t>&& aBytes) override; + bool RecvWriteComplete(const nsCString& aRecordName, + const GMPErr& aStatus) override; + bool RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames, + const GMPErr& aStatus) override; + bool RecvShutdown() override; + +private: + Monitor mMonitor; + nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords; + GMPChild* mPlugin; + + struct RecordIteratorContext { + explicit RecordIteratorContext(RecvGMPRecordIteratorPtr aFunc, + void* aUserArg) + : mFunc(aFunc) + , mUserArg(aUserArg) + {} + RecordIteratorContext() {} + RecvGMPRecordIteratorPtr mFunc; + void* mUserArg; + }; + + std::queue<RecordIteratorContext> mPendingRecordIterators; + bool mShutdown; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPStorageChild_h_ diff --git a/dom/media/gmp/GMPStorageParent.cpp b/dom/media/gmp/GMPStorageParent.cpp new file mode 100644 index 000000000..e3eadeccf --- /dev/null +++ b/dom/media/gmp/GMPStorageParent.cpp @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPStorageParent.h" +#include "GMPParent.h" +#include "gmp-storage.h" +#include "mozilla/Unused.h" +#include "mozIGeckoMediaPluginService.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +namespace gmp { + +GMPStorageParent::GMPStorageParent(const nsCString& aNodeId, + GMPParent* aPlugin) + : mNodeId(aNodeId) + , mPlugin(aPlugin) + , mShutdown(true) +{ +} + +nsresult +GMPStorageParent::Init() +{ + LOGD(("GMPStorageParent[%p]::Init()", this)); + + if (NS_WARN_IF(mNodeId.IsEmpty())) { + return NS_ERROR_FAILURE; + } + RefPtr<GeckoMediaPluginServiceParent> mps(GeckoMediaPluginServiceParent::GetSingleton()); + if (NS_WARN_IF(!mps)) { + return NS_ERROR_FAILURE; + } + + bool persistent = false; + if (NS_WARN_IF(NS_FAILED(mps->IsPersistentStorageAllowed(mNodeId, &persistent)))) { + return NS_ERROR_FAILURE; + } + if (persistent) { + mStorage = CreateGMPDiskStorage(mNodeId, mPlugin->GetPluginBaseName()); + } else { + mStorage = mps->GetMemoryStorageFor(mNodeId); + } + if (!mStorage) { + return NS_ERROR_FAILURE; + } + + mShutdown = false; + return NS_OK; +} + +bool +GMPStorageParent::RecvOpen(const nsCString& aRecordName) +{ + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')", + this, aRecordName.get())); + + if (mShutdown) { + return false; + } + + if (mNodeId.EqualsLiteral("null")) { + // Refuse to open storage if the page is opened from local disk, + // or shared across origin. + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; null nodeId", + this, aRecordName.get())); + Unused << SendOpenComplete(aRecordName, GMPGenericErr); + return true; + } + + if (aRecordName.IsEmpty()) { + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty", + this, aRecordName.get())); + Unused << SendOpenComplete(aRecordName, GMPGenericErr); + return true; + } + + if (mStorage->IsOpen(aRecordName)) { + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use", + this, aRecordName.get())); + Unused << SendOpenComplete(aRecordName, GMPRecordInUse); + return true; + } + + auto err = mStorage->Open(aRecordName); + MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName)); + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d", + this, aRecordName.get(), err)); + Unused << SendOpenComplete(aRecordName, err); + + return true; +} + +bool +GMPStorageParent::RecvRead(const nsCString& aRecordName) +{ + LOGD(("GMPStorageParent[%p]::RecvRead(record='%s')", + this, aRecordName.get())); + + if (mShutdown) { + return false; + } + + nsTArray<uint8_t> data; + if (!mStorage->IsOpen(aRecordName)) { + LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') failed; record not open", + this, aRecordName.get())); + Unused << SendReadComplete(aRecordName, GMPClosedErr, data); + } else { + GMPErr rv = mStorage->Read(aRecordName, data); + LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') read %d bytes rv=%d", + this, aRecordName.get(), data.Length(), rv)); + Unused << SendReadComplete(aRecordName, rv, data); + } + + return true; +} + +bool +GMPStorageParent::RecvWrite(const nsCString& aRecordName, + InfallibleTArray<uint8_t>&& aBytes) +{ + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') %d bytes", + this, aRecordName.get(), aBytes.Length())); + + if (mShutdown) { + return false; + } + + if (!mStorage->IsOpen(aRecordName)) { + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record not open", + this, aRecordName.get())); + Unused << SendWriteComplete(aRecordName, GMPClosedErr); + return true; + } + + if (aBytes.Length() > GMP_MAX_RECORD_SIZE) { + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record too big", + this, aRecordName.get())); + Unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr); + return true; + } + + GMPErr rv = mStorage->Write(aRecordName, aBytes); + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') write complete rv=%d", + this, aRecordName.get(), rv)); + + Unused << SendWriteComplete(aRecordName, rv); + + return true; +} + +bool +GMPStorageParent::RecvGetRecordNames() +{ + if (mShutdown) { + return true; + } + + nsTArray<nsCString> recordNames; + GMPErr status = mStorage->GetRecordNames(recordNames); + + LOGD(("GMPStorageParent[%p]::RecvGetRecordNames() status=%d numRecords=%d", + this, status, recordNames.Length())); + + Unused << SendRecordNames(recordNames, status); + + return true; +} + +bool +GMPStorageParent::RecvClose(const nsCString& aRecordName) +{ + LOGD(("GMPStorageParent[%p]::RecvClose(record='%s')", + this, aRecordName.get())); + + if (mShutdown) { + return true; + } + + mStorage->Close(aRecordName); + + return true; +} + +void +GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD(("GMPStorageParent[%p]::ActorDestroy(reason=%d)", this, aWhy)); + Shutdown(); +} + +void +GMPStorageParent::Shutdown() +{ + LOGD(("GMPStorageParent[%p]::Shutdown()", this)); + + if (mShutdown) { + return; + } + mShutdown = true; + Unused << SendShutdown(); + + mStorage = nullptr; + +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPStorageParent.h b/dom/media/gmp/GMPStorageParent.h new file mode 100644 index 000000000..3d2ea69ac --- /dev/null +++ b/dom/media/gmp/GMPStorageParent.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPStorageParent_h_ +#define GMPStorageParent_h_ + +#include "mozilla/gmp/PGMPStorageParent.h" +#include "GMPStorage.h" + +namespace mozilla { +namespace gmp { + +class GMPParent; + +class GMPStorageParent : public PGMPStorageParent { +public: + NS_INLINE_DECL_REFCOUNTING(GMPStorageParent) + GMPStorageParent(const nsCString& aNodeId, + GMPParent* aPlugin); + + nsresult Init(); + void Shutdown(); + +protected: + bool RecvOpen(const nsCString& aRecordName) override; + bool RecvRead(const nsCString& aRecordName) override; + bool RecvWrite(const nsCString& aRecordName, + InfallibleTArray<uint8_t>&& aBytes) override; + bool RecvGetRecordNames() override; + bool RecvClose(const nsCString& aRecordName) override; + void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + ~GMPStorageParent() {} + + RefPtr<GMPStorage> mStorage; + + const nsCString mNodeId; + RefPtr<GMPParent> mPlugin; + // True after Shutdown(), or if Init() has not completed successfully. + bool mShutdown; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPStorageParent_h_ diff --git a/dom/media/gmp/GMPTimerChild.cpp b/dom/media/gmp/GMPTimerChild.cpp new file mode 100644 index 000000000..0b2b55c79 --- /dev/null +++ b/dom/media/gmp/GMPTimerChild.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPTimerChild.h" +#include "GMPPlatform.h" +#include "GMPChild.h" + +#define MAX_NUM_TIMERS 1000 + +namespace mozilla { +namespace gmp { + +GMPTimerChild::GMPTimerChild(GMPChild* aPlugin) + : mTimerCount(1) + , mPlugin(aPlugin) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); +} + +GMPTimerChild::~GMPTimerChild() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); +} + +GMPErr +GMPTimerChild::SetTimer(GMPTask* aTask, int64_t aTimeoutMS) +{ + if (!aTask) { + NS_WARNING("Tried to set timer with null task!"); + return GMPGenericErr; + } + + if (mPlugin->GMPMessageLoop() != MessageLoop::current()) { + NS_WARNING("Tried to set GMP timer on non-main thread."); + return GMPGenericErr; + } + + if (mTimers.Count() > MAX_NUM_TIMERS) { + return GMPQuotaExceededErr; + } + uint32_t timerId = mTimerCount; + mTimers.Put(timerId, aTask); + mTimerCount++; + + if (!SendSetTimer(timerId, aTimeoutMS)) { + return GMPGenericErr; + } + return GMPNoErr; +} + +bool +GMPTimerChild::RecvTimerExpired(const uint32_t& aTimerId) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + GMPTask* task = mTimers.Get(aTimerId); + mTimers.Remove(aTimerId); + if (task) { + RunOnMainThread(task); + } + return true; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPTimerChild.h b/dom/media/gmp/GMPTimerChild.h new file mode 100644 index 000000000..c63a49480 --- /dev/null +++ b/dom/media/gmp/GMPTimerChild.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPTimerChild_h_ +#define GMPTimerChild_h_ + +#include "mozilla/gmp/PGMPTimerChild.h" +#include "mozilla/Monitor.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "gmp-errors.h" +#include "gmp-platform.h" + +namespace mozilla { +namespace gmp { + +class GMPChild; + +class GMPTimerChild : public PGMPTimerChild +{ +public: + NS_INLINE_DECL_REFCOUNTING(GMPTimerChild) + + explicit GMPTimerChild(GMPChild* aPlugin); + + GMPErr SetTimer(GMPTask* aTask, int64_t aTimeoutMS); + +protected: + // GMPTimerChild + bool RecvTimerExpired(const uint32_t& aTimerId) override; + +private: + ~GMPTimerChild(); + + nsDataHashtable<nsUint32HashKey, GMPTask*> mTimers; + uint32_t mTimerCount; + + GMPChild* mPlugin; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPTimerChild_h_ diff --git a/dom/media/gmp/GMPTimerParent.cpp b/dom/media/gmp/GMPTimerParent.cpp new file mode 100644 index 000000000..50861b97f --- /dev/null +++ b/dom/media/gmp/GMPTimerParent.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPTimerParent.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Unused.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPParent" + +namespace gmp { + +GMPTimerParent::GMPTimerParent(nsIThread* aGMPThread) + : mGMPThread(aGMPThread) + , mIsOpen(true) +{ +} + +bool +GMPTimerParent::RecvSetTimer(const uint32_t& aTimerId, + const uint32_t& aTimeoutMs) +{ + LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen)); + + MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); + + if (!mIsOpen) { + return true; + } + + nsresult rv; + nsAutoPtr<Context> ctx(new Context()); + ctx->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, true); + + ctx->mId = aTimerId; + rv = ctx->mTimer->SetTarget(mGMPThread); + NS_ENSURE_SUCCESS(rv, true); + ctx->mParent = this; + + rv = ctx->mTimer->InitWithFuncCallback(&GMPTimerParent::GMPTimerExpired, + ctx, + aTimeoutMs, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, true); + + mTimers.PutEntry(ctx.forget()); + + return true; +} + +void +GMPTimerParent::Shutdown() +{ + LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen)); + + MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); + + for (auto iter = mTimers.Iter(); !iter.Done(); iter.Next()) { + Context* context = iter.Get()->GetKey(); + context->mTimer->Cancel(); + delete context; + } + + mTimers.Clear(); + mIsOpen = false; +} + +void +GMPTimerParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen)); + + Shutdown(); +} + +/* static */ +void +GMPTimerParent::GMPTimerExpired(nsITimer *aTimer, void *aClosure) +{ + MOZ_ASSERT(aClosure); + nsAutoPtr<Context> ctx(static_cast<Context*>(aClosure)); + MOZ_ASSERT(ctx->mParent); + if (ctx->mParent) { + ctx->mParent->TimerExpired(ctx); + } +} + +void +GMPTimerParent::TimerExpired(Context* aContext) +{ + LOGD(("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this, mIsOpen)); + MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); + + if (!mIsOpen) { + return; + } + + uint32_t id = aContext->mId; + mTimers.RemoveEntry(aContext); + if (id) { + Unused << SendTimerExpired(id); + } +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPTimerParent.h b/dom/media/gmp/GMPTimerParent.h new file mode 100644 index 000000000..0aad36121 --- /dev/null +++ b/dom/media/gmp/GMPTimerParent.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPTimerParent_h_ +#define GMPTimerParent_h_ + +#include "mozilla/gmp/PGMPTimerParent.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "mozilla/Monitor.h" +#include "nsIThread.h" + +namespace mozilla { +namespace gmp { + +class GMPTimerParent : public PGMPTimerParent { +public: + NS_INLINE_DECL_REFCOUNTING(GMPTimerParent) + explicit GMPTimerParent(nsIThread* aGMPThread); + + void Shutdown(); + +protected: + bool RecvSetTimer(const uint32_t& aTimerId, + const uint32_t& aTimeoutMs) override; + void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + ~GMPTimerParent() {} + + static void GMPTimerExpired(nsITimer *aTimer, void *aClosure); + + struct Context { + Context() { + MOZ_COUNT_CTOR(Context); + } + ~Context() { + MOZ_COUNT_DTOR(Context); + } + nsCOMPtr<nsITimer> mTimer; + RefPtr<GMPTimerParent> mParent; // Note: live timers keep the GMPTimerParent alive. + uint32_t mId; + }; + + void TimerExpired(Context* aContext); + + nsTHashtable<nsPtrHashKey<Context>> mTimers; + + nsCOMPtr<nsIThread> mGMPThread; + + bool mIsOpen; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPTimerParent_h_ diff --git a/dom/media/gmp/GMPTypes.ipdlh b/dom/media/gmp/GMPTypes.ipdlh new file mode 100644 index 000000000..44df5fc3d --- /dev/null +++ b/dom/media/gmp/GMPTypes.ipdlh @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +using GMPBufferType from "gmp-video-codec.h"; +using GMPAudioCodecType from "gmp-audio-codec.h"; +using GMPMediaKeyStatus from "gmp-decryption.h"; + +namespace mozilla { +namespace gmp { + +struct GMPDecryptionData { + uint8_t[] mKeyId; + uint8_t[] mIV; + uint16_t[] mClearBytes; + uint32_t[] mCipherBytes; + nsCString[] mSessionIds; +}; + +struct GMPVideoEncodedFrameData +{ + uint32_t mEncodedWidth; + uint32_t mEncodedHeight; + uint64_t mTimestamp; // microseconds + uint64_t mDuration; // microseconds + uint32_t mFrameType; + uint32_t mSize; + GMPBufferType mBufferType; + Shmem mBuffer; + bool mCompleteFrame; + GMPDecryptionData mDecryptionData; +}; + +struct GMPPlaneData +{ + int32_t mSize; + int32_t mStride; + Shmem mBuffer; +}; + +struct GMPVideoi420FrameData +{ + GMPPlaneData mYPlane; + GMPPlaneData mUPlane; + GMPPlaneData mVPlane; + int32_t mWidth; + int32_t mHeight; + uint64_t mTimestamp; // microseconds + uint64_t mDuration; // microseconds +}; + +struct GMPAudioCodecData +{ + GMPAudioCodecType mCodecType; + uint32_t mChannelCount; + uint32_t mBitsPerChannel; + uint32_t mSamplesPerSecond; + + uint8_t[] mExtraData; +}; + +struct GMPAudioEncodedSampleData +{ + uint8_t[] mData; + uint64_t mTimeStamp; // microseconds. + GMPDecryptionData mDecryptionData; + uint32_t mChannelCount; + uint32_t mSamplesPerSecond; +}; + +struct GMPAudioDecodedSampleData +{ + int16_t[] mData; + uint64_t mTimeStamp; // microseconds. + uint32_t mChannelCount; + uint32_t mSamplesPerSecond; +}; + +struct GMPKeyInformation { + uint8_t[] keyId; + GMPMediaKeyStatus status; +}; + +} +} diff --git a/dom/media/gmp/GMPUtils.cpp b/dom/media/gmp/GMPUtils.cpp new file mode 100644 index 000000000..d5863516a --- /dev/null +++ b/dom/media/gmp/GMPUtils.cpp @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsLiteralString.h" +#include "nsCRTGlue.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/Base64.h" +#include "nsISimpleEnumerator.h" +#include "prio.h" + +namespace mozilla { + +bool +GetEMEVoucherPath(nsIFile** aPath) +{ + nsCOMPtr<nsIFile> path; + NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(path)); + if (!path) { + NS_WARNING("GetEMEVoucherPath can't get NS_GRE_DIR!"); + return false; + } + path->AppendNative(NS_LITERAL_CSTRING("voucher.bin")); + path.forget(aPath); + return true; +} + +bool +EMEVoucherFileExists() +{ + nsCOMPtr<nsIFile> path; + bool exists; + return GetEMEVoucherPath(getter_AddRefs(path)) && + NS_SUCCEEDED(path->Exists(&exists)) && + exists; +} + +void +SplitAt(const char* aDelims, + const nsACString& aInput, + nsTArray<nsCString>& aOutTokens) +{ + nsAutoCString str(aInput); + char* end = str.BeginWriting(); + const char* start = nullptr; + while (!!(start = NS_strtok(aDelims, &end))) { + aOutTokens.AppendElement(nsCString(start)); + } +} + +nsCString +ToBase64(const nsTArray<uint8_t>& aBytes) +{ + nsAutoCString base64; + nsDependentCSubstring raw(reinterpret_cast<const char*>(aBytes.Elements()), + aBytes.Length()); + nsresult rv = Base64Encode(raw, base64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_LITERAL_CSTRING("[Base64EncodeFailed]"); + } + return base64; +} + +bool +FileExists(nsIFile* aFile) +{ + bool exists = false; + return aFile && NS_SUCCEEDED(aFile->Exists(&exists)) && exists; +} + +DirectoryEnumerator::DirectoryEnumerator(nsIFile* aPath, Mode aMode) + : mMode(aMode) +{ + aPath->GetDirectoryEntries(getter_AddRefs(mIter)); +} + +already_AddRefed<nsIFile> +DirectoryEnumerator::Next() +{ + if (!mIter) { + return nullptr; + } + bool hasMore = false; + while (NS_SUCCEEDED(mIter->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + nsresult rv = mIter->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv)); + if (NS_FAILED(rv)) { + continue; + } + + if (mMode == DirsOnly) { + bool isDirectory = false; + rv = path->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) { + continue; + } + } + return path.forget(); + } + return nullptr; +} + +bool +ReadIntoArray(nsIFile* aFile, + nsTArray<uint8_t>& aOutDst, + size_t aMaxLength) +{ + if (!FileExists(aFile)) { + return false; + } + + PRFileDesc* fd = nullptr; + nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); + if (NS_FAILED(rv)) { + return false; + } + + int32_t length = PR_Seek(fd, 0, PR_SEEK_END); + PR_Seek(fd, 0, PR_SEEK_SET); + + if (length < 0 || (size_t)length > aMaxLength) { + NS_WARNING("EME file is longer than maximum allowed length"); + PR_Close(fd); + return false; + } + aOutDst.SetLength(length); + int32_t bytesRead = PR_Read(fd, aOutDst.Elements(), length); + PR_Close(fd); + return (bytesRead == length); +} + +bool +ReadIntoString(nsIFile* aFile, + nsCString& aOutDst, + size_t aMaxLength) +{ + nsTArray<uint8_t> buf; + bool rv = ReadIntoArray(aFile, buf, aMaxLength); + if (rv) { + buf.AppendElement(0); // Append null terminator, required by nsC*String. + aOutDst = nsDependentCString((const char*)buf.Elements(), buf.Length() - 1); + } + return rv; +} + +bool +GMPInfoFileParser::Init(nsIFile* aInfoFile) +{ + nsTArray<nsCString> lines; + static const size_t MAX_GMP_INFO_FILE_LENGTH = 5 * 1024; + + nsAutoCString info; + if (!ReadIntoString(aInfoFile, info, MAX_GMP_INFO_FILE_LENGTH)) { + NS_WARNING("Failed to read info file in GMP process."); + return false; + } + + // Note: we pass "\r\n" to SplitAt so that we'll split lines delimited + // by \n (Unix), \r\n (Windows) and \r (old MacOSX). + SplitAt("\r\n", info, lines); + + for (nsCString line : lines) { + // Field name is the string up to but not including the first ':' + // character on the line. + int32_t colon = line.FindChar(':'); + if (colon <= 0) { + // Not allowed to be the first character. + // Info field name must be at least one character. + continue; + } + nsAutoCString key(Substring(line, 0, colon)); + ToLowerCase(key); + key.Trim(" "); + + nsCString* value = new nsCString(Substring(line, colon + 1)); + value->Trim(" "); + mValues.Put(key, value); // Hashtable assumes ownership of value. + } + + return true; +} + +bool +GMPInfoFileParser::Contains(const nsCString& aKey) const { + nsCString key(aKey); + ToLowerCase(key); + return mValues.Contains(key); +} + +nsCString +GMPInfoFileParser::Get(const nsCString& aKey) const { + MOZ_ASSERT(Contains(aKey)); + nsCString key(aKey); + ToLowerCase(key); + nsCString* p = nullptr; + if (mValues.Get(key, &p)) { + return nsCString(*p); + } + return EmptyCString(); +} + +bool +HaveGMPFor(const nsCString& aAPI, + nsTArray<nsCString>&& aTags) +{ + nsCOMPtr<mozIGeckoMediaPluginService> mps = + do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + if (NS_WARN_IF(!mps)) { + return false; + } + + bool hasPlugin = false; + if (NS_FAILED(mps->HasPluginForAPI(aAPI, &aTags, &hasPlugin))) { + return false; + } + return hasPlugin; +} + + +} // namespace mozilla diff --git a/dom/media/gmp/GMPUtils.h b/dom/media/gmp/GMPUtils.h new file mode 100644 index 000000000..7e7b9f26f --- /dev/null +++ b/dom/media/gmp/GMPUtils.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPUtils_h_ +#define GMPUtils_h_ + +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsClassHashtable.h" + +class nsIFile; +class nsCString; +class nsISimpleEnumerator; + +namespace mozilla { + +template<typename T> +struct DestroyPolicy +{ + void operator()(T* aGMPObject) const { + aGMPObject->Destroy(); + } +}; + +template<typename T> +using GMPUniquePtr = mozilla::UniquePtr<T, DestroyPolicy<T>>; + +bool GetEMEVoucherPath(nsIFile** aPath); + +bool EMEVoucherFileExists(); + +void +SplitAt(const char* aDelims, + const nsACString& aInput, + nsTArray<nsCString>& aOutTokens); + +nsCString +ToBase64(const nsTArray<uint8_t>& aBytes); + +bool +FileExists(nsIFile* aFile); + +// Enumerate directory entries for a specified path. +class DirectoryEnumerator { +public: + + enum Mode { + DirsOnly, // Enumeration only includes directories. + FilesAndDirs // Enumeration includes directories and non-directory files. + }; + + DirectoryEnumerator(nsIFile* aPath, Mode aMode); + + already_AddRefed<nsIFile> Next(); + +private: + Mode mMode; + nsCOMPtr<nsISimpleEnumerator> mIter; +}; + +class GMPInfoFileParser { +public: + bool Init(nsIFile* aFile); + bool Contains(const nsCString& aKey) const; + nsCString Get(const nsCString& aKey) const; +private: + nsClassHashtable<nsCStringHashKey, nsCString> mValues; +}; + +bool +ReadIntoArray(nsIFile* aFile, + nsTArray<uint8_t>& aOutDst, + size_t aMaxLength); + +bool +ReadIntoString(nsIFile* aFile, + nsCString& aOutDst, + size_t aMaxLength); + +bool +HaveGMPFor(const nsCString& aAPI, + nsTArray<nsCString>&& aTags); + +} // namespace mozilla + +#endif diff --git a/dom/media/gmp/GMPVideoDecoderChild.cpp b/dom/media/gmp/GMPVideoDecoderChild.cpp new file mode 100644 index 000000000..f9c1956e7 --- /dev/null +++ b/dom/media/gmp/GMPVideoDecoderChild.cpp @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoDecoderChild.h" +#include "GMPVideoi420FrameImpl.h" +#include "GMPContentChild.h" +#include <stdio.h> +#include "mozilla/Unused.h" +#include "GMPVideoEncodedFrameImpl.h" +#include "runnable_utils.h" + +namespace mozilla { +namespace gmp { + +GMPVideoDecoderChild::GMPVideoDecoderChild(GMPContentChild* aPlugin) + : GMPSharedMemManager(aPlugin) + , mPlugin(aPlugin) + , mVideoDecoder(nullptr) + , mVideoHost(this) + , mNeedShmemIntrCount(0) + , mPendingDecodeComplete(false) +{ + MOZ_ASSERT(mPlugin); +} + +GMPVideoDecoderChild::~GMPVideoDecoderChild() +{ + MOZ_ASSERT(!mNeedShmemIntrCount); +} + +void +GMPVideoDecoderChild::Init(GMPVideoDecoder* aDecoder) +{ + MOZ_ASSERT(aDecoder, "Cannot initialize video decoder child without a video decoder!"); + mVideoDecoder = aDecoder; +} + +GMPVideoHostImpl& +GMPVideoDecoderChild::Host() +{ + return mVideoHost; +} + +void +GMPVideoDecoderChild::Decoded(GMPVideoi420Frame* aDecodedFrame) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + if (!aDecodedFrame) { + MOZ_CRASH("Not given a decoded frame!"); + } + + auto df = static_cast<GMPVideoi420FrameImpl*>(aDecodedFrame); + + GMPVideoi420FrameData frameData; + df->InitFrameData(frameData); + SendDecoded(frameData); + + aDecodedFrame->Destroy(); +} + +void +GMPVideoDecoderChild::ReceivedDecodedReferenceFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendReceivedDecodedReferenceFrame(aPictureId); +} + +void +GMPVideoDecoderChild::ReceivedDecodedFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendReceivedDecodedFrame(aPictureId); +} + +void +GMPVideoDecoderChild::InputDataExhausted() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendInputDataExhausted(); +} + +void +GMPVideoDecoderChild::DrainComplete() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendDrainComplete(); +} + +void +GMPVideoDecoderChild::ResetComplete() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendResetComplete(); +} + +void +GMPVideoDecoderChild::Error(GMPErr aError) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendError(aError); +} + +bool +GMPVideoDecoderChild::RecvInitDecode(const GMPVideoCodec& aCodecSettings, + InfallibleTArray<uint8_t>&& aCodecSpecific, + const int32_t& aCoreCount) +{ + if (!mVideoDecoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoDecoder->InitDecode(aCodecSettings, + aCodecSpecific.Elements(), + aCodecSpecific.Length(), + this, + aCoreCount); + return true; +} + +bool +GMPVideoDecoderChild::RecvDecode(const GMPVideoEncodedFrameData& aInputFrame, + const bool& aMissingFrames, + InfallibleTArray<uint8_t>&& aCodecSpecificInfo, + const int64_t& aRenderTimeMs) +{ + if (!mVideoDecoder) { + return false; + } + + auto f = new GMPVideoEncodedFrameImpl(aInputFrame, &mVideoHost); + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoDecoder->Decode(f, + aMissingFrames, + aCodecSpecificInfo.Elements(), + aCodecSpecificInfo.Length(), + aRenderTimeMs); + + return true; +} + +bool +GMPVideoDecoderChild::RecvChildShmemForPool(Shmem&& aFrameBuffer) +{ + if (aFrameBuffer.IsWritable()) { + mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, + aFrameBuffer); + } + return true; +} + +bool +GMPVideoDecoderChild::RecvReset() +{ + if (!mVideoDecoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoDecoder->Reset(); + + return true; +} + +bool +GMPVideoDecoderChild::RecvDrain() +{ + if (!mVideoDecoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoDecoder->Drain(); + + return true; +} + +bool +GMPVideoDecoderChild::RecvDecodingComplete() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + if (mNeedShmemIntrCount) { + // There's a GMP blocked in Alloc() waiting for the CallNeedShem() to + // return a frame they can use. Don't call the GMP's DecodingComplete() + // now and don't delete the GMPVideoDecoderChild, defer processing the + // DecodingComplete() until once the Alloc() finishes. + mPendingDecodeComplete = true; + return true; + } + if (mVideoDecoder) { + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoDecoder->DecodingComplete(); + mVideoDecoder = nullptr; + } + + mVideoHost.DoneWithAPI(); + + mPlugin = nullptr; + + Unused << Send__delete__(this); + + return true; +} + +bool +GMPVideoDecoderChild::Alloc(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aMem) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + bool rv; +#ifndef SHMEM_ALLOC_IN_CHILD + ++mNeedShmemIntrCount; + rv = CallNeedShmem(aSize, aMem); + --mNeedShmemIntrCount; + if (mPendingDecodeComplete && mNeedShmemIntrCount == 0) { + mPendingDecodeComplete = false; + mPlugin->GMPMessageLoop()->PostTask( + NewRunnableMethod(this, &GMPVideoDecoderChild::RecvDecodingComplete)); + } +#else +#ifdef GMP_SAFE_SHMEM + rv = AllocShmem(aSize, aType, aMem); +#else + rv = AllocUnsafeShmem(aSize, aType, aMem); +#endif +#endif + return rv; +} + +void +GMPVideoDecoderChild::Dealloc(Shmem& aMem) +{ +#ifndef SHMEM_ALLOC_IN_CHILD + SendParentShmemForPool(aMem); +#else + DeallocShmem(aMem); +#endif +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoDecoderChild.h b/dom/media/gmp/GMPVideoDecoderChild.h new file mode 100644 index 000000000..2271a70a1 --- /dev/null +++ b/dom/media/gmp/GMPVideoDecoderChild.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoDecoderChild_h_ +#define GMPVideoDecoderChild_h_ + +#include "nsString.h" +#include "mozilla/gmp/PGMPVideoDecoderChild.h" +#include "gmp-video-decode.h" +#include "GMPSharedMemManager.h" +#include "GMPVideoHost.h" +#include "mozilla/gmp/GMPTypes.h" + +namespace mozilla { +namespace gmp { + +class GMPContentChild; + +class GMPVideoDecoderChild : public PGMPVideoDecoderChild, + public GMPVideoDecoderCallback, + public GMPSharedMemManager +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoDecoderChild); + + explicit GMPVideoDecoderChild(GMPContentChild* aPlugin); + + void Init(GMPVideoDecoder* aDecoder); + GMPVideoHostImpl& Host(); + + // GMPVideoDecoderCallback + void Decoded(GMPVideoi420Frame* decodedFrame) override; + void ReceivedDecodedReferenceFrame(const uint64_t pictureId) override; + void ReceivedDecodedFrame(const uint64_t pictureId) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aError) override; + + // GMPSharedMemManager + bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override; + void Dealloc(Shmem& aMem) override; + +private: + virtual ~GMPVideoDecoderChild(); + + // PGMPVideoDecoderChild + bool RecvInitDecode(const GMPVideoCodec& aCodecSettings, + InfallibleTArray<uint8_t>&& aCodecSpecific, + const int32_t& aCoreCount) override; + bool RecvDecode(const GMPVideoEncodedFrameData& aInputFrame, + const bool& aMissingFrames, + InfallibleTArray<uint8_t>&& aCodecSpecificInfo, + const int64_t& aRenderTimeMs) override; + bool RecvChildShmemForPool(Shmem&& aFrameBuffer) override; + bool RecvReset() override; + bool RecvDrain() override; + bool RecvDecodingComplete() override; + + GMPContentChild* mPlugin; + GMPVideoDecoder* mVideoDecoder; + GMPVideoHostImpl mVideoHost; + + // Non-zero when a GMP is blocked spinning the IPC message loop while + // waiting on an NeedShmem to complete. + int mNeedShmemIntrCount; + bool mPendingDecodeComplete; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPVideoDecoderChild_h_ diff --git a/dom/media/gmp/GMPVideoDecoderParent.cpp b/dom/media/gmp/GMPVideoDecoderParent.cpp new file mode 100644 index 000000000..bd5408adb --- /dev/null +++ b/dom/media/gmp/GMPVideoDecoderParent.cpp @@ -0,0 +1,513 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoDecoderParent.h" +#include "mozilla/Logging.h" +#include "mozilla/Unused.h" +#include "nsAutoRef.h" +#include "nsThreadUtils.h" +#include "GMPUtils.h" +#include "GMPVideoEncodedFrameImpl.h" +#include "GMPVideoi420FrameImpl.h" +#include "GMPContentParent.h" +#include "GMPMessageUtils.h" +#include "mozilla/gmp/GMPTypes.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg) +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOGE(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Error, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +namespace gmp { + +// States: +// Initial: mIsOpen == false +// on InitDecode success -> Open +// on Shutdown -> Dead +// Open: mIsOpen == true +// on Close -> Dead +// on ActorDestroy -> Dead +// on Shutdown -> Dead +// Dead: mIsOpen == false + +GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin) + : GMPSharedMemManager(aPlugin) + , mIsOpen(false) + , mShuttingDown(false) + , mActorDestroyed(false) + , mIsAwaitingResetComplete(false) + , mIsAwaitingDrainComplete(false) + , mPlugin(aPlugin) + , mCallback(nullptr) + , mVideoHost(this) + , mPluginId(aPlugin->GetPluginId()) + , mFrameCount(0) +{ + MOZ_ASSERT(mPlugin); +} + +GMPVideoDecoderParent::~GMPVideoDecoderParent() +{ +} + +GMPVideoHostImpl& +GMPVideoDecoderParent::Host() +{ + return mVideoHost; +} + +// Note: may be called via Terminated() +void +GMPVideoDecoderParent::Close() +{ + LOGD(("GMPVideoDecoderParent[%p]::Close()", this)); + MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); + + // Ensure if we've received a Close while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the close. This seems unlikely to happen, but better to be careful. + UnblockResetAndDrain(); + + // Consumer is done with us; we can shut down. No more callbacks should + // be made to mCallback. Note: do this before Shutdown()! + mCallback = nullptr; + // Let Shutdown mark us as dead so it knows if we had been alive + + // In case this is the last reference + RefPtr<GMPVideoDecoderParent> kungfudeathgrip(this); + Release(); + Shutdown(); +} + +nsresult +GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings, + const nsTArray<uint8_t>& aCodecSpecific, + GMPVideoDecoderCallbackProxy* aCallback, + int32_t aCoreCount) +{ + LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this)); + + if (mActorDestroyed) { + NS_WARNING("Trying to use a destroyed GMP video decoder!"); + return NS_ERROR_FAILURE; + } + if (mIsOpen) { + NS_WARNING("Trying to re-init an in-use GMP video decoder!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!aCallback) { + return NS_ERROR_FAILURE; + } + mCallback = aCallback; + + if (!SendInitDecode(aCodecSettings, aCodecSpecific, aCoreCount)) { + return NS_ERROR_FAILURE; + } + mIsOpen = true; + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +nsresult +GMPVideoDecoderParent::Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame, + bool aMissingFrames, + const nsTArray<uint8_t>& aCodecSpecificInfo, + int64_t aRenderTimeMs) +{ + LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%lld keyframe=%d", + this, aInputFrame->TimeStamp(), + aInputFrame->FrameType() == kGMPKeyFrame)); + + if (!mIsOpen) { + LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; dead GMPVideoDecoder", this)); + NS_WARNING("Trying to use an dead GMP video decoder"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + GMPUniquePtr<GMPVideoEncodedFrameImpl> inputFrameImpl( + static_cast<GMPVideoEncodedFrameImpl*>(aInputFrame.release())); + + // Very rough kill-switch if the plugin stops processing. If it's merely + // hung and continues, we'll come back to life eventually. + // 3* is because we're using 3 buffers per frame for i420 data for now. + if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) || + (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) { + LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; shmem buffer limit hit frame=%d encoded=%d", + this, NumInUse(GMPSharedMem::kGMPFrameData), NumInUse(GMPSharedMem::kGMPEncodedData))); + return NS_ERROR_FAILURE; + } + + GMPVideoEncodedFrameData frameData; + inputFrameImpl->RelinquishFrameData(frameData); + + if (!SendDecode(frameData, + aMissingFrames, + aCodecSpecificInfo, + aRenderTimeMs)) { + LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; SendDecode() failure.", this)); + return NS_ERROR_FAILURE; + } + mFrameCount++; + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +nsresult +GMPVideoDecoderParent::Reset() +{ + LOGD(("GMPVideoDecoderParent[%p]::Reset()", this)); + + if (!mIsOpen) { + NS_WARNING("Trying to use an dead GMP video decoder"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendReset()) { + return NS_ERROR_FAILURE; + } + + mIsAwaitingResetComplete = true; + + RefPtr<GMPVideoDecoderParent> self(this); + nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self]() -> void + { + LOGD(("GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out waiting for ResetComplete", self.get())); + self->mResetCompleteTimeout = nullptr; + LogToBrowserConsole(NS_LITERAL_STRING("GMPVideoDecoderParent timed out waiting for ResetComplete()")); + }); + CancelResetCompleteTimeout(); + mResetCompleteTimeout = SimpleTimer::Create(task, 5000, mPlugin->GMPThread()); + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +void +GMPVideoDecoderParent::CancelResetCompleteTimeout() +{ + if (mResetCompleteTimeout) { + mResetCompleteTimeout->Cancel(); + mResetCompleteTimeout = nullptr; + } +} + +nsresult +GMPVideoDecoderParent::Drain() +{ + LOGD(("GMPVideoDecoderParent[%p]::Drain() frameCount=%d", this, mFrameCount)); + + if (!mIsOpen) { + NS_WARNING("Trying to use an dead GMP video decoder"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendDrain()) { + return NS_ERROR_FAILURE; + } + + mIsAwaitingDrainComplete = true; + + // Async IPC, we don't have access to a return value. + return NS_OK; +} + +const nsCString& +GMPVideoDecoderParent::GetDisplayName() const +{ + if (!mIsOpen) { + NS_WARNING("Trying to use an dead GMP video decoder"); + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + return mPlugin->GetDisplayName(); +} + +// Note: Consider keeping ActorDestroy sync'd up when making changes here. +nsresult +GMPVideoDecoderParent::Shutdown() +{ + LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this)); + MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (mShuttingDown) { + return NS_OK; + } + mShuttingDown = true; + + // Ensure if we've received a shutdown while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the shutdown. + UnblockResetAndDrain(); + + // Notify client we're gone! Won't occur after Close() + if (mCallback) { + mCallback->Terminated(); + mCallback = nullptr; + } + + mIsOpen = false; + if (!mActorDestroyed) { + Unused << SendDecodingComplete(); + } + + return NS_OK; +} + +// Note: Keep this sync'd up with Shutdown +void +GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy)); + + mIsOpen = false; + mActorDestroyed = true; + + // Ensure if we've received a destroy while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the error. + UnblockResetAndDrain(); + + if (mCallback) { + // May call Close() (and Shutdown()) immediately or with a delay + mCallback->Terminated(); + mCallback = nullptr; + } + if (mPlugin) { + // Ignore any return code. It is OK for this to fail without killing the process. + mPlugin->VideoDecoderDestroyed(this); + mPlugin = nullptr; + } + mVideoHost.ActorDestroyed(); + MaybeDisconnect(aWhy == AbnormalShutdown); +} + +bool +GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame) +{ + --mFrameCount; + LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%lld frameCount=%d", + this, aDecodedFrame.mTimestamp(), mFrameCount)); + + if (!mCallback) { + return false; + } + + if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) { + LOGE(("GMPVideoDecoderParent[%p]::RecvDecoded() " + "timestamp=%lld decoded frame corrupt, ignoring")); + return false; + } + auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost); + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->Decoded(f); + + return true; +} + +bool +GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId) +{ + if (!mCallback) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->ReceivedDecodedReferenceFrame(aPictureId); + + return true; +} + +bool +GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId) +{ + if (!mCallback) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->ReceivedDecodedFrame(aPictureId); + + return true; +} + +bool +GMPVideoDecoderParent::RecvInputDataExhausted() +{ + LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this)); + + if (!mCallback) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->InputDataExhausted(); + + return true; +} + +bool +GMPVideoDecoderParent::RecvDrainComplete() +{ + LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete() frameCount=%d", this, mFrameCount)); + nsAutoString msg; + msg.AppendLiteral("GMPVideoDecoderParent::RecvDrainComplete() outstanding frames="); + msg.AppendInt(mFrameCount); + LogToBrowserConsole(msg); + if (!mCallback) { + return false; + } + + if (!mIsAwaitingDrainComplete) { + return true; + } + mIsAwaitingDrainComplete = false; + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->DrainComplete(); + + return true; +} + +bool +GMPVideoDecoderParent::RecvResetComplete() +{ + LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this)); + + CancelResetCompleteTimeout(); + + if (!mCallback) { + return false; + } + + if (!mIsAwaitingResetComplete) { + return true; + } + mIsAwaitingResetComplete = false; + mFrameCount = 0; + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->ResetComplete(); + + return true; +} + +bool +GMPVideoDecoderParent::RecvError(const GMPErr& aError) +{ + LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError)); + + if (!mCallback) { + return false; + } + + // Ensure if we've received an error while waiting for a ResetComplete + // or DrainComplete notification, we'll unblock the caller before processing + // the error. + UnblockResetAndDrain(); + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->Error(aError); + + return true; +} + +bool +GMPVideoDecoderParent::RecvShutdown() +{ + LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this)); + + Shutdown(); + return true; +} + +bool +GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer) +{ + if (aEncodedBuffer.IsWritable()) { + mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, + aEncodedBuffer); + } + return true; +} + +bool +GMPVideoDecoderParent::AnswerNeedShmem(const uint32_t& aFrameBufferSize, + Shmem* aMem) +{ + ipc::Shmem mem; + + if (!mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData, + aFrameBufferSize, + ipc::SharedMemory::TYPE_BASIC, &mem)) + { + LOGE(("%s: Failed to get a shared mem buffer for Child! size %u", + __FUNCTION__, aFrameBufferSize)); + return false; + } + *aMem = mem; + mem = ipc::Shmem(); + return true; +} + +bool +GMPVideoDecoderParent::Recv__delete__() +{ + LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this)); + + if (mPlugin) { + // Ignore any return code. It is OK for this to fail without killing the process. + mPlugin->VideoDecoderDestroyed(this); + mPlugin = nullptr; + } + + return true; +} + +void +GMPVideoDecoderParent::UnblockResetAndDrain() +{ + LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain() " + "awaitingResetComplete=%d awaitingDrainComplete=%d", + this, mIsAwaitingResetComplete, mIsAwaitingDrainComplete)); + + if (!mCallback) { + MOZ_ASSERT(!mIsAwaitingResetComplete); + MOZ_ASSERT(!mIsAwaitingDrainComplete); + return; + } + if (mIsAwaitingResetComplete) { + mIsAwaitingResetComplete = false; + mCallback->ResetComplete(); + } + if (mIsAwaitingDrainComplete) { + mIsAwaitingDrainComplete = false; + mCallback->DrainComplete(); + } + CancelResetCompleteTimeout(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoDecoderParent.h b/dom/media/gmp/GMPVideoDecoderParent.h new file mode 100644 index 000000000..215c784c1 --- /dev/null +++ b/dom/media/gmp/GMPVideoDecoderParent.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoDecoderParent_h_ +#define GMPVideoDecoderParent_h_ + +#include "mozilla/RefPtr.h" +#include "gmp-video-decode.h" +#include "mozilla/gmp/PGMPVideoDecoderParent.h" +#include "GMPMessageUtils.h" +#include "GMPSharedMemManager.h" +#include "GMPUtils.h" +#include "GMPVideoHost.h" +#include "GMPVideoDecoderProxy.h" +#include "VideoUtils.h" +#include "GMPCrashHelperHolder.h" + +namespace mozilla { +namespace gmp { + +class GMPContentParent; + +class GMPVideoDecoderParent final : public PGMPVideoDecoderParent + , public GMPVideoDecoderProxy + , public GMPSharedMemManager + , public GMPCrashHelperHolder +{ +public: + NS_INLINE_DECL_REFCOUNTING(GMPVideoDecoderParent) + + explicit GMPVideoDecoderParent(GMPContentParent *aPlugin); + + GMPVideoHostImpl& Host(); + nsresult Shutdown(); + + // GMPVideoDecoder + void Close() override; + nsresult InitDecode(const GMPVideoCodec& aCodecSettings, + const nsTArray<uint8_t>& aCodecSpecific, + GMPVideoDecoderCallbackProxy* aCallback, + int32_t aCoreCount) override; + nsresult Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame, + bool aMissingFrames, + const nsTArray<uint8_t>& aCodecSpecificInfo, + int64_t aRenderTimeMs = -1) override; + nsresult Reset() override; + nsresult Drain() override; + uint32_t GetPluginId() const override { return mPluginId; } + const nsCString& GetDisplayName() const override; + + // GMPSharedMemManager + bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override + { +#ifdef GMP_SAFE_SHMEM + return AllocShmem(aSize, aType, aMem); +#else + return AllocUnsafeShmem(aSize, aType, aMem); +#endif + } + void Dealloc(Shmem& aMem) override + { + DeallocShmem(aMem); + } + +private: + ~GMPVideoDecoderParent(); + + // PGMPVideoDecoderParent + void ActorDestroy(ActorDestroyReason aWhy) override; + bool RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame) override; + bool RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId) override; + bool RecvReceivedDecodedFrame(const uint64_t& aPictureId) override; + bool RecvInputDataExhausted() override; + bool RecvDrainComplete() override; + bool RecvResetComplete() override; + bool RecvError(const GMPErr& aError) override; + bool RecvShutdown() override; + bool RecvParentShmemForPool(Shmem&& aEncodedBuffer) override; + bool AnswerNeedShmem(const uint32_t& aFrameBufferSize, + Shmem* aMem) override; + bool Recv__delete__() override; + + void UnblockResetAndDrain(); + void CancelResetCompleteTimeout(); + + bool mIsOpen; + bool mShuttingDown; + bool mActorDestroyed; + bool mIsAwaitingResetComplete; + bool mIsAwaitingDrainComplete; + RefPtr<GMPContentParent> mPlugin; + GMPVideoDecoderCallbackProxy* mCallback; + GMPVideoHostImpl mVideoHost; + const uint32_t mPluginId; + int32_t mFrameCount; + RefPtr<SimpleTimer> mResetCompleteTimeout; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPVideoDecoderParent_h_ diff --git a/dom/media/gmp/GMPVideoDecoderProxy.h b/dom/media/gmp/GMPVideoDecoderProxy.h new file mode 100644 index 000000000..b9596fd21 --- /dev/null +++ b/dom/media/gmp/GMPVideoDecoderProxy.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoDecoderProxy_h_ +#define GMPVideoDecoderProxy_h_ + +#include "nsTArray.h" +#include "gmp-video-decode.h" +#include "gmp-video-frame-i420.h" +#include "gmp-video-frame-encoded.h" + +#include "GMPCallbackBase.h" +#include "GMPUtils.h" + +class GMPVideoDecoderCallbackProxy : public GMPCallbackBase, + public GMPVideoDecoderCallback +{ +public: + virtual ~GMPVideoDecoderCallbackProxy() {} +}; + +// A proxy to GMPVideoDecoder in the child process. +// GMPVideoDecoderParent exposes this to users the GMP. +// This enables Gecko to pass nsTArrays to the child GMP and avoid +// an extra copy when doing so. + +// The consumer must call Close() when done with the codec, or when +// Terminated() is called by the GMP plugin indicating an abnormal shutdown +// of the underlying plugin. After calling Close(), the consumer must +// not access this again. + +// This interface is not thread-safe and must only be used from GMPThread. +class GMPVideoDecoderProxy { +public: + virtual nsresult InitDecode(const GMPVideoCodec& aCodecSettings, + const nsTArray<uint8_t>& aCodecSpecific, + GMPVideoDecoderCallbackProxy* aCallback, + int32_t aCoreCount) = 0; + virtual nsresult Decode(mozilla::GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame, + bool aMissingFrames, + const nsTArray<uint8_t>& aCodecSpecificInfo, + int64_t aRenderTimeMs = -1) = 0; + virtual nsresult Reset() = 0; + virtual nsresult Drain() = 0; + virtual uint32_t GetPluginId() const = 0; + + // Call to tell GMP/plugin the consumer will no longer use this + // interface/codec. + virtual void Close() = 0; + + virtual const nsCString& GetDisplayName() const = 0; +}; + +#endif diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp new file mode 100644 index 000000000..7725c5521 --- /dev/null +++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp @@ -0,0 +1,324 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoEncodedFrameImpl.h" +#include "GMPVideoHost.h" +#include "mozilla/gmp/GMPTypes.h" +#include "GMPSharedMemManager.h" +#include "GMPEncryptedBufferDataImpl.h" + +namespace mozilla { +namespace gmp { + +GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost) +: mEncodedWidth(0), + mEncodedHeight(0), + mTimeStamp(0ll), + mDuration(0ll), + mFrameType(kGMPDeltaFrame), + mSize(0), + mCompleteFrame(false), + mHost(aHost), + mBufferType(GMP_BufferSingle) +{ + MOZ_ASSERT(aHost); + aHost->EncodedFrameCreated(this); +} + +GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData, + GMPVideoHostImpl* aHost) +: mEncodedWidth(aFrameData.mEncodedWidth()), + mEncodedHeight(aFrameData.mEncodedHeight()), + mTimeStamp(aFrameData.mTimestamp()), + mDuration(aFrameData.mDuration()), + mFrameType(static_cast<GMPVideoFrameType>(aFrameData.mFrameType())), + mSize(aFrameData.mSize()), + mCompleteFrame(aFrameData.mCompleteFrame()), + mHost(aHost), + mBuffer(aFrameData.mBuffer()), + mBufferType(aFrameData.mBufferType()) +{ + MOZ_ASSERT(aHost); + if (aFrameData.mDecryptionData().mKeyId().Length() > 0) { + mCrypto = new GMPEncryptedBufferDataImpl(aFrameData.mDecryptionData()); + } + aHost->EncodedFrameCreated(this); +} + +GMPVideoEncodedFrameImpl::~GMPVideoEncodedFrameImpl() +{ + DestroyBuffer(); + if (mHost) { + mHost->EncodedFrameDestroyed(this); + } +} + +void +GMPVideoEncodedFrameImpl::InitCrypto(const CryptoSample& aCrypto) +{ + mCrypto = new GMPEncryptedBufferDataImpl(aCrypto); +} + +const GMPEncryptedBufferMetadata* +GMPVideoEncodedFrameImpl::GetDecryptionData() const +{ + return mCrypto; +} + +GMPVideoFrameFormat +GMPVideoEncodedFrameImpl::GetFrameFormat() +{ + return kGMPEncodedVideoFrame; +} + +void +GMPVideoEncodedFrameImpl::DoneWithAPI() +{ + DestroyBuffer(); + + // Do this after destroying the buffer because destruction + // involves deallocation, which requires a host. + mHost = nullptr; +} + +void +GMPVideoEncodedFrameImpl::ActorDestroyed() +{ + // Simply clear out Shmem reference, do not attempt to + // properly free it. It has already been freed. + mBuffer = ipc::Shmem(); + // No more host. + mHost = nullptr; +} + +bool +GMPVideoEncodedFrameImpl::RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData) +{ + aFrameData.mEncodedWidth() = mEncodedWidth; + aFrameData.mEncodedHeight() = mEncodedHeight; + aFrameData.mTimestamp() = mTimeStamp; + aFrameData.mDuration() = mDuration; + aFrameData.mFrameType() = mFrameType; + aFrameData.mSize() = mSize; + aFrameData.mCompleteFrame() = mCompleteFrame; + aFrameData.mBuffer() = mBuffer; + aFrameData.mBufferType() = mBufferType; + if (mCrypto) { + mCrypto->RelinquishData(aFrameData.mDecryptionData()); + } + + // This method is called right before Shmem is sent to another process. + // We need to effectively zero out our member copy so that we don't + // try to delete Shmem we don't own later. + mBuffer = ipc::Shmem(); + + return true; +} + +void +GMPVideoEncodedFrameImpl::DestroyBuffer() +{ + if (mHost && mBuffer.IsWritable()) { + mHost->SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, mBuffer); + } + mBuffer = ipc::Shmem(); +} + +GMPErr +GMPVideoEncodedFrameImpl::CreateEmptyFrame(uint32_t aSize) +{ + if (aSize == 0) { + DestroyBuffer(); + } else if (aSize > AllocatedSize()) { + DestroyBuffer(); + if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, aSize, + ipc::SharedMemory::TYPE_BASIC, &mBuffer) || + !Buffer()) { + return GMPAllocErr; + } + } + mSize = aSize; + + return GMPNoErr; +} + +GMPErr +GMPVideoEncodedFrameImpl::CopyFrame(const GMPVideoEncodedFrame& aFrame) +{ + auto& f = static_cast<const GMPVideoEncodedFrameImpl&>(aFrame); + + if (f.mSize != 0) { + GMPErr err = CreateEmptyFrame(f.mSize); + if (err != GMPNoErr) { + return err; + } + memcpy(Buffer(), f.Buffer(), f.mSize); + } + mEncodedWidth = f.mEncodedWidth; + mEncodedHeight = f.mEncodedHeight; + mTimeStamp = f.mTimeStamp; + mDuration = f.mDuration; + mFrameType = f.mFrameType; + mSize = f.mSize; // already set... + mCompleteFrame = f.mCompleteFrame; + mBufferType = f.mBufferType; + mCrypto = new GMPEncryptedBufferDataImpl(*(f.mCrypto)); + // Don't copy host, that should have been set properly on object creation via host. + + return GMPNoErr; +} + +void +GMPVideoEncodedFrameImpl::SetEncodedWidth(uint32_t aEncodedWidth) +{ + mEncodedWidth = aEncodedWidth; +} + +uint32_t +GMPVideoEncodedFrameImpl::EncodedWidth() +{ + return mEncodedWidth; +} + +void +GMPVideoEncodedFrameImpl::SetEncodedHeight(uint32_t aEncodedHeight) +{ + mEncodedHeight = aEncodedHeight; +} + +uint32_t +GMPVideoEncodedFrameImpl::EncodedHeight() +{ + return mEncodedHeight; +} + +void +GMPVideoEncodedFrameImpl::SetTimeStamp(uint64_t aTimeStamp) +{ + mTimeStamp = aTimeStamp; +} + +uint64_t +GMPVideoEncodedFrameImpl::TimeStamp() +{ + return mTimeStamp; +} + +void +GMPVideoEncodedFrameImpl::SetDuration(uint64_t aDuration) +{ + mDuration = aDuration; +} + +uint64_t +GMPVideoEncodedFrameImpl::Duration() const +{ + return mDuration; +} + +void +GMPVideoEncodedFrameImpl::SetFrameType(GMPVideoFrameType aFrameType) +{ + mFrameType = aFrameType; +} + +GMPVideoFrameType +GMPVideoEncodedFrameImpl::FrameType() +{ + return mFrameType; +} + +void +GMPVideoEncodedFrameImpl::SetAllocatedSize(uint32_t aNewSize) +{ + if (aNewSize <= AllocatedSize()) { + return; + } + + if (!mHost) { + return; + } + + ipc::Shmem new_mem; + if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, aNewSize, + ipc::SharedMemory::TYPE_BASIC, &new_mem) || + !new_mem.get<uint8_t>()) { + return; + } + + if (mBuffer.IsReadable()) { + memcpy(new_mem.get<uint8_t>(), Buffer(), mSize); + } + + DestroyBuffer(); + + mBuffer = new_mem; +} + +uint32_t +GMPVideoEncodedFrameImpl::AllocatedSize() +{ + if (mBuffer.IsWritable()) { + return mBuffer.Size<uint8_t>(); + } + return 0; +} + +void +GMPVideoEncodedFrameImpl::SetSize(uint32_t aSize) +{ + mSize = aSize; +} + +uint32_t +GMPVideoEncodedFrameImpl::Size() +{ + return mSize; +} + +void +GMPVideoEncodedFrameImpl::SetCompleteFrame(bool aCompleteFrame) +{ + mCompleteFrame = aCompleteFrame; +} + +bool +GMPVideoEncodedFrameImpl::CompleteFrame() +{ + return mCompleteFrame; +} + +const uint8_t* +GMPVideoEncodedFrameImpl::Buffer() const +{ + return mBuffer.get<uint8_t>(); +} + +uint8_t* +GMPVideoEncodedFrameImpl::Buffer() +{ + return mBuffer.get<uint8_t>(); +} + +void +GMPVideoEncodedFrameImpl::Destroy() +{ + delete this; +} + +GMPBufferType +GMPVideoEncodedFrameImpl::BufferType() const +{ + return mBufferType; +} + +void +GMPVideoEncodedFrameImpl::SetBufferType(GMPBufferType aBufferType) +{ + mBufferType = aBufferType; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.h b/dom/media/gmp/GMPVideoEncodedFrameImpl.h new file mode 100644 index 000000000..69858f1c6 --- /dev/null +++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2014, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMPVideoEncodedFrameImpl_h_ +#define GMPVideoEncodedFrameImpl_h_ + +#include "gmp-errors.h" +#include "gmp-video-frame.h" +#include "gmp-video-frame-encoded.h" +#include "gmp-decryption.h" +#include "mozilla/ipc/Shmem.h" +#include "nsAutoPtr.h" + +namespace mozilla { +class CryptoSample; + +namespace gmp { + +class GMPVideoHostImpl; +class GMPVideoEncodedFrameData; +class GMPEncryptedBufferDataImpl; + +class GMPVideoEncodedFrameImpl: public GMPVideoEncodedFrame +{ + friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoEncodedFrameImpl>; +public: + explicit GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost); + GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData, GMPVideoHostImpl* aHost); + virtual ~GMPVideoEncodedFrameImpl(); + + void InitCrypto(const CryptoSample& aCrypto); + + // This is called during a normal destroy sequence, which is + // when a consumer is finished or during XPCOM shutdown. + void DoneWithAPI(); + // Does not attempt to release Shmem, as the Shmem has already been released. + void ActorDestroyed(); + + bool RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData); + + // GMPVideoFrame + GMPVideoFrameFormat GetFrameFormat() override; + void Destroy() override; + + // GMPVideoEncodedFrame + GMPErr CreateEmptyFrame(uint32_t aSize) override; + GMPErr CopyFrame(const GMPVideoEncodedFrame& aFrame) override; + void SetEncodedWidth(uint32_t aEncodedWidth) override; + uint32_t EncodedWidth() override; + void SetEncodedHeight(uint32_t aEncodedHeight) override; + uint32_t EncodedHeight() override; + // Microseconds + void SetTimeStamp(uint64_t aTimeStamp) override; + uint64_t TimeStamp() override; + // Set frame duration (microseconds) + // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration() + // depending on rounding to avoid having to track roundoff errors + // and dropped/missing frames(!) (which may leave a large gap) + void SetDuration(uint64_t aDuration) override; + uint64_t Duration() const override; + void SetFrameType(GMPVideoFrameType aFrameType) override; + GMPVideoFrameType FrameType() override; + void SetAllocatedSize(uint32_t aNewSize) override; + uint32_t AllocatedSize() override; + void SetSize(uint32_t aSize) override; + uint32_t Size() override; + void SetCompleteFrame(bool aCompleteFrame) override; + bool CompleteFrame() override; + const uint8_t* Buffer() const override; + uint8_t* Buffer() override; + GMPBufferType BufferType() const override; + void SetBufferType(GMPBufferType aBufferType) override; + const GMPEncryptedBufferMetadata* GetDecryptionData() const override; + +private: + void DestroyBuffer(); + + uint32_t mEncodedWidth; + uint32_t mEncodedHeight; + uint64_t mTimeStamp; + uint64_t mDuration; + GMPVideoFrameType mFrameType; + uint32_t mSize; + bool mCompleteFrame; + GMPVideoHostImpl* mHost; + ipc::Shmem mBuffer; + GMPBufferType mBufferType; + nsAutoPtr<GMPEncryptedBufferDataImpl> mCrypto; +}; + +} // namespace gmp + +} // namespace mozilla + +#endif // GMPVideoEncodedFrameImpl_h_ diff --git a/dom/media/gmp/GMPVideoEncoderChild.cpp b/dom/media/gmp/GMPVideoEncoderChild.cpp new file mode 100644 index 000000000..f5c3dda95 --- /dev/null +++ b/dom/media/gmp/GMPVideoEncoderChild.cpp @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoEncoderChild.h" +#include "GMPContentChild.h" +#include <stdio.h> +#include "mozilla/Unused.h" +#include "GMPVideoEncodedFrameImpl.h" +#include "GMPVideoi420FrameImpl.h" +#include "runnable_utils.h" + +namespace mozilla { +namespace gmp { + +GMPVideoEncoderChild::GMPVideoEncoderChild(GMPContentChild* aPlugin) + : GMPSharedMemManager(aPlugin) + , mPlugin(aPlugin) + , mVideoEncoder(nullptr) + , mVideoHost(this) + , mNeedShmemIntrCount(0) + , mPendingEncodeComplete(false) +{ + MOZ_ASSERT(mPlugin); +} + +GMPVideoEncoderChild::~GMPVideoEncoderChild() +{ + MOZ_ASSERT(!mNeedShmemIntrCount); +} + +void +GMPVideoEncoderChild::Init(GMPVideoEncoder* aEncoder) +{ + MOZ_ASSERT(aEncoder, "Cannot initialize video encoder child without a video encoder!"); + mVideoEncoder = aEncoder; +} + +GMPVideoHostImpl& +GMPVideoEncoderChild::Host() +{ + return mVideoHost; +} + +void +GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + auto ef = static_cast<GMPVideoEncodedFrameImpl*>(aEncodedFrame); + + GMPVideoEncodedFrameData frameData; + ef->RelinquishFrameData(frameData); + + nsTArray<uint8_t> codecSpecific; + codecSpecific.AppendElements(aCodecSpecificInfo, aCodecSpecificInfoLength); + SendEncoded(frameData, codecSpecific); + + aEncodedFrame->Destroy(); +} + +void +GMPVideoEncoderChild::Error(GMPErr aError) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + SendError(aError); +} + +bool +GMPVideoEncoderChild::RecvInitEncode(const GMPVideoCodec& aCodecSettings, + InfallibleTArray<uint8_t>&& aCodecSpecific, + const int32_t& aNumberOfCores, + const uint32_t& aMaxPayloadSize) +{ + if (!mVideoEncoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoEncoder->InitEncode(aCodecSettings, + aCodecSpecific.Elements(), + aCodecSpecific.Length(), + this, + aNumberOfCores, + aMaxPayloadSize); + + return true; +} + +bool +GMPVideoEncoderChild::RecvEncode(const GMPVideoi420FrameData& aInputFrame, + InfallibleTArray<uint8_t>&& aCodecSpecificInfo, + InfallibleTArray<GMPVideoFrameType>&& aFrameTypes) +{ + if (!mVideoEncoder) { + return false; + } + + auto f = new GMPVideoi420FrameImpl(aInputFrame, &mVideoHost); + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoEncoder->Encode(f, + aCodecSpecificInfo.Elements(), + aCodecSpecificInfo.Length(), + aFrameTypes.Elements(), + aFrameTypes.Length()); + + return true; +} + +bool +GMPVideoEncoderChild::RecvChildShmemForPool(Shmem&& aEncodedBuffer) +{ + if (aEncodedBuffer.IsWritable()) { + mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, + aEncodedBuffer); + } + return true; +} + +bool +GMPVideoEncoderChild::RecvSetChannelParameters(const uint32_t& aPacketLoss, + const uint32_t& aRTT) +{ + if (!mVideoEncoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoEncoder->SetChannelParameters(aPacketLoss, aRTT); + + return true; +} + +bool +GMPVideoEncoderChild::RecvSetRates(const uint32_t& aNewBitRate, + const uint32_t& aFrameRate) +{ + if (!mVideoEncoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoEncoder->SetRates(aNewBitRate, aFrameRate); + + return true; +} + +bool +GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(const bool& aEnable) +{ + if (!mVideoEncoder) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoEncoder->SetPeriodicKeyFrames(aEnable); + + return true; +} + +bool +GMPVideoEncoderChild::RecvEncodingComplete() +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + if (mNeedShmemIntrCount) { + // There's a GMP blocked in Alloc() waiting for the CallNeedShem() to + // return a frame they can use. Don't call the GMP's EncodingComplete() + // now and don't delete the GMPVideoEncoderChild, defer processing the + // EncodingComplete() until once the Alloc() finishes. + mPendingEncodeComplete = true; + return true; + } + + if (!mVideoEncoder) { + Unused << Send__delete__(this); + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mVideoEncoder->EncodingComplete(); + + mVideoHost.DoneWithAPI(); + + mPlugin = nullptr; + + Unused << Send__delete__(this); + + return true; +} + +bool +GMPVideoEncoderChild::Alloc(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aMem) +{ + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + + bool rv; +#ifndef SHMEM_ALLOC_IN_CHILD + ++mNeedShmemIntrCount; + rv = CallNeedShmem(aSize, aMem); + --mNeedShmemIntrCount; + if (mPendingEncodeComplete && mNeedShmemIntrCount == 0) { + mPendingEncodeComplete = false; + mPlugin->GMPMessageLoop()->PostTask( + NewRunnableMethod(this, &GMPVideoEncoderChild::RecvEncodingComplete)); + } +#else +#ifdef GMP_SAFE_SHMEM + rv = AllocShmem(aSize, aType, aMem); +#else + rv = AllocUnsafeShmem(aSize, aType, aMem); +#endif +#endif + return rv; +} + +void +GMPVideoEncoderChild::Dealloc(Shmem& aMem) +{ +#ifndef SHMEM_ALLOC_IN_CHILD + SendParentShmemForPool(aMem); +#else + DeallocShmem(aMem); +#endif +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h new file mode 100644 index 000000000..44e2fe605 --- /dev/null +++ b/dom/media/gmp/GMPVideoEncoderChild.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoEncoderChild_h_ +#define GMPVideoEncoderChild_h_ + +#include "nsString.h" +#include "mozilla/gmp/PGMPVideoEncoderChild.h" +#include "gmp-video-encode.h" +#include "GMPSharedMemManager.h" +#include "GMPVideoHost.h" + +namespace mozilla { +namespace gmp { + +class GMPContentChild; + +class GMPVideoEncoderChild : public PGMPVideoEncoderChild, + public GMPVideoEncoderCallback, + public GMPSharedMemManager +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoEncoderChild); + + explicit GMPVideoEncoderChild(GMPContentChild* aPlugin); + + void Init(GMPVideoEncoder* aEncoder); + GMPVideoHostImpl& Host(); + + // GMPVideoEncoderCallback + void Encoded(GMPVideoEncodedFrame* aEncodedFrame, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength) override; + void Error(GMPErr aError) override; + + // GMPSharedMemManager + bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aMem) override; + void Dealloc(Shmem& aMem) override; + +private: + virtual ~GMPVideoEncoderChild(); + + // PGMPVideoEncoderChild + bool RecvInitEncode(const GMPVideoCodec& aCodecSettings, + InfallibleTArray<uint8_t>&& aCodecSpecific, + const int32_t& aNumberOfCores, + const uint32_t& aMaxPayloadSize) override; + bool RecvEncode(const GMPVideoi420FrameData& aInputFrame, + InfallibleTArray<uint8_t>&& aCodecSpecificInfo, + InfallibleTArray<GMPVideoFrameType>&& aFrameTypes) override; + bool RecvChildShmemForPool(Shmem&& aEncodedBuffer) override; + bool RecvSetChannelParameters(const uint32_t& aPacketLoss, + const uint32_t& aRTT) override; + bool RecvSetRates(const uint32_t& aNewBitRate, + const uint32_t& aFrameRate) override; + bool RecvSetPeriodicKeyFrames(const bool& aEnable) override; + bool RecvEncodingComplete() override; + + GMPContentChild* mPlugin; + GMPVideoEncoder* mVideoEncoder; + GMPVideoHostImpl mVideoHost; + + // Non-zero when a GMP is blocked spinning the IPC message loop while + // waiting on an NeedShmem to complete. + int mNeedShmemIntrCount; + bool mPendingEncodeComplete; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPVideoEncoderChild_h_ diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp new file mode 100644 index 000000000..95583cd6e --- /dev/null +++ b/dom/media/gmp/GMPVideoEncoderParent.cpp @@ -0,0 +1,382 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoEncoderParent.h" +#include "mozilla/Logging.h" +#include "GMPVideoi420FrameImpl.h" +#include "GMPVideoEncodedFrameImpl.h" +#include "mozilla/Unused.h" +#include "GMPMessageUtils.h" +#include "nsAutoRef.h" +#include "GMPContentParent.h" +#include "mozilla/gmp/GMPTypes.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "runnable_utils.h" +#include "GMPUtils.h" + +namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern LogModule* GetGMPLog(); + +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPVideoEncoderParent" + +namespace gmp { + +// States: +// Initial: mIsOpen == false +// on InitDecode success -> Open +// on Shutdown -> Dead +// Open: mIsOpen == true +// on Close -> Dead +// on ActorDestroy -> Dead +// on Shutdown -> Dead +// Dead: mIsOpen == false + +GMPVideoEncoderParent::GMPVideoEncoderParent(GMPContentParent *aPlugin) +: GMPSharedMemManager(aPlugin), + mIsOpen(false), + mShuttingDown(false), + mActorDestroyed(false), + mPlugin(aPlugin), + mCallback(nullptr), + mVideoHost(this), + mPluginId(aPlugin->GetPluginId()) +{ + MOZ_ASSERT(mPlugin); + + nsresult rv = NS_NewNamedThread("GMPEncoded", getter_AddRefs(mEncodedThread)); + if (NS_FAILED(rv)) { + MOZ_CRASH(); + } +} + +GMPVideoEncoderParent::~GMPVideoEncoderParent() +{ + if (mEncodedThread) { + mEncodedThread->Shutdown(); + } +} + +GMPVideoHostImpl& +GMPVideoEncoderParent::Host() +{ + return mVideoHost; +} + +// Note: may be called via Terminated() +void +GMPVideoEncoderParent::Close() +{ + LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this)); + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + // Consumer is done with us; we can shut down. No more callbacks should + // be made to mCallback. Note: do this before Shutdown()! + mCallback = nullptr; + // Let Shutdown mark us as dead so it knows if we had been alive + + // In case this is the last reference + RefPtr<GMPVideoEncoderParent> kungfudeathgrip(this); + Release(); + Shutdown(); +} + +GMPErr +GMPVideoEncoderParent::InitEncode(const GMPVideoCodec& aCodecSettings, + const nsTArray<uint8_t>& aCodecSpecific, + GMPVideoEncoderCallbackProxy* aCallback, + int32_t aNumberOfCores, + uint32_t aMaxPayloadSize) +{ + LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this)); + if (mIsOpen) { + NS_WARNING("Trying to re-init an in-use GMP video encoder!"); + return GMPGenericErr;; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!aCallback) { + return GMPGenericErr; + } + mCallback = aCallback; + + if (!SendInitEncode(aCodecSettings, aCodecSpecific, aNumberOfCores, aMaxPayloadSize)) { + return GMPGenericErr; + } + mIsOpen = true; + + // Async IPC, we don't have access to a return value. + return GMPNoErr; +} + +GMPErr +GMPVideoEncoderParent::Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame, + const nsTArray<uint8_t>& aCodecSpecificInfo, + const nsTArray<GMPVideoFrameType>& aFrameTypes) +{ + if (!mIsOpen) { + NS_WARNING("Trying to use an dead GMP video encoder"); + return GMPGenericErr; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + GMPUniquePtr<GMPVideoi420FrameImpl> inputFrameImpl( + static_cast<GMPVideoi420FrameImpl*>(aInputFrame.release())); + + // Very rough kill-switch if the plugin stops processing. If it's merely + // hung and continues, we'll come back to life eventually. + // 3* is because we're using 3 buffers per frame for i420 data for now. + if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) || + (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) { + return GMPGenericErr; + } + + GMPVideoi420FrameData frameData; + inputFrameImpl->InitFrameData(frameData); + + if (!SendEncode(frameData, + aCodecSpecificInfo, + aFrameTypes)) { + return GMPGenericErr; + } + + // Async IPC, we don't have access to a return value. + return GMPNoErr; +} + +GMPErr +GMPVideoEncoderParent::SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) +{ + if (!mIsOpen) { + NS_WARNING("Trying to use an invalid GMP video encoder!"); + return GMPGenericErr; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendSetChannelParameters(aPacketLoss, aRTT)) { + return GMPGenericErr; + } + + // Async IPC, we don't have access to a return value. + return GMPNoErr; +} + +GMPErr +GMPVideoEncoderParent::SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) +{ + if (!mIsOpen) { + NS_WARNING("Trying to use an dead GMP video decoder"); + return GMPGenericErr; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendSetRates(aNewBitRate, aFrameRate)) { + return GMPGenericErr; + } + + // Async IPC, we don't have access to a return value. + return GMPNoErr; +} + +GMPErr +GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable) +{ + if (!mIsOpen) { + NS_WARNING("Trying to use an invalid GMP video encoder!"); + return GMPGenericErr; + } + + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (!SendSetPeriodicKeyFrames(aEnable)) { + return GMPGenericErr; + } + + // Async IPC, we don't have access to a return value. + return GMPNoErr; +} + +// Note: Consider keeping ActorDestroy sync'd up when making changes here. +void +GMPVideoEncoderParent::Shutdown() +{ + LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this)); + MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread()); + + if (mShuttingDown) { + return; + } + mShuttingDown = true; + + // Notify client we're gone! Won't occur after Close() + if (mCallback) { + mCallback->Terminated(); + mCallback = nullptr; + } + + mIsOpen = false; + if (!mActorDestroyed) { + Unused << SendEncodingComplete(); + } +} + +static void +ShutdownEncodedThread(nsCOMPtr<nsIThread>& aThread) +{ + aThread->Shutdown(); +} + +// Note: Keep this sync'd up with Shutdown +void +GMPVideoEncoderParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD(("%s::%s: %p (%d)", __CLASS__, __FUNCTION__, this, (int) aWhy)); + mIsOpen = false; + mActorDestroyed = true; + if (mCallback) { + // May call Close() (and Shutdown()) immediately or with a delay + mCallback->Terminated(); + mCallback = nullptr; + } + // Must be shut down before VideoEncoderDestroyed(), since this can recurse + // the GMPThread event loop. See bug 1049501 + if (mEncodedThread) { + // Can't get it to allow me to use WrapRunnable with a nsCOMPtr<nsIThread>() + NS_DispatchToMainThread( + WrapRunnableNM<decltype(&ShutdownEncodedThread), + nsCOMPtr<nsIThread> >(&ShutdownEncodedThread, mEncodedThread)); + mEncodedThread = nullptr; + } + if (mPlugin) { + // Ignore any return code. It is OK for this to fail without killing the process. + mPlugin->VideoEncoderDestroyed(this); + mPlugin = nullptr; + } + mVideoHost.ActorDestroyed(); // same as DoneWithAPI + MaybeDisconnect(aWhy == AbnormalShutdown); +} + +static void +EncodedCallback(GMPVideoEncoderCallbackProxy* aCallback, + GMPVideoEncodedFrame* aEncodedFrame, + nsTArray<uint8_t>* aCodecSpecificInfo, + nsCOMPtr<nsIThread> aThread) +{ + aCallback->Encoded(aEncodedFrame, *aCodecSpecificInfo); + delete aCodecSpecificInfo; + // Ugh. Must destroy the frame on GMPThread. + // XXX add locks to the ShmemManager instead? + aThread->Dispatch(WrapRunnable(aEncodedFrame, + &GMPVideoEncodedFrame::Destroy), + NS_DISPATCH_NORMAL); +} + +bool +GMPVideoEncoderParent::RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame, + InfallibleTArray<uint8_t>&& aCodecSpecificInfo) +{ + if (!mCallback) { + return false; + } + + auto f = new GMPVideoEncodedFrameImpl(aEncodedFrame, &mVideoHost); + nsTArray<uint8_t> *codecSpecificInfo = new nsTArray<uint8_t>; + codecSpecificInfo->AppendElements((uint8_t*)aCodecSpecificInfo.Elements(), aCodecSpecificInfo.Length()); + nsCOMPtr<nsIThread> thread = NS_GetCurrentThread(); + + mEncodedThread->Dispatch(WrapRunnableNM(&EncodedCallback, + mCallback, f, codecSpecificInfo, thread), + NS_DISPATCH_NORMAL); + + return true; +} + +bool +GMPVideoEncoderParent::RecvError(const GMPErr& aError) +{ + if (!mCallback) { + return false; + } + + // Ignore any return code. It is OK for this to fail without killing the process. + mCallback->Error(aError); + + return true; +} + +bool +GMPVideoEncoderParent::RecvShutdown() +{ + Shutdown(); + return true; +} + +bool +GMPVideoEncoderParent::RecvParentShmemForPool(Shmem&& aFrameBuffer) +{ + if (aFrameBuffer.IsWritable()) { + // This test may be paranoia now that we don't shut down the VideoHost + // in ::Shutdown, but doesn't hurt + if (mVideoHost.SharedMemMgr()) { + mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, + aFrameBuffer); + } else { + LOGD(("%s::%s: %p Called in shutdown, ignoring and freeing directly", __CLASS__, __FUNCTION__, this)); + DeallocShmem(aFrameBuffer); + } + } + return true; +} + +bool +GMPVideoEncoderParent::AnswerNeedShmem(const uint32_t& aEncodedBufferSize, + Shmem* aMem) +{ + ipc::Shmem mem; + + // This test may be paranoia now that we don't shut down the VideoHost + // in ::Shutdown, but doesn't hurt + if (!mVideoHost.SharedMemMgr() || + !mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPEncodedData, + aEncodedBufferSize, + ipc::SharedMemory::TYPE_BASIC, &mem)) + { + LOG(LogLevel::Error, ("%s::%s: Failed to get a shared mem buffer for Child! size %u", + __CLASS__, __FUNCTION__, aEncodedBufferSize)); + return false; + } + *aMem = mem; + mem = ipc::Shmem(); + return true; +} + +bool +GMPVideoEncoderParent::Recv__delete__() +{ + if (mPlugin) { + // Ignore any return code. It is OK for this to fail without killing the process. + mPlugin->VideoEncoderDestroyed(this); + mPlugin = nullptr; + } + + return true; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h new file mode 100644 index 000000000..e7dade692 --- /dev/null +++ b/dom/media/gmp/GMPVideoEncoderParent.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoEncoderParent_h_ +#define GMPVideoEncoderParent_h_ + +#include "mozilla/RefPtr.h" +#include "gmp-video-encode.h" +#include "mozilla/gmp/PGMPVideoEncoderParent.h" +#include "GMPMessageUtils.h" +#include "GMPSharedMemManager.h" +#include "GMPUtils.h" +#include "GMPVideoHost.h" +#include "GMPVideoEncoderProxy.h" +#include "GMPCrashHelperHolder.h" + +namespace mozilla { +namespace gmp { + +class GMPContentParent; + +class GMPVideoEncoderParent : public GMPVideoEncoderProxy, + public PGMPVideoEncoderParent, + public GMPSharedMemManager, + public GMPCrashHelperHolder +{ +public: + NS_INLINE_DECL_REFCOUNTING(GMPVideoEncoderParent) + + explicit GMPVideoEncoderParent(GMPContentParent *aPlugin); + + GMPVideoHostImpl& Host(); + void Shutdown(); + + // GMPVideoEncoderProxy + void Close() override; + GMPErr InitEncode(const GMPVideoCodec& aCodecSettings, + const nsTArray<uint8_t>& aCodecSpecific, + GMPVideoEncoderCallbackProxy* aCallback, + int32_t aNumberOfCores, + uint32_t aMaxPayloadSize) override; + GMPErr Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame, + const nsTArray<uint8_t>& aCodecSpecificInfo, + const nsTArray<GMPVideoFrameType>& aFrameTypes) override; + GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override; + GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override; + GMPErr SetPeriodicKeyFrames(bool aEnable) override; + uint32_t GetPluginId() const override { return mPluginId; } + + // GMPSharedMemManager + bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override + { +#ifdef GMP_SAFE_SHMEM + return AllocShmem(aSize, aType, aMem); +#else + return AllocUnsafeShmem(aSize, aType, aMem); +#endif + } + void Dealloc(Shmem& aMem) override + { + DeallocShmem(aMem); + } + +private: + virtual ~GMPVideoEncoderParent(); + + // PGMPVideoEncoderParent + void ActorDestroy(ActorDestroyReason aWhy) override; + bool RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame, + InfallibleTArray<uint8_t>&& aCodecSpecificInfo) override; + bool RecvError(const GMPErr& aError) override; + bool RecvShutdown() override; + bool RecvParentShmemForPool(Shmem&& aFrameBuffer) override; + bool AnswerNeedShmem(const uint32_t& aEncodedBufferSize, + Shmem* aMem) override; + bool Recv__delete__() override; + + bool mIsOpen; + bool mShuttingDown; + bool mActorDestroyed; + RefPtr<GMPContentParent> mPlugin; + GMPVideoEncoderCallbackProxy* mCallback; + GMPVideoHostImpl mVideoHost; + nsCOMPtr<nsIThread> mEncodedThread; + const uint32_t mPluginId; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPVideoEncoderParent_h_ diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h new file mode 100644 index 000000000..655b1e9ae --- /dev/null +++ b/dom/media/gmp/GMPVideoEncoderProxy.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoEncoderProxy_h_ +#define GMPVideoEncoderProxy_h_ + +#include "nsTArray.h" +#include "gmp-video-encode.h" +#include "gmp-video-frame-i420.h" +#include "gmp-video-frame-encoded.h" + +#include "GMPCallbackBase.h" +#include "GMPUtils.h" + +class GMPVideoEncoderCallbackProxy : public GMPCallbackBase { +public: + virtual ~GMPVideoEncoderCallbackProxy() {} + virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame, + const nsTArray<uint8_t>& aCodecSpecificInfo) = 0; + virtual void Error(GMPErr aError) = 0; +}; + +// A proxy to GMPVideoEncoder in the child process. +// GMPVideoEncoderParent exposes this to users the GMP. +// This enables Gecko to pass nsTArrays to the child GMP and avoid +// an extra copy when doing so. + +// The consumer must call Close() when done with the codec, or when +// Terminated() is called by the GMP plugin indicating an abnormal shutdown +// of the underlying plugin. After calling Close(), the consumer must +// not access this again. + +// This interface is not thread-safe and must only be used from GMPThread. +class GMPVideoEncoderProxy { +public: + virtual GMPErr InitEncode(const GMPVideoCodec& aCodecSettings, + const nsTArray<uint8_t>& aCodecSpecific, + GMPVideoEncoderCallbackProxy* aCallback, + int32_t aNumberOfCores, + uint32_t aMaxPayloadSize) = 0; + virtual GMPErr Encode(mozilla::GMPUniquePtr<GMPVideoi420Frame> aInputFrame, + const nsTArray<uint8_t>& aCodecSpecificInfo, + const nsTArray<GMPVideoFrameType>& aFrameTypes) = 0; + virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0; + virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0; + virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0; + virtual uint32_t GetPluginId() const = 0; + + // Call to tell GMP/plugin the consumer will no longer use this + // interface/codec. + virtual void Close() = 0; +}; + +#endif // GMPVideoEncoderProxy_h_ diff --git a/dom/media/gmp/GMPVideoHost.cpp b/dom/media/gmp/GMPVideoHost.cpp new file mode 100644 index 000000000..db40ebdae --- /dev/null +++ b/dom/media/gmp/GMPVideoHost.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoHost.h" +#include "mozilla/Assertions.h" +#include "GMPVideoi420FrameImpl.h" +#include "GMPVideoEncodedFrameImpl.h" + +namespace mozilla { +namespace gmp { + +GMPVideoHostImpl::GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr) +: mSharedMemMgr(aSharedMemMgr) +{ +} + +GMPVideoHostImpl::~GMPVideoHostImpl() +{ +} + +GMPErr +GMPVideoHostImpl::CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) +{ + if (!mSharedMemMgr) { + return GMPGenericErr; + } + + if (!aFrame) { + return GMPGenericErr; + } + *aFrame = nullptr; + + switch (aFormat) { + case kGMPI420VideoFrame: + *aFrame = new GMPVideoi420FrameImpl(this); + return GMPNoErr; + case kGMPEncodedVideoFrame: + *aFrame = new GMPVideoEncodedFrameImpl(this); + return GMPNoErr; + default: + NS_NOTREACHED("Unknown frame format!"); + } + + return GMPGenericErr; +} + +GMPErr +GMPVideoHostImpl::CreatePlane(GMPPlane** aPlane) +{ + if (!mSharedMemMgr) { + return GMPGenericErr; + } + + if (!aPlane) { + return GMPGenericErr; + } + *aPlane = nullptr; + + auto p = new GMPPlaneImpl(this); + + *aPlane = p; + + return GMPNoErr; +} + +GMPSharedMemManager* +GMPVideoHostImpl::SharedMemMgr() +{ + return mSharedMemMgr; +} + +// XXX This should merge with ActorDestroyed +void +GMPVideoHostImpl::DoneWithAPI() +{ + ActorDestroyed(); +} + +void +GMPVideoHostImpl::ActorDestroyed() +{ + for (uint32_t i = mPlanes.Length(); i > 0; i--) { + mPlanes[i - 1]->DoneWithAPI(); + mPlanes.RemoveElementAt(i - 1); + } + for (uint32_t i = mEncodedFrames.Length(); i > 0; i--) { + mEncodedFrames[i - 1]->DoneWithAPI(); + mEncodedFrames.RemoveElementAt(i - 1); + } + mSharedMemMgr = nullptr; +} + +void +GMPVideoHostImpl::PlaneCreated(GMPPlaneImpl* aPlane) +{ + mPlanes.AppendElement(aPlane); +} + +void +GMPVideoHostImpl::PlaneDestroyed(GMPPlaneImpl* aPlane) +{ + MOZ_ALWAYS_TRUE(mPlanes.RemoveElement(aPlane)); +} + +void +GMPVideoHostImpl::EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame) +{ + mEncodedFrames.AppendElement(aEncodedFrame); +} + +void +GMPVideoHostImpl::EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame) +{ + MOZ_ALWAYS_TRUE(mEncodedFrames.RemoveElement(aFrame)); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoHost.h b/dom/media/gmp/GMPVideoHost.h new file mode 100644 index 000000000..b3e42f08e --- /dev/null +++ b/dom/media/gmp/GMPVideoHost.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoHost_h_ +#define GMPVideoHost_h_ + +#include "gmp-video-host.h" +#include "gmp-video-plane.h" +#include "gmp-video-frame.h" +#include "gmp-video-host.h" +#include "nsTArray.h" + +namespace mozilla { +namespace gmp { + +class GMPSharedMemManager; +class GMPPlaneImpl; +class GMPVideoEncodedFrameImpl; + +class GMPVideoHostImpl : public GMPVideoHost +{ +public: + explicit GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr); + virtual ~GMPVideoHostImpl(); + + // Used for shared memory allocation and deallocation. + GMPSharedMemManager* SharedMemMgr(); + void DoneWithAPI(); + void ActorDestroyed(); + void PlaneCreated(GMPPlaneImpl* aPlane); + void PlaneDestroyed(GMPPlaneImpl* aPlane); + void EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame); + void EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame); + + // GMPVideoHost + GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) override; + GMPErr CreatePlane(GMPPlane** aPlane) override; + +private: + // All shared memory allocations have to be made by an IPDL actor. + // This is a reference to the owning actor. If this reference is + // null then the actor has died and all allocations must fail. + GMPSharedMemManager* mSharedMemMgr; + + // We track all of these things because they need to handle further + // allocations through us and we need to notify them when they + // can't use us any more. + nsTArray<GMPPlaneImpl*> mPlanes; + nsTArray<GMPVideoEncodedFrameImpl*> mEncodedFrames; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPVideoHost_h_ diff --git a/dom/media/gmp/GMPVideoPlaneImpl.cpp b/dom/media/gmp/GMPVideoPlaneImpl.cpp new file mode 100644 index 000000000..074a965e8 --- /dev/null +++ b/dom/media/gmp/GMPVideoPlaneImpl.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoPlaneImpl.h" +#include "mozilla/gmp/GMPTypes.h" +#include "GMPVideoHost.h" +#include "GMPSharedMemManager.h" + +namespace mozilla { +namespace gmp { + +GMPPlaneImpl::GMPPlaneImpl(GMPVideoHostImpl* aHost) +: mSize(0), + mStride(0), + mHost(aHost) +{ + MOZ_ASSERT(mHost); + mHost->PlaneCreated(this); +} + +GMPPlaneImpl::GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost) +: mBuffer(aPlaneData.mBuffer()), + mSize(aPlaneData.mSize()), + mStride(aPlaneData.mStride()), + mHost(aHost) +{ + MOZ_ASSERT(mHost); + mHost->PlaneCreated(this); +} + +GMPPlaneImpl::~GMPPlaneImpl() +{ + DestroyBuffer(); + if (mHost) { + mHost->PlaneDestroyed(this); + } +} + +void +GMPPlaneImpl::DoneWithAPI() +{ + DestroyBuffer(); + + // Do this after destroying the buffer because destruction + // involves deallocation, which requires a host. + mHost = nullptr; +} + +void +GMPPlaneImpl::ActorDestroyed() +{ + // Simply clear out Shmem reference, do not attempt to + // properly free it. It has already been freed. + mBuffer = ipc::Shmem(); + // No more host. + mHost = nullptr; +} + +bool +GMPPlaneImpl::InitPlaneData(GMPPlaneData& aPlaneData) +{ + aPlaneData.mBuffer() = mBuffer; + aPlaneData.mSize() = mSize; + aPlaneData.mStride() = mStride; + + // This method is called right before Shmem is sent to another process. + // We need to effectively zero out our member copy so that we don't + // try to delete memory we don't own later. + mBuffer = ipc::Shmem(); + + return true; +} + +GMPErr +GMPPlaneImpl::MaybeResize(int32_t aNewSize) { + if (aNewSize <= AllocatedSize()) { + return GMPNoErr; + } + + if (!mHost) { + return GMPGenericErr; + } + + ipc::Shmem new_mem; + if (!mHost->SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData, aNewSize, + ipc::SharedMemory::TYPE_BASIC, &new_mem) || + !new_mem.get<uint8_t>()) { + return GMPAllocErr; + } + + if (mBuffer.IsReadable()) { + memcpy(new_mem.get<uint8_t>(), Buffer(), mSize); + } + + DestroyBuffer(); + + mBuffer = new_mem; + + return GMPNoErr; +} + +void +GMPPlaneImpl::DestroyBuffer() +{ + if (mHost && mBuffer.IsWritable()) { + mHost->SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, mBuffer); + } + mBuffer = ipc::Shmem(); +} + +GMPErr +GMPPlaneImpl::CreateEmptyPlane(int32_t aAllocatedSize, int32_t aStride, int32_t aPlaneSize) +{ + if (aAllocatedSize < 1 || aStride < 1 || aPlaneSize < 1) { + return GMPGenericErr; + } + + GMPErr err = MaybeResize(aAllocatedSize); + if (err != GMPNoErr) { + return err; + } + + mSize = aPlaneSize; + mStride = aStride; + + return GMPNoErr; +} + +GMPErr +GMPPlaneImpl::Copy(const GMPPlane& aPlane) +{ + auto& planeimpl = static_cast<const GMPPlaneImpl&>(aPlane); + + GMPErr err = MaybeResize(planeimpl.mSize); + if (err != GMPNoErr) { + return err; + } + + if (planeimpl.Buffer() && planeimpl.mSize > 0) { + memcpy(Buffer(), planeimpl.Buffer(), mSize); + } + + mSize = planeimpl.mSize; + mStride = planeimpl.mStride; + + return GMPNoErr; +} + +GMPErr +GMPPlaneImpl::Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer) +{ + GMPErr err = MaybeResize(aSize); + if (err != GMPNoErr) { + return err; + } + + if (aBuffer && aSize > 0) { + memcpy(Buffer(), aBuffer, aSize); + } + + mSize = aSize; + mStride = aStride; + + return GMPNoErr; +} + +void +GMPPlaneImpl::Swap(GMPPlane& aPlane) +{ + auto& planeimpl = static_cast<GMPPlaneImpl&>(aPlane); + + std::swap(mStride, planeimpl.mStride); + std::swap(mSize, planeimpl.mSize); + std::swap(mBuffer, planeimpl.mBuffer); +} + +int32_t +GMPPlaneImpl::AllocatedSize() const +{ + if (mBuffer.IsWritable()) { + return mBuffer.Size<uint8_t>(); + } + return 0; +} + +void +GMPPlaneImpl::ResetSize() +{ + mSize = 0; +} + +bool +GMPPlaneImpl::IsZeroSize() const +{ + return (mSize == 0); +} + +int32_t +GMPPlaneImpl::Stride() const +{ + return mStride; +} + +const uint8_t* +GMPPlaneImpl::Buffer() const +{ + return mBuffer.get<uint8_t>(); +} + +uint8_t* +GMPPlaneImpl::Buffer() +{ + return mBuffer.get<uint8_t>(); +} + +void +GMPPlaneImpl::Destroy() +{ + delete this; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoPlaneImpl.h b/dom/media/gmp/GMPVideoPlaneImpl.h new file mode 100644 index 000000000..d3a0d753a --- /dev/null +++ b/dom/media/gmp/GMPVideoPlaneImpl.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoPlaneImpl_h_ +#define GMPVideoPlaneImpl_h_ + +#include "gmp-video-plane.h" +#include "mozilla/ipc/Shmem.h" + +namespace mozilla { +namespace gmp { + +class GMPVideoHostImpl; +class GMPPlaneData; + +class GMPPlaneImpl : public GMPPlane +{ + friend struct IPC::ParamTraits<mozilla::gmp::GMPPlaneImpl>; +public: + explicit GMPPlaneImpl(GMPVideoHostImpl* aHost); + GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost); + virtual ~GMPPlaneImpl(); + + // This is called during a normal destroy sequence, which is + // when a consumer is finished or during XPCOM shutdown. + void DoneWithAPI(); + // This is called when something has gone wrong - specicifically, + // a child process has crashed. Does not attempt to release Shmem, + // as the Shmem has already been released. + void ActorDestroyed(); + + bool InitPlaneData(GMPPlaneData& aPlaneData); + + // GMPPlane + GMPErr CreateEmptyPlane(int32_t aAllocatedSize, + int32_t aStride, + int32_t aPlaneSize) override; + GMPErr Copy(const GMPPlane& aPlane) override; + GMPErr Copy(int32_t aSize, + int32_t aStride, + const uint8_t* aBuffer) override; + void Swap(GMPPlane& aPlane) override; + int32_t AllocatedSize() const override; + void ResetSize() override; + bool IsZeroSize() const override; + int32_t Stride() const override; + const uint8_t* Buffer() const override; + uint8_t* Buffer() override; + void Destroy() override; + +private: + GMPErr MaybeResize(int32_t aNewSize); + void DestroyBuffer(); + + ipc::Shmem mBuffer; + int32_t mSize; + int32_t mStride; + GMPVideoHostImpl* mHost; +}; + +} // namespace gmp +} // namespace mozilla + +#endif // GMPVideoPlaneImpl_h_ diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.cpp b/dom/media/gmp/GMPVideoi420FrameImpl.cpp new file mode 100644 index 000000000..fdbb9a962 --- /dev/null +++ b/dom/media/gmp/GMPVideoi420FrameImpl.cpp @@ -0,0 +1,365 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoi420FrameImpl.h" +#include "mozilla/gmp/GMPTypes.h" +#include "mozilla/CheckedInt.h" + +namespace mozilla { +namespace gmp { + +GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost) +: mYPlane(aHost), + mUPlane(aHost), + mVPlane(aHost), + mWidth(0), + mHeight(0), + mTimestamp(0ll), + mDuration(0ll) +{ + MOZ_ASSERT(aHost); +} + +GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, + GMPVideoHostImpl* aHost) +: mYPlane(aFrameData.mYPlane(), aHost), + mUPlane(aFrameData.mUPlane(), aHost), + mVPlane(aFrameData.mVPlane(), aHost), + mWidth(aFrameData.mWidth()), + mHeight(aFrameData.mHeight()), + mTimestamp(aFrameData.mTimestamp()), + mDuration(aFrameData.mDuration()) +{ + MOZ_ASSERT(aHost); +} + +GMPVideoi420FrameImpl::~GMPVideoi420FrameImpl() +{ +} + +bool +GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData) +{ + mYPlane.InitPlaneData(aFrameData.mYPlane()); + mUPlane.InitPlaneData(aFrameData.mUPlane()); + mVPlane.InitPlaneData(aFrameData.mVPlane()); + aFrameData.mWidth() = mWidth; + aFrameData.mHeight() = mHeight; + aFrameData.mTimestamp() = mTimestamp; + aFrameData.mDuration() = mDuration; + return true; +} + +GMPVideoFrameFormat +GMPVideoi420FrameImpl::GetFrameFormat() +{ + return kGMPI420VideoFrame; +} + +void +GMPVideoi420FrameImpl::Destroy() +{ + delete this; +} + +/* static */ bool +GMPVideoi420FrameImpl::CheckFrameData(const GMPVideoi420FrameData& aFrameData) +{ + // We may be passed the "wrong" shmem (one smaller than the actual size). + // This implies a bug or serious error on the child size. Ignore this frame if so. + // Note: Size() greater than expected is also an error, but with no negative consequences + int32_t half_width = (aFrameData.mWidth() + 1) / 2; + if ((aFrameData.mYPlane().mStride() <= 0) || (aFrameData.mYPlane().mSize() <= 0) || + (aFrameData.mUPlane().mStride() <= 0) || (aFrameData.mUPlane().mSize() <= 0) || + (aFrameData.mVPlane().mStride() <= 0) || (aFrameData.mVPlane().mSize() <= 0) || + (aFrameData.mYPlane().mSize() > (int32_t) aFrameData.mYPlane().mBuffer().Size<uint8_t>()) || + (aFrameData.mUPlane().mSize() > (int32_t) aFrameData.mUPlane().mBuffer().Size<uint8_t>()) || + (aFrameData.mVPlane().mSize() > (int32_t) aFrameData.mVPlane().mBuffer().Size<uint8_t>()) || + (aFrameData.mYPlane().mStride() < aFrameData.mWidth()) || + (aFrameData.mUPlane().mStride() < half_width) || + (aFrameData.mVPlane().mStride() < half_width) || + (aFrameData.mYPlane().mSize() < aFrameData.mYPlane().mStride() * aFrameData.mHeight()) || + (aFrameData.mUPlane().mSize() < aFrameData.mUPlane().mStride() * ((aFrameData.mHeight()+1)/2)) || + (aFrameData.mVPlane().mSize() < aFrameData.mVPlane().mStride() * ((aFrameData.mHeight()+1)/2))) + { + return false; + } + return true; +} + +bool +GMPVideoi420FrameImpl::CheckDimensions(int32_t aWidth, int32_t aHeight, + int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) +{ + int32_t half_width = (aWidth + 1) / 2; + if (aWidth < 1 || aHeight < 1 || + aStride_y < aWidth || aStride_u < half_width || aStride_v < half_width || + !(CheckedInt<int32_t>(aHeight) * aStride_y + + ((CheckedInt<int32_t>(aHeight) + 1) / 2) + * (CheckedInt<int32_t>(aStride_u) + aStride_v)).isValid()) { + return false; + } + return true; +} + +const GMPPlaneImpl* +GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) const { + switch (aType) { + case kGMPYPlane: + return &mYPlane; + case kGMPUPlane: + return &mUPlane; + case kGMPVPlane: + return &mVPlane; + default: + MOZ_CRASH("Unknown plane type!"); + } + return nullptr; +} + +GMPPlaneImpl* +GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) { + switch (aType) { + case kGMPYPlane : + return &mYPlane; + case kGMPUPlane : + return &mUPlane; + case kGMPVPlane : + return &mVPlane; + default: + MOZ_CRASH("Unknown plane type!"); + } + return nullptr; +} + +GMPErr +GMPVideoi420FrameImpl::CreateEmptyFrame(int32_t aWidth, int32_t aHeight, + int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) +{ + if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) { + return GMPGenericErr; + } + + int32_t size_y = aStride_y * aHeight; + int32_t half_height = (aHeight + 1) / 2; + int32_t size_u = aStride_u * half_height; + int32_t size_v = aStride_v * half_height; + + GMPErr err = mYPlane.CreateEmptyPlane(size_y, aStride_y, size_y); + if (err != GMPNoErr) { + return err; + } + err = mUPlane.CreateEmptyPlane(size_u, aStride_u, size_u); + if (err != GMPNoErr) { + return err; + } + err = mVPlane.CreateEmptyPlane(size_v, aStride_v, size_v); + if (err != GMPNoErr) { + return err; + } + + mWidth = aWidth; + mHeight = aHeight; + mTimestamp = 0ll; + mDuration = 0ll; + + return GMPNoErr; +} + +GMPErr +GMPVideoi420FrameImpl::CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y, + int32_t aSize_u, const uint8_t* aBuffer_u, + int32_t aSize_v, const uint8_t* aBuffer_v, + int32_t aWidth, int32_t aHeight, + int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) +{ + MOZ_ASSERT(aBuffer_y); + MOZ_ASSERT(aBuffer_u); + MOZ_ASSERT(aBuffer_v); + + if (aSize_y < 1 || aSize_u < 1 || aSize_v < 1) { + return GMPGenericErr; + } + + if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) { + return GMPGenericErr; + } + + GMPErr err = mYPlane.Copy(aSize_y, aStride_y, aBuffer_y); + if (err != GMPNoErr) { + return err; + } + err = mUPlane.Copy(aSize_u, aStride_u, aBuffer_u); + if (err != GMPNoErr) { + return err; + } + err = mVPlane.Copy(aSize_v, aStride_v, aBuffer_v); + if (err != GMPNoErr) { + return err; + } + + mWidth = aWidth; + mHeight = aHeight; + + return GMPNoErr; +} + +GMPErr +GMPVideoi420FrameImpl::CopyFrame(const GMPVideoi420Frame& aFrame) +{ + auto& f = static_cast<const GMPVideoi420FrameImpl&>(aFrame); + + GMPErr err = mYPlane.Copy(f.mYPlane); + if (err != GMPNoErr) { + return err; + } + + err = mUPlane.Copy(f.mUPlane); + if (err != GMPNoErr) { + return err; + } + + err = mVPlane.Copy(f.mVPlane); + if (err != GMPNoErr) { + return err; + } + + mWidth = f.mWidth; + mHeight = f.mHeight; + mTimestamp = f.mTimestamp; + mDuration = f.mDuration; + + return GMPNoErr; +} + +void +GMPVideoi420FrameImpl::SwapFrame(GMPVideoi420Frame* aFrame) +{ + auto f = static_cast<GMPVideoi420FrameImpl*>(aFrame); + mYPlane.Swap(f->mYPlane); + mUPlane.Swap(f->mUPlane); + mVPlane.Swap(f->mVPlane); + std::swap(mWidth, f->mWidth); + std::swap(mHeight, f->mHeight); + std::swap(mTimestamp, f->mTimestamp); + std::swap(mDuration, f->mDuration); +} + +uint8_t* +GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) +{ + GMPPlane* p = GetPlane(aType); + if (p) { + return p->Buffer(); + } + return nullptr; +} + +const uint8_t* +GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) const +{ + const GMPPlane* p = GetPlane(aType); + if (p) { + return p->Buffer(); + } + return nullptr; +} + +int32_t +GMPVideoi420FrameImpl::AllocatedSize(GMPPlaneType aType) const +{ + const GMPPlane* p = GetPlane(aType); + if (p) { + return p->AllocatedSize(); + } + return -1; +} + +int32_t +GMPVideoi420FrameImpl::Stride(GMPPlaneType aType) const +{ + const GMPPlane* p = GetPlane(aType); + if (p) { + return p->Stride(); + } + return -1; +} + +GMPErr +GMPVideoi420FrameImpl::SetWidth(int32_t aWidth) +{ + if (!CheckDimensions(aWidth, mHeight, + mYPlane.Stride(), mUPlane.Stride(), + mVPlane.Stride())) { + return GMPGenericErr; + } + mWidth = aWidth; + return GMPNoErr; +} + +GMPErr +GMPVideoi420FrameImpl::SetHeight(int32_t aHeight) +{ + if (!CheckDimensions(mWidth, aHeight, + mYPlane.Stride(), mUPlane.Stride(), + mVPlane.Stride())) { + return GMPGenericErr; + } + mHeight = aHeight; + return GMPNoErr; +} + +int32_t +GMPVideoi420FrameImpl::Width() const +{ + return mWidth; +} + +int32_t +GMPVideoi420FrameImpl::Height() const +{ + return mHeight; +} + +void +GMPVideoi420FrameImpl::SetTimestamp(uint64_t aTimestamp) +{ + mTimestamp = aTimestamp; +} + +uint64_t +GMPVideoi420FrameImpl::Timestamp() const +{ + return mTimestamp; +} + +void +GMPVideoi420FrameImpl::SetDuration(uint64_t aDuration) +{ + mDuration = aDuration; +} + +uint64_t +GMPVideoi420FrameImpl::Duration() const +{ + return mDuration; +} + +bool +GMPVideoi420FrameImpl::IsZeroSize() const +{ + return (mYPlane.IsZeroSize() && mUPlane.IsZeroSize() && mVPlane.IsZeroSize()); +} + +void +GMPVideoi420FrameImpl::ResetSize() +{ + mYPlane.ResetSize(); + mUPlane.ResetSize(); + mVPlane.ResetSize(); +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.h b/dom/media/gmp/GMPVideoi420FrameImpl.h new file mode 100644 index 000000000..f5cb0254b --- /dev/null +++ b/dom/media/gmp/GMPVideoi420FrameImpl.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMPVideoi420FrameImpl_h_ +#define GMPVideoi420FrameImpl_h_ + +#include "gmp-video-frame-i420.h" +#include "mozilla/ipc/Shmem.h" +#include "GMPVideoPlaneImpl.h" + +namespace mozilla { +namespace gmp { + +class GMPVideoi420FrameData; + +class GMPVideoi420FrameImpl : public GMPVideoi420Frame +{ + friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoi420FrameImpl>; +public: + explicit GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost); + GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, GMPVideoHostImpl* aHost); + virtual ~GMPVideoi420FrameImpl(); + + static bool CheckFrameData(const GMPVideoi420FrameData& aFrameData); + + bool InitFrameData(GMPVideoi420FrameData& aFrameData); + const GMPPlaneImpl* GetPlane(GMPPlaneType aType) const; + GMPPlaneImpl* GetPlane(GMPPlaneType aType); + + // GMPVideoFrame + GMPVideoFrameFormat GetFrameFormat() override; + void Destroy() override; + + // GMPVideoi420Frame + GMPErr CreateEmptyFrame(int32_t aWidth, + int32_t aHeight, + int32_t aStride_y, + int32_t aStride_u, + int32_t aStride_v) override; + GMPErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y, + int32_t aSize_u, const uint8_t* aBuffer_u, + int32_t aSize_v, const uint8_t* aBuffer_v, + int32_t aWidth, + int32_t aHeight, + int32_t aStride_y, + int32_t aStride_u, + int32_t aStride_v) override; + GMPErr CopyFrame(const GMPVideoi420Frame& aFrame) override; + void SwapFrame(GMPVideoi420Frame* aFrame) override; + uint8_t* Buffer(GMPPlaneType aType) override; + const uint8_t* Buffer(GMPPlaneType aType) const override; + int32_t AllocatedSize(GMPPlaneType aType) const override; + int32_t Stride(GMPPlaneType aType) const override; + GMPErr SetWidth(int32_t aWidth) override; + GMPErr SetHeight(int32_t aHeight) override; + int32_t Width() const override; + int32_t Height() const override; + void SetTimestamp(uint64_t aTimestamp) override; + uint64_t Timestamp() const override; + void SetDuration(uint64_t aDuration) override; + uint64_t Duration() const override; + bool IsZeroSize() const override; + void ResetSize() override; + +private: + bool CheckDimensions(int32_t aWidth, int32_t aHeight, + int32_t aStride_y, int32_t aStride_u, int32_t aStride_v); + + GMPPlaneImpl mYPlane; + GMPPlaneImpl mUPlane; + GMPPlaneImpl mVPlane; + int32_t mWidth; + int32_t mHeight; + uint64_t mTimestamp; + uint64_t mDuration; +}; + +} // namespace gmp + +} // namespace mozilla + +#endif // GMPVideoi420FrameImpl_h_ diff --git a/dom/media/gmp/PGMP.ipdl b/dom/media/gmp/PGMP.ipdl new file mode 100644 index 000000000..e1738d010 --- /dev/null +++ b/dom/media/gmp/PGMP.ipdl @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMPContent; +include protocol PGMPTimer; +include protocol PGMPStorage; + +using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h"; + +namespace mozilla { +namespace gmp { + +intr protocol PGMP +{ + parent opens PGMPContent; + + manages PGMPTimer; + manages PGMPStorage; + +parent: + async PGMPTimer(); + async PGMPStorage(); + + async PGMPContentChildDestroyed(); + + async AsyncShutdownComplete(); + async AsyncShutdownRequired(); + +child: + async BeginAsyncShutdown(); + async CrashPluginNow(); + intr StartPlugin(nsString adapter); + async SetNodeId(nsCString nodeId); + async PreloadLibs(nsCString libs); + async CloseActive(); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPAudioDecoder.ipdl b/dom/media/gmp/PGMPAudioDecoder.ipdl new file mode 100644 index 000000000..9af9415c8 --- /dev/null +++ b/dom/media/gmp/PGMPAudioDecoder.ipdl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMPContent; +include GMPTypes; + +using GMPCodecSpecificInfo from "gmp-audio-codec.h"; +using GMPErr from "gmp-errors.h"; + +include "GMPMessageUtils.h"; + +namespace mozilla { +namespace gmp { + +async protocol PGMPAudioDecoder +{ + manager PGMPContent; +child: + async InitDecode(GMPAudioCodecData aCodecSettings); + async Decode(GMPAudioEncodedSampleData aInput); + async Reset(); + async Drain(); + async DecodingComplete(); +parent: + async __delete__(); + async Decoded(GMPAudioDecodedSampleData aDecoded); + async InputDataExhausted(); + async DrainComplete(); + async ResetComplete(); + async Error(GMPErr aErr); + async Shutdown(); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPContent.ipdl b/dom/media/gmp/PGMPContent.ipdl new file mode 100644 index 000000000..00e16c02f --- /dev/null +++ b/dom/media/gmp/PGMPContent.ipdl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMP; +include protocol PGMPService; +include protocol PGMPVideoDecoder; +include protocol PGMPVideoEncoder; +include protocol PGMPDecryptor; +include protocol PGMPAudioDecoder; + +namespace mozilla { +namespace gmp { + +intr protocol PGMPContent +{ + bridges PGMPService, PGMP; + + manages PGMPAudioDecoder; + manages PGMPDecryptor; + manages PGMPVideoDecoder; + manages PGMPVideoEncoder; + +child: + async PGMPAudioDecoder(); + async PGMPDecryptor(); + async PGMPVideoDecoder(uint32_t aDecryptorId); + async PGMPVideoEncoder(); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPDecryptor.ipdl b/dom/media/gmp/PGMPDecryptor.ipdl new file mode 100644 index 000000000..06b9b9cb6 --- /dev/null +++ b/dom/media/gmp/PGMPDecryptor.ipdl @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMPContent; +include GMPTypes; + +using GMPSessionMessageType from "gmp-decryption.h"; +using GMPSessionType from "gmp-decryption.h"; +using GMPDOMException from "gmp-decryption.h"; +using GMPErr from "gmp-errors.h"; + +namespace mozilla { +namespace gmp { + +async protocol PGMPDecryptor +{ + manager PGMPContent; +child: + + async Init(bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired); + + async CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + nsCString aInitDataType, + uint8_t[] aInitData, + GMPSessionType aSessionType); + + async LoadSession(uint32_t aPromiseId, + nsCString aSessionId); + + async UpdateSession(uint32_t aPromiseId, + nsCString aSessionId, + uint8_t[] aResponse); + + async CloseSession(uint32_t aPromiseId, + nsCString aSessionId); + + async RemoveSession(uint32_t aPromiseId, + nsCString aSessionId); + + async SetServerCertificate(uint32_t aPromiseId, + uint8_t[] aServerCert); + + async Decrypt(uint32_t aId, + uint8_t[] aBuffer, + GMPDecryptionData aMetadata); + + async DecryptingComplete(); + +parent: + async __delete__(); + + async SetDecryptorId(uint32_t aId); + + async SetSessionId(uint32_t aCreateSessionToken, + nsCString aSessionId); + + async ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess); + + async ResolvePromise(uint32_t aPromiseId); + + async RejectPromise(uint32_t aPromiseId, + GMPDOMException aDOMExceptionCode, + nsCString aMessage); + + async SessionMessage(nsCString aSessionId, + GMPSessionMessageType aMessageType, + uint8_t[] aMessage); + + async ExpirationChange(nsCString aSessionId, double aExpiryTime); + + async SessionClosed(nsCString aSessionId); + + async SessionError(nsCString aSessionId, + GMPDOMException aDOMExceptionCode, + uint32_t aSystemCode, + nsCString aMessage); + + async Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer); + + async Shutdown(); + + async BatchedKeyStatusChanged(nsCString aSessionId, + GMPKeyInformation[] aKeyInfos); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPService.ipdl b/dom/media/gmp/PGMPService.ipdl new file mode 100644 index 000000000..db3fb6388 --- /dev/null +++ b/dom/media/gmp/PGMPService.ipdl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMP; + +using base::ProcessId from "base/process.h"; + +namespace mozilla { +namespace gmp { + +sync protocol PGMPService +{ + parent spawns PGMP as child; + +parent: + + sync SelectGMP(nsCString nodeId, nsCString api, nsCString[] tags) + returns (uint32_t pluginId, nsresult aResult); + + sync LaunchGMP(uint32_t pluginId, ProcessId[] alreadyBridgedTo) + returns (ProcessId id, nsCString displayName, nsresult aResult); + + sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, + nsString gmpName, + bool inPrivateBrowsing) + returns (nsCString id); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPStorage.ipdl b/dom/media/gmp/PGMPStorage.ipdl new file mode 100644 index 000000000..4d858312a --- /dev/null +++ b/dom/media/gmp/PGMPStorage.ipdl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMP; +include GMPTypes; + +using GMPErr from "gmp-errors.h"; + +namespace mozilla { +namespace gmp { + +async protocol PGMPStorage +{ + manager PGMP; + +child: + async OpenComplete(nsCString aRecordName, GMPErr aStatus); + async ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes); + async WriteComplete(nsCString aRecordName, GMPErr aStatus); + async RecordNames(nsCString[] aRecordNames, GMPErr aStatus); + async Shutdown(); + +parent: + async Open(nsCString aRecordName); + async Read(nsCString aRecordName); + async Write(nsCString aRecordName, uint8_t[] aBytes); + async Close(nsCString aRecordName); + async GetRecordNames(); + async __delete__(); + +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPTimer.ipdl b/dom/media/gmp/PGMPTimer.ipdl new file mode 100644 index 000000000..7a486bc3a --- /dev/null +++ b/dom/media/gmp/PGMPTimer.ipdl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMP; + +namespace mozilla { +namespace gmp { + +async protocol PGMPTimer +{ + manager PGMP; +child: + async TimerExpired(uint32_t aTimerId); +parent: + async SetTimer(uint32_t aTimerId, uint32_t aTimeoutMs); + async __delete__(); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPVideoDecoder.ipdl b/dom/media/gmp/PGMPVideoDecoder.ipdl new file mode 100644 index 000000000..83ad8f700 --- /dev/null +++ b/dom/media/gmp/PGMPVideoDecoder.ipdl @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMPContent; +include GMPTypes; + +using GMPVideoCodec from "gmp-video-codec.h"; +using GMPErr from "gmp-errors.h"; + +include "GMPMessageUtils.h"; + +namespace mozilla { +namespace gmp { + +intr protocol PGMPVideoDecoder +{ + manager PGMPContent; +child: + async InitDecode(GMPVideoCodec aCodecSettings, + uint8_t[] aCodecSpecific, + int32_t aCoreCount); + async Decode(GMPVideoEncodedFrameData aInputFrame, + bool aMissingFrames, + uint8_t[] aCodecSpecificInfo, + int64_t aRenderTimeMs); + async Reset(); + async Drain(); + async DecodingComplete(); + async ChildShmemForPool(Shmem aFrameBuffer); + +parent: + async __delete__(); + async Decoded(GMPVideoi420FrameData aDecodedFrame); + async ReceivedDecodedReferenceFrame(uint64_t aPictureId); + async ReceivedDecodedFrame(uint64_t aPictureId); + async InputDataExhausted(); + async DrainComplete(); + async ResetComplete(); + async Error(GMPErr aErr); + async Shutdown(); + async ParentShmemForPool(Shmem aEncodedBuffer); + // MUST be intr - if sync and we create a new Shmem, when the returned + // Shmem is received in the Child it will fail to Deserialize + intr NeedShmem(uint32_t aFrameBufferSize) returns (Shmem aMem); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/PGMPVideoEncoder.ipdl b/dom/media/gmp/PGMPVideoEncoder.ipdl new file mode 100644 index 000000000..ef013352a --- /dev/null +++ b/dom/media/gmp/PGMPVideoEncoder.ipdl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PGMPContent; +include GMPTypes; + +using GMPVideoCodec from "gmp-video-codec.h"; +using GMPVideoFrameType from "gmp-video-frame-encoded.h"; +using GMPErr from "gmp-errors.h"; + +include "GMPMessageUtils.h"; + +namespace mozilla { +namespace gmp { + +intr protocol PGMPVideoEncoder +{ + manager PGMPContent; +child: + async InitEncode(GMPVideoCodec aCodecSettings, + uint8_t[] aCodecSpecific, + int32_t aNumberOfCores, + uint32_t aMaxPayloadSize); + async Encode(GMPVideoi420FrameData aInputFrame, + uint8_t[] aCodecSpecificInfo, + GMPVideoFrameType[] aFrameTypes); + async SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT); + async SetRates(uint32_t aNewBitRate, uint32_t aFrameRate); + async SetPeriodicKeyFrames(bool aEnable); + async EncodingComplete(); + async ChildShmemForPool(Shmem aEncodedBuffer); + +parent: + async __delete__(); + async Encoded(GMPVideoEncodedFrameData aEncodedFrame, + uint8_t[] aCodecSpecificInfo); + async Error(GMPErr aErr); + async Shutdown(); + async ParentShmemForPool(Shmem aFrameBuffer); + // MUST be intr - if sync and we create a new Shmem, when the returned + // Shmem is received in the Child it will fail to Deserialize + intr NeedShmem(uint32_t aEncodedBufferSize) returns (Shmem aMem); +}; + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/README.txt b/dom/media/gmp/README.txt new file mode 100644 index 000000000..189cf3b30 --- /dev/null +++ b/dom/media/gmp/README.txt @@ -0,0 +1 @@ +This directory contains code supporting Gecko Media Plugins (GMPs). The GMP API is not the same thing as the Media Plugin API (MPAPI). diff --git a/dom/media/gmp/gmp-api/gmp-async-shutdown.h b/dom/media/gmp/gmp-api/gmp-async-shutdown.h new file mode 100644 index 000000000..42268668f --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-async-shutdown.h @@ -0,0 +1,54 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_ASYNC_SHUTDOWN_H_ +#define GMP_ASYNC_SHUTDOWN_H_ + +#define GMP_API_ASYNC_SHUTDOWN "async-shutdown" + +// API exposed by the plugin library to manage asynchronous shutdown. +// Some plugins require special cleanup which may need to make calls +// to host services and wait for async responses. +// +// To enable a plugins to block shutdown until its async shutdown is +// complete, implement the GMPAsyncShutdown interface and return it when +// your plugin's GMPGetAPI function is called with "async-shutdown". +// When your GMPAsyncShutdown's BeginShutdown() implementation is called +// by the GMP host, you should initate your async shutdown process. +// Once you have completed shutdown, call the ShutdownComplete() function +// of the GMPAsyncShutdownHost that is passed as the host argument to the +// GMPGetAPI() call. +// +// Note: Your GMP's GMPShutdown function will still be called after your +// call to ShutdownComplete(). +// +// API name macro: GMP_API_ASYNC_SHUTDOWN +// Host API: GMPAsyncShutdownHost +class GMPAsyncShutdown { +public: + virtual ~GMPAsyncShutdown() {} + + virtual void BeginShutdown() = 0; +}; + +class GMPAsyncShutdownHost { +public: + virtual ~GMPAsyncShutdownHost() {} + + virtual void ShutdownComplete() = 0; +}; + +#endif // GMP_ASYNC_SHUTDOWN_H_ diff --git a/dom/media/gmp/gmp-api/gmp-audio-codec.h b/dom/media/gmp/gmp-api/gmp-audio-codec.h new file mode 100644 index 000000000..5a5c17bb9 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-audio-codec.h @@ -0,0 +1,43 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_AUDIO_CODEC_h_ +#define GMP_AUDIO_CODEC_h_ + +#include <stdint.h> + +enum GMPAudioCodecType +{ + kGMPAudioCodecAAC, + kGMPAudioCodecVorbis, + kGMPAudioCodecInvalid // Should always be last. +}; + +struct GMPAudioCodec +{ + GMPAudioCodecType mCodecType; + uint32_t mChannelCount; + uint32_t mBitsPerChannel; + uint32_t mSamplesPerSecond; + + // Codec extra data, such as vorbis setup header, or + // AAC AudioSpecificConfig. + // These are null/0 if not externally negotiated + const uint8_t* mExtraData; + uint32_t mExtraDataLen; +}; + +#endif // GMP_AUDIO_CODEC_h_ diff --git a/dom/media/gmp/gmp-api/gmp-audio-decode.h b/dom/media/gmp/gmp-api/gmp-audio-decode.h new file mode 100644 index 000000000..8b017c0af --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-audio-decode.h @@ -0,0 +1,84 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_AUDIO_DECODE_h_ +#define GMP_AUDIO_DECODE_h_ + +#include "gmp-errors.h" +#include "gmp-audio-samples.h" +#include "gmp-audio-codec.h" +#include <stdint.h> + +// ALL METHODS MUST BE CALLED ON THE MAIN THREAD +class GMPAudioDecoderCallback +{ +public: + virtual ~GMPAudioDecoderCallback() {} + + virtual void Decoded(GMPAudioSamples* aDecodedSamples) = 0; + + virtual void InputDataExhausted() = 0; + + virtual void DrainComplete() = 0; + + virtual void ResetComplete() = 0; + + // Called when the decoder encounters a catestrophic error and cannot + // continue. Gecko will not send any more input for decoding. + virtual void Error(GMPErr aError) = 0; +}; + +#define GMP_API_AUDIO_DECODER "decode-audio" + +// Audio decoding for a single stream. A GMP may be asked to create multiple +// decoders concurrently. +// +// API name macro: GMP_API_AUDIO_DECODER +// Host API: GMPAudioHost +// +// ALL METHODS MUST BE CALLED ON THE MAIN THREAD +class GMPAudioDecoder +{ +public: + virtual ~GMPAudioDecoder() {} + + // aCallback: Subclass should retain reference to it until DecodingComplete + // is called. Do not attempt to delete it, host retains ownership. + // TODO: Pass AudioHost so decoder can create GMPAudioEncodedFrame objects? + virtual void InitDecode(const GMPAudioCodec& aCodecSettings, + GMPAudioDecoderCallback* aCallback) = 0; + + // Decode encoded audio frames (as a part of an audio stream). The decoded + // frames must be returned to the user through the decode complete callback. + virtual void Decode(GMPAudioSamples* aEncodedSamples) = 0; + + // Reset decoder state and prepare for a new call to Decode(...). + // Flushes the decoder pipeline. + // The decoder should enqueue a task to run ResetComplete() on the main + // thread once the reset has finished. + virtual void Reset() = 0; + + // Output decoded frames for any data in the pipeline, regardless of ordering. + // All remaining decoded frames should be immediately returned via callback. + // The decoder should enqueue a task to run DrainComplete() on the main + // thread once the reset has finished. + virtual void Drain() = 0; + + // May free decoder memory. + virtual void DecodingComplete() = 0; +}; + +#endif // GMP_VIDEO_DECODE_h_ diff --git a/dom/media/gmp/gmp-api/gmp-audio-host.h b/dom/media/gmp/gmp-api/gmp-audio-host.h new file mode 100644 index 000000000..fe3641938 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-audio-host.h @@ -0,0 +1,32 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_AUDIO_HOST_h_ +#define GMP_AUDIO_HOST_h_ + +#include "gmp-errors.h" +#include "gmp-audio-samples.h" + +class GMPAudioHost +{ +public: + // Construct various Audio API objects. Host does not retain reference, + // caller is owner and responsible for deleting. + virtual GMPErr CreateSamples(GMPAudioFormat aFormat, + GMPAudioSamples** aSamples) = 0; +}; + +#endif // GMP_AUDIO_HOST_h_ diff --git a/dom/media/gmp/gmp-api/gmp-audio-samples.h b/dom/media/gmp/gmp-api/gmp-audio-samples.h new file mode 100644 index 000000000..a47fc74b9 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-audio-samples.h @@ -0,0 +1,74 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_AUDIO_FRAME_h_ +#define GMP_AUDIO_FRAME_h_ + +#include <stdint.h> +#include "gmp-errors.h" +#include "gmp-decryption.h" + +enum GMPAudioFormat +{ + kGMPAudioEncodedSamples, // Raw compressed data, i.e. an AAC/Vorbis packet. + kGMPAudioIS16Samples, // Interleaved int16_t PCM samples. + kGMPAudioSamplesFormatInvalid // Should always be last. +}; + +class GMPAudioSamples { +public: + // The format of the buffer. + virtual GMPAudioFormat GetFormat() = 0; + virtual void Destroy() = 0; + + // MAIN THREAD ONLY + // Buffer size must be exactly what's required to contain all samples in + // the buffer; every byte is assumed to be part of a sample. + virtual GMPErr SetBufferSize(uint32_t aSize) = 0; + + // Size of the buffer in bytes. + virtual uint32_t Size() = 0; + + // Timestamps are in microseconds, and are the playback start time of the + // first sample in the buffer. + virtual void SetTimeStamp(uint64_t aTimeStamp) = 0; + virtual uint64_t TimeStamp() = 0; + virtual const uint8_t* Buffer() const = 0; + virtual uint8_t* Buffer() = 0; + + // Get metadata describing how this frame is encrypted, or nullptr if the + // buffer is not encrypted. + virtual const GMPEncryptedBufferMetadata* GetDecryptionData() const = 0; + + virtual uint32_t Channels() const = 0; + virtual void SetChannels(uint32_t aChannels) = 0; + + // Rate; the number of frames per second, where a "frame" is one sample for + // each channel. + // + // For IS16 samples, the number of samples should be: + // Size() / (Channels() * sizeof(int16_t)). + // + // Note: Channels() and Rate() may not be constant across a decoding + // session. For example the rate for decoded samples may be different + // than the rate advertised by the MP4 container for encoded samples + // for HE-AAC streams with SBR/PS, and an EME-GMP may need to downsample + // to satisfy DRM requirements. + virtual uint32_t Rate() const = 0; + virtual void SetRate(uint32_t aRate) = 0; +}; + +#endif // GMP_AUDIO_FRAME_h_ diff --git a/dom/media/gmp/gmp-api/gmp-decryption.h b/dom/media/gmp/gmp-api/gmp-decryption.h new file mode 100644 index 000000000..046a05759 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-decryption.h @@ -0,0 +1,459 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_DECRYPTION_h_ +#define GMP_DECRYPTION_h_ + +#include "gmp-platform.h" + +class GMPStringList { +public: + virtual uint32_t Size() const = 0; + + virtual void StringAt(uint32_t aIndex, + const char** aOutString, uint32_t* aOutLength) const = 0; + + virtual ~GMPStringList() { } +}; + +class GMPEncryptedBufferMetadata { +public: + // Key ID to identify the decryption key. + virtual const uint8_t* KeyId() const = 0; + + // Size (in bytes) of |KeyId()|. + virtual uint32_t KeyIdSize() const = 0; + + // Initialization vector. + virtual const uint8_t* IV() const = 0; + + // Size (in bytes) of |IV|. + virtual uint32_t IVSize() const = 0; + + // Number of entries returned by ClearBytes() and CipherBytes(). + virtual uint32_t NumSubsamples() const = 0; + + virtual const uint16_t* ClearBytes() const = 0; + + virtual const uint32_t* CipherBytes() const = 0; + + virtual ~GMPEncryptedBufferMetadata() {} + + // The set of MediaKeySession IDs associated with this decryption key in + // the current stream. + virtual const GMPStringList* SessionIds() const = 0; +}; + +class GMPBuffer { +public: + virtual uint32_t Id() const = 0; + virtual uint8_t* Data() = 0; + virtual uint32_t Size() const = 0; + virtual void Resize(uint32_t aSize) = 0; + virtual ~GMPBuffer() {} +}; + +// These match to the DOMException codes as per: +// http://www.w3.org/TR/dom/#domexception +enum GMPDOMException { + kGMPNoModificationAllowedError = 7, + kGMPNotFoundError = 8, + kGMPNotSupportedError = 9, + kGMPInvalidStateError = 11, + kGMPSyntaxError = 12, + kGMPInvalidModificationError = 13, + kGMPInvalidAccessError = 15, + kGMPSecurityError = 18, + kGMPAbortError = 20, + kGMPQuotaExceededError = 22, + kGMPTimeoutError = 23, + kGMPTypeError = 52 +}; + +enum GMPSessionMessageType { + kGMPLicenseRequest = 0, + kGMPLicenseRenewal = 1, + kGMPLicenseRelease = 2, + kGMPIndividualizationRequest = 3, + kGMPMessageInvalid = 4 // Must always be last. +}; + +enum GMPMediaKeyStatus { + kGMPUsable = 0, + kGMPExpired = 1, + kGMPOutputDownscaled = 2, + kGMPOutputRestricted = 3, + kGMPInternalError = 4, + kGMPUnknown = 5, // Removes key from MediaKeyStatusMap + kGMPReleased = 6, + kGMPStatusPending = 7, + kGMPMediaKeyStatusInvalid = 8 // Must always be last. +}; + +struct GMPMediaKeyInfo { + GMPMediaKeyInfo() {} + GMPMediaKeyInfo(const uint8_t* aKeyId, + uint32_t aKeyIdSize, + GMPMediaKeyStatus aStatus) + : keyid(aKeyId) + , keyid_size(aKeyIdSize) + , status(aStatus) + {} + const uint8_t* keyid; + uint32_t keyid_size; + GMPMediaKeyStatus status; +}; + +// Time in milliseconds, as offset from epoch, 1 Jan 1970. +typedef int64_t GMPTimestamp; + +// Callbacks to be called from the CDM. Threadsafe. +class GMPDecryptorCallback { +public: + + // The GMPDecryptor should call this in response to a call to + // GMPDecryptor::CreateSession(). The GMP host calls CreateSession() when + // MediaKeySession.generateRequest() is called by JavaScript. + // After CreateSession() is called, the GMPDecryptor should call + // GMPDecryptorCallback::SetSessionId() to set the sessionId exposed to + // JavaScript on the MediaKeySession on which the generateRequest() was + // called. SetSessionId() must be called before + // GMPDecryptorCallback::SessionMessage() will work. + // aSessionId must be null terminated. + // Note: pass the aCreateSessionToken from the CreateSession() call, + // and then once the session has sent any messages required for the + // license request to be sent, then resolve the aPromiseId that was passed + // to GMPDecryptor::CreateSession(). + // Note: GMPDecryptor::LoadSession() does *not* need to call SetSessionId() + // for GMPDecryptorCallback::SessionMessage() to work. + virtual void SetSessionId(uint32_t aCreateSessionToken, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Resolves a promise for a session loaded. + // Resolves to false if we don't have any session data stored for the given + // session ID. + // Must be called before SessionMessage(). + virtual void ResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) = 0; + + // Called to resolve a specified promise with "undefined". + virtual void ResolvePromise(uint32_t aPromiseId) = 0; + + // Called to reject a promise with a DOMException. + // aMessage is logged to the WebConsole. + // aMessage is optional, but if present must be null terminated. + virtual void RejectPromise(uint32_t aPromiseId, + GMPDOMException aException, + const char* aMessage, + uint32_t aMessageLength) = 0; + + // Called by the CDM when it has a message for a session. + // Length parameters should not include null termination. + // aSessionId must be null terminated. + virtual void SessionMessage(const char* aSessionId, + uint32_t aSessionIdLength, + GMPSessionMessageType aMessageType, + const uint8_t* aMessage, + uint32_t aMessageLength) = 0; + + // aSessionId must be null terminated. + virtual void ExpirationChange(const char* aSessionId, + uint32_t aSessionIdLength, + GMPTimestamp aExpiryTime) = 0; + + // Called by the GMP when a session is closed. All file IO + // that a session requires should be complete before calling this. + // aSessionId must be null terminated. + virtual void SessionClosed(const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Called by the GMP when an error occurs in a session. + // aSessionId must be null terminated. + // aMessage is logged to the WebConsole. + // aMessage is optional, but if present must be null terminated. + virtual void SessionError(const char* aSessionId, + uint32_t aSessionIdLength, + GMPDOMException aException, + uint32_t aSystemCode, + const char* aMessage, + uint32_t aMessageLength) = 0; + + // Notifies the status of a key. Gecko will not call into the CDM to decrypt + // or decode content encrypted with a key unless the CDM has marked it + // usable first. So a CDM *MUST* mark its usable keys as usable! + virtual void KeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aKeyId, + uint32_t aKeyIdLength, + GMPMediaKeyStatus aStatus) = 0; + + // DEPRECATED; this function has no affect. + virtual void SetCapabilities(uint64_t aCaps) = 0; + + // Returns decrypted buffer to Gecko, or reports failure. + virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) = 0; + + // To aggregate KeyStatusChanged into single callback per session id. + virtual void BatchedKeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const GMPMediaKeyInfo* aKeyInfos, + uint32_t aKeyInfosLength) = 0; + + virtual ~GMPDecryptorCallback() {} +}; + +// Host interface, passed to GetAPIFunc(), with "decrypt". +class GMPDecryptorHost { +public: + virtual void GetSandboxVoucher(const uint8_t** aVoucher, + uint32_t* aVoucherLength) = 0; + + virtual void GetPluginVoucher(const uint8_t** aVoucher, + uint32_t* aVoucherLength) = 0; + + virtual ~GMPDecryptorHost() {} +}; + +enum GMPSessionType { + kGMPTemporySession = 0, + kGMPPersistentSession = 1, + kGMPSessionInvalid = 2 // Must always be last. +}; + +// Gecko supports the current GMPDecryptor version, and the obsolete +// version that the Adobe GMP still uses. +#define GMP_API_DECRYPTOR "eme-decrypt-v9" +#define GMP_API_DECRYPTOR_BACKWARDS_COMPAT "eme-decrypt-v7" + +// API exposed by plugin library to manage decryption sessions. +// When the Host requests this by calling GMPGetAPIFunc(). +// +// API name macro: GMP_API_DECRYPTOR +// Host API: GMPDecryptorHost +class GMPDecryptor { +public: + + // Sets the callback to use with the decryptor to return results + // to Gecko. + virtual void Init(GMPDecryptorCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) = 0; + + // Initiates the creation of a session given |aType| and |aInitData|, and + // the generation of a license request message. + // + // This corresponds to a MediaKeySession.generateRequest() call in JS. + // + // The GMPDecryptor must do the following, in order, upon this method + // being called: + // + // 1. Generate a sessionId to expose to JS, and call + // GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...) + // with the sessionId to be exposed to JS/EME on the MediaKeySession + // object on which generateRequest() was called, and then + // 2. send any messages to JS/EME required to generate a license request + // given the supplied initData, and then + // 3. generate a license request message, and send it to JS/EME, and then + // 4. call GMPDecryptorCallback::ResolvePromise(). + // + // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...) + // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...) + // will work. + // + // If generating the request fails, reject aPromiseId by calling + // GMPDecryptorCallback::RejectPromise(). + virtual void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) = 0; + + // Loads a previously loaded persistent session. + // + // This corresponds to a MediaKeySession.load() call in JS. + // + // The GMPDecryptor must do the following, in order, upon this method + // being called: + // + // 1. Send any messages to JS/EME, or read from storage, whatever is + // required to load the session, and then + // 2. if there is no session with the given sessionId loadable, call + // ResolveLoadSessionPromise(aPromiseId, false), otherwise + // 2. mark the session's keys as usable, and then + // 3. update the session's expiration, and then + // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true). + // + // If loading the session fails due to error, reject aPromiseId by calling + // GMPDecryptorCallback::RejectPromise(). + virtual void LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Updates the session with |aResponse|. + // This corresponds to a MediaKeySession.update() call in JS. + virtual void UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) = 0; + + // Releases the resources (keys) for the specified session. + // This corresponds to a MediaKeySession.close() call in JS. + virtual void CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Removes the resources (keys) for the specified session. + // This corresponds to a MediaKeySession.remove() call in JS. + virtual void RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Resolve/reject promise on completion. + // This corresponds to a MediaKeySession.setServerCertificate() call in JS. + virtual void SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) = 0; + + // Asynchronously decrypts aBuffer in place. When the decryption is + // complete, GMPDecryptor should write the decrypted data back into the + // same GMPBuffer object and return it to Gecko by calling Decrypted(), + // with the GMPNoErr successcode. If decryption fails, call Decrypted() + // with a failure code, and an error event will fire on the media element. + // Note: When Decrypted() is called and aBuffer is passed back, aBuffer + // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's + // memory will leak! + virtual void Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) = 0; + + // Called when the decryption operations are complete. + // Do not call the GMPDecryptorCallback's functions after this is called. + virtual void DecryptingComplete() = 0; + + virtual ~GMPDecryptor() {} +}; + +// v7 is the latest decryptor version supported by the Adobe GMP. +// +// API name macro: GMP_API_DECRYPTOR_BACKWARDS_COMPAT +// Host API: GMPDecryptorHost +class GMPDecryptor7 { +public: + + // Sets the callback to use with the decryptor to return results + // to Gecko. + virtual void Init(GMPDecryptorCallback* aCallback) = 0; + + // Initiates the creation of a session given |aType| and |aInitData|, and + // the generation of a license request message. + // + // This corresponds to a MediaKeySession.generateRequest() call in JS. + // + // The GMPDecryptor must do the following, in order, upon this method + // being called: + // + // 1. Generate a sessionId to expose to JS, and call + // GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...) + // with the sessionId to be exposed to JS/EME on the MediaKeySession + // object on which generateRequest() was called, and then + // 2. send any messages to JS/EME required to generate a license request + // given the supplied initData, and then + // 3. generate a license request message, and send it to JS/EME, and then + // 4. call GMPDecryptorCallback::ResolvePromise(). + // + // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...) + // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...) + // will work. + // + // If generating the request fails, reject aPromiseId by calling + // GMPDecryptorCallback::RejectPromise(). + virtual void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) = 0; + + // Loads a previously loaded persistent session. + // + // This corresponds to a MediaKeySession.load() call in JS. + // + // The GMPDecryptor must do the following, in order, upon this method + // being called: + // + // 1. Send any messages to JS/EME, or read from storage, whatever is + // required to load the session, and then + // 2. if there is no session with the given sessionId loadable, call + // ResolveLoadSessionPromise(aPromiseId, false), otherwise + // 2. mark the session's keys as usable, and then + // 3. update the session's expiration, and then + // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true). + // + // If loading the session fails due to error, reject aPromiseId by calling + // GMPDecryptorCallback::RejectPromise(). + virtual void LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Updates the session with |aResponse|. + // This corresponds to a MediaKeySession.update() call in JS. + virtual void UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) = 0; + + // Releases the resources (keys) for the specified session. + // This corresponds to a MediaKeySession.close() call in JS. + virtual void CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Removes the resources (keys) for the specified session. + // This corresponds to a MediaKeySession.remove() call in JS. + virtual void RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) = 0; + + // Resolve/reject promise on completion. + // This corresponds to a MediaKeySession.setServerCertificate() call in JS. + virtual void SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) = 0; + + // Asynchronously decrypts aBuffer in place. When the decryption is + // complete, GMPDecryptor should write the decrypted data back into the + // same GMPBuffer object and return it to Gecko by calling Decrypted(), + // with the GMPNoErr successcode. If decryption fails, call Decrypted() + // with a failure code, and an error event will fire on the media element. + // Note: When Decrypted() is called and aBuffer is passed back, aBuffer + // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's + // memory will leak! + virtual void Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) = 0; + + // Called when the decryption operations are complete. + // Do not call the GMPDecryptorCallback's functions after this is called. + virtual void DecryptingComplete() = 0; + + virtual ~GMPDecryptor7() {} +}; + +#endif // GMP_DECRYPTION_h_ diff --git a/dom/media/gmp/gmp-api/gmp-entrypoints.h b/dom/media/gmp/gmp-api/gmp-entrypoints.h new file mode 100644 index 000000000..214c9dbfc --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-entrypoints.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_ENTRYPOINTS_h_ +#define GMP_ENTRYPOINTS_h_ + +#include "gmp-errors.h" +#include "gmp-platform.h" + +/* C functions exposed by Gecko Media Plugin shared library. */ + +// GMPInit +// - Called once after plugin library is loaded, before GMPGetAPI or GMPShutdown are called. +// - Called on main thread. +// - 'aPlatformAPI' is a structure containing platform-provided APIs. It is valid until +// 'GMPShutdown' is called. Owned and must be deleted by plugin. +typedef GMPErr (*GMPInitFunc)(const GMPPlatformAPI* aPlatformAPI); + +// GMPGetAPI +// - Called when host wants to use an API. +// - Called on main thread. +// - 'aAPIName' is a string indicating the API being requested. This should +// match one of the GMP_API_* macros. Subsequent iterations of the GMP_APIs +// may change the value of the GMP_API_* macros when ABI changes occur. So +// make sure you compare aAPIName against the corresponding GMP_API_* macro! +// - 'aHostAPI' is the host API which is specific to the API being requested +// from the plugin. It is valid so long as the API object requested from the +// plugin is valid. It is owned by the host, plugin should not attempt to delete. +// May be null. +// - 'aPluginAPI' is for returning the requested API. Destruction of the requsted +// API object is defined by the API. +typedef GMPErr (*GMPGetAPIFunc)(const char* aAPIName, void* aHostAPI, void** aPluginAPI); + +// GMPShutdown +// - Called once before exiting process (unloading library). +// - Called on main thread. +typedef void (*GMPShutdownFunc)(void); + +// GMPSetNodeId +// - Optional, not required to be implemented. Only useful for EME plugins. +// - Called after GMPInit to set the device-bound origin-specific node id +// that this GMP instance is running under. +typedef void (*GMPSetNodeIdFunc)(const char* aNodeId, uint32_t aLength); + +#endif // GMP_ENTRYPOINTS_h_ diff --git a/dom/media/gmp/gmp-api/gmp-errors.h b/dom/media/gmp/gmp-api/gmp-errors.h new file mode 100644 index 000000000..7f20e2a79 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-errors.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_ERRORS_h_ +#define GMP_ERRORS_h_ + +typedef enum { + GMPNoErr = 0, + GMPGenericErr = 1, + GMPClosedErr = 2, + GMPAllocErr = 3, + GMPNotImplementedErr = 4, + GMPRecordInUse = 5, + GMPQuotaExceededErr = 6, + GMPDecodeErr = 7, + GMPEncodeErr = 8, + GMPNoKeyErr = 9, + GMPCryptoErr = 10, + GMPEndOfEnumeration = 11, + GMPInvalidArgErr = 12, + GMPAbortedErr = 13, + GMPRecordCorrupted = 14, + GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive! +} GMPErr; + +#define GMP_SUCCEEDED(x) ((x) == GMPNoErr) +#define GMP_FAILED(x) ((x) != GMPNoErr) + +#endif // GMP_ERRORS_h_ diff --git a/dom/media/gmp/gmp-api/gmp-platform.h b/dom/media/gmp/gmp-api/gmp-platform.h new file mode 100644 index 000000000..f915050b3 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-platform.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_PLATFORM_h_ +#define GMP_PLATFORM_h_ + +#include "gmp-errors.h" +#include "gmp-storage.h" +#include <stdint.h> + +/* Platform helper API. */ + +class GMPTask { +public: + virtual void Destroy() = 0; // Deletes object. + virtual ~GMPTask() {} + virtual void Run() = 0; +}; + +class GMPThread { +public: + virtual ~GMPThread() {} + virtual void Post(GMPTask* aTask) = 0; + virtual void Join() = 0; // Deletes object after join completes. +}; + +// A re-entrant monitor; can be locked from the same thread multiple times. +// Must be unlocked the same number of times it's locked. +class GMPMutex { +public: + virtual ~GMPMutex() {} + virtual void Acquire() = 0; + virtual void Release() = 0; + virtual void Destroy() = 0; // Deletes object. +}; + +// Time is defined as the number of milliseconds since the +// Epoch (00:00:00 UTC, January 1, 1970). +typedef int64_t GMPTimestamp; + +typedef GMPErr (*GMPCreateThreadPtr)(GMPThread** aThread); +typedef GMPErr (*GMPRunOnMainThreadPtr)(GMPTask* aTask); +typedef GMPErr (*GMPSyncRunOnMainThreadPtr)(GMPTask* aTask); +typedef GMPErr (*GMPCreateMutexPtr)(GMPMutex** aMutex); + +// Call on main thread only. +typedef GMPErr (*GMPCreateRecordPtr)(const char* aRecordName, + uint32_t aRecordNameSize, + GMPRecord** aOutRecord, + GMPRecordClient* aClient); + +// Call on main thread only. +typedef GMPErr (*GMPSetTimerOnMainThreadPtr)(GMPTask* aTask, int64_t aTimeoutMS); +typedef GMPErr (*GMPGetCurrentTimePtr)(GMPTimestamp* aOutTime); + +typedef void (*RecvGMPRecordIteratorPtr)(GMPRecordIterator* aRecordIterator, + void* aUserArg, + GMPErr aStatus); + +// Creates a GMPCreateRecordIterator to enumerate the records in storage. +// When the iterator is ready, the function at aRecvIteratorFunc +// is called with the GMPRecordIterator as an argument. If the operation +// fails, RecvGMPRecordIteratorPtr is called with a failure aStatus code. +// The list that the iterator is covering is fixed when +// GMPCreateRecordIterator is called, it is *not* updated when changes are +// made to storage. +// Iterator begins pointing at first record. +// aUserArg is passed to the aRecvIteratorFunc upon completion. +typedef GMPErr (*GMPCreateRecordIteratorPtr)(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg); + +struct GMPPlatformAPI { + // Increment the version when things change. Can only add to the struct, + // do not change what already exists. Pointers to functions may be NULL + // when passed to plugins, but beware backwards compat implications of + // doing that. + uint16_t version; // Currently version 0 + + GMPCreateThreadPtr createthread; + GMPRunOnMainThreadPtr runonmainthread; + GMPSyncRunOnMainThreadPtr syncrunonmainthread; + GMPCreateMutexPtr createmutex; + GMPCreateRecordPtr createrecord; + GMPSetTimerOnMainThreadPtr settimer; + GMPGetCurrentTimePtr getcurrenttime; + GMPCreateRecordIteratorPtr getrecordenumerator; +}; + +#endif // GMP_PLATFORM_h_ diff --git a/dom/media/gmp/gmp-api/gmp-storage.h b/dom/media/gmp/gmp-api/gmp-storage.h new file mode 100644 index 000000000..43ad12b01 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-storage.h @@ -0,0 +1,141 @@ +/* +* Copyright 2013, Mozilla Foundation and contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GMP_STORAGE_h_ +#define GMP_STORAGE_h_ + +#include "gmp-errors.h" +#include <stdint.h> + +// Maximum size of a record, in bytes; 10 megabytes. +#define GMP_MAX_RECORD_SIZE (10 * 1024 * 1024) + +// Maximum length of a record name in bytes. +#define GMP_MAX_RECORD_NAME_SIZE 2000 + +// Provides basic per-origin storage for CDMs. GMPRecord instances can be +// retrieved by calling GMPPlatformAPI->openstorage. Multiple GMPRecords +// with different names can be open at once, but a single record can only +// be opened by one client at a time. This interface is asynchronous, with +// results being returned via callbacks to the GMPRecordClient pointer +// provided to the GMPPlatformAPI->openstorage call, on the main thread. +// +// Lifecycle: Once opened, the GMPRecord object remains allocated until +// GMPRecord::Close() is called. If any GMPRecord function, either +// synchronously or asynchronously through a GMPRecordClient callback, +// returns an error, the GMP is responsible for calling Close() on the +// GMPRecord to delete the GMPRecord object's memory. If your GMP does not +// call Close(), the GMPRecord's memory will leak. +class GMPRecord { +public: + + // Opens the record. Calls OpenComplete() once the record is open. + // Note: Only work when GMP is loading content from a webserver. + // Does not work for web pages on loaded from disk. + // Note: OpenComplete() is only called if this returns GMPNoErr. + virtual GMPErr Open() = 0; + + // Reads the entire contents of the record, and calls + // GMPRecordClient::ReadComplete() once the operation is complete. + // Note: ReadComplete() is only called if this returns GMPNoErr. + virtual GMPErr Read() = 0; + + // Writes aDataSize bytes of aData into the record, overwriting the + // contents of the record, truncating it to aDataSize length. + // Overwriting with 0 bytes "deletes" the record. + // Note: WriteComplete is only called if this returns GMPNoErr. + virtual GMPErr Write(const uint8_t* aData, uint32_t aDataSize) = 0; + + // Closes a record, deletes the GMPRecord object. The GMPRecord object + // must not be used after this is called, request a new one with + // GMPPlatformAPI->openstorage to re-open this record. Cancels all + // callbacks. + virtual GMPErr Close() = 0; + + virtual ~GMPRecord() {} +}; + +// Callback object that receives the results of GMPRecord calls. Callbacks +// run asynchronously to the GMPRecord call, on the main thread. +class GMPRecordClient { + public: + + // Response to a GMPRecord::Open() call with the open |status|. + // aStatus values: + // - GMPNoErr - Record opened successfully. Record may be empty. + // - GMPRecordInUse - This record is in use by another client. + // - GMPGenericErr - Unspecified error. + // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must + // call Close() on the GMPRecord to dispose of it. + virtual void OpenComplete(GMPErr aStatus) = 0; + + // Response to a GMPRecord::Read() call, where aData is the record contents, + // of length aDataSize. + // aData is only valid for the duration of the call to ReadComplete. + // Copy it if you want to hang onto it! + // aStatus values: + // - GMPNoErr - Record contents read successfully, aDataSize 0 means record + // is empty. + // - GMPRecordInUse - There are other operations or clients in use on + // this record. + // - GMPGenericErr - Unspecified error. + // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must + // call Close() on the GMPRecord to dispose of it. + virtual void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) = 0; + + // Response to a GMPRecord::Write() call. + // - GMPNoErr - File contents written successfully. + // - GMPRecordInUse - There are other operations or clients in use on + // this record. + // - GMPGenericErr - Unspecified error. + // If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must + // call Close() on the GMPRecord to dispose of it. + virtual void WriteComplete(GMPErr aStatus) = 0; + + virtual ~GMPRecordClient() {} +}; + +// Iterates over the records that are available. Note: this list maintains +// a snapshot of the records that were present when the iterator was created. +// Create by calling the GMPCreateRecordIteratorPtr function on the +// GMPPlatformAPI struct. +// Iteration is in alphabetical order. +class GMPRecordIterator { +public: + // Retrieve the name for the current record. + // aOutName is null terminated at character at index (*aOutNameLength). + // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has + // reached the end. + virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0; + + // Advance iteration to the next record. + // Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has + // reached the end. + virtual GMPErr NextRecord() = 0; + + // Signals to the GMP host that the GMP is finished with the + // GMPRecordIterator. GMPs must call this to release memory held by + // the GMPRecordIterator. Do not access the GMPRecordIterator pointer + // after calling this! + // Memory retrieved by GetName is *not* valid after calling Close()! + virtual void Close() = 0; + + virtual ~GMPRecordIterator() {} +}; + +#endif // GMP_STORAGE_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-codec.h b/dom/media/gmp/gmp-api/gmp-video-codec.h new file mode 100644 index 000000000..e068ab43d --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-codec.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_CODEC_h_ +#define GMP_VIDEO_CODEC_h_ + +#include <stdint.h> +#include <stddef.h> + +enum { kGMPPayloadNameSize = 32}; +enum { kGMPMaxSimulcastStreams = 4}; + +enum GMPVideoCodecComplexity +{ + kGMPComplexityNormal = 0, + kGMPComplexityHigh = 1, + kGMPComplexityHigher = 2, + kGMPComplexityMax = 3, + kGMPComplexityInvalid // Should always be last +}; + +enum GMPVP8ResilienceMode { + kResilienceOff, // The stream produced by the encoder requires a + // recovery frame (typically a key frame) to be + // decodable after a packet loss. + kResilientStream, // A stream produced by the encoder is resilient to + // packet losses, but packets within a frame subsequent + // to a loss can't be decoded. + kResilientFrames, // Same as kResilientStream but with added resilience + // within a frame. + kResilienceInvalid // Should always be last. +}; + +// VP8 specific +struct GMPVideoCodecVP8 +{ + bool mPictureLossIndicationOn; + bool mFeedbackModeOn; + GMPVideoCodecComplexity mComplexity; + GMPVP8ResilienceMode mResilience; + uint32_t mNumberOfTemporalLayers; + bool mDenoisingOn; + bool mErrorConcealmentOn; + bool mAutomaticResizeOn; +}; + +// H264 specific + +// Needs to match a binary spec for this structure. +// Note: the mSPS at the end of this structure is variable length. +struct GMPVideoCodecH264AVCC +{ + uint8_t mVersion; // == 0x01 + uint8_t mProfile; // these 3 are profile_level_id + uint8_t mConstraints; + uint8_t mLevel; + uint8_t mLengthSizeMinusOne; // lower 2 bits (== GMPBufferType-1). Top 6 reserved (1's) + + // SPS/PPS will not generally be present for interactive use unless SDP + // parameter-sets are used. + uint8_t mNumSPS; // lower 5 bits; top 5 reserved (1's) + + /*** uint8_t mSPS[]; (Not defined due to compiler warnings and warnings-as-errors ...) **/ + // Following mNumSPS is a variable number of bytes, which is the SPS and PPS. + // Each SPS == 16 bit size, ("N"), then "N" bytes, + // then uint8_t mNumPPS, then each PPS == 16 bit size ("N"), then "N" bytes. +}; + +// Codec specific data for H.264 decoding/encoding. +// Cast the "aCodecSpecific" parameter of GMPVideoDecoder::InitDecode() and +// GMPVideoEncoder::InitEncode() to this structure. +struct GMPVideoCodecH264 +{ + uint8_t mPacketizationMode; // 0 or 1 + struct GMPVideoCodecH264AVCC mAVCC; // holds a variable-sized struct GMPVideoCodecH264AVCC mAVCC; +}; + +enum GMPVideoCodecType +{ + kGMPVideoCodecVP8, + + // Encoded frames are in AVCC format; NAL length field of 4 bytes, followed + // by frame data. May be multiple NALUs per sample. Codec specific extra data + // is the AVCC extra data (in AVCC format). + kGMPVideoCodecH264, + kGMPVideoCodecVP9, + kGMPVideoCodecInvalid // Should always be last. +}; + +// Simulcast is when the same stream is encoded multiple times with different +// settings such as resolution. +struct GMPSimulcastStream +{ + uint32_t mWidth; + uint32_t mHeight; + uint32_t mNumberOfTemporalLayers; + uint32_t mMaxBitrate; // kilobits/sec. + uint32_t mTargetBitrate; // kilobits/sec. + uint32_t mMinBitrate; // kilobits/sec. + uint32_t mQPMax; // minimum quality +}; + +enum GMPVideoCodecMode { + kGMPRealtimeVideo, + kGMPScreensharing, + kGMPStreamingVideo, + kGMPCodecModeInvalid // Should always be last. +}; + +enum GMPApiVersion { + kGMPVersion32 = 1, // leveraging that V32 had mCodecType first, and only supported H264 + kGMPVersion33 = 33, +}; + +struct GMPVideoCodec +{ + uint32_t mGMPApiVersion; + + GMPVideoCodecType mCodecType; + char mPLName[kGMPPayloadNameSize]; // Must be NULL-terminated! + uint32_t mPLType; + + uint32_t mWidth; + uint32_t mHeight; + + uint32_t mStartBitrate; // kilobits/sec. + uint32_t mMaxBitrate; // kilobits/sec. + uint32_t mMinBitrate; // kilobits/sec. + uint32_t mMaxFramerate; + + bool mFrameDroppingOn; + int32_t mKeyFrameInterval; + + uint32_t mQPMax; + uint32_t mNumberOfSimulcastStreams; + GMPSimulcastStream mSimulcastStream[kGMPMaxSimulcastStreams]; + + GMPVideoCodecMode mMode; +}; + +// Either single encoded unit, or multiple units separated by 8/16/24/32 +// bit lengths, all with the same timestamp. Note there is no final 0-length +// entry; one should check the overall end-of-buffer against where the next +// length would be. +enum GMPBufferType { + GMP_BufferSingle = 0, + GMP_BufferLength8, + GMP_BufferLength16, + GMP_BufferLength24, + GMP_BufferLength32, + GMP_BufferInvalid, +}; + +struct GMPCodecSpecificInfoGeneric { + uint8_t mSimulcastIdx; +}; + +struct GMPCodecSpecificInfoH264 { + uint8_t mSimulcastIdx; +}; + +// Note: if any pointers are added to this struct, it must be fitted +// with a copy-constructor. See below. +struct GMPCodecSpecificInfoVP8 +{ + bool mHasReceivedSLI; + uint8_t mPictureIdSLI; + bool mHasReceivedRPSI; + uint64_t mPictureIdRPSI; + int16_t mPictureId; // negative value to skip pictureId + bool mNonReference; + uint8_t mSimulcastIdx; + uint8_t mTemporalIdx; + bool mLayerSync; + int32_t mTL0PicIdx; // negative value to skip tl0PicIdx + int8_t mKeyIdx; // negative value to skip keyIdx +}; + +union GMPCodecSpecificInfoUnion +{ + GMPCodecSpecificInfoGeneric mGeneric; + GMPCodecSpecificInfoVP8 mVP8; + GMPCodecSpecificInfoH264 mH264; +}; + +// Note: if any pointers are added to this struct or its sub-structs, it +// must be fitted with a copy-constructor. This is because it is copied +// in the copy-constructor of VCMEncodedFrame. +struct GMPCodecSpecificInfo +{ + GMPVideoCodecType mCodecType; + GMPBufferType mBufferType; + GMPCodecSpecificInfoUnion mCodecSpecific; +}; + +#endif // GMP_VIDEO_CODEC_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-decode.h b/dom/media/gmp/gmp-api/gmp-video-decode.h new file mode 100644 index 000000000..e07a7525e --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-decode.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_DECODE_h_ +#define GMP_VIDEO_DECODE_h_ + +#include "gmp-errors.h" +#include "gmp-video-frame-i420.h" +#include "gmp-video-frame-encoded.h" +#include "gmp-video-codec.h" +#include <stdint.h> + +// ALL METHODS MUST BE CALLED ON THE MAIN THREAD +class GMPVideoDecoderCallback +{ +public: + virtual ~GMPVideoDecoderCallback() {} + + virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) = 0; + + virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) = 0; + + virtual void ReceivedDecodedFrame(const uint64_t aPictureId) = 0; + + virtual void InputDataExhausted() = 0; + + virtual void DrainComplete() = 0; + + virtual void ResetComplete() = 0; + + // Called when the decoder encounters a catestrophic error and cannot + // continue. Gecko will not send any more input for decoding. + virtual void Error(GMPErr aError) = 0; +}; + +#define GMP_API_VIDEO_DECODER "decode-video" + +// Video decoding for a single stream. A GMP may be asked to create multiple +// decoders concurrently. +// +// API name macro: GMP_API_VIDEO_DECODER +// Host API: GMPVideoHost +// +// ALL METHODS MUST BE CALLED ON THE MAIN THREAD +class GMPVideoDecoder +{ +public: + virtual ~GMPVideoDecoder() {} + + // - aCodecSettings: Details of decoder to create. + // - aCodecSpecific: codec specific data, cast to a GMPVideoCodecXXX struct + // to get codec specific config data. + // - aCodecSpecificLength: number of bytes in aCodecSpecific. + // - aCallback: Subclass should retain reference to it until DecodingComplete + // is called. Do not attempt to delete it, host retains ownership. + // aCoreCount: number of CPU cores. + virtual void InitDecode(const GMPVideoCodec& aCodecSettings, + const uint8_t* aCodecSpecific, + uint32_t aCodecSpecificLength, + GMPVideoDecoderCallback* aCallback, + int32_t aCoreCount) = 0; + + // Decode encoded frame (as a part of a video stream). The decoded frame + // will be returned to the user through the decode complete callback. + // + // - aInputFrame: Frame to decode. Call Destroy() on frame when it's decoded. + // - aMissingFrames: True if one or more frames have been lost since the + // previous decode call. + // - aCodecSpecificInfo : codec specific data, pointer to a + // GMPCodecSpecificInfo structure appropriate for + // this codec type. + // - aCodecSpecificInfoLength : number of bytes in aCodecSpecificInfo + // - renderTimeMs : System time to render in milliseconds. Only used by + // decoders with internal rendering. + virtual void Decode(GMPVideoEncodedFrame* aInputFrame, + bool aMissingFrames, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength, + int64_t aRenderTimeMs = -1) = 0; + + // Reset decoder state and prepare for a new call to Decode(...). + // Flushes the decoder pipeline. + // The decoder should enqueue a task to run ResetComplete() on the main + // thread once the reset has finished. + virtual void Reset() = 0; + + // Output decoded frames for any data in the pipeline, regardless of ordering. + // All remaining decoded frames should be immediately returned via callback. + // The decoder should enqueue a task to run DrainComplete() on the main + // thread once the reset has finished. + virtual void Drain() = 0; + + // May free decoder memory. + virtual void DecodingComplete() = 0; +}; + +#endif // GMP_VIDEO_DECODE_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-encode.h b/dom/media/gmp/gmp-api/gmp-video-encode.h new file mode 100644 index 000000000..5c9cde39c --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-encode.h @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_ENCODE_h_ +#define GMP_VIDEO_ENCODE_h_ + +#include <vector> +#include <stdint.h> + +#include "gmp-errors.h" +#include "gmp-video-frame-i420.h" +#include "gmp-video-frame-encoded.h" +#include "gmp-video-codec.h" + +// ALL METHODS MUST BE CALLED ON THE MAIN THREAD +class GMPVideoEncoderCallback +{ +public: + virtual ~GMPVideoEncoderCallback() {} + + virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength) = 0; + + // Called when the encoder encounters a catestrophic error and cannot + // continue. Gecko will not send any more input for encoding. + virtual void Error(GMPErr aError) = 0; +}; + +#define GMP_API_VIDEO_ENCODER "encode-video" + +// Video encoding for a single stream. A GMP may be asked to create multiple +// encoders concurrently. +// +// API name macro: GMP_API_VIDEO_ENCODER +// Host API: GMPVideoHost +// +// ALL METHODS MUST BE CALLED ON THE MAIN THREAD +class GMPVideoEncoder +{ +public: + virtual ~GMPVideoEncoder() {} + + // Initialize the encoder with the information from the VideoCodec. + // + // Input: + // - codecSettings : Codec settings + // - aCodecSpecific : codec specific data, pointer to a + // GMPCodecSpecific structure appropriate for + // this codec type. + // - aCodecSpecificLength : number of bytes in aCodecSpecific + // - aCallback: Subclass should retain reference to it until EncodingComplete + // is called. Do not attempt to delete it, host retains ownership. + // - aNnumberOfCores : Number of cores available for the encoder + // - aMaxPayloadSize : The maximum size each payload is allowed + // to have. Usually MTU - overhead. + virtual void InitEncode(const GMPVideoCodec& aCodecSettings, + const uint8_t* aCodecSpecific, + uint32_t aCodecSpecificLength, + GMPVideoEncoderCallback* aCallback, + int32_t aNumberOfCores, + uint32_t aMaxPayloadSize) = 0; + + // Encode an I420 frame (as a part of a video stream). The encoded frame + // will be returned to the user through the encode complete callback. + // + // Input: + // - aInputFrame : Frame to be encoded + // - aCodecSpecificInfo : codec specific data, pointer to a + // GMPCodecSpecificInfo structure appropriate for + // this codec type. + // - aCodecSpecificInfoLength : number of bytes in aCodecSpecific + // - aFrameTypes : The frame type to encode + // - aFrameTypesLength : The number of elements in aFrameTypes array. + virtual void Encode(GMPVideoi420Frame* aInputFrame, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength, + const GMPVideoFrameType* aFrameTypes, + uint32_t aFrameTypesLength) = 0; + + // Inform the encoder about the packet loss and round trip time on the + // network used to decide the best pattern and signaling. + // + // - packetLoss : Fraction lost (loss rate in percent = + // 100 * packetLoss / 255) + // - rtt : Round-trip time in milliseconds + virtual void SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0; + + // Inform the encoder about the new target bit rate. + // + // - newBitRate : New target bit rate + // - frameRate : The target frame rate + virtual void SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0; + + // Use this function to enable or disable periodic key frames. Can be useful for codecs + // which have other ways of stopping error propagation. + // + // - enable : Enable or disable periodic key frames + virtual void SetPeriodicKeyFrames(bool aEnable) = 0; + + // May free Encoder memory. + virtual void EncodingComplete() = 0; +}; + +#endif // GMP_VIDEO_ENCODE_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h new file mode 100644 index 000000000..76af7349f --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_FRAME_ENCODED_h_ +#define GMP_VIDEO_FRAME_ENCODED_h_ + +#include <stdint.h> +#include "gmp-decryption.h" +#include "gmp-video-frame.h" +#include "gmp-video-codec.h" + +enum GMPVideoFrameType +{ + kGMPKeyFrame = 0, + kGMPDeltaFrame = 1, + kGMPGoldenFrame = 2, + kGMPAltRefFrame = 3, + kGMPSkipFrame = 4, + kGMPVideoFrameInvalid = 5 // Must always be last. +}; + +// The implementation backing this interface uses shared memory for the +// buffer(s). This means it can only be used by the "owning" process. +// At first the process which created the object owns it. When the object +// is passed to an interface the creator loses ownership and must Destroy() +// the object. Further attempts to use it may fail due to not being able to +// access the underlying buffer(s). +// +// Methods that create or destroy shared memory must be called on the main +// thread. They are marked below. +class GMPVideoEncodedFrame : public GMPVideoFrame +{ +public: + // MAIN THREAD ONLY + virtual GMPErr CreateEmptyFrame(uint32_t aSize) = 0; + // MAIN THREAD ONLY + virtual GMPErr CopyFrame(const GMPVideoEncodedFrame& aVideoFrame) = 0; + virtual void SetEncodedWidth(uint32_t aEncodedWidth) = 0; + virtual uint32_t EncodedWidth() = 0; + virtual void SetEncodedHeight(uint32_t aEncodedHeight) = 0; + virtual uint32_t EncodedHeight() = 0; + // Microseconds + virtual void SetTimeStamp(uint64_t aTimeStamp) = 0; + virtual uint64_t TimeStamp() = 0; + // Set frame duration (microseconds) + // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration() + // depending on rounding to avoid having to track roundoff errors + // and dropped/missing frames(!) (which may leave a large gap) + virtual void SetDuration(uint64_t aDuration) = 0; + virtual uint64_t Duration() const = 0; + virtual void SetFrameType(GMPVideoFrameType aFrameType) = 0; + virtual GMPVideoFrameType FrameType() = 0; + virtual void SetAllocatedSize(uint32_t aNewSize) = 0; + virtual uint32_t AllocatedSize() = 0; + virtual void SetSize(uint32_t aSize) = 0; + virtual uint32_t Size() = 0; + virtual void SetCompleteFrame(bool aCompleteFrame) = 0; + virtual bool CompleteFrame() = 0; + virtual const uint8_t* Buffer() const = 0; + virtual uint8_t* Buffer() = 0; + virtual GMPBufferType BufferType() const = 0; + virtual void SetBufferType(GMPBufferType aBufferType) = 0; + + // Get metadata describing how this frame is encrypted, or nullptr if the + // frame is not encrypted. + virtual const GMPEncryptedBufferMetadata* GetDecryptionData() const = 0; +}; + +#endif // GMP_VIDEO_FRAME_ENCODED_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-frame-i420.h b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h new file mode 100644 index 000000000..14c2c33cd --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_FRAME_I420_h_ +#define GMP_VIDEO_FRAME_I420_h_ + +#include "gmp-errors.h" +#include "gmp-video-frame.h" +#include "gmp-video-plane.h" + +#include <stdint.h> + +enum GMPPlaneType { + kGMPYPlane = 0, + kGMPUPlane = 1, + kGMPVPlane = 2, + kGMPNumOfPlanes = 3 +}; + +// The implementation backing this interface uses shared memory for the +// buffer(s). This means it can only be used by the "owning" process. +// At first the process which created the object owns it. When the object +// is passed to an interface the creator loses ownership and must Destroy() +// the object. Further attempts to use it may fail due to not being able to +// access the underlying buffer(s). +// +// Methods that create or destroy shared memory must be called on the main +// thread. They are marked below. +class GMPVideoi420Frame : public GMPVideoFrame { +public: + // MAIN THREAD ONLY + // CreateEmptyFrame: Sets frame dimensions and allocates buffers based + // on set dimensions - height and plane stride. + // If required size is bigger than the allocated one, new buffers of adequate + // size will be allocated. + virtual GMPErr CreateEmptyFrame(int32_t aWidth, int32_t aHeight, + int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0; + + // MAIN THREAD ONLY + // CreateFrame: Sets the frame's members and buffers. If required size is + // bigger than allocated one, new buffers of adequate size will be allocated. + virtual GMPErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y, + int32_t aSize_u, const uint8_t* aBuffer_u, + int32_t aSize_v, const uint8_t* aBuffer_v, + int32_t aWidth, int32_t aHeight, + int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0; + + // MAIN THREAD ONLY + // Copy frame: If required size is bigger than allocated one, new buffers of + // adequate size will be allocated. + virtual GMPErr CopyFrame(const GMPVideoi420Frame& aVideoFrame) = 0; + + // Swap Frame. + virtual void SwapFrame(GMPVideoi420Frame* aVideoFrame) = 0; + + // Get pointer to buffer per plane. + virtual uint8_t* Buffer(GMPPlaneType aType) = 0; + + // Overloading with const. + virtual const uint8_t* Buffer(GMPPlaneType aType) const = 0; + + // Get allocated size per plane. + virtual int32_t AllocatedSize(GMPPlaneType aType) const = 0; + + // Get allocated stride per plane. + virtual int32_t Stride(GMPPlaneType aType) const = 0; + + // Set frame width. + virtual GMPErr SetWidth(int32_t aWidth) = 0; + + // Set frame height. + virtual GMPErr SetHeight(int32_t aHeight) = 0; + + // Get frame width. + virtual int32_t Width() const = 0; + + // Get frame height. + virtual int32_t Height() const = 0; + + // Set frame timestamp (microseconds) + virtual void SetTimestamp(uint64_t aTimestamp) = 0; + + // Get frame timestamp (microseconds) + virtual uint64_t Timestamp() const = 0; + + // Set frame duration (microseconds) + // NOTE: next-frame's Timestamp() != this-frame's TimeStamp()+Duration() + // depending on rounding to avoid having to track roundoff errors + // and dropped/missing frames(!) (which may leave a large gap) + virtual void SetDuration(uint64_t aDuration) = 0; + + // Get frame duration (microseconds) + virtual uint64_t Duration() const = 0; + + // Return true if underlying plane buffers are of zero size, false if not. + virtual bool IsZeroSize() const = 0; + + // Reset underlying plane buffers sizes to 0. This function doesn't clear memory. + virtual void ResetSize() = 0; +}; + +#endif // GMP_VIDEO_FRAME_I420_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-frame.h b/dom/media/gmp/gmp-api/gmp-video-frame.h new file mode 100644 index 000000000..b3c9f53ab --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-frame.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_FRAME_h_ +#define GMP_VIDEO_FRAME_h_ + +#include "gmp-video-plane.h" + +enum GMPVideoFrameFormat { + kGMPEncodedVideoFrame = 0, + kGMPI420VideoFrame = 1 +}; + +class GMPVideoFrame { +public: + virtual GMPVideoFrameFormat GetFrameFormat() = 0; + // MAIN THREAD ONLY IF OWNING PROCESS + virtual void Destroy() = 0; +}; + +#endif // GMP_VIDEO_FRAME_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-host.h b/dom/media/gmp/gmp-api/gmp-video-host.h new file mode 100644 index 000000000..cf20e3f46 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-host.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_HOST_h_ +#define GMP_VIDEO_HOST_h_ + +#include "gmp-errors.h" +#include "gmp-video-frame-i420.h" +#include "gmp-video-frame-encoded.h" +#include "gmp-video-codec.h" + +// This interface must be called on the main thread only. +class GMPVideoHost +{ +public: + // Construct various video API objects. Host does not retain reference, + // caller is owner and responsible for deleting. + // MAIN THREAD ONLY + virtual GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) = 0; + virtual GMPErr CreatePlane(GMPPlane** aPlane) = 0; +}; + +#endif // GMP_VIDEO_HOST_h_ diff --git a/dom/media/gmp/gmp-api/gmp-video-plane.h b/dom/media/gmp/gmp-api/gmp-video-plane.h new file mode 100644 index 000000000..777cc2495 --- /dev/null +++ b/dom/media/gmp/gmp-api/gmp-video-plane.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * Copyright (c) 2014, Mozilla + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + ** Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + ** Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + ** Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GMP_VIDEO_PLANE_h_ +#define GMP_VIDEO_PLANE_h_ + +#include "gmp-errors.h" +#include <stdint.h> + +// The implementation backing this interface uses shared memory for the +// buffer(s). This means it can only be used by the "owning" process. +// At first the process which created the object owns it. When the object +// is passed to an interface the creator loses ownership and must Destroy() +// the object. Further attempts to use it may fail due to not being able to +// access the underlying buffer(s). +// +// Methods that create or destroy shared memory must be called on the main +// thread. They are marked below. +class GMPPlane { +public: + // MAIN THREAD ONLY + // CreateEmptyPlane - set allocated size, actual plane size and stride: + // If current size is smaller than current size, then a buffer of sufficient + // size will be allocated. + virtual GMPErr CreateEmptyPlane(int32_t aAllocatedSize, + int32_t aStride, + int32_t aPlaneSize) = 0; + + // MAIN THREAD ONLY + // Copy the entire plane data. + virtual GMPErr Copy(const GMPPlane& aPlane) = 0; + + // MAIN THREAD ONLY + // Copy buffer: If current size is smaller + // than current size, then a buffer of sufficient size will be allocated. + virtual GMPErr Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer) = 0; + + // Swap plane data. + virtual void Swap(GMPPlane& aPlane) = 0; + + // Get allocated size. + virtual int32_t AllocatedSize() const = 0; + + // Set actual size. + virtual void ResetSize() = 0; + + // Return true is plane size is zero, false if not. + virtual bool IsZeroSize() const = 0; + + // Get stride value. + virtual int32_t Stride() const = 0; + + // Return data pointer. + virtual const uint8_t* Buffer() const = 0; + + // Overloading with non-const. + virtual uint8_t* Buffer() = 0; + + // MAIN THREAD ONLY IF OWNING PROCESS + // Call this when done with the object. This may delete it. + virtual void Destroy() = 0; +}; + +#endif // GMP_VIDEO_PLANE_h_ diff --git a/dom/media/gmp/moz.build b/dom/media/gmp/moz.build new file mode 100644 index 000000000..3ff90b2c8 --- /dev/null +++ b/dom/media/gmp/moz.build @@ -0,0 +1,140 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_MODULE = 'content_geckomediaplugins' + +XPIDL_SOURCES += [ + 'mozIGeckoMediaPluginChromeService.idl', + 'mozIGeckoMediaPluginService.idl', +] + +EXPORTS += [ + 'gmp-api/gmp-async-shutdown.h', + 'gmp-api/gmp-audio-codec.h', + 'gmp-api/gmp-audio-decode.h', + 'gmp-api/gmp-audio-host.h', + 'gmp-api/gmp-audio-samples.h', + 'gmp-api/gmp-decryption.h', + 'gmp-api/gmp-entrypoints.h', + 'gmp-api/gmp-errors.h', + 'gmp-api/gmp-platform.h', + 'gmp-api/gmp-storage.h', + 'gmp-api/gmp-video-codec.h', + 'gmp-api/gmp-video-decode.h', + 'gmp-api/gmp-video-encode.h', + 'gmp-api/gmp-video-frame-encoded.h', + 'gmp-api/gmp-video-frame-i420.h', + 'gmp-api/gmp-video-frame.h', + 'gmp-api/gmp-video-host.h', + 'gmp-api/gmp-video-plane.h', + 'GMPAudioDecoderChild.h', + 'GMPAudioDecoderParent.h', + 'GMPAudioDecoderProxy.h', + 'GMPAudioHost.h', + 'GMPCallbackBase.h', + 'GMPChild.h', + 'GMPContentChild.h', + 'GMPContentParent.h', + 'GMPCrashHelperHolder.h', + 'GMPDecryptorChild.h', + 'GMPDecryptorParent.h', + 'GMPDecryptorProxy.h', + 'GMPEncryptedBufferDataImpl.h', + 'GMPLoader.h', + 'GMPMessageUtils.h', + 'GMPParent.h', + 'GMPPlatform.h', + 'GMPProcessChild.h', + 'GMPProcessParent.h', + 'GMPService.h', + 'GMPServiceChild.h', + 'GMPServiceParent.h', + 'GMPSharedMemManager.h', + 'GMPStorage.h', + 'GMPStorageChild.h', + 'GMPStorageParent.h', + 'GMPTimerChild.h', + 'GMPTimerParent.h', + 'GMPUtils.h', + 'GMPVideoDecoderChild.h', + 'GMPVideoDecoderParent.h', + 'GMPVideoDecoderProxy.h', + 'GMPVideoEncodedFrameImpl.h', + 'GMPVideoEncoderChild.h', + 'GMPVideoEncoderParent.h', + 'GMPVideoEncoderProxy.h', + 'GMPVideoHost.h', + 'GMPVideoi420FrameImpl.h', + 'GMPVideoPlaneImpl.h', +] + +SOURCES += [ + 'GMPAudioDecoderChild.cpp', + 'GMPAudioDecoderParent.cpp', + 'GMPAudioHost.cpp', + 'GMPChild.cpp', + 'GMPContentChild.cpp', + 'GMPContentParent.cpp', + 'GMPDecryptorChild.cpp', + 'GMPDecryptorParent.cpp', + 'GMPDiskStorage.cpp', + 'GMPEncryptedBufferDataImpl.cpp', + 'GMPMemoryStorage.cpp', + 'GMPParent.cpp', + 'GMPPlatform.cpp', + 'GMPProcessChild.cpp', + 'GMPProcessParent.cpp', + 'GMPService.cpp', + 'GMPServiceChild.cpp', + 'GMPServiceParent.cpp', + 'GMPSharedMemManager.cpp', + 'GMPStorageChild.cpp', + 'GMPStorageParent.cpp', + 'GMPTimerChild.cpp', + 'GMPTimerParent.cpp', + 'GMPUtils.cpp', + 'GMPVideoDecoderChild.cpp', + 'GMPVideoDecoderParent.cpp', + 'GMPVideoEncodedFrameImpl.cpp', + 'GMPVideoEncoderChild.cpp', + 'GMPVideoEncoderParent.cpp', + 'GMPVideoHost.cpp', + 'GMPVideoi420FrameImpl.cpp', + 'GMPVideoPlaneImpl.cpp', +] + +DIRS += ['rlz'] + +IPDL_SOURCES += [ + 'GMPTypes.ipdlh', + 'PGMP.ipdl', + 'PGMPAudioDecoder.ipdl', + 'PGMPContent.ipdl', + 'PGMPDecryptor.ipdl', + 'PGMPService.ipdl', + 'PGMPStorage.ipdl', + 'PGMPTimer.ipdl', + 'PGMPVideoDecoder.ipdl', + 'PGMPVideoEncoder.ipdl', +] + +if CONFIG['GKMEDIAS_SHARED_LIBRARY']: + NO_VISIBILITY_FLAGS = True + +# comment this out to use Unsafe Shmem for more performance +DEFINES['GMP_SAFE_SHMEM'] = True + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/xpcom/base', + '/xpcom/build', + '/xpcom/threads', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl new file mode 100644 index 000000000..9e3286485 --- /dev/null +++ b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIFile.idl" + +[scriptable, uuid(32d35d21-181f-4630-8caa-a431e2ebad72)] +interface mozIGeckoMediaPluginChromeService : nsISupports +{ + /** + * Add a directory to scan for gecko media plugins. + * @note Main-thread API. + */ + void addPluginDirectory(in AString directory); + + /** + * Remove a directory for gecko media plugins. + * @note Main-thread API. + */ + void removePluginDirectory(in AString directory); + + /** + * Remove a directory for gecko media plugins and delete it from disk. + * If |defer| is true, wait until the plugin is unused before removing. + * @note Main-thread API. + */ + void removeAndDeletePluginDirectory(in AString directory, + [optional] in bool defer); + + /** + * Clears storage data associated with the site and the originAttributes + * pattern in JSON format. + */ + void forgetThisSite(in AString site, + in DOMString aPattern); + + /** + * Returns true if the given node id is allowed to store things + * persistently on disk. Private Browsing and local content are not + * allowed to store persistent data. + */ + bool isPersistentStorageAllowed(in ACString nodeId); + + /** + * Returns the directory to use as the base for storing data about GMPs. + */ + nsIFile getStorageDir(); + +}; diff --git a/dom/media/gmp/mozIGeckoMediaPluginService.idl b/dom/media/gmp/mozIGeckoMediaPluginService.idl new file mode 100644 index 000000000..388c58142 --- /dev/null +++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIThread.idl" + +%{C++ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "nsStringGlue.h" +class GMPAudioDecoderProxy; +class GMPDecryptorProxy; +class GMPVideoDecoderProxy; +class GMPVideoEncoderProxy; +class GMPVideoHost; +class GMPCrashHelper; + +template<class T> +class GMPGetterCallback +{ +public: + GMPGetterCallback() { MOZ_COUNT_CTOR(GMPGetterCallback<T>); } + virtual ~GMPGetterCallback() { MOZ_COUNT_DTOR(GMPGetterCallback<T>); } + virtual void Done(T*) = 0; +}; +template<class T> +class GMPVideoGetterCallback +{ +public: + GMPVideoGetterCallback() { MOZ_COUNT_CTOR(GMPVideoGetterCallback<T>); } + virtual ~GMPVideoGetterCallback() { MOZ_COUNT_DTOR(GMPVideoGetterCallback<T>); } + virtual void Done(T*, GMPVideoHost*) = 0; +}; +typedef GMPGetterCallback<GMPDecryptorProxy> GetGMPDecryptorCallback; +typedef GMPGetterCallback<GMPAudioDecoderProxy> GetGMPAudioDecoderCallback; +typedef GMPVideoGetterCallback<GMPVideoDecoderProxy> GetGMPVideoDecoderCallback; +typedef GMPVideoGetterCallback<GMPVideoEncoderProxy> GetGMPVideoEncoderCallback; +class GetNodeIdCallback +{ +public: + GetNodeIdCallback() { MOZ_COUNT_CTOR(GetNodeIdCallback); } + virtual ~GetNodeIdCallback() { MOZ_COUNT_DTOR(GetNodeIdCallback); } + virtual void Done(nsresult aResult, const nsACString& aNodeId) = 0; +}; +%} + +[ptr] native TagArray(nsTArray<nsCString>); +native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&); +native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&); +native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&); +native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&); +native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&); +native GMPCrashHelperPtr(GMPCrashHelper*); + +[scriptable, uuid(44d362ae-937a-4803-bee6-f2512a0149d1)] +interface mozIGeckoMediaPluginService : nsISupports +{ + + /** + * The GMP thread. Callable from any thread. + */ + readonly attribute nsIThread thread; + + /** + * Run through windows registered registered for pluginId, sending + * 'PluginCrashed' chrome-only event + */ + void RunPluginCrashCallbacks(in unsigned long pluginId, in ACString pluginName); + + /** + * Get a plugin that supports the specified tags. + * Callable on any thread + */ + [noscript] + boolean hasPluginForAPI(in ACString api, in TagArray tags); + + /** + * Get a video decoder that supports the specified tags. + * The array of tags should at least contain a codec tag, and optionally + * other tags such as for EME keysystem. + * Callable only on GMP thread. + * This is an asynchronous operation, the Done method of the callback object + * will be called on the GMP thread with the result (which might be null in + * the case of failure). This method always takes ownership of the callback + * object, but if this method returns an error then the Done method of the + * callback object will not be called at all. + */ + [noscript] + void getGMPVideoDecoder(in GMPCrashHelperPtr helper, + in TagArray tags, + [optional] in ACString nodeId, + in GetGMPVideoDecoderCallback callback); + + /** + * Gets a video decoder as per getGMPVideoDecoder, except it is linked to + * with a corresponding GMPDecryptor via the decryptor's ID. + * This is a temporary measure, until we can implement a Chromium CDM + * GMP protocol which does both decryption and decoding. + */ + [noscript] + void getDecryptingGMPVideoDecoder(in GMPCrashHelperPtr helper, + in TagArray tags, + in ACString nodeId, + in GetGMPVideoDecoderCallback callback, + in uint32_t decryptorId); + + /** + * Get a video encoder that supports the specified tags. + * The array of tags should at least contain a codec tag, and optionally + * other tags. + * Callable only on GMP thread. + * This is an asynchronous operation, the Done method of the callback object + * will be called on the GMP thread with the result (which might be null in + * the case of failure). This method always takes ownership of the callback + * object, but if this method returns an error then the Done method of the + * callback object will not be called at all. + */ + [noscript] + void getGMPVideoEncoder(in GMPCrashHelperPtr helper, + in TagArray tags, + [optional] in ACString nodeId, + in GetGMPVideoEncoderCallback callback); + + /** + * Returns an audio decoder that supports the specified tags. + * The array of tags should at least contain a codec tag, and optionally + * other tags such as for EME keysystem. + * Callable only on GMP thread. + * This is an asynchronous operation, the Done method of the callback object + * will be called on the GMP thread with the result (which might be null in + * the case of failure). This method always takes ownership of the callback + * object, but if this method returns an error then the Done method of the + * callback object will not be called at all. + */ + [noscript] + void getGMPAudioDecoder(in GMPCrashHelperPtr helper, + in TagArray tags, + [optional] in ACString nodeId, + in GetGMPAudioDecoderCallback callback); + + /** + * Returns a decryption session manager that supports the specified tags. + * The array of tags should at least contain a key system tag, and optionally + * other tags. + * Callable only on GMP thread. + * This is an asynchronous operation, the Done method of the callback object + * will be called on the GMP thread with the result (which might be null in + * the case of failure). This method always takes ownership of the callback + * object, but if this method returns an error then the Done method of the + * callback object will not be called at all. + */ + [noscript] + void getGMPDecryptor(in GMPCrashHelperPtr helper, + in TagArray tags, + in ACString nodeId, + in GetGMPDecryptorCallback callback); + + /** + * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple. + */ + [noscript] + void getNodeId(in AString origin, + in AString topLevelOrigin, + in AString gmpName, + in bool inPrivateBrowsingMode, + in GetNodeIdCallback callback); +}; diff --git a/dom/media/gmp/rlz/COPYING b/dom/media/gmp/rlz/COPYING new file mode 100644 index 000000000..b89042ace --- /dev/null +++ b/dom/media/gmp/rlz/COPYING @@ -0,0 +1,14 @@ +Copyright 2010 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.cpp b/dom/media/gmp/rlz/GMPDeviceBinding.cpp new file mode 100644 index 000000000..0871d2e4e --- /dev/null +++ b/dom/media/gmp/rlz/GMPDeviceBinding.cpp @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPDeviceBinding.h" +#include "mozilla/Attributes.h" +#include "prenv.h" + +#include <string> + +#ifdef XP_WIN +#include "windows.h" +#endif + +#if defined(HASH_NODE_ID_WITH_DEVICE_ID) + +// In order to provide EME plugins with a "device binding" capability, +// in the parent we generate and store some random bytes as salt for every +// (origin, urlBarOrigin) pair that uses EME. We store these bytes so +// that every time we revisit the same origin we get the same salt. +// We send this salt to the child on startup. The child collects some +// device specific data and munges that with the salt to create the +// "node id" that we expose to EME plugins. It then overwrites the device +// specific data, and activates the sandbox. + +#include "rlz/lib/machine_id.h" +#include "rlz/lib/string_utils.h" +#include "sha256.h" + +#ifdef XP_WIN +#include "windows.h" +#endif + + +#endif // HASH_NODE_ID_WITH_DEVICE_ID + +namespace mozilla { +namespace gmp { + +#if defined(XP_WIN) && defined(HASH_NODE_ID_WITH_DEVICE_ID) +MOZ_NEVER_INLINE +static bool +GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom) +{ + // "Top" of the free space on the stack is directly after the memory + // holding our return address. + uint8_t* top = (uint8_t*)_AddressOfReturnAddress(); + + // Look down the stack until we find the guard page... + MEMORY_BASIC_INFORMATION memInfo = {0}; + uint8_t* bottom = top; + while (1) { + if (!VirtualQuery(bottom, &memInfo, sizeof(memInfo))) { + return false; + } + if ((memInfo.Protect & PAGE_GUARD) == PAGE_GUARD) { + bottom = (uint8_t*)memInfo.BaseAddress + memInfo.RegionSize; +#ifdef DEBUG + if (!VirtualQuery(bottom, &memInfo, sizeof(memInfo))) { + return false; + } + assert(!(memInfo.Protect & PAGE_GUARD)); // Should have found boundary. +#endif + break; + } else if (memInfo.State != MEM_COMMIT || + (memInfo.AllocationProtect & PAGE_READWRITE) != PAGE_READWRITE) { + return false; + } + bottom = (uint8_t*)memInfo.BaseAddress - 1; + } + *aOutTop = top; + *aOutBottom = bottom; + return true; +} +#endif + +#ifdef HASH_NODE_ID_WITH_DEVICE_ID +static void SecureMemset(void* start, uint8_t value, size_t size) +{ + // Inline instructions equivalent to RtlSecureZeroMemory(). + for (size_t i = 0; i < size; ++i) { + volatile uint8_t* p = static_cast<volatile uint8_t*>(start) + i; + *p = value; + } +} +#endif + +bool +CalculateGMPDeviceId(char* aOriginSalt, + uint32_t aOriginSaltLen, + std::string& aOutNodeId) +{ +#ifdef HASH_NODE_ID_WITH_DEVICE_ID + if (aOriginSaltLen > 0) { + std::vector<uint8_t> deviceId; + int volumeId; + if (!rlz_lib::GetRawMachineId(&deviceId, &volumeId)) { + return false; + } + + SHA256Context ctx; + SHA256_Begin(&ctx); + SHA256_Update(&ctx, (const uint8_t*)aOriginSalt, aOriginSaltLen); + SHA256_Update(&ctx, deviceId.data(), deviceId.size()); + SHA256_Update(&ctx, (const uint8_t*)&volumeId, sizeof(int)); + uint8_t digest[SHA256_LENGTH] = {0}; + unsigned int digestLen = 0; + SHA256_End(&ctx, digest, &digestLen, SHA256_LENGTH); + + // Overwrite all data involved in calculation as it could potentially + // identify the user, so there's no chance a GMP can read it and use + // it for identity tracking. + SecureMemset(&ctx, 0, sizeof(ctx)); + SecureMemset(aOriginSalt, 0, aOriginSaltLen); + SecureMemset(&volumeId, 0, sizeof(volumeId)); + SecureMemset(deviceId.data(), '*', deviceId.size()); + deviceId.clear(); + + if (!rlz_lib::BytesToString(digest, SHA256_LENGTH, &aOutNodeId)) { + return false; + } + + if (!PR_GetEnv("MOZ_GMP_DISABLE_NODE_ID_CLEANUP")) { + // We've successfully bound the origin salt to node id. + // rlz_lib::GetRawMachineId and/or the system functions it + // called could have left user identifiable data on the stack, + // so carefully zero the stack down to the guard page. + uint8_t* top; + uint8_t* bottom; + if (!GetStackAfterCurrentFrame(&top, &bottom)) { + return false; + } + assert(top >= bottom); + // Inline instructions equivalent to RtlSecureZeroMemory(). + // We can't just use RtlSecureZeroMemory here directly, as in debug + // builds, RtlSecureZeroMemory() can't be inlined, and the stack + // memory it uses would get wiped by itself running, causing crashes. + for (volatile uint8_t* p = (volatile uint8_t*)bottom; p < top; p++) { + *p = 0; + } + } + } else +#endif + { + aOutNodeId = std::string(aOriginSalt, aOriginSalt + aOriginSaltLen); + } + return true; +} + +} // namespace gmp +} // namespace mozilla diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.h b/dom/media/gmp/rlz/GMPDeviceBinding.h new file mode 100644 index 000000000..ee1664466 --- /dev/null +++ b/dom/media/gmp/rlz/GMPDeviceBinding.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GMP_DEVICE_BINDING_h_ +#define GMP_DEVICE_BINDING_h_ + +#include <string> + +namespace mozilla { +namespace gmp { + +bool CalculateGMPDeviceId(char* aOriginSalt, + uint32_t aOriginSaltLen, + std::string& aOutNodeId); + +} // namespace gmp +} // namespace mozilla + +#endif // GMP_DEVICE_BINDING_h_ diff --git a/dom/media/gmp/rlz/README.mozilla b/dom/media/gmp/rlz/README.mozilla new file mode 100644 index 000000000..fffc5deb5 --- /dev/null +++ b/dom/media/gmp/rlz/README.mozilla @@ -0,0 +1,6 @@ +Code taken from rlz project: https://code.google.com/p/rlz/ + +Revision: 134, then with unused code stripped out. + +Note: base/ contains wrappers/dummies to provide implementations of the +Chromium APIs that this code relies upon. diff --git a/dom/media/gmp/rlz/lib/assert.h b/dom/media/gmp/rlz/lib/assert.h new file mode 100644 index 000000000..68737b1e2 --- /dev/null +++ b/dom/media/gmp/rlz/lib/assert.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FAKE_ASSERT_H_
+#define FAKE_ASSERT_H_
+
+#include <assert.h>
+
+#define ASSERT_STRING(x) { assert(false); }
+#define VERIFY(x) { assert(x); };
+
+#endif
diff --git a/dom/media/gmp/rlz/lib/machine_id.h b/dom/media/gmp/rlz/lib/machine_id.h new file mode 100644 index 000000000..67661cfce --- /dev/null +++ b/dom/media/gmp/rlz/lib/machine_id.h @@ -0,0 +1,19 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Use of this source code is governed by an Apache-style license that can be +// found in the COPYING file. + +#ifndef RLZ_LIB_MACHINE_ID_H_ +#define RLZ_LIB_MACHINE_ID_H_ + +#include <vector> + +namespace rlz_lib { + +// Retrieves a raw machine identifier string and a machine-specific +// 4 byte value. GetMachineId() will SHA1 |data|, append |more_data|, compute +// the Crc8 of that, and return a hex-encoded string of that data. +bool GetRawMachineId(std::vector<uint8_t>* data, int* more_data); + +} // namespace rlz_lib + +#endif // RLZ_LIB_MACHINE_ID_H_ diff --git a/dom/media/gmp/rlz/lib/string_utils.cc b/dom/media/gmp/rlz/lib/string_utils.cc new file mode 100644 index 000000000..b6a71acdb --- /dev/null +++ b/dom/media/gmp/rlz/lib/string_utils.cc @@ -0,0 +1,34 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// Use of this source code is governed by an Apache-style license that can be +// found in the COPYING file. +// +// String manipulation functions used in the RLZ library. + +#include "rlz/lib/string_utils.h" + +namespace rlz_lib { + +bool BytesToString(const unsigned char* data, + int data_len, + std::string* string) { + if (!string) + return false; + + string->clear(); + if (data_len < 1 || !data) + return false; + + static const char kHex[] = "0123456789ABCDEF"; + + // Fix the buffer size to begin with to avoid repeated re-allocation. + string->resize(data_len * 2); + int index = data_len; + while (index--) { + string->at(2 * index) = kHex[data[index] >> 4]; // high digit + string->at(2 * index + 1) = kHex[data[index] & 0x0F]; // low digit + } + + return true; +} + +} // namespace rlz_lib diff --git a/dom/media/gmp/rlz/lib/string_utils.h b/dom/media/gmp/rlz/lib/string_utils.h new file mode 100644 index 000000000..294f1b2d9 --- /dev/null +++ b/dom/media/gmp/rlz/lib/string_utils.h @@ -0,0 +1,20 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// Use of this source code is governed by an Apache-style license that can be +// found in the COPYING file. +// +// String manipulation functions used in the RLZ library. + +#ifndef RLZ_LIB_STRING_UTILS_H_ +#define RLZ_LIB_STRING_UTILS_H_ + +#include <string> + +namespace rlz_lib { + +bool BytesToString(const unsigned char* data, + int data_len, + std::string* string); + +}; // namespace + +#endif // RLZ_LIB_STRING_UTILS_H_ diff --git a/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc new file mode 100644 index 000000000..2bea0f55f --- /dev/null +++ b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc @@ -0,0 +1,320 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/network/IOEthernetController.h> +#include <IOKit/network/IOEthernetInterface.h> +#include <IOKit/network/IONetworkInterface.h> +#include <vector> +#include <string> + +// Note: The original machine_id_mac.cc code is in namespace rlz_lib below. +// It depends on some external files, which would bring in a log of Chromium +// code if imported as well. +// Instead only the necessary code has been extracted from the relevant files, +// and further combined and reduced to limit the maintenance burden. + +// [Extracted from base/logging.h] +#define DCHECK assert + +namespace base { + +// [Extracted from base/mac/scoped_typeref.h and base/mac/scoped_cftyperef.h] +template<typename T> +class ScopedCFTypeRef { + public: + typedef T element_type; + + explicit ScopedCFTypeRef(T object) + : object_(object) { + } + + ScopedCFTypeRef(const ScopedCFTypeRef<T>& that) = delete; + ScopedCFTypeRef(ScopedCFTypeRef<T>&& that) = delete; + + ~ScopedCFTypeRef() { + if (object_) + CFRelease(object_); + } + + ScopedCFTypeRef& operator=(const ScopedCFTypeRef<T>& that) = delete; + ScopedCFTypeRef& operator=(ScopedCFTypeRef<T>&& that) = delete; + + operator T() const { + return object_; + } + + // ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for CFRelease(). + T release() { + T temp = object_; + object_ = NULL; + return temp; + } + + private: + T object_; +}; + +namespace mac { + +// [Extracted from base/mac/scoped_ioobject.h] +// Just like ScopedCFTypeRef but for io_object_t and subclasses. +template<typename IOT> +class ScopedIOObject { + public: + typedef IOT element_type; + + explicit ScopedIOObject(IOT object = IO_OBJECT_NULL) + : object_(object) { + } + + ~ScopedIOObject() { + if (object_) + IOObjectRelease(object_); + } + + ScopedIOObject(const ScopedIOObject&) = delete; + void operator=(const ScopedIOObject&) = delete; + + void reset(IOT object = IO_OBJECT_NULL) { + if (object_) + IOObjectRelease(object_); + object_ = object; + } + + operator IOT() const { + return object_; + } + + private: + IOT object_; +}; + +// [Extracted from base/mac/foundation_util.h] +template<typename T> +T CFCast(const CFTypeRef& cf_val); + +template<> +CFDataRef +CFCast<CFDataRef>(const CFTypeRef& cf_val) { + if (cf_val == NULL) { + return NULL; + } + if (CFGetTypeID(cf_val) == CFDataGetTypeID()) { + return (CFDataRef)(cf_val); + } + return NULL; +} + +template<> +CFStringRef +CFCast<CFStringRef>(const CFTypeRef& cf_val) { + if (cf_val == NULL) { + return NULL; + } + if (CFGetTypeID(cf_val) == CFStringGetTypeID()) { + return (CFStringRef)(cf_val); + } + return NULL; +} + +} // namespace mac + +// [Extracted from base/strings/sys_string_conversions_mac.mm] +static const CFStringEncoding kNarrowStringEncoding = kCFStringEncodingUTF8; + +template<typename StringType> +static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring, + CFStringEncoding encoding) { + CFIndex length = CFStringGetLength(cfstring); + if (length == 0) + return StringType(); + + CFRange whole_string = CFRangeMake(0, length); + CFIndex out_size; + CFIndex converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + NULL, // buffer + 0, // maxBufLen + &out_size); + if (converted == 0 || out_size == 0) + return StringType(); + + // out_size is the number of UInt8-sized units needed in the destination. + // A buffer allocated as UInt8 units might not be properly aligned to + // contain elements of StringType::value_type. Use a container for the + // proper value_type, and convert out_size by figuring the number of + // value_type elements per UInt8. Leave room for a NUL terminator. + typename StringType::size_type elements = + out_size * sizeof(UInt8) / sizeof(typename StringType::value_type) + 1; + + std::vector<typename StringType::value_type> out_buffer(elements); + converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + reinterpret_cast<UInt8*>(&out_buffer[0]), + out_size, + NULL); // usedBufLen + if (converted == 0) + return StringType(); + + out_buffer[elements - 1] = '\0'; + return StringType(&out_buffer[0], elements - 1); +} + +std::string SysCFStringRefToUTF8(CFStringRef ref) +{ + return CFStringToSTLStringWithEncodingT<std::string>(ref, + kNarrowStringEncoding); +} + +} // namespace base + + +namespace rlz_lib { + +namespace { + +// See http://developer.apple.com/library/mac/#technotes/tn1103/_index.html + +// The caller is responsible for freeing |matching_services|. +bool FindEthernetInterfaces(io_iterator_t* matching_services) { + base::ScopedCFTypeRef<CFMutableDictionaryRef> matching_dict( + IOServiceMatching(kIOEthernetInterfaceClass)); + if (!matching_dict) + return false; + + base::ScopedCFTypeRef<CFMutableDictionaryRef> primary_interface( + CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!primary_interface) + return false; + + CFDictionarySetValue( + primary_interface, CFSTR(kIOPrimaryInterface), kCFBooleanTrue); + CFDictionarySetValue( + matching_dict, CFSTR(kIOPropertyMatchKey), primary_interface); + + kern_return_t kern_result = IOServiceGetMatchingServices( + kIOMasterPortDefault, matching_dict.release(), matching_services); + + return kern_result == KERN_SUCCESS; +} + +bool GetMACAddressFromIterator(io_iterator_t primary_interface_iterator, + uint8_t* buffer, size_t buffer_size) { + if (buffer_size < kIOEthernetAddressSize) + return false; + + bool success = false; + + bzero(buffer, buffer_size); + base::mac::ScopedIOObject<io_object_t> primary_interface; + while (primary_interface.reset(IOIteratorNext(primary_interface_iterator)), + primary_interface) { + io_object_t primary_interface_parent; + kern_return_t kern_result = IORegistryEntryGetParentEntry( + primary_interface, kIOServicePlane, &primary_interface_parent); + base::mac::ScopedIOObject<io_object_t> primary_interface_parent_deleter( + primary_interface_parent); + success = kern_result == KERN_SUCCESS; + + if (!success) + continue; + + base::ScopedCFTypeRef<CFTypeRef> mac_data( + IORegistryEntryCreateCFProperty(primary_interface_parent, + CFSTR(kIOMACAddress), + kCFAllocatorDefault, + 0)); + CFDataRef mac_data_data = base::mac::CFCast<CFDataRef>(mac_data); + if (mac_data_data) { + CFDataGetBytes( + mac_data_data, CFRangeMake(0, kIOEthernetAddressSize), buffer); + } + } + + return success; +} + +bool GetMacAddress(unsigned char* buffer, size_t size) { + io_iterator_t primary_interface_iterator; + if (!FindEthernetInterfaces(&primary_interface_iterator)) + return false; + bool result = GetMACAddressFromIterator( + primary_interface_iterator, buffer, size); + IOObjectRelease(primary_interface_iterator); + return result; +} + +CFStringRef CopySerialNumber() { + base::mac::ScopedIOObject<io_service_t> expert_device( + IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice"))); + if (!expert_device) + return NULL; + + base::ScopedCFTypeRef<CFTypeRef> serial_number( + IORegistryEntryCreateCFProperty(expert_device, + CFSTR(kIOPlatformSerialNumberKey), + kCFAllocatorDefault, + 0)); + CFStringRef serial_number_cfstring = + base::mac::CFCast<CFStringRef>(serial_number.release()); + if (!serial_number_cfstring) + return NULL; + + return serial_number_cfstring; +} + +} // namespace + +bool GetRawMachineId(std::vector<uint8_t>* data, int* more_data) { + uint8_t mac_address[kIOEthernetAddressSize]; + + std::string id; + if (GetMacAddress(mac_address, sizeof(mac_address))) { + id += "mac:"; + static const char hex[] = + { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + for (int i = 0; i < kIOEthernetAddressSize; ++i) { + uint8_t byte = mac_address[i]; + id += hex[byte >> 4]; + id += hex[byte & 0xF]; + } + } + + // A MAC address is enough to uniquely identify a machine, but it's only 6 + // bytes, 3 of which are manufacturer-determined. To make brute-forcing the + // SHA1 of this harder, also append the system's serial number. + CFStringRef serial = CopySerialNumber(); + if (serial) { + if (!id.empty()) { + id += ' '; + } + id += "serial:"; + id += base::SysCFStringRefToUTF8(serial); + CFRelease(serial); + } + + // Get the contents of the string 'id' as a bunch of bytes. + data->assign(&id[0], &id[id.size()]); + + // On windows, this is set to the volume id. Since it's not scrambled before + // being sent, just set it to 1. + *more_data = 1; + return true; +} + +} // namespace rlz_lib diff --git a/dom/media/gmp/rlz/moz.build b/dom/media/gmp/rlz/moz.build new file mode 100644 index 000000000..aa6f5fece --- /dev/null +++ b/dom/media/gmp/rlz/moz.build @@ -0,0 +1,34 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Note: build rlz in its own moz.build, so it doesn't pickup any of +# Chromium IPC's headers used in the moz.build of the parent file. + +Library('rlz') + +UNIFIED_SOURCES += [ + 'GMPDeviceBinding.cpp', +] + +if CONFIG['OS_TARGET'] == 'WINNT': + UNIFIED_SOURCES += [ + 'win/lib/machine_id_win.cc', + ] + +if CONFIG['OS_TARGET'] == 'Darwin': + UNIFIED_SOURCES += [ + 'mac/lib/machine_id_mac.cc', + ] + OS_LIBS += [ + '-framework IOKit', + ] + +LOCAL_INCLUDES += [ + '..', +] + +EXPORTS += [ + 'GMPDeviceBinding.h', +] diff --git a/dom/media/gmp/rlz/sha256.c b/dom/media/gmp/rlz/sha256.c new file mode 100644 index 000000000..072de5195 --- /dev/null +++ b/dom/media/gmp/rlz/sha256.c @@ -0,0 +1,469 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Stripped down version of security/nss/lib/freebl/sha512.c +// and related headers. + +#include "sha256.h" +#include "string.h" +#include "prcpucfg.h" +#if defined(NSS_X86) || defined(SHA_NO_LONG_LONG) +#define NOUNROLL512 1 +#undef HAVE_LONG_LONG +#endif +
+#define SHA256_BLOCK_LENGTH 64 /* bytes */
+
+typedef enum _SECStatus {
+ SECWouldBlock = -2,
+ SECFailure = -1,
+ SECSuccess = 0
+} SECStatus; + +/* ============= Common constants and defines ======================= */ + +#define W ctx->u.w +#define B ctx->u.b +#define H ctx->h + +#define SHR(x,n) (x >> n) +#define SHL(x,n) (x << n) +#define Ch(x,y,z) ((x & y) ^ (~x & z)) +#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) +#define SHA_MIN(a,b) (a < b ? a : b) + +/* Padding used with all flavors of SHA */ +static const PRUint8 pad[240] = { +0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + /* compiler will fill the rest in with zeros */ +}; + +/* ============= SHA256 implementation ================================== */ + +/* SHA-256 constants, K256. */ +static const PRUint32 K256[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/* SHA-256 initial hash values */ +static const PRUint32 H256[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +#if defined(_MSC_VER) +#include <stdlib.h> +#pragma intrinsic(_byteswap_ulong) +#define SHA_HTONL(x) _byteswap_ulong(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) +#elif defined(__GNUC__) && defined(NSS_X86_OR_X64) +static __inline__ PRUint32 swap4b(PRUint32 value) +{ + __asm__("bswap %0" : "+r" (value)); + return (value); +} +#define SHA_HTONL(x) swap4b(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) + +#elif defined(__GNUC__) && (defined(__thumb2__) || \ + (!defined(__thumb__) && \ + (defined(__ARM_ARCH_6__) || \ + defined(__ARM_ARCH_6J__) || \ + defined(__ARM_ARCH_6K__) || \ + defined(__ARM_ARCH_6Z__) || \ + defined(__ARM_ARCH_6ZK__) || \ + defined(__ARM_ARCH_6T2__) || \ + defined(__ARM_ARCH_7__) || \ + defined(__ARM_ARCH_7A__) || \ + defined(__ARM_ARCH_7R__)))) +static __inline__ PRUint32 swap4b(PRUint32 value) +{ + PRUint32 ret; + __asm__("rev %0, %1" : "=r" (ret) : "r"(value)); + return ret; +} +#define SHA_HTONL(x) swap4b(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) + +#else +#define SWAP4MASK 0x00FF00FF +#define SHA_HTONL(x) (t1 = (x), t1 = (t1 << 16) | (t1 >> 16), \ + ((t1 & SWAP4MASK) << 8) | ((t1 >> 8) & SWAP4MASK)) +#define BYTESWAP4(x) x = SHA_HTONL(x) +#endif + +#if defined(_MSC_VER) +#pragma intrinsic (_lrotr, _lrotl) +#define ROTR32(x,n) _lrotr(x,n) +#define ROTL32(x,n) _lrotl(x,n) +#else +#define ROTR32(x,n) ((x >> n) | (x << ((8 * sizeof x) - n))) +#define ROTL32(x,n) ((x << n) | (x >> ((8 * sizeof x) - n))) +#endif + +/* Capitol Sigma and lower case sigma functions */ +#define S0(x) (ROTR32(x, 2) ^ ROTR32(x,13) ^ ROTR32(x,22)) +#define S1(x) (ROTR32(x, 6) ^ ROTR32(x,11) ^ ROTR32(x,25)) +#define s0(x) (t1 = x, ROTR32(t1, 7) ^ ROTR32(t1,18) ^ SHR(t1, 3)) +#define s1(x) (t2 = x, ROTR32(t2,17) ^ ROTR32(t2,19) ^ SHR(t2,10)) + +void +SHA256_Begin(SHA256Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H256, sizeof H256); +} + +static void +SHA256_Compress(SHA256Context *ctx) +{ + { + register PRUint32 t1, t2; + +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(W[0]); + BYTESWAP4(W[1]); + BYTESWAP4(W[2]); + BYTESWAP4(W[3]); + BYTESWAP4(W[4]); + BYTESWAP4(W[5]); + BYTESWAP4(W[6]); + BYTESWAP4(W[7]); + BYTESWAP4(W[8]); + BYTESWAP4(W[9]); + BYTESWAP4(W[10]); + BYTESWAP4(W[11]); + BYTESWAP4(W[12]); + BYTESWAP4(W[13]); + BYTESWAP4(W[14]); + BYTESWAP4(W[15]); +#endif + +#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16]) + + /* prepare the "message schedule" */ +#ifdef NOUNROLL256 + { + int t; + for (t = 16; t < 64; ++t) { + INITW(t); + } + } +#else + INITW(16); + INITW(17); + INITW(18); + INITW(19); + + INITW(20); + INITW(21); + INITW(22); + INITW(23); + INITW(24); + INITW(25); + INITW(26); + INITW(27); + INITW(28); + INITW(29); + + INITW(30); + INITW(31); + INITW(32); + INITW(33); + INITW(34); + INITW(35); + INITW(36); + INITW(37); + INITW(38); + INITW(39); + + INITW(40); + INITW(41); + INITW(42); + INITW(43); + INITW(44); + INITW(45); + INITW(46); + INITW(47); + INITW(48); + INITW(49); + + INITW(50); + INITW(51); + INITW(52); + INITW(53); + INITW(54); + INITW(55); + INITW(56); + INITW(57); + INITW(58); + INITW(59); + + INITW(60); + INITW(61); + INITW(62); + INITW(63); + +#endif +#undef INITW + } + { + PRUint32 a, b, c, d, e, f, g, h; + + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + h += S1(e) + Ch(e,f,g) + K256[n] + W[n]; \ + d += h; \ + h += S0(a) + Maj(a,b,c); + +#ifdef NOUNROLL256 + { + int t; + for (t = 0; t < 64; t+= 8) { + ROUND(t+0,a,b,c,d,e,f,g,h) + ROUND(t+1,h,a,b,c,d,e,f,g) + ROUND(t+2,g,h,a,b,c,d,e,f) + ROUND(t+3,f,g,h,a,b,c,d,e) + ROUND(t+4,e,f,g,h,a,b,c,d) + ROUND(t+5,d,e,f,g,h,a,b,c) + ROUND(t+6,c,d,e,f,g,h,a,b) + ROUND(t+7,b,c,d,e,f,g,h,a) + } + } +#else + ROUND( 0,a,b,c,d,e,f,g,h) + ROUND( 1,h,a,b,c,d,e,f,g) + ROUND( 2,g,h,a,b,c,d,e,f) + ROUND( 3,f,g,h,a,b,c,d,e) + ROUND( 4,e,f,g,h,a,b,c,d) + ROUND( 5,d,e,f,g,h,a,b,c) + ROUND( 6,c,d,e,f,g,h,a,b) + ROUND( 7,b,c,d,e,f,g,h,a) + + ROUND( 8,a,b,c,d,e,f,g,h) + ROUND( 9,h,a,b,c,d,e,f,g) + ROUND(10,g,h,a,b,c,d,e,f) + ROUND(11,f,g,h,a,b,c,d,e) + ROUND(12,e,f,g,h,a,b,c,d) + ROUND(13,d,e,f,g,h,a,b,c) + ROUND(14,c,d,e,f,g,h,a,b) + ROUND(15,b,c,d,e,f,g,h,a) + + ROUND(16,a,b,c,d,e,f,g,h) + ROUND(17,h,a,b,c,d,e,f,g) + ROUND(18,g,h,a,b,c,d,e,f) + ROUND(19,f,g,h,a,b,c,d,e) + ROUND(20,e,f,g,h,a,b,c,d) + ROUND(21,d,e,f,g,h,a,b,c) + ROUND(22,c,d,e,f,g,h,a,b) + ROUND(23,b,c,d,e,f,g,h,a) + + ROUND(24,a,b,c,d,e,f,g,h) + ROUND(25,h,a,b,c,d,e,f,g) + ROUND(26,g,h,a,b,c,d,e,f) + ROUND(27,f,g,h,a,b,c,d,e) + ROUND(28,e,f,g,h,a,b,c,d) + ROUND(29,d,e,f,g,h,a,b,c) + ROUND(30,c,d,e,f,g,h,a,b) + ROUND(31,b,c,d,e,f,g,h,a) + + ROUND(32,a,b,c,d,e,f,g,h) + ROUND(33,h,a,b,c,d,e,f,g) + ROUND(34,g,h,a,b,c,d,e,f) + ROUND(35,f,g,h,a,b,c,d,e) + ROUND(36,e,f,g,h,a,b,c,d) + ROUND(37,d,e,f,g,h,a,b,c) + ROUND(38,c,d,e,f,g,h,a,b) + ROUND(39,b,c,d,e,f,g,h,a) + + ROUND(40,a,b,c,d,e,f,g,h) + ROUND(41,h,a,b,c,d,e,f,g) + ROUND(42,g,h,a,b,c,d,e,f) + ROUND(43,f,g,h,a,b,c,d,e) + ROUND(44,e,f,g,h,a,b,c,d) + ROUND(45,d,e,f,g,h,a,b,c) + ROUND(46,c,d,e,f,g,h,a,b) + ROUND(47,b,c,d,e,f,g,h,a) + + ROUND(48,a,b,c,d,e,f,g,h) + ROUND(49,h,a,b,c,d,e,f,g) + ROUND(50,g,h,a,b,c,d,e,f) + ROUND(51,f,g,h,a,b,c,d,e) + ROUND(52,e,f,g,h,a,b,c,d) + ROUND(53,d,e,f,g,h,a,b,c) + ROUND(54,c,d,e,f,g,h,a,b) + ROUND(55,b,c,d,e,f,g,h,a) + + ROUND(56,a,b,c,d,e,f,g,h) + ROUND(57,h,a,b,c,d,e,f,g) + ROUND(58,g,h,a,b,c,d,e,f) + ROUND(59,f,g,h,a,b,c,d,e) + ROUND(60,e,f,g,h,a,b,c,d) + ROUND(61,d,e,f,g,h,a,b,c) + ROUND(62,c,d,e,f,g,h,a,b) + ROUND(63,b,c,d,e,f,g,h,a) +#endif + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; + } +#undef ROUND +} + +#undef s0 +#undef s1 +#undef S0 +#undef S1 + +void +SHA256_Update(SHA256Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + unsigned int inBuf = ctx->sizeLo & 0x3f; + if (!inputLen) + return; + + /* Add inputLen into the count of bytes processed, before processing */ + if ((ctx->sizeLo += inputLen) < inputLen) + ctx->sizeHi++; + + /* if data already in buffer, attemp to fill rest of buffer */ + if (inBuf) { + unsigned int todo = SHA256_BLOCK_LENGTH - inBuf; + if (inputLen < todo) + todo = inputLen; + memcpy(B + inBuf, input, todo); + input += todo; + inputLen -= todo; + if (inBuf + todo == SHA256_BLOCK_LENGTH) + SHA256_Compress(ctx); + } + + /* if enough data to fill one or more whole buffers, process them. */ + while (inputLen >= SHA256_BLOCK_LENGTH) { + memcpy(B, input, SHA256_BLOCK_LENGTH); + input += SHA256_BLOCK_LENGTH; + inputLen -= SHA256_BLOCK_LENGTH; + SHA256_Compress(ctx); + } + /* if data left over, fill it into buffer */ + if (inputLen) + memcpy(B, input, inputLen); +} + +void +SHA256_End(SHA256Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ + unsigned int inBuf = ctx->sizeLo & 0x3f; + unsigned int padLen = (inBuf < 56) ? (56 - inBuf) : (56 + 64 - inBuf); + PRUint32 hi, lo; +#ifdef SWAP4MASK + PRUint32 t1; +#endif + + hi = (ctx->sizeHi << 3) | (ctx->sizeLo >> 29); + lo = (ctx->sizeLo << 3); + + SHA256_Update(ctx, pad, padLen); + +#if defined(IS_LITTLE_ENDIAN) + W[14] = SHA_HTONL(hi); + W[15] = SHA_HTONL(lo); +#else + W[14] = hi; + W[15] = lo; +#endif + SHA256_Compress(ctx); + + /* now output the answer */ +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(H[0]); + BYTESWAP4(H[1]); + BYTESWAP4(H[2]); + BYTESWAP4(H[3]); + BYTESWAP4(H[4]); + BYTESWAP4(H[5]); + BYTESWAP4(H[6]); + BYTESWAP4(H[7]); +#endif + padLen = PR_MIN(SHA256_LENGTH, maxDigestLen); + memcpy(digest, H, padLen); + if (digestLen) + *digestLen = padLen; +} + +void +SHA256_EndRaw(SHA256Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ + PRUint32 h[8]; + unsigned int len; +#ifdef SWAP4MASK + PRUint32 t1; +#endif + + memcpy(h, ctx->h, sizeof(h)); + +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(h[0]); + BYTESWAP4(h[1]); + BYTESWAP4(h[2]); + BYTESWAP4(h[3]); + BYTESWAP4(h[4]); + BYTESWAP4(h[5]); + BYTESWAP4(h[6]); + BYTESWAP4(h[7]); +#endif + + len = PR_MIN(SHA256_LENGTH, maxDigestLen); + memcpy(digest, h, len); + if (digestLen) + *digestLen = len; +} + +SECStatus +SHA256_HashBuf(unsigned char *dest, const unsigned char *src, + PRUint32 src_length) +{ + SHA256Context ctx; + unsigned int outLen; + + SHA256_Begin(&ctx); + SHA256_Update(&ctx, src, src_length); + SHA256_End(&ctx, dest, &outLen, SHA256_LENGTH); + memset(&ctx, 0, sizeof ctx); + + return SECSuccess; +} diff --git a/dom/media/gmp/rlz/sha256.h b/dom/media/gmp/rlz/sha256.h new file mode 100644 index 000000000..6958e382c --- /dev/null +++ b/dom/media/gmp/rlz/sha256.h @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Stripped down version of security/nss/lib/freebl/blapi.h +// and related headers. + +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#define SHA256_LENGTH 32 + +#include "prtypes.h" /* for PRUintXX */ +#include "prlong.h" + +#ifdef __cplusplus
+extern "C" {
+#endif + +struct SHA256Context {
+ union {
+ PRUint32 w[64]; /* message schedule, input buffer, plus 48 words */
+ PRUint8 b[256];
+ } u;
+ PRUint32 h[8]; /* 8 state variables */
+ PRUint32 sizeHi,sizeLo; /* 64-bit count of hashed bytes. */
+}; + +typedef struct SHA256Context SHA256Context; + +extern void +SHA256_Begin(SHA256Context *ctx); + +extern void +SHA256_Update(SHA256Context *ctx, const unsigned char *input, + unsigned int inputLen); + +extern void +SHA256_End(SHA256Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen); + +#ifdef __cplusplus
+} /* extern C */ +#endif + +#endif /* _SHA256_H_ */ diff --git a/dom/media/gmp/rlz/win/lib/machine_id_win.cc b/dom/media/gmp/rlz/win/lib/machine_id_win.cc new file mode 100644 index 000000000..0a636ebd3 --- /dev/null +++ b/dom/media/gmp/rlz/win/lib/machine_id_win.cc @@ -0,0 +1,134 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// Use of this source code is governed by an Apache-style license that can be +// found in the COPYING file. + +#include <windows.h> +#include <sddl.h> // For ConvertSidToStringSidW. +#include <string> +#include <vector> +#include "mozilla/ArrayUtils.h"
+ +#include "rlz/lib/assert.h" + +namespace rlz_lib { + +namespace { + +bool GetSystemVolumeSerialNumber(int* number) { + if (!number) + return false; + + *number = 0; + + // Find the system root path (e.g: C:\). + wchar_t system_path[MAX_PATH + 1]; + if (!GetSystemDirectoryW(system_path, MAX_PATH)) + return false; + + wchar_t* first_slash = wcspbrk(system_path, L"\\/"); + if (first_slash != NULL) + *(first_slash + 1) = 0; + + DWORD number_local = 0; + if (!GetVolumeInformationW(system_path, NULL, 0, &number_local, NULL, NULL, + NULL, 0)) + return false; + + *number = (int)number_local; + return true; +} + +bool GetComputerSid(const wchar_t* account_name, SID* sid, DWORD sid_size) { + static const DWORD kStartDomainLength = 128; // reasonable to start with + + std::vector<wchar_t> domain_buffer(kStartDomainLength, 0); + DWORD domain_size = kStartDomainLength; + DWORD sid_dword_size = sid_size; + SID_NAME_USE sid_name_use; + + BOOL success = ::LookupAccountNameW(NULL, account_name, sid, + &sid_dword_size, &domain_buffer.front(), + &domain_size, &sid_name_use); + if (!success && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // We could have gotten the insufficient buffer error because + // one or both of sid and szDomain was too small. Check for that + // here. + if (sid_dword_size > sid_size) + return false; + + if (domain_size > kStartDomainLength) + domain_buffer.resize(domain_size); + + success = ::LookupAccountNameW(NULL, account_name, sid, &sid_dword_size, + &domain_buffer.front(), &domain_size, + &sid_name_use); + } + + return success != FALSE; +} + +std::vector<uint8_t> ConvertSidToBytes(SID* sid) { + std::wstring sid_string; +#if _WIN32_WINNT >= 0x500 + wchar_t* sid_buffer = NULL; + if (ConvertSidToStringSidW(sid, &sid_buffer)) { + sid_string = sid_buffer; + LocalFree(sid_buffer); + } +#else + SID_IDENTIFIER_AUTHORITY* sia = ::GetSidIdentifierAuthority(sid); + + if(sia->Value[0] || sia->Value[1]) { + base::SStringPrintf( + &sid_string, L"S-%d-0x%02hx%02hx%02hx%02hx%02hx%02hx", + SID_REVISION, (USHORT)sia->Value[0], (USHORT)sia->Value[1], + (USHORT)sia->Value[2], (USHORT)sia->Value[3], (USHORT)sia->Value[4], + (USHORT)sia->Value[5]); + } else { + ULONG authority = 0; + for (int i = 2; i < 6; ++i) { + authority <<= 8; + authority |= sia->Value[i]; + } + base::SStringPrintf(&sid_string, L"S-%d-%lu", SID_REVISION, authority); + } + + int sub_auth_count = *::GetSidSubAuthorityCount(sid); + for(int i = 0; i < sub_auth_count; ++i) + base::StringAppendF(&sid_string, L"-%lu", *::GetSidSubAuthority(sid, i)); +#endif + + // Get the contents of the string as a bunch of bytes. + return std::vector<uint8_t>( + reinterpret_cast<uint8_t*>(&sid_string[0]), + reinterpret_cast<uint8_t*>(&sid_string[sid_string.size()])); +} + +} // namespace + +bool GetRawMachineId(std::vector<uint8_t>* sid_bytes, int* volume_id) { + // Calculate the Windows SID. + + wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {0}; + DWORD size = mozilla::ArrayLength(computer_name); + + if (!GetComputerNameW(computer_name, &size)) { + return false; + } + char sid_buffer[SECURITY_MAX_SID_SIZE]; + SID* sid = reinterpret_cast<SID*>(sid_buffer); + if (GetComputerSid(computer_name, sid, SECURITY_MAX_SID_SIZE)) { + *sid_bytes = ConvertSidToBytes(sid); + } + + // Get the system drive volume serial number. + *volume_id = 0; + if (!GetSystemVolumeSerialNumber(volume_id)) { + ASSERT_STRING("GetMachineId: Failed to retrieve volume serial number"); + *volume_id = 0; + } + + return true; +} + +} // namespace rlz_lib diff --git a/dom/media/moz.build b/dom/media/moz.build index 6b0efc32b..ec36dfaf2 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -3,9 +3,15 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +with Files('*'): + BUG_COMPONENT = ('Core', 'Video/Audio') + DIRS += [ 'encoder', 'flac', + 'gmp', + 'gmp-plugin', + 'gmp-plugin-openh264', 'imagecapture', 'ipc', 'mediasink', diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp index ecae512be..b8082d1ff 100644 --- a/dom/media/platforms/PDMFactory.cpp +++ b/dom/media/platforms/PDMFactory.cpp @@ -15,6 +15,7 @@ #ifdef MOZ_FFMPEG #include "FFmpegRuntimeLinker.h" #endif +#include "GMPDecoderModule.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/SharedThreadPool.h" @@ -208,6 +209,9 @@ PDMFactory::CreateDecoder(const CreateDecoderParams& aParams) if (mFFmpegFailedToLoad) { diagnostics->SetFFmpegFailedToLoad(); } + if (mGMPPDMFailedToStartup) { + diagnostics->SetGMPPDMFailedToStartup(); + } } for (auto& current : mCurrentPDMs) { @@ -363,6 +367,13 @@ PDMFactory::CreatePDMs() m = new AgnosticDecoderModule(); StartupPDM(m); + + if (MediaPrefs::PDMGMPEnabled()) { + m = new GMPDecoderModule(); + mGMPPDMFailedToStartup = !StartupPDM(m); + } else { + mGMPPDMFailedToStartup = false; + } } void @@ -395,6 +406,9 @@ PDMFactory::GetDecoder(const TrackInfo& aTrackInfo, if (mFFmpegFailedToLoad) { aDiagnostics->SetFFmpegFailedToLoad(); } + if (mGMPPDMFailedToStartup) { + aDiagnostics->SetGMPPDMFailedToStartup(); + } } RefPtr<PlatformDecoderModule> pdm; diff --git a/dom/media/platforms/PDMFactory.h b/dom/media/platforms/PDMFactory.h index 129876ac2..a42436477 100644 --- a/dom/media/platforms/PDMFactory.h +++ b/dom/media/platforms/PDMFactory.h @@ -61,6 +61,7 @@ private: bool mWMFFailedToLoad = false; bool mFFmpegFailedToLoad = false; + bool mGMPPDMFailedToStartup = false; void EnsureInit() const; template<class T> friend class StaticAutoPtr; diff --git a/dom/media/platforms/PlatformDecoderModule.h b/dom/media/platforms/PlatformDecoderModule.h index e2ef1f752..62855335f 100644 --- a/dom/media/platforms/PlatformDecoderModule.h +++ b/dom/media/platforms/PlatformDecoderModule.h @@ -16,6 +16,7 @@ #include "mozilla/layers/KnowsCompositor.h" #include "nsTArray.h" #include "mozilla/RefPtr.h" +#include "GMPService.h" #include <queue> #include "MediaResult.h" @@ -37,6 +38,7 @@ class RemoteDecoderModule; class MediaDataDecoder; class MediaDataDecoderCallback; class TaskQueue; +class CDMProxy; static LazyLogModule sPDMLog("PlatformDecoderModule"); @@ -79,6 +81,7 @@ struct MOZ_STACK_CLASS CreateDecoderParams final { layers::ImageContainer* mImageContainer = nullptr; MediaResult* mError = nullptr; RefPtr<layers::KnowsCompositor> mKnowsCompositor; + RefPtr<GMPCrashHelper> mCrashHelper; bool mUseBlankDecoder = false; private: @@ -87,6 +90,7 @@ private: void Set(DecoderDoctorDiagnostics* aDiagnostics) { mDiagnostics = aDiagnostics; } void Set(layers::ImageContainer* aImageContainer) { mImageContainer = aImageContainer; } void Set(MediaResult* aError) { mError = aError; } + void Set(GMPCrashHelper* aCrashHelper) { mCrashHelper = aCrashHelper; } void Set(bool aUseBlankDecoder) { mUseBlankDecoder = aUseBlankDecoder; } void Set(layers::KnowsCompositor* aKnowsCompositor) { mKnowsCompositor = aKnowsCompositor; } template <typename T1, typename T2, typename... Ts> diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp new file mode 100644 index 000000000..d863d44d4 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPAudioDecoder.h" +#include "nsServiceManagerUtils.h" +#include "MediaInfo.h" +#include "GMPDecoderModule.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +#if defined(DEBUG) +bool IsOnGMPThread() +{ + nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + + nsCOMPtr<nsIThread> gmpThread; + nsresult rv = mps->GetThread(getter_AddRefs(gmpThread)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread); + return NS_GetCurrentThread() == gmpThread; +} +#endif + +void +AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (aRate == 0 || aChannels == 0) { + mCallback->Error(MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL( + "Invalid rate or num channels returned on GMP audio samples"))); + return; + } + + size_t numFrames = aPCM.Length() / aChannels; + MOZ_ASSERT((aPCM.Length() % aChannels) == 0); + AlignedAudioBuffer audioData(aPCM.Length()); + if (!audioData) { + mCallback->Error( + MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Unable to allocate audio buffer"))); + return; + } + + for (size_t i = 0; i < aPCM.Length(); ++i) { + audioData[i] = AudioSampleToFloat(aPCM[i]); + } + + if (mMustRecaptureAudioPosition) { + mAudioFrameSum = 0; + auto timestamp = UsecsToFrames(aTimeStamp, aRate); + if (!timestamp.isValid()) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid timestamp"))); + return; + } + mAudioFrameOffset = timestamp.value(); + mMustRecaptureAudioPosition = false; + } + + auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate); + if (!timestamp.isValid()) { + mCallback->Error( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid timestamp on audio samples"))); + return; + } + mAudioFrameSum += numFrames; + + auto duration = FramesToUsecs(numFrames, aRate); + if (!duration.isValid()) { + mCallback->Error( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid duration on audio samples"))); + return; + } + + RefPtr<AudioData> audio(new AudioData(mLastStreamOffset, + timestamp.value(), + duration.value(), + numFrames, + Move(audioData), + aChannels, + aRate)); + +#ifdef LOG_SAMPLE_DECODE + LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u", + timestamp, duration, currentLength); +#endif + + mCallback->Output(audio); +} + +void +AudioCallbackAdapter::InputDataExhausted() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->InputExhausted(); +} + +void +AudioCallbackAdapter::DrainComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->DrainComplete(); +} + +void +AudioCallbackAdapter::ResetComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mMustRecaptureAudioPosition = true; + mCallback->FlushComplete(); +} + +void +AudioCallbackAdapter::Error(GMPErr aErr) +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->Error(MediaResult(aErr == GMPDecodeErr + ? NS_ERROR_DOM_MEDIA_DECODE_ERR + : NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("GMPErr:%x", aErr))); +} + +void +AudioCallbackAdapter::Terminated() +{ + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Audio GMP decoder terminated."))); +} + +GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mCrashHelper(aParams.mCrashHelper) +{} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mAdapter(aParams.mAdapter) + , mCrashHelper(aParams.mCrashHelper) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new AudioCallbackAdapter(mCallback); + } +} + +void +GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + aTags.AppendElement(NS_LITERAL_CSTRING("aac")); + const Maybe<nsCString> gmp( + GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm"))); + if (gmp.isSome()) { + aTags.AppendElement(gmp.value()); + } +} + +nsCString +GMPAudioDecoder::GetNodeId() +{ + return SHARED_GMP_DECODING_NODE_ID; +} + +void +GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!aGMP) { + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + if (mInitPromise.IsEmpty()) { + // GMP must have been shutdown while we were waiting for Init operation + // to complete. + aGMP->Close(); + return; + } + nsTArray<uint8_t> codecSpecific; + codecSpecific.AppendElements(mConfig.mCodecSpecificConfig->Elements(), + mConfig.mCodecSpecificConfig->Length()); + + nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC, + mConfig.mChannels, + mConfig.mBitDepth, + mConfig.mRate, + codecSpecific, + mAdapter); + if (NS_FAILED(rv)) { + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + + mGMP = aGMP; + mInitPromise.Resolve(TrackInfo::kAudioTrack, __func__); +} + +RefPtr<MediaDataDecoder::InitPromise> +GMPAudioDecoder::Init() +{ + MOZ_ASSERT(IsOnGMPThread()); + + mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mMPS); + + RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__)); + + nsTArray<nsCString> tags; + InitTags(tags); + UniquePtr<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this)); + if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) { + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return promise; +} + +void +GMPAudioDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + RefPtr<MediaRawData> sample(aSample); + if (!mGMP) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("mGMP not initialized"))); + return; + } + + mAdapter->SetLastStreamOffset(sample->mOffset); + + gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate); + nsresult rv = mGMP->Decode(samples); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(rv, __func__)); + } +} + +void +GMPAudioDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } +} + +void +GMPAudioDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } +} + +void +GMPAudioDecoder::Shutdown() +{ + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + if (!mGMP) { + return; + } + // Note this unblocks flush and drain operations waiting for callbacks. + mGMP->Close(); + mGMP = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h new file mode 100644 index 000000000..90e3ebdb6 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GMPAudioDecoder_h_) +#define GMPAudioDecoder_h_ + +#include "GMPAudioDecoderProxy.h" +#include "MediaDataDecoderProxy.h" +#include "PlatformDecoderModule.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy { +public: + explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mAudioFrameSum(0) + , mAudioFrameOffset(0) + , mMustRecaptureAudioPosition(true) + {} + + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + + // GMPAudioDecoderCallbackProxy + void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aErr) override; + void Terminated() override; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + int64_t mAudioFrameSum; + int64_t mAudioFrameOffset; + bool mMustRecaptureAudioPosition; +}; + +struct GMPAudioDecoderParams { + explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams); + GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter); + + const AudioInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + AudioCallbackAdapter* mAdapter; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +class GMPAudioDecoder : public MediaDataDecoder { +public: + explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "GMP audio decoder"; + } + +protected: + virtual void InitTags(nsTArray<nsCString>& aTags); + virtual nsCString GetNodeId(); + +private: + + class GMPInitDoneCallback : public GetGMPAudioDecoderCallback + { + public: + explicit GMPInitDoneCallback(GMPAudioDecoder* aDecoder) + : mDecoder(aDecoder) + { + } + + void Done(GMPAudioDecoderProxy* aGMP) override + { + mDecoder->GMPInitDone(aGMP); + } + + private: + RefPtr<GMPAudioDecoder> mDecoder; + }; + void GMPInitDone(GMPAudioDecoderProxy* aGMP); + + const AudioInfo mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr<mozIGeckoMediaPluginService> mMPS; + GMPAudioDecoderProxy* mGMP; + nsAutoPtr<AudioCallbackAdapter> mAdapter; + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} // namespace mozilla + +#endif // GMPAudioDecoder_h_ diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp new file mode 100644 index 000000000..50a5097ac --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPDecoderModule.h" +#include "DecoderDoctorDiagnostics.h" +#include "GMPAudioDecoder.h" +#include "GMPVideoDecoder.h" +#include "GMPUtils.h" +#include "MediaDataDecoderProxy.h" +#include "MediaPrefs.h" +#include "VideoUtils.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/StaticMutex.h" +#include "gmp-audio-decode.h" +#include "gmp-video-decode.h" +#include "MP4Decoder.h" +#include "VPXDecoder.h" +#ifdef XP_WIN +#include "WMFDecoderModule.h" +#endif + +namespace mozilla { + +GMPDecoderModule::GMPDecoderModule() +{ +} + +GMPDecoderModule::~GMPDecoderModule() +{ +} + +static already_AddRefed<MediaDataDecoderProxy> +CreateDecoderWrapper(MediaDataDecoderCallback* aCallback) +{ + RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); + if (!s) { + return nullptr; + } + RefPtr<AbstractThread> thread(s->GetAbstractGMPThread()); + if (!thread) { + return nullptr; + } + RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback)); + return decoder.forget(); +} + +already_AddRefed<MediaDataDecoder> +GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) +{ + if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) && + !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) && + !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) { + return nullptr; + } + + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); + if (preferredGMP.isSome()) { + aParams.mDiagnostics->SetGMP(preferredGMP.value()); + } + } + + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPVideoDecoder(params)); + return wrapper.forget(); +} + +already_AddRefed<MediaDataDecoder> +GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) +{ + if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) { + return nullptr; + } + + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); + if (preferredGMP.isSome()) { + aParams.mDiagnostics->SetGMP(preferredGMP.value()); + } + } + + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPAudioDecoder(params)); + return wrapper.forget(); +} + +PlatformDecoderModule::ConversionRequired +GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const +{ + // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format. + if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) { + return ConversionRequired::kNeedAVCC; + } else { + return ConversionRequired::kNeedNone; + } +} + +/* static */ +const Maybe<nsCString> +GMPDecoderModule::PreferredGMP(const nsACString& aMimeType) +{ + Maybe<nsCString> rv; + if (aMimeType.EqualsLiteral("audio/mp4a-latm")) { + switch (MediaPrefs::GMPAACPreferred()) { + case 1: rv.emplace(kEMEKeySystemClearkey); break; + default: break; + } + } + + if (MP4Decoder::IsH264(aMimeType)) { + switch (MediaPrefs::GMPH264Preferred()) { + case 1: rv.emplace(kEMEKeySystemClearkey); break; + default: break; + } + } + + return rv; +} + +/* static */ +bool +GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType, + const Maybe<nsCString>& aGMP) +{ + if (aGMP.isNothing()) { + return false; + } + + if (MP4Decoder::IsH264(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("h264"), aGMP.value()}); + } + + if (VPXDecoder::IsVP9(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("vp9"), aGMP.value()}); + } + + if (VPXDecoder::IsVP8(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("vp8"), aGMP.value()}); + } + + if (MP4Decoder::IsAAC(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER), + { NS_LITERAL_CSTRING("aac"), aGMP.value()}); + } + + return false; +} + +bool +GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const +{ + const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType); + bool rv = SupportsMimeType(aMimeType, preferredGMP); + if (rv && aDiagnostics && preferredGMP.isSome()) { + aDiagnostics->SetGMP(preferredGMP.value()); + } + return rv; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h new file mode 100644 index 000000000..b501ecb54 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GMPDecoderModule_h_) +#define GMPDecoderModule_h_ + +#include "PlatformDecoderModule.h" +#include "mozilla/Maybe.h" + +// The special NodeId we use when doing unencrypted decoding using the GMP's +// decoder. This ensures that each GMP MediaDataDecoder we create doesn't +// require spinning up a new process, but instead we run all instances of +// GMP decoders in the one process, to reduce overhead. +// +// Note: GMP storage is isolated by NodeId, and non persistent for this +// special NodeId, and the only way a GMP can communicate with the outside +// world is through the EME GMP APIs, and we never run EME with this NodeID +// (because NodeIds are random strings which can't contain the '-' character), +// so there's no way a malicious GMP can harvest, store, and then report any +// privacy sensitive data about what users are watching. +#define SHARED_GMP_DECODING_NODE_ID NS_LITERAL_CSTRING("gmp-shared-decoding") + +namespace mozilla { + +class GMPDecoderModule : public PlatformDecoderModule { +public: + GMPDecoderModule(); + + virtual ~GMPDecoderModule(); + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override; + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override; + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override; + + bool + SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType); + + static bool SupportsMimeType(const nsACString& aMimeType, + const Maybe<nsCString>& aGMP); +}; + +} // namespace mozilla + +#endif // GMPDecoderModule_h_ diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp new file mode 100644 index 000000000..26d029da0 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoDecoder.h" +#include "GMPVideoHost.h" +#include "mozilla/EndianUtils.h" +#include "prsystem.h" +#include "MediaData.h" +#include "GMPDecoderModule.h" +#include "MP4Decoder.h" +#include "VPXDecoder.h" + +namespace mozilla { + +#if defined(DEBUG) +extern bool IsOnGMPThread(); +#endif + +void +VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame) +{ + GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame); + + MOZ_ASSERT(IsOnGMPThread()); + + VideoData::YCbCrBuffer b; + for (int i = 0; i < kGMPNumOfPlanes; ++i) { + b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i)); + b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i)); + if (i == kGMPYPlane) { + b.mPlanes[i].mWidth = decodedFrame->Width(); + b.mPlanes[i].mHeight = decodedFrame->Height(); + } else { + b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2; + b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2; + } + b.mPlanes[i].mOffset = 0; + b.mPlanes[i].mSkip = 0; + } + + gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height()); + RefPtr<VideoData> v = + VideoData::CreateAndCopyData(mVideoInfo, + mImageContainer, + mLastStreamOffset, + decodedFrame->Timestamp(), + decodedFrame->Duration(), + b, + false, + -1, + pictureRegion); + if (v) { + mCallback->Output(v); + } else { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + } +} + +void +VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(IsOnGMPThread()); +} + +void +VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(IsOnGMPThread()); +} + +void +VideoCallbackAdapter::InputDataExhausted() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->InputExhausted(); +} + +void +VideoCallbackAdapter::DrainComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->DrainComplete(); +} + +void +VideoCallbackAdapter::ResetComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->FlushComplete(); +} + +void +VideoCallbackAdapter::Error(GMPErr aErr) +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->Error(MediaResult(aErr == GMPDecodeErr + ? NS_ERROR_DOM_MEDIA_DECODE_ERR + : NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("GMPErr:%x", aErr))); +} + +void +VideoCallbackAdapter::Terminated() +{ + // Note that this *may* be called from the proxy thread also. + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Video GMP decoder terminated."))); +} + +GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.VideoConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mImageContainer(aParams.mImageContainer) + , mLayersBackend(aParams.GetLayersBackend()) + , mCrashHelper(aParams.mCrashHelper) +{} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mHost(nullptr) + , mAdapter(aParams.mAdapter) + , mConvertNALUnitLengths(false) + , mCrashHelper(aParams.mCrashHelper) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new VideoCallbackAdapter(mCallback, + VideoInfo(mConfig.mDisplay.width, + mConfig.mDisplay.height), + aParams.mImageContainer); + } +} + +void +GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("h264")); + const Maybe<nsCString> gmp( + GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc"))); + if (gmp.isSome()) { + aTags.AppendElement(gmp.value()); + } + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp8")); + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp9")); + } +} + +nsCString +GMPVideoDecoder::GetNodeId() +{ + return SHARED_GMP_DECODING_NODE_ID; +} + +GMPUniquePtr<GMPVideoEncodedFrame> +GMPVideoDecoder::CreateFrame(MediaRawData* aSample) +{ + GMPVideoFrame* ftmp = nullptr; + GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp); + if (GMP_FAILED(err)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Host::CreateFrame:%x", err))); + return nullptr; + } + + GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp)); + err = frame->CreateEmptyFrame(aSample->Size()); + if (GMP_FAILED(err)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("GMPVideoEncodedFrame::CreateEmptyFrame:%x", err))); + return nullptr; + } + + memcpy(frame->Buffer(), aSample->Data(), frame->Size()); + + // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to + // suit the GMP API. + if (mConvertNALUnitLengths) { + const int kNALLengthSize = 4; + uint8_t* buf = frame->Buffer(); + while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) { + uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize; + *reinterpret_cast<uint32_t *>(buf) = length; + buf += length; + } + } + + frame->SetBufferType(GMP_BufferLength32); + + frame->SetEncodedWidth(mConfig.mDisplay.width); + frame->SetEncodedHeight(mConfig.mDisplay.height); + frame->SetTimeStamp(aSample->mTime); + frame->SetCompleteFrame(true); + frame->SetDuration(aSample->mDuration); + frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame); + + return frame; +} + +const VideoInfo& +GMPVideoDecoder::GetConfig() const +{ + return mConfig; +} + +void +GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!aGMP) { + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + MOZ_ASSERT(aHost); + + if (mInitPromise.IsEmpty()) { + // GMP must have been shutdown while we were waiting for Init operation + // to complete. + aGMP->Close(); + return; + } + + GMPVideoCodec codec; + memset(&codec, 0, sizeof(codec)); + + codec.mGMPApiVersion = kGMPVersion33; + nsTArray<uint8_t> codecSpecific; + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecH264; + codecSpecific.AppendElement(0); // mPacketizationMode. + codecSpecific.AppendElements(mConfig.mExtraData->Elements(), + mConfig.mExtraData->Length()); + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecVP8; + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecVP9; + } else { + // Unrecognized mime type + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + codec.mWidth = mConfig.mImage.width; + codec.mHeight = mConfig.mImage.height; + + nsresult rv = aGMP->InitDecode(codec, + codecSpecific, + mAdapter, + PR_GetNumberOfProcessors()); + if (NS_FAILED(rv)) { + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + + mGMP = aGMP; + mHost = aHost; + + // GMP implementations have interpreted the meaning of GMP_BufferLength32 + // differently. The OpenH264 GMP expects GMP_BufferLength32 to behave as + // specified in the GMP API, where each buffer is prefixed by a 32-bit + // host-endian buffer length that includes the size of the buffer length + // field. Other existing GMPs currently expect GMP_BufferLength32 (when + // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to + // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian + // and do not include the length of the buffer length field. + mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264"); + + mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__); +} + +RefPtr<MediaDataDecoder::InitPromise> +GMPVideoDecoder::Init() +{ + MOZ_ASSERT(IsOnGMPThread()); + + mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mMPS); + + RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__)); + + nsTArray<nsCString> tags; + InitTags(tags); + UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this)); + if (NS_FAILED(mMPS->GetDecryptingGMPVideoDecoder(mCrashHelper, + &tags, + GetNodeId(), + Move(callback), + DecryptorId()))) { + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return promise; +} + +void +GMPVideoDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + RefPtr<MediaRawData> sample(aSample); + if (!mGMP) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("mGMP not initialized"))); + return; + } + + mAdapter->SetLastStreamOffset(sample->mOffset); + + GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample); + if (!frame) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("CreateFrame returned null"))); + return; + } + nsTArray<uint8_t> info; // No codec specific per-frame info to pass. + nsresult rv = mGMP->Decode(Move(frame), false, info, 0); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("mGMP->Decode:%x", rv))); + } +} + +void +GMPVideoDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } +} + +void +GMPVideoDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } +} + +void +GMPVideoDecoder::Shutdown() +{ + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + // Note that this *may* be called from the proxy thread also. + if (!mGMP) { + return; + } + // Note this unblocks flush and drain operations waiting for callbacks. + mGMP->Close(); + mGMP = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h new file mode 100644 index 000000000..900ef4553 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GMPVideoDecoder_h_) +#define GMPVideoDecoder_h_ + +#include "GMPVideoDecoderProxy.h" +#include "ImageContainer.h" +#include "MediaDataDecoderProxy.h" +#include "PlatformDecoderModule.h" +#include "mozIGeckoMediaPluginService.h" +#include "MediaInfo.h" + +namespace mozilla { + +class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy { +public: + VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback, + VideoInfo aVideoInfo, + layers::ImageContainer* aImageContainer) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mVideoInfo(aVideoInfo) + , mImageContainer(aImageContainer) + {} + + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + + // GMPVideoDecoderCallbackProxy + void Decoded(GMPVideoi420Frame* aDecodedFrame) override; + void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override; + void ReceivedDecodedFrame(const uint64_t aPictureId) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aErr) override; + void Terminated() override; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + VideoInfo mVideoInfo; + RefPtr<layers::ImageContainer> mImageContainer; +}; + +struct GMPVideoDecoderParams { + explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams); + GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter); + + const VideoInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + VideoCallbackAdapter* mAdapter; + layers::ImageContainer* mImageContainer; + layers::LayersBackend mLayersBackend; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +class GMPVideoDecoder : public MediaDataDecoder { +public: + explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "GMP video decoder"; + } + +protected: + virtual void InitTags(nsTArray<nsCString>& aTags); + virtual nsCString GetNodeId(); + virtual uint32_t DecryptorId() const { return 0; } + virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample); + virtual const VideoInfo& GetConfig() const; + +private: + + class GMPInitDoneCallback : public GetGMPVideoDecoderCallback + { + public: + explicit GMPInitDoneCallback(GMPVideoDecoder* aDecoder) + : mDecoder(aDecoder) + { + } + + void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override + { + mDecoder->GMPInitDone(aGMP, aHost); + } + + private: + RefPtr<GMPVideoDecoder> mDecoder; + }; + void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost); + + const VideoInfo mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr<mozIGeckoMediaPluginService> mMPS; + GMPVideoDecoderProxy* mGMP; + GMPVideoHost* mHost; + nsAutoPtr<VideoCallbackAdapter> mAdapter; + bool mConvertNALUnitLengths; + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} // namespace mozilla + +#endif // GMPVideoDecoder_h_ diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp new file mode 100644 index 000000000..5a196f8e6 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MediaDataDecoderProxy.h" +#include "MediaData.h" + +namespace mozilla { + +void +MediaDataDecoderCallbackProxy::Error(const MediaResult& aError) +{ + mProxyCallback->Error(aError); +} + +void +MediaDataDecoderCallbackProxy::FlushComplete() +{ + mProxyDecoder->FlushComplete(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaDataDecoderProxy::InternalInit() +{ + return mProxyDecoder->Init(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaDataDecoderProxy::Init() +{ + MOZ_ASSERT(!mIsShutdown); + + return InvokeAsync(mProxyThread, this, __func__, + &MediaDataDecoderProxy::InternalInit); +} + +void +MediaDataDecoderProxy::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + nsCOMPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample)); + mProxyThread->Dispatch(task.forget()); +} + +void +MediaDataDecoderProxy::Flush() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mFlushComplete.Set(false); + + mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush)); + + mFlushComplete.WaitUntil(true); +} + +void +MediaDataDecoderProxy::Drain() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain)); +} + +void +MediaDataDecoderProxy::Shutdown() +{ + // Note that this *may* be called from the proxy thread also. + MOZ_ASSERT(!mIsShutdown); +#if defined(DEBUG) + mIsShutdown = true; +#endif + mProxyThread->AsXPCOMThread()->Dispatch(NewRunnableMethod(mProxyDecoder, + &MediaDataDecoder::Shutdown), + NS_DISPATCH_SYNC); +} + +void +MediaDataDecoderProxy::FlushComplete() +{ + mFlushComplete.Set(true); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h new file mode 100644 index 000000000..735b6126e --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MediaDataDecoderProxy_h_) +#define MediaDataDecoderProxy_h_ + +#include "PlatformDecoderModule.h" +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" +#include "nscore.h" +#include "GMPService.h" + +namespace mozilla { + +class InputTask : public Runnable { +public: + InputTask(MediaDataDecoder* aDecoder, + MediaRawData* aSample) + : mDecoder(aDecoder) + , mSample(aSample) + {} + + NS_IMETHOD Run() override { + mDecoder->Input(mSample); + return NS_OK; + } + +private: + RefPtr<MediaDataDecoder> mDecoder; + RefPtr<MediaRawData> mSample; +}; + +template<typename T> +class Condition { +public: + explicit Condition(T aValue) + : mMonitor("Condition") + , mCondition(aValue) + {} + + void Set(T aValue) { + MonitorAutoLock mon(mMonitor); + mCondition = aValue; + mon.NotifyAll(); + } + + void WaitUntil(T aValue) { + MonitorAutoLock mon(mMonitor); + while (mCondition != aValue) { + mon.Wait(); + } + } + +private: + Monitor mMonitor; + T mCondition; +}; + +class MediaDataDecoderProxy; + +class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback { +public: + MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder, + MediaDataDecoderCallback* aCallback) + : mProxyDecoder(aProxyDecoder) + , mProxyCallback(aCallback) + { + } + + void Output(MediaData* aData) override { + mProxyCallback->Output(aData); + } + + void Error(const MediaResult& aError) override; + + void InputExhausted() override { + mProxyCallback->InputExhausted(); + } + + void DrainComplete() override { + mProxyCallback->DrainComplete(); + } + + void ReleaseMediaResources() override { + mProxyCallback->ReleaseMediaResources(); + } + + void FlushComplete(); + + bool OnReaderTaskQueue() override + { + return mProxyCallback->OnReaderTaskQueue(); + } + + void WaitingForKey() override + { + mProxyCallback->WaitingForKey(); + } + +private: + MediaDataDecoderProxy* mProxyDecoder; + MediaDataDecoderCallback* mProxyCallback; +}; + +class MediaDataDecoderProxy : public MediaDataDecoder { +public: + MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread, + MediaDataDecoderCallback* aCallback) + : mProxyThread(aProxyThread) + , mProxyCallback(this, aCallback) + , mFlushComplete(false) +#if defined(DEBUG) + , mIsShutdown(false) +#endif + { + } + + // Ideally, this would return a regular MediaDataDecoderCallback pointer + // to retain the clean abstraction, but until MediaDataDecoderCallback + // supports the FlushComplete interface, this will have to do. When MDDC + // supports FlushComplete, this, the GMP*Decoders, and the + // *CallbackAdapters can be reverted to accepting a regular + // MediaDataDecoderCallback pointer. + MediaDataDecoderCallbackProxy* Callback() + { + return &mProxyCallback; + } + + void SetProxyTarget(MediaDataDecoder* aProxyDecoder) + { + MOZ_ASSERT(aProxyDecoder); + mProxyDecoder = aProxyDecoder; + } + + // These are called from the decoder thread pool. + // Init and Shutdown run synchronously on the proxy thread, all others are + // asynchronously and responded to via the MediaDataDecoderCallback. + // Note: the nsresults returned by the proxied decoder are lost. + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + + const char* GetDescriptionName() const override + { + return "GMP proxy data decoder"; + } + + // Called by MediaDataDecoderCallbackProxy. + void FlushComplete(); + +private: + RefPtr<InitPromise> InternalInit(); + +#ifdef DEBUG + bool IsOnProxyThread() { + return mProxyThread && mProxyThread->IsCurrentThreadIn(); + } +#endif + + friend class InputTask; + friend class InitTask; + + RefPtr<MediaDataDecoder> mProxyDecoder; + RefPtr<AbstractThread> mProxyThread; + + MediaDataDecoderCallbackProxy mProxyCallback; + + Condition<bool> mFlushComplete; +#if defined(DEBUG) + bool mIsShutdown; +#endif +}; + +} // namespace mozilla + +#endif // MediaDataDecoderProxy_h_ diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build new file mode 100644 index 000000000..64e258620 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'GMPAudioDecoder.h', + 'GMPDecoderModule.h', + 'GMPVideoDecoder.h', + 'MediaDataDecoderProxy.h', +] + +SOURCES += [ + 'GMPAudioDecoder.cpp', + 'GMPDecoderModule.cpp', + 'GMPVideoDecoder.cpp', + 'MediaDataDecoderProxy.cpp', +] + +# GMPVideoEncodedFrameImpl.h needs IPC +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/dom/media/platforms/moz.build b/dom/media/platforms/moz.build index b570eb7d9..5da113e77 100644 --- a/dom/media/platforms/moz.build +++ b/dom/media/platforms/moz.build @@ -29,6 +29,7 @@ SOURCES += [ ] DIRS += [ + 'agnostic/gmp', 'omx' ] diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp index 840348315..8cf22c241 100644 --- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp @@ -29,6 +29,7 @@ #include "mozilla/WindowsVersion.h" #include "nsPrintfCString.h" #include "nsIFile.h" +#include "GMPUtils.h" // For SplitAt. TODO: Move SplitAt to a central place. #include "MP4Decoder.h" #include "VPXDecoder.h" #include "mozilla/SyncRunnable.h" @@ -70,21 +71,6 @@ const CLSID CLSID_WebmMfVpxDec = namespace mozilla { -// Utility function only used here. -// XXXMC: Perhaps make this available globally? -void -SplitAt(const char* aDelims, - const nsACString& aInput, - nsTArray<nsCString>& aOutTokens) -{ - nsAutoCString str(aInput); - char* end = str.BeginWriting(); - const char* start = nullptr; - while (!!(start = NS_strtok(aDelims, &end))) { - aOutTokens.AppendElement(nsCString(start)); - } -} - LayersBackend GetCompositorBackendType(layers::KnowsCompositor* aKnowsCompositor) { diff --git a/dom/media/platforms/wrappers/H264Converter.cpp b/dom/media/platforms/wrappers/H264Converter.cpp index 3d9d2e62f..cca03fceb 100644 --- a/dom/media/platforms/wrappers/H264Converter.cpp +++ b/dom/media/platforms/wrappers/H264Converter.cpp @@ -26,6 +26,7 @@ H264Converter::H264Converter(PlatformDecoderModule* aPDM, , mTaskQueue(aParams.mTaskQueue) , mCallback(aParams.mCallback) , mDecoder(nullptr) + , mGMPCrashHelper(aParams.mCrashHelper) , mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig) == PlatformDecoderModule::ConversionRequired::kNeedAVCC) , mLastError(NS_OK) @@ -201,7 +202,8 @@ H264Converter::CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics) mCallback, aDiagnostics, mImageContainer, - mKnowsCompositor + mKnowsCompositor, + mGMPCrashHelper }); if (!mDecoder) { diff --git a/dom/media/platforms/wrappers/H264Converter.h b/dom/media/platforms/wrappers/H264Converter.h index 627a56731..6905b1c74 100644 --- a/dom/media/platforms/wrappers/H264Converter.h +++ b/dom/media/platforms/wrappers/H264Converter.h @@ -63,6 +63,7 @@ private: MediaDataDecoderCallback* mCallback; RefPtr<MediaDataDecoder> mDecoder; MozPromiseRequestHolder<InitPromise> mInitPromiseRequest; + RefPtr<GMPCrashHelper> mGMPCrashHelper; bool mNeedAVCC; nsresult mLastError; bool mNeedKeyframe = true; diff --git a/dom/media/webaudio/BufferDecoder.cpp b/dom/media/webaudio/BufferDecoder.cpp index de0a79ffd..ddd9e7d1b 100644 --- a/dom/media/webaudio/BufferDecoder.cpp +++ b/dom/media/webaudio/BufferDecoder.cpp @@ -7,13 +7,15 @@ #include "nsISupports.h" #include "MediaResource.h" +#include "GMPService.h" namespace mozilla { NS_IMPL_ISUPPORTS0(BufferDecoder) -BufferDecoder::BufferDecoder(MediaResource* aResource) +BufferDecoder::BufferDecoder(MediaResource* aResource, GMPCrashHelper* aCrashHelper) : mResource(aResource) + , mCrashHelper(aCrashHelper) { MOZ_ASSERT(NS_IsMainThread()); MOZ_COUNT_CTOR(BufferDecoder); @@ -65,4 +67,10 @@ BufferDecoder::GetOwner() const return nullptr; } +already_AddRefed<GMPCrashHelper> +BufferDecoder::GetCrashHelper() +{ + return do_AddRef(mCrashHelper); +} + } // namespace mozilla diff --git a/dom/media/webaudio/BufferDecoder.h b/dom/media/webaudio/BufferDecoder.h index 5b75d6405..2c6c49454 100644 --- a/dom/media/webaudio/BufferDecoder.h +++ b/dom/media/webaudio/BufferDecoder.h @@ -23,7 +23,7 @@ class BufferDecoder final : public AbstractMediaDecoder public: // This class holds a weak pointer to MediaResource. It's the responsibility // of the caller to manage the memory of the MediaResource object. - explicit BufferDecoder(MediaResource* aResource); + explicit BufferDecoder(MediaResource* aResource, GMPCrashHelper* aCrashHelper); NS_DECL_THREADSAFE_ISUPPORTS @@ -39,10 +39,13 @@ public: MediaDecoderOwner* GetOwner() const final override; + already_AddRefed<GMPCrashHelper> GetCrashHelper() override; + private: virtual ~BufferDecoder(); RefPtr<TaskQueue> mTaskQueueIdentity; RefPtr<MediaResource> mResource; + RefPtr<GMPCrashHelper> mCrashHelper; }; } // namespace mozilla diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp index c57c9b9b5..f590d2f68 100644 --- a/dom/media/webaudio/MediaBufferDecoder.cpp +++ b/dom/media/webaudio/MediaBufferDecoder.cpp @@ -25,6 +25,7 @@ #include "WebAudioUtils.h" #include "mozilla/dom/Promise.h" #include "nsPrintfCString.h" +#include "GMPService.h" namespace mozilla { @@ -180,6 +181,24 @@ MediaDecodeTask::Run() return NS_OK; } +class BufferDecoderGMPCrashHelper : public GMPCrashHelper +{ +public: + explicit BufferDecoderGMPCrashHelper(nsPIDOMWindowInner* aParent) + : mParent(do_GetWeakReference(aParent)) + { + MOZ_ASSERT(NS_IsMainThread()); + } + already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override + { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mParent); + return window.forget(); + } +private: + nsWeakPtr mParent; +}; + bool MediaDecodeTask::CreateReader() { @@ -197,7 +216,8 @@ MediaDecodeTask::CreateReader() mLength, principal, mContentType); MOZ_ASSERT(!mBufferDecoder); - mBufferDecoder = new BufferDecoder(resource); + mBufferDecoder = new BufferDecoder(resource, + new BufferDecoderGMPCrashHelper(mDecodeJob.mContext->GetParentObject())); // If you change this list to add support for new decoders, please consider // updating HTMLMediaElement::CreateDecoder as well. diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build index aac7985ac..5aba958ab 100644 --- a/dom/media/webaudio/moz.build +++ b/dom/media/webaudio/moz.build @@ -3,6 +3,9 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +with Files('*'): + BUG_COMPONENT = ('Core', 'Web Audio') + DIRS += ['blink'] TEST_DIRS += ['gtest'] |