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