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