前言
一键上传选中的网页内容,实现知识快速收藏。如飞书剪存,有道云剪报,MrDoc速记。早在2008年,我参考了有道云一键上传,实现了一个简单的浏览器插件,能方便保存网页内容到个人网站。这些插件目前都很难兼容全部的浏览器,Chrome支持好一些。
时移势迁,光阴荏苒,这一小插件已经使用了十多年。之前作为网页书签存在,现在已经变成浏览器插件了。但之前的插件版本只支持Chrome和Opera,不支持firefox,而我平常用Chrome访问国外站点,firefox访问国内站点和个人开发网站。所以近期终于得闲开发了firefox版本。
个人使用,所以也没美化。
实现原理
调用window.getSelection()获得选中的网页内容,然后发给popup脚本,再在popup里调用fetch()函数上传数据。
网页剪报功能简述
以飞书剪存为例,它是一个兼容于各大浏览器的扩展程序。它可以将网页正文保存至飞书文档,且自动剥离广告。
一键保存网页正文,告别手动复制粘贴: 浏览到喜欢的网页,点击飞书剪存,即可将网页内容保存至你的飞书文档中。
智能剥离网页广告,告别无用干扰信息: 飞书剪存可以智能识别并去除悬浮或嵌在网页中的广告,还你干净、无噪的网页内容。
插件定义
v2插件定义
这个是飞书的:
{"name": "__MSG_appName__","description": "__MSG_CreationDoc_Operation_AppStoreBrief__","version": "1.0.26","manifest_version": 2,"default_locale": "zh_CN","browser_action": {"default_icon": {"16": "assets/icons/48.png","24": "assets/icons/48.png","32": "assets/icons/48.png"},"default_title": "__MSG_appName__"},"icons": {"16": "assets/icons/48.png","32": "assets/icons/48.png","48": "assets/icons/48.png","128": "assets/icons/128.png"},"background": {"persistent": false,"scripts": ["background.js"]},"content_scripts": [{"matches": ["http://*/*","https://*/*"],"run_at": "document_idle","js": ["content.js"]}],"permissions": ["tabs","activeTab","contextMenus","cookies","storage","*://*/*"],"web_accessible_resources": ["assets/*","app.html","app.js","page-post-frame-id.html","page-post-frame-id.js"],"incognito": "not_allowed","content_security_policy": "script-src 'self' 'unsafe-eval' https://*.bytegoofy.com blob: https://*.ibytedapm.com ; object-src 'self'","homepage_url": "https://www.feishu.cn/hc/zh-CN/articles/606278856233"
}
v3版本的插件定义
{"manifest_version": 3,"name": "CMS一键上传8.0","version": "8.0","homepage_url": "http://www.icodelib.cn","background": {"scripts": ["background.js"]},"content_scripts": [{"matches": ["<all_urls>"],"js": ["jquery-1.8.3.js","content-script.js", "inject.js"]}],// 浏览器右上角图标设置,browser_action、page_action、app必须三选一"action": {"default_title": "一键上传","default_popup": "popup.html","default_icon": {"16": "icons/16x16.png","48": "icons/48x48.png","128": "icons/128x128.png"}},"host_permissions": ["<all_urls>"],"web_accessible_resources": [{"resources": ["inject.js", "popup.js"], "matches": ["<all_urls>"]}],"permissions":["contextMenus", // 右键菜单"tabs", // 标签"activeTab","scripting","notifications", // 通知"webRequest", // web请求"webRequestBlocking","webNavigation","declarativeNetRequestWithHostAccess","declarativeNetRequest","declarativeNetRequestFeedback","storage"]
// "content_security_policy": "script-src 'self' 'unsafe-eval'"
}
实现
在content-script里获得选中内容,然后发送:
// 发送消息给后台
async function sendMessageToBackground(html) {try { await browser.runtime.sendMessage({src: window.location.href,title: document.title,content: html})// showMsg('上传完成')} catch (err) {console.error(err);// showMsg('上传失败')}}
在popup.js里监听消息,然后获得数据后,调用后端API:
browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { const formData = new FormData();formData.append('src', message.src);formData.append('title', message.title);formData.append('content', message.content);try {const response = await fetch(target_site, {method: 'POST',mode: "cors",body: formData,})const result = await response.json();debug(result)} catch (err) {debug(err)}// sendResponse({success: 'ok'})return true;
});
火狐需要用户选择接受外部访问
const permissions = {// This origin is listed in host_permissions:origins: ["<all_urls>"],
};const checkbox_host_permission = document.getElementById("checkbox_host_permission");
checkbox_host_permission.onchange = async () => {if (checkbox_host_permission.checked) {let granted = await browser.permissions.request(permissions);if (!granted) {// Permission request was denied by the user.checkbox_host_permission.checked = false;}} else {try {await browser.permissions.remove(permissions);} catch (e) {// While Chrome allows granting of host_permissions that have manually// been revoked by the user, it fails when revoking them, with// "Error: You cannot remove required permissions."console.error(e);checkbox_host_permission.checked = true;}}
};
browser.permissions.contains(permissions).then(granted => {checkbox_host_permission.checked = granted;
});
发布
对于Firefox而言,插件开发完后,需要上传到https://addons.mozilla.org/签名才能安装。
无品牌的firefox构建版本可以安装不用签名的插件。在about:config里将xpinstall.signatures.required设为false即可。此方法能装上,但功能有点问题,所以我还是选择了正式途径。
注意事项
- 后端API需要使用https
当使用manifest_version: 3时,fetch()函数会报错,TypeError: NetworkError when attacking to fetch resource,网上查了半天,发现v2和v3的诸多不同,且Content Security Policy (CSP) 的限制,需要以https或wss协议访问外部才行。 - firefox需要插件有一个addon ID
链接
-Firefox插件开发文档
-火狐Unbranded_Builds