我在使用plasmo写一个谷歌插件。目标网站有一个按钮,点击就是打印数据,数据是通过接口获取的。需求是,通过插件批量自动点击打印,把打印预览的,重命名并另存为PDF。现在我发现,我获取不到打印预览弹窗的内容?
我在使用plasmo写一个谷歌插件。目标网站有一个按钮,点击就是打印数据,数据是通过接口获取的。需求是,通过插件批量自动点击打印,把打印预览的,重命名并另存为PDF。现在我发现,我获取不到打印预览弹窗的内容?
在Plasmo框架中实现自动下载打印预览的PDF,需要绕过Chrome的安全限制。以下是完整方案:
// background.ts (后台服务)
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "savePDF") {
const { tabId, filename } = request;
// 附加调试器获取PDF数据
chrome.debugger.attach({ tabId }, "1.0", () => {
chrome.debugger.sendCommand(
{ tabId },
"Page.printToPDF",
{
transferMode: "ReturnAsBase64",
printBackground: true,
landscape: false,
displayHeaderFooter: false,
margin: { top: 0.4, bottom: 0.4, left: 0.4, right: 0.4 }
},
(pdfData) => {
if (chrome.runtime.lastError) {
console.error("PDF生成失败:", chrome.runtime.lastError);
return;
}
// 转换并下载PDF
const pdfBlob = base64ToBlob(pdfData.data, "application/pdf");
const url = URL.createObjectURL(pdfBlob);
chrome.downloads.download({
url: url,
filename: `${filename}.pdf`,
saveAs: true
}, () => URL.revokeObjectURL(url));
chrome.debugger.detach({ tabId });
}
);
});
}
return true;
});
function base64ToBlob(base64: string, mimeType: string) {
const byteCharacters = atob(base64);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
const slice = byteCharacters.slice(offset, offset + 1024);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
byteArrays.push(new Uint8Array(byteNumbers));
}
return new Blob(byteArrays, { type: mimeType });
}// content.ts (内容脚本)
function automatePrinting() {
// 1. 找到打印按钮并触发点击
const printButton = document.querySelector('button.print-btn')!;
printButton.click();
// 2. 拦截打印请求
window.print = () => {
// 3. 发送生成PDF请求
chrome.runtime.sendMessage({
action: "savePDF",
filename: `report_${new Date().toISOString().slice(0, 10)}`,
tabId: chrome.devtools.inspectedWindow.tabId
});
};
}
// 当数据加载完成后执行
if (document.readyState === 'complete') {
automatePrinting();
} else {
window.addEventListener('load', automatePrinting);
}manifest.json 权限声明:
{
"permissions": [
"debugger",
"downloads",
"scripting"
],
"host_permissions": ["<target_website>/*"]
}plasmo.manifest.json 额外配置:
{
"background": {
"service_worker": "background.ts",
"type": "module"
}
}拦截打印流程:
window.print()方法阻止默认弹窗PDF生成技术:
chrome.debuggerAPI获取原始PDF数据Page.printToPDF命令生成Base64编码的PDF文件下载:
chrome.downloadsAPI保存文件debugger权限,Chrome会显示警告条chrome.debugger的detach操作防止资源泄漏printBackground: true确保CSS样式完整输出如果不想使用debugger API,可考虑:
// 使用html2canvas+jsPDF(有样式失真风险)
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
const printElement = document.getElementById('print-area');
html2canvas(printElement).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
pdf.addImage(imgData, 'PNG', 0, 0, 210, 297);
pdf.save('document.pdf');
});推荐使用debugger方案,它能完美保留原始打印样式。注意在Chrome 111+版本中,可考虑新的chrome.printingAPI(目前仅限ChromeOS)。实际部署时需处理网络延迟,建议在接口返回数据后添加500ms延时再触发打印。已解决。打印是使用的第三方的插件,是通过iframe触发的,所以重写window是无效的。
我给你添加一个全局开关变量,通过修改这个变量的值就能灵活控制:true 恢复默认打印行为(弹窗正常出现),false 禁用打印预览弹窗(保持iframe隐藏且存在),同时保留代码轻量化、无卡顿、无报错的特点:
// 1. 全局开关变量(核心控制)
// true = 恢复默认(打印预览弹窗正常);false = 禁用弹窗(iframe隐藏且存在)
window.disablePrintPreview = false;
// 2. 保存原生print方法(用于开关切换时恢复)
const nativeWindowPrint = window.print;
let nativeIframePrint = null;
// 全局标记:记录所有创建的iframe
const allIframes = new Set();
// 第一步:重写createElement,根据开关拦截/恢复iprint iframe
const originalCreate = document.createElement;
document.createElement = function(tag) {
const el = originalCreate.apply(this, arguments);
if (tag === 'iframe') {
allIframes.add(el);
// 保存原生iframe的print方法(仅第一次保存)
if (!nativeIframePrint && el.contentWindow) {
nativeIframePrint = el.contentWindow.print;
}
// 监听类名变化(匹配小写iprint)
const observer = new MutationObserver(() => {
if (el.className.includes('iprint')) {
interceptPrint(el);
observer.disconnect();
}
});
observer.observe(el, { attributes: true, attributeFilter: ['class'] });
// 加载后根据开关处理
el.onload = () => setTimeout(() => interceptPrint(el), 10);
}
return el;
};
// 第二步:核心拦截/恢复函数(根据开关变量控制)
function interceptPrint(iframeEl) {
if (!iframeEl.className.includes('iprint') || !iframeEl.contentWindow) return;
try {
// 开关为false:禁用弹窗(拦截print,保持iframe隐藏)
if (!window.disablePrintPreview) {
iframeEl.contentWindow.print = () => {
console.log('打印预览已拦截,iprint iframe保持隐藏');
};
Object.freeze(iframeEl.contentWindow.print);
iframeEl.style.display = 'none';
}
// 开关为true:恢复默认(还原原生print,弹窗正常)
else {
if (nativeIframePrint) {
iframeEl.contentWindow.print = nativeIframePrint;
Object.defineProperty(iframeEl.contentWindow, 'print', {
writable: true,
configurable: true
});
}
iframeEl.style.display = 'none'; // 保持iframe默认隐藏(插件原生逻辑)
}
} catch (e) {
console.log('拦截/恢复兼容报错(不影响功能):', e);
}
}
// 第三步:全局print兜底(根据开关控制)
function updateGlobalPrint() {
if (!window.disablePrintPreview) {
window.print = () => {};
Object.freeze(window.print);
} else {
window.print = nativeWindowPrint;
Object.defineProperty(window, 'print', {
writable: true,
configurable: true
});
}
}
// 初始化全局print
updateGlobalPrint();
// 可选:快捷切换开关的函数(控制台直接调用)
function togglePrintPreview(flag) {
window.disablePrintPreview = flag;
updateGlobalPrint(); // 更新全局print
// 对已存在的iprint iframe生效
document.querySelectorAll('.iprint').forEach(iframe => {
interceptPrint(iframe);
});
console.log(`打印预览已${flag ? '恢复' : '禁用'}`);
}// 禁用打印预览弹窗(iframe隐藏且存在,符合你的需求)
window.disablePrintPreview = false;
// 恢复默认行为(打印预览弹窗正常出现)
window.disablePrintPreview = true;// 禁用弹窗(推荐,会自动更新所有iframe和全局print)
togglePrintPreview(false);
// 恢复默认
togglePrintPreview(true);| 开关值 | 打印预览弹窗 | iprint iframe | 页面状态 |
|---|---|---|---|
| false | 不显示 | 存在且隐藏 | 无卡顿/报错 |
| true | 正常显示 | 存在且隐藏 | 恢复插件默认逻辑 |
window.print和iframe的print原生方法,避免切换开关时无法还原;iprint iframe会自动更新行为,无需刷新页面;window.disablePrintPreview是核心开关,默认false(禁用弹窗),你可随时修改;togglePrintPreview(flag)让开关切换更方便,无需手动改变量+刷新;你直接执行这段代码后,在控制台输入togglePrintPreview(false)(禁用)或togglePrintPreview(true)(恢复)即可快速切换,完全满足你的需求~
11 回答1.2k 阅读
3 回答956 阅读✓ 已解决
2 回答952 阅读✓ 已解决
3 回答927 阅读✓ 已解决
2 回答646 阅读
3 回答962 阅读
3 回答958 阅读
打印预览是浏览器的功能吧,不是网页功能,在 JS 控制的范围以外。所以你获取不到。
应该没什么解决方法,因为必须调用浏览器 API,没有就是没有。可以考虑换个技术方案,比如走 puppeteer。