- From: Matt Reynolds <notifications@github.com>
- Date: Mon, 26 Oct 2026 08:29:02 -0700
- To: w3c/gamepad <gamepad@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <w3c/gamepad/pull/234/review/5650383766@github.com>
@nondebug requested changes on this pull request.
> <li>Let |now:DOMHighResTimeStamp| be the [=current high resolution
time=] given |gamepad|'s [=relevant global object=].
</li>
+ <li>Let |oldGamepad:Gamepad| be a {{Gamepad}} object.
+ </li>
+ <li>Copy the state from |gamepad| into |oldGamepad|.
"copy the state" is ambiguous and maybe violates `FrozenArray` nature of the input state arrays depending on how it's implemented. Let's update these steps to more clearly describe how the state should be copied.
```
<li>Let |oldGamepad:Gamepad| be a newly created {{Gamepad}} instance:
<ol>
<li>Set |oldGamepad|.{{Gamepad/[[axes]]}} to a [=list/clone=] of
|gamepad|.{{Gamepad/[[axes]]}}.
</li>
<li>Set |oldGamepad|.{{Gamepad/[[buttons]]}} to an empty
[=list=].
</li>
<li>[=list/For each=] |button:GamepadButton| of
|gamepad|.{{Gamepad/[[buttons]]}}:
<ol>
<li>Let |oldButton:GamepadButton| be a [=new=]
{{GamepadButton}}.
</li>
<li>Set |oldButton|.{{GamepadButton/[[value]]}} to
|button|.{{GamepadButton/[[value]]}}.
</li>
<li>Set |oldButton|.{{GamepadButton/[[pressed]]}} to
|button|.{{GamepadButton/[[pressed]]}}.
</li>
<li>Set |oldButton|.{{GamepadButton/[[touched]]}} to
|button|.{{GamepadButton/[[touched]]}}.
</li>
<li>[=list/Append=] |oldButton| to
|oldGamepad|.{{Gamepad/[[buttons]]}}.
</li>
</ol>
</li>
<li>Set |oldGamepad|.{{Gamepad/[[touches]]}} to a [=list/clone=]
of |gamepad|.{{Gamepad/[[touches]]}}.
</li>
</ol>
</li>
```
>
The steps for "record touches" include an assertion that `[[touches]]` will be empty before recording, but for input state comparisons we expect that `[[touches]]` will retain the touch data from the previous update until the next update. Let's change the Assert here to instead `[=list/Empty=]` the array.
There's another minor bug here: `{{Gamepad}}.{{Gamepad/[[touches]]}}` should be `|gamepad|.{{Gamepad/[[touches]]}}` to update the state for the specific `gamepad` instance.
>
The agent code reviewer flagged a bug here, `|touch|` is accessed before it is declared. We should move this step into the `<ol>` below after the declaration. Let's fix this in a followup PR to avoid bloating this PR with unrelated spec changes.
> </li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
+ <li>If |navigator|.{{Navigator/[[hasGamepadGesture]]}} is `true`:
I think we want to suppress the `gamepadrawinputchanged` event if we fired `gamepadconnected` for the same input change (matching behavior of the current Chromium implementation). Let's make this an "Otherwise, if..." so it will be skipped `hasGamepadGesture` was set in the preceding block.
> </li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
+ <li>If |navigator|.{{Navigator/[[hasGamepadGesture]]}} is `true`:
+ <ol>
+ <li>Let |hasInputChanged:boolean| be the result of running the
+ steps to [=detect gamepad input changes=] between |oldGamepad|
+ and |gamepad|.
+ </li>
+ <li>If |hasInputChanged| is `true`:
The steps to "update gamepad state" should skip firing a `gamepadrawinputchanged` event if `gamepad` is no longer connected (`|gamepad|.{{Gamepad/[[connected]]}}` is false) or if it was never exposed to the page (`|gamepad|.{{Gamepad/[[exposed]]}}` is false).
>
Agent review flagged a spec bug: As written, if `[[hasGamepadGesture]]` is already true when the gamepad becomes available, the `gamepadconnected` event here will be dispatched with default (zero) values for each button and axis and empty touches. In the Chromium implementation, we don't actually dispatch the event until the first inputs are received from the device, so the default values are never used. We should refactor the spec to remove "when a gamepad becomes available" and instead move this handling into the "receives new button or axis input values" step (and update the name to include touches). Let's file a new issue and address this in a separate PR.
> @@ -787,9 +787,25 @@ <h3>
following steps:
</p>
<ol>
+ <li>Let |navigator:Navigator| be |gamepad|'s [=relevant global
+ object=]'s {{Navigator}} object.
+ </li>
+ <li>Let |document:Document?| be |gamepad|'s [=relevant global
+ object=]'s [=associated `Document`=]; otherwise `null`.
+ </li>
+ <li>If |document| is `null` or not [=Document/fully active=], abort
I think "update gamepad state" should also check page visibility, which matches Chromium's implemented behavior.
We should also early-exit here if `[[hasGamepadGesture]]` is false and the tab is not focused.
```
<li>If |document| is `null`, or |document| is not [=Document/fully
active=], or |document|'s [=Document/visibility state=] is
`"hidden"`, abort these steps.
</li>
<li>If |navigator|.{{Navigator/[[hasGamepadGesture]]}} is `false` and
|document| is not a [=Document/fully active descendant of a top-level
traversable with user attention=], abort these steps.
</li>
```
> @@ -1006,6 +1028,108 @@ <h3>
</ol>
</li>
</ol>
+ <p>
+ To <dfn>detect gamepad input changes</dfn> between |gamepad1:Gamepad|
+ and |gamepad2:Gamepad|, run the following steps:
+ </p>
+ <ol class="algorithm">
+ <li>Assert: |gamepad1| and |gamepad2| are not `null`.
+ </li>
+ <li>If |gamepad1|.{{Gamepad/[[axes]]}}'s [=list/size=] is not equal
+ to |gamepad2|.{{Gamepad/[[axes]]}}'s [=list/size=], return `true`.
+ </li>
+ <li>[=list/For each=] index |axisIndex| of
nit: use `[=list/indices=]` to iterate over indices.
`[=list/For each=] |axisIndex:long| of |gamepad1|.{{Gamepad/[[axes]]}}'s [=list/indices=]:`
> + <li>If |gamepad1|.{{Gamepad/[[axes]]}}'s [=list/size=] is not equal
+ to |gamepad2|.{{Gamepad/[[axes]]}}'s [=list/size=], return `true`.
+ </li>
+ <li>[=list/For each=] index |axisIndex| of
+ |gamepad1|.{{Gamepad/[[axes]]}}:
+ <ol>
+ <li>If |gamepad1|.{{Gamepad/[[axes]]}}[|axisIndex|] is not equal
+ to |gamepad2|.{{Gamepad/[[axes]]}}[|axisIndex|], return `true`.
+ </li>
+ </ol>
+ </li>
+ <li>If |gamepad1|.{{Gamepad/[[buttons]]}}'s [=list/size=] is not
+ equal to |gamepad2|.{{Gamepad/[[buttons]]}}'s [=list/size=], return
+ `true`.
+ </li>
+ <li>[=list/For each=] index |buttonIndex| of
nit: `[=list/For each=] |buttonIndex:long| of |gamepad1|.{{Gamepad/[[buttons]]}}'s [=list/indices=]:`
> + </li>
+ <li>|button1|.{{GamepadButton/[[pressed]]}} is not equal to
+ |button2|.{{GamepadButton/[[pressed]]}}
+ </li>
+ <li>|button1|.{{GamepadButton/[[touched]]}} is not equal to
+ |button2|.{{GamepadButton/[[touched]]}}
+ </li>
+ </ul>
+ </li>
+ </ol>
+ </li>
+ <li>If |gamepad1|.{{Gamepad/[[touches]]}}'s [=list/size=] is not
+ equal to |gamepad2|.{{Gamepad/[[touches]]}}'s [=list/size=], return
+ `true`.
+ </li>
+ <li>[=list/For each=] index |touchIndex| of
nit: `[=list/For each=] |touchIndex:long| of |gamepad1|.{{Gamepad/[[touches]]}}'s [=list/indices=]:`
> @@ -870,7 +892,7 @@ <h3>
<li>Let |normalizedValue:double| be 2 (|logicalValue| −
|logicalMinimum|) / (|logicalMaximum| − |logicalMinimum|) − 1.
Agent review flagged a spec bug: our steps to normalize button and axis values can potentially divide by zero, introducing NaN values which will satisfy the gamepad comparison checks and always register as an input change (since NaN != NaN). Let's file an issue to decide how to resolve this. (Probably we can just ignore any input with bounds where max <= min; we don't expect to hit this case with real gamepads.)
>
Agent code review flagged a spec bug: the description for the `timestamp` attribute doesn't consider updates to `{{Gamepad/touches}}`, let's add it in a followup PR.
>
Please update the "other events" section to reflect the changes in this PR. Maybe we can just delete this section.
>
Filed https://github.com/w3c/gamepad/issues/235
>
Filed https://github.com/w3c/gamepad/issues/237
>
Filed https://github.com/w3c/gamepad/issues/236
--
Reply to this email directly or view it on GitHub:
https://github.com/w3c/gamepad/pull/234#pullrequestreview-5650383766
You are receiving this because you are subscribed to this thread.
Message ID: <w3c/gamepad/pull/234/review/5650383766@github.com>
Received on Monday, 26 October 2026 15:29:06 UTC