字段 1:你使用的浏览器版本 + 扩展版本号
Cent Browser 5.2.1168.83(内核 Chromium 134.0.6998.136)/ 扩展 1.5.35
(另在 Edge 同版本内核下不出现,见"对比"一节)
字段 2:问题描述
现象
在 linux.do 列表页(如 /latest)向下滚动到一定程度后开始无限转圈,后续分页数据不再渲染;刷新页面后前几页正常,继续下拉又会在相近位置卡住。停用本扩展后立即恢复正常。
复现步骤
- 启用 LinuxDo Scripts 1.5.35
- 打开
https://linux.do/latest(或任意列表页)
- 向下滚动,持续加载下一页(无限滚动)
- 滚到大约第 3~5 页时开始一直转圈,新话题不再出现
- 停用扩展后重复步骤 2-3 → 正常,可一直滚到底
控制台报错
NotFoundError: Failed to execute 'removeChild' on 'Node':
The node to be removed is not a child of this node.
调用栈指向 Discourse 自身的懒加载渲染块(注意:报错不在扩展代码里):
assets/chunk-d5aq5i5v.digested.js:4:40762 ← 抛出点
→ 经 Vue/Ember 渲染链上抛:
vue_execute → renderRoots(#app) → invoke → flush → e.progress
→ patchBlockChildren → unmount → handleException
同时伴随一条与渲染无关但同屏出现的消息(供参考):
MESSAGE DISM FAIL: callback /presence/chat/online ... disallowed
分析:为什么本扩展会触发它
- 本扩展通过
createShadowRootUi 把 Vue UI 挂到 Shadow DOM,宿主元素挂在 document.body(见 entrypoints/content.js)。
- Discourse 的列表页使用虚拟化渲染(virtual list),会频繁挂载/卸载帖子节点。
- 一旦本扩展在渲染周期中移动/替换/回收了 Discourse 虚拟列表所持有的节点,Discourse 那段移除逻辑就会拿到一个"父节点已经不是它"的节点而抛
NotFoundError。
- 该异常沿异步渲染链上抛后会中断后续渲染流程 → 分页数据拿到了但渲染不出来 → 表现为一直转圈(网络面板里
/latest.json?page=N 是成功的,这是"渲染中断"而非"请求失败"的典型特征)。
- 对照实验:同一台机器、同一网络、同一时间,Edge(未装本扩展)完全正常 → 差异在浏览器侧的扩展行为,而非网络/缓存。
已做过的关键验证(供参考,证明不是网络或缓存问题)
在主世界对 Node.prototype.removeChild / insertBefore 做容错包裹(当 child.parentNode !== this 时不抛错,改为尽力移除或退化为 appendChild),保持本扩展启用:
| 场景 |
结果 |
| 无容错(当前扩展行为) |
一直转圈(复现) |
| 有容错 |
✅ 下拉滚动恢复正常,可一直加载 |
对照:主世界对 removeChild 直接调用(父节点不匹配) |
THREW:NotFoundError —— 成功复刻了上面的报错 |
也就是说:只需要"不让这个异常中断渲染链",问题即消失。
建议的修复方向(任选)
- 收敛对 Discourse 节点树的直接操作:本扩展挂在
body 上的 Shadow DOM 宿主元素,以及后续对宿主/帖子节点的挂载与回收,建议改为不做破坏性移除(不调用 .remove() / removeChild 移除可能被框架持有的节点),改用隐藏(display:none)或移动到独立容器;回收逻辑加 if (el.parentNode) el.parentNode.removeChild(el) 形式的守卫。
- 加一层防御性容错:在扩展的 content script 里(或在主世界注入极小的一段守卫)包裹
Node.prototype.removeChild / insertBefore,父节点不匹配时降级处理而不抛错。实测可完全解决本问题,代价是掩盖真实 DOM 错误(可加开关)。
- 排查与虚拟列表的交互时机:确认是否有逻辑在
MutationObserver 回调里移动被 Discourse 虚拟列表回收的节点,或对 #main-outlet / .topic-list 容器内的节点做了重排。
补充说明:崩溃点的 removeChild 调用本身也未校验父节点(该 chunk 内注释写着 "NOT use removeChild to remove the node",但实际仍走了 removeChild),所以根治也需要 Discourse 侧加守卫。本条 issue 主要诉求是:本扩展可否避免触发它,或增加上述容错选项。
环境
| 项 |
值 |
| 浏览器 |
Cent Browser 5.2.1168.83(Chromium 134.0.6998.136) |
| 扩展 |
LinuxDo Scripts 1.5.35(仓库最新 tag 亦为 1.5.35) |
| 系统 |
Windows 11 build 10.0.26200 |
| 网络 |
系统代理(Clash,127.0.0.1:7897);站点在 Cloudflare 之后 |
| 对照浏览器 |
Microsoft Edge(同机同网络,未装本扩展)→ 正常 |
用户侧临时绕过(已验证有效)
在浏览器里额外装一个"主世界容错"的最小扩展(world: "MAIN", run_at: document_start,包裹 removeChild/insertBefore)即可,扩展功能不受影响。方案细节见上述"建议的修复方向 2"。
字段 1:你使用的浏览器版本 + 扩展版本号
字段 2:问题描述
现象
在 linux.do 列表页(如
/latest)向下滚动到一定程度后开始无限转圈,后续分页数据不再渲染;刷新页面后前几页正常,继续下拉又会在相近位置卡住。停用本扩展后立即恢复正常。复现步骤
https://linux.do/latest(或任意列表页)控制台报错
调用栈指向 Discourse 自身的懒加载渲染块(注意:报错不在扩展代码里):
同时伴随一条与渲染无关但同屏出现的消息(供参考):
分析:为什么本扩展会触发它
createShadowRootUi把 Vue UI 挂到 Shadow DOM,宿主元素挂在document.body(见entrypoints/content.js)。NotFoundError。/latest.json?page=N是成功的,这是"渲染中断"而非"请求失败"的典型特征)。已做过的关键验证(供参考,证明不是网络或缓存问题)
在主世界对
Node.prototype.removeChild/insertBefore做容错包裹(当child.parentNode !== this时不抛错,改为尽力移除或退化为appendChild),保持本扩展启用:removeChild直接调用(父节点不匹配)THREW:NotFoundError—— 成功复刻了上面的报错也就是说:只需要"不让这个异常中断渲染链",问题即消失。
建议的修复方向(任选)
body上的 Shadow DOM 宿主元素,以及后续对宿主/帖子节点的挂载与回收,建议改为不做破坏性移除(不调用.remove()/removeChild移除可能被框架持有的节点),改用隐藏(display:none)或移动到独立容器;回收逻辑加if (el.parentNode) el.parentNode.removeChild(el)形式的守卫。Node.prototype.removeChild/insertBefore,父节点不匹配时降级处理而不抛错。实测可完全解决本问题,代价是掩盖真实 DOM 错误(可加开关)。MutationObserver回调里移动被 Discourse 虚拟列表回收的节点,或对#main-outlet/.topic-list容器内的节点做了重排。环境
127.0.0.1:7897);站点在 Cloudflare 之后用户侧临时绕过(已验证有效)
在浏览器里额外装一个"主世界容错"的最小扩展(
world: "MAIN",run_at: document_start,包裹removeChild/insertBefore)即可,扩展功能不受影响。方案细节见上述"建议的修复方向 2"。