1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 // mostly derived from the Allegro source code at:
8 // http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
9 
10 #include "mozilla/dom/GamepadPlatformService.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "nsITimer.h"
13 #include "nsThreadUtils.h"
14 #include <CoreFoundation/CoreFoundation.h>
15 #include <IOKit/hid/IOHIDBase.h>
16 #include <IOKit/hid/IOHIDKeys.h>
17 #include <IOKit/hid/IOHIDManager.h>
18 
19 #include <stdio.h>
20 #include <vector>
21 
22 namespace {
23 
24 using namespace mozilla;
25 using namespace mozilla::dom;
26 using std::vector;
27 class DarwinGamepadService;
28 
29 DarwinGamepadService* gService = nullptr;
30 
31 struct Button {
32   int id;
33   bool analog;
34   IOHIDElementRef element;
35   CFIndex min;
36   CFIndex max;
37 
Button__anond6aa904a0111::Button38   Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax)
39       : id(aId),
40         analog((aMax - aMin) > 1),
41         element(aElement),
42         min(aMin),
43         max(aMax) {}
44 };
45 
46 struct Axis {
47   int id;
48   IOHIDElementRef element;
49   uint32_t usagePage;
50   uint32_t usage;
51   CFIndex min;
52   CFIndex max;
53 };
54 
55 typedef bool dpad_buttons[4];
56 
57 // These values can be found in the USB HID Usage Tables:
58 // http://www.usb.org/developers/hidpage
59 const unsigned kDesktopUsagePage = 0x01;
60 const unsigned kSimUsagePage = 0x02;
61 const unsigned kAcceleratorUsage = 0xC4;
62 const unsigned kBrakeUsage = 0xC5;
63 const unsigned kJoystickUsage = 0x04;
64 const unsigned kGamepadUsage = 0x05;
65 const unsigned kAxisUsageMin = 0x30;
66 const unsigned kAxisUsageMax = 0x35;
67 const unsigned kDpadUsage = 0x39;
68 const unsigned kButtonUsagePage = 0x09;
69 const unsigned kConsumerPage = 0x0C;
70 const unsigned kHomeUsage = 0x223;
71 const unsigned kBackUsage = 0x224;
72 
73 // We poll it periodically,
74 // 50ms is arbitrarily chosen.
75 const uint32_t kDarwinGamepadPollInterval = 50;
76 
77 class Gamepad {
78  private:
79   IOHIDDeviceRef mDevice;
80   nsTArray<Button> buttons;
81   nsTArray<Axis> axes;
82   IOHIDElementRef mDpad;
83   dpad_buttons mDpadState;
84 
85  public:
Gamepad()86   Gamepad() : mDevice(nullptr), mDpad(nullptr), mSuperIndex(-1) {}
operator ==(IOHIDDeviceRef device) const87   bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
empty() const88   bool empty() const { return mDevice == nullptr; }
clear()89   void clear() {
90     mDevice = nullptr;
91     buttons.Clear();
92     axes.Clear();
93     mDpad = nullptr;
94     mSuperIndex = -1;
95   }
96   void init(IOHIDDeviceRef device);
numButtons()97   size_t numButtons() { return buttons.Length() + (mDpad ? 4 : 0); }
numAxes()98   size_t numAxes() { return axes.Length(); }
99 
100   // Index given by our superclass.
101   uint32_t mSuperIndex;
102 
isDpad(IOHIDElementRef element) const103   bool isDpad(IOHIDElementRef element) const { return element == mDpad; }
104 
getDpadState() const105   const dpad_buttons& getDpadState() const { return mDpadState; }
106 
setDpadState(const dpad_buttons & dpadState)107   void setDpadState(const dpad_buttons& dpadState) {
108     for (unsigned i = 0; i < ArrayLength(mDpadState); i++) {
109       mDpadState[i] = dpadState[i];
110     }
111   }
112 
lookupButton(IOHIDElementRef element) const113   const Button* lookupButton(IOHIDElementRef element) const {
114     for (unsigned i = 0; i < buttons.Length(); i++) {
115       if (buttons[i].element == element) return &buttons[i];
116     }
117     return nullptr;
118   }
119 
lookupAxis(IOHIDElementRef element) const120   const Axis* lookupAxis(IOHIDElementRef element) const {
121     for (unsigned i = 0; i < axes.Length(); i++) {
122       if (axes[i].element == element) return &axes[i];
123     }
124     return nullptr;
125   }
126 };
127 
128 class AxisComparator {
129  public:
Equals(const Axis & a1,const Axis & a2) const130   bool Equals(const Axis& a1, const Axis& a2) const {
131     return a1.usagePage == a2.usagePage && a1.usage == a2.usage;
132   }
LessThan(const Axis & a1,const Axis & a2) const133   bool LessThan(const Axis& a1, const Axis& a2) const {
134     if (a1.usagePage == a2.usagePage) {
135       return a1.usage < a2.usage;
136     }
137     return a1.usagePage < a2.usagePage;
138   }
139 };
140 
init(IOHIDDeviceRef device)141 void Gamepad::init(IOHIDDeviceRef device) {
142   clear();
143   mDevice = device;
144 
145   CFArrayRef elements =
146       IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
147   CFIndex n = CFArrayGetCount(elements);
148   for (CFIndex i = 0; i < n; i++) {
149     IOHIDElementRef element =
150         (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
151     uint32_t usagePage = IOHIDElementGetUsagePage(element);
152     uint32_t usage = IOHIDElementGetUsage(element);
153 
154     if (usagePage == kDesktopUsagePage && usage >= kAxisUsageMin &&
155         usage <= kAxisUsageMax) {
156       Axis axis = {int(axes.Length()),
157                    element,
158                    usagePage,
159                    usage,
160                    IOHIDElementGetLogicalMin(element),
161                    IOHIDElementGetLogicalMax(element)};
162       axes.AppendElement(axis);
163     } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
164                // Don't know how to handle d-pads that return weird values.
165                IOHIDElementGetLogicalMax(element) -
166                        IOHIDElementGetLogicalMin(element) ==
167                    7) {
168       mDpad = element;
169     } else if ((usagePage == kSimUsagePage &&
170                 (usage == kAcceleratorUsage || usage == kBrakeUsage)) ||
171                (usagePage == kButtonUsagePage) ||
172                (usagePage == kConsumerPage &&
173                 (usage == kHomeUsage || usage == kBackUsage))) {
174       Button button(int(buttons.Length()), element,
175                     IOHIDElementGetLogicalMin(element),
176                     IOHIDElementGetLogicalMax(element));
177       buttons.AppendElement(button);
178     } else {
179       // TODO: handle other usage pages
180     }
181   }
182 
183   AxisComparator comparator;
184   axes.Sort(comparator);
185   for (unsigned i = 0; i < axes.Length(); i++) {
186     axes[i].id = i;
187   }
188 }
189 
190 // This service is created and destroyed in Background thread while
191 // operates in a seperate thread(We call it Monitor thread here).
192 class DarwinGamepadService {
193  private:
194   IOHIDManagerRef mManager;
195   vector<Gamepad> mGamepads;
196 
197   nsCOMPtr<nsIThread> mMonitorThread;
198   nsCOMPtr<nsIThread> mBackgroundThread;
199   nsCOMPtr<nsITimer> mPollingTimer;
200   bool mIsRunning;
201 
202   static void DeviceAddedCallback(void* data, IOReturn result, void* sender,
203                                   IOHIDDeviceRef device);
204   static void DeviceRemovedCallback(void* data, IOReturn result, void* sender,
205                                     IOHIDDeviceRef device);
206   static void InputValueChangedCallback(void* data, IOReturn result,
207                                         void* sender, IOHIDValueRef newValue);
208   static void EventLoopOnceCallback(nsITimer* aTimer, void* aClosure);
209 
210   void DeviceAdded(IOHIDDeviceRef device);
211   void DeviceRemoved(IOHIDDeviceRef device);
212   void InputValueChanged(IOHIDValueRef value);
213   void StartupInternal();
214   void RunEventLoopOnce();
215 
216  public:
217   DarwinGamepadService();
218   ~DarwinGamepadService();
219   void Startup();
220   void Shutdown();
221   friend class DarwinGamepadServiceStartupRunnable;
222   friend class DarwinGamepadServiceShutdownRunnable;
223 };
224 
225 class DarwinGamepadServiceStartupRunnable final : public Runnable {
226  private:
~DarwinGamepadServiceStartupRunnable()227   ~DarwinGamepadServiceStartupRunnable() {}
228   // This Runnable schedules startup of DarwinGamepadService
229   // in a new thread, pointer to DarwinGamepadService is only
230   // used by this Runnable within its thread.
231   DarwinGamepadService MOZ_NON_OWNING_REF* mService;
232 
233  public:
DarwinGamepadServiceStartupRunnable(DarwinGamepadService * service)234   explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService* service)
235       : Runnable("DarwinGamepadServiceStartupRunnable"), mService(service) {}
Run()236   NS_IMETHOD Run() override {
237     MOZ_ASSERT(mService);
238     mService->StartupInternal();
239     return NS_OK;
240   }
241 };
242 
243 class DarwinGamepadServiceShutdownRunnable final : public Runnable {
244  private:
~DarwinGamepadServiceShutdownRunnable()245   ~DarwinGamepadServiceShutdownRunnable() {}
246 
247  public:
248   // This Runnable schedules shutdown of DarwinGamepadService
249   // in background thread.
DarwinGamepadServiceShutdownRunnable()250   explicit DarwinGamepadServiceShutdownRunnable()
251       : Runnable("DarwinGamepadServiceStartupRunnable") {}
Run()252   NS_IMETHOD Run() override {
253     MOZ_ASSERT(gService);
254     MOZ_ASSERT(NS_GetCurrentThread() == gService->mBackgroundThread);
255 
256     IOHIDManagerRef manager = (IOHIDManagerRef)gService->mManager;
257 
258     if (manager) {
259       IOHIDManagerClose(manager, 0);
260       CFRelease(manager);
261       gService->mManager = nullptr;
262     }
263     gService->mMonitorThread->Shutdown();
264     delete gService;
265     gService = nullptr;
266     return NS_OK;
267   }
268 };
269 
DeviceAdded(IOHIDDeviceRef device)270 void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
271   RefPtr<GamepadPlatformService> service =
272       GamepadPlatformService::GetParentService();
273   if (!service) {
274     return;
275   }
276 
277   size_t slot = size_t(-1);
278   for (size_t i = 0; i < mGamepads.size(); i++) {
279     if (mGamepads[i] == device) return;
280     if (slot == size_t(-1) && mGamepads[i].empty()) slot = i;
281   }
282 
283   if (slot == size_t(-1)) {
284     slot = mGamepads.size();
285     mGamepads.push_back(Gamepad());
286   }
287   mGamepads[slot].init(device);
288 
289   // Gather some identifying information
290   CFNumberRef vendorIdRef =
291       (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
292   CFNumberRef productIdRef =
293       (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
294   CFStringRef productRef =
295       (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
296   int vendorId, productId;
297   CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
298   CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
299   char product_name[128];
300   CFStringGetCString(productRef, product_name, sizeof(product_name),
301                      kCFStringEncodingASCII);
302   char buffer[256];
303   sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
304   uint32_t index = service->AddGamepad(
305       buffer, mozilla::dom::GamepadMappingType::_empty,
306       mozilla::dom::GamepadHand::_empty, (int)mGamepads[slot].numButtons(),
307       (int)mGamepads[slot].numAxes(),
308       0);  // TODO: Bug 680289, implement gamepad haptics for cocoa
309   mGamepads[slot].mSuperIndex = index;
310 }
311 
DeviceRemoved(IOHIDDeviceRef device)312 void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
313   RefPtr<GamepadPlatformService> service =
314       GamepadPlatformService::GetParentService();
315   if (!service) {
316     return;
317   }
318   for (size_t i = 0; i < mGamepads.size(); i++) {
319     if (mGamepads[i] == device) {
320       service->RemoveGamepad(mGamepads[i].mSuperIndex);
321       mGamepads[i].clear();
322       return;
323     }
324   }
325 }
326 
327 /*
328  * Given a value from a d-pad (POV hat in USB HID terminology),
329  * represent it as 4 buttons, one for each cardinal direction.
330  */
UnpackDpad(int dpad_value,int min,int max,dpad_buttons & buttons)331 static void UnpackDpad(int dpad_value, int min, int max,
332                        dpad_buttons& buttons) {
333   const unsigned kUp = 0;
334   const unsigned kDown = 1;
335   const unsigned kLeft = 2;
336   const unsigned kRight = 3;
337 
338   // Different controllers have different ways of representing
339   // "nothing is pressed", but they're all outside the range of values.
340   if (dpad_value < min || dpad_value > max) {
341     // Nothing is pressed.
342     return;
343   }
344 
345   // Normalize value to start at 0.
346   int value = dpad_value - min;
347 
348   // Value will be in the range 0-7. The value represents the
349   // position of the d-pad around a circle, with 0 being straight up,
350   // 2 being right, 4 being straight down, and 6 being left.
351   if (value < 2 || value > 6) {
352     buttons[kUp] = true;
353   }
354   if (value > 2 && value < 6) {
355     buttons[kDown] = true;
356   }
357   if (value > 4) {
358     buttons[kLeft] = true;
359   }
360   if (value > 0 && value < 4) {
361     buttons[kRight] = true;
362   }
363 }
364 
InputValueChanged(IOHIDValueRef value)365 void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) {
366   RefPtr<GamepadPlatformService> service =
367       GamepadPlatformService::GetParentService();
368   if (!service) {
369     return;
370   }
371 
372   uint32_t value_length = IOHIDValueGetLength(value);
373   if (value_length > 4) {
374     // Workaround for bizarre issue with PS3 controllers that try to return
375     // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
376     return;
377   }
378   IOHIDElementRef element = IOHIDValueGetElement(value);
379   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
380 
381   for (unsigned i = 0; i < mGamepads.size(); i++) {
382     Gamepad& gamepad = mGamepads[i];
383     if (gamepad == device) {
384       if (gamepad.isDpad(element)) {
385         const dpad_buttons& oldState = gamepad.getDpadState();
386         dpad_buttons newState = {false, false, false, false};
387         UnpackDpad(IOHIDValueGetIntegerValue(value),
388                    IOHIDElementGetLogicalMin(element),
389                    IOHIDElementGetLogicalMax(element), newState);
390         const int numButtons = gamepad.numButtons();
391         for (unsigned b = 0; b < ArrayLength(newState); b++) {
392           if (newState[b] != oldState[b]) {
393             service->NewButtonEvent(gamepad.mSuperIndex, numButtons - 4 + b,
394                                     newState[b]);
395           }
396         }
397         gamepad.setDpadState(newState);
398       } else if (const Axis* axis = gamepad.lookupAxis(element)) {
399         double d = IOHIDValueGetIntegerValue(value);
400         double v =
401             2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f;
402         service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
403       } else if (const Button* button = gamepad.lookupButton(element)) {
404         int iv = IOHIDValueGetIntegerValue(value);
405         bool pressed = iv != 0;
406         double v = 0;
407         if (button->analog) {
408           double dv = iv;
409           v = (dv - button->min) / (double)(button->max - button->min);
410         } else {
411           v = pressed ? 1.0 : 0.0;
412         }
413         service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
414       }
415       return;
416     }
417   }
418 }
419 
DeviceAddedCallback(void * data,IOReturn result,void * sender,IOHIDDeviceRef device)420 void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
421                                                void* sender,
422                                                IOHIDDeviceRef device) {
423   DarwinGamepadService* service = (DarwinGamepadService*)data;
424   service->DeviceAdded(device);
425 }
426 
DeviceRemovedCallback(void * data,IOReturn result,void * sender,IOHIDDeviceRef device)427 void DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
428                                                  void* sender,
429                                                  IOHIDDeviceRef device) {
430   DarwinGamepadService* service = (DarwinGamepadService*)data;
431   service->DeviceRemoved(device);
432 }
433 
InputValueChangedCallback(void * data,IOReturn result,void * sender,IOHIDValueRef newValue)434 void DarwinGamepadService::InputValueChangedCallback(void* data,
435                                                      IOReturn result,
436                                                      void* sender,
437                                                      IOHIDValueRef newValue) {
438   DarwinGamepadService* service = (DarwinGamepadService*)data;
439   service->InputValueChanged(newValue);
440 }
441 
EventLoopOnceCallback(nsITimer * aTimer,void * aClosure)442 void DarwinGamepadService::EventLoopOnceCallback(nsITimer* aTimer,
443                                                  void* aClosure) {
444   DarwinGamepadService* service = static_cast<DarwinGamepadService*>(aClosure);
445   service->RunEventLoopOnce();
446 }
447 
MatchingDictionary(UInt32 inUsagePage,UInt32 inUsage)448 static CFMutableDictionaryRef MatchingDictionary(UInt32 inUsagePage,
449                                                  UInt32 inUsage) {
450   CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
451       kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
452       &kCFTypeDictionaryValueCallBacks);
453   if (!dict) return nullptr;
454   CFNumberRef number =
455       CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
456   if (!number) {
457     CFRelease(dict);
458     return nullptr;
459   }
460   CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
461   CFRelease(number);
462 
463   number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
464   if (!number) {
465     CFRelease(dict);
466     return nullptr;
467   }
468   CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
469   CFRelease(number);
470 
471   return dict;
472 }
473 
DarwinGamepadService()474 DarwinGamepadService::DarwinGamepadService()
475     : mManager(nullptr), mIsRunning(false) {}
476 
~DarwinGamepadService()477 DarwinGamepadService::~DarwinGamepadService() {
478   if (mManager != nullptr) CFRelease(mManager);
479   mMonitorThread = nullptr;
480   mBackgroundThread = nullptr;
481   if (mPollingTimer) {
482     mPollingTimer->Cancel();
483     mPollingTimer = nullptr;
484   }
485 }
486 
RunEventLoopOnce()487 void DarwinGamepadService::RunEventLoopOnce() {
488   MOZ_ASSERT(NS_GetCurrentThread() == mMonitorThread);
489   CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
490 
491   // This timer must be created in monitor thread
492   if (!mPollingTimer) {
493     mPollingTimer = do_CreateInstance("@mozilla.org/timer;1");
494   }
495   mPollingTimer->Cancel();
496   if (mIsRunning) {
497     mPollingTimer->InitWithNamedFuncCallback(
498         EventLoopOnceCallback, this, kDarwinGamepadPollInterval,
499         nsITimer::TYPE_ONE_SHOT, "EventLoopOnceCallback");
500   } else {
501     // We schedule a task shutdown and cleaning up resources to Background
502     // thread here to make sure no runloop is running to prevent potential race
503     // condition.
504     RefPtr<Runnable> shutdownTask = new DarwinGamepadServiceShutdownRunnable();
505     mBackgroundThread->Dispatch(shutdownTask.forget(), NS_DISPATCH_NORMAL);
506   }
507 }
508 
StartupInternal()509 void DarwinGamepadService::StartupInternal() {
510   if (mManager != nullptr) return;
511 
512   IOHIDManagerRef manager =
513       IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
514 
515   CFMutableDictionaryRef criteria_arr[2];
516   criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, kJoystickUsage);
517   if (!criteria_arr[0]) {
518     CFRelease(manager);
519     return;
520   }
521 
522   criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, kGamepadUsage);
523   if (!criteria_arr[1]) {
524     CFRelease(criteria_arr[0]);
525     CFRelease(manager);
526     return;
527   }
528 
529   CFArrayRef criteria = CFArrayCreate(kCFAllocatorDefault,
530                                       (const void**)criteria_arr, 2, nullptr);
531   if (!criteria) {
532     CFRelease(criteria_arr[1]);
533     CFRelease(criteria_arr[0]);
534     CFRelease(manager);
535     return;
536   }
537 
538   IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
539   CFRelease(criteria);
540   CFRelease(criteria_arr[1]);
541   CFRelease(criteria_arr[0]);
542 
543   IOHIDManagerRegisterDeviceMatchingCallback(manager, DeviceAddedCallback,
544                                              this);
545   IOHIDManagerRegisterDeviceRemovalCallback(manager, DeviceRemovedCallback,
546                                             this);
547   IOHIDManagerRegisterInputValueCallback(manager, InputValueChangedCallback,
548                                          this);
549   IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(),
550                                   kCFRunLoopDefaultMode);
551   IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
552   if (rv != kIOReturnSuccess) {
553     CFRelease(manager);
554     return;
555   }
556 
557   mManager = manager;
558 
559   mIsRunning = true;
560   RunEventLoopOnce();
561 }
562 
Startup()563 void DarwinGamepadService::Startup() {
564   mBackgroundThread = NS_GetCurrentThread();
565   Unused << NS_NewNamedThread("Gamepad", getter_AddRefs(mMonitorThread),
566                               new DarwinGamepadServiceStartupRunnable(this));
567 }
568 
Shutdown()569 void DarwinGamepadService::Shutdown() {
570   // Flipping this flag will stop the eventloop in Monitor thread
571   // and dispatch a task destroying and cleaning up resources in
572   // Background thread
573   mIsRunning = false;
574 }
575 
576 }  // namespace
577 
578 namespace mozilla {
579 namespace dom {
580 
StartGamepadMonitoring()581 void StartGamepadMonitoring() {
582   AssertIsOnBackgroundThread();
583   if (gService) {
584     return;
585   }
586 
587   gService = new DarwinGamepadService();
588   gService->Startup();
589 }
590 
StopGamepadMonitoring()591 void StopGamepadMonitoring() {
592   AssertIsOnBackgroundThread();
593   if (!gService) {
594     return;
595   }
596 
597   // Calling Shutdown() will delete gService as well
598   gService->Shutdown();
599 }
600 
601 }  // namespace dom
602 }  // namespace mozilla
603