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 "Gamepad.h"
33#include "GamepadEvent.h"
34#include "GamepadProvider.h"
35#include "Logging.h"
36#include "NavigatorGamepad.h"
37#include "PlatformGamepad.h"
38
39namespace WebCore {
40
41static NavigatorGamepad* navigatorGamepadFromDOMWindow(DOMWindow* window)
42{
43    Navigator* navigator = window->navigator();
44    if (!navigator)
45        return nullptr;
46
47    return NavigatorGamepad::from(navigator);
48}
49
50GamepadManager& GamepadManager::shared()
51{
52    static NeverDestroyed<GamepadManager> sharedManager;
53    return sharedManager;
54}
55
56GamepadManager::GamepadManager()
57    : m_isMonitoringGamepads(false)
58{
59}
60
61void GamepadManager::platformGamepadConnected(PlatformGamepad& platformGamepad)
62{
63    // Notify blind Navigators and Windows about all gamepads except for this one.
64    for (auto* gamepad : GamepadProvider::shared().platformGamepads()) {
65        if (!gamepad || gamepad == &platformGamepad)
66            continue;
67
68        makeGamepadVisible(*gamepad, m_gamepadBlindNavigators, m_gamepadBlindDOMWindows);
69    }
70
71    m_gamepadBlindNavigators.clear();
72    m_gamepadBlindDOMWindows.clear();
73
74    // Notify everyone of this new gamepad.
75    makeGamepadVisible(platformGamepad, m_navigators, m_domWindows);
76}
77
78void GamepadManager::platformGamepadDisconnected(PlatformGamepad& platformGamepad)
79{
80    Vector<DOMWindow*> domWindowVector;
81    copyToVector(m_domWindows, domWindowVector);
82
83    HashSet<NavigatorGamepad*> notifiedNavigators;
84
85    // Handle the disconnect for all DOMWindows with event listeners and their Navigators.
86    for (auto* window : domWindowVector) {
87        // Event dispatch might have made this window go away.
88        if (!m_domWindows.contains(window))
89            continue;
90
91        NavigatorGamepad* navigator = navigatorGamepadFromDOMWindow(window);
92        if (!navigator)
93            continue;
94
95        // If this Navigator hasn't seen gamepads yet then its Window should not get the disconnect event.
96        if (m_gamepadBlindNavigators.contains(navigator))
97            continue;
98
99        RefPtr<Gamepad> gamepad = navigator->gamepadAtIndex(platformGamepad.index());
100        ASSERT(gamepad);
101
102        navigator->gamepadDisconnected(platformGamepad);
103        notifiedNavigators.add(navigator);
104
105        window->dispatchEvent(GamepadEvent::create(eventNames().gamepaddisconnectedEvent, gamepad.get()), window->document());
106    }
107
108    // Notify all the Navigators that haven't already been notified.
109    for (auto* navigator : m_navigators) {
110        if (!notifiedNavigators.contains(navigator))
111            navigator->gamepadDisconnected(platformGamepad);
112    }
113}
114
115void GamepadManager::platformGamepadInputActivity()
116{
117    if (m_gamepadBlindNavigators.isEmpty() && m_gamepadBlindDOMWindows.isEmpty())
118        return;
119
120    for (auto* gamepad : GamepadProvider::shared().platformGamepads())
121        makeGamepadVisible(*gamepad, m_gamepadBlindNavigators, m_gamepadBlindDOMWindows);
122
123    m_gamepadBlindNavigators.clear();
124    m_gamepadBlindDOMWindows.clear();
125}
126
127void GamepadManager::makeGamepadVisible(PlatformGamepad& platformGamepad, HashSet<NavigatorGamepad*>& navigatorSet, HashSet<DOMWindow*>& domWindowSet)
128{
129    if (navigatorSet.isEmpty() && domWindowSet.isEmpty())
130        return;
131
132    for (auto* navigator : navigatorSet)
133        navigator->gamepadConnected(platformGamepad);
134
135    Vector<DOMWindow*> domWindowVector;
136    copyToVector(domWindowSet, domWindowVector);
137
138    for (auto* window : domWindowVector) {
139        // Event dispatch might have made this window go away.
140        if (!m_domWindows.contains(window))
141            continue;
142
143        NavigatorGamepad* navigator = navigatorGamepadFromDOMWindow(window);
144        if (!navigator)
145            continue;
146
147        RefPtr<Gamepad> gamepad = navigator->gamepadAtIndex(platformGamepad.index());
148        ASSERT(gamepad);
149
150        window->dispatchEvent(GamepadEvent::create(eventNames().gamepadconnectedEvent, gamepad.get()), window->document());
151    }
152}
153
154void GamepadManager::registerNavigator(NavigatorGamepad* navigator)
155{
156    LOG(Gamepad, "GamepadManager registering NavigatorGamepad %p", navigator);
157
158    ASSERT(!m_navigators.contains(navigator));
159    m_navigators.add(navigator);
160    m_gamepadBlindNavigators.add(navigator);
161
162    maybeStartMonitoringGamepads();
163}
164
165void GamepadManager::unregisterNavigator(NavigatorGamepad* navigator)
166{
167    LOG(Gamepad, "GamepadManager unregistering NavigatorGamepad %p", navigator);
168
169    ASSERT(m_navigators.contains(navigator));
170    m_navigators.remove(navigator);
171    m_gamepadBlindNavigators.remove(navigator);
172
173    maybeStopMonitoringGamepads();
174}
175
176void GamepadManager::registerDOMWindow(DOMWindow* window)
177{
178    LOG(Gamepad, "GamepadManager registering DOMWindow %p", window);
179
180    ASSERT(!m_domWindows.contains(window));
181    m_domWindows.add(window);
182
183    // Anytime we register a DOMWindow, we should also double check that its NavigatorGamepad is registered.
184    NavigatorGamepad* navigator = navigatorGamepadFromDOMWindow(window);
185    ASSERT(navigator);
186
187    if (m_navigators.add(navigator).isNewEntry)
188        m_gamepadBlindNavigators.add(navigator);
189
190    // If this DOMWindow's NavigatorGamepad was already registered but was still blind,
191    // then this DOMWindow should be blind.
192    if (m_gamepadBlindNavigators.contains(navigator))
193        m_gamepadBlindDOMWindows.add(window);
194
195    maybeStartMonitoringGamepads();
196}
197
198void GamepadManager::unregisterDOMWindow(DOMWindow* window)
199{
200    LOG(Gamepad, "GamepadManager unregistering DOMWindow %p", window);
201
202    ASSERT(m_domWindows.contains(window));
203    m_domWindows.remove(window);
204    m_gamepadBlindDOMWindows.remove(window);
205
206    maybeStopMonitoringGamepads();
207}
208
209void GamepadManager::maybeStartMonitoringGamepads()
210{
211    if (m_isMonitoringGamepads)
212        return;
213
214    if (!m_navigators.isEmpty() || !m_domWindows.isEmpty()) {
215        LOG(Gamepad, "GamepadManager has %i NavigatorGamepads and %i DOMWindows registered, is starting gamepad monitoring", m_navigators.size(), m_domWindows.size());
216        m_isMonitoringGamepads = true;
217        GamepadProvider::shared().startMonitoringGamepads(this);
218    }
219}
220
221void GamepadManager::maybeStopMonitoringGamepads()
222{
223    if (!m_isMonitoringGamepads)
224        return;
225
226    if (m_navigators.isEmpty() && m_domWindows.isEmpty()) {
227        LOG(Gamepad, "GamepadManager has no NavigatorGamepads or DOMWindows registered, is stopping gamepad monitoring");
228        m_isMonitoringGamepads = false;
229        GamepadProvider::shared().stopMonitoringGamepads(this);
230    }
231}
232
233} // namespace WebCore
234
235#endif // ENABLE(GAMEPAD)
236