1/*
2 * Copyright (C) 2011, Google 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 are met:
6 *
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'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
23 * DAMAGE.
24 */
25
26#include "config.h"
27#include "modules/gamepad/NavigatorGamepad.h"
28
29#include "core/dom/Document.h"
30#include "core/frame/LocalDOMWindow.h"
31#include "core/frame/LocalFrame.h"
32#include "core/frame/Navigator.h"
33#include "core/page/Page.h"
34#include "modules/gamepad/GamepadDispatcher.h"
35#include "modules/gamepad/GamepadEvent.h"
36#include "modules/gamepad/GamepadList.h"
37#include "modules/gamepad/WebKitGamepadList.h"
38#include "platform/RuntimeEnabledFeatures.h"
39
40namespace blink {
41
42template<typename T>
43static void sampleGamepad(unsigned index, T& gamepad, const WebGamepad& webGamepad)
44{
45    gamepad.setId(webGamepad.id);
46    gamepad.setIndex(index);
47    gamepad.setConnected(webGamepad.connected);
48    gamepad.setTimestamp(webGamepad.timestamp);
49    gamepad.setMapping(webGamepad.mapping);
50    gamepad.setAxes(webGamepad.axesLength, webGamepad.axes);
51    gamepad.setButtons(webGamepad.buttonsLength, webGamepad.buttons);
52}
53
54template<typename GamepadType, typename ListType>
55static void sampleGamepads(ListType* into)
56{
57    WebGamepads gamepads;
58
59    GamepadDispatcher::instance().sampleGamepads(gamepads);
60
61    for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
62        WebGamepad& webGamepad = gamepads.items[i];
63        if (i < gamepads.length && webGamepad.connected) {
64            GamepadType* gamepad = into->item(i);
65            if (!gamepad)
66                gamepad = GamepadType::create();
67            sampleGamepad(i, *gamepad, webGamepad);
68            into->set(i, gamepad);
69        } else {
70            into->set(i, 0);
71        }
72    }
73}
74
75NavigatorGamepad* NavigatorGamepad::from(Document& document)
76{
77    if (!document.frame() || !document.frame()->domWindow())
78        return 0;
79    Navigator& navigator = document.frame()->domWindow()->navigator();
80    return &from(navigator);
81}
82
83NavigatorGamepad& NavigatorGamepad::from(Navigator& navigator)
84{
85    NavigatorGamepad* supplement = static_cast<NavigatorGamepad*>(WillBeHeapSupplement<Navigator>::from(navigator, supplementName()));
86    if (!supplement) {
87        supplement = new NavigatorGamepad(navigator.frame());
88        provideTo(navigator, supplementName(), adoptPtrWillBeNoop(supplement));
89    }
90    return *supplement;
91}
92
93WebKitGamepadList* NavigatorGamepad::webkitGetGamepads(Navigator& navigator)
94{
95    return NavigatorGamepad::from(navigator).webkitGamepads();
96}
97
98GamepadList* NavigatorGamepad::getGamepads(Navigator& navigator)
99{
100    return NavigatorGamepad::from(navigator).gamepads();
101}
102
103WebKitGamepadList* NavigatorGamepad::webkitGamepads()
104{
105    if (!m_webkitGamepads)
106        m_webkitGamepads = WebKitGamepadList::create();
107    if (window()) {
108        startUpdating();
109        sampleGamepads<WebKitGamepad>(m_webkitGamepads.get());
110    }
111    return m_webkitGamepads.get();
112}
113
114GamepadList* NavigatorGamepad::gamepads()
115{
116    if (!m_gamepads)
117        m_gamepads = GamepadList::create();
118    if (window()) {
119        startUpdating();
120        sampleGamepads<Gamepad>(m_gamepads.get());
121    }
122    return m_gamepads.get();
123}
124
125void NavigatorGamepad::trace(Visitor* visitor)
126{
127    visitor->trace(m_gamepads);
128    visitor->trace(m_webkitGamepads);
129    visitor->trace(m_pendingEvents);
130    WillBeHeapSupplement<Navigator>::trace(visitor);
131    DOMWindowProperty::trace(visitor);
132}
133
134void NavigatorGamepad::didUpdateData()
135{
136    // We should stop listening once we detached.
137    ASSERT(window());
138
139    // We register to the dispatcher before sampling gamepads so we need to check if we actually have an event listener.
140    if (!m_hasEventListener)
141        return;
142
143    if (window()->document()->activeDOMObjectsAreStopped() || window()->document()->activeDOMObjectsAreSuspended())
144        return;
145
146    const GamepadDispatcher::ConnectionChange& change = GamepadDispatcher::instance().latestConnectionChange();
147
148    if (!m_gamepads)
149        m_gamepads = GamepadList::create();
150
151    Gamepad* gamepad = m_gamepads->item(change.index);
152    if (!gamepad)
153        gamepad = Gamepad::create();
154    sampleGamepad(change.index, *gamepad, change.pad);
155    m_gamepads->set(change.index, gamepad);
156
157    m_pendingEvents.append(gamepad);
158    m_dispatchOneEventRunner.runAsync();
159}
160
161void NavigatorGamepad::dispatchOneEvent()
162{
163    ASSERT(window());
164    ASSERT(!m_pendingEvents.isEmpty());
165
166    Gamepad* gamepad = m_pendingEvents.takeFirst();
167    const AtomicString& eventName = gamepad->connected() ? EventTypeNames::gamepadconnected : EventTypeNames::gamepaddisconnected;
168    window()->dispatchEvent(GamepadEvent::create(eventName, false, true, gamepad));
169
170    if (!m_pendingEvents.isEmpty())
171        m_dispatchOneEventRunner.runAsync();
172}
173
174NavigatorGamepad::NavigatorGamepad(LocalFrame* frame)
175    : DOMWindowProperty(frame)
176    , PlatformEventController(frame ? frame->page() : 0)
177    , DOMWindowLifecycleObserver(frame ? frame->domWindow() : 0)
178    , m_dispatchOneEventRunner(this, &NavigatorGamepad::dispatchOneEvent)
179{
180}
181
182NavigatorGamepad::~NavigatorGamepad()
183{
184#if ENABLE(OILPAN)
185    stopUpdating();
186#endif
187}
188
189const char* NavigatorGamepad::supplementName()
190{
191    return "NavigatorGamepad";
192}
193
194void NavigatorGamepad::willDestroyGlobalObjectInFrame()
195{
196    stopUpdating();
197    DOMWindowProperty::willDestroyGlobalObjectInFrame();
198}
199
200void NavigatorGamepad::willDetachGlobalObjectFromFrame()
201{
202    stopUpdating();
203    DOMWindowProperty::willDetachGlobalObjectFromFrame();
204}
205
206void NavigatorGamepad::registerWithDispatcher()
207{
208    GamepadDispatcher::instance().addController(this);
209    m_dispatchOneEventRunner.resume();
210}
211
212void NavigatorGamepad::unregisterWithDispatcher()
213{
214    m_dispatchOneEventRunner.suspend();
215    GamepadDispatcher::instance().removeController(this);
216}
217
218bool NavigatorGamepad::hasLastData()
219{
220    // Gamepad data is polled instead of pushed.
221    return false;
222}
223
224static bool isGamepadEvent(const AtomicString& eventType)
225{
226    return eventType == EventTypeNames::gamepadconnected || eventType == EventTypeNames::gamepaddisconnected;
227}
228
229void NavigatorGamepad::didAddEventListener(LocalDOMWindow*, const AtomicString& eventType)
230{
231    if (RuntimeEnabledFeatures::gamepadEnabled() && isGamepadEvent(eventType)) {
232        if (page() && page()->visibilityState() == PageVisibilityStateVisible)
233            startUpdating();
234        m_hasEventListener = true;
235    }
236}
237
238void NavigatorGamepad::didRemoveEventListener(LocalDOMWindow* window, const AtomicString& eventType)
239{
240    if (isGamepadEvent(eventType)
241        && !window->hasEventListeners(EventTypeNames::gamepadconnected)
242        && !window->hasEventListeners(EventTypeNames::gamepaddisconnected)) {
243        didRemoveGamepadEventListeners();
244    }
245}
246
247void NavigatorGamepad::didRemoveAllEventListeners(LocalDOMWindow*)
248{
249    didRemoveGamepadEventListeners();
250}
251
252void NavigatorGamepad::didRemoveGamepadEventListeners()
253{
254    m_hasEventListener = false;
255    m_dispatchOneEventRunner.stop();
256    m_pendingEvents.clear();
257}
258
259void NavigatorGamepad::pageVisibilityChanged()
260{
261    // Inform the embedder whether it needs to provide gamepad data for us.
262    bool visible = page()->visibilityState() == PageVisibilityStateVisible;
263    if (visible && (m_hasEventListener || m_gamepads || m_webkitGamepads))
264        startUpdating();
265    else
266        stopUpdating();
267
268    if (!visible || !m_hasEventListener)
269        return;
270
271    // Tell the page what has changed. m_gamepads contains the state before we became hidden.
272    // We create a new snapshot and compare them.
273    GamepadList* oldGamepads = m_gamepads.release();
274    gamepads();
275    GamepadList* newGamepads = m_gamepads.get();
276    ASSERT(newGamepads);
277
278    for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
279        Gamepad* oldGamepad = oldGamepads ? oldGamepads->item(i) : 0;
280        Gamepad* newGamepad = newGamepads->item(i);
281        bool oldWasConnected = oldGamepad && oldGamepad->connected();
282        bool newIsConnected = newGamepad && newGamepad->connected();
283        bool connectedGamepadChanged = oldWasConnected && newIsConnected && oldGamepad->id() != newGamepad->id();
284        if (connectedGamepadChanged || (oldWasConnected && !newIsConnected)) {
285            oldGamepad->setConnected(false);
286            m_pendingEvents.append(oldGamepad);
287        }
288        if (connectedGamepadChanged || (!oldWasConnected && newIsConnected)) {
289            m_pendingEvents.append(newGamepad);
290        }
291    }
292
293    if (!m_pendingEvents.isEmpty())
294        m_dispatchOneEventRunner.runAsync();
295}
296
297} // namespace blink
298