火狐浏览器用户界面上目前没有将网页内容保存为PDF格式文件的功能。但扩展API库中提供了用于实现这一功能的API:
var saving = browser.tabs.saveAsPDF(
pageSettings // object
)
这个API可以将整个网页保存为PDF格式文件。并且,它还有一个特别有用的特性,在官方文档中并没有提到,即它可以识别“@media print{}”这个CSS标记。在这个标记中设置CSS样式,就可以对PDF文件内容进行单独的排版,而不影响原来网页的排版显示。有些遗憾的是,目前只有Windows系统下的火狐支持这个API,MAC OS不支持。
扩展API库中还有另外一个很有用的API:
var inserting = browser.tabs.insertCSS(
tabId, // optional integer
details // object
)
它可以将新的CSS样式注入网页中,取代原来的CSS样式。
利用以上两个API及其它一些技巧,本人编写了一个用于保存CSDN博客文章内容的火狐扩展,并且已经放到火狐官网的组件库中了。下载页面
安装好这个扩展后,当访问CSDN博客文章页面时(https://blog.csdn.net),在浏览器的地址栏中会出现一个PDF图标,用于保存博客文章内容。
扩展源码并不复杂,简单地修改一下,可以用于保存其它网站的博客文章。下面贴出这个扩展的源码:
content.js
(() => {
function noprint(sel) {
Array.from(sel.parentNode.children).forEach(e => {
if (e != sel) {
if (!e.classList.contains('noprint')) e.classList.add('noprint');
} else {
if (!e.classList.contains('print')) e.classList.add('print');
}
})
if (sel.parentNode.tagName != 'HTML') noprint(sel.parentNode);
}
let port = browser.runtime.connect({});
port.onMessage.addListener(async e => {
try {
let sel = document.querySelector(".blog-content-box");
if (sel) {
window.stop();
switch (e.command) {
case "c_css":
let more = document.querySelector("#btn-readmore");
if (more) more.click();
let css = '.noprint{display:none !important}';
css += '.print{top:0px !important;margin-top:0px !important;left:0px !important;margin-left:0px !important;right:0px !important;margin-right:0px !important;';
css += 'width:' + sel.offsetWidth + 'px !important;min-width:' + sel.offsetWidth + 'px !important;}';
css += '.xprint{width:' + sel.offsetWidth + 'px !important;top:0px !important;margin-top:0px !important;margin-left:0px !important;}';
css += '.xprint code{white-space:pre-wrap !important;word-wrap:break-word !important;}';
css += 'ol.hljs-ln{width:auto !important;}';
css += '.hljs-ln-code{height:auto !important}';
css = '@media print{' + css + '}';// + css;
await port.postMessage({ css: css, command: "b_css" });
break;
case "c_pdf":
if (!sel.classList.contains('xprint')) sel.classList.add('xprint');
noprint(sel);
document.querySelectorAll('.hljs-ln-numbers,.xprint code+ul,td.gutter').forEach(e => {
if (!e.classList.contains('noprint')) e.classList.add('noprint');
});
await port.postMessage({ command: "b_pdf" });
break;
}
}
} catch (e) {
console.log(e);
}
});
})();
background.js
(() => {
let port;
browser.runtime.onConnect.addListener(p => {
port = p;
port.onMessage.addListener(async e => {
try {
switch (e.command) {
case "b_css":
await browser.tabs.insertCSS({ code: e.css, cssOrigin: 'user' });
await port.postMessage({ command: "c_pdf" });
break;
case "b_pdf":
let res = await browser.tabs.saveAsPDF({ "footerLeft": "", "footerCenter": "&PT", "footerRight": "", "headerLeft": "", "headerCenter": "", "headerRight": "" });
if (res == "saved" || res == "replaced") {
await browser.notifications.create("pdf", {
"type": "basic",
"iconUrl": browser.extension.getURL("icons/pdf.svg"),
"title": "提示",
"message": " PDF文件保存完毕!"
});
} else {
await browser.notifications.create("pdf", {
"type": "basic",
"iconUrl": browser.extension.getURL("icons/pdf.svg"),
"title": "提示",
"message": " PDF文件保存失败!"
});
console.log(res);
};
await new Promise(res => {
setTimeout(() => {
browser.notifications.clear("pdf").then(res);
}, 3000);
});
break;
}
} catch (e) {
console.log(e);
}
});
});
browser.pageAction.onClicked.addListener(async () => {
try {
if (port) {
await port.postMessage({ command: "c_css" });
}
} catch (e) {
console.log(e);
}
});
})();
manifest.json
{
"description": "以PDF文件形式保存CSDN博客的文章内容。",
"author": "chenjs",
"manifest_version": 2,
"name": "PDF Saver For CSDN Blog",
"version": "1.3",
"icons": {
"96": "icons/pdf.svg"
},
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"tabs",
"notifications",
"<all_urls>"
],
"page_action": {
"default_title": "保存文章内容",
"default_icon": {
"96": "icons/pdf.svg"
},
"show_matches": [
"*://blog.csdn.net/*"
]
},
"applications": {
"gecko": {
"id": "{0DCF7DAC-26F3-4ECD-95BB-C2974E5B3041}",
"strict_min_version": "59.0"
}
},
"content_scripts": [
{
"matches": [
"*://blog.csdn.net/*"
],
"js": [
"content.js"
]
}
]
}