浏览器扩展隔离内容脚本中自定义元素定义与使用问题求助
浏览器扩展隔离内容脚本中使用自定义元素的解决方案
原代码失败原因
隔离内容脚本运行在独立沙盒环境,其中的HTMLElement构造函数与主世界文档的HTMLElement并非同一引用。你定义的自定义元素继承自隔离环境的HTMLElement,但Shadow DOM属于主世界文档,实例化时浏览器会尝试用主世界的构造逻辑调用隔离环境的类,导致构造函数不兼容,最终抛出Illegal constructor错误。
可行解决方法
方法1:将自定义元素代码注入主世界执行
隔离脚本无法直接让自身环境的自定义元素在主世界DOM中生效,需把元素定义代码注入到主世界执行:
// 隔离内容脚本内的代码 // 定义自定义元素的逻辑(要注入到主世界) const counterDef = ` class MyCounter extends HTMLElement { constructor() { super(); const start = this.getAttribute('startvalue') || 0; const step = this.getAttribute('step') || 1; this.innerHTML = \`<button>Count: \${start}</button>\`; this.btn = this.querySelector('button'); this.count = parseInt(start); this.btn.addEventListener('click', () => { this.count += parseInt(step); this.btn.textContent = \`Count: \${this.count}\`; }); } } customElements.define('my-counter', MyCounter); `; // 创建脚本标签注入主世界 const script = document.createElement('script'); script.textContent = counterDef; document.head.appendChild(script); script.remove(); // 注入后清理脚本标签 // 现在可以在隔离脚本中创建元素并添加到页面 const container = document.createElement('div'); container.innerHTML = `<my-counter startvalue="1" step="3"></my-counter>`; document.body.appendChild(container);
方法2:作用域化Registry配合主世界注入
如果不想全局注册自定义元素,可在注入主世界的代码中创建作用域化CustomElementRegistry,并绑定到指定Shadow DOM:
// 隔离内容脚本内的代码 const scopedSetup = ` class Counter extends HTMLElement { constructor() { super(); // 自定义元素逻辑 const start = this.getAttribute('startvalue') || 0; const step = this.getAttribute('step') || 1; this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = \`<span>Count: \${start}</span><button>+</button>\`; this.count = parseInt(start); this.shadowRoot.querySelector('button').addEventListener('click', () => { this.count += parseInt(step); this.shadowRoot.querySelector('span').textContent = \`Count: \${this.count}\`; }); } } const scopedRegistry = new CustomElementRegistry(); scopedRegistry.define('my-counter', Counter); // 暴露方法给隔离脚本调用(用完即删避免污染) window.__createScopedCounter = () => { const container = document.createElement('div'); container.attachShadow({ mode: 'open', customElementRegistry: scopedRegistry }); container.shadowRoot.innerHTML = '<my-counter startvalue="1" step="3"></my-counter>'; return container; }; `; // 注入主世界 const script = document.createElement('script'); script.textContent = scopedSetup; document.head.appendChild(script); script.remove(); // 调用主世界方法获取容器并添加 const container = window.__createScopedCounter(); document.body.appendChild(container); delete window.__createScopedCounter;
内容的提问来源于stack exchange,提问作者ack_inc
相关产品推荐
相关产品推荐

