Chrome扩展Manifest V2转V3时点击按钮触发CSP错误求助
本人是自学的业余开发者,为工作场景开发了一款仅在Chrome上运行的扩展。一年前将其转为Manifest V3,但部分功能无法正常运行,因此将这部分功能保留在开发者模式运行的Manifest V2小扩展中。由于V2即将停止支持,现需解决V3中的问题:执行代码中document.querySelector("#FormLinkPanel_ButtonSave").click();行时,控制台抛出内容安全策略(CSP)错误,提示违反script-src指令,需要unsafe-inline等配置。该代码在Manifest V2中可正常执行。
相关执行代码:
if (document.querySelector("[id^=GridViewTissueTeamSignatures_][id$=_TextBoxESigPassword]")) { // Select all elements with IDs starting with 'GridViewTissueTeamSignatures_' and ending with '_TextBoxESigPassword' document.querySelectorAll("[id^=GridViewTissueTeamSignatures_][id$=_TextBoxESigPassword]").forEach(function(element) { var num = element.id.split("_"); var span = document.createElement('span'); span.innerHTML = ' <button id="simbutton_' + num[1] + '" class="header-button blue" style="display:none;">Save</button>'; element.insertAdjacentElement('afterend', span); }); // Add click event listeners to checkboxes document.querySelectorAll("[id^=GridViewTissueTeamSignatures_][id$=_CheckBoxESigSign]").forEach(function(checkbox) { checkbox.addEventListener('click', function(e) { var num = this.id.split("_"); var simbutton = document.getElementById("simbutton_" + num[1]); if (this.checked) { simbutton.style.display = 'inline'; } else { simbutton.style.display = 'none'; } }); }); // Add click event listener for simbuttons document.addEventListener('click', function(e) { if (e.target.id && e.target.id.startsWith('simbutton')) { e.preventDefault(); //document.querySelector("#GridViewTissueTeamSignatures_ctl02_SignatureBlockTissueTeamMember_ctl00_CheckBoxESigInvalidate").click(); document.querySelector("#FormLinkPanel_ButtonSave").click(); return false; } }); }
Manifest V3配置:
{ "name":"example.com Extension", "description":"Helps with the **** part of example.com.", "version":"3.0.8.6", "manifest_version":3, "icons":{ "48":"icon48.png", "128":"icon128.png" }, "permissions":[ "https://example.com/*" ], "content_scripts":[ { "run_at":"document_end", "matches":[ "https://example.com/*" ], "css":[ "css/style.css", "css/jquery.multiselect.css" ], "js":[ "js/dependencies/download.js", "js/dependencies/pdf-lib.min.js", "js/dependencies/jquery.min.js", "js/dependencies/jquery-ui.min.js", "js/dependencies/simulate.js", "js/dependencies/JsBarcode.code128.min.js", "js/dependencies/DYMO.Label.Framework.2.0.2.js", "js/functions.js", "js/content.js", "js/main.js", "js/QAfunction.js", "js/addons/savebutton.js", "js/addons/hospitalNames.js", "js/addons/blood2.js", "js/addons/pdf-lib/trif.js", "js/addons/pdf-lib/versiti.js", "js/addons/pdf-lib/lnh.js", "js/addons/pdf-lib/VRL.js", "js/addons/pdf-lib/BBAcult.js", "js/addons/pdf-lib/Solvita.js", "js/addons/pdf-lib/BodyBag.js", "js/addons/pdf-lib/coroner.js", "js/addons/pdf-lib/chain.js", "js/addons/pdf-lib/property.js", "js/addons/pdf-lib/shipping.js", "js/addons/pdf-lib/VF.js", "js/addons/pdf-lib/Ossium.js", "js/addons/pdf-lib/OssiumCult.js" ] } ], "web_accessible_resources": [{ "resources": ["images/*.gif","images/*.png","labels/*.label","pdf/*.pdf","html/*.html"], "matches": [ "https://example.com/*" ], "extension_ids": [] }] }
CSP错误的核心原因是:页面的#FormLinkPanel_ButtonSave按钮大概率带有内联onclick事件处理代码,而目标网站的内容安全策略禁用了unsafe-inline脚本。Manifest V2中内容脚本的执行环境相对宽松,允许触发这类内联脚本;但Manifest V3对隔离性要求更严格,同时网站的CSP会直接阻止内联脚本运行,导致调用click()时触发报错。
另外,你通过span.innerHTML创建按钮的方式属于内联HTML操作,虽然当前未触发报错,但不符合Manifest V3的安全规范,存在潜在风险。
1. 替换内联HTML创建按钮的方式
改用原生DOM方法创建按钮,避免内联HTML带来的安全风险:
// 替换原代码中创建按钮的逻辑 document.querySelectorAll("[id^=GridViewTissueTeamSignatures_][id$=_TextBoxESigPassword]").forEach(function(element) { var num = element.id.split("_"); var span = document.createElement('span'); // 改用DOM方法创建按钮 const button = document.createElement('button'); button.id = `simbutton_${num[1]}`; button.className = 'header-button blue'; button.style.display = 'none'; button.textContent = 'Save'; span.appendChild(button); element.insertAdjacentElement('afterend', span); });
2. 绕过CSP触发保存按钮的逻辑
方案A:直接调用按钮的事件处理函数(推荐)
跳过触发click事件的流程,直接获取并执行按钮绑定的事件处理函数:
// 替换原代码中的document.querySelector("#FormLinkPanel_ButtonSave").click(); const saveBtn = document.querySelector("#FormLinkPanel_ButtonSave"); // 处理原生事件绑定 const clickEvents = getEventListeners(saveBtn).click || []; clickEvents.forEach(evt => evt.listener.call(saveBtn)); // 如果按钮是通过jQuery绑定的事件,替换为下面的代码: // $(saveBtn).triggerHandler("click");
方案B:注入脚本到页面上下文执行点击
如果方案A无效,可通过chrome.scripting.executeScript将点击逻辑注入到页面上下文执行(需要先添加scripting权限):
步骤1:更新Manifest V3配置,添加scripting权限
{ // ... 其他配置不变 "permissions":[ "https://example.com/*", "scripting" ] }
步骤2:修改内容脚本中的点击逻辑
// 替换原代码中的document.querySelector("#FormLinkPanel_ButtonSave").click(); chrome.tabs.query({active: true, currentWindow: true}, (tabs) => { chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, func: () => { document.querySelector("#FormLinkPanel_ButtonSave").click(); } }); });
if (document.querySelector("[id^=GridViewTissueTeamSignatures_][id$=_TextBoxESigPassword]")) { // Select all elements with IDs starting with 'GridViewTissueTeamSignatures_' and ending with '_TextBoxESigPassword' document.querySelectorAll("[id^=GridViewTissueTeamSignatures_][id$=_TextBoxESigPassword]").forEach(function(element) { var num = element.id.split("_"); var span = document.createElement('span'); // 改用DOM方法创建按钮 const button = document.createElement('button'); button.id = `simbutton_${num[1]}`; button.className = 'header-button blue'; button.style.display = 'none'; button.textContent = 'Save'; span.appendChild(button); element.insertAdjacentElement('afterend', span); }); // Add click event listeners to checkboxes document.querySelectorAll("[id^=GridViewTissueTeamSignatures_][id$=_CheckBoxESigSign]").forEach(function(checkbox) { checkbox.addEventListener('click', function(e) { var num = this.id.split("_"); var simbutton = document.getElementById("simbutton_" + num[1]); if (this.checked) { simbutton.style.display = 'inline'; } else { simbutton.style.display = 'none'; } }); }); // Add click event listener for simbuttons document.addEventListener('click', function(e) { if (e.target.id && e.target.id.startsWith('simbutton')) { e.preventDefault(); // 调用按钮绑定的事件处理函数 const saveBtn = document.querySelector("#FormLinkPanel_ButtonSave"); const clickEvents = getEventListeners(saveBtn).click || []; clickEvents.forEach(evt => evt.listener.call(saveBtn)); return false; } }); }
内容的提问来源于stack exchange,提问作者Lemuel P

