Describe the bug
When MakeCode is embedded in an iframe on Android, dragging the innermost melody block (melody_editor) on a touch device glitches and the drag never ends. After the finger lifts, the block stays attached to the pointer. Every subsequent touch moves it around the workspace and there is no way to drop it or otherwise recover short of reloading.
It reproduces in a plain pxt-microbit dev server via the controller.html iframe test page. It does not reproduce with a mouse, and does not reproduce in the non-embedded editor, even with touch. Fortunately, you can reproduce it in Chrome dev tools device mode which has touch event emulation (reload the page after opening dev tools and enabling device mode).
Not a recent regression: reproduces on pxt-microbit v7 and v8 as well as current master, so unrelated to the Blockly 13 upgrade.
We hit this in a user testing session for micro:bit CreateAI. I think it's unlikely that the user was intentionally dragging the melody itself, but it's easy to do when you're attempting to drag the play block and can be part of how you realise the block structure.
To Reproduce
Steps to reproduce the behavior:
- Go to http://localhost:3232/controller.html on Android (assumes running pxt-microbit dev server)
- Drag out a play melody...at tempo block (top of Music category)
- Drag the block with the musical note and the coloured notes (blank at this point)
- Note it glitches and you can't drag it far (screenshot 1)
- Try more interactions, the block is still being dragged but won't drop and there's nothing you can do short of reloading
Alternatively for a quick repro you can reproduce on micro:bit classroom in dev tools device mode. Create a new session and edit the starter code to get to embedded MakeCode.
Expected behavior
Block drags out cleanly.
Screenshots
Initial glitch state:
You can drag it to the toolbox but it won't delete so you can't recover:
Desktop (please complete the following information):
- OS: Android or Chrome with device mode
- Browser: Chrome
- Version: 150.0.7871.114 (Official Build) (arm64) - device mode desktop repro
- Version: 150.0.7871.128 - Android repro
Analysis
I was puzzled that this was iframe only. This is what I think is going on:
- Touch implicit pointer capture. On touch, the pointerdown target implicitly captures the pointer; the whole event stream is retargeted to that element.
- The melody field rebuilds its DOM on every render. pxt's renderer re-runs field.init() each render (the input.init() in RenderInfo.measure, added in the great Blockly upgrade to replace the fork-era initSvg calls). Blockly.Field.init() is idempotent, but FieldCustomMelody.init() runs onInit() unconditionally, and updateFieldLabel() clears and recreates the preview elements. There's a render at drag start (perhaps triggered by duplicate on drag), so the captured element is destroyed ~50ms into the gesture (confirmed by logging lostpointercapture document event).
- The iframe. After the capture target is destroyed, Chrome's fallback seems to differ in the iframe case. Top-level: remaining events fall back to hit-testing in the same document and events get sent correctly, iframe: the touch's remaining events don't make it to the iframe's document at all (maybe routed to the top-level document, not confirmed).
Suggested fix:
Make the pxt field init() overrides idempotent, matching the Blockly.Field.init() approach so onInit() can't destroy the DOM under an active pointer.
FieldSoundEffect (via FieldBase) has the same rebuild-on-init pattern and although I couldn't reproduce it there it seems sensible to update in the same way.
I can't see anything that relies on these extra init calls for correctness.
I'll open a PR.
FWIW, if Blockly's pointer handling was modernised they could be robust to this by using setPointerCapture to handle the event stream on e.g. the workspace or injection div.
Describe the bug
When MakeCode is embedded in an iframe on Android, dragging the innermost melody block (melody_editor) on a touch device glitches and the drag never ends. After the finger lifts, the block stays attached to the pointer. Every subsequent touch moves it around the workspace and there is no way to drop it or otherwise recover short of reloading.
It reproduces in a plain pxt-microbit dev server via the controller.html iframe test page. It does not reproduce with a mouse, and does not reproduce in the non-embedded editor, even with touch. Fortunately, you can reproduce it in Chrome dev tools device mode which has touch event emulation (reload the page after opening dev tools and enabling device mode).
Not a recent regression: reproduces on pxt-microbit v7 and v8 as well as current master, so unrelated to the Blockly 13 upgrade.
We hit this in a user testing session for micro:bit CreateAI. I think it's unlikely that the user was intentionally dragging the melody itself, but it's easy to do when you're attempting to drag the play block and can be part of how you realise the block structure.
To Reproduce
Steps to reproduce the behavior:
Alternatively for a quick repro you can reproduce on micro:bit classroom in dev tools device mode. Create a new session and edit the starter code to get to embedded MakeCode.
Expected behavior
Block drags out cleanly.
Screenshots
Initial glitch state:
You can drag it to the toolbox but it won't delete so you can't recover:
Desktop (please complete the following information):
Analysis
I was puzzled that this was iframe only. This is what I think is going on:
Suggested fix:
Make the pxt field init() overrides idempotent, matching the Blockly.Field.init() approach so onInit() can't destroy the DOM under an active pointer.
FieldSoundEffect (via FieldBase) has the same rebuild-on-init pattern and although I couldn't reproduce it there it seems sensible to update in the same way.
I can't see anything that relies on these extra init calls for correctness.
I'll open a PR.
FWIW, if Blockly's pointer handling was modernised they could be robust to this by using setPointerCapture to handle the event stream on e.g. the workspace or injection div.