我的第一个浏览器插件网页一键上传的开发历史

本文讲述了作者在2022年更新浏览器插件,兼容Chrome和Firefox,使用window.getSelection和fetch实现网页内容抓取。介绍了v2和v3插件定义的区别,以及在Firefox中对CSP和权限要求的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

一键上传选中的网页内容,实现知识快速收藏。如飞书剪存,有道云剪报,MrDoc速记。这些插件目前都很难兼容全部的浏览器,Chrome支持好一些。

早在2008年,我参考了有道云一键上传,实现了一个简单的浏览器插件,能方便保存网页内容到个人网站。

时移势迁,光阴荏苒,这一小插件使用了十多年。2022年暑假,由于浏览器最新版本的限制,全新做了一个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即可。此方法能装上,但功能有点问题,所以我还是选择了正式途径。

插件安装

  • Chrome插件打包为crx文件后,直接拖放到扩展程序界面即可;也可以解压,然后选”加载已解压的扩展程序“
  • firefox插件,从扩展程序里,选”从文件安装“即可

总结几点

  • 后端API需要使用https
    当使用manifest_version: 3时,fetch()函数会报错,TypeError: NetworkError when attacking to fetch resource,网上查了半天,发现v2和v3的诸多不同,且Content Security Policy (CSP) 的限制,需要以https或wss协议访问外部才行。
  • firefox需要v3插件有一个addon ID
  • 要有一个checkbox,让用户选择是否允许插件申请的权限
  • 要签名才能正常安装运行,没有自签名之说

链接

-Firefox插件开发文档
-火狐Unbranded_Builds
-plasmo: The Browser Extension Framework

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北极象

如果觉得对您有帮助,鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值