Skip to content

BlocksUI doesn't remove its blocksHolder DOM event listeners (or the holder itself) on destroy() #170

Description

@ilyamore88

BlocksUI.#prepareBlocksHolder() (packages/ui/src/Blocks/Blocks.ts) registers three DOM listeners directly on blocksHolder:

  • beforeinput (line 89)
  • keydown (line 118)
  • copy (line 140)

BlocksUI.destroy() only cleans up rendered block elements:

public destroy(): void {
  this.#blocks.forEach(block => block.remove());
  this.#blocks = [];
}

None of the three listeners above are ever removed, and #blocksHolder itself is never detached. The holder is handed off via BlocksHolderRenderedUIEvent to EditorUI, which appends it into #editorWrapper (packages/ui/src/index.ts) — but EditorUI.destroy() is currently a no-op (// Cleanup if needed), so nothing ever detaches it either.

Impact

If a blocksHolder element is reused, or a new BlocksUI/Core instance is created against a holder from a previous instance, the old listeners stay attached and continue firing alongside the new instance's listeners. Concretely this means duplicate BeforeInputUIEvent/CopyUIEvent dispatches per user action, and stale keydown (undo/redo) handling from a destroyed instance.

Where this came from

Flagged by review on #129, specifically for the new copy listener: #129 (comment). Investigation showed the same gap already existed for beforeinput/keydown before this PR — this issue is to track fixing all three (and the missing holder detachment) rather than just the newest one.

Suggested fix

  • Store references to the three listener functions (or use an AbortController/AbortSignal passed to addEventListener for the whole group) so destroy() can call removeEventListener for each, matching the pattern already used in ClipboardPlugin/ShortcutsPlugin elsewhere in this codebase.
  • Consider detaching #blocksHolder from the DOM in destroy(), and implementing EditorUI.destroy() (currently a no-op) to tear down #editorWrapper.
  • Add test coverage — there's currently no Blocks.spec.ts at all, so destroy() behavior is completely unverified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions