Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "GMPServiceChild.h"
7 : #include "mozilla/dom/ContentChild.h"
8 : #include "mozilla/ClearOnShutdown.h"
9 : #include "mozilla/StaticPtr.h"
10 : #include "mozIGeckoMediaPluginService.h"
11 : #include "mozIGeckoMediaPluginChromeService.h"
12 : #include "nsCOMPtr.h"
13 : #include "GMPParent.h"
14 : #include "GMPContentParent.h"
15 : #include "nsXPCOMPrivate.h"
16 : #include "mozilla/SyncRunnable.h"
17 : #include "mozilla/StaticMutex.h"
18 : #include "runnable_utils.h"
19 : #include "base/task.h"
20 : #include "nsIObserverService.h"
21 : #include "nsComponentManagerUtils.h"
22 :
23 : namespace mozilla {
24 :
25 : #ifdef LOG
26 : #undef LOG
27 : #endif
28 :
29 : #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
30 : #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
31 :
32 : #ifdef __CLASS__
33 : #undef __CLASS__
34 : #endif
35 : #define __CLASS__ "GMPService"
36 :
37 : namespace gmp {
38 :
39 : already_AddRefed<GeckoMediaPluginServiceChild>
40 0 : GeckoMediaPluginServiceChild::GetSingleton()
41 : {
42 0 : MOZ_ASSERT(!XRE_IsParentProcess());
43 : RefPtr<GeckoMediaPluginService> service(
44 0 : GeckoMediaPluginService::GetGeckoMediaPluginService());
45 : #ifdef DEBUG
46 0 : if (service) {
47 0 : nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService;
48 0 : CallQueryInterface(service.get(), getter_AddRefs(chromeService));
49 0 : MOZ_ASSERT(!chromeService);
50 : }
51 : #endif
52 0 : return service.forget().downcast<GeckoMediaPluginServiceChild>();
53 : }
54 :
55 : RefPtr<GetGMPContentParentPromise>
56 0 : GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
57 : const nsACString& aNodeIdString,
58 : const nsCString& aAPI,
59 : const nsTArray<nsCString>& aTags)
60 : {
61 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
62 :
63 0 : MozPromiseHolder<GetGMPContentParentPromise>* rawHolder = new MozPromiseHolder<GetGMPContentParentPromise>();
64 0 : RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
65 0 : RefPtr<AbstractThread> thread(GetAbstractGMPThread());
66 :
67 0 : nsCString nodeIdString(aNodeIdString);
68 0 : nsCString api(aAPI);
69 0 : nsTArray<nsCString> tags(aTags);
70 0 : RefPtr<GMPCrashHelper> helper(aHelper);
71 0 : RefPtr<GeckoMediaPluginServiceChild> self(this);
72 0 : GetServiceChild()->Then(
73 : thread,
74 : __func__,
75 0 : [self, nodeIdString, api, tags, helper, rawHolder](GMPServiceChild* child) {
76 0 : UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
77 : nsresult rv;
78 :
79 0 : nsTArray<base::ProcessId> alreadyBridgedTo;
80 0 : child->GetAlreadyBridgedTo(alreadyBridgedTo);
81 :
82 : base::ProcessId otherProcess;
83 0 : nsCString displayName;
84 0 : uint32_t pluginId = 0;
85 0 : ipc::Endpoint<PGMPContentParent> endpoint;
86 0 : bool ok = child->SendLaunchGMP(nodeIdString,
87 : api,
88 : tags,
89 : alreadyBridgedTo,
90 : &pluginId,
91 : &otherProcess,
92 : &displayName,
93 : &endpoint,
94 0 : &rv);
95 0 : if (helper && pluginId) {
96 : // Note: Even if the launch failed, we need to connect the crash
97 : // helper so that if the launch failed due to the plugin crashing,
98 : // we can report the crash via the crash reporter. The crash
99 : // handling notification will arrive shortly if the launch failed
100 : // due to the plugin crashing.
101 0 : self->ConnectCrashHelper(pluginId, helper);
102 : }
103 :
104 0 : if (!ok || NS_FAILED(rv)) {
105 0 : LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP "
106 : "failed rv=0x%x",
107 : static_cast<uint32_t>(rv)));
108 0 : holder->Reject(rv, __func__);
109 0 : return;
110 : }
111 :
112 : RefPtr<GMPContentParent> parent =
113 0 : child->GetBridgedGMPContentParent(otherProcess, Move(endpoint));
114 0 : if (!alreadyBridgedTo.Contains(otherProcess)) {
115 0 : parent->SetDisplayName(displayName);
116 0 : parent->SetPluginId(pluginId);
117 : }
118 : RefPtr<GMPContentParent::CloseBlocker> blocker(
119 0 : new GMPContentParent::CloseBlocker(parent));
120 0 : holder->Resolve(blocker, __func__);
121 : },
122 0 : [rawHolder](nsresult rv) {
123 0 : UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
124 0 : holder->Reject(rv, __func__);
125 0 : });
126 :
127 0 : return promise;
128 : }
129 :
130 : RefPtr<GetGMPContentParentPromise>
131 0 : GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
132 : const NodeId& aNodeId,
133 : const nsCString& aAPI,
134 : const nsTArray<nsCString>& aTags)
135 : {
136 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
137 :
138 : MozPromiseHolder<GetGMPContentParentPromise>* rawHolder =
139 0 : new MozPromiseHolder<GetGMPContentParentPromise>();
140 0 : RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
141 0 : RefPtr<AbstractThread> thread(GetAbstractGMPThread());
142 :
143 0 : NodeIdData nodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName);
144 0 : nsCString api(aAPI);
145 0 : nsTArray<nsCString> tags(aTags);
146 0 : RefPtr<GMPCrashHelper> helper(aHelper);
147 0 : RefPtr<GeckoMediaPluginServiceChild> self(this);
148 0 : GetServiceChild()->Then(thread, __func__,
149 0 : [self, nodeId, api, tags, helper, rawHolder](GMPServiceChild* child) {
150 0 : UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
151 : nsresult rv;
152 :
153 0 : nsTArray<base::ProcessId> alreadyBridgedTo;
154 0 : child->GetAlreadyBridgedTo(alreadyBridgedTo);
155 :
156 : base::ProcessId otherProcess;
157 0 : nsCString displayName;
158 0 : uint32_t pluginId = 0;
159 0 : ipc::Endpoint<PGMPContentParent> endpoint;
160 :
161 0 : bool ok = child->SendLaunchGMPForNodeId(nodeId,
162 : api,
163 : tags,
164 : alreadyBridgedTo,
165 : &pluginId,
166 : &otherProcess,
167 : &displayName,
168 : &endpoint,
169 0 : &rv);
170 :
171 0 : if (helper && pluginId) {
172 : // Note: Even if the launch failed, we need to connect the crash
173 : // helper so that if the launch failed due to the plugin crashing,
174 : // we can report the crash via the crash reporter. The crash
175 : // handling notification will arrive shortly if the launch failed
176 : // due to the plugin crashing.
177 0 : self->ConnectCrashHelper(pluginId, helper);
178 : }
179 :
180 0 : if (!ok || NS_FAILED(rv)) {
181 0 : LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP failed rv=%" PRIu32,
182 : static_cast<uint32_t>(rv)));
183 0 : holder->Reject(rv, __func__);
184 0 : return;
185 : }
186 :
187 0 : RefPtr<GMPContentParent> parent = child->GetBridgedGMPContentParent(otherProcess,
188 0 : Move(endpoint));
189 0 : if (!alreadyBridgedTo.Contains(otherProcess)) {
190 0 : parent->SetDisplayName(displayName);
191 0 : parent->SetPluginId(pluginId);
192 : }
193 :
194 0 : RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(parent));
195 0 : holder->Resolve(blocker, __func__);
196 : },
197 0 : [rawHolder](nsresult rv) {
198 0 : UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
199 0 : holder->Reject(rv, __func__);
200 0 : });
201 :
202 0 : return promise;
203 : }
204 :
205 : typedef mozilla::dom::GMPCapabilityData GMPCapabilityData;
206 : typedef mozilla::dom::GMPAPITags GMPAPITags;
207 :
208 7 : struct GMPCapabilityAndVersion
209 : {
210 3 : explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities)
211 3 : : mName(aCapabilities.name())
212 3 : , mVersion(aCapabilities.version())
213 : {
214 6 : for (const GMPAPITags& tags : aCapabilities.capabilities()) {
215 6 : GMPCapability cap;
216 3 : cap.mAPIName = tags.api();
217 6 : for (const nsCString& tag : tags.tags()) {
218 3 : cap.mAPITags.AppendElement(tag);
219 : }
220 3 : mCapabilities.AppendElement(Move(cap));
221 : }
222 3 : }
223 :
224 0 : nsCString ToString() const
225 : {
226 0 : nsCString s;
227 0 : s.Append(mName);
228 0 : s.Append(" version=");
229 0 : s.Append(mVersion);
230 0 : s.Append(" tags=[");
231 0 : nsCString tags;
232 0 : for (const GMPCapability& cap : mCapabilities) {
233 0 : if (!tags.IsEmpty()) {
234 0 : tags.Append(" ");
235 : }
236 0 : tags.Append(cap.mAPIName);
237 0 : for (const nsCString& tag : cap.mAPITags) {
238 0 : tags.Append(":");
239 0 : tags.Append(tag);
240 : }
241 : }
242 0 : s.Append(tags);
243 0 : s.Append("]");
244 0 : return s;
245 : }
246 :
247 : nsCString mName;
248 : nsCString mVersion;
249 : nsTArray<GMPCapability> mCapabilities;
250 : };
251 :
252 3 : StaticMutex sGMPCapabilitiesMutex;
253 3 : StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> sGMPCapabilities;
254 :
255 : static nsCString
256 0 : GMPCapabilitiesToString()
257 : {
258 0 : nsCString s;
259 0 : for (const GMPCapabilityAndVersion& gmp : *sGMPCapabilities) {
260 0 : if (!s.IsEmpty()) {
261 0 : s.Append(", ");
262 : }
263 0 : s.Append(gmp.ToString());
264 : }
265 0 : return s;
266 : }
267 :
268 : /* static */
269 : void
270 3 : GeckoMediaPluginServiceChild::UpdateGMPCapabilities(nsTArray<GMPCapabilityData>&& aCapabilities)
271 : {
272 : {
273 : // The mutex should unlock before sending the "gmp-changed" observer service notification.
274 6 : StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
275 3 : if (!sGMPCapabilities) {
276 2 : sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>();
277 2 : ClearOnShutdown(&sGMPCapabilities);
278 : }
279 3 : sGMPCapabilities->Clear();
280 6 : for (const GMPCapabilityData& plugin : aCapabilities) {
281 3 : sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin));
282 : }
283 :
284 3 : LOGD(("UpdateGMPCapabilities {%s}", GMPCapabilitiesToString().get()));
285 : }
286 :
287 : // Fire a notification so that any MediaKeySystemAccess
288 : // requests waiting on a CDM to download will retry.
289 6 : nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
290 3 : MOZ_ASSERT(obsService);
291 3 : if (obsService) {
292 3 : obsService->NotifyObservers(nullptr, "gmp-changed", nullptr);
293 : }
294 3 : }
295 :
296 : void
297 0 : GeckoMediaPluginServiceChild::BeginShutdown()
298 : {
299 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
300 0 : mShuttingDownOnGMPThread = true;
301 0 : }
302 :
303 : NS_IMETHODIMP
304 0 : GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI,
305 : nsTArray<nsCString>* aTags,
306 : bool* aHasPlugin)
307 : {
308 0 : StaticMutexAutoLock lock(sGMPCapabilitiesMutex);
309 0 : if (!sGMPCapabilities) {
310 0 : *aHasPlugin = false;
311 0 : return NS_OK;
312 : }
313 :
314 0 : nsCString api(aAPI);
315 0 : for (const GMPCapabilityAndVersion& plugin : *sGMPCapabilities) {
316 0 : if (GMPCapability::Supports(plugin.mCapabilities, api, *aTags)) {
317 0 : *aHasPlugin = true;
318 0 : return NS_OK;
319 : }
320 : }
321 :
322 0 : *aHasPlugin = false;
323 0 : return NS_OK;
324 : }
325 :
326 : NS_IMETHODIMP
327 0 : GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
328 : const nsAString& aTopLevelOrigin,
329 : const nsAString& aGMPName,
330 : UniquePtr<GetNodeIdCallback>&& aCallback)
331 : {
332 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
333 :
334 0 : GetNodeIdCallback* rawCallback = aCallback.release();
335 0 : RefPtr<AbstractThread> thread(GetAbstractGMPThread());
336 0 : nsString origin(aOrigin);
337 0 : nsString topLevelOrigin(aTopLevelOrigin);
338 0 : nsString gmpName(aGMPName);
339 0 : GetServiceChild()->Then(thread, __func__,
340 0 : [rawCallback, origin, topLevelOrigin, gmpName](GMPServiceChild* child) {
341 0 : UniquePtr<GetNodeIdCallback> callback(rawCallback);
342 0 : nsCString outId;
343 0 : if (!child->SendGetGMPNodeId(origin, topLevelOrigin,
344 : gmpName, &outId)) {
345 0 : callback->Done(NS_ERROR_FAILURE, EmptyCString());
346 0 : return;
347 : }
348 :
349 0 : callback->Done(NS_OK, outId);
350 : },
351 0 : [rawCallback](nsresult rv) {
352 0 : UniquePtr<GetNodeIdCallback> callback(rawCallback);
353 0 : callback->Done(NS_ERROR_FAILURE, EmptyCString());
354 0 : });
355 :
356 0 : return NS_OK;
357 : }
358 :
359 : NS_IMETHODIMP
360 0 : GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject,
361 : const char* aTopic,
362 : const char16_t* aSomeData)
363 : {
364 0 : LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic));
365 0 : if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
366 0 : if (mServiceChild) {
367 0 : mozilla::SyncRunnable::DispatchToThread(mGMPThread,
368 0 : WrapRunnable(mServiceChild.get(),
369 0 : &PGMPServiceChild::Close));
370 0 : mServiceChild = nullptr;
371 : }
372 0 : ShutdownGMPThread();
373 : }
374 :
375 0 : return NS_OK;
376 : }
377 :
378 : RefPtr<GeckoMediaPluginServiceChild::GetServiceChildPromise>
379 0 : GeckoMediaPluginServiceChild::GetServiceChild()
380 : {
381 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
382 :
383 0 : if (!mServiceChild) {
384 0 : if (mShuttingDownOnGMPThread) {
385 : // We have begun shutdown. Don't allow a new connection to the main
386 : // process to be instantiated. This also prevents new plugins being
387 : // instantiated.
388 : return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE,
389 0 : __func__);
390 : }
391 0 : dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
392 0 : if (!contentChild) {
393 0 : return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
394 : }
395 0 : MozPromiseHolder<GetServiceChildPromise>* holder = mGetServiceChildPromises.AppendElement();
396 0 : RefPtr<GetServiceChildPromise> promise = holder->Ensure(__func__);
397 0 : if (mGetServiceChildPromises.Length() == 1) {
398 0 : nsCOMPtr<nsIRunnable> r = WrapRunnable(
399 0 : contentChild, &dom::ContentChild::SendCreateGMPService);
400 0 : SystemGroup::Dispatch("SendCreateGMPService", TaskCategory::Other, r.forget());
401 : }
402 0 : return promise;
403 : }
404 0 : return GetServiceChildPromise::CreateAndResolve(mServiceChild.get(), __func__);
405 : }
406 :
407 : void
408 0 : GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild)
409 : {
410 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
411 :
412 0 : mServiceChild = Move(aServiceChild);
413 :
414 0 : nsTArray<MozPromiseHolder<GetServiceChildPromise>> holders;
415 0 : holders.SwapElements(mGetServiceChildPromises);
416 0 : for (MozPromiseHolder<GetServiceChildPromise>& holder : holders) {
417 0 : holder.Resolve(mServiceChild.get(), __func__);
418 : }
419 0 : }
420 :
421 : void
422 0 : GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
423 : {
424 0 : MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread());
425 :
426 0 : if (mServiceChild) {
427 0 : mServiceChild->RemoveGMPContentParent(aGMPContentParent);
428 0 : if (mShuttingDownOnGMPThread && !mServiceChild->HaveContentParents()) {
429 0 : mServiceChild->Close();
430 0 : mServiceChild = nullptr;
431 : }
432 : }
433 0 : }
434 :
435 0 : GMPServiceChild::GMPServiceChild()
436 : {
437 0 : }
438 :
439 0 : GMPServiceChild::~GMPServiceChild()
440 : {
441 0 : }
442 :
443 : already_AddRefed<GMPContentParent>
444 0 : GMPServiceChild::GetBridgedGMPContentParent(ProcessId aOtherPid,
445 : ipc::Endpoint<PGMPContentParent>&& endpoint)
446 : {
447 0 : RefPtr<GMPContentParent> parent;
448 0 : mContentParents.Get(aOtherPid, getter_AddRefs(parent));
449 :
450 0 : if (parent) {
451 0 : return parent.forget();
452 : }
453 :
454 0 : MOZ_ASSERT(aOtherPid == endpoint.OtherPid());
455 :
456 0 : parent = new GMPContentParent();
457 :
458 0 : DebugOnly<bool> ok = endpoint.Bind(parent);
459 0 : MOZ_ASSERT(ok);
460 :
461 0 : mContentParents.Put(aOtherPid, parent);
462 :
463 0 : return parent.forget();
464 : }
465 :
466 : void
467 0 : GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
468 : {
469 0 : for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
470 0 : RefPtr<GMPContentParent>& parent = iter.Data();
471 0 : if (parent == aGMPContentParent) {
472 0 : iter.Remove();
473 0 : break;
474 : }
475 : }
476 0 : }
477 :
478 : void
479 0 : GMPServiceChild::GetAlreadyBridgedTo(nsTArray<base::ProcessId>& aAlreadyBridgedTo)
480 : {
481 0 : aAlreadyBridgedTo.SetCapacity(mContentParents.Count());
482 0 : for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) {
483 0 : const uint64_t& id = iter.Key();
484 0 : aAlreadyBridgedTo.AppendElement(id);
485 : }
486 0 : }
487 :
488 0 : class OpenPGMPServiceChild : public mozilla::Runnable
489 : {
490 : public:
491 0 : OpenPGMPServiceChild(UniquePtr<GMPServiceChild>&& aGMPServiceChild,
492 : ipc::Endpoint<PGMPServiceChild>&& aEndpoint)
493 0 : : Runnable("gmp::OpenPGMPServiceChild")
494 0 : , mGMPServiceChild(Move(aGMPServiceChild))
495 0 : , mEndpoint(Move(aEndpoint))
496 : {
497 0 : }
498 :
499 0 : NS_IMETHOD Run() override
500 : {
501 : RefPtr<GeckoMediaPluginServiceChild> gmp =
502 0 : GeckoMediaPluginServiceChild::GetSingleton();
503 0 : MOZ_ASSERT(!gmp->mServiceChild);
504 0 : if (mEndpoint.Bind(mGMPServiceChild.get())) {
505 0 : gmp->SetServiceChild(Move(mGMPServiceChild));
506 : } else {
507 0 : gmp->SetServiceChild(nullptr);
508 : }
509 0 : return NS_OK;
510 : }
511 :
512 : private:
513 : UniquePtr<GMPServiceChild> mGMPServiceChild;
514 : ipc::Endpoint<PGMPServiceChild> mEndpoint;
515 : };
516 :
517 : /* static */
518 : bool
519 0 : GMPServiceChild::Create(Endpoint<PGMPServiceChild>&& aGMPService)
520 : {
521 : RefPtr<GeckoMediaPluginServiceChild> gmp =
522 0 : GeckoMediaPluginServiceChild::GetSingleton();
523 0 : MOZ_ASSERT(!gmp->mServiceChild);
524 :
525 0 : UniquePtr<GMPServiceChild> serviceChild(new GMPServiceChild());
526 :
527 0 : nsCOMPtr<nsIThread> gmpThread;
528 0 : nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread));
529 0 : NS_ENSURE_SUCCESS(rv, false);
530 :
531 0 : rv = gmpThread->Dispatch(new OpenPGMPServiceChild(Move(serviceChild),
532 0 : Move(aGMPService)),
533 0 : NS_DISPATCH_NORMAL);
534 0 : return NS_SUCCEEDED(rv);
535 : }
536 :
537 : ipc::IPCResult
538 0 : GMPServiceChild::RecvBeginShutdown()
539 : {
540 : RefPtr<GeckoMediaPluginServiceChild> service =
541 0 : GeckoMediaPluginServiceChild::GetSingleton();
542 0 : MOZ_ASSERT(service && service->mServiceChild.get() == this);
543 0 : if (service) {
544 0 : service->BeginShutdown();
545 : }
546 0 : return IPC_OK();
547 : }
548 :
549 : bool
550 0 : GMPServiceChild::HaveContentParents() const
551 : {
552 0 : return mContentParents.Count() > 0;
553 : }
554 :
555 : } // namespace gmp
556 : } // namespace mozilla
|