Skip to content

Make custom field init() idempotent to fix stuck touch drags#11491

Merged
riknoll merged 1 commit into
microsoft:masterfrom
microbit-matt-hillsdon:fix-iframe-melody-drag
Jul 21, 2026
Merged

Make custom field init() idempotent to fix stuck touch drags#11491
riknoll merged 1 commit into
microsoft:masterfrom
microbit-matt-hillsdon:fix-iframe-melody-drag

Conversation

@microbit-matt-hillsdon

Copy link
Copy Markdown
Contributor

pxt's renderer re-runs field.init() on every render (the input.init() in RenderInfo.measure, added in #9865 to replace the fork-era re-runnable BlockSvg.initSvg). Blockly.Field.init() is idempotent, but FieldBase and FieldCustomMelody ran onInit() unconditionally, rebuilding the field's DOM on every render.

On touch, the pointerdown target implicitly captures the pointer, so destroying that element mid-drag breaks event delivery. Dragging a melody_editor shadow by its preview triggers exactly this: duplicate- on-drag forces a render at drag start, updateFieldLabel() removes the captured element, and in an iframe embed Chrome then routes the rest of the touch's events (including pointerup) to the top-level document. The gesture never ends, Blockly's touchIdentifier_ stays pinned to the dead pointer, and every subsequent touch moves the block with no way to drop it.

Top-level documents seem to recover from this so we can only reproduce issues in an iframe (e.g. with controller.html).

Guard init() so onInit() runs once per field, matching the Blockly.Field.init behavior.

Fixes microsoft/pxt-microbit#7045

pxt's renderer re-runs field.init() on every render (the input.init()
sweep in RenderInfo.measure, added in microsoft#9865 to replace the fork-era
re-runnable BlockSvg.initSvg). Blockly.Field.init() is idempotent, but
FieldBase and FieldCustomMelody ran onInit() unconditionally, rebuilding
the field's DOM on every render.

On touch, the pointerdown target implicitly captures the pointer, so
destroying that element mid-drag breaks event delivery. Dragging a
melody_editor shadow by its preview triggers exactly this: duplicate-
on-drag forces a render at drag start, updateFieldLabel() removes the
captured element, and in an iframe embed Chrome then routes the rest of
the touch's events (including pointerup) to the top-level document. The
gesture never ends, Blockly's touchIdentifier_ stays pinned to the dead
pointer, and every subsequent touch moves the block with no way to drop
it.

Top-level documents seem to recover from this so we can only reproduce
issues in an iframe (e.g. with controller.html).

Guard init() so onInit() runs once per field, matching the
Blockly.Field.init behavior.

Fixes microsoft/pxt-microbit#7045
@microbit-matt-hillsdon

Copy link
Copy Markdown
Contributor Author

If you agree this fix is straightforward / low risk then it would be good to consider porting this one to stable.

@riknoll
riknoll enabled auto-merge July 21, 2026 20:53
@riknoll
riknoll added this pull request to the merge queue Jul 21, 2026
Merged via the queue into microsoft:master with commit 0c7998e Jul 21, 2026
12 checks passed
jwunderl added a commit that referenced this pull request Jul 22, 2026
…#11496)

pxt's renderer re-runs field.init() on every render (the input.init()
sweep in RenderInfo.measure, added in #9865 to replace the fork-era
re-runnable BlockSvg.initSvg). Blockly.Field.init() is idempotent, but
FieldBase and FieldCustomMelody ran onInit() unconditionally, rebuilding
the field's DOM on every render.

On touch, the pointerdown target implicitly captures the pointer, so
destroying that element mid-drag breaks event delivery. Dragging a
melody_editor shadow by its preview triggers exactly this: duplicate-
on-drag forces a render at drag start, updateFieldLabel() removes the
captured element, and in an iframe embed Chrome then routes the rest of
the touch's events (including pointerup) to the top-level document. The
gesture never ends, Blockly's touchIdentifier_ stays pinned to the dead
pointer, and every subsequent touch moves the block with no way to drop
it.

Top-level documents seem to recover from this so we can only reproduce
issues in an iframe (e.g. with controller.html).

Guard init() so onInit() runs once per field, matching the
Blockly.Field.init behavior.

Fixes microsoft/pxt-microbit#7045

Co-authored-by: Matt Hillsdon <matt.hillsdon@microbit.org>
@jwunderl

Copy link
Copy Markdown
Member

@microbit-matt-hillsdon have this in https://makecode.microbit.org/stable for testing~

@microbit-matt-hillsdon

Copy link
Copy Markdown
Contributor Author

@microbit-matt-hillsdon have this in https://makecode.microbit.org/stable for testing~

Thanks for porting this to stable. We've tested it and the fix is looking good.

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.

Touch drag of melody block in iframe embed permanently sticks the block to the pointer (Android / Chrome touch emulation)

3 participants