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 40 namespace blink { 41 42 template<typename T> 43 static 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 54 template<typename GamepadType, typename ListType> 55 static 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 75 NavigatorGamepad* 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 83 NavigatorGamepad& 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 93 WebKitGamepadList* NavigatorGamepad::webkitGetGamepads(Navigator& navigator) 94 { 95 return NavigatorGamepad::from(navigator).webkitGamepads(); 96 } 97 98 GamepadList* NavigatorGamepad::getGamepads(Navigator& navigator) 99 { 100 return NavigatorGamepad::from(navigator).gamepads(); 101 } 102 103 WebKitGamepadList* 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 114 GamepadList* 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 125 void 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 134 void 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 161 void 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 174 NavigatorGamepad::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 182 NavigatorGamepad::~NavigatorGamepad() 183 { 184 #if ENABLE(OILPAN) 185 stopUpdating(); 186 #endif 187 } 188 189 const char* NavigatorGamepad::supplementName() 190 { 191 return "NavigatorGamepad"; 192 } 193 194 void NavigatorGamepad::willDestroyGlobalObjectInFrame() 195 { 196 stopUpdating(); 197 DOMWindowProperty::willDestroyGlobalObjectInFrame(); 198 } 199 200 void NavigatorGamepad::willDetachGlobalObjectFromFrame() 201 { 202 stopUpdating(); 203 DOMWindowProperty::willDetachGlobalObjectFromFrame(); 204 } 205 206 void NavigatorGamepad::registerWithDispatcher() 207 { 208 GamepadDispatcher::instance().addController(this); 209 m_dispatchOneEventRunner.resume(); 210 } 211 212 void NavigatorGamepad::unregisterWithDispatcher() 213 { 214 m_dispatchOneEventRunner.suspend(); 215 GamepadDispatcher::instance().removeController(this); 216 } 217 218 bool NavigatorGamepad::hasLastData() 219 { 220 // Gamepad data is polled instead of pushed. 221 return false; 222 } 223 224 static bool isGamepadEvent(const AtomicString& eventType) 225 { 226 return eventType == EventTypeNames::gamepadconnected || eventType == EventTypeNames::gamepaddisconnected; 227 } 228 229 void 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 238 void 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 247 void NavigatorGamepad::didRemoveAllEventListeners(LocalDOMWindow*) 248 { 249 didRemoveGamepadEventListeners(); 250 } 251 252 void NavigatorGamepad::didRemoveGamepadEventListeners() 253 { 254 m_hasEventListener = false; 255 m_dispatchOneEventRunner.stop(); 256 m_pendingEvents.clear(); 257 } 258 259 void 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