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