| <!DOCTYPE html> |
| <body> |
| <script src="../resources/testharness.js"></script> |
| <script src="../resources/testharnessreport.js"></script> |
| <script src="resources/gamepad-helpers.js"></script> |
| <script> |
| |
| function changeOneGamepadId(gamepadIdString) { |
| // Change the gamepad ID string to a new value. |
| gamepadController.setId(0, gamepadIdString); |
| gamepadController.dispatchConnected(0); |
| } |
| |
| function changeTwoGamepadIds(gamepadIdString) { |
| // Change the gamepad ID strings for two gamepads. Dispatch after modifying |
| // both IDs to simulate both changing as a result of the same gamepad poll |
| // event. |
| gamepadController.setId(0, gamepadIdString); |
| gamepadController.setId(1, gamepadIdString); |
| gamepadController.dispatchConnected(0); |
| gamepadController.dispatchConnected(1); |
| } |
| |
| promise_test(async (t) => { |
| disconnectGamepads(); |
| testGamepadStateAllDisconnected(); |
| |
| let connectListener1Called = false; |
| let connectListener2Called = false; |
| let disconnectListenerCalled = false; |
| |
| // Typically it is not possible for the gamepad connection state to change |
| // while an event listener is being run, but the test API allows us to |
| // disconnect a gamepad in javascript. Make sure that the gamepad state is |
| // not changed while listeners are still running, and that disconnect |
| // listeners are still called if the disconnect occurs within an event |
| // listener. |
| let connectListener1 = (e) => { |
| assert_equals( |
| e.gamepad.connected, true, |
| "expected gamepad connected in first connect listener"); |
| disconnectGamepads(); |
| assert_equals( |
| e.gamepad.connected, true, |
| "expected gamepad still connected in first listener"); |
| connectListener1Called = true; |
| }; |
| |
| let connectListener2 = (e) => { |
| assert_equals( |
| e.gamepad.connected, true, |
| "expected gamepad connected in second connect listener"); |
| disconnectGamepads(); |
| assert_equals( |
| e.gamepad.connected, true, |
| "expected gamepad still connected in second listener"); |
| connectListener2Called = true; |
| }; |
| |
| let disconnectListener = (e) => { |
| assert_equals( |
| e.gamepad.connected, false, |
| "expected gamepad disconnected in disconnect listener"); |
| // Ensure all gamepadconnected listeners have completed. |
| assert_equals( |
| connectListener1Called, true, |
| "expected first connect listener called before disconnect"); |
| assert_equals( |
| connectListener2Called, true, |
| "expected second connect listener called before disconnect"); |
| disconnectListenerCalled = true; |
| }; |
| |
| window.addEventListener('gamepadconnected', connectListener1); |
| window.addEventListener('gamepadconnected', connectListener2); |
| window.addEventListener('gamepaddisconnected', disconnectListener); |
| |
| // Simulate a connection, and expect a disconnection to occur during the |
| // gamepadconnected event listeners. |
| let connectPromise = ongamepadconnected(); |
| let disconnectPromise = ongamepaddisconnected(); |
| connectGamepads(1); |
| await Promise.all([connectPromise, disconnectPromise]); |
| |
| assert_equals(connectListener1Called, true, |
| "expected first gamepadconnected listener to be called"); |
| assert_equals(connectListener2Called, true, |
| "expected second gamepadconnected listener to be called"); |
| assert_equals(disconnectListenerCalled, true, |
| "expected gamepaddisconnected listener to be called"); |
| |
| testGamepadStateAllDisconnected(); |
| |
| // Clean up. |
| window.removeEventListener('gamepadconnected', connectListener1); |
| window.removeEventListener('gamepadconnected', connectListener2); |
| window.removeEventListener('gamepaddisconnected', disconnectListener); |
| }, "Gamepad disconnection inside event listener."); |
| |
| promise_test(async (t) => { |
| disconnectGamepads(); |
| testGamepadStateAllDisconnected(); |
| |
| let connectListener1Called = false; |
| let connectListener2Called = false; |
| |
| let disconnectListener1Called = false; |
| let disconnectListener2Called = false; |
| |
| // Configure two event listeners such that the first listener to be executed |
| // will remove the other listener. |
| let connectListener1 = (e) => { |
| window.removeEventListener('gamepadconnected', connectListener2); |
| connectListener1Called = true; |
| }; |
| |
| let connectListener2 = (e) => { |
| window.removeEventListener('gamepadconnected', connectListener1); |
| connectListener2Called = true; |
| }; |
| |
| let disconnectListener1 = (e) => { |
| window.removeEventListener('gamepaddisconnected', disconnectListener2); |
| disconnectListener1Called = true; |
| }; |
| |
| let disconnectListener2 = (e) => { |
| window.removeEventListener('gamepaddisconnected', disconnectListener1); |
| disconnectListener2Called = true; |
| }; |
| |
| window.addEventListener('gamepadconnected', connectListener1); |
| window.addEventListener('gamepadconnected', connectListener2); |
| window.addEventListener('gamepaddisconnected', disconnectListener1); |
| window.addEventListener('gamepaddisconnected', disconnectListener2); |
| |
| // Simulate a connection. |
| let connectPromise = ongamepadconnected(); |
| connectGamepads(1); |
| await connectPromise; |
| |
| let numConnectListenersCalled = Number(connectListener1Called) + |
| Number(connectListener2Called); |
| assert_equals(numConnectListenersCalled, 1, |
| "expected exactly one connect listener to be called"); |
| |
| // Simulate a disconnection. |
| let disconnectPromise = ongamepaddisconnected(); |
| disconnectGamepads(); |
| await disconnectPromise; |
| |
| let numDisconnectListenersCalled = Number(disconnectListener1Called) + |
| Number(disconnectListener2Called); |
| assert_equals(numDisconnectListenersCalled, 1, |
| "expected exactly one disconnect listener to be called"); |
| |
| testGamepadStateAllDisconnected(); |
| |
| // Clean up. |
| window.removeEventListener('gamepadconnected', connectListener1); |
| window.removeEventListener('gamepadconnected', connectListener2); |
| window.removeEventListener('gamepaddisconnected', disconnectListener1); |
| window.removeEventListener('gamepaddisconnected', disconnectListener2); |
| }, "Remove event listener from inside event listener."); |
| |
| promise_test(async (t) => { |
| disconnectGamepads(); |
| testGamepadStateAllDisconnected(); |
| |
| // In normal circumstances, a gamepad is only connected or disconnected when |
| // a signal is received from the host API. However, if the page is inactive |
| // when this signal is received, the renderer checks for connection changes |
| // and fires the appropriate events once the page is active. |
| // |
| // If both a disconnection and a connection occur while the page is |
| // inactive, the renderer compares the previous device info with the current |
| // device info to determine whether it is the same device. If it differs, |
| // gamepaddisconnected and gamepadconnected are fired. |
| // |
| // This test simulates this case by changing the gamepad ID string. |
| |
| // Connect a gamepad. |
| let connectPromise1 = ongamepadconnected(); |
| connectGamepads(1); |
| await connectPromise1; |
| |
| // Register connect and disconnect listeners, and check that they are called |
| // in the correct order (disconnect first). |
| let connectListenerCalled = false; |
| let disconnectListenerCalled = false; |
| |
| let disconnectListener = (e) => { |
| assert_equals(connectListenerCalled, false, |
| "disconnect listener should be called first"); |
| assert_equals(disconnectListenerCalled, false, |
| "expected disconnect listener to be called only once"); |
| disconnectListenerCalled = true; |
| }; |
| |
| let connectListener = (e) => { |
| assert_equals(disconnectListenerCalled, true, |
| "connect listener should be called second"); |
| assert_equals(connectListenerCalled, false, |
| "expected connect listener to be called only once"); |
| connectListenerCalled = true; |
| }; |
| |
| window.addEventListener('gamepadconnected', connectListener, {once: true}); |
| window.addEventListener('gamepaddisconnected', disconnectListener, |
| {once: true}); |
| |
| // Simulate a change to the gamepad ID. This should be detected as a |
| // different gamepad and fire connection change events. |
| let disconnectPromise1 = ongamepaddisconnected(); |
| let connectPromise2 = ongamepadconnected(); |
| changeOneGamepadId("MockStick 3001"); |
| await Promise.all([disconnectPromise1, connectPromise2]); |
| |
| assert_equals(disconnectListenerCalled, true, |
| "expected disconnect listener to be called"); |
| assert_equals(connectListenerCalled, true, |
| "expected connect listener to be called"); |
| |
| // Simulate a disconnection. |
| let disconnectPromise2 = ongamepaddisconnected(); |
| disconnectGamepads(); |
| await disconnectPromise2; |
| |
| testGamepadStateAllDisconnected(); |
| }, "Check disconnect listener called first during gamepad ID change."); |
| |
| promise_test(async (t) => { |
| disconnectGamepads(); |
| testGamepadStateAllDisconnected(); |
| |
| // Connect a gamepad. |
| let connectPromise = ongamepadconnected(); |
| connectGamepads(1); |
| await connectPromise; |
| |
| // Change the gamepad ID. When the ID is changed there are no |
| // gamepadconnected listeners, but one is added in the gamepaddisconnected |
| // listener. Make sure the newly-added gamepadconnected listener is called. |
| let disconnectThenConnectPromise = new Promise(resolve => { |
| window.addEventListener('gamepaddisconnected', (e) => { |
| window.addEventListener('gamepadconnected', (e) => { |
| resolve(); |
| }, {once: true}); |
| }, {once: true}); |
| }); |
| changeOneGamepadId("MockStick 3001"); |
| await disconnectThenConnectPromise; |
| |
| // Disconnect the gamepad. |
| let disconnectPromise = ongamepaddisconnected(); |
| disconnectGamepads(); |
| await disconnectPromise; |
| |
| testGamepadStateAllDisconnected(); |
| }, "Add connection event listener while handling a gamepad ID change."); |
| |
| promise_test(async (t) => { |
| disconnectGamepads(); |
| testGamepadStateAllDisconnected(); |
| |
| let eventWatcher = new EventWatcher(t, window, ['gamepadconnected', |
| 'gamepaddisconnected']); |
| let eventPromise = eventWatcher.wait_for([ |
| // Connect gamepads. |
| 'gamepadconnected', |
| 'gamepadconnected', |
| // Change one gamepad ID. |
| 'gamepaddisconnected', |
| 'gamepadconnected', |
| // Change the other gamepad ID. |
| 'gamepaddisconnected', |
| 'gamepadconnected', |
| // Disconnect gamepads. |
| 'gamepaddisconnected', |
| 'gamepaddisconnected' |
| ]); |
| |
| // Connect two gamepads. |
| let connectPromise1 = onGamepadEventWithIndex('gamepadconnected', 0); |
| let connectPromise2 = onGamepadEventWithIndex('gamepadconnected', 1); |
| connectGamepads(2); |
| await Promise.all([connectPromise1, connectPromise2]); |
| |
| // Simulate a gamepad ID change for both gamepads. |
| let disconnectPromise1 = onGamepadEventWithIndex('gamepaddisconnected', 0); |
| let disconnectPromise2 = onGamepadEventWithIndex('gamepaddisconnected', 1); |
| let connectPromise3 = onGamepadEventWithIndex('gamepadconnected', 0); |
| let connectPromise4 = onGamepadEventWithIndex('gamepadconnected', 1); |
| changeTwoGamepadIds("MockStick 3001"); |
| await Promise.all([connectPromise3, connectPromise4, |
| disconnectPromise1, disconnectPromise2]); |
| |
| // Disconnect both gamepads. |
| let disconnectPromise3 = onGamepadEventWithIndex('gamepaddisconnected', 0); |
| let disconnectPromise4 = onGamepadEventWithIndex('gamepaddisconnected', 1); |
| disconnectGamepads(); |
| await Promise.all([disconnectPromise3, disconnectPromise4]); |
| |
| // Verify that gamepadconnected and gamepaddisconnected events were received |
| // in the expected order. |
| await eventPromise; |
| testGamepadStateAllDisconnected(); |
| }, "Check connection event order during two gamepad ID changes."); |
| |
| promise_test(async (t) => { |
| disconnectGamepads(); |
| testGamepadStateAllDisconnected(); |
| |
| // Connect two gamepads. |
| let connectPromise1 = onGamepadEventWithIndex('gamepadconnected', 0); |
| let connectPromise2 = onGamepadEventWithIndex('gamepadconnected', 1); |
| connectGamepads(2); |
| await Promise.all([connectPromise1, connectPromise2]); |
| |
| // Change the gamepad IDs. When the ID is changed there are gamepadconnected |
| // and gamepaddisconnected listeners, but both are removed by the |
| // gamepaddisconnected listener. This leaves zero listeners for the |
| // remainder of events. Make sure the removed listeners are not called. |
| let listenersRemoved = false; |
| let disconnectListener = (e) => { |
| assert_equals(listenersRemoved, false, |
| "disconnect listener called unexpectedly"); |
| window.removeEventListener('gamepaddisconnected', disconnectListener); |
| window.removeEventListener('gamepadconnected', connectListener); |
| listenersRemoved = true; |
| } |
| let connectListener = (e) => { |
| assert_equals(listenersRemoved, false, |
| "connect listener called unexpectedly"); |
| }; |
| window.addEventListener('gamepadconnected', connectListener); |
| window.addEventListener('gamepaddisconnected', disconnectListener); |
| |
| // Simulate changing both gamepad IDs. Set a zero-length timeout instead of |
| // waiting for connection events to avoid registering more listeners. |
| changeTwoGamepadIds("MockStick 3001"); |
| await new Promise(resolve => setInterval(resolve, 0)); |
| |
| // Disconnect both gamepads. |
| let disconnectPromise1 = onGamepadEventWithIndex('gamepaddisconnected', 0); |
| let disconnectPromise2 = onGamepadEventWithIndex('gamepaddisconnected', 1); |
| disconnectGamepads(); |
| await Promise.all([disconnectPromise1, disconnectPromise2]); |
| |
| testGamepadStateAllDisconnected(); |
| }, "Remove all connection event listeners while handling a gamepad ID change."); |
| |
| </script> |
| </body> |