diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc | 299 |
1 files changed, 124 insertions, 175 deletions
diff --git a/chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc b/chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc index 3f027a1ad5a..3df145867ba 100644 --- a/chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc +++ b/chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc @@ -25,7 +25,8 @@ #include "third_party/blink/renderer/modules/gamepad/navigator_gamepad.h" -#include "device/gamepad/public/cpp/gamepad.h" +#include "base/auto_reset.h" +#include "device/gamepad/public/cpp/gamepads.h" #include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/events/event.h" @@ -35,6 +36,7 @@ #include "third_party/blink/renderer/core/origin_trials/origin_trials.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/timing/performance.h" +#include "third_party/blink/renderer/modules/gamepad/gamepad_comparisons.h" #include "third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.h" #include "third_party/blink/renderer/modules/gamepad/gamepad_event.h" #include "third_party/blink/renderer/modules/gamepad/gamepad_list.h" @@ -44,59 +46,29 @@ namespace blink { namespace { -// A button press must have a value at least this large to qualify as a user -// activation. The selected value should be greater than 0.5 so that axes -// incorrectly mapped as triggers do not generate activations in the idle -// position. -const double kButtonActivationThreshold = 0.9; - -void HasGamepadConnectionChanged(const String& old_id, - const String& new_id, - bool old_connected, - bool new_connected, - bool* gamepad_found, - bool* gamepad_lost) { - // If the gamepad ID changes, treat it as a disconnection and connection. - bool id_changed = old_connected && new_connected && old_id != new_id; - - if (gamepad_found) - *gamepad_found = id_changed || (!old_connected && new_connected); - if (gamepad_lost) - *gamepad_lost = id_changed || (old_connected && !new_connected); +bool IsGamepadConnectionEvent(const AtomicString& event_type) { + return event_type == event_type_names::kGamepadconnected || + event_type == event_type_names::kGamepaddisconnected; } -bool HasUserActivation(GamepadList* gamepads) { - if (!gamepads) - return false; - // A button press counts as a user activation if the button's value is greater - // than the activation threshold. A threshold is used so that analog buttons - // or triggers do not generate an activation from a light touch. - for (wtf_size_t pad_index = 0; pad_index < gamepads->length(); ++pad_index) { - Gamepad* pad = gamepads->item(pad_index); - if (pad) { - const GamepadButtonVector& buttons = pad->buttons(); - for (auto button : buttons) { - double value = button->value(); - if (value > kButtonActivationThreshold) - return true; - } - } - } - return false; +bool HasConnectionEventListeners(LocalDOMWindow* window) { + return window->HasEventListeners(event_type_names::kGamepadconnected) || + window->HasEventListeners(event_type_names::kGamepaddisconnected); } -} // namespace - -template <typename T> -static void SampleGamepad(unsigned index, - T& gamepad, +static void SampleGamepad(uint32_t index, + Gamepad& gamepad, const device::Gamepad& device_gamepad, - const TimeTicks& navigation_start) { + const TimeTicks& navigation_start, + const TimeTicks& gamepads_start) { String old_id = gamepad.id(); bool old_was_connected = gamepad.connected(); TimeTicks last_updated = TimeTicks() + TimeDelta::FromMicroseconds(device_gamepad.timestamp); + if (last_updated < gamepads_start) + last_updated = gamepads_start; + DOMHighResTimeStamp timestamp = Performance::MonotonicTimeToDOMHighResTimeStamp(navigation_start, last_updated, false); @@ -115,8 +87,9 @@ static void SampleGamepad(unsigned index, } bool newly_connected; - HasGamepadConnectionChanged(old_id, gamepad.id(), old_was_connected, - gamepad.connected(), &newly_connected, nullptr); + GamepadComparisons::HasGamepadConnectionChanged( + old_was_connected, gamepad.connected(), old_id != gamepad.id(), + &newly_connected, nullptr); // These fields are not expected to change and will only be written when the // gamepad is newly connected. @@ -136,15 +109,15 @@ static void SampleGamepad(unsigned index, } } -template <typename GamepadType, typename ListType> -static void SampleGamepads(ListType* into, - const ExecutionContext* context, - const TimeTicks& navigation_start) { +static void SampleGamepads(GamepadList* into, + ExecutionContext* context, + const TimeTicks& navigation_start, + const TimeTicks& gamepads_start) { device::Gamepads gamepads; GamepadDispatcher::Instance().SampleGamepads(gamepads); - for (unsigned i = 0; i < device::Gamepads::kItemsLengthCap; ++i) { + for (uint32_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) { device::Gamepad& web_gamepad = gamepads.items[i]; bool hide_xr_gamepad = false; @@ -164,10 +137,10 @@ static void SampleGamepads(ListType* into, if (hide_xr_gamepad) { into->Set(i, nullptr); } else if (web_gamepad.connected) { - GamepadType* gamepad = into->item(i); + Gamepad* gamepad = into->item(i); if (!gamepad) - gamepad = GamepadType::Create(); - SampleGamepad(i, *gamepad, web_gamepad, navigation_start); + gamepad = Gamepad::Create(context); + SampleGamepad(i, *gamepad, web_gamepad, navigation_start, gamepads_start); into->Set(i, gamepad); } else { into->Set(i, nullptr); @@ -175,6 +148,8 @@ static void SampleGamepads(ListType* into, } } +} // namespace + // static const char NavigatorGamepad::kSupplementName[] = "NavigatorGamepad"; @@ -210,14 +185,20 @@ GamepadList* NavigatorGamepad::Gamepads() { } } - SampleAndCheckConnectedGamepads(); + SampleAndCompareGamepadState(); + + // Ensure |gamepads_| is not null. + if (!gamepads_) + gamepads_ = GamepadList::Create(); // Allow gamepad button presses to qualify as user activations if the page is // visible. if (RuntimeEnabledFeatures::UserActivationV2Enabled() && GetFrame() && - GetPage() && GetPage()->IsPageVisible() && HasUserActivation(gamepads_)) { + GetPage() && GetPage()->IsPageVisible() && + GamepadComparisons::HasUserActivation(gamepads_)) { LocalFrame::NotifyUserActivation(GetFrame(), UserGestureToken::kNewGesture); } + is_gamepads_exposed_ = true; return gamepads_.Get(); } @@ -225,8 +206,6 @@ GamepadList* NavigatorGamepad::Gamepads() { void NavigatorGamepad::Trace(blink::Visitor* visitor) { visitor->Trace(gamepads_); visitor->Trace(gamepads_back_); - visitor->Trace(pending_events_); - visitor->Trace(dispatch_one_event_runner_); Supplement<Navigator>::Trace(visitor); DOMWindowClient::Trace(visitor); PlatformEventController::Trace(visitor); @@ -246,43 +225,21 @@ void NavigatorGamepad::DidUpdateData() { DCHECK(GetFrame()); DCHECK(DomWindow()); - // We register to the dispatcher before sampling gamepads so we need to check - // if we actually have an event listener. - if (!has_event_listener_) - return; + // Record when gamepad data was first made available to the page. + if (gamepads_start_.is_null()) + gamepads_start_ = base::TimeTicks::Now(); - SampleAndCheckConnectedGamepads(); -} - -void NavigatorGamepad::DispatchOneEvent() { - DCHECK(DomWindow()); - DCHECK(!pending_events_.IsEmpty()); - - Gamepad* gamepad = pending_events_.TakeFirst(); - const AtomicString& event_name = gamepad->connected() - ? event_type_names::kGamepadconnected - : event_type_names::kGamepaddisconnected; - DomWindow()->DispatchEvent(*GamepadEvent::Create( - event_name, Event::Bubbles::kNo, Event::Cancelable::kYes, gamepad)); - - if (!pending_events_.IsEmpty()) { - DCHECK(dispatch_one_event_runner_); - dispatch_one_event_runner_->RunAsync(); - } + // Fetch the new gamepad state and dispatch gamepad events. + if (has_event_listener_) + SampleAndCompareGamepadState(); } NavigatorGamepad::NavigatorGamepad(Navigator& navigator) : Supplement<Navigator>(navigator), DOMWindowClient(navigator.DomWindow()), - PlatformEventController( - navigator.GetFrame() ? navigator.GetFrame()->GetDocument() : nullptr), - dispatch_one_event_runner_( - navigator.GetFrame() ? AsyncMethodRunner<NavigatorGamepad>::Create( - this, - &NavigatorGamepad::DispatchOneEvent, - navigator.GetFrame()->GetTaskRunner( - TaskType::kMiscPlatformAPI)) - : nullptr) { + PlatformEventController(navigator.GetFrame() + ? navigator.GetFrame()->GetDocument() + : nullptr) { if (navigator.DomWindow()) navigator.DomWindow()->RegisterEventListenerObserver(this); @@ -299,13 +256,9 @@ NavigatorGamepad::~NavigatorGamepad() = default; void NavigatorGamepad::RegisterWithDispatcher() { GamepadDispatcher::Instance().AddController(this); - if (dispatch_one_event_runner_) - dispatch_one_event_runner_->Unpause(); } void NavigatorGamepad::UnregisterWithDispatcher() { - if (dispatch_one_event_runner_) - dispatch_one_event_runner_->Pause(); GamepadDispatcher::Instance().RemoveController(this); } @@ -314,32 +267,27 @@ bool NavigatorGamepad::HasLastData() { return false; } -static bool IsGamepadEvent(const AtomicString& event_type) { - return event_type == event_type_names::kGamepadconnected || - event_type == event_type_names::kGamepaddisconnected; -} - void NavigatorGamepad::DidAddEventListener(LocalDOMWindow*, const AtomicString& event_type) { - if (!IsGamepadEvent(event_type)) - return; - - bool first_event_listener = !has_event_listener_; - has_event_listener_ = true; - - if (GetPage() && GetPage()->IsPageVisible()) { - StartUpdatingIfAttached(); - if (first_event_listener) - SampleAndCheckConnectedGamepads(); + if (IsGamepadConnectionEvent(event_type)) { + has_connection_event_listener_ = true; + bool first_event_listener = !has_event_listener_; + has_event_listener_ = true; + + if (GetPage() && GetPage()->IsPageVisible()) { + StartUpdatingIfAttached(); + if (first_event_listener) + SampleAndCompareGamepadState(); + } } } void NavigatorGamepad::DidRemoveEventListener(LocalDOMWindow* window, const AtomicString& event_type) { - if (IsGamepadEvent(event_type) && - !window->HasEventListeners(event_type_names::kGamepadconnected) && - !window->HasEventListeners(event_type_names::kGamepaddisconnected)) { - DidRemoveGamepadEventListeners(); + if (IsGamepadConnectionEvent(event_type)) { + has_connection_event_listener_ = HasConnectionEventListeners(window); + if (!has_connection_event_listener_) + DidRemoveGamepadEventListeners(); } } @@ -349,82 +297,83 @@ void NavigatorGamepad::DidRemoveAllEventListeners(LocalDOMWindow*) { void NavigatorGamepad::DidRemoveGamepadEventListeners() { has_event_listener_ = false; - if (dispatch_one_event_runner_) - dispatch_one_event_runner_->Stop(); - pending_events_.clear(); StopUpdating(); } -void NavigatorGamepad::SampleAndCheckConnectedGamepads() { +void NavigatorGamepad::SampleAndCompareGamepadState() { + // Avoid re-entry. Do not fetch a new sample until we are finished dispatching + // events from the previous sample. + if (processing_events_) + return; + ExecutionContext* execution_context = DomWindow() ? DomWindow()->GetExecutionContext() : nullptr; + base::AutoReset<bool>(&processing_events_, true); if (StartUpdatingIfAttached()) { - if (!gamepads_) - gamepads_ = GamepadList::Create(); - if (GetPage()->IsPageVisible() && has_event_listener_) { + if (GetPage()->IsPageVisible()) { + // Allocate a buffer to hold the new gamepad state, if needed. if (!gamepads_back_) gamepads_back_ = GamepadList::Create(); - - // Compare the current sample with the old data and enqueue connection - // events for any differences. - SampleGamepads<Gamepad>(gamepads_back_.Get(), execution_context, - navigation_start_); - if (CheckConnectedGamepads(gamepads_.Get(), gamepads_back_.Get())) { - // If we had any disconnected gamepads, we can't overwrite gamepads_ - // because the Gamepad object from the old buffer is reused as the - // disconnection event and will be overwritten with new data. Instead, - // recreate the buffer. - gamepads_ = GamepadList::Create(); + SampleGamepads(gamepads_back_.Get(), execution_context, navigation_start_, + gamepads_start_); + + // Compare the new sample with the previous sample and record which + // gamepad events should be dispatched. Swap buffers if the gamepad + // state changed. We must swap buffers before dispatching events to + // ensure |gamepads_| holds the correct data when getGamepads is called + // from inside a gamepad event listener. + auto compare_result = GamepadComparisons::Compare( + gamepads_.Get(), gamepads_back_.Get(), false, false); + if (compare_result.IsDifferent()) { + gamepads_.Swap(gamepads_back_); + bool is_gamepads_back_exposed = is_gamepads_exposed_; + is_gamepads_exposed_ = false; + + // Dispatch gamepad events. Dispatching an event calls the event + // listeners synchronously. + // + // Note: In some instances the gamepad connection state may change while + // inside an event listener. This is most common when using test APIs + // that allow the gamepad state to be changed from javascript. The set + // of event listeners may also change if listeners are added or removed + // by another listener. + for (uint32_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) { + // When a gamepad is disconnected and connected in the same update, + // dispatch the gamepaddisconnected event first. + if (has_connection_event_listener_ && + compare_result.IsGamepadDisconnected(i)) { + Gamepad* pad = gamepads_back_->item(i); + DCHECK(pad); + pad->SetConnected(false); + is_gamepads_back_exposed = true; + DispatchGamepadEvent(event_type_names::kGamepaddisconnected, pad); + } + if (has_connection_event_listener_ && + compare_result.IsGamepadConnected(i)) { + Gamepad* pad = gamepads_->item(i); + DCHECK(pad); + is_gamepads_exposed_ = true; + DispatchGamepadEvent(event_type_names::kGamepadconnected, pad); + } + } + + // Clear |gamepads_back_| if it was ever exposed to the page so it can + // be garbage collected when no active references remain. If it was + // never exposed, retain the buffer so it can be reused. + if (is_gamepads_back_exposed) + gamepads_back_.Clear(); } - if (!pending_events_.IsEmpty()) { - DCHECK(dispatch_one_event_runner_); - dispatch_one_event_runner_->RunAsync(); - } - } - SampleGamepads<Gamepad>(gamepads_.Get(), execution_context, - navigation_start_); - } -} - -bool NavigatorGamepad::CheckConnectedGamepads(GamepadList* old_gamepads, - GamepadList* new_gamepads) { - int disconnection_count = 0; - for (unsigned i = 0; i < device::Gamepads::kItemsLengthCap; ++i) { - Gamepad* old_gamepad = old_gamepads ? old_gamepads->item(i) : nullptr; - Gamepad* new_gamepad = new_gamepads->item(i); - bool connected, disconnected; - CheckConnectedGamepad(old_gamepad, new_gamepad, &connected, &disconnected); - - if (disconnected) { - old_gamepad->SetConnected(false); - pending_events_.push_back(old_gamepad); - disconnection_count++; - } - if (connected) { - pending_events_.push_back(new_gamepad); } } - return disconnection_count > 0; } -void NavigatorGamepad::CheckConnectedGamepad(Gamepad* old_gamepad, - Gamepad* new_gamepad, - bool* gamepad_found, - bool* gamepad_lost) { - bool old_connected = old_gamepad && old_gamepad->connected(); - bool new_connected = new_gamepad && new_gamepad->connected(); - if (old_gamepad && new_gamepad) { - HasGamepadConnectionChanged(old_gamepad->id(), new_gamepad->id(), - old_connected, new_connected, gamepad_found, - gamepad_lost); - return; - } - - if (gamepad_found) - *gamepad_found = new_connected; - if (gamepad_lost) - *gamepad_lost = old_connected; +void NavigatorGamepad::DispatchGamepadEvent(const AtomicString& event_name, + Gamepad* gamepad) { + DCHECK(has_connection_event_listener_); + DCHECK(gamepad); + DomWindow()->DispatchEvent(*GamepadEvent::Create( + event_name, Event::Bubbles::kNo, Event::Cancelable::kYes, gamepad)); } void NavigatorGamepad::PageVisibilityChanged() { @@ -437,7 +386,7 @@ void NavigatorGamepad::PageVisibilityChanged() { } if (visible && has_event_listener_) - SampleAndCheckConnectedGamepads(); + SampleAndCompareGamepadState(); } } // namespace blink |