Make custom field init() idempotent to fix stuck touch drags#11491
Merged
riknoll merged 1 commit intoJul 21, 2026
Conversation
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
Contributor
Author
|
If you agree this fix is straightforward / low risk then it would be good to consider porting this one to stable. |
riknoll
approved these changes
Jul 21, 2026
riknoll
enabled auto-merge
July 21, 2026 20:53
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>
Member
|
@microbit-matt-hillsdon have this in https://makecode.microbit.org/stable for testing~ |
Contributor
Author
Thanks for porting this to stable. We've tested it and the fix is looking good. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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