为什么在未进行沙箱限制的iframe中,父页面注册的Web组件会不起作用?
当在一个(未沙箱)iframe的脚本中调用 customElements.define(name, window.top.customElements.get(name)) 时(若这有实际意义,这里以type="module" 为前提),在Chrome中总是会抛出错误 Uncaught TypeError: custom element constructors must call super() first and must not return a different object,在Firefox中则是 Uncaught TypeError: Custom element constructor returned a wrong element,即使这是一个在顶层注册的极其简单的Web组件,例如:
customElements.define(name, class extends HTMLElement {
constructor() {
super();
}
})
但如果我把那个类直接替换到 window.top.customElements.get(name) 的位置,那么就能工作。
是否有某些原因,父页面注册的Web组件在iframe中不可用?
下面给出一个完整的、最小可复现的示例(注意,这个示例在像codepen这样的工具中使用iframe来运行代码时将不起作用,因为那会改变 window.top 的含义。这个示例需要放在一个HTML文件中来试用):
<script type="module">
customElements.define(
"my-el",
class extends HTMLElement {
constructor() {
super();
}
}
);
</script>
<iframe
srcdoc="
<my-el></my-el>
<script>
customElements.define('my-el', window.top.customElements.get('my-el'))
</script>"
>
</iframe>
回答后的补充:另请参阅:How we can copy custom elements from parent window inside iframe window? 该文中有关于该领域新网页标准的回答,以及如何在顶层页面和iframe中对定义自定义组件的文件直接使用import的做法。不过该文并未回答为何不能在iframe中注册在顶层页面定义的Web组件,这篇才有。
解决方案
仅回答原因:
在 js
class extends HTMLElement { 中被引用的 HTMLElement 接口来自顶层上下文,而不是来自iframe自身的接口。当iframe的文档尝试使用它来生成一个元素时,它会发现它并不是自身返回的 HTMLElement 的一个实例,因此会抛出。
这等同于直接对 class extends window.top.HTMLElement 进行操作,结果同样会失败:https://jsfiddle.net/f7dr23ew/1(因为StackSnippet的 iframe没有原点,无法运行该演示)。