summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/media/gmp
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/media/gmp-plugin-openh264/fakeopenh264.info4
-rw-r--r--dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp406
-rw-r--r--dom/media/gmp-plugin-openh264/moz.build25
-rw-r--r--dom/media/gmp/CDMStorageIdProvider.cpp68
-rw-r--r--dom/media/gmp/CDMStorageIdProvider.h41
-rw-r--r--dom/media/gmp/ChromiumCDMAdapter.cpp307
-rw-r--r--dom/media/gmp/ChromiumCDMAdapter.h81
-rw-r--r--dom/media/gmp/ChromiumCDMCallback.h56
-rw-r--r--dom/media/gmp/ChromiumCDMCallbackProxy.cpp159
-rw-r--r--dom/media/gmp/ChromiumCDMCallbackProxy.h62
-rw-r--r--dom/media/gmp/ChromiumCDMChild.cpp866
-rw-r--r--dom/media/gmp/ChromiumCDMChild.h148
-rw-r--r--dom/media/gmp/ChromiumCDMParent.cpp1319
-rw-r--r--dom/media/gmp/ChromiumCDMParent.h232
-rw-r--r--dom/media/gmp/ChromiumCDMProxy.cpp636
-rw-r--r--dom/media/gmp/ChromiumCDMProxy.h136
-rw-r--r--dom/media/gmp/DecryptJob.cpp46
-rw-r--r--dom/media/gmp/DecryptJob.h36
-rw-r--r--dom/media/gmp/GMPCallbackBase.h24
-rw-r--r--dom/media/gmp/GMPChild.cpp657
-rw-r--r--dom/media/gmp/GMPChild.h106
-rw-r--r--dom/media/gmp/GMPContentChild.cpp131
-rw-r--r--dom/media/gmp/GMPContentChild.h66
-rw-r--r--dom/media/gmp/GMPContentParent.cpp204
-rw-r--r--dom/media/gmp/GMPContentParent.h94
-rw-r--r--dom/media/gmp/GMPCrashHelper.h37
-rw-r--r--dom/media/gmp/GMPCrashHelperHolder.cpp31
-rw-r--r--dom/media/gmp/GMPCrashHelperHolder.h63
-rw-r--r--dom/media/gmp/GMPDiskStorage.cpp454
-rw-r--r--dom/media/gmp/GMPLoader.cpp203
-rw-r--r--dom/media/gmp/GMPLoader.h80
-rw-r--r--dom/media/gmp/GMPLog.h62
-rw-r--r--dom/media/gmp/GMPMemoryStorage.cpp75
-rw-r--r--dom/media/gmp/GMPMessageUtils.h190
-rw-r--r--dom/media/gmp/GMPNativeTypes.h17
-rw-r--r--dom/media/gmp/GMPParent.cpp1209
-rw-r--r--dom/media/gmp/GMPParent.h244
-rw-r--r--dom/media/gmp/GMPPlatform.cpp281
-rw-r--r--dom/media/gmp/GMPPlatform.h32
-rw-r--r--dom/media/gmp/GMPProcessChild.cpp45
-rw-r--r--dom/media/gmp/GMPProcessChild.h33
-rw-r--r--dom/media/gmp/GMPProcessParent.cpp297
-rw-r--r--dom/media/gmp/GMPProcessParent.h103
-rw-r--r--dom/media/gmp/GMPSanitizedExports.h22
-rw-r--r--dom/media/gmp/GMPService.cpp570
-rw-r--r--dom/media/gmp/GMPService.h130
-rw-r--r--dom/media/gmp/GMPServiceChild.cpp586
-rw-r--r--dom/media/gmp/GMPServiceChild.h164
-rw-r--r--dom/media/gmp/GMPServiceParent.cpp1943
-rw-r--r--dom/media/gmp/GMPServiceParent.h281
-rw-r--r--dom/media/gmp/GMPSharedMemManager.cpp89
-rw-r--r--dom/media/gmp/GMPSharedMemManager.h78
-rw-r--r--dom/media/gmp/GMPStorage.h39
-rw-r--r--dom/media/gmp/GMPStorageChild.cpp244
-rw-r--r--dom/media/gmp/GMPStorageChild.h94
-rw-r--r--dom/media/gmp/GMPStorageParent.cpp194
-rw-r--r--dom/media/gmp/GMPStorageParent.h47
-rw-r--r--dom/media/gmp/GMPTimerChild.cpp59
-rw-r--r--dom/media/gmp/GMPTimerChild.h45
-rw-r--r--dom/media/gmp/GMPTimerParent.cpp105
-rw-r--r--dom/media/gmp/GMPTimerParent.h56
-rw-r--r--dom/media/gmp/GMPTypes.ipdlh116
-rw-r--r--dom/media/gmp/GMPUtils.cpp228
-rw-r--r--dom/media/gmp/GMPUtils.h83
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.cpp210
-rw-r--r--dom/media/gmp/GMPVideoDecoderChild.h75
-rw-r--r--dom/media/gmp/GMPVideoDecoderParent.cpp461
-rw-r--r--dom/media/gmp/GMPVideoDecoderParent.h103
-rw-r--r--dom/media/gmp/GMPVideoDecoderProxy.h57
-rw-r--r--dom/media/gmp/GMPVideoEncodedFrameImpl.cpp226
-rw-r--r--dom/media/gmp/GMPVideoEncodedFrameImpl.h116
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.cpp203
-rw-r--r--dom/media/gmp/GMPVideoEncoderChild.h75
-rw-r--r--dom/media/gmp/GMPVideoEncoderParent.cpp307
-rw-r--r--dom/media/gmp/GMPVideoEncoderParent.h85
-rw-r--r--dom/media/gmp/GMPVideoEncoderProxy.h56
-rw-r--r--dom/media/gmp/GMPVideoHost.cpp94
-rw-r--r--dom/media/gmp/GMPVideoHost.h54
-rw-r--r--dom/media/gmp/GMPVideoPlaneImpl.cpp179
-rw-r--r--dom/media/gmp/GMPVideoPlaneImpl.h61
-rw-r--r--dom/media/gmp/GMPVideoi420FrameImpl.cpp328
-rw-r--r--dom/media/gmp/GMPVideoi420FrameImpl.h80
-rw-r--r--dom/media/gmp/PChromiumCDM.ipdl129
-rw-r--r--dom/media/gmp/PGMP.ipdl59
-rw-r--r--dom/media/gmp/PGMPContent.ipdl37
-rw-r--r--dom/media/gmp/PGMPService.ipdl38
-rw-r--r--dom/media/gmp/PGMPStorage.ipdl37
-rw-r--r--dom/media/gmp/PGMPTimer.ipdl26
-rw-r--r--dom/media/gmp/PGMPVideoDecoder.ipdl50
-rw-r--r--dom/media/gmp/PGMPVideoEncoder.ipdl48
-rw-r--r--dom/media/gmp/README.txt1
-rw-r--r--dom/media/gmp/gmp-api/gmp-entrypoints.h73
-rw-r--r--dom/media/gmp/gmp-api/gmp-errors.h59
-rw-r--r--dom/media/gmp/gmp-api/gmp-platform.h102
-rw-r--r--dom/media/gmp/gmp-api/gmp-storage.h110
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-codec.h302
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-decode.h125
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-encode.h133
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame-encoded.h92
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame-i420.h142
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-frame.h48
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-host.h53
-rw-r--r--dom/media/gmp/gmp-api/gmp-video-plane.h94
-rw-r--r--dom/media/gmp/moz.build149
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginChromeService.idl58
-rw-r--r--dom/media/gmp/mozIGeckoMediaPluginService.idl122
-rw-r--r--dom/media/gmp/rlz/OWNERS4
-rw-r--r--dom/media/gmp/rlz/README.mozilla4
-rw-r--r--dom/media/gmp/rlz/lib/assert.h14
-rw-r--r--dom/media/gmp/rlz/lib/crc8.cc90
-rw-r--r--dom/media/gmp/rlz/lib/crc8.h24
-rw-r--r--dom/media/gmp/rlz/lib/machine_id.cc93
-rw-r--r--dom/media/gmp/rlz/lib/machine_id.h33
-rw-r--r--dom/media/gmp/rlz/lib/string_utils.cc34
-rw-r--r--dom/media/gmp/rlz/lib/string_utils.h20
-rw-r--r--dom/media/gmp/rlz/mac/lib/machine_id_mac.cc322
-rw-r--r--dom/media/gmp/rlz/moz.build34
-rw-r--r--dom/media/gmp/rlz/win/lib/machine_id_win.cc136
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.cpp100
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineFileIO.h41
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.cpp61
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineUtils.h84
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp142
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoFrame.h54
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module.h1359
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_export.h38
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_ext.h64
-rw-r--r--dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h121
-rw-r--r--dom/media/gmp/widevine-adapter/moz.build21
129 files changed, 22289 insertions, 0 deletions
diff --git a/dom/media/gmp-plugin-openh264/fakeopenh264.info b/dom/media/gmp-plugin-openh264/fakeopenh264.info
new file mode 100644
index 0000000000..814a96be32
--- /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/gmp-fake-openh264.cpp b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
new file mode 100644
index 0000000000..4c5e95643e
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
@@ -0,0 +1,406 @@
+/*!
+ * \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 <cstring>
+#include <iostream>
+#include <assert.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"
+
+#include "mozilla/PodOperations.h"
+
+#if defined(_MSC_VER)
+# define PUBLIC_FUNC __declspec(dllexport)
+#else
+# define PUBLIC_FUNC
+#endif
+
+#define BIG_FRAME 10000
+
+#define GL_CRIT 0
+#define GL_ERROR 1
+#define GL_INFO 2
+#define GL_DEBUG 3
+
+const char* kLogStrings[] = {"Critical", "Error", "Info", "Debug"};
+
+static int g_log_level = GL_CRIT;
+
+#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)
+
+class FakeVideoEncoder;
+class FakeVideoDecoder;
+
+// Turn off padding for this structure. Having extra bytes can result in
+// bitstream parsing problems.
+#pragma pack(push, 1)
+struct EncodedFrame {
+ struct SPSNalu {
+ uint32_t size_;
+ uint8_t payload[14];
+ } sps_nalu;
+ struct PPSNalu {
+ uint32_t size_;
+ uint8_t payload[4];
+ } pps_nalu;
+ struct IDRNalu {
+ uint32_t size_;
+ 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_;
+ } idr_nalu;
+};
+#pragma pack(pop)
+
+#define ENCODED_FRAME_MAGIC 0x004000b8
+
+template <typename T>
+class SelfDestruct {
+ public:
+ explicit SelfDestruct(T* t) : t_(t) {}
+ ~SelfDestruct() {
+ if (t_) {
+ t_->Destroy();
+ }
+ }
+
+ private:
+ T* t_;
+};
+
+class FakeVideoEncoder : public GMPVideoEncoder {
+ public:
+ explicit FakeVideoEncoder(GMPVideoHost* hostAPI) : host_(hostAPI) {}
+
+ void InitEncode(const GMPVideoCodec& codecSettings,
+ const uint8_t* aCodecSpecific, uint32_t aCodecSpecificSize,
+ GMPVideoEncoderCallback* callback, int32_t numberOfCores,
+ uint32_t maxPayloadSize) override {
+ callback_ = callback;
+ frame_size_ = (maxPayloadSize > 0 && maxPayloadSize < BIG_FRAME)
+ ? maxPayloadSize
+ : BIG_FRAME;
+ frame_size_ -= 24 + 40;
+ // default header+extension size is 24, but let's leave extra room if
+ // we enable more extensions.
+ // XXX -- why isn't the size passed in based on the size minus extensions?
+
+ const char* env = getenv("GMP_LOGGING");
+ if (env) {
+ g_log_level = atoi(env);
+ }
+ GMPLOG(GL_INFO, "Initialized encoder");
+ }
+
+ void SendFrame(GMPVideoi420Frame* inputImage, GMPVideoFrameType frame_type,
+ int nal_type) {
+ // Encode this in a frame that looks a little bit like H.264.
+ // Send SPS/PPS/IDR to avoid confusing people
+ // Copy the data. This really should convert this to network byte order.
+ EncodedFrame eframe;
+
+ // These values were chosen to force a SPS id of 0
+ eframe.sps_nalu = {sizeof(EncodedFrame::SPSNalu) - sizeof(uint32_t),
+ {0x67, 0x42, 0xc0, 0xd, 0x8c, 0x8d, 0x40, 0xa0, 0xf9,
+ 0x0, 0xf0, 0x88, 0x46, 0xa0}};
+
+ // These values were chosen to force a PPS id of 0
+ eframe.pps_nalu = {sizeof(EncodedFrame::PPSNalu) - sizeof(uint32_t),
+ {0x68, 0xce, 0x3c, 0x80}};
+
+ eframe.idr_nalu.size_ = sizeof(EncodedFrame::IDRNalu) - sizeof(uint32_t);
+ // We force IFrame here - if we send PFrames, the webrtc.org code gets
+ // tripped up attempting to find non-existent previous IFrames.
+ eframe.idr_nalu.h264_compat_ =
+ nal_type; // 5 = IFrame/IDR slice, 1=PFrame/slice
+ eframe.idr_nalu.magic_ = ENCODED_FRAME_MAGIC;
+ eframe.idr_nalu.width_ = inputImage->Width();
+ eframe.idr_nalu.height_ = inputImage->Height();
+ eframe.idr_nalu.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
+ inputImage->AllocatedSize(kGMPYPlane));
+ eframe.idr_nalu.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
+ inputImage->AllocatedSize(kGMPUPlane));
+ eframe.idr_nalu.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
+ inputImage->AllocatedSize(kGMPVPlane));
+
+ eframe.idr_nalu.timestamp_ = inputImage->Timestamp();
+
+ // 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);
+
+ err = f->CreateEmptyFrame(sizeof(eframe));
+ if (err != GMPNoErr) {
+ GMPLOG(GL_ERROR, "Error allocating frame data");
+ f->Destroy();
+ return;
+ }
+ memcpy(f->Buffer(), &eframe, sizeof(eframe));
+ f->SetEncodedWidth(eframe.idr_nalu.width_);
+ f->SetEncodedHeight(eframe.idr_nalu.height_);
+ f->SetTimeStamp(eframe.idr_nalu.timestamp_);
+ f->SetFrameType(frame_type);
+ f->SetCompleteFrame(true);
+ f->SetBufferType(GMP_BufferLength32);
+
+ GMPLOG(GL_DEBUG, "Encoding complete. type= "
+ << f->FrameType()
+ << " NAL_type=" << (int)eframe.idr_nalu.h264_compat_
+ << " length=" << f->Size()
+ << " timestamp=" << f->TimeStamp()
+ << " width/height=" << eframe.idr_nalu.width_ << "x"
+ << eframe.idr_nalu.height_);
+
+ // Return the encoded frame.
+ GMPCodecSpecificInfo info;
+ mozilla::PodZero(&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");
+ }
+
+ void Encode(GMPVideoi420Frame* inputImage, const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ const GMPVideoFrameType* aFrameTypes,
+ uint32_t aFrameTypesLength) override {
+ GMPLOG(GL_DEBUG, __FUNCTION__ << " size=" << inputImage->Width() << "x"
+ << inputImage->Height());
+
+ assert(aFrameTypesLength != 0);
+ GMPVideoFrameType frame_type = aFrameTypes[0];
+
+ SelfDestruct<GMPVideoi420Frame> ifd(inputImage);
+
+ if (frame_type == kGMPKeyFrame) {
+ if (!inputImage) return;
+ }
+ if (!inputImage) {
+ GMPLOG(GL_ERROR, "no input image");
+ return;
+ }
+
+ if (frame_type == kGMPKeyFrame ||
+ frames_encoded_++ % 10 == 0) { // periodically send iframes anyways
+ // 5 = IFrame/IDR slice
+ SendFrame(inputImage, kGMPKeyFrame, 5);
+ } else {
+ // 1 = PFrame/slice
+ SendFrame(inputImage, frame_type, 1);
+ }
+ }
+
+ void SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override {}
+
+ void SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override {}
+
+ void SetPeriodicKeyFrames(bool aEnable) override {}
+
+ void EncodingComplete() override { 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_ = nullptr;
+ uint32_t frame_size_ = BIG_FRAME;
+ uint32_t frames_encoded_ = 0;
+};
+
+class FakeVideoDecoder : public GMPVideoDecoder {
+ public:
+ explicit FakeVideoDecoder(GMPVideoHost* hostAPI)
+ : host_(hostAPI), callback_(nullptr) {}
+
+ ~FakeVideoDecoder() override = default;
+
+ void InitDecode(const GMPVideoCodec& codecSettings,
+ const uint8_t* aCodecSpecific, uint32_t aCodecSpecificSize,
+ GMPVideoDecoderCallback* callback,
+ int32_t coreCount) override {
+ GMPLOG(GL_INFO, "InitDecode");
+
+ const char* env = getenv("GMP_LOGGING");
+ if (env) {
+ g_log_level = atoi(env);
+ }
+ callback_ = callback;
+ }
+
+ void Decode(GMPVideoEncodedFrame* inputFrame, bool missingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t renderTimeMs = -1) override {
+ GMPLOG(GL_DEBUG, __FUNCTION__
+ << "Decoding frame size=" << inputFrame->Size()
+ << " timestamp=" << inputFrame->TimeStamp());
+
+ // Attach a self-destructor so that the input frame is destroyed on return.
+ SelfDestruct<GMPVideoEncodedFrame> ifd(inputFrame);
+
+ EncodedFrame* eframe;
+ eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
+ GMPLOG(GL_DEBUG, "magic=" << eframe->idr_nalu.magic_ << " h264_compat="
+ << (int)eframe->idr_nalu.h264_compat_
+ << " width=" << eframe->idr_nalu.width_
+ << " height=" << eframe->idr_nalu.height_
+ << " timestamp=" << inputFrame->TimeStamp()
+ << " y/u/v=" << (int)eframe->idr_nalu.y_ << ":"
+ << (int)eframe->idr_nalu.u_ << ":"
+ << (int)eframe->idr_nalu.v_);
+ if (inputFrame->Size() != (sizeof(*eframe))) {
+ GMPLOG(GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size());
+ return;
+ }
+
+ if (eframe->idr_nalu.magic_ != ENCODED_FRAME_MAGIC) {
+ GMPLOG(GL_ERROR,
+ "Couldn't decode frame. Magic=" << eframe->idr_nalu.magic_);
+ return;
+ }
+ if (eframe->idr_nalu.h264_compat_ != 5 &&
+ eframe->idr_nalu.h264_compat_ != 1) {
+ // only return video for iframes or pframes
+ GMPLOG(GL_DEBUG, "Not a video frame: NAL type "
+ << (int)eframe->idr_nalu.h264_compat_);
+ return;
+ }
+
+ int width = eframe->idr_nalu.width_;
+ int height = eframe->idr_nalu.height_;
+ int ystride = eframe->idr_nalu.width_;
+ int uvstride = eframe->idr_nalu.width_ / 2;
+
+ GMPLOG(GL_DEBUG, "Video frame ready for display "
+ << width << "x" << height
+ << " timestamp=" << inputFrame->TimeStamp());
+
+ GMPVideoFrame* ftmp = nullptr;
+
+ // 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->idr_nalu.y_,
+ frame->AllocatedSize(kGMPYPlane));
+ memset(frame->Buffer(kGMPUPlane), eframe->idr_nalu.u_,
+ frame->AllocatedSize(kGMPUPlane));
+ memset(frame->Buffer(kGMPVPlane), eframe->idr_nalu.v_,
+ frame->AllocatedSize(kGMPVPlane));
+
+ GMPLOG(GL_DEBUG, "Allocated size = " << frame->AllocatedSize(kGMPYPlane));
+ frame->SetTimestamp(inputFrame->TimeStamp());
+ frame->SetDuration(inputFrame->Duration());
+ callback_->Decoded(frame);
+ }
+
+ void Reset() override {}
+
+ void Drain() override {}
+
+ void DecodingComplete() override { delete this; }
+
+ GMPVideoHost* host_;
+ GMPVideoDecoderCallback* callback_;
+};
+
+extern "C" {
+
+PUBLIC_FUNC GMPErr GMPInit(const GMPPlatformAPI* 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;
+ }
+ if (!strcmp(aApiName, GMP_API_VIDEO_ENCODER)) {
+ *aPluginApi = new FakeVideoEncoder(static_cast<GMPVideoHost*>(aHostAPI));
+ return GMPNoErr;
+ }
+ return GMPGenericErr;
+}
+
+PUBLIC_FUNC void GMPShutdown(void) {}
+
+} // 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 0000000000..e85a50673e
--- /dev/null
+++ b/dom/media/gmp-plugin-openh264/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# largely a copy of dom/media/gmp-fake/moz.build
+
+FINAL_TARGET = "dist/bin/gmp-fakeopenh264/1.0"
+
+FINAL_TARGET_FILES += [
+ "fakeopenh264.info",
+]
+
+SOURCES += [
+ "gmp-fake-openh264.cpp",
+]
+
+SharedLibrary("fakeopenh264")
+
+USE_STATIC_LIBS = True
+NoVisibilityFlags()
+# Don't use STL wrappers; this isn't Gecko code
+DisableStlWrapping()
+NO_PGO = True
diff --git a/dom/media/gmp/CDMStorageIdProvider.cpp b/dom/media/gmp/CDMStorageIdProvider.cpp
new file mode 100644
index 0000000000..52255879b3
--- /dev/null
+++ b/dom/media/gmp/CDMStorageIdProvider.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CDMStorageIdProvider.h"
+#include "GMPLog.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+
+#ifdef SUPPORT_STORAGE_ID
+# include "rlz/lib/machine_id.h"
+#endif
+
+namespace mozilla {
+
+/*static*/
+nsCString CDMStorageIdProvider::ComputeStorageId(const nsCString& aOriginSalt) {
+#ifndef SUPPORT_STORAGE_ID
+ return ""_ns;
+#else
+ GMP_LOG_DEBUG("CDMStorageIdProvider::ComputeStorageId");
+
+ std::string machineId;
+ if (!rlz_lib::GetMachineId(&machineId)) {
+ GMP_LOG_DEBUG(
+ "CDMStorageIdProvider::ComputeStorageId: get machineId failed.");
+ return ""_ns;
+ }
+
+ std::string originSalt(aOriginSalt.BeginReading(), aOriginSalt.Length());
+ std::string input =
+ machineId + originSalt + CDMStorageIdProvider::kBrowserIdentifier;
+ nsCOMPtr<nsICryptoHash> hasher;
+ nsresult rv = NS_NewCryptoHash(nsICryptoHash::SHA256, getter_AddRefs(hasher));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_LOG_DEBUG(
+ "CDMStorageIdProvider::ComputeStorageId: failed to initialize "
+ "hash(0x%08" PRIx32 ")",
+ static_cast<uint32_t>(rv));
+ return ""_ns;
+ }
+
+ rv = hasher->Update(reinterpret_cast<const uint8_t*>(input.c_str()),
+ input.size());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_LOG_DEBUG(
+ "CDMStorageIdProvider::ComputeStorageId: failed to update "
+ "hash(0x%08" PRIx32 ")",
+ static_cast<uint32_t>(rv));
+ return ""_ns;
+ }
+
+ nsCString storageId;
+ rv = hasher->Finish(false, storageId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_LOG_DEBUG(
+ "CDMStorageIdProvider::ComputeStorageId: failed to get the final hash "
+ "result(0x%08" PRIx32 ")",
+ static_cast<uint32_t>(rv));
+ return ""_ns;
+ }
+ return storageId;
+#endif
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/CDMStorageIdProvider.h b/dom/media/gmp/CDMStorageIdProvider.h
new file mode 100644
index 0000000000..6904792607
--- /dev/null
+++ b/dom/media/gmp/CDMStorageIdProvider.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 CDMStorageIdProvider_h_
+#define CDMStorageIdProvider_h_
+
+/**
+ * CDM will try to request a latest version(0) of storage id.
+ * If the storage id computation algorithm changed, we should increase the
+ * kCurrentVersion.
+ */
+
+#include <string>
+
+#include "nsString.h"
+
+namespace mozilla {
+
+class CDMStorageIdProvider {
+ static constexpr const char* kBrowserIdentifier = "mozilla_firefox_gecko";
+
+ public:
+ // Should increase the value when the storage id algorithm changed.
+ static constexpr int kCurrentVersion = 1;
+ static constexpr int kCDMRequestLatestVersion = 0;
+
+ // Return empty string when
+ // 1. Call on unsupported storageid platform.
+ // 2. Failed to compute the storage id.
+ // This function only provide the storage id for kCurrentVersion=1.
+ // If you want to change the algorithm or output of storageid,
+ // you should keep the version 1 storage id and consider to provide
+ // higher version storage id in another function.
+ static nsCString ComputeStorageId(const nsCString& aOriginSalt);
+};
+
+} // namespace mozilla
+
+#endif // CDMStorageIdProvider_h_
diff --git a/dom/media/gmp/ChromiumCDMAdapter.cpp b/dom/media/gmp/ChromiumCDMAdapter.cpp
new file mode 100644
index 0000000000..a5e866f83e
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMAdapter.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMAdapter.h"
+
+#include <utility>
+
+#include "GMPLog.h"
+#include "WidevineUtils.h"
+#include "content_decryption_module.h"
+#include "content_decryption_module_ext.h"
+#include "gmp-api/gmp-entrypoints.h"
+#include "gmp-api/gmp-video-codec.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/HelperMacros.h"
+#include "mozilla/dom/KeySystemNames.h"
+
+#ifdef XP_WIN
+# include "WinUtils.h"
+# include "nsWindowsDllInterceptor.h"
+# include <windows.h>
+# include <strsafe.h>
+# include <unordered_map>
+# include <vector>
+#else
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <unistd.h>
+# include <fcntl.h>
+#endif
+
+const GMPPlatformAPI* sPlatform = nullptr;
+
+namespace mozilla {
+
+#ifdef XP_WIN
+static void InitializeHooks();
+#endif
+
+ChromiumCDMAdapter::ChromiumCDMAdapter(
+ nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) {
+#ifdef XP_WIN
+ InitializeHooks();
+#endif
+ PopulateHostFiles(std::move(aHostPathPairs));
+}
+
+void ChromiumCDMAdapter::SetAdaptee(PRLibrary* aLib) { mLib = aLib; }
+
+void* ChromiumCdmHost(int aHostInterfaceVersion, void* aUserData) {
+ GMP_LOG_DEBUG("ChromiumCdmHostFunc(%d, %p)", aHostInterfaceVersion,
+ aUserData);
+ if (aHostInterfaceVersion != cdm::Host_10::kVersion) {
+ return nullptr;
+ }
+ return aUserData;
+}
+
+#ifdef MOZILLA_OFFICIAL
+static cdm::HostFile TakeToCDMHostFile(HostFileData& aHostFileData) {
+ return cdm::HostFile(aHostFileData.mBinary.Path().get(),
+ aHostFileData.mBinary.TakePlatformFile(),
+ aHostFileData.mSig.TakePlatformFile());
+}
+#endif
+
+GMPErr ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI) {
+ GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPInit");
+ sPlatform = aPlatformAPI;
+ if (NS_WARN_IF(!mLib)) {
+ MOZ_CRASH("Missing library!");
+ return GMPGenericErr;
+ }
+
+#ifdef MOZILLA_OFFICIAL
+ // Note: we must call the VerifyCdmHost_0 function if it's present before
+ // we call the initialize function.
+ auto verify = reinterpret_cast<decltype(::VerifyCdmHost_0)*>(
+ PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(VerifyCdmHost_0)));
+ if (verify) {
+ nsTArray<cdm::HostFile> files;
+ for (HostFileData& hostFile : mHostFiles) {
+ files.AppendElement(TakeToCDMHostFile(hostFile));
+ }
+ bool result = verify(files.Elements(), files.Length());
+ GMP_LOG_DEBUG("%s VerifyCdmHost_0 returned %d", __func__, result);
+ MOZ_DIAGNOSTIC_ASSERT(result, "Verification failed!");
+ }
+#endif
+
+ auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
+ PR_FindFunctionSymbol(mLib, MOZ_STRINGIFY(INITIALIZE_CDM_MODULE)));
+ if (!init) {
+ MOZ_CRASH("Missing init method!");
+ return GMPGenericErr;
+ }
+
+ GMP_LOG_DEBUG(MOZ_STRINGIFY(INITIALIZE_CDM_MODULE) "()");
+ init();
+
+ return GMPNoErr;
+}
+
+GMPErr ChromiumCDMAdapter::GMPGetAPI(const char* aAPIName, void* aHostAPI,
+ void** aPluginAPI,
+ const nsACString& aKeySystem) {
+ MOZ_ASSERT(
+ aKeySystem.EqualsLiteral(kWidevineKeySystemName) ||
+ aKeySystem.EqualsLiteral(kClearKeyKeySystemName) ||
+ aKeySystem.EqualsLiteral(kClearKeyWithProtectionQueryKeySystemName) ||
+ aKeySystem.EqualsLiteral("fake"),
+ "Should not get an unrecognized key system. Why didn't it get "
+ "blocked by MediaKeySystemAccess?");
+ GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %s) this=0x%p",
+ aAPIName, aHostAPI, aPluginAPI,
+ PromiseFlatCString(aKeySystem).get(), this);
+ bool isCdm10 = !strcmp(aAPIName, CHROMIUM_CDM_API);
+
+ if (!isCdm10) {
+ MOZ_ASSERT_UNREACHABLE("We only support and expect cdm10!");
+ GMP_LOG_DEBUG(
+ "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p got "
+ "unsupported CDM version!",
+ aAPIName, aHostAPI, aPluginAPI, this);
+ return GMPGenericErr;
+ }
+ auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
+ PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
+ if (!create) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p "
+ "FAILED to find CreateCdmInstance",
+ aAPIName, aHostAPI, aPluginAPI, this);
+ return GMPGenericErr;
+ }
+
+ const int version = cdm::ContentDecryptionModule_10::kVersion;
+ void* cdm = create(version, aKeySystem.BeginReading(), aKeySystem.Length(),
+ &ChromiumCdmHost, aHostAPI);
+ if (!cdm) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p "
+ "FAILED to create cdm version %d",
+ aAPIName, aHostAPI, aPluginAPI, this, version);
+ return GMPGenericErr;
+ }
+ GMP_LOG_DEBUG("cdm: 0x%p, version: %d", cdm, version);
+ *aPluginAPI = cdm;
+
+ return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
+}
+
+void ChromiumCDMAdapter::GMPShutdown() {
+ GMP_LOG_DEBUG("ChromiumCDMAdapter::GMPShutdown()");
+
+ decltype(::DeinitializeCdmModule)* deinit;
+ deinit =
+ (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
+ if (deinit) {
+ GMP_LOG_DEBUG("DeinitializeCdmModule()");
+ deinit();
+ }
+}
+
+/* static */
+bool ChromiumCDMAdapter::Supports(int32_t aModuleVersion,
+ int32_t aInterfaceVersion,
+ int32_t aHostVersion) {
+ return aModuleVersion == CDM_MODULE_VERSION &&
+ aInterfaceVersion == cdm::ContentDecryptionModule_10::kVersion &&
+ aHostVersion == cdm::Host_10::kVersion;
+}
+
+#ifdef XP_WIN
+
+static WindowsDllInterceptor sKernel32Intercept;
+
+typedef DWORD(WINAPI* QueryDosDeviceWFnPtr)(_In_opt_ LPCWSTR lpDeviceName,
+ _Out_ LPWSTR lpTargetPath,
+ _In_ DWORD ucchMax);
+
+static WindowsDllInterceptor::FuncHookType<QueryDosDeviceWFnPtr>
+ sOriginalQueryDosDeviceWFnPtr;
+
+static std::unordered_map<std::wstring, std::wstring>* sDeviceNames = nullptr;
+
+DWORD WINAPI QueryDosDeviceWHook(LPCWSTR lpDeviceName, LPWSTR lpTargetPath,
+ DWORD ucchMax) {
+ if (!sDeviceNames) {
+ return 0;
+ }
+ std::wstring name = std::wstring(lpDeviceName);
+ auto iter = sDeviceNames->find(name);
+ if (iter == sDeviceNames->end()) {
+ return 0;
+ }
+ const std::wstring& device = iter->second;
+ if (device.size() + 1 > ucchMax) {
+ return 0;
+ }
+ PodCopy(lpTargetPath, device.c_str(), device.size());
+ lpTargetPath[device.size()] = 0;
+ GMP_LOG_DEBUG("QueryDosDeviceWHook %S -> %S", lpDeviceName, lpTargetPath);
+ return device.size();
+}
+
+static std::vector<std::wstring> GetDosDeviceNames() {
+ std::vector<std::wstring> v;
+ std::vector<wchar_t> buf;
+ buf.resize(1024);
+ DWORD rv = GetLogicalDriveStringsW(buf.size(), buf.data());
+ if (rv == 0 || rv > buf.size()) {
+ return v;
+ }
+
+ // buf will be a list of null terminated strings, with the last string
+ // being 0 length.
+ const wchar_t* p = buf.data();
+ const wchar_t* end = &buf.back();
+ size_t l;
+ while (p < end && (l = wcsnlen_s(p, end - p)) > 0) {
+ // The string is of the form "C:\". We need to strip off the trailing
+ // backslash.
+ std::wstring drive = std::wstring(p, p + l);
+ if (drive.back() == '\\') {
+ drive.erase(drive.end() - 1);
+ }
+ v.push_back(std::move(drive));
+ p += l + 1;
+ }
+ return v;
+}
+
+static std::wstring GetDeviceMapping(const std::wstring& aDosDeviceName) {
+ wchar_t buf[MAX_PATH] = {0};
+ DWORD rv = QueryDosDeviceW(aDosDeviceName.c_str(), buf, MAX_PATH);
+ if (rv == 0) {
+ return std::wstring(L"");
+ }
+ return std::wstring(buf, buf + rv);
+}
+
+static void InitializeHooks() {
+ static bool initialized = false;
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+ sDeviceNames = new std::unordered_map<std::wstring, std::wstring>();
+ for (const std::wstring& name : GetDosDeviceNames()) {
+ sDeviceNames->emplace(name, GetDeviceMapping(name));
+ }
+
+ sKernel32Intercept.Init("kernelbase.dll");
+ sOriginalQueryDosDeviceWFnPtr.Set(sKernel32Intercept, "QueryDosDeviceW",
+ &QueryDosDeviceWHook);
+}
+#endif
+
+HostFile::HostFile(HostFile&& aOther)
+ : mPath(aOther.mPath), mFile(aOther.TakePlatformFile()) {}
+
+HostFile::~HostFile() {
+ if (mFile != cdm::kInvalidPlatformFile) {
+#ifdef XP_WIN
+ CloseHandle(mFile);
+#else
+ close(mFile);
+#endif
+ mFile = cdm::kInvalidPlatformFile;
+ }
+}
+
+#ifdef XP_WIN
+HostFile::HostFile(const nsCString& aPath)
+ : mPath(NS_ConvertUTF8toUTF16(aPath)) {
+ HANDLE handle =
+ CreateFileW(mPath.get(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ mFile = (handle == INVALID_HANDLE_VALUE) ? cdm::kInvalidPlatformFile : handle;
+}
+#endif
+
+#ifndef XP_WIN
+HostFile::HostFile(const nsCString& aPath) : mPath(aPath) {
+ // Note: open() returns -1 on failure; i.e. kInvalidPlatformFile.
+ mFile = open(aPath.get(), O_RDONLY);
+}
+#endif
+
+cdm::PlatformFile HostFile::TakePlatformFile() {
+ cdm::PlatformFile f = mFile;
+ mFile = cdm::kInvalidPlatformFile;
+ return f;
+}
+
+void ChromiumCDMAdapter::PopulateHostFiles(
+ nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs) {
+ for (const auto& pair : aHostPathPairs) {
+ mHostFiles.AppendElement(HostFileData(mozilla::HostFile(pair.first),
+ mozilla::HostFile(pair.second)));
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/ChromiumCDMAdapter.h b/dom/media/gmp/ChromiumCDMAdapter.h
new file mode 100644
index 0000000000..747345fb27
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMAdapter.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 ChromiumAdapter_h_
+#define ChromiumAdapter_h_
+
+#include "GMPLoader.h"
+#include "prlink.h"
+#include "GMPUtils.h"
+#include "nsTArray.h"
+#include "content_decryption_module_ext.h"
+#include "nsString.h"
+
+#include <utility>
+
+struct GMPPlatformAPI;
+
+namespace mozilla {
+
+#if defined(XP_WIN)
+typedef nsString HostFileString;
+#else
+typedef nsCString HostFileString;
+#endif
+
+class HostFile {
+ public:
+ explicit HostFile(const nsCString& aPath);
+ HostFile(HostFile&& aOther);
+ ~HostFile();
+
+ const HostFileString& Path() const { return mPath; }
+ cdm::PlatformFile TakePlatformFile();
+
+ private:
+ const HostFileString mPath;
+ cdm::PlatformFile mFile = cdm::kInvalidPlatformFile;
+};
+
+struct HostFileData {
+ HostFileData(HostFile&& aBinary, HostFile&& aSig)
+ : mBinary(std::move(aBinary)), mSig(std::move(aSig)) {}
+
+ HostFileData(HostFileData&& aOther)
+ : mBinary(std::move(aOther.mBinary)), mSig(std::move(aOther.mSig)) {}
+
+ ~HostFileData() = default;
+
+ HostFile mBinary;
+ HostFile mSig;
+};
+
+class ChromiumCDMAdapter : public gmp::GMPAdapter {
+ public:
+ explicit ChromiumCDMAdapter(
+ nsTArray<std::pair<nsCString, nsCString>>&& aHostPathPairs);
+
+ void SetAdaptee(PRLibrary* aLib) override;
+
+ // These are called in place of the corresponding GMP API functions.
+ GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override;
+ GMPErr GMPGetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI,
+ const nsACString& aKeySystem) override;
+ void GMPShutdown() override;
+
+ static bool Supports(int32_t aModuleVersion, int32_t aInterfaceVersion,
+ int32_t aHostVersion);
+
+ private:
+ void PopulateHostFiles(
+ nsTArray<std::pair<nsCString, nsCString>>&& aHostFilePaths);
+
+ PRLibrary* mLib = nullptr;
+ nsTArray<HostFileData> mHostFiles;
+};
+
+} // namespace mozilla
+
+#endif // ChromiumAdapter_h_
diff --git a/dom/media/gmp/ChromiumCDMCallback.h b/dom/media/gmp/ChromiumCDMCallback.h
new file mode 100644
index 0000000000..3f62634933
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMCallback.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 ChromiumCDMCallback_h_
+#define ChromiumCDMCallback_h_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
+#include "mozilla/gmp/GMPTypes.h" // For CDMKeyInformation
+
+namespace mozilla {
+class ErrorResult;
+}
+
+class ChromiumCDMCallback {
+ public:
+ virtual ~ChromiumCDMCallback() = default;
+
+ virtual void SetSessionId(uint32_t aPromiseId,
+ const nsCString& aSessionId) = 0;
+
+ virtual void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccessful) = 0;
+
+ virtual void ResolvePromiseWithKeyStatus(uint32_t aPromiseId,
+ uint32_t aKeyStatus) = 0;
+
+ virtual void ResolvePromise(uint32_t aPromiseId) = 0;
+
+ virtual void RejectPromise(uint32_t aPromiseId, mozilla::ErrorResult&& aError,
+ const nsCString& aErrorMessage) = 0;
+
+ virtual void SessionMessage(const nsACString& aSessionId,
+ uint32_t aMessageType,
+ nsTArray<uint8_t>&& aMessage) = 0;
+
+ virtual void SessionKeysChange(
+ const nsCString& aSessionId,
+ nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) = 0;
+
+ virtual void ExpirationChange(const nsCString& aSessionId,
+ double aSecondsSinceEpoch) = 0;
+
+ virtual void SessionClosed(const nsCString& aSessionId) = 0;
+
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ virtual void Terminated() = 0;
+
+ virtual void Shutdown() = 0;
+};
+
+#endif
diff --git a/dom/media/gmp/ChromiumCDMCallbackProxy.cpp b/dom/media/gmp/ChromiumCDMCallbackProxy.cpp
new file mode 100644
index 0000000000..481534c0e4
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMCallbackProxy.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMCallbackProxy.h"
+
+#include <type_traits>
+
+#include "ChromiumCDMProxy.h"
+#include "content_decryption_module.h"
+
+namespace mozilla {
+
+template <class Func, class... Args>
+void ChromiumCDMCallbackProxy::DispatchToMainThread(const char* const aLabel,
+ Func aFunc,
+ Args&&... aArgs) {
+ mMainThread->Dispatch(
+ // Use decay to ensure all the types are passed by value not by reference.
+ NewRunnableMethod<std::decay_t<Args>...>(aLabel, mProxy, aFunc,
+ std::forward<Args>(aArgs)...),
+ NS_DISPATCH_NORMAL);
+}
+
+void ChromiumCDMCallbackProxy::SetSessionId(uint32_t aPromiseId,
+ const nsCString& aSessionId) {
+ DispatchToMainThread("ChromiumCDMProxy::OnSetSessionId",
+ &ChromiumCDMProxy::OnSetSessionId, aPromiseId,
+ NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void ChromiumCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccessful) {
+ DispatchToMainThread("ChromiumCDMProxy::OnResolveLoadSessionPromise",
+ &ChromiumCDMProxy::OnResolveLoadSessionPromise,
+ aPromiseId, aSuccessful);
+}
+
+void ChromiumCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId) {
+ DispatchToMainThread("ChromiumCDMProxy::ResolvePromise",
+ &ChromiumCDMProxy::ResolvePromise, aPromiseId);
+}
+
+void ChromiumCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+ ErrorResult&& aException,
+ const nsCString& aErrorMessage) {
+ // Use CopyableErrorResult to store our exception in the runnable,
+ // because ErrorResult is not OK to move across threads.
+ DispatchToMainThread<decltype(&ChromiumCDMProxy::RejectPromiseOnMainThread),
+ int32_t, StoreCopyPassByRRef<CopyableErrorResult>,
+ const nsCString&>(
+ "ChromiumCDMProxy::RejectPromise",
+ &ChromiumCDMProxy::RejectPromiseOnMainThread, aPromiseId,
+ std::move(aException), aErrorMessage);
+}
+
+static dom::MediaKeyMessageType ToDOMMessageType(uint32_t aMessageType) {
+ switch (static_cast<cdm::MessageType>(aMessageType)) {
+ case cdm::kLicenseRequest:
+ return dom::MediaKeyMessageType::License_request;
+ case cdm::kLicenseRenewal:
+ return dom::MediaKeyMessageType::License_renewal;
+ case cdm::kLicenseRelease:
+ return dom::MediaKeyMessageType::License_release;
+ case cdm::kIndividualizationRequest:
+ return dom::MediaKeyMessageType::Individualization_request;
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
+ return dom::MediaKeyMessageType::License_request;
+}
+
+void ChromiumCDMCallbackProxy::SessionMessage(const nsACString& aSessionId,
+ uint32_t aMessageType,
+ nsTArray<uint8_t>&& aMessage) {
+ DispatchToMainThread("ChromiumCDMProxy::OnSessionMessage",
+ &ChromiumCDMProxy::OnSessionMessage,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ ToDOMMessageType(aMessageType), std::move(aMessage));
+}
+
+static dom::MediaKeyStatus ToDOMMediaKeyStatus(uint32_t aStatus) {
+ switch (static_cast<cdm::KeyStatus>(aStatus)) {
+ case cdm::kUsable:
+ return dom::MediaKeyStatus::Usable;
+ case cdm::kInternalError:
+ return dom::MediaKeyStatus::Internal_error;
+ case cdm::kExpired:
+ return dom::MediaKeyStatus::Expired;
+ case cdm::kOutputRestricted:
+ return dom::MediaKeyStatus::Output_restricted;
+ case cdm::kOutputDownscaled:
+ return dom::MediaKeyStatus::Output_downscaled;
+ case cdm::kStatusPending:
+ return dom::MediaKeyStatus::Status_pending;
+ case cdm::kReleased:
+ return dom::MediaKeyStatus::Released;
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
+ return dom::MediaKeyStatus::Internal_error;
+}
+
+void ChromiumCDMCallbackProxy::ResolvePromiseWithKeyStatus(
+ uint32_t aPromiseId, uint32_t aKeyStatus) {
+ DispatchToMainThread("ChromiumCDMProxy::OnResolvePromiseWithKeyStatus",
+ &ChromiumCDMProxy::OnResolvePromiseWithKeyStatus,
+ aPromiseId, ToDOMMediaKeyStatus(aKeyStatus));
+}
+
+void ChromiumCDMCallbackProxy::SessionKeysChange(
+ const nsCString& aSessionId,
+ nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) {
+ bool keyStatusesChange = false;
+ {
+ auto caps = mProxy->Capabilites().Lock();
+ for (const auto& keyInfo : aKeysInfo) {
+ keyStatusesChange |= caps->SetKeyStatus(
+ keyInfo.mKeyId(), NS_ConvertUTF8toUTF16(aSessionId),
+ dom::Optional<dom::MediaKeyStatus>(
+ ToDOMMediaKeyStatus(keyInfo.mStatus())));
+ }
+ }
+ if (keyStatusesChange) {
+ DispatchToMainThread("ChromiumCDMProxy::OnKeyStatusesChange",
+ &ChromiumCDMProxy::OnKeyStatusesChange,
+ NS_ConvertUTF8toUTF16(aSessionId));
+ }
+}
+
+void ChromiumCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+ double aSecondsSinceEpoch) {
+ DispatchToMainThread("ChromiumCDMProxy::OnExpirationChange",
+ &ChromiumCDMProxy::OnExpirationChange,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ UnixTime(aSecondsSinceEpoch * 1000));
+}
+
+void ChromiumCDMCallbackProxy::SessionClosed(const nsCString& aSessionId) {
+ DispatchToMainThread("ChromiumCDMProxy::OnSessionClosed",
+ &ChromiumCDMProxy::OnSessionClosed,
+ NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void ChromiumCDMCallbackProxy::QueryOutputProtectionStatus() {
+ DispatchToMainThread("ChromiumCDMProxy::QueryOutputProtectionStatus",
+ &ChromiumCDMProxy::QueryOutputProtectionStatus);
+}
+
+void ChromiumCDMCallbackProxy::Terminated() {
+ DispatchToMainThread("ChromiumCDMProxy::Terminated",
+ &ChromiumCDMProxy::Terminated);
+}
+
+void ChromiumCDMCallbackProxy::Shutdown() {
+ DispatchToMainThread("ChromiumCDMProxy::Shutdown",
+ &ChromiumCDMProxy::Shutdown);
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/ChromiumCDMCallbackProxy.h b/dom/media/gmp/ChromiumCDMCallbackProxy.h
new file mode 100644
index 0000000000..5b240f9102
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMCallbackProxy.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 ChromiumCDMCallbackProxy_h_
+#define ChromiumCDMCallbackProxy_h_
+
+#include "ChromiumCDMCallback.h"
+#include "ChromiumCDMProxy.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class ChromiumCDMCallbackProxy : public ChromiumCDMCallback {
+ public:
+ ChromiumCDMCallbackProxy(ChromiumCDMProxy* aProxy,
+ nsIEventTarget* aMainThread)
+ : mProxy(aProxy), mMainThread(aMainThread) {}
+
+ void SetSessionId(uint32_t aPromiseId, const nsCString& aSessionId) override;
+
+ void ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccessful) override;
+
+ void ResolvePromiseWithKeyStatus(uint32_t aPromiseId,
+ uint32_t aKeyStatus) override;
+
+ void ResolvePromise(uint32_t aPromiseId) override;
+
+ void RejectPromise(uint32_t aPromiseId, ErrorResult&& aException,
+ const nsCString& aErrorMessage) override;
+
+ void SessionMessage(const nsACString& aSessionId, uint32_t aMessageType,
+ nsTArray<uint8_t>&& aMessage) override;
+
+ void SessionKeysChange(
+ const nsCString& aSessionId,
+ nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) override;
+
+ void ExpirationChange(const nsCString& aSessionId,
+ double aSecondsSinceEpoch) override;
+
+ void SessionClosed(const nsCString& aSessionId) override;
+
+ void QueryOutputProtectionStatus() override;
+
+ void Terminated() override;
+
+ void Shutdown() override;
+
+ private:
+ template <class Func, class... Args>
+ void DispatchToMainThread(const char* const aLabel, Func aFunc,
+ Args&&... aArgs);
+ // Warning: Weak ref.
+ ChromiumCDMProxy* mProxy;
+ const nsCOMPtr<nsIEventTarget> mMainThread;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/gmp/ChromiumCDMChild.cpp b/dom/media/gmp/ChromiumCDMChild.cpp
new file mode 100644
index 0000000000..4592f2e291
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -0,0 +1,866 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMChild.h"
+#include "GMPContentChild.h"
+#include "WidevineUtils.h"
+#include "WidevineFileIO.h"
+#include "WidevineVideoFrame.h"
+#include "GMPLog.h"
+#include "GMPPlatform.h"
+#include "mozilla/Unused.h"
+#include "nsPrintfCString.h"
+#include "base/time.h"
+#include "GMPUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "CDMStorageIdProvider.h"
+#include "nsReadableUtils.h"
+
+#include <type_traits>
+
+namespace mozilla::gmp {
+
+ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
+ : mPlugin(aPlugin) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild:: ctor this=%p", this);
+}
+
+void ChromiumCDMChild::Init(cdm::ContentDecryptionModule_10* aCDM,
+ const nsACString& aStorageId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ mCDM = aCDM;
+ MOZ_ASSERT(mCDM);
+ mStorageId = aStorageId;
+}
+
+void ChromiumCDMChild::TimerExpired(void* aContext) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::TimerExpired(context=0x%p)", aContext);
+ if (mCDM) {
+ mCDM->TimerExpired(aContext);
+ }
+}
+
+class CDMShmemBuffer : public CDMBuffer {
+ public:
+ CDMShmemBuffer(ChromiumCDMChild* aProtocol, ipc::Shmem aShmem)
+ : mProtocol(aProtocol), mSize(aShmem.Size<uint8_t>()), mShmem(aShmem) {
+ GMP_LOG_DEBUG("CDMShmemBuffer(size=%" PRIu32 ") created", Size());
+ // Note: Chrome initializes the size of a buffer to it capacity. We do the
+ // same.
+ }
+
+ CDMShmemBuffer(ChromiumCDMChild* aProtocol, ipc::Shmem aShmem,
+ WidevineBuffer* aLocalBuffer)
+ : CDMShmemBuffer(aProtocol, aShmem) {
+ MOZ_ASSERT(aLocalBuffer->Size() == Size());
+ memcpy(Data(), aLocalBuffer->Data(),
+ std::min<uint32_t>(aLocalBuffer->Size(), Size()));
+ }
+
+ ~CDMShmemBuffer() override {
+ GMP_LOG_DEBUG("CDMShmemBuffer(size=%" PRIu32 ") destructed writable=%d",
+ Size(), mShmem.IsWritable());
+ if (mShmem.IsWritable()) {
+ // The shmem wasn't extracted to send its data back up to the parent
+ // process, so we can reuse the shmem.
+ mProtocol->GiveBuffer(std::move(mShmem));
+ }
+ }
+
+ void Destroy() override {
+ GMP_LOG_DEBUG("CDMShmemBuffer::Destroy(size=%" PRIu32 ")", Size());
+ delete this;
+ }
+ uint32_t Capacity() const override { return mShmem.Size<uint8_t>(); }
+
+ uint8_t* Data() override { return mShmem.get<uint8_t>(); }
+
+ void SetSize(uint32_t aSize) override {
+ MOZ_ASSERT(aSize <= Capacity());
+ // Note: We can't use the shmem's size member after ExtractShmem(),
+ // has been called, so we track the size exlicitly so that we can use
+ // Size() in logging after we've called ExtractShmem().
+ GMP_LOG_DEBUG("CDMShmemBuffer::SetSize(size=%" PRIu32 ")", Size());
+ mSize = aSize;
+ }
+
+ uint32_t Size() const override { return mSize; }
+
+ ipc::Shmem ExtractShmem() {
+ ipc::Shmem shmem = mShmem;
+ mShmem = ipc::Shmem();
+ return shmem;
+ }
+
+ CDMShmemBuffer* AsShmemBuffer() override { return this; }
+
+ private:
+ RefPtr<ChromiumCDMChild> mProtocol;
+ uint32_t mSize;
+ mozilla::ipc::Shmem mShmem;
+ CDMShmemBuffer(const CDMShmemBuffer&);
+ void operator=(const CDMShmemBuffer&);
+};
+
+static auto ToString(const nsTArray<ipc::Shmem>& aBuffers) {
+ return StringJoin(","_ns, aBuffers, [](auto& s, const ipc::Shmem& shmem) {
+ s.AppendInt(static_cast<uint32_t>(shmem.Size<uint8_t>()));
+ });
+}
+
+cdm::Buffer* ChromiumCDMChild::Allocate(uint32_t aCapacity) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::Allocate(capacity=%" PRIu32
+ ") bufferSizes={%s}",
+ aCapacity, ToString(mBuffers).get());
+ MOZ_ASSERT(IsOnMessageLoopThread());
+
+ if (mBuffers.IsEmpty()) {
+ Unused << SendIncreaseShmemPoolSize();
+ }
+
+ // Find the shmem with the least amount of wasted space if we were to
+ // select it for this sized allocation. We need to do this because shmems
+ // for decrypted audio as well as video frames are both stored in this
+ // list, and we don't want to use the video frame shmems for audio samples.
+ const size_t invalid = std::numeric_limits<size_t>::max();
+ size_t best = invalid;
+ auto wastedSpace = [this, aCapacity](size_t index) {
+ return mBuffers[index].Size<uint8_t>() - aCapacity;
+ };
+ for (size_t i = 0; i < mBuffers.Length(); i++) {
+ if (mBuffers[i].Size<uint8_t>() >= aCapacity &&
+ (best == invalid || wastedSpace(i) < wastedSpace(best))) {
+ best = i;
+ }
+ }
+ if (best == invalid) {
+ // The parent process should have bestowed upon us a shmem of appropriate
+ // size, but did not! Do a "dive and catch", and create an non-shared
+ // memory buffer. The parent will detect this and send us an extra shmem
+ // so future frames can be in shmems, i.e. returned on the fast path.
+ return new WidevineBuffer(aCapacity);
+ }
+ ipc::Shmem shmem = mBuffers[best];
+ mBuffers.RemoveElementAt(best);
+ return new CDMShmemBuffer(this, shmem);
+}
+
+void ChromiumCDMChild::SetTimer(int64_t aDelayMs, void* aContext) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::SetTimer(delay=%" PRId64 ", context=0x%p)",
+ aDelayMs, aContext);
+ RefPtr<ChromiumCDMChild> self(this);
+ SetTimerOnMainThread(
+ NewGMPTask([self, aContext]() { self->TimerExpired(aContext); }),
+ aDelayMs);
+}
+
+cdm::Time ChromiumCDMChild::GetCurrentWallTime() {
+ return base::Time::Now().ToDoubleT();
+}
+
+template <typename MethodType, typename... ParamType>
+void ChromiumCDMChild::CallMethod(MethodType aMethod, ParamType&&... aParams) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ // Avoid calling member function after destroy.
+ if (!mDestroyed) {
+ Unused << (this->*aMethod)(std::forward<ParamType>(aParams)...);
+ }
+}
+
+template <typename MethodType, typename... ParamType>
+void ChromiumCDMChild::CallOnMessageLoopThread(const char* const aName,
+ MethodType aMethod,
+ ParamType&&... aParams) {
+ if (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
+ if (IsOnMessageLoopThread()) {
+ CallMethod(aMethod, std::forward<ParamType>(aParams)...);
+ } else {
+ auto m = &ChromiumCDMChild::CallMethod<
+ decltype(aMethod), const std::remove_reference_t<ParamType>&...>;
+ RefPtr<mozilla::Runnable> t =
+ NewRunnableMethod<decltype(aMethod),
+ const std::remove_reference_t<ParamType>...>(
+ aName, this, m, aMethod, std::forward<ParamType>(aParams)...);
+ mPlugin->GMPMessageLoop()->PostTask(t.forget());
+ }
+}
+
+void ChromiumCDMChild::OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnResolveKeyStatusPromise(pid=%" PRIu32
+ "keystatus=%d)",
+ aPromiseId, aKeyStatus);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolveKeyStatusPromise",
+ &ChromiumCDMChild::SendOnResolvePromiseWithKeyStatus,
+ aPromiseId, static_cast<uint32_t>(aKeyStatus));
+}
+
+bool ChromiumCDMChild::OnResolveNewSessionPromiseInternal(
+ uint32_t aPromiseId, const nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ if (mLoadSessionPromiseIds.Contains(aPromiseId)) {
+ // As laid out in the Chromium CDM API, if the CDM fails to load
+ // a session it calls OnResolveNewSessionPromise with nullptr as the
+ // sessionId. We can safely assume this means that we have failed to load a
+ // session as the other methods specify calling 'OnRejectPromise' when they
+ // fail.
+ bool loadSuccessful = !aSessionId.IsEmpty();
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
+ "resolving %s load session ",
+ aPromiseId, PromiseFlatCString(aSessionId).get(),
+ (loadSuccessful ? "successful" : "failed"));
+ mLoadSessionPromiseIds.RemoveElement(aPromiseId);
+ return SendResolveLoadSessionPromise(aPromiseId, loadSuccessful);
+ }
+
+ return SendOnResolveNewSessionPromise(aPromiseId, aSessionId);
+}
+void ChromiumCDMChild::OnResolveNewSessionPromise(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdSize) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%" PRIu32
+ ", sid=%s)",
+ aPromiseId, aSessionId);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolveNewSessionPromise",
+ &ChromiumCDMChild::OnResolveNewSessionPromiseInternal,
+ aPromiseId, nsCString(aSessionId, aSessionIdSize));
+}
+
+void ChromiumCDMChild::OnResolvePromise(uint32_t aPromiseId) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnResolvePromise(pid=%" PRIu32 ")",
+ aPromiseId);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolvePromise",
+ &ChromiumCDMChild::SendOnResolvePromise, aPromiseId);
+}
+
+void ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId,
+ cdm::Exception aException,
+ uint32_t aSystemCode,
+ const char* aErrorMessage,
+ uint32_t aErrorMessageSize) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnRejectPromise(pid=%" PRIu32
+ ", err=%" PRIu32 " code=%" PRIu32 ", msg='%s')",
+ aPromiseId, aException, aSystemCode, aErrorMessage);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnRejectPromise",
+ &ChromiumCDMChild::SendOnRejectPromise, aPromiseId,
+ static_cast<uint32_t>(aException), aSystemCode,
+ nsCString(aErrorMessage, aErrorMessageSize));
+}
+
+void ChromiumCDMChild::OnSessionMessage(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType,
+ const char* aMessage,
+ uint32_t aMessageSize) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
+ " size=%" PRIu32 ")",
+ aSessionId, aMessageType, aMessageSize);
+ CopyableTArray<uint8_t> message;
+ message.AppendElements(aMessage, aMessageSize);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
+ &ChromiumCDMChild::SendOnSessionMessage,
+ nsCString(aSessionId, aSessionIdSize),
+ static_cast<uint32_t>(aMessageType), message);
+}
+
+static auto ToString(const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) {
+ return StringJoin(","_ns, Span{aKeysInfo, aKeysInfoCount},
+ [](auto& str, const cdm::KeyInformation& key) {
+ str.Append(ToHexString(key.key_id, key.key_id_size));
+ str.AppendLiteral("=");
+ str.AppendInt(key.status);
+ });
+}
+
+void ChromiumCDMChild::OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
+ aSessionId, ToString(aKeysInfo, aKeysInfoCount).get());
+
+ CopyableTArray<CDMKeyInformation> keys;
+ keys.SetCapacity(aKeysInfoCount);
+ for (uint32_t i = 0; i < aKeysInfoCount; i++) {
+ const cdm::KeyInformation& key = aKeysInfo[i];
+ nsTArray<uint8_t> kid;
+ kid.AppendElements(key.key_id, key.key_id_size);
+ keys.AppendElement(CDMKeyInformation(kid, key.status, key.system_code));
+ }
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
+ &ChromiumCDMChild::SendOnSessionKeysChange,
+ nsCString(aSessionId, aSessionIdSize), keys);
+}
+
+void ChromiumCDMChild::OnExpirationChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ cdm::Time aNewExpiryTime) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnExpirationChange(sid=%s, time=%lf)",
+ aSessionId, aNewExpiryTime);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnExpirationChange",
+ &ChromiumCDMChild::SendOnExpirationChange,
+ nsCString(aSessionId, aSessionIdSize),
+ aNewExpiryTime);
+}
+
+void ChromiumCDMChild::OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionClosed",
+ &ChromiumCDMChild::SendOnSessionClosed,
+ nsCString(aSessionId, aSessionIdSize));
+}
+
+void ChromiumCDMChild::QueryOutputProtectionStatus() {
+ GMP_LOG_DEBUG("ChromiumCDMChild::QueryOutputProtectionStatus()");
+ // We'll handle the response in `CompleteQueryOutputProtectionStatus`.
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::QueryOutputProtectionStatus",
+ &ChromiumCDMChild::SendOnQueryOutputProtectionStatus);
+}
+
+void ChromiumCDMChild::OnInitialized(bool aSuccess) {
+ MOZ_ASSERT(!mInitPromise.IsEmpty(),
+ "mInitPromise should exist during init callback!");
+ if (!aSuccess) {
+ mInitPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+ }
+ mInitPromise.ResolveIfExists(true, __func__);
+}
+
+cdm::FileIO* ChromiumCDMChild::CreateFileIO(cdm::FileIOClient* aClient) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::CreateFileIO()");
+ if (!mPersistentStateAllowed) {
+ return nullptr;
+ }
+ return new WidevineFileIO(aClient);
+}
+
+void ChromiumCDMChild::RequestStorageId(uint32_t aVersion) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RequestStorageId() aVersion = %u", aVersion);
+ // aVersion >= 0x80000000 are reserved.
+ if (aVersion >= 0x80000000) {
+ mCDM->OnStorageId(aVersion, nullptr, 0);
+ return;
+ }
+ if (aVersion > CDMStorageIdProvider::kCurrentVersion) {
+ mCDM->OnStorageId(aVersion, nullptr, 0);
+ return;
+ }
+
+ mCDM->OnStorageId(CDMStorageIdProvider::kCurrentVersion,
+ !mStorageId.IsEmpty()
+ ? reinterpret_cast<const uint8_t*>(mStorageId.get())
+ : nullptr,
+ mStorageId.Length());
+}
+
+ChromiumCDMChild::~ChromiumCDMChild() {
+ GMP_LOG_DEBUG("ChromiumCDMChild:: dtor this=%p", this);
+}
+
+bool ChromiumCDMChild::IsOnMessageLoopThread() {
+ return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
+}
+
+void ChromiumCDMChild::ActorDestroy(ActorDestroyReason aReason) {
+ mPlugin = nullptr;
+}
+
+void ChromiumCDMChild::PurgeShmems() {
+ for (ipc::Shmem& shmem : mBuffers) {
+ DeallocShmem(shmem);
+ }
+ mBuffers.Clear();
+}
+
+ipc::IPCResult ChromiumCDMChild::RecvPurgeShmems() {
+ PurgeShmems();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvInit(
+ const bool& aAllowDistinctiveIdentifier, const bool& aAllowPersistentState,
+ InitResolver&& aResolver) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvInit(distinctiveId=%s, persistentState=%s)",
+ aAllowDistinctiveIdentifier ? "true" : "false",
+ aAllowPersistentState ? "true" : "false");
+ mPersistentStateAllowed = aAllowPersistentState;
+
+ RefPtr<ChromiumCDMChild::InitPromise> promise = mInitPromise.Ensure(__func__);
+ promise->Then(
+ mPlugin->GMPMessageLoop()->SerialEventTarget(), __func__,
+ [aResolver](bool /* unused */) { aResolver(true); },
+ [aResolver](nsresult rv) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvInit() init promise rejected with "
+ "rv=%" PRIu32,
+ static_cast<uint32_t>(rv));
+ aResolver(false);
+ });
+
+ if (mCDM) {
+ // Once the CDM is initialized we expect it to resolve mInitPromise via
+ // ChromiumCDMChild::OnInitialized
+ mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState,
+ // We do not yet support hardware secure codecs
+ false);
+ } else {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvInit() mCDM not set! Is GMP shutting down?");
+ mInitPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvSetServerCertificate(
+ const uint32_t& aPromiseId, nsTArray<uint8_t>&& aServerCert)
+
+{
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvSetServerCertificate() certlen=%zu",
+ aServerCert.Length());
+ if (mCDM) {
+ mCDM->SetServerCertificate(aPromiseId, aServerCert.Elements(),
+ aServerCert.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvCreateSessionAndGenerateRequest(
+ const uint32_t& aPromiseId, const uint32_t& aSessionType,
+ const uint32_t& aInitDataType, nsTArray<uint8_t>&& aInitData) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
+ "pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
+ ") initDataLen=%zu",
+ aPromiseId, aSessionType, aInitDataType, aInitData.Length());
+ MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentUsageRecord);
+ MOZ_ASSERT(aInitDataType <= cdm::InitDataType::kWebM);
+ if (mCDM) {
+ mCDM->CreateSessionAndGenerateRequest(
+ aPromiseId, static_cast<cdm::SessionType>(aSessionType),
+ static_cast<cdm::InitDataType>(aInitDataType), aInitData.Elements(),
+ aInitData.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvLoadSession(
+ const uint32_t& aPromiseId, const uint32_t& aSessionType,
+ const nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
+ aPromiseId, aSessionType, PromiseFlatCString(aSessionId).get());
+ if (mCDM) {
+ mLoadSessionPromiseIds.AppendElement(aPromiseId);
+ mCDM->LoadSession(aPromiseId, static_cast<cdm::SessionType>(aSessionType),
+ aSessionId.BeginReading(), aSessionId.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvUpdateSession(
+ const uint32_t& aPromiseId, const nsACString& aSessionId,
+ nsTArray<uint8_t>&& aResponse) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
+ ", sid=%s) responseLen=%zu",
+ aPromiseId, PromiseFlatCString(aSessionId).get(),
+ aResponse.Length());
+ if (mCDM) {
+ mCDM->UpdateSession(aPromiseId, aSessionId.BeginReading(),
+ aSessionId.Length(), aResponse.Elements(),
+ aResponse.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvCloseSession(
+ const uint32_t& aPromiseId, const nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
+ aPromiseId, PromiseFlatCString(aSessionId).get());
+ if (mCDM) {
+ mCDM->CloseSession(aPromiseId, aSessionId.BeginReading(),
+ aSessionId.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvRemoveSession(
+ const uint32_t& aPromiseId, const nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
+ aPromiseId, PromiseFlatCString(aSessionId).get());
+ if (mCDM) {
+ mCDM->RemoveSession(aPromiseId, aSessionId.BeginReading(),
+ aSessionId.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvCompleteQueryOutputProtectionStatus(
+ const bool& aSuccess, const uint32_t& aLinkMask,
+ const uint32_t& aProtectionMask) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvCompleteQueryOutputProtectionStatus(aSuccess=%s, "
+ "aLinkMask=%" PRIu32 ", aProtectionMask=%" PRIu32 ")",
+ aSuccess ? "true" : "false", aLinkMask, aProtectionMask);
+
+ if (mCDM) {
+ cdm::QueryResult queryResult = aSuccess ? cdm::QueryResult::kQuerySucceeded
+ : cdm::QueryResult::kQueryFailed;
+ mCDM->OnQueryOutputProtectionStatus(queryResult, aLinkMask,
+ aProtectionMask);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvGetStatusForPolicy(
+ const uint32_t& aPromiseId, const cdm::HdcpVersion& aMinHdcpVersion) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvGetStatusForPolicy(pid=%" PRIu32
+ ", MinHdcpVersion=%" PRIu32 ")",
+ aPromiseId, static_cast<uint32_t>(aMinHdcpVersion));
+ if (mCDM) {
+ cdm::Policy policy;
+ policy.min_hdcp_version = aMinHdcpVersion;
+ mCDM->GetStatusForPolicy(aPromiseId, policy);
+ }
+ return IPC_OK();
+}
+
+static void InitInputBuffer(const CDMInputBuffer& aBuffer,
+ nsTArray<cdm::SubsampleEntry>& aSubSamples,
+ cdm::InputBuffer_2& aInputBuffer) {
+ aInputBuffer.data = aBuffer.mData().get<uint8_t>();
+ aInputBuffer.data_size = aBuffer.mData().Size<uint8_t>();
+
+ if (aBuffer.mEncryptionScheme() != cdm::EncryptionScheme::kUnencrypted) {
+ MOZ_ASSERT(aBuffer.mEncryptionScheme() == cdm::EncryptionScheme::kCenc ||
+ aBuffer.mEncryptionScheme() == cdm::EncryptionScheme::kCbcs);
+ aInputBuffer.key_id = aBuffer.mKeyId().Elements();
+ aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
+
+ aInputBuffer.iv = aBuffer.mIV().Elements();
+ aInputBuffer.iv_size = aBuffer.mIV().Length();
+
+ aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
+ for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
+ aSubSamples.AppendElement(cdm::SubsampleEntry{aBuffer.mClearBytes()[i],
+ aBuffer.mCipherBytes()[i]});
+ }
+ aInputBuffer.subsamples = aSubSamples.Elements();
+ aInputBuffer.num_subsamples = aSubSamples.Length();
+ aInputBuffer.encryption_scheme = aBuffer.mEncryptionScheme();
+ }
+ aInputBuffer.pattern.crypt_byte_block = aBuffer.mCryptByteBlock();
+ aInputBuffer.pattern.skip_byte_block = aBuffer.mSkipByteBlock();
+ aInputBuffer.timestamp = aBuffer.mTimestamp();
+}
+
+bool ChromiumCDMChild::HasShmemOfSize(size_t aSize) const {
+ for (const ipc::Shmem& shmem : mBuffers) {
+ if (shmem.Size<uint8_t>() == aSize) {
+ return true;
+ }
+ }
+ return false;
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvDecrypt(
+ const uint32_t& aId, const CDMInputBuffer& aBuffer) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecrypt()");
+
+ // Parent should have already gifted us a shmem to use as output.
+ size_t outputShmemSize = aBuffer.mData().Size<uint8_t>();
+ MOZ_ASSERT(HasShmemOfSize(outputShmemSize));
+
+ // Ensure we deallocate the shmem used to send input.
+ RefPtr<ChromiumCDMChild> self = this;
+ auto autoDeallocateInputShmem =
+ MakeScopeExit([&, self] { self->DeallocShmem(aBuffer.mData()); });
+
+ // On failure, we need to ensure that the shmem that the parent sent
+ // for the CDM to use to return output back to the parent is deallocated.
+ // Otherwise, it will leak.
+ auto autoDeallocateOutputShmem = MakeScopeExit([self, outputShmemSize] {
+ self->mBuffers.RemoveElementsBy(
+ [outputShmemSize, self](ipc::Shmem& aShmem) {
+ if (aShmem.Size<uint8_t>() != outputShmemSize) {
+ return false;
+ }
+ self->DeallocShmem(aShmem);
+ return true;
+ });
+ });
+
+ if (!mCDM) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecrypt() no CDM");
+ Unused << SendDecryptFailed(aId, cdm::kDecryptError);
+ return IPC_OK();
+ }
+ if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
+ "match");
+ Unused << SendDecryptFailed(aId, cdm::kDecryptError);
+ return IPC_OK();
+ }
+
+ cdm::InputBuffer_2 input = {};
+ nsTArray<cdm::SubsampleEntry> subsamples;
+ InitInputBuffer(aBuffer, subsamples, input);
+
+ WidevineDecryptedBlock output;
+ cdm::Status status = mCDM->Decrypt(input, &output);
+
+ // CDM should have allocated a cdm::Buffer for output.
+ CDMShmemBuffer* buffer =
+ output.DecryptedBuffer()
+ ? static_cast<CDMShmemBuffer*>(output.DecryptedBuffer())
+ : nullptr;
+ MOZ_ASSERT_IF(buffer, buffer->AsShmemBuffer());
+ if (status != cdm::kSuccess || !buffer) {
+ Unused << SendDecryptFailed(aId, status);
+ return IPC_OK();
+ }
+
+ // Success! Return the decrypted sample to parent.
+ MOZ_ASSERT(!HasShmemOfSize(outputShmemSize));
+ ipc::Shmem shmem = buffer->ExtractShmem();
+ if (SendDecrypted(aId, cdm::kSuccess, std::move(shmem))) {
+ // No need to deallocate the output shmem; it should have been returned
+ // to the content process.
+ autoDeallocateOutputShmem.release();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvInitializeVideoDecoder(
+ const CDMVideoDecoderConfig& aConfig) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ MOZ_ASSERT(!mDecoderInitialized);
+ if (!mCDM) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvInitializeVideoDecoder() no CDM");
+ Unused << SendOnDecoderInitDone(cdm::kInitializationError);
+ return IPC_OK();
+ }
+ cdm::VideoDecoderConfig_2 config = {};
+ config.codec = static_cast<cdm::VideoCodec>(aConfig.mCodec());
+ config.profile = static_cast<cdm::VideoCodecProfile>(aConfig.mProfile());
+ config.format = static_cast<cdm::VideoFormat>(aConfig.mFormat());
+ config.coded_size =
+ mCodedSize = {aConfig.mImageWidth(), aConfig.mImageHeight()};
+ nsTArray<uint8_t> extraData(aConfig.mExtraData().Clone());
+ config.extra_data = extraData.Elements();
+ config.extra_data_size = extraData.Length();
+ config.encryption_scheme = aConfig.mEncryptionScheme();
+ cdm::Status status = mCDM->InitializeVideoDecoder(config);
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvInitializeVideoDecoder() status=%u",
+ status);
+ Unused << SendOnDecoderInitDone(status);
+ mDecoderInitialized = status == cdm::kSuccess;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvDeinitializeVideoDecoder() {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
+ MOZ_ASSERT(mDecoderInitialized);
+ if (mDecoderInitialized && mCDM) {
+ mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
+ }
+ mDecoderInitialized = false;
+ PurgeShmems();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvResetVideoDecoder() {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvResetVideoDecoder()");
+ if (mDecoderInitialized && mCDM) {
+ mCDM->ResetDecoder(cdm::kStreamTypeVideo);
+ }
+ Unused << SendResetVideoDecoderComplete();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvDecryptAndDecodeFrame(
+ const CDMInputBuffer& aBuffer) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64 ")",
+ aBuffer.mTimestamp());
+ MOZ_ASSERT(mDecoderInitialized);
+
+ if (!mCDM) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() no CDM");
+ Unused << SendDecodeFailed(cdm::kDecodeError);
+ return IPC_OK();
+ }
+
+ RefPtr<ChromiumCDMChild> self = this;
+ auto autoDeallocateShmem =
+ MakeScopeExit([&, self] { self->DeallocShmem(aBuffer.mData()); });
+
+ // The output frame may not have the same timestamp as the frame we put in.
+ // We may need to input a number of frames before we receive output. The
+ // CDM's decoder reorders to ensure frames output are in presentation order.
+ // So we need to store the durations of the frames input, and retrieve them
+ // on output.
+ mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration());
+
+ cdm::InputBuffer_2 input = {};
+ nsTArray<cdm::SubsampleEntry> subsamples;
+ InitInputBuffer(aBuffer, subsamples, input);
+
+ WidevineVideoFrame frame;
+ cdm::Status rv = mCDM->DecryptAndDecodeFrame(input, &frame);
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64
+ " CDM decoder rv=%d",
+ aBuffer.mTimestamp(), rv);
+
+ switch (rv) {
+ case cdm::kNeedMoreData:
+ Unused << SendDecodeFailed(rv);
+ break;
+ case cdm::kNoKey:
+ GMP_LOG_DEBUG("NoKey for sample at time=%" PRId64 "!", input.timestamp);
+ // Somehow our key became unusable. Typically this would happen when
+ // a stream requires output protection, and the configuration changed
+ // such that output protection is no longer available. For example, a
+ // non-compliant monitor was attached. The JS player should notice the
+ // key status changing to "output-restricted", and is supposed to switch
+ // to a stream that doesn't require OP. In order to keep the playback
+ // pipeline rolling, just output a black frame. See bug 1343140.
+ if (!frame.InitToBlack(mCodedSize.width, mCodedSize.height,
+ input.timestamp)) {
+ Unused << SendDecodeFailed(cdm::kDecodeError);
+ break;
+ }
+ [[fallthrough]];
+ case cdm::kSuccess:
+ if (frame.FrameBuffer()) {
+ ReturnOutput(frame);
+ break;
+ }
+ // CDM didn't set a frame buffer on the sample, report it as an error.
+ [[fallthrough]];
+ default:
+ Unused << SendDecodeFailed(rv);
+ break;
+ }
+
+ return IPC_OK();
+}
+
+void ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ MOZ_ASSERT(aFrame.FrameBuffer());
+ gmp::CDMVideoFrame output;
+ output.mFormat() = static_cast<cdm::VideoFormat>(aFrame.Format());
+ output.mImageWidth() = aFrame.Size().width;
+ output.mImageHeight() = aFrame.Size().height;
+ output.mYPlane() = {aFrame.PlaneOffset(cdm::VideoPlane::kYPlane),
+ aFrame.Stride(cdm::VideoPlane::kYPlane)};
+ output.mUPlane() = {aFrame.PlaneOffset(cdm::VideoPlane::kUPlane),
+ aFrame.Stride(cdm::VideoPlane::kUPlane)};
+ output.mVPlane() = {aFrame.PlaneOffset(cdm::VideoPlane::kVPlane),
+ aFrame.Stride(cdm::VideoPlane::kVPlane)};
+ output.mTimestamp() = aFrame.Timestamp();
+
+ uint64_t duration = 0;
+ if (mFrameDurations.Find(aFrame.Timestamp(), duration)) {
+ output.mDuration() = duration;
+ }
+
+ CDMBuffer* base = reinterpret_cast<CDMBuffer*>(aFrame.FrameBuffer());
+ if (base->AsShmemBuffer()) {
+ ipc::Shmem shmem = base->AsShmemBuffer()->ExtractShmem();
+ Unused << SendDecodedShmem(output, std::move(shmem));
+ } else {
+ MOZ_ASSERT(base->AsArrayBuffer());
+ Unused << SendDecodedData(output, base->AsArrayBuffer()->ExtractBuffer());
+ }
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvDrain() {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ if (!mCDM) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDrain() no CDM");
+ Unused << SendDrainComplete();
+ return IPC_OK();
+ }
+ WidevineVideoFrame frame;
+ cdm::InputBuffer_2 sample = {};
+ cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDrain(); DecryptAndDecodeFrame() rv=%d",
+ rv);
+ if (rv == cdm::kSuccess) {
+ MOZ_ASSERT(frame.Format() != cdm::kUnknownVideoFormat);
+ ReturnOutput(frame);
+ } else {
+ Unused << SendDrainComplete();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvDestroy() {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDestroy()");
+
+ MOZ_ASSERT(!mDecoderInitialized);
+
+ mInitPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+
+ if (mCDM) {
+ mCDM->Destroy();
+ mCDM = nullptr;
+ }
+ mDestroyed = true;
+
+ Unused << Send__delete__(this);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvGiveBuffer(ipc::Shmem&& aBuffer) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+
+ GiveBuffer(std::move(aBuffer));
+ return IPC_OK();
+}
+
+void ChromiumCDMChild::GiveBuffer(ipc::Shmem&& aBuffer) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ size_t sz = aBuffer.Size<uint8_t>();
+ mBuffers.AppendElement(std::move(aBuffer));
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvGiveBuffer(capacity=%zu"
+ ") bufferSizes={%s} mDecoderInitialized=%d",
+ sz, ToString(mBuffers).get(), mDecoderInitialized);
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/ChromiumCDMChild.h b/dom/media/gmp/ChromiumCDMChild.h
new file mode 100644
index 0000000000..1bf153a5d5
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMChild.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 ChromiumCDMChild_h_
+#define ChromiumCDMChild_h_
+
+#include "content_decryption_module.h"
+#include "mozilla/gmp/PChromiumCDMChild.h"
+#include "SimpleMap.h"
+#include "WidevineVideoFrame.h"
+
+namespace mozilla::gmp {
+
+class GMPContentChild;
+
+class ChromiumCDMChild : public PChromiumCDMChild, public cdm::Host_10 {
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PChromiumCDMChild.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMChild, final);
+
+ explicit ChromiumCDMChild(GMPContentChild* aPlugin);
+
+ void Init(cdm::ContentDecryptionModule_10* aCDM,
+ const nsACString& aStorageId);
+
+ void TimerExpired(void* aContext);
+
+ // cdm::Host_10 implementation
+ cdm::Buffer* Allocate(uint32_t aCapacity) override;
+ void SetTimer(int64_t aDelayMs, void* aContext) override;
+ cdm::Time GetCurrentWallTime() override;
+ void OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) override;
+ void OnResolveNewSessionPromise(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void OnResolvePromise(uint32_t aPromiseId) override;
+ void OnRejectPromise(uint32_t aPromiseId, cdm::Exception aException,
+ uint32_t aSystemCode, const char* aErrorMessage,
+ uint32_t aErrorMessageSize) override;
+ void OnSessionMessage(const char* aSessionId, uint32_t aSessionIdSize,
+ cdm::MessageType aMessageType, const char* aMessage,
+ uint32_t aMessageSize) override;
+ void OnSessionKeysChange(const char* aSessionId, uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) override;
+ void OnExpirationChange(const char* aSessionId, uint32_t aSessionIdSize,
+ cdm::Time aNewExpiryTime) override;
+ void OnSessionClosed(const char* aSessionId,
+ uint32_t aSessionIdSize) override;
+ void SendPlatformChallenge(const char* aServiceId, uint32_t aServiceIdSize,
+ const char* aChallenge,
+ uint32_t aChallengeSize) override {}
+ void EnableOutputProtection(uint32_t aDesiredProtectionMask) override {}
+ void QueryOutputProtectionStatus() override;
+ void OnDeferredInitializationDone(cdm::StreamType aStreamType,
+ cdm::Status aDecoderStatus) override {}
+ void RequestStorageId(uint32_t aVersion) override;
+ cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
+ void OnInitialized(bool aSuccess) override;
+ // end cdm::Host_10 specific methods
+
+ void GiveBuffer(ipc::Shmem&& aBuffer);
+
+ protected:
+ ~ChromiumCDMChild();
+
+ bool OnResolveNewSessionPromiseInternal(uint32_t aPromiseId,
+ const nsACString& aSessionId);
+
+ bool IsOnMessageLoopThread();
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ ipc::IPCResult RecvGiveBuffer(ipc::Shmem&& aShmem) override;
+ ipc::IPCResult RecvPurgeShmems() override;
+ void PurgeShmems();
+ ipc::IPCResult RecvInit(const bool& aAllowDistinctiveIdentifier,
+ const bool& aAllowPersistentState,
+ InitResolver&& aResolver) override;
+ ipc::IPCResult RecvSetServerCertificate(
+ const uint32_t& aPromiseId, nsTArray<uint8_t>&& aServerCert) override;
+ ipc::IPCResult RecvCreateSessionAndGenerateRequest(
+ const uint32_t& aPromiseId, const uint32_t& aSessionType,
+ const uint32_t& aInitDataType, nsTArray<uint8_t>&& aInitData) override;
+ ipc::IPCResult RecvLoadSession(const uint32_t& aPromiseId,
+ const uint32_t& aSessionType,
+ const nsACString& aSessionId) override;
+ ipc::IPCResult RecvUpdateSession(const uint32_t& aPromiseId,
+ const nsACString& aSessionId,
+ nsTArray<uint8_t>&& aResponse) override;
+ ipc::IPCResult RecvCloseSession(const uint32_t& aPromiseId,
+ const nsACString& aSessionId) override;
+ ipc::IPCResult RecvRemoveSession(const uint32_t& aPromiseId,
+ const nsACString& aSessionId) override;
+ ipc::IPCResult RecvCompleteQueryOutputProtectionStatus(
+ const bool& aSuccess, const uint32_t& aLinkMask,
+ const uint32_t& aProtectionMask) override;
+ ipc::IPCResult RecvGetStatusForPolicy(
+ const uint32_t& aPromiseId,
+ const cdm::HdcpVersion& aMinHdcpVersion) override;
+ ipc::IPCResult RecvDecrypt(const uint32_t& aId,
+ const CDMInputBuffer& aBuffer) override;
+ ipc::IPCResult RecvInitializeVideoDecoder(
+ const CDMVideoDecoderConfig& aConfig) override;
+ ipc::IPCResult RecvDeinitializeVideoDecoder() override;
+ ipc::IPCResult RecvResetVideoDecoder() override;
+ ipc::IPCResult RecvDecryptAndDecodeFrame(
+ const CDMInputBuffer& aBuffer) override;
+ ipc::IPCResult RecvDrain() override;
+ ipc::IPCResult RecvDestroy() override;
+
+ void ReturnOutput(WidevineVideoFrame& aFrame);
+ bool HasShmemOfSize(size_t aSize) const;
+
+ template <typename MethodType, typename... ParamType>
+ void CallMethod(MethodType, ParamType&&...);
+
+ template <typename MethodType, typename... ParamType>
+ void CallOnMessageLoopThread(const char* const, MethodType, ParamType&&...);
+
+ GMPContentChild* mPlugin = nullptr;
+ cdm::ContentDecryptionModule_10* mCDM = nullptr;
+
+ typedef SimpleMap<uint64_t> DurationMap;
+ DurationMap mFrameDurations;
+ nsTArray<uint32_t> mLoadSessionPromiseIds;
+
+ cdm::Size mCodedSize = {0, 0};
+ nsTArray<ipc::Shmem> mBuffers;
+
+ bool mDecoderInitialized = false;
+ bool mPersistentStateAllowed = false;
+ bool mDestroyed = false;
+ nsCString mStorageId;
+
+ typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> InitPromise;
+ // Created when we RecvInit, should be resolved once the CDM is initialized
+ // or rejected if init fails.
+ MozPromiseHolder<InitPromise> mInitPromise;
+};
+
+} // namespace mozilla::gmp
+
+#endif // ChromiumCDMChild_h_
diff --git a/dom/media/gmp/ChromiumCDMParent.cpp b/dom/media/gmp/ChromiumCDMParent.cpp
new file mode 100644
index 0000000000..ddb1e38d33
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -0,0 +1,1319 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMParent.h"
+
+#include "ChromiumCDMCallback.h"
+#include "ChromiumCDMCallbackProxy.h"
+#include "ChromiumCDMProxy.h"
+#include "content_decryption_module.h"
+#include "GMPContentChild.h"
+#include "GMPContentParent.h"
+#include "GMPLog.h"
+#include "GMPService.h"
+#include "GMPUtils.h"
+#include "VideoUtils.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/Unused.h"
+#include "AnnexB.h"
+#include "H264.h"
+
+#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
+
+namespace mozilla::gmp {
+
+using namespace eme;
+
+ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
+ uint32_t aPluginId)
+ : mPluginId(aPluginId),
+ mContentParent(aContentParent),
+ mVideoShmemLimit(StaticPrefs::media_eme_chromium_api_video_shmems())
+#ifdef DEBUG
+ ,
+ mGMPThread(aContentParent->GMPEventTarget())
+#endif
+{
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, "
+ "id=%" PRIu32 ")",
+ this, aContentParent, aPluginId);
+}
+
+RefPtr<ChromiumCDMParent::InitPromise> ChromiumCDMParent::Init(
+ ChromiumCDMCallback* aCDMCallback, bool aAllowDistinctiveIdentifier,
+ bool aAllowPersistentState, nsIEventTarget* aMainThread) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Init(this=%p) shutdown=%s abormalShutdown=%s "
+ "actorDestroyed=%s",
+ this, mIsShutdown ? "true" : "false",
+ mAbnormalShutdown ? "true" : "false", mActorDestroyed ? "true" : "false");
+ if (!aCDMCallback || !aMainThread) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Init(this=%p) failed "
+ "nullCallback=%s nullMainThread=%s",
+ this, !aCDMCallback ? "true" : "false",
+ !aMainThread ? "true" : "false");
+
+ return ChromiumCDMParent::InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_FAILURE,
+ nsPrintfCString("ChromiumCDMParent::Init() failed "
+ "nullCallback=%s nullMainThread=%s",
+ !aCDMCallback ? "true" : "false",
+ !aMainThread ? "true" : "false")),
+ __func__);
+ }
+
+ RefPtr<ChromiumCDMParent::InitPromise> promise =
+ mInitPromise.Ensure(__func__);
+ RefPtr<ChromiumCDMParent> self = this;
+ SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, aCDMCallback](bool aSuccess) {
+ if (!aSuccess) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Init() failed with callback from "
+ "child indicating CDM failed init");
+ self->mInitPromise.RejectIfExists(
+ MediaResult(NS_ERROR_FAILURE,
+ "ChromiumCDMParent::Init() failed with callback "
+ "from child indicating CDM failed init"),
+ __func__);
+ return;
+ }
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Init() succeeded with callback from child");
+ self->mCDMCallback = aCDMCallback;
+ self->mInitPromise.ResolveIfExists(true /* unused */, __func__);
+ },
+ [self](ResponseRejectReason&& aReason) {
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ bool xpcomWillShutdown =
+ service && service->XPCOMWillShutdownReceived();
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Init(this=%p) failed "
+ "shutdown=%s cdmCrash=%s actorDestroyed=%s "
+ "browserShutdown=%s promiseRejectReason=%d",
+ self.get(), self->mIsShutdown ? "true" : "false",
+ self->mAbnormalShutdown ? "true" : "false",
+ self->mActorDestroyed ? "true" : "false",
+ xpcomWillShutdown ? "true" : "false",
+ static_cast<int>(aReason));
+ self->mInitPromise.RejectIfExists(
+ MediaResult(
+ NS_ERROR_FAILURE,
+ nsPrintfCString("ChromiumCDMParent::Init() failed "
+ "shutdown=%s cdmCrash=%s actorDestroyed=%s "
+ "browserShutdown=%s promiseRejectReason=%d",
+ self->mIsShutdown ? "true" : "false",
+ self->mAbnormalShutdown ? "true" : "false",
+ self->mActorDestroyed ? "true" : "false",
+ xpcomWillShutdown ? "true" : "false",
+ static_cast<int>(aReason))),
+ __func__);
+ });
+ return promise;
+}
+
+void ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aSessionType,
+ uint32_t aInitDataType,
+ uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aInitData) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::CreateSession(this=%p)", this);
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ if (!SendCreateSessionAndGenerateRequest(aPromiseId, aSessionType,
+ aInitDataType, aInitData)) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send generateRequest to CDM process."_ns);
+ return;
+ }
+ mPromiseToCreateSessionToken.InsertOrUpdate(aPromiseId, aCreateSessionToken);
+}
+
+void ChromiumCDMParent::LoadSession(uint32_t aPromiseId, uint32_t aSessionType,
+ nsString aSessionId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::LoadSession(this=%p, pid=%" PRIu32
+ ", type=%" PRIu32 ", sid=%s)",
+ this, aPromiseId, aSessionType,
+ NS_ConvertUTF16toUTF8(aSessionId).get());
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ if (!SendLoadSession(aPromiseId, aSessionType,
+ NS_ConvertUTF16toUTF8(aSessionId))) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send loadSession to CDM process."_ns);
+ return;
+ }
+}
+
+void ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ if (!SendSetServerCertificate(aPromiseId, aCert)) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send setServerCertificate to CDM process"_ns);
+ }
+}
+
+void ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
+ uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aResponse) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::UpdateSession(this=%p)", this);
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send updateSession to CDM process"_ns);
+ }
+}
+
+void ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
+ uint32_t aPromiseId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::CloseSession(this=%p)", this);
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ if (!SendCloseSession(aPromiseId, aSessionId)) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send closeSession to CDM process"_ns);
+ }
+}
+
+void ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
+ uint32_t aPromiseId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RemoveSession(this=%p)", this);
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ if (!SendRemoveSession(aPromiseId, aSessionId)) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send removeSession to CDM process"_ns);
+ }
+}
+
+void ChromiumCDMParent::NotifyOutputProtectionStatus(bool aSuccess,
+ uint32_t aLinkMask,
+ uint32_t aProtectionMask) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::NotifyOutputProtectionStatus(this=%p)",
+ this);
+ if (mIsShutdown) {
+ return;
+ }
+ const bool haveCachedValue = mOutputProtectionLinkMask.isSome();
+ if (mAwaitingOutputProtectionInformation && !aSuccess) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !haveCachedValue,
+ "Should not have a cached value if we're still awaiting infomation");
+ // We're awaiting info and don't yet have a cached value, and the check
+ // failed, don't cache the result, just forward the failure.
+ CompleteQueryOutputProtectionStatus(false, aLinkMask, aProtectionMask);
+ return;
+ }
+ if (!mAwaitingOutputProtectionInformation && haveCachedValue && !aSuccess) {
+ // We're not awaiting info, already have a cached value, and the check
+ // failed. Ignore this, we'll update our info from any future successful
+ // checks.
+ return;
+ }
+ MOZ_ASSERT(aSuccess, "Failed checks should be handled by this point");
+ // Update our protection information.
+ mOutputProtectionLinkMask = Some(aLinkMask);
+
+ if (mAwaitingOutputProtectionInformation) {
+ // If we have an outstanding query, service that query with this
+ // information.
+ mAwaitingOutputProtectionInformation = false;
+ MOZ_ASSERT(!haveCachedValue,
+ "If we were waiting on information, we shouldn't have yet "
+ "cached a value");
+ CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
+ aProtectionMask);
+ }
+}
+
+void ChromiumCDMParent::CompleteQueryOutputProtectionStatus(
+ bool aSuccess, uint32_t aLinkMask, uint32_t aProtectionMask) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::CompleteQueryOutputProtectionStatus(this=%p) "
+ "mIsShutdown=%s aSuccess=%s aLinkMask=%" PRIu32,
+ this, mIsShutdown ? "true" : "false", aSuccess ? "true" : "false",
+ aLinkMask);
+ if (mIsShutdown) {
+ return;
+ }
+ Unused << SendCompleteQueryOutputProtectionStatus(aSuccess, aLinkMask,
+ aProtectionMask);
+}
+
+// See
+// https://cs.chromium.org/chromium/src/media/blink/webcontentdecryptionmodule_impl.cc?l=33-66&rcl=d49aa59ac8c2925d5bec229f3f1906537b6b4547
+static Result<cdm::HdcpVersion, nsresult> ToCDMHdcpVersion(
+ const nsCString& aMinHdcpVersion) {
+ if (aMinHdcpVersion.IsEmpty()) {
+ return cdm::HdcpVersion::kHdcpVersionNone;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("1.0")) {
+ return cdm::HdcpVersion::kHdcpVersion1_0;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("1.1")) {
+ return cdm::HdcpVersion::kHdcpVersion1_1;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("1.2")) {
+ return cdm::HdcpVersion::kHdcpVersion1_2;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("1.3")) {
+ return cdm::HdcpVersion::kHdcpVersion1_3;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("1.4")) {
+ return cdm::HdcpVersion::kHdcpVersion1_4;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("2.0")) {
+ return cdm::HdcpVersion::kHdcpVersion2_0;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("2.1")) {
+ return cdm::HdcpVersion::kHdcpVersion2_1;
+ }
+ if (aMinHdcpVersion.EqualsIgnoreCase("2.2")) {
+ return cdm::HdcpVersion::kHdcpVersion2_2;
+ }
+ // When adding another version remember to update GMPMessageUtils so that we
+ // can serialize it correctly and have correct bounds on the enum!
+
+ // Invalid hdcp version string.
+ return Err(NS_ERROR_INVALID_ARG);
+}
+
+void ChromiumCDMParent::GetStatusForPolicy(uint32_t aPromiseId,
+ const nsCString& aMinHdcpVersion) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::GetStatusForPolicy(this=%p)", this);
+ if (mIsShutdown) {
+ RejectPromiseShutdown(aPromiseId);
+ return;
+ }
+ auto hdcpVersionResult = ToCDMHdcpVersion(aMinHdcpVersion);
+ if (hdcpVersionResult.isErr()) {
+ ErrorResult rv;
+ // XXXbz there's no spec for this yet, and
+ // <https://github.com/WICG/hdcp-detection/blob/master/explainer.md>
+ // does not define what exceptions get thrown. Let's assume
+ // TypeError for invalid args, as usual.
+ constexpr auto err =
+ "getStatusForPolicy failed due to bad hdcp version argument"_ns;
+ rv.ThrowTypeError(err);
+ RejectPromise(aPromiseId, std::move(rv), err);
+ return;
+ }
+
+ if (!SendGetStatusForPolicy(aPromiseId, hdcpVersionResult.unwrap())) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Failed to send getStatusForPolicy to CDM process"_ns);
+ }
+}
+
+bool ChromiumCDMParent::InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer,
+ MediaRawData* aSample) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ const CryptoSample& crypto = aSample->mCrypto;
+ if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
+ GMP_LOG_DEBUG("InitCDMInputBuffer clear/cipher subsamples don't match");
+ return false;
+ }
+
+ Shmem shmem;
+ if (!AllocShmem(aSample->Size(), &shmem)) {
+ return false;
+ }
+ memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size());
+ cdm::EncryptionScheme encryptionScheme = cdm::EncryptionScheme::kUnencrypted;
+ switch (crypto.mCryptoScheme) {
+ case CryptoScheme::None:
+ break; // Default to none
+ case CryptoScheme::Cenc:
+ encryptionScheme = cdm::EncryptionScheme::kCenc;
+ break;
+ case CryptoScheme::Cbcs:
+ encryptionScheme = cdm::EncryptionScheme::kCbcs;
+ break;
+ default:
+ GMP_LOG_DEBUG(
+ "InitCDMInputBuffer got unexpected encryption scheme with "
+ "value of %" PRIu8 ". Treating as no encryption.",
+ static_cast<uint8_t>(crypto.mCryptoScheme));
+ MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
+ break;
+ }
+
+ const nsTArray<uint8_t>& iv = encryptionScheme != cdm::EncryptionScheme::kCbcs
+ ? crypto.mIV
+ : crypto.mConstantIV;
+ aBuffer = gmp::CDMInputBuffer(
+ shmem, crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(),
+ aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
+ crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock,
+ encryptionScheme);
+ return true;
+}
+
+bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32,
+ aSizeInBytes);
+ Shmem shmem;
+ if (!AllocShmem(aSizeInBytes, &shmem)) {
+ return false;
+ }
+ if (!SendGiveBuffer(std::move(shmem))) {
+ DeallocShmem(shmem);
+ return false;
+ }
+ return true;
+}
+
+RefPtr<DecryptPromise> ChromiumCDMParent::Decrypt(MediaRawData* aSample) {
+ if (mIsShutdown) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+ __func__);
+ }
+ CDMInputBuffer buffer;
+ if (!InitCDMInputBuffer(buffer, aSample)) {
+ return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+ __func__);
+ }
+ // Send a buffer to the CDM to store the output. The CDM will either fill
+ // it with the decrypted sample and return it, or deallocate it on failure.
+ if (!SendBufferToCDM(aSample->Size())) {
+ DeallocShmem(buffer.mData());
+ return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+ __func__);
+ }
+
+ RefPtr<DecryptJob> job = new DecryptJob(aSample);
+ if (!SendDecrypt(job->mId, buffer)) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
+ this);
+ DeallocShmem(buffer.mData());
+ return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+ __func__);
+ }
+ RefPtr<DecryptPromise> promise = job->Ensure();
+ mDecrypts.AppendElement(job);
+ return promise;
+}
+
+ipc::IPCResult ChromiumCDMParent::Recv__delete__() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ MOZ_ASSERT(mIsShutdown);
+ GMP_LOG_DEBUG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
+ if (mContentParent) {
+ mContentParent->ChromiumCDMDestroyed(this);
+ mContentParent = nullptr;
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(
+ const uint32_t& aPromiseId, const uint32_t& aKeyStatus) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(this=%p, "
+ "pid=%" PRIu32 ", keystatus=%" PRIu32 ")",
+ this, aPromiseId, aKeyStatus);
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ mCDMCallback->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus);
+
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnResolveNewSessionPromise(
+ const uint32_t& aPromiseId, const nsCString& aSessionId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%" PRIu32
+ ", sid=%s)",
+ this, aPromiseId, aSessionId.get());
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ Maybe<uint32_t> token = mPromiseToCreateSessionToken.Extract(aPromiseId);
+ if (token.isNothing()) {
+ RejectPromiseWithStateError(aPromiseId,
+ "Lost session token for new session."_ns);
+ return IPC_OK();
+ }
+
+ mCDMCallback->SetSessionId(token.value(), aSessionId);
+
+ ResolvePromise(aPromiseId);
+
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvResolveLoadSessionPromise(
+ const uint32_t& aPromiseId, const bool& aSuccessful) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%" PRIu32
+ ", successful=%d)",
+ this, aPromiseId, aSuccessful);
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful);
+
+ return IPC_OK();
+}
+
+void ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::ResolvePromise(this=%p, pid=%" PRIu32 ")",
+ this, aPromiseId);
+
+ // Note: The MediaKeys rejects all pending DOM promises when it
+ // initiates shutdown.
+ if (!mCDMCallback || mIsShutdown) {
+ return;
+ }
+
+ mCDMCallback->ResolvePromise(aPromiseId);
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise(
+ const uint32_t& aPromiseId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ ResolvePromise(aPromiseId);
+ return IPC_OK();
+}
+
+void ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
+ ErrorResult&& aException,
+ const nsCString& aErrorMessage) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RejectPromise(this=%p, pid=%" PRIu32 ")",
+ this, aPromiseId);
+ // Note: The MediaKeys rejects all pending DOM promises when it
+ // initiates shutdown.
+ if (!mCDMCallback || mIsShutdown) {
+ // Suppress the exception as it will not be explicitly handled due to the
+ // early return.
+ aException.SuppressException();
+ return;
+ }
+
+ mCDMCallback->RejectPromise(aPromiseId, std::move(aException), aErrorMessage);
+}
+
+void ChromiumCDMParent::RejectPromiseShutdown(uint32_t aPromiseId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ RejectPromiseWithStateError(aPromiseId, "CDM is shutdown"_ns);
+}
+
+void ChromiumCDMParent::RejectPromiseWithStateError(
+ uint32_t aPromiseId, const nsCString& aErrorMessage) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(aErrorMessage);
+ RejectPromise(aPromiseId, std::move(rv), aErrorMessage);
+}
+
+static ErrorResult ToErrorResult(uint32_t aException,
+ const nsCString& aErrorMessage) {
+ // XXXbz could we have a CopyableErrorResult sent to us with a better error
+ // message?
+ ErrorResult rv;
+ switch (static_cast<cdm::Exception>(aException)) {
+ case cdm::Exception::kExceptionNotSupportedError:
+ rv.ThrowNotSupportedError(aErrorMessage);
+ break;
+ case cdm::Exception::kExceptionInvalidStateError:
+ rv.ThrowInvalidStateError(aErrorMessage);
+ break;
+ case cdm::Exception::kExceptionTypeError:
+ rv.ThrowTypeError(aErrorMessage);
+ break;
+ case cdm::Exception::kExceptionQuotaExceededError:
+ rv.ThrowQuotaExceededError(aErrorMessage);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid cdm::Exception enum value.");
+ // Note: Unique placeholder.
+ rv.ThrowTimeoutError(aErrorMessage);
+ };
+ return rv;
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise(
+ const uint32_t& aPromiseId, const uint32_t& aException,
+ const uint32_t& aSystemCode, const nsCString& aErrorMessage) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ RejectPromise(aPromiseId, ToErrorResult(aException, aErrorMessage),
+ aErrorMessage);
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage(
+ const nsCString& aSessionId, const uint32_t& aMessageType,
+ nsTArray<uint8_t>&& aMessage) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
+ this, aSessionId.get());
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ mCDMCallback->SessionMessage(aSessionId, aMessageType, std::move(aMessage));
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange(
+ const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ mCDMCallback->SessionKeysChange(aSessionId, std::move(aKeysInfo));
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange(
+ const nsCString& aSessionId, const double& aSecondsSinceEpoch) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
+ this, aSecondsSinceEpoch);
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch);
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed(
+ const nsCString& aSessionId) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
+ if (!mCDMCallback || mIsShutdown) {
+ return IPC_OK();
+ }
+
+ mCDMCallback->SessionClosed(aSessionId);
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnQueryOutputProtectionStatus() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::RecvOnQueryOutputProtectionStatus(this=%p) "
+ "mIsShutdown=%s mCDMCallback=%s mAwaitingOutputProtectionInformation=%s",
+ this, mIsShutdown ? "true" : "false", mCDMCallback ? "true" : "false",
+ mAwaitingOutputProtectionInformation ? "true" : "false");
+ if (mIsShutdown) {
+ // We're shutdown, don't try to service the query.
+ return IPC_OK();
+ }
+ if (!mCDMCallback) {
+ // We don't have a callback. We're not yet outputting anything so can report
+ // we're safe.
+ CompleteQueryOutputProtectionStatus(true, uint32_t{}, uint32_t{});
+ return IPC_OK();
+ }
+
+ if (mOutputProtectionLinkMask.isSome()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mAwaitingOutputProtectionInformation,
+ "If we have a cached value we should not be awaiting information");
+ // We have a cached value, use that.
+ CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
+ uint32_t{});
+ return IPC_OK();
+ }
+
+ // We need to call up the stack to get the info. The CDM proxy will finish
+ // the request via `NotifyOutputProtectionStatus`.
+ mAwaitingOutputProtectionInformation = true;
+ mCDMCallback->QueryOutputProtectionStatus();
+ return IPC_OK();
+}
+
+DecryptStatus ToDecryptStatus(uint32_t aStatus) {
+ switch (static_cast<cdm::Status>(aStatus)) {
+ case cdm::kSuccess:
+ return DecryptStatus::Ok;
+ case cdm::kNoKey:
+ return DecryptStatus::NoKeyErr;
+ default:
+ return DecryptStatus::GenericErr;
+ }
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId,
+ const uint32_t& aStatus) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptFailed(this=%p, id=%" PRIu32
+ ", status=%" PRIu32 ")",
+ this, aId, aStatus);
+
+ if (mIsShutdown) {
+ MOZ_ASSERT(mDecrypts.IsEmpty());
+ return IPC_OK();
+ }
+
+ for (size_t i = 0; i < mDecrypts.Length(); i++) {
+ if (mDecrypts[i]->mId == aId) {
+ mDecrypts[i]->PostResult(ToDecryptStatus(aStatus));
+ mDecrypts.RemoveElementAt(i);
+ break;
+ }
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
+ const uint32_t& aStatus,
+ ipc::Shmem&& aShmem) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%" PRIu32
+ ", status=%" PRIu32 ")",
+ this, aId, aStatus);
+
+ // We must deallocate the shmem once we've copied the result out of it
+ // in PostResult below.
+ auto autoDeallocateShmem = MakeScopeExit([&, this] { DeallocShmem(aShmem); });
+
+ if (mIsShutdown) {
+ MOZ_ASSERT(mDecrypts.IsEmpty());
+ return IPC_OK();
+ }
+ for (size_t i = 0; i < mDecrypts.Length(); i++) {
+ if (mDecrypts[i]->mId == aId) {
+ mDecrypts[i]->PostResult(
+ ToDecryptStatus(aStatus),
+ Span<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
+ mDecrypts.RemoveElementAt(i);
+ break;
+ }
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvIncreaseShmemPoolSize() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32, __func__, this,
+ mVideoShmemLimit, mVideoShmemsActive);
+
+ // Put an upper limit on the number of shmems we tolerate the CDM asking
+ // for, to prevent a memory blow-out. In practice, we expect the CDM to
+ // need less than 5, but some encodings require more.
+ // We'd expect CDMs to not have video frames larger than 720p-1080p
+ // (due to DRM robustness requirements), which is about 1.5MB-3MB per
+ // frame.
+ if (mVideoShmemLimit > 50) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
+ __func__);
+ Shutdown();
+ return IPC_OK();
+ }
+ mVideoShmemLimit++;
+
+ EnsureSufficientShmems(mVideoFrameBufferSize);
+
+ return IPC_OK();
+}
+
+bool ChromiumCDMParent::PurgeShmems() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%zu"
+ " limit=%" PRIu32 " active=%" PRIu32,
+ this, mVideoFrameBufferSize, mVideoShmemLimit, mVideoShmemsActive);
+
+ if (mVideoShmemsActive == 0) {
+ // We haven't allocated any shmems, nothing to do here.
+ return true;
+ }
+ if (!SendPurgeShmems()) {
+ return false;
+ }
+ mVideoShmemsActive = 0;
+ return true;
+}
+
+bool ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
+ "size=%zu expected_size=%zu limit=%" PRIu32 " active=%" PRIu32,
+ this, aVideoFrameSize, mVideoFrameBufferSize, mVideoShmemLimit,
+ mVideoShmemsActive);
+
+ // The Chromium CDM API requires us to implement a synchronous
+ // interface to supply buffers to the CDM for it to write decrypted samples
+ // into. We want our buffers to be backed by shmems, in order to reduce
+ // the overhead of transferring decoded frames. However due to sandboxing
+ // restrictions, the CDM process cannot allocate shmems itself.
+ // We don't want to be doing synchronous IPC to request shmems from the
+ // content process, nor do we want to have to do intr IPC or make async
+ // IPC conform to the sync allocation interface. So instead we have the
+ // content process pre-allocate a set of shmems and give them to the CDM
+ // process in advance of them being needed.
+ //
+ // When the CDM needs to allocate a buffer for storing a decoded video
+ // frame, the CDM host gives it one of these shmems' buffers. When this
+ // is sent back to the content process, we upload it to a GPU surface,
+ // and send the shmem back to the CDM process so it can reuse it.
+ //
+ // Normally the CDM won't allocate more than one buffer at once, but
+ // we've seen cases where it allocates multiple buffers, returns one and
+ // holds onto the rest. So we need to ensure we have several extra
+ // shmems pre-allocated for the CDM. This threshold is set by the pref
+ // media.eme.chromium-api.video-shmems.
+ //
+ // We also have a failure recovery mechanism; if the CDM asks for more
+ // buffers than we have shmem's available, ChromiumCDMChild gives the
+ // CDM a non-shared memory buffer, and returns the frame to the parent
+ // in an nsTArray<uint8_t> instead of a shmem. The child then sends a
+ // message to the parent asking it to increase the number of shmems in
+ // the pool. Via this mechanism we should recover from incorrectly
+ // predicting how many shmems to pre-allocate.
+ //
+ // At decoder start up, we guess how big the shmems need to be based on
+ // the video frame dimensions. If we guess wrong, the CDM will follow
+ // the non-shmem path, and we'll re-create the shmems of the correct size.
+ // This meanns we can recover from guessing the shmem size wrong.
+ // We must re-take this path after every decoder de-init/re-init, as the
+ // frame sizes should change every time we switch video stream.
+
+ if (mVideoFrameBufferSize < aVideoFrameSize) {
+ if (!PurgeShmems()) {
+ return false;
+ }
+ mVideoFrameBufferSize = aVideoFrameSize;
+ }
+
+ while (mVideoShmemsActive < mVideoShmemLimit) {
+ if (!SendBufferToCDM(mVideoFrameBufferSize)) {
+ return false;
+ }
+ mVideoShmemsActive++;
+ }
+
+ return true;
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
+ nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64,
+ this, aFrame.mTimestamp());
+
+ if (mIsShutdown || mDecodePromise.IsEmpty()) {
+ return IPC_OK();
+ }
+
+ if (!EnsureSufficientShmems(aData.Length())) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
+ __func__);
+ return IPC_OK();
+ }
+
+ RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
+ if (!v) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Can't create VideoData")),
+ __func__);
+ return IPC_OK();
+ }
+
+ ReorderAndReturnOutput(std::move(v));
+
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
+ ipc::Shmem&& aShmem) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedShmem(this=%p) time=%" PRId64
+ " duration=%" PRId64,
+ this, aFrame.mTimestamp(), aFrame.mDuration());
+
+ // On failure we need to deallocate the shmem we're to return to the
+ // CDM. On success we return it to the CDM to be reused.
+ auto autoDeallocateShmem =
+ MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
+
+ if (mIsShutdown || mDecodePromise.IsEmpty()) {
+ return IPC_OK();
+ }
+
+ RefPtr<VideoData> v = CreateVideoFrame(
+ aFrame, Span<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
+ if (!v) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Can't create VideoData")),
+ __func__);
+ return IPC_OK();
+ }
+
+ // Return the shmem to the CDM so the shmem can be reused to send us
+ // another frame.
+ if (!SendGiveBuffer(std::move(aShmem))) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Can't return shmem to CDM process")),
+ __func__);
+ return IPC_OK();
+ }
+
+ // Don't need to deallocate the shmem since the CDM process is responsible
+ // for it again.
+ autoDeallocateShmem.release();
+
+ ReorderAndReturnOutput(std::move(v));
+
+ return IPC_OK();
+}
+
+void ChromiumCDMParent::ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ if (mMaxRefFrames == 0) {
+ mDecodePromise.ResolveIfExists(
+ MediaDataDecoder::DecodedData({std::move(aFrame)}), __func__);
+ return;
+ }
+ mReorderQueue.Push(std::move(aFrame));
+ MediaDataDecoder::DecodedData results;
+ while (mReorderQueue.Length() > mMaxRefFrames) {
+ results.AppendElement(mReorderQueue.Pop());
+ }
+ mDecodePromise.Resolve(std::move(results), __func__);
+}
+
+already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame(
+ const CDMVideoFrame& aFrame, Span<uint8_t> aData) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ VideoData::YCbCrBuffer b;
+ MOZ_ASSERT(aData.Length() > 0);
+
+ // Since we store each plane separately we can just roll the offset
+ // into our pointer to that plane and store that.
+ b.mPlanes[0].mData = aData.Elements() + aFrame.mYPlane().mPlaneOffset();
+ b.mPlanes[0].mWidth = aFrame.mImageWidth();
+ b.mPlanes[0].mHeight = aFrame.mImageHeight();
+ b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
+ b.mPlanes[0].mSkip = 0;
+
+ b.mPlanes[1].mData = aData.Elements() + aFrame.mUPlane().mPlaneOffset();
+ b.mPlanes[1].mWidth = (aFrame.mImageWidth() + 1) / 2;
+ b.mPlanes[1].mHeight = (aFrame.mImageHeight() + 1) / 2;
+ b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
+ b.mPlanes[1].mSkip = 0;
+
+ b.mPlanes[2].mData = aData.Elements() + aFrame.mVPlane().mPlaneOffset();
+ b.mPlanes[2].mWidth = (aFrame.mImageWidth() + 1) / 2;
+ b.mPlanes[2].mHeight = (aFrame.mImageHeight() + 1) / 2;
+ b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
+ b.mPlanes[2].mSkip = 0;
+
+ b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+
+ // We unfortunately can't know which colorspace the video is using at this
+ // stage.
+ b.mYUVColorSpace =
+ DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()});
+
+ gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
+ RefPtr<VideoData> v = VideoData::CreateAndCopyData(
+ mVideoInfo, mImageContainer, mLastStreamOffset,
+ media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()),
+ media::TimeUnit::FromMicroseconds(aFrame.mDuration()), b, false,
+ media::TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor);
+
+ if (!v || !v->mImage) {
+ NS_WARNING("Failed to decode video frame.");
+ return v.forget();
+ }
+
+ // This is a DRM image.
+ v->mImage->SetIsDRM(true);
+
+ return v.forget();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32
+ ")",
+ this, aStatus);
+ if (mIsShutdown) {
+ MOZ_ASSERT(mDecodePromise.IsEmpty());
+ return IPC_OK();
+ }
+
+ if (aStatus == cdm::kNeedMoreData) {
+ mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
+ return IPC_OK();
+ }
+
+ mDecodePromise.RejectIfExists(
+ MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL(
+ "ChromiumCDMParent::RecvDecodeFailed with status %s (%" PRIu32
+ ")",
+ CdmStatusToString(aStatus), aStatus)),
+ __func__);
+ return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvShutdown() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
+ Shutdown();
+ return IPC_OK();
+}
+
+void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this,
+ aWhy);
+ MOZ_ASSERT(!mActorDestroyed);
+ mActorDestroyed = true;
+ // Shutdown() will clear mCDMCallback, so let's keep a reference for later
+ // use.
+ auto callback = mCDMCallback;
+ if (!mIsShutdown) {
+ // Plugin crash.
+ MOZ_ASSERT(aWhy == AbnormalShutdown);
+ Shutdown();
+ }
+ MOZ_ASSERT(mIsShutdown);
+ RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
+ if (mContentParent) {
+ mContentParent->ChromiumCDMDestroyed(this);
+ mContentParent = nullptr;
+ }
+ mAbnormalShutdown = (aWhy == AbnormalShutdown);
+ if (mAbnormalShutdown && callback) {
+ callback->Terminated();
+ }
+ MaybeDisconnect(mAbnormalShutdown);
+}
+
+RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
+ const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
+ RefPtr<layers::ImageContainer> aImageContainer,
+ RefPtr<layers::KnowsCompositor> aKnowsCompositor) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ if (mIsShutdown) {
+ return MediaDataDecoder::InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+ }
+
+ // The Widevine CDM version 1.4.8.970 and above contain a video decoder that
+ // does not optimally allocate video frames; it requests buffers much larger
+ // than required. The exact formula the CDM uses to calculate their frame
+ // sizes isn't obvious, but they normally request around or slightly more
+ // than 1.5X the optimal amount. So pad the size of buffers we allocate so
+ // that we're likely to have buffers big enough to accomodate the CDM's weird
+ // frame size calculation.
+ const size_t bufferSize =
+ 1.7 * I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
+ if (bufferSize <= 0) {
+ return MediaDataDecoder::InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Video frame buffer size is invalid.")),
+ __func__);
+ }
+
+ if (!EnsureSufficientShmems(bufferSize)) {
+ return MediaDataDecoder::InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failed to init shmems for video decoder")),
+ __func__);
+ }
+
+ if (!SendInitializeVideoDecoder(aConfig)) {
+ return MediaDataDecoder::InitPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failed to send init video decoder to CDM")),
+ __func__);
+ }
+
+ mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264)
+ ? H264::HasSPS(aInfo.mExtraData)
+ ? H264::ComputeMaxRefFrames(aInfo.mExtraData)
+ : 16
+ : 0;
+
+ mVideoDecoderInitialized = true;
+ mImageContainer = aImageContainer;
+ mKnowsCompositor = aKnowsCompositor;
+ mVideoInfo = aInfo;
+ mVideoFrameBufferSize = bufferSize;
+
+ return mInitVideoDecoderPromise.Ensure(__func__);
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone(
+ const uint32_t& aStatus) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%" PRIu32 ")",
+ this, aStatus);
+ if (mIsShutdown) {
+ MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
+ return IPC_OK();
+ }
+ if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
+ mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
+ } else {
+ mVideoDecoderInitialized = false;
+ mInitVideoDecoderPromise.RejectIfExists(
+ MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("CDM init decode failed with status %s (%" PRIu32 ")",
+ CdmStatusToString(aStatus), aStatus)),
+ __func__);
+ }
+ return IPC_OK();
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample) {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ if (mIsShutdown) {
+ return MediaDataDecoder::DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+ }
+
+ GMP_LOG_DEBUG("ChromiumCDMParent::DecryptAndDecodeFrame t=%" PRId64,
+ aSample->mTime.ToMicroseconds());
+
+ CDMInputBuffer buffer;
+
+ if (!InitCDMInputBuffer(buffer, aSample)) {
+ return MediaDataDecoder::DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
+ __func__);
+ }
+
+ mLastStreamOffset = aSample->mOffset;
+
+ if (!SendDecryptAndDecodeFrame(buffer)) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
+ this);
+ DeallocShmem(buffer.mData());
+ return MediaDataDecoder::DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ "Failed to send decrypt to CDM process."),
+ __func__);
+ }
+
+ return mDecodePromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMParent::FlushVideoDecoder() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ if (mIsShutdown) {
+ MOZ_ASSERT(mReorderQueue.IsEmpty());
+ return MediaDataDecoder::FlushPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+ }
+
+ mReorderQueue.Clear();
+
+ mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ if (!SendResetVideoDecoder()) {
+ return MediaDataDecoder::FlushPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ "Failed to send flush to CDM."),
+ __func__);
+ }
+ return mFlushDecoderPromise.Ensure(__func__);
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvResetVideoDecoderComplete() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ MOZ_ASSERT(mReorderQueue.IsEmpty());
+ if (mIsShutdown) {
+ MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
+ return IPC_OK();
+ }
+ mFlushDecoderPromise.ResolveIfExists(true, __func__);
+ return IPC_OK();
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMParent::Drain() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
+ if (mIsShutdown) {
+ return MediaDataDecoder::DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+ }
+
+ RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
+ if (!SendDrain()) {
+ mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
+ }
+ return p;
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDrainComplete() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ if (mIsShutdown) {
+ MOZ_ASSERT(mDecodePromise.IsEmpty());
+ return IPC_OK();
+ }
+
+ MediaDataDecoder::DecodedData samples;
+ while (!mReorderQueue.IsEmpty()) {
+ samples.AppendElement(mReorderQueue.Pop());
+ }
+
+ mDecodePromise.ResolveIfExists(std::move(samples), __func__);
+ return IPC_OK();
+}
+RefPtr<ShutdownPromise> ChromiumCDMParent::ShutdownVideoDecoder() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ if (mIsShutdown || !mVideoDecoderInitialized) {
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+ }
+ mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
+ __func__);
+ mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
+ if (!SendDeinitializeVideoDecoder()) {
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+ }
+ mVideoDecoderInitialized = false;
+
+ GMP_LOG_DEBUG("ChromiumCDMParent::~ShutdownVideoDecoder(this=%p) ", this);
+
+ // The ChromiumCDMChild will purge its shmems, so if the decoder is
+ // reinitialized the shmems need to be re-allocated, and they may need
+ // to be a different size.
+ mVideoShmemsActive = 0;
+ mVideoFrameBufferSize = 0;
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+}
+
+void ChromiumCDMParent::Shutdown() {
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+ GMP_LOG_DEBUG("ChromiumCDMParent::Shutdown(this=%p)", this);
+
+ if (mIsShutdown) {
+ return;
+ }
+ mIsShutdown = true;
+
+ // If we're shutting down due to the plugin shutting down due to application
+ // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
+ // proxy will shutdown when the owning MediaKeys is destroyed during cycle
+ // collection, and that will not shut down cleanly as the GMP thread will be
+ // shutdown by then.
+ if (mCDMCallback) {
+ mCDMCallback->Shutdown();
+ }
+
+ // We may be called from a task holding the last reference to the CDM
+ // callback, so let's clear our local weak pointer to ensure it will not be
+ // used afterward (including from an already-queued task, e.g.: ActorDestroy).
+ mCDMCallback = nullptr;
+
+ mReorderQueue.Clear();
+
+ for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
+ decrypt->PostResult(eme::AbortedErr);
+ }
+ mDecrypts.Clear();
+
+ if (mVideoDecoderInitialized && !mActorDestroyed) {
+ Unused << SendDeinitializeVideoDecoder();
+ mVideoDecoderInitialized = false;
+ }
+
+ // Note: MediaKeys rejects all outstanding promises when it initiates
+ // shutdown.
+ mPromiseToCreateSessionToken.Clear();
+
+ mInitPromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_ABORT_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+
+ mInitVideoDecoderPromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+ mFlushDecoderPromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+ __func__);
+
+ if (!mActorDestroyed) {
+ Unused << SendDestroy();
+ }
+}
+
+} // namespace mozilla::gmp
+
+#undef NS_DispatchToMainThread
diff --git a/dom/media/gmp/ChromiumCDMParent.h b/dom/media/gmp/ChromiumCDMParent.h
new file mode 100644
index 0000000000..b6c28fb1c5
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 ChromiumCDMParent_h_
+#define ChromiumCDMParent_h_
+
+#include "DecryptJob.h"
+#include "GMPCrashHelper.h"
+#include "GMPCrashHelperHolder.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/gmp/PChromiumCDMParent.h"
+#include "mozilla/RefPtr.h"
+#include "nsTHashMap.h"
+#include "PlatformDecoderModule.h"
+#include "ImageContainer.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "ReorderQueue.h"
+
+class ChromiumCDMCallback;
+
+namespace mozilla {
+
+class ErrorResult;
+class MediaRawData;
+class ChromiumCDMProxy;
+
+namespace gmp {
+
+class GMPContentParent;
+
+/**
+ * ChromiumCDMParent is the content process IPC actor used to communicate with a
+ * CDM in the GMP process (where ChromiumCDMChild lives). All non-static
+ * members of this class are GMP thread only.
+ */
+class ChromiumCDMParent final : public PChromiumCDMParent,
+ public GMPCrashHelperHolder {
+ friend class PChromiumCDMParent;
+
+ public:
+ typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> InitPromise;
+
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PChromiumCDMParent.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMParent, final)
+
+ ChromiumCDMParent(GMPContentParent* aContentParent, uint32_t aPluginId);
+
+ uint32_t PluginId() const { return mPluginId; }
+
+ RefPtr<InitPromise> Init(ChromiumCDMCallback* aCDMCallback,
+ bool aAllowDistinctiveIdentifier,
+ bool aAllowPersistentState,
+ nsIEventTarget* aMainThread);
+
+ void CreateSession(uint32_t aCreateSessionToken, uint32_t aSessionType,
+ uint32_t aInitDataType, uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aInitData);
+
+ void LoadSession(uint32_t aPromiseId, uint32_t aSessionType,
+ nsString aSessionId);
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aCert);
+
+ void UpdateSession(const nsCString& aSessionId, uint32_t aPromiseId,
+ const nsTArray<uint8_t>& aResponse);
+
+ void CloseSession(const nsCString& aSessionId, uint32_t aPromiseId);
+
+ void RemoveSession(const nsCString& aSessionId, uint32_t aPromiseId);
+
+ // Notifies this parent of the current output protection status. This will
+ // update cached status and resolve outstanding queries from the CDM if one
+ // exists.
+ void NotifyOutputProtectionStatus(bool aSuccess, uint32_t aLinkMask,
+ uint32_t aProtectionMask);
+
+ void GetStatusForPolicy(uint32_t aPromiseId,
+ const nsCString& aMinHdcpVersion);
+
+ RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample);
+
+ // TODO: Add functions for clients to send data to CDM, and
+ // a Close() function.
+ RefPtr<MediaDataDecoder::InitPromise> InitializeVideoDecoder(
+ const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
+ RefPtr<layers::ImageContainer> aImageContainer,
+ RefPtr<layers::KnowsCompositor> aKnowsCompositor);
+
+ RefPtr<MediaDataDecoder::DecodePromise> DecryptAndDecodeFrame(
+ MediaRawData* aSample);
+
+ RefPtr<MediaDataDecoder::FlushPromise> FlushVideoDecoder();
+
+ RefPtr<MediaDataDecoder::DecodePromise> Drain();
+
+ RefPtr<ShutdownPromise> ShutdownVideoDecoder();
+
+ void Shutdown();
+
+ protected:
+ ~ChromiumCDMParent() = default;
+
+ ipc::IPCResult Recv__delete__() override;
+ ipc::IPCResult RecvOnResolvePromiseWithKeyStatus(const uint32_t& aPromiseId,
+ const uint32_t& aKeyStatus);
+ ipc::IPCResult RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId,
+ const nsCString& aSessionId);
+ ipc::IPCResult RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+ const bool& aSuccessful);
+ ipc::IPCResult RecvOnResolvePromise(const uint32_t& aPromiseId);
+ ipc::IPCResult RecvOnRejectPromise(const uint32_t& aPromiseId,
+ const uint32_t& aError,
+ const uint32_t& aSystemCode,
+ const nsCString& aErrorMessage);
+ ipc::IPCResult RecvOnSessionMessage(const nsCString& aSessionId,
+ const uint32_t& aMessageType,
+ nsTArray<uint8_t>&& aMessage);
+ ipc::IPCResult RecvOnSessionKeysChange(
+ const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo);
+ ipc::IPCResult RecvOnExpirationChange(const nsCString& aSessionId,
+ const double& aSecondsSinceEpoch);
+ ipc::IPCResult RecvOnSessionClosed(const nsCString& aSessionId);
+ ipc::IPCResult RecvOnQueryOutputProtectionStatus();
+ ipc::IPCResult RecvDecrypted(const uint32_t& aId, const uint32_t& aStatus,
+ ipc::Shmem&& aData);
+ ipc::IPCResult RecvDecryptFailed(const uint32_t& aId,
+ const uint32_t& aStatus);
+ ipc::IPCResult RecvOnDecoderInitDone(const uint32_t& aStatus);
+ ipc::IPCResult RecvDecodedShmem(const CDMVideoFrame& aFrame,
+ ipc::Shmem&& aShmem);
+ ipc::IPCResult RecvDecodedData(const CDMVideoFrame& aFrame,
+ nsTArray<uint8_t>&& aData);
+ ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus);
+ ipc::IPCResult RecvShutdown();
+ ipc::IPCResult RecvResetVideoDecoderComplete();
+ ipc::IPCResult RecvDrainComplete();
+ ipc::IPCResult RecvIncreaseShmemPoolSize();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool SendBufferToCDM(uint32_t aSizeInBytes);
+
+ void ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame);
+
+ void RejectPromise(uint32_t aPromiseId, ErrorResult&& aException,
+ const nsCString& aErrorMessage);
+
+ void ResolvePromise(uint32_t aPromiseId);
+ // Helpers to reject our promise if we are shut down.
+ void RejectPromiseShutdown(uint32_t aPromiseId);
+ // Helper to reject our promise with an InvalidStateError and the given
+ // message.
+ void RejectPromiseWithStateError(uint32_t aPromiseId,
+ const nsCString& aErrorMessage);
+
+ // Complete the CDMs request for us to check protection status by responding
+ // to the CDM child with the requested info.
+ void CompleteQueryOutputProtectionStatus(bool aSuccess, uint32_t aLinkMask,
+ uint32_t aProtectionMask);
+
+ bool InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, MediaRawData* aSample);
+
+ bool PurgeShmems();
+ bool EnsureSufficientShmems(size_t aVideoFrameSize);
+ already_AddRefed<VideoData> CreateVideoFrame(const CDMVideoFrame& aFrame,
+ Span<uint8_t> aData);
+
+ const uint32_t mPluginId;
+ GMPContentParent* mContentParent;
+ // Note: this pointer is a weak reference as ChromiumCDMProxy has a strong
+ // reference to the ChromiumCDMCallback.
+ ChromiumCDMCallback* mCDMCallback = nullptr;
+ nsTHashMap<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
+ nsTArray<RefPtr<DecryptJob>> mDecrypts;
+
+ MozPromiseHolder<InitPromise> mInitPromise;
+
+ MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise;
+ MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
+
+ RefPtr<layers::ImageContainer> mImageContainer;
+ RefPtr<layers::KnowsCompositor> mKnowsCompositor;
+ VideoInfo mVideoInfo;
+ uint64_t mLastStreamOffset = 0;
+
+ MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
+
+ size_t mVideoFrameBufferSize = 0;
+
+ // Count of the number of shmems in the set used to return decoded video
+ // frames from the CDM to Gecko.
+ uint32_t mVideoShmemsActive = 0;
+ // Maximum number of shmems to use to return decoded video frames.
+ uint32_t mVideoShmemLimit;
+
+ // Tracks if we have an outstanding request for output protection information.
+ // This will be set to true if the CDM requests the information and we haven't
+ // yet received it from up the stack and need to query up.
+ bool mAwaitingOutputProtectionInformation = false;
+ // The cached link mask for QueryOutputProtectionStatus related calls. If
+ // this isn't set we'll call up the stack to MediaKeys to request the
+ // information, otherwise we'll use the cached value and rely on MediaKeys
+ // to notify us if the mask changes.
+ Maybe<uint32_t> mOutputProtectionLinkMask;
+
+ bool mIsShutdown = false;
+ bool mVideoDecoderInitialized = false;
+ bool mActorDestroyed = false;
+ bool mAbnormalShutdown = false;
+
+ // The H.264 decoder in Widevine CDM versions 970 and later output in decode
+ // order rather than presentation order, so we reorder in presentation order
+ // before presenting. mMaxRefFrames is non-zero if we have an initialized
+ // decoder and we are decoding H.264. If so, it stores the maximum length of
+ // the reorder queue that we need. Note we may have multiple decoders for the
+ // life time of this object, but never more than one active at once.
+ uint32_t mMaxRefFrames = 0;
+ ReorderQueue mReorderQueue;
+
+#ifdef DEBUG
+ // The GMP thread. Used to MOZ_ASSERT methods run on the GMP thread.
+ const nsCOMPtr<nsISerialEventTarget> mGMPThread;
+#endif
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // ChromiumCDMParent_h_
diff --git a/dom/media/gmp/ChromiumCDMProxy.cpp b/dom/media/gmp/ChromiumCDMProxy.cpp
new file mode 100644
index 0000000000..26d0475ad8
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -0,0 +1,636 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMProxy.h"
+#include "ChromiumCDMCallbackProxy.h"
+#include "MediaResult.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "GMPUtils.h"
+#include "nsPrintfCString.h"
+#include "GMPService.h"
+#include "content_decryption_module.h"
+
+#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
+
+namespace mozilla {
+
+ChromiumCDMProxy::ChromiumCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired,
+ aPersistentStateRequired),
+ mCrashHelper(aCrashHelper),
+ mCDMMutex("ChromiumCDMProxy"),
+ mGMPThread(GetGMPThread()) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+ChromiumCDMProxy::~ChromiumCDMProxy() {
+ EME_LOG("ChromiumCDMProxy::~ChromiumCDMProxy(this=%p)", this);
+}
+
+void ChromiumCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<GMPCrashHelper> helper(std::move(mCrashHelper));
+
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("ChromiumCDMProxy::Init(this=%p, pid=%" PRIu32
+ ", origin=%s, topLevelOrigin=%s, "
+ "gmp=%s)",
+ this, aPromiseId, NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ NS_ConvertUTF16toUTF8(aGMPName).get());
+
+ if (!mGMPThread) {
+ RejectPromiseWithStateError(
+ aPromiseId, "Couldn't get GMP thread ChromiumCDMProxy::Init"_ns);
+ return;
+ }
+
+ if (aGMPName.IsEmpty()) {
+ RejectPromiseWithStateError(
+ aPromiseId, nsPrintfCString("Unknown GMP for keysystem '%s'",
+ NS_ConvertUTF16toUTF8(mKeySystem).get()));
+ return;
+ }
+
+ gmp::NodeIdParts nodeIdParts{nsString(aOrigin), nsString(aTopLevelOrigin),
+ nsString(aGMPName)};
+ nsCOMPtr<nsISerialEventTarget> thread = mGMPThread;
+ RefPtr<ChromiumCDMProxy> self(this);
+ nsCString keySystem = NS_ConvertUTF16toUTF8(mKeySystem);
+ RefPtr<Runnable> task(NS_NewRunnableFunction(
+ "ChromiumCDMProxy::Init",
+ [self, nodeIdParts, helper, aPromiseId, thread, keySystem]() -> void {
+ MOZ_ASSERT(self->IsOnOwnerThread());
+
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ if (!service) {
+ self->RejectPromiseWithStateError(
+ aPromiseId,
+ nsLiteralCString("Couldn't get GeckoMediaPluginService in "
+ "ChromiumCDMProxy::Init"));
+ return;
+ }
+ RefPtr<gmp::GetCDMParentPromise> promise =
+ service->GetCDM(nodeIdParts, keySystem, helper);
+ promise->Then(
+ thread, __func__,
+ [self, aPromiseId, thread](RefPtr<gmp::ChromiumCDMParent> cdm) {
+ // service->GetCDM succeeded
+ self->mCallback =
+ MakeUnique<ChromiumCDMCallbackProxy>(self, self->mMainThread);
+ cdm->Init(self->mCallback.get(),
+ self->mDistinctiveIdentifierRequired,
+ self->mPersistentStateRequired, self->mMainThread)
+ ->Then(
+ self->mMainThread, __func__,
+ [self, aPromiseId, cdm](bool /* unused */) {
+ // CDM init succeeded
+ {
+ MutexAutoLock lock(self->mCDMMutex);
+ self->mCDM = cdm;
+ }
+ if (self->mIsShutdown) {
+ self->RejectPromiseWithStateError(
+ aPromiseId, nsLiteralCString(
+ "ChromiumCDMProxy shutdown "
+ "during ChromiumCDMProxy::Init"));
+ // If shutdown happened while waiting to init, we
+ // need to explicitly shutdown the CDM to avoid it
+ // referencing this proxy which is on its way out.
+ self->ShutdownCDMIfExists();
+ return;
+ }
+ self->OnCDMCreated(aPromiseId);
+ },
+ [self, aPromiseId](MediaResult aResult) {
+ // CDM init failed.
+ ErrorResult rv;
+ // XXXbz MediaResult should really store a
+ // CopyableErrorResult or something. See
+ // <https://bugzilla.mozilla.org/show_bug.cgi?id=1612216>.
+ rv.Throw(aResult.Code());
+ self->RejectPromise(aPromiseId, std::move(rv),
+ aResult.Message());
+ });
+ },
+ [self, aPromiseId](MediaResult rv) {
+ // service->GetCDM failed
+ ErrorResult result;
+ // XXXbz MediaResult should really store a CopyableErrorResult or
+ // something. See
+ // <https://bugzilla.mozilla.org/show_bug.cgi?id=1612216>.
+ result.Throw(rv.Code());
+ self->RejectPromise(aPromiseId, std::move(result),
+ rv.Description());
+ });
+ }));
+
+ mGMPThread->Dispatch(task.forget());
+}
+
+void ChromiumCDMProxy::OnCDMCreated(uint32_t aPromiseId) {
+ EME_LOG("ChromiumCDMProxy::OnCDMCreated(this=%p, pid=%" PRIu32
+ ") isMainThread=%d",
+ this, aPromiseId, NS_IsMainThread());
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ // This should only be called once the CDM has been created.
+ MOZ_ASSERT(cdm);
+ if (cdm) {
+ mKeys->OnCDMCreated(aPromiseId, cdm->PluginId());
+ } else {
+ // No CDM? Shouldn't be possible, but reject the promise anyway...
+ constexpr auto err = "Null CDM in OnCDMCreated()"_ns;
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(err);
+ mKeys->RejectPromise(aPromiseId, std::move(rv), err);
+ }
+}
+
+void ChromiumCDMProxy::ShutdownCDMIfExists() {
+ EME_LOG(
+ "ChromiumCDMProxy::ShutdownCDMIfExists(this=%p) mCDM=%p, mIsShutdown=%s",
+ this, mCDM.get(), mIsShutdown ? "true" : "false");
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mGMPThread);
+ MOZ_ASSERT(mIsShutdown,
+ "Should only shutdown the CDM if the proxy is shutting down");
+ RefPtr<gmp::ChromiumCDMParent> cdm;
+ {
+ MutexAutoLock lock(mCDMMutex);
+ cdm.swap(mCDM);
+ }
+ if (cdm) {
+ // We need to keep this proxy alive until the parent has finished its
+ // Shutdown (as it may still try to use the proxy until then).
+ RefPtr<ChromiumCDMProxy> self(this);
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "ChromiumCDMProxy::Shutdown", [self, cdm]() { cdm->Shutdown(); });
+ mGMPThread->Dispatch(task.forget());
+ }
+}
+
+#ifdef DEBUG
+bool ChromiumCDMProxy::IsOnOwnerThread() {
+ return mGMPThread && mGMPThread->IsOnCurrentThread();
+}
+#endif
+
+static uint32_t ToCDMSessionType(dom::MediaKeySessionType aSessionType) {
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary:
+ return static_cast<uint32_t>(cdm::kTemporary);
+ case dom::MediaKeySessionType::Persistent_license:
+ return static_cast<uint32_t>(cdm::kPersistentLicense);
+ default:
+ return static_cast<uint32_t>(cdm::kTemporary);
+ };
+};
+
+static uint32_t ToCDMInitDataType(const nsAString& aInitDataType) {
+ if (aInitDataType.EqualsLiteral("cenc")) {
+ return static_cast<uint32_t>(cdm::kCenc);
+ }
+ if (aInitDataType.EqualsLiteral("webm")) {
+ return static_cast<uint32_t>(cdm::kWebM);
+ }
+ if (aInitDataType.EqualsLiteral("keyids")) {
+ return static_cast<uint32_t>(cdm::kKeyIds);
+ }
+ return static_cast<uint32_t>(cdm::kCenc);
+}
+
+void ChromiumCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::CreateSession(this=%p, token=%" PRIu32
+ ", type=%d, pid=%" PRIu32
+ ") "
+ "initDataLen=%zu",
+ this, aCreateSessionToken, (int)aSessionType, aPromiseId,
+ aInitData.Length());
+
+ uint32_t sessionType = ToCDMSessionType(aSessionType);
+ uint32_t initDataType = ToCDMInitDataType(aInitDataType);
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId, "Null CDM in CreateSession"_ns);
+ return;
+ }
+
+ mGMPThread->Dispatch(NewRunnableMethod<uint32_t, uint32_t, uint32_t, uint32_t,
+ nsTArray<uint8_t>>(
+ "gmp::ChromiumCDMParent::CreateSession", cdm,
+ &gmp::ChromiumCDMParent::CreateSession, aCreateSessionToken, sessionType,
+ initDataType, aPromiseId, std::move(aInitData)));
+}
+
+void ChromiumCDMProxy::LoadSession(PromiseId aPromiseId,
+ dom::MediaKeySessionType aSessionType,
+ const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId, "Null CDM in LoadSession"_ns);
+ return;
+ }
+
+ mGMPThread->Dispatch(NewRunnableMethod<uint32_t, uint32_t, nsString>(
+ "gmp::ChromiumCDMParent::LoadSession", cdm,
+ &gmp::ChromiumCDMParent::LoadSession, aPromiseId,
+ ToCDMSessionType(aSessionType), aSessionId));
+}
+
+void ChromiumCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::SetServerCertificate(this=%p, pid=%" PRIu32
+ ") certLen=%zu",
+ this, aPromiseId, aCert.Length());
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId,
+ "Null CDM in SetServerCertificate"_ns);
+ return;
+ }
+
+ mGMPThread->Dispatch(NewRunnableMethod<uint32_t, nsTArray<uint8_t>>(
+ "gmp::ChromiumCDMParent::SetServerCertificate", cdm,
+ &gmp::ChromiumCDMParent::SetServerCertificate, aPromiseId,
+ std::move(aCert)));
+}
+
+void ChromiumCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::UpdateSession(this=%p, sid='%s', pid=%" PRIu32
+ ") "
+ "responseLen=%zu",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get(), aPromiseId,
+ aResponse.Length());
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId, "Null CDM in UpdateSession"_ns);
+ return;
+ }
+ mGMPThread->Dispatch(
+ NewRunnableMethod<nsCString, uint32_t, nsTArray<uint8_t>>(
+ "gmp::ChromiumCDMParent::UpdateSession", cdm,
+ &gmp::ChromiumCDMParent::UpdateSession,
+ NS_ConvertUTF16toUTF8(aSessionId), aPromiseId, std::move(aResponse)));
+}
+
+void ChromiumCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::CloseSession(this=%p, sid='%s', pid=%" PRIu32 ")",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get(), aPromiseId);
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId, "Null CDM in CloseSession"_ns);
+ return;
+ }
+ mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
+ "gmp::ChromiumCDMParent::CloseSession", cdm,
+ &gmp::ChromiumCDMParent::CloseSession, NS_ConvertUTF16toUTF8(aSessionId),
+ aPromiseId));
+}
+
+void ChromiumCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::RemoveSession(this=%p, sid='%s', pid=%" PRIu32 ")",
+ this, NS_ConvertUTF16toUTF8(aSessionId).get(), aPromiseId);
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId, "Null CDM in RemoveSession"_ns);
+ return;
+ }
+ mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
+ "gmp::ChromiumCDMParent::RemoveSession", cdm,
+ &gmp::ChromiumCDMParent::RemoveSession, NS_ConvertUTF16toUTF8(aSessionId),
+ aPromiseId));
+}
+
+void ChromiumCDMProxy::QueryOutputProtectionStatus() {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::QueryOutputProtectionStatus(this=%p)", this);
+
+ if (mKeys.IsNull()) {
+ EME_LOG(
+ "ChromiumCDMProxy::QueryOutputProtectionStatus(this=%p), mKeys "
+ "missing!",
+ this);
+ // If we can't get mKeys, we're probably in shutdown. But do our best to
+ // respond to the request and indicate the check failed.
+ NotifyOutputProtectionStatus(OutputProtectionCheckStatus::CheckFailed,
+ OutputProtectionCaptureStatus::Unused);
+ return;
+ }
+ // The keys will call back via `NotifyOutputProtectionStatus` to notify the
+ // result of the check.
+ mKeys->CheckIsElementCapturePossible();
+}
+
+void ChromiumCDMProxy::NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // If the check failed aCaptureStatus should be unused, otherwise not.
+ MOZ_ASSERT_IF(aCheckStatus == OutputProtectionCheckStatus::CheckFailed,
+ aCaptureStatus == OutputProtectionCaptureStatus::Unused);
+ MOZ_ASSERT_IF(aCheckStatus == OutputProtectionCheckStatus::CheckSuccessful,
+ aCaptureStatus != OutputProtectionCaptureStatus::Unused);
+ EME_LOG(
+ "ChromiumCDMProxy::NotifyOutputProtectionStatus(this=%p) "
+ "aCheckStatus=%" PRIu8 " aCaptureStatus=%" PRIu8,
+ this, static_cast<uint8_t>(aCheckStatus),
+ static_cast<uint8_t>(aCaptureStatus));
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ // If we're in shutdown the CDM may have been cleared while a notification
+ // is in flight. If this happens outside of shutdown we have a bug.
+ MOZ_ASSERT(mIsShutdown);
+ return;
+ }
+
+ uint32_t linkMask{};
+ uint32_t protectionMask{}; // Unused/always zeroed.
+ if (aCheckStatus == OutputProtectionCheckStatus::CheckSuccessful &&
+ aCaptureStatus == OutputProtectionCaptureStatus::CapturePossilbe) {
+ // The result indicates the capture is possible, so set the mask
+ // to indicate this.
+ linkMask |= cdm::OutputLinkTypes::kLinkTypeNetwork;
+ }
+ mGMPThread->Dispatch(NewRunnableMethod<bool, uint32_t, uint32_t>(
+ "gmp::ChromiumCDMParent::NotifyOutputProtectionStatus", cdm,
+ &gmp::ChromiumCDMParent::NotifyOutputProtectionStatus,
+ aCheckStatus == OutputProtectionCheckStatus::CheckSuccessful, linkMask,
+ protectionMask));
+}
+
+void ChromiumCDMProxy::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::Shutdown(this=%p) mCDM=%p, mIsShutdown=%s", this,
+ mCDM.get(), mIsShutdown ? "true" : "false");
+ if (mIsShutdown) {
+ return;
+ }
+ mIsShutdown = true;
+ mKeys.Clear();
+ ShutdownCDMIfExists();
+}
+
+void ChromiumCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) {
+ if (!NS_IsMainThread()) {
+ // Use CopyableErrorResult to store our exception in the runnable,
+ // because ErrorResult is not OK to move across threads.
+ mMainThread->Dispatch(
+ NewRunnableMethod<PromiseId, StoreCopyPassByRRef<CopyableErrorResult>,
+ nsCString>(
+ "ChromiumCDMProxy::RejectPromise", this,
+ &ChromiumCDMProxy::RejectPromiseOnMainThread, aId,
+ std::move(aException), aReason),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+ EME_LOG("ChromiumCDMProxy::RejectPromise(this=%p, pid=%" PRIu32
+ ", code=0x%x, "
+ "reason='%s')",
+ this, aId, aException.ErrorCodeAsInt(), aReason.get());
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, std::move(aException), aReason);
+ } else {
+ // We don't have a MediaKeys object to pass the exception to, so silence
+ // the exception to avoid it asserting due to being unused.
+ aException.SuppressException();
+ }
+}
+
+void ChromiumCDMProxy::RejectPromiseWithStateError(PromiseId aId,
+ const nsCString& aReason) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(aReason);
+ RejectPromise(aId, std::move(rv), aReason);
+}
+
+void ChromiumCDMProxy::RejectPromiseOnMainThread(
+ PromiseId aId, CopyableErrorResult&& aException, const nsCString& aReason) {
+ // Moving into or out of a non-copyable ErrorResult will assert that both
+ // ErorResults are from our current thread. Avoid the assertion by moving
+ // into a current-thread CopyableErrorResult first. Note that this is safe,
+ // because CopyableErrorResult never holds state that can't move across
+ // threads.
+ CopyableErrorResult rv(std::move(aException));
+ RejectPromise(aId, std::move(rv), aReason);
+}
+
+void ChromiumCDMProxy::ResolvePromise(PromiseId aId) {
+ if (!NS_IsMainThread()) {
+ mMainThread->Dispatch(
+ NewRunnableMethod<PromiseId>("ChromiumCDMProxy::ResolvePromise", this,
+ &ChromiumCDMProxy::ResolvePromise, aId),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ EME_LOG("ChromiumCDMProxy::ResolvePromise(this=%p, pid=%" PRIu32 ")", this,
+ aId);
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("ChromiumCDMProxy unable to resolve promise!");
+ }
+}
+
+void ChromiumCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::OnSetSessionId(this=%p, token=%" PRIu32
+ ", sid='%s')",
+ this, aCreateSessionToken, NS_ConvertUTF16toUTF8(aSessionId).get());
+
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(
+ mKeys->GetPendingSession(aCreateSessionToken));
+ if (session) {
+ session->SetSessionId(aSessionId);
+ }
+}
+
+void ChromiumCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void ChromiumCDMProxy::OnResolvePromiseWithKeyStatus(
+ uint32_t aPromiseId, dom::MediaKeyStatus aKeyStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus);
+}
+
+void ChromiumCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void ChromiumCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void ChromiumCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ // Expiry of 0 is interpreted as "never expire". See bug 1345341.
+ double t = (aExpiryTime == 0) ? std::numeric_limits<double>::quiet_NaN()
+ : static_cast<double>(aExpiryTime);
+ session->SetExpiration(t);
+ }
+}
+
+void ChromiumCDMProxy::OnSessionClosed(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool keyStatusesChange = false;
+ {
+ auto caps = Capabilites().Lock();
+ keyStatusesChange = caps->RemoveKeysForSession(nsString(aSessionId));
+ }
+ if (keyStatusesChange) {
+ OnKeyStatusesChange(aSessionId);
+ }
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void ChromiumCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) {}
+
+void ChromiumCDMProxy::OnSessionError(const nsAString& aSessionId,
+ nsresult aException, uint32_t aSystemCode,
+ const nsAString& aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyError(aSystemCode);
+ }
+ LogToConsole(aMsg);
+}
+
+void ChromiumCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ ErrorResult&& aException,
+ const nsCString& aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, std::move(aException), aMsg);
+}
+
+RefPtr<DecryptPromise> ChromiumCDMProxy::Decrypt(MediaRawData* aSample) {
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ return DecryptPromise::CreateAndReject(
+ DecryptResult(eme::AbortedErr, aSample), __func__);
+ }
+ RefPtr<MediaRawData> sample = aSample;
+ return InvokeAsync(mGMPThread, __func__,
+ [cdm, sample]() { return cdm->Decrypt(sample); });
+}
+
+void ChromiumCDMProxy::GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("ChromiumCDMProxy::GetStatusForPolicy(this=%p, pid=%" PRIu32
+ ") minHdcpVersion=%s",
+ this, aPromiseId, NS_ConvertUTF16toUTF8(aMinHdcpVersion).get());
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+ if (!cdm) {
+ RejectPromiseWithStateError(aPromiseId,
+ "Null CDM in GetStatusForPolicy"_ns);
+ return;
+ }
+
+ mGMPThread->Dispatch(NewRunnableMethod<uint32_t, nsCString>(
+ "gmp::ChromiumCDMParent::GetStatusForPolicy", cdm,
+ &gmp::ChromiumCDMParent::GetStatusForPolicy, aPromiseId,
+ NS_ConvertUTF16toUTF8(aMinHdcpVersion)));
+}
+
+void ChromiumCDMProxy::Terminated() {
+ if (!mKeys.IsNull()) {
+ mKeys->Terminated();
+ }
+}
+
+already_AddRefed<gmp::ChromiumCDMParent> ChromiumCDMProxy::GetCDMParent() {
+ MutexAutoLock lock(mCDMMutex);
+ RefPtr<gmp::ChromiumCDMParent> cdm = mCDM;
+ return cdm.forget();
+}
+
+} // namespace mozilla
+
+#undef NS_DispatchToMainThread
diff --git a/dom/media/gmp/ChromiumCDMProxy.h b/dom/media/gmp/ChromiumCDMProxy.h
new file mode 100644
index 0000000000..7f60153ae7
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMProxy.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ChromiumCDMProxy_h_
+#define ChromiumCDMProxy_h_
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/CDMProxy.h"
+#include "ChromiumCDMParent.h"
+
+namespace mozilla {
+
+class ErrorResult;
+class MediaRawData;
+class DecryptJob;
+class ChromiumCDMCallbackProxy;
+class ChromiumCDMProxy : public CDMProxy {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMProxy, override)
+
+ ChromiumCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem,
+ GMPCrashHelper* aCrashHelper,
+ bool aAllowDistinctiveIdentifier,
+ bool aAllowPersistentState);
+
+ void Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ dom::MediaKeySessionType aSessionType,
+ PromiseId aPromiseId, const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) override;
+
+ void LoadSession(PromiseId aPromiseId, dom::MediaKeySessionType aSessionType,
+ const nsAString& aSessionId) override;
+
+ void SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) override;
+
+ void UpdateSession(const nsAString& aSessionId, PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) override;
+
+ void CloseSession(const nsAString& aSessionId, PromiseId aPromiseId) override;
+
+ void RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) override;
+
+ void QueryOutputProtectionStatus() override;
+
+ void NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) override;
+
+ void Shutdown() override;
+
+ void Terminated() override;
+
+ void OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) override;
+
+ void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
+
+ void OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) override;
+
+ void OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) override;
+
+ void OnSessionClosed(const nsAString& aSessionId) override;
+
+ void OnSessionError(const nsAString& aSessionId, nsresult aException,
+ uint32_t aSystemCode, const nsAString& aMsg) override;
+
+ void OnRejectPromise(uint32_t aPromiseId, ErrorResult&& aException,
+ const nsCString& aMsg) override;
+
+ RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override;
+
+ void OnDecrypted(uint32_t aId, DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override;
+
+ void RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) override;
+ // Reject promise with an InvalidStateError and the given message.
+ void RejectPromiseWithStateError(PromiseId aId, const nsCString& aReason);
+ // For use for moving rejections from off-main thread.
+ void RejectPromiseOnMainThread(PromiseId aId,
+ CopyableErrorResult&& aException,
+ const nsCString& aReason);
+
+ void ResolvePromise(PromiseId aId) override;
+
+ void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+ void GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) override;
+
+#ifdef DEBUG
+ bool IsOnOwnerThread() override;
+#endif
+
+ ChromiumCDMProxy* AsChromiumCDMProxy() override { return this; }
+
+ // Threadsafe. Note this may return a reference to a shutdown
+ // CDM, which will fail on all operations.
+ already_AddRefed<gmp::ChromiumCDMParent> GetCDMParent();
+
+ void OnResolvePromiseWithKeyStatus(uint32_t aPromiseId,
+ dom::MediaKeyStatus aKeyStatus);
+
+ private:
+ void OnCDMCreated(uint32_t aPromiseId);
+ void ShutdownCDMIfExists();
+
+ ~ChromiumCDMProxy();
+
+ // True if Shutdown() has been called. Should only be read and written on
+ // main thread.
+ bool mIsShutdown = false;
+
+ RefPtr<GMPCrashHelper> mCrashHelper;
+
+ Mutex mCDMMutex MOZ_UNANNOTATED;
+ RefPtr<gmp::ChromiumCDMParent> mCDM;
+ nsCOMPtr<nsISerialEventTarget> mGMPThread;
+ UniquePtr<ChromiumCDMCallbackProxy> mCallback;
+};
+
+} // namespace mozilla
+
+#endif // ChromiumCDMProxy_h_
diff --git a/dom/media/gmp/DecryptJob.cpp b/dom/media/gmp/DecryptJob.cpp
new file mode 100644
index 0000000000..affda3668f
--- /dev/null
+++ b/dom/media/gmp/DecryptJob.cpp
@@ -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/. */
+
+#include "DecryptJob.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla {
+
+static Atomic<uint32_t> sDecryptJobInstanceCount(0u);
+
+DecryptJob::DecryptJob(MediaRawData* aSample)
+ : mId(++sDecryptJobInstanceCount), mSample(aSample) {}
+
+RefPtr<DecryptPromise> DecryptJob::Ensure() {
+ return mPromise.Ensure(__func__);
+}
+
+void DecryptJob::PostResult(DecryptStatus aResult) {
+ nsTArray<uint8_t> empty;
+ PostResult(aResult, empty);
+}
+
+void DecryptJob::PostResult(DecryptStatus aResult,
+ Span<const uint8_t> aDecryptedData) {
+ if (aDecryptedData.Length() != mSample->Size()) {
+ NS_WARNING("CDM returned incorrect number of decrypted bytes");
+ }
+ if (aResult == eme::Ok) {
+ UniquePtr<MediaRawDataWriter> writer(mSample->CreateWriter());
+ PodCopy(writer->Data(), aDecryptedData.Elements(),
+ std::min<size_t>(aDecryptedData.Length(), mSample->Size()));
+ } else if (aResult == eme::NoKeyErr) {
+ NS_WARNING("CDM returned NoKeyErr");
+ // We still have the encrypted sample, so we can re-enqueue it to be
+ // decrypted again once the key is usable again.
+ } else {
+ nsAutoCString str("CDM returned decode failure DecryptStatus=");
+ str.AppendInt(aResult);
+ NS_WARNING(str.get());
+ }
+ mPromise.Resolve(DecryptResult(aResult, mSample), __func__);
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/DecryptJob.h b/dom/media/gmp/DecryptJob.h
new file mode 100644
index 0000000000..c4f25206e3
--- /dev/null
+++ b/dom/media/gmp/DecryptJob.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DecryptJob_h_
+#define DecryptJob_h_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/Span.h"
+
+namespace mozilla {
+
+class DecryptJob {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecryptJob)
+
+ explicit DecryptJob(MediaRawData* aSample);
+
+ void PostResult(DecryptStatus aResult, Span<const uint8_t> aDecryptedData);
+ void PostResult(DecryptStatus aResult);
+
+ RefPtr<DecryptPromise> Ensure();
+
+ const uint32_t mId;
+ RefPtr<MediaRawData> mSample;
+
+ private:
+ ~DecryptJob() = default;
+ MozPromiseHolder<DecryptPromise> mPromise;
+};
+
+} // namespace mozilla
+
+#endif // DecryptJob_h_
diff --git a/dom/media/gmp/GMPCallbackBase.h b/dom/media/gmp/GMPCallbackBase.h
new file mode 100644
index 0000000000..7284f18e86
--- /dev/null
+++ b/dom/media/gmp/GMPCallbackBase.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_
+
+#include "nsISupportsImpl.h"
+
+class GMPCallbackBase {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual ~GMPCallbackBase() = default;
+
+ // 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 0000000000..0d485345bc
--- /dev/null
+++ b/dom/media/gmp/GMPChild.cpp
@@ -0,0 +1,657 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "base/command_line.h"
+#include "base/task.h"
+#include "ChildProfilerController.h"
+#include "ChromiumCDMAdapter.h"
+#ifdef XP_LINUX
+# include "dlfcn.h"
+#endif
+#include "gmp-video-decode.h"
+#include "gmp-video-encode.h"
+#include "GMPContentChild.h"
+#include "GMPLoader.h"
+#include "GMPLog.h"
+#include "GMPPlatform.h"
+#include "GMPProcessChild.h"
+#include "GMPProcessParent.h"
+#include "GMPUtils.h"
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "GMPVideoHost.h"
+#include "mozilla/Algorithm.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/ipc/CrashReporterClient.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/TextUtils.h"
+#include "nsDebugImpl.h"
+#include "nsExceptionHandler.h"
+#include "nsIFile.h"
+#include "nsReadableUtils.h"
+#include "nsThreadManager.h"
+#include "nsXULAppAPI.h"
+#include "nsIXULRuntime.h"
+#include "prio.h"
+#ifdef XP_WIN
+# include <stdlib.h> // for _exit()
+# include "WinUtils.h"
+#else
+# include <unistd.h> // for _exit()
+#endif
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+
+#define GMP_CHILD_LOG_DEBUG(x, ...) \
+ GMP_LOG_DEBUG("GMPChild[pid=%d] " x, (int)base::GetCurrentProcId(), \
+ ##__VA_ARGS__)
+
+namespace gmp {
+
+GMPChild::GMPChild()
+ : mGMPMessageLoop(MessageLoop::current()), mGMPLoader(nullptr) {
+ GMP_CHILD_LOG_DEBUG("GMPChild ctor");
+ nsDebugImpl::SetMultiprocessMode("GMP");
+}
+
+GMPChild::~GMPChild() {
+ GMP_CHILD_LOG_DEBUG("GMPChild dtor");
+#ifdef XP_LINUX
+ for (auto& libHandle : mLibHandles) {
+ dlclose(libHandle);
+ }
+#endif
+}
+
+bool GMPChild::Init(const nsAString& aPluginPath,
+ mozilla::ipc::UntypedEndpoint&& aEndpoint) {
+ GMP_CHILD_LOG_DEBUG("%s pluginPath=%s", __FUNCTION__,
+ NS_ConvertUTF16toUTF8(aPluginPath).get());
+
+ // GMPChild needs nsThreadManager to create the ProfilerChild thread.
+ // It is also used on debug builds for the sandbox tests.
+ if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!aEndpoint.Bind(this))) {
+ return false;
+ }
+
+ CrashReporterClient::InitSingleton(this);
+
+ mPluginPath = aPluginPath;
+
+ return true;
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvProvideStorageId(
+ const nsCString& aStorageId) {
+ GMP_CHILD_LOG_DEBUG("%s", __FUNCTION__);
+ mStorageId = aStorageId;
+ return IPC_OK();
+}
+
+GMPErr GMPChild::GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI,
+ const nsACString& aKeySystem) {
+ if (!mGMPLoader) {
+ return GMPGenericErr;
+ }
+ return mGMPLoader->GetAPI(aAPIName, aHostAPI, aPluginAPI, aKeySystem);
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvPreloadLibs(const nsCString& aLibs) {
+ // Pre-load libraries that may need to be used by the EME plugin but that
+ // can't be loaded after the sandbox has started.
+#ifdef XP_WIN
+ // Items in this must be lowercase!
+ constexpr static const char16_t* whitelist[] = {
+ u"dxva2.dll", // Get monitor information
+ u"evr.dll", // MFGetStrideForBitmapInfoHeader
+ u"freebl3.dll", // NSS for clearkey CDM
+ u"mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer,
+ // MFCreateMediaType
+ u"msmpeg2vdec.dll", // H.264 decoder
+ u"nss3.dll", // NSS for clearkey CDM
+ u"ole32.dll", // required for OPM
+ u"oleaut32.dll", // For _bstr_t use in libwebrtc, see bug 1788592
+ u"psapi.dll", // For GetMappedFileNameW, see bug 1383611
+ u"softokn3.dll", // NSS for clearkey CDM
+ u"winmm.dll", // Dependency for widevine
+ };
+ constexpr static bool (*IsASCII)(const char16_t*) =
+ IsAsciiNullTerminated<char16_t>;
+ static_assert(AllOf(std::begin(whitelist), std::end(whitelist), IsASCII),
+ "Items in the whitelist must not contain non-ASCII "
+ "characters!");
+
+ nsTArray<nsCString> libs;
+ SplitAt(", ", aLibs, libs);
+ for (nsCString lib : libs) {
+ ToLowerCase(lib);
+ for (const char16_t* whiteListedLib : whitelist) {
+ if (nsDependentString(whiteListedLib)
+ .EqualsASCII(lib.Data(), lib.Length())) {
+ LoadLibraryW(char16ptr_t(whiteListedLib));
+ break;
+ }
+ }
+ }
+#elif defined(XP_LINUX)
+ constexpr static const char* whitelist[] = {
+ // NSS libraries used by clearkey.
+ "libfreeblpriv3.so",
+ "libsoftokn3.so",
+ // glibc libraries merged into libc.so.6; see bug 1725828 and
+ // the corresponding code in GMPParent.cpp.
+ "libdl.so.2",
+ "libpthread.so.0",
+ "librt.so.1",
+ };
+
+ nsTArray<nsCString> libs;
+ SplitAt(", ", aLibs, libs);
+ for (const nsCString& lib : libs) {
+ for (const char* whiteListedLib : whitelist) {
+ if (lib.EqualsASCII(whiteListedLib)) {
+ auto libHandle = dlopen(whiteListedLib, RTLD_NOW | RTLD_GLOBAL);
+ if (libHandle) {
+ mLibHandles.AppendElement(libHandle);
+ } else {
+ // TODO(bug 1698718): remove the logging once we've identified
+ // the cause of the load failure.
+ const char* error = dlerror();
+ if (error) {
+ // We should always have an error, but gracefully handle just in
+ // case.
+ nsAutoCString nsError{error};
+ CrashReporter::AppendAppNotesToCrashReport(nsError);
+ }
+ // End bug 1698718 logging.
+
+ MOZ_CRASH("Couldn't load lib needed by media plugin");
+ }
+ }
+ }
+ }
+#endif
+ return IPC_OK();
+}
+
+bool GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) {
+ nsCOMPtr<nsIFile> libFile;
+
+#define GMP_PATH_CRASH(explain) \
+ do { \
+ nsAutoString path; \
+ if (!libFile || NS_FAILED(libFile->GetPath(path))) { \
+ path = mPluginPath; \
+ } \
+ CrashReporter::AnnotateCrashReport( \
+ CrashReporter::Annotation::GMPLibraryPath, \
+ NS_ConvertUTF16toUTF8(path)); \
+ MOZ_CRASH(explain); \
+ } while (false)
+
+ nsresult rv = NS_NewLocalFile(mPluginPath, true, getter_AddRefs(libFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_PATH_CRASH("Failed to create file for plugin path");
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> parent;
+ rv = libFile->GetParent(getter_AddRefs(parent));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_PATH_CRASH("Failed to get parent file for plugin file");
+ return false;
+ }
+
+ nsAutoString parentLeafName;
+ rv = parent->GetLeafName(parentLeafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_PATH_CRASH("Failed to get leaf for plugin file");
+ return false;
+ }
+
+ nsAutoString baseName;
+ baseName = Substring(parentLeafName, 4, parentLeafName.Length() - 1);
+
+#if defined(XP_MACOSX)
+ nsAutoString binaryName = u"lib"_ns + baseName + u".dylib"_ns;
+#elif defined(OS_POSIX)
+ nsAutoString binaryName = u"lib"_ns + baseName + u".so"_ns;
+#elif defined(XP_WIN)
+ nsAutoString binaryName = baseName + u".dll"_ns;
+#else
+# error not defined
+#endif
+ rv = libFile->AppendRelativePath(binaryName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_PATH_CRASH("Failed to append lib to plugin file");
+ return false;
+ }
+
+ if (NS_WARN_IF(!FileExists(libFile))) {
+ GMP_PATH_CRASH("Plugin file does not exist");
+ return false;
+ }
+
+ nsAutoString path;
+ rv = libFile->GetPath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_PATH_CRASH("Failed to get path for plugin file");
+ return false;
+ }
+
+ CopyUTF16toUTF8(path, aOutLibPath);
+ return true;
+}
+
+static nsCOMPtr<nsIFile> AppendFile(nsCOMPtr<nsIFile>&& aFile,
+ const nsString& aStr) {
+ return (aFile && NS_SUCCEEDED(aFile->Append(aStr))) ? aFile : nullptr;
+}
+
+static nsCOMPtr<nsIFile> CloneFile(const nsCOMPtr<nsIFile>& aFile) {
+ nsCOMPtr<nsIFile> clone;
+ return (aFile && NS_SUCCEEDED(aFile->Clone(getter_AddRefs(clone)))) ? clone
+ : nullptr;
+}
+
+static nsCOMPtr<nsIFile> GetParentFile(const nsCOMPtr<nsIFile>& aFile) {
+ nsCOMPtr<nsIFile> parent;
+ return (aFile && NS_SUCCEEDED(aFile->GetParent(getter_AddRefs(parent))))
+ ? parent
+ : nullptr;
+}
+
+#if defined(XP_WIN)
+static bool IsFileLeafEqualToASCII(const nsCOMPtr<nsIFile>& aFile,
+ const char* aStr) {
+ nsAutoString leafName;
+ return aFile && NS_SUCCEEDED(aFile->GetLeafName(leafName)) &&
+ leafName.EqualsASCII(aStr);
+}
+#endif
+
+#if defined(XP_WIN)
+# define FIREFOX_FILE u"firefox.exe"_ns
+# define XUL_LIB_FILE u"xul.dll"_ns
+#elif defined(XP_MACOSX)
+# define FIREFOX_FILE u"firefox"_ns
+# define XUL_LIB_FILE u"XUL"_ns
+#else
+# define FIREFOX_FILE u"firefox"_ns
+# define XUL_LIB_FILE u"libxul.so"_ns
+#endif
+
+static nsCOMPtr<nsIFile> GetFirefoxAppPath(
+ nsCOMPtr<nsIFile> aPluginContainerPath) {
+ MOZ_ASSERT(aPluginContainerPath);
+#if defined(XP_MACOSX)
+ // On MacOS the firefox binary is a few parent directories up from
+ // plugin-container.
+ // aPluginContainerPath will end with something like:
+ // xxxx/NightlyDebug.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
+ nsCOMPtr<nsIFile> path = aPluginContainerPath;
+ for (int i = 0; i < 4; i++) {
+ path = GetParentFile(path);
+ }
+ return path;
+#else
+ nsCOMPtr<nsIFile> parent = GetParentFile(aPluginContainerPath);
+# if XP_WIN
+ if (IsFileLeafEqualToASCII(parent, "i686")) {
+ // We must be on Windows on ARM64, where the plugin-container path will
+ // be in the 'i686' subdir. The firefox.exe is in the parent directory.
+ parent = GetParentFile(parent);
+ }
+# endif
+ return parent;
+#endif
+}
+
+#if defined(XP_MACOSX)
+static bool GetSigPath(const int aRelativeLayers,
+ const nsString& aTargetSigFileName,
+ nsCOMPtr<nsIFile> aExecutablePath,
+ nsCOMPtr<nsIFile>& aOutSigPath) {
+ // The sig file will be located in
+ // xxxx/NightlyDebug.app/Contents/Resources/XUL.sig
+ // xxxx/NightlyDebug.app/Contents/Resources/firefox.sig
+ // xxxx/NightlyDebug.app/Contents/MacOS/plugin-container.app/Contents/Resources/plugin-container.sig
+ // On MacOS the sig file is a few parent directories up from
+ // its executable file.
+ // Start to search the path from the path of the executable file we provided.
+ MOZ_ASSERT(aExecutablePath);
+ nsCOMPtr<nsIFile> path = aExecutablePath;
+ for (int i = 0; i < aRelativeLayers; i++) {
+ nsCOMPtr<nsIFile> parent;
+ if (NS_WARN_IF(NS_FAILED(path->GetParent(getter_AddRefs(parent))))) {
+ return false;
+ }
+ path = parent;
+ }
+ MOZ_ASSERT(path);
+ aOutSigPath = path;
+ return NS_SUCCEEDED(path->Append(u"Resources"_ns)) &&
+ NS_SUCCEEDED(path->Append(aTargetSigFileName));
+}
+#endif
+
+static bool AppendHostPath(nsCOMPtr<nsIFile>& aFile,
+ nsTArray<std::pair<nsCString, nsCString>>& aPaths) {
+ nsString str;
+ if (!FileExists(aFile) || NS_FAILED(aFile->GetPath(str))) {
+ return false;
+ }
+
+ nsCString filePath = NS_ConvertUTF16toUTF8(str);
+ nsCString sigFilePath;
+#if defined(XP_MACOSX)
+ nsAutoString binary;
+ if (NS_FAILED(aFile->GetLeafName(binary))) {
+ return false;
+ }
+ binary.Append(u".sig"_ns);
+ nsCOMPtr<nsIFile> sigFile;
+ if (GetSigPath(2, binary, aFile, sigFile) &&
+ NS_SUCCEEDED(sigFile->GetPath(str))) {
+ CopyUTF16toUTF8(str, sigFilePath);
+ } else {
+ // Cannot successfully get the sig file path.
+ // Assume it is located at the same place as plugin-container
+ // alternatively.
+ sigFilePath = nsCString(NS_ConvertUTF16toUTF8(str) + ".sig"_ns);
+ }
+#else
+ sigFilePath = nsCString(NS_ConvertUTF16toUTF8(str) + ".sig"_ns);
+#endif
+ aPaths.AppendElement(
+ std::make_pair(std::move(filePath), std::move(sigFilePath)));
+ return true;
+}
+
+nsTArray<std::pair<nsCString, nsCString>>
+GMPChild::MakeCDMHostVerificationPaths(const nsACString& aPluginLibPath) {
+ // Record the file path and its sig file path.
+ nsTArray<std::pair<nsCString, nsCString>> paths;
+ // Plugin binary path.
+ paths.AppendElement(
+ std::make_pair(nsCString(aPluginLibPath), aPluginLibPath + ".sig"_ns));
+
+ // Plugin-container binary path.
+ // Note: clang won't let us initialize an nsString from a wstring, so we
+ // need to go through UTF8 to get to an nsString.
+ const std::string pluginContainer =
+ WideToUTF8(CommandLine::ForCurrentProcess()->program());
+ nsString str;
+
+ CopyUTF8toUTF16(nsDependentCString(pluginContainer.c_str()), str);
+ nsCOMPtr<nsIFile> path;
+ if (NS_FAILED(NS_NewLocalFile(str, true, /* aFollowLinks */
+ getter_AddRefs(path))) ||
+ !AppendHostPath(path, paths)) {
+ // Without successfully determining plugin-container's path, we can't
+ // determine libxul's or Firefox's. So give up.
+ return paths;
+ }
+
+#if defined(XP_WIN)
+ // On Windows on ARM64, we should also append the x86 plugin-container's
+ // xul.dll.
+ const bool isWindowsOnARM64 =
+ IsFileLeafEqualToASCII(GetParentFile(path), "i686");
+ if (isWindowsOnARM64) {
+ nsCOMPtr<nsIFile> x86XulPath =
+ AppendFile(GetParentFile(path), XUL_LIB_FILE);
+ if (!AppendHostPath(x86XulPath, paths)) {
+ return paths;
+ }
+ }
+#endif
+
+ // Firefox application binary path.
+ nsCOMPtr<nsIFile> appDir = GetFirefoxAppPath(path);
+ path = AppendFile(CloneFile(appDir), FIREFOX_FILE);
+ if (!AppendHostPath(path, paths)) {
+ return paths;
+ }
+
+ // Libxul path. Note: re-using 'appDir' var here, as we assume libxul is in
+ // the same directory as Firefox executable.
+ appDir->GetPath(str);
+ path = AppendFile(CloneFile(appDir), XUL_LIB_FILE);
+ if (!AppendHostPath(path, paths)) {
+ return paths;
+ }
+
+ return paths;
+}
+
+static auto ToCString(const nsTArray<std::pair<nsCString, nsCString>>& aPairs) {
+ return StringJoin(","_ns, aPairs, [](nsACString& dest, const auto& p) {
+ dest.AppendPrintf("(%s,%s)", p.first.get(), p.second.get());
+ });
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvStartPlugin(const nsString& aAdapter) {
+ GMP_CHILD_LOG_DEBUG("%s", __FUNCTION__);
+
+ nsAutoCString libPath;
+ if (!GetUTF8LibPath(libPath)) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::GMPLibraryPath,
+ NS_ConvertUTF16toUTF8(mPluginPath));
+#ifdef XP_WIN
+ return IPC_FAIL(this,
+ nsPrintfCString("Failed to get lib path with error(%lu).",
+ GetLastError())
+ .get());
+#else
+ return IPC_FAIL(this, "Failed to get lib path.");
+#endif
+ }
+
+ auto platformAPI = new GMPPlatformAPI();
+ InitPlatformAPI(*platformAPI, this);
+
+ mGMPLoader = MakeUnique<GMPLoader>();
+#if defined(MOZ_SANDBOX) && !defined(XP_MACOSX)
+ if (!mGMPLoader->CanSandbox()) {
+ GMP_CHILD_LOG_DEBUG("%s Can't sandbox GMP, failing", __FUNCTION__);
+ delete platformAPI;
+ return IPC_FAIL(this, "Can't sandbox GMP.");
+ }
+#endif
+
+ GMPAdapter* adapter = nullptr;
+ if (aAdapter.EqualsLiteral("chromium")) {
+ auto&& paths = MakeCDMHostVerificationPaths(libPath);
+ GMP_CHILD_LOG_DEBUG("%s CDM host paths=%s", __func__,
+ ToCString(paths).get());
+ adapter = new ChromiumCDMAdapter(std::move(paths));
+ }
+
+ if (!mGMPLoader->Load(libPath.get(), libPath.Length(), platformAPI,
+ adapter)) {
+ NS_WARNING("Failed to load GMP");
+ delete platformAPI;
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::GMPLibraryPath,
+ NS_ConvertUTF16toUTF8(mPluginPath));
+
+#ifdef XP_WIN
+ return IPC_FAIL(this, nsPrintfCString("Failed to load GMP with error(%lu).",
+ GetLastError())
+ .get());
+#else
+ return IPC_FAIL(this, "Failed to load GMP.");
+#endif
+ }
+
+ return IPC_OK();
+}
+
+MessageLoop* GMPChild::GMPMessageLoop() { return mGMPMessageLoop; }
+
+void GMPChild::ActorDestroy(ActorDestroyReason aWhy) {
+ GMP_CHILD_LOG_DEBUG("%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();
+ }
+
+ // Send the last bits of Glean data over to the main process.
+ glean::FlushFOGData(
+ [](ByteBuf&& aBuf) { glean::SendFOGData(std::move(aBuf)); });
+
+ if (mProfilerController) {
+ mProfilerController->Shutdown();
+ mProfilerController = nullptr;
+ }
+
+ CrashReporterClient::DestroySingleton();
+
+ 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;
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvCrashPluginNow() {
+ MOZ_CRASH();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvCloseActive() {
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ mGMPContentChildren[i - 1]->CloseActive();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvInitGMPContentChild(
+ Endpoint<PGMPContentChild>&& aEndpoint) {
+ GMPContentChild* child =
+ mGMPContentChildren.AppendElement(new GMPContentChild(this))->get();
+ aEndpoint.Bind(child);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvFlushFOGData(
+ FlushFOGDataResolver&& aResolver) {
+ GMP_CHILD_LOG_DEBUG("GMPChild RecvFlushFOGData");
+ glean::FlushFOGData(std::move(aResolver));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvTestTriggerMetrics(
+ TestTriggerMetricsResolver&& aResolve) {
+ GMP_CHILD_LOG_DEBUG("GMPChild RecvTestTriggerMetrics");
+ mozilla::glean::test_only_ipc::a_counter.Add(
+ nsIXULRuntime::PROCESS_TYPE_GMPLUGIN);
+ aResolve(true);
+ return IPC_OK();
+}
+
+void GMPChild::GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild) {
+ for (uint32_t i = mGMPContentChildren.Length(); i > 0; i--) {
+ RefPtr<GMPContentChild>& destroyedActor = mGMPContentChildren[i - 1];
+ if (destroyedActor.get() == aGMPContentChild) {
+ SendPGMPContentChildDestroyed();
+ mGMPContentChildren.RemoveElementAt(i - 1);
+ break;
+ }
+ }
+}
+
+mozilla::ipc::IPCResult GMPChild::RecvInitProfiler(
+ Endpoint<PProfilerChild>&& aEndpoint) {
+ mProfilerController =
+ mozilla::ChildProfilerController::Create(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+#undef GMP_CHILD_LOG_DEBUG
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPChild.h b/dom/media/gmp/GMPChild.h
new file mode 100644
index 0000000000..7201833501
--- /dev/null
+++ b/dom/media/gmp/GMPChild.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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-entrypoints.h"
+#include "prlink.h"
+
+namespace mozilla {
+
+class ChildProfilerController;
+
+namespace gmp {
+
+class GMPContentChild;
+
+class GMPChild : public PGMPChild {
+ friend class PGMPChild;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(GMPChild, override)
+
+ GMPChild();
+
+ bool Init(const nsAString& aPluginPath,
+ mozilla::ipc::UntypedEndpoint&& aEndpoint);
+ MessageLoop* GMPMessageLoop();
+
+ // Main thread only.
+ GMPTimerChild* GetGMPTimers();
+ GMPStorageChild* GetGMPStorage();
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ bool SetMacSandboxInfo(bool aAllowWindowServer);
+#endif
+
+ private:
+ friend class GMPContentChild;
+
+ virtual ~GMPChild();
+
+ bool GetUTF8LibPath(nsACString& aOutLibPath);
+
+ mozilla::ipc::IPCResult RecvProvideStorageId(const nsCString& aStorageId);
+
+ mozilla::ipc::IPCResult RecvStartPlugin(const nsString& aAdapter);
+ mozilla::ipc::IPCResult RecvPreloadLibs(const nsCString& aLibs);
+
+ PGMPTimerChild* AllocPGMPTimerChild();
+ bool DeallocPGMPTimerChild(PGMPTimerChild* aActor);
+
+ PGMPStorageChild* AllocPGMPStorageChild();
+ bool DeallocPGMPStorageChild(PGMPStorageChild* aActor);
+
+ void GMPContentChildActorDestroy(GMPContentChild* aGMPContentChild);
+
+ mozilla::ipc::IPCResult RecvCrashPluginNow();
+ mozilla::ipc::IPCResult RecvCloseActive();
+
+ mozilla::ipc::IPCResult RecvInitGMPContentChild(
+ Endpoint<PGMPContentChild>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvTestTriggerMetrics(
+ TestTriggerMetricsResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvInitProfiler(
+ Endpoint<mozilla::PProfilerChild>&& aEndpoint);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI,
+ const nsACString& aKeySystem = ""_ns);
+
+ nsTArray<std::pair<nsCString, nsCString>> MakeCDMHostVerificationPaths(
+ const nsACString& aPluginLibPath);
+
+ nsTArray<RefPtr<GMPContentChild>> mGMPContentChildren;
+
+ RefPtr<GMPTimerChild> mTimerChild;
+ RefPtr<GMPStorageChild> mStorage;
+
+ RefPtr<ChildProfilerController> mProfilerController;
+
+ MessageLoop* mGMPMessageLoop;
+ nsString mPluginPath;
+ nsCString mStorageId;
+ UniquePtr<GMPLoader> mGMPLoader;
+#ifdef XP_LINUX
+ nsTArray<void*> mLibHandles;
+#endif
+};
+
+} // 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 0000000000..85fc6e7a70
--- /dev/null
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "ChromiumCDMChild.h"
+#include "base/task.h"
+#include "GMPUtils.h"
+
+namespace mozilla::gmp {
+
+MessageLoop* GMPContentChild::GMPMessageLoop() {
+ return mGMPChild->GMPMessageLoop();
+}
+
+void GMPContentChild::CheckThread() {
+ MOZ_ASSERT(mGMPChild->mGMPMessageLoop == MessageLoop::current());
+}
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+mozilla::ipc::IPCResult GMPContentChild::RecvInitSandboxTesting(
+ Endpoint<PSandboxTestingChild>&& aEndpoint) {
+ if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) {
+ return IPC_FAIL(
+ this, "InitSandboxTesting failed to initialise the child process.");
+ }
+ return IPC_OK();
+}
+#endif
+
+void GMPContentChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mGMPChild->GMPContentChildActorDestroy(this);
+}
+
+void GMPContentChild::ProcessingError(Result aCode, const char* aReason) {
+ mGMPChild->ProcessingError(aCode, aReason);
+}
+
+already_AddRefed<PGMPVideoDecoderChild>
+GMPContentChild::AllocPGMPVideoDecoderChild() {
+ return MakeAndAddRef<GMPVideoDecoderChild>(this);
+}
+
+already_AddRefed<PGMPVideoEncoderChild>
+GMPContentChild::AllocPGMPVideoEncoderChild() {
+ return MakeAndAddRef<GMPVideoEncoderChild>(this);
+}
+
+already_AddRefed<PChromiumCDMChild> GMPContentChild::AllocPChromiumCDMChild(
+ const nsACString& aKeySystem) {
+ return MakeAndAddRef<ChromiumCDMChild>(this);
+}
+
+mozilla::ipc::IPCResult GMPContentChild::RecvPGMPVideoDecoderConstructor(
+ PGMPVideoDecoderChild* aActor) {
+ auto vdc = static_cast<GMPVideoDecoderChild*>(aActor);
+
+ void* vd = nullptr;
+ GMPErr err = mGMPChild->GetAPI(GMP_API_VIDEO_DECODER, &vdc->Host(), &vd);
+ if (err != GMPNoErr || !vd) {
+ return IPC_FAIL(this, "GMPGetAPI call failed trying to construct decoder.");
+ }
+
+ vdc->Init(static_cast<GMPVideoDecoder*>(vd));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult 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) {
+ return IPC_FAIL(this, "GMPGetAPI call failed trying to construct encoder.");
+ }
+
+ vec->Init(static_cast<GMPVideoEncoder*>(ve));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPContentChild::RecvPChromiumCDMConstructor(
+ PChromiumCDMChild* aActor, const nsACString& aKeySystem) {
+ ChromiumCDMChild* child = static_cast<ChromiumCDMChild*>(aActor);
+ cdm::Host_10* host10 = child;
+
+ void* cdm = nullptr;
+ GMPErr err = mGMPChild->GetAPI(CHROMIUM_CDM_API, host10, &cdm, aKeySystem);
+ if (err != GMPNoErr || !cdm) {
+ return IPC_FAIL(this, "GMPGetAPI call failed trying to get CDM.");
+ }
+
+ child->Init(static_cast<cdm::ContentDecryptionModule_10*>(cdm),
+ mGMPChild->mStorageId);
+
+ return IPC_OK();
+}
+
+void GMPContentChild::CloseActive() {
+ // Invalidate and remove any remaining API objects.
+ const ManagedContainer<PGMPVideoDecoderChild>& videoDecoders =
+ ManagedPGMPVideoDecoderChild();
+ for (const auto& key : videoDecoders) {
+ key->SendShutdown();
+ }
+
+ const ManagedContainer<PGMPVideoEncoderChild>& videoEncoders =
+ ManagedPGMPVideoEncoderChild();
+ for (const auto& key : videoEncoders) {
+ key->SendShutdown();
+ }
+
+ const ManagedContainer<PChromiumCDMChild>& cdms = ManagedPChromiumCDMChild();
+ for (const auto& key : cdms) {
+ key->SendShutdown();
+ }
+}
+
+bool GMPContentChild::IsUsed() {
+ return !ManagedPGMPVideoDecoderChild().IsEmpty() ||
+ !ManagedPGMPVideoEncoderChild().IsEmpty() ||
+ !ManagedPChromiumCDMChild().IsEmpty();
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPContentChild.h b/dom/media/gmp/GMPContentChild.h
new file mode 100644
index 0000000000..a31b48b00f
--- /dev/null
+++ b/dom/media/gmp/GMPContentChild.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 GMPContentChild_h_
+#define GMPContentChild_h_
+
+#include "mozilla/gmp/PGMPContentChild.h"
+#include "GMPSharedMemManager.h"
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+# include "mozilla/SandboxTestingChild.h"
+#endif
+
+namespace mozilla::gmp {
+
+class GMPChild;
+
+class GMPContentChild : public PGMPContentChild, public GMPSharedMem {
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPContentChild.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPContentChild, final)
+
+ explicit GMPContentChild(GMPChild* aChild) : mGMPChild(aChild) {}
+
+ MessageLoop* GMPMessageLoop();
+
+ mozilla::ipc::IPCResult RecvPGMPVideoDecoderConstructor(
+ PGMPVideoDecoderChild* aActor) override;
+ mozilla::ipc::IPCResult RecvPGMPVideoEncoderConstructor(
+ PGMPVideoEncoderChild* aActor) override;
+ mozilla::ipc::IPCResult RecvPChromiumCDMConstructor(
+ PChromiumCDMChild* aActor, const nsACString& aKeySystem) override;
+
+ already_AddRefed<PGMPVideoDecoderChild> AllocPGMPVideoDecoderChild();
+
+ already_AddRefed<PGMPVideoEncoderChild> AllocPGMPVideoEncoderChild();
+
+ already_AddRefed<PChromiumCDMChild> AllocPChromiumCDMChild(
+ const nsACString& aKeySystem);
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ mozilla::ipc::IPCResult RecvInitSandboxTesting(
+ Endpoint<PSandboxTestingChild>&& aEndpoint);
+#endif
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ // GMPSharedMem
+ void CheckThread() override;
+
+ void CloseActive();
+ bool IsUsed();
+
+ GMPChild* mGMPChild;
+
+ private:
+ ~GMPContentChild() = default;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPContentChild_h_
diff --git a/dom/media/gmp/GMPContentParent.cpp b/dom/media/gmp/GMPContentParent.cpp
new file mode 100644
index 0000000000..153d4da7fe
--- /dev/null
+++ b/dom/media/gmp/GMPContentParent.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "GMPLog.h"
+#include "GMPParent.h"
+#include "GMPServiceChild.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "ChromiumCDMParent.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Unused.h"
+#include "base/task.h"
+
+namespace mozilla::gmp {
+
+static const char* GetBoolString(bool aBool) {
+ return aBool ? "true" : "false";
+}
+
+GMPContentParent::GMPContentParent(GMPParent* aParent)
+ : mParent(aParent), mPluginId(0) {
+ GMP_LOG_DEBUG("GMPContentParent::GMPContentParent(this=%p), aParent=%p", this,
+ aParent);
+ if (mParent) {
+ SetDisplayName(mParent->GetDisplayName());
+ SetPluginId(mParent->GetPluginId());
+ SetPluginType(mParent->GetPluginType());
+ }
+}
+
+GMPContentParent::~GMPContentParent() {
+ GMP_LOG_DEBUG(
+ "GMPContentParent::~GMPContentParent(this=%p) mVideoDecoders.IsEmpty=%s, "
+ "mVideoEncoders.IsEmpty=%s, mChromiumCDMs.IsEmpty=%s, "
+ "mCloseBlockerCount=%" PRIu32,
+ this, GetBoolString(mVideoDecoders.IsEmpty()),
+ GetBoolString(mVideoEncoders.IsEmpty()),
+ GetBoolString(mChromiumCDMs.IsEmpty()), mCloseBlockerCount);
+}
+
+void GMPContentParent::ActorDestroy(ActorDestroyReason aWhy) {
+ GMP_LOG_DEBUG("GMPContentParent::ActorDestroy(this=%p, aWhy=%d)", this,
+ static_cast<int>(aWhy));
+ MOZ_ASSERT(mVideoDecoders.IsEmpty() && mVideoEncoders.IsEmpty() &&
+ mChromiumCDMs.IsEmpty());
+}
+
+void GMPContentParent::CheckThread() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+}
+
+void GMPContentParent::ChromiumCDMDestroyed(ChromiumCDMParent* aCDM) {
+ GMP_LOG_DEBUG("GMPContentParent::ChromiumCDMDestroyed(this=%p, aCDM=%p)",
+ this, aCDM);
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+
+ MOZ_ALWAYS_TRUE(mChromiumCDMs.RemoveElement(aCDM));
+ CloseIfUnused();
+}
+
+void GMPContentParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder) {
+ GMP_LOG_DEBUG("GMPContentParent::VideoDecoderDestroyed(this=%p, aDecoder=%p)",
+ this, aDecoder);
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+
+ // 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) {
+ GMP_LOG_DEBUG("GMPContentParent::VideoEncoderDestroyed(this=%p, aEncoder=%p)",
+ this, aEncoder);
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+
+ // If the constructor fails, we'll get called before it's added
+ Unused << NS_WARN_IF(!mVideoEncoders.RemoveElement(aEncoder));
+ CloseIfUnused();
+}
+
+void GMPContentParent::AddCloseBlocker() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ ++mCloseBlockerCount;
+ GMP_LOG_DEBUG(
+ "GMPContentParent::AddCloseBlocker(this=%p) mCloseBlockerCount=%" PRIu32,
+ this, mCloseBlockerCount);
+}
+
+void GMPContentParent::RemoveCloseBlocker() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ --mCloseBlockerCount;
+ GMP_LOG_DEBUG(
+ "GMPContentParent::RemoveCloseBlocker(this=%p) "
+ "mCloseBlockerCount=%" PRIu32,
+ this, mCloseBlockerCount);
+ CloseIfUnused();
+}
+
+void GMPContentParent::CloseIfUnused() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_LOG_DEBUG(
+ "GMPContentParent::CloseIfUnused(this=%p) mVideoDecoders.IsEmpty=%s, "
+ "mVideoEncoders.IsEmpty=%s, mChromiumCDMs.IsEmpty=%s, "
+ "mCloseBlockerCount=%" PRIu32,
+ this, GetBoolString(mVideoDecoders.IsEmpty()),
+ GetBoolString(mVideoEncoders.IsEmpty()),
+ GetBoolString(mChromiumCDMs.IsEmpty()), mCloseBlockerCount);
+ if (mVideoDecoders.IsEmpty() && mVideoEncoders.IsEmpty() &&
+ mChromiumCDMs.IsEmpty() && mCloseBlockerCount == 0) {
+ RefPtr<GMPContentParent> toClose;
+ if (mParent) {
+ toClose = mParent->ForgetGMPContentParent();
+ } else {
+ toClose = this;
+ RefPtr<GeckoMediaPluginServiceChild> gmp(
+ GeckoMediaPluginServiceChild::GetSingleton());
+ if (gmp) {
+ gmp->RemoveGMPContentParent(toClose);
+ }
+ }
+ NS_DispatchToCurrentThread(NewRunnableMethod(
+ "gmp::GMPContentParent::Close", toClose, &GMPContentParent::Close));
+ }
+}
+
+nsCOMPtr<nsISerialEventTarget> GMPContentParent::GMPEventTarget() {
+ if (!mGMPEventTarget) {
+ GMP_LOG_DEBUG("GMPContentParent::GMPEventTarget(this=%p)", this);
+ 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 mGMPEventTarget, 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.
+ nsCOMPtr<nsIThread> gmpThread;
+ mps->GetThread(getter_AddRefs(gmpThread));
+ MOZ_ASSERT(gmpThread);
+
+ mGMPEventTarget = gmpThread;
+ }
+
+ return mGMPEventTarget;
+}
+
+already_AddRefed<ChromiumCDMParent> GMPContentParent::GetChromiumCDM(
+ const nsCString& aKeySystem) {
+ GMP_LOG_DEBUG("GMPContentParent::GetChromiumCDM(this=%p aKeySystem=%s)", this,
+ aKeySystem.get());
+
+ RefPtr<ChromiumCDMParent> parent = new ChromiumCDMParent(this, GetPluginId());
+ if (!SendPChromiumCDMConstructor(parent, aKeySystem)) {
+ return nullptr;
+ }
+
+ // TODO: Remove parent from mChromiumCDMs in ChromiumCDMParent::Destroy().
+ mChromiumCDMs.AppendElement(parent);
+
+ return parent.forget();
+}
+
+nsresult GMPContentParent::GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD) {
+ GMP_LOG_DEBUG("GMPContentParent::GetGMPVideoDecoder(this=%p)", this);
+
+ RefPtr<GMPVideoDecoderParent> vdp = new GMPVideoDecoderParent(this);
+ if (!SendPGMPVideoDecoderConstructor(vdp)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ vdp.get()->AddRef();
+ *aGMPVD = vdp;
+ mVideoDecoders.AppendElement(vdp);
+
+ return NS_OK;
+}
+
+nsresult GMPContentParent::GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE) {
+ GMP_LOG_DEBUG("GMPContentParent::GetGMPVideoEncoder(this=%p)", this);
+
+ RefPtr<GMPVideoEncoderParent> vep = new GMPVideoEncoderParent(this);
+ if (!SendPGMPVideoEncoderConstructor(vep)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This addref corresponds to the Proxy pointer the consumer is returned.
+ // It's dropped by calling Close() on the interface.
+ vep.get()->AddRef();
+ *aGMPVE = vep;
+ mVideoEncoders.AppendElement(vep);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPContentParent.h b/dom/media/gmp/GMPContentParent.h
new file mode 100644
index 0000000000..eb68e0fd4a
--- /dev/null
+++ b/dom/media/gmp/GMPContentParent.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "GMPNativeTypes.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::gmp {
+
+class GMPParent;
+class GMPVideoDecoderParent;
+class GMPVideoEncoderParent;
+class ChromiumCDMParent;
+
+class GMPContentParent final : public PGMPContentParent, public GMPSharedMem {
+ friend class PGMPContentParent;
+
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPContentParent.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPContentParent, final)
+
+ explicit GMPContentParent(GMPParent* aParent = nullptr);
+
+ nsresult GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD);
+ void VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder);
+
+ nsresult GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE);
+ void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder);
+
+ already_AddRefed<ChromiumCDMParent> GetChromiumCDM(
+ const nsCString& aKeySystem);
+ void ChromiumCDMDestroyed(ChromiumCDMParent* aCDM);
+
+ nsCOMPtr<nsISerialEventTarget> GMPEventTarget();
+
+ // GMPSharedMem
+ void CheckThread() override;
+
+ void SetDisplayName(const nsCString& aDisplayName) {
+ mDisplayName = aDisplayName;
+ }
+ const nsCString& GetDisplayName() const { return mDisplayName; }
+ void SetPluginId(const uint32_t aPluginId) { mPluginId = aPluginId; }
+ uint32_t GetPluginId() const { return mPluginId; }
+ void SetPluginType(GMPPluginType aPluginType) { mPluginType = aPluginType; }
+ GMPPluginType GetPluginType() const { return mPluginType; }
+
+ class CloseBlocker {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CloseBlocker)
+
+ explicit CloseBlocker(GMPContentParent* aParent) : mParent(aParent) {
+ mParent->AddCloseBlocker();
+ }
+ RefPtr<GMPContentParent> mParent;
+
+ private:
+ ~CloseBlocker() { mParent->RemoveCloseBlocker(); }
+ };
+
+ private:
+ void AddCloseBlocker();
+ void RemoveCloseBlocker();
+
+ ~GMPContentParent();
+
+ void ActorDestroy(ActorDestroyReason aWhy) 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<ChromiumCDMParent>> mChromiumCDMs;
+ nsCOMPtr<nsISerialEventTarget> mGMPEventTarget;
+ RefPtr<GMPParent> mParent;
+ nsCString mDisplayName;
+ uint32_t mPluginId;
+ GMPPluginType mPluginType = GMPPluginType::Unknown;
+ uint32_t mCloseBlockerCount = 0;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPParent_h_
diff --git a/dom/media/gmp/GMPCrashHelper.h b/dom/media/gmp/GMPCrashHelper.h
new file mode 100644
index 0000000000..0034d54ee7
--- /dev/null
+++ b/dom/media/gmp/GMPCrashHelper.h
@@ -0,0 +1,37 @@
+/* -*- 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(GMPCrashHelper_h_)
+# define GMPCrashHelper_h_
+
+# include "MainThreadUtils.h"
+# include "nsISupportsImpl.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+// 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_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
+ GMPCrashHelper);
+
+ // Called on the main thread.
+ virtual already_AddRefed<nsPIDOMWindowInner>
+ GetPluginCrashedEventTarget() = 0;
+
+ protected:
+ virtual ~GMPCrashHelper() { MOZ_ASSERT(NS_IsMainThread()); }
+};
+
+} // namespace mozilla
+
+#endif // GMPCrashHelper_h_
diff --git a/dom/media/gmp/GMPCrashHelperHolder.cpp b/dom/media/gmp/GMPCrashHelperHolder.cpp
new file mode 100644
index 0000000000..1c0182155b
--- /dev/null
+++ b/dom/media/gmp/GMPCrashHelperHolder.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPCrashHelperHolder.h"
+#include "GMPService.h"
+#include "mozilla/RefPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla {
+
+void GMPCrashHelperHolder::SetCrashHelper(GMPCrashHelper* aHelper) {
+ mCrashHelper = aHelper;
+}
+
+GMPCrashHelper* GMPCrashHelperHolder::GetCrashHelper() { return mCrashHelper; }
+
+void GMPCrashHelperHolder::MaybeDisconnect(bool aAbnormalShutdown) {
+ if (!aAbnormalShutdown) {
+ RefPtr<gmp::GeckoMediaPluginService> service(
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (service) {
+ service->DisconnectCrashHelper(GetCrashHelper());
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPCrashHelperHolder.h b/dom/media/gmp/GMPCrashHelperHolder.h
new file mode 100644
index 0000000000..d8c63234da
--- /dev/null
+++ b/dom/media/gmp/GMPCrashHelperHolder.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 GMPCrashHelperHolder_h_
+#define GMPCrashHelperHolder_h_
+
+#include "mozilla/RefPtr.h"
+#include "GMPCrashHelper.h"
+
+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);
+
+ GMPCrashHelper* GetCrashHelper();
+
+ void MaybeDisconnect(bool aAbnormalShutdown);
+
+ private:
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPCrashHelperHolder_h_
diff --git a/dom/media/gmp/GMPDiskStorage.cpp b/dom/media/gmp/GMPDiskStorage.cpp
new file mode 100644
index 0000000000..5f7a023b9b
--- /dev/null
+++ b/dom/media/gmp/GMPDiskStorage.cpp
@@ -0,0 +1,454 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::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 nsAString& aGMPName,
+ const nsACString& 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->AppendNative("storage"_ns);
+ if (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 nsACString& aNodeId, const nsAString& aGMPName)
+ : mNodeId(aNodeId), mGMPName(aGMPName) {}
+
+ ~GMPDiskStorage() {
+ // Close all open file handles.
+ for (const auto& record : mRecords.Values()) {
+ 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_WARN_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_WARN_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_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ mRecords.InsertOrUpdate(recordName,
+ MakeUnique<Record>(filename, recordName));
+ }
+
+ return NS_OK;
+ }
+
+ GMPErr Open(const nsACString& aRecordName) override {
+ MOZ_ASSERT(!IsOpen(aRecordName));
+
+ Record* const record =
+ mRecords.WithEntryHandle(aRecordName, [&](auto&& entry) -> Record* {
+ if (!entry) {
+ // New file.
+ nsAutoString filename;
+ nsresult rv = GetUnusedFilename(aRecordName, filename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return entry.Insert(MakeUnique<Record>(filename, aRecordName))
+ .get();
+ }
+
+ return entry->get();
+ });
+ if (!record) {
+ return GMPGenericErr;
+ }
+
+ MOZ_ASSERT(record);
+ if (record->mFileDesc) {
+ NS_WARNING("Tried to open already open record");
+ return GMPRecordInUse;
+ }
+
+ nsresult 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 nsACString& 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 nsACString& 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_WARN_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 nsACString& 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_WARN_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.BeginReading(),
+ 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;
+ }
+
+ void Close(const nsACString& 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 nsAString& 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 nsACString& aNodeId,
+ const nsAString& 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 mozilla::gmp
diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp
new file mode 100644
index 0000000000..9fa7756165
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 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 "nsExceptionHandler.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+#include "prenv.h"
+#include "prerror.h"
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+# include "mozilla/sandboxTarget.h"
+# include "mozilla/sandboxing/SandboxInitialization.h"
+# include "mozilla/sandboxing/sandboxLogging.h"
+#endif
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+# include "mozilla/SandboxInfo.h"
+#endif
+
+#include <string>
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+namespace mozilla::gmp {
+class PassThroughGMPAdapter : public GMPAdapter {
+ public:
+ ~PassThroughGMPAdapter() override {
+ // 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 (NS_WARN_IF(!mLib)) {
+ MOZ_CRASH("Missing library!");
+ return GMPGenericErr;
+ }
+ GMPInitFunc initFunc =
+ reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
+ if (!initFunc) {
+ MOZ_CRASH("Missing init method!");
+ return GMPNotImplementedErr;
+ }
+ return initFunc(aPlatformAPI);
+ }
+
+ GMPErr GMPGetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI,
+ const nsACString& /* aKeySystem */) 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;
+ }
+ }
+
+ private:
+ PRLibrary* mLib = nullptr;
+};
+
+bool GMPLoader::Load(const char* aUTF8LibPath, uint32_t aUTF8LibPathLen,
+ const GMPPlatformAPI* aPlatformAPI, GMPAdapter* aAdapter) {
+ CrashReporter::AutoAnnotateCrashReport autoLibPath(
+ CrashReporter::Annotation::GMPLibraryPath,
+ nsDependentCString(aUTF8LibPath));
+
+ if (!getenv("MOZ_DISABLE_GMP_SANDBOX") && mSandboxStarter &&
+ !mSandboxStarter->Start(aUTF8LibPath)) {
+ MOZ_CRASH("Cannot start sandbox!");
+ return false;
+ }
+
+ // Load the GMP.
+ PRLibSpec libSpec;
+#ifdef XP_WIN
+ int pathLen = MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, nullptr, 0);
+ if (pathLen == 0) {
+ MOZ_CRASH("Cannot get path length as wide char!");
+ return false;
+ }
+
+ auto widePath = MakeUnique<wchar_t[]>(pathLen);
+ if (MultiByteToWideChar(CP_UTF8, 0, aUTF8LibPath, -1, widePath.get(),
+ pathLen) == 0) {
+ MOZ_CRASH("Cannot convert path to wide char!");
+ 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) {
+ MOZ_CRASH_UNSAFE_PRINTF("Cannot load plugin as library %d %d",
+ PR_GetError(), PR_GetOSError());
+ return false;
+ }
+
+ mAdapter.reset((!aAdapter) ? new PassThroughGMPAdapter() : aAdapter);
+ mAdapter->SetAdaptee(lib);
+
+ if (mAdapter->GMPInit(aPlatformAPI) != GMPNoErr) {
+ MOZ_CRASH("Cannot initialize plugin adapter!");
+ return false;
+ }
+
+ return true;
+}
+
+GMPErr GMPLoader::GetAPI(const char* aAPIName, void* aHostAPI,
+ void** aPluginAPI, const nsACString& aKeySystem) {
+ return mAdapter->GMPGetAPI(aAPIName, aHostAPI, aPluginAPI, aKeySystem);
+}
+
+void GMPLoader::Shutdown() {
+ if (mAdapter) {
+ mAdapter->GMPShutdown();
+ }
+}
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+class WinSandboxStarter : public mozilla::gmp::SandboxStarter {
+ public:
+ bool Start(const char* aLibPath) override {
+ // Cause advapi32 to load before the sandbox is turned on, as
+ // Widevine version 970 and later require it and the sandbox
+ // blocks it on Win7.
+ unsigned int dummy_rand;
+ rand_s(&dummy_rand);
+
+ mozilla::SandboxTarget::Instance()->StartSandbox();
+ return true;
+ }
+};
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+namespace {
+class LinuxSandboxStarter : public mozilla::gmp::SandboxStarter {
+ private:
+ LinuxSandboxStarter() = default;
+ friend mozilla::detail::UniqueSelector<LinuxSandboxStarter>::SingleObject
+ mozilla::MakeUnique<LinuxSandboxStarter>();
+
+ public:
+ static UniquePtr<SandboxStarter> Make() {
+ if (mozilla::SandboxInfo::Get().CanSandboxMedia()) {
+ return MakeUnique<LinuxSandboxStarter>();
+ }
+ // Sandboxing isn't possible, but the parent has already
+ // checked that this plugin doesn't require it. (Bug 1074561)
+ return nullptr;
+ }
+ bool Start(const char* aLibPath) override {
+ mozilla::SetMediaPluginSandbox(aLibPath);
+ return true;
+ }
+};
+} // anonymous namespace
+#endif // XP_LINUX && MOZ_SANDBOX
+
+static UniquePtr<SandboxStarter> MakeSandboxStarter() {
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ return mozilla::MakeUnique<WinSandboxStarter>();
+#elif defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ return LinuxSandboxStarter::Make();
+#else
+ return nullptr;
+#endif
+}
+
+GMPLoader::GMPLoader() : mSandboxStarter(MakeSandboxStarter()) {}
+
+bool GMPLoader::CanSandbox() const { return !!mSandboxStarter; }
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPLoader.h b/dom/media/gmp/GMPLoader.h
new file mode 100644
index 0000000000..a0e1513d34
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 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"
+#include "nsString.h"
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+namespace mozilla::gmp {
+
+class SandboxStarter {
+ public:
+ virtual ~SandboxStarter() = default;
+ virtual bool Start(const char* aLibPath) = 0;
+};
+
+// Interface that adapts a plugin to the GMP API.
+class GMPAdapter {
+ public:
+ virtual ~GMPAdapter() = default;
+ // 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;
+ // The `aKeySystem` arg is used to specify the key system when loading CDMs,
+ // and will be ignored by non-CDM GMPs. It is not part of the public GMP API
+ // Gecko exposes.
+ virtual GMPErr GMPGetAPI(const char* aAPIName, void* aHostAPI,
+ void** aPluginAPI, const nsACString& aKeySystem) = 0;
+ virtual void GMPShutdown() = 0;
+};
+
+// Encapsulates activating the sandbox, and loading the GMP.
+// Load() takes an optional GMPAdapter which can be used to adapt non-GMPs
+// to adhere to the GMP API.
+class GMPLoader {
+ public:
+ GMPLoader();
+
+ // Activates the sandbox, then loads the GMP library. 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.
+ bool Load(const char* aUTF8LibPath, uint32_t aLibPathLen,
+ const GMPPlatformAPI* aPlatformAPI, GMPAdapter* aAdapter = nullptr);
+
+ // Retrieves an interface pointer from the GMP. If the GMP is loading a CDM,
+ // aKeySystem is passed to the CDM to allow for key system specific
+ // configuration by the CDM.
+ GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI,
+ const nsACString& aKeySystem);
+
+ // Calls the GMPShutdown function exported by the GMP lib, and unloads the
+ // plugin library.
+ void Shutdown();
+
+ bool CanSandbox() const;
+
+ private:
+ UniquePtr<SandboxStarter> mSandboxStarter;
+ UniquePtr<GMPAdapter> mAdapter;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMP_LOADER_H__
diff --git a/dom/media/gmp/GMPLog.h b/dom/media/gmp/GMPLog.h
new file mode 100644
index 0000000000..94e797e827
--- /dev/null
+++ b/dom/media/gmp/GMPLog.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 DOM_MEDIA_GMPLOG_H_
+#define DOM_MEDIA_GMPLOG_H_
+
+#include "content_decryption_module.h"
+#include "gmp-video-codec.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+extern LogModule* GetGMPLibraryLog();
+extern GMPLogLevel GetGMPLibraryLogLevel();
+
+#define GMP_LOG_ERROR(msg, ...) \
+ MOZ_LOG(GetGMPLog(), LogLevel::Error, (msg, ##__VA_ARGS__))
+#define GMP_LOG_WARNING(msg, ...) \
+ MOZ_LOG(GetGMPLog(), LogLevel::Warning, (msg, ##__VA_ARGS__))
+#define GMP_LOG_INFO(msg, ...) \
+ MOZ_LOG(GetGMPLog(), LogLevel::Info, (msg, ##__VA_ARGS__))
+#define GMP_LOG_DEBUG(msg, ...) \
+ MOZ_LOG(GetGMPLog(), LogLevel::Debug, (msg, ##__VA_ARGS__))
+#define GMP_LOG_VERBOSE(msg, ...) \
+ MOZ_LOG(GetGMPLog(), LogLevel::Verbose, (msg, ##__VA_ARGS__))
+
+// Helpers
+
+inline const char* CdmStatusToString(cdm::Status aStatus) {
+ switch (aStatus) {
+ case cdm::Status::kSuccess:
+ return "success";
+ case cdm::Status::kNeedMoreData:
+ return "need more data";
+ case cdm::Status::kNoKey:
+ return "no key";
+ case cdm::Status::kInitializationError:
+ return "initialization error";
+ case cdm::Status::kDecryptError:
+ return "decrypt error";
+ case cdm::Status::kDecodeError:
+ return "decode error";
+ case cdm::Status::kDeferredInitialization:
+ return "deferred initialization";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Should have coverage of entire enum");
+ return "unexpected status code"; // Gracefully handle disabled asserts.
+ }
+}
+
+inline const char* CdmStatusToString(uint32_t aStatus) {
+ return CdmStatusToString(cdm::Status(aStatus));
+}
+
+// End helpers
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_GMPLOG_H_
diff --git a/dom/media/gmp/GMPMemoryStorage.cpp b/dom/media/gmp/GMPMemoryStorage.cpp
new file mode 100644
index 0000000000..fe8cfee5f8
--- /dev/null
+++ b/dom/media/gmp/GMPMemoryStorage.cpp
@@ -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/. */
+
+#include "GMPStorage.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla::gmp {
+
+class GMPMemoryStorage : public GMPStorage {
+ public:
+ GMPErr Open(const nsACString& aRecordName) override {
+ MOZ_ASSERT(!IsOpen(aRecordName));
+
+ Record* record = mRecords.GetOrInsertNew(aRecordName);
+ record->mIsOpen = true;
+ return GMPNoErr;
+ }
+
+ bool IsOpen(const nsACString& aRecordName) const override {
+ const Record* record = mRecords.Get(aRecordName);
+ if (!record) {
+ return false;
+ }
+ return record->mIsOpen;
+ }
+
+ GMPErr Read(const nsACString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) override {
+ const Record* record = mRecords.Get(aRecordName);
+ if (!record) {
+ return GMPGenericErr;
+ }
+ aOutBytes = record->mData.Clone();
+ return GMPNoErr;
+ }
+
+ GMPErr Write(const nsACString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) override {
+ Record* record = nullptr;
+ if (!mRecords.Get(aRecordName, &record)) {
+ return GMPClosedErr;
+ }
+ record->mData = aBytes.Clone();
+ return GMPNoErr;
+ }
+
+ void Close(const nsACString& 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 mozilla::gmp
diff --git a/dom/media/gmp/GMPMessageUtils.h b/dom/media/gmp/GMPMessageUtils.h
new file mode 100644
index 0000000000..29740a49a6
--- /dev/null
+++ b/dom/media/gmp/GMPMessageUtils.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "GMPNativeTypes.h"
+#include "GMPSanitizedExports.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<GMPPluginType>
+ : public ContiguousEnumSerializerInclusive<
+ GMPPluginType, GMPPluginType::Unknown, GMPPluginType::Widevine> {};
+
+template <>
+struct ParamTraits<GMPErr>
+ : public ContiguousEnumSerializer<GMPErr, GMPNoErr, GMPLastErr> {};
+
+template <>
+struct ParamTraits<GMPVideoFrameType>
+ : public ContiguousEnumSerializer<GMPVideoFrameType, kGMPKeyFrame,
+ kGMPVideoFrameInvalid> {};
+
+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<GMPLogLevel>
+ : public ContiguousEnumSerializerInclusive<GMPLogLevel, kGMPLogDefault,
+ kGMPLogInvalid> {};
+
+template <>
+struct ParamTraits<GMPBufferType>
+ : public ContiguousEnumSerializer<GMPBufferType, GMP_BufferSingle,
+ GMP_BufferInvalid> {};
+
+template <>
+struct ParamTraits<cdm::EncryptionScheme>
+ : public ContiguousEnumSerializerInclusive<
+ cdm::EncryptionScheme, cdm::EncryptionScheme::kUnencrypted,
+ cdm::EncryptionScheme::kCbcs> {};
+
+template <>
+struct ParamTraits<cdm::HdcpVersion>
+ : public ContiguousEnumSerializerInclusive<
+ cdm::HdcpVersion, cdm::HdcpVersion::kHdcpVersionNone,
+ cdm::HdcpVersion::kHdcpVersion2_2> {};
+
+template <>
+struct ParamTraits<GMPSimulcastStream> {
+ typedef GMPSimulcastStream paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mWidth);
+ WriteParam(aWriter, aParam.mHeight);
+ WriteParam(aWriter, aParam.mNumberOfTemporalLayers);
+ WriteParam(aWriter, aParam.mMaxBitrate);
+ WriteParam(aWriter, aParam.mTargetBitrate);
+ WriteParam(aWriter, aParam.mMinBitrate);
+ WriteParam(aWriter, aParam.mQPMax);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (ReadParam(aReader, &(aResult->mWidth)) &&
+ ReadParam(aReader, &(aResult->mHeight)) &&
+ ReadParam(aReader, &(aResult->mNumberOfTemporalLayers)) &&
+ ReadParam(aReader, &(aResult->mMaxBitrate)) &&
+ ReadParam(aReader, &(aResult->mTargetBitrate)) &&
+ ReadParam(aReader, &(aResult->mMinBitrate)) &&
+ ReadParam(aReader, &(aResult->mQPMax))) {
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<GMPVideoCodec> {
+ typedef GMPVideoCodec paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mGMPApiVersion);
+ WriteParam(aWriter, aParam.mCodecType);
+ WriteParam(aWriter, static_cast<const nsCString&>(
+ nsDependentCString(aParam.mPLName)));
+ WriteParam(aWriter, aParam.mPLType);
+ WriteParam(aWriter, aParam.mWidth);
+ WriteParam(aWriter, aParam.mHeight);
+ WriteParam(aWriter, aParam.mStartBitrate);
+ WriteParam(aWriter, aParam.mMaxBitrate);
+ WriteParam(aWriter, aParam.mMinBitrate);
+ WriteParam(aWriter, aParam.mMaxFramerate);
+ WriteParam(aWriter, aParam.mFrameDroppingOn);
+ WriteParam(aWriter, aParam.mKeyFrameInterval);
+ WriteParam(aWriter, aParam.mQPMax);
+ WriteParam(aWriter, aParam.mNumberOfSimulcastStreams);
+ for (uint32_t i = 0; i < aParam.mNumberOfSimulcastStreams; i++) {
+ WriteParam(aWriter, aParam.mSimulcastStream[i]);
+ }
+ WriteParam(aWriter, aParam.mMode);
+ WriteParam(aWriter, aParam.mUseThreadedDecode);
+ WriteParam(aWriter, aParam.mLogLevel);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ // NOTE: make sure this matches any versions supported
+ if (!ReadParam(aReader, &(aResult->mGMPApiVersion)) ||
+ (aResult->mGMPApiVersion != kGMPVersion33 &&
+ aResult->mGMPApiVersion != kGMPVersion34)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &(aResult->mCodecType))) {
+ return false;
+ }
+
+ nsAutoCString plName;
+ if (!ReadParam(aReader, &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(aReader, &(aResult->mPLType)) ||
+ !ReadParam(aReader, &(aResult->mWidth)) ||
+ !ReadParam(aReader, &(aResult->mHeight)) ||
+ !ReadParam(aReader, &(aResult->mStartBitrate)) ||
+ !ReadParam(aReader, &(aResult->mMaxBitrate)) ||
+ !ReadParam(aReader, &(aResult->mMinBitrate)) ||
+ !ReadParam(aReader, &(aResult->mMaxFramerate)) ||
+ !ReadParam(aReader, &(aResult->mFrameDroppingOn)) ||
+ !ReadParam(aReader, &(aResult->mKeyFrameInterval))) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &(aResult->mQPMax)) ||
+ !ReadParam(aReader, &(aResult->mNumberOfSimulcastStreams))) {
+ return false;
+ }
+
+ if (aResult->mNumberOfSimulcastStreams > kGMPMaxSimulcastStreams) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < aResult->mNumberOfSimulcastStreams; i++) {
+ if (!ReadParam(aReader, &(aResult->mSimulcastStream[i]))) {
+ return false;
+ }
+ }
+
+ if (!ReadParam(aReader, &(aResult->mMode)) ||
+ !ReadParam(aReader, &(aResult->mUseThreadedDecode)) ||
+ !ReadParam(aReader, &(aResult->mLogLevel))) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // GMPMessageUtils_h_
diff --git a/dom/media/gmp/GMPNativeTypes.h b/dom/media/gmp/GMPNativeTypes.h
new file mode 100644
index 0000000000..39cd6ddc05
--- /dev/null
+++ b/dom/media/gmp/GMPNativeTypes.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 GMPNativeTypes_h_
+#define GMPNativeTypes_h_
+
+enum class GMPPluginType {
+ Unknown,
+ Fake,
+ Clearkey,
+ OpenH264,
+ Widevine,
+};
+
+#endif
diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp
new file mode 100644
index 0000000000..6c1acc3a20
--- /dev/null
+++ b/dom/media/gmp/GMPParent.cpp
@@ -0,0 +1,1209 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "CDMStorageIdProvider.h"
+#include "ChromiumCDMAdapter.h"
+#include "GMPContentParent.h"
+#include "GMPLog.h"
+#include "GMPTimerParent.h"
+#include "MediaResult.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/dom/KeySystemNames.h"
+#include "mozilla/dom/WidevineCDMManifestBinding.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/ipc/CrashReporterHost.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxInfo.h"
+# include "base/shared_memory.h"
+#endif
+#include "mozilla/Services.h"
+#include "mozilla/SSE.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIRunnable.h"
+#include "nsIObserverService.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+#include "ProfilerParent.h"
+#include "runnable_utils.h"
+#ifdef XP_WIN
+# include "mozilla/FileUtilsWin.h"
+# include "WMFDecoderModule.h"
+#endif
+#if defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/java/GeckoProcessManagerWrappers.h"
+# include "mozilla/java/GeckoProcessTypeWrappers.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+# include "base/process_util.h"
+#endif // defined(XP_MACOSX)
+
+using mozilla::ipc::GeckoChildProcessHost;
+
+using CrashReporter::AnnotationTable;
+
+namespace mozilla::gmp {
+
+#define GMP_PARENT_LOG_DEBUG(x, ...) \
+ GMP_LOG_DEBUG("GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__)
+
+#ifdef __CLASS__
+# undef __CLASS__
+#endif
+#define __CLASS__ "GMPParent"
+
+GMPParent::GMPParent()
+ : mState(GMPState::NotLoaded),
+ mPluginId(GeckoChildProcessHost::GetUniqueID()),
+ mProcess(nullptr),
+ mDeleteProcessOnlyOnUnload(false),
+ mAbnormalShutdownInProgress(false),
+ mIsBlockingDeletion(false),
+ mCanDecrypt(false),
+ mGMPContentChildCount(0),
+ mChildPid(0),
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ mChildLaunchArch(base::PROCESS_ARCH_INVALID),
+#endif
+ mMainThread(GetMainThreadSerialEventTarget()) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("GMPParent ctor id=%u", mPluginId);
+}
+
+GMPParent::~GMPParent() {
+ // This method is not restricted to a specific thread.
+ GMP_PARENT_LOG_DEBUG("GMPParent dtor id=%u", mPluginId);
+ MOZ_ASSERT(!mProcess);
+}
+
+void GMPParent::CloneFrom(const GMPParent* aOther) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ 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;
+ mPluginType = aOther->mPluginType;
+#if defined(XP_WIN) || defined(XP_LINUX)
+ mLibs = aOther->mLibs;
+#endif
+ for (const GMPCapability& cap : aOther->mCapabilities) {
+ mCapabilities.AppendElement(cap);
+ }
+ mAdapter = aOther->mAdapter;
+
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ mChildLaunchArch = aOther->mChildLaunchArch;
+#endif
+}
+
+#if defined(XP_WIN) || defined(XP_MACOSX)
+nsresult GMPParent::GetPluginFileArch(nsIFile* aPluginDir,
+ const nsString& aBaseName,
+ uint32_t& aArchSet) {
+ // Build up the plugin filename
+# if defined(XP_MACOSX)
+ nsAutoString pluginFileName = u"lib"_ns + aBaseName + u".dylib"_ns;
+# elif defined(XP_WIN)
+ nsAutoString pluginFileName = aBaseName + u".dll"_ns;
+# endif
+ GMP_PARENT_LOG_DEBUG("%s: pluginFileName: %s", __FUNCTION__,
+ NS_LossyConvertUTF16toASCII(pluginFileName).get());
+
+ // Create an nsIFile representing the plugin
+ nsCOMPtr<nsIFile> pluginFile;
+ nsresult rv = aPluginDir->Clone(getter_AddRefs(pluginFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pluginFile->AppendRelativePath(pluginFileName);
+
+# if defined(XP_MACOSX)
+ // Get the full plugin path
+ nsAutoCString pluginPath;
+ rv = pluginFile->GetNativePath(pluginPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GMP_PARENT_LOG_DEBUG("%s: pluginPath: %s", __FUNCTION__, pluginPath.get());
+
+ rv = nsMacUtilsImpl::GetArchitecturesForBinary(pluginPath.get(), &aArchSet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+# if defined(__aarch64__)
+ mPluginFilePath = pluginPath;
+# endif
+# elif defined(XP_WIN)
+ // Get the full plugin path
+ nsAutoString pluginPath;
+ rv = pluginFile->GetTarget(pluginPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GMP_PARENT_LOG_DEBUG("%s: pluginPath: %s", __FUNCTION__,
+ NS_LossyConvertUTF16toASCII(pluginPath).get());
+
+ aArchSet = GetExecutableArchitecture(pluginPath.get());
+ if (aArchSet == base::PROCESS_ARCH_INVALID) {
+ return NS_ERROR_FAILURE;
+ }
+# endif
+
+ return NS_OK;
+}
+#endif // defined(XP_WIN) || defined(XP_MACOSX)
+
+RefPtr<GenericPromise> GMPParent::Init(GeckoMediaPluginServiceParent* aService,
+ nsIFile* aPluginDir) {
+ MOZ_ASSERT(aPluginDir);
+ MOZ_ASSERT(aService);
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+
+ 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_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ nsAutoString parentLeafName;
+ rv = parent->GetLeafName(parentLeafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__,
+ NS_LossyConvertUTF16toASCII(parentLeafName).get());
+
+ MOZ_ASSERT(parentLeafName.Length() > 4);
+ mName = Substring(parentLeafName, 4);
+
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ uint32_t pluginArch = base::PROCESS_ARCH_INVALID;
+ rv = GetPluginFileArch(aPluginDir, mName, pluginArch);
+ if (NS_FAILED(rv)) {
+ GMP_PARENT_LOG_DEBUG("%s: Plugin arch error: %d", __FUNCTION__, rv);
+ } else {
+ GMP_PARENT_LOG_DEBUG("%s: Plugin arch: 0x%x", __FUNCTION__, pluginArch);
+ }
+
+ const uint32_t x86 = base::PROCESS_ARCH_X86_64 | base::PROCESS_ARCH_I386;
+# ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ const uint32_t arm64 = base::PROCESS_ARCH_ARM_64;
+
+ mChildLaunchArch = pluginArch;
+ // When executing in an ARM64 process, if the library is x86 or x64,
+ // set |mChildLaunchArch| to x64 and allow the library to be used as long
+ // as this process is a universal binary.
+ if (!(pluginArch & arm64) && (pluginArch & x86)) {
+ bool isWidevine = parentLeafName.Find(u"widevine") != kNotFound;
+ bool isWidevineAllowed =
+ StaticPrefs::media_gmp_widevinecdm_allow_x64_plugin_on_arm64();
+ bool isH264 = parentLeafName.Find(u"openh264") != kNotFound;
+ bool isH264Allowed =
+ StaticPrefs::media_gmp_gmpopenh264_allow_x64_plugin_on_arm64();
+ bool isClearkey = parentLeafName.Find(u"clearkey") != kNotFound;
+ bool isClearkeyAllowed =
+ StaticPrefs::media_gmp_gmpclearkey_allow_x64_plugin_on_arm64();
+
+ // Only allow x64 child GMP processes for Widevine, OpenH264 and Clearkey
+ if (!isWidevine && !isH264 && !isClearkey) {
+ return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
+ __func__);
+ }
+ // And only if prefs permit it.
+ if ((isWidevine && !isWidevineAllowed) || (isH264 && !isH264Allowed) ||
+ (isClearkey && !isClearkeyAllowed)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_PLUGIN_DISABLED,
+ __func__);
+ }
+
+# ifdef XP_MACOSX
+ // We have an x64 library. Get the bundle architecture to determine
+ // if we are a universal binary and hence if we can launch an x64
+ // child process to host this plugin.
+ uint32_t bundleArch = base::PROCESS_ARCH_INVALID;
+ rv = nsMacUtilsImpl::GetArchitecturesForBundle(&bundleArch);
+ if (NS_FAILED(rv)) {
+ // If we fail here, continue as if this is not a univeral binary.
+ GMP_PARENT_LOG_DEBUG("%s: Bundle arch error: %d", __FUNCTION__, rv);
+ } else {
+ GMP_PARENT_LOG_DEBUG("%s: Bundle arch: 0x%x", __FUNCTION__, bundleArch);
+ }
+
+ bool isUniversalBinary = (bundleArch & base::PROCESS_ARCH_X86_64) &&
+ (bundleArch & base::PROCESS_ARCH_ARM_64);
+ if (isUniversalBinary) {
+ mChildLaunchArch = base::PROCESS_ARCH_X86_64;
+ PreTranslateBins();
+ } else {
+ return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
+ __func__);
+ }
+# endif
+ }
+# else
+ // When executing in a non-ARM process, if the library is not x86 or x64,
+ // remove it and return an error. This prevents a child process crash due
+ // to loading an incompatible library and forces a new plugin version to be
+ // downloaded when the check is next performed. This could occur if a profile
+ // is moved from an ARM64 system to an x64 system.
+ if ((pluginArch & x86) == 0) {
+ GMP_PARENT_LOG_DEBUG("%s: Removing plugin directory", __FUNCTION__);
+ aPluginDir->Remove(true);
+ return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
+ }
+# endif // defined(ALLOW_GECKO_CHILD_PROCESS_ARCH)
+#endif // defined(XP_WIN) || defined(XP_MACOSX)
+
+ return ReadGMPMetaData();
+}
+
+void GMPParent::Crash() {
+ if (mState != GMPState::NotLoaded) {
+ Unused << SendCrashPluginNow();
+ }
+}
+
+class NotifyGMPProcessLoadedTask : public Runnable {
+ public:
+ explicit NotifyGMPProcessLoadedTask(const ::base::ProcessId aProcessId,
+ GMPParent* aGMPParent)
+ : Runnable("NotifyGMPProcessLoadedTask"),
+ mProcessId(aProcessId),
+ mGMPParent(aGMPParent) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool canProfile = true;
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ if (SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia) &&
+ base::SharedMemory::UsingPosixShm()) {
+ canProfile = false;
+ }
+#endif
+
+ if (canProfile) {
+ nsCOMPtr<nsISerialEventTarget> gmpEventTarget =
+ mGMPParent->GMPEventTarget();
+ if (!gmpEventTarget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ipc::Endpoint<PProfilerChild> profilerParent(
+ ProfilerParent::CreateForProcess(mProcessId));
+
+ gmpEventTarget->Dispatch(
+ NewRunnableMethod<ipc::Endpoint<mozilla::PProfilerChild>&&>(
+ "GMPParent::SendInitProfiler", mGMPParent,
+ &GMPParent::SendInitProfiler, std::move(profilerParent)));
+ }
+
+ return NS_OK;
+ }
+
+ ::base::ProcessId mProcessId;
+ const RefPtr<GMPParent> mGMPParent;
+};
+
+nsresult GMPParent::LoadProcess() {
+ MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ MOZ_ASSERT(mState == GMPState::NotLoaded);
+
+ nsAutoString path;
+ if (NS_WARN_IF(NS_FAILED(mDirectory->GetPath(path)))) {
+ return NS_ERROR_FAILURE;
+ }
+ GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__,
+ NS_ConvertUTF16toUTF8(path).get());
+
+ if (!mProcess) {
+ mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ mProcess->SetRequiresWindowServer(mAdapter.EqualsLiteral("chromium"));
+#endif
+
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ mProcess->SetLaunchArchitecture(mChildLaunchArch);
+#endif
+
+ if (!mProcess->Launch(30 * 1000)) {
+ GMP_PARENT_LOG_DEBUG("%s: Failed to launch new child process",
+ __FUNCTION__);
+ mProcess->Delete();
+ mProcess = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mChildPid = mProcess->GetChildProcessId();
+ GMP_PARENT_LOG_DEBUG("%s: Launched new child process", __FUNCTION__);
+
+ bool opened = mProcess->TakeInitialEndpoint().Bind(this);
+ if (!opened) {
+ GMP_PARENT_LOG_DEBUG("%s: Failed to open channel to new child process",
+ __FUNCTION__);
+ mProcess->Delete();
+ mProcess = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ GMP_PARENT_LOG_DEBUG("%s: Opened channel to new child process",
+ __FUNCTION__);
+
+ // ComputeStorageId may return empty string, we leave the error handling to
+ // CDM. The CDM will reject the promise once we provide a empty string of
+ // storage id.
+ bool ok =
+ SendProvideStorageId(CDMStorageIdProvider::ComputeStorageId(mNodeId));
+ if (!ok) {
+ GMP_PARENT_LOG_DEBUG("%s: Failed to send storage id to child process",
+ __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ GMP_PARENT_LOG_DEBUG("%s: Sent storage id to child process", __FUNCTION__);
+
+#if defined(XP_WIN) || defined(XP_LINUX)
+ if (!mLibs.IsEmpty()) {
+ bool ok = SendPreloadLibs(mLibs);
+ if (!ok) {
+ GMP_PARENT_LOG_DEBUG("%s: Failed to send preload-libs to child process",
+ __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ GMP_PARENT_LOG_DEBUG("%s: Sent preload-libs ('%s') to child process",
+ __FUNCTION__, mLibs.get());
+ }
+#endif
+
+ NS_DispatchToMainThread(new NotifyGMPProcessLoadedTask(OtherPid(), this));
+
+ // Intr call to block initialization on plugin load.
+ if (!SendStartPlugin(mAdapter)) {
+ GMP_PARENT_LOG_DEBUG("%s: Failed to send start to child process",
+ __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ GMP_PARENT_LOG_DEBUG("%s: Sent StartPlugin to child process", __FUNCTION__);
+ }
+
+ mState = GMPState::Loaded;
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult GMPParent::RecvPGMPContentChildDestroyed() {
+ --mGMPContentChildCount;
+ if (!IsUsed()) {
+ CloseIfUnused();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPParent::RecvFOGData(ByteBuf&& aBuf) {
+ GMP_PARENT_LOG_DEBUG("GMPParent RecvFOGData");
+ glean::FOGData(std::move(aBuf));
+ return IPC_OK();
+}
+
+void GMPParent::CloseIfUnused() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
+
+ if ((mDeleteProcessOnlyOnUnload || mState == GMPState::Loaded ||
+ mState == GMPState::Unloading) &&
+ !IsUsed()) {
+ // Ensure all timers are killed.
+ for (uint32_t i = mTimers.Length(); i > 0; i--) {
+ mTimers[i - 1]->Shutdown();
+ }
+
+ // Shutdown GMPStorage. Given that all protocol actors must be shutdown
+ // (!Used() is true), all storage operations should be complete.
+ for (size_t i = mStorage.Length(); i > 0; i--) {
+ mStorage[i - 1]->Shutdown();
+ }
+ Shutdown();
+ }
+}
+
+void GMPParent::CloseActive(bool aDieWhenUnloaded) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("%s: state %u", __FUNCTION__,
+ uint32_t(GMPState(mState)));
+
+ if (aDieWhenUnloaded) {
+ mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
+ }
+ if (mState == GMPState::Loaded) {
+ mState = GMPState::Unloading;
+ }
+ if (mState != GMPState::NotLoaded && IsUsed()) {
+ Unused << SendCloseActive();
+ CloseIfUnused();
+ }
+}
+
+void GMPParent::MarkForDeletion() {
+ mDeleteProcessOnlyOnUnload = true;
+ mIsBlockingDeletion = true;
+}
+
+bool GMPParent::IsMarkedForDeletion() { return mIsBlockingDeletion; }
+
+void GMPParent::Shutdown() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
+
+ if (mAbnormalShutdownInProgress) {
+ return;
+ }
+
+ MOZ_ASSERT(!IsUsed());
+ if (mState == GMPState::NotLoaded || mState == GMPState::Closing) {
+ 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 == GMPState::NotLoaded);
+}
+
+class NotifyGMPShutdownTask : public Runnable {
+ public:
+ explicit NotifyGMPShutdownTask(const nsAString& aNodeId)
+ : Runnable("NotifyGMPShutdownTask"), 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);
+ nsCOMPtr<nsISerialEventTarget> gmpEventTarget = GMPEventTarget();
+
+ if (!gmpEventTarget) {
+ // 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.
+ GMP_PARENT_LOG_DEBUG("%s::%s: GMPEventTarget() returned nullptr.",
+ __CLASS__, __FUNCTION__);
+ } else {
+ gmpEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<GMPParent>>(
+ "gmp::GeckoMediaPluginServiceParent::PluginTerminated", mService,
+ &GeckoMediaPluginServiceParent::PluginTerminated, self),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+void GMPParent::DeleteProcess() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
+
+ if (mState != GMPState::Closing) {
+ // Don't Close() twice!
+ // Probably remove when bug 1043671 is resolved
+ mState = GMPState::Closing;
+ Close();
+ }
+ mProcess->Delete(NewRunnableMethod("gmp::GMPParent::ChildTerminated", this,
+ &GMPParent::ChildTerminated));
+ GMP_PARENT_LOG_DEBUG("%s: Shut down process", __FUNCTION__);
+ mProcess = nullptr;
+
+#if defined(MOZ_WIDGET_ANDROID)
+ if (mState != GMPState::NotLoaded) {
+ nsCOMPtr<nsIEventTarget> launcherThread(GetIPCLauncher());
+ MOZ_ASSERT(launcherThread);
+
+ auto procType = java::GeckoProcessType::GMPLUGIN();
+ auto selector =
+ java::GeckoProcessManager::Selector::New(procType, OtherPid());
+
+ launcherThread->Dispatch(NS_NewRunnableFunction(
+ "GMPParent::DeleteProcess",
+ [selector =
+ java::GeckoProcessManager::Selector::GlobalRef(selector)]() {
+ java::GeckoProcessManager::ShutdownProcess(selector);
+ }));
+ }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+ mState = GMPState::NotLoaded;
+
+ nsCOMPtr<nsIRunnable> r =
+ new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId));
+ mMainThread->Dispatch(r.forget());
+}
+
+GMPState GMPParent::State() const { return mState; }
+
+nsCOMPtr<nsISerialEventTarget> GMPParent::GMPEventTarget() {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+ if (!mps) {
+ return nullptr;
+ }
+ // Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
+ // nullptr if the GeckoMediaPluginService has started shutdown.
+ nsCOMPtr<nsIThread> gmpThread;
+ mps->GetThread(getter_AddRefs(gmpThread));
+ return gmpThread;
+}
+
+/* static */
+bool GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsACString& 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 nsACString& aAPI, const nsCString& aTag) {
+ for (const GMPCapability& capabilities : aCapabilities) {
+ if (!capabilities.mAPIName.Equals(aAPI)) {
+ continue;
+ }
+ for (const nsCString& tag : capabilities.mAPITags) {
+ if (tag.Equals(aTag)) {
+#ifdef XP_WIN
+ // Clearkey on Windows advertises that it can decode in its GMP info
+ // file, but uses Windows Media Foundation to decode. That's not present
+ // on Windows XP, and on some Vista, Windows N, and KN variants without
+ // certain services packs.
+ if (tag.EqualsLiteral(kClearKeyKeySystemName)) {
+ if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) {
+ if (!WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::H264)) {
+ continue;
+ }
+ }
+ }
+#endif
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool GMPParent::EnsureProcessLoaded() {
+ switch (mState) {
+ case GMPState::NotLoaded:
+ return NS_SUCCEEDED(LoadProcess());
+ case GMPState::Loaded:
+ return true;
+ case GMPState::Unloading:
+ case GMPState::Closing:
+ return false;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unhandled GMPState!");
+ return false;
+}
+
+void GMPParent::AddCrashAnnotations() {
+ if (mCrashReporter) {
+ mCrashReporter->AddAnnotation(CrashReporter::Annotation::GMPPlugin, true);
+ mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginFilename,
+ NS_ConvertUTF16toUTF8(mName));
+ mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginName,
+ mDisplayName);
+ mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginVersion,
+ mVersion);
+ }
+}
+
+void GMPParent::GetCrashID(nsString& aResult) {
+ AddCrashAnnotations();
+ GenerateCrashReport(OtherPid(), &aResult);
+}
+
+static void GMPNotifyObservers(const uint32_t aPluginID,
+ const nsACString& aPluginName,
+ const nsAString& aPluginDumpID) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ nsCOMPtr<nsIWritablePropertyBag2> propbag =
+ do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ if (obs && propbag) {
+ propbag->SetPropertyAsUint32(u"pluginID"_ns, aPluginID);
+ propbag->SetPropertyAsACString(u"pluginName"_ns, aPluginName);
+ propbag->SetPropertyAsAString(u"pluginDumpID"_ns, aPluginDumpID);
+ obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr);
+ }
+
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ if (service) {
+ service->RunPluginCrashCallbacks(aPluginID, aPluginName);
+ }
+}
+
+void GMPParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("%s: (%d)", __FUNCTION__, (int)aWhy);
+
+ if (AbnormalShutdown == aWhy) {
+ Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "gmplugin"_ns,
+ 1);
+ nsString dumpID;
+ GetCrashID(dumpID);
+ if (dumpID.IsEmpty()) {
+ NS_WARNING("GMP crash without crash report");
+ dumpID = mName;
+ dumpID += '-';
+ AppendUTF8toUTF16(mVersion, dumpID);
+ }
+
+ // NotifyObservers is mainthread-only
+ nsCOMPtr<nsIRunnable> r =
+ WrapRunnableNM(&GMPNotifyObservers, mPluginId, mDisplayName, dumpID);
+ mMainThread->Dispatch(r.forget());
+ }
+
+ // warn us off trying to close again
+ mState = GMPState::Closing;
+ mAbnormalShutdownInProgress = true;
+ CloseActive(false);
+
+ // Normal Shutdown() will delete the process on unwind.
+ if (AbnormalShutdown == aWhy) {
+ RefPtr<GMPParent> self(this);
+ // Must not call Close() again in DeleteProcess(), as we'll recurse
+ // infinitely if we do.
+ MOZ_ASSERT(mState == GMPState::Closing);
+ 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;
+}
+
+mozilla::ipc::IPCResult GMPParent::RecvPGMPStorageConstructor(
+ PGMPStorageParent* aActor) {
+ GMPStorageParent* p = (GMPStorageParent*)aActor;
+ if (NS_FAILED(p->Init())) {
+ // TODO: Verify if this is really a good reason to IPC_FAIL.
+ // There might be shutdown edge cases here.
+ return IPC_FAIL(this,
+ "GMPParent::RecvPGMPStorageConstructor: p->Init() failed.");
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPParent::RecvPGMPTimerConstructor(
+ PGMPTimerParent* actor) {
+ return IPC_OK();
+}
+
+PGMPTimerParent* GMPParent::AllocPGMPTimerParent() {
+ nsCOMPtr<nsISerialEventTarget> target = GMPEventTarget();
+ GMPTimerParent* p = new GMPTimerParent(target);
+ 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(GMPEventTarget()->IsOnCurrentThread());
+ 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_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ infoFile->AppendRelativePath(mName + u".info"_ns);
+
+ if (FileExists(infoFile)) {
+ return ReadGMPInfoFile(infoFile);
+ }
+
+ // Maybe this is the Widevine adapted plugin?
+ nsCOMPtr<nsIFile> manifestFile;
+ rv = mDirectory->Clone(getter_AddRefs(manifestFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GenericPromise::CreateAndReject(rv, __func__);
+ }
+ manifestFile->AppendRelativePath(u"manifest.json"_ns);
+ return ReadChromiumManifestFile(manifestFile);
+}
+
+#if defined(XP_LINUX)
+static void ApplyGlibcWorkaround(nsCString& aLibs) {
+ // These glibc libraries were merged into libc.so.6 as of glibc
+ // 2.34; they now exist only as stub libraries for compatibility and
+ // newly linked code won't depend on them, so we need to ensure
+ // they're loaded for plugins that may have been linked against a
+ // different version of glibc. (See also bug 1725828.)
+ if (!aLibs.IsEmpty()) {
+ aLibs.AppendLiteral(", ");
+ }
+ aLibs.AppendLiteral("libdl.so.2, libpthread.so.0, librt.so.1");
+}
+#endif
+
+#if defined(XP_WIN)
+static void ApplyOleaut32(nsCString& aLibs) {
+ // In the libwebrtc update in bug 1766646 an include of comdef.h for using
+ // _bstr_t was introduced. This resulted in a dependency on comsupp.lib which
+ // contains a `_variant_t vtMissing` that would get cleared in an exit
+ // handler. VariantClear is defined in oleaut32.dll, and so we'd try to load
+ // oleaut32.dll on exit but get denied by the sandbox.
+ // Note that we had includes of comdef.h before bug 1766646 but it is the use
+ // of _bstr_t that triggers the vtMissing exit handler.
+ // See bug 1788592 for details.
+ if (!aLibs.IsEmpty()) {
+ aLibs.AppendLiteral(", ");
+ }
+ aLibs.AppendLiteral("oleaut32.dll");
+}
+#endif
+
+RefPtr<GenericPromise> GMPParent::ReadGMPInfoFile(nsIFile* aFile) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMPInfoFileParser parser;
+ if (!parser.Init(aFile)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ nsAutoCString apis;
+ if (!ReadInfoField(parser, "name"_ns, mDisplayName) ||
+ !ReadInfoField(parser, "description"_ns, mDescription) ||
+ !ReadInfoField(parser, "version"_ns, mVersion) ||
+ !ReadInfoField(parser, "apis"_ns, apis)) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+#if defined(XP_WIN) || defined(XP_LINUX)
+ // "Libraries" field is optional.
+ ReadInfoField(parser, "libraries"_ns, mLibs);
+#endif
+
+ UpdatePluginType();
+
+#ifdef XP_LINUX
+ // The glibc workaround (see above) isn't needed for clearkey
+ // because it's built along with the browser.
+ if (mPluginType != GMPPluginType::Clearkey) {
+ ApplyGlibcWorkaround(mLibs);
+ }
+#endif
+
+#ifdef XP_WIN
+ ApplyOleaut32(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);
+ }
+ }
+ }
+
+ mCapabilities.AppendElement(std::move(cap));
+ }
+
+ if (mCapabilities.IsEmpty()) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+RefPtr<GenericPromise> GMPParent::ReadChromiumManifestFile(nsIFile* aFile) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ 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(mMainThread, this, __func__,
+ &GMPParent::ParseChromiumManifest,
+ NS_ConvertUTF8toUTF16(json));
+}
+
+static bool IsCDMAPISupported(
+ const mozilla::dom::WidevineCDMManifest& aManifest) {
+ nsresult ignored; // Note: ToInteger returns 0 on failure.
+ int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored);
+ int32_t interfaceVersion =
+ aManifest.mX_cdm_interface_versions.ToInteger(&ignored);
+ int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored);
+ return ChromiumCDMAdapter::Supports(moduleVersion, interfaceVersion,
+ hostVersion);
+}
+
+RefPtr<GenericPromise> GMPParent::ParseChromiumManifest(
+ const nsAString& aJSON) {
+ GMP_PARENT_LOG_DEBUG("%s: for '%s'", __FUNCTION__,
+ NS_LossyConvertUTF16toASCII(aJSON).get());
+
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::WidevineCDMManifest m;
+ if (!m.Init(aJSON)) {
+ GMP_PARENT_LOG_DEBUG("%s: Failed to initialize json parser, failing.",
+ __FUNCTION__);
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ if (!IsCDMAPISupported(m)) {
+ GMP_PARENT_LOG_DEBUG("%s: CDM API not supported, failing.", __FUNCTION__);
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ CopyUTF16toUTF8(m.mName, mDisplayName);
+ CopyUTF16toUTF8(m.mDescription, mDescription);
+ CopyUTF16toUTF8(m.mVersion, mVersion);
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
+ nsPrintfCString msg(
+ "GMPParent::ParseChromiumManifest: Plugin \"%s\" is an EME CDM"
+ " but this system can't sandbox it; not loading.",
+ mDisplayName.get());
+ printf_stderr("%s\n", msg.get());
+ GMP_PARENT_LOG_DEBUG("%s", msg.get());
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+#endif
+
+ UpdatePluginType();
+
+ GMPCapability video;
+
+ // We hard code a few of the settings because they can't be stored in the
+ // widevine manifest without making our API different to widevine's.
+ switch (mPluginType) {
+ case GMPPluginType::Clearkey:
+ video.mAPITags.AppendElement(nsCString{kClearKeyKeySystemName});
+ video.mAPITags.AppendElement(
+ nsCString{kClearKeyWithProtectionQueryKeySystemName});
+#if XP_WIN
+ mLibs = nsLiteralCString(
+ "dxva2.dll, evr.dll, freebl3.dll, mfh264dec.dll, mfplat.dll, "
+ "msmpeg2vdec.dll, nss3.dll, softokn3.dll");
+#elif XP_LINUX
+ mLibs = "libfreeblpriv3.so, libsoftokn3.so"_ns;
+#endif
+ break;
+ case GMPPluginType::Widevine:
+ video.mAPITags.AppendElement(nsCString{kWidevineKeySystemName});
+#if XP_WIN
+ // psapi.dll added for GetMappedFileNameW, which could possibly be avoided
+ // in future versions, see bug 1383611 for details.
+ mLibs = "dxva2.dll, ole32.dll, psapi.dll, winmm.dll"_ns;
+#endif
+ break;
+ case GMPPluginType::Fake:
+ // The fake CDM just exposes a key system with id "fake".
+ video.mAPITags.AppendElement(nsCString{"fake"});
+#if XP_WIN
+ mLibs = "dxva2.dll, ole32.dll"_ns;
+#endif
+ break;
+ default:
+ GMP_PARENT_LOG_DEBUG("%s: Unrecognized key system: %s, failing.",
+ __FUNCTION__, mDisplayName.get());
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+#ifdef XP_LINUX
+ ApplyGlibcWorkaround(mLibs);
+#endif
+
+#ifdef XP_WIN
+ ApplyOleaut32(mLibs);
+#endif
+
+ nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
+ nsTArray<nsCString> codecs;
+ SplitAt(",", codecsString, codecs);
+
+ // Parse the codec strings in the manifest and map them to strings used
+ // internally by Gecko for capability recognition.
+ //
+ // Google's code to parse manifests can be used as a reference for strings
+ // the manifest may contain
+ // https://source.chromium.org/chromium/chromium/src/+/master:components/cdm/common/cdm_manifest.cc;l=74;drc=775880ced8a989191281e93854c7f2201f25068f
+ //
+ // Gecko's internal strings can be found at
+ // https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/dom/media/eme/MediaKeySystemAccess.cpp#149-155
+ for (const nsCString& chromiumCodec : codecs) {
+ nsCString codec;
+ if (chromiumCodec.EqualsASCII("vp8")) {
+ codec = "vp8"_ns;
+ } else if (chromiumCodec.EqualsASCII("vp9.0") || // Legacy string.
+ chromiumCodec.EqualsASCII("vp09")) {
+ codec = "vp9"_ns;
+ } else if (chromiumCodec.EqualsASCII("avc1")) {
+ codec = "h264"_ns;
+ } else if (chromiumCodec.EqualsASCII("av01")) {
+ codec = "av1"_ns;
+ } else {
+ GMP_PARENT_LOG_DEBUG("%s: Unrecognized codec: %s.", __FUNCTION__,
+ chromiumCodec.get());
+ MOZ_ASSERT_UNREACHABLE(
+ "Unhandled codec string! Need to add it to the parser.");
+ continue;
+ }
+
+ video.mAPITags.AppendElement(codec);
+ }
+
+ video.mAPIName = nsLiteralCString(CHROMIUM_CDM_API);
+ mAdapter = u"chromium"_ns;
+
+ mCapabilities.AppendElement(std::move(video));
+
+ GMP_PARENT_LOG_DEBUG("%s: Successfully parsed manifest.", __FUNCTION__);
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+bool GMPParent::CanBeSharedCrossNodeIds() const {
+ return 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 mNodeId == aNodeId;
+}
+
+void GMPParent::SetNodeId(const nsACString& aNodeId) {
+ MOZ_ASSERT(!aNodeId.IsEmpty());
+ mNodeId = aNodeId;
+}
+
+void GMPParent::UpdatePluginType() {
+ if (mDisplayName.EqualsLiteral("WidevineCdm")) {
+ mPluginType = GMPPluginType::Widevine;
+ } else if (mDisplayName.EqualsLiteral("gmpopenh264")) {
+ mPluginType = GMPPluginType::OpenH264;
+ } else if (mDisplayName.EqualsLiteral("clearkey")) {
+ mPluginType = GMPPluginType::Clearkey;
+ } else if (mDisplayName.EqualsLiteral("fake")) {
+ mPluginType = GMPPluginType::Fake;
+ } else {
+ mPluginType = GMPPluginType::Unknown;
+ }
+}
+
+const nsCString& GMPParent::GetDisplayName() const { return mDisplayName; }
+
+const nsCString& GMPParent::GetVersion() const { return mVersion; }
+
+uint32_t GMPParent::GetPluginId() const { return mPluginId; }
+
+void GMPParent::ResolveGetContentParentPromises() {
+ nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises =
+ std::move(mGetContentParentPromises);
+ MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
+ RefPtr<GMPContentParent::CloseBlocker> blocker(
+ new GMPContentParent::CloseBlocker(mGMPContentParent));
+ for (auto& holder : promises) {
+ holder->Resolve(blocker, __func__);
+ }
+}
+
+bool GMPParent::OpenPGMPContent() {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ MOZ_ASSERT(!mGMPContentParent);
+
+ Endpoint<PGMPContentParent> parent;
+ Endpoint<PGMPContentChild> child;
+ if (NS_WARN_IF(NS_FAILED(PGMPContent::CreateEndpoints(
+ base::GetCurrentProcId(), OtherPid(), &parent, &child)))) {
+ return false;
+ }
+
+ mGMPContentParent = new GMPContentParent(this);
+
+ if (!parent.Bind(mGMPContentParent)) {
+ return false;
+ }
+
+ if (!SendInitGMPContentChild(std::move(child))) {
+ return false;
+ }
+
+ ResolveGetContentParentPromises();
+
+ return true;
+}
+
+void GMPParent::RejectGetContentParentPromises() {
+ nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises =
+ std::move(mGetContentParentPromises);
+ MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
+ for (auto& holder : promises) {
+ holder->Reject(NS_ERROR_FAILURE, __func__);
+ }
+}
+
+void GMPParent::GetGMPContentParent(
+ UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>&& aPromiseHolder) {
+ MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
+ GMP_PARENT_LOG_DEBUG("%s %p", __FUNCTION__, this);
+
+ if (mGMPContentParent) {
+ RefPtr<GMPContentParent::CloseBlocker> blocker(
+ new GMPContentParent::CloseBlocker(mGMPContentParent));
+ aPromiseHolder->Resolve(blocker, __func__);
+ } else {
+ mGetContentParentPromises.AppendElement(std::move(aPromiseHolder));
+ // If we don't have a GMPContentParent and we try to get one for the first
+ // time (mGetContentParentPromises.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 (mGetContentParentPromises.Length() == 1) {
+ if (!EnsureProcessLoaded() || !OpenPGMPContent()) {
+ RejectGetContentParentPromises();
+ return;
+ }
+ // 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;
+ }
+ }
+}
+
+already_AddRefed<GMPContentParent> GMPParent::ForgetGMPContentParent() {
+ MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
+ return mGMPContentParent.forget();
+}
+
+bool GMPParent::EnsureProcessLoaded(base::ProcessId* aID) {
+ if (!EnsureProcessLoaded()) {
+ return false;
+ }
+ *aID = OtherPid();
+ return true;
+}
+
+void GMPParent::IncrementGMPContentChildCount() { ++mGMPContentChildCount; }
+
+nsString GMPParent::GetPluginBaseName() const { return u"gmp-"_ns + mName; }
+
+#if defined(XP_MACOSX) && defined(__aarch64__)
+void GMPParent::PreTranslateBins() {
+ nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
+ "RosettaTranslation", this, &GMPParent::PreTranslateBinsWorker);
+
+ DebugOnly<nsresult> rv =
+ NS_DispatchBackgroundTask(event.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void GMPParent::PreTranslateBinsWorker() {
+ int rv = nsMacUtilsImpl::PreTranslateXUL();
+ GMP_PARENT_LOG_DEBUG("%s: XUL translation result: %d", __FUNCTION__, rv);
+
+ rv = nsMacUtilsImpl::PreTranslateBinary(mPluginFilePath);
+ GMP_PARENT_LOG_DEBUG("%s: %s translation result: %d", __FUNCTION__,
+ mPluginFilePath.get(), rv);
+}
+#endif
+
+} // namespace mozilla::gmp
+
+#undef GMP_PARENT_LOG_DEBUG
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h
new file mode 100644
index 0000000000..25fe1f88d1
--- /dev/null
+++ b/dom/media/gmp/GMPParent.h
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "GMPNativeTypes.h"
+#include "GMPProcessParent.h"
+#include "GMPServiceParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "GMPTimerParent.h"
+#include "GMPStorageParent.h"
+#include "mozilla/gmp/PGMPParent.h"
+#include "mozilla/ipc/CrashReporterHelper.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIFile.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::gmp {
+
+class GMPCapability {
+ public:
+ explicit GMPCapability() = default;
+ GMPCapability(GMPCapability&& aOther)
+ : mAPIName(std::move(aOther.mAPIName)),
+ mAPITags(std::move(aOther.mAPITags)) {}
+ explicit GMPCapability(const nsACString& aAPIName) : mAPIName(aAPIName) {}
+ explicit GMPCapability(const GMPCapability& aOther) = default;
+ nsCString mAPIName;
+ CopyableTArray<nsCString> mAPITags;
+
+ static bool Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsACString& aAPI,
+ const nsTArray<nsCString>& aTags);
+
+ static bool Supports(const nsTArray<GMPCapability>& aCapabilities,
+ const nsACString& aAPI, const nsCString& aTag);
+};
+
+enum class GMPState : uint32_t { NotLoaded, Loaded, Unloading, Closing };
+
+class GMPContentParent;
+
+class GMPParent final
+ : public PGMPParent,
+ public ipc::CrashReporterHelper<GeckoProcessType_GMPlugin> {
+ friend class PGMPParent;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPParent, final)
+
+ GMPParent();
+
+ RefPtr<GenericPromise> Init(GeckoMediaPluginServiceParent* aService,
+ nsIFile* aPluginDir);
+ void 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;
+ nsCOMPtr<nsISerialEventTarget> GMPEventTarget();
+
+ // 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;
+ GMPPluginType GetPluginType() const { return mPluginType; }
+ 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 OpenPGMPContent();
+
+ void GetGMPContentParent(
+ UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>&& aPromiseHolder);
+ already_AddRefed<GMPContentParent> ForgetGMPContentParent();
+
+ bool EnsureProcessLoaded(base::ProcessId* aID);
+
+ void IncrementGMPContentChildCount();
+
+ const nsTArray<GMPCapability>& GetCapabilities() const {
+ return mCapabilities;
+ }
+
+ private:
+ ~GMPParent();
+ void UpdatePluginType();
+
+ RefPtr<GeckoMediaPluginServiceParent> mService;
+ bool EnsureProcessLoaded();
+ RefPtr<GenericPromise> ReadGMPMetaData();
+ RefPtr<GenericPromise> ReadGMPInfoFile(nsIFile* aFile);
+ RefPtr<GenericPromise> ParseChromiumManifest(
+ const nsAString& aJSON); // Worker thread.
+ RefPtr<GenericPromise> ReadChromiumManifestFile(
+ nsIFile* aFile); // GMP thread.
+ void AddCrashAnnotations();
+ void GetCrashID(nsString& aResult);
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvPGMPStorageConstructor(
+ PGMPStorageParent* actor) override;
+ PGMPStorageParent* AllocPGMPStorageParent();
+ bool DeallocPGMPStorageParent(PGMPStorageParent* aActor);
+
+ mozilla::ipc::IPCResult RecvPGMPTimerConstructor(
+ PGMPTimerParent* actor) override;
+ PGMPTimerParent* AllocPGMPTimerParent();
+ bool DeallocPGMPTimerParent(PGMPTimerParent* aActor);
+
+ mozilla::ipc::IPCResult RecvPGMPContentChildDestroyed();
+
+ mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& aBuf);
+
+ bool IsUsed() {
+ return mGMPContentChildCount > 0 || !mGetContentParentPromises.IsEmpty();
+ }
+
+ void ResolveGetContentParentPromises();
+ void RejectGetContentParentPromises();
+
+#if defined(XP_MACOSX) && defined(__aarch64__)
+ // We pre-translate XUL and our plugin file to avoid x64 child process
+ // startup delays caused by translation for instances when the child
+ // process binary translations have not already been cached. i.e., the
+ // first time we launch an x64 child process after installation or
+ // update. Measured by binary size of a recent XUL and Widevine plugin,
+ // this makes up 94% of the translation needed. Re-translating the
+ // same binary does not cause translation to occur again.
+ void PreTranslateBins();
+ void PreTranslateBinsWorker();
+#endif
+
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ nsresult GetPluginFileArch(nsIFile* aPluginDir, const nsString& aBaseName,
+ uint32_t& aArchSet);
+#endif
+
+ Atomic<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;
+#if defined(XP_WIN) || defined(XP_LINUX)
+ nsCString mLibs;
+#endif
+ nsString mAdapter;
+ const uint32_t mPluginId;
+ GMPPluginType mPluginType = GMPPluginType::Unknown;
+ nsTArray<GMPCapability> mCapabilities;
+ GMPProcessParent* mProcess;
+ bool mDeleteProcessOnlyOnUnload;
+ bool mAbnormalShutdownInProgress;
+ bool mIsBlockingDeletion;
+
+ bool mCanDecrypt;
+
+ nsTArray<RefPtr<GMPTimerParent>> mTimers;
+ nsTArray<RefPtr<GMPStorageParent>> mStorage;
+ // 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<MozPromiseHolder<GetGMPContentParentPromise>>>
+ mGetContentParentPromises;
+ uint32_t mGMPContentChildCount;
+
+ int mChildPid;
+
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ // The child process architecture to use.
+ uint32_t mChildLaunchArch;
+#endif
+#if defined(XP_MACOSX) && defined(__aarch64__)
+ nsCString mPluginFilePath;
+#endif
+
+ const nsCOMPtr<nsISerialEventTarget> mMainThread;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPParent_h_
diff --git a/dom/media/gmp/GMPPlatform.cpp b/dom/media/gmp/GMPPlatform.cpp
new file mode 100644
index 0000000000..1a76015ed4
--- /dev/null
+++ b/dom/media/gmp/GMPPlatform.cpp
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozilla/Mutex.h"
+#include "base/thread.h"
+#include "base/time.h"
+#include "mozilla/ReentrantMonitor.h"
+
+#include <ctime>
+
+namespace mozilla::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() = default;
+
+ 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("gmp::GMPSyncRunnable::Run", 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() = default;
+
+ bool mDone;
+ GMPTask* mTask;
+ MessageLoop* mMessageLoop;
+ Monitor mMonitor MOZ_UNANNOTATED;
+};
+
+class GMPThreadImpl : public GMPThread {
+ public:
+ GMPThreadImpl();
+ virtual ~GMPThreadImpl();
+
+ // GMPThread
+ void Post(GMPTask* aTask) override;
+ void Join() override;
+
+ private:
+ Mutex mMutex MOZ_UNANNOTATED;
+ base::Thread mThread;
+};
+
+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("gmp::GMPRunnable::Run", 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;
+}
+
+class GMPMutexImpl : public GMPMutex {
+ public:
+ GMPMutexImpl();
+ virtual ~GMPMutexImpl();
+
+ // GMPMutex
+ void Acquire() override;
+ void Release() override;
+ void Destroy() override;
+
+ private:
+ ReentrantMonitor mMonitor MOZ_UNANNOTATED;
+};
+
+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) {
+ if (!aOutTime) {
+ return GMPGenericErr;
+ }
+ *aOutTime = base::Time::Now().ToDoubleT() * 1000.0;
+ return GMPNoErr;
+}
+
+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;
+}
+
+void SendFOGData(ipc::ByteBuf&& buf) {
+ if (sChild) {
+ sChild->SendFOGData(std::move(buf));
+ }
+}
+
+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("gmp::GMPRunnable::Run", 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; }
+
+MOZ_PUSH_IGNORE_THREAD_SAFETY
+void GMPMutexImpl::Acquire() { mMonitor.Enter(); }
+
+void GMPMutexImpl::Release() { mMonitor.Exit(); }
+MOZ_POP_THREAD_SAFETY
+
+GMPTask* NewGMPTask(std::function<void()>&& aFunction) {
+ class Task : public GMPTask {
+ public:
+ explicit Task(std::function<void()>&& aFunction)
+ : mFunction(std::move(aFunction)) {}
+ void Destroy() override { delete this; }
+ ~Task() override = default;
+ void Run() override { mFunction(); }
+
+ private:
+ std::function<void()> mFunction;
+ };
+ return new Task(std::move(aFunction));
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPPlatform.h b/dom/media/gmp/GMPPlatform.h
new file mode 100644
index 0000000000..ee4571d856
--- /dev/null
+++ b/dom/media/gmp/GMPPlatform.h
@@ -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/. */
+
+#ifndef GMPPlatform_h_
+#define GMPPlatform_h_
+
+#include "gmp-platform.h"
+#include <functional>
+
+namespace mozilla::ipc {
+class ByteBuf;
+} // namespace mozilla::ipc
+
+namespace mozilla::gmp {
+
+class GMPChild;
+
+void InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild);
+
+GMPErr RunOnMainThread(GMPTask* aTask);
+
+GMPTask* NewGMPTask(std::function<void()>&& aFunction);
+
+GMPErr SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
+
+void SendFOGData(ipc::ByteBuf&& buf);
+
+} // namespace mozilla::gmp
+
+#endif // GMPPlatform_h_
diff --git a/dom/media/gmp/GMPProcessChild.cpp b/dom/media/gmp/GMPProcessChild.cpp
new file mode 100644
index 0000000000..17c70079cb
--- /dev/null
+++ b/dom/media/gmp/GMPProcessChild.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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::gmp {
+
+GMPProcessChild::~GMPProcessChild() = default;
+
+bool GMPProcessChild::Init(int aArgc, char* aArgv[]) {
+ nsAutoString pluginFilename;
+
+#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() >= 2, "not enough args");
+ CopyUTF8toUTF16(nsDependentCString(values[1].c_str()), pluginFilename);
+#elif defined(OS_WIN)
+ std::vector<std::wstring> values =
+ CommandLine::ForCurrentProcess()->GetLooseValues();
+ MOZ_ASSERT(values.size() >= 1, "not enough loose args");
+ pluginFilename = nsDependentString(values[0].c_str());
+#else
+# error Not implemented
+#endif
+
+ BackgroundHangMonitor::Startup();
+
+ return mPlugin->Init(pluginFilename, TakeInitialEndpoint());
+}
+
+void GMPProcessChild::CleanUp() { BackgroundHangMonitor::Shutdown(); }
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPProcessChild.h b/dom/media/gmp/GMPProcessChild.h
new file mode 100644
index 0000000000..88d0141979
--- /dev/null
+++ b/dom/media/gmp/GMPProcessChild.h
@@ -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/. */
+
+#ifndef GMPProcessChild_h_
+#define GMPProcessChild_h_
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPChild.h"
+
+namespace mozilla::gmp {
+
+class GMPLoader;
+
+class GMPProcessChild final : public mozilla::ipc::ProcessChild {
+ protected:
+ typedef mozilla::ipc::ProcessChild ProcessChild;
+
+ public:
+ using ProcessChild::ProcessChild;
+ ~GMPProcessChild();
+
+ bool Init(int aArgc, char* aArgv[]) override;
+ void CleanUp() override;
+
+ private:
+ const RefPtr<GMPChild> mPlugin = new GMPChild;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPProcessChild_h_
diff --git a/dom/media/gmp/GMPProcessParent.cpp b/dom/media/gmp/GMPProcessParent.cpp
new file mode 100644
index 0000000000..f5ffafcce4
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.cpp
@@ -0,0 +1,297 @@
+/* -*- 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 "nsIRunnable.h"
+#ifdef XP_WIN
+# include "WinUtils.h"
+#endif
+#include "GMPLog.h"
+
+#include "base/string_util.h"
+#include "base/process_util.h"
+
+#include <string>
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "mozilla/Omnijar.h"
+# include "mozilla/Preferences.h"
+# include "mozilla/Sandbox.h"
+# include "mozilla/SandboxSettings.h"
+# include "nsMacUtilsImpl.h"
+#endif
+
+using std::string;
+using std::vector;
+
+using mozilla::gmp::GMPProcessParent;
+using mozilla::ipc::GeckoChildProcessHost;
+
+#ifdef MOZ_WIDGET_ANDROID
+static const int kInvalidFd = -1;
+#endif
+
+namespace mozilla::gmp {
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool GMPProcessParent::sLaunchWithMacSandbox = true;
+bool GMPProcessParent::sMacSandboxGMPLogging = false;
+# if defined(DEBUG)
+bool GMPProcessParent::sIsMainThreadInitDone = false;
+# endif
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+/* static */
+void GMPProcessParent::InitStaticMainThread() {
+ // The GMPProcessParent constructor is called off the
+ // main thread. Do main thread initialization here.
+ MOZ_ASSERT(NS_IsMainThread());
+ sMacSandboxGMPLogging =
+ Preferences::GetBool("security.sandbox.logging.enabled") ||
+ PR_GetEnv("MOZ_SANDBOX_GMP_LOGGING") || PR_GetEnv("MOZ_SANDBOX_LOGGING");
+ GMP_LOG_DEBUG("GMPProcessParent::InitStaticMainThread: sandbox logging=%s",
+ sMacSandboxGMPLogging ? "true" : "false");
+# if defined(DEBUG)
+ sIsMainThreadInitDone = true;
+# endif
+}
+#endif
+
+GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
+ : GeckoChildProcessHost(GeckoProcessType_GMPlugin),
+ mGMPPath(aGMPPath)
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ ,
+ mRequiresWindowServer(false)
+#endif
+{
+ MOZ_COUNT_CTOR(GMPProcessParent);
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ MOZ_ASSERT(sIsMainThreadInitDone == true);
+ mDisableOSActivityMode = sLaunchWithMacSandbox;
+#endif
+}
+
+GMPProcessParent::~GMPProcessParent() { MOZ_COUNT_DTOR(GMPProcessParent); }
+
+bool GMPProcessParent::Launch(int32_t aTimeoutMs) {
+ vector<string> args;
+
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ GMP_LOG_DEBUG("GMPProcessParent::Launch() mLaunchArch: %d", mLaunchArch);
+# if defined(XP_MACOSX)
+ mLaunchOptions->arch = mLaunchArch;
+ if (mLaunchArch == base::PROCESS_ARCH_X86_64) {
+ mLaunchOptions->env_map["MOZ_SHMEM_PAGESIZE_16K"] = 1;
+ }
+# endif
+#endif
+
+ // Resolve symlinks in the plugin path. The sandbox prevents
+ // resolving symlinks in the child process if access to link
+ // source file is denied.
+#ifdef XP_WIN
+ nsAutoString normalizedPath;
+#else
+ nsAutoCString normalizedPath;
+#endif
+ nsresult rv = NormalizePath(mGMPPath.c_str(), normalizedPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::Launch: "
+ "plugin path normaliziation failed for path: %s",
+ mGMPPath.c_str());
+ }
+
+#ifdef XP_WIN
+ std::wstring wGMPPath;
+ if (NS_SUCCEEDED(rv)) {
+ wGMPPath = normalizedPath.get();
+ } else {
+ wGMPPath = UTF8ToWide(mGMPPath.c_str());
+ }
+
+ // The sandbox doesn't allow file system rules where the paths contain
+ // symbolic links or junction points. Sometimes the Users folder has been
+ // moved to another drive using a junction point, so allow for this specific
+ // case. See bug 1236680 for details.
+ if (NS_WARN_IF(
+ !widget::WinUtils::ResolveJunctionPointsAndSymLinks(wGMPPath))) {
+ GMP_LOG_DEBUG("ResolveJunctionPointsAndSymLinks failed for GMP path=%S",
+ wGMPPath.c_str());
+ return false;
+ }
+ GMP_LOG_DEBUG("GMPProcessParent::Launch() resolved path to %S",
+ wGMPPath.c_str());
+
+# ifdef MOZ_SANDBOX
+ // If the GMP path is a network path that is not mapped to a drive letter,
+ // then we need to fix the path format for the sandbox rule.
+ wchar_t volPath[MAX_PATH];
+ if (::GetVolumePathNameW(wGMPPath.c_str(), volPath, MAX_PATH) &&
+ ::GetDriveTypeW(volPath) == DRIVE_REMOTE &&
+ wGMPPath.compare(0, 2, L"\\\\") == 0) {
+ std::wstring sandboxGMPPath(wGMPPath);
+ sandboxGMPPath.insert(1, L"??\\UNC");
+ mAllowedFilesRead.push_back(sandboxGMPPath + L"\\*");
+ } else {
+ mAllowedFilesRead.push_back(wGMPPath + L"\\*");
+ }
+# endif
+
+ args.push_back(WideToUTF8(wGMPPath));
+#else
+ if (NS_SUCCEEDED(rv)) {
+ args.push_back(normalizedPath.get());
+ } else {
+ args.push_back(mGMPPath);
+ }
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Add dummy values for pref and pref map to the file descriptors remapping
+ // table. See bug 1440207 and 1481139.
+ AddFdToRemap(kInvalidFd, kInvalidFd);
+ AddFdToRemap(kInvalidFd, kInvalidFd);
+#endif
+ return SyncLaunch(args, aTimeoutMs);
+}
+
+void GMPProcessParent::Delete(nsCOMPtr<nsIRunnable> aCallback) {
+ mDeletedCallback = aCallback;
+ XRE_GetIOMessageLoop()->PostTask(NewNonOwningRunnableMethod(
+ "gmp::GMPProcessParent::DoDelete", this, &GMPProcessParent::DoDelete));
+}
+
+void GMPProcessParent::DoDelete() {
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ if (mDeletedCallback) {
+ mDeletedCallback->Run();
+ }
+
+ Destroy();
+}
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool GMPProcessParent::IsMacSandboxLaunchEnabled() {
+ return sLaunchWithMacSandbox;
+}
+
+void GMPProcessParent::SetRequiresWindowServer(bool aRequiresWindowServer) {
+ mRequiresWindowServer = aRequiresWindowServer;
+}
+
+bool GMPProcessParent::FillMacSandboxInfo(MacSandboxInfo& aInfo) {
+ aInfo.type = MacSandboxType_GMP;
+ aInfo.hasWindowServer = mRequiresWindowServer;
+ aInfo.shouldLog = (aInfo.shouldLog || sMacSandboxGMPLogging);
+ nsAutoCString appPath;
+ if (!nsMacUtilsImpl::GetAppPath(appPath)) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: failed to get app path");
+ return false;
+ }
+ aInfo.appPath.assign(appPath.get());
+
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: "
+ "plugin dir path: %s",
+ mGMPPath.c_str());
+ nsCOMPtr<nsIFile> pluginDir;
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(mGMPPath.c_str()), true,
+ getter_AddRefs(pluginDir));
+ if (NS_FAILED(rv)) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: "
+ "NS_NewLocalFile failed for plugin dir, rv=%d",
+ rv);
+ return false;
+ }
+
+ rv = pluginDir->Normalize();
+ if (NS_FAILED(rv)) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: "
+ "failed to normalize plugin dir path, rv=%d",
+ rv);
+ return false;
+ }
+
+ nsAutoCString resolvedPluginPath;
+ pluginDir->GetNativePath(resolvedPluginPath);
+ aInfo.pluginPath.assign(resolvedPluginPath.get());
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: "
+ "resolved plugin dir path: %s",
+ resolvedPluginPath.get());
+
+ if (!mozilla::IsPackagedBuild()) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: IsPackagedBuild()=false");
+
+ // Repo dir
+ nsCOMPtr<nsIFile> repoDir;
+ rv = nsMacUtilsImpl::GetRepoDir(getter_AddRefs(repoDir));
+ if (NS_FAILED(rv)) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: failed to get repo dir");
+ return false;
+ }
+ nsCString repoDirPath;
+ Unused << repoDir->GetNativePath(repoDirPath);
+ aInfo.testingReadPath1 = repoDirPath.get();
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: "
+ "repo dir path: %s",
+ repoDirPath.get());
+
+ // Object dir
+ nsCOMPtr<nsIFile> objDir;
+ rv = nsMacUtilsImpl::GetObjDir(getter_AddRefs(objDir));
+ if (NS_FAILED(rv)) {
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: failed to get object dir");
+ return false;
+ }
+ nsCString objDirPath;
+ Unused << objDir->GetNativePath(objDirPath);
+ aInfo.testingReadPath2 = objDirPath.get();
+ GMP_LOG_DEBUG(
+ "GMPProcessParent::FillMacSandboxInfo: "
+ "object dir path: %s",
+ objDirPath.get());
+ }
+ return true;
+}
+#endif
+
+nsresult GMPProcessParent::NormalizePath(const char* aPath,
+ PathString& aNormalizedPath) {
+ nsCOMPtr<nsIFile> fileOrDir;
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(aPath), true,
+ getter_AddRefs(fileOrDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = fileOrDir->Normalize();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef XP_WIN
+ return fileOrDir->GetTarget(aNormalizedPath);
+#else
+ bool isLink = false;
+ rv = fileOrDir->IsSymlink(&isLink);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isLink) {
+ return fileOrDir->GetNativeTarget(aNormalizedPath);
+ }
+ return fileOrDir->GetNativePath(aNormalizedPath);
+#endif
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPProcessParent.h b/dom/media/gmp/GMPProcessParent.h
new file mode 100644
index 0000000000..efe6bff17f
--- /dev/null
+++ b/dom/media/gmp/GMPProcessParent.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 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"
+#include "nsIFile.h"
+
+class nsIRunnable;
+
+namespace mozilla::gmp {
+
+class GMPProcessParent final : public mozilla::ipc::GeckoChildProcessHost {
+ public:
+ explicit GMPProcessParent(const std::string& aGMPPath);
+
+ // 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; }
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Init static members on the main thread
+ static void InitStaticMainThread();
+
+ // Read prefs and environment variables to determine
+ // when and if to start the Mac sandbox for the child
+ // process. Starting the sandbox at launch is the new
+ // preferred method. Code to support starting the sandbox
+ // later at plugin start time should be removed once
+ // starting at launch is stable and shipping.
+ bool IsMacSandboxLaunchEnabled() override;
+
+ // For process sandboxing purposes, set whether or not this
+ // instance of the GMP process requires access to the macOS
+ // window server. At present, Widevine requires window server
+ // access, but OpenH264 decoding does not.
+ void SetRequiresWindowServer(bool aRequiresWindowServer);
+
+ // Return the sandbox type to be used with this process type.
+ static MacSandboxType GetMacSandboxType() { return MacSandboxType_GMP; };
+#endif
+
+ using mozilla::ipc::GeckoChildProcessHost::GetChannel;
+ using mozilla::ipc::GeckoChildProcessHost::GetChildProcessHandle;
+
+ private:
+ ~GMPProcessParent();
+
+ void DoDelete();
+
+ std::string mGMPPath;
+ nsCOMPtr<nsIRunnable> mDeletedCallback;
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Indicates whether we'll start the Mac GMP sandbox during
+ // process launch (earlyinit) which is the new preferred method
+ // or later in the process lifetime.
+ static bool sLaunchWithMacSandbox;
+
+ // Whether or not Mac sandbox violation logging is enabled.
+ static bool sMacSandboxGMPLogging;
+
+ // Override so we can set GMP-specific sandbox parameters
+ bool FillMacSandboxInfo(MacSandboxInfo& aInfo) override;
+
+ // Controls whether or not the sandbox will be configured with
+ // window service access.
+ bool mRequiresWindowServer;
+
+# if defined(DEBUG)
+ // Used to assert InitStaticMainThread() is called before the constructor.
+ static bool sIsMainThreadInitDone;
+# endif
+#endif
+
+ // For normalizing paths to be compatible with sandboxing.
+ // We use normalized paths to generate the sandbox ruleset. Once
+ // the sandbox has been started, resolving symlinks that point to
+ // allowed directories could require reading paths not allowed by
+ // the sandbox, so we should only attempt to load plugin libraries
+ // using normalized paths.
+ static nsresult NormalizePath(const char* aPath, PathString& aNormalizedPath);
+
+ DISALLOW_COPY_AND_ASSIGN(GMPProcessParent);
+};
+
+} // namespace mozilla::gmp
+
+#endif // ifndef GMPProcessParent_h
diff --git a/dom/media/gmp/GMPSanitizedExports.h b/dom/media/gmp/GMPSanitizedExports.h
new file mode 100644
index 0000000000..76c00dba9b
--- /dev/null
+++ b/dom/media/gmp/GMPSanitizedExports.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// This file exposes symbols from the CDM headers + undefines macros which
+// X11 defines and which clash with the CDM headers.
+//
+// Ideally we can prune where X11 headers are included so that we don't need
+// this, but this band aid is useful until then.
+
+#ifndef DOM_MEDIA_GMP_GMP_API_GMP_SANITIZED_CDM_EXPORTS_H_
+#define DOM_MEDIA_GMP_GMP_API_GMP_SANITIZED_CDM_EXPORTS_H_
+
+// If Status is defined undef it so we don't break the CDM headers.
+#ifdef Status
+# undef Status
+#endif // Status
+#include "content_decryption_module.h"
+
+#endif // DOM_MEDIA_GMP_GMP_API_GMP_SANITIZED_CDM_EXPORTS_H_
diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp
new file mode 100644
index 0000000000..1d2444a4aa
--- /dev/null
+++ b/dom/media/gmp/GMPService.cpp
@@ -0,0 +1,570 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "ChromiumCDMParent.h"
+#include "GMPLog.h"
+#include "GMPParent.h"
+#include "GMPProcessParent.h"
+#include "GMPServiceChild.h"
+#include "GMPServiceParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "nsThreadUtils.h"
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxInfo.h"
+#endif
+#include "VideoUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsHashKeys.h"
+#include "nsIObserverService.h"
+#include "nsIXULAppInfo.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "prio.h"
+#include "runnable_utils.h"
+
+namespace mozilla {
+
+LogModule* GetGMPLog() {
+ static LazyLogModule sLog("GMP");
+ return sLog;
+}
+
+LogModule* GetGMPLibraryLog() {
+ static LazyLogModule sLog("GMPLibrary");
+ return sLog;
+}
+
+GMPLogLevel GetGMPLibraryLogLevel() {
+ switch (GetGMPLibraryLog()->Level()) {
+ case LogLevel::Disabled:
+ return kGMPLogQuiet;
+ case LogLevel::Error:
+ return kGMPLogError;
+ case LogLevel::Warning:
+ return kGMPLogWarning;
+ case LogLevel::Info:
+ return kGMPLogInfo;
+ case LogLevel::Debug:
+ return kGMPLogDebug;
+ case LogLevel::Verbose:
+ return kGMPLogDetail;
+ }
+ return kGMPLogInvalid;
+}
+
+#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 {
+ RefPtr<GMPServiceCreateHelper> createHelper =
+ new GMPServiceCreateHelper();
+
+ mozilla::SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(),
+ createHelper, true);
+
+ service = std::move(createHelper->mService);
+ }
+
+ return service.forget();
+ }
+
+ private:
+ GMPServiceCreateHelper() : Runnable("GMPServiceCreateHelper") {}
+
+ ~GMPServiceCreateHelper() { MOZ_ASSERT(!mService); }
+
+ static already_AddRefed<GeckoMediaPluginService> GetOrCreateOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingletonService) {
+ if (XRE_IsParentProcess()) {
+ RefPtr<GeckoMediaPluginServiceParent> service =
+ new GeckoMediaPluginServiceParent();
+ if (NS_WARN_IF(NS_FAILED(service->Init()))) {
+ return nullptr;
+ }
+ sSingletonService = service;
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // GMPProcessParent should only be instantiated in the parent
+ // so initialization only needs to be done in the parent.
+ GMPProcessParent::InitStaticMainThread();
+#endif
+ } else {
+ RefPtr<GeckoMediaPluginServiceChild> service =
+ new GeckoMediaPluginServiceChild();
+ if (NS_WARN_IF(NS_FAILED(service->Init()))) {
+ return nullptr;
+ }
+ 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"),
+ mMainThread(GetMainThreadSerialEventTarget()),
+ mGMPThreadShutdown(false),
+ mShuttingDownOnGMPThread(false),
+ mXPCOMWillShutdown(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (appInfo) {
+ nsAutoCString version;
+ nsAutoCString buildID;
+ if (NS_SUCCEEDED(appInfo->GetVersion(version)) &&
+ NS_SUCCEEDED(appInfo->GetAppBuildID(buildID))) {
+ GMP_LOG_DEBUG(
+ "GeckoMediaPluginService created; Gecko version=%s buildID=%s",
+ version.get(), buildID.get());
+ }
+ }
+}
+
+GeckoMediaPluginService::~GeckoMediaPluginService() = default;
+
+NS_IMETHODIMP
+GeckoMediaPluginService::RunPluginCrashCallbacks(
+ uint32_t aPluginId, const nsACString& aPluginName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId);
+
+ mozilla::UniquePtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers;
+ {
+ MutexAutoLock lock(mMutex);
+ mPluginCrashHelpers.Remove(aPluginId, &helpers);
+ }
+ if (!helpers) {
+ GMP_LOG_DEBUG("%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;
+ }
+ RefPtr<dom::Document> 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, u"PluginCrashed"_ns,
+ 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));
+}
+
+RefPtr<GetCDMParentPromise> GeckoMediaPluginService::GetCDM(
+ const NodeIdParts& aNodeIdParts, const nsACString& aKeySystem,
+ GMPCrashHelper* aHelper) {
+ AssertOnGMPThread();
+
+ if (mShuttingDownOnGMPThread || aKeySystem.IsEmpty()) {
+ nsPrintfCString reason(
+ "%s::%s failed, aKeySystem.IsEmpty() = %d, mShuttingDownOnGMPThread = "
+ "%d.",
+ __CLASS__, __FUNCTION__, aKeySystem.IsEmpty(),
+ mShuttingDownOnGMPThread);
+ return GetCDMParentPromise::CreateAndReject(
+ MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
+ }
+
+ typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder;
+ PromiseHolder* rawHolder(new PromiseHolder());
+ RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__);
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ nsTArray<nsCString> tags{nsCString{aKeySystem}};
+ GetContentParent(aHelper, NodeIdVariant{aNodeIdParts},
+ nsLiteralCString(CHROMIUM_CDM_API), tags)
+ ->Then(
+ thread, __func__,
+ [rawHolder, helper, keySystem = nsCString{aKeySystem}](
+ RefPtr<GMPContentParent::CloseBlocker> wrapper) {
+ RefPtr<GMPContentParent> parent = wrapper->mParent;
+ MOZ_ASSERT(
+ parent,
+ "Wrapper should wrap a valid parent if we're in this path.");
+ UniquePtr<PromiseHolder> holder(rawHolder);
+ RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM(keySystem);
+ if (!cdm) {
+ nsPrintfCString reason(
+ "%s::%s failed since GetChromiumCDM returns nullptr.",
+ __CLASS__, __FUNCTION__);
+ holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
+ __func__);
+ return;
+ }
+ if (helper) {
+ cdm->SetCrashHelper(helper);
+ }
+ holder->Resolve(cdm, __func__);
+ },
+ [rawHolder](MediaResult result) {
+ nsPrintfCString reason(
+ "%s::%s failed since GetContentParent rejects the promise with "
+ "reason %s.",
+ __CLASS__, __FUNCTION__, result.Description().get());
+ UniquePtr<PromiseHolder> holder(rawHolder);
+ holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
+ __func__);
+ });
+
+ return promise;
+}
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+RefPtr<GetGMPContentParentPromise>
+GeckoMediaPluginService::GetContentParentForTest() {
+ AssertOnGMPThread();
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement("fake"_ns);
+
+ const nsString origin1 = u"http://example1.com"_ns;
+ const nsString origin2 = u"http://example2.org"_ns;
+ const nsString gmpName = u"gmp-fake"_ns;
+
+ NodeIdParts nodeIdParts = NodeIdParts{origin1, origin2, gmpName};
+
+ if (mShuttingDownOnGMPThread) {
+ nsPrintfCString reason("%s::%s failed, mShuttingDownOnGMPThread = %d.",
+ __CLASS__, __FUNCTION__, mShuttingDownOnGMPThread);
+ return GetGMPContentParentPromise::CreateAndReject(
+ MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
+ }
+
+ using PromiseHolder = MozPromiseHolder<GetGMPContentParentPromise>;
+ PromiseHolder* rawHolder(new PromiseHolder());
+ RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ GetContentParent(nullptr, NodeIdVariant{nodeIdParts},
+ nsLiteralCString(CHROMIUM_CDM_API), tags)
+ ->Then(
+ thread, __func__,
+ [rawHolder](const RefPtr<GMPContentParent::CloseBlocker>& wrapper) {
+ RefPtr<GMPContentParent> parent = wrapper->mParent;
+ MOZ_ASSERT(
+ parent,
+ "Wrapper should wrap a valid parent if we're in this path.");
+ UniquePtr<PromiseHolder> holder(rawHolder);
+ if (!parent) {
+ nsPrintfCString reason("%s::%s failed since no GMPContentParent.",
+ __CLASS__, __FUNCTION__);
+ holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
+ __func__);
+ return;
+ }
+ holder->Resolve(wrapper, __func__);
+ },
+ [rawHolder](const MediaResult& result) {
+ nsPrintfCString reason(
+ "%s::%s failed since GetContentParent rejects the promise with "
+ "reason %s.",
+ __CLASS__, __FUNCTION__, result.Description().get());
+ UniquePtr<PromiseHolder> holder(rawHolder);
+ holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
+ __func__);
+ });
+
+ return promise;
+}
+#endif
+
+void GeckoMediaPluginService::ShutdownGMPThread() {
+ GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ mGMPThreadShutdown = true;
+ mGMPThread.swap(gmpThread);
+ }
+
+ if (gmpThread) {
+ gmpThread->Shutdown();
+ }
+}
+
+/* static */
+nsCOMPtr<nsIAsyncShutdownClient> GeckoMediaPluginService::GetShutdownBarrier() {
+ nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
+ if (NS_WARN_IF(!svc)) {
+ MOZ_ASSERT_UNREACHABLE("No async shutdown service!");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier;
+ nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ASSERT_UNREACHABLE("Could not create shutdown barrier!");
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(barrier);
+ return barrier;
+}
+
+nsresult GeckoMediaPluginService::GMPDispatch(nsIRunnable* event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> r(event);
+ return GMPDispatch(r.forget(), flags);
+}
+
+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_WARN_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);
+
+ return GetThreadLocked(aThread);
+}
+
+// always call with getter_AddRefs, because it does
+nsresult GeckoMediaPluginService::GetThreadLocked(nsIThread** aThread) {
+ MOZ_ASSERT(aThread);
+
+ mMutex.AssertCurrentThreadOwns();
+
+ 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_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Tell the thread to initialize plugins
+ InitializePlugins(mGMPThread);
+ }
+
+ nsCOMPtr<nsIThread> copy = mGMPThread;
+ copy.forget(aThread);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsISerialEventTarget> GeckoMediaPluginService::GetGMPThread() {
+ nsCOMPtr<nsISerialEventTarget> thread;
+ {
+ MutexAutoLock lock(mMutex);
+ thread = mGMPThread;
+ }
+ return thread.forget();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPVideoDecoder(
+ GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) {
+ AssertOnGMPThread();
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GetGMPVideoDecoderCallback* rawCallback = aCallback.release();
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)},
+ nsLiteralCString(GMP_API_VIDEO_DECODER), *aTags)
+ ->Then(
+ thread, __func__,
+ [rawCallback,
+ helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
+ RefPtr<GMPContentParent> parent = wrapper->mParent;
+ UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback);
+ GMPVideoDecoderParent* actor = nullptr;
+ GMPVideoHostImpl* host = nullptr;
+ if (parent && NS_SUCCEEDED(parent->GetGMPVideoDecoder(&actor))) {
+ host = &(actor->Host());
+ actor->SetCrashHelper(helper);
+ }
+ callback->Done(actor, host);
+ },
+ [rawCallback] {
+ UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback);
+ callback->Done(nullptr, nullptr);
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPVideoEncoder(
+ GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) {
+ AssertOnGMPThread();
+ NS_ENSURE_ARG(aTags && aTags->Length() > 0);
+ NS_ENSURE_ARG(aCallback);
+
+ if (mShuttingDownOnGMPThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GetGMPVideoEncoderCallback* rawCallback = aCallback.release();
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)},
+ nsLiteralCString(GMP_API_VIDEO_ENCODER), *aTags)
+ ->Then(
+ thread, __func__,
+ [rawCallback,
+ helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
+ RefPtr<GMPContentParent> parent = wrapper->mParent;
+ UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback);
+ GMPVideoEncoderParent* actor = nullptr;
+ GMPVideoHostImpl* host = nullptr;
+ if (parent && NS_SUCCEEDED(parent->GetGMPVideoEncoder(&actor))) {
+ host = &(actor->Host());
+ actor->SetCrashHelper(helper);
+ }
+ callback->Done(actor, host);
+ },
+ [rawCallback] {
+ UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback);
+ callback->Done(nullptr, nullptr);
+ });
+
+ return NS_OK;
+}
+
+void GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId,
+ GMPCrashHelper* aHelper) {
+ if (!aHelper) {
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+ mPluginCrashHelpers.WithEntryHandle(aPluginId, [&](auto&& entry) {
+ if (!entry) {
+ entry.Insert(MakeUnique<nsTArray<RefPtr<GMPCrashHelper>>>());
+ } else if (entry.Data()->Contains(aHelper)) {
+ return;
+ }
+ entry.Data()->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.UserData();
+ 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
+
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPService.h b/dom/media/gmp/GMPService.h
new file mode 100644
index 0000000000..8f4271812e
--- /dev/null
+++ b/dom/media/gmp/GMPService.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "GMPContentParent.h"
+#include "GMPCrashHelper.h"
+#include "gmp-video-codec.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/MozPromise.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIAsyncShutdownClient;
+class nsIRunnable;
+class nsISerialEventTarget;
+class nsIThread;
+
+template <class>
+struct already_AddRefed;
+
+namespace mozilla {
+
+class GMPCrashHelper;
+class MediaResult;
+
+extern LogModule* GetGMPLog();
+extern LogModule* GetGMPLibraryLog();
+extern GMPLogLevel GetGMPLibraryLogLevel();
+
+namespace gmp {
+
+typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>, MediaResult,
+ /* IsExclusive = */ true>
+ GetGMPContentParentPromise;
+typedef MozPromise<RefPtr<ChromiumCDMParent>, MediaResult,
+ /* IsExclusive = */ true>
+ GetCDMParentPromise;
+
+class GeckoMediaPluginService : public mozIGeckoMediaPluginService,
+ public nsIObserver {
+ public:
+ static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
+
+ virtual nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ RefPtr<GetCDMParentPromise> GetCDM(const NodeIdParts& aNodeIdParts,
+ const nsACString& aKeySystem,
+ GMPCrashHelper* aHelper);
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ RefPtr<GetGMPContentParentPromise> GetContentParentForTest();
+#endif
+
+ // mozIGeckoMediaPluginService
+ NS_IMETHOD GetThread(nsIThread** aThread) override;
+ nsresult GetThreadLocked(nsIThread** aThread);
+ NS_IMETHOD GetGMPVideoDecoder(
+ GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) override;
+ NS_IMETHOD GetGMPVideoEncoder(
+ GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
+ const nsACString& aNodeId,
+ UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) override;
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD RunPluginCrashCallbacks(
+ uint32_t aPluginId, const nsACString& aPluginName) override;
+
+ already_AddRefed<nsISerialEventTarget> GetGMPThread();
+
+ void ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper);
+ void DisconnectCrashHelper(GMPCrashHelper* aHelper);
+
+ bool XPCOMWillShutdownReceived() const { return mXPCOMWillShutdown; }
+
+ protected:
+ GeckoMediaPluginService();
+ virtual ~GeckoMediaPluginService();
+
+ void AssertOnGMPThread() {
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+#endif
+ }
+
+ virtual void InitializePlugins(nsISerialEventTarget* aGMPThread) = 0;
+
+ virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
+ GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant,
+ const nsACString& aAPI, const nsTArray<nsCString>& aTags) = 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();
+
+ static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier();
+
+ Mutex mMutex MOZ_UNANNOTATED; // Protects mGMPThread, mPluginCrashHelpers,
+ // mGMPThreadShutdown and some members in
+ // derived classes.
+
+ const nsCOMPtr<nsISerialEventTarget> mMainThread;
+
+ nsCOMPtr<nsIThread> mGMPThread;
+ bool mGMPThreadShutdown;
+ bool mShuttingDownOnGMPThread;
+ Atomic<bool> mXPCOMWillShutdown;
+
+ 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 0000000000..ebcb9dc84c
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -0,0 +1,586 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "GMPContentParent.h"
+#include "GMPLog.h"
+#include "GMPParent.h"
+#include "base/task.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsReadableUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "runnable_utils.h"
+
+namespace mozilla::gmp {
+
+#ifdef __CLASS__
+# undef __CLASS__
+#endif
+#define __CLASS__ "GMPServiceChild"
+
+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>();
+}
+
+nsresult GeckoMediaPluginServiceChild::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
+
+ nsresult rv = AddShutdownBlocker();
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "We expect xpcom to be live when calling this, so we should be able to "
+ "add a blocker");
+ GMP_LOG_DEBUG("%s::%s failed to add shutdown blocker!", __CLASS__,
+ __FUNCTION__);
+ return rv;
+ }
+
+ return GeckoMediaPluginService::Init();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceChild,
+ GeckoMediaPluginService, nsIAsyncShutdownBlocker)
+
+// Used to identify blockers that we put in place.
+static const nsLiteralString kShutdownBlockerName =
+ u"GeckoMediaPluginServiceChild: shutdown"_ns;
+
+// nsIAsyncShutdownBlocker members
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetName(nsAString& aName) {
+ aName = kShutdownBlockerName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetState(nsIPropertyBag**) { return NS_OK; }
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::BlockShutdown(nsIAsyncShutdownClient*) {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
+
+ mXPCOMWillShutdown = true;
+
+ MutexAutoLock lock(mMutex);
+ Unused << NS_WARN_IF(NS_FAILED(mGMPThread->Dispatch(
+ NewRunnableMethod("GeckoMediaPluginServiceChild::BeginShutdown", this,
+ &GeckoMediaPluginServiceChild::BeginShutdown))));
+ return NS_OK;
+}
+// End nsIAsyncShutdownBlocker members
+
+GeckoMediaPluginServiceChild::~GeckoMediaPluginServiceChild() {
+ MOZ_ASSERT(!mServiceChild);
+}
+
+RefPtr<GetGMPContentParentPromise>
+GeckoMediaPluginServiceChild::GetContentParent(
+ GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant,
+ const nsACString& aAPI, const nsTArray<nsCString>& aTags) {
+ AssertOnGMPThread();
+ MOZ_ASSERT(!mShuttingDownOnGMPThread,
+ "Should not be called if GMPThread is shutting down!");
+
+ MozPromiseHolder<GetGMPContentParentPromise>* rawHolder =
+ new MozPromiseHolder<GetGMPContentParentPromise>();
+ RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+
+ nsCString api(aAPI);
+ RefPtr<GMPCrashHelper> helper(aHelper);
+ RefPtr<GeckoMediaPluginServiceChild> self(this);
+
+ mPendingGetContentParents += 1;
+
+ GetServiceChild()->Then(
+ thread, __func__,
+ [nodeIdVariant = aNodeIdVariant, self, api, tags = aTags.Clone(), helper,
+ rawHolder](GMPServiceChild* child) {
+ UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(
+ rawHolder);
+ nsresult rv;
+
+ nsTArray<base::ProcessId> alreadyBridgedTo;
+ child->GetAlreadyBridgedTo(alreadyBridgedTo);
+
+ base::ProcessId otherProcess;
+ nsCString displayName;
+ uint32_t pluginId = 0;
+ GMPPluginType pluginType = GMPPluginType::Unknown;
+ ipc::Endpoint<PGMPContentParent> endpoint;
+ nsCString errorDescription;
+
+ bool ok = child->SendLaunchGMP(
+ nodeIdVariant, api, tags, alreadyBridgedTo, &pluginId, &pluginType,
+ &otherProcess, &displayName, &endpoint, &rv, &errorDescription);
+
+ if (helper && pluginId) {
+ // Note: Even if the launch failed, we need to connect the crash
+ // helper so that if the launch failed due to the plugin crashing, we
+ // can report the crash via the crash reporter. The crash handling
+ // notification will arrive shortly if the launch failed due to the
+ // plugin crashing.
+ self->ConnectCrashHelper(pluginId, helper);
+ }
+
+ if (!ok || NS_FAILED(rv)) {
+ MediaResult error(
+ rv, nsPrintfCString(
+ "GeckoMediaPluginServiceChild::GetContentParent "
+ "SendLaunchGMPForNodeId failed with description (%s)",
+ errorDescription.get()));
+
+ GMP_LOG_DEBUG("%s failed to launch GMP with error: %s", __CLASS__,
+ error.Description().get());
+ self->mPendingGetContentParents -= 1;
+ self->RemoveShutdownBlockerIfNeeded();
+
+ holder->Reject(error, __func__);
+ return;
+ }
+
+ RefPtr<GMPContentParent> parent = child->GetBridgedGMPContentParent(
+ otherProcess, std::move(endpoint));
+ if (!alreadyBridgedTo.Contains(otherProcess)) {
+ parent->SetDisplayName(displayName);
+ parent->SetPluginId(pluginId);
+ parent->SetPluginType(pluginType);
+ }
+
+ // The content parent is no longer pending.
+ self->mPendingGetContentParents -= 1;
+ MOZ_ASSERT(child->HaveContentParents(),
+ "We should have at least one content parent!");
+ // We don't check if we need to remove the shutdown blocker here as
+ // we should always have at least one live content parent.
+
+ RefPtr<GMPContentParent::CloseBlocker> blocker(
+ new GMPContentParent::CloseBlocker(parent));
+ holder->Resolve(blocker, __func__);
+ },
+ [self, rawHolder](MediaResult result) {
+ self->mPendingGetContentParents -= 1;
+ self->RemoveShutdownBlockerIfNeeded();
+ UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(
+ rawHolder);
+ holder->Reject(result, __func__);
+ });
+
+ return promise;
+}
+
+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 nsACString& tag : tags.tags()) {
+ cap.mAPITags.AppendElement(tag);
+ }
+ mCapabilities.AppendElement(std::move(cap));
+ }
+ }
+
+ nsCString ToString() const {
+ nsCString s;
+ s.Append(mName);
+ s.AppendLiteral(" version=");
+ s.Append(mVersion);
+ s.AppendLiteral(" tags=[");
+ StringJoinAppend(s, " "_ns, mCapabilities,
+ [](auto& tags, const GMPCapability& cap) {
+ tags.Append(cap.mAPIName);
+ for (const nsACString& tag : cap.mAPITags) {
+ tags.AppendLiteral(":");
+ tags.Append(tag);
+ }
+ });
+ s.AppendLiteral("]");
+ return s;
+ }
+
+ nsCString mName;
+ nsCString mVersion;
+ nsTArray<GMPCapability> mCapabilities;
+};
+
+StaticMutex sGMPCapabilitiesMutex;
+StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities;
+
+static auto GMPCapabilitiesToString() {
+ return StringJoin(", "_ns, *sGMPCapabilities,
+ [](nsACString& dest, const GMPCapabilityAndVersion& gmp) {
+ dest.Append(gmp.ToString());
+ });
+}
+
+/* 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));
+ }
+
+ GMP_LOG_DEBUG("%s::%s {%s}", __CLASS__, __FUNCTION__,
+ 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);
+ }
+}
+
+void GeckoMediaPluginServiceChild::BeginShutdown() {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%s::%s: mServiceChild=%p,", __CLASS__, __FUNCTION__,
+ mServiceChild.get());
+ // It's possible this gets called twice if the parent sends us a message to
+ // shutdown and we block shutdown in content in close proximity.
+ mShuttingDownOnGMPThread = true;
+ RemoveShutdownBlockerIfNeeded();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI,
+ const 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;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::GetNodeId(
+ const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName, UniquePtr<GetNodeIdCallback>&& aCallback) {
+ AssertOnGMPThread();
+
+ GetNodeIdCallback* rawCallback = aCallback.release();
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ nsString origin(aOrigin);
+ nsString topLevelOrigin(aTopLevelOrigin);
+ nsString gmpName(aGMPName);
+ GetServiceChild()->Then(
+ thread, __func__,
+ [rawCallback, origin, topLevelOrigin, gmpName](GMPServiceChild* child) {
+ UniquePtr<GetNodeIdCallback> callback(rawCallback);
+ nsCString outId;
+ if (!child->SendGetGMPNodeId(origin, topLevelOrigin, gmpName, &outId)) {
+ callback->Done(NS_ERROR_FAILURE, ""_ns);
+ return;
+ }
+
+ callback->Done(NS_OK, outId);
+ },
+ [rawCallback](nsresult rv) {
+ UniquePtr<GetNodeIdCallback> callback(rawCallback);
+ callback->Done(NS_ERROR_FAILURE, ""_ns);
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aSomeData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ GMP_LOG_DEBUG("%s::%s: aTopic=%s", __CLASS__, __FUNCTION__, aTopic);
+ if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+ if (mServiceChild) {
+ MutexAutoLock lock(mMutex);
+ mozilla::SyncRunnable::DispatchToThread(
+ mGMPThread,
+ WrapRunnable(mServiceChild.get(), &PGMPServiceChild::Close));
+ mServiceChild = nullptr;
+ }
+ ShutdownGMPThread();
+ }
+
+ return NS_OK;
+}
+
+RefPtr<GeckoMediaPluginServiceChild::GetServiceChildPromise>
+GeckoMediaPluginServiceChild::GetServiceChild() {
+ AssertOnGMPThread();
+
+ if (!mServiceChild) {
+ if (mShuttingDownOnGMPThread) {
+ // We have begun shutdown. Don't allow a new connection to the main
+ // process to be instantiated. This also prevents new plugins being
+ // instantiated.
+ return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (!contentChild) {
+ return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ MozPromiseHolder<GetServiceChildPromise>* holder =
+ mGetServiceChildPromises.AppendElement();
+ RefPtr<GetServiceChildPromise> promise = holder->Ensure(__func__);
+ if (mGetServiceChildPromises.Length() == 1) {
+ nsCOMPtr<nsIRunnable> r =
+ WrapRunnable(contentChild, &dom::ContentChild::SendCreateGMPService);
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+ return promise;
+ }
+ return GetServiceChildPromise::CreateAndResolve(mServiceChild.get(),
+ __func__);
+}
+
+void GeckoMediaPluginServiceChild::SetServiceChild(
+ RefPtr<GMPServiceChild>&& aServiceChild) {
+ AssertOnGMPThread();
+ MOZ_ASSERT(!mServiceChild, "Should not already have service child!");
+ GMP_LOG_DEBUG("%s::%s: aServiceChild=%p", __CLASS__, __FUNCTION__,
+ aServiceChild.get());
+
+ mServiceChild = std::move(aServiceChild);
+
+ nsTArray<MozPromiseHolder<GetServiceChildPromise>> holders =
+ std::move(mGetServiceChildPromises);
+ for (MozPromiseHolder<GetServiceChildPromise>& holder : holders) {
+ holder.Resolve(mServiceChild.get(), __func__);
+ }
+}
+
+void GeckoMediaPluginServiceChild::RemoveGMPContentParent(
+ GMPContentParent* aGMPContentParent) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG(
+ "%s::%s: aGMPContentParent=%p, mServiceChild=%p, "
+ "mShuttingDownOnGMPThread=%s",
+ __CLASS__, __FUNCTION__, aGMPContentParent, mServiceChild.get(),
+ mShuttingDownOnGMPThread ? "true" : "false");
+
+ if (mServiceChild) {
+ mServiceChild->RemoveGMPContentParent(aGMPContentParent);
+ GMP_LOG_DEBUG(
+ "%s::%s: aGMPContentParent removed, "
+ "mServiceChild->HaveContentParents()=%s",
+ __CLASS__, __FUNCTION__,
+ mServiceChild->HaveContentParents() ? "true" : "false");
+ RemoveShutdownBlockerIfNeeded();
+ }
+}
+
+nsresult GeckoMediaPluginServiceChild::AddShutdownBlocker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mShuttingDownOnGMPThread,
+ "No call paths should add blockers once we're shutting down!");
+ MOZ_ASSERT(!mShutdownBlockerAdded, "Should only add blocker once!");
+ GMP_LOG_DEBUG("%s::%s ", __CLASS__, __FUNCTION__);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ if (NS_WARN_IF(!barrier)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv =
+ barrier->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
+ __LINE__, kShutdownBlockerName);
+#ifdef DEBUG
+ mShutdownBlockerAdded = NS_SUCCEEDED(rv);
+#endif
+ return rv;
+}
+
+void GeckoMediaPluginServiceChild::RemoveShutdownBlocker() {
+ AssertOnGMPThread();
+ MOZ_ASSERT(mShuttingDownOnGMPThread,
+ "We should only remove blockers once we're "
+ "shutting down!");
+ GMP_LOG_DEBUG("%s::%s ", __CLASS__, __FUNCTION__);
+ nsresult rv = mMainThread->Dispatch(NS_NewRunnableFunction(
+ "GeckoMediaPluginServiceChild::"
+ "RemoveShutdownBlocker",
+ [this, self = RefPtr<GeckoMediaPluginServiceChild>(this)]() {
+ nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ if (NS_WARN_IF(!barrier)) {
+ return;
+ }
+
+ nsresult rv = barrier->RemoveBlocker(this);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Main thread should always be alive when we "
+ "call this!");
+ }
+}
+
+void GeckoMediaPluginServiceChild::RemoveShutdownBlockerIfNeeded() {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG(
+ "%s::%s mPendingGetContentParents=%" PRIu32
+ " mServiceChild->HaveContentParents()=%s "
+ "mShuttingDownOnGMPThread=%s",
+ __CLASS__, __FUNCTION__, mPendingGetContentParents,
+ mServiceChild && mServiceChild->HaveContentParents() ? "true" : "false",
+ mShuttingDownOnGMPThread ? "true" : "false");
+
+ bool haveOneOrMoreContentParents =
+ mPendingGetContentParents > 0 ||
+ (mServiceChild && mServiceChild->HaveContentParents());
+
+ if (!mShuttingDownOnGMPThread || haveOneOrMoreContentParents) {
+ return;
+ }
+ RemoveShutdownBlocker();
+}
+
+already_AddRefed<GMPContentParent> GMPServiceChild::GetBridgedGMPContentParent(
+ ProcessId aOtherPid, ipc::Endpoint<PGMPContentParent>&& endpoint) {
+ return do_AddRef(mContentParents.LookupOrInsertWith(aOtherPid, [&] {
+ MOZ_ASSERT(aOtherPid == endpoint.OtherPid());
+
+ auto parent = MakeRefPtr<GMPContentParent>();
+
+ DebugOnly<bool> ok = endpoint.Bind(parent);
+ MOZ_ASSERT(ok);
+
+ return parent;
+ }));
+}
+
+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) {
+ AppendToArray(aAlreadyBridgedTo, mContentParents.Keys());
+}
+
+class OpenPGMPServiceChild : public mozilla::Runnable {
+ public:
+ OpenPGMPServiceChild(RefPtr<GMPServiceChild>&& aGMPServiceChild,
+ ipc::Endpoint<PGMPServiceChild>&& aEndpoint)
+ : Runnable("gmp::OpenPGMPServiceChild"),
+ mGMPServiceChild(std::move(aGMPServiceChild)),
+ mEndpoint(std::move(aEndpoint)) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_RELEASE_ASSERT(gmp);
+ MOZ_ASSERT(!gmp->mServiceChild);
+ if (mEndpoint.Bind(mGMPServiceChild.get())) {
+ gmp->SetServiceChild(std::move(mGMPServiceChild));
+ } else {
+ gmp->SetServiceChild(nullptr);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<GMPServiceChild> mGMPServiceChild;
+ ipc::Endpoint<PGMPServiceChild> mEndpoint;
+};
+
+/* static */
+bool GMPServiceChild::Create(Endpoint<PGMPServiceChild>&& aGMPService) {
+ RefPtr<GeckoMediaPluginServiceChild> gmp =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ if (NS_WARN_IF(!gmp)) {
+ return false;
+ }
+
+ MOZ_ASSERT(!gmp->mServiceChild);
+
+ RefPtr<GMPServiceChild> serviceChild(new GMPServiceChild());
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = gmpThread->Dispatch(
+ new OpenPGMPServiceChild(std::move(serviceChild), std::move(aGMPService)),
+ NS_DISPATCH_NORMAL);
+ return NS_SUCCEEDED(rv);
+}
+
+ipc::IPCResult GMPServiceChild::RecvBeginShutdown() {
+ RefPtr<GeckoMediaPluginServiceChild> service =
+ GeckoMediaPluginServiceChild::GetSingleton();
+ MOZ_ASSERT(service && service->mServiceChild.get() == this);
+ if (service) {
+ service->BeginShutdown();
+ }
+ return IPC_OK();
+}
+
+bool GMPServiceChild::HaveContentParents() const {
+ return mContentParents.Count() > 0;
+}
+
+} // namespace mozilla::gmp
+
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPServiceChild.h b/dom/media/gmp/GMPServiceChild.h
new file mode 100644
index 0000000000..2775a83489
--- /dev/null
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "MediaResult.h"
+#include "base/process.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/gmp/PGMPServiceChild.h"
+#include "mozilla/MozPromise.h"
+#include "nsIAsyncShutdown.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla::gmp {
+
+class GMPContentParent;
+class GMPServiceChild;
+
+class GeckoMediaPluginServiceChild : public GeckoMediaPluginService,
+ public nsIAsyncShutdownBlocker {
+ friend class GMPServiceChild;
+
+ public:
+ static already_AddRefed<GeckoMediaPluginServiceChild> GetSingleton();
+ nsresult Init() override;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCSHUTDOWNBLOCKER
+
+ NS_IMETHOD HasPluginForAPI(const nsACString& aAPI,
+ const nsTArray<nsCString>& aTags,
+ bool* aRetVal) override;
+ NS_IMETHOD GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ UniquePtr<GetNodeIdCallback>&& aCallback) override;
+
+ NS_DECL_NSIOBSERVER
+
+ void SetServiceChild(RefPtr<GMPServiceChild>&& aServiceChild);
+
+ void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
+
+ static void UpdateGMPCapabilities(
+ nsTArray<mozilla::dom::GMPCapabilityData>&& aCapabilities);
+
+ void BeginShutdown();
+
+ protected:
+ void InitializePlugins(nsISerialEventTarget*) override {
+ // Nothing to do here.
+ }
+
+ RefPtr<GetGMPContentParentPromise> GetContentParent(
+ GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant,
+ const nsACString& aAPI, const nsTArray<nsCString>& aTags) override;
+
+ private:
+ friend class OpenPGMPServiceChild;
+
+ ~GeckoMediaPluginServiceChild() override;
+
+ typedef MozPromise<GMPServiceChild*, MediaResult, /* IsExclusive = */ true>
+ GetServiceChildPromise;
+ RefPtr<GetServiceChildPromise> GetServiceChild();
+
+ nsTArray<MozPromiseHolder<GetServiceChildPromise>> mGetServiceChildPromises;
+ RefPtr<GMPServiceChild> mServiceChild;
+
+ // Shutdown blocker management. A shutdown blocker is used to ensure that
+ // we do not shutdown until all content parents are removed from
+ // GMPServiceChild::mContentParents.
+ //
+ // The following rules let us shutdown block (and thus we shouldn't
+ // violate them):
+ // - We will only call GetContentParent if mShuttingDownOnGMPThread is false.
+ // - mShuttingDownOnGMPThread will become true once profile teardown is
+ // observed in the parent process (and once GMPServiceChild receives a
+ // message from GMPServiceParent) or if we block shutdown (which implies
+ // we're in shutdown).
+ // - If we currently have mPendingGetContentParents > 0 or parents in
+ // GMPServiceChild::mContentParents we should block shutdown so such
+ // parents can be cleanly shutdown.
+ // therefore
+ // - We can block shutdown at xpcom-will-shutdown until our content parents
+ // are handled.
+ // - Because once mShuttingDownOnGMPThread is true we cannot add new content
+ // parents, we know that when mShuttingDownOnGMPThread && all content
+ // parents are handled we'll never add more and it's safe to stop blocking
+ // shutdown.
+ // this relies on
+ // - Once we're shutting down, we need to ensure all content parents are
+ // shutdown and removed. Failure to do so will result in a shutdown stall.
+
+ // Note that at the time of writing there are significant differences in how
+ // xpcom shutdown is handled in release and non-release builds. For example,
+ // in release builds content processes are exited early, so xpcom shutdown
+ // is not observed (and not blocked by blockers). This is important to keep
+ // in mind when testing the shutdown blocking machinery (you won't see most
+ // of it be invoked in release).
+
+ // All of these members should only be used on the GMP thread unless
+ // otherwise noted!
+
+ // Add a shutdown blocker. Main thread only. Should only be called once when
+ // we init the service.
+ nsresult AddShutdownBlocker();
+ // Remove a shutdown blocker. Should be called once at most and only when
+ // mShuttingDownOnGMPThread. Prefer RemoveShutdownBlockerIfNeeded unless
+ // absolutely certain you need to call this directly.
+ void RemoveShutdownBlocker();
+ // Remove shutdown blocker if the following conditions are met:
+ // - mShuttingDownOnGMPThread.
+ // - !mServiceChild->HaveContentParents.
+ // - mPendingGetContentParents == 0.
+ // - mShutdownBlockerHasBeenAdded.
+ void RemoveShutdownBlockerIfNeeded();
+
+#ifdef DEBUG
+ // Track if we've added a shutdown blocker for sanity checking. Main thread
+ // only.
+ bool mShutdownBlockerAdded = false;
+#endif // DEBUG
+ // The number of GetContentParent calls that have not yet been resolved or
+ // rejected. We use this value to help determine if we need to block
+ // shutdown. Should only be used on GMP thread to avoid races.
+ uint32_t mPendingGetContentParents = 0;
+ // End shutdown blocker management.
+};
+
+class GMPServiceChild : public PGMPServiceChild {
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPServiceChild.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPServiceChild, final)
+
+ explicit GMPServiceChild() = default;
+
+ already_AddRefed<GMPContentParent> GetBridgedGMPContentParent(
+ ProcessId aOtherPid, ipc::Endpoint<PGMPContentParent>&& endpoint);
+
+ void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
+
+ void GetAlreadyBridgedTo(nsTArray<ProcessId>& aAlreadyBridgedTo);
+
+ static bool Create(Endpoint<PGMPServiceChild>&& aGMPService);
+
+ ipc::IPCResult RecvBeginShutdown() override;
+
+ bool HaveContentParents() const;
+
+ private:
+ ~GMPServiceChild() = default;
+
+ nsRefPtrHashtable<nsUint64HashKey, GMPContentParent> mContentParents;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPServiceChild_h_
diff --git a/dom/media/gmp/GMPServiceParent.cpp b/dom/media/gmp/GMPServiceParent.cpp
new file mode 100644
index 0000000000..d5d72a3655
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -0,0 +1,1943 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <limits>
+
+#include "GMPDecoderModule.h"
+#include "GMPLog.h"
+#include "GMPParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "base/task.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsThreadUtils.h"
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxInfo.h"
+#endif
+#include "VideoUtils.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsNetUtil.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsIXULRuntime.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "prio.h"
+#include "runnable_utils.h"
+
+#ifdef DEBUG
+# include "mozilla/dom/MediaKeys.h" // MediaKeys::kMediaKeysRequestTopic
+#endif
+
+namespace mozilla::gmp {
+
+#ifdef __CLASS__
+# undef __CLASS__
+#endif
+#define __CLASS__ "GMPServiceParent"
+
+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()
+ : mScannedPluginOnDisk(false),
+ mShuttingDown(false),
+ mWaitingForPluginsSyncShutdown(false),
+ mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor"),
+ mInitPromise(&mInitPromiseMonitor),
+ mLoadPluginsFromDiskComplete(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent() {
+ MOZ_ASSERT(mPlugins.IsEmpty());
+}
+
+nsresult GeckoMediaPluginServiceParent::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (AppShutdown::GetCurrentShutdownPhase() != ShutdownPhase::NotInShutdown) {
+ return NS_OK;
+ }
+
+ 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));
+ MOZ_ALWAYS_SUCCEEDS(
+ obsService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false));
+
+#ifdef DEBUG
+ MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(
+ this, dom::MediaKeys::kMediaKeysRequestTopic, 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_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Kick off scanning for plugins
+ nsCOMPtr<nsIThread> thread;
+ rv = GetThread(getter_AddRefs(thread));
+ if (NS_WARN_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("gmp::GeckoMediaPluginServiceParent::ClearStorage",
+ 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_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString arch;
+ rv = runtime->GetXPCOMABI(arch);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString platform;
+ platform.Append(OS);
+ platform.AppendLiteral("_");
+ platform.Append(arch);
+
+ CopyUTF8toUTF16(platform, aOutPlatform);
+
+ 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("gmp"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString platform;
+ rv = GMPPlatformString(platform);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mStorageBaseDir->Append(platform);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return GeckoMediaPluginService::Init();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) {
+ GMP_LOG_DEBUG("%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 (u"media.gmp.plugin.crash"_ns.Equals(aSomeData)) {
+ branch->GetBoolPref("media.gmp.plugin.crash", &crashNow);
+ }
+ if (crashNow) {
+ nsCOMPtr<nsIThread> gmpThread;
+ {
+ MutexAutoLock lock(mMutex);
+ gmpThread = mGMPThread;
+ }
+ if (gmpThread) {
+ // Note: the GeckoMediaPluginServiceParent singleton is kept alive by
+ // a static refptr that is only cleared in the final stage of shutdown
+ // after everything else is shutdown, so this RefPtr<> is not strictly
+ // necessary so long as that is true, but it's safer.
+ gmpThread->Dispatch(
+ WrapRunnable(RefPtr<GeckoMediaPluginServiceParent>(this),
+ &GeckoMediaPluginServiceParent::CrashPlugins),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ }
+ } else if (!strcmp("profile-change-teardown", aTopic)) {
+ mWaitingForPluginsSyncShutdown = true;
+
+ nsCOMPtr<nsIThread> gmpThread;
+ DebugOnly<bool> plugins_empty;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mShuttingDown);
+ mShuttingDown = true;
+ gmpThread = mGMPThread;
+#ifdef DEBUG
+ plugins_empty = mPlugins.IsEmpty();
+#endif
+ }
+
+ if (gmpThread) {
+ GMP_LOG_DEBUG(
+ "%s::%s Starting to unload plugins, waiting for sync shutdown...",
+ __CLASS__, __FUNCTION__);
+ gmpThread->Dispatch(
+ NewRunnableMethod("gmp::GeckoMediaPluginServiceParent::UnloadPlugins",
+ this,
+ &GeckoMediaPluginServiceParent::UnloadPlugins),
+ NS_DISPATCH_NORMAL);
+
+ // Wait for UnloadPlugins() to do sync shutdown...
+ SpinEventLoopUntil(
+ "GeckoMediaPluginServiceParent::Observe "
+ "WaitingForPluginsSyncShutdown"_ns,
+ [&]() { return !mWaitingForPluginsSyncShutdown; });
+ } else {
+ // GMP thread has already shutdown.
+ MOZ_ASSERT(plugins_empty);
+ mWaitingForPluginsSyncShutdown = false;
+ }
+
+ } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+#ifdef DEBUG
+ MOZ_ASSERT(mShuttingDown);
+#endif
+ ShutdownGMPThread();
+ } else if (!strcmp(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, aTopic)) {
+ mXPCOMWillShutdown = true;
+ } 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(
+ "gmp::GeckoMediaPluginServiceParent::ClearStorage", this,
+ &GeckoMediaPluginServiceParent::ClearStorage));
+ }
+
+ // Clear nodeIds/records modified after |t|.
+ nsresult rv;
+ PRTime t = nsDependentString(aSomeData).ToInteger64(&rv, 10);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return GMPDispatch(NewRunnableMethod<PRTime>(
+ "gmp::GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread",
+ 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__);
+}
+
+RefPtr<GetGMPContentParentPromise>
+GeckoMediaPluginServiceParent::GetContentParent(
+ GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant,
+ const nsACString& aAPI, const nsTArray<nsCString>& aTags) {
+ AssertOnGMPThread();
+
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ if (!thread) {
+ MOZ_ASSERT_UNREACHABLE(
+ "We should always be called on GMP thread, so it should be live");
+ return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ nsCString nodeIdString;
+ nsresult rv = GetNodeId(aNodeIdVariant, nodeIdString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ auto holder = MakeUnique<MozPromiseHolder<GetGMPContentParentPromise>>();
+ RefPtr<GetGMPContentParentPromise> promise = holder->Ensure(__func__);
+ EnsureInitialized()->Then(
+ thread, __func__,
+ [self = RefPtr<GeckoMediaPluginServiceParent>(this),
+ nodeIdString = std::move(nodeIdString), api = nsCString(aAPI),
+ tags = aTags.Clone(), helper = RefPtr<GMPCrashHelper>(aHelper),
+ holder = std::move(holder)](
+ const GenericPromise::ResolveOrRejectValue& aValue) mutable -> void {
+ if (aValue.IsReject()) {
+ NS_WARNING("GMPService::EnsureInitialized failed.");
+ holder->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ RefPtr<GMPParent> gmp =
+ self->SelectPluginForAPI(nodeIdString, api, tags);
+ GMP_LOG_DEBUG("%s: %p returning %p for api %s", __FUNCTION__,
+ self.get(), gmp.get(), api.get());
+ if (!gmp) {
+ NS_WARNING(
+ "GeckoMediaPluginServiceParent::GetContentParentFrom failed");
+ holder->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ self->ConnectCrashHelper(gmp->GetPluginId(), helper);
+ gmp->GetGMPContentParent(std::move(holder));
+ });
+
+ return promise;
+}
+
+void GeckoMediaPluginServiceParent::InitializePlugins(
+ nsISerialEventTarget* aGMPThread) {
+ MOZ_ASSERT(aGMPThread);
+ MonitorAutoLock lock(mInitPromiseMonitor);
+ if (mLoadPluginsFromDiskComplete) {
+ return;
+ }
+
+ RefPtr<GeckoMediaPluginServiceParent> self(this);
+ RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__);
+ InvokeAsync(aGMPThread, this, __func__,
+ &GeckoMediaPluginServiceParent::LoadFromEnvironment)
+ ->Then(
+ aGMPThread, __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::NotifySyncShutdownComplete() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mWaitingForPluginsSyncShutdown = false;
+}
+
+bool GeckoMediaPluginServiceParent::IsShuttingDown() {
+ AssertOnGMPThread();
+ return mShuttingDownOnGMPThread;
+}
+
+void GeckoMediaPluginServiceParent::UnloadPlugins() {
+ AssertOnGMPThread();
+ 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).
+ std::swap(plugins, mPlugins);
+
+ for (GMPServiceParent* parent : mServiceParents) {
+ Unused << parent->SendBeginShutdown();
+ }
+
+ GMP_LOG_DEBUG("%s::%s plugins:%zu", __CLASS__, __FUNCTION__,
+ plugins.Length());
+#ifdef DEBUG
+ for (const auto& plugin : plugins) {
+ GMP_LOG_DEBUG("%s::%s 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(
+ "GeckoMediaPluginServiceParent::NotifySyncShutdownComplete", this,
+ &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete);
+ mMainThread->Dispatch(task.forget());
+}
+
+void GeckoMediaPluginServiceParent::CrashPlugins() {
+ GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
+ AssertOnGMPThread();
+
+ MutexAutoLock lock(mMutex);
+ for (size_t i = 0; i < mPlugins.Length(); i++) {
+ mPlugins[i]->Crash();
+ }
+}
+
+RefPtr<GenericPromise> GeckoMediaPluginServiceParent::LoadFromEnvironment() {
+ AssertOnGMPThread();
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ if (!thread) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ const char* env = PR_GetEnv("MOZ_GMP_PATH");
+ if (!env || !*env) {
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }
+
+ nsString allpaths;
+ if (NS_WARN_IF(NS_FAILED(
+ NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) {
+ return GenericPromise::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)
+ ->Then(
+ thread, __func__,
+ []() { return GenericPromise::CreateAndResolve(true, __func__); },
+ []() {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+class NotifyObserversTask final : public mozilla::Runnable {
+ public:
+ explicit NotifyObserversTask(const char* aTopic, nsString aData = u""_ns)
+ : Runnable(aTopic), 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() = default;
+ 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(
+ ContentParent* aContentProcess) {
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task = NewRunnableMethod<ContentParent*>(
+ "GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities",
+ this,
+ &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities,
+ aContentProcess);
+ mMainThread->Dispatch(task.forget());
+ return;
+ }
+
+ typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
+ typedef mozilla::dom::GMPAPITags GMPAPITags;
+ typedef mozilla::dom::ContentParent ContentParent;
+
+ const uint32_t NO_H264 = 0;
+ const uint32_t HAS_H264 = 1;
+ const uint32_t NO_H264_1_DIR = 2;
+ const uint32_t NO_H264_2_PLUS_DIRS = 3;
+ const uint32_t NO_H264_DIR_IN_PROGRESS = 4;
+ uint32_t hasH264 = NO_H264;
+ if (mDirectoriesAdded == 1) {
+ hasH264 = NO_H264_1_DIR;
+ } else if (mDirectoriesAdded > 1) {
+ hasH264 = NO_H264_2_PLUS_DIRS;
+ }
+ if (mDirectoriesInProgress) {
+ hasH264 = NO_H264_DIR_IN_PROGRESS;
+ }
+ 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));
+ if (tag.mAPIName == nsLiteralCString(GMP_API_VIDEO_ENCODER) &&
+ tag.mAPITags.Contains("h264"_ns)) {
+ hasH264 = HAS_H264;
+ }
+ }
+ caps.AppendElement(std::move(x));
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::MEDIA_GMP_UPDATE_CONTENT_PROCESS_HAS_H264,
+ hasH264);
+
+ if (aContentProcess) {
+ Unused << aContentProcess->SendGMPsChanged(caps);
+ return;
+ }
+
+ 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);
+ }
+}
+
+void GeckoMediaPluginServiceParent::SendFlushFOGData(
+ nsTArray<RefPtr<FlushFOGDataPromise>>& promises) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mMutex);
+
+ for (const RefPtr<GMPParent>& gmp : mPlugins) {
+ if (gmp->State() != GMPState::Loaded) {
+ // Plugins that are not in the Loaded state have no process attached to
+ // them, and any IPC we would attempt to send them would be ignored (or
+ // result in a warning on debug builds).
+ continue;
+ }
+ RefPtr<FlushFOGDataPromise::Private> promise =
+ new FlushFOGDataPromise::Private(__func__);
+ // Direct dispatch will resolve the promise on the same thread, which is
+ // faster; FOGIPC will move execution back to the main thread.
+ promise->UseDirectTaskDispatch(__func__);
+ promises.EmplaceBack(promise);
+
+ mGMPThread->Dispatch(
+ NewRunnableMethod<ipc::ResolveCallback<ipc::ByteBuf>&&,
+ ipc::RejectCallback&&>(
+ "GMPParent::SendFlushFOGData", gmp,
+ static_cast<void (GMPParent::*)(
+ mozilla::ipc::ResolveCallback<ipc::ByteBuf>&& aResolve,
+ mozilla::ipc::RejectCallback&& aReject)>(
+ &GMPParent::SendFlushFOGData),
+
+ [promise](ipc::ByteBuf&& aValue) {
+ promise->Resolve(std::move(aValue), __func__);
+ },
+ [promise](ipc::ResponseRejectReason&& aReason) {
+ promise->Reject(std::move(aReason), __func__);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+RefPtr<PGMPParent::TestTriggerMetricsPromise>
+GeckoMediaPluginServiceParent::TestTriggerMetrics() {
+ MOZ_ASSERT(NS_IsMainThread());
+ {
+ MutexAutoLock lock(mMutex);
+ for (const RefPtr<GMPParent>& gmp : mPlugins) {
+ if (gmp->State() != GMPState::Loaded) {
+ // Plugins that are not in the Loaded state have no process attached to
+ // them, and any IPC we would attempt to send them would be ignored (or
+ // result in a warning on debug builds).
+ continue;
+ }
+
+ RefPtr<PGMPParent::TestTriggerMetricsPromise::Private> promise =
+ new PGMPParent::TestTriggerMetricsPromise::Private(__func__);
+ // Direct dispatch will resolve the promise on the same thread, which is
+ // faster; FOGIPC will move execution back to the main thread.
+ promise->UseDirectTaskDispatch(__func__);
+
+ mGMPThread->Dispatch(
+ NewRunnableMethod<ipc::ResolveCallback<bool>&&,
+ ipc::RejectCallback&&>(
+ "GMPParent::SendTestTriggerMetrics", gmp,
+ static_cast<void (GMPParent::*)(
+ mozilla::ipc::ResolveCallback<bool>&& aResolve,
+ mozilla::ipc::RejectCallback&& aReject)>(
+ &PGMPParent::SendTestTriggerMetrics),
+
+ [promise](bool aValue) {
+ promise->Resolve(std::move(aValue), __func__);
+ },
+ [promise](ipc::ResponseRejectReason&& aReason) {
+ promise->Reject(std::move(aReason), __func__);
+ }),
+ NS_DISPATCH_NORMAL);
+
+ return promise;
+ }
+ }
+
+ return PGMPParent::TestTriggerMetricsPromise::CreateAndReject(
+ ipc::ResponseRejectReason::SendError, __func__);
+}
+
+RefPtr<GenericPromise> GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(
+ const nsAString& aDirectory) {
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ if (!thread) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ mDirectoriesAdded++;
+ mDirectoriesInProgress++;
+
+ nsString dir(aDirectory);
+ RefPtr<GeckoMediaPluginServiceParent> self = this;
+ return InvokeAsync(thread, this, __func__,
+ &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
+ ->Then(
+ mMainThread, __func__,
+ [dir, self](bool aVal) {
+ GMP_LOG_DEBUG(
+ "GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s "
+ "succeeded",
+ NS_ConvertUTF16toUTF8(dir).get());
+ MOZ_ASSERT(NS_IsMainThread());
+ self->mDirectoriesInProgress--;
+ self->UpdateContentProcessGMPCapabilities();
+ return GenericPromise::CreateAndResolve(aVal, __func__);
+ },
+ [dir, self](nsresult aResult) {
+ GMP_LOG_DEBUG(
+ "GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s "
+ "failed",
+ NS_ConvertUTF16toUTF8(dir).get());
+ self->mDirectoriesInProgress--;
+ return GenericPromise::CreateAndReject(aResult, __func__);
+ });
+}
+
+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,
+ const nsTArray<nsCString>& aTags,
+ bool* aHasPlugin) {
+ NS_ENSURE_ARG(!aTags.IsEmpty());
+ 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.
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = GetThread(getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned"_ns, thread,
+ MakeAndAddRef<mozilla::Runnable>("GMPDummyRunnable"));
+ 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 nsACString& 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 nsACString& aAPI,
+ const nsTArray<nsCString>& aTags) {
+ AssertOnGMPThread();
+
+ 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;
+}
+
+static already_AddRefed<GMPParent> CreateGMPParent() {
+ // Should run on the GMP thread.
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ if (!SandboxInfo::Get().CanSandboxMedia()) {
+ if (!StaticPrefs::media_gmp_insecure_allow()) {
+ NS_WARNING("Denying media plugin load due to lack of sandboxing.");
+ return nullptr;
+ }
+ NS_WARNING("Loading media plugin despite lack of sandboxing.");
+ }
+#endif
+ RefPtr<GMPParent> gmpParent = new GMPParent();
+ return gmpParent.forget();
+}
+
+already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::ClonePlugin(
+ const GMPParent* aOriginal) {
+ AssertOnGMPThread();
+ MOZ_ASSERT(aOriginal);
+
+ RefPtr<GMPParent> gmp = CreateGMPParent();
+ if (!gmp) {
+ NS_WARNING("Can't Create GMPParent");
+ return nullptr;
+ }
+
+ gmp->CloneFrom(aOriginal);
+ return gmp.forget();
+}
+
+RefPtr<GenericPromise> GeckoMediaPluginServiceParent::AddOnGMPThread(
+ nsString aDirectory) {
+#ifdef XP_WIN
+ // On Windows our various test harnesses often pass paths with UNIX dir
+ // separators, or a mix of dir separators. NS_NewLocalFile() can't handle
+ // that, so fixup to match the platform's expected format. This makes us
+ // more robust in the face of bad input and test harnesses changing...
+ std::replace(aDirectory.BeginWriting(), aDirectory.EndWriting(), '/', '\\');
+#endif
+
+ AssertOnGMPThread();
+ nsCString dir = NS_ConvertUTF16toUTF8(aDirectory);
+ nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
+ if (!thread) {
+ GMP_LOG_DEBUG("%s::%s: %s No GMP Thread", __CLASS__, __FUNCTION__,
+ dir.get());
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+ GMP_LOG_DEBUG("%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))) {
+ GMP_LOG_DEBUG("%s::%s: failed to create nsIFile for dir=%s rv=%" PRIx32,
+ __CLASS__, __FUNCTION__, dir.get(),
+ static_cast<uint32_t>(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](bool aVal) {
+ GMP_LOG_DEBUG("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__,
+ dir.get());
+ {
+ MutexAutoLock lock(self->mMutex);
+ self->mPlugins.AppendElement(gmp);
+ }
+ return GenericPromise::CreateAndResolve(aVal, __func__);
+ },
+ [dir](nsresult aResult) {
+ GMP_LOG_DEBUG("%s::%s: %s Failed", __CLASS__, __FUNCTION__,
+ dir.get());
+ return GenericPromise::CreateAndReject(aResult, __func__);
+ });
+}
+
+void GeckoMediaPluginServiceParent::RemoveOnGMPThread(
+ const nsAString& aDirectory, const bool aDeleteFromDisk,
+ const bool aCanDefer) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%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() != GMPState::NotLoaded) {
+ // 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() == GMPState::NotLoaded || !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->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);
+ nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(
+ "gmp-directory-deleted", nsString(aDirectory));
+ mMainThread->Dispatch(task.forget());
+ }
+ }
+}
+
+// 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) {
+ AssertOnGMPThread();
+
+ if (aPlugin->IsMarkedForDeletion()) {
+ nsString path;
+ RefPtr<nsIFile> dir = aPlugin->GetDirectory();
+ nsresult rv = dir->GetPath(path);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (mPluginsWaitingForDeletion.Contains(path)) {
+ RemoveOnGMPThread(path, true /* delete */, true /* can defer */);
+ }
+ }
+}
+
+void GeckoMediaPluginServiceParent::ReAddOnGMPThread(
+ const RefPtr<GMPParent>& aOld) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%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);
+}
+
+nsresult WriteToFile(nsIFile* aPath, const nsACString& aFileName,
+ const nsACString& 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.BeginReading(), 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, "salt"_ns, aOutData, NodeIdSaltLength);
+}
+
+already_AddRefed<GMPStorage> GeckoMediaPluginServiceParent::GetMemoryStorageFor(
+ const nsACString& aNodeId) {
+ return do_AddRef(mTempGMPStorage.LookupOrInsertWith(
+ aNodeId, [] { return CreateGMPMemoryStorage(); }));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::IsPersistentStorageAllowed(
+ const nsACString& aNodeId, bool* aOutAllowed) {
+ AssertOnGMPThread();
+ 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, nsACString& aOutId) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%s::%s: (%s, %s)", __CLASS__, __FUNCTION__,
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get());
+
+ 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.InsertOrUpdate(salt, false);
+ return NS_OK;
+ }
+
+ const uint32_t hash =
+ AddToHash(HashString(aOrigin), HashString(aTopLevelOrigin));
+
+ if (OriginAttributes::IsPrivateBrowsing(NS_ConvertUTF16toUTF8(aOrigin))) {
+ // 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);
+ return mTempNodeIds.WithEntryHandle(pbHash, [&](auto&& entry) {
+ if (!entry) {
+ // 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;
+ }
+ auto salt = MakeUnique<nsCString>(newSalt);
+
+ mPersistentStorageAllowed.InsertOrUpdate(*salt, false);
+
+ entry.Insert(std::move(salt));
+ }
+
+ aOutId = *entry.Data();
+ 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;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/
+ rv = path->Append(aGMPName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/
+ rv = path->AppendNative("id"_ns);
+ if (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("salt"_ns);
+ 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, "salt"_ns, salt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/origin
+ rv = WriteToFile(path, "origin"_ns, NS_ConvertUTF16toUTF8(aOrigin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin
+ rv = WriteToFile(path, "topLevelOrigin"_ns,
+ 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.InsertOrUpdate(salt, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetNodeId(
+ const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName, UniquePtr<GetNodeIdCallback>&& aCallback) {
+ nsCString nodeId;
+ nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, nodeId);
+ aCallback->Done(rv, nodeId);
+ return rv;
+}
+
+nsresult GeckoMediaPluginServiceParent::GetNodeId(
+ const NodeIdVariant& aNodeIdVariant, nsACString& aOutId) {
+ if (aNodeIdVariant.type() == NodeIdVariant::TnsCString) {
+ // The union already contains a node ID as a string, use that.
+ aOutId = aNodeIdVariant.get_nsCString();
+ return NS_OK;
+ }
+ // The union contains a NodeIdParts, convert it to a node ID string.
+ NodeIdParts nodeIdParts{aNodeIdVariant.get_NodeIdParts()};
+ return GetNodeId(nodeIdParts.mOrigin(), nodeIdParts.mTopLevelOrigin(),
+ nodeIdParts.mGMPName(), aOutId);
+}
+
+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;
+}
+
+constexpr uint32_t kMaxDomainLength = 253;
+// http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax
+
+constexpr std::array<nsLiteralCString, 2> kFileNames = {"origin"_ns,
+ "topLevelOrigin"_ns};
+
+bool MatchOrigin(nsIFile* aPath, const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern) {
+ nsresult rv;
+ nsCString str;
+ nsCString originNoSuffix;
+ for (const auto& fileName : kFileNames) {
+ rv = ReadFromFile(aPath, fileName, str, kMaxDomainLength);
+ mozilla::OriginAttributes originAttributes;
+ if (NS_FAILED(rv) ||
+ !originAttributes.PopulateFromOrigin(str, originNoSuffix)) {
+ // Fails on parsing the originAttributes, treat this as a non-match.
+ return false;
+ }
+
+ if (ExtractHostName(originNoSuffix, str) && str.Equals(aSite) &&
+ aPattern.Matches(originAttributes)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MatchBaseDomain(nsIFile* aPath, const nsACString& aBaseDomain) {
+ nsresult rv;
+ nsCString fileContent;
+ nsCString originNoSuffix;
+ for (const auto& fileName : kFileNames) {
+ rv = ReadFromFile(aPath, fileName, fileContent, kMaxDomainLength);
+ mozilla::OriginAttributes originAttributes;
+ if (NS_FAILED(rv) ||
+ !originAttributes.PopulateFromOrigin(fileContent, originNoSuffix)) {
+ // Fails on parsing the originAttributes, treat this as a non-match.
+ return false;
+ }
+ nsCString originHostname;
+ if (!ExtractHostName(originNoSuffix, originHostname)) {
+ return false;
+ }
+ bool success;
+ rv = net::HasRootDomain(originHostname, aBaseDomain, &success);
+ if (NS_SUCCEEDED(rv) && success) {
+ 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);
+ }
+}
+
+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_WARN_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, u"id"_ns);
+ 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, u"storage"_ns);
+ if (!path) {
+ return;
+ }
+
+ for (const nsACString& nodeId : nodeIDsToClear) {
+ nsCOMPtr<nsIFile> dirEntry;
+ nsresult rv = path->Clone(getter_AddRefs(dirEntry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ rv = dirEntry->AppendNative(nodeId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ if (NS_FAILED(dirEntry->Remove(true))) {
+ NS_WARNING("Failed to delete GMP storage directory for the node");
+ }
+ }
+}
+
+void GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread(
+ const nsACString& aSite, const mozilla::OriginAttributesPattern& aPattern) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%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::ForgetThisBaseDomainOnGMPThread(
+ const nsACString& aBaseDomain) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%s::%s: baseDomain=%s", __CLASS__, __FUNCTION__,
+ aBaseDomain.Data());
+
+ struct BaseDomainFilter : public DirectoryFilter {
+ explicit BaseDomainFilter(const nsACString& aBaseDomain)
+ : mBaseDomain(aBaseDomain) {}
+ bool operator()(nsIFile* aPath) override {
+ return MatchBaseDomain(aPath, mBaseDomain);
+ }
+
+ private:
+ const nsACString& mBaseDomain;
+ } filter(aBaseDomain);
+
+ ClearNodeIdAndPlugin(filter);
+}
+
+void GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread(
+ PRTime aSince) {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%s::%s: since=%" PRId64, __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_WARN_IF(NS_FAILED(ReadSalt(aPath, salt)))) {
+ return false;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/id/
+ nsCOMPtr<nsIFile> idDir;
+ if (NS_WARN_IF(NS_FAILED(aPath->GetParent(getter_AddRefs(idDir))))) {
+ return false;
+ }
+ // $profileDir/gmp/$platform/$gmpName/
+ nsCOMPtr<nsIFile> temp;
+ if (NS_WARN_IF(NS_FAILED(idDir->GetParent(getter_AddRefs(temp))))) {
+ return false;
+ }
+
+ // $profileDir/gmp/$platform/$gmpName/storage/
+ if (NS_WARN_IF(NS_FAILED(temp->Append(u"storage"_ns)))) {
+ return false;
+ }
+ // $profileDir/gmp/$platform/$gmpName/storage/$originSalt
+ return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp);
+ }
+
+ private:
+ const PRTime mSince;
+ } filter(aSince);
+
+ ClearNodeIdAndPlugin(filter);
+
+ nsCOMPtr<nsIRunnable> task =
+ new NotifyObserversTask("gmp-clear-storage-complete");
+ mMainThread->Dispatch(task.forget());
+}
+
+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>(
+ "gmp::GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread", this,
+ &GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread,
+ NS_ConvertUTF16toUTF8(aSite), aPattern));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::ForgetThisBaseDomain(
+ const nsAString& aBaseDomain) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return ForgetThisBaseDomainNative(aBaseDomain);
+}
+
+nsresult GeckoMediaPluginServiceParent::ForgetThisBaseDomainNative(
+ const nsAString& aBaseDomain) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return GMPDispatch(NewRunnableMethod<nsCString>(
+ "gmp::GeckoMediaPluginServiceParent::ForgetThisBaseDomainOnGMPThread",
+ this, &GeckoMediaPluginServiceParent::ForgetThisBaseDomainOnGMPThread,
+ NS_ConvertUTF16toUTF8(aBaseDomain)));
+}
+
+static bool IsNodeIdValid(GMPParent* aParent) {
+ return !aParent->GetNodeId().IsEmpty();
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetName(nsAString& aName) {
+ aName = u"GeckoMediaPluginServiceParent: shutdown"_ns;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::GetState(nsIPropertyBag**) { return NS_OK; }
+
+NS_IMETHODIMP
+GeckoMediaPluginServiceParent::BlockShutdown(nsIAsyncShutdownClient*) {
+ return NS_OK;
+}
+
+// Called from GMPServiceParent::Create() which holds the lock
+void GeckoMediaPluginServiceParent::ServiceUserCreated(
+ GMPServiceParent* aServiceParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ MOZ_ASSERT(!mServiceParents.Contains(aServiceParent));
+ mServiceParents.AppendElement(aServiceParent);
+ if (mServiceParents.Length() == 1) {
+ nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ MOZ_RELEASE_ASSERT(barrier);
+
+ nsresult rv = barrier->AddBlocker(
+ this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
+ u"GeckoMediaPluginServiceParent shutdown"_ns);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void GeckoMediaPluginServiceParent::ServiceUserDestroyed(
+ GMPServiceParent* aServiceParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mServiceParents.Length() > 0);
+ MOZ_ASSERT(mServiceParents.Contains(aServiceParent));
+ mServiceParents.RemoveElement(aServiceParent);
+ if (mServiceParents.IsEmpty()) {
+ nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ MOZ_RELEASE_ASSERT(barrier);
+ nsresult rv = barrier->RemoveBlocker(this);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void GeckoMediaPluginServiceParent::ClearStorage() {
+ AssertOnGMPThread();
+ GMP_LOG_DEBUG("%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(path->Remove(true))) {
+ NS_WARNING("Failed to delete GMP storage directory");
+ }
+
+ // Clear private-browsing storage.
+ mTempGMPStorage.Clear();
+
+ nsCOMPtr<nsIRunnable> task =
+ new NotifyObserversTask("gmp-clear-storage-complete");
+ mMainThread->Dispatch(task.forget());
+}
+
+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(GeckoMediaPluginServiceParent* aService)
+ : mService(aService) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be constructed on the main thread");
+ MOZ_ASSERT(mService);
+ mService->ServiceUserCreated(this);
+}
+
+GMPServiceParent::~GMPServiceParent() {
+ MOZ_ASSERT(NS_IsMainThread(), "Should be destroyted on the main thread");
+ MOZ_ASSERT(mService);
+ mService->ServiceUserDestroyed(this);
+}
+
+mozilla::ipc::IPCResult GMPServiceParent::RecvLaunchGMP(
+ const NodeIdVariant& aNodeIdVariant, const nsACString& aAPI,
+ nsTArray<nsCString>&& aTags, nsTArray<ProcessId>&& aAlreadyBridgedTo,
+ uint32_t* aOutPluginId, GMPPluginType* aOutPluginType,
+ ProcessId* aOutProcessId, nsCString* aOutDisplayName,
+ Endpoint<PGMPContentParent>* aOutEndpoint, nsresult* aOutRv,
+ nsCString* aOutErrorDescription) {
+ if (mService->IsShuttingDown()) {
+ *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ *aOutErrorDescription = "Service is shutting down."_ns;
+ *aOutPluginId = 0;
+ *aOutPluginType = GMPPluginType::Unknown;
+ return IPC_OK();
+ }
+
+ nsCString nodeIdString;
+ nsresult rv = mService->GetNodeId(aNodeIdVariant, nodeIdString);
+ if (!NS_SUCCEEDED(rv)) {
+ *aOutRv = rv;
+ *aOutErrorDescription = "GetNodeId failed."_ns;
+ *aOutPluginId = 0;
+ *aOutPluginType = GMPPluginType::Unknown;
+ return IPC_OK();
+ }
+
+ RefPtr<GMPParent> gmp =
+ mService->SelectPluginForAPI(nodeIdString, aAPI, aTags);
+ if (gmp) {
+ *aOutPluginId = gmp->GetPluginId();
+ *aOutPluginType = gmp->GetPluginType();
+ } else {
+ *aOutRv = NS_ERROR_FAILURE;
+ *aOutErrorDescription = "SelectPluginForAPI returns nullptr."_ns;
+ *aOutPluginId = 0;
+ *aOutPluginType = GMPPluginType::Unknown;
+ return IPC_OK();
+ }
+
+ if (!gmp->EnsureProcessLoaded(aOutProcessId)) {
+ *aOutRv = NS_ERROR_FAILURE;
+ *aOutErrorDescription = "Process has not loaded."_ns;
+ return IPC_OK();
+ }
+
+ *aOutDisplayName = gmp->GetDisplayName();
+
+ if (aAlreadyBridgedTo.Contains(*aOutProcessId)) {
+ *aOutRv = NS_OK;
+ return IPC_OK();
+ }
+
+ Endpoint<PGMPContentParent> parent;
+ Endpoint<PGMPContentChild> child;
+ rv =
+ PGMPContent::CreateEndpoints(OtherPid(), *aOutProcessId, &parent, &child);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ *aOutRv = rv;
+ *aOutErrorDescription = "PGMPContent::CreateEndpoints failed."_ns;
+ return IPC_OK();
+ }
+
+ *aOutEndpoint = std::move(parent);
+
+ if (!gmp->SendInitGMPContentChild(std::move(child))) {
+ *aOutRv = NS_ERROR_FAILURE;
+ *aOutErrorDescription = "SendInitGMPContentChild failed."_ns;
+ return IPC_OK();
+ }
+
+ gmp->IncrementGMPContentChildCount();
+
+ *aOutRv = NS_OK;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPServiceParent::RecvGetGMPNodeId(
+ const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName, nsCString* aID) {
+ nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, *aID);
+ if (!NS_SUCCEEDED(rv)) {
+ return IPC_FAIL(
+ this,
+ "GMPServiceParent::RecvGetGMPNodeId: mService->GetNodeId failed.");
+ }
+ return IPC_OK();
+}
+
+class OpenPGMPServiceParent : public mozilla::Runnable {
+ public:
+ OpenPGMPServiceParent(RefPtr<GMPServiceParent>&& aGMPServiceParent,
+ ipc::Endpoint<PGMPServiceParent>&& aEndpoint,
+ bool* aResult)
+ : Runnable("gmp::OpenPGMPServiceParent"),
+ mGMPServiceParent(aGMPServiceParent),
+ mEndpoint(std::move(aEndpoint)),
+ mResult(aResult) {}
+
+ NS_IMETHOD Run() override {
+ *mResult = mEndpoint.Bind(mGMPServiceParent);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<GMPServiceParent> mGMPServiceParent;
+ ipc::Endpoint<PGMPServiceParent> mEndpoint;
+ bool* mResult;
+};
+
+/* static */
+bool GMPServiceParent::Create(Endpoint<PGMPServiceParent>&& aGMPService) {
+ RefPtr<GeckoMediaPluginServiceParent> gmp =
+ GeckoMediaPluginServiceParent::GetSingleton();
+
+ if (!gmp || gmp->mShuttingDown) {
+ // Shutdown is initiated. There is no point creating a new actor.
+ return false;
+ }
+
+ nsCOMPtr<nsIThread> gmpThread;
+ RefPtr<GMPServiceParent> serviceParent;
+ {
+ MutexAutoLock lock(gmp->mMutex);
+ nsresult rv = gmp->GetThreadLocked(getter_AddRefs(gmpThread));
+ NS_ENSURE_SUCCESS(rv, false);
+ serviceParent = new GMPServiceParent(gmp);
+ }
+ bool ok;
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "GMPServiceParent::Create"_ns, gmpThread,
+ do_AddRef(new OpenPGMPServiceParent(std::move(serviceParent),
+ std::move(aGMPService), &ok)));
+
+ if (NS_WARN_IF(NS_FAILED(rv) || !ok)) {
+ return false;
+ }
+
+ // Now that the service parent is set up, it will be released by IPC
+ // refcounting, so we don't need to hold any more references here.
+
+ return true;
+}
+
+} // namespace mozilla::gmp
+
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPServiceParent.h b/dom/media/gmp/GMPServiceParent.h
new file mode 100644
index 0000000000..0a897d6a39
--- /dev/null
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/ContentParent.h"
+#include "mozilla/gmp/PGMPServiceParent.h"
+#include "mozIGeckoMediaPluginChromeService.h"
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "mozilla/Atomics.h"
+#include "nsNetUtil.h"
+#include "nsIAsyncShutdown.h"
+#include "nsRefPtrHashtable.h"
+#include "nsThreadUtils.h"
+#include "mozilla/gmp/PGMPParent.h"
+#include "mozilla/MozPromise.h"
+#include "GMPStorage.h"
+
+template <class>
+struct already_AddRefed;
+using FlushFOGDataPromise = mozilla::dom::ContentParent::FlushFOGDataPromise;
+using ContentParent = mozilla::dom::ContentParent;
+
+namespace mozilla {
+class OriginAttributesPattern;
+
+namespace gmp {
+
+class GMPParent;
+class GMPServiceParent;
+
+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,
+ const nsTArray<nsCString>& aTags,
+ bool* aRetVal) override;
+ NS_IMETHOD GetNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ UniquePtr<GetNodeIdCallback>&& aCallback) override;
+
+ NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
+ NS_DECL_NSIOBSERVER
+
+ 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);
+
+ nsresult ForgetThisBaseDomainNative(const nsAString& aBaseDomain);
+
+ // Notifies that some user of this class is created/destroyed.
+ void ServiceUserCreated(GMPServiceParent* aServiceParent);
+ void ServiceUserDestroyed(GMPServiceParent* aServiceParent);
+
+ // If aContentProcess is specified, this will only update GMP caps in that
+ // content process, otherwise will update all content processes.
+ void UpdateContentProcessGMPCapabilities(
+ ContentParent* aContentProcess = nullptr);
+
+ void SendFlushFOGData(nsTArray<RefPtr<FlushFOGDataPromise>>& promises);
+
+ /*
+ * ** Test-only Method **
+ *
+ * Trigger GMP-process test metric instrumentation.
+ */
+ RefPtr<PGMPParent::TestTriggerMetricsPromise> TestTriggerMetrics();
+
+ private:
+ friend class GMPServiceParent;
+
+ virtual ~GeckoMediaPluginServiceParent();
+
+ void ClearStorage();
+
+ already_AddRefed<GMPParent> SelectPluginForAPI(
+ const nsACString& aNodeId, const nsACString& aAPI,
+ const nsTArray<nsCString>& aTags);
+
+ already_AddRefed<GMPParent> FindPluginForAPIFrom(
+ size_t aSearchStartIndex, const nsACString& aAPI,
+ const nsTArray<nsCString>& aTags, size_t* aOutPluginIndex);
+
+ nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName, nsACString& aOutId);
+
+ void UnloadPlugins();
+ void CrashPlugins();
+ void NotifySyncShutdownComplete();
+
+ void RemoveOnGMPThread(const nsAString& aDirectory,
+ const bool aDeleteFromDisk, const bool aCanDefer);
+
+ struct DirectoryFilter {
+ virtual bool operator()(nsIFile* aPath) = 0;
+ ~DirectoryFilter() = default;
+ };
+ void ClearNodeIdAndPlugin(DirectoryFilter& aFilter);
+ void ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+ DirectoryFilter& aFilter);
+ void ForgetThisSiteOnGMPThread(
+ const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern);
+ void ForgetThisBaseDomainOnGMPThread(const nsACString& aBaseDomain);
+ 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(nsISerialEventTarget* GMPThread) override;
+ RefPtr<GenericPromise> LoadFromEnvironment();
+ RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory);
+
+ RefPtr<GetGMPContentParentPromise> GetContentParent(
+ GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant,
+ const nsACString& aAPI, const nsTArray<nsCString>& aTags) 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();
+
+ // Get a string based node ID from a NodeIdVariant. This will
+ // either fetch the internal string, or convert the internal NodeIdParts to a
+ // string. The conversion process is fallible, so the return value should be
+ // checked.
+ nsresult GetNodeId(const NodeIdVariant& aNodeIdVariant, nsACString& aOutId);
+
+ class PathRunnable : public Runnable {
+ public:
+ enum EOperation {
+ REMOVE,
+ REMOVE_AND_DELETE_FROM_DISK,
+ };
+
+ PathRunnable(GeckoMediaPluginServiceParent* aService,
+ const nsAString& aPath, EOperation aOperation,
+ bool aDefer = false)
+ : Runnable("gmp::GeckoMediaPluginServiceParent::PathRunnable"),
+ 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;
+
+ // 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> mShuttingDown;
+ 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.
+ nsTHashMap<nsCStringHashKey, bool> mPersistentStorageAllowed;
+
+ // Synchronization for barrier that ensures we've loaded GMPs from
+ // MOZ_GMP_PATH before allowing GetContentParentFrom() to proceed.
+ Monitor mInitPromiseMonitor MOZ_UNANNOTATED;
+ MozMonitoredPromiseHolder<GenericPromise> mInitPromise;
+ bool mLoadPluginsFromDiskComplete;
+
+ // Hashes nodeId to the hashtable of storage for that nodeId.
+ nsRefPtrHashtable<nsCStringHashKey, GMPStorage> mTempGMPStorage;
+
+ // Tracks how many IPC connections to GMPServices running in content
+ // processes we have. When this is empty we can safely shut down.
+ // Synchronized across thread via mMutex in base class.
+ nsTArray<GMPServiceParent*> mServiceParents;
+
+ uint32_t mDirectoriesAdded = 0;
+ uint32_t mDirectoriesInProgress = 0;
+};
+
+nsresult WriteToFile(nsIFile* aPath, const nsACString& aFileName,
+ const nsACString& aData);
+nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData);
+bool MatchOrigin(nsIFile* aPath, const nsACString& aSite,
+ const mozilla::OriginAttributesPattern& aPattern);
+bool MatchBaseDomain(nsIFile* aPath, const nsACString& aBaseDomain);
+
+class GMPServiceParent final : public PGMPServiceParent {
+ public:
+ explicit GMPServiceParent(GeckoMediaPluginServiceParent* aService);
+
+ // Our refcounting is thread safe, and when our refcount drops to zero
+ // we dispatch an event to the main thread to delete the GMPServiceParent.
+ // Note that this means it's safe for references to this object to be
+ // released on a non main thread, but the destructor will always run on
+ // the main thread.
+
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPServiceParent.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
+ GMPServiceParent, final);
+
+ ipc::IPCResult RecvGetGMPNodeId(const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ nsCString* aID) override;
+
+ static bool Create(Endpoint<PGMPServiceParent>&& aGMPService);
+
+ ipc::IPCResult RecvLaunchGMP(
+ const NodeIdVariant& aNodeIdVariant, const nsACString& aAPI,
+ nsTArray<nsCString>&& aTags, nsTArray<ProcessId>&& aAlreadyBridgedTo,
+ uint32_t* aOutPluginId, GMPPluginType* aOutPluginType,
+ ProcessId* aOutProcessId, nsCString* aOutDisplayName,
+ Endpoint<PGMPContentParent>* aOutEndpoint, nsresult* aOutRv,
+ nsCString* aOutErrorDescription) override;
+
+ private:
+ ~GMPServiceParent();
+
+ 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 0000000000..5da04247cb
--- /dev/null
+++ b/dom/media/gmp/GMPSharedMemManager.cpp
@@ -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/. */
+
+#include "GMPSharedMemManager.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla::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* 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, 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>();
+
+ // 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(std::move(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());
+ 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 mozilla::gmp
diff --git a/dom/media/gmp/GMPSharedMemManager.h b/dom/media/gmp/GMPSharedMemManager.h
new file mode 100644
index 0000000000..1a04bdde14
--- /dev/null
+++ b/dom/media/gmp/GMPSharedMemManager.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::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 = 40;
+
+ GMPSharedMem() {
+ for (size_t i = 0; i < sizeof(mGmpAllocated) / sizeof(mGmpAllocated[0]);
+ i++) {
+ mGmpAllocated[i] = 0;
+ }
+ }
+ virtual ~GMPSharedMem() = default;
+
+ // 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() = default;
+
+ virtual bool MgrAllocShmem(GMPSharedMem::GMPMemoryClasses aClass,
+ size_t aSize, 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* aMem) = 0;
+ virtual void Dealloc(ipc::Shmem&& aMem) = 0;
+
+ private:
+ nsTArray<ipc::Shmem>& GetGmpFreelist(GMPSharedMem::GMPMemoryClasses aTypes) {
+ return mData->mGmpFreelist[aTypes];
+ }
+
+ GMPSharedMem* mData;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPSharedMemManager_h_
diff --git a/dom/media/gmp/GMPStorage.h b/dom/media/gmp/GMPStorage.h
new file mode 100644
index 0000000000..aca8ca3f26
--- /dev/null
+++ b/dom/media/gmp/GMPStorage.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::gmp {
+
+class GMPStorage {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPStorage)
+
+ virtual GMPErr Open(const nsACString& aRecordName) = 0;
+ virtual bool IsOpen(const nsACString& aRecordName) const = 0;
+ virtual GMPErr Read(const nsACString& aRecordName,
+ nsTArray<uint8_t>& aOutBytes) = 0;
+ virtual GMPErr Write(const nsACString& aRecordName,
+ const nsTArray<uint8_t>& aBytes) = 0;
+ virtual void Close(const nsACString& aRecordName) = 0;
+
+ protected:
+ virtual ~GMPStorage() = default;
+};
+
+already_AddRefed<GMPStorage> CreateGMPMemoryStorage();
+already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsACString& aNodeId,
+ const nsAString& aGMPName);
+
+} // namespace mozilla::gmp
+
+#endif
diff --git a/dom/media/gmp/GMPStorageChild.cpp b/dom/media/gmp/GMPStorageChild.cpp
new file mode 100644
index 0000000000..b08356e0d6
--- /dev/null
+++ b/dom/media/gmp/GMPStorageChild.cpp
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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)
+
+namespace mozilla::gmp {
+
+static nsTArray<uint8_t> ToArray(const uint8_t* aData, uint32_t aDataSize) {
+ nsTArray<uint8_t> data;
+ data.AppendElements(aData, aDataSize);
+ return data;
+}
+
+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.InsertOrUpdate(aRecordName, RefPtr{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;
+}
+
+mozilla::ipc::IPCResult 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 IPC_OK();
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return IPC_OK();
+ }
+ record->OpenComplete(aStatus);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPStorageChild::RecvReadComplete(
+ const nsCString& aRecordName, const GMPErr& aStatus,
+ nsTArray<uint8_t>&& aBytes) {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return IPC_OK();
+ }
+ record->ReadComplete(aStatus, aBytes.Elements(), aBytes.Length());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPStorageChild::RecvWriteComplete(
+ const nsCString& aRecordName, const GMPErr& aStatus) {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ RefPtr<GMPRecordImpl> record = GetRecord(aRecordName);
+ if (!record) {
+ // Not fatal.
+ return IPC_OK();
+ }
+ record->WriteComplete(aStatus);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult 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;
+ return IPC_OK();
+}
+
+} // namespace mozilla::gmp
+
+// 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 0000000000..97c61a5007
--- /dev/null
+++ b/dom/media/gmp/GMPStorageChild.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::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() = default;
+ const nsCString mName;
+ GMPRecordClient* const mClient;
+ GMPStorageChild* const mOwner;
+};
+
+class GMPStorageChild : public PGMPStorageChild {
+ friend class 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);
+
+ private:
+ bool HasRecord(const nsCString& aRecordName);
+ already_AddRefed<GMPRecordImpl> GetRecord(const nsCString& aRecordName);
+
+ protected:
+ ~GMPStorageChild() = default;
+
+ // PGMPStorageChild
+ mozilla::ipc::IPCResult RecvOpenComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus);
+ mozilla::ipc::IPCResult RecvReadComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus,
+ nsTArray<uint8_t>&& aBytes);
+ mozilla::ipc::IPCResult RecvWriteComplete(const nsCString& aRecordName,
+ const GMPErr& aStatus);
+ mozilla::ipc::IPCResult RecvShutdown();
+
+ private:
+ Monitor mMonitor MOZ_UNANNOTATED;
+ nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
+ GMPChild* mPlugin;
+ bool mShutdown;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPStorageChild_h_
diff --git a/dom/media/gmp/GMPStorageParent.cpp b/dom/media/gmp/GMPStorageParent.cpp
new file mode 100644
index 0000000000..d153f5cace
--- /dev/null
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+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 nsACString& 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;
+}
+
+mozilla::ipc::IPCResult GMPStorageParent::RecvOpen(
+ const nsACString& aRecordName) {
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')", this,
+ PromiseFlatCString(aRecordName).get()));
+
+ if (mShutdown) {
+ // Shutdown is an expected state, so we do not IPC_FAIL.
+ return IPC_OK();
+ }
+
+ 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, PromiseFlatCString(aRecordName).get()));
+ Unused << SendOpenComplete(aRecordName, GMPGenericErr);
+ return IPC_OK();
+ }
+
+ if (aRecordName.IsEmpty()) {
+ LOGD((
+ "GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty",
+ this, PromiseFlatCString(aRecordName).get()));
+ Unused << SendOpenComplete(aRecordName, GMPGenericErr);
+ return IPC_OK();
+ }
+
+ if (mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use",
+ this, PromiseFlatCString(aRecordName).get()));
+ Unused << SendOpenComplete(aRecordName, GMPRecordInUse);
+ return IPC_OK();
+ }
+
+ auto err = mStorage->Open(aRecordName);
+ MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName));
+ LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d", this,
+ PromiseFlatCString(aRecordName).get(), err));
+ Unused << SendOpenComplete(aRecordName, err);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPStorageParent::RecvRead(
+ const nsACString& aRecordName) {
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s')", this,
+ PromiseFlatCString(aRecordName).get()));
+
+ if (mShutdown) {
+ // Shutdown is an expected state, so we do not IPC_FAIL.
+ return IPC_OK();
+ }
+
+ nsTArray<uint8_t> data;
+ if (!mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') failed; record not open",
+ this, PromiseFlatCString(aRecordName).get()));
+ Unused << SendReadComplete(aRecordName, GMPClosedErr, data);
+ } else {
+ GMPErr rv = mStorage->Read(aRecordName, data);
+ LOGD(
+ ("GMPStorageParent[%p]::RecvRead(record='%s') read %zu bytes "
+ "rv=%" PRIu32,
+ this, PromiseFlatCString(aRecordName).get(), data.Length(),
+ static_cast<uint32_t>(rv)));
+ Unused << SendReadComplete(aRecordName, rv, data);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPStorageParent::RecvWrite(
+ const nsACString& aRecordName, nsTArray<uint8_t>&& aBytes) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') %zu bytes", this,
+ PromiseFlatCString(aRecordName).get(), aBytes.Length()));
+
+ if (mShutdown) {
+ // Shutdown is an expected state, so we do not IPC_FAIL.
+ return IPC_OK();
+ }
+
+ if (!mStorage->IsOpen(aRecordName)) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record not open",
+ this, PromiseFlatCString(aRecordName).get()));
+ Unused << SendWriteComplete(aRecordName, GMPClosedErr);
+ return IPC_OK();
+ }
+
+ if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record too big",
+ this, PromiseFlatCString(aRecordName).get()));
+ Unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
+ return IPC_OK();
+ }
+
+ GMPErr rv = mStorage->Write(aRecordName, aBytes);
+ LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') write complete rv=%d",
+ this, PromiseFlatCString(aRecordName).get(), rv));
+
+ Unused << SendWriteComplete(aRecordName, rv);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPStorageParent::RecvClose(
+ const nsACString& aRecordName) {
+ LOGD(("GMPStorageParent[%p]::RecvClose(record='%s')", this,
+ PromiseFlatCString(aRecordName).get()));
+
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ mStorage->Close(aRecordName);
+
+ return IPC_OK();
+}
+
+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 0000000000..b0cbba14c1
--- /dev/null
+++ b/dom/media/gmp/GMPStorageParent.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::gmp {
+
+class GMPParent;
+
+class GMPStorageParent : public PGMPStorageParent {
+ friend class PGMPStorageParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
+ GMPStorageParent(const nsACString& aNodeId, GMPParent* aPlugin);
+
+ nsresult Init();
+ void Shutdown();
+
+ protected:
+ mozilla::ipc::IPCResult RecvOpen(const nsACString& aRecordName) override;
+ mozilla::ipc::IPCResult RecvRead(const nsACString& aRecordName) override;
+ mozilla::ipc::IPCResult RecvWrite(const nsACString& aRecordName,
+ nsTArray<uint8_t>&& aBytes) override;
+ mozilla::ipc::IPCResult RecvClose(const nsACString& aRecordName) override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ ~GMPStorageParent() = default;
+
+ RefPtr<GMPStorage> mStorage;
+
+ const nsCString mNodeId;
+ RefPtr<GMPParent> mPlugin;
+ // True after Shutdown(), or if Init() has not completed successfully.
+ bool mShutdown;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPStorageParent_h_
diff --git a/dom/media/gmp/GMPTimerChild.cpp b/dom/media/gmp/GMPTimerChild.cpp
new file mode 100644
index 0000000000..8858f5f87f
--- /dev/null
+++ b/dom/media/gmp/GMPTimerChild.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPTimerChild.h"
+#include "GMPPlatform.h"
+#include "GMPChild.h"
+
+#define MAX_NUM_TIMERS 1000
+
+namespace mozilla::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.InsertOrUpdate(timerId, aTask);
+ mTimerCount++;
+
+ if (!SendSetTimer(timerId, aTimeoutMS)) {
+ return GMPGenericErr;
+ }
+ return GMPNoErr;
+}
+
+mozilla::ipc::IPCResult 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 IPC_OK();
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPTimerChild.h b/dom/media/gmp/GMPTimerChild.h
new file mode 100644
index 0000000000..f81f796e12
--- /dev/null
+++ b/dom/media/gmp/GMPTimerChild.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+
+namespace mozilla::gmp {
+
+class GMPChild;
+
+class GMPTimerChild : public PGMPTimerChild {
+ friend class PGMPTimerChild;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(GMPTimerChild)
+
+ explicit GMPTimerChild(GMPChild* aPlugin);
+
+ GMPErr SetTimer(GMPTask* aTask, int64_t aTimeoutMS);
+
+ protected:
+ // GMPTimerChild
+ mozilla::ipc::IPCResult RecvTimerExpired(const uint32_t& aTimerId);
+
+ private:
+ ~GMPTimerChild();
+
+ nsTHashMap<nsUint32HashKey, GMPTask*> mTimers;
+ uint32_t mTimerCount;
+
+ GMPChild* mPlugin;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPTimerChild_h_
diff --git a/dom/media/gmp/GMPTimerParent.cpp b/dom/media/gmp/GMPTimerParent.cpp
new file mode 100644
index 0000000000..31b17ef214
--- /dev/null
+++ b/dom/media/gmp/GMPTimerParent.cpp
@@ -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/. */
+
+#include "GMPTimerParent.h"
+
+#include "GMPLog.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+
+#ifdef __CLASS__
+# undef __CLASS__
+#endif
+#define __CLASS__ "GMPTimerParent"
+
+namespace gmp {
+
+GMPTimerParent::GMPTimerParent(nsISerialEventTarget* aGMPEventTarget)
+ : mGMPEventTarget(aGMPEventTarget), mIsOpen(true) {}
+
+mozilla::ipc::IPCResult GMPTimerParent::RecvSetTimer(
+ const uint32_t& aTimerId, const uint32_t& aTimeoutMs) {
+ GMP_LOG_DEBUG("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this,
+ mIsOpen);
+
+ MOZ_ASSERT(mGMPEventTarget->IsOnCurrentThread());
+
+ if (!mIsOpen) {
+ return IPC_OK();
+ }
+
+ nsresult rv;
+ UniquePtr<Context> ctx(new Context());
+
+ rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(ctx->mTimer), &GMPTimerParent::GMPTimerExpired, ctx.get(),
+ aTimeoutMs, nsITimer::TYPE_ONE_SHOT, "gmp::GMPTimerParent::RecvSetTimer",
+ mGMPEventTarget);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+
+ ctx->mId = aTimerId;
+ ctx->mParent = this;
+
+ mTimers.Insert(ctx.release());
+
+ return IPC_OK();
+}
+
+void GMPTimerParent::Shutdown() {
+ GMP_LOG_DEBUG("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this,
+ mIsOpen);
+
+ MOZ_ASSERT(mGMPEventTarget->IsOnCurrentThread());
+
+ for (Context* context : mTimers) {
+ context->mTimer->Cancel();
+ delete context;
+ }
+
+ mTimers.Clear();
+ mIsOpen = false;
+}
+
+void GMPTimerParent::ActorDestroy(ActorDestroyReason aWhy) {
+ GMP_LOG_DEBUG("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this,
+ mIsOpen);
+
+ Shutdown();
+}
+
+/* static */
+void GMPTimerParent::GMPTimerExpired(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(aClosure);
+ UniquePtr<Context> ctx(static_cast<Context*>(aClosure));
+ MOZ_ASSERT(ctx->mParent);
+ if (ctx->mParent) {
+ ctx->mParent->TimerExpired(ctx.get());
+ }
+}
+
+void GMPTimerParent::TimerExpired(Context* aContext) {
+ GMP_LOG_DEBUG("%s::%s: %p mIsOpen=%d", __CLASS__, __FUNCTION__, this,
+ mIsOpen);
+ MOZ_ASSERT(mGMPEventTarget->IsOnCurrentThread());
+
+ if (!mIsOpen) {
+ return;
+ }
+
+ uint32_t id = aContext->mId;
+ mTimers.Remove(aContext);
+ if (id) {
+ Unused << SendTimerExpired(id);
+ }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPTimerParent.h b/dom/media/gmp/GMPTimerParent.h
new file mode 100644
index 0000000000..dae1d9a1fb
--- /dev/null
+++ b/dom/media/gmp/GMPTimerParent.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 GMPTimerParent_h_
+#define GMPTimerParent_h_
+
+#include "mozilla/gmp/PGMPTimerParent.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsTHashSet.h"
+#include "mozilla/Monitor.h"
+
+namespace mozilla::gmp {
+
+class GMPTimerParent : public PGMPTimerParent {
+ friend class PGMPTimerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(GMPTimerParent)
+ explicit GMPTimerParent(nsISerialEventTarget* aGMPEventTarget);
+
+ void Shutdown();
+
+ protected:
+ mozilla::ipc::IPCResult RecvSetTimer(const uint32_t& aTimerId,
+ const uint32_t& aTimeoutMs);
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ ~GMPTimerParent() = default;
+
+ static void GMPTimerExpired(nsITimer* aTimer, void* aClosure);
+
+ struct Context {
+ Context() : mId(0) { MOZ_COUNT_CTOR(Context); }
+ MOZ_COUNTED_DTOR(Context)
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<GMPTimerParent>
+ mParent; // Note: live timers keep the GMPTimerParent alive.
+ uint32_t mId;
+ };
+
+ void TimerExpired(Context* aContext);
+
+ nsTHashSet<Context*> mTimers;
+
+ nsCOMPtr<nsISerialEventTarget> mGMPEventTarget;
+
+ bool mIsOpen;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPTimerParent_h_
diff --git a/dom/media/gmp/GMPTypes.ipdlh b/dom/media/gmp/GMPTypes.ipdlh
new file mode 100644
index 0000000000..6cb37b6068
--- /dev/null
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include "GMPMessageUtils.h";
+
+using cdm::EncryptionScheme from "GMPSanitizedExports.h";
+using GMPBufferType from "gmp-video-codec.h";
+
+namespace mozilla {
+namespace gmp {
+
+// GMP processes are associated with a specific node ID, so all GMP requests
+// which have the same node ID will use the same GMP process. Depending on the
+// use case, the node ID may be represented by a string (such as when used for
+// WebRTC) or by this structure, which is populated according to the origin
+// initiating the request. This structure will eventually be converted to a
+// string representing a node ID. For this structure, the process ensures the
+// strings are unique for the combination of origin, top level origin and GMP
+// name.
+struct NodeIdParts {
+ nsString mOrigin;
+ nsString mTopLevelOrigin;
+ nsString mGMPName;
+};
+
+// A NodeIdVariant should contain either
+// - A string representing an already computed node ID.
+// - A NodeIdParts representing a node ID that still needs to be computed by
+// processing those parts.
+// This union is used to simplify passing of node ID information. Some
+// GMP use cases can hard code their node ID, while others need to compute
+// the node ID later. This lets us avoid having overloads to handle
+// the two different paths.
+union NodeIdVariant {
+ nsCString;
+ NodeIdParts;
+};
+
+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;
+};
+
+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? mUpdatedTimestamp; // microseconds
+ uint64_t mDuration; // microseconds
+};
+
+struct CDMInputBuffer {
+ Shmem mData;
+ uint8_t[] mKeyId;
+ uint8_t[] mIV;
+ int64_t mTimestamp;
+ int64_t mDuration;
+ uint32_t[] mClearBytes;
+ uint32_t[] mCipherBytes;
+ uint8_t mCryptByteBlock;
+ uint8_t mSkipByteBlock;
+ EncryptionScheme mEncryptionScheme;
+};
+
+struct CDMVideoDecoderConfig {
+ uint32_t mCodec;
+ uint32_t mProfile;
+ uint32_t mFormat;
+ int32_t mImageWidth;
+ int32_t mImageHeight;
+ uint8_t[] mExtraData;
+ EncryptionScheme mEncryptionScheme;
+};
+
+struct CDMKeyInformation {
+ uint8_t[] mKeyId;
+ uint32_t mStatus;
+ uint32_t mSystemCode;
+};
+
+struct CDMVideoPlane {
+ uint32_t mPlaneOffset;
+ uint32_t mStride;
+};
+
+struct CDMVideoFrame {
+ uint32_t mFormat;
+ int32_t mImageWidth;
+ int32_t mImageHeight;
+ CDMVideoPlane mYPlane;
+ CDMVideoPlane mUPlane;
+ CDMVideoPlane mVPlane;
+ int64_t mTimestamp;
+ int64_t mDuration;
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPUtils.cpp b/dom/media/gmp/GMPUtils.cpp
new file mode 100644
index 0000000000..1bc02d7027
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPUtils.h"
+
+#include "GMPService.h"
+#include "VideoLimits.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Base64.h"
+#include "nsCOMPtr.h"
+#include "nsCRTGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIConsoleService.h"
+#include "nsIFile.h"
+#include "nsLiteralString.h"
+#include "prio.h"
+
+namespace mozilla {
+
+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 ToHexString(const uint8_t* aBytes, uint32_t aLength) {
+ static const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ nsCString str;
+ for (uint32_t i = 0; i < aLength; i++) {
+ char buf[3];
+ buf[0] = hex[(aBytes[i] & 0xf0) >> 4];
+ buf[1] = hex[aBytes[i] & 0x0f];
+ buf[2] = 0;
+ str.AppendASCII(buf);
+ }
+ return str;
+}
+
+nsCString ToHexString(const nsTArray<uint8_t>& aBytes) {
+ return ToHexString(aBytes.Elements(), aBytes.Length());
+}
+
+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_WARN_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(" ");
+
+ auto value = MakeUnique<nsCString>(Substring(line, colon + 1));
+ value->Trim(" ");
+ mValues.InsertOrUpdate(
+ key,
+ std::move(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 ""_ns;
+}
+
+bool HaveGMPFor(const nsACString& aAPI, const 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;
+}
+
+void LogToConsole(const nsAString& aMsg) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+ nsAutoString msg(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+already_AddRefed<nsISerialEventTarget> GetGMPThread() {
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ nsCOMPtr<nsISerialEventTarget> thread =
+ service ? service->GetGMPThread() : nullptr;
+ return thread.forget();
+}
+
+static size_t Align16(size_t aNumber) {
+ const size_t mask = 15; // Alignment - 1.
+ return (aNumber + mask) & ~mask;
+}
+
+size_t I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight) {
+ if (aWidth <= 0 || aHeight <= 0 || aWidth > MAX_VIDEO_WIDTH ||
+ aHeight > MAX_VIDEO_HEIGHT) {
+ return 0;
+ }
+
+ size_t ySize = Align16(aWidth) * Align16(aHeight);
+ return ySize + (ySize / 4) * 2;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/GMPUtils.h b/dom/media/gmp/GMPUtils.h
new file mode 100644
index 0000000000..0ac097420c
--- /dev/null
+++ b/dom/media/gmp/GMPUtils.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/AbstractThread.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+#define CHROMIUM_CDM_API_BACKWARD_COMPAT "chromium-cdm9-host4"
+#define CHROMIUM_CDM_API "chromium-cdm10-host4"
+
+class nsIFile;
+class nsIDirectoryEnumerator;
+
+namespace mozilla {
+
+template <typename T>
+struct DestroyPolicy {
+ void operator()(T* aGMPObject) const { aGMPObject->Destroy(); }
+};
+
+template <typename T>
+using GMPUniquePtr = mozilla::UniquePtr<T, DestroyPolicy<T>>;
+
+void SplitAt(const char* aDelims, const nsACString& aInput,
+ nsTArray<nsCString>& aOutTokens);
+
+nsCString ToHexString(const nsTArray<uint8_t>& aBytes);
+
+nsCString ToHexString(const uint8_t* aBytes, uint32_t aLength);
+
+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<nsIDirectoryEnumerator> 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 ReadIntoString(nsIFile* aFile, nsCString& aOutDst, size_t aMaxLength);
+
+bool HaveGMPFor(const nsACString& aAPI, const nsTArray<nsCString>& aTags);
+
+void LogToConsole(const nsAString& aMsg);
+
+already_AddRefed<nsISerialEventTarget> GetGMPThread();
+
+// Returns the number of bytes required to store an aWidth x aHeight image in
+// I420 format, padded so that the width and height are multiples of 16.
+size_t I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight);
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/GMPVideoDecoderChild.cpp b/dom/media/gmp/GMPVideoDecoderChild.cpp
new file mode 100644
index 0000000000..0f605cca9b
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderChild.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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::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);
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvInitDecode(
+ const GMPVideoCodec& aCodecSettings, nsTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aCoreCount) {
+ if (!mVideoDecoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // 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 IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDecode(
+ const GMPVideoEncodedFrameData& aInputFrame, const bool& aMissingFrames,
+ nsTArray<uint8_t>&& aCodecSpecificInfo, const int64_t& aRenderTimeMs) {
+ if (!mVideoDecoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ 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 IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvChildShmemForPool(
+ Shmem&& aFrameBuffer) {
+ if (aFrameBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBuffer);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvReset() {
+ if (!mVideoDecoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the
+ // process.
+ mVideoDecoder->Reset();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderChild::RecvDrain() {
+ if (!mVideoDecoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the
+ // process.
+ mVideoDecoder->Drain();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult 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 IPC_OK();
+ }
+ 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 IPC_OK();
+}
+
+bool GMPVideoDecoderChild::Alloc(size_t aSize, Shmem* aMem) {
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ bool rv;
+#ifndef SHMEM_ALLOC_IN_CHILD
+ ++mNeedShmemIntrCount;
+ rv = SendNeedShmem(aSize, aMem);
+ --mNeedShmemIntrCount;
+ if (mPendingDecodeComplete && mNeedShmemIntrCount == 0) {
+ mPendingDecodeComplete = false;
+ mPlugin->GMPMessageLoop()->PostTask(
+ NewRunnableMethod("gmp::GMPVideoDecoderChild::RecvDecodingComplete",
+ this, &GMPVideoDecoderChild::RecvDecodingComplete));
+ }
+#else
+ rv = AllocShmem(aSize, aType, aMem);
+#endif
+ return rv;
+}
+
+void GMPVideoDecoderChild::Dealloc(Shmem&& aMem) {
+#ifndef SHMEM_ALLOC_IN_CHILD
+ SendParentShmemForPool(std::move(aMem));
+#else
+ DeallocShmem(aMem);
+#endif
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoDecoderChild.h b/dom/media/gmp/GMPVideoDecoderChild.h
new file mode 100644
index 0000000000..3c74e5f02c
--- /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::gmp {
+
+class GMPContentChild;
+
+class GMPVideoDecoderChild : public PGMPVideoDecoderChild,
+ public GMPVideoDecoderCallback,
+ public GMPSharedMemManager {
+ friend class PGMPVideoDecoderChild;
+
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPVideoDecoderChild.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoDecoderChild, final);
+
+ 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* aMem) override;
+ void Dealloc(Shmem&& aMem) override;
+
+ private:
+ virtual ~GMPVideoDecoderChild();
+
+ // PGMPVideoDecoderChild
+ mozilla::ipc::IPCResult RecvInitDecode(const GMPVideoCodec& aCodecSettings,
+ nsTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aCoreCount);
+ mozilla::ipc::IPCResult RecvDecode(
+ const GMPVideoEncodedFrameData& aInputFrame, const bool& aMissingFrames,
+ nsTArray<uint8_t>&& aCodecSpecificInfo, const int64_t& aRenderTimeMs);
+ mozilla::ipc::IPCResult RecvChildShmemForPool(Shmem&& aFrameBuffer);
+ mozilla::ipc::IPCResult RecvReset();
+ mozilla::ipc::IPCResult RecvDrain();
+ mozilla::ipc::IPCResult RecvDecodingComplete();
+
+ 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 mozilla::gmp
+
+#endif // GMPVideoDecoderChild_h_
diff --git a/dom/media/gmp/GMPVideoDecoderParent.cpp b/dom/media/gmp/GMPVideoDecoderParent.cpp
new file mode 100644
index 0000000000..dfe7182c41
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "GMPContentParent.h"
+#include "GMPUtils.h"
+#include "GMPLog.h"
+#include "GMPMessageUtils.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/Unused.h"
+#include "nsAutoRef.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::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()),
+ mPluginType(aPlugin->GetPluginType()),
+ mFrameCount(0) {
+ MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderParent::~GMPVideoDecoderParent() = default;
+
+GMPVideoHostImpl& GMPVideoDecoderParent::Host() { return mVideoHost; }
+
+// Note: may be called via Terminated()
+void GMPVideoDecoderParent::Close() {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::Close()", this);
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPEventTarget()->IsOnCurrentThread());
+
+ // 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) {
+ GMP_LOG_DEBUG("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->GMPEventTarget()->IsOnCurrentThread());
+
+ 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) {
+ GMP_LOG_VERBOSE(
+ "GMPVideoDecoderParent[%p]::Decode() timestamp=%" PRId64 " keyframe=%d",
+ this, aInputFrame->TimeStamp(), aInputFrame->FrameType() == kGMPKeyFrame);
+
+ if (!mIsOpen) {
+ GMP_LOG_ERROR(
+ "GMPVideoDecoderParent[%p]::Decode() ERROR; dead GMPVideoDecoder",
+ this);
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
+
+ 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)) {
+ GMP_LOG_ERROR(
+ "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)) {
+ GMP_LOG_ERROR(
+ "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() {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::Reset()", this);
+
+ if (!mIsOpen) {
+ NS_WARNING("Trying to use an dead GMP video decoder");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
+
+ if (!SendReset()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingResetComplete = true;
+
+ RefPtr<GMPVideoDecoderParent> self(this);
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "gmp::GMPVideoDecoderParent::Reset", [self]() -> void {
+ GMP_LOG_DEBUG(
+ "GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out "
+ "waiting for ResetComplete",
+ self.get());
+ self->mResetCompleteTimeout = nullptr;
+ LogToBrowserConsole(nsLiteralString(
+ u"GMPVideoDecoderParent timed out waiting for ResetComplete()"));
+ });
+ CancelResetCompleteTimeout();
+ nsCOMPtr<nsISerialEventTarget> target = mPlugin->GMPEventTarget();
+ mResetCompleteTimeout = SimpleTimer::Create(task, 5000, target);
+
+ // 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() {
+ GMP_LOG_DEBUG("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->GMPEventTarget()->IsOnCurrentThread());
+
+ if (!SendDrain()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsAwaitingDrainComplete = true;
+
+ // Async IPC, we don't have access to a return value.
+ return NS_OK;
+}
+
+nsCString GMPVideoDecoderParent::GetDisplayName() const {
+ if (NS_WARN_IF(!mIsOpen)) {
+ return ""_ns;
+ }
+
+ MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
+ return mPlugin->GetDisplayName();
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+nsresult GMPVideoDecoderParent::Shutdown() {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::Shutdown()", this);
+ MOZ_ASSERT(!mPlugin || mPlugin->GMPEventTarget()->IsOnCurrentThread());
+
+ 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) {
+ GMP_LOG_DEBUG("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);
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvDecoded(
+ const GMPVideoi420FrameData& aDecodedFrame) {
+ --mFrameCount;
+ if (aDecodedFrame.mUpdatedTimestamp() &&
+ aDecodedFrame.mUpdatedTimestamp().value() != aDecodedFrame.mTimestamp()) {
+ GMP_LOG_VERBOSE(
+ "GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=[%" PRId64
+ " -> %" PRId64 "] frameCount=%d",
+ this, aDecodedFrame.mTimestamp(),
+ aDecodedFrame.mUpdatedTimestamp().value(), mFrameCount);
+ } else {
+ GMP_LOG_VERBOSE(
+ "GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%" PRId64
+ " frameCount=%d",
+ this, aDecodedFrame.mTimestamp(), mFrameCount);
+ }
+
+ if (mCallback) {
+ if (GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) {
+ auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
+
+ mCallback->Decoded(f);
+ } else {
+ GMP_LOG_ERROR(
+ "GMPVideoDecoderParent[%p]::RecvDecoded() "
+ "timestamp=%" PRId64 " decoded frame corrupt, ignoring",
+ this, aDecodedFrame.mTimestamp());
+ // TODO: Verify if we should take more serious the arrival of
+ // a corrupted frame, see bug 1750506.
+ }
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(
+ const uint64_t& aPictureId) {
+ if (mCallback) {
+ mCallback->ReceivedDecodedReferenceFrame(aPictureId);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvReceivedDecodedFrame(
+ const uint64_t& aPictureId) {
+ if (mCallback) {
+ mCallback->ReceivedDecodedFrame(aPictureId);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvInputDataExhausted() {
+ GMP_LOG_VERBOSE("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this);
+
+ if (mCallback) {
+ mCallback->InputDataExhausted();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvDrainComplete() {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::RecvDrainComplete() frameCount=%d",
+ this, mFrameCount);
+ nsAutoString msg;
+ msg.AppendLiteral(
+ "GMPVideoDecoderParent::RecvDrainComplete() outstanding frames=");
+ msg.AppendInt(mFrameCount);
+ LogToBrowserConsole(msg);
+
+ if (mCallback && mIsAwaitingDrainComplete) {
+ mIsAwaitingDrainComplete = false;
+
+ mCallback->DrainComplete();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvResetComplete() {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::RecvResetComplete()", this);
+
+ CancelResetCompleteTimeout();
+
+ if (mCallback && mIsAwaitingResetComplete) {
+ mIsAwaitingResetComplete = false;
+ mFrameCount = 0;
+
+ mCallback->ResetComplete();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvError(const GMPErr& aError) {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError);
+
+ if (mCallback) {
+ // 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();
+
+ mCallback->Error(aError);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvShutdown() {
+ GMP_LOG_DEBUG("GMPVideoDecoderParent[%p]::RecvShutdown()", this);
+
+ Shutdown();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvParentShmemForPool(
+ Shmem&& aEncodedBuffer) {
+ if (aEncodedBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBuffer);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::RecvNeedShmem(
+ const uint32_t& aFrameBufferSize, Shmem* aMem) {
+ ipc::Shmem mem;
+
+ if (!mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData,
+ aFrameBufferSize, &mem)) {
+ GMP_LOG_ERROR("%s: Failed to get a shared mem buffer for Child! size %u",
+ __FUNCTION__, aFrameBufferSize);
+ return IPC_FAIL(this, "Failed to get a shared mem buffer for Child!");
+ }
+ *aMem = mem;
+ mem = ipc::Shmem();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoDecoderParent::Recv__delete__() {
+ GMP_LOG_DEBUG("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 IPC_OK();
+}
+
+void GMPVideoDecoderParent::UnblockResetAndDrain() {
+ GMP_LOG_DEBUG(
+ "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 mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoDecoderParent.h b/dom/media/gmp/GMPVideoDecoderParent.h
new file mode 100644
index 0000000000..9265202a63
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderParent.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 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::gmp {
+
+class GMPContentParent;
+
+class GMPVideoDecoderParent final : public PGMPVideoDecoderParent,
+ public GMPVideoDecoderProxy,
+ public GMPSharedMemManager,
+ public GMPCrashHelperHolder {
+ friend class PGMPVideoDecoderParent;
+
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPVideoDecoderParent.
+ NS_INLINE_DECL_REFCOUNTING(GMPVideoDecoderParent, final)
+
+ 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; }
+ GMPPluginType GetPluginType() const override { return mPluginType; }
+ nsCString GetDisplayName() const override;
+
+ // GMPSharedMemManager
+ bool Alloc(size_t aSize, Shmem* aMem) override {
+ return AllocShmem(aSize, aMem);
+ }
+ void Dealloc(Shmem&& aMem) override { DeallocShmem(aMem); }
+
+ private:
+ ~GMPVideoDecoderParent();
+
+ // PGMPVideoDecoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ mozilla::ipc::IPCResult RecvDecoded(
+ const GMPVideoi420FrameData& aDecodedFrame) override;
+ mozilla::ipc::IPCResult RecvReceivedDecodedReferenceFrame(
+ const uint64_t& aPictureId) override;
+ mozilla::ipc::IPCResult RecvReceivedDecodedFrame(
+ const uint64_t& aPictureId) override;
+ mozilla::ipc::IPCResult RecvInputDataExhausted() override;
+ mozilla::ipc::IPCResult RecvDrainComplete() override;
+ mozilla::ipc::IPCResult RecvResetComplete() override;
+ mozilla::ipc::IPCResult RecvError(const GMPErr& aError) override;
+ mozilla::ipc::IPCResult RecvShutdown() override;
+ mozilla::ipc::IPCResult RecvParentShmemForPool(
+ Shmem&& aEncodedBuffer) override;
+ mozilla::ipc::IPCResult RecvNeedShmem(const uint32_t& aFrameBufferSize,
+ Shmem* aMem) override;
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ void UnblockResetAndDrain();
+ void CancelResetCompleteTimeout();
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ bool mIsAwaitingResetComplete;
+ bool mIsAwaitingDrainComplete;
+ RefPtr<GMPContentParent> mPlugin;
+ RefPtr<GMPVideoDecoderCallbackProxy> mCallback;
+ GMPVideoHostImpl mVideoHost;
+ const uint32_t mPluginId;
+ GMPPluginType mPluginType = GMPPluginType::Unknown;
+ int32_t mFrameCount;
+ RefPtr<SimpleTimer> mResetCompleteTimeout;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPVideoDecoderParent_h_
diff --git a/dom/media/gmp/GMPVideoDecoderProxy.h b/dom/media/gmp/GMPVideoDecoderProxy.h
new file mode 100644
index 0000000000..e12668755c
--- /dev/null
+++ b/dom/media/gmp/GMPVideoDecoderProxy.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 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 "GMPNativeTypes.h"
+#include "GMPUtils.h"
+
+class GMPVideoDecoderCallbackProxy : public GMPCallbackBase,
+ public GMPVideoDecoderCallback {
+ public:
+ virtual ~GMPVideoDecoderCallbackProxy() = default;
+};
+
+// 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;
+ virtual GMPPluginType GetPluginType() const = 0;
+
+ // Call to tell GMP/plugin the consumer will no longer use this
+ // interface/codec.
+ virtual void Close() = 0;
+
+ virtual nsCString GetDisplayName() const = 0;
+};
+
+#endif
diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp
new file mode 100644
index 0000000000..c0ed87df4d
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.cpp
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+namespace mozilla::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);
+ aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::~GMPVideoEncodedFrameImpl() {
+ DestroyBuffer();
+ if (mHost) {
+ mHost->EncodedFrameDestroyed(this);
+ }
+}
+
+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;
+
+ // 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, &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;
+ // 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, &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 mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoEncodedFrameImpl.h b/dom/media/gmp/GMPVideoEncodedFrameImpl.h
new file mode 100644
index 0000000000..fbb5f92146
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncodedFrameImpl.h
@@ -0,0 +1,116 @@
+/* -*- 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 "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+class CryptoSample;
+
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPVideoEncodedFrameData;
+
+class GMPVideoEncodedFrameImpl : public GMPVideoEncodedFrame {
+ friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoEncodedFrameImpl>;
+
+ public:
+ explicit GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost);
+ GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData,
+ GMPVideoHostImpl* aHost);
+ virtual ~GMPVideoEncodedFrameImpl();
+
+ // 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;
+
+ 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;
+};
+
+} // 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 0000000000..19a96b5efe
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderChild.cpp
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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::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);
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvInitEncode(
+ const GMPVideoCodec& aCodecSettings, nsTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aNumberOfCores, const uint32_t& aMaxPayloadSize) {
+ if (!mVideoEncoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // 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 IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvEncode(
+ const GMPVideoi420FrameData& aInputFrame,
+ nsTArray<uint8_t>&& aCodecSpecificInfo,
+ nsTArray<GMPVideoFrameType>&& aFrameTypes) {
+ if (!mVideoEncoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ 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 IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvChildShmemForPool(
+ Shmem&& aEncodedBuffer) {
+ if (aEncodedBuffer.IsWritable()) {
+ mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
+ aEncodedBuffer);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvSetChannelParameters(
+ const uint32_t& aPacketLoss, const uint32_t& aRTT) {
+ if (!mVideoEncoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the
+ // process.
+ mVideoEncoder->SetChannelParameters(aPacketLoss, aRTT);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvSetRates(
+ const uint32_t& aNewBitRate, const uint32_t& aFrameRate) {
+ if (!mVideoEncoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the
+ // process.
+ mVideoEncoder->SetRates(aNewBitRate, aFrameRate);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(
+ const bool& aEnable) {
+ if (!mVideoEncoder) {
+ return IPC_FAIL(this, "!mVideoDecoder");
+ }
+
+ // Ignore any return code. It is OK for this to fail without killing the
+ // process.
+ mVideoEncoder->SetPeriodicKeyFrames(aEnable);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult 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 IPC_OK();
+ }
+
+ if (!mVideoEncoder) {
+ // There is not much to clean up anymore.
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ // 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 IPC_OK();
+}
+
+bool GMPVideoEncoderChild::Alloc(size_t aSize, Shmem* aMem) {
+ MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+ bool rv;
+#ifndef SHMEM_ALLOC_IN_CHILD
+ ++mNeedShmemIntrCount;
+ rv = SendNeedShmem(aSize, aMem);
+ --mNeedShmemIntrCount;
+ if (mPendingEncodeComplete && mNeedShmemIntrCount == 0) {
+ mPendingEncodeComplete = false;
+ mPlugin->GMPMessageLoop()->PostTask(
+ NewRunnableMethod("gmp::GMPVideoEncoderChild::RecvEncodingComplete",
+ this, &GMPVideoEncoderChild::RecvEncodingComplete));
+ }
+#else
+ rv = AllocShmem(aSize, aMem);
+#endif
+ return rv;
+}
+
+void GMPVideoEncoderChild::Dealloc(Shmem&& aMem) {
+#ifndef SHMEM_ALLOC_IN_CHILD
+ SendParentShmemForPool(std::move(aMem));
+#else
+ DeallocShmem(aMem);
+#endif
+}
+
+} // namespace mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h
new file mode 100644
index 0000000000..dd3c0fdf37
--- /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::gmp {
+
+class GMPContentChild;
+
+class GMPVideoEncoderChild : public PGMPVideoEncoderChild,
+ public GMPVideoEncoderCallback,
+ public GMPSharedMemManager {
+ friend class PGMPVideoEncoderChild;
+
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPVideoEncoderChild.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoEncoderChild, final);
+
+ 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* aMem) override;
+ void Dealloc(Shmem&& aMem) override;
+
+ private:
+ virtual ~GMPVideoEncoderChild();
+
+ // PGMPVideoEncoderChild
+ mozilla::ipc::IPCResult RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+ nsTArray<uint8_t>&& aCodecSpecific,
+ const int32_t& aNumberOfCores,
+ const uint32_t& aMaxPayloadSize);
+ mozilla::ipc::IPCResult RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+ nsTArray<uint8_t>&& aCodecSpecificInfo,
+ nsTArray<GMPVideoFrameType>&& aFrameTypes);
+ mozilla::ipc::IPCResult RecvChildShmemForPool(Shmem&& aEncodedBuffer);
+ mozilla::ipc::IPCResult RecvSetChannelParameters(const uint32_t& aPacketLoss,
+ const uint32_t& aRTT);
+ mozilla::ipc::IPCResult RecvSetRates(const uint32_t& aNewBitRate,
+ const uint32_t& aFrameRate);
+ mozilla::ipc::IPCResult RecvSetPeriodicKeyFrames(const bool& aEnable);
+ mozilla::ipc::IPCResult RecvEncodingComplete();
+
+ 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 mozilla::gmp
+
+#endif // GMPVideoEncoderChild_h_
diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp
new file mode 100644
index 0000000000..21c86ff9ab
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "GMPContentParent.h"
+#include "GMPCrashHelper.h"
+#include "GMPLog.h"
+#include "GMPMessageUtils.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/Unused.h"
+#include "nsAutoRef.h"
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "runnable_utils.h"
+
+namespace mozilla::gmp {
+
+#ifdef __CLASS__
+# undef __CLASS__
+#endif
+#define __CLASS__ "GMPVideoEncoderParent"
+
+// 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);
+}
+
+GMPVideoHostImpl& GMPVideoEncoderParent::Host() { return mVideoHost; }
+
+// Note: may be called via Terminated()
+void GMPVideoEncoderParent::Close() {
+ GMP_LOG_DEBUG("%s::%s: %p", __CLASS__, __FUNCTION__, this);
+ MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
+ // 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) {
+ GMP_LOG_DEBUG("%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->GMPEventTarget()->IsOnCurrentThread());
+ MOZ_ASSERT(!mCallback);
+
+ 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->GMPEventTarget()->IsOnCurrentThread());
+
+ 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)) {
+ GMP_LOG_ERROR(
+ "%s::%s: Out of mem buffers. Frame Buffers:%lu Max:%lu, Encoded "
+ "Buffers: %lu Max: %lu",
+ __CLASS__, __FUNCTION__,
+ static_cast<unsigned long>(NumInUse(GMPSharedMem::kGMPFrameData)),
+ static_cast<unsigned long>(3 * GMPSharedMem::kGMPBufLimit),
+ static_cast<unsigned long>(NumInUse(GMPSharedMem::kGMPEncodedData)),
+ static_cast<unsigned long>(GMPSharedMem::kGMPBufLimit));
+ return GMPGenericErr;
+ }
+
+ GMPVideoi420FrameData frameData;
+ inputFrameImpl->InitFrameData(frameData);
+
+ if (!SendEncode(frameData, aCodecSpecificInfo, aFrameTypes)) {
+ GMP_LOG_ERROR("%s::%s: failed to send encode", __CLASS__, __FUNCTION__);
+ 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->GMPEventTarget()->IsOnCurrentThread());
+
+ 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->GMPEventTarget()->IsOnCurrentThread());
+
+ 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->GMPEventTarget()->IsOnCurrentThread());
+
+ 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() {
+ GMP_LOG_DEBUG("%s::%s: %p", __CLASS__, __FUNCTION__, this);
+ MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
+
+ 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();
+ }
+}
+
+// Note: Keep this sync'd up with Shutdown
+void GMPVideoEncoderParent::ActorDestroy(ActorDestroyReason aWhy) {
+ GMP_LOG_DEBUG("%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;
+ }
+ 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);
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvEncoded(
+ const GMPVideoEncodedFrameData& aEncodedFrame,
+ nsTArray<uint8_t>&& aCodecSpecificInfo) {
+ if (mCallback) {
+ auto f = new GMPVideoEncodedFrameImpl(aEncodedFrame, &mVideoHost);
+ mCallback->Encoded(f, aCodecSpecificInfo);
+ f->Destroy();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvError(const GMPErr& aError) {
+ if (mCallback) {
+ mCallback->Error(aError);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvShutdown() {
+ Shutdown();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult 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 {
+ GMP_LOG_DEBUG(
+ "%s::%s: %p Called in shutdown, ignoring and freeing directly",
+ __CLASS__, __FUNCTION__, this);
+ DeallocShmem(aFrameBuffer);
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvNeedShmem(
+ 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, &mem)) {
+ GMP_LOG_ERROR(
+ "%s::%s: Failed to get a shared mem buffer for Child! size %u",
+ __CLASS__, __FUNCTION__, aEncodedBufferSize);
+ return IPC_FAIL(this, "Failed to get a shared mem buffer for Child!");
+ }
+ *aMem = mem;
+ mem = ipc::Shmem();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult 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 IPC_OK();
+}
+
+} // namespace mozilla::gmp
+
+#undef __CLASS__
diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h
new file mode 100644
index 0000000000..c5c81194ec
--- /dev/null
+++ b/dom/media/gmp/GMPVideoEncoderParent.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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::gmp {
+
+class GMPContentParent;
+
+class GMPVideoEncoderParent : public GMPVideoEncoderProxy,
+ public PGMPVideoEncoderParent,
+ public GMPSharedMemManager,
+ public GMPCrashHelperHolder {
+ friend class PGMPVideoEncoderParent;
+
+ public:
+ // Mark AddRef and Release as `final`, as they overload pure virtual
+ // implementations in PGMPVideoEncoderParent.
+ NS_INLINE_DECL_REFCOUNTING(GMPVideoEncoderParent, final)
+
+ 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* aMem) override {
+ return AllocShmem(aSize, aMem);
+ }
+ void Dealloc(Shmem&& aMem) override { DeallocShmem(aMem); }
+
+ private:
+ virtual ~GMPVideoEncoderParent() = default;
+
+ // PGMPVideoEncoderParent
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ mozilla::ipc::IPCResult RecvEncoded(
+ const GMPVideoEncodedFrameData& aEncodedFrame,
+ nsTArray<uint8_t>&& aCodecSpecificInfo) override;
+ mozilla::ipc::IPCResult RecvError(const GMPErr& aError) override;
+ mozilla::ipc::IPCResult RecvShutdown() override;
+ mozilla::ipc::IPCResult RecvParentShmemForPool(Shmem&& aFrameBuffer) override;
+ mozilla::ipc::IPCResult RecvNeedShmem(const uint32_t& aEncodedBufferSize,
+ Shmem* aMem) override;
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ bool mIsOpen;
+ bool mShuttingDown;
+ bool mActorDestroyed;
+ RefPtr<GMPContentParent> mPlugin;
+ RefPtr<GMPVideoEncoderCallbackProxy> mCallback;
+ GMPVideoHostImpl mVideoHost;
+ const uint32_t mPluginId;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPVideoEncoderParent_h_
diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h
new file mode 100644
index 0000000000..ad638ebb3a
--- /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() = default;
+ 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 0000000000..7ed6c5abc2
--- /dev/null
+++ b/dom/media/gmp/GMPVideoHost.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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::gmp {
+
+GMPVideoHostImpl::GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr)
+ : mSharedMemMgr(aSharedMemMgr) {}
+
+GMPVideoHostImpl::~GMPVideoHostImpl() = default;
+
+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:
+ MOZ_ASSERT_UNREACHABLE("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 mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoHost.h b/dom/media/gmp/GMPVideoHost.h
new file mode 100644
index 0000000000..8b75ff28fe
--- /dev/null
+++ b/dom/media/gmp/GMPVideoHost.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsTArray.h"
+
+namespace mozilla::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 mozilla::gmp
+
+#endif // GMPVideoHost_h_
diff --git a/dom/media/gmp/GMPVideoPlaneImpl.cpp b/dom/media/gmp/GMPVideoPlaneImpl.cpp
new file mode 100644
index 0000000000..6c24b5b4a6
--- /dev/null
+++ b/dom/media/gmp/GMPVideoPlaneImpl.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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::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, &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 mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoPlaneImpl.h b/dom/media/gmp/GMPVideoPlaneImpl.h
new file mode 100644
index 0000000000..d0c1de1cd3
--- /dev/null
+++ b/dom/media/gmp/GMPVideoPlaneImpl.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 GMPVideoPlaneImpl_h_
+#define GMPVideoPlaneImpl_h_
+
+#include "gmp-video-plane.h"
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla::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 mozilla::gmp
+
+#endif // GMPVideoPlaneImpl_h_
diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.cpp b/dom/media/gmp/GMPVideoi420FrameImpl.cpp
new file mode 100644
index 0000000000..1ca3e8123f
--- /dev/null
+++ b/dom/media/gmp/GMPVideoi420FrameImpl.cpp
@@ -0,0 +1,328 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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::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()),
+ mUpdatedTimestamp(aFrameData.mUpdatedTimestamp()),
+ mDuration(aFrameData.mDuration()) {
+ MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::~GMPVideoi420FrameImpl() = default;
+
+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.mUpdatedTimestamp() = mUpdatedTimestamp;
+ 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;
+ mUpdatedTimestamp.reset();
+ 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;
+ mUpdatedTimestamp = f.mUpdatedTimestamp;
+ 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(mUpdatedTimestamp, f->mUpdatedTimestamp);
+ 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::SetUpdatedTimestamp(uint64_t aTimestamp) {
+ mUpdatedTimestamp = Some(aTimestamp);
+}
+
+uint64_t GMPVideoi420FrameImpl::UpdatedTimestamp() const {
+ return mUpdatedTimestamp ? *mUpdatedTimestamp : 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 mozilla::gmp
diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.h b/dom/media/gmp/GMPVideoi420FrameImpl.h
new file mode 100644
index 0000000000..53377beb8d
--- /dev/null
+++ b/dom/media/gmp/GMPVideoi420FrameImpl.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoi420FrameImpl_h_
+#define GMPVideoi420FrameImpl_h_
+
+#include "gmp-video-frame-i420.h"
+#include "mozilla/ipc/Shmem.h"
+#include "GMPVideoPlaneImpl.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla::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 SetUpdatedTimestamp(uint64_t aTimestamp) override;
+ uint64_t UpdatedTimestamp() 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;
+ Maybe<uint64_t> mUpdatedTimestamp;
+ uint64_t mDuration;
+};
+
+} // namespace mozilla::gmp
+
+#endif // GMPVideoi420FrameImpl_h_
diff --git a/dom/media/gmp/PChromiumCDM.ipdl b/dom/media/gmp/PChromiumCDM.ipdl
new file mode 100644
index 0000000000..2355839c17
--- /dev/null
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -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/. */
+
+include protocol PGMPContent;
+include GMPTypes;
+
+include "GMPMessageUtils.h";
+include "ChromiumCDMParent.h";
+
+using cdm::HdcpVersion from "GMPSanitizedExports.h";
+
+namespace mozilla {
+namespace gmp {
+
+[ChildImpl=virtual, ParentImpl="ChromiumCDMParent"]
+async protocol PChromiumCDM
+{
+ manager PGMPContent;
+child:
+
+ // cdm::ContentDecryptionModule_10
+ async Init(bool aAllowDistinctiveIdentifier,
+ bool aAllowPersistentState) returns (bool aSuccess);
+
+ async GetStatusForPolicy(uint32_t aPromiseId,
+ HdcpVersion aMinHdcpVersion);
+
+ async SetServerCertificate(uint32_t aPromiseId,
+ uint8_t[] aServerCert);
+
+ async CreateSessionAndGenerateRequest(uint32_t aPromiseId,
+ uint32_t aSessionType,
+ uint32_t aInitDataType,
+ uint8_t[] aInitData);
+
+ async LoadSession(uint32_t aPromiseId,
+ uint32_t aSessionType,
+ 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);
+
+ // This is called after the parent has verified the protection status and
+ // round tripped the call back to the child.
+ async CompleteQueryOutputProtectionStatus(bool aSuccess,
+ uint32_t aLinkMask,
+ uint32_t aProtectionMask);
+
+ async Decrypt(uint32_t aId, CDMInputBuffer aBuffer);
+
+ async InitializeVideoDecoder(CDMVideoDecoderConfig aConfig);
+
+ async DeinitializeVideoDecoder();
+
+ async ResetVideoDecoder();
+
+ async DecryptAndDecodeFrame(CDMInputBuffer aBuffer);
+
+ async Drain();
+
+ async Destroy();
+
+ async GiveBuffer(Shmem aShmem);
+
+ async PurgeShmems();
+
+
+parent:
+ async __delete__();
+
+ // cdm::Host_10
+ async OnResolvePromiseWithKeyStatus(uint32_t aPromiseId, uint32_t aKeyStatus);
+
+ async OnResolveNewSessionPromise(uint32_t aPromiseId, nsCString aSessionId);
+
+ async OnResolvePromise(uint32_t aPromiseId);
+
+ async OnRejectPromise(uint32_t aPromiseId,
+ uint32_t aException,
+ uint32_t aSystemCode,
+ nsCString aErrorMessage);
+
+ async OnSessionMessage(nsCString aSessionId,
+ uint32_t aMessageType,
+ uint8_t[] aMessage);
+
+ async OnSessionKeysChange(nsCString aSessionId,
+ CDMKeyInformation[] aKeysInfo);
+
+ async OnExpirationChange(nsCString aSessionId,
+ double aSecondsSinceEpoch);
+
+ async OnSessionClosed(nsCString aSessionId);
+
+ async OnQueryOutputProtectionStatus();
+
+ async ResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccessful);
+
+ // Return values of cdm::ContentDecryptionModule_10::Decrypt
+ async Decrypted(uint32_t aId, uint32_t aStatus, Shmem aDecryptedData);
+ async DecryptFailed(uint32_t aId, uint32_t aStatus);
+
+ async OnDecoderInitDone(uint32_t aStatus);
+
+ // Return values of cdm::ContentDecryptionModule_10::DecryptAndDecodeFrame
+ async DecodedShmem(CDMVideoFrame aFrame, Shmem aData);
+ async DecodedData(CDMVideoFrame aFrame, uint8_t[] aData);
+ async DecodeFailed(uint32_t aStatus);
+
+ async ResetVideoDecoderComplete();
+
+ async DrainComplete();
+
+ async Shutdown();
+
+ async IncreaseShmemPoolSize();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMP.ipdl b/dom/media/gmp/PGMP.ipdl
new file mode 100644
index 0000000000..076592f49b
--- /dev/null
+++ b/dom/media/gmp/PGMP.ipdl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include protocol PGMPTimer;
+include protocol PGMPStorage;
+include protocol PProfiler;
+
+include "mozilla/ipc/ByteBufUtils.h";
+include "GMPParent.h";
+include "GMPChild.h";
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h";
+
+namespace mozilla {
+namespace gmp {
+
+[NeedsOtherPid, NestedUpTo=inside_sync, ChildImpl="GMPChild", ParentImpl="GMPParent"]
+sync protocol PGMP
+{
+ manages PGMPTimer;
+ manages PGMPStorage;
+
+parent:
+ async InitCrashReporter(NativeThreadId threadId);
+ async PGMPTimer();
+ async PGMPStorage();
+
+ async PGMPContentChildDestroyed();
+
+ // Sent from time-to-time to limit the amount of telemetry vulnerable to loss
+ // Buffer contains bincoded Rust structs.
+ // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html
+ async FOGData(ByteBuf buf);
+
+child:
+ async CrashPluginNow();
+ [Nested=inside_sync] sync StartPlugin(nsString adapter);
+ async ProvideStorageId(nsCString storageId);
+ async PreloadLibs(nsCString libs);
+ async CloseActive();
+ async InitGMPContentChild(Endpoint<PGMPContentChild> endpoint);
+ async InitProfiler(Endpoint<PProfilerChild> endpoint);
+
+ // Tells the GMP process to flush any pending telemetry.
+ // Used in tests and ping assembly. Buffer contains bincoded Rust structs.
+ // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html
+ async FlushFOGData() returns (ByteBuf buf);
+
+ // Test-only method.
+ // Asks the GMP process to trigger test-only instrumentation.
+ // The unused returned value is to have a promise we can await.
+ async TestTriggerMetrics() returns (bool unused);
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPContent.ipdl b/dom/media/gmp/PGMPContent.ipdl
new file mode 100644
index 0000000000..db47aadc62
--- /dev/null
+++ b/dom/media/gmp/PGMPContent.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 PGMPVideoDecoder;
+include protocol PGMPVideoEncoder;
+include protocol PChromiumCDM;
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+include protocol PSandboxTesting;
+#endif
+
+include "GMPContentChild.h";
+
+namespace mozilla {
+namespace gmp {
+
+[NeedsOtherPid, ChildImpl="GMPContentChild", ParentImpl=virtual]
+sync protocol PGMPContent
+{
+ manages PGMPVideoDecoder;
+ manages PGMPVideoEncoder;
+ manages PChromiumCDM;
+
+child:
+ async PGMPVideoDecoder();
+ async PGMPVideoEncoder();
+ async PChromiumCDM(nsCString aKeySystem);
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
+#endif
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPService.ipdl b/dom/media/gmp/PGMPService.ipdl
new file mode 100644
index 0000000000..e186aaee80
--- /dev/null
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 base::ProcessId from "base/process.h";
+using GMPPluginType from "GMPNativeTypes.h";
+
+namespace mozilla {
+namespace gmp {
+
+[NeedsOtherPid, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PGMPService
+{
+parent:
+ sync LaunchGMP(NodeIdVariant nodeIdVariant,
+ nsCString api,
+ nsCString[] tags,
+ ProcessId[] alreadyBridgedTo)
+ returns (uint32_t pluginId,
+ GMPPluginType pluginType,
+ ProcessId id,
+ nsCString displayName,
+ Endpoint<PGMPContentParent> endpoint,
+ nsresult aResult,
+ nsCString aErrorDescription);
+
+ sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, nsString gmpName)
+ returns (nsCString id);
+child:
+ async BeginShutdown();
+};
+
+} // namespace gmp
+} // namespace mozilla
diff --git a/dom/media/gmp/PGMPStorage.ipdl b/dom/media/gmp/PGMPStorage.ipdl
new file mode 100644
index 0000000000..6efe7fbb23
--- /dev/null
+++ b/dom/media/gmp/PGMPStorage.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 PGMP;
+include GMPTypes;
+
+include "GMPStorageChild.h";
+
+using GMPErr from "gmp-errors.h";
+
+namespace mozilla {
+namespace gmp {
+
+[ManualDealloc, ChildImpl="GMPStorageChild", ParentImpl=virtual]
+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 Shutdown();
+
+parent:
+ async Open(nsCString aRecordName);
+ async Read(nsCString aRecordName);
+ async Write(nsCString aRecordName, uint8_t[] aBytes);
+ async Close(nsCString aRecordName);
+ 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 0000000000..c6c8170b43
--- /dev/null
+++ b/dom/media/gmp/PGMPTimer.ipdl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "GMPTimerParent.h";
+include "GMPTimerChild.h";
+
+namespace mozilla {
+namespace gmp {
+
+[ManualDealloc, ChildImpl="GMPTimerChild", ParentImpl="GMPTimerParent"]
+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 0000000000..e0c7a21a64
--- /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";
+include "GMPVideoDecoderChild.h";
+
+namespace mozilla {
+namespace gmp {
+
+[ChildImpl="GMPVideoDecoderChild", ParentImpl=virtual]
+sync 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);
+ sync 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 0000000000..afd4f7ce9a
--- /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";
+include "GMPVideoEncoderChild.h";
+
+namespace mozilla {
+namespace gmp {
+
+[ChildImpl="GMPVideoEncoderChild", ParentImpl=virtual]
+sync 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);
+ sync 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 0000000000..189cf3b302
--- /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-entrypoints.h b/dom/media/gmp/gmp-api/gmp-entrypoints.h
new file mode 100644
index 0000000000..fd2457e9be
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-entrypoints.h
@@ -0,0 +1,73 @@
+/* -*- 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);
+
+#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 0000000000..a95db1f7a5
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-errors.h
@@ -0,0 +1,59 @@
+/* -*- 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 0000000000..530abcbbfe
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-platform.h
@@ -0,0 +1,102 @@
+/* -*- 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() = default;
+ virtual void Run() = 0;
+};
+
+class GMPThread {
+ public:
+ virtual ~GMPThread() = default;
+ 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() = default;
+ 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);
+
+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;
+};
+
+#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 0000000000..af53256d43
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-storage.h
@@ -0,0 +1,110 @@
+/*
+ * 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() = default;
+};
+
+// 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() = default;
+};
+
+#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 0000000000..1fb4930a8b
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-codec.h
@@ -0,0 +1,302 @@
+/* -*- 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,
+ kGMPNonRealtimeVideo,
+ kGMPCodecModeInvalid // Should always be last.
+};
+
+enum GMPLogLevel {
+ kGMPLogDefault,
+ kGMPLogQuiet,
+ kGMPLogError,
+ kGMPLogWarning,
+ kGMPLogInfo,
+ kGMPLogDebug,
+ kGMPLogDetail,
+ kGMPLogInvalid // Should always be last.
+};
+
+enum GMPProfile {
+ kGMPH264ProfileUnknown,
+ kGMPH264ProfileBaseline,
+ kGMPH264ProfileMain,
+ kGMPH264ProfileExtended,
+ kGMPH264ProfileHigh,
+ kGMPH264ProfileHigh10,
+ kGMPH264ProfileHigh422,
+ kGMPH264ProfileHigh444,
+ kGMPH264ProfileCavlc444,
+ kGMPH264ProfileScalableBaseline,
+ kGMPH264ProfileScalableHigh
+};
+
+enum GMPLevel {
+ kGMPH264LevelUnknown,
+ kGMPH264Level1_0,
+ kGMPH264Level1_B,
+ kGMPH264Level1_1,
+ kGMPH264Level1_2,
+ kGMPH264Level1_3,
+ kGMPH264Level2_0,
+ kGMPH264Level2_1,
+ kGMPH264Level2_2,
+ kGMPH264Level3_0,
+ kGMPH264Level3_1,
+ kGMPH264Level3_2,
+ kGMPH264Level4_0,
+ kGMPH264Level4_1,
+ kGMPH264Level4_2,
+ kGMPH264Level5_0,
+ kGMPH264Level5_1,
+ kGMPH264Level5_2
+};
+
+enum GMPRateControlMode {
+ kGMPRateControlUnknown,
+ kGMPRateControlBitrate,
+ kGMPRateControlQuality,
+ kGMPRateControlBufferBased,
+ kGMPRateControlTimestamp,
+ kGMPRateControlBitratePostskip,
+ kGMPRateControlOff
+};
+
+enum GMPSliceMode {
+ kGMPSliceUnknown,
+ kGMPSliceSingle,
+ kGMPSliceFixedSlcNum,
+ kGMPSliceRaster,
+ kGMPSliceSizeLimited
+};
+
+enum GMPApiVersion {
+ kGMPVersion32 =
+ 1, // leveraging that V32 had mCodecType first, and only supported H264
+ kGMPVersion33 = 33,
+
+ // Adds GMPVideoi420Frame::SetUpdatedTimestamp/UpdatedTimestamp
+ kGMPVersion34 = 34,
+
+ // Adds new options for encoding
+ kGMPVersion35 = 35,
+};
+
+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;
+
+ // Since GMP version 34
+ bool mUseThreadedDecode;
+ GMPLogLevel mLogLevel;
+
+ // Since GMP version 35
+ GMPLevel mLevel;
+ GMPProfile mProfile;
+ GMPRateControlMode mRateControlMode;
+ GMPSliceMode mSliceMode;
+ bool mUseThreadedEncode;
+};
+
+// 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 0000000000..561e841d39
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-decode.h
@@ -0,0 +1,125 @@
+/* -*- 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 0000000000..850d34abd1
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-encode.h
@@ -0,0 +1,133 @@
+/* -*- 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 0000000000..42683aa129
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame-encoded.h
@@ -0,0 +1,92 @@
+/* -*- 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-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;
+};
+
+#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 0000000000..bf64b90903
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame-i420.h
@@ -0,0 +1,142 @@
+/* -*- 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;
+
+ // -- These methods have been added in kGMPVersion34 --
+
+ // Set an updated frame timestamp (microseconds) from decoder
+ virtual void SetUpdatedTimestamp(uint64_t aTimestamp) = 0;
+
+ // Get an updated frame timestamp (microseconds) from decoder
+ virtual uint64_t UpdatedTimestamp() const = 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 0000000000..8f202e1196
--- /dev/null
+++ b/dom/media/gmp/gmp-api/gmp-video-frame.h
@@ -0,0 +1,48 @@
+/* -*- 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 0000000000..e1a6c742aa
--- /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 0000000000..58ca1041ca
--- /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 0000000000..4557793b4a
--- /dev/null
+++ b/dom/media/gmp/moz.build
@@ -0,0 +1,149 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_MODULE = "content_geckomediaplugins"
+
+XPIDL_SOURCES += [
+ "mozIGeckoMediaPluginChromeService.idl",
+ "mozIGeckoMediaPluginService.idl",
+]
+
+EXPORTS += [
+ "ChromiumCDMCallback.h",
+ "ChromiumCDMParent.h",
+ "ChromiumCDMProxy.h",
+ "DecryptJob.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",
+ "GMPCallbackBase.h",
+ "GMPChild.h",
+ "GMPContentChild.h",
+ "GMPContentParent.h",
+ "GMPCrashHelper.h",
+ "GMPCrashHelperHolder.h",
+ "GMPLoader.h",
+ "GMPMessageUtils.h",
+ "GMPNativeTypes.h",
+ "GMPParent.h",
+ "GMPPlatform.h",
+ "GMPProcessChild.h",
+ "GMPProcessParent.h",
+ "GMPSanitizedExports.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",
+ "widevine-adapter/content_decryption_module.h",
+ "widevine-adapter/content_decryption_module_export.h",
+ "widevine-adapter/content_decryption_module_ext.h",
+ "widevine-adapter/content_decryption_module_proxy.h",
+]
+
+UNIFIED_SOURCES += [
+ "CDMStorageIdProvider.cpp",
+ "ChromiumCDMAdapter.cpp",
+ "ChromiumCDMCallbackProxy.cpp",
+ "ChromiumCDMChild.cpp",
+ "ChromiumCDMParent.cpp",
+ "ChromiumCDMProxy.cpp",
+ "DecryptJob.cpp",
+ "GMPChild.cpp",
+ "GMPContentChild.cpp",
+ "GMPContentParent.cpp",
+ "GMPCrashHelperHolder.cpp",
+ "GMPDiskStorage.cpp",
+ "GMPLoader.cpp",
+ "GMPMemoryStorage.cpp",
+ "GMPParent.cpp",
+ "GMPPlatform.cpp",
+ "GMPProcessChild.cpp",
+ "GMPProcessParent.cpp",
+ "GMPService.cpp",
+ "GMPServiceChild.cpp",
+ "GMPServiceParent.cpp",
+ "GMPSharedMemManager.cpp",
+ "GMPStorageChild.cpp",
+ "GMPStorageParent.cpp",
+ "GMPTimerChild.cpp",
+ "GMPTimerParent.cpp",
+ "GMPUtils.cpp",
+ "GMPVideoDecoderChild.cpp",
+ "GMPVideoDecoderParent.cpp",
+ "GMPVideoEncodedFrameImpl.cpp",
+ "GMPVideoEncoderChild.cpp",
+ "GMPVideoEncoderParent.cpp",
+ "GMPVideoHost.cpp",
+ "GMPVideoi420FrameImpl.cpp",
+ "GMPVideoPlaneImpl.cpp",
+]
+
+DIRS += [
+ "rlz",
+ "widevine-adapter",
+]
+
+IPDL_SOURCES += [
+ "GMPTypes.ipdlh",
+ "PChromiumCDM.ipdl",
+ "PGMP.ipdl",
+ "PGMPService.ipdl",
+ "PGMPStorage.ipdl",
+ "PGMPTimer.ipdl",
+ "PGMPVideoDecoder.ipdl",
+ "PGMPVideoEncoder.ipdl",
+]
+
+PREPROCESSED_IPDL_SOURCES += [
+ "PGMPContent.ipdl",
+]
+
+if CONFIG["OS_TARGET"] in ["WINNT", "Darwin"]:
+ DEFINES["SUPPORT_STORAGE_ID"] = 1
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+if CONFIG["MOZ_SANDBOX"]:
+ # For sandbox includes and the include dependencies those have
+ LOCAL_INCLUDES += [
+ "/security/sandbox/chromium",
+ "/security/sandbox/chromium-shim",
+ ]
+
+
+FINAL_LIBRARY = "xul"
+# dom/media/webrtc/transport so we work with --disable-webrtc
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport",
+ "/xpcom/base",
+ "/xpcom/build",
+ "/xpcom/threads",
+]
diff --git a/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl
new file mode 100644
index 0000000000..51dc545092
--- /dev/null
+++ b/dom/media/gmp/mozIGeckoMediaPluginChromeService.idl
@@ -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/. */
+
+#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 AString aPattern);
+
+ /**
+ * Clears storage data associated with the base domain
+ * This means cleaning any storage that is associated
+ * either by origin or top level origin with the base domain
+ */
+ void forgetThisBaseDomain(in AString baseDomain);
+
+ /**
+ * 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 0000000000..1691e82ac7
--- /dev/null
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsString.h"
+class GMPDecryptorProxy;
+class GMPVideoDecoderProxy;
+class GMPVideoEncoderProxy;
+class GMPVideoHost;
+
+namespace mozilla {
+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 GMPVideoGetterCallback<GMPVideoDecoderProxy> GetGMPVideoDecoderCallback;
+typedef GMPVideoGetterCallback<GMPVideoEncoderProxy> GetGMPVideoEncoderCallback;
+class GetNodeIdCallback
+{
+public:
+ MOZ_COUNTED_DEFAULT_CTOR(GetNodeIdCallback)
+ MOZ_COUNTED_DTOR_VIRTUAL(GetNodeIdCallback)
+ virtual void Done(nsresult aResult, const nsACString& aNodeId) = 0;
+};
+%}
+
+[ptr] native TagArray(nsTArray<nsCString>);
+[ref] native ConstTagArrayRef(const nsTArray<nsCString>);
+native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
+native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
+native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
+native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
+native GMPCrashHelperPtr(mozilla::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 ConstTagArrayRef 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);
+
+ /**
+ * 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);
+
+ /**
+ * Gets the NodeId for a (origin, urlbarOrigin) pair.
+ */
+ [noscript]
+ void getNodeId(in AString origin,
+ in AString topLevelOrigin,
+ in AString gmpName,
+ in GetNodeIdCallback callback);
+};
diff --git a/dom/media/gmp/rlz/OWNERS b/dom/media/gmp/rlz/OWNERS
new file mode 100644
index 0000000000..66f24ebb37
--- /dev/null
+++ b/dom/media/gmp/rlz/OWNERS
@@ -0,0 +1,4 @@
+rogerta@chromium.org
+thakis@chromium.org
+
+# COMPONENT: Internals>Core
diff --git a/dom/media/gmp/rlz/README.mozilla b/dom/media/gmp/rlz/README.mozilla
new file mode 100644
index 0000000000..4408737a5e
--- /dev/null
+++ b/dom/media/gmp/rlz/README.mozilla
@@ -0,0 +1,4 @@
+Code taken from rlz project in Chromium repository: https://chromium.googlesource.com/chromium/src.git/+/6f3478dfd7d29b9872871718bd08493ed0c8bc8e
+
+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 0000000000..c64dd7ff82
--- /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/crc8.cc b/dom/media/gmp/rlz/lib/crc8.cc
new file mode 100644
index 0000000000..fe02eabf1b
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/crc8.cc
@@ -0,0 +1,90 @@
+// 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 "rlz/lib/crc8.h"
+
+namespace {
+
+// The CRC lookup table used for ATM HES (Polynomial = 0x07).
+// These are 256 unique 8-bit values.
+const unsigned char kCrcTable[256] = {
+ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
+ 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
+ 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
+ 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
+ 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
+ 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+ 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
+ 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
+ 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
+ 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
+ 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
+ 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+ 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
+ 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
+ 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
+ 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
+ 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
+ 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+ 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
+ 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
+ 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
+ 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
+ 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
+ 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+ 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
+ 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
+ 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
+ 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
+ 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
+ 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+ 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
+ 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
+};
+
+} // namespace anonymous
+
+
+namespace rlz_lib {
+
+bool Crc8::Generate(const unsigned char *data, int length,
+ unsigned char* check_sum) {
+ if (!check_sum)
+ return false;
+
+ *check_sum = 0;
+ if (!data)
+ return false;
+
+ // The inital and final constants are as used in the ATM HEC.
+ static const unsigned char kInitial = 0x00;
+ static const unsigned char kFinal = 0x55;
+ unsigned char crc = kInitial;
+ for (int i = 0; i < length; ++i) {
+ crc = kCrcTable[(data[i] ^ crc) & 0xFFU];
+ }
+
+ *check_sum = crc ^ kFinal;
+ return true;
+}
+
+bool Crc8::Verify(const unsigned char* data, int length,
+ unsigned char check_sum, bool* matches) {
+ if (!matches)
+ return false;
+
+ *matches = false;
+ if (!data)
+ return false;
+
+ unsigned char calculated_crc;
+ if (!Generate(data, length, &calculated_crc))
+ return false;
+
+ *matches = check_sum == calculated_crc;
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/rlz/lib/crc8.h b/dom/media/gmp/rlz/lib/crc8.h
new file mode 100644
index 0000000000..6c3c84859b
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/crc8.h
@@ -0,0 +1,24 @@
+// 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.
+//
+// Crc8 utility functions.
+
+#ifndef RLZ_LIB_CRC8_H_
+#define RLZ_LIB_CRC8_H_
+
+namespace rlz_lib {
+// CRC-8 methods:
+class Crc8 {
+ public:
+ static bool Generate(const unsigned char* data,
+ int length,
+ unsigned char* check_sum);
+ static bool Verify(const unsigned char* data,
+ int length,
+ unsigned char checksum,
+ bool * matches);
+};
+}; // namespace rlz_lib
+
+#endif // RLZ_LIB_CRC8_H_
diff --git a/dom/media/gmp/rlz/lib/machine_id.cc b/dom/media/gmp/rlz/lib/machine_id.cc
new file mode 100644
index 0000000000..b2943f90ff
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/machine_id.cc
@@ -0,0 +1,93 @@
+// 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 "rlz/lib/machine_id.h"
+
+#include <stddef.h>
+
+#include "rlz/lib/assert.h"
+#include "rlz/lib/crc8.h"
+#include "rlz/lib/string_utils.h"
+
+// Note: The original machine_id.cc code depends on Chromium's sha1 implementation.
+// Using Mozilla's implmentation as replacement to reduce the dependency of
+// some external files.
+#include "mozilla/SHA1.h"
+
+namespace rlz_lib {
+
+bool GetMachineId(std::string* machine_id) {
+ if (!machine_id)
+ return false;
+
+ static std::string calculated_id;
+ static bool calculated = false;
+ if (calculated) {
+ *machine_id = calculated_id;
+ return true;
+ }
+
+ std::vector<uint8_t> sid_bytes;
+ int volume_id;
+ if (!GetRawMachineId(&sid_bytes, &volume_id))
+ return false;
+
+ if (!testing::GetMachineIdImpl(sid_bytes, volume_id, machine_id))
+ return false;
+
+ calculated = true;
+ calculated_id = *machine_id;
+ return true;
+}
+
+namespace testing {
+
+bool GetMachineIdImpl(const std::vector<uint8_t>& sid_bytes,
+ int volume_id,
+ std::string* machine_id) {
+ machine_id->clear();
+
+ // The ID should be the SID hash + the Hard Drive SNo. + checksum byte.
+ static const int kSizeWithoutChecksum = mozilla::SHA1Sum::kHashSize + sizeof(int);
+ std::basic_string<unsigned char> id_binary(kSizeWithoutChecksum + 1, 0);
+
+ if (!sid_bytes.empty()) {
+ // In order to be compatible with the old version of RLZ, the hash of the
+ // SID must be done with all the original bytes from the unicode string.
+ // However, the chromebase SHA1 hash function takes only an std::string as
+ // input, so the unicode string needs to be converted to std::string
+ // "as is".
+ size_t byte_count = sid_bytes.size() * sizeof(std::vector<uint8_t>::value_type);
+ const char* buffer = reinterpret_cast<const char*>(sid_bytes.data());
+
+ // Note that digest can have embedded nulls.
+ mozilla::SHA1Sum SHA1;
+ mozilla::SHA1Sum::Hash hash;
+ SHA1.update(buffer, byte_count);
+ SHA1.finish(hash);
+ std::string digest(reinterpret_cast<char*>(hash), mozilla::SHA1Sum::kHashSize);
+ VERIFY(digest.size() == mozilla::SHA1Sum::kHashSize);
+ std::copy(digest.begin(), digest.end(), id_binary.begin());
+ }
+
+ // Convert from int to binary (makes big-endian).
+ for (size_t i = 0; i < sizeof(int); i++) {
+ int shift_bits = 8 * (sizeof(int) - i - 1);
+ id_binary[mozilla::SHA1Sum::kHashSize + i] = static_cast<unsigned char>(
+ (volume_id >> shift_bits) & 0xFF);
+ }
+
+ // Append the checksum byte.
+ if (!sid_bytes.empty() || (0 != volume_id))
+ rlz_lib::Crc8::Generate(id_binary.c_str(),
+ kSizeWithoutChecksum,
+ &id_binary[kSizeWithoutChecksum]);
+
+ return rlz_lib::BytesToString(
+ id_binary.c_str(), kSizeWithoutChecksum + 1, machine_id);
+}
+
+} // namespace testing
+
+} // namespace rlz_lib
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 0000000000..5fa343efa3
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/machine_id.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RLZ_LIB_MACHINE_ID_H_
+#define RLZ_LIB_MACHINE_ID_H_
+
+#include <string>
+#include <vector>
+
+namespace rlz_lib {
+
+// Gets the unique ID for the machine used for RLZ tracking purposes. On
+// Windows, this ID is derived from the Windows machine SID, and is the string
+// representation of a 20 byte hash + 4 bytes volum id + a 1 byte checksum.
+// Included in financial pings with events, unless explicitly forbidden by the
+// calling application.
+bool GetMachineId(std::string* machine_id);
+
+// 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 testing {
+bool GetMachineIdImpl(const std::vector<uint8_t>& sid_bytes,
+ int volume_id,
+ std::string* machine_id);
+} // namespace testing
+
+} // 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 0000000000..6da7323823
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/string_utils.cc
@@ -0,0 +1,34 @@
+// 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.
+//
+// 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 0000000000..500133ade1
--- /dev/null
+++ b/dom/media/gmp/rlz/lib/string_utils.h
@@ -0,0 +1,20 @@
+// 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.
+//
+// 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 0000000000..8c0bae22e8
--- /dev/null
+++ b/dom/media/gmp/rlz/mac/lib/machine_id_mac.cc
@@ -0,0 +1,322 @@
+// 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 <stddef.h>
+#include <stdint.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;
+ for (primary_interface.reset(IOIteratorNext(primary_interface_iterator));
+ primary_interface;
+ primary_interface.reset(IOIteratorNext(primary_interface_iterator))) {
+ 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 0000000000..8e2d9ea5d1
--- /dev/null
+++ b/dom/media/gmp/rlz/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Note: build rlz in its own moz.build, so it doesn't pickup any of
+# Chromium IPC's headers used in the moz.build of the parent file.
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['OS_TARGET'] in ['WINNT', 'Darwin']:
+ UNIFIED_SOURCES += [
+ 'lib/crc8.cc',
+ 'lib/machine_id.cc',
+ 'lib/string_utils.cc',
+ ]
+
+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 += [
+ '..',
+]
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 0000000000..85f1d6cf54
--- /dev/null
+++ b/dom/media/gmp/rlz/win/lib/machine_id_win.cc
@@ -0,0 +1,136 @@
+// 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 <windows.h>
+#include <sddl.h> // For ConvertSidToStringSidW.
+
+#include <memory>
+#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::unique_ptr<wchar_t[]> domain_buffer(new wchar_t[kStartDomainLength]);
+ 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.get(),
+ &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.reset(new wchar_t[domain_size]);
+
+ success = ::LookupAccountNameW(NULL, account_name, sid, &sid_dword_size,
+ domain_buffer.get(), &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)) {
+ char sid_buffer[SECURITY_MAX_SID_SIZE];
+ SID* sid = reinterpret_cast<SID*>(sid_buffer);
+ if (GetComputerSid(computer_name, sid, SECURITY_MAX_SID_SIZE)) {
+ *sid_bytes = ConvertSidToBytes(sid);
+ }
+ }
+
+ // Get the system drive volume serial number.
+ *volume_id = 0;
+ if (!GetSystemVolumeSerialNumber(volume_id)) {
+ ASSERT_STRING("GetMachineId: Failed to retrieve volume serial number");
+ *volume_id = 0;
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
new file mode 100644
index 0000000000..913f6ba288
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidevineFileIO.h"
+#include "GMPLog.h"
+#include "WidevineUtils.h"
+
+#include "gmp-api/gmp-platform.h"
+
+// Declared in ChromiumCDMAdapter.cpp.
+extern const GMPPlatformAPI* sPlatform;
+
+namespace mozilla {
+
+void WidevineFileIO::Open(const char* aFilename, uint32_t aFilenameLength) {
+ mName = std::string(aFilename, aFilename + aFilenameLength);
+ GMPRecord* record = nullptr;
+ GMPErr err = sPlatform->createrecord(aFilename, aFilenameLength, &record,
+ static_cast<GMPRecordClient*>(this));
+ if (GMP_FAILED(err)) {
+ GMP_LOG_DEBUG("WidevineFileIO::Open() '%s' GMPCreateRecord failed",
+ mName.c_str());
+ mClient->OnOpenComplete(cdm::FileIOClient::Status::kError);
+ return;
+ }
+ if (GMP_FAILED(record->Open())) {
+ GMP_LOG_DEBUG("WidevineFileIO::Open() '%s' record open failed",
+ mName.c_str());
+ mClient->OnOpenComplete(cdm::FileIOClient::Status::kError);
+ return;
+ }
+
+ GMP_LOG_DEBUG("WidevineFileIO::Open() '%s'", mName.c_str());
+ mRecord = record;
+}
+
+void WidevineFileIO::Read() {
+ if (!mRecord) {
+ GMP_LOG_DEBUG("WidevineFileIO::Read() '%s' used uninitialized!",
+ mName.c_str());
+ mClient->OnReadComplete(cdm::FileIOClient::Status::kError, nullptr, 0);
+ return;
+ }
+ GMP_LOG_DEBUG("WidevineFileIO::Read() '%s'", mName.c_str());
+ mRecord->Read();
+}
+
+void WidevineFileIO::Write(const uint8_t* aData, uint32_t aDataSize) {
+ if (!mRecord) {
+ GMP_LOG_DEBUG("WidevineFileIO::Write() '%s' used uninitialized!",
+ mName.c_str());
+ mClient->OnWriteComplete(cdm::FileIOClient::Status::kError);
+ return;
+ }
+ mRecord->Write(aData, aDataSize);
+}
+
+void WidevineFileIO::Close() {
+ GMP_LOG_DEBUG("WidevineFileIO::Close() '%s'", mName.c_str());
+ if (mRecord) {
+ mRecord->Close();
+ mRecord = nullptr;
+ }
+ delete this;
+}
+
+static cdm::FileIOClient::Status GMPToWidevineFileStatus(GMPErr aStatus) {
+ switch (aStatus) {
+ case GMPRecordInUse:
+ return cdm::FileIOClient::Status::kInUse;
+ case GMPNoErr:
+ return cdm::FileIOClient::Status::kSuccess;
+ default:
+ return cdm::FileIOClient::Status::kError;
+ }
+}
+
+void WidevineFileIO::OpenComplete(GMPErr aStatus) {
+ GMP_LOG_DEBUG("WidevineFileIO::OpenComplete() '%s' status=%d", mName.c_str(),
+ aStatus);
+ mClient->OnOpenComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+void WidevineFileIO::ReadComplete(GMPErr aStatus, const uint8_t* aData,
+ uint32_t aDataSize) {
+ GMP_LOG_DEBUG("WidevineFileIO::OnReadComplete() '%s' status=%d",
+ mName.c_str(), aStatus);
+ mClient->OnReadComplete(GMPToWidevineFileStatus(aStatus), aData, aDataSize);
+}
+
+void WidevineFileIO::WriteComplete(GMPErr aStatus) {
+ GMP_LOG_DEBUG("WidevineFileIO::WriteComplete() '%s' status=%d", mName.c_str(),
+ aStatus);
+ mClient->OnWriteComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.h b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
new file mode 100644
index 0000000000..8c36f032be
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.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 WidevineFileIO_h_
+#define WidevineFileIO_h_
+
+#include <stddef.h>
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-storage.h"
+#include <string>
+
+namespace mozilla {
+
+class WidevineFileIO : public cdm::FileIO, public GMPRecordClient {
+ public:
+ explicit WidevineFileIO(cdm::FileIOClient* aClient)
+ : mClient(aClient), mRecord(nullptr) {}
+
+ // cdm::FileIO
+ void Open(const char* aFilename, uint32_t aFilenameLength) override;
+ void Read() override;
+ void Write(const uint8_t* aData, uint32_t aDataSize) override;
+ void Close() override;
+
+ // GMPRecordClient
+ void OpenComplete(GMPErr aStatus) override;
+ void ReadComplete(GMPErr aStatus, const uint8_t* aData,
+ uint32_t aDataSize) override;
+ void WriteComplete(GMPErr aStatus) override;
+
+ private:
+ cdm::FileIOClient* mClient;
+ GMPRecord* mRecord;
+ std::string mName;
+};
+
+} // namespace mozilla
+
+#endif // WidevineFileIO_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.cpp b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
new file mode 100644
index 0000000000..d0551406b2
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp
@@ -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/. */
+
+#include "WidevineUtils.h"
+#include "GMPLog.h"
+#include "gmp-api/gmp-errors.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+namespace mozilla {
+
+WidevineBuffer::WidevineBuffer(size_t aSize) {
+ GMP_LOG_DEBUG("WidevineBuffer(size=%zu) created", aSize);
+ mBuffer.SetLength(aSize);
+}
+
+WidevineBuffer::~WidevineBuffer() {
+ GMP_LOG_DEBUG("WidevineBuffer(size=%" PRIu32 ") destroyed", Size());
+}
+
+void WidevineBuffer::Destroy() { delete this; }
+
+uint32_t WidevineBuffer::Capacity() const { return mBuffer.Length(); }
+
+uint8_t* WidevineBuffer::Data() { return mBuffer.Elements(); }
+
+void WidevineBuffer::SetSize(uint32_t aSize) { mBuffer.SetLength(aSize); }
+
+uint32_t WidevineBuffer::Size() const { return mBuffer.Length(); }
+
+nsTArray<uint8_t> WidevineBuffer::ExtractBuffer() {
+ nsTArray<uint8_t> out = std::move(mBuffer);
+ return out;
+}
+
+WidevineDecryptedBlock::WidevineDecryptedBlock()
+ : mBuffer(nullptr), mTimestamp(0) {}
+
+WidevineDecryptedBlock::~WidevineDecryptedBlock() {
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+}
+
+void WidevineDecryptedBlock::SetDecryptedBuffer(cdm::Buffer* aBuffer) {
+ mBuffer = aBuffer;
+}
+
+cdm::Buffer* WidevineDecryptedBlock::DecryptedBuffer() { return mBuffer; }
+
+void WidevineDecryptedBlock::SetTimestamp(int64_t aTimestamp) {
+ mTimestamp = aTimestamp;
+}
+
+int64_t WidevineDecryptedBlock::Timestamp() const { return mTimestamp; }
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.h b/dom/media/gmp/widevine-adapter/WidevineUtils.h
new file mode 100644
index 0000000000..1502621451
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineUtils.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 WidevineUtils_h_
+#define WidevineUtils_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+#define ENSURE_TRUE(condition, rv) \
+ { \
+ if (!(condition)) { \
+ GMP_LOG_DEBUG("ENSURE_TRUE FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+ }
+
+#define ENSURE_GMP_SUCCESS(err, rv) \
+ { \
+ if (GMP_FAILED(err)) { \
+ GMP_LOG_DEBUG("ENSURE_GMP_SUCCESS FAILED %s:%d", __FILE__, __LINE__); \
+ return rv; \
+ } \
+ }
+
+namespace gmp {
+class CDMShmemBuffer;
+}
+class WidevineBuffer;
+
+// Base class for our cdm::Buffer implementations, so we can tell at runtime
+// whether the buffer is a Shmem or non-Shmem buffer.
+class CDMBuffer : public cdm::Buffer {
+ public:
+ virtual WidevineBuffer* AsArrayBuffer() { return nullptr; }
+ virtual gmp::CDMShmemBuffer* AsShmemBuffer() { return nullptr; }
+};
+
+class WidevineBuffer : public CDMBuffer {
+ public:
+ explicit WidevineBuffer(size_t aSize);
+ ~WidevineBuffer() override;
+ void Destroy() override;
+ uint32_t Capacity() const override;
+ uint8_t* Data() override;
+ void SetSize(uint32_t aSize) override;
+ uint32_t Size() const override;
+
+ // Moves contents of buffer out into temporary.
+ // Note: This empties the buffer.
+ nsTArray<uint8_t> ExtractBuffer();
+
+ WidevineBuffer* AsArrayBuffer() override { return this; }
+
+ private:
+ nsTArray<uint8_t> mBuffer;
+ WidevineBuffer(const WidevineBuffer&);
+ void operator=(const WidevineBuffer&);
+};
+
+class WidevineDecryptedBlock : public cdm::DecryptedBlock {
+ public:
+ WidevineDecryptedBlock();
+ ~WidevineDecryptedBlock() override;
+ void SetDecryptedBuffer(cdm::Buffer* aBuffer) override;
+ cdm::Buffer* DecryptedBuffer() override;
+ void SetTimestamp(int64_t aTimestamp) override;
+ int64_t Timestamp() const override;
+
+ private:
+ cdm::Buffer* mBuffer;
+ int64_t mTimestamp;
+};
+
+} // namespace mozilla
+
+#endif // WidevineUtils_h_
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
new file mode 100644
index 0000000000..3f136c4ac8
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp
@@ -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/. */
+
+#include "WidevineVideoFrame.h"
+#include "GMPLog.h"
+#include "WidevineUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+namespace mozilla {
+
+WidevineVideoFrame::WidevineVideoFrame()
+ : mFormat(cdm::VideoFormat::kUnknownVideoFormat),
+ mSize{0, 0},
+ mBuffer(nullptr),
+ mTimestamp(0) {
+ MOZ_ASSERT(mSize.height == 0 && mSize.width == 0, "Size should be zeroed");
+ GMP_LOG_DEBUG("WidevineVideoFrame::WidevineVideoFrame() this=%p", this);
+ memset(mPlaneOffsets, 0, sizeof(mPlaneOffsets));
+ memset(mPlaneStrides, 0, sizeof(mPlaneStrides));
+}
+
+WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&& aOther)
+ : mFormat(aOther.mFormat),
+ mSize(aOther.mSize),
+ mBuffer(aOther.mBuffer),
+ mTimestamp(aOther.mTimestamp) {
+ GMP_LOG_DEBUG(
+ "WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&&) "
+ "this=%p, other=%p",
+ this, &aOther);
+ memcpy(mPlaneOffsets, aOther.mPlaneOffsets, sizeof(mPlaneOffsets));
+ memcpy(mPlaneStrides, aOther.mPlaneStrides, sizeof(mPlaneStrides));
+ aOther.mBuffer = nullptr;
+}
+
+WidevineVideoFrame::~WidevineVideoFrame() {
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+}
+
+void WidevineVideoFrame::SetFormat(cdm::VideoFormat aFormat) {
+ GMP_LOG_DEBUG("WidevineVideoFrame::SetFormat(%d) this=%p", aFormat, this);
+ mFormat = aFormat;
+}
+
+cdm::VideoFormat WidevineVideoFrame::Format() const { return mFormat; }
+
+void WidevineVideoFrame::SetSize(cdm::Size aSize) {
+ GMP_LOG_DEBUG("WidevineVideoFrame::SetSize(%d,%d) this=%p", aSize.width,
+ aSize.height, this);
+ mSize.width = aSize.width;
+ mSize.height = aSize.height;
+}
+
+cdm::Size WidevineVideoFrame::Size() const { return mSize; }
+
+void WidevineVideoFrame::SetFrameBuffer(cdm::Buffer* aFrameBuffer) {
+ GMP_LOG_DEBUG("WidevineVideoFrame::SetFrameBuffer(%p) this=%p", aFrameBuffer,
+ this);
+ MOZ_ASSERT(!mBuffer);
+ mBuffer = aFrameBuffer;
+}
+
+cdm::Buffer* WidevineVideoFrame::FrameBuffer() { return mBuffer; }
+
+void WidevineVideoFrame::SetPlaneOffset(cdm::VideoPlane aPlane,
+ uint32_t aOffset) {
+ GMP_LOG_DEBUG("WidevineVideoFrame::SetPlaneOffset(%d, %" PRIu32 ") this=%p",
+ aPlane, aOffset, this);
+ mPlaneOffsets[aPlane] = aOffset;
+}
+
+uint32_t WidevineVideoFrame::PlaneOffset(cdm::VideoPlane aPlane) {
+ return mPlaneOffsets[aPlane];
+}
+
+void WidevineVideoFrame::SetStride(cdm::VideoPlane aPlane, uint32_t aStride) {
+ GMP_LOG_DEBUG("WidevineVideoFrame::SetStride(%d, %" PRIu32 ") this=%p",
+ aPlane, aStride, this);
+ mPlaneStrides[aPlane] = aStride;
+}
+
+uint32_t WidevineVideoFrame::Stride(cdm::VideoPlane aPlane) {
+ return mPlaneStrides[aPlane];
+}
+
+void WidevineVideoFrame::SetTimestamp(int64_t timestamp) {
+ GMP_LOG_DEBUG("WidevineVideoFrame::SetTimestamp(%" PRId64 ") this=%p",
+ timestamp, this);
+ mTimestamp = timestamp;
+}
+
+int64_t WidevineVideoFrame::Timestamp() const { return mTimestamp; }
+
+bool WidevineVideoFrame::InitToBlack(int32_t aWidth, int32_t aHeight,
+ int64_t aTimeStamp) {
+ if (NS_WARN_IF(aWidth < 0 || aHeight < 0)) {
+ MOZ_ASSERT_UNREACHABLE("Frame dimensions should be positive");
+ return false;
+ }
+
+ const uint32_t halfWidth = (uint32_t(aWidth) + 1) / 2;
+ CheckedInt<size_t> ySizeChk = aWidth;
+ ySizeChk *= aHeight;
+ CheckedInt<size_t> uSizeChk = halfWidth;
+ uSizeChk *= (uint32_t(aHeight) + 1) / 2;
+ CheckedInt<size_t> yuSizeChk = ySizeChk + uSizeChk;
+ if (NS_WARN_IF(!yuSizeChk.isValid())) {
+ return false;
+ }
+
+ WidevineBuffer* buffer = new WidevineBuffer(yuSizeChk.value());
+ const size_t ySize = ySizeChk.value();
+ const size_t uSize = uSizeChk.value();
+ // Black in YCbCr is (0,128,128).
+ memset(buffer->Data(), 0, ySize);
+ memset(buffer->Data() + ySize, 128, uSize);
+ if (mBuffer) {
+ mBuffer->Destroy();
+ mBuffer = nullptr;
+ }
+ SetFormat(cdm::VideoFormat::kI420);
+ SetSize(cdm::Size{aWidth, aHeight});
+ SetFrameBuffer(buffer);
+ SetPlaneOffset(cdm::VideoPlane::kYPlane, 0);
+ SetStride(cdm::VideoPlane::kYPlane, aWidth);
+ // Note: U and V planes are stored at the same place in order to
+ // save memory since their contents are the same.
+ SetPlaneOffset(cdm::VideoPlane::kUPlane, ySize);
+ SetStride(cdm::VideoPlane::kUPlane, halfWidth);
+ SetPlaneOffset(cdm::VideoPlane::kVPlane, ySize);
+ SetStride(cdm::VideoPlane::kVPlane, halfWidth);
+ SetTimestamp(aTimeStamp);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
new file mode 100644
index 0000000000..0ae65ac1e5
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineVideoFrame_h_
+#define WidevineVideoFrame_h_
+
+#include "stddef.h"
+#include "content_decryption_module.h"
+#include "mozilla/Attributes.h"
+#include <vector>
+
+namespace mozilla {
+
+class WidevineVideoFrame : public cdm::VideoFrame {
+ public:
+ WidevineVideoFrame();
+ WidevineVideoFrame(WidevineVideoFrame&& other);
+ ~WidevineVideoFrame();
+
+ void SetFormat(cdm::VideoFormat aFormat) override;
+ cdm::VideoFormat Format() const override;
+
+ void SetSize(cdm::Size aSize) override;
+ cdm::Size Size() const override;
+
+ void SetFrameBuffer(cdm::Buffer* aFrameBuffer) override;
+ cdm::Buffer* FrameBuffer() override;
+
+ void SetPlaneOffset(cdm::VideoPlane aPlane, uint32_t aOffset) override;
+ uint32_t PlaneOffset(cdm::VideoPlane aPlane) override;
+
+ void SetStride(cdm::VideoPlane aPlane, uint32_t aStride) override;
+ uint32_t Stride(cdm::VideoPlane aPlane) override;
+
+ void SetTimestamp(int64_t aTimestamp) override;
+ int64_t Timestamp() const override;
+
+ [[nodiscard]] bool InitToBlack(int32_t aWidth, int32_t aHeight,
+ int64_t aTimeStamp);
+
+ protected:
+ cdm::VideoFormat mFormat;
+ cdm::Size mSize;
+ cdm::Buffer* mBuffer;
+ uint32_t mPlaneOffsets[cdm::VideoPlane::kMaxPlanes];
+ uint32_t mPlaneStrides[cdm::VideoPlane::kMaxPlanes];
+ int64_t mTimestamp;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module.h b/dom/media/gmp/widevine-adapter/content_decryption_module.h
new file mode 100644
index 0000000000..68fee35195
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module.h
@@ -0,0 +1,1359 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_H_
+
+#include <type_traits>
+
+#include "content_decryption_module_export.h"
+#include "content_decryption_module_proxy.h"
+
+#if defined(_MSC_VER)
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+typedef int int32_t;
+typedef __int64 int64_t;
+#else
+# include <stdint.h>
+#endif
+
+// The version number must be rolled when the exported functions are updated!
+// If the CDM and the adapter use different versions of these functions, the
+// adapter will fail to load or crash!
+#define CDM_MODULE_VERSION 4
+
+// Build the versioned entrypoint name.
+// The extra macros are necessary to expand version to an actual value.
+#define INITIALIZE_CDM_MODULE \
+ BUILD_ENTRYPOINT(InitializeCdmModule, CDM_MODULE_VERSION)
+#define BUILD_ENTRYPOINT(name, version) \
+ BUILD_ENTRYPOINT_NO_EXPANSION(name, version)
+#define BUILD_ENTRYPOINT_NO_EXPANSION(name, version) name##_##version
+
+// Macro to check that |type| does the following:
+// 1. is a standard layout.
+// 2. is trivial.
+// 3. sizeof(type) matches the expected size in bytes. As some types contain
+// pointers, the size is specified for both 32 and 64 bit.
+#define CHECK_TYPE(type, size_32, size_64) \
+ static_assert(std::is_standard_layout<type>(), \
+ #type " not standard_layout"); \
+ static_assert(std::is_trivial<type>(), #type " not trivial"); \
+ static_assert((sizeof(void*) == 4 && sizeof(type) == size_32) || \
+ (sizeof(void*) == 8 && sizeof(type) == size_64), \
+ #type " size mismatch")
+
+extern "C" {
+
+CDM_API void INITIALIZE_CDM_MODULE();
+
+CDM_API void DeinitializeCdmModule();
+
+// Returns a pointer to the requested CDM Host interface upon success.
+// Returns NULL if the requested CDM Host interface is not supported.
+// The caller should cast the returned pointer to the type matching
+// |host_interface_version|.
+typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data);
+
+// Returns a pointer to the requested CDM upon success.
+// Returns NULL if an error occurs or the requested |cdm_interface_version| or
+// |key_system| is not supported or another error occurs.
+// The caller should cast the returned pointer to the type matching
+// |cdm_interface_version|.
+// Caller retains ownership of arguments and must call Destroy() on the returned
+// object.
+CDM_API void* CreateCdmInstance(int cdm_interface_version,
+ const char* key_system,
+ uint32_t key_system_size,
+ GetCdmHostFunc get_cdm_host_func,
+ void* user_data);
+
+CDM_API const char* GetCdmVersion();
+
+} // extern "C"
+
+namespace cdm {
+
+enum Status : uint32_t {
+ kSuccess = 0,
+ kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample.
+ kNoKey, // The required decryption key is not available.
+ kInitializationError, // Initialization error.
+ kDecryptError, // Decryption failed.
+ kDecodeError, // Error decoding audio or video.
+ kDeferredInitialization // Decoder is not ready for initialization.
+};
+CHECK_TYPE(Status, 4, 4);
+
+// Exceptions used by the CDM to reject promises.
+// https://w3c.github.io/encrypted-media/#exceptions
+enum Exception : uint32_t {
+ kExceptionTypeError,
+ kExceptionNotSupportedError,
+ kExceptionInvalidStateError,
+ kExceptionQuotaExceededError
+};
+CHECK_TYPE(Exception, 4, 4);
+
+// The encryption scheme. The definitions are from ISO/IEC 23001-7:2016.
+enum class EncryptionScheme : uint32_t {
+ kUnencrypted = 0,
+ kCenc, // 'cenc' subsample encryption using AES-CTR mode.
+ kCbcs // 'cbcs' pattern encryption using AES-CBC mode.
+};
+CHECK_TYPE(EncryptionScheme, 4, 4);
+
+// The pattern used for pattern encryption. Note that ISO/IEC 23001-7:2016
+// defines each block to be 16-bytes.
+struct Pattern {
+ uint32_t crypt_byte_block; // Count of the encrypted blocks.
+ uint32_t skip_byte_block; // Count of the unencrypted blocks.
+};
+CHECK_TYPE(Pattern, 8, 8);
+
+enum class ColorRange : uint8_t {
+ kInvalid,
+ kLimited, // 709 color range with RGB values ranging from 16 to 235.
+ kFull, // Full RGB color range with RGB values from 0 to 255.
+ kDerived // Range is defined by |transfer_id| and |matrix_id|.
+};
+CHECK_TYPE(ColorRange, 1, 1);
+
+// Described in ISO 23001-8:2016, section 7. All the IDs are in the range
+// [0, 255] so 8-bit integer is sufficient. An unspecified ColorSpace should be
+// {2, 2, 2, ColorRange::kInvalid}, where value 2 means "Unspecified" for all
+// the IDs, as defined by the spec.
+struct ColorSpace {
+ uint8_t primary_id; // 7.1 colour primaries, table 2
+ uint8_t transfer_id; // 7.2 transfer characteristics, table 3
+ uint8_t matrix_id; // 7.3 matrix coefficients, table 4
+ ColorRange range;
+};
+CHECK_TYPE(ColorSpace, 4, 4);
+
+// Time is defined as the number of seconds since the Epoch
+// (00:00:00 UTC, January 1, 1970), not including any added leap second.
+// Also see Time definition in spec: https://w3c.github.io/encrypted-media/#time
+// Note that Time is defined in millisecond accuracy in the spec but in second
+// accuracy here.
+typedef double Time;
+
+// An input buffer can be split into several continuous subsamples.
+// A SubsampleEntry specifies the number of clear and cipher bytes in each
+// subsample. For example, the following buffer has three subsamples:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 |
+//
+// For decryption, all of the cipher bytes in a buffer should be concatenated
+// (in the subsample order) into a single logical stream. The clear bytes should
+// not be considered as part of decryption.
+//
+// Stream to decrypt: | cipher1 | cipher2 | cipher3 |
+// Decrypted stream: | decrypted1| decrypted2 | decrypted3 |
+//
+// After decryption, the decrypted bytes should be copied over the position
+// of the corresponding cipher bytes in the original buffer to form the output
+// buffer. Following the above example, the decrypted buffer should be:
+//
+// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
+// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 |
+//
+struct SubsampleEntry {
+ uint32_t clear_bytes;
+ uint32_t cipher_bytes;
+};
+CHECK_TYPE(SubsampleEntry, 8, 8);
+
+// Represents an input buffer to be decrypted (and possibly decoded). It does
+// not own any pointers in this struct. If |encryption_scheme| = kUnencrypted,
+// the data is unencrypted.
+// Note that this struct is organized so that sizeof(InputBuffer_2)
+// equals the sum of sizeof() all members in both 32-bit and 64-bit compiles.
+// Padding has been added to keep the fields aligned.
+struct InputBuffer_2 {
+ const uint8_t* data; // Pointer to the beginning of the input data.
+ uint32_t data_size; // Size (in bytes) of |data|.
+
+ EncryptionScheme encryption_scheme;
+
+ const uint8_t* key_id; // Key ID to identify the decryption key.
+ uint32_t key_id_size; // Size (in bytes) of |key_id|.
+ uint32_t : 32; // Padding.
+
+ const uint8_t* iv; // Initialization vector.
+ uint32_t iv_size; // Size (in bytes) of |iv|.
+ uint32_t : 32; // Padding.
+
+ const struct SubsampleEntry* subsamples;
+ uint32_t num_subsamples; // Number of subsamples in |subsamples|.
+ uint32_t : 32; // Padding.
+
+ // |pattern| is required if |encryption_scheme| specifies pattern encryption.
+ Pattern pattern;
+
+ int64_t timestamp; // Presentation timestamp in microseconds.
+};
+CHECK_TYPE(InputBuffer_2, 64, 80);
+
+enum AudioCodec : uint32_t { kUnknownAudioCodec = 0, kCodecVorbis, kCodecAac };
+CHECK_TYPE(AudioCodec, 4, 4);
+
+struct AudioDecoderConfig_2 {
+ AudioCodec codec;
+ int32_t channel_count;
+ int32_t bits_per_channel;
+ int32_t samples_per_second;
+
+ // Optional byte data required to initialize audio decoders, such as the
+ // vorbis setup header.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+
+ // Encryption scheme.
+ EncryptionScheme encryption_scheme;
+};
+CHECK_TYPE(AudioDecoderConfig_2, 28, 32);
+
+// Supported sample formats for AudioFrames.
+enum AudioFormat : uint32_t {
+ kUnknownAudioFormat = 0, // Unknown format value. Used for error reporting.
+ kAudioFormatU8, // Interleaved unsigned 8-bit w/ bias of 128.
+ kAudioFormatS16, // Interleaved signed 16-bit.
+ kAudioFormatS32, // Interleaved signed 32-bit.
+ kAudioFormatF32, // Interleaved float 32-bit.
+ kAudioFormatPlanarS16, // Signed 16-bit planar.
+ kAudioFormatPlanarF32, // Float 32-bit planar.
+};
+CHECK_TYPE(AudioFormat, 4, 4);
+
+// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php
+// Values are chosen to be consistent with Chromium's VideoPixelFormat values.
+enum VideoFormat : uint32_t {
+ kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting.
+ kYv12 = 1, // 12bpp YVU planar 1x1 Y, 2x2 VU samples.
+ kI420 = 2, // 12bpp YUV planar 1x1 Y, 2x2 UV samples.
+
+ // In the following formats, each sample uses 16-bit in storage, while the
+ // sample value is stored in the least significant N bits where N is
+ // specified by the number after "P". For example, for YUV420P9, each Y, U,
+ // and V sample is stored in the least significant 9 bits in a 2-byte block.
+ kYUV420P9 = 16,
+ kYUV420P10 = 17,
+ kYUV422P9 = 18,
+ kYUV422P10 = 19,
+ kYUV444P9 = 20,
+ kYUV444P10 = 21,
+ kYUV420P12 = 22,
+ kYUV422P12 = 23,
+ kYUV444P12 = 24,
+};
+CHECK_TYPE(VideoFormat, 4, 4);
+
+struct Size {
+ int32_t width;
+ int32_t height;
+};
+CHECK_TYPE(Size, 8, 8);
+
+enum VideoCodec : uint32_t {
+ kUnknownVideoCodec = 0,
+ kCodecVp8,
+ kCodecH264,
+ kCodecVp9,
+ kCodecAv1
+};
+CHECK_TYPE(VideoCodec, 4, 4);
+
+enum VideoCodecProfile : uint32_t {
+ kUnknownVideoCodecProfile = 0,
+ kProfileNotNeeded,
+ kH264ProfileBaseline,
+ kH264ProfileMain,
+ kH264ProfileExtended,
+ kH264ProfileHigh,
+ kH264ProfileHigh10,
+ kH264ProfileHigh422,
+ kH264ProfileHigh444Predictive,
+ kVP9Profile0,
+ kVP9Profile1,
+ kVP9Profile2,
+ kVP9Profile3,
+ kAv1ProfileMain,
+ kAv1ProfileHigh,
+ kAv1ProfilePro
+};
+CHECK_TYPE(VideoCodecProfile, 4, 4);
+
+// Deprecated: New CDM implementations should use VideoDecoderConfig_3.
+// Note that this struct is organized so that sizeof(VideoDecoderConfig_2)
+// equals the sum of sizeof() all members in both 32-bit and 64-bit compiles.
+// Padding has been added to keep the fields aligned.
+struct VideoDecoderConfig_2 {
+ VideoCodec codec;
+ VideoCodecProfile profile;
+ VideoFormat format;
+ uint32_t : 32; // Padding.
+
+ // Width and height of video frame immediately post-decode. Not all pixels
+ // in this region are valid.
+ Size coded_size;
+
+ // Optional byte data required to initialize video decoders, such as H.264
+ // AAVC data.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+
+ // Encryption scheme.
+ EncryptionScheme encryption_scheme;
+};
+CHECK_TYPE(VideoDecoderConfig_2, 36, 40);
+
+struct VideoDecoderConfig_3 {
+ VideoCodec codec;
+ VideoCodecProfile profile;
+ VideoFormat format;
+ ColorSpace color_space;
+
+ // Width and height of video frame immediately post-decode. Not all pixels
+ // in this region are valid.
+ Size coded_size;
+
+ // Optional byte data required to initialize video decoders, such as H.264
+ // AAVC data.
+ uint8_t* extra_data;
+ uint32_t extra_data_size;
+
+ EncryptionScheme encryption_scheme;
+};
+CHECK_TYPE(VideoDecoderConfig_3, 36, 40);
+
+enum StreamType : uint32_t { kStreamTypeAudio = 0, kStreamTypeVideo = 1 };
+CHECK_TYPE(StreamType, 4, 4);
+
+// Structure provided to ContentDecryptionModule::OnPlatformChallengeResponse()
+// after a platform challenge was initiated via Host::SendPlatformChallenge().
+// All values will be NULL / zero in the event of a challenge failure.
+struct PlatformChallengeResponse {
+ // |challenge| provided during Host::SendPlatformChallenge() combined with
+ // nonce data and signed with the platform's private key.
+ const uint8_t* signed_data;
+ uint32_t signed_data_length;
+
+ // RSASSA-PKCS1-v1_5-SHA256 signature of the |signed_data| block.
+ const uint8_t* signed_data_signature;
+ uint32_t signed_data_signature_length;
+
+ // X.509 device specific certificate for the |service_id| requested.
+ const uint8_t* platform_key_certificate;
+ uint32_t platform_key_certificate_length;
+};
+CHECK_TYPE(PlatformChallengeResponse, 24, 48);
+
+// The current status of the associated key. The valid types are defined in the
+// spec: https://w3c.github.io/encrypted-media/#dom-mediakeystatus
+enum KeyStatus : uint32_t {
+ kUsable = 0,
+ kInternalError = 1,
+ kExpired = 2,
+ kOutputRestricted = 3,
+ kOutputDownscaled = 4,
+ kStatusPending = 5,
+ kReleased = 6
+};
+CHECK_TYPE(KeyStatus, 4, 4);
+
+// Used when passing arrays of key information. Does not own the referenced
+// data. |system_code| is an additional error code for unusable keys and
+// should be 0 when |status| == kUsable.
+struct KeyInformation {
+ const uint8_t* key_id;
+ uint32_t key_id_size;
+ KeyStatus status;
+ uint32_t system_code;
+};
+CHECK_TYPE(KeyInformation, 16, 24);
+
+// Supported output protection methods for use with EnableOutputProtection() and
+// returned by OnQueryOutputProtectionStatus().
+enum OutputProtectionMethods : uint32_t {
+ kProtectionNone = 0,
+ kProtectionHDCP = 1 << 0
+};
+CHECK_TYPE(OutputProtectionMethods, 4, 4);
+
+// Connected output link types returned by OnQueryOutputProtectionStatus().
+enum OutputLinkTypes : uint32_t {
+ kLinkTypeNone = 0,
+ kLinkTypeUnknown = 1 << 0,
+ kLinkTypeInternal = 1 << 1,
+ kLinkTypeVGA = 1 << 2,
+ kLinkTypeHDMI = 1 << 3,
+ kLinkTypeDVI = 1 << 4,
+ kLinkTypeDisplayPort = 1 << 5,
+ kLinkTypeNetwork = 1 << 6
+};
+CHECK_TYPE(OutputLinkTypes, 4, 4);
+
+// Result of the QueryOutputProtectionStatus() call.
+enum QueryResult : uint32_t { kQuerySucceeded = 0, kQueryFailed };
+CHECK_TYPE(QueryResult, 4, 4);
+
+// The Initialization Data Type. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/format-registry/initdata/index.html#registry
+enum InitDataType : uint32_t { kCenc = 0, kKeyIds = 1, kWebM = 2 };
+CHECK_TYPE(InitDataType, 4, 4);
+
+// The type of session to create. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#dom-mediakeysessiontype
+enum SessionType : uint32_t {
+ kTemporary = 0,
+ kPersistentLicense = 1,
+ kPersistentUsageRecord = 2
+};
+CHECK_TYPE(SessionType, 4, 4);
+
+// The type of the message event. The valid types are defined in the spec:
+// https://w3c.github.io/encrypted-media/#dom-mediakeymessagetype
+enum MessageType : uint32_t {
+ kLicenseRequest = 0,
+ kLicenseRenewal = 1,
+ kLicenseRelease = 2,
+ kIndividualizationRequest = 3
+};
+CHECK_TYPE(MessageType, 4, 4);
+
+enum HdcpVersion : uint32_t {
+ kHdcpVersionNone,
+ kHdcpVersion1_0,
+ kHdcpVersion1_1,
+ kHdcpVersion1_2,
+ kHdcpVersion1_3,
+ kHdcpVersion1_4,
+ kHdcpVersion2_0,
+ kHdcpVersion2_1,
+ kHdcpVersion2_2,
+ kHdcpVersion2_3
+};
+CHECK_TYPE(HdcpVersion, 4, 4);
+
+struct Policy {
+ HdcpVersion min_hdcp_version;
+};
+CHECK_TYPE(Policy, 4, 4);
+
+// Represents a buffer created by Allocator implementations.
+class CDM_CLASS_API Buffer {
+ public:
+ // Destroys the buffer in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ virtual uint32_t Capacity() const = 0;
+ virtual uint8_t* Data() = 0;
+ virtual void SetSize(uint32_t size) = 0;
+ virtual uint32_t Size() const = 0;
+
+ protected:
+ Buffer() {}
+ virtual ~Buffer() {}
+
+ private:
+ Buffer(const Buffer&);
+ void operator=(const Buffer&);
+};
+
+// Represents a decrypted block that has not been decoded.
+class CDM_CLASS_API DecryptedBlock {
+ public:
+ virtual void SetDecryptedBuffer(Buffer* buffer) = 0;
+ virtual Buffer* DecryptedBuffer() = 0;
+
+ // TODO(tomfinegan): Figure out if timestamp is really needed. If it is not,
+ // we can just pass Buffer pointers around.
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ DecryptedBlock() {}
+ virtual ~DecryptedBlock() {}
+};
+
+enum VideoPlane : uint32_t {
+ kYPlane = 0,
+ kUPlane = 1,
+ kVPlane = 2,
+ kMaxPlanes = 3,
+};
+CHECK_TYPE(VideoPlane, 4, 4);
+
+class CDM_CLASS_API VideoFrame {
+ public:
+ virtual void SetFormat(VideoFormat format) = 0;
+ virtual VideoFormat Format() const = 0;
+
+ virtual void SetSize(cdm::Size size) = 0;
+ virtual cdm::Size Size() const = 0;
+
+ virtual void SetFrameBuffer(Buffer* frame_buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0;
+ virtual uint32_t PlaneOffset(VideoPlane plane) = 0;
+
+ virtual void SetStride(VideoPlane plane, uint32_t stride) = 0;
+ virtual uint32_t Stride(VideoPlane plane) = 0;
+
+ // Sets and gets the presentation timestamp which is in microseconds.
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual int64_t Timestamp() const = 0;
+
+ protected:
+ VideoFrame() {}
+ virtual ~VideoFrame() {}
+};
+
+// Represents a decoded video frame. The CDM should call the interface methods
+// to set the frame attributes. See DecryptAndDecodeFrame().
+class CDM_CLASS_API VideoFrame_2 {
+ public:
+ virtual void SetFormat(VideoFormat format) = 0;
+ virtual void SetSize(cdm::Size size) = 0;
+ virtual void SetFrameBuffer(Buffer* frame_buffer) = 0;
+ virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0;
+ virtual void SetStride(VideoPlane plane, uint32_t stride) = 0;
+ // Sets the presentation timestamp which is in microseconds.
+ virtual void SetTimestamp(int64_t timestamp) = 0;
+ virtual void SetColorSpace(ColorSpace color_space) = 0;
+
+ protected:
+ VideoFrame_2() {}
+ virtual ~VideoFrame_2() {}
+};
+
+// Represents decrypted and decoded audio frames. AudioFrames can contain
+// multiple audio output buffers, which are serialized into this format:
+//
+// |<------------------- serialized audio buffer ------------------->|
+// | int64_t timestamp | int64_t length | length bytes of audio data |
+//
+// For example, with three audio output buffers, the AudioFrames will look
+// like this:
+//
+// |<----------------- AudioFrames ------------------>|
+// | audio buffer 0 | audio buffer 1 | audio buffer 2 |
+class CDM_CLASS_API AudioFrames {
+ public:
+ virtual void SetFrameBuffer(Buffer* buffer) = 0;
+ virtual Buffer* FrameBuffer() = 0;
+
+ // The CDM must call this method, providing a valid format, when providing
+ // frame buffers. Planar data should be stored end to end; e.g.,
+ // |ch1 sample1||ch1 sample2|....|ch1 sample_last||ch2 sample1|...
+ virtual void SetFormat(AudioFormat format) = 0;
+ virtual AudioFormat Format() const = 0;
+
+ protected:
+ AudioFrames() {}
+ virtual ~AudioFrames() {}
+};
+
+// FileIO interface provides a way for the CDM to store data in a file in
+// persistent storage. This interface aims only at providing basic read/write
+// capabilities and should not be used as a full fledged file IO API.
+// Each CDM and origin (e.g. HTTPS, "foo.example.com", 443) combination has
+// its own persistent storage. All instances of a given CDM associated with a
+// given origin share the same persistent storage.
+// Note to implementors of this interface:
+// Per-origin storage and the ability for users to clear it are important.
+// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo.
+class CDM_CLASS_API FileIO {
+ public:
+ // Opens the file with |file_name| for read and write.
+ // FileIOClient::OnOpenComplete() will be called after the opening
+ // operation finishes.
+ // - When the file is opened by a CDM instance, it will be classified as "in
+ // use". In this case other CDM instances in the same domain may receive
+ // kInUse status when trying to open it.
+ // - |file_name| must only contain letters (A-Za-z), digits(0-9), or "._-".
+ // It must not start with an underscore ('_'), and must be at least 1
+ // character and no more than 256 characters long.
+ virtual void Open(const char* file_name, uint32_t file_name_size) = 0;
+
+ // Reads the contents of the file. FileIOClient::OnReadComplete() will be
+ // called with the read status. Read() should not be called if a previous
+ // Read() or Write() call is still pending; otherwise OnReadComplete() will
+ // be called with kInUse.
+ virtual void Read() = 0;
+
+ // Writes |data_size| bytes of |data| into the file.
+ // FileIOClient::OnWriteComplete() will be called with the write status.
+ // All existing contents in the file will be overwritten. Calling Write() with
+ // NULL |data| will clear all contents in the file. Write() should not be
+ // called if a previous Write() or Read() call is still pending; otherwise
+ // OnWriteComplete() will be called with kInUse.
+ virtual void Write(const uint8_t* data, uint32_t data_size) = 0;
+
+ // Closes the file if opened, destroys this FileIO object and releases any
+ // resources allocated. The CDM must call this method when it finished using
+ // this object. A FileIO object must not be used after Close() is called.
+ virtual void Close() = 0;
+
+ protected:
+ FileIO() {}
+ virtual ~FileIO() {}
+};
+
+// Responses to FileIO calls. All responses will be called asynchronously.
+// When kError is returned, the FileIO object could be in an error state. All
+// following calls (other than Close()) could return kError. The CDM should
+// still call Close() to destroy the FileIO object.
+class CDM_CLASS_API FileIOClient {
+ public:
+ enum class Status : uint32_t { kSuccess = 0, kInUse, kError };
+
+ // Response to a FileIO::Open() call with the open |status|.
+ virtual void OnOpenComplete(Status status) = 0;
+
+ // Response to a FileIO::Read() call to provide |data_size| bytes of |data|
+ // read from the file.
+ // - kSuccess indicates that all contents of the file has been successfully
+ // read. In this case, 0 |data_size| means that the file is empty.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates read failure, e.g. the storage is not open or cannot be
+ // fully read.
+ virtual void OnReadComplete(Status status, const uint8_t* data,
+ uint32_t data_size) = 0;
+
+ // Response to a FileIO::Write() call.
+ // - kSuccess indicates that all the data has been written into the file
+ // successfully.
+ // - kInUse indicates that there are other read/write operations pending.
+ // - kError indicates write failure, e.g. the storage is not open or cannot be
+ // fully written. Upon write failure, the contents of the file should be
+ // regarded as corrupt and should not used.
+ virtual void OnWriteComplete(Status status) = 0;
+
+ protected:
+ FileIOClient() {}
+ virtual ~FileIOClient() {}
+};
+
+class CDM_CLASS_API Host_10;
+class CDM_CLASS_API Host_11;
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class CDM_CLASS_API ContentDecryptionModule_10 {
+ public:
+ static const int kVersion = 10;
+ static const bool kIsStable = true;
+ typedef Host_10 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities. The CDM must respond by calling Host::OnInitialized()
+ // with whether the initialization succeeded. No other calls will be made by
+ // the host before Host::OnInitialized() returns.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ // If |use_hw_secure_codecs| is true, the CDM must ensure the decryption key
+ // and video buffers (compressed and uncompressed) are securely protected by
+ // hardware.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state,
+ bool use_hw_secure_codecs) = 0;
+
+ // Gets the key status if the CDM has a hypothetical key with the |policy|.
+ // The CDM must respond by calling either Host::OnResolveKeyStatusPromise()
+ // with the result key status or Host::OnRejectPromise() if an unexpected
+ // error happened or this method is not supported.
+ virtual void GetStatusForPolicy(uint32_t promise_id,
+ const Policy& policy) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ // If the CDM does not support server certificates, the promise should be
+ // rejected with kExceptionNotSupportedError. If |server_certificate_data|
+ // is empty, reject with kExceptionTypeError. Any other error should be
+ // rejected with kExceptionInvalidStateError or kExceptionQuotaExceededError.
+ // TODO(crbug.com/796417): Add support for the promise to return true or
+ // false, rather than using kExceptionNotSupportedError to mean false.
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id, SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id, const char* session_id,
+ uint32_t session_id_size, const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id, const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Will only be
+ // called for persistent sessions. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id, const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer_2& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kInitializationError if |audio_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig_2& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kInitializationError if |video_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig_2& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until
+ // kNeedMoreData is returned.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer_2& encrypted_buffer,
+ VideoFrame* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer_2& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result, uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Called by the host after a call to Host::RequestStorageId(). If the
+ // version of the storage ID requested is available, |storage_id| and
+ // |storage_id_size| are set appropriately. |version| will be the same as
+ // what was requested, unless 0 (latest) was requested, in which case
+ // |version| will be the actual version number for the |storage_id| returned.
+ // If the requested version is not available, null/zero will be provided as
+ // |storage_id| and |storage_id_size|, respectively, and |version| should be
+ // ignored.
+ virtual void OnStorageId(uint32_t version, const uint8_t* storage_id,
+ uint32_t storage_id_size) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_10() {}
+ virtual ~ContentDecryptionModule_10() {}
+};
+
+// ----- Note: CDM interface(s) below still in development and not stable! -----
+
+// ContentDecryptionModule interface that all CDMs need to implement.
+// The interface is versioned for backward compatibility.
+// Note: ContentDecryptionModule implementations must use the allocator
+// provided in CreateCdmInstance() to allocate any Buffer that needs to
+// be passed back to the caller. Implementations must call Buffer::Destroy()
+// when a Buffer is created that will never be returned to the caller.
+class CDM_CLASS_API ContentDecryptionModule_11 {
+ public:
+ static const int kVersion = 11;
+ static const bool kIsStable = false;
+ typedef Host_11 Host;
+
+ // Initializes the CDM instance, providing information about permitted
+ // functionalities. The CDM must respond by calling Host::OnInitialized()
+ // with whether the initialization succeeded. No other calls will be made by
+ // the host before Host::OnInitialized() returns.
+ // If |allow_distinctive_identifier| is false, messages from the CDM,
+ // such as message events, must not contain a Distinctive Identifier,
+ // even in an encrypted form.
+ // If |allow_persistent_state| is false, the CDM must not attempt to
+ // persist state. Calls to CreateFileIO() will fail.
+ // If |use_hw_secure_codecs| is true, the CDM must ensure the decryption key
+ // and video buffers (compressed and uncompressed) are securely protected by
+ // hardware.
+ virtual void Initialize(bool allow_distinctive_identifier,
+ bool allow_persistent_state,
+ bool use_hw_secure_codecs) = 0;
+
+ // Gets the key status if the CDM has a hypothetical key with the |policy|.
+ // The CDM must respond by calling either Host::OnResolveKeyStatusPromise()
+ // with the result key status or Host::OnRejectPromise() if an unexpected
+ // error happened or this method is not supported.
+ virtual void GetStatusForPolicy(uint32_t promise_id,
+ const Policy& policy) = 0;
+
+ // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(),
+ // UpdateSession(), CloseSession(), and RemoveSession() all accept a
+ // |promise_id|, which must be passed to the completion Host method
+ // (e.g. Host::OnResolveNewSessionPromise()).
+
+ // Provides a server certificate to be used to encrypt messages to the
+ // license server. The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise().
+ // If the CDM does not support server certificates, the promise should be
+ // rejected with kExceptionNotSupportedError. If |server_certificate_data|
+ // is empty, reject with kExceptionTypeError. Any other error should be
+ // rejected with kExceptionInvalidStateError or kExceptionQuotaExceededError.
+ // TODO(crbug.com/796417): Add support for the promise to return true or
+ // false, rather than using kExceptionNotSupportedError to mean false.
+ virtual void SetServerCertificate(uint32_t promise_id,
+ const uint8_t* server_certificate_data,
+ uint32_t server_certificate_data_size) = 0;
+
+ // Creates a session given |session_type|, |init_data_type|, and |init_data|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise().
+ virtual void CreateSessionAndGenerateRequest(uint32_t promise_id,
+ SessionType session_type,
+ InitDataType init_data_type,
+ const uint8_t* init_data,
+ uint32_t init_data_size) = 0;
+
+ // Loads the session of type |session_type| specified by |session_id|.
+ // The CDM must respond by calling either Host::OnResolveNewSessionPromise()
+ // or Host::OnRejectPromise(). If the session is not found, call
+ // Host::OnResolveNewSessionPromise() with session_id = NULL.
+ virtual void LoadSession(uint32_t promise_id, SessionType session_type,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Updates the session with |response|. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise().
+ virtual void UpdateSession(uint32_t promise_id, const char* session_id,
+ uint32_t session_id_size, const uint8_t* response,
+ uint32_t response_size) = 0;
+
+ // Requests that the CDM close the session. The CDM must respond by calling
+ // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request
+ // has been processed. This may be before the session is closed. Once the
+ // session is closed, Host::OnSessionClosed() must also be called.
+ virtual void CloseSession(uint32_t promise_id, const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Removes any stored session data associated with this session. Removes all
+ // license(s) and key(s) associated with the session, whether they are in
+ // memory, persistent store, or both. For persistent session types, other
+ // session data (e.g. record of license destruction) will be cleared as
+ // defined for each session type once a release message acknowledgment is
+ // processed by UpdateSession(). The CDM must respond by calling either
+ // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has
+ // been processed.
+ virtual void RemoveSession(uint32_t promise_id, const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Performs scheduled operation with |context| when the timer fires.
+ virtual void TimerExpired(void* context) = 0;
+
+ // Decrypts the |encrypted_buffer|.
+ //
+ // Returns kSuccess if decryption succeeded, in which case the callee
+ // should have filled the |decrypted_buffer| and passed the ownership of
+ // |data| in |decrypted_buffer| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kDecryptError if any other error happened.
+ // If the return value is not kSuccess, |decrypted_buffer| should be ignored
+ // by the caller.
+ virtual Status Decrypt(const InputBuffer_2& encrypted_buffer,
+ DecryptedBlock* decrypted_buffer) = 0;
+
+ // Initializes the CDM audio decoder with |audio_decoder_config|. This
+ // function must be called before DecryptAndDecodeSamples() is called.
+ //
+ // Returns kSuccess if the |audio_decoder_config| is supported and the CDM
+ // audio decoder is successfully initialized.
+ // Returns kInitializationError if |audio_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeAudioDecoder(
+ const AudioDecoderConfig_2& audio_decoder_config) = 0;
+
+ // Initializes the CDM video decoder with |video_decoder_config|. This
+ // function must be called before DecryptAndDecodeFrame() is called.
+ //
+ // Returns kSuccess if the |video_decoder_config| is supported and the CDM
+ // video decoder is successfully initialized.
+ // Returns kInitializationError if |video_decoder_config| is not supported.
+ // The CDM may still be able to do Decrypt().
+ // Returns kDeferredInitialization if the CDM is not ready to initialize the
+ // decoder at this time. Must call Host::OnDeferredInitializationDone() once
+ // initialization is complete.
+ virtual Status InitializeVideoDecoder(
+ const VideoDecoderConfig_3& video_decoder_config) = 0;
+
+ // De-initializes the CDM decoder and sets it to an uninitialized state. The
+ // caller can initialize the decoder again after this call to re-initialize
+ // it. This can be used to reconfigure the decoder if the configuration
+ // changes.
+ virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
+
+ // Resets the CDM decoder to an initialized clean state. All internal buffers
+ // MUST be flushed.
+ virtual void ResetDecoder(StreamType decoder_type) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
+ // |video_frame|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until
+ // kNeedMoreData is returned.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled the |video_frame| and passed the ownership of
+ // |frame_buffer| in |video_frame| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // a decoded frame (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |video_frame| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeFrame(const InputBuffer_2& encrypted_buffer,
+ VideoFrame_2* video_frame) = 0;
+
+ // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
+ // |audio_frames|. Upon end-of-stream, the caller should call this function
+ // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
+ // |audio_frames| is produced.
+ //
+ // Returns kSuccess if decryption and decoding both succeeded, in which case
+ // the callee will have filled |audio_frames| and passed the ownership of
+ // |data| in |audio_frames| to the caller.
+ // Returns kNoKey if the CDM did not have the necessary decryption key
+ // to decrypt.
+ // Returns kNeedMoreData if more data was needed by the decoder to generate
+ // audio samples (e.g. during initialization and end-of-stream).
+ // Returns kDecryptError if any decryption error happened.
+ // Returns kDecodeError if any decoding error happened.
+ // If the return value is not kSuccess, |audio_frames| should be ignored by
+ // the caller.
+ virtual Status DecryptAndDecodeSamples(const InputBuffer_2& encrypted_buffer,
+ AudioFrames* audio_frames) = 0;
+
+ // Called by the host after a platform challenge was initiated via
+ // Host::SendPlatformChallenge().
+ virtual void OnPlatformChallengeResponse(
+ const PlatformChallengeResponse& response) = 0;
+
+ // Called by the host after a call to Host::QueryOutputProtectionStatus(). The
+ // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask|
+ // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed,
+ // then |link_mask| and |output_protection_mask| are undefined and should
+ // be ignored.
+ virtual void OnQueryOutputProtectionStatus(
+ QueryResult result, uint32_t link_mask,
+ uint32_t output_protection_mask) = 0;
+
+ // Called by the host after a call to Host::RequestStorageId(). If the
+ // version of the storage ID requested is available, |storage_id| and
+ // |storage_id_size| are set appropriately. |version| will be the same as
+ // what was requested, unless 0 (latest) was requested, in which case
+ // |version| will be the actual version number for the |storage_id| returned.
+ // If the requested version is not available, null/zero will be provided as
+ // |storage_id| and |storage_id_size|, respectively, and |version| should be
+ // ignored.
+ virtual void OnStorageId(uint32_t version, const uint8_t* storage_id,
+ uint32_t storage_id_size) = 0;
+
+ // Destroys the object in the same context as it was created.
+ virtual void Destroy() = 0;
+
+ protected:
+ ContentDecryptionModule_11() {}
+ virtual ~ContentDecryptionModule_11() {}
+};
+
+class CDM_CLASS_API Host_10 {
+ public:
+ static const int kVersion = 10;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM with the result after the CDM instance was initialized.
+ virtual void OnInitialized(bool success) = 0;
+
+ // Called by the CDM when a key status is available in response to
+ // GetStatusForPolicy().
+ virtual void OnResolveKeyStatusPromise(uint32_t promise_id,
+ KeyStatus key_status) = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |exception| must be specified. |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id, Exception exception,
+ uint32_t system_code, const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type, const char* message,
+ uint32_t message_size) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |keys_info| is the list of key IDs for this session along with their
+ // current status. |keys_info_count| is the number of entries in |keys_info|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // represents the time after which the key(s) in the session will no longer
+ // be usable for decryption. It can be 0 if no such time exists or if the
+ // license explicitly never expires. Size parameter should not include null
+ // termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ // Requests a specific version of the storage ID. A storage ID is a stable,
+ // device specific ID used by the CDM to securely store persistent data. The
+ // ID will be returned by the host via ContentDecryptionModule::OnStorageId().
+ // If |version| is 0, the latest version will be returned. All |version|s
+ // that are greater than or equal to 0x80000000 are reserved for the CDM and
+ // should not be supported or returned by the host. The CDM must not expose
+ // the ID outside the client device, even in encrypted form.
+ virtual void RequestStorageId(uint32_t version) = 0;
+
+ protected:
+ Host_10() {}
+ virtual ~Host_10() {}
+};
+
+class CDM_CLASS_API Host_11 {
+ public:
+ static const int kVersion = 11;
+
+ // Returns a Buffer* containing non-zero members upon success, or NULL on
+ // failure. The caller owns the Buffer* after this call. The buffer is not
+ // guaranteed to be zero initialized. The capacity of the allocated Buffer
+ // is guaranteed to be not less than |capacity|.
+ virtual Buffer* Allocate(uint32_t capacity) = 0;
+
+ // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms|
+ // from now with |context|.
+ virtual void SetTimer(int64_t delay_ms, void* context) = 0;
+
+ // Returns the current wall time.
+ virtual Time GetCurrentWallTime() = 0;
+
+ // Called by the CDM with the result after the CDM instance was initialized.
+ virtual void OnInitialized(bool success) = 0;
+
+ // Called by the CDM when a key status is available in response to
+ // GetStatusForPolicy().
+ virtual void OnResolveKeyStatusPromise(uint32_t promise_id,
+ KeyStatus key_status) = 0;
+
+ // Called by the CDM when a session is created or loaded and the value for the
+ // MediaKeySession's sessionId attribute is available (|session_id|).
+ // This must be called before OnSessionMessage() or
+ // OnSessionKeysChange() is called for the same session. |session_id_size|
+ // should not include null termination.
+ // When called in response to LoadSession(), the |session_id| must be the
+ // same as the |session_id| passed in LoadSession(), or NULL if the
+ // session could not be loaded.
+ virtual void OnResolveNewSessionPromise(uint32_t promise_id,
+ const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // Called by the CDM when a session is updated or released.
+ virtual void OnResolvePromise(uint32_t promise_id) = 0;
+
+ // Called by the CDM when an error occurs as a result of one of the
+ // ContentDecryptionModule calls that accept a |promise_id|.
+ // |exception| must be specified. |error_message| and |system_code|
+ // are optional. |error_message_size| should not include null termination.
+ virtual void OnRejectPromise(uint32_t promise_id, Exception exception,
+ uint32_t system_code, const char* error_message,
+ uint32_t error_message_size) = 0;
+
+ // Called by the CDM when it has a message for session |session_id|.
+ // Size parameters should not include null termination.
+ virtual void OnSessionMessage(const char* session_id,
+ uint32_t session_id_size,
+ MessageType message_type, const char* message,
+ uint32_t message_size) = 0;
+
+ // Called by the CDM when there has been a change in keys or their status for
+ // session |session_id|. |has_additional_usable_key| should be set if a
+ // key is newly usable (e.g. new key available, previously expired key has
+ // been renewed, etc.) and the browser should attempt to resume playback.
+ // |keys_info| is the list of key IDs for this session along with their
+ // current status. |keys_info_count| is the number of entries in |keys_info|.
+ // Size parameter for |session_id| should not include null termination.
+ virtual void OnSessionKeysChange(const char* session_id,
+ uint32_t session_id_size,
+ bool has_additional_usable_key,
+ const KeyInformation* keys_info,
+ uint32_t keys_info_count) = 0;
+
+ // Called by the CDM when there has been a change in the expiration time for
+ // session |session_id|. This can happen as the result of an Update() call
+ // or some other event. If this happens as a result of a call to Update(),
+ // it must be called before resolving the Update() promise. |new_expiry_time|
+ // represents the time after which the key(s) in the session will no longer
+ // be usable for decryption. It can be 0 if no such time exists or if the
+ // license explicitly never expires. Size parameter should not include null
+ // termination.
+ virtual void OnExpirationChange(const char* session_id,
+ uint32_t session_id_size,
+ Time new_expiry_time) = 0;
+
+ // Called by the CDM when session |session_id| is closed. Size
+ // parameter should not include null termination.
+ virtual void OnSessionClosed(const char* session_id,
+ uint32_t session_id_size) = 0;
+
+ // The following are optional methods that may not be implemented on all
+ // platforms.
+
+ // Sends a platform challenge for the given |service_id|. |challenge| is at
+ // most 256 bits of data to be signed. Once the challenge has been completed,
+ // the host will call ContentDecryptionModule::OnPlatformChallengeResponse()
+ // with the signed challenge response and platform certificate. Size
+ // parameters should not include null termination.
+ virtual void SendPlatformChallenge(const char* service_id,
+ uint32_t service_id_size,
+ const char* challenge,
+ uint32_t challenge_size) = 0;
+
+ // Attempts to enable output protection (e.g. HDCP) on the display link. The
+ // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No
+ // status callback is issued, the CDM must call QueryOutputProtectionStatus()
+ // periodically to ensure the desired protections are applied.
+ virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0;
+
+ // Requests the current output protection status. Once the host has the status
+ // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus().
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // Must be called by the CDM if it returned kDeferredInitialization during
+ // InitializeAudioDecoder() or InitializeVideoDecoder().
+ virtual void OnDeferredInitializationDone(StreamType stream_type,
+ Status decoder_status) = 0;
+
+ // Creates a FileIO object from the host to do file IO operation. Returns NULL
+ // if a FileIO object cannot be obtained. Once a valid FileIO object is
+ // returned, |client| must be valid until FileIO::Close() is called. The
+ // CDM can call this method multiple times to operate on different files.
+ virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
+
+ // Requests a CdmProxy that proxies part of CDM functionalities to a different
+ // entity, e.g. a hardware CDM module. A CDM instance can have at most one
+ // CdmProxy throughout its lifetime, which must be requested and initialized
+ // during CDM instance initialization time, i.e. in or after CDM::Initialize()
+ // and before OnInitialized() is called, to ensure proper connection of the
+ // CdmProxy and the media player (e.g. hardware decoder). The CdmProxy is
+ // owned by the host and is guaranteed to be valid throughout the CDM
+ // instance's lifetime. The CDM must ensure that the |client| remain valid
+ // before the CDM instance is destroyed. Returns null if CdmProxy is not
+ // supported, called before CDM::Initialize(), RequestCdmProxy() is called
+ // more than once, or called after the CDM instance has been initialized.
+ virtual CdmProxy* RequestCdmProxy(CdmProxyClient* client) = 0;
+
+ // Requests a specific version of the storage ID. A storage ID is a stable,
+ // device specific ID used by the CDM to securely store persistent data. The
+ // ID will be returned by the host via ContentDecryptionModule::OnStorageId().
+ // If |version| is 0, the latest version will be returned. All |version|s
+ // that are greater than or equal to 0x80000000 are reserved for the CDM and
+ // should not be supported or returned by the host. The CDM must not expose
+ // the ID outside the client device, even in encrypted form.
+ virtual void RequestStorageId(uint32_t version) = 0;
+
+ protected:
+ Host_11() {}
+ virtual ~Host_11() {}
+};
+
+} // namespace cdm
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_export.h b/dom/media/gmp/widevine-adapter/content_decryption_module_export.h
new file mode 100644
index 0000000000..932708deba
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_export.h
@@ -0,0 +1,38 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
+
+// Define CDM_API so that functionality implemented by the CDM module
+// can be exported to consumers.
+#if defined(_WIN32)
+
+#if defined(CDM_IMPLEMENTATION)
+#define CDM_API __declspec(dllexport)
+#else
+#define CDM_API __declspec(dllimport)
+#endif // defined(CDM_IMPLEMENTATION)
+
+#else // defined(_WIN32)
+#define CDM_API __attribute__((visibility("default")))
+#endif // defined(_WIN32)
+
+// Define CDM_CLASS_API to export class types. We have to add visibility
+// attributes to make sure virtual tables in CDM consumer and CDM implementation
+// are the same. Generally, it was always a good idea, as there're no guarantees
+// about that for the internal symbols, but it has only become a practical issue
+// after introduction of LTO devirtualization. See more details on
+// https://crbug.com/609564#c35
+#if defined(_WIN32)
+#if defined(__clang__)
+#define CDM_CLASS_API [[clang::lto_visibility_public]]
+#else
+#define CDM_CLASS_API
+#endif
+#else // defined(_WIN32)
+#define CDM_CLASS_API __attribute__((visibility("default")))
+#endif // defined(_WIN32)
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h b/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
new file mode 100644
index 0000000000..5df8344e60
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
+
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+#include "content_decryption_module_export.h"
+
+#if defined(_MSC_VER)
+typedef unsigned int uint32_t;
+#else
+#include <stdint.h>
+#endif
+
+namespace cdm {
+
+#if defined(_WIN32)
+typedef wchar_t FilePathCharType;
+typedef HANDLE PlatformFile;
+const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
+#else
+typedef char FilePathCharType;
+typedef int PlatformFile;
+const PlatformFile kInvalidPlatformFile = -1;
+#endif // defined(_WIN32)
+
+struct HostFile {
+ HostFile(const FilePathCharType* file_path,
+ PlatformFile file,
+ PlatformFile sig_file)
+ : file_path(file_path), file(file), sig_file(sig_file) {}
+
+ // File that is part of the host of the CDM.
+ const FilePathCharType* file_path = nullptr;
+ PlatformFile file = kInvalidPlatformFile;
+
+ // Signature file for |file|.
+ PlatformFile sig_file = kInvalidPlatformFile;
+};
+
+} // namespace cdm
+
+extern "C" {
+
+// Functions in this file are dynamically retrieved by their versioned function
+// names. Increment the version number for any backward incompatible API
+// changes.
+
+// Verifies CDM host. All files in |host_files| are opened in read-only mode.
+//
+// Returns false and closes all files if there is an immediate failure.
+// Otherwise returns true as soon as possible and processes the files
+// asynchronously. All files MUST be closed by the CDM after this one-time
+// processing is finished.
+CDM_API bool VerifyCdmHost_0(const cdm::HostFile* host_files,
+ uint32_t num_files);
+}
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_EXT_H_
diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h b/dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h
new file mode 100644
index 0000000000..d3edff8b37
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h
@@ -0,0 +1,121 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CDM_CONTENT_DECRYPTION_MODULE_PROXY_H_
+#define CDM_CONTENT_DECRYPTION_MODULE_PROXY_H_
+
+#include "content_decryption_module_export.h"
+
+#if defined(_MSC_VER)
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+typedef unsigned __int64 uint64_t;
+#else
+# include <stdint.h>
+#endif
+
+namespace cdm {
+
+class CDM_CLASS_API CdmProxyClient;
+
+// A proxy class for the CDM.
+// In general, the interpretation of the CdmProxy and CdmProxyClient method
+// parameters are protocol dependent. For enum parameters, values outside the
+// enum range may not work.
+class CDM_CLASS_API CdmProxy {
+ public:
+ enum Function : uint32_t {
+ // For Intel Negotiate Crypto SessionKey Exchange (CSME) path to call
+ // ID3D11VideoContext::NegotiateCryptoSessionKeyExchange.
+ kIntelNegotiateCryptoSessionKeyExchange = 1,
+ // There will be more values in the future e.g. for D3D11 RSA method.
+ };
+
+ enum KeyType : uint32_t {
+ kDecryptOnly = 0,
+ kDecryptAndDecode = 1,
+ };
+
+ // Initializes the proxy. The results will be returned in
+ // CdmProxyClient::OnInitialized().
+ virtual void Initialize() = 0;
+
+ // Processes and updates the state of the proxy.
+ // |output_data_size| is required by some protocol to set up the output data.
+ // The operation may fail if the |output_data_size| is wrong. The results will
+ // be returned in CdmProxyClient::OnProcessed().
+ virtual void Process(Function function, uint32_t crypto_session_id,
+ const uint8_t* input_data, uint32_t input_data_size,
+ uint32_t output_data_size) = 0;
+
+ // Creates a crypto session for handling media.
+ // If extra data has to be passed to further setup the media crypto session,
+ // pass the data as |input_data|. The results will be returned in
+ // CdmProxyClient::OnMediaCryptoSessionCreated().
+ virtual void CreateMediaCryptoSession(const uint8_t* input_data,
+ uint32_t input_data_size) = 0;
+
+ // Sets a key for the session identified by |crypto_session_id|.
+ virtual void SetKey(uint32_t crypto_session_id, const uint8_t* key_id,
+ uint32_t key_id_size, KeyType key_type,
+ const uint8_t* key_blob, uint32_t key_blob_size) = 0;
+
+ // Removes a key for the session identified by |crypto_session_id|.
+ virtual void RemoveKey(uint32_t crypto_session_id, const uint8_t* key_id,
+ uint32_t key_id_size) = 0;
+
+ protected:
+ CdmProxy() {}
+ virtual ~CdmProxy() {}
+};
+
+// Responses to CdmProxy calls. All responses will be called asynchronously.
+class CDM_CLASS_API CdmProxyClient {
+ public:
+ enum Status : uint32_t {
+ kOk,
+ kFail,
+ };
+
+ enum Protocol : uint32_t {
+ kNone = 0, // No protocol supported. Can be used in failure cases.
+ kIntel, // Method using Intel CSME.
+ // There will be more values in the future e.g. kD3D11RsaHardware,
+ // kD3D11RsaSoftware to use the D3D11 RSA method.
+ };
+
+ // Callback for Initialize(). If the proxy created a crypto session, then the
+ // ID for the crypto session is |crypto_session_id|.
+ virtual void OnInitialized(Status status, Protocol protocol,
+ uint32_t crypto_session_id) = 0;
+
+ // Callback for Process(). |output_data| is the output of processing.
+ virtual void OnProcessed(Status status, const uint8_t* output_data,
+ uint32_t output_data_size) = 0;
+
+ // Callback for CreateMediaCryptoSession(). On success:
+ // - |crypto_session_id| is the ID for the created crypto session.
+ // - |output_data| is extra value, if any.
+ // Otherwise, |crypto_session_id| and |output_data| should be ignored.
+ virtual void OnMediaCryptoSessionCreated(Status status,
+ uint32_t crypto_session_id,
+ uint64_t output_data) = 0;
+
+ // Callback for SetKey().
+ virtual void OnKeySet(Status status) = 0;
+
+ // Callback for RemoveKey().
+ virtual void OnKeyRemoved(Status status) = 0;
+
+ // Called when there is a hardware reset and all the hardware context is lost.
+ virtual void NotifyHardwareReset() = 0;
+
+ protected:
+ CdmProxyClient() {}
+ virtual ~CdmProxyClient() {}
+};
+
+} // namespace cdm
+
+#endif // CDM_CONTENT_DECRYPTION_MODULE_PROXY_H_
diff --git a/dom/media/gmp/widevine-adapter/moz.build b/dom/media/gmp/widevine-adapter/moz.build
new file mode 100644
index 0000000000..b1b7582407
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ "WidevineFileIO.cpp",
+ "WidevineUtils.cpp",
+ "WidevineVideoFrame.cpp",
+]
+
+EXPORTS += ["WidevineFileIO.h", "WidevineUtils.h", "WidevineVideoFrame.h"]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/media/gmp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")