summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
diff options
context:
space:
mode:
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.cc299
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