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