Chrome扩展Manifest V3注入脚本遇CSP错误,如何获取宿主window变量?
问题描述
我正在开发一款针对无控制权公共网站的Chrome扩展,需要获取宿主页面上下文的window变量。原本在内容脚本中通过注入内联脚本、设置body节点属性的方式传递变量,代码能运行但触发了CSP错误——宿主页面不允许此类操作。错误提示要求添加SHA-256哈希或nonce,但我无法修改宿主页面,因此需要其他可行方案。
另外有两个疑问:
- Chrome扩展的内容脚本中,是否存在无需修改宿主CSP即可获取宿主
window变量的方法?了解到user_script可以绕过CSP,但不确定Chrome是否支持。 - 我采用动态模块加载内容脚本(方便导入其他脚本),不确定这是否会引发问题,希望保留模块特性。
特别说明:并非向扩展引入外部脚本,CSP报错是因为扩展尝试在宿主上下文执行内联代码。
相关代码
contentScript-portfolio-module-loader.js
// Dynamically load contentScript-portfolio.js as a module (async () => { const contentScript = await import(chrome.runtime.getURL('contentScript-portfolio.js')); contentScript.main(); })();
manifest.json
"content_scripts": [ { "matches": ["https://finance.com/portfolio/positions*"], "run_at": "document_idle", "js": ["contentScript-portfolio-module-loader.js"] } ], "web_accessible_resources": [ { "resources": ["contentScript-portfolio.js", "contentScript-trade-options.js", "util.js"], "matches": ["https://finance.com/*"] } ],
contentScript-portfolio.js
import { parseObjectFromScript } from './util.js'; // Get window vars from the host context function getWindowVariables(variables) { let ret = {}; let scriptContent = ""; for (var i = 0; i < variables.length; i++) { const currVariable = variables[i]; scriptContent += "if (typeof " + currVariable + " !== 'undefined') document.body.setAttribute('tmp_" + currVariable + "', JSON.stringify(" + currVariable + "));\n" } let script = document.createElement('script'); script.id = 'tmpScript'; script.appendChild(document.createTextNode(scriptContent)); (document.body || document.head || document.documentElement).appendChild(script); // <-- CSP ERROR HERE for (var i = 0; i < variables.length; i++) { const currVariable = variables[i]; ret[currVariable] = JSON.parse(document.body.getAttribute("tmp_" + currVariable)); document.body.removeAttribute("tmp_" + currVariable); } document.getElementById(script.id).remove(); return ret; }
解决方案
核心思路是将原本的内联脚本内容提取为独立的外部脚本文件,通过src加载而非内联注入,绕过CSP对内部脚本的限制。同时针对宿主页面懒加载的window变量做了等待处理。
步骤1:更新manifest.json
在web_accessible_resources中添加getPositions.js:
"web_accessible_resources": [ { "resources": ["contentScript-portfolio.js", "contentScript-trade-options.js", "util.js", "getPositions.js"], "matches": ["https://finance.com/*"] } ],
步骤2:创建getPositions.js
该文件将在宿主上下文运行,负责获取目标window变量并通过body属性传递:
// 处理循环引用,避免JSON.stringify失败 function replacerFunc() { const visited = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (visited.has(value)) { return; } visited.add(value); } return value; }; } // 等待宿主页面懒加载的window.positions变量 function getPositions() { if (window.positions) { document.body.setAttribute('tmp_positions', JSON.stringify(window.positions, replacerFunc())); } else { window.requestAnimationFrame(getPositions); } } window.requestAnimationFrame(getPositions);
步骤3:修改contentScript-portfolio.js
改为加载外部脚本,并轮询等待变量传递完成:
var myPositions = null; function getPositions() { return new Promise((resolve, reject) => { let script = document.createElement('script'); script.id = 'tmpScript'; script.src = chrome.runtime.getURL('getPositions.js'); // 注入并运行宿主上下文的脚本 (document.body || document.head || document.documentElement).appendChild(script); // 轮询等待懒加载的变量出现 let posInterval = setInterval(() => { const pos = JSON.parse(document.body.getAttribute('tmp_positions')); if (pos) { clearInterval(posInterval); document.getElementById(script.id).remove(); document.body.removeAttribute('tmp_positions'); resolve(pos); } }, 500); }); } document.onreadystatechange = (e) => { if (document.readyState === 'complete') { getPositions().then((r) => { myPositions = r; }); } };
补充说明:由于宿主页面的window变量是懒加载的,所以获取变量的逻辑和读取属性的逻辑需要分属宿主上下文脚本和扩展内容脚本两个文件。
内容的提问来源于stack exchange,提问作者bjjer
相关产品推荐
相关产品推荐

