Bumping manifests a=b2g-bump
[gecko.git] / dom / gamepad / GamepadService.cpp
blob236c9b641bf7c5ab151aec9025653586026c3410
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/Hal.h"
6 #include "mozilla/ClearOnShutdown.h"
7 #include "mozilla/Preferences.h"
8 #include "mozilla/StaticPtr.h"
10 #include "GamepadService.h"
11 #include "Gamepad.h"
12 #include "nsAutoPtr.h"
13 #include "nsIDOMEvent.h"
14 #include "nsIDOMDocument.h"
15 #include "nsIDOMWindow.h"
16 #include "nsIObserver.h"
17 #include "nsIObserverService.h"
18 #include "nsIServiceManager.h"
19 #include "nsITimer.h"
20 #include "nsThreadUtils.h"
21 #include "mozilla/Services.h"
23 #include "mozilla/dom/GamepadAxisMoveEvent.h"
24 #include "mozilla/dom/GamepadButtonEvent.h"
25 #include "mozilla/dom/GamepadEvent.h"
27 #include <cstddef>
29 namespace mozilla {
30 namespace dom {
32 namespace {
33 const char* kGamepadEnabledPref = "dom.gamepad.enabled";
34 const char* kGamepadEventsEnabledPref =
35 "dom.gamepad.non_standard_events.enabled";
36 // Amount of time to wait before cleaning up gamepad resources
37 // when no pages are listening for events.
38 const int kCleanupDelayMS = 2000;
39 const nsTArray<nsRefPtr<nsGlobalWindow> >::index_type NoIndex =
40 nsTArray<nsRefPtr<nsGlobalWindow> >::NoIndex;
42 StaticRefPtr<GamepadService> gGamepadServiceSingleton;
44 } // namespace
46 bool GamepadService::sShutdown = false;
48 NS_IMPL_ISUPPORTS(GamepadService, nsIObserver)
50 GamepadService::GamepadService()
51 : mStarted(false),
52 mShuttingDown(false)
54 mEnabled = IsAPIEnabled();
55 mNonstandardEventsEnabled =
56 Preferences::GetBool(kGamepadEventsEnabledPref, false);
57 nsCOMPtr<nsIObserverService> observerService =
58 mozilla::services::GetObserverService();
59 observerService->AddObserver(this,
60 NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
61 false);
64 NS_IMETHODIMP
65 GamepadService::Observe(nsISupports* aSubject,
66 const char* aTopic,
67 const char16_t* aData)
69 nsCOMPtr<nsIObserverService> observerService =
70 mozilla::services::GetObserverService();
71 observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
73 BeginShutdown();
74 return NS_OK;
77 void
78 GamepadService::BeginShutdown()
80 mShuttingDown = true;
81 if (mTimer) {
82 mTimer->Cancel();
84 if (mStarted) {
85 mozilla::hal::StopMonitoringGamepadStatus();
86 mStarted = false;
88 // Don't let windows call back to unregister during shutdown
89 for (uint32_t i = 0; i < mListeners.Length(); i++) {
90 mListeners[i]->SetHasGamepadEventListener(false);
92 mListeners.Clear();
93 mGamepads.Clear();
94 sShutdown = true;
97 void
98 GamepadService::AddListener(nsGlobalWindow* aWindow)
100 MOZ_ASSERT(aWindow);
101 MOZ_ASSERT(aWindow->IsInnerWindow());
103 if (mShuttingDown) {
104 return;
107 if (mListeners.IndexOf(aWindow) != NoIndex) {
108 return; // already exists
111 if (!mStarted && mEnabled) {
112 mozilla::hal::StartMonitoringGamepadStatus();
113 mStarted = true;
116 mListeners.AppendElement(aWindow);
119 void
120 GamepadService::RemoveListener(nsGlobalWindow* aWindow)
122 MOZ_ASSERT(aWindow);
123 MOZ_ASSERT(aWindow->IsInnerWindow());
125 if (mShuttingDown) {
126 // Doesn't matter at this point. It's possible we're being called
127 // as a result of our own destructor here, so just bail out.
128 return;
131 if (mListeners.IndexOf(aWindow) == NoIndex) {
132 return; // doesn't exist
135 mListeners.RemoveElement(aWindow);
137 if (mListeners.Length() == 0 && !mShuttingDown && mStarted) {
138 StartCleanupTimer();
142 uint32_t
143 GamepadService::AddGamepad(const char* aId,
144 GamepadMappingType aMapping,
145 uint32_t aNumButtons,
146 uint32_t aNumAxes)
148 //TODO: bug 852258: get initial button/axis state
149 nsRefPtr<Gamepad> gamepad =
150 new Gamepad(nullptr,
151 NS_ConvertUTF8toUTF16(nsDependentCString(aId)),
153 aMapping,
154 aNumButtons,
155 aNumAxes);
156 int index = -1;
157 for (uint32_t i = 0; i < mGamepads.Length(); i++) {
158 if (!mGamepads[i]) {
159 mGamepads[i] = gamepad;
160 index = i;
161 break;
164 if (index == -1) {
165 mGamepads.AppendElement(gamepad);
166 index = mGamepads.Length() - 1;
169 gamepad->SetIndex(index);
170 NewConnectionEvent(index, true);
172 return index;
175 void
176 GamepadService::RemoveGamepad(uint32_t aIndex)
178 if (aIndex < mGamepads.Length()) {
179 mGamepads[aIndex]->SetConnected(false);
180 NewConnectionEvent(aIndex, false);
181 // If this is the last entry in the list, just remove it.
182 if (aIndex == mGamepads.Length() - 1) {
183 mGamepads.RemoveElementAt(aIndex);
184 } else {
185 // Otherwise just null it out and leave it, so the
186 // indices of the following entries remain valid.
187 mGamepads[aIndex] = nullptr;
192 void
193 GamepadService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed)
195 // Synthesize a value: 1.0 for pressed, 0.0 for unpressed.
196 NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L);
199 void
200 GamepadService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
201 double aValue)
203 if (mShuttingDown || aIndex >= mGamepads.Length()) {
204 return;
207 mGamepads[aIndex]->SetButton(aButton, aPressed, aValue);
209 // Hold on to listeners in a separate array because firing events
210 // can mutate the mListeners array.
211 nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
213 for (uint32_t i = listeners.Length(); i > 0 ; ) {
214 --i;
216 // Only send events to non-background windows
217 if (!listeners[i]->IsCurrentInnerWindow() ||
218 listeners[i]->GetOuterWindow()->IsBackground()) {
219 continue;
222 bool first_time = false;
223 if (!WindowHasSeenGamepad(listeners[i], aIndex)) {
224 // This window hasn't seen this gamepad before, so
225 // send a connection event first.
226 SetWindowHasSeenGamepad(listeners[i], aIndex);
227 first_time = true;
230 nsRefPtr<Gamepad> gamepad = listeners[i]->GetGamepad(aIndex);
231 if (gamepad) {
232 gamepad->SetButton(aButton, aPressed, aValue);
233 if (first_time) {
234 FireConnectionEvent(listeners[i], gamepad, true);
236 if (mNonstandardEventsEnabled) {
237 // Fire event
238 FireButtonEvent(listeners[i], gamepad, aButton, aValue);
244 void
245 GamepadService::FireButtonEvent(EventTarget* aTarget,
246 Gamepad* aGamepad,
247 uint32_t aButton,
248 double aValue)
250 nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") :
251 NS_LITERAL_STRING("gamepadbuttonup");
252 GamepadButtonEventInit init;
253 init.mBubbles = false;
254 init.mCancelable = false;
255 init.mGamepad = aGamepad;
256 init.mButton = aButton;
257 nsRefPtr<GamepadButtonEvent> event =
258 GamepadButtonEvent::Constructor(aTarget, name, init);
260 event->SetTrusted(true);
262 bool defaultActionEnabled = true;
263 aTarget->DispatchEvent(event, &defaultActionEnabled);
266 void
267 GamepadService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue)
269 if (mShuttingDown || aIndex >= mGamepads.Length()) {
270 return;
272 mGamepads[aIndex]->SetAxis(aAxis, aValue);
274 // Hold on to listeners in a separate array because firing events
275 // can mutate the mListeners array.
276 nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
278 for (uint32_t i = listeners.Length(); i > 0 ; ) {
279 --i;
281 // Only send events to non-background windows
282 if (!listeners[i]->IsCurrentInnerWindow() ||
283 listeners[i]->GetOuterWindow()->IsBackground()) {
284 continue;
287 bool first_time = false;
288 if (!WindowHasSeenGamepad(listeners[i], aIndex)) {
289 // This window hasn't seen this gamepad before, so
290 // send a connection event first.
291 SetWindowHasSeenGamepad(listeners[i], aIndex);
292 first_time = true;
295 nsRefPtr<Gamepad> gamepad = listeners[i]->GetGamepad(aIndex);
296 if (gamepad) {
297 gamepad->SetAxis(aAxis, aValue);
298 if (first_time) {
299 FireConnectionEvent(listeners[i], gamepad, true);
301 if (mNonstandardEventsEnabled) {
302 // Fire event
303 FireAxisMoveEvent(listeners[i], gamepad, aAxis, aValue);
309 void
310 GamepadService::FireAxisMoveEvent(EventTarget* aTarget,
311 Gamepad* aGamepad,
312 uint32_t aAxis,
313 double aValue)
315 GamepadAxisMoveEventInit init;
316 init.mBubbles = false;
317 init.mCancelable = false;
318 init.mGamepad = aGamepad;
319 init.mAxis = aAxis;
320 init.mValue = aValue;
321 nsRefPtr<GamepadAxisMoveEvent> event =
322 GamepadAxisMoveEvent::Constructor(aTarget,
323 NS_LITERAL_STRING("gamepadaxismove"),
324 init);
326 event->SetTrusted(true);
328 bool defaultActionEnabled = true;
329 aTarget->DispatchEvent(event, &defaultActionEnabled);
332 void
333 GamepadService::NewConnectionEvent(uint32_t aIndex, bool aConnected)
335 if (mShuttingDown || aIndex >= mGamepads.Length()) {
336 return;
339 // Hold on to listeners in a separate array because firing events
340 // can mutate the mListeners array.
341 nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
343 if (aConnected) {
344 for (uint32_t i = listeners.Length(); i > 0 ; ) {
345 --i;
347 // Only send events to non-background windows
348 if (!listeners[i]->IsCurrentInnerWindow() ||
349 listeners[i]->GetOuterWindow()->IsBackground()) {
350 continue;
353 // We don't fire a connected event here unless the window
354 // has seen input from at least one device.
355 if (!listeners[i]->HasSeenGamepadInput()) {
356 continue;
359 SetWindowHasSeenGamepad(listeners[i], aIndex);
361 nsRefPtr<Gamepad> gamepad = listeners[i]->GetGamepad(aIndex);
362 if (gamepad) {
363 // Fire event
364 FireConnectionEvent(listeners[i], gamepad, aConnected);
367 } else {
368 // For disconnection events, fire one at every window that has received
369 // data from this gamepad.
370 for (uint32_t i = listeners.Length(); i > 0 ; ) {
371 --i;
373 // Even background windows get these events, so we don't have to
374 // deal with the hassle of syncing the state of removed gamepads.
376 if (WindowHasSeenGamepad(listeners[i], aIndex)) {
377 nsRefPtr<Gamepad> gamepad = listeners[i]->GetGamepad(aIndex);
378 if (gamepad) {
379 gamepad->SetConnected(false);
380 // Fire event
381 FireConnectionEvent(listeners[i], gamepad, false);
382 listeners[i]->RemoveGamepad(aIndex);
389 void
390 GamepadService::FireConnectionEvent(EventTarget* aTarget,
391 Gamepad* aGamepad,
392 bool aConnected)
394 nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") :
395 NS_LITERAL_STRING("gamepaddisconnected");
396 GamepadEventInit init;
397 init.mBubbles = false;
398 init.mCancelable = false;
399 init.mGamepad = aGamepad;
400 nsRefPtr<GamepadEvent> event =
401 GamepadEvent::Constructor(aTarget, name, init);
403 event->SetTrusted(true);
405 bool defaultActionEnabled = true;
406 aTarget->DispatchEvent(event, &defaultActionEnabled);
409 void
410 GamepadService::SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad)
412 if (mShuttingDown || !mEnabled || aIndex > mGamepads.Length()) {
413 return;
416 aGamepad->SyncState(mGamepads[aIndex]);
419 // static
420 already_AddRefed<GamepadService>
421 GamepadService::GetService()
423 if (sShutdown) {
424 return nullptr;
427 if (!gGamepadServiceSingleton) {
428 gGamepadServiceSingleton = new GamepadService();
429 ClearOnShutdown(&gGamepadServiceSingleton);
431 nsRefPtr<GamepadService> service(gGamepadServiceSingleton);
432 return service.forget();
435 // static
436 bool
437 GamepadService::IsAPIEnabled() {
438 return Preferences::GetBool(kGamepadEnabledPref, false);
441 bool
442 GamepadService::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex)
444 nsRefPtr<Gamepad> gamepad = aWindow->GetGamepad(aIndex);
445 return gamepad != nullptr;
448 void
449 GamepadService::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow,
450 uint32_t aIndex,
451 bool aHasSeen)
453 MOZ_ASSERT(aWindow);
454 MOZ_ASSERT(aWindow->IsInnerWindow());
456 if (mListeners.IndexOf(aWindow) == NoIndex) {
457 // This window isn't even listening for gamepad events.
458 return;
461 if (aHasSeen) {
462 aWindow->SetHasSeenGamepadInput(true);
463 nsCOMPtr<nsISupports> window = ToSupports(aWindow);
464 nsRefPtr<Gamepad> gamepad = mGamepads[aIndex]->Clone(window);
465 aWindow->AddGamepad(aIndex, gamepad);
466 } else {
467 aWindow->RemoveGamepad(aIndex);
471 // static
472 void
473 GamepadService::TimeoutHandler(nsITimer* aTimer, void* aClosure)
475 // the reason that we use self, instead of just using nsITimerCallback or nsIObserver
476 // is so that subclasses are free to use timers without worry about the base classes's
477 // usage.
478 GamepadService* self = reinterpret_cast<GamepadService*>(aClosure);
479 if (!self) {
480 NS_ERROR("no self");
481 return;
484 if (self->mShuttingDown) {
485 return;
488 if (self->mListeners.Length() == 0) {
489 mozilla::hal::StopMonitoringGamepadStatus();
490 self->mStarted = false;
491 if (!self->mGamepads.IsEmpty()) {
492 self->mGamepads.Clear();
497 void
498 GamepadService::StartCleanupTimer()
500 if (mTimer) {
501 mTimer->Cancel();
504 mTimer = do_CreateInstance("@mozilla.org/timer;1");
505 if (mTimer) {
506 mTimer->InitWithFuncCallback(TimeoutHandler,
507 this,
508 kCleanupDelayMS,
509 nsITimer::TYPE_ONE_SHOT);
514 * Implementation of the test service. This is just to provide a simple binding
515 * of the GamepadService to JavaScript via XPCOM so that we can write Mochitests
516 * that add and remove fake gamepads, avoiding the platform-specific backends.
518 NS_IMPL_ISUPPORTS(GamepadServiceTest, nsIGamepadServiceTest)
520 GamepadServiceTest* GamepadServiceTest::sSingleton = nullptr;
522 // static
523 already_AddRefed<GamepadServiceTest>
524 GamepadServiceTest::CreateService()
526 if (sSingleton == nullptr) {
527 sSingleton = new GamepadServiceTest();
529 nsRefPtr<GamepadServiceTest> service = sSingleton;
530 return service.forget();
533 GamepadServiceTest::GamepadServiceTest()
535 /* member initializers and constructor code */
536 nsRefPtr<GamepadService> service = GamepadService::GetService();
539 /* uint32_t addGamepad (in string id, in unsigned long mapping, in unsigned long numButtons, in unsigned long numAxes); */
540 NS_IMETHODIMP GamepadServiceTest::AddGamepad(const char* aID,
541 uint32_t aMapping,
542 uint32_t aNumButtons,
543 uint32_t aNumAxes,
544 uint32_t* aRetval)
546 *aRetval = gGamepadServiceSingleton->AddGamepad(aID,
547 static_cast<GamepadMappingType>(aMapping),
548 aNumButtons,
549 aNumAxes);
550 return NS_OK;
553 /* void removeGamepad (in uint32_t index); */
554 NS_IMETHODIMP GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
556 gGamepadServiceSingleton->RemoveGamepad(aIndex);
557 return NS_OK;
560 /* void newButtonEvent (in uint32_t index, in uint32_t button,
561 in boolean pressed); */
562 NS_IMETHODIMP GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
563 uint32_t aButton,
564 bool aPressed)
566 gGamepadServiceSingleton->NewButtonEvent(aIndex, aButton, aPressed);
567 return NS_OK;
570 /* void newAxisMoveEvent (in uint32_t index, in uint32_t axis,
571 in double value); */
572 NS_IMETHODIMP GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
573 uint32_t aAxis,
574 double aValue)
576 gGamepadServiceSingleton->NewAxisMoveEvent(aIndex, aAxis, aValue);
577 return NS_OK;
580 } // namespace dom
581 } // namespace mozilla