Line data Source code
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 "GMPParent.h"
7 : #include "mozilla/Logging.h"
8 : #include "nsComponentManagerUtils.h"
9 : #include "nsComponentManagerUtils.h"
10 : #include "nsThreadUtils.h"
11 : #include "nsIRunnable.h"
12 : #include "nsIWritablePropertyBag2.h"
13 : #include "mozIGeckoMediaPluginService.h"
14 : #include "mozilla/AbstractThread.h"
15 : #include "mozilla/ipc/GeckoChildProcessHost.h"
16 : #include "mozilla/SSE.h"
17 : #include "mozilla/SyncRunnable.h"
18 : #include "mozilla/Unused.h"
19 : #include "nsIObserverService.h"
20 : #include "GMPTimerParent.h"
21 : #include "runnable_utils.h"
22 : #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
23 : #include "mozilla/SandboxInfo.h"
24 : #endif
25 : #include "GMPContentParent.h"
26 : #include "MediaPrefs.h"
27 : #include "VideoUtils.h"
28 :
29 : using mozilla::ipc::GeckoChildProcessHost;
30 :
31 : #ifdef MOZ_CRASHREPORTER
32 : #include "nsPrintfCString.h"
33 : #include "mozilla/ipc/CrashReporterHost.h"
34 : using CrashReporter::AnnotationTable;
35 : using CrashReporter::GetIDFromMinidump;
36 : #endif
37 :
38 : #include "mozilla/Telemetry.h"
39 :
40 : #ifdef XP_WIN
41 : #include "WMFDecoderModule.h"
42 : #endif
43 :
44 : #include "mozilla/dom/WidevineCDMManifestBinding.h"
45 : #include "widevine-adapter/WidevineAdapter.h"
46 : #include "ChromiumCDMAdapter.h"
47 :
48 : namespace mozilla {
49 :
50 : #undef LOG
51 : #undef LOGD
52 :
53 : extern LogModule* GetGMPLog();
54 : #define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
55 : #define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__)
56 :
57 : #ifdef __CLASS__
58 : #undef __CLASS__
59 : #endif
60 : #define __CLASS__ "GMPParent"
61 :
62 : namespace gmp {
63 :
64 1 : GMPParent::GMPParent(AbstractThread* aMainThread)
65 : : mState(GMPStateNotLoaded)
66 : , mProcess(nullptr)
67 : , mDeleteProcessOnlyOnUnload(false)
68 : , mAbnormalShutdownInProgress(false)
69 : , mIsBlockingDeletion(false)
70 : , mCanDecrypt(false)
71 : , mGMPContentChildCount(0)
72 : , mChildPid(0)
73 : , mHoldingSelfRef(false)
74 1 : , mMainThread(aMainThread)
75 : {
76 1 : mPluginId = GeckoChildProcessHost::GetUniqueID();
77 1 : LOGD("GMPParent ctor id=%u", mPluginId);
78 1 : }
79 :
80 0 : GMPParent::~GMPParent()
81 : {
82 0 : LOGD("GMPParent dtor id=%u", mPluginId);
83 0 : MOZ_ASSERT(!mProcess);
84 0 : }
85 :
86 : nsresult
87 0 : GMPParent::CloneFrom(const GMPParent* aOther)
88 : {
89 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
90 0 : MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
91 :
92 0 : mService = aOther->mService;
93 0 : mDirectory = aOther->mDirectory;
94 0 : mName = aOther->mName;
95 0 : mVersion = aOther->mVersion;
96 0 : mDescription = aOther->mDescription;
97 0 : mDisplayName = aOther->mDisplayName;
98 : #ifdef XP_WIN
99 : mLibs = aOther->mLibs;
100 : #endif
101 0 : for (const GMPCapability& cap : aOther->mCapabilities) {
102 0 : mCapabilities.AppendElement(cap);
103 : }
104 0 : mAdapter = aOther->mAdapter;
105 0 : return NS_OK;
106 : }
107 :
108 : RefPtr<GenericPromise>
109 1 : GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir)
110 : {
111 1 : MOZ_ASSERT(aPluginDir);
112 1 : MOZ_ASSERT(aService);
113 1 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
114 :
115 1 : mService = aService;
116 1 : mDirectory = aPluginDir;
117 :
118 : // aPluginDir is <profile-dir>/<gmp-plugin-id>/<version>
119 : // where <gmp-plugin-id> should be gmp-gmpopenh264
120 2 : nsCOMPtr<nsIFile> parent;
121 1 : nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent));
122 1 : if (NS_FAILED(rv)) {
123 0 : return GenericPromise::CreateAndReject(rv, __func__);
124 : }
125 2 : nsAutoString parentLeafName;
126 1 : rv = parent->GetLeafName(parentLeafName);
127 1 : if (NS_FAILED(rv)) {
128 0 : return GenericPromise::CreateAndReject(rv, __func__);
129 : }
130 1 : LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get());
131 :
132 1 : MOZ_ASSERT(parentLeafName.Length() > 4);
133 1 : mName = Substring(parentLeafName, 4);
134 :
135 1 : return ReadGMPMetaData();
136 : }
137 :
138 : void
139 0 : GMPParent::Crash()
140 : {
141 0 : if (mState != GMPStateNotLoaded) {
142 0 : Unused << SendCrashPluginNow();
143 : }
144 0 : }
145 :
146 : nsresult
147 0 : GMPParent::LoadProcess()
148 : {
149 0 : MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
150 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
151 0 : MOZ_ASSERT(mState == GMPStateNotLoaded);
152 :
153 0 : nsAutoString path;
154 0 : if (NS_FAILED(mDirectory->GetPath(path))) {
155 0 : return NS_ERROR_FAILURE;
156 : }
157 0 : LOGD("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get());
158 :
159 0 : if (!mProcess) {
160 0 : mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
161 0 : if (!mProcess->Launch(30 * 1000)) {
162 0 : LOGD("%s: Failed to launch new child process", __FUNCTION__);
163 0 : mProcess->Delete();
164 0 : mProcess = nullptr;
165 0 : return NS_ERROR_FAILURE;
166 : }
167 :
168 0 : mChildPid = base::GetProcId(mProcess->GetChildProcessHandle());
169 0 : LOGD("%s: Launched new child process", __FUNCTION__);
170 :
171 0 : bool opened = Open(mProcess->GetChannel(),
172 0 : base::GetProcId(mProcess->GetChildProcessHandle()));
173 0 : if (!opened) {
174 0 : LOGD("%s: Failed to open channel to new child process", __FUNCTION__);
175 0 : mProcess->Delete();
176 0 : mProcess = nullptr;
177 0 : return NS_ERROR_FAILURE;
178 : }
179 0 : LOGD("%s: Opened channel to new child process", __FUNCTION__);
180 :
181 : #ifdef XP_WIN
182 : if (!mLibs.IsEmpty()) {
183 : bool ok = SendPreloadLibs(mLibs);
184 : if (!ok) {
185 : LOGD("%s: Failed to send preload-libs to child process", __FUNCTION__);
186 : return NS_ERROR_FAILURE;
187 : }
188 : LOGD("%s: Sent preload-libs ('%s') to child process", __FUNCTION__, mLibs.get());
189 : }
190 : #endif
191 :
192 : // Intr call to block initialization on plugin load.
193 0 : if (!CallStartPlugin(mAdapter)) {
194 0 : LOGD("%s: Failed to send start to child process", __FUNCTION__);
195 0 : return NS_ERROR_FAILURE;
196 : }
197 0 : LOGD("%s: Sent StartPlugin to child process", __FUNCTION__);
198 : }
199 :
200 0 : mState = GMPStateLoaded;
201 :
202 : // Hold a self ref while the child process is alive. This ensures that
203 : // during shutdown the GMPParent stays alive long enough to
204 : // terminate the child process.
205 0 : MOZ_ASSERT(!mHoldingSelfRef);
206 0 : mHoldingSelfRef = true;
207 0 : AddRef();
208 :
209 0 : return NS_OK;
210 : }
211 :
212 : mozilla::ipc::IPCResult
213 0 : GMPParent::RecvPGMPContentChildDestroyed()
214 : {
215 0 : --mGMPContentChildCount;
216 0 : if (!IsUsed()) {
217 0 : CloseIfUnused();
218 : }
219 0 : return IPC_OK();
220 : }
221 :
222 : void
223 0 : GMPParent::CloseIfUnused()
224 : {
225 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
226 0 : LOGD("%s", __FUNCTION__);
227 :
228 0 : if ((mDeleteProcessOnlyOnUnload ||
229 0 : mState == GMPStateLoaded ||
230 0 : mState == GMPStateUnloading) &&
231 0 : !IsUsed()) {
232 : // Ensure all timers are killed.
233 0 : for (uint32_t i = mTimers.Length(); i > 0; i--) {
234 0 : mTimers[i - 1]->Shutdown();
235 : }
236 :
237 : // Shutdown GMPStorage. Given that all protocol actors must be shutdown
238 : // (!Used() is true), all storage operations should be complete.
239 0 : for (size_t i = mStorage.Length(); i > 0; i--) {
240 0 : mStorage[i - 1]->Shutdown();
241 : }
242 0 : Shutdown();
243 : }
244 0 : }
245 :
246 : void
247 0 : GMPParent::CloseActive(bool aDieWhenUnloaded)
248 : {
249 0 : LOGD("%s: state %d", __FUNCTION__, mState);
250 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
251 :
252 0 : if (aDieWhenUnloaded) {
253 0 : mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
254 : }
255 0 : if (mState == GMPStateLoaded) {
256 0 : mState = GMPStateUnloading;
257 : }
258 0 : if (mState != GMPStateNotLoaded && IsUsed()) {
259 0 : Unused << SendCloseActive();
260 0 : CloseIfUnused();
261 : }
262 0 : }
263 :
264 : void
265 0 : GMPParent::MarkForDeletion()
266 : {
267 0 : mDeleteProcessOnlyOnUnload = true;
268 0 : mIsBlockingDeletion = true;
269 0 : }
270 :
271 : bool
272 0 : GMPParent::IsMarkedForDeletion()
273 : {
274 0 : return mIsBlockingDeletion;
275 : }
276 :
277 : void
278 0 : GMPParent::Shutdown()
279 : {
280 0 : LOGD("%s", __FUNCTION__);
281 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
282 :
283 0 : if (mAbnormalShutdownInProgress) {
284 0 : return;
285 : }
286 :
287 0 : MOZ_ASSERT(!IsUsed());
288 0 : if (mState == GMPStateNotLoaded || mState == GMPStateClosing) {
289 0 : return;
290 : }
291 :
292 0 : RefPtr<GMPParent> self(this);
293 0 : DeleteProcess();
294 :
295 : // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
296 : // Bug 1043671 is fixed
297 0 : if (!mDeleteProcessOnlyOnUnload) {
298 : // Destroy ourselves and rise from the fire to save memory
299 0 : mService->ReAddOnGMPThread(self);
300 : } // else we've been asked to die and stay dead
301 0 : MOZ_ASSERT(mState == GMPStateNotLoaded);
302 : }
303 :
304 0 : class NotifyGMPShutdownTask : public Runnable {
305 : public:
306 0 : explicit NotifyGMPShutdownTask(const nsAString& aNodeId)
307 0 : : Runnable("NotifyGMPShutdownTask")
308 0 : , mNodeId(aNodeId)
309 : {
310 0 : }
311 0 : NS_IMETHOD Run() override {
312 0 : MOZ_ASSERT(NS_IsMainThread());
313 0 : nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
314 0 : MOZ_ASSERT(obsService);
315 0 : if (obsService) {
316 0 : obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get());
317 : }
318 0 : return NS_OK;
319 : }
320 : nsString mNodeId;
321 : };
322 :
323 : void
324 0 : GMPParent::ChildTerminated()
325 : {
326 0 : RefPtr<GMPParent> self(this);
327 0 : nsCOMPtr<nsISerialEventTarget> gmpEventTarget = GMPEventTarget();
328 :
329 0 : if (!gmpEventTarget) {
330 : // Bug 1163239 - this can happen on shutdown.
331 : // PluginTerminated removes the GMP from the GMPService.
332 : // On shutdown we can have this case where it is already been
333 : // removed so there is no harm in not trying to remove it again.
334 0 : LOGD("%s::%s: GMPEventTarget() returned nullptr.", __CLASS__, __FUNCTION__);
335 : } else {
336 0 : gmpEventTarget->Dispatch(NewRunnableMethod<RefPtr<GMPParent>>(
337 : "gmp::GeckoMediaPluginServiceParent::PluginTerminated",
338 : mService,
339 : &GeckoMediaPluginServiceParent::PluginTerminated,
340 : self),
341 0 : NS_DISPATCH_NORMAL);
342 : }
343 0 : }
344 :
345 : void
346 0 : GMPParent::DeleteProcess()
347 : {
348 0 : LOGD("%s", __FUNCTION__);
349 :
350 0 : if (mState != GMPStateClosing) {
351 : // Don't Close() twice!
352 : // Probably remove when bug 1043671 is resolved
353 0 : mState = GMPStateClosing;
354 0 : Close();
355 : }
356 0 : mProcess->Delete(NewRunnableMethod(
357 0 : "gmp::GMPParent::ChildTerminated", this, &GMPParent::ChildTerminated));
358 0 : LOGD("%s: Shut down process", __FUNCTION__);
359 0 : mProcess = nullptr;
360 0 : mState = GMPStateNotLoaded;
361 :
362 : nsCOMPtr<nsIRunnable> r
363 0 : = new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId));
364 0 : mMainThread->Dispatch(r.forget());
365 :
366 0 : if (mHoldingSelfRef) {
367 0 : Release();
368 0 : mHoldingSelfRef = false;
369 : }
370 0 : }
371 :
372 : GMPState
373 0 : GMPParent::State() const
374 : {
375 0 : return mState;
376 : }
377 :
378 : nsCOMPtr<nsISerialEventTarget>
379 1 : GMPParent::GMPEventTarget()
380 : {
381 : nsCOMPtr<mozIGeckoMediaPluginService> mps =
382 2 : do_GetService("@mozilla.org/gecko-media-plugin-service;1");
383 1 : MOZ_ASSERT(mps);
384 1 : if (!mps) {
385 0 : return nullptr;
386 : }
387 : // Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
388 : // nullptr if the GeckoMediaPluginService has started shutdown.
389 2 : nsCOMPtr<nsIThread> gmpThread;
390 1 : mps->GetThread(getter_AddRefs(gmpThread));
391 1 : return gmpThread ? gmpThread->SerialEventTarget() : nullptr;
392 : }
393 :
394 : /* static */
395 : bool
396 0 : GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
397 : const nsCString& aAPI,
398 : const nsTArray<nsCString>& aTags)
399 : {
400 0 : for (const nsCString& tag : aTags) {
401 0 : if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) {
402 0 : return false;
403 : }
404 : }
405 0 : return true;
406 : }
407 :
408 : /* static */
409 : bool
410 0 : GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
411 : const nsCString& aAPI,
412 : const nsCString& aTag)
413 : {
414 0 : for (const GMPCapability& capabilities : aCapabilities) {
415 0 : if (!capabilities.mAPIName.Equals(aAPI)) {
416 0 : continue;
417 : }
418 0 : for (const nsCString& tag : capabilities.mAPITags) {
419 0 : if (tag.Equals(aTag)) {
420 : #ifdef XP_WIN
421 : // Clearkey on Windows advertises that it can decode in its GMP info
422 : // file, but uses Windows Media Foundation to decode. That's not present
423 : // on Windows XP, and on some Vista, Windows N, and KN variants without
424 : // certain services packs.
425 : if (tag.Equals(kEMEKeySystemClearkey)) {
426 : if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) {
427 : if (!WMFDecoderModule::HasH264()) {
428 : continue;
429 : }
430 : }
431 : }
432 : #endif
433 0 : return true;
434 : }
435 : }
436 : }
437 0 : return false;
438 : }
439 :
440 : bool
441 0 : GMPParent::EnsureProcessLoaded()
442 : {
443 0 : if (mState == GMPStateLoaded) {
444 0 : return true;
445 : }
446 0 : if (mState == GMPStateClosing ||
447 0 : mState == GMPStateUnloading) {
448 0 : return false;
449 : }
450 :
451 0 : nsresult rv = LoadProcess();
452 :
453 0 : return NS_SUCCEEDED(rv);
454 : }
455 :
456 : #ifdef MOZ_CRASHREPORTER
457 : void
458 0 : GMPParent::WriteExtraDataForMinidump()
459 : {
460 0 : mCrashReporter->AddNote(NS_LITERAL_CSTRING("GMPPlugin"), NS_LITERAL_CSTRING("1"));
461 0 : mCrashReporter->AddNote(NS_LITERAL_CSTRING("PluginFilename"), NS_ConvertUTF16toUTF8(mName));
462 0 : mCrashReporter->AddNote(NS_LITERAL_CSTRING("PluginName"), mDisplayName);
463 0 : mCrashReporter->AddNote(NS_LITERAL_CSTRING("PluginVersion"), mVersion);
464 0 : }
465 :
466 : bool
467 0 : GMPParent::GetCrashID(nsString& aResult)
468 : {
469 0 : if (!mCrashReporter) {
470 0 : return false;
471 : }
472 :
473 0 : WriteExtraDataForMinidump();
474 0 : if (!mCrashReporter->GenerateCrashReport(OtherPid())) {
475 0 : return false;
476 : }
477 :
478 0 : aResult = mCrashReporter->MinidumpID();
479 0 : return true;
480 : }
481 :
482 : static void
483 0 : GMPNotifyObservers(const uint32_t aPluginID, const nsACString& aPluginName, const nsAString& aPluginDumpID)
484 : {
485 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
486 : nsCOMPtr<nsIWritablePropertyBag2> propbag =
487 0 : do_CreateInstance("@mozilla.org/hash-property-bag;1");
488 0 : if (obs && propbag) {
489 0 : propbag->SetPropertyAsUint32(NS_LITERAL_STRING("pluginID"), aPluginID);
490 0 : propbag->SetPropertyAsACString(NS_LITERAL_STRING("pluginName"), aPluginName);
491 0 : propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), aPluginDumpID);
492 0 : obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr);
493 : }
494 :
495 : RefPtr<gmp::GeckoMediaPluginService> service =
496 0 : gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
497 0 : if (service) {
498 0 : service->RunPluginCrashCallbacks(aPluginID, aPluginName);
499 : }
500 0 : }
501 : #endif
502 : void
503 0 : GMPParent::ActorDestroy(ActorDestroyReason aWhy)
504 : {
505 0 : LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
506 : #ifdef MOZ_CRASHREPORTER
507 0 : if (AbnormalShutdown == aWhy) {
508 0 : Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT,
509 0 : NS_LITERAL_CSTRING("gmplugin"), 1);
510 0 : nsString dumpID;
511 0 : if (!GetCrashID(dumpID)) {
512 0 : NS_WARNING("GMP crash without crash report");
513 0 : dumpID = mName;
514 0 : dumpID += '-';
515 0 : AppendUTF8toUTF16(mVersion, dumpID);
516 : }
517 :
518 : // NotifyObservers is mainthread-only
519 0 : nsCOMPtr<nsIRunnable> r = WrapRunnableNM(
520 0 : &GMPNotifyObservers, mPluginId, mDisplayName, dumpID);
521 0 : mMainThread->Dispatch(r.forget());
522 : }
523 : #endif
524 : // warn us off trying to close again
525 0 : mState = GMPStateClosing;
526 0 : mAbnormalShutdownInProgress = true;
527 0 : CloseActive(false);
528 :
529 : // Normal Shutdown() will delete the process on unwind.
530 0 : if (AbnormalShutdown == aWhy) {
531 0 : RefPtr<GMPParent> self(this);
532 : // Must not call Close() again in DeleteProcess(), as we'll recurse
533 : // infinitely if we do.
534 0 : MOZ_ASSERT(mState == GMPStateClosing);
535 0 : DeleteProcess();
536 : // Note: final destruction will be Dispatched to ourself
537 0 : mService->ReAddOnGMPThread(self);
538 : }
539 0 : }
540 :
541 : mozilla::ipc::IPCResult
542 0 : GMPParent::RecvInitCrashReporter(Shmem&& aShmem, const NativeThreadId& aThreadId)
543 : {
544 : #ifdef MOZ_CRASHREPORTER
545 0 : mCrashReporter = MakeUnique<ipc::CrashReporterHost>(
546 : GeckoProcessType_GMPlugin,
547 : aShmem,
548 0 : aThreadId);
549 : #endif
550 0 : return IPC_OK();
551 : }
552 :
553 : PGMPStorageParent*
554 0 : GMPParent::AllocPGMPStorageParent()
555 : {
556 0 : GMPStorageParent* p = new GMPStorageParent(mNodeId, this);
557 0 : mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
558 0 : return p;
559 : }
560 :
561 : bool
562 0 : GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
563 : {
564 0 : GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
565 0 : p->Shutdown();
566 0 : mStorage.RemoveElement(p);
567 0 : return true;
568 : }
569 :
570 : mozilla::ipc::IPCResult
571 0 : GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor)
572 : {
573 0 : GMPStorageParent* p = (GMPStorageParent*)aActor;
574 0 : if (NS_WARN_IF(NS_FAILED(p->Init()))) {
575 0 : return IPC_FAIL_NO_REASON(this);
576 : }
577 0 : return IPC_OK();
578 : }
579 :
580 : mozilla::ipc::IPCResult
581 0 : GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
582 : {
583 0 : return IPC_OK();
584 : }
585 :
586 : PGMPTimerParent*
587 0 : GMPParent::AllocPGMPTimerParent()
588 : {
589 0 : nsCOMPtr<nsISerialEventTarget> target = GMPEventTarget();
590 0 : GMPTimerParent* p = new GMPTimerParent(target);
591 0 : mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
592 0 : return p;
593 : }
594 :
595 : bool
596 0 : GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
597 : {
598 0 : GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
599 0 : p->Shutdown();
600 0 : mTimers.RemoveElement(p);
601 0 : return true;
602 : }
603 :
604 : bool
605 0 : ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOutValue)
606 : {
607 0 : if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) {
608 0 : return false;
609 : }
610 0 : aOutValue = aParser.Get(aKey);
611 0 : return true;
612 : }
613 :
614 : RefPtr<GenericPromise>
615 1 : GMPParent::ReadGMPMetaData()
616 : {
617 1 : MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
618 1 : MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!");
619 :
620 2 : nsCOMPtr<nsIFile> infoFile;
621 1 : nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
622 1 : if (NS_FAILED(rv)) {
623 0 : return GenericPromise::CreateAndReject(rv, __func__);
624 : }
625 1 : infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info"));
626 :
627 1 : if (FileExists(infoFile)) {
628 0 : return ReadGMPInfoFile(infoFile);
629 : }
630 :
631 : // Maybe this is the Widevine adapted plugin?
632 2 : nsCOMPtr<nsIFile> manifestFile;
633 1 : rv = mDirectory->Clone(getter_AddRefs(manifestFile));
634 1 : if (NS_FAILED(rv)) {
635 0 : return GenericPromise::CreateAndReject(rv, __func__);
636 : }
637 1 : manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json"));
638 1 : return ReadChromiumManifestFile(manifestFile);
639 : }
640 :
641 : RefPtr<GenericPromise>
642 0 : GMPParent::ReadGMPInfoFile(nsIFile* aFile)
643 : {
644 0 : GMPInfoFileParser parser;
645 0 : if (!parser.Init(aFile)) {
646 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
647 : }
648 :
649 0 : nsAutoCString apis;
650 0 : if (!ReadInfoField(parser, NS_LITERAL_CSTRING("name"), mDisplayName) ||
651 0 : !ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) ||
652 0 : !ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) ||
653 0 : !ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) {
654 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
655 : }
656 :
657 : #ifdef XP_WIN
658 : // "Libraries" field is optional.
659 : ReadInfoField(parser, NS_LITERAL_CSTRING("libraries"), mLibs);
660 : #endif
661 :
662 0 : nsTArray<nsCString> apiTokens;
663 0 : SplitAt(", ", apis, apiTokens);
664 0 : for (nsCString api : apiTokens) {
665 0 : int32_t tagsStart = api.FindChar('[');
666 0 : if (tagsStart == 0) {
667 : // Not allowed to be the first character.
668 : // API name must be at least one character.
669 0 : continue;
670 : }
671 :
672 0 : GMPCapability cap;
673 0 : if (tagsStart == -1) {
674 : // No tags.
675 0 : cap.mAPIName.Assign(api);
676 : } else {
677 0 : auto tagsEnd = api.FindChar(']');
678 0 : if (tagsEnd == -1 || tagsEnd < tagsStart) {
679 : // Invalid syntax, skip whole capability.
680 0 : continue;
681 : }
682 :
683 0 : cap.mAPIName.Assign(Substring(api, 0, tagsStart));
684 :
685 0 : if ((tagsEnd - tagsStart) > 1) {
686 0 : const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
687 0 : nsTArray<nsCString> tagTokens;
688 0 : SplitAt(":", ts, tagTokens);
689 0 : for (nsCString tag : tagTokens) {
690 0 : cap.mAPITags.AppendElement(tag);
691 : }
692 : }
693 : }
694 :
695 0 : if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
696 0 : mCanDecrypt = true;
697 :
698 : #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
699 : if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
700 : nsPrintfCString msg(
701 : "GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
702 : " but this system can't sandbox it; not loading.",
703 : mDisplayName.get());
704 : printf_stderr("%s\n", msg.get());
705 : LOGD("%s", msg.get());
706 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
707 : }
708 : #endif
709 : }
710 :
711 0 : mCapabilities.AppendElement(Move(cap));
712 : }
713 :
714 0 : if (mCapabilities.IsEmpty()) {
715 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
716 : }
717 :
718 0 : return GenericPromise::CreateAndResolve(true, __func__);
719 : }
720 :
721 : RefPtr<GenericPromise>
722 1 : GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
723 : {
724 2 : nsAutoCString json;
725 1 : if (!ReadIntoString(aFile, json, 5 * 1024)) {
726 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
727 : }
728 :
729 : // DOM JSON parsing needs to run on the main thread.
730 : return InvokeAsync(
731 : mMainThread, this, __func__,
732 1 : &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
733 : }
734 :
735 : static bool
736 1 : IsCDMAPISupported(const mozilla::dom::WidevineCDMManifest& aManifest)
737 : {
738 : nsresult ignored; // Note: ToInteger returns 0 on failure.
739 1 : int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored);
740 : int32_t interfaceVersion =
741 1 : aManifest.mX_cdm_interface_versions.ToInteger(&ignored);
742 1 : int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored);
743 1 : if (MediaPrefs::EMEChromiumAPIEnabled()) {
744 : return ChromiumCDMAdapter::Supports(
745 1 : moduleVersion, interfaceVersion, hostVersion);
746 : }
747 : return WidevineAdapter::Supports(
748 0 : moduleVersion, interfaceVersion, hostVersion);
749 : }
750 :
751 : RefPtr<GenericPromise>
752 1 : GMPParent::ParseChromiumManifest(const nsAString& aJSON)
753 : {
754 1 : LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
755 :
756 1 : MOZ_ASSERT(NS_IsMainThread());
757 2 : mozilla::dom::WidevineCDMManifest m;
758 1 : if (!m.Init(aJSON)) {
759 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
760 : }
761 :
762 1 : if (!IsCDMAPISupported(m)) {
763 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
764 : }
765 :
766 1 : mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
767 1 : mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
768 1 : mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
769 :
770 : #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
771 : if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
772 : nsPrintfCString msg(
773 : "GMPParent::ParseChromiumManifest: Plugin \"%s\" is an EME CDM"
774 : " but this system can't sandbox it; not loading.",
775 : mDisplayName.get());
776 : printf_stderr("%s\n", msg.get());
777 : LOGD("%s", msg.get());
778 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
779 : }
780 : #endif
781 :
782 2 : nsCString kEMEKeySystem;
783 :
784 : // We hard code a few of the settings because they can't be stored in the
785 : // widevine manifest without making our API different to widevine's.
786 1 : if (mDisplayName.EqualsASCII("clearkey")) {
787 1 : kEMEKeySystem = kEMEKeySystemClearkey;
788 : #if XP_WIN
789 : mLibs = NS_LITERAL_CSTRING("dxva2.dll, msmpeg2vdec.dll, evr.dll, mfh264dec.dll, mfplat.dll");
790 : #endif
791 0 : } else if (mDisplayName.EqualsASCII("WidevineCdm")) {
792 0 : kEMEKeySystem = kEMEKeySystemWidevine;
793 : #if XP_WIN
794 : mLibs = NS_LITERAL_CSTRING("dxva2.dll");
795 : #endif
796 : } else {
797 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
798 : }
799 :
800 2 : GMPCapability video;
801 :
802 2 : nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
803 2 : nsTArray<nsCString> codecs;
804 1 : SplitAt(",", codecsString, codecs);
805 :
806 1 : for (const nsCString& chromiumCodec : codecs) {
807 0 : nsCString codec;
808 0 : if (chromiumCodec.EqualsASCII("vp8")) {
809 0 : codec = NS_LITERAL_CSTRING("vp8");
810 0 : } else if (chromiumCodec.EqualsASCII("vp9.0")) {
811 0 : codec = NS_LITERAL_CSTRING("vp9");
812 0 : } else if (chromiumCodec.EqualsASCII("avc1")) {
813 0 : codec = NS_LITERAL_CSTRING("h264");
814 : } else {
815 0 : return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
816 : }
817 :
818 0 : video.mAPITags.AppendElement(codec);
819 : }
820 :
821 1 : video.mAPITags.AppendElement(kEMEKeySystem);
822 :
823 1 : if (MediaPrefs::EMEChromiumAPIEnabled()) {
824 1 : video.mAPIName = NS_LITERAL_CSTRING(CHROMIUM_CDM_API);
825 1 : mAdapter = NS_LITERAL_STRING("chromium");
826 : } else {
827 0 : video.mAPIName = NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
828 0 : mAdapter = NS_LITERAL_STRING("widevine");
829 :
830 0 : GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
831 0 : decrypt.mAPITags.AppendElement(kEMEKeySystem);
832 0 : mCapabilities.AppendElement(Move(decrypt));
833 : }
834 1 : mCapabilities.AppendElement(Move(video));
835 :
836 1 : return GenericPromise::CreateAndResolve(true, __func__);
837 : }
838 :
839 : bool
840 0 : GMPParent::CanBeSharedCrossNodeIds() const
841 : {
842 0 : return mNodeId.IsEmpty() &&
843 : // XXX bug 1159300 hack -- maybe remove after openh264 1.4
844 : // We don't want to use CDM decoders for non-encrypted playback
845 : // just yet; especially not for WebRTC. Don't allow CDMs to be used
846 : // without a node ID.
847 0 : !mCanDecrypt;
848 : }
849 :
850 : bool
851 0 : GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const
852 : {
853 0 : return mNodeId == aNodeId;
854 : }
855 :
856 : void
857 0 : GMPParent::SetNodeId(const nsACString& aNodeId)
858 : {
859 0 : MOZ_ASSERT(!aNodeId.IsEmpty());
860 0 : mNodeId = aNodeId;
861 0 : }
862 :
863 : const nsCString&
864 0 : GMPParent::GetDisplayName() const
865 : {
866 0 : return mDisplayName;
867 : }
868 :
869 : const nsCString&
870 3 : GMPParent::GetVersion() const
871 : {
872 3 : return mVersion;
873 : }
874 :
875 : uint32_t
876 0 : GMPParent::GetPluginId() const
877 : {
878 0 : return mPluginId;
879 : }
880 :
881 : void
882 0 : GMPParent::ResolveGetContentParentPromises()
883 : {
884 0 : nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises;
885 0 : promises.SwapElements(mGetContentParentPromises);
886 0 : MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
887 0 : RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(mGMPContentParent));
888 0 : for (auto& holder : promises) {
889 0 : holder->Resolve(blocker, __func__);
890 : }
891 0 : }
892 :
893 : bool
894 0 : GMPParent::OpenPGMPContent()
895 : {
896 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
897 0 : MOZ_ASSERT(!mGMPContentParent);
898 :
899 0 : Endpoint<PGMPContentParent> parent;
900 0 : Endpoint<PGMPContentChild> child;
901 0 : if (NS_FAILED(PGMPContent::CreateEndpoints(base::GetCurrentProcId(),
902 : OtherPid(), &parent, &child))) {
903 0 : return false;
904 : }
905 :
906 0 : mGMPContentParent = new GMPContentParent(this);
907 :
908 0 : if (!parent.Bind(mGMPContentParent)) {
909 0 : return false;
910 : }
911 :
912 0 : if (!SendInitGMPContentChild(Move(child))) {
913 0 : return false;
914 : }
915 :
916 0 : ResolveGetContentParentPromises();
917 :
918 0 : return true;
919 : }
920 :
921 : void
922 0 : GMPParent::RejectGetContentParentPromises()
923 : {
924 0 : nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises;
925 0 : promises.SwapElements(mGetContentParentPromises);
926 0 : MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
927 0 : for (auto& holder : promises) {
928 0 : holder->Reject(NS_ERROR_FAILURE, __func__);
929 : }
930 0 : }
931 :
932 : void
933 0 : GMPParent::GetGMPContentParent(UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>&& aPromiseHolder)
934 : {
935 0 : LOGD("%s %p", __FUNCTION__, this);
936 0 : MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
937 :
938 0 : if (mGMPContentParent) {
939 0 : RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(mGMPContentParent));
940 0 : aPromiseHolder->Resolve(blocker, __func__);
941 : } else {
942 0 : mGetContentParentPromises.AppendElement(Move(aPromiseHolder));
943 : // If we don't have a GMPContentParent and we try to get one for the first
944 : // time (mGetContentParentPromises.Length() == 1) then call PGMPContent::Open. If more
945 : // calls to GetGMPContentParent happen before mGMPContentParent has been
946 : // set then we should just store them, so that they get called when we set
947 : // mGMPContentParent as a result of the PGMPContent::Open call.
948 0 : if (mGetContentParentPromises.Length() == 1) {
949 0 : if (!EnsureProcessLoaded() || !OpenPGMPContent()) {
950 0 : RejectGetContentParentPromises();
951 0 : return;
952 : }
953 : // We want to increment this as soon as possible, to avoid that we'd try
954 : // to shut down the GMP process while we're still trying to get a
955 : // PGMPContentParent actor.
956 0 : ++mGMPContentChildCount;
957 : }
958 : }
959 : }
960 :
961 : already_AddRefed<GMPContentParent>
962 0 : GMPParent::ForgetGMPContentParent()
963 : {
964 0 : MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
965 0 : return Move(mGMPContentParent.forget());
966 : }
967 :
968 : bool
969 0 : GMPParent::EnsureProcessLoaded(base::ProcessId* aID)
970 : {
971 0 : if (!EnsureProcessLoaded()) {
972 0 : return false;
973 : }
974 0 : *aID = OtherPid();
975 0 : return true;
976 : }
977 :
978 : void
979 0 : GMPParent::IncrementGMPContentChildCount()
980 : {
981 0 : ++mGMPContentChildCount;
982 0 : }
983 :
984 : nsString
985 3 : GMPParent::GetPluginBaseName() const
986 : {
987 3 : return NS_LITERAL_STRING("gmp-") + mName;
988 : }
989 :
990 : } // namespace gmp
991 : } // namespace mozilla
992 :
993 : #undef LOG
994 : #undef LOGD
|