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 "GMPParent.h"
7
8 #include "CDMStorageIdProvider.h"
9 #include "ChromiumCDMAdapter.h"
10 #include "GMPContentParent.h"
11 #include "GMPLog.h"
12 #include "GMPTimerParent.h"
13 #include "MediaResult.h"
14 #include "mozIGeckoMediaPluginService.h"
15 #include "mozilla/dom/WidevineCDMManifestBinding.h"
16 #include "mozilla/ipc/CrashReporterHost.h"
17 #include "mozilla/ipc/Endpoint.h"
18 #include "mozilla/ipc/GeckoChildProcessHost.h"
19 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
20 # include "mozilla/SandboxInfo.h"
21 #endif
22 #include "mozilla/Services.h"
23 #include "mozilla/SSE.h"
24 #include "mozilla/SyncRunnable.h"
25 #include "mozilla/Telemetry.h"
26 #include "mozilla/Unused.h"
27 #include "nsComponentManagerUtils.h"
28 #include "nsIRunnable.h"
29 #include "nsIObserverService.h"
30 #include "nsIWritablePropertyBag2.h"
31 #include "nsPrintfCString.h"
32 #include "nsThreadUtils.h"
33 #include "runnable_utils.h"
34 #include "VideoUtils.h"
35 #ifdef XP_WIN
36 # include "WMFDecoderModule.h"
37 #endif
38 #if defined(MOZ_WIDGET_ANDROID)
39 # include "mozilla/java/GeckoProcessManagerWrappers.h"
40 # include "mozilla/java/GeckoProcessTypeWrappers.h"
41 #endif // defined(MOZ_WIDGET_ANDROID)
42 #if defined(XP_MACOSX)
43 # include "nsMacUtilsImpl.h"
44 # include "base/process_util.h"
45 #endif // defined(XP_MACOSX)
46
47 using mozilla::ipc::GeckoChildProcessHost;
48
49 using CrashReporter::AnnotationTable;
50 using CrashReporter::GetIDFromMinidump;
51
52 namespace mozilla::gmp {
53
54 #define GMP_PARENT_LOG_DEBUG(x, ...) \
55 GMP_LOG_DEBUG("GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__)
56
57 #ifdef __CLASS__
58 # undef __CLASS__
59 #endif
60 #define __CLASS__ "GMPParent"
61
GMPParent()62 GMPParent::GMPParent()
63 : mState(GMPStateNotLoaded),
64 mPluginId(GeckoChildProcessHost::GetUniqueID()),
65 mProcess(nullptr),
66 mDeleteProcessOnlyOnUnload(false),
67 mAbnormalShutdownInProgress(false),
68 mIsBlockingDeletion(false),
69 mCanDecrypt(false),
70 mGMPContentChildCount(0),
71 mChildPid(0),
72 mHoldingSelfRef(false),
73 #if defined(XP_MACOSX) && defined(__aarch64__)
74 mChildLaunchArch(base::PROCESS_ARCH_INVALID),
75 #endif
76 mMainThread(GetMainThreadSerialEventTarget()) {
77 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
78 GMP_PARENT_LOG_DEBUG("GMPParent ctor id=%u", mPluginId);
79 }
80
~GMPParent()81 GMPParent::~GMPParent() {
82 // This method is not restricted to a specific thread.
83 GMP_PARENT_LOG_DEBUG("GMPParent dtor id=%u", mPluginId);
84 MOZ_ASSERT(!mProcess);
85 }
86
CloneFrom(const GMPParent * aOther)87 void GMPParent::CloneFrom(const GMPParent* aOther) {
88 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
89 MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
90
91 mService = aOther->mService;
92 mDirectory = aOther->mDirectory;
93 mName = aOther->mName;
94 mVersion = aOther->mVersion;
95 mDescription = aOther->mDescription;
96 mDisplayName = aOther->mDisplayName;
97 #if defined(XP_WIN) || defined(XP_LINUX)
98 mLibs = aOther->mLibs;
99 #endif
100 for (const GMPCapability& cap : aOther->mCapabilities) {
101 mCapabilities.AppendElement(cap);
102 }
103 mAdapter = aOther->mAdapter;
104
105 #if defined(XP_MACOSX) && defined(__aarch64__)
106 mChildLaunchArch = aOther->mChildLaunchArch;
107 #endif
108 }
109
110 #if defined(XP_MACOSX)
GetPluginFileArch(nsIFile * aPluginDir,nsAutoString & aLeafName,uint32_t & aArchSet)111 nsresult GMPParent::GetPluginFileArch(nsIFile* aPluginDir,
112 nsAutoString& aLeafName,
113 uint32_t& aArchSet) {
114 // Build up the plugin filename
115 nsAutoString baseName;
116 baseName = Substring(aLeafName, 4, aLeafName.Length() - 1);
117 nsAutoString pluginFileName = u"lib"_ns + baseName + u".dylib"_ns;
118 GMP_PARENT_LOG_DEBUG("%s: pluginFileName: %s", __FUNCTION__,
119 NS_LossyConvertUTF16toASCII(pluginFileName).get());
120
121 // Create an nsIFile representing the plugin
122 nsCOMPtr<nsIFile> pluginFile;
123 nsresult rv = aPluginDir->Clone(getter_AddRefs(pluginFile));
124 NS_ENSURE_SUCCESS(rv, rv);
125 pluginFile->AppendRelativePath(pluginFileName);
126
127 // Get the full plugin path
128 nsCString pluginPath;
129 rv = pluginFile->GetNativePath(pluginPath);
130 NS_ENSURE_SUCCESS(rv, rv);
131 GMP_PARENT_LOG_DEBUG("%s: pluginPath: %s", __FUNCTION__, pluginPath.get());
132
133 rv = nsMacUtilsImpl::GetArchitecturesForBinary(pluginPath.get(), &aArchSet);
134 NS_ENSURE_SUCCESS(rv, rv);
135
136 # if defined(__aarch64__)
137 mPluginFilePath = pluginPath;
138 # endif
139
140 return NS_OK;
141 }
142 #endif // defined(XP_MACOSX)
143
Init(GeckoMediaPluginServiceParent * aService,nsIFile * aPluginDir)144 RefPtr<GenericPromise> GMPParent::Init(GeckoMediaPluginServiceParent* aService,
145 nsIFile* aPluginDir) {
146 MOZ_ASSERT(aPluginDir);
147 MOZ_ASSERT(aService);
148 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
149
150 mService = aService;
151 mDirectory = aPluginDir;
152
153 // aPluginDir is <profile-dir>/<gmp-plugin-id>/<version>
154 // where <gmp-plugin-id> should be gmp-gmpopenh264
155 nsCOMPtr<nsIFile> parent;
156 nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent));
157 if (NS_WARN_IF(NS_FAILED(rv))) {
158 return GenericPromise::CreateAndReject(rv, __func__);
159 }
160 nsAutoString parentLeafName;
161 rv = parent->GetLeafName(parentLeafName);
162 if (NS_WARN_IF(NS_FAILED(rv))) {
163 return GenericPromise::CreateAndReject(rv, __func__);
164 }
165 GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__,
166 NS_LossyConvertUTF16toASCII(parentLeafName).get());
167
168 MOZ_ASSERT(parentLeafName.Length() > 4);
169 mName = Substring(parentLeafName, 4);
170
171 #if defined(XP_MACOSX)
172 uint32_t pluginArch = 0;
173 rv = GetPluginFileArch(aPluginDir, parentLeafName, pluginArch);
174 if (NS_FAILED(rv)) {
175 GMP_PARENT_LOG_DEBUG("%s: Plugin arch error: %d", __FUNCTION__, rv);
176 } else {
177 GMP_PARENT_LOG_DEBUG("%s: Plugin arch: 0x%x", __FUNCTION__, pluginArch);
178 }
179
180 uint32_t x86 = base::PROCESS_ARCH_X86_64 | base::PROCESS_ARCH_I386;
181 # if defined(__aarch64__)
182 uint32_t arm64 = base::PROCESS_ARCH_ARM_64;
183 // When executing in an ARM64 process, if the library is x86 or x64,
184 // set |mChildLaunchArch| to x64 and allow the library to be used as long
185 // as this process is a universal binary.
186 if (!(pluginArch & arm64) && (pluginArch & x86)) {
187 bool isWidevine = parentLeafName.Find("widevine") != kNotFound;
188 bool isWidevineAllowed =
189 StaticPrefs::media_gmp_widevinecdm_allow_x64_plugin_on_arm64();
190 bool isH264 = parentLeafName.Find("openh264") != kNotFound;
191 bool isH264Allowed =
192 StaticPrefs::media_gmp_gmpopenh264_allow_x64_plugin_on_arm64();
193
194 // Only allow x64 child GMP processes for Widevine and OpenH264
195 if (!isWidevine && !isH264) {
196 return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
197 __func__);
198 }
199 // And only if prefs permit it.
200 if ((isWidevine && !isWidevineAllowed) || (isH264 && !isH264Allowed)) {
201 return GenericPromise::CreateAndReject(NS_ERROR_PLUGIN_DISABLED,
202 __func__);
203 }
204
205 // We have an x64 library. Get the bundle architecture to determine
206 // if we are a universal binary and hence if we can launch an x64
207 // child process to host this plugin.
208 uint32_t bundleArch = base::PROCESS_ARCH_INVALID;
209 rv = nsMacUtilsImpl::GetArchitecturesForBundle(&bundleArch);
210 if (NS_FAILED(rv)) {
211 // If we fail here, continue as if this is not a univeral binary.
212 GMP_PARENT_LOG_DEBUG("%s: Bundle arch error: %d", __FUNCTION__, rv);
213 } else {
214 GMP_PARENT_LOG_DEBUG("%s: Bundle arch: 0x%x", __FUNCTION__, bundleArch);
215 }
216
217 bool isUniversalBinary = (bundleArch & base::PROCESS_ARCH_X86_64) &&
218 (bundleArch & base::PROCESS_ARCH_ARM_64);
219 if (isUniversalBinary) {
220 mChildLaunchArch = base::PROCESS_ARCH_X86_64;
221 PreTranslateBins();
222 } else {
223 return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
224 __func__);
225 }
226 }
227 # else
228 // When executing in a non-ARM process, if the library is not x86 or x64,
229 // remove it and return an error. This prevents a child process crash due
230 // to loading an incompatible library and forces a new plugin version to be
231 // downloaded when the check is next performed. This could occur if a profile
232 // is moved from an ARM64 system to an x64 system.
233 if ((pluginArch & x86) == 0) {
234 GMP_PARENT_LOG_DEBUG("%s: Removing plugin directory", __FUNCTION__);
235 aPluginDir->Remove(true);
236 return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
237 }
238 # endif // defined(__aarch64__)
239 #endif // defined(XP_MACOSX)
240
241 return ReadGMPMetaData();
242 }
243
Crash()244 void GMPParent::Crash() {
245 if (mState != GMPStateNotLoaded) {
246 Unused << SendCrashPluginNow();
247 }
248 }
249
LoadProcess()250 nsresult GMPParent::LoadProcess() {
251 MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
252 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
253 MOZ_ASSERT(mState == GMPStateNotLoaded);
254
255 nsAutoString path;
256 if (NS_WARN_IF(NS_FAILED(mDirectory->GetPath(path)))) {
257 return NS_ERROR_FAILURE;
258 }
259 GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__,
260 NS_ConvertUTF16toUTF8(path).get());
261
262 if (!mProcess) {
263 mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
264 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
265 mProcess->SetRequiresWindowServer(mAdapter.EqualsLiteral("chromium"));
266 #endif
267
268 #if defined(XP_MACOSX) && defined(__aarch64__)
269 mProcess->SetLaunchArchitecture(mChildLaunchArch);
270 #endif
271
272 if (!mProcess->Launch(30 * 1000)) {
273 GMP_PARENT_LOG_DEBUG("%s: Failed to launch new child process",
274 __FUNCTION__);
275 mProcess->Delete();
276 mProcess = nullptr;
277 return NS_ERROR_FAILURE;
278 }
279
280 mChildPid = base::GetProcId(mProcess->GetChildProcessHandle());
281 GMP_PARENT_LOG_DEBUG("%s: Launched new child process", __FUNCTION__);
282
283 bool opened = Open(mProcess->TakeInitialPort(),
284 base::GetProcId(mProcess->GetChildProcessHandle()));
285 if (!opened) {
286 GMP_PARENT_LOG_DEBUG("%s: Failed to open channel to new child process",
287 __FUNCTION__);
288 mProcess->Delete();
289 mProcess = nullptr;
290 return NS_ERROR_FAILURE;
291 }
292 GMP_PARENT_LOG_DEBUG("%s: Opened channel to new child process",
293 __FUNCTION__);
294
295 // ComputeStorageId may return empty string, we leave the error handling to
296 // CDM. The CDM will reject the promise once we provide a empty string of
297 // storage id.
298 bool ok =
299 SendProvideStorageId(CDMStorageIdProvider::ComputeStorageId(mNodeId));
300 if (!ok) {
301 GMP_PARENT_LOG_DEBUG("%s: Failed to send storage id to child process",
302 __FUNCTION__);
303 return NS_ERROR_FAILURE;
304 }
305 GMP_PARENT_LOG_DEBUG("%s: Sent storage id to child process", __FUNCTION__);
306
307 #if defined(XP_WIN) || defined(XP_LINUX)
308 if (!mLibs.IsEmpty()) {
309 bool ok = SendPreloadLibs(mLibs);
310 if (!ok) {
311 GMP_PARENT_LOG_DEBUG("%s: Failed to send preload-libs to child process",
312 __FUNCTION__);
313 return NS_ERROR_FAILURE;
314 }
315 GMP_PARENT_LOG_DEBUG("%s: Sent preload-libs ('%s') to child process",
316 __FUNCTION__, mLibs.get());
317 }
318 #endif
319
320 // Intr call to block initialization on plugin load.
321 if (!CallStartPlugin(mAdapter)) {
322 GMP_PARENT_LOG_DEBUG("%s: Failed to send start to child process",
323 __FUNCTION__);
324 return NS_ERROR_FAILURE;
325 }
326 GMP_PARENT_LOG_DEBUG("%s: Sent StartPlugin to child process", __FUNCTION__);
327 }
328
329 mState = GMPStateLoaded;
330
331 // Hold a self ref while the child process is alive. This ensures that
332 // during shutdown the GMPParent stays alive long enough to
333 // terminate the child process.
334 MOZ_ASSERT(!mHoldingSelfRef);
335 mHoldingSelfRef = true;
336 AddRef();
337
338 return NS_OK;
339 }
340
RecvPGMPContentChildDestroyed()341 mozilla::ipc::IPCResult GMPParent::RecvPGMPContentChildDestroyed() {
342 --mGMPContentChildCount;
343 if (!IsUsed()) {
344 CloseIfUnused();
345 }
346 return IPC_OK();
347 }
348
CloseIfUnused()349 void GMPParent::CloseIfUnused() {
350 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
351 GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
352
353 if ((mDeleteProcessOnlyOnUnload || mState == GMPStateLoaded ||
354 mState == GMPStateUnloading) &&
355 !IsUsed()) {
356 // Ensure all timers are killed.
357 for (uint32_t i = mTimers.Length(); i > 0; i--) {
358 mTimers[i - 1]->Shutdown();
359 }
360
361 // Shutdown GMPStorage. Given that all protocol actors must be shutdown
362 // (!Used() is true), all storage operations should be complete.
363 for (size_t i = mStorage.Length(); i > 0; i--) {
364 mStorage[i - 1]->Shutdown();
365 }
366 Shutdown();
367 }
368 }
369
CloseActive(bool aDieWhenUnloaded)370 void GMPParent::CloseActive(bool aDieWhenUnloaded) {
371 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
372 GMP_PARENT_LOG_DEBUG("%s: state %d", __FUNCTION__, mState);
373
374 if (aDieWhenUnloaded) {
375 mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
376 }
377 if (mState == GMPStateLoaded) {
378 mState = GMPStateUnloading;
379 }
380 if (mState != GMPStateNotLoaded && IsUsed()) {
381 Unused << SendCloseActive();
382 CloseIfUnused();
383 }
384 }
385
MarkForDeletion()386 void GMPParent::MarkForDeletion() {
387 mDeleteProcessOnlyOnUnload = true;
388 mIsBlockingDeletion = true;
389 }
390
IsMarkedForDeletion()391 bool GMPParent::IsMarkedForDeletion() { return mIsBlockingDeletion; }
392
Shutdown()393 void GMPParent::Shutdown() {
394 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
395 GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
396
397 if (mAbnormalShutdownInProgress) {
398 return;
399 }
400
401 MOZ_ASSERT(!IsUsed());
402 if (mState == GMPStateNotLoaded || mState == GMPStateClosing) {
403 return;
404 }
405
406 RefPtr<GMPParent> self(this);
407 DeleteProcess();
408
409 // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
410 // Bug 1043671 is fixed
411 if (!mDeleteProcessOnlyOnUnload) {
412 // Destroy ourselves and rise from the fire to save memory
413 mService->ReAddOnGMPThread(self);
414 } // else we've been asked to die and stay dead
415 MOZ_ASSERT(mState == GMPStateNotLoaded);
416 }
417
418 class NotifyGMPShutdownTask : public Runnable {
419 public:
NotifyGMPShutdownTask(const nsAString & aNodeId)420 explicit NotifyGMPShutdownTask(const nsAString& aNodeId)
421 : Runnable("NotifyGMPShutdownTask"), mNodeId(aNodeId) {}
Run()422 NS_IMETHOD Run() override {
423 MOZ_ASSERT(NS_IsMainThread());
424 nsCOMPtr<nsIObserverService> obsService =
425 mozilla::services::GetObserverService();
426 MOZ_ASSERT(obsService);
427 if (obsService) {
428 obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get());
429 }
430 return NS_OK;
431 }
432 nsString mNodeId;
433 };
434
ChildTerminated()435 void GMPParent::ChildTerminated() {
436 RefPtr<GMPParent> self(this);
437 nsCOMPtr<nsISerialEventTarget> gmpEventTarget = GMPEventTarget();
438
439 if (!gmpEventTarget) {
440 // Bug 1163239 - this can happen on shutdown.
441 // PluginTerminated removes the GMP from the GMPService.
442 // On shutdown we can have this case where it is already been
443 // removed so there is no harm in not trying to remove it again.
444 GMP_PARENT_LOG_DEBUG("%s::%s: GMPEventTarget() returned nullptr.",
445 __CLASS__, __FUNCTION__);
446 } else {
447 gmpEventTarget->Dispatch(
448 NewRunnableMethod<RefPtr<GMPParent>>(
449 "gmp::GeckoMediaPluginServiceParent::PluginTerminated", mService,
450 &GeckoMediaPluginServiceParent::PluginTerminated, self),
451 NS_DISPATCH_NORMAL);
452 }
453 }
454
DeleteProcess()455 void GMPParent::DeleteProcess() {
456 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
457 GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
458
459 if (mState != GMPStateClosing) {
460 // Don't Close() twice!
461 // Probably remove when bug 1043671 is resolved
462 mState = GMPStateClosing;
463 Close();
464 }
465 mProcess->Delete(NewRunnableMethod("gmp::GMPParent::ChildTerminated", this,
466 &GMPParent::ChildTerminated));
467 GMP_PARENT_LOG_DEBUG("%s: Shut down process", __FUNCTION__);
468 mProcess = nullptr;
469
470 #if defined(MOZ_WIDGET_ANDROID)
471 if (mState != GMPStateNotLoaded) {
472 nsCOMPtr<nsIEventTarget> launcherThread(GetIPCLauncher());
473 MOZ_ASSERT(launcherThread);
474
475 auto procType = java::GeckoProcessType::GMPLUGIN();
476 auto selector =
477 java::GeckoProcessManager::Selector::New(procType, OtherPid());
478
479 launcherThread->Dispatch(NS_NewRunnableFunction(
480 "GMPParent::DeleteProcess",
481 [selector =
482 java::GeckoProcessManager::Selector::GlobalRef(selector)]() {
483 java::GeckoProcessManager::ShutdownProcess(selector);
484 }));
485 }
486 #endif // defined(MOZ_WIDGET_ANDROID)
487
488 mState = GMPStateNotLoaded;
489
490 nsCOMPtr<nsIRunnable> r =
491 new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId));
492 mMainThread->Dispatch(r.forget());
493
494 if (mHoldingSelfRef) {
495 Release();
496 mHoldingSelfRef = false;
497 }
498 }
499
State() const500 GMPState GMPParent::State() const { return mState; }
501
GMPEventTarget()502 nsCOMPtr<nsISerialEventTarget> GMPParent::GMPEventTarget() {
503 nsCOMPtr<mozIGeckoMediaPluginService> mps =
504 do_GetService("@mozilla.org/gecko-media-plugin-service;1");
505 MOZ_ASSERT(mps);
506 if (!mps) {
507 return nullptr;
508 }
509 // Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
510 // nullptr if the GeckoMediaPluginService has started shutdown.
511 nsCOMPtr<nsIThread> gmpThread;
512 mps->GetThread(getter_AddRefs(gmpThread));
513 return gmpThread ? gmpThread->SerialEventTarget() : nullptr;
514 }
515
516 /* static */
Supports(const nsTArray<GMPCapability> & aCapabilities,const nsCString & aAPI,const nsTArray<nsCString> & aTags)517 bool GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
518 const nsCString& aAPI,
519 const nsTArray<nsCString>& aTags) {
520 for (const nsCString& tag : aTags) {
521 if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) {
522 return false;
523 }
524 }
525 return true;
526 }
527
528 /* static */
Supports(const nsTArray<GMPCapability> & aCapabilities,const nsCString & aAPI,const nsCString & aTag)529 bool GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
530 const nsCString& aAPI, const nsCString& aTag) {
531 for (const GMPCapability& capabilities : aCapabilities) {
532 if (!capabilities.mAPIName.Equals(aAPI)) {
533 continue;
534 }
535 for (const nsCString& tag : capabilities.mAPITags) {
536 if (tag.Equals(aTag)) {
537 #ifdef XP_WIN
538 // Clearkey on Windows advertises that it can decode in its GMP info
539 // file, but uses Windows Media Foundation to decode. That's not present
540 // on Windows XP, and on some Vista, Windows N, and KN variants without
541 // certain services packs.
542 if (tag.EqualsLiteral(EME_KEY_SYSTEM_CLEARKEY)) {
543 if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) {
544 if (!WMFDecoderModule::HasH264()) {
545 continue;
546 }
547 }
548 }
549 #endif
550 return true;
551 }
552 }
553 }
554 return false;
555 }
556
EnsureProcessLoaded()557 bool GMPParent::EnsureProcessLoaded() {
558 if (mState == GMPStateLoaded) {
559 return true;
560 }
561 if (mState == GMPStateClosing || mState == GMPStateUnloading) {
562 return false;
563 }
564
565 nsresult rv = LoadProcess();
566
567 return NS_SUCCEEDED(rv);
568 }
569
AddCrashAnnotations()570 void GMPParent::AddCrashAnnotations() {
571 if (mCrashReporter) {
572 mCrashReporter->AddAnnotation(CrashReporter::Annotation::GMPPlugin, true);
573 mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginFilename,
574 NS_ConvertUTF16toUTF8(mName));
575 mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginName,
576 mDisplayName);
577 mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginVersion,
578 mVersion);
579 }
580 }
581
GetCrashID(nsString & aResult)582 void GMPParent::GetCrashID(nsString& aResult) {
583 AddCrashAnnotations();
584 GenerateCrashReport(OtherPid(), &aResult);
585 }
586
GMPNotifyObservers(const uint32_t aPluginID,const nsACString & aPluginName,const nsAString & aPluginDumpID)587 static void GMPNotifyObservers(const uint32_t aPluginID,
588 const nsACString& aPluginName,
589 const nsAString& aPluginDumpID) {
590 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
591 nsCOMPtr<nsIWritablePropertyBag2> propbag =
592 do_CreateInstance("@mozilla.org/hash-property-bag;1");
593 if (obs && propbag) {
594 propbag->SetPropertyAsUint32(u"pluginID"_ns, aPluginID);
595 propbag->SetPropertyAsACString(u"pluginName"_ns, aPluginName);
596 propbag->SetPropertyAsAString(u"pluginDumpID"_ns, aPluginDumpID);
597 obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr);
598 }
599
600 RefPtr<gmp::GeckoMediaPluginService> service =
601 gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
602 if (service) {
603 service->RunPluginCrashCallbacks(aPluginID, aPluginName);
604 }
605 }
606
ActorDestroy(ActorDestroyReason aWhy)607 void GMPParent::ActorDestroy(ActorDestroyReason aWhy) {
608 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
609 GMP_PARENT_LOG_DEBUG("%s: (%d)", __FUNCTION__, (int)aWhy);
610
611 if (AbnormalShutdown == aWhy) {
612 Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "gmplugin"_ns,
613 1);
614 nsString dumpID;
615 GetCrashID(dumpID);
616 if (dumpID.IsEmpty()) {
617 NS_WARNING("GMP crash without crash report");
618 dumpID = mName;
619 dumpID += '-';
620 AppendUTF8toUTF16(mVersion, dumpID);
621 }
622
623 // NotifyObservers is mainthread-only
624 nsCOMPtr<nsIRunnable> r =
625 WrapRunnableNM(&GMPNotifyObservers, mPluginId, mDisplayName, dumpID);
626 mMainThread->Dispatch(r.forget());
627 }
628
629 // warn us off trying to close again
630 mState = GMPStateClosing;
631 mAbnormalShutdownInProgress = true;
632 CloseActive(false);
633
634 // Normal Shutdown() will delete the process on unwind.
635 if (AbnormalShutdown == aWhy) {
636 RefPtr<GMPParent> self(this);
637 // Must not call Close() again in DeleteProcess(), as we'll recurse
638 // infinitely if we do.
639 MOZ_ASSERT(mState == GMPStateClosing);
640 DeleteProcess();
641 // Note: final destruction will be Dispatched to ourself
642 mService->ReAddOnGMPThread(self);
643 }
644 }
645
AllocPGMPStorageParent()646 PGMPStorageParent* GMPParent::AllocPGMPStorageParent() {
647 GMPStorageParent* p = new GMPStorageParent(mNodeId, this);
648 mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
649 return p;
650 }
651
DeallocPGMPStorageParent(PGMPStorageParent * aActor)652 bool GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor) {
653 GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
654 p->Shutdown();
655 mStorage.RemoveElement(p);
656 return true;
657 }
658
RecvPGMPStorageConstructor(PGMPStorageParent * aActor)659 mozilla::ipc::IPCResult GMPParent::RecvPGMPStorageConstructor(
660 PGMPStorageParent* aActor) {
661 GMPStorageParent* p = (GMPStorageParent*)aActor;
662 if (NS_WARN_IF(NS_FAILED(p->Init()))) {
663 return IPC_FAIL_NO_REASON(this);
664 }
665 return IPC_OK();
666 }
667
RecvPGMPTimerConstructor(PGMPTimerParent * actor)668 mozilla::ipc::IPCResult GMPParent::RecvPGMPTimerConstructor(
669 PGMPTimerParent* actor) {
670 return IPC_OK();
671 }
672
AllocPGMPTimerParent()673 PGMPTimerParent* GMPParent::AllocPGMPTimerParent() {
674 nsCOMPtr<nsISerialEventTarget> target = GMPEventTarget();
675 GMPTimerParent* p = new GMPTimerParent(target);
676 mTimers.AppendElement(
677 p); // Released in DeallocPGMPTimerParent, or on shutdown.
678 return p;
679 }
680
DeallocPGMPTimerParent(PGMPTimerParent * aActor)681 bool GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor) {
682 GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
683 p->Shutdown();
684 mTimers.RemoveElement(p);
685 return true;
686 }
687
ReadInfoField(GMPInfoFileParser & aParser,const nsCString & aKey,nsACString & aOutValue)688 bool ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey,
689 nsACString& aOutValue) {
690 if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) {
691 return false;
692 }
693 aOutValue = aParser.Get(aKey);
694 return true;
695 }
696
ReadGMPMetaData()697 RefPtr<GenericPromise> GMPParent::ReadGMPMetaData() {
698 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
699 MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
700 MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!");
701
702 nsCOMPtr<nsIFile> infoFile;
703 nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
704 if (NS_WARN_IF(NS_FAILED(rv))) {
705 return GenericPromise::CreateAndReject(rv, __func__);
706 }
707 infoFile->AppendRelativePath(mName + u".info"_ns);
708
709 if (FileExists(infoFile)) {
710 return ReadGMPInfoFile(infoFile);
711 }
712
713 // Maybe this is the Widevine adapted plugin?
714 nsCOMPtr<nsIFile> manifestFile;
715 rv = mDirectory->Clone(getter_AddRefs(manifestFile));
716 if (NS_WARN_IF(NS_FAILED(rv))) {
717 return GenericPromise::CreateAndReject(rv, __func__);
718 }
719 manifestFile->AppendRelativePath(u"manifest.json"_ns);
720 return ReadChromiumManifestFile(manifestFile);
721 }
722
ReadGMPInfoFile(nsIFile * aFile)723 RefPtr<GenericPromise> GMPParent::ReadGMPInfoFile(nsIFile* aFile) {
724 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
725 GMPInfoFileParser parser;
726 if (!parser.Init(aFile)) {
727 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
728 }
729
730 nsAutoCString apis;
731 if (!ReadInfoField(parser, "name"_ns, mDisplayName) ||
732 !ReadInfoField(parser, "description"_ns, mDescription) ||
733 !ReadInfoField(parser, "version"_ns, mVersion) ||
734 !ReadInfoField(parser, "apis"_ns, apis)) {
735 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
736 }
737
738 #if defined(XP_WIN) || defined(XP_LINUX)
739 // "Libraries" field is optional.
740 ReadInfoField(parser, "libraries"_ns, mLibs);
741 #endif
742
743 nsTArray<nsCString> apiTokens;
744 SplitAt(", ", apis, apiTokens);
745 for (nsCString api : apiTokens) {
746 int32_t tagsStart = api.FindChar('[');
747 if (tagsStart == 0) {
748 // Not allowed to be the first character.
749 // API name must be at least one character.
750 continue;
751 }
752
753 GMPCapability cap;
754 if (tagsStart == -1) {
755 // No tags.
756 cap.mAPIName.Assign(api);
757 } else {
758 auto tagsEnd = api.FindChar(']');
759 if (tagsEnd == -1 || tagsEnd < tagsStart) {
760 // Invalid syntax, skip whole capability.
761 continue;
762 }
763
764 cap.mAPIName.Assign(Substring(api, 0, tagsStart));
765
766 if ((tagsEnd - tagsStart) > 1) {
767 const nsDependentCSubstring ts(
768 Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
769 nsTArray<nsCString> tagTokens;
770 SplitAt(":", ts, tagTokens);
771 for (nsCString tag : tagTokens) {
772 cap.mAPITags.AppendElement(tag);
773 }
774 }
775 }
776
777 mCapabilities.AppendElement(std::move(cap));
778 }
779
780 if (mCapabilities.IsEmpty()) {
781 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
782 }
783
784 return GenericPromise::CreateAndResolve(true, __func__);
785 }
786
ReadChromiumManifestFile(nsIFile * aFile)787 RefPtr<GenericPromise> GMPParent::ReadChromiumManifestFile(nsIFile* aFile) {
788 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
789 nsAutoCString json;
790 if (!ReadIntoString(aFile, json, 5 * 1024)) {
791 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
792 }
793
794 // DOM JSON parsing needs to run on the main thread.
795 return InvokeAsync(mMainThread, this, __func__,
796 &GMPParent::ParseChromiumManifest,
797 NS_ConvertUTF8toUTF16(json));
798 }
799
IsCDMAPISupported(const mozilla::dom::WidevineCDMManifest & aManifest)800 static bool IsCDMAPISupported(
801 const mozilla::dom::WidevineCDMManifest& aManifest) {
802 nsresult ignored; // Note: ToInteger returns 0 on failure.
803 int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored);
804 int32_t interfaceVersion =
805 aManifest.mX_cdm_interface_versions.ToInteger(&ignored);
806 int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored);
807 return ChromiumCDMAdapter::Supports(moduleVersion, interfaceVersion,
808 hostVersion);
809 }
810
ParseChromiumManifest(const nsAString & aJSON)811 RefPtr<GenericPromise> GMPParent::ParseChromiumManifest(
812 const nsAString& aJSON) {
813 GMP_PARENT_LOG_DEBUG("%s: for '%s'", __FUNCTION__,
814 NS_LossyConvertUTF16toASCII(aJSON).get());
815
816 MOZ_ASSERT(NS_IsMainThread());
817 mozilla::dom::WidevineCDMManifest m;
818 if (!m.Init(aJSON)) {
819 GMP_PARENT_LOG_DEBUG("%s: Failed to initialize json parser, failing.",
820 __FUNCTION__);
821 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
822 }
823
824 if (!IsCDMAPISupported(m)) {
825 GMP_PARENT_LOG_DEBUG("%s: CDM API not supported, failing.", __FUNCTION__);
826 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
827 }
828
829 CopyUTF16toUTF8(m.mName, mDisplayName);
830 CopyUTF16toUTF8(m.mDescription, mDescription);
831 CopyUTF16toUTF8(m.mVersion, mVersion);
832
833 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
834 if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
835 nsPrintfCString msg(
836 "GMPParent::ParseChromiumManifest: Plugin \"%s\" is an EME CDM"
837 " but this system can't sandbox it; not loading.",
838 mDisplayName.get());
839 printf_stderr("%s\n", msg.get());
840 GMP_PARENT_LOG_DEBUG("%s", msg.get());
841 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
842 }
843 #endif
844
845 nsCString kEMEKeySystem;
846
847 // We hard code a few of the settings because they can't be stored in the
848 // widevine manifest without making our API different to widevine's.
849 if (mDisplayName.EqualsASCII("clearkey")) {
850 kEMEKeySystem.AssignLiteral(EME_KEY_SYSTEM_CLEARKEY);
851 #if XP_WIN
852 mLibs = nsLiteralCString(
853 "dxva2.dll, evr.dll, freebl3.dll, mfh264dec.dll, mfplat.dll, "
854 "msmpeg2vdec.dll, nss3.dll, softokn3.dll");
855 #elif XP_LINUX
856 mLibs = "libfreeblpriv3.so, libsoftokn3.so"_ns;
857 #endif
858 } else if (mDisplayName.EqualsASCII("WidevineCdm")) {
859 kEMEKeySystem.AssignLiteral(EME_KEY_SYSTEM_WIDEVINE);
860 #if XP_WIN
861 // psapi.dll added for GetMappedFileNameW, which could possibly be avoided
862 // in future versions, see bug 1383611 for details.
863 mLibs = "dxva2.dll, psapi.dll"_ns;
864 #endif
865 } else if (mDisplayName.EqualsASCII("fake")) {
866 kEMEKeySystem.AssignLiteral("fake");
867 #if XP_WIN
868 mLibs = "dxva2.dll"_ns;
869 #endif
870 } else {
871 GMP_PARENT_LOG_DEBUG("%s: Unrecognized key system: %s, failing.",
872 __FUNCTION__, mDisplayName.get());
873 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
874 }
875
876 #ifdef XP_LINUX
877 // These glibc libraries were merged into libc.so.6 as of glibc
878 // 2.34; they now exist only as stub libraries for compatibility and
879 // newly linked code won't depend on them, so we need to ensure
880 // they're loaded for plugins that may have been linked against a
881 // different version of glibc. (See also bug 1725828.)
882 if (!mDisplayName.EqualsASCII("clearkey")) {
883 if (!mLibs.IsEmpty()) {
884 mLibs.AppendLiteral(", ");
885 }
886 mLibs.AppendLiteral("libdl.so.2, libpthread.so.0, librt.so.1");
887 }
888 #endif
889
890 GMPCapability video;
891
892 nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
893 nsTArray<nsCString> codecs;
894 SplitAt(",", codecsString, codecs);
895
896 // Parse the codec strings in the manifest and map them to strings used
897 // internally by Gecko for capability recognition.
898 //
899 // Google's code to parse manifests can be used as a reference for strings
900 // the manifest may contain
901 // https://source.chromium.org/chromium/chromium/src/+/master:components/cdm/common/cdm_manifest.cc;l=74;drc=775880ced8a989191281e93854c7f2201f25068f
902 //
903 // Gecko's internal strings can be found at
904 // https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/dom/media/eme/MediaKeySystemAccess.cpp#149-155
905 for (const nsCString& chromiumCodec : codecs) {
906 nsCString codec;
907 if (chromiumCodec.EqualsASCII("vp8")) {
908 codec = "vp8"_ns;
909 } else if (chromiumCodec.EqualsASCII("vp9.0") || // Legacy string.
910 chromiumCodec.EqualsASCII("vp09")) {
911 codec = "vp9"_ns;
912 } else if (chromiumCodec.EqualsASCII("avc1")) {
913 codec = "h264"_ns;
914 } else if (chromiumCodec.EqualsASCII("av01")) {
915 codec = "av1"_ns;
916 } else {
917 GMP_PARENT_LOG_DEBUG("%s: Unrecognized codec: %s.", __FUNCTION__,
918 chromiumCodec.get());
919 MOZ_ASSERT_UNREACHABLE(
920 "Unhandled codec string! Need to add it to the parser.");
921 continue;
922 }
923
924 video.mAPITags.AppendElement(codec);
925 }
926
927 video.mAPITags.AppendElement(kEMEKeySystem);
928
929 video.mAPIName = nsLiteralCString(CHROMIUM_CDM_API);
930 mAdapter = u"chromium"_ns;
931
932 mCapabilities.AppendElement(std::move(video));
933
934 GMP_PARENT_LOG_DEBUG("%s: Successfully parsed manifest.", __FUNCTION__);
935 return GenericPromise::CreateAndResolve(true, __func__);
936 }
937
CanBeSharedCrossNodeIds() const938 bool GMPParent::CanBeSharedCrossNodeIds() const {
939 return mNodeId.IsEmpty() &&
940 // XXX bug 1159300 hack -- maybe remove after openh264 1.4
941 // We don't want to use CDM decoders for non-encrypted playback
942 // just yet; especially not for WebRTC. Don't allow CDMs to be used
943 // without a node ID.
944 !mCanDecrypt;
945 }
946
CanBeUsedFrom(const nsACString & aNodeId) const947 bool GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const {
948 return mNodeId == aNodeId;
949 }
950
SetNodeId(const nsACString & aNodeId)951 void GMPParent::SetNodeId(const nsACString& aNodeId) {
952 MOZ_ASSERT(!aNodeId.IsEmpty());
953 mNodeId = aNodeId;
954 }
955
GetDisplayName() const956 const nsCString& GMPParent::GetDisplayName() const { return mDisplayName; }
957
GetVersion() const958 const nsCString& GMPParent::GetVersion() const { return mVersion; }
959
GetPluginId() const960 uint32_t GMPParent::GetPluginId() const { return mPluginId; }
961
ResolveGetContentParentPromises()962 void GMPParent::ResolveGetContentParentPromises() {
963 nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises =
964 std::move(mGetContentParentPromises);
965 MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
966 RefPtr<GMPContentParent::CloseBlocker> blocker(
967 new GMPContentParent::CloseBlocker(mGMPContentParent));
968 for (auto& holder : promises) {
969 holder->Resolve(blocker, __func__);
970 }
971 }
972
OpenPGMPContent()973 bool GMPParent::OpenPGMPContent() {
974 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
975 MOZ_ASSERT(!mGMPContentParent);
976
977 Endpoint<PGMPContentParent> parent;
978 Endpoint<PGMPContentChild> child;
979 if (NS_WARN_IF(NS_FAILED(PGMPContent::CreateEndpoints(
980 base::GetCurrentProcId(), OtherPid(), &parent, &child)))) {
981 return false;
982 }
983
984 mGMPContentParent = new GMPContentParent(this);
985
986 if (!parent.Bind(mGMPContentParent)) {
987 return false;
988 }
989
990 if (!SendInitGMPContentChild(std::move(child))) {
991 return false;
992 }
993
994 ResolveGetContentParentPromises();
995
996 return true;
997 }
998
RejectGetContentParentPromises()999 void GMPParent::RejectGetContentParentPromises() {
1000 nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises =
1001 std::move(mGetContentParentPromises);
1002 MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
1003 for (auto& holder : promises) {
1004 holder->Reject(NS_ERROR_FAILURE, __func__);
1005 }
1006 }
1007
GetGMPContentParent(UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> && aPromiseHolder)1008 void GMPParent::GetGMPContentParent(
1009 UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>&& aPromiseHolder) {
1010 MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
1011 GMP_PARENT_LOG_DEBUG("%s %p", __FUNCTION__, this);
1012
1013 if (mGMPContentParent) {
1014 RefPtr<GMPContentParent::CloseBlocker> blocker(
1015 new GMPContentParent::CloseBlocker(mGMPContentParent));
1016 aPromiseHolder->Resolve(blocker, __func__);
1017 } else {
1018 mGetContentParentPromises.AppendElement(std::move(aPromiseHolder));
1019 // If we don't have a GMPContentParent and we try to get one for the first
1020 // time (mGetContentParentPromises.Length() == 1) then call
1021 // PGMPContent::Open. If more calls to GetGMPContentParent happen before
1022 // mGMPContentParent has been set then we should just store them, so that
1023 // they get called when we set mGMPContentParent as a result of the
1024 // PGMPContent::Open call.
1025 if (mGetContentParentPromises.Length() == 1) {
1026 if (!EnsureProcessLoaded() || !OpenPGMPContent()) {
1027 RejectGetContentParentPromises();
1028 return;
1029 }
1030 // We want to increment this as soon as possible, to avoid that we'd try
1031 // to shut down the GMP process while we're still trying to get a
1032 // PGMPContentParent actor.
1033 ++mGMPContentChildCount;
1034 }
1035 }
1036 }
1037
ForgetGMPContentParent()1038 already_AddRefed<GMPContentParent> GMPParent::ForgetGMPContentParent() {
1039 MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
1040 return mGMPContentParent.forget();
1041 }
1042
EnsureProcessLoaded(base::ProcessId * aID)1043 bool GMPParent::EnsureProcessLoaded(base::ProcessId* aID) {
1044 if (!EnsureProcessLoaded()) {
1045 return false;
1046 }
1047 *aID = OtherPid();
1048 return true;
1049 }
1050
IncrementGMPContentChildCount()1051 void GMPParent::IncrementGMPContentChildCount() { ++mGMPContentChildCount; }
1052
GetPluginBaseName() const1053 nsString GMPParent::GetPluginBaseName() const { return u"gmp-"_ns + mName; }
1054
1055 #if defined(XP_MACOSX) && defined(__aarch64__)
PreTranslateBins()1056 void GMPParent::PreTranslateBins() {
1057 nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
1058 "RosettaTranslation", this, &GMPParent::PreTranslateBinsWorker);
1059
1060 DebugOnly<nsresult> rv =
1061 NS_DispatchBackgroundTask(event.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
1062
1063 MOZ_ASSERT(NS_SUCCEEDED(rv));
1064 }
1065
PreTranslateBinsWorker()1066 void GMPParent::PreTranslateBinsWorker() {
1067 int rv = nsMacUtilsImpl::PreTranslateXUL();
1068 GMP_PARENT_LOG_DEBUG("%s: XUL translation result: %d", __FUNCTION__, rv);
1069
1070 rv = nsMacUtilsImpl::PreTranslateBinary(mPluginFilePath);
1071 GMP_PARENT_LOG_DEBUG("%s: %s translation result: %d", __FUNCTION__,
1072 mPluginFilePath.get(), rv);
1073 }
1074 #endif
1075
1076 } // namespace mozilla::gmp
1077
1078 #undef GMP_PARENT_LOG_DEBUG
1079 #undef __CLASS__
1080