closed模式无法实现高度隔离,真正有效的是Shadow DOM+iframe+CSP+composed:false组合;它不阻止DOM快照、事件监听、CSS注入、原型劫持或DevTools查看。

closed 模式不能构建“高度隔离的第三方插件运行时环境”——它既不阻止 DOM 快照、事件监听、CSS 注入,也不防原型劫持或 DevTools 查看;真正起作用的是 Shadow DOM + iframe + CSP + composed: false 的组合,而非 mode 字段本身。
closed 模式在运行时环境里实际失效的典型场景
设置 attachShadow({ mode: 'closed' }) 后,外部脚本确实拿不到 element.shadowRoot,但以下行为依然畅通无阻:
-
document.querySelectorAll('my-plugin')仍能定位宿主元素,再用MutationObserver监听其子树变化,间接推断内部结构 - 浏览器 DevTools(Chrome/Firefox)展开元素节点后,shadow tree 仍完整可见,无需任何绕过
-
getComputedStyle(el)可读取渲染后样式,反推出内部 class 布局和 slot 分发逻辑 - 所有事件默认
composed: true,el.addEventListener('click', handler)仍能捕获内部按钮点击 - 第三方监控 SDK 调用
el.cloneNode(true)会完整复制 shadow tree,closed完全不干预
真正影响插件隔离强度的四个关键配置
比起纠结 mode,应优先确保这四点落地:
-
dispatchEvent()时显式传{ composed: false },防止事件穿透到 light DOM - 用
:host和:host-context()控制外部样式影响范围,禁用* { all: initial }类重置 - 只通过
<slot name="xxx">显式开放内容入口,并配exportparts="icon button"供有限定制 - 对所有动态插入的 HTML 做严格净化(如
DOMPurify.sanitize(html)),不依赖 Shadow DOM 自身过滤
为什么测试和调试必须用 open 模式
jsdom(Jest 默认环境)根本不支持 closed 模式,element.shadowRoot 永远为 null,导致测试代码无法 setup / teardown。这意味着:
- 开发时用
open,上线切closed—— 行为不一致,CI 流程不可靠 - Puppeteer/Playwright 虽能跑
closed,但 E2E 成本高、速度慢,不适合作为日常验证手段 - DevTools 无法 inspect 内部结构,一旦插件出错,只能靠日志盲调,协作效率大幅下降
更可行的运行时隔离方案组合
若真需承载第三方插件,推荐分层防护:
- 最外层:用
<iframe sandbox="allow-scripts" srcdoc="...">提供 JS 执行沙箱 - 中层:iframe 内部组件使用
attachShadow({ mode: 'open' })+composed: false+exportparts - 内层:关键逻辑移至 WebAssembly(Rust 编译的
.wasm)或服务端,前端只做 UI 渲染 - 配套:启用严格 CSP(
script-src 'self')、禁用eval、限制postMessage通信 schema
closed 模式唯一合理用途,是配合上述组合做一层轻量访问提示,而非当作隔离主力——它不解决任何真实攻击面,却显著抬高维护成本。

















