diff --git a/packages/blockly/core/gesture.ts b/packages/blockly/core/gesture.ts index 9d617c4c62b..aefbce51cb1 100644 --- a/packages/blockly/core/gesture.ts +++ b/packages/blockly/core/gesture.ts @@ -418,6 +418,14 @@ export class Gesture { this.mouseDownXY = new Coordinate(e.clientX, e.clientY); + // Re-establish the touch identifier for this gesture's starting pointer. + // If getGesture() cancelled a stale gesture for this pointer (a missed + // pointerup), that cancel() cleared the global touch identifier via + // dispose(). Without restoring it here, this gesture's terminating + // pointerup is rejected by Touch.shouldHandleEvent and the gesture is + // never disposed, permanently locking the workspace. + Touch.checkTouchIdentifier(e); + this.bindMouseEvents(e); if (!this.isEnding_) { diff --git a/packages/blockly/tests/mocha/gesture_test.js b/packages/blockly/tests/mocha/gesture_test.js index 9036141ef25..5dd90f6b478 100644 --- a/packages/blockly/tests/mocha/gesture_test.js +++ b/packages/blockly/tests/mocha/gesture_test.js @@ -130,4 +130,26 @@ suite('Gesture', function () { this.workspace.id, ); }); + + test('Duplicate pointerdown (missed pointerup) does not lock the workspace', function () { + // Some touch environments emit a pointerdown for a pointer that is already + // down (a missed pointerup). getGesture() handles this by cancelling the + // stale gesture and starting a new one; that new gesture must still be + // disposable so the workspace does not lock up permanently. + const target = this.workspace.getSvgGroup(); + dispatchPointerEvent(target, 'pointerdown', {pointerId: 1}); + assert.isTrue( + Blockly.Gesture.inProgress(), + 'Precondition: first pointerdown must start a gesture.', + ); + // Duplicate pointerdown for the same pointer id (missed pointerup). + dispatchPointerEvent(target, 'pointerdown', {pointerId: 1}); + // Releasing the pointer must end the gesture. + dispatchPointerEvent(document, 'pointerup', {pointerId: 1}); + + assert.isFalse( + Blockly.Gesture.inProgress(), + 'Gesture should not remain in progress after the pointer is released.', + ); + }); });