From e93ef99f1f991497bdf370193b96306f80167f3d Mon Sep 17 00:00:00 2001
From: Matt Reynolds
[Exposed=Window, SecureContext]
interface Gamepad {
+ attribute EventHandler onbuttondown;
+ attribute EventHandler onbuttonup;
+ attribute EventHandler onbuttonchange;
+ attribute EventHandler onaxischange;
+
readonly attribute DOMString id;
readonly attribute long index;
readonly attribute boolean connected;
@@ -304,6 +309,34 @@
+
following steps:
+ If |button|.{{GamepadButton/pressed}} is `true`, [=queue + a task=] on the [=gamepad task source=] to [=fire an + event=] named {{buttondown}} at |gamepad| using + {{GamepadButtonEvent}} with its + {{GamepadButtonEvent/gamepadIndex}} attribute initialized + to |gamepad|.{{Gamepad/index}}, its + {{GamepadButtonEvent/buttonIndex}} attribute initialized + to |buttonIndex|, its + {{GamepadButtonEvent/buttonSnapshot}} attribute + initialized to |buttonCopy|, and its + {{GamepadButtonEvent/gamepadTimestamp}} attribute + initialized to |now|. +
++ Otherwise, [=queue a task=] on the [=gamepad task + source=] to [=fire an event=] named {{buttonup}} at + |gamepad| using {{GamepadButtonEvent}} with its + {{GamepadButtonEvent/gamepadIndex}} attribute initialized + to |gamepad|.{{Gamepad/index}}, its + {{GamepadButtonEvent/buttonIndex}} attribute initialized + to |buttonIndex|, its + {{GamepadButtonEvent/buttonSnapshot}} attribute + initialized to |buttonCopy|, and its + {{GamepadButtonEvent/gamepadTimestamp}} attribute + initialized to |now|. +
+To map and normalize axes for |gamepad:Gamepad|, run the @@ -1207,6 +1354,173 @@
+ [Exposed=Window, SecureContext] + + interface GamepadAxisEvent: Event { + constructor(DOMString type, GamepadAxisEventInit eventInitDict); + readonly attribute long gamepadIndex; + readonly attribute long axisIndex; + readonly attribute double axisSnapshot; + readonly attribute DOMHighResTimeStamp gamepadTimestamp; + }; ++
+ dictionary GamepadAxisEventInit : EventInit { + required long gamepadIndex; + required long axisIndex; + required double axisSnapshot; + required DOMHighResTimeStamp gamepadTimestamp; + }; ++
+ [Exposed=Window, SecureContext] + + interface GamepadButtonEvent: Event { + constructor(DOMString type, GamepadButtonEventInit eventInitDict); + readonly attribute long gamepadIndex; + readonly attribute long buttonIndex; + readonly attribute GamepadButton buttonSnapshot; + readonly attribute DOMHighResTimeStamp gamepadTimestamp; + }; ++
+ dictionary GamepadButtonEventInit : EventInit { + required long gamepadIndex; + required long buttonIndex; + required GamepadButton buttonSnapshot; + required DOMHighResTimeStamp gamepadTimestamp; + }; ++
- More discussion needed, on whether to include or exclude axis and - button changed events, and whether to roll them more together - (`"gamepadchanged"`?), separate somewhat (`"gamepadaxischanged"`?), or - separate by individual axis and button. + [=User agent=]s implementing this specification MUST provide new DOM + events named axischange, buttonchange, buttondown, and + buttonup. The corresponding event MUST be of + type {{GamepadAxisEvent}} for {{axischange}}, or {{GamepadButtonEvent}} + for {{buttonchange}}, {{buttondown}}, and {{buttonup}}. All events MUST + fire on the {{Gamepad}} object. +
++ When the [=user agent=] receives new button or axis input values from a + gamepad, the [=user agent=] MUST compare the current button and axis + values with the previous values and dispatch {{axischange}} and + {{buttonchange}} events for each axis and button with a changed value. + The [=user agent=] MUST also compare the current button pressed state + with the previous button pressed state and dispatch {{buttondown}} and + {{buttonup}} events. +
++ These events MUST NOT be dispatched before the [=user agent=] has + dispatched a {{gamepadconnected}} event for that gamepad.
If |button|.{{GamepadButton/pressed}} is `true`, [=queue @@ -641,7 +626,8 @@
+ To create a button + snapshot for |button:GamepadButton|, run the following steps: +
+[Exposed=Window, SecureContext] - - interface GamepadEvent: Event { + interface GamepadEvent : Event { constructor(DOMString type, GamepadEventInit eventInitDict); [SameObject] readonly attribute Gamepad gamepad; }; @@ -1360,8 +1367,7 @@
[Exposed=Window, SecureContext] - - interface GamepadAxisEvent: Event { + interface GamepadAxisEvent : Event { constructor(DOMString type, GamepadAxisEventInit eventInitDict); readonly attribute long gamepadIndex; readonly attribute long axisIndex; @@ -1443,8 +1449,7 @@
[Exposed=Window, SecureContext] - - interface GamepadButtonEvent: Event { + interface GamepadButtonEvent : Event { constructor(DOMString type, GamepadButtonEventInit eventInitDict); readonly attribute long gamepadIndex; readonly attribute long buttonIndex; From a5def3d5fef25f281ce25de2dab64b57fe0b3502 Mon Sep 17 00:00:00 2001 From: Matt ReynoldsDate: Thu, 5 Aug 2021 15:44:44 -0700 Subject: [PATCH 3/4] Make Gamepad an EventTarget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marcos Cáceres --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 72289bf..ee1f8f3 100644 --- a/index.html +++ b/index.html @@ -146,7 +146,7 @@
[Exposed=Window, SecureContext] - interface Gamepad { + interface Gamepad : EventTarget { attribute EventHandler onbuttondown; attribute EventHandler onbuttonup; attribute EventHandler onbuttonchange; From 059111d7d3aa1f5dd3baa6a4c9ed1b0c43ee3c32 Mon Sep 17 00:00:00 2001 From: Matt ReynoldsDate: Tue, 27 Jul 2021 13:52:54 -0700 Subject: [PATCH 4/4] gamepadchange and rawgamepadchange --- index.html | 505 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 379 insertions(+), 126 deletions(-) diff --git a/index.html b/index.html index ee1f8f3..4137a01 100644 --- a/index.html +++ b/index.html @@ -149,8 +149,8 @@ interface Gamepad : EventTarget { attribute EventHandler onbuttondown; attribute EventHandler onbuttonup; - attribute EventHandler onbuttonchange; - attribute EventHandler onaxischange; + attribute EventHandler ongamepadchange; + attribute EventHandler onrawgamepadchange; readonly attribute DOMString id; readonly attribute long index; @@ -307,6 +307,17 @@
A [=list=] containing the maximum logical value for each button +
+ + [[\pendingChanges]] + ++ An empty [=list=] + ++ A [=list=] containing pending {{GamepadChangeEvent}}s. + +
- @@ -324,18 +335,18 @@
{{buttonup}} event type.
- - onbuttonchange attribute + ongamepadchange attribute
- - {{Gamepad/onbuttonchange}} is an [=event handler IDL attribute=] for - the {{buttonchange}} event type. + {{Gamepad/ongamepadchange}} is an [=event handler IDL attribute=] for + the {{gamepadchange}} event type.
- - onaxischange attribute + onrawgamepadchange attribute
- - {{Gamepad/onaxischange}} is an [=event handler IDL attribute=] for - the {{axischange}} event type. + {{Gamepad/onrawgamepadchange}} is an [=event handler IDL attribute=] + for the {{rawgamepadchange}} event type.
- id attribute @@ -505,16 +516,15 @@
+
- Initialize |oldAxisValues:list| to be an empty [=list=].
-- [=list/For each=] |axis:double| of - |gamepad|.{{Gamepad/[[axes]]}}, [=list/append=] |axis| to - |oldAxisValues|. +
- [=list/For each=] |axis:double| of |gamepad|.{{Gamepad/axes}}, + [=list/append=] |axis| to |oldAxisValues|.
- Initialize |oldButtonValues:list| to be an empty [=list=].
- Initialize |oldButtonPressed:list| to be an empty [=list=].
- [=list/For each=] |button:GamepadButton| of - |gamepad|.{{Gamepad/[[buttons]]}}: + |gamepad|.{{Gamepad/buttons}}:
@@ -573,84 +583,208 @@
- [=list/Append=] |button|.{{GamepadButton/value}} to |oldButtonValues|. @@ -558,11 +568,11 @@
`null`.
- If |document| is not `null` and is [=Document/fully - active=], then [=queue a task=] on the [=gamepad task - source=] to [=fire an event=] named {{gamepadconnected}} - at |gamepad|'s [=relevant global object=] using - {{GamepadEvent}} with its {{GamepadEvent/gamepad}} - attribute initialized to |connectedGamepad|. + active=], then [=fire an event=] named + {{gamepadconnected}} at |gamepad|'s [=relevant global + object=] using {{GamepadEvent}} with its + {{GamepadEvent/gamepad}} attribute initialized to + |connectedGamepad|.
- If |navigator|.{{Navigator/[[hasGamepadGesture]]}} is `false`, abort these steps.
+- Let |axesChanged| be an empty [=list=]. +
- [=list/For each=] |axisIndex:long| of [=the range=] from 0 to the [=list/size=] of |gamepad|.{{Gamepad/axes}} − 1:
+
- If |oldAxisValues|[|axisIndex|] is not equal to - |gamepad|.{{Gamepad/[[axes]]}}[|axisIndex|], [=queue a task=] on - the [=gamepad task source=] to [=fire an event=] named - {{axischange}} at |gamepad| using {{GamepadAxisEvent}} with its - {{GamepadAxisEvent/gamepadIndex}} attribute initialized to - |gamepad|.{{Gamepad/index}}, its {{GamepadAxisEvent/axisIndex}} - attribute initialized to |axisIndex|, its - {{GamepadAxisEvent/axisSnapshot}} attribute initialized to - |newValue|, and its {{GamepadAxisEvent/gamepadTimestamp}} - attribute initialized to |now|. + |gamepad|.{{Gamepad/axes}}[|axisIndex|], [=list/append=] + |axisIndex| to |axesChanged|.
- Let |buttonsChanged| be an empty [=list=]. +
+- Let |buttonsPressed| be an empty [=list=]. +
+- Let |buttonsReleased| be an empty [=list=]. +
- [=list/For each=] |buttonIndex:long| of [=the range=] from 0 to the [=list/size=] of |gamepad|.{{Gamepad/buttons}} − 1:
++
- Let |button:GamepadButton| be - |gamepad|.{{Gamepad/[[buttons]]}}[|buttonIndex|]. + |gamepad|.{{Gamepad/buttons}}[|buttonIndex|].
- If |oldButtonValues|[|buttonIndex|] is not equal to - |button|.{{GamepadButton/value}}: -
--
+ |button|.{{GamepadButton/value}}, [=list/append=] |buttonIndex| + to |buttonsChanged|.- [=Queue a task=] on the [=gamepad task source=] to [=fire - an event=] named {{buttonchange}} at |gamepad| using - {{GamepadButtonEvent}} with its - {{GamepadButtonEvent/gamepadIndex}} attribute initialized to - |gamepad|.{{Gamepad/index}}, its - {{GamepadButtonEvent/buttonIndex}} attribute initialized to - |buttonIndex|, its {{GamepadButtonEvent/buttonSnapshot}} - attribute initialized to the result of [=creating a button - snapshot=] of |button|, and its - {{GamepadButtonEvent/gamepadTimestamp}} attribute initialized - to |now|. -
-- If |oldButtonPressed|[|buttonIndex|] is not equal to - |button|.{{GamepadButton/pressed}}: -
-
+- -
-- If |button|.{{GamepadButton/pressed}} is `true`, [=queue - a task=] on the [=gamepad task source=] to [=fire an - event=] named {{buttondown}} at |gamepad| using - {{GamepadButtonEvent}} with its - {{GamepadButtonEvent/gamepadIndex}} attribute initialized - to |gamepad|.{{Gamepad/index}}, its - {{GamepadButtonEvent/buttonIndex}} attribute initialized - to |buttonIndex|, its - {{GamepadButtonEvent/buttonSnapshot}} attribute - initialized to the result of [=creating a button - snapshot=] of |button|, and its - {{GamepadButtonEvent/gamepadTimestamp}} attribute - initialized to |now|. -
-- Otherwise, [=queue a task=] on the [=gamepad task - source=] to [=fire an event=] named {{buttonup}} at - |gamepad| using {{GamepadButtonEvent}} with its - {{GamepadButtonEvent/gamepadIndex}} attribute initialized - to |gamepad|.{{Gamepad/index}}, its - {{GamepadButtonEvent/buttonIndex}} attribute initialized - to |buttonIndex|, its - {{GamepadButtonEvent/buttonSnapshot}} attribute - initialized to the result of [=creating a button - snapshot=] of |button|, and its - {{GamepadButtonEvent/gamepadTimestamp}} attribute - initialized to |now|. -
-- If |oldButtonPressed|[|buttonIndex|] is `false` and + |button|.{{GamepadButton/pressed}} is `true`, [=list/append=] + |buttonIndex| to |buttonsPressed|. +
+- If |oldButtonPressed|[|buttonIndex|] is `true` and + |button|.{{GamepadButton/pressed}} is `false`, [=list/append=] + |buttonIndex| to |buttonsReleased|. +
+- If any of |axesChanged| or |buttonsChanged|, |buttonsPressed|, or + |buttonsReleased| [=list/is not empty=]: +
++
+- Let |gamepadSnapshot| be the result of [=creating a + snapshot=] for |gamepad|. +
+- [=Fire an event=] named {{rawgamepadchange}} at |gamepad| + using {{GamepadChangeEvent}} with its + {{GamepadChangeEvent/gamepadSnapshot}} attribute initialized to + |gamepadSnapshot|, its {{GamepadChangeEvent/axesChanged}} + attribute initialized to |axesChanged|, its + {{GamepadChangeEvent/buttonsChanged}} attribute initialized to + |buttonsChanged|, its {{GamepadChangeEvent/buttonsPressed}} + attribute initialized to |buttonsPressed|, and its + {{GamepadChangeEvent/buttonsReleased}} attribute initialized to + |buttonsReleased|. +
+- Let |event:GamepadChangeEvent| be a newly created + {{GamepadChangeEvent}} with its + {{GamepadChangeEvent/gamepadSnapshot}} attribute initialized to + |gamepadSnapshot|, its {{GamepadChangeEvent/axesChanged}} + attribute initialized to |axesChanged|, its + {{GamepadChangeEvent/buttonsChanged}} attribute initialized to + |buttonsChanged|, its {{GamepadChangeEvent/buttonsPressed}} + attribute initialized to |buttonsPressed|, and its + {{GamepadChangeEvent/buttonsReleased}} attribute initialized to + |buttonsReleased|. +
+- Append |event| to |gamepad|.{{Gamepad/[[pendingChanges]]}}. +
+- If |buttonsPressed| or |buttonsReleased| [=list/is not empty=], + [=flush pending change events=] for |gamepad|. +
+- [=list/For each=] |buttonIndex:long| of |buttonsPressed|: +
++
+- [=Fire an event=] named {{buttondown}} at |gamepad| using + {{GamepadButtonEvent}} with its + {{GamepadButtonEvent/gamepadIndex}} attribute initialized to + |gamepad|.{{Gamepad/index}}, its + {{GamepadButtonEvent/buttonIndex}} attribute initialized to + |buttonIndex|, its {{GamepadButtonEvent/buttonSnapshot}} + attribute initialized to the result of [=creating a button + snapshot=] of |gamepad|.{{Gamepad/buttons}}[|buttonIndex|], and + its {{GamepadButtonEvent/gamepadTimestamp}} attribute initialized + to |now|. +
+- [=list/For each=] |buttonIndex:long| of |buttonsReleased|: +
++
+- [=Fire an event=] named {{buttonup}} at |gamepad| using + {{GamepadButtonEvent}} with its + {{GamepadButtonEvent/gamepadIndex}} attribute initialized to + |gamepad|.{{Gamepad/index}}, its + {{GamepadButtonEvent/buttonIndex}} attribute initialized to + |buttonIndex|, its {{GamepadButtonEvent/buttonSnapshot}} + attribute initialized to the result of [=creating a button + snapshot=] of |gamepad|.{{Gamepad/buttons}}[|buttonIndex|], and + its {{GamepadButtonEvent/gamepadTimestamp}} attribute initialized + to |now|. +
++ To flush pending change events for |gamepad:Gamepad|, run + the following steps: +
++
+- If |gamepad|.{{Gamepad/[[pendingChanges]]}} [=list/is empty=], + abort these steps. +
+- Let |axesChanged:ordered set| be an empty [=ordered set=]. +
+- Let |buttonsChanged:ordered set| be an empty [=ordered set=]. +
+- Let |buttonsPressed:ordered set| be an empty [=ordered set=]. +
+- Let |buttonsReleased:ordered set| be an empty [=ordered set=]. +
+- Let |coalescedEvents:list| be an empty [=list=]. +
+- [=list/For each=] |event:GamepadChangeEvent| of + |gamepad|.{{Gamepad/[[pendingChanges]]}}: +
++
+- [=list/For each=] |axisIndex:long| of + |event|.{{GamepadChangeEvent/axesChanged}}, [=list/append=] + |axisIndex| to |axesChanged|. +
+- [=list/For each=] |buttonIndex:long| of + |event|.{{GamepadChangeEvent/buttonsChanged}}, [=list/append=] + |buttonIndex| to |buttonsChanged|. +
+- [=list/For each=] |buttonIndex:long| of + |event|.{{GamepadChangeEvent/buttonsPressed}}, [=list/append=] + |buttonIndex| to |buttonsPressed|. +
+- [=list/For each=] |buttonIndex:long| of + |event|.{{GamepadChangeEvent/buttonsReleased}}, [=list/append=] + |buttonIndex| to |buttonsReleased|. +
+- [=list/Append=] |event| to |coalescedEvents|. +
+- Let |lastChange:GamepadChangeEvent| be the last [=list/item=] of + |gamepad|.{{Gamepad/[[pendingChanges]]}}. +
+- Let |event:GamepadChangeEvent| be a newly created + {{GamepadChangeEvent}} instance: +
++
+- Initialize |event|.{{GamepadChangeEvent/gamepadSnapshot}} to + |lastChange|.{{GamepadChangeEvent/gamepadSnapshot}}. +
+- Initialize |event|.{{GamepadChangeEvent/axesChanged}} to the + result of [=list/sorting in ascending order=] |axesChanged|. +
+- Initialize |event|.{{GamepadChangeEvent/buttonsChanged}} to + the result of [=list/sorting in ascending order=] + |buttonsChanged|. +
+- Initialize |event|.{{GamepadChangeEvent/buttonsPressed}} to + the result of [=list/sorting in ascending order=] + |buttonsPressed|. +
+- Initialize |event|.{{GamepadChangeEvent/buttonsReleased}} to + the result of [=list/sorting in ascending order=] + |buttonsReleased|. +
+- Initialize |event|.{{GamepadChangeEvent/[[coalescedEvents]]}} + to |coalescedEvents|. +
+- [=list/Empty=] |gamepad|.{{Gamepad/[[pendingChanges]]}}. +
+- [=Dispatch=] |event| to |gamepad|. +
++ To run the gamepad steps for a [=document=] + |document:document|, run these steps: +
++
+- Let |navigator:Navigator| be |document|'s [=relevant global + object=]'s {{Navigator}} object. +
+- If |navigator|.{{Navigator/[[hasGamepadGesture]]}} is `false`, + abort these steps. +
+- [=list/For each=] |gamepad:Gamepad?| of + |navigator|.{{Navigator/[[gamepads]]}}: +
+
- If |gamepad| is not `null` and + |gamepad|.{{Gamepad/[[exposed]]}} is `true`, [=flush pending + change events=] for |gamepad|.
+ These steps integrate with the event loop defined in HTML. [HTML] +To map and normalize axes for |gamepad:Gamepad|, run the following steps: @@ -718,7 +852,7 @@
|logicalMinimum|) / (|logicalMaximum| − |logicalMinimum|).
- Let |button:GamepadButton| be - |gamepad|.{{Gamepad/[[buttons]]}}[|mappedIndex|]. + |gamepad|.{{Gamepad/buttons}}[|mappedIndex|].
- Set |button|.{{GamepadButton/[[value]]}} to |normalizedValue|. @@ -771,6 +905,45 @@
- Return |buttonCopy|.
++ To create a snapshot for + |gamepad:Gamepad|, run the following steps: +
++
- Let |gamepadCopy| be a newly created {{Gamepad}} instance: +
++
+- Initialize |gamepadCopy|.{{Gamepad/id}} to + |gamepad|.{{Gamepad/id}}. +
+- Initialize |gamepadCopy|.{{Gamepad/index}} to + |gamepad|.{{Gamepad/index}}. +
+- Initialize |gamepadCopy|.{{Gamepad/mapping}} to + |gamepad|.{{Gamepad/mapping}}. +
+- Initialize |gamepadCopy|.{{Gamepad/[[connected]]}} to + |gamepad|.{{Gamepad/connected}}. +
+- Initialize |gamepadCopy|.{{Gamepad/[[timestamp]]}} to + |gamepad|.{{Gamepad/timestamp}}. +
+- Initialize |gamepadCopy|.{{Gamepad/[[axes]]}} to an empty + [=sequence=]. +
+- [=list/For each=] |axisValue:double| of + |gamepad|.{{Gamepad/axes}}, [=list/append=] |axisValue| to + |gamepadCopy|.{{Gamepad/[[axes]]}}. +
+- [=list/For each=] |button:GamepadButton| of + |gamepad|.{{Gamepad/buttons}}, [=create a button snapshot=] for + |button| and [=list/append=] it to + |gamepadCopy|.{{Gamepad/[[buttons]]}}. +
+- Return |gamepadCopy|. +
+
[Exposed=Window, SecureContext] - interface GamepadAxisEvent : Event { - constructor(DOMString type, GamepadAxisEventInit eventInitDict); - readonly attribute long gamepadIndex; - readonly attribute long axisIndex; - readonly attribute double axisSnapshot; - readonly attribute DOMHighResTimeStamp gamepadTimestamp; + interface GamepadChangeEvent : Event { + constructor(DOMString type, GamepadChangeEventInit eventInitDict); + readonly attribute Gamepad gamepadSnapshot; + readonly attribute FrozenArray<long> axesChanged; + readonly attribute FrozenArray<long> buttonsChanged; + readonly attribute FrozenArray<long> buttonsPressed; + readonly attribute FrozenArray<long> buttonsReleased; + sequence<GamepadChangeEvent> getCoalescedEvents(); };+
+ Instances of {{GamepadChangeEvent}} are created with the internal slots + described in the following table: +
++ Internal slot + | ++ Initial value + | ++ Description (non-normative) + | +
---|---|---|
+ [[\coalescedEvents]] + | ++ An empty [=sequence=] + | ++ A [=sequence=] of {{GamepadChangeEvent}} instances that were + coalesced to create this event. + | +
- dictionary GamepadAxisEventInit : EventInit { - required long gamepadIndex; - required long axisIndex; - required double axisSnapshot; - required DOMHighResTimeStamp gamepadTimestamp; + dictionary GamepadChangeEventInit : EventInit { + required Gamepad gamepadSnapshot; + required sequence<long> axesChanged; + required sequence<long> buttonsChanged; + required sequence<long> buttonsPressed; + required sequence<long> buttonsReleased; }; --
[=User agent=]s implementing this specification MUST provide new DOM - events named axischange, buttonchange, buttondown, and - buttonup. The corresponding event MUST be of - type {{GamepadAxisEvent}} for {{axischange}}, or {{GamepadButtonEvent}} - for {{buttonchange}}, {{buttondown}}, and {{buttonup}}. All events MUST - fire on the {{Gamepad}} object. + events named gamepadchange and rawgamepadchange. These events MUST be of type + {{GamepadChangeEvent}} and MUST fire on the {{Gamepad}} object.
When the [=user agent=] receives new button or axis input values from a gamepad, the [=user agent=] MUST compare the current button and axis - values with the previous values and dispatch {{axischange}} and - {{buttonchange}} events for each axis and button with a changed value. - The [=user agent=] MUST also compare the current button pressed state - with the previous button pressed state and dispatch {{buttondown}} and - {{buttonup}} events. + state with the previous state. If any button or axis has changed, a + {{rawgamepadchange}} event MUST be dispatched. +
++ If any button or axis has changed, a pending {{gamepadchange}} event + MUST be created and enqueued. The [=user agent=] MAY [=flush pending + change events=] at any time after the {{rawgamepadchange}} has been + dispatched, but MUST do so before the animation frame callbacks are run + in the event loop. [[HTML]] +
++ The {{gamepadchange}} and {{rawgamepadchange}} events MUST NOT be + dispatched before the [=user agent=] has dispatched a + {{gamepadconnected}} event for the gamepad. +
++ [=User agent=]s implementing this specification MUST provide new DOM + events named buttondown and buttonup. The corresponding events MUST be of type + {{GamepadButtonEvent}} and MUST fire on the {{Gamepad}} object. +
++ When the [=user agent=] receives new button or axis input values from a + gamepad, the [=user agent=] MUST compare the current button pressed + state with the previous button pressed state and dispatch + {{buttondown}} and {{buttonup}} events.
- These events MUST NOT be dispatched before the [=user agent=] has - dispatched a {{gamepadconnected}} event for that gamepad. + The {{buttondown}} and {{buttonup}} events MUST NOT be dispatched + before the [=user agent=] has dispatched a {{gamepadconnected}} event + for the gamepad. The [=user agent=] MUST [=flush pending change + events=] for a gamepad before firing {{buttondown}} or {{buttonup}} for + a button on that gamepad.