Complete Omission of block.destroy() in Blocks Collection
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 84/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- typescript
- 领域
- frontend
调研方向
读取 src/components/blocks.ts 中的 insert、replace、remove 和 removeAll,然后将它们的生命周期调用与 src/components/block/index.ts 中的 Block.destroy() 进行比较。当每条移除或替换 block 的路径都执行所需的清理,同时保留现有的 removal hook 行为时,即表示完成。
由索引模型根据 Issue 内容生成。
描述
1. Complete Omission of block.destroy() in Blocks Collection
📍 Affected Locations
src/components/blocks.ts(Lines 200–205, 230–240, 285–295, 300–306)src/components/block/index.ts(Lines 688–698)
🔍 Deep Technical Diagnosis
The Block class defines an essential cleanup method in src/components/block/index.ts:
// src/components/block/index.ts: Lines 688-698
public destroy(): void {
this.holder.removeEventListener('change', this.handleSelectChange);
this.unwatchBlockMutations();
this.removeInputEvents();
super.destroy();
if (_.isFunction(this.toolInstance.destroy)) {
this.toolInstance.destroy();
}
}
However, in the core collection manager src/components/blocks.ts, block.destroy() is never called anywhere:
1. When removing a block:
// src/components/blocks.ts: Lines 285-295
public remove(index: number): void {
if (isNaN(index)) {
index = this.length - 1;
}
this.blocks[index].holder.remove();
this.blocks[index].call(BlockToolAPI.REMOVED); // <-- Calls removed hook, BUT NEVER destroy()!
this.blocks.splice(index, 1);
}
2. When clearing all blocks (removeAll / blocks.clear()):
// src/components/blocks.ts: Lines 300-306
public removeAll(): void {
this.workingArea.innerHTML = '';
this.blocks.forEach((block) => block.call(BlockToolAPI.REMOVED)); // <-- NEVER destroy()!
this.blocks.length = 0;
}
3. When updating a block (replace / blocks.update()):
// src/components/blocks.ts: Lines 230-240
public replace(index: number, block: Block): void {
const prevBlock = this.blocks[index];
prevBlock.holder.replaceWith(block.holder);
this.blocks[index] = block; // <-- Neither removed NOR destroy() is called!
}
4. When inserting with replace (insert(..., replace = true)):
// src/components/blocks.ts: Lines 200-204
if (replace) {
this.blocks[index].holder.remove();
this.blocks[index].call(BlockToolAPI.REMOVED); // <-- destroy() skipped!
}
💥 Real-World Impact
- Leaked
MutationObservers:this.unwatchBlockMutations()is never executed. Each deleted block leaves an activeMutationObserverobserving an orphaned DOM node. - Leaked Input Event Listeners:
this.removeInputEvents()is never executed; all keydown, paste, and input handlers remain bound to inputs. - Broken Tool Teardown: Third-party plugins that implement
destroy()(e.g., stopping video streams, tearing down WebGL/Three.js canvases, clearingsetIntervaltimers, closing WebSocket channels) never have theirdestroy()hook fired when a block is deleted or updated! - Over a long editing session or dynamic updates in collaborative editors (e.g., Yjs), hundreds of orphaned blocks accumulate in memory.
💡 Proposed Solution & Patch
src/components/blocks.ts
@@ -201,6 +201,7 @@ export default class Blocks {
if (replace) {
this.blocks[index].holder.remove();
this.blocks[index].call(BlockToolAPI.REMOVED);
+ this.blocks[index].destroy();
}
const deleteCount = replace ? 1 : 0;
@@ -236,6 +237,8 @@ export default class Blocks {
const prevBlock = this.blocks[index];
prevBlock.holder.replaceWith(block.holder);
+ prevBlock.call(BlockToolAPI.REMOVED);
+ prevBlock.destroy();
this.blocks[index] = block;
}
@@ -290,6 +293,7 @@ export default class Blocks {
this.blocks[index].holder.remove();
this.blocks[index].call(BlockToolAPI.REMOVED);
+ this.blocks[index].destroy();
this.blocks.splice(index, 1);
}
@@ -301,6 +305,9 @@ export default class Blocks {
this.workingArea.innerHTML = '';
- this.blocks.forEach((block) => block.call(BlockToolAPI.REMOVED));
+ this.blocks.forEach((block) => {
+ block.call(BlockToolAPI.REMOVED);
+ block.destroy();
+ });
this.blocks.length = 0;
}
- 主要语言
- TypeScript
- 星标
- 32k
- 派生
- 2.2k
- 平均合并
- 2 天 1 小时
- 30 天内合并 PR
- 1
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
codex-team/editor.js 的其他 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 70/100
codex-team/editor.js#3037 ·
-
Floating Promise Anti-Pattern & Premature Caret in Multi-Block Paste可能已有人在做 @anshurajbisoyi98-ctrl 于 13 天前认领。 未关闭bug good first issue
难度 2/5 1-3 小时 新手友好度 78/100
codex-team/editor.js#3032 · 1 条评论 ·
-
Silent Data Loss & Broken Error Contract in Saver.save()可能已有人在做 @Sion612 于 23 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 82/100
codex-team/editor.js#3030 ·
-
Bug: Link cannot be removed可能已有人在做 @prathmeshyelne 于 129 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 65/100
codex-team/editor.js#3005 · 1 条评论 ·
-
good first issue
难度 2/5 1-3 小时 新手友好度 72/100
codex-team/editor.js#2616 · 1 条评论 · 1 个 reaction ·
查看 codex-team/editor.js 的全部 Issue
相似的 Issue
-
bug engine
难度 2/5 1-3 小时 新手友好度 86/100
frostney/GocciaScript#1746 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 67/100
agilepathway/label-checker#739 · 4 条评论 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 68/100
CopilotKit/OpenBot#800 ·
维护者通常 1 天内回复
-
Endpoint links in the tag operations list are below the 24px target size可能已有人在做 @AdzerKI 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 79/100
ehmpathy/rhachet-roles-bhuild#471 ·
维护者通常 1 天内回复