Skip to content

Commit 1c4f57f

Browse files
authored
Merge pull request #484 from scratchfoundation/fix-focus-throw
Fix two exceptions thrown for focus-related reasons
2 parents 387053f + 96b874f commit 1c4f57f

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

packages/scratch-gui/src/containers/blocks.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,20 @@ class Blocks extends React.Component {
240240
}
241241
componentWillUnmount () {
242242
this.detachVM();
243+
// Hide any open field editor and move Blockly focus to the workspace
244+
// root before disposing. Without this, BlockSvg.dispose() detects the
245+
// focused element is inside a block and schedules a stale
246+
// setTimeout(() => focusTree(workspace)), which fires after the
247+
// workspace is unregistered and throws
248+
// "Attempted to focus unregistered tree" (scratch-blocks#3460).
249+
//
250+
// focusNode(workspace) — not focusTree(workspace) — is used here
251+
// because focusTree would restore focus to whatever was previously
252+
// focused in this workspace (likely the same block about to be
253+
// disposed). focusNode pins focus to the workspace root directly,
254+
// ensuring no block is focused when dispose() runs.
255+
this.ScratchBlocks.WidgetDiv.hide();
256+
this.ScratchBlocks.getFocusManager().focusNode(this.workspace);
243257
this.workspace.dispose();
244258
clearTimeout(this.toolboxUpdateTimeout);
245259

packages/scratch-gui/src/containers/custom-procedures.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ class CustomProcedures extends React.Component {
2626
}
2727
componentWillUnmount () {
2828
if (this.workspace) {
29+
// When BlockSvg.dispose() runs with the focused element inside the
30+
// block, it schedules setTimeout(() => focusTree(workspace)) to
31+
// return focus somewhere sensible. For procedures_declaration
32+
// (no parent block), that setTimeout fires *after* the workspace
33+
// is unregistered, throwing "Attempted to focus unregistered tree".
34+
//
35+
// Fix: hide any open field editor (releases ephemeral focus) then
36+
// move Blockly focus to the main workspace before disposing.
37+
// BlockSvg.dispose() then sees the focused element is not inside
38+
// any dialog block and skips the stale focusTree setTimeout.
39+
ScratchBlocks.WidgetDiv.hide();
40+
const mainWorkspace = ScratchBlocks.getMainWorkspace();
41+
if (mainWorkspace) {
42+
ScratchBlocks.getFocusManager().focusTree(mainWorkspace);
43+
}
2944
this.workspace.dispose();
3045
}
3146
}

0 commit comments

Comments
 (0)