1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "ChromiumCDMParent.h"
7
8 #include "ChromiumCDMCallback.h"
9 #include "ChromiumCDMCallbackProxy.h"
10 #include "ChromiumCDMProxy.h"
11 #include "content_decryption_module.h"
12 #include "GMPContentChild.h"
13 #include "GMPContentParent.h"
14 #include "GMPLog.h"
15 #include "GMPService.h"
16 #include "GMPUtils.h"
17 #include "VideoUtils.h"
18 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
19 #include "mozilla/gmp/GMPTypes.h"
20 #include "mozilla/ScopeExit.h"
21 #include "mozilla/StaticPrefs_media.h"
22 #include "mozilla/Unused.h"
23 #include "AnnexB.h"
24 #include "H264.h"
25
26 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
27
28 namespace mozilla::gmp {
29
30 using namespace eme;
31
ChromiumCDMParent(GMPContentParent * aContentParent,uint32_t aPluginId)32 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
33 uint32_t aPluginId)
34 : mPluginId(aPluginId),
35 mContentParent(aContentParent),
36 mVideoShmemLimit(StaticPrefs::media_eme_chromium_api_video_shmems())
37 #ifdef DEBUG
38 ,
39 mGMPThread(aContentParent->GMPEventTarget())
40 #endif
41 {
42 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
43 GMP_LOG_DEBUG(
44 "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, "
45 "id=%" PRIu32 ")",
46 this, aContentParent, aPluginId);
47 }
48
Init(ChromiumCDMCallback * aCDMCallback,bool aAllowDistinctiveIdentifier,bool aAllowPersistentState,nsIEventTarget * aMainThread)49 RefPtr<ChromiumCDMParent::InitPromise> ChromiumCDMParent::Init(
50 ChromiumCDMCallback* aCDMCallback, bool aAllowDistinctiveIdentifier,
51 bool aAllowPersistentState, nsIEventTarget* aMainThread) {
52 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
53 GMP_LOG_DEBUG(
54 "ChromiumCDMParent::Init(this=%p) shutdown=%s abormalShutdown=%s "
55 "actorDestroyed=%s",
56 this, mIsShutdown ? "true" : "false",
57 mAbnormalShutdown ? "true" : "false", mActorDestroyed ? "true" : "false");
58 if (!aCDMCallback || !aMainThread) {
59 GMP_LOG_DEBUG(
60 "ChromiumCDMParent::Init(this=%p) failed "
61 "nullCallback=%s nullMainThread=%s",
62 this, !aCDMCallback ? "true" : "false",
63 !aMainThread ? "true" : "false");
64
65 return ChromiumCDMParent::InitPromise::CreateAndReject(
66 MediaResult(NS_ERROR_FAILURE,
67 nsPrintfCString("ChromiumCDMParent::Init() failed "
68 "nullCallback=%s nullMainThread=%s",
69 !aCDMCallback ? "true" : "false",
70 !aMainThread ? "true" : "false")),
71 __func__);
72 }
73
74 RefPtr<ChromiumCDMParent::InitPromise> promise =
75 mInitPromise.Ensure(__func__);
76 RefPtr<ChromiumCDMParent> self = this;
77 SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState)
78 ->Then(
79 GetCurrentSerialEventTarget(), __func__,
80 [self, aCDMCallback](bool aSuccess) {
81 if (!aSuccess) {
82 GMP_LOG_DEBUG(
83 "ChromiumCDMParent::Init() failed with callback from "
84 "child indicating CDM failed init");
85 self->mInitPromise.RejectIfExists(
86 MediaResult(NS_ERROR_FAILURE,
87 "ChromiumCDMParent::Init() failed with callback "
88 "from child indicating CDM failed init"),
89 __func__);
90 return;
91 }
92 GMP_LOG_DEBUG(
93 "ChromiumCDMParent::Init() succeeded with callback from child");
94 self->mCDMCallback = aCDMCallback;
95 self->mInitPromise.ResolveIfExists(true /* unused */, __func__);
96 },
97 [self](ResponseRejectReason&& aReason) {
98 RefPtr<gmp::GeckoMediaPluginService> service =
99 gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
100 bool xpcomWillShutdown =
101 service && service->XPCOMWillShutdownReceived();
102 GMP_LOG_DEBUG(
103 "ChromiumCDMParent::Init(this=%p) failed "
104 "shutdown=%s cdmCrash=%s actorDestroyed=%s "
105 "browserShutdown=%s promiseRejectReason=%d",
106 self.get(), self->mIsShutdown ? "true" : "false",
107 self->mAbnormalShutdown ? "true" : "false",
108 self->mActorDestroyed ? "true" : "false",
109 xpcomWillShutdown ? "true" : "false",
110 static_cast<int>(aReason));
111 self->mInitPromise.RejectIfExists(
112 MediaResult(
113 NS_ERROR_FAILURE,
114 nsPrintfCString("ChromiumCDMParent::Init() failed "
115 "shutdown=%s cdmCrash=%s actorDestroyed=%s "
116 "browserShutdown=%s promiseRejectReason=%d",
117 self->mIsShutdown ? "true" : "false",
118 self->mAbnormalShutdown ? "true" : "false",
119 self->mActorDestroyed ? "true" : "false",
120 xpcomWillShutdown ? "true" : "false",
121 static_cast<int>(aReason))),
122 __func__);
123 });
124 return promise;
125 }
126
CreateSession(uint32_t aCreateSessionToken,uint32_t aSessionType,uint32_t aInitDataType,uint32_t aPromiseId,const nsTArray<uint8_t> & aInitData)127 void ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
128 uint32_t aSessionType,
129 uint32_t aInitDataType,
130 uint32_t aPromiseId,
131 const nsTArray<uint8_t>& aInitData) {
132 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
133 GMP_LOG_DEBUG("ChromiumCDMParent::CreateSession(this=%p)", this);
134 if (mIsShutdown) {
135 RejectPromiseShutdown(aPromiseId);
136 return;
137 }
138 if (!SendCreateSessionAndGenerateRequest(aPromiseId, aSessionType,
139 aInitDataType, aInitData)) {
140 RejectPromiseWithStateError(
141 aPromiseId, "Failed to send generateRequest to CDM process."_ns);
142 return;
143 }
144 mPromiseToCreateSessionToken.InsertOrUpdate(aPromiseId, aCreateSessionToken);
145 }
146
LoadSession(uint32_t aPromiseId,uint32_t aSessionType,nsString aSessionId)147 void ChromiumCDMParent::LoadSession(uint32_t aPromiseId, uint32_t aSessionType,
148 nsString aSessionId) {
149 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
150 GMP_LOG_DEBUG("ChromiumCDMParent::LoadSession(this=%p, pid=%" PRIu32
151 ", type=%" PRIu32 ", sid=%s)",
152 this, aPromiseId, aSessionType,
153 NS_ConvertUTF16toUTF8(aSessionId).get());
154 if (mIsShutdown) {
155 RejectPromiseShutdown(aPromiseId);
156 return;
157 }
158 if (!SendLoadSession(aPromiseId, aSessionType,
159 NS_ConvertUTF16toUTF8(aSessionId))) {
160 RejectPromiseWithStateError(
161 aPromiseId, "Failed to send loadSession to CDM process."_ns);
162 return;
163 }
164 }
165
SetServerCertificate(uint32_t aPromiseId,const nsTArray<uint8_t> & aCert)166 void ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
167 const nsTArray<uint8_t>& aCert) {
168 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
169 GMP_LOG_DEBUG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
170 if (mIsShutdown) {
171 RejectPromiseShutdown(aPromiseId);
172 return;
173 }
174 if (!SendSetServerCertificate(aPromiseId, aCert)) {
175 RejectPromiseWithStateError(
176 aPromiseId, "Failed to send setServerCertificate to CDM process"_ns);
177 }
178 }
179
UpdateSession(const nsCString & aSessionId,uint32_t aPromiseId,const nsTArray<uint8_t> & aResponse)180 void ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
181 uint32_t aPromiseId,
182 const nsTArray<uint8_t>& aResponse) {
183 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
184 GMP_LOG_DEBUG("ChromiumCDMParent::UpdateSession(this=%p)", this);
185 if (mIsShutdown) {
186 RejectPromiseShutdown(aPromiseId);
187 return;
188 }
189 if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
190 RejectPromiseWithStateError(
191 aPromiseId, "Failed to send updateSession to CDM process"_ns);
192 }
193 }
194
CloseSession(const nsCString & aSessionId,uint32_t aPromiseId)195 void ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
196 uint32_t aPromiseId) {
197 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
198 GMP_LOG_DEBUG("ChromiumCDMParent::CloseSession(this=%p)", this);
199 if (mIsShutdown) {
200 RejectPromiseShutdown(aPromiseId);
201 return;
202 }
203 if (!SendCloseSession(aPromiseId, aSessionId)) {
204 RejectPromiseWithStateError(
205 aPromiseId, "Failed to send closeSession to CDM process"_ns);
206 }
207 }
208
RemoveSession(const nsCString & aSessionId,uint32_t aPromiseId)209 void ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
210 uint32_t aPromiseId) {
211 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
212 GMP_LOG_DEBUG("ChromiumCDMParent::RemoveSession(this=%p)", this);
213 if (mIsShutdown) {
214 RejectPromiseShutdown(aPromiseId);
215 return;
216 }
217 if (!SendRemoveSession(aPromiseId, aSessionId)) {
218 RejectPromiseWithStateError(
219 aPromiseId, "Failed to send removeSession to CDM process"_ns);
220 }
221 }
222
NotifyOutputProtectionStatus(bool aSuccess,uint32_t aLinkMask,uint32_t aProtectionMask)223 void ChromiumCDMParent::NotifyOutputProtectionStatus(bool aSuccess,
224 uint32_t aLinkMask,
225 uint32_t aProtectionMask) {
226 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
227 GMP_LOG_DEBUG("ChromiumCDMParent::NotifyOutputProtectionStatus(this=%p)",
228 this);
229 if (mIsShutdown) {
230 return;
231 }
232 const bool haveCachedValue = mOutputProtectionLinkMask.isSome();
233 if (mAwaitingOutputProtectionInformation && !aSuccess) {
234 MOZ_DIAGNOSTIC_ASSERT(
235 !haveCachedValue,
236 "Should not have a cached value if we're still awaiting infomation");
237 // We're awaiting info and don't yet have a cached value, and the check
238 // failed, don't cache the result, just forward the failure.
239 CompleteQueryOutputProtectionStatus(false, aLinkMask, aProtectionMask);
240 return;
241 }
242 if (!mAwaitingOutputProtectionInformation && haveCachedValue && !aSuccess) {
243 // We're not awaiting info, already have a cached value, and the check
244 // failed. Ignore this, we'll update our info from any future successful
245 // checks.
246 return;
247 }
248 MOZ_ASSERT(aSuccess, "Failed checks should be handled by this point");
249 // Update our protection information.
250 mOutputProtectionLinkMask = Some(aLinkMask);
251
252 if (mAwaitingOutputProtectionInformation) {
253 // If we have an outstanding query, service that query with this
254 // information.
255 mAwaitingOutputProtectionInformation = false;
256 MOZ_ASSERT(!haveCachedValue,
257 "If we were waiting on information, we shouldn't have yet "
258 "cached a value");
259 CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
260 aProtectionMask);
261 }
262 }
263
CompleteQueryOutputProtectionStatus(bool aSuccess,uint32_t aLinkMask,uint32_t aProtectionMask)264 void ChromiumCDMParent::CompleteQueryOutputProtectionStatus(
265 bool aSuccess, uint32_t aLinkMask, uint32_t aProtectionMask) {
266 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
267 GMP_LOG_DEBUG(
268 "ChromiumCDMParent::CompleteQueryOutputProtectionStatus(this=%p) "
269 "mIsShutdown=%s aSuccess=%s aLinkMask=%" PRIu32,
270 this, mIsShutdown ? "true" : "false", aSuccess ? "true" : "false",
271 aLinkMask);
272 if (mIsShutdown) {
273 return;
274 }
275 Unused << SendCompleteQueryOutputProtectionStatus(aSuccess, aLinkMask,
276 aProtectionMask);
277 }
278
279 // See
280 // https://cs.chromium.org/chromium/src/media/blink/webcontentdecryptionmodule_impl.cc?l=33-66&rcl=d49aa59ac8c2925d5bec229f3f1906537b6b4547
ToCDMHdcpVersion(const nsCString & aMinHdcpVersion)281 static Result<cdm::HdcpVersion, nsresult> ToCDMHdcpVersion(
282 const nsCString& aMinHdcpVersion) {
283 if (aMinHdcpVersion.IsEmpty()) {
284 return cdm::HdcpVersion::kHdcpVersionNone;
285 }
286 if (aMinHdcpVersion.EqualsIgnoreCase("1.0")) {
287 return cdm::HdcpVersion::kHdcpVersion1_0;
288 }
289 if (aMinHdcpVersion.EqualsIgnoreCase("1.1")) {
290 return cdm::HdcpVersion::kHdcpVersion1_1;
291 }
292 if (aMinHdcpVersion.EqualsIgnoreCase("1.2")) {
293 return cdm::HdcpVersion::kHdcpVersion1_2;
294 }
295 if (aMinHdcpVersion.EqualsIgnoreCase("1.3")) {
296 return cdm::HdcpVersion::kHdcpVersion1_3;
297 }
298 if (aMinHdcpVersion.EqualsIgnoreCase("1.4")) {
299 return cdm::HdcpVersion::kHdcpVersion1_4;
300 }
301 if (aMinHdcpVersion.EqualsIgnoreCase("2.0")) {
302 return cdm::HdcpVersion::kHdcpVersion2_0;
303 }
304 if (aMinHdcpVersion.EqualsIgnoreCase("2.1")) {
305 return cdm::HdcpVersion::kHdcpVersion2_1;
306 }
307 if (aMinHdcpVersion.EqualsIgnoreCase("2.2")) {
308 return cdm::HdcpVersion::kHdcpVersion2_2;
309 }
310 // When adding another version remember to update GMPMessageUtils so that we
311 // can serialize it correctly and have correct bounds on the enum!
312
313 // Invalid hdcp version string.
314 return Err(NS_ERROR_INVALID_ARG);
315 }
316
GetStatusForPolicy(uint32_t aPromiseId,const nsCString & aMinHdcpVersion)317 void ChromiumCDMParent::GetStatusForPolicy(uint32_t aPromiseId,
318 const nsCString& aMinHdcpVersion) {
319 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
320 GMP_LOG_DEBUG("ChromiumCDMParent::GetStatusForPolicy(this=%p)", this);
321 if (mIsShutdown) {
322 RejectPromiseShutdown(aPromiseId);
323 return;
324 }
325 auto hdcpVersionResult = ToCDMHdcpVersion(aMinHdcpVersion);
326 if (hdcpVersionResult.isErr()) {
327 ErrorResult rv;
328 // XXXbz there's no spec for this yet, and
329 // <https://github.com/WICG/hdcp-detection/blob/master/explainer.md>
330 // does not define what exceptions get thrown. Let's assume
331 // TypeError for invalid args, as usual.
332 constexpr auto err =
333 "getStatusForPolicy failed due to bad hdcp version argument"_ns;
334 rv.ThrowTypeError(err);
335 RejectPromise(aPromiseId, std::move(rv), err);
336 return;
337 }
338
339 if (!SendGetStatusForPolicy(aPromiseId, hdcpVersionResult.unwrap())) {
340 RejectPromiseWithStateError(
341 aPromiseId, "Failed to send getStatusForPolicy to CDM process"_ns);
342 }
343 }
344
InitCDMInputBuffer(gmp::CDMInputBuffer & aBuffer,MediaRawData * aSample)345 bool ChromiumCDMParent::InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer,
346 MediaRawData* aSample) {
347 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
348 const CryptoSample& crypto = aSample->mCrypto;
349 if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
350 GMP_LOG_DEBUG("InitCDMInputBuffer clear/cipher subsamples don't match");
351 return false;
352 }
353
354 Shmem shmem;
355 if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
356 return false;
357 }
358 memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size());
359 cdm::EncryptionScheme encryptionScheme = cdm::EncryptionScheme::kUnencrypted;
360 switch (crypto.mCryptoScheme) {
361 case CryptoScheme::None:
362 break; // Default to none
363 case CryptoScheme::Cenc:
364 encryptionScheme = cdm::EncryptionScheme::kCenc;
365 break;
366 case CryptoScheme::Cbcs:
367 encryptionScheme = cdm::EncryptionScheme::kCbcs;
368 break;
369 default:
370 GMP_LOG_DEBUG(
371 "InitCDMInputBuffer got unexpected encryption scheme with "
372 "value of %" PRIu8 ". Treating as no encryption.",
373 static_cast<uint8_t>(crypto.mCryptoScheme));
374 MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
375 break;
376 }
377
378 const nsTArray<uint8_t>& iv = encryptionScheme != cdm::EncryptionScheme::kCbcs
379 ? crypto.mIV
380 : crypto.mConstantIV;
381 aBuffer = gmp::CDMInputBuffer(
382 shmem, crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(),
383 aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
384 crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock,
385 encryptionScheme);
386 return true;
387 }
388
SendBufferToCDM(uint32_t aSizeInBytes)389 bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
390 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
391 GMP_LOG_DEBUG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32,
392 aSizeInBytes);
393 Shmem shmem;
394 if (!AllocShmem(aSizeInBytes, Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
395 return false;
396 }
397 if (!SendGiveBuffer(std::move(shmem))) {
398 DeallocShmem(shmem);
399 return false;
400 }
401 return true;
402 }
403
Decrypt(MediaRawData * aSample)404 RefPtr<DecryptPromise> ChromiumCDMParent::Decrypt(MediaRawData* aSample) {
405 if (mIsShutdown) {
406 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
407 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
408 __func__);
409 }
410 CDMInputBuffer buffer;
411 if (!InitCDMInputBuffer(buffer, aSample)) {
412 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
413 __func__);
414 }
415 // Send a buffer to the CDM to store the output. The CDM will either fill
416 // it with the decrypted sample and return it, or deallocate it on failure.
417 if (!SendBufferToCDM(aSample->Size())) {
418 DeallocShmem(buffer.mData());
419 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
420 __func__);
421 }
422
423 RefPtr<DecryptJob> job = new DecryptJob(aSample);
424 if (!SendDecrypt(job->mId, buffer)) {
425 GMP_LOG_DEBUG(
426 "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
427 this);
428 DeallocShmem(buffer.mData());
429 return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
430 __func__);
431 }
432 RefPtr<DecryptPromise> promise = job->Ensure();
433 mDecrypts.AppendElement(job);
434 return promise;
435 }
436
Recv__delete__()437 ipc::IPCResult ChromiumCDMParent::Recv__delete__() {
438 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
439 MOZ_ASSERT(mIsShutdown);
440 GMP_LOG_DEBUG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
441 if (mContentParent) {
442 mContentParent->ChromiumCDMDestroyed(this);
443 mContentParent = nullptr;
444 }
445 return IPC_OK();
446 }
447
RecvOnResolvePromiseWithKeyStatus(const uint32_t & aPromiseId,const uint32_t & aKeyStatus)448 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(
449 const uint32_t& aPromiseId, const uint32_t& aKeyStatus) {
450 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
451 GMP_LOG_DEBUG(
452 "ChromiumCDMParent::RecvOnResolvePromiseWithKeyStatus(this=%p, "
453 "pid=%" PRIu32 ", keystatus=%" PRIu32 ")",
454 this, aPromiseId, aKeyStatus);
455 if (!mCDMCallback || mIsShutdown) {
456 return IPC_OK();
457 }
458
459 mCDMCallback->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus);
460
461 return IPC_OK();
462 }
463
RecvOnResolveNewSessionPromise(const uint32_t & aPromiseId,const nsCString & aSessionId)464 ipc::IPCResult ChromiumCDMParent::RecvOnResolveNewSessionPromise(
465 const uint32_t& aPromiseId, const nsCString& aSessionId) {
466 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
467 GMP_LOG_DEBUG(
468 "ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%" PRIu32
469 ", sid=%s)",
470 this, aPromiseId, aSessionId.get());
471 if (!mCDMCallback || mIsShutdown) {
472 return IPC_OK();
473 }
474
475 Maybe<uint32_t> token = mPromiseToCreateSessionToken.Extract(aPromiseId);
476 if (token.isNothing()) {
477 RejectPromiseWithStateError(aPromiseId,
478 "Lost session token for new session."_ns);
479 return IPC_OK();
480 }
481
482 mCDMCallback->SetSessionId(token.value(), aSessionId);
483
484 ResolvePromise(aPromiseId);
485
486 return IPC_OK();
487 }
488
RecvResolveLoadSessionPromise(const uint32_t & aPromiseId,const bool & aSuccessful)489 ipc::IPCResult ChromiumCDMParent::RecvResolveLoadSessionPromise(
490 const uint32_t& aPromiseId, const bool& aSuccessful) {
491 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
492 GMP_LOG_DEBUG(
493 "ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%" PRIu32
494 ", successful=%d)",
495 this, aPromiseId, aSuccessful);
496 if (!mCDMCallback || mIsShutdown) {
497 return IPC_OK();
498 }
499
500 mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful);
501
502 return IPC_OK();
503 }
504
ResolvePromise(uint32_t aPromiseId)505 void ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId) {
506 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
507 GMP_LOG_DEBUG("ChromiumCDMParent::ResolvePromise(this=%p, pid=%" PRIu32 ")",
508 this, aPromiseId);
509
510 // Note: The MediaKeys rejects all pending DOM promises when it
511 // initiates shutdown.
512 if (!mCDMCallback || mIsShutdown) {
513 return;
514 }
515
516 mCDMCallback->ResolvePromise(aPromiseId);
517 }
518
RecvOnResolvePromise(const uint32_t & aPromiseId)519 ipc::IPCResult ChromiumCDMParent::RecvOnResolvePromise(
520 const uint32_t& aPromiseId) {
521 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
522 ResolvePromise(aPromiseId);
523 return IPC_OK();
524 }
525
RejectPromise(uint32_t aPromiseId,ErrorResult && aException,const nsCString & aErrorMessage)526 void ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
527 ErrorResult&& aException,
528 const nsCString& aErrorMessage) {
529 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
530 GMP_LOG_DEBUG("ChromiumCDMParent::RejectPromise(this=%p, pid=%" PRIu32 ")",
531 this, aPromiseId);
532 // Note: The MediaKeys rejects all pending DOM promises when it
533 // initiates shutdown.
534 if (!mCDMCallback || mIsShutdown) {
535 // Suppress the exception as it will not be explicitly handled due to the
536 // early return.
537 aException.SuppressException();
538 return;
539 }
540
541 mCDMCallback->RejectPromise(aPromiseId, std::move(aException), aErrorMessage);
542 }
543
RejectPromiseShutdown(uint32_t aPromiseId)544 void ChromiumCDMParent::RejectPromiseShutdown(uint32_t aPromiseId) {
545 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
546 RejectPromiseWithStateError(aPromiseId, "CDM is shutdown"_ns);
547 }
548
RejectPromiseWithStateError(uint32_t aPromiseId,const nsCString & aErrorMessage)549 void ChromiumCDMParent::RejectPromiseWithStateError(
550 uint32_t aPromiseId, const nsCString& aErrorMessage) {
551 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
552 ErrorResult rv;
553 rv.ThrowInvalidStateError(aErrorMessage);
554 RejectPromise(aPromiseId, std::move(rv), aErrorMessage);
555 }
556
ToErrorResult(uint32_t aException,const nsCString & aErrorMessage)557 static ErrorResult ToErrorResult(uint32_t aException,
558 const nsCString& aErrorMessage) {
559 // XXXbz could we have a CopyableErrorResult sent to us with a better error
560 // message?
561 ErrorResult rv;
562 switch (static_cast<cdm::Exception>(aException)) {
563 case cdm::Exception::kExceptionNotSupportedError:
564 rv.ThrowNotSupportedError(aErrorMessage);
565 break;
566 case cdm::Exception::kExceptionInvalidStateError:
567 rv.ThrowInvalidStateError(aErrorMessage);
568 break;
569 case cdm::Exception::kExceptionTypeError:
570 rv.ThrowTypeError(aErrorMessage);
571 break;
572 case cdm::Exception::kExceptionQuotaExceededError:
573 rv.ThrowQuotaExceededError(aErrorMessage);
574 break;
575 default:
576 MOZ_ASSERT_UNREACHABLE("Invalid cdm::Exception enum value.");
577 // Note: Unique placeholder.
578 rv.ThrowTimeoutError(aErrorMessage);
579 };
580 return rv;
581 }
582
RecvOnRejectPromise(const uint32_t & aPromiseId,const uint32_t & aException,const uint32_t & aSystemCode,const nsCString & aErrorMessage)583 ipc::IPCResult ChromiumCDMParent::RecvOnRejectPromise(
584 const uint32_t& aPromiseId, const uint32_t& aException,
585 const uint32_t& aSystemCode, const nsCString& aErrorMessage) {
586 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
587 RejectPromise(aPromiseId, ToErrorResult(aException, aErrorMessage),
588 aErrorMessage);
589 return IPC_OK();
590 }
591
RecvOnSessionMessage(const nsCString & aSessionId,const uint32_t & aMessageType,nsTArray<uint8_t> && aMessage)592 ipc::IPCResult ChromiumCDMParent::RecvOnSessionMessage(
593 const nsCString& aSessionId, const uint32_t& aMessageType,
594 nsTArray<uint8_t>&& aMessage) {
595 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
596 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
597 this, aSessionId.get());
598 if (!mCDMCallback || mIsShutdown) {
599 return IPC_OK();
600 }
601
602 mCDMCallback->SessionMessage(aSessionId, aMessageType, std::move(aMessage));
603 return IPC_OK();
604 }
605
RecvOnSessionKeysChange(const nsCString & aSessionId,nsTArray<CDMKeyInformation> && aKeysInfo)606 ipc::IPCResult ChromiumCDMParent::RecvOnSessionKeysChange(
607 const nsCString& aSessionId, nsTArray<CDMKeyInformation>&& aKeysInfo) {
608 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
609 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
610 if (!mCDMCallback || mIsShutdown) {
611 return IPC_OK();
612 }
613
614 mCDMCallback->SessionKeysChange(aSessionId, std::move(aKeysInfo));
615 return IPC_OK();
616 }
617
RecvOnExpirationChange(const nsCString & aSessionId,const double & aSecondsSinceEpoch)618 ipc::IPCResult ChromiumCDMParent::RecvOnExpirationChange(
619 const nsCString& aSessionId, const double& aSecondsSinceEpoch) {
620 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
621 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
622 this, aSecondsSinceEpoch);
623 if (!mCDMCallback || mIsShutdown) {
624 return IPC_OK();
625 }
626
627 mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch);
628 return IPC_OK();
629 }
630
RecvOnSessionClosed(const nsCString & aSessionId)631 ipc::IPCResult ChromiumCDMParent::RecvOnSessionClosed(
632 const nsCString& aSessionId) {
633 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
634 GMP_LOG_DEBUG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
635 if (!mCDMCallback || mIsShutdown) {
636 return IPC_OK();
637 }
638
639 mCDMCallback->SessionClosed(aSessionId);
640 return IPC_OK();
641 }
642
RecvOnQueryOutputProtectionStatus()643 ipc::IPCResult ChromiumCDMParent::RecvOnQueryOutputProtectionStatus() {
644 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
645 GMP_LOG_DEBUG(
646 "ChromiumCDMParent::RecvOnQueryOutputProtectionStatus(this=%p) "
647 "mIsShutdown=%s mCDMCallback=%s mAwaitingOutputProtectionInformation=%s",
648 this, mIsShutdown ? "true" : "false", mCDMCallback ? "true" : "false",
649 mAwaitingOutputProtectionInformation ? "true" : "false");
650 if (mIsShutdown) {
651 // We're shutdown, don't try to service the query.
652 return IPC_OK();
653 }
654 if (!mCDMCallback) {
655 // We don't have a callback. We're not yet outputting anything so can report
656 // we're safe.
657 CompleteQueryOutputProtectionStatus(true, uint32_t{}, uint32_t{});
658 return IPC_OK();
659 }
660
661 if (mOutputProtectionLinkMask.isSome()) {
662 MOZ_DIAGNOSTIC_ASSERT(
663 !mAwaitingOutputProtectionInformation,
664 "If we have a cached value we should not be awaiting information");
665 // We have a cached value, use that.
666 CompleteQueryOutputProtectionStatus(true, mOutputProtectionLinkMask.value(),
667 uint32_t{});
668 return IPC_OK();
669 }
670
671 // We need to call up the stack to get the info. The CDM proxy will finish
672 // the request via `NotifyOutputProtectionStatus`.
673 mAwaitingOutputProtectionInformation = true;
674 mCDMCallback->QueryOutputProtectionStatus();
675 return IPC_OK();
676 }
677
ToDecryptStatus(uint32_t aStatus)678 DecryptStatus ToDecryptStatus(uint32_t aStatus) {
679 switch (static_cast<cdm::Status>(aStatus)) {
680 case cdm::kSuccess:
681 return DecryptStatus::Ok;
682 case cdm::kNoKey:
683 return DecryptStatus::NoKeyErr;
684 default:
685 return DecryptStatus::GenericErr;
686 }
687 }
688
RecvDecryptFailed(const uint32_t & aId,const uint32_t & aStatus)689 ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId,
690 const uint32_t& aStatus) {
691 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
692 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptFailed(this=%p, id=%" PRIu32
693 ", status=%" PRIu32 ")",
694 this, aId, aStatus);
695
696 if (mIsShutdown) {
697 MOZ_ASSERT(mDecrypts.IsEmpty());
698 return IPC_OK();
699 }
700
701 for (size_t i = 0; i < mDecrypts.Length(); i++) {
702 if (mDecrypts[i]->mId == aId) {
703 mDecrypts[i]->PostResult(ToDecryptStatus(aStatus));
704 mDecrypts.RemoveElementAt(i);
705 break;
706 }
707 }
708 return IPC_OK();
709 }
710
RecvDecrypted(const uint32_t & aId,const uint32_t & aStatus,ipc::Shmem && aShmem)711 ipc::IPCResult ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
712 const uint32_t& aStatus,
713 ipc::Shmem&& aShmem) {
714 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
715 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%" PRIu32
716 ", status=%" PRIu32 ")",
717 this, aId, aStatus);
718
719 // We must deallocate the shmem once we've copied the result out of it
720 // in PostResult below.
721 auto autoDeallocateShmem = MakeScopeExit([&, this] { DeallocShmem(aShmem); });
722
723 if (mIsShutdown) {
724 MOZ_ASSERT(mDecrypts.IsEmpty());
725 return IPC_OK();
726 }
727 for (size_t i = 0; i < mDecrypts.Length(); i++) {
728 if (mDecrypts[i]->mId == aId) {
729 mDecrypts[i]->PostResult(
730 ToDecryptStatus(aStatus),
731 Span<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
732 mDecrypts.RemoveElementAt(i);
733 break;
734 }
735 }
736 return IPC_OK();
737 }
738
RecvIncreaseShmemPoolSize()739 ipc::IPCResult ChromiumCDMParent::RecvIncreaseShmemPoolSize() {
740 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
741 GMP_LOG_DEBUG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32, __func__, this,
742 mVideoShmemLimit, mVideoShmemsActive);
743
744 // Put an upper limit on the number of shmems we tolerate the CDM asking
745 // for, to prevent a memory blow-out. In practice, we expect the CDM to
746 // need less than 5, but some encodings require more.
747 // We'd expect CDMs to not have video frames larger than 720p-1080p
748 // (due to DRM robustness requirements), which is about 1.5MB-3MB per
749 // frame.
750 if (mVideoShmemLimit > 50) {
751 mDecodePromise.RejectIfExists(
752 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
753 RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
754 __func__);
755 Shutdown();
756 return IPC_OK();
757 }
758 mVideoShmemLimit++;
759
760 EnsureSufficientShmems(mVideoFrameBufferSize);
761
762 return IPC_OK();
763 }
764
PurgeShmems()765 bool ChromiumCDMParent::PurgeShmems() {
766 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
767 GMP_LOG_DEBUG(
768 "ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%zu"
769 " limit=%" PRIu32 " active=%" PRIu32,
770 this, mVideoFrameBufferSize, mVideoShmemLimit, mVideoShmemsActive);
771
772 if (mVideoShmemsActive == 0) {
773 // We haven't allocated any shmems, nothing to do here.
774 return true;
775 }
776 if (!SendPurgeShmems()) {
777 return false;
778 }
779 mVideoShmemsActive = 0;
780 return true;
781 }
782
EnsureSufficientShmems(size_t aVideoFrameSize)783 bool ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize) {
784 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
785 GMP_LOG_DEBUG(
786 "ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
787 "size=%zu expected_size=%zu limit=%" PRIu32 " active=%" PRIu32,
788 this, aVideoFrameSize, mVideoFrameBufferSize, mVideoShmemLimit,
789 mVideoShmemsActive);
790
791 // The Chromium CDM API requires us to implement a synchronous
792 // interface to supply buffers to the CDM for it to write decrypted samples
793 // into. We want our buffers to be backed by shmems, in order to reduce
794 // the overhead of transferring decoded frames. However due to sandboxing
795 // restrictions, the CDM process cannot allocate shmems itself.
796 // We don't want to be doing synchronous IPC to request shmems from the
797 // content process, nor do we want to have to do intr IPC or make async
798 // IPC conform to the sync allocation interface. So instead we have the
799 // content process pre-allocate a set of shmems and give them to the CDM
800 // process in advance of them being needed.
801 //
802 // When the CDM needs to allocate a buffer for storing a decoded video
803 // frame, the CDM host gives it one of these shmems' buffers. When this
804 // is sent back to the content process, we upload it to a GPU surface,
805 // and send the shmem back to the CDM process so it can reuse it.
806 //
807 // Normally the CDM won't allocate more than one buffer at once, but
808 // we've seen cases where it allocates multiple buffers, returns one and
809 // holds onto the rest. So we need to ensure we have several extra
810 // shmems pre-allocated for the CDM. This threshold is set by the pref
811 // media.eme.chromium-api.video-shmems.
812 //
813 // We also have a failure recovery mechanism; if the CDM asks for more
814 // buffers than we have shmem's available, ChromiumCDMChild gives the
815 // CDM a non-shared memory buffer, and returns the frame to the parent
816 // in an nsTArray<uint8_t> instead of a shmem. The child then sends a
817 // message to the parent asking it to increase the number of shmems in
818 // the pool. Via this mechanism we should recover from incorrectly
819 // predicting how many shmems to pre-allocate.
820 //
821 // At decoder start up, we guess how big the shmems need to be based on
822 // the video frame dimensions. If we guess wrong, the CDM will follow
823 // the non-shmem path, and we'll re-create the shmems of the correct size.
824 // This meanns we can recover from guessing the shmem size wrong.
825 // We must re-take this path after every decoder de-init/re-init, as the
826 // frame sizes should change every time we switch video stream.
827
828 if (mVideoFrameBufferSize < aVideoFrameSize) {
829 if (!PurgeShmems()) {
830 return false;
831 }
832 mVideoFrameBufferSize = aVideoFrameSize;
833 }
834
835 while (mVideoShmemsActive < mVideoShmemLimit) {
836 if (!SendBufferToCDM(mVideoFrameBufferSize)) {
837 return false;
838 }
839 mVideoShmemsActive++;
840 }
841
842 return true;
843 }
844
RecvDecodedData(const CDMVideoFrame & aFrame,nsTArray<uint8_t> && aData)845 ipc::IPCResult ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
846 nsTArray<uint8_t>&& aData) {
847 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
848 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64,
849 this, aFrame.mTimestamp());
850
851 if (mIsShutdown || mDecodePromise.IsEmpty()) {
852 return IPC_OK();
853 }
854
855 if (!EnsureSufficientShmems(aData.Length())) {
856 mDecodePromise.RejectIfExists(
857 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
858 RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
859 __func__);
860 return IPC_OK();
861 }
862
863 RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
864 if (!v) {
865 mDecodePromise.RejectIfExists(
866 MediaResult(NS_ERROR_OUT_OF_MEMORY,
867 RESULT_DETAIL("Can't create VideoData")),
868 __func__);
869 return IPC_OK();
870 }
871
872 ReorderAndReturnOutput(std::move(v));
873
874 return IPC_OK();
875 }
876
RecvDecodedShmem(const CDMVideoFrame & aFrame,ipc::Shmem && aShmem)877 ipc::IPCResult ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
878 ipc::Shmem&& aShmem) {
879 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
880 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodedShmem(this=%p) time=%" PRId64
881 " duration=%" PRId64,
882 this, aFrame.mTimestamp(), aFrame.mDuration());
883
884 // On failure we need to deallocate the shmem we're to return to the
885 // CDM. On success we return it to the CDM to be reused.
886 auto autoDeallocateShmem =
887 MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
888
889 if (mIsShutdown || mDecodePromise.IsEmpty()) {
890 return IPC_OK();
891 }
892
893 RefPtr<VideoData> v = CreateVideoFrame(
894 aFrame, Span<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
895 if (!v) {
896 mDecodePromise.RejectIfExists(
897 MediaResult(NS_ERROR_OUT_OF_MEMORY,
898 RESULT_DETAIL("Can't create VideoData")),
899 __func__);
900 return IPC_OK();
901 }
902
903 // Return the shmem to the CDM so the shmem can be reused to send us
904 // another frame.
905 if (!SendGiveBuffer(std::move(aShmem))) {
906 mDecodePromise.RejectIfExists(
907 MediaResult(NS_ERROR_OUT_OF_MEMORY,
908 RESULT_DETAIL("Can't return shmem to CDM process")),
909 __func__);
910 return IPC_OK();
911 }
912
913 // Don't need to deallocate the shmem since the CDM process is responsible
914 // for it again.
915 autoDeallocateShmem.release();
916
917 ReorderAndReturnOutput(std::move(v));
918
919 return IPC_OK();
920 }
921
ReorderAndReturnOutput(RefPtr<VideoData> && aFrame)922 void ChromiumCDMParent::ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame) {
923 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
924 if (mMaxRefFrames == 0) {
925 mDecodePromise.ResolveIfExists(
926 MediaDataDecoder::DecodedData({std::move(aFrame)}), __func__);
927 return;
928 }
929 mReorderQueue.Push(std::move(aFrame));
930 MediaDataDecoder::DecodedData results;
931 while (mReorderQueue.Length() > mMaxRefFrames) {
932 results.AppendElement(mReorderQueue.Pop());
933 }
934 mDecodePromise.Resolve(std::move(results), __func__);
935 }
936
CreateVideoFrame(const CDMVideoFrame & aFrame,Span<uint8_t> aData)937 already_AddRefed<VideoData> ChromiumCDMParent::CreateVideoFrame(
938 const CDMVideoFrame& aFrame, Span<uint8_t> aData) {
939 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
940 VideoData::YCbCrBuffer b;
941 MOZ_ASSERT(aData.Length() > 0);
942
943 // Since we store each plane separately we can just roll the offset
944 // into our pointer to that plane and store that.
945 b.mPlanes[0].mData = aData.Elements() + aFrame.mYPlane().mPlaneOffset();
946 b.mPlanes[0].mWidth = aFrame.mImageWidth();
947 b.mPlanes[0].mHeight = aFrame.mImageHeight();
948 b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
949 b.mPlanes[0].mSkip = 0;
950
951 b.mPlanes[1].mData = aData.Elements() + aFrame.mUPlane().mPlaneOffset();
952 b.mPlanes[1].mWidth = (aFrame.mImageWidth() + 1) / 2;
953 b.mPlanes[1].mHeight = (aFrame.mImageHeight() + 1) / 2;
954 b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
955 b.mPlanes[1].mSkip = 0;
956
957 b.mPlanes[2].mData = aData.Elements() + aFrame.mVPlane().mPlaneOffset();
958 b.mPlanes[2].mWidth = (aFrame.mImageWidth() + 1) / 2;
959 b.mPlanes[2].mHeight = (aFrame.mImageHeight() + 1) / 2;
960 b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
961 b.mPlanes[2].mSkip = 0;
962
963 // We unfortunately can't know which colorspace the video is using at this
964 // stage.
965 b.mYUVColorSpace =
966 DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()});
967
968 gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
969 RefPtr<VideoData> v = VideoData::CreateAndCopyData(
970 mVideoInfo, mImageContainer, mLastStreamOffset,
971 media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()),
972 media::TimeUnit::FromMicroseconds(aFrame.mDuration()), b, false,
973 media::TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor);
974
975 return v.forget();
976 }
977
RecvDecodeFailed(const uint32_t & aStatus)978 ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) {
979 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
980 GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32
981 ")",
982 this, aStatus);
983 if (mIsShutdown) {
984 MOZ_ASSERT(mDecodePromise.IsEmpty());
985 return IPC_OK();
986 }
987
988 if (aStatus == cdm::kNeedMoreData) {
989 mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
990 return IPC_OK();
991 }
992
993 mDecodePromise.RejectIfExists(
994 MediaResult(
995 NS_ERROR_DOM_MEDIA_FATAL_ERR,
996 RESULT_DETAIL(
997 "ChromiumCDMParent::RecvDecodeFailed with status %s (%" PRIu32
998 ")",
999 CdmStatusToString(aStatus), aStatus)),
1000 __func__);
1001 return IPC_OK();
1002 }
1003
RecvShutdown()1004 ipc::IPCResult ChromiumCDMParent::RecvShutdown() {
1005 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1006 GMP_LOG_DEBUG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
1007 Shutdown();
1008 return IPC_OK();
1009 }
1010
ActorDestroy(ActorDestroyReason aWhy)1011 void ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy) {
1012 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1013 GMP_LOG_DEBUG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this,
1014 aWhy);
1015 MOZ_ASSERT(!mActorDestroyed);
1016 mActorDestroyed = true;
1017 // Shutdown() will clear mCDMCallback, so let's keep a reference for later
1018 // use.
1019 auto callback = mCDMCallback;
1020 if (!mIsShutdown) {
1021 // Plugin crash.
1022 MOZ_ASSERT(aWhy == AbnormalShutdown);
1023 Shutdown();
1024 }
1025 MOZ_ASSERT(mIsShutdown);
1026 RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
1027 if (mContentParent) {
1028 mContentParent->ChromiumCDMDestroyed(this);
1029 mContentParent = nullptr;
1030 }
1031 mAbnormalShutdown = (aWhy == AbnormalShutdown);
1032 if (mAbnormalShutdown && callback) {
1033 callback->Terminated();
1034 }
1035 MaybeDisconnect(mAbnormalShutdown);
1036 }
1037
InitializeVideoDecoder(const gmp::CDMVideoDecoderConfig & aConfig,const VideoInfo & aInfo,RefPtr<layers::ImageContainer> aImageContainer,RefPtr<layers::KnowsCompositor> aKnowsCompositor)1038 RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
1039 const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
1040 RefPtr<layers::ImageContainer> aImageContainer,
1041 RefPtr<layers::KnowsCompositor> aKnowsCompositor) {
1042 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1043 if (mIsShutdown) {
1044 return MediaDataDecoder::InitPromise::CreateAndReject(
1045 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1046 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1047 __func__);
1048 }
1049
1050 // The Widevine CDM version 1.4.8.970 and above contain a video decoder that
1051 // does not optimally allocate video frames; it requests buffers much larger
1052 // than required. The exact formula the CDM uses to calculate their frame
1053 // sizes isn't obvious, but they normally request around or slightly more
1054 // than 1.5X the optimal amount. So pad the size of buffers we allocate so
1055 // that we're likely to have buffers big enough to accomodate the CDM's weird
1056 // frame size calculation.
1057 const size_t bufferSize =
1058 1.7 * I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
1059 if (bufferSize <= 0) {
1060 return MediaDataDecoder::InitPromise::CreateAndReject(
1061 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1062 RESULT_DETAIL("Video frame buffer size is invalid.")),
1063 __func__);
1064 }
1065
1066 if (!EnsureSufficientShmems(bufferSize)) {
1067 return MediaDataDecoder::InitPromise::CreateAndReject(
1068 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1069 RESULT_DETAIL("Failed to init shmems for video decoder")),
1070 __func__);
1071 }
1072
1073 if (!SendInitializeVideoDecoder(aConfig)) {
1074 return MediaDataDecoder::InitPromise::CreateAndReject(
1075 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1076 RESULT_DETAIL("Failed to send init video decoder to CDM")),
1077 __func__);
1078 }
1079
1080 mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264)
1081 ? H264::HasSPS(aInfo.mExtraData)
1082 ? H264::ComputeMaxRefFrames(aInfo.mExtraData)
1083 : 16
1084 : 0;
1085
1086 mVideoDecoderInitialized = true;
1087 mImageContainer = aImageContainer;
1088 mKnowsCompositor = aKnowsCompositor;
1089 mVideoInfo = aInfo;
1090 mVideoFrameBufferSize = bufferSize;
1091
1092 return mInitVideoDecoderPromise.Ensure(__func__);
1093 }
1094
RecvOnDecoderInitDone(const uint32_t & aStatus)1095 ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone(
1096 const uint32_t& aStatus) {
1097 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1098 GMP_LOG_DEBUG(
1099 "ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%" PRIu32 ")",
1100 this, aStatus);
1101 if (mIsShutdown) {
1102 MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
1103 return IPC_OK();
1104 }
1105 if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
1106 mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
1107 } else {
1108 mVideoDecoderInitialized = false;
1109 mInitVideoDecoderPromise.RejectIfExists(
1110 MediaResult(
1111 NS_ERROR_DOM_MEDIA_FATAL_ERR,
1112 RESULT_DETAIL("CDM init decode failed with status %s (%" PRIu32 ")",
1113 CdmStatusToString(aStatus), aStatus)),
1114 __func__);
1115 }
1116 return IPC_OK();
1117 }
1118
1119 RefPtr<MediaDataDecoder::DecodePromise>
DecryptAndDecodeFrame(MediaRawData * aSample)1120 ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample) {
1121 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1122 if (mIsShutdown) {
1123 return MediaDataDecoder::DecodePromise::CreateAndReject(
1124 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1125 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1126 __func__);
1127 }
1128
1129 GMP_LOG_DEBUG("ChromiumCDMParent::DecryptAndDecodeFrame t=%" PRId64,
1130 aSample->mTime.ToMicroseconds());
1131
1132 CDMInputBuffer buffer;
1133
1134 if (!InitCDMInputBuffer(buffer, aSample)) {
1135 return MediaDataDecoder::DecodePromise::CreateAndReject(
1136 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
1137 __func__);
1138 }
1139
1140 mLastStreamOffset = aSample->mOffset;
1141
1142 if (!SendDecryptAndDecodeFrame(buffer)) {
1143 GMP_LOG_DEBUG(
1144 "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
1145 this);
1146 DeallocShmem(buffer.mData());
1147 return MediaDataDecoder::DecodePromise::CreateAndReject(
1148 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1149 "Failed to send decrypt to CDM process."),
1150 __func__);
1151 }
1152
1153 return mDecodePromise.Ensure(__func__);
1154 }
1155
FlushVideoDecoder()1156 RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMParent::FlushVideoDecoder() {
1157 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1158 if (mIsShutdown) {
1159 MOZ_ASSERT(mReorderQueue.IsEmpty());
1160 return MediaDataDecoder::FlushPromise::CreateAndReject(
1161 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1162 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1163 __func__);
1164 }
1165
1166 mReorderQueue.Clear();
1167
1168 mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
1169 if (!SendResetVideoDecoder()) {
1170 return MediaDataDecoder::FlushPromise::CreateAndReject(
1171 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1172 "Failed to send flush to CDM."),
1173 __func__);
1174 }
1175 return mFlushDecoderPromise.Ensure(__func__);
1176 }
1177
RecvResetVideoDecoderComplete()1178 ipc::IPCResult ChromiumCDMParent::RecvResetVideoDecoderComplete() {
1179 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1180 MOZ_ASSERT(mReorderQueue.IsEmpty());
1181 if (mIsShutdown) {
1182 MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
1183 return IPC_OK();
1184 }
1185 mFlushDecoderPromise.ResolveIfExists(true, __func__);
1186 return IPC_OK();
1187 }
1188
Drain()1189 RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMParent::Drain() {
1190 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1191 MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
1192 if (mIsShutdown) {
1193 return MediaDataDecoder::DecodePromise::CreateAndReject(
1194 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1195 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1196 __func__);
1197 }
1198
1199 RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
1200 if (!SendDrain()) {
1201 mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
1202 }
1203 return p;
1204 }
1205
RecvDrainComplete()1206 ipc::IPCResult ChromiumCDMParent::RecvDrainComplete() {
1207 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1208 if (mIsShutdown) {
1209 MOZ_ASSERT(mDecodePromise.IsEmpty());
1210 return IPC_OK();
1211 }
1212
1213 MediaDataDecoder::DecodedData samples;
1214 while (!mReorderQueue.IsEmpty()) {
1215 samples.AppendElement(mReorderQueue.Pop());
1216 }
1217
1218 mDecodePromise.ResolveIfExists(std::move(samples), __func__);
1219 return IPC_OK();
1220 }
ShutdownVideoDecoder()1221 RefPtr<ShutdownPromise> ChromiumCDMParent::ShutdownVideoDecoder() {
1222 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1223 if (mIsShutdown || !mVideoDecoderInitialized) {
1224 return ShutdownPromise::CreateAndResolve(true, __func__);
1225 }
1226 mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
1227 __func__);
1228 mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
1229 MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
1230 if (!SendDeinitializeVideoDecoder()) {
1231 return ShutdownPromise::CreateAndResolve(true, __func__);
1232 }
1233 mVideoDecoderInitialized = false;
1234
1235 GMP_LOG_DEBUG("ChromiumCDMParent::~ShutdownVideoDecoder(this=%p) ", this);
1236
1237 // The ChromiumCDMChild will purge its shmems, so if the decoder is
1238 // reinitialized the shmems need to be re-allocated, and they may need
1239 // to be a different size.
1240 mVideoShmemsActive = 0;
1241 mVideoFrameBufferSize = 0;
1242 return ShutdownPromise::CreateAndResolve(true, __func__);
1243 }
1244
Shutdown()1245 void ChromiumCDMParent::Shutdown() {
1246 MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
1247 GMP_LOG_DEBUG("ChromiumCDMParent::Shutdown(this=%p)", this);
1248
1249 if (mIsShutdown) {
1250 return;
1251 }
1252 mIsShutdown = true;
1253
1254 // If we're shutting down due to the plugin shutting down due to application
1255 // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
1256 // proxy will shutdown when the owning MediaKeys is destroyed during cycle
1257 // collection, and that will not shut down cleanly as the GMP thread will be
1258 // shutdown by then.
1259 if (mCDMCallback) {
1260 mCDMCallback->Shutdown();
1261 }
1262
1263 // We may be called from a task holding the last reference to the CDM
1264 // callback, so let's clear our local weak pointer to ensure it will not be
1265 // used afterward (including from an already-queued task, e.g.: ActorDestroy).
1266 mCDMCallback = nullptr;
1267
1268 mReorderQueue.Clear();
1269
1270 for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
1271 decrypt->PostResult(eme::AbortedErr);
1272 }
1273 mDecrypts.Clear();
1274
1275 if (mVideoDecoderInitialized && !mActorDestroyed) {
1276 Unused << SendDeinitializeVideoDecoder();
1277 mVideoDecoderInitialized = false;
1278 }
1279
1280 // Note: MediaKeys rejects all outstanding promises when it initiates
1281 // shutdown.
1282 mPromiseToCreateSessionToken.Clear();
1283
1284 mInitPromise.RejectIfExists(
1285 MediaResult(NS_ERROR_DOM_ABORT_ERR,
1286 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1287 __func__);
1288
1289 mInitVideoDecoderPromise.RejectIfExists(
1290 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1291 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1292 __func__);
1293 mDecodePromise.RejectIfExists(
1294 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1295 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1296 __func__);
1297 mFlushDecoderPromise.RejectIfExists(
1298 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
1299 RESULT_DETAIL("ChromiumCDMParent is shutdown")),
1300 __func__);
1301
1302 if (!mActorDestroyed) {
1303 Unused << SendDestroy();
1304 }
1305 }
1306
1307 } // namespace mozilla::gmp
1308
1309 #undef NS_DispatchToMainThread
1310