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