Chrome扩展MV3:沙箱iframe中reCAPTCHA WebWorker加载失败(源为'null')
解决Chrome扩展Manifest V3中reCAPTCHA v3的WebWorker加载错误
问题场景
开发Manifest V3版本的Chrome扩展时,弹窗页面需要集成Google reCAPTCHA v3。由于MV3禁止扩展页面直接加载外部脚本,采用官方沙箱方案,通过iframe将reCAPTCHA加载到沙箱化的HTML文件中。页面能显示reCAPTCHA徽章,且onloadCallback正常执行,但控制台抛出以下错误:
Uncaught SecurityError: Failed to construct 'Worker': Script at 'https://www.google.com/recaptcha/api2/webworker.js?hl=en&v=…' cannot be accessed from origin 'null'.
相关配置代码
manifest.json
{ "manifest_version": 3, … "permissions": ["declarativeNetRequest", "scripting", "storage", "tabs", "unlimitedStorage"], "host_permissions": [ "https://www.google.com/*", "https://www.gstatic.com/*", "https://recaptcha.google.com/*", "https://recaptcha.net/*", ... ], "content_security_policy": { "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' https://www.google.com/ https://www.gstatic.com/ https://recaptcha.google.com/ https://recaptcha.net/; child-src 'self';" }, "sandbox": { "pages": ["recaptcha_sandbox.html"] }, "background": { "service_worker": "background.js" }, "web_accessible_resources": [ { "resources": ["recaptcha_sandbox.html"], "matches": ["<all_urls>"] } ], … }
recaptcha_sandbox.html
<!DOCTYPE html> <html> <body> <script src="https://www.google.com/recaptcha/api.js? onload=onloadCallback&render=MY_SITE_KEY" defer> </script> <script> function onloadCallback() { console.log('RECAPTCHA_READY ') window.grecaptcha.ready(async () => { try { const token = await window.grecaptcha.execute( 'MY_SITE_KEY', { action: 'login' } ); console.log('TOKEN', token); } catch (err) { console.error('ERROR', err); } }); } </script> </body> </html>
弹窗React代码
function LoginForm() { ... return ( ... <iframe ref={iframeRef} src={chrome.runtime.getURL('recaptcha_sandbox.html')} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', border: 'none', zIndex: -1, pointerEvents: 'none' }} title="reCAPTCHA Sandbox" /> ... ); }
错误原因
- MV3的沙箱页面默认使用
nullorigin,浏览器同源策略禁止nullorigin加载跨域Worker脚本。 - 当前CSP的
sandbox指令未显式允许Worker创建,且缺少worker-src规则授权reCAPTCHA域名加载Worker脚本。 - 不必要的
web_accessible_resources配置可能引发额外的安全限制(扩展内部页面无需对外暴露沙箱页面)。
修复方案
1. 更新Manifest的CSP沙箱配置
修改content_security_policy中的sandbox规则,添加allow-worker指令,并新增worker-src授权reCAPTCHA相关域名:
"content_security_policy": { "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals allow-worker; script-src 'self' 'unsafe-inline' https://www.google.com/ https://www.gstatic.com/ https://recaptcha.google.com/ https://recaptcha.net/; worker-src https://www.google.com/ https://www.gstatic.com/ https://recaptcha.google.com/ https://recaptcha.net/;" }
2. 移除不必要的资源暴露
从web_accessible_resources中删除recaptcha_sandbox.html的配置,因为弹窗属于扩展内部页面,直接通过chrome.runtime.getURL访问沙箱页面无需对外暴露:
// 删除以下配置 "web_accessible_resources": [ { "resources": ["recaptcha_sandbox.html"], "matches": ["<all_urls>"] } ]
3. 完善沙箱与弹窗的通信逻辑
沙箱页面获取token后,通过postMessage传递到弹窗页面,弹窗监听消息接收token:
修正后的recaptcha_sandbox.html
<!DOCTYPE html> <html> <body> <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=MY_SITE_KEY" defer></script> <script> function onloadCallback() { console.log('RECAPTCHA_READY ') window.grecaptcha.ready(async () => { try { const token = await window.grecaptcha.execute( 'MY_SITE_KEY', { action: 'login' } ); console.log('TOKEN', token); // 向弹窗页面发送token,限制接收方为扩展内部域名 window.parent.postMessage( { type: 'RECAPTCHA_TOKEN', token }, chrome.runtime.getURL('/') ); } catch (err) { console.error('ERROR', err); } }); } </script> </body> </html>
弹窗React代码添加消息监听
import { useEffect } from 'react'; function LoginForm() { useEffect(() => { const handleRecaptchaToken = (event) => { // 验证消息来源和类型,确保安全 if (event.origin === chrome.runtime.getURL('/') && event.data.type === 'RECAPTCHA_TOKEN') { const token = event.data.token; console.log('Received reCAPTCHA token:', token); // 在这里处理token的后续逻辑(如提交登录请求) } }; window.addEventListener('message', handleRecaptchaToken); return () => window.removeEventListener('message', handleRecaptchaToken); }, []); return ( ... <iframe ref={iframeRef} src={chrome.runtime.getURL('recaptcha_sandbox.html')} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', border: 'none', zIndex: -1, pointerEvents: 'none' }} title="reCAPTCHA Sandbox" /> ... ); }
验证效果
重新加载扩展后,沙箱页面能正常加载reCAPTCHA的WebWorker,控制台不再抛出SecurityError,且弹窗能成功接收reCAPTCHA token。
内容的提问来源于stack exchange,提问作者ucanfil
相关产品推荐
相关产品推荐

