Require a button press on a gamepad for them to be exposed to the DOM.
[WebKit-https.git] / Source / WebCore / Modules / gamepad / GamepadManager.cpp
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #include "config.h"
26 #include "GamepadManager.h"
27
28 #if ENABLE(GAMEPAD)
29
30 #include "DOMWindow.h"
31 #include "Document.h"
32 #include "EventNames.h"
33 #include "Gamepad.h"
34 #include "GamepadEvent.h"
35 #include "GamepadProvider.h"
36 #include "Logging.h"
37 #include "NavigatorGamepad.h"
38 #include "PlatformGamepad.h"
39
40 namespace WebCore {
41
42 static NavigatorGamepad* navigatorGamepadFromDOMWindow(DOMWindow* window)
43 {
44     Navigator* navigator = window->navigator();
45     if (!navigator)
46         return nullptr;
47
48     return NavigatorGamepad::from(navigator);
49 }
50
51 GamepadManager& GamepadManager::singleton()
52 {
53     static NeverDestroyed<GamepadManager> sharedManager;
54     return sharedManager;
55 }
56
57 GamepadManager::GamepadManager()
58     : m_isMonitoringGamepads(false)
59 {
60 }
61
62 void GamepadManager::platformGamepadConnected(PlatformGamepad& platformGamepad)
63 {
64     // Notify blind Navigators and Windows about all gamepads except for this one.
65     for (auto* gamepad : GamepadProvider::singleton().platformGamepads()) {
66         if (!gamepad || gamepad == &platformGamepad)
67             continue;
68
69         makeGamepadVisible(*gamepad, m_gamepadBlindNavigators, m_gamepadBlindDOMWindows);
70     }
71
72     m_gamepadBlindNavigators.clear();
73     m_gamepadBlindDOMWindows.clear();
74
75     // Notify everyone of this new gamepad.
76     makeGamepadVisible(platformGamepad, m_navigators, m_domWindows);
77 }
78
79 void GamepadManager::platformGamepadDisconnected(PlatformGamepad& platformGamepad)
80 {
81     Vector<WeakPtr<DOMWindow>> weakWindows;
82     for (auto* domWindow : m_domWindows)
83         weakWindows.append(domWindow->createWeakPtr());
84
85     HashSet<NavigatorGamepad*> notifiedNavigators;
86
87     // Handle the disconnect for all DOMWindows with event listeners and their Navigators.
88     for (auto& window : weakWindows) {
89         // Event dispatch might have made this window go away.
90         if (!window)
91             continue;
92
93         // This DOMWindow's Navigator might not be accessible. e.g. The DOMWindow might be in the back/forward cache.
94         // If this happens the DOMWindow will not get this gamepaddisconnected event.
95         NavigatorGamepad* navigator = navigatorGamepadFromDOMWindow(window.get());
96         if (!navigator)
97             continue;
98
99         // If this Navigator hasn't seen gamepads yet then its Window should not get the disconnect event.
100         if (m_gamepadBlindNavigators.contains(navigator))
101             continue;
102
103         Ref<Gamepad> gamepad(navigator->gamepadFromPlatformGamepad(platformGamepad));
104
105         navigator->gamepadDisconnected(platformGamepad);
106         notifiedNavigators.add(navigator);
107
108         window->dispatchEvent(GamepadEvent::create(eventNames().gamepaddisconnectedEvent, gamepad.get()), window->document());
109     }
110
111     // Notify all the Navigators that haven't already been notified.
112     for (auto* navigator : m_navigators) {
113         if (!notifiedNavigators.contains(navigator))
114             navigator->gamepadDisconnected(platformGamepad);
115     }
116 }
117
118 void GamepadManager::platformGamepadInputActivity(bool shouldMakeGamepadVisible)
119 {
120     if (!shouldMakeGamepadVisible)
121         return;
122
123     if (m_gamepadBlindNavigators.isEmpty() && m_gamepadBlindDOMWindows.isEmpty())
124         return;
125
126     for (auto* gamepad : GamepadProvider::singleton().platformGamepads()) {
127         if (gamepad)
128             makeGamepadVisible(*gamepad, m_gamepadBlindNavigators, m_gamepadBlindDOMWindows);
129     }
130
131     m_gamepadBlindNavigators.clear();
132     m_gamepadBlindDOMWindows.clear();
133 }
134
135 void GamepadManager::makeGamepadVisible(PlatformGamepad& platformGamepad, HashSet<NavigatorGamepad*>& navigatorSet, HashSet<DOMWindow*>& domWindowSet)
136 {
137     if (navigatorSet.isEmpty() && domWindowSet.isEmpty())
138         return;
139
140     for (auto* navigator : navigatorSet)
141         navigator->gamepadConnected(platformGamepad);
142
143     Vector<WeakPtr<DOMWindow>> weakWindows;
144     for (auto* domWindow : m_domWindows)
145         weakWindows.append(domWindow->createWeakPtr());
146
147     for (auto& window : weakWindows) {
148         // Event dispatch might have made this window go away.
149         if (!window)
150             continue;
151
152         // This DOMWindow's Navigator might not be accessible. e.g. The DOMWindow might be in the back/forward cache.
153         // If this happens the DOMWindow will not get this gamepadconnected event.
154         // The new gamepad will still be visibile to it once it is restored from the back/forward cache.
155         NavigatorGamepad* navigator = navigatorGamepadFromDOMWindow(window.get());
156         if (!navigator)
157             continue;
158
159         Ref<Gamepad> gamepad(navigator->gamepadFromPlatformGamepad(platformGamepad));
160         window->dispatchEvent(GamepadEvent::create(eventNames().gamepadconnectedEvent, gamepad.get()), window->document());
161     }
162 }
163
164 void GamepadManager::registerNavigator(NavigatorGamepad* navigator)
165 {
166     LOG(Gamepad, "GamepadManager registering NavigatorGamepad %p", navigator);
167
168     ASSERT(!m_navigators.contains(navigator));
169     m_navigators.add(navigator);
170     m_gamepadBlindNavigators.add(navigator);
171
172     maybeStartMonitoringGamepads();
173 }
174
175 void GamepadManager::unregisterNavigator(NavigatorGamepad* navigator)
176 {
177     LOG(Gamepad, "GamepadManager unregistering NavigatorGamepad %p", navigator);
178
179     ASSERT(m_navigators.contains(navigator));
180     m_navigators.remove(navigator);
181     m_gamepadBlindNavigators.remove(navigator);
182
183     maybeStopMonitoringGamepads();
184 }
185
186 void GamepadManager::registerDOMWindow(DOMWindow* window)
187 {
188     LOG(Gamepad, "GamepadManager registering DOMWindow %p", window);
189
190     ASSERT(!m_domWindows.contains(window));
191     m_domWindows.add(window);
192
193     // Anytime we register a DOMWindow, we should also double check that its NavigatorGamepad is registered.
194     NavigatorGamepad* navigator = navigatorGamepadFromDOMWindow(window);
195     ASSERT(navigator);
196
197     if (m_navigators.add(navigator).isNewEntry)
198         m_gamepadBlindNavigators.add(navigator);
199
200     // If this DOMWindow's NavigatorGamepad was already registered but was still blind,
201     // then this DOMWindow should be blind.
202     if (m_gamepadBlindNavigators.contains(navigator))
203         m_gamepadBlindDOMWindows.add(window);
204
205     maybeStartMonitoringGamepads();
206 }
207
208 void GamepadManager::unregisterDOMWindow(DOMWindow* window)
209 {
210     LOG(Gamepad, "GamepadManager unregistering DOMWindow %p", window);
211
212     ASSERT(m_domWindows.contains(window));
213     m_domWindows.remove(window);
214     m_gamepadBlindDOMWindows.remove(window);
215
216     maybeStopMonitoringGamepads();
217 }
218
219 void GamepadManager::maybeStartMonitoringGamepads()
220 {
221     if (m_isMonitoringGamepads)
222         return;
223
224     if (!m_navigators.isEmpty() || !m_domWindows.isEmpty()) {
225         LOG(Gamepad, "GamepadManager has %i NavigatorGamepads and %i DOMWindows registered, is starting gamepad monitoring", m_navigators.size(), m_domWindows.size());
226         m_isMonitoringGamepads = true;
227         GamepadProvider::singleton().startMonitoringGamepads(*this);
228     }
229 }
230
231 void GamepadManager::maybeStopMonitoringGamepads()
232 {
233     if (!m_isMonitoringGamepads)
234         return;
235
236     if (m_navigators.isEmpty() && m_domWindows.isEmpty()) {
237         LOG(Gamepad, "GamepadManager has no NavigatorGamepads or DOMWindows registered, is stopping gamepad monitoring");
238         m_isMonitoringGamepads = false;
239         GamepadProvider::singleton().stopMonitoringGamepads(*this);
240     }
241 }
242
243 } // namespace WebCore
244
245 #endif // ENABLE(GAMEPAD)