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 "GMPService.h"
7 
8 #include "ChromiumCDMParent.h"
9 #include "GMPLog.h"
10 #include "GMPParent.h"
11 #include "GMPProcessParent.h"
12 #include "GMPServiceChild.h"
13 #include "GMPServiceParent.h"
14 #include "GMPVideoDecoderParent.h"
15 #include "mozilla/ipc/GeckoChildProcessHost.h"
16 #include "mozilla/ClearOnShutdown.h"
17 #include "mozilla/EventDispatcher.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/PluginCrashedEvent.h"
20 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
21 #  include "mozilla/SandboxInfo.h"
22 #endif
23 #include "VideoUtils.h"
24 #include "mozilla/Services.h"
25 #include "mozilla/SyncRunnable.h"
26 #include "mozilla/Unused.h"
27 #include "nsAppDirectoryServiceDefs.h"
28 #include "nsComponentManagerUtils.h"
29 #include "nsDirectoryServiceDefs.h"
30 #include "nsDirectoryServiceUtils.h"
31 #include "nsHashKeys.h"
32 #include "nsIObserverService.h"
33 #include "nsIXULAppInfo.h"
34 #include "nsNativeCharsetUtils.h"
35 #include "nsXPCOMPrivate.h"
36 #include "prio.h"
37 #include "runnable_utils.h"
38 
39 namespace mozilla {
40 
GetGMPLog()41 LogModule* GetGMPLog() {
42   static LazyLogModule sLog("GMP");
43   return sLog;
44 }
45 
46 #ifdef __CLASS__
47 #  undef __CLASS__
48 #endif
49 #define __CLASS__ "GMPService"
50 
51 namespace gmp {
52 
53 static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
54 
55 class GMPServiceCreateHelper final : public mozilla::Runnable {
56   RefPtr<GeckoMediaPluginService> mService;
57 
58  public:
GetOrCreate()59   static already_AddRefed<GeckoMediaPluginService> GetOrCreate() {
60     RefPtr<GeckoMediaPluginService> service;
61 
62     if (NS_IsMainThread()) {
63       service = GetOrCreateOnMainThread();
64     } else {
65       RefPtr<GMPServiceCreateHelper> createHelper =
66           new GMPServiceCreateHelper();
67 
68       mozilla::SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(),
69                                               createHelper, true);
70 
71       service = std::move(createHelper->mService);
72     }
73 
74     return service.forget();
75   }
76 
77  private:
GMPServiceCreateHelper()78   GMPServiceCreateHelper() : Runnable("GMPServiceCreateHelper") {}
79 
~GMPServiceCreateHelper()80   ~GMPServiceCreateHelper() { MOZ_ASSERT(!mService); }
81 
GetOrCreateOnMainThread()82   static already_AddRefed<GeckoMediaPluginService> GetOrCreateOnMainThread() {
83     MOZ_ASSERT(NS_IsMainThread());
84 
85     if (!sSingletonService) {
86       if (XRE_IsParentProcess()) {
87         RefPtr<GeckoMediaPluginServiceParent> service =
88             new GeckoMediaPluginServiceParent();
89         service->Init();
90         sSingletonService = service;
91 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
92         // GMPProcessParent should only be instantiated in the parent
93         // so initialization only needs to be done in the parent.
94         GMPProcessParent::InitStaticMainThread();
95 #endif
96       } else {
97         RefPtr<GeckoMediaPluginServiceChild> service =
98             new GeckoMediaPluginServiceChild();
99         service->Init();
100         sSingletonService = service;
101       }
102       ClearOnShutdown(&sSingletonService);
103     }
104 
105     RefPtr<GeckoMediaPluginService> service = sSingletonService.get();
106     return service.forget();
107   }
108 
109   NS_IMETHOD
Run()110   Run() override {
111     MOZ_ASSERT(NS_IsMainThread());
112 
113     mService = GetOrCreateOnMainThread();
114     return NS_OK;
115   }
116 };
117 
118 already_AddRefed<GeckoMediaPluginService>
GetGeckoMediaPluginService()119 GeckoMediaPluginService::GetGeckoMediaPluginService() {
120   return GMPServiceCreateHelper::GetOrCreate();
121 }
122 
NS_IMPL_ISUPPORTS(GeckoMediaPluginService,mozIGeckoMediaPluginService,nsIObserver)123 NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService,
124                   nsIObserver)
125 
126 GeckoMediaPluginService::GeckoMediaPluginService()
127     : mMutex("GeckoMediaPluginService::mMutex"),
128       mMainThread(GetMainThreadSerialEventTarget()),
129       mGMPThreadShutdown(false),
130       mShuttingDownOnGMPThread(false),
131       mXPCOMWillShutdown(false) {
132   MOZ_ASSERT(NS_IsMainThread());
133 
134   nsCOMPtr<nsIXULAppInfo> appInfo =
135       do_GetService("@mozilla.org/xre/app-info;1");
136   if (appInfo) {
137     nsAutoCString version;
138     nsAutoCString buildID;
139     if (NS_SUCCEEDED(appInfo->GetVersion(version)) &&
140         NS_SUCCEEDED(appInfo->GetAppBuildID(buildID))) {
141       GMP_LOG_DEBUG(
142           "GeckoMediaPluginService created; Gecko version=%s buildID=%s",
143           version.get(), buildID.get());
144     }
145   }
146 }
147 
148 GeckoMediaPluginService::~GeckoMediaPluginService() = default;
149 
150 NS_IMETHODIMP
RunPluginCrashCallbacks(uint32_t aPluginId,const nsACString & aPluginName)151 GeckoMediaPluginService::RunPluginCrashCallbacks(
152     uint32_t aPluginId, const nsACString& aPluginName) {
153   MOZ_ASSERT(NS_IsMainThread());
154   GMP_LOG_DEBUG("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId);
155 
156   mozilla::UniquePtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers;
157   {
158     MutexAutoLock lock(mMutex);
159     mPluginCrashHelpers.Remove(aPluginId, &helpers);
160   }
161   if (!helpers) {
162     GMP_LOG_DEBUG("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__,
163                   __FUNCTION__, aPluginId);
164     return NS_OK;
165   }
166 
167   for (const auto& helper : *helpers) {
168     nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget();
169     if (NS_WARN_IF(!window)) {
170       continue;
171     }
172     RefPtr<dom::Document> document(window->GetExtantDoc());
173     if (NS_WARN_IF(!document)) {
174       continue;
175     }
176 
177     dom::PluginCrashedEventInit init;
178     init.mPluginID = aPluginId;
179     init.mBubbles = true;
180     init.mCancelable = true;
181     init.mGmpPlugin = true;
182     CopyUTF8toUTF16(aPluginName, init.mPluginName);
183     init.mSubmittedCrashReport = false;
184     RefPtr<dom::PluginCrashedEvent> event =
185         dom::PluginCrashedEvent::Constructor(document, u"PluginCrashed"_ns,
186                                              init);
187     event->SetTrusted(true);
188     event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
189 
190     EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr);
191   }
192 
193   return NS_OK;
194 }
195 
Init()196 nsresult GeckoMediaPluginService::Init() {
197   MOZ_ASSERT(NS_IsMainThread());
198 
199   nsCOMPtr<nsIObserverService> obsService =
200       mozilla::services::GetObserverService();
201   MOZ_ASSERT(obsService);
202   MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(
203       this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false));
204 
205   // Kick off scanning for plugins
206   nsCOMPtr<nsIThread> thread;
207   return GetThread(getter_AddRefs(thread));
208 }
209 
GetCDM(const NodeIdParts & aNodeIdParts,nsTArray<nsCString> aTags,GMPCrashHelper * aHelper)210 RefPtr<GetCDMParentPromise> GeckoMediaPluginService::GetCDM(
211     const NodeIdParts& aNodeIdParts, nsTArray<nsCString> aTags,
212     GMPCrashHelper* aHelper) {
213   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
214 
215   if (mShuttingDownOnGMPThread || aTags.IsEmpty()) {
216     nsPrintfCString reason(
217         "%s::%s failed, aTags.IsEmpty() = %d, mShuttingDownOnGMPThread = %d.",
218         __CLASS__, __FUNCTION__, aTags.IsEmpty(), mShuttingDownOnGMPThread);
219     return GetCDMParentPromise::CreateAndReject(
220         MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
221   }
222 
223   typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder;
224   PromiseHolder* rawHolder(new PromiseHolder());
225   RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__);
226   nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
227   RefPtr<GMPCrashHelper> helper(aHelper);
228   GetContentParent(aHelper, NodeIdVariant{aNodeIdParts},
229                    nsLiteralCString(CHROMIUM_CDM_API), aTags)
230       ->Then(
231           thread, __func__,
232           [rawHolder, helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
233             RefPtr<GMPContentParent> parent = wrapper->mParent;
234             MOZ_ASSERT(
235                 parent,
236                 "Wrapper should wrap a valid parent if we're in this path.");
237             UniquePtr<PromiseHolder> holder(rawHolder);
238             RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM();
239             if (!cdm) {
240               nsPrintfCString reason(
241                   "%s::%s failed since GetChromiumCDM returns nullptr.",
242                   __CLASS__, __FUNCTION__);
243               holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
244                              __func__);
245               return;
246             }
247             if (helper) {
248               cdm->SetCrashHelper(helper);
249             }
250             holder->Resolve(cdm, __func__);
251           },
252           [rawHolder](MediaResult result) {
253             nsPrintfCString reason(
254                 "%s::%s failed since GetContentParent rejects the promise with "
255                 "reason %s.",
256                 __CLASS__, __FUNCTION__, result.Description().get());
257             UniquePtr<PromiseHolder> holder(rawHolder);
258             holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
259                            __func__);
260           });
261 
262   return promise;
263 }
264 
265 #if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
266 RefPtr<GetGMPContentParentPromise>
GetContentParentForTest()267 GeckoMediaPluginService::GetContentParentForTest() {
268   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
269 
270   nsTArray<nsCString> tags;
271   tags.AppendElement("fake"_ns);
272 
273   const nsString origin1 = u"http://example1.com"_ns;
274   const nsString origin2 = u"http://example2.org"_ns;
275   const nsString gmpName = u"gmp-fake"_ns;
276 
277   NodeIdParts nodeIdParts = NodeIdParts{origin1, origin2, gmpName};
278 
279   if (mShuttingDownOnGMPThread) {
280     nsPrintfCString reason("%s::%s failed, mShuttingDownOnGMPThread = %d.",
281                            __CLASS__, __FUNCTION__, mShuttingDownOnGMPThread);
282     return GetGMPContentParentPromise::CreateAndReject(
283         MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
284   }
285 
286   using PromiseHolder = MozPromiseHolder<GetGMPContentParentPromise>;
287   PromiseHolder* rawHolder(new PromiseHolder());
288   RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
289   nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
290   GetContentParent(nullptr, NodeIdVariant{nodeIdParts},
291                    nsLiteralCString(CHROMIUM_CDM_API), tags)
292       ->Then(
293           thread, __func__,
294           [rawHolder](const RefPtr<GMPContentParent::CloseBlocker>& wrapper) {
295             RefPtr<GMPContentParent> parent = wrapper->mParent;
296             MOZ_ASSERT(
297                 parent,
298                 "Wrapper should wrap a valid parent if we're in this path.");
299             UniquePtr<PromiseHolder> holder(rawHolder);
300             if (!parent) {
301               nsPrintfCString reason("%s::%s failed since no GMPContentParent.",
302                                      __CLASS__, __FUNCTION__);
303               holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
304                              __func__);
305               return;
306             }
307             holder->Resolve(wrapper, __func__);
308           },
309           [rawHolder](const MediaResult& result) {
310             nsPrintfCString reason(
311                 "%s::%s failed since GetContentParent rejects the promise with "
312                 "reason %s.",
313                 __CLASS__, __FUNCTION__, result.Description().get());
314             UniquePtr<PromiseHolder> holder(rawHolder);
315             holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
316                            __func__);
317           });
318 
319   return promise;
320 }
321 #endif
322 
ShutdownGMPThread()323 void GeckoMediaPluginService::ShutdownGMPThread() {
324   GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
325   nsCOMPtr<nsIThread> gmpThread;
326   {
327     MutexAutoLock lock(mMutex);
328     mGMPThreadShutdown = true;
329     mGMPThread.swap(gmpThread);
330   }
331 
332   if (gmpThread) {
333     gmpThread->Shutdown();
334   }
335 }
336 
337 /* static */
GetShutdownBarrier()338 nsCOMPtr<nsIAsyncShutdownClient> GeckoMediaPluginService::GetShutdownBarrier() {
339   nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
340   MOZ_RELEASE_ASSERT(svc);
341 
342   nsCOMPtr<nsIAsyncShutdownClient> barrier;
343   nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
344 
345   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
346   MOZ_RELEASE_ASSERT(barrier);
347   return barrier;
348 }
349 
GMPDispatch(nsIRunnable * event,uint32_t flags)350 nsresult GeckoMediaPluginService::GMPDispatch(nsIRunnable* event,
351                                               uint32_t flags) {
352   nsCOMPtr<nsIRunnable> r(event);
353   return GMPDispatch(r.forget());
354 }
355 
GMPDispatch(already_AddRefed<nsIRunnable> event,uint32_t flags)356 nsresult GeckoMediaPluginService::GMPDispatch(
357     already_AddRefed<nsIRunnable> event, uint32_t flags) {
358   nsCOMPtr<nsIRunnable> r(event);
359   nsCOMPtr<nsIThread> thread;
360   nsresult rv = GetThread(getter_AddRefs(thread));
361   if (NS_WARN_IF(NS_FAILED(rv))) {
362     return rv;
363   }
364   return thread->Dispatch(r, flags);
365 }
366 
367 // always call with getter_AddRefs, because it does
368 NS_IMETHODIMP
GetThread(nsIThread ** aThread)369 GeckoMediaPluginService::GetThread(nsIThread** aThread) {
370   MOZ_ASSERT(aThread);
371 
372   // This can be called from any thread.
373   MutexAutoLock lock(mMutex);
374 
375   if (!mGMPThread) {
376     // Don't allow the thread to be created after shutdown has started.
377     if (mGMPThreadShutdown) {
378       return NS_ERROR_FAILURE;
379     }
380 
381     nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread));
382     if (NS_WARN_IF(NS_FAILED(rv))) {
383       return rv;
384     }
385 
386     // Tell the thread to initialize plugins
387     InitializePlugins(mGMPThread);
388   }
389 
390   nsCOMPtr<nsIThread> copy = mGMPThread;
391   copy.forget(aThread);
392 
393   return NS_OK;
394 }
395 
GetGMPThread()396 already_AddRefed<nsISerialEventTarget> GeckoMediaPluginService::GetGMPThread() {
397   nsCOMPtr<nsISerialEventTarget> thread;
398   {
399     MutexAutoLock lock(mMutex);
400     thread = mGMPThread;
401   }
402   return thread.forget();
403 }
404 
405 NS_IMETHODIMP
GetDecryptingGMPVideoDecoder(GMPCrashHelper * aHelper,nsTArray<nsCString> * aTags,const nsACString & aNodeId,UniquePtr<GetGMPVideoDecoderCallback> && aCallback,uint32_t aDecryptorId)406 GeckoMediaPluginService::GetDecryptingGMPVideoDecoder(
407     GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
408     const nsACString& aNodeId,
409     UniquePtr<GetGMPVideoDecoderCallback>&& aCallback, uint32_t aDecryptorId) {
410   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
411   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
412   NS_ENSURE_ARG(aCallback);
413 
414   if (mShuttingDownOnGMPThread) {
415     return NS_ERROR_FAILURE;
416   }
417 
418   GetGMPVideoDecoderCallback* rawCallback = aCallback.release();
419   nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
420   RefPtr<GMPCrashHelper> helper(aHelper);
421   GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)},
422                    nsLiteralCString(GMP_API_VIDEO_DECODER), *aTags)
423       ->Then(
424           thread, __func__,
425           [rawCallback, helper,
426            aDecryptorId](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
427             RefPtr<GMPContentParent> parent = wrapper->mParent;
428             UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback);
429             GMPVideoDecoderParent* actor = nullptr;
430             GMPVideoHostImpl* host = nullptr;
431             if (parent && NS_SUCCEEDED(parent->GetGMPVideoDecoder(
432                               &actor, aDecryptorId))) {
433               host = &(actor->Host());
434               actor->SetCrashHelper(helper);
435             }
436             callback->Done(actor, host);
437           },
438           [rawCallback] {
439             UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback);
440             callback->Done(nullptr, nullptr);
441           });
442 
443   return NS_OK;
444 }
445 
446 NS_IMETHODIMP
GetGMPVideoEncoder(GMPCrashHelper * aHelper,nsTArray<nsCString> * aTags,const nsACString & aNodeId,UniquePtr<GetGMPVideoEncoderCallback> && aCallback)447 GeckoMediaPluginService::GetGMPVideoEncoder(
448     GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
449     const nsACString& aNodeId,
450     UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) {
451   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
452   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
453   NS_ENSURE_ARG(aCallback);
454 
455   if (mShuttingDownOnGMPThread) {
456     return NS_ERROR_FAILURE;
457   }
458 
459   GetGMPVideoEncoderCallback* rawCallback = aCallback.release();
460   nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
461   RefPtr<GMPCrashHelper> helper(aHelper);
462   GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)},
463                    nsLiteralCString(GMP_API_VIDEO_ENCODER), *aTags)
464       ->Then(
465           thread, __func__,
466           [rawCallback,
467            helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
468             RefPtr<GMPContentParent> parent = wrapper->mParent;
469             UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback);
470             GMPVideoEncoderParent* actor = nullptr;
471             GMPVideoHostImpl* host = nullptr;
472             if (parent && NS_SUCCEEDED(parent->GetGMPVideoEncoder(&actor))) {
473               host = &(actor->Host());
474               actor->SetCrashHelper(helper);
475             }
476             callback->Done(actor, host);
477           },
478           [rawCallback] {
479             UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback);
480             callback->Done(nullptr, nullptr);
481           });
482 
483   return NS_OK;
484 }
485 
ConnectCrashHelper(uint32_t aPluginId,GMPCrashHelper * aHelper)486 void GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId,
487                                                  GMPCrashHelper* aHelper) {
488   if (!aHelper) {
489     return;
490   }
491 
492   MutexAutoLock lock(mMutex);
493   mPluginCrashHelpers.WithEntryHandle(aPluginId, [&](auto&& entry) {
494     if (!entry) {
495       entry.Insert(MakeUnique<nsTArray<RefPtr<GMPCrashHelper>>>());
496     } else if (entry.Data()->Contains(aHelper)) {
497       return;
498     }
499     entry.Data()->AppendElement(aHelper);
500   });
501 }
502 
DisconnectCrashHelper(GMPCrashHelper * aHelper)503 void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper) {
504   if (!aHelper) {
505     return;
506   }
507   MutexAutoLock lock(mMutex);
508   for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) {
509     nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.UserData();
510     if (!helpers->Contains(aHelper)) {
511       continue;
512     }
513     helpers->RemoveElement(aHelper);
514     MOZ_ASSERT(!helpers->Contains(aHelper));  // Ensure there aren't duplicates.
515     if (helpers->IsEmpty()) {
516       iter.Remove();
517     }
518   }
519 }
520 
521 }  // namespace gmp
522 }  // namespace mozilla
523 
524 #undef __CLASS__
525