/src/mozilla-central/dom/media/gmp/GMPService.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 "GMPService.h" |
7 | | #include "GMPServiceParent.h" |
8 | | #include "GMPServiceChild.h" |
9 | | #include "GMPContentParent.h" |
10 | | #include "prio.h" |
11 | | #include "mozilla/Logging.h" |
12 | | #include "GMPParent.h" |
13 | | #include "GMPVideoDecoderParent.h" |
14 | | #include "nsIObserverService.h" |
15 | | #include "GeckoChildProcessHost.h" |
16 | | #include "mozilla/ClearOnShutdown.h" |
17 | | #include "mozilla/SyncRunnable.h" |
18 | | #include "nsXPCOMPrivate.h" |
19 | | #include "mozilla/Services.h" |
20 | | #include "nsNativeCharsetUtils.h" |
21 | | #include "nsIXULAppInfo.h" |
22 | | #include "nsIConsoleService.h" |
23 | | #include "mozilla/Unused.h" |
24 | | #include "nsComponentManagerUtils.h" |
25 | | #include "runnable_utils.h" |
26 | | #include "VideoUtils.h" |
27 | | #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX) |
28 | | #include "mozilla/SandboxInfo.h" |
29 | | #endif |
30 | | #include "nsAppDirectoryServiceDefs.h" |
31 | | #include "nsDirectoryServiceUtils.h" |
32 | | #include "nsDirectoryServiceDefs.h" |
33 | | #include "nsHashKeys.h" |
34 | | #include "nsIFile.h" |
35 | | #include "nsISimpleEnumerator.h" |
36 | | #include "nsThreadUtils.h" |
37 | | #include "GMPCrashHelper.h" |
38 | | |
39 | | #include "MediaResult.h" |
40 | | #include "mozilla/dom/PluginCrashedEvent.h" |
41 | | #include "mozilla/EventDispatcher.h" |
42 | | #include "mozilla/Attributes.h" |
43 | | #include "mozilla/SystemGroup.h" |
44 | | |
45 | | namespace mozilla { |
46 | | |
47 | | #ifdef LOG |
48 | | #undef LOG |
49 | | #endif |
50 | | |
51 | | LogModule* |
52 | | GetGMPLog() |
53 | 0 | { |
54 | 0 | static LazyLogModule sLog("GMP"); |
55 | 0 | return sLog; |
56 | 0 | } |
57 | | |
58 | 0 | #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) |
59 | | #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) |
60 | | |
61 | | #ifdef __CLASS__ |
62 | | #undef __CLASS__ |
63 | | #endif |
64 | 0 | #define __CLASS__ "GMPService" |
65 | | |
66 | | namespace gmp { |
67 | | |
68 | | static StaticRefPtr<GeckoMediaPluginService> sSingletonService; |
69 | | |
70 | | class GMPServiceCreateHelper final : public mozilla::Runnable |
71 | | { |
72 | | RefPtr<GeckoMediaPluginService> mService; |
73 | | |
74 | | public: |
75 | | static already_AddRefed<GeckoMediaPluginService> |
76 | | GetOrCreate() |
77 | 0 | { |
78 | 0 | RefPtr<GeckoMediaPluginService> service; |
79 | 0 |
|
80 | 0 | if (NS_IsMainThread()) { |
81 | 0 | service = GetOrCreateOnMainThread(); |
82 | 0 | } else { |
83 | 0 | RefPtr<GMPServiceCreateHelper> createHelper = new GMPServiceCreateHelper(); |
84 | 0 |
|
85 | 0 | mozilla::SyncRunnable::DispatchToThread( |
86 | 0 | SystemGroup::EventTargetFor(mozilla::TaskCategory::Other), |
87 | 0 | createHelper, true); |
88 | 0 |
|
89 | 0 | service = createHelper->mService.forget(); |
90 | 0 | } |
91 | 0 |
|
92 | 0 | return service.forget(); |
93 | 0 | } |
94 | | |
95 | | private: |
96 | | GMPServiceCreateHelper() |
97 | | : Runnable("GMPServiceCreateHelper") |
98 | 0 | { |
99 | 0 | } |
100 | | |
101 | | ~GMPServiceCreateHelper() |
102 | 0 | { |
103 | 0 | MOZ_ASSERT(!mService); |
104 | 0 | } |
105 | | |
106 | | static already_AddRefed<GeckoMediaPluginService> |
107 | | GetOrCreateOnMainThread() |
108 | 0 | { |
109 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
110 | 0 |
|
111 | 0 | if (!sSingletonService) { |
112 | 0 | if (XRE_IsParentProcess()) { |
113 | 0 | RefPtr<GeckoMediaPluginServiceParent> service = |
114 | 0 | new GeckoMediaPluginServiceParent(); |
115 | 0 | service->Init(); |
116 | 0 | sSingletonService = service; |
117 | 0 | } else { |
118 | 0 | RefPtr<GeckoMediaPluginServiceChild> service = |
119 | 0 | new GeckoMediaPluginServiceChild(); |
120 | 0 | service->Init(); |
121 | 0 | sSingletonService = service; |
122 | 0 | } |
123 | 0 |
|
124 | 0 | ClearOnShutdown(&sSingletonService); |
125 | 0 | } |
126 | 0 |
|
127 | 0 | RefPtr<GeckoMediaPluginService> service = sSingletonService.get(); |
128 | 0 | return service.forget(); |
129 | 0 | } |
130 | | |
131 | | NS_IMETHOD |
132 | | Run() override |
133 | 0 | { |
134 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
135 | 0 |
|
136 | 0 | mService = GetOrCreateOnMainThread(); |
137 | 0 | return NS_OK; |
138 | 0 | } |
139 | | }; |
140 | | |
141 | | already_AddRefed<GeckoMediaPluginService> |
142 | | GeckoMediaPluginService::GetGeckoMediaPluginService() |
143 | 0 | { |
144 | 0 | return GMPServiceCreateHelper::GetOrCreate(); |
145 | 0 | } |
146 | | |
147 | | NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, nsIObserver) |
148 | | |
149 | | GeckoMediaPluginService::GeckoMediaPluginService() |
150 | | : mMutex("GeckoMediaPluginService::mMutex") |
151 | | , mGMPThreadShutdown(false) |
152 | | , mShuttingDownOnGMPThread(false) |
153 | | , mXPCOMWillShutdown(false) |
154 | 0 | { |
155 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
156 | 0 |
|
157 | 0 | nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1"); |
158 | 0 | if (appInfo) { |
159 | 0 | nsAutoCString version; |
160 | 0 | nsAutoCString buildID; |
161 | 0 | if (NS_SUCCEEDED(appInfo->GetVersion(version)) && |
162 | 0 | NS_SUCCEEDED(appInfo->GetAppBuildID(buildID))) { |
163 | 0 | LOGD(("GeckoMediaPluginService created; Gecko version=%s buildID=%s", |
164 | 0 | version.get(), buildID.get())); |
165 | 0 | } |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | GeckoMediaPluginService::~GeckoMediaPluginService() |
170 | 0 | { |
171 | 0 | } |
172 | | |
173 | | NS_IMETHODIMP |
174 | | GeckoMediaPluginService::RunPluginCrashCallbacks(uint32_t aPluginId, |
175 | | const nsACString& aPluginName) |
176 | 0 | { |
177 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
178 | 0 | LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId)); |
179 | 0 |
|
180 | 0 | nsAutoPtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers; |
181 | 0 | { |
182 | 0 | MutexAutoLock lock(mMutex); |
183 | 0 | mPluginCrashHelpers.Remove(aPluginId, &helpers); |
184 | 0 | } |
185 | 0 | if (!helpers) { |
186 | 0 | LOGD(("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__, __FUNCTION__, aPluginId)); |
187 | 0 | return NS_OK; |
188 | 0 | } |
189 | 0 |
|
190 | 0 | for (const auto& helper : *helpers) { |
191 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget(); |
192 | 0 | if (NS_WARN_IF(!window)) { |
193 | 0 | continue; |
194 | 0 | } |
195 | 0 | nsCOMPtr<nsIDocument> document(window->GetExtantDoc()); |
196 | 0 | if (NS_WARN_IF(!document)) { |
197 | 0 | continue; |
198 | 0 | } |
199 | 0 | |
200 | 0 | dom::PluginCrashedEventInit init; |
201 | 0 | init.mPluginID = aPluginId; |
202 | 0 | init.mBubbles = true; |
203 | 0 | init.mCancelable = true; |
204 | 0 | init.mGmpPlugin = true; |
205 | 0 | CopyUTF8toUTF16(aPluginName, init.mPluginName); |
206 | 0 | init.mSubmittedCrashReport = false; |
207 | 0 | RefPtr<dom::PluginCrashedEvent> event = |
208 | 0 | dom::PluginCrashedEvent::Constructor(document, |
209 | 0 | NS_LITERAL_STRING("PluginCrashed"), |
210 | 0 | init); |
211 | 0 | event->SetTrusted(true); |
212 | 0 | event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; |
213 | 0 |
|
214 | 0 | EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr); |
215 | 0 | } |
216 | 0 |
|
217 | 0 | return NS_OK; |
218 | 0 | } |
219 | | |
220 | | nsresult |
221 | | GeckoMediaPluginService::Init() |
222 | 0 | { |
223 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
224 | 0 |
|
225 | 0 | nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); |
226 | 0 | MOZ_ASSERT(obsService); |
227 | 0 | MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)); |
228 | 0 | MOZ_ALWAYS_SUCCEEDS( |
229 | 0 | obsService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false)); |
230 | 0 |
|
231 | 0 | // Kick off scanning for plugins |
232 | 0 | nsCOMPtr<nsIThread> thread; |
233 | 0 | return GetThread(getter_AddRefs(thread)); |
234 | 0 | } |
235 | | |
236 | | RefPtr<GetCDMParentPromise> |
237 | | GeckoMediaPluginService::GetCDM(const NodeId& aNodeId, |
238 | | nsTArray<nsCString> aTags, |
239 | | GMPCrashHelper* aHelper) |
240 | 0 | { |
241 | 0 | MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); |
242 | 0 |
|
243 | 0 | if (mShuttingDownOnGMPThread || aTags.IsEmpty()) { |
244 | 0 | nsPrintfCString reason("%s::%s failed, aTags.IsEmpty() = %d, mShuttingDownOnGMPThread = %d.", |
245 | 0 | __CLASS__, __FUNCTION__, aTags.IsEmpty(), mShuttingDownOnGMPThread); |
246 | 0 | return GetCDMParentPromise::CreateAndReject(MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); |
247 | 0 | } |
248 | 0 |
|
249 | 0 | typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder; |
250 | 0 | PromiseHolder* rawHolder(new PromiseHolder()); |
251 | 0 | RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__); |
252 | 0 | RefPtr<AbstractThread> thread(GetAbstractGMPThread()); |
253 | 0 | RefPtr<GMPCrashHelper> helper(aHelper); |
254 | 0 | GetContentParent( |
255 | 0 | aHelper, aNodeId, NS_LITERAL_CSTRING(CHROMIUM_CDM_API), aTags) |
256 | 0 | ->Then(thread, |
257 | 0 | __func__, |
258 | 0 | [rawHolder, helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) { |
259 | 0 | RefPtr<GMPContentParent> parent = wrapper->mParent; |
260 | 0 | UniquePtr<PromiseHolder> holder(rawHolder); |
261 | 0 | RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM(); |
262 | 0 | if (!parent) { |
263 | 0 | nsPrintfCString reason( |
264 | 0 | "%s::%s failed since GetChromiumCDM returns nullptr.", |
265 | 0 | __CLASS__, __FUNCTION__); |
266 | 0 | holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); |
267 | 0 | return; |
268 | 0 | } |
269 | 0 | if (helper) { |
270 | 0 | cdm->SetCrashHelper(helper); |
271 | 0 | } |
272 | 0 | holder->Resolve(cdm, __func__); |
273 | 0 | }, |
274 | 0 | [rawHolder](MediaResult result) { |
275 | 0 | nsPrintfCString reason( |
276 | 0 | "%s::%s failed since GetContentParent rejects the promise with reason %s.", |
277 | 0 | __CLASS__, __FUNCTION__, result.Description().get()); |
278 | 0 | UniquePtr<PromiseHolder> holder(rawHolder); |
279 | 0 | holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); |
280 | 0 | }); |
281 | 0 |
|
282 | 0 | return promise; |
283 | 0 | } |
284 | | |
285 | | void |
286 | | GeckoMediaPluginService::ShutdownGMPThread() |
287 | 0 | { |
288 | 0 | LOGD(("%s::%s", __CLASS__, __FUNCTION__)); |
289 | 0 | nsCOMPtr<nsIThread> gmpThread; |
290 | 0 | { |
291 | 0 | MutexAutoLock lock(mMutex); |
292 | 0 | mGMPThreadShutdown = true; |
293 | 0 | mGMPThread.swap(gmpThread); |
294 | 0 | mAbstractGMPThread = nullptr; |
295 | 0 | } |
296 | 0 |
|
297 | 0 | if (gmpThread) { |
298 | 0 | gmpThread->Shutdown(); |
299 | 0 | } |
300 | 0 | } |
301 | | |
302 | | nsresult |
303 | | GeckoMediaPluginService::GMPDispatch(nsIRunnable* event, |
304 | | uint32_t flags) |
305 | 0 | { |
306 | 0 | nsCOMPtr<nsIRunnable> r(event); |
307 | 0 | return GMPDispatch(r.forget()); |
308 | 0 | } |
309 | | |
310 | | nsresult |
311 | | GeckoMediaPluginService::GMPDispatch(already_AddRefed<nsIRunnable> event, |
312 | | uint32_t flags) |
313 | 0 | { |
314 | 0 | nsCOMPtr<nsIRunnable> r(event); |
315 | 0 | nsCOMPtr<nsIThread> thread; |
316 | 0 | nsresult rv = GetThread(getter_AddRefs(thread)); |
317 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
318 | 0 | return rv; |
319 | 0 | } |
320 | 0 | return thread->Dispatch(r, flags); |
321 | 0 | } |
322 | | |
323 | | // always call with getter_AddRefs, because it does |
324 | | NS_IMETHODIMP |
325 | | GeckoMediaPluginService::GetThread(nsIThread** aThread) |
326 | 0 | { |
327 | 0 | MOZ_ASSERT(aThread); |
328 | 0 |
|
329 | 0 | // This can be called from any thread. |
330 | 0 | MutexAutoLock lock(mMutex); |
331 | 0 |
|
332 | 0 | if (!mGMPThread) { |
333 | 0 | // Don't allow the thread to be created after shutdown has started. |
334 | 0 | if (mGMPThreadShutdown) { |
335 | 0 | return NS_ERROR_FAILURE; |
336 | 0 | } |
337 | 0 | |
338 | 0 | nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread)); |
339 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
340 | 0 | return rv; |
341 | 0 | } |
342 | 0 | |
343 | 0 | mAbstractGMPThread = AbstractThread::CreateXPCOMThreadWrapper(mGMPThread, false); |
344 | 0 |
|
345 | 0 | // Tell the thread to initialize plugins |
346 | 0 | InitializePlugins(mAbstractGMPThread.get()); |
347 | 0 | } |
348 | 0 |
|
349 | 0 | nsCOMPtr<nsIThread> copy = mGMPThread; |
350 | 0 | copy.forget(aThread); |
351 | 0 |
|
352 | 0 | return NS_OK; |
353 | 0 | } |
354 | | |
355 | | RefPtr<AbstractThread> |
356 | | GeckoMediaPluginService::GetAbstractGMPThread() |
357 | 0 | { |
358 | 0 | MutexAutoLock lock(mMutex); |
359 | 0 | return mAbstractGMPThread; |
360 | 0 | } |
361 | | |
362 | | NS_IMETHODIMP |
363 | | GeckoMediaPluginService::GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper, |
364 | | nsTArray<nsCString>* aTags, |
365 | | const nsACString& aNodeId, |
366 | | UniquePtr<GetGMPVideoDecoderCallback>&& aCallback, |
367 | | uint32_t aDecryptorId) |
368 | 0 | { |
369 | 0 | MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); |
370 | 0 | NS_ENSURE_ARG(aTags && aTags->Length() > 0); |
371 | 0 | NS_ENSURE_ARG(aCallback); |
372 | 0 |
|
373 | 0 | if (mShuttingDownOnGMPThread) { |
374 | 0 | return NS_ERROR_FAILURE; |
375 | 0 | } |
376 | 0 | |
377 | 0 | GetGMPVideoDecoderCallback* rawCallback = aCallback.release(); |
378 | 0 | RefPtr<AbstractThread> thread(GetAbstractGMPThread()); |
379 | 0 | RefPtr<GMPCrashHelper> helper(aHelper); |
380 | 0 | GetContentParent(aHelper, aNodeId, NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), *aTags) |
381 | 0 | ->Then(thread, __func__, |
382 | 0 | [rawCallback, helper, aDecryptorId](RefPtr<GMPContentParent::CloseBlocker> wrapper) { |
383 | 0 | RefPtr<GMPContentParent> parent = wrapper->mParent; |
384 | 0 | UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback); |
385 | 0 | GMPVideoDecoderParent* actor = nullptr; |
386 | 0 | GMPVideoHostImpl* host = nullptr; |
387 | 0 | if (parent && NS_SUCCEEDED(parent->GetGMPVideoDecoder(&actor, aDecryptorId))) { |
388 | 0 | host = &(actor->Host()); |
389 | 0 | actor->SetCrashHelper(helper); |
390 | 0 | } |
391 | 0 | callback->Done(actor, host); |
392 | 0 | }, |
393 | 0 | [rawCallback] { |
394 | 0 | UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback); |
395 | 0 | callback->Done(nullptr, nullptr); |
396 | 0 | }); |
397 | 0 |
|
398 | 0 | return NS_OK; |
399 | 0 | } |
400 | | |
401 | | NS_IMETHODIMP |
402 | | GeckoMediaPluginService::GetGMPVideoEncoder(GMPCrashHelper* aHelper, |
403 | | nsTArray<nsCString>* aTags, |
404 | | const nsACString& aNodeId, |
405 | | UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) |
406 | 0 | { |
407 | 0 | MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); |
408 | 0 | NS_ENSURE_ARG(aTags && aTags->Length() > 0); |
409 | 0 | NS_ENSURE_ARG(aCallback); |
410 | 0 |
|
411 | 0 | if (mShuttingDownOnGMPThread) { |
412 | 0 | return NS_ERROR_FAILURE; |
413 | 0 | } |
414 | 0 | |
415 | 0 | GetGMPVideoEncoderCallback* rawCallback = aCallback.release(); |
416 | 0 | RefPtr<AbstractThread> thread(GetAbstractGMPThread()); |
417 | 0 | RefPtr<GMPCrashHelper> helper(aHelper); |
418 | 0 | GetContentParent(aHelper, aNodeId, NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER), *aTags) |
419 | 0 | ->Then(thread, __func__, |
420 | 0 | [rawCallback, helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) { |
421 | 0 | RefPtr<GMPContentParent> parent = wrapper->mParent; |
422 | 0 | UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback); |
423 | 0 | GMPVideoEncoderParent* actor = nullptr; |
424 | 0 | GMPVideoHostImpl* host = nullptr; |
425 | 0 | if (parent && NS_SUCCEEDED(parent->GetGMPVideoEncoder(&actor))) { |
426 | 0 | host = &(actor->Host()); |
427 | 0 | actor->SetCrashHelper(helper); |
428 | 0 | } |
429 | 0 | callback->Done(actor, host); |
430 | 0 | }, |
431 | 0 | [rawCallback] { |
432 | 0 | UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback); |
433 | 0 | callback->Done(nullptr, nullptr); |
434 | 0 | }); |
435 | 0 |
|
436 | 0 | return NS_OK; |
437 | 0 | } |
438 | | |
439 | | void |
440 | | GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId, GMPCrashHelper* aHelper) |
441 | 0 | { |
442 | 0 | if (!aHelper) { |
443 | 0 | return; |
444 | 0 | } |
445 | 0 | MutexAutoLock lock(mMutex); |
446 | 0 | nsTArray<RefPtr<GMPCrashHelper>>* helpers; |
447 | 0 | if (!mPluginCrashHelpers.Get(aPluginId, &helpers)) { |
448 | 0 | helpers = new nsTArray<RefPtr<GMPCrashHelper>>(); |
449 | 0 | mPluginCrashHelpers.Put(aPluginId, helpers); |
450 | 0 | } else if (helpers->Contains(aHelper)) { |
451 | 0 | return; |
452 | 0 | } |
453 | 0 | helpers->AppendElement(aHelper); |
454 | 0 | } |
455 | | |
456 | | void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper) |
457 | 0 | { |
458 | 0 | if (!aHelper) { |
459 | 0 | return; |
460 | 0 | } |
461 | 0 | MutexAutoLock lock(mMutex); |
462 | 0 | for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) { |
463 | 0 | nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.Data(); |
464 | 0 | if (!helpers->Contains(aHelper)) { |
465 | 0 | continue; |
466 | 0 | } |
467 | 0 | helpers->RemoveElement(aHelper); |
468 | 0 | MOZ_ASSERT(!helpers->Contains(aHelper)); // Ensure there aren't duplicates. |
469 | 0 | if (helpers->IsEmpty()) { |
470 | 0 | iter.Remove(); |
471 | 0 | } |
472 | 0 | } |
473 | 0 | } |
474 | | |
475 | | } // namespace gmp |
476 | | } // namespace mozilla |