| /* |
| * Copyright (C) 2011, Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| #include "modules/gamepad/NavigatorGamepad.h" |
| |
| #include "core/dom/Document.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Navigator.h" |
| #include "core/page/Page.h" |
| #include "modules/gamepad/GamepadDispatcher.h" |
| #include "modules/gamepad/GamepadEvent.h" |
| #include "modules/gamepad/GamepadList.h" |
| |
| namespace blink { |
| |
| template<typename T> |
| static void sampleGamepad(unsigned index, T& gamepad, const WebGamepad& webGamepad) |
| { |
| gamepad.setId(webGamepad.id); |
| gamepad.setIndex(index); |
| gamepad.setConnected(webGamepad.connected); |
| gamepad.setTimestamp(webGamepad.timestamp); |
| gamepad.setMapping(webGamepad.mapping); |
| gamepad.setAxes(webGamepad.axesLength, webGamepad.axes); |
| gamepad.setButtons(webGamepad.buttonsLength, webGamepad.buttons); |
| gamepad.setPose(webGamepad.pose); |
| gamepad.setHand(webGamepad.hand); |
| gamepad.setDisplayId(webGamepad.displayId); |
| } |
| |
| template<typename GamepadType, typename ListType> |
| static void sampleGamepads(ListType* into) |
| { |
| WebGamepads gamepads; |
| |
| GamepadDispatcher::instance().sampleGamepads(gamepads); |
| |
| for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| WebGamepad& webGamepad = gamepads.items[i]; |
| if (i < gamepads.length && webGamepad.connected) { |
| GamepadType* gamepad = into->item(i); |
| if (!gamepad) |
| gamepad = GamepadType::create(); |
| sampleGamepad(i, *gamepad, webGamepad); |
| into->set(i, gamepad); |
| } else { |
| into->set(i, 0); |
| } |
| } |
| } |
| |
| NavigatorGamepad* NavigatorGamepad::from(Document& document) |
| { |
| if (!document.frame() || !document.frame()->domWindow()) |
| return 0; |
| Navigator& navigator = *document.frame()->domWindow()->navigator(); |
| return &from(navigator); |
| } |
| |
| NavigatorGamepad& NavigatorGamepad::from(Navigator& navigator) |
| { |
| NavigatorGamepad* supplement = static_cast<NavigatorGamepad*>(Supplement<Navigator>::from(navigator, supplementName())); |
| if (!supplement) { |
| supplement = new NavigatorGamepad(navigator.frame()); |
| provideTo(navigator, supplementName(), supplement); |
| } |
| return *supplement; |
| } |
| |
| GamepadList* NavigatorGamepad::getGamepads(Navigator& navigator) |
| { |
| return NavigatorGamepad::from(navigator).gamepads(); |
| } |
| |
| GamepadList* NavigatorGamepad::gamepads() |
| { |
| if (!m_gamepads) |
| m_gamepads = GamepadList::create(); |
| if (startUpdatingIfAttached()) |
| sampleGamepads<Gamepad>(m_gamepads.get()); |
| return m_gamepads.get(); |
| } |
| |
| DEFINE_TRACE(NavigatorGamepad) |
| { |
| visitor->trace(m_gamepads); |
| visitor->trace(m_pendingEvents); |
| visitor->trace(m_dispatchOneEventRunner); |
| Supplement<Navigator>::trace(visitor); |
| ContextLifecycleObserver::trace(visitor); |
| PlatformEventController::trace(visitor); |
| } |
| |
| bool NavigatorGamepad::startUpdatingIfAttached() |
| { |
| Document* document = static_cast<Document*>(getExecutionContext()); |
| // The frame must be attached to start updating. |
| if (document && document->frame()) { |
| startUpdating(); |
| return true; |
| } |
| return false; |
| } |
| |
| void NavigatorGamepad::didUpdateData() |
| { |
| // We should stop listening once we detached. |
| Document* document = static_cast<Document*>(getExecutionContext()); |
| DCHECK(document->frame()); |
| DCHECK(document->frame()->domWindow()); |
| |
| // We register to the dispatcher before sampling gamepads so we need to check if we actually have an event listener. |
| if (!m_hasEventListener) |
| return; |
| |
| if (document->activeDOMObjectsAreStopped() || document->activeDOMObjectsAreSuspended()) |
| return; |
| |
| const GamepadDispatcher::ConnectionChange& change = GamepadDispatcher::instance().latestConnectionChange(); |
| |
| if (!m_gamepads) |
| m_gamepads = GamepadList::create(); |
| |
| Gamepad* gamepad = m_gamepads->item(change.index); |
| if (!gamepad) |
| gamepad = Gamepad::create(); |
| sampleGamepad(change.index, *gamepad, change.pad); |
| m_gamepads->set(change.index, gamepad); |
| |
| m_pendingEvents.append(gamepad); |
| m_dispatchOneEventRunner->runAsync(); |
| } |
| |
| void NavigatorGamepad::dispatchOneEvent() |
| { |
| Document* document = static_cast<Document*>(getExecutionContext()); |
| DCHECK(document->frame()); |
| DCHECK(document->frame()->domWindow()); |
| DCHECK(!m_pendingEvents.isEmpty()); |
| |
| Gamepad* gamepad = m_pendingEvents.takeFirst(); |
| const AtomicString& eventName = gamepad->connected() ? EventTypeNames::gamepadconnected : EventTypeNames::gamepaddisconnected; |
| document->frame()->domWindow()->dispatchEvent(GamepadEvent::create(eventName, false, true, gamepad)); |
| |
| if (!m_pendingEvents.isEmpty()) |
| m_dispatchOneEventRunner->runAsync(); |
| } |
| |
| NavigatorGamepad::NavigatorGamepad(LocalFrame* frame) |
| : ContextLifecycleObserver(frame->document()) |
| , PlatformEventController(frame ? frame->page() : 0) |
| , m_dispatchOneEventRunner(AsyncMethodRunner<NavigatorGamepad>::create(this, &NavigatorGamepad::dispatchOneEvent)) |
| { |
| if (frame) |
| frame->localDOMWindow()->registerEventListenerObserver(this); |
| } |
| |
| NavigatorGamepad::~NavigatorGamepad() |
| { |
| } |
| |
| const char* NavigatorGamepad::supplementName() |
| { |
| return "NavigatorGamepad"; |
| } |
| |
| void NavigatorGamepad::contextDestroyed() |
| { |
| stopUpdating(); |
| } |
| |
| void NavigatorGamepad::registerWithDispatcher() |
| { |
| GamepadDispatcher::instance().addController(this); |
| m_dispatchOneEventRunner->resume(); |
| } |
| |
| void NavigatorGamepad::unregisterWithDispatcher() |
| { |
| m_dispatchOneEventRunner->suspend(); |
| GamepadDispatcher::instance().removeController(this); |
| } |
| |
| bool NavigatorGamepad::hasLastData() |
| { |
| // Gamepad data is polled instead of pushed. |
| return false; |
| } |
| |
| static bool isGamepadEvent(const AtomicString& eventType) |
| { |
| return eventType == EventTypeNames::gamepadconnected || eventType == EventTypeNames::gamepaddisconnected; |
| } |
| |
| void NavigatorGamepad::didAddEventListener(LocalDOMWindow*, const AtomicString& eventType) |
| { |
| if (isGamepadEvent(eventType)) { |
| if (page() && page()->isPageVisible()) |
| startUpdatingIfAttached(); |
| m_hasEventListener = true; |
| } |
| } |
| |
| void NavigatorGamepad::didRemoveEventListener(LocalDOMWindow* window, const AtomicString& eventType) |
| { |
| if (isGamepadEvent(eventType) |
| && !window->hasEventListeners(EventTypeNames::gamepadconnected) |
| && !window->hasEventListeners(EventTypeNames::gamepaddisconnected)) { |
| didRemoveGamepadEventListeners(); |
| } |
| } |
| |
| void NavigatorGamepad::didRemoveAllEventListeners(LocalDOMWindow*) |
| { |
| didRemoveGamepadEventListeners(); |
| } |
| |
| void NavigatorGamepad::didRemoveGamepadEventListeners() |
| { |
| m_hasEventListener = false; |
| m_dispatchOneEventRunner->stop(); |
| m_pendingEvents.clear(); |
| } |
| |
| void NavigatorGamepad::pageVisibilityChanged() |
| { |
| // Inform the embedder whether it needs to provide gamepad data for us. |
| bool visible = page()->isPageVisible(); |
| if (visible && (m_hasEventListener || m_gamepads)) |
| startUpdatingIfAttached(); |
| else |
| stopUpdating(); |
| |
| if (!visible || !m_hasEventListener) |
| return; |
| |
| // Tell the page what has changed. m_gamepads contains the state before we became hidden. |
| // We create a new snapshot and compare them. |
| GamepadList* oldGamepads = m_gamepads.release(); |
| gamepads(); |
| GamepadList* newGamepads = m_gamepads.get(); |
| DCHECK(newGamepads); |
| |
| for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { |
| Gamepad* oldGamepad = oldGamepads ? oldGamepads->item(i) : 0; |
| Gamepad* newGamepad = newGamepads->item(i); |
| bool oldWasConnected = oldGamepad && oldGamepad->connected(); |
| bool newIsConnected = newGamepad && newGamepad->connected(); |
| bool connectedGamepadChanged = oldWasConnected && newIsConnected && oldGamepad->id() != newGamepad->id(); |
| if (connectedGamepadChanged || (oldWasConnected && !newIsConnected)) { |
| oldGamepad->setConnected(false); |
| m_pendingEvents.append(oldGamepad); |
| } |
| if (connectedGamepadChanged || (!oldWasConnected && newIsConnected)) { |
| m_pendingEvents.append(newGamepad); |
| } |
| } |
| |
| if (!m_pendingEvents.isEmpty()) |
| m_dispatchOneEventRunner->runAsync(); |
| } |
| |
| } // namespace blink |