Skip to content

Fix simulated touch state on repeated mouse down#161

Open
shrinktofit wants to merge 1 commit into
cocos:v4.0.0from
shrinktofit:stf/simulate-touch
Open

Fix simulated touch state on repeated mouse down#161
shrinktofit wants to merge 1 commit into
cocos:v4.0.0from
shrinktofit:stf/simulate-touch

Conversation

@shrinktofit

Copy link
Copy Markdown
Contributor

Summary

Fix simulated touch lifecycle handling when mouse down/up events are not paired. If a new mouse down arrives while the simulated touch is still active, the previous simulated touch is now cancelled before emitting the new touch start.

Problem

Mouse events are not guaranteed to arrive as perfectly paired mousedown/mouseup sequences. For example, a user can press on the canvas, keep the mouse button pressed while moving outside the canvas, release elsewhere, then move back into the canvas and click again. This is easier to hit when:

  • The page contains elements other than the canvas, so the pointer can move onto another element.
  • A debugger is triggered while the mouse button is pressed.

Before this change, the simulated touch sequence could become touch-start, touch-start, touch-end. In most touch workflows, a new touch start while the previous simulated touch is still active is unexpected.

Fix

When simulating touch from mouse input, the engine uses touch id 0. On a new simulated TOUCH_START, if touch id 0 is still alive, we first dispatch TOUCH_CANCEL for the previous touch and release it from touchManager. The new mouse down can then create a fresh simulated touch and dispatch the expected TOUCH_START.

This keeps the simulated touch lifecycle well formed:

mousedown, mousedown, mouseup
=> touch-start, touch-cancel, touch-start, touch-end

Reproduction

The following standalone page demonstrates that element-level mouse events can become unpaired. Press inside the canvas, drag to the gray area, release there, then move back into the canvas and click again. The canvas receives the second mousedown without receiving the first mouseup.

<!doctype html>
<html>
<body>
  <canvas id="canvas" width="360" height="180" style="border: 1px solid #333"></canvas>
  <div style="width: 360px; height: 120px; margin-top: 16px; background: #eee; display: flex; align-items: center; justify-content: center;">
    Release mouse here
  </div>
  <pre id="log"></pre>

  <script>
    const canvas = document.getElementById('canvas');
    const log = document.getElementById('log');

    for (const type of ['mousedown', 'mouseup', 'mouseleave', 'mouseenter']) {
      canvas.addEventListener(type, (event) => {
        log.textContent += `${type} button=${event.button} buttons=${event.buttons}\n`;
      });
    }
  </script>
</body>
</html>

Tests

./node_modules/.bin/jest.cmd tests/core/input.test.ts --runInBand

@shrinktofit

Copy link
Copy Markdown
Contributor Author

By the way, as another data point: in a native browser touch sequence, if I start touching on the canvas, move out of the canvas, and then release, the canvas-side flow still receives the corresponding touchend. In other words, a touchstart is expected to be paired with either touchend or touchcancel.

This is why the simulated touch path should preserve the same pairing guarantee as web touch input: when mouse events are not paired, we should explicitly cancel the previous simulated touch before starting a new one.

@github-actions

Copy link
Copy Markdown

Code Size Check Report

Wechat (WASM) Before After Diff
2D Empty (legacy pipeline) 1014459 bytes 1014596 bytes ⚠️ +137 bytes
2D All (legacy pipeline) 2681984 bytes 2682121 bytes ⚠️ +137 bytes
2D All (new pipeline) 2773753 bytes 2773890 bytes ⚠️ +137 bytes
(2D + 3D) All 10030969 bytes 10031106 bytes ⚠️ +137 bytes
Web (WASM + ASMJS) Before After Diff
(2D + 3D) All 16867382 bytes 16867519 bytes ⚠️ +137 bytes

Interface Check Report

This pull request does not change any public interfaces !

@star-e

star-e commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

@cocos-robot run test cases

@star-e
star-e requested review from bofeng-song and qiuguohua and removed request for bofeng-song June 22, 2026 08:20
@github-actions

Copy link
Copy Markdown

@shrinktofit, Please check the result of run test cases:

Task Details

@github-actions

Copy link
Copy Markdown

@shrinktofit, Please check the result of run test cases:

Task Details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants