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 "GMPServiceParent.h"
7
8 #include <limits>
9
10 #include "base/task.h"
11 #include "GeckoChildProcessHost.h"
12 #include "GMPDecoderModule.h"
13 #include "GMPLog.h"
14 #include "GMPParent.h"
15 #include "GMPVideoDecoderParent.h"
16 #include "mozilla/AbstractThread.h"
17 #include "mozilla/ClearOnShutdown.h"
18 #include "mozilla/dom/ContentParent.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/Preferences.h"
21 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
22 # include "mozilla/SandboxInfo.h"
23 #endif
24 #include "mozilla/Services.h"
25 #include "mozilla/StaticPrefs_media.h"
26 #include "mozilla/SyncRunnable.h"
27 #include "mozilla/Unused.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "nsComponentManagerUtils.h"
30 #include "nsDirectoryServiceDefs.h"
31 #include "nsDirectoryServiceUtils.h"
32 #include "nsHashKeys.h"
33 #include "nsIFile.h"
34 #include "nsIObserverService.h"
35 #include "nsIXULRuntime.h"
36 #include "nsNativeCharsetUtils.h"
37 #include "nsXPCOMPrivate.h"
38 #include "prio.h"
39 #include "runnable_utils.h"
40 #include "VideoUtils.h"
41
42 using mozilla::ipc::Transport;
43
44 namespace mozilla {
45
46 #ifdef __CLASS__
47 # undef __CLASS__
48 #endif
49 #define __CLASS__ "GMPServiceParent"
50
51 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
52
53 namespace gmp {
54
55 static const uint32_t NodeIdSaltLength = 32;
56
57 already_AddRefed<GeckoMediaPluginServiceParent>
GetSingleton()58 GeckoMediaPluginServiceParent::GetSingleton() {
59 MOZ_ASSERT(XRE_IsParentProcess());
60 RefPtr<GeckoMediaPluginService> service(
61 GeckoMediaPluginServiceParent::GetGeckoMediaPluginService());
62 #ifdef DEBUG
63 if (service) {
64 nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
65 CallQueryInterface(service.get(), getter_AddRefs(chromeService));
66 MOZ_ASSERT(chromeService);
67 }
68 #endif
69 return service.forget().downcast<GeckoMediaPluginServiceParent>();
70 }
71
NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceParent,GeckoMediaPluginService,mozIGeckoMediaPluginChromeService,nsIAsyncShutdownBlocker)72 NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceParent,
73 GeckoMediaPluginService,
74 mozIGeckoMediaPluginChromeService,
75 nsIAsyncShutdownBlocker)
76
77 GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
78 : mShuttingDown(false),
79 mScannedPluginOnDisk(false),
80 mWaitingForPluginsSyncShutdown(false),
81 mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor"),
82 mInitPromise(&mInitPromiseMonitor),
83 mLoadPluginsFromDiskComplete(false) {
84 MOZ_ASSERT(NS_IsMainThread());
85 }
86
~GeckoMediaPluginServiceParent()87 GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent() {
88 MOZ_ASSERT(mPlugins.IsEmpty());
89 }
90
Init()91 nsresult GeckoMediaPluginServiceParent::Init() {
92 MOZ_ASSERT(NS_IsMainThread());
93
94 nsCOMPtr<nsIObserverService> obsService =
95 mozilla::services::GetObserverService();
96 MOZ_ASSERT(obsService);
97
98 MOZ_ALWAYS_SUCCEEDS(
99 obsService->AddObserver(this, "profile-change-teardown", false));
100 MOZ_ALWAYS_SUCCEEDS(
101 obsService->AddObserver(this, "last-pb-context-exited", false));
102 MOZ_ALWAYS_SUCCEEDS(
103 obsService->AddObserver(this, "browser:purge-session-history", false));
104 MOZ_ALWAYS_SUCCEEDS(
105 obsService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false));
106
107 #ifdef DEBUG
108 MOZ_ALWAYS_SUCCEEDS(
109 obsService->AddObserver(this, "mediakeys-request", false));
110 #endif
111
112 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
113 if (prefs) {
114 prefs->AddObserver("media.gmp.plugin.crash", this, false);
115 }
116
117 nsresult rv = InitStorage();
118 if (NS_WARN_IF(NS_FAILED(rv))) {
119 return rv;
120 }
121
122 // Kick off scanning for plugins
123 nsCOMPtr<nsIThread> thread;
124 rv = GetThread(getter_AddRefs(thread));
125 if (NS_WARN_IF(NS_FAILED(rv))) {
126 return rv;
127 }
128
129 // Detect if GMP storage has an incompatible version, and if so nuke it.
130 int32_t version =
131 Preferences::GetInt("media.gmp.storage.version.observed", 0);
132 int32_t expected =
133 Preferences::GetInt("media.gmp.storage.version.expected", 0);
134 if (version != expected) {
135 Preferences::SetInt("media.gmp.storage.version.observed", expected);
136 return GMPDispatch(
137 NewRunnableMethod("gmp::GeckoMediaPluginServiceParent::ClearStorage",
138 this, &GeckoMediaPluginServiceParent::ClearStorage));
139 }
140 return NS_OK;
141 }
142
CloneAndAppend(nsIFile * aFile,const nsAString & aDir)143 already_AddRefed<nsIFile> CloneAndAppend(nsIFile* aFile,
144 const nsAString& aDir) {
145 nsCOMPtr<nsIFile> f;
146 nsresult rv = aFile->Clone(getter_AddRefs(f));
147 if (NS_WARN_IF(NS_FAILED(rv))) {
148 return nullptr;
149 }
150
151 rv = f->Append(aDir);
152 if (NS_WARN_IF(NS_FAILED(rv))) {
153 return nullptr;
154 }
155 return f.forget();
156 }
157
GMPPlatformString(nsAString & aOutPlatform)158 static nsresult GMPPlatformString(nsAString& aOutPlatform) {
159 // Append the OS and arch so that we don't reuse the storage if the profile is
160 // copied or used under a different bit-ness, or copied to another platform.
161 nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
162 if (!runtime) {
163 return NS_ERROR_FAILURE;
164 }
165
166 nsAutoCString OS;
167 nsresult rv = runtime->GetOS(OS);
168 if (NS_WARN_IF(NS_FAILED(rv))) {
169 return rv;
170 }
171
172 nsAutoCString arch;
173 rv = runtime->GetXPCOMABI(arch);
174 if (NS_WARN_IF(NS_FAILED(rv))) {
175 return rv;
176 }
177
178 nsCString platform;
179 platform.Append(OS);
180 platform.AppendLiteral("_");
181 platform.Append(arch);
182
183 aOutPlatform = NS_ConvertUTF8toUTF16(platform);
184
185 return NS_OK;
186 }
187
InitStorage()188 nsresult GeckoMediaPluginServiceParent::InitStorage() {
189 MOZ_ASSERT(NS_IsMainThread());
190
191 // GMP storage should be used in the chrome process only.
192 if (!XRE_IsParentProcess()) {
193 return NS_OK;
194 }
195
196 // Directory service is main thread only, so cache the profile dir here
197 // so that we can use it off main thread.
198 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
199 getter_AddRefs(mStorageBaseDir));
200
201 if (NS_WARN_IF(NS_FAILED(rv))) {
202 return rv;
203 }
204
205 rv = mStorageBaseDir->AppendNative(NS_LITERAL_CSTRING("gmp"));
206 if (NS_WARN_IF(NS_FAILED(rv))) {
207 return rv;
208 }
209
210 nsAutoString platform;
211 rv = GMPPlatformString(platform);
212 if (NS_WARN_IF(NS_FAILED(rv))) {
213 return rv;
214 }
215
216 rv = mStorageBaseDir->Append(platform);
217 if (NS_WARN_IF(NS_FAILED(rv))) {
218 return rv;
219 }
220
221 return GeckoMediaPluginService::Init();
222 }
223
224 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aSomeData)225 GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
226 const char* aTopic,
227 const char16_t* aSomeData) {
228 GMP_LOG_DEBUG("%s::%s topic='%s' data='%s'", __CLASS__, __FUNCTION__, aTopic,
229 NS_ConvertUTF16toUTF8(aSomeData).get());
230 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
231 nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(aSubject));
232 if (branch) {
233 bool crashNow = false;
234 if (NS_LITERAL_STRING("media.gmp.plugin.crash").Equals(aSomeData)) {
235 branch->GetBoolPref("media.gmp.plugin.crash", &crashNow);
236 }
237 if (crashNow) {
238 nsCOMPtr<nsIThread> gmpThread;
239 {
240 MutexAutoLock lock(mMutex);
241 gmpThread = mGMPThread;
242 }
243 if (gmpThread) {
244 // Note: the GeckoMediaPluginServiceParent singleton is kept alive by
245 // a static refptr that is only cleared in the final stage of shutdown
246 // after everything else is shutdown, so this RefPtr<> is not strictly
247 // necessary so long as that is true, but it's safer.
248 gmpThread->Dispatch(
249 WrapRunnable(RefPtr<GeckoMediaPluginServiceParent>(this),
250 &GeckoMediaPluginServiceParent::CrashPlugins),
251 NS_DISPATCH_NORMAL);
252 }
253 }
254 }
255 } else if (!strcmp("profile-change-teardown", aTopic)) {
256 mWaitingForPluginsSyncShutdown = true;
257
258 nsCOMPtr<nsIThread> gmpThread;
259 {
260 MutexAutoLock lock(mMutex);
261 MOZ_ASSERT(!mShuttingDown);
262 mShuttingDown = true;
263 gmpThread = mGMPThread;
264 }
265
266 if (gmpThread) {
267 GMP_LOG_DEBUG(
268 "%s::%s Starting to unload plugins, waiting for sync shutdown...",
269 __CLASS__, __FUNCTION__);
270 gmpThread->Dispatch(
271 NewRunnableMethod("gmp::GeckoMediaPluginServiceParent::UnloadPlugins",
272 this,
273 &GeckoMediaPluginServiceParent::UnloadPlugins),
274 NS_DISPATCH_NORMAL);
275
276 // Wait for UnloadPlugins() to do sync shutdown...
277 SpinEventLoopUntil([&]() { return !mWaitingForPluginsSyncShutdown; });
278 } else {
279 // GMP thread has already shutdown.
280 MOZ_ASSERT(mPlugins.IsEmpty());
281 mWaitingForPluginsSyncShutdown = false;
282 }
283
284 } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
285 MOZ_ASSERT(mShuttingDown);
286 ShutdownGMPThread();
287 } else if (!strcmp(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, aTopic)) {
288 mXPCOMWillShutdown = true;
289 } else if (!strcmp("last-pb-context-exited", aTopic)) {
290 // When Private Browsing mode exits, all we need to do is clear
291 // mTempNodeIds. This drops all the node ids we've cached in memory
292 // for PB origin-pairs. If we try to open an origin-pair for non-PB
293 // mode, we'll get the NodeId salt stored on-disk, and if we try to
294 // open a PB mode origin-pair, we'll re-generate new salt.
295 mTempNodeIds.Clear();
296 } else if (!strcmp("browser:purge-session-history", aTopic)) {
297 // Clear everything!
298 if (!aSomeData || nsDependentString(aSomeData).IsEmpty()) {
299 return GMPDispatch(NewRunnableMethod(
300 "gmp::GeckoMediaPluginServiceParent::ClearStorage", this,
301 &GeckoMediaPluginServiceParent::ClearStorage));
302 }
303
304 // Clear nodeIds/records modified after |t|.
305 nsresult rv;
306 PRTime t = nsDependentString(aSomeData).ToInteger64(&rv, 10);
307 if (NS_WARN_IF(NS_FAILED(rv))) {
308 return rv;
309 }
310 return GMPDispatch(NewRunnableMethod<PRTime>(
311 "gmp::GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread",
312 this, &GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread,
313 t));
314 }
315
316 return NS_OK;
317 }
318
EnsureInitialized()319 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::EnsureInitialized() {
320 MonitorAutoLock lock(mInitPromiseMonitor);
321 if (mLoadPluginsFromDiskComplete) {
322 return GenericPromise::CreateAndResolve(true, __func__);
323 }
324 // We should have an init promise in flight.
325 MOZ_ASSERT(!mInitPromise.IsEmpty());
326 return mInitPromise.Ensure(__func__);
327 }
328
329 RefPtr<GetGMPContentParentPromise>
GetContentParent(GMPCrashHelper * aHelper,const nsACString & aNodeIdString,const nsCString & aAPI,const nsTArray<nsCString> & aTags)330 GeckoMediaPluginServiceParent::GetContentParent(
331 GMPCrashHelper* aHelper, const nsACString& aNodeIdString,
332 const nsCString& aAPI, const nsTArray<nsCString>& aTags) {
333 RefPtr<AbstractThread> thread(GetAbstractGMPThread());
334 if (!thread) {
335 return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE,
336 __func__);
337 }
338
339 auto holder = MakeUnique<MozPromiseHolder<GetGMPContentParentPromise>>();
340 RefPtr<GetGMPContentParentPromise> promise = holder->Ensure(__func__);
341 EnsureInitialized()->Then(
342 thread, __func__,
343 [self = RefPtr<GeckoMediaPluginServiceParent>(this),
344 nodeIdString = nsCString(aNodeIdString), api = nsCString(aAPI),
345 tags = aTags.Clone(), helper = RefPtr<GMPCrashHelper>(aHelper),
346 holder = std::move(holder)](
347 const GenericPromise::ResolveOrRejectValue& aValue) mutable -> void {
348 if (aValue.IsReject()) {
349 NS_WARNING("GMPService::EnsureInitialized failed.");
350 holder->Reject(NS_ERROR_FAILURE, __func__);
351 return;
352 }
353 RefPtr<GMPParent> gmp =
354 self->SelectPluginForAPI(nodeIdString, api, tags);
355 GMP_LOG_DEBUG("%s: %p returning %p for api %s", __FUNCTION__,
356 self.get(), gmp.get(), api.get());
357 if (!gmp) {
358 NS_WARNING(
359 "GeckoMediaPluginServiceParent::GetContentParentFrom failed");
360 holder->Reject(NS_ERROR_FAILURE, __func__);
361 return;
362 }
363 self->ConnectCrashHelper(gmp->GetPluginId(), helper);
364 gmp->GetGMPContentParent(std::move(holder));
365 });
366
367 return promise;
368 }
369
370 RefPtr<GetGMPContentParentPromise>
GetContentParent(GMPCrashHelper * aHelper,const NodeId & aNodeId,const nsCString & aAPI,const nsTArray<nsCString> & aTags)371 GeckoMediaPluginServiceParent::GetContentParent(
372 GMPCrashHelper* aHelper, const NodeId& aNodeId, const nsCString& aAPI,
373 const nsTArray<nsCString>& aTags) {
374 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
375
376 nsCString nodeIdString;
377 nsresult rv = GetNodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin,
378 aNodeId.mGMPName, nodeIdString);
379 if (NS_WARN_IF(NS_FAILED(rv))) {
380 return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE,
381 __func__);
382 }
383 return GetContentParent(aHelper, nodeIdString, aAPI, aTags);
384 }
385
InitializePlugins(AbstractThread * aAbstractGMPThread)386 void GeckoMediaPluginServiceParent::InitializePlugins(
387 AbstractThread* aAbstractGMPThread) {
388 MOZ_ASSERT(aAbstractGMPThread);
389 MonitorAutoLock lock(mInitPromiseMonitor);
390 if (mLoadPluginsFromDiskComplete) {
391 return;
392 }
393
394 RefPtr<GeckoMediaPluginServiceParent> self(this);
395 RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__);
396 InvokeAsync(aAbstractGMPThread, this, __func__,
397 &GeckoMediaPluginServiceParent::LoadFromEnvironment)
398 ->Then(
399 aAbstractGMPThread, __func__,
400 [self]() -> void {
401 MonitorAutoLock lock(self->mInitPromiseMonitor);
402 self->mLoadPluginsFromDiskComplete = true;
403 self->mInitPromise.Resolve(true, __func__);
404 },
405 [self]() -> void {
406 MonitorAutoLock lock(self->mInitPromiseMonitor);
407 self->mLoadPluginsFromDiskComplete = true;
408 self->mInitPromise.Reject(NS_ERROR_FAILURE, __func__);
409 });
410 }
411
NotifySyncShutdownComplete()412 void GeckoMediaPluginServiceParent::NotifySyncShutdownComplete() {
413 MOZ_ASSERT(NS_IsMainThread());
414 mWaitingForPluginsSyncShutdown = false;
415 }
416
IsShuttingDown()417 bool GeckoMediaPluginServiceParent::IsShuttingDown() {
418 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
419 return mShuttingDownOnGMPThread;
420 }
421
UnloadPlugins()422 void GeckoMediaPluginServiceParent::UnloadPlugins() {
423 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
424 MOZ_ASSERT(!mShuttingDownOnGMPThread);
425 mShuttingDownOnGMPThread = true;
426
427 nsTArray<RefPtr<GMPParent>> plugins;
428 {
429 MutexAutoLock lock(mMutex);
430 // Move all plugins references to a local array. This way mMutex won't be
431 // locked when calling CloseActive (to avoid inter-locking).
432 std::swap(plugins, mPlugins);
433
434 for (GMPServiceParent* parent : mServiceParents) {
435 Unused << parent->SendBeginShutdown();
436 }
437 }
438
439 GMP_LOG_DEBUG("%s::%s plugins:%zu", __CLASS__, __FUNCTION__,
440 plugins.Length());
441 #ifdef DEBUG
442 for (const auto& plugin : plugins) {
443 GMP_LOG_DEBUG("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__,
444 plugin->GetDisplayName().get());
445 }
446 #endif
447 // Note: CloseActive may be async; it could actually finish
448 // shutting down when all the plugins have unloaded.
449 for (const auto& plugin : plugins) {
450 plugin->CloseActive(true);
451 }
452
453 nsCOMPtr<nsIRunnable> task = NewRunnableMethod(
454 "GeckoMediaPluginServiceParent::NotifySyncShutdownComplete", this,
455 &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete);
456 mMainThread->Dispatch(task.forget());
457 }
458
CrashPlugins()459 void GeckoMediaPluginServiceParent::CrashPlugins() {
460 GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
461 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
462
463 MutexAutoLock lock(mMutex);
464 for (size_t i = 0; i < mPlugins.Length(); i++) {
465 mPlugins[i]->Crash();
466 }
467 }
468
LoadFromEnvironment()469 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::LoadFromEnvironment() {
470 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
471 RefPtr<AbstractThread> thread(GetAbstractGMPThread());
472 if (!thread) {
473 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
474 }
475
476 const char* env = PR_GetEnv("MOZ_GMP_PATH");
477 if (!env || !*env) {
478 return GenericPromise::CreateAndResolve(true, __func__);
479 }
480
481 nsString allpaths;
482 if (NS_WARN_IF(NS_FAILED(
483 NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) {
484 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
485 }
486
487 nsTArray<RefPtr<GenericPromise>> promises;
488 uint32_t pos = 0;
489 while (pos < allpaths.Length()) {
490 // Loop over multiple path entries separated by colons (*nix) or
491 // semicolons (Windows)
492 int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos);
493 if (next == -1) {
494 promises.AppendElement(
495 AddOnGMPThread(nsString(Substring(allpaths, pos))));
496 break;
497 } else {
498 promises.AppendElement(
499 AddOnGMPThread(nsString(Substring(allpaths, pos, next - pos))));
500 pos = next + 1;
501 }
502 }
503
504 mScannedPluginOnDisk = true;
505 return GenericPromise::All(thread, promises)
506 ->Then(
507 thread, __func__,
508 []() { return GenericPromise::CreateAndResolve(true, __func__); },
509 []() {
510 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
511 });
512 }
513
514 class NotifyObserversTask final : public mozilla::Runnable {
515 public:
NotifyObserversTask(const char * aTopic,nsString aData=EmptyString ())516 explicit NotifyObserversTask(const char* aTopic,
517 nsString aData = EmptyString())
518 : Runnable(aTopic), mTopic(aTopic), mData(aData) {}
Run()519 NS_IMETHOD Run() override {
520 MOZ_ASSERT(NS_IsMainThread());
521 nsCOMPtr<nsIObserverService> obsService =
522 mozilla::services::GetObserverService();
523 MOZ_ASSERT(obsService);
524 if (obsService) {
525 obsService->NotifyObservers(nullptr, mTopic, mData.get());
526 }
527 return NS_OK;
528 }
529
530 private:
531 ~NotifyObserversTask() = default;
532 const char* mTopic;
533 const nsString mData;
534 };
535
536 NS_IMETHODIMP
Run()537 GeckoMediaPluginServiceParent::PathRunnable::Run() {
538 mService->RemoveOnGMPThread(mPath, mOperation == REMOVE_AND_DELETE_FROM_DISK,
539 mDefer);
540
541 mService->UpdateContentProcessGMPCapabilities();
542 return NS_OK;
543 }
544
UpdateContentProcessGMPCapabilities()545 void GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities() {
546 if (!NS_IsMainThread()) {
547 nsCOMPtr<nsIRunnable> task = NewRunnableMethod(
548 "GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities",
549 this,
550 &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities);
551 mMainThread->Dispatch(task.forget());
552 return;
553 }
554
555 typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
556 typedef mozilla::dom::GMPAPITags GMPAPITags;
557 typedef mozilla::dom::ContentParent ContentParent;
558
559 nsTArray<GMPCapabilityData> caps;
560 {
561 MutexAutoLock lock(mMutex);
562 for (const RefPtr<GMPParent>& gmp : mPlugins) {
563 // We have multiple instances of a GMPParent for a given GMP in the
564 // list, one per origin. So filter the list so that we don't include
565 // the same GMP's capabilities twice.
566 NS_ConvertUTF16toUTF8 name(gmp->GetPluginBaseName());
567 bool found = false;
568 for (const GMPCapabilityData& cap : caps) {
569 if (cap.name().Equals(name)) {
570 found = true;
571 break;
572 }
573 }
574 if (found) {
575 continue;
576 }
577 GMPCapabilityData x;
578 x.name() = name;
579 x.version() = gmp->GetVersion();
580 for (const GMPCapability& tag : gmp->GetCapabilities()) {
581 x.capabilities().AppendElement(GMPAPITags(tag.mAPIName, tag.mAPITags));
582 }
583 caps.AppendElement(std::move(x));
584 }
585 }
586 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
587 Unused << cp->SendGMPsChanged(caps);
588 }
589
590 // For non-e10s, we must fire a notification so that any MediaKeySystemAccess
591 // requests waiting on a CDM to download will retry.
592 nsCOMPtr<nsIObserverService> obsService =
593 mozilla::services::GetObserverService();
594 MOZ_ASSERT(obsService);
595 if (obsService) {
596 obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
597 }
598 }
599
AsyncAddPluginDirectory(const nsAString & aDirectory)600 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::AsyncAddPluginDirectory(
601 const nsAString& aDirectory) {
602 RefPtr<AbstractThread> thread(GetAbstractGMPThread());
603 if (!thread) {
604 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
605 }
606
607 nsString dir(aDirectory);
608 RefPtr<GeckoMediaPluginServiceParent> self = this;
609 return InvokeAsync(thread, this, __func__,
610 &GeckoMediaPluginServiceParent::AddOnGMPThread, dir)
611 ->Then(
612 mMainThread, __func__,
613 [dir, self](bool aVal) {
614 GMP_LOG_DEBUG(
615 "GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s "
616 "succeeded",
617 NS_ConvertUTF16toUTF8(dir).get());
618 MOZ_ASSERT(NS_IsMainThread());
619 self->UpdateContentProcessGMPCapabilities();
620 return GenericPromise::CreateAndResolve(aVal, __func__);
621 },
622 [dir](nsresult aResult) {
623 GMP_LOG_DEBUG(
624 "GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s "
625 "failed",
626 NS_ConvertUTF16toUTF8(dir).get());
627 return GenericPromise::CreateAndReject(aResult, __func__);
628 });
629 }
630
631 NS_IMETHODIMP
AddPluginDirectory(const nsAString & aDirectory)632 GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory) {
633 MOZ_ASSERT(NS_IsMainThread());
634 RefPtr<GenericPromise> p = AsyncAddPluginDirectory(aDirectory);
635 Unused << p;
636 return NS_OK;
637 }
638
639 NS_IMETHODIMP
RemovePluginDirectory(const nsAString & aDirectory)640 GeckoMediaPluginServiceParent::RemovePluginDirectory(
641 const nsAString& aDirectory) {
642 MOZ_ASSERT(NS_IsMainThread());
643 return GMPDispatch(
644 new PathRunnable(this, aDirectory, PathRunnable::EOperation::REMOVE));
645 }
646
647 NS_IMETHODIMP
RemoveAndDeletePluginDirectory(const nsAString & aDirectory,const bool aDefer)648 GeckoMediaPluginServiceParent::RemoveAndDeletePluginDirectory(
649 const nsAString& aDirectory, const bool aDefer) {
650 MOZ_ASSERT(NS_IsMainThread());
651 return GMPDispatch(new PathRunnable(
652 this, aDirectory, PathRunnable::EOperation::REMOVE_AND_DELETE_FROM_DISK,
653 aDefer));
654 }
655
656 NS_IMETHODIMP
HasPluginForAPI(const nsACString & aAPI,nsTArray<nsCString> * aTags,bool * aHasPlugin)657 GeckoMediaPluginServiceParent::HasPluginForAPI(const nsACString& aAPI,
658 nsTArray<nsCString>* aTags,
659 bool* aHasPlugin) {
660 NS_ENSURE_ARG(aTags && aTags->Length() > 0);
661 NS_ENSURE_ARG(aHasPlugin);
662
663 nsresult rv = EnsurePluginsOnDiskScanned();
664 if (NS_FAILED(rv)) {
665 NS_WARNING("Failed to load GMPs from disk.");
666 return rv;
667 }
668
669 {
670 MutexAutoLock lock(mMutex);
671 nsCString api(aAPI);
672 size_t index = 0;
673 RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, *aTags, &index);
674 *aHasPlugin = !!gmp;
675 }
676
677 return NS_OK;
678 }
679
EnsurePluginsOnDiskScanned()680 nsresult GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned() {
681 const char* env = nullptr;
682 if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) {
683 // We have a MOZ_GMP_PATH environment variable which may specify the
684 // location of plugins to load, and we haven't yet scanned the disk to
685 // see if there are plugins there. Get the GMP thread, which will
686 // cause an event to be dispatched to which scans for plugins. We
687 // dispatch a sync event to the GMP thread here in order to wait until
688 // after the GMP thread has scanned any paths in MOZ_GMP_PATH.
689 nsresult rv = GMPDispatch(new mozilla::Runnable("GMPDummyRunnable"),
690 NS_DISPATCH_SYNC);
691 NS_ENSURE_SUCCESS(rv, rv);
692 MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now");
693 }
694
695 return NS_OK;
696 }
697
FindPluginForAPIFrom(size_t aSearchStartIndex,const nsCString & aAPI,const nsTArray<nsCString> & aTags,size_t * aOutPluginIndex)698 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::FindPluginForAPIFrom(
699 size_t aSearchStartIndex, const nsCString& aAPI,
700 const nsTArray<nsCString>& aTags, size_t* aOutPluginIndex) {
701 mMutex.AssertCurrentThreadOwns();
702 for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) {
703 RefPtr<GMPParent> gmp = mPlugins[i];
704 if (!GMPCapability::Supports(gmp->GetCapabilities(), aAPI, aTags)) {
705 continue;
706 }
707 if (aOutPluginIndex) {
708 *aOutPluginIndex = i;
709 }
710 return gmp.forget();
711 }
712 return nullptr;
713 }
714
SelectPluginForAPI(const nsACString & aNodeId,const nsCString & aAPI,const nsTArray<nsCString> & aTags)715 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::SelectPluginForAPI(
716 const nsACString& aNodeId, const nsCString& aAPI,
717 const nsTArray<nsCString>& aTags) {
718 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread(),
719 "Can't clone GMP plugins on non-GMP threads.");
720
721 GMPParent* gmpToClone = nullptr;
722 {
723 MutexAutoLock lock(mMutex);
724 size_t index = 0;
725 RefPtr<GMPParent> gmp;
726 while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) {
727 if (aNodeId.IsEmpty()) {
728 if (gmp->CanBeSharedCrossNodeIds()) {
729 return gmp.forget();
730 }
731 } else if (gmp->CanBeUsedFrom(aNodeId)) {
732 return gmp.forget();
733 }
734
735 if (!gmpToClone ||
736 (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) {
737 // This GMP has the correct type but has the wrong nodeId; hold on to it
738 // in case we need to clone it.
739 // Prefer GMPs in-use for the case where an upgraded plugin version is
740 // waiting for the old one to die. If the old plugin is in use, we
741 // should continue using it so that any persistent state remains
742 // consistent. Otherwise, just check that the plugin isn't scheduled
743 // for deletion.
744 gmpToClone = gmp;
745 }
746 // Loop around and try the next plugin; it may be usable from aNodeId.
747 index++;
748 }
749 }
750
751 // Plugin exists, but we can't use it due to cross-origin separation. Create a
752 // new one.
753 if (gmpToClone) {
754 RefPtr<GMPParent> clone = ClonePlugin(gmpToClone);
755 {
756 MutexAutoLock lock(mMutex);
757 mPlugins.AppendElement(clone);
758 }
759 if (!aNodeId.IsEmpty()) {
760 clone->SetNodeId(aNodeId);
761 }
762 return clone.forget();
763 }
764
765 return nullptr;
766 }
767
CreateGMPParent(AbstractThread * aMainThread)768 RefPtr<GMPParent> CreateGMPParent(AbstractThread* aMainThread) {
769 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
770 if (!SandboxInfo::Get().CanSandboxMedia()) {
771 if (!StaticPrefs::media_gmp_insecure_allow()) {
772 NS_WARNING("Denying media plugin load due to lack of sandboxing.");
773 return nullptr;
774 }
775 NS_WARNING("Loading media plugin despite lack of sandboxing.");
776 }
777 #endif
778 return new GMPParent(aMainThread);
779 }
780
ClonePlugin(const GMPParent * aOriginal)781 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::ClonePlugin(
782 const GMPParent* aOriginal) {
783 MOZ_ASSERT(aOriginal);
784
785 RefPtr<GMPParent> gmp = CreateGMPParent(mMainThread);
786 if (!gmp) {
787 NS_WARNING("Can't Create GMPParent");
788 return nullptr;
789 }
790
791 gmp->CloneFrom(aOriginal);
792 return gmp.forget();
793 }
794
AddOnGMPThread(nsString aDirectory)795 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::AddOnGMPThread(
796 nsString aDirectory) {
797 #ifdef XP_WIN
798 // On Windows our various test harnesses often pass paths with UNIX dir
799 // separators, or a mix of dir separators. NS_NewLocalFile() can't handle
800 // that, so fixup to match the platform's expected format. This makes us
801 // more robust in the face of bad input and test harnesses changing...
802 std::replace(aDirectory.BeginWriting(), aDirectory.EndWriting(), '/', '\\');
803 #endif
804
805 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
806 nsCString dir = NS_ConvertUTF16toUTF8(aDirectory);
807 RefPtr<AbstractThread> thread(GetAbstractGMPThread());
808 if (!thread) {
809 GMP_LOG_DEBUG("%s::%s: %s No GMP Thread", __CLASS__, __FUNCTION__,
810 dir.get());
811 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
812 }
813 GMP_LOG_DEBUG("%s::%s: %s", __CLASS__, __FUNCTION__, dir.get());
814
815 nsCOMPtr<nsIFile> directory;
816 nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
817 if (NS_WARN_IF(NS_FAILED(rv))) {
818 GMP_LOG_DEBUG("%s::%s: failed to create nsIFile for dir=%s rv=%" PRIx32,
819 __CLASS__, __FUNCTION__, dir.get(),
820 static_cast<uint32_t>(rv));
821 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
822 }
823
824 RefPtr<GMPParent> gmp = CreateGMPParent(mMainThread);
825 if (!gmp) {
826 NS_WARNING("Can't Create GMPParent");
827 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
828 }
829
830 RefPtr<GeckoMediaPluginServiceParent> self(this);
831 return gmp->Init(this, directory)
832 ->Then(
833 thread, __func__,
834 [gmp, self, dir](bool aVal) {
835 GMP_LOG_DEBUG("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__,
836 dir.get());
837 {
838 MutexAutoLock lock(self->mMutex);
839 self->mPlugins.AppendElement(gmp);
840 }
841 return GenericPromise::CreateAndResolve(aVal, __func__);
842 },
843 [dir](nsresult aResult) {
844 GMP_LOG_DEBUG("%s::%s: %s Failed", __CLASS__, __FUNCTION__,
845 dir.get());
846 return GenericPromise::CreateAndReject(aResult, __func__);
847 });
848 }
849
RemoveOnGMPThread(const nsAString & aDirectory,const bool aDeleteFromDisk,const bool aCanDefer)850 void GeckoMediaPluginServiceParent::RemoveOnGMPThread(
851 const nsAString& aDirectory, const bool aDeleteFromDisk,
852 const bool aCanDefer) {
853 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
854 GMP_LOG_DEBUG("%s::%s: %s", __CLASS__, __FUNCTION__,
855 NS_LossyConvertUTF16toASCII(aDirectory).get());
856
857 nsCOMPtr<nsIFile> directory;
858 nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
859 if (NS_WARN_IF(NS_FAILED(rv))) {
860 return;
861 }
862
863 // Plugin destruction can modify |mPlugins|. Put them aside for now and
864 // destroy them once we're done with |mPlugins|.
865 nsTArray<RefPtr<GMPParent>> deadPlugins;
866
867 bool inUse = false;
868 MutexAutoLock lock(mMutex);
869 for (size_t i = mPlugins.Length(); i-- > 0;) {
870 nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
871 bool equals;
872 if (NS_FAILED(directory->Equals(pluginpath, &equals)) || !equals) {
873 continue;
874 }
875
876 RefPtr<GMPParent> gmp = mPlugins[i];
877 if (aDeleteFromDisk && gmp->State() != GMPStateNotLoaded) {
878 // We have to wait for the child process to release its lib handle
879 // before we can delete the GMP.
880 inUse = true;
881 gmp->MarkForDeletion();
882
883 if (!mPluginsWaitingForDeletion.Contains(aDirectory)) {
884 mPluginsWaitingForDeletion.AppendElement(aDirectory);
885 }
886 }
887
888 if (gmp->State() == GMPStateNotLoaded || !aCanDefer) {
889 // GMP not in use or shutdown is being forced; can shut it down now.
890 deadPlugins.AppendElement(gmp);
891 mPlugins.RemoveElementAt(i);
892 }
893 }
894
895 {
896 MutexAutoUnlock unlock(mMutex);
897 for (auto& gmp : deadPlugins) {
898 gmp->CloseActive(true);
899 }
900 }
901
902 if (aDeleteFromDisk && !inUse) {
903 // Ensure the GMP dir and all files in it are writable, so we have
904 // permission to delete them.
905 directory->SetPermissions(0700);
906 DirectoryEnumerator iter(directory, DirectoryEnumerator::FilesAndDirs);
907 for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
908 dirEntry->SetPermissions(0700);
909 }
910 if (NS_SUCCEEDED(directory->Remove(true))) {
911 mPluginsWaitingForDeletion.RemoveElement(aDirectory);
912 nsCOMPtr<nsIRunnable> task = new NotifyObserversTask(
913 "gmp-directory-deleted", nsString(aDirectory));
914 mMainThread->Dispatch(task.forget());
915 }
916 }
917 }
918
919 // May remove when Bug 1043671 is fixed
Dummy(RefPtr<GMPParent> aOnDeathsDoor)920 static void Dummy(RefPtr<GMPParent> aOnDeathsDoor) {
921 // exists solely to do nothing and let the Runnable kill the GMPParent
922 // when done.
923 }
924
PluginTerminated(const RefPtr<GMPParent> & aPlugin)925 void GeckoMediaPluginServiceParent::PluginTerminated(
926 const RefPtr<GMPParent>& aPlugin) {
927 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
928
929 if (aPlugin->IsMarkedForDeletion()) {
930 nsString path;
931 RefPtr<nsIFile> dir = aPlugin->GetDirectory();
932 nsresult rv = dir->GetPath(path);
933 NS_ENSURE_SUCCESS_VOID(rv);
934 if (mPluginsWaitingForDeletion.Contains(path)) {
935 RemoveOnGMPThread(path, true /* delete */, true /* can defer */);
936 }
937 }
938 }
939
ReAddOnGMPThread(const RefPtr<GMPParent> & aOld)940 void GeckoMediaPluginServiceParent::ReAddOnGMPThread(
941 const RefPtr<GMPParent>& aOld) {
942 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
943 GMP_LOG_DEBUG("%s::%s: %p", __CLASS__, __FUNCTION__, (void*)aOld);
944
945 RefPtr<GMPParent> gmp;
946 if (!mShuttingDownOnGMPThread) {
947 // We're not shutting down, so replace the old plugin in the list with a
948 // clone which is in a pristine state. Note: We place the plugin in
949 // the same slot in the array as a hack to ensure if we re-request with
950 // the same capabilities we get an instance of the same plugin.
951 gmp = ClonePlugin(aOld);
952 MutexAutoLock lock(mMutex);
953 MOZ_ASSERT(mPlugins.Contains(aOld));
954 if (mPlugins.Contains(aOld)) {
955 mPlugins[mPlugins.IndexOf(aOld)] = gmp;
956 }
957 } else {
958 // We're shutting down; don't re-add plugin, let the old plugin die.
959 MutexAutoLock lock(mMutex);
960 mPlugins.RemoveElement(aOld);
961 }
962 // Schedule aOld to be destroyed. We can't destroy it from here since we
963 // may be inside ActorDestroyed() for it.
964 NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
965 }
966
967 NS_IMETHODIMP
GetStorageDir(nsIFile ** aOutFile)968 GeckoMediaPluginServiceParent::GetStorageDir(nsIFile** aOutFile) {
969 if (NS_WARN_IF(!mStorageBaseDir)) {
970 return NS_ERROR_FAILURE;
971 }
972 return mStorageBaseDir->Clone(aOutFile);
973 }
974
WriteToFile(nsIFile * aPath,const nsCString & aFileName,const nsCString & aData)975 static nsresult WriteToFile(nsIFile* aPath, const nsCString& aFileName,
976 const nsCString& aData) {
977 nsCOMPtr<nsIFile> path;
978 nsresult rv = aPath->Clone(getter_AddRefs(path));
979 if (NS_WARN_IF(NS_FAILED(rv))) {
980 return rv;
981 }
982
983 rv = path->AppendNative(aFileName);
984 if (NS_WARN_IF(NS_FAILED(rv))) {
985 return rv;
986 }
987
988 PRFileDesc* f = nullptr;
989 rv = path->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, PR_IRWXU, &f);
990 if (NS_WARN_IF(NS_FAILED(rv))) {
991 return rv;
992 }
993
994 int32_t len = PR_Write(f, aData.get(), aData.Length());
995 PR_Close(f);
996 if (NS_WARN_IF(len < 0 || (size_t)len != aData.Length())) {
997 return NS_ERROR_FAILURE;
998 }
999
1000 return NS_OK;
1001 }
1002
ReadFromFile(nsIFile * aPath,const nsACString & aFileName,nsACString & aOutData,int32_t aMaxLength)1003 static nsresult ReadFromFile(nsIFile* aPath, const nsACString& aFileName,
1004 nsACString& aOutData, int32_t aMaxLength) {
1005 nsCOMPtr<nsIFile> path;
1006 nsresult rv = aPath->Clone(getter_AddRefs(path));
1007 if (NS_WARN_IF(NS_FAILED(rv))) {
1008 return rv;
1009 }
1010
1011 rv = path->AppendNative(aFileName);
1012 if (NS_WARN_IF(NS_FAILED(rv))) {
1013 return rv;
1014 }
1015
1016 PRFileDesc* f = nullptr;
1017 rv = path->OpenNSPRFileDesc(PR_RDONLY | PR_CREATE_FILE, PR_IRWXU, &f);
1018 if (NS_WARN_IF(NS_FAILED(rv))) {
1019 return rv;
1020 }
1021
1022 auto size = PR_Seek(f, 0, PR_SEEK_END);
1023 PR_Seek(f, 0, PR_SEEK_SET);
1024
1025 if (size > aMaxLength) {
1026 return NS_ERROR_FAILURE;
1027 }
1028 aOutData.SetLength(size);
1029
1030 auto len = PR_Read(f, aOutData.BeginWriting(), size);
1031 PR_Close(f);
1032 if (NS_WARN_IF(len != size)) {
1033 return NS_ERROR_FAILURE;
1034 }
1035
1036 return NS_OK;
1037 }
1038
ReadSalt(nsIFile * aPath,nsACString & aOutData)1039 nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData) {
1040 return ReadFromFile(aPath, NS_LITERAL_CSTRING("salt"), aOutData,
1041 NodeIdSaltLength);
1042 }
1043
GetMemoryStorageFor(const nsACString & aNodeId)1044 already_AddRefed<GMPStorage> GeckoMediaPluginServiceParent::GetMemoryStorageFor(
1045 const nsACString& aNodeId) {
1046 RefPtr<GMPStorage> s;
1047 if (!mTempGMPStorage.Get(aNodeId, getter_AddRefs(s))) {
1048 s = CreateGMPMemoryStorage();
1049 mTempGMPStorage.Put(aNodeId, RefPtr{s});
1050 }
1051 return s.forget();
1052 }
1053
1054 NS_IMETHODIMP
IsPersistentStorageAllowed(const nsACString & aNodeId,bool * aOutAllowed)1055 GeckoMediaPluginServiceParent::IsPersistentStorageAllowed(
1056 const nsACString& aNodeId, bool* aOutAllowed) {
1057 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
1058 NS_ENSURE_ARG(aOutAllowed);
1059 // We disallow persistent storage for the NodeId used for shared GMP
1060 // decoding, to prevent GMP decoding being used to track what a user
1061 // watches somehow.
1062 *aOutAllowed = !aNodeId.Equals(SHARED_GMP_DECODING_NODE_ID) &&
1063 mPersistentStorageAllowed.Get(aNodeId);
1064 return NS_OK;
1065 }
1066
GetNodeId(const nsAString & aOrigin,const nsAString & aTopLevelOrigin,const nsAString & aGMPName,nsACString & aOutId)1067 nsresult GeckoMediaPluginServiceParent::GetNodeId(
1068 const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
1069 const nsAString& aGMPName, nsACString& aOutId) {
1070 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
1071 GMP_LOG_DEBUG("%s::%s: (%s, %s)", __CLASS__, __FUNCTION__,
1072 NS_ConvertUTF16toUTF8(aOrigin).get(),
1073 NS_ConvertUTF16toUTF8(aTopLevelOrigin).get());
1074
1075 nsresult rv;
1076
1077 if (aOrigin.EqualsLiteral("null") || aOrigin.IsEmpty() ||
1078 aTopLevelOrigin.EqualsLiteral("null") || aTopLevelOrigin.IsEmpty()) {
1079 // (origin, topLevelOrigin) is null or empty; this is for an anonymous
1080 // origin, probably a local file, for which we don't provide persistent
1081 // storage. Generate a random node id, and don't store it so that the GMP's
1082 // storage is temporary and the process for this GMP is not shared with GMP
1083 // instances that have the same nodeId.
1084 nsAutoCString salt;
1085 rv = GenerateRandomPathName(salt, NodeIdSaltLength);
1086 if (NS_WARN_IF(NS_FAILED(rv))) {
1087 return rv;
1088 }
1089 aOutId = salt;
1090 mPersistentStorageAllowed.Put(salt, false);
1091 return NS_OK;
1092 }
1093
1094 const uint32_t hash =
1095 AddToHash(HashString(aOrigin), HashString(aTopLevelOrigin));
1096
1097 if (OriginAttributes::IsPrivateBrowsing(NS_ConvertUTF16toUTF8(aOrigin))) {
1098 // For PB mode, we store the node id, indexed by the origin pair and GMP
1099 // name, so that if the same origin pair is opened for the same GMP in this
1100 // session, it gets the same node id.
1101 const uint32_t pbHash = AddToHash(HashString(aGMPName), hash);
1102 nsCString* salt = nullptr;
1103 if (!(salt = mTempNodeIds.Get(pbHash))) {
1104 // No salt stored, generate and temporarily store some for this id.
1105 nsAutoCString newSalt;
1106 rv = GenerateRandomPathName(newSalt, NodeIdSaltLength);
1107 if (NS_WARN_IF(NS_FAILED(rv))) {
1108 return rv;
1109 }
1110 salt = new nsCString(newSalt);
1111 mTempNodeIds.Put(pbHash, salt);
1112 mPersistentStorageAllowed.Put(*salt, false);
1113 }
1114 aOutId = *salt;
1115 return NS_OK;
1116 }
1117
1118 // Otherwise, try to see if we've previously generated and stored salt
1119 // for this origin pair.
1120 nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
1121 rv = GetStorageDir(getter_AddRefs(path));
1122 if (NS_WARN_IF(NS_FAILED(rv))) {
1123 return rv;
1124 }
1125
1126 // $profileDir/gmp/$platform/$gmpName/
1127 rv = path->Append(aGMPName);
1128 if (NS_WARN_IF(NS_FAILED(rv))) {
1129 return rv;
1130 }
1131
1132 // $profileDir/gmp/$platform/$gmpName/id/
1133 rv = path->AppendNative(NS_LITERAL_CSTRING("id"));
1134 if (NS_WARN_IF(NS_FAILED(rv))) {
1135 return rv;
1136 }
1137
1138 nsAutoCString hashStr;
1139 hashStr.AppendInt((int64_t)hash);
1140
1141 // $profileDir/gmp/$platform/$gmpName/id/$hash
1142 rv = path->AppendNative(hashStr);
1143 if (NS_WARN_IF(NS_FAILED(rv))) {
1144 return rv;
1145 }
1146
1147 rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
1148 if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
1149 return rv;
1150 }
1151
1152 nsCOMPtr<nsIFile> saltFile;
1153 rv = path->Clone(getter_AddRefs(saltFile));
1154 if (NS_WARN_IF(NS_FAILED(rv))) {
1155 return rv;
1156 }
1157
1158 rv = saltFile->AppendNative(NS_LITERAL_CSTRING("salt"));
1159 if (NS_WARN_IF(NS_FAILED(rv))) {
1160 return rv;
1161 }
1162
1163 nsAutoCString salt;
1164 bool exists = false;
1165 rv = saltFile->Exists(&exists);
1166 if (NS_WARN_IF(NS_FAILED(rv))) {
1167 return rv;
1168 }
1169 if (!exists) {
1170 // No stored salt for this origin. Generate salt, and store it and
1171 // the origin on disk.
1172 nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength);
1173 if (NS_WARN_IF(NS_FAILED(rv))) {
1174 return rv;
1175 }
1176 MOZ_ASSERT(salt.Length() == NodeIdSaltLength);
1177
1178 // $profileDir/gmp/$platform/$gmpName/id/$hash/salt
1179 rv = WriteToFile(path, NS_LITERAL_CSTRING("salt"), salt);
1180 if (NS_WARN_IF(NS_FAILED(rv))) {
1181 return rv;
1182 }
1183
1184 // $profileDir/gmp/$platform/$gmpName/id/$hash/origin
1185 rv = WriteToFile(path, NS_LITERAL_CSTRING("origin"),
1186 NS_ConvertUTF16toUTF8(aOrigin));
1187 if (NS_WARN_IF(NS_FAILED(rv))) {
1188 return rv;
1189 }
1190
1191 // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin
1192 rv = WriteToFile(path, NS_LITERAL_CSTRING("topLevelOrigin"),
1193 NS_ConvertUTF16toUTF8(aTopLevelOrigin));
1194 if (NS_WARN_IF(NS_FAILED(rv))) {
1195 return rv;
1196 }
1197
1198 } else {
1199 rv = ReadSalt(path, salt);
1200 if (NS_WARN_IF(NS_FAILED(rv))) {
1201 return rv;
1202 }
1203 }
1204
1205 aOutId = salt;
1206 mPersistentStorageAllowed.Put(salt, true);
1207
1208 return NS_OK;
1209 }
1210
1211 NS_IMETHODIMP
GetNodeId(const nsAString & aOrigin,const nsAString & aTopLevelOrigin,const nsAString & aGMPName,UniquePtr<GetNodeIdCallback> && aCallback)1212 GeckoMediaPluginServiceParent::GetNodeId(
1213 const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
1214 const nsAString& aGMPName, UniquePtr<GetNodeIdCallback>&& aCallback) {
1215 nsCString nodeId;
1216 nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, nodeId);
1217 aCallback->Done(rv, nodeId);
1218 return rv;
1219 }
1220
ExtractHostName(const nsACString & aOrigin,nsACString & aOutData)1221 static bool ExtractHostName(const nsACString& aOrigin, nsACString& aOutData) {
1222 nsCString str;
1223 str.Assign(aOrigin);
1224 int begin = str.Find("://");
1225 // The scheme is missing!
1226 if (begin == -1) {
1227 return false;
1228 }
1229
1230 int end = str.RFind(":");
1231 // Remove the port number
1232 if (end != begin) {
1233 str.SetLength(end);
1234 }
1235
1236 nsDependentCSubstring host(str, begin + 3);
1237 aOutData.Assign(host);
1238 return true;
1239 }
1240
MatchOrigin(nsIFile * aPath,const nsACString & aSite,const mozilla::OriginAttributesPattern & aPattern)1241 bool MatchOrigin(nsIFile* aPath, const nsACString& aSite,
1242 const mozilla::OriginAttributesPattern& aPattern) {
1243 // http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax
1244 static const uint32_t MaxDomainLength = 253;
1245
1246 nsresult rv;
1247 nsCString str;
1248 nsCString originNoSuffix;
1249 mozilla::OriginAttributes originAttributes;
1250
1251 rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("origin"), str, MaxDomainLength);
1252 if (!originAttributes.PopulateFromOrigin(str, originNoSuffix)) {
1253 // Fails on parsing the originAttributes, treat this as a non-match.
1254 return false;
1255 }
1256
1257 if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) &&
1258 str.Equals(aSite) && aPattern.Matches(originAttributes)) {
1259 return true;
1260 }
1261
1262 mozilla::OriginAttributes topLevelOriginAttributes;
1263 rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("topLevelOrigin"), str,
1264 MaxDomainLength);
1265 if (!topLevelOriginAttributes.PopulateFromOrigin(str, originNoSuffix)) {
1266 // Fails on paring the originAttributes, treat this as a non-match.
1267 return false;
1268 }
1269
1270 if (NS_SUCCEEDED(rv) && ExtractHostName(originNoSuffix, str) &&
1271 str.Equals(aSite) && aPattern.Matches(topLevelOriginAttributes)) {
1272 return true;
1273 }
1274 return false;
1275 }
1276
1277 template <typename T>
KillPlugins(const nsTArray<RefPtr<GMPParent>> & aPlugins,Mutex & aMutex,T && aFilter)1278 static void KillPlugins(const nsTArray<RefPtr<GMPParent>>& aPlugins,
1279 Mutex& aMutex, T&& aFilter) {
1280 // Shutdown the plugins when |aFilter| evaluates to true.
1281 // After we clear storage data, node IDs will become invalid and shouldn't be
1282 // used anymore. We need to kill plugins with such nodeIDs.
1283 // Note: we can't shut them down while holding the lock,
1284 // as the lock is not re-entrant and shutdown requires taking the lock.
1285 // The plugin list is only edited on the GMP thread, so this should be OK.
1286 nsTArray<RefPtr<GMPParent>> pluginsToKill;
1287 {
1288 MutexAutoLock lock(aMutex);
1289 for (size_t i = 0; i < aPlugins.Length(); i++) {
1290 RefPtr<GMPParent> parent(aPlugins[i]);
1291 if (aFilter(parent)) {
1292 pluginsToKill.AppendElement(parent);
1293 }
1294 }
1295 }
1296
1297 for (size_t i = 0; i < pluginsToKill.Length(); i++) {
1298 pluginsToKill[i]->CloseActive(false);
1299 }
1300 }
1301
1302 struct NodeFilter {
NodeFiltermozilla::gmp::NodeFilter1303 explicit NodeFilter(const nsTArray<nsCString>& nodeIDs) : mNodeIDs(nodeIDs) {}
operator ()mozilla::gmp::NodeFilter1304 bool operator()(GMPParent* aParent) {
1305 return mNodeIDs.Contains(aParent->GetNodeId());
1306 }
1307
1308 private:
1309 const nsTArray<nsCString>& mNodeIDs;
1310 };
1311
ClearNodeIdAndPlugin(DirectoryFilter & aFilter)1312 void GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(
1313 DirectoryFilter& aFilter) {
1314 // $profileDir/gmp/$platform/
1315 nsCOMPtr<nsIFile> path;
1316 nsresult rv = GetStorageDir(getter_AddRefs(path));
1317 if (NS_WARN_IF(NS_FAILED(rv))) {
1318 return;
1319 }
1320
1321 // Iterate all sub-folders of $profileDir/gmp/$platform/, i.e. the dirs in
1322 // which specific GMPs store their data.
1323 DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
1324 for (nsCOMPtr<nsIFile> pluginDir; (pluginDir = iter.Next()) != nullptr;) {
1325 ClearNodeIdAndPlugin(pluginDir, aFilter);
1326 }
1327 }
1328
ClearNodeIdAndPlugin(nsIFile * aPluginStorageDir,DirectoryFilter & aFilter)1329 void GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(
1330 nsIFile* aPluginStorageDir, DirectoryFilter& aFilter) {
1331 // $profileDir/gmp/$platform/$gmpName/id/
1332 nsCOMPtr<nsIFile> path =
1333 CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("id"));
1334 if (!path) {
1335 return;
1336 }
1337
1338 // Iterate all sub-folders of $profileDir/gmp/$platform/$gmpName/id/
1339 nsTArray<nsCString> nodeIDsToClear;
1340 DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
1341 for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
1342 // dirEntry is the hash of origins, i.e.:
1343 // $profileDir/gmp/$platform/$gmpName/id/$originHash/
1344 if (!aFilter(dirEntry)) {
1345 continue;
1346 }
1347 nsAutoCString salt;
1348 if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) {
1349 // Keep node IDs to clear data/plugins associated with them later.
1350 nodeIDsToClear.AppendElement(salt);
1351 // Also remove node IDs from the table.
1352 mPersistentStorageAllowed.Remove(salt);
1353 }
1354 // Now we can remove the directory for the origin pair.
1355 if (NS_FAILED(dirEntry->Remove(true))) {
1356 NS_WARNING("Failed to delete the directory for the origin pair");
1357 }
1358 }
1359
1360 // Kill plugin instances that have node IDs being cleared.
1361 KillPlugins(mPlugins, mMutex, NodeFilter(nodeIDsToClear));
1362
1363 // Clear all storage in $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
1364 path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("storage"));
1365 if (!path) {
1366 return;
1367 }
1368
1369 for (const nsCString& nodeId : nodeIDsToClear) {
1370 nsCOMPtr<nsIFile> dirEntry;
1371 nsresult rv = path->Clone(getter_AddRefs(dirEntry));
1372 if (NS_WARN_IF(NS_FAILED(rv))) {
1373 continue;
1374 }
1375
1376 rv = dirEntry->AppendNative(nodeId);
1377 if (NS_WARN_IF(NS_FAILED(rv))) {
1378 continue;
1379 }
1380
1381 if (NS_FAILED(dirEntry->Remove(true))) {
1382 NS_WARNING("Failed to delete GMP storage directory for the node");
1383 }
1384 }
1385 }
1386
ForgetThisSiteOnGMPThread(const nsACString & aSite,const mozilla::OriginAttributesPattern & aPattern)1387 void GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread(
1388 const nsACString& aSite, const mozilla::OriginAttributesPattern& aPattern) {
1389 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
1390 GMP_LOG_DEBUG("%s::%s: origin=%s", __CLASS__, __FUNCTION__, aSite.Data());
1391
1392 struct OriginFilter : public DirectoryFilter {
1393 explicit OriginFilter(const nsACString& aSite,
1394 const mozilla::OriginAttributesPattern& aPattern)
1395 : mSite(aSite), mPattern(aPattern) {}
1396 bool operator()(nsIFile* aPath) override {
1397 return MatchOrigin(aPath, mSite, mPattern);
1398 }
1399
1400 private:
1401 const nsACString& mSite;
1402 const mozilla::OriginAttributesPattern& mPattern;
1403 } filter(aSite, aPattern);
1404
1405 ClearNodeIdAndPlugin(filter);
1406 }
1407
ClearRecentHistoryOnGMPThread(PRTime aSince)1408 void GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread(
1409 PRTime aSince) {
1410 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
1411 GMP_LOG_DEBUG("%s::%s: since=%" PRId64, __CLASS__, __FUNCTION__,
1412 (int64_t)aSince);
1413
1414 struct MTimeFilter : public DirectoryFilter {
1415 explicit MTimeFilter(PRTime aSince) : mSince(aSince) {}
1416
1417 // Return true if any files under aPath is modified after |mSince|.
1418 bool IsModifiedAfter(nsIFile* aPath) {
1419 PRTime lastModified;
1420 nsresult rv = aPath->GetLastModifiedTime(&lastModified);
1421 if (NS_SUCCEEDED(rv) && lastModified >= mSince) {
1422 return true;
1423 }
1424 DirectoryEnumerator iter(aPath, DirectoryEnumerator::FilesAndDirs);
1425 for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
1426 if (IsModifiedAfter(dirEntry)) {
1427 return true;
1428 }
1429 }
1430 return false;
1431 }
1432
1433 // |aPath| is $profileDir/gmp/$platform/$gmpName/id/$originHash/
1434 bool operator()(nsIFile* aPath) override {
1435 if (IsModifiedAfter(aPath)) {
1436 return true;
1437 }
1438
1439 nsAutoCString salt;
1440 if (NS_WARN_IF(NS_FAILED(ReadSalt(aPath, salt)))) {
1441 return false;
1442 }
1443
1444 // $profileDir/gmp/$platform/$gmpName/id/
1445 nsCOMPtr<nsIFile> idDir;
1446 if (NS_WARN_IF(NS_FAILED(aPath->GetParent(getter_AddRefs(idDir))))) {
1447 return false;
1448 }
1449 // $profileDir/gmp/$platform/$gmpName/
1450 nsCOMPtr<nsIFile> temp;
1451 if (NS_WARN_IF(NS_FAILED(idDir->GetParent(getter_AddRefs(temp))))) {
1452 return false;
1453 }
1454
1455 // $profileDir/gmp/$platform/$gmpName/storage/
1456 if (NS_WARN_IF(NS_FAILED(temp->Append(NS_LITERAL_STRING("storage"))))) {
1457 return false;
1458 }
1459 // $profileDir/gmp/$platform/$gmpName/storage/$originSalt
1460 return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp);
1461 }
1462
1463 private:
1464 const PRTime mSince;
1465 } filter(aSince);
1466
1467 ClearNodeIdAndPlugin(filter);
1468
1469 nsCOMPtr<nsIRunnable> task =
1470 new NotifyObserversTask("gmp-clear-storage-complete");
1471 mMainThread->Dispatch(task.forget());
1472 }
1473
1474 NS_IMETHODIMP
ForgetThisSite(const nsAString & aSite,const nsAString & aPattern)1475 GeckoMediaPluginServiceParent::ForgetThisSite(const nsAString& aSite,
1476 const nsAString& aPattern) {
1477 MOZ_ASSERT(NS_IsMainThread());
1478
1479 mozilla::OriginAttributesPattern pattern;
1480
1481 if (!pattern.Init(aPattern)) {
1482 return NS_ERROR_INVALID_ARG;
1483 }
1484
1485 return ForgetThisSiteNative(aSite, pattern);
1486 }
1487
ForgetThisSiteNative(const nsAString & aSite,const mozilla::OriginAttributesPattern & aPattern)1488 nsresult GeckoMediaPluginServiceParent::ForgetThisSiteNative(
1489 const nsAString& aSite, const mozilla::OriginAttributesPattern& aPattern) {
1490 MOZ_ASSERT(NS_IsMainThread());
1491
1492 return GMPDispatch(
1493 NewRunnableMethod<nsCString, mozilla::OriginAttributesPattern>(
1494 "gmp::GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread", this,
1495 &GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread,
1496 NS_ConvertUTF16toUTF8(aSite), aPattern));
1497 }
1498
IsNodeIdValid(GMPParent * aParent)1499 static bool IsNodeIdValid(GMPParent* aParent) {
1500 return !aParent->GetNodeId().IsEmpty();
1501 }
1502
1503 NS_IMETHODIMP
GetName(nsAString & aName)1504 GeckoMediaPluginServiceParent::GetName(nsAString& aName) {
1505 aName = NS_LITERAL_STRING("GeckoMediaPluginServiceParent: shutdown");
1506 return NS_OK;
1507 }
1508
1509 NS_IMETHODIMP
GetState(nsIPropertyBag **)1510 GeckoMediaPluginServiceParent::GetState(nsIPropertyBag**) { return NS_OK; }
1511
1512 NS_IMETHODIMP
BlockShutdown(nsIAsyncShutdownClient *)1513 GeckoMediaPluginServiceParent::BlockShutdown(nsIAsyncShutdownClient*) {
1514 return NS_OK;
1515 }
1516
ServiceUserCreated(GMPServiceParent * aServiceParent)1517 void GeckoMediaPluginServiceParent::ServiceUserCreated(
1518 GMPServiceParent* aServiceParent) {
1519 MOZ_ASSERT(NS_IsMainThread());
1520 MutexAutoLock lock(mMutex);
1521 MOZ_ASSERT(!mServiceParents.Contains(aServiceParent));
1522 mServiceParents.AppendElement(aServiceParent);
1523 if (mServiceParents.Length() == 1) {
1524 nsresult rv = GetShutdownBarrier()->AddBlocker(
1525 this, NS_LITERAL_STRING(__FILE__), __LINE__,
1526 NS_LITERAL_STRING("GeckoMediaPluginServiceParent shutdown"));
1527 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
1528 }
1529 }
1530
ServiceUserDestroyed(GMPServiceParent * aServiceParent)1531 void GeckoMediaPluginServiceParent::ServiceUserDestroyed(
1532 GMPServiceParent* aServiceParent) {
1533 MOZ_ASSERT(NS_IsMainThread());
1534 MutexAutoLock lock(mMutex);
1535 MOZ_ASSERT(mServiceParents.Length() > 0);
1536 MOZ_ASSERT(mServiceParents.Contains(aServiceParent));
1537 mServiceParents.RemoveElement(aServiceParent);
1538 if (mServiceParents.IsEmpty()) {
1539 nsresult rv = GetShutdownBarrier()->RemoveBlocker(this);
1540 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
1541 }
1542 }
1543
ClearStorage()1544 void GeckoMediaPluginServiceParent::ClearStorage() {
1545 MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
1546 GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
1547
1548 // Kill plugins with valid nodeIDs.
1549 KillPlugins(mPlugins, mMutex, &IsNodeIdValid);
1550
1551 nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
1552 nsresult rv = GetStorageDir(getter_AddRefs(path));
1553 if (NS_WARN_IF(NS_FAILED(rv))) {
1554 return;
1555 }
1556
1557 if (NS_FAILED(path->Remove(true))) {
1558 NS_WARNING("Failed to delete GMP storage directory");
1559 }
1560
1561 // Clear private-browsing storage.
1562 mTempGMPStorage.Clear();
1563
1564 nsCOMPtr<nsIRunnable> task =
1565 new NotifyObserversTask("gmp-clear-storage-complete");
1566 mMainThread->Dispatch(task.forget());
1567 }
1568
GetById(uint32_t aPluginId)1569 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::GetById(
1570 uint32_t aPluginId) {
1571 MutexAutoLock lock(mMutex);
1572 for (const RefPtr<GMPParent>& gmp : mPlugins) {
1573 if (gmp->GetPluginId() == aPluginId) {
1574 return do_AddRef(gmp);
1575 }
1576 }
1577 return nullptr;
1578 }
1579
GMPServiceParent(GeckoMediaPluginServiceParent * aService)1580 GMPServiceParent::GMPServiceParent(GeckoMediaPluginServiceParent* aService)
1581 : mService(aService) {
1582 MOZ_ASSERT(NS_IsMainThread(), "Should be constructed on the main thread");
1583 MOZ_ASSERT(mService);
1584 mService->ServiceUserCreated(this);
1585 }
1586
~GMPServiceParent()1587 GMPServiceParent::~GMPServiceParent() {
1588 MOZ_ASSERT(NS_IsMainThread(), "Should be destroyted on the main thread");
1589 MOZ_ASSERT(mService);
1590 mService->ServiceUserDestroyed(this);
1591 }
1592
Destroy()1593 void GMPServiceParent::Destroy() {
1594 // Ensures we only delete the GMPServiceParent on the main thread.
1595 if (NS_IsMainThread()) {
1596 delete this;
1597 return;
1598 }
1599 nsresult rv = SchedulerGroup::Dispatch(
1600 TaskCategory::Other,
1601 NewNonOwningRunnableMethod("GMPServiceParent::Destroy", this,
1602 &GMPServiceParent::Destroy));
1603 MOZ_ALWAYS_SUCCEEDS(rv);
1604 }
1605
RecvLaunchGMP(const nsCString & aNodeId,const nsCString & aAPI,nsTArray<nsCString> && aTags,nsTArray<ProcessId> && aAlreadyBridgedTo,uint32_t * aOutPluginId,ProcessId * aOutProcessId,nsCString * aOutDisplayName,Endpoint<PGMPContentParent> * aOutEndpoint,nsresult * aOutRv,nsCString * aOutErrorDescription)1606 mozilla::ipc::IPCResult GMPServiceParent::RecvLaunchGMP(
1607 const nsCString& aNodeId, const nsCString& aAPI,
1608 nsTArray<nsCString>&& aTags, nsTArray<ProcessId>&& aAlreadyBridgedTo,
1609 uint32_t* aOutPluginId, ProcessId* aOutProcessId,
1610 nsCString* aOutDisplayName, Endpoint<PGMPContentParent>* aOutEndpoint,
1611 nsresult* aOutRv, nsCString* aOutErrorDescription) {
1612 if (mService->IsShuttingDown()) {
1613 *aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
1614 *aOutErrorDescription = NS_LITERAL_CSTRING("Service is shutting down.");
1615 return IPC_OK();
1616 }
1617
1618 RefPtr<GMPParent> gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags);
1619 if (gmp) {
1620 *aOutPluginId = gmp->GetPluginId();
1621 } else {
1622 *aOutRv = NS_ERROR_FAILURE;
1623 *aOutErrorDescription =
1624 NS_LITERAL_CSTRING("SelectPluginForAPI returns nullptr.");
1625 *aOutPluginId = 0;
1626 return IPC_OK();
1627 }
1628
1629 if (!gmp->EnsureProcessLoaded(aOutProcessId)) {
1630 *aOutRv = NS_ERROR_FAILURE;
1631 *aOutErrorDescription = NS_LITERAL_CSTRING("Process has not loaded.");
1632 return IPC_OK();
1633 }
1634
1635 *aOutDisplayName = gmp->GetDisplayName();
1636
1637 if (aAlreadyBridgedTo.Contains(*aOutProcessId)) {
1638 *aOutRv = NS_OK;
1639 return IPC_OK();
1640 }
1641
1642 Endpoint<PGMPContentParent> parent;
1643 Endpoint<PGMPContentChild> child;
1644 nsresult rv =
1645 PGMPContent::CreateEndpoints(OtherPid(), *aOutProcessId, &parent, &child);
1646 if (NS_WARN_IF(NS_FAILED(rv))) {
1647 *aOutRv = rv;
1648 *aOutErrorDescription =
1649 NS_LITERAL_CSTRING("PGMPContent::CreateEndpoints failed.");
1650 return IPC_OK();
1651 }
1652
1653 *aOutEndpoint = std::move(parent);
1654
1655 if (!gmp->SendInitGMPContentChild(std::move(child))) {
1656 *aOutRv = NS_ERROR_FAILURE;
1657 *aOutErrorDescription =
1658 NS_LITERAL_CSTRING("SendInitGMPContentChild failed.");
1659 return IPC_OK();
1660 }
1661
1662 gmp->IncrementGMPContentChildCount();
1663
1664 *aOutRv = NS_OK;
1665 return IPC_OK();
1666 }
1667
RecvLaunchGMPForNodeId(const NodeIdData & aNodeId,const nsCString & aApi,nsTArray<nsCString> && aTags,nsTArray<ProcessId> && aAlreadyBridgedTo,uint32_t * aOutPluginId,ProcessId * aOutId,nsCString * aOutDisplayName,Endpoint<PGMPContentParent> * aOutEndpoint,nsresult * aOutRv,nsCString * aOutErrorDescription)1668 mozilla::ipc::IPCResult GMPServiceParent::RecvLaunchGMPForNodeId(
1669 const NodeIdData& aNodeId, const nsCString& aApi,
1670 nsTArray<nsCString>&& aTags, nsTArray<ProcessId>&& aAlreadyBridgedTo,
1671 uint32_t* aOutPluginId, ProcessId* aOutId, nsCString* aOutDisplayName,
1672 Endpoint<PGMPContentParent>* aOutEndpoint, nsresult* aOutRv,
1673 nsCString* aOutErrorDescription) {
1674 nsCString nodeId;
1675 nsresult rv = mService->GetNodeId(
1676 aNodeId.mOrigin(), aNodeId.mTopLevelOrigin(), aNodeId.mGMPName(), nodeId);
1677 if (!NS_SUCCEEDED(rv)) {
1678 *aOutRv = rv;
1679 *aOutErrorDescription = NS_LITERAL_CSTRING("GetNodeId failed.");
1680 return IPC_OK();
1681 }
1682 return RecvLaunchGMP(nodeId, aApi, std::move(aTags),
1683 std::move(aAlreadyBridgedTo), aOutPluginId, aOutId,
1684 aOutDisplayName, aOutEndpoint, aOutRv,
1685 aOutErrorDescription);
1686 }
1687
RecvGetGMPNodeId(const nsString & aOrigin,const nsString & aTopLevelOrigin,const nsString & aGMPName,nsCString * aID)1688 mozilla::ipc::IPCResult GMPServiceParent::RecvGetGMPNodeId(
1689 const nsString& aOrigin, const nsString& aTopLevelOrigin,
1690 const nsString& aGMPName, nsCString* aID) {
1691 nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, *aID);
1692 if (!NS_SUCCEEDED(rv)) {
1693 return IPC_FAIL_NO_REASON(this);
1694 }
1695 return IPC_OK();
1696 }
1697
1698 class OpenPGMPServiceParent : public mozilla::Runnable {
1699 public:
OpenPGMPServiceParent(RefPtr<GMPServiceParent> && aGMPServiceParent,ipc::Endpoint<PGMPServiceParent> && aEndpoint,bool * aResult)1700 OpenPGMPServiceParent(RefPtr<GMPServiceParent>&& aGMPServiceParent,
1701 ipc::Endpoint<PGMPServiceParent>&& aEndpoint,
1702 bool* aResult)
1703 : Runnable("gmp::OpenPGMPServiceParent"),
1704 mGMPServiceParent(aGMPServiceParent),
1705 mEndpoint(std::move(aEndpoint)),
1706 mResult(aResult) {}
1707
Run()1708 NS_IMETHOD Run() override {
1709 *mResult = mEndpoint.Bind(mGMPServiceParent);
1710 return NS_OK;
1711 }
1712
1713 private:
1714 RefPtr<GMPServiceParent> mGMPServiceParent;
1715 ipc::Endpoint<PGMPServiceParent> mEndpoint;
1716 bool* mResult;
1717 };
1718
1719 /* static */
Create(Endpoint<PGMPServiceParent> && aGMPService)1720 bool GMPServiceParent::Create(Endpoint<PGMPServiceParent>&& aGMPService) {
1721 RefPtr<GeckoMediaPluginServiceParent> gmp =
1722 GeckoMediaPluginServiceParent::GetSingleton();
1723
1724 if (gmp->mShuttingDown) {
1725 // Shutdown is initiated. There is no point creating a new actor.
1726 return false;
1727 }
1728
1729 nsCOMPtr<nsIThread> gmpThread;
1730 nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
1731 NS_ENSURE_SUCCESS(rv, false);
1732
1733 RefPtr<GMPServiceParent> serviceParent(new GMPServiceParent(gmp));
1734 bool ok;
1735 rv = gmpThread->Dispatch(
1736 new OpenPGMPServiceParent(std::move(serviceParent),
1737 std::move(aGMPService), &ok),
1738 NS_DISPATCH_SYNC);
1739
1740 if (NS_WARN_IF(NS_FAILED(rv) || !ok)) {
1741 return false;
1742 }
1743
1744 // Now that the service parent is set up, it will be released by IPC
1745 // refcounting, so we don't need to hold any more references here.
1746
1747 return true;
1748 }
1749
1750 } // namespace gmp
1751 } // namespace mozilla
1752
1753 #undef NS_DispatchToMainThread
1754 #undef __CLASS__
1755