02-谷歌插件开发
教程:https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions
官方教程:https://developer.chrome.com/docs/extensions/mv2/architecture-overview?hl=zh-cn
谷歌商店:https://chromewebstore.google.com/
扩展程序:chrome://extensions/
优秀博客:https://xieyufei.com/2021/11/09/Chrome-Plugin.html
插件构成
chrome 插件通常由以下几部分组成:
manifest.json:相当于插件的 meta 信息,包含插件的名称、版本号、图标、脚本文件名称等,这个文件是每个插件都必须提供的,其他几部分都是可选的。
background script:可以调用全部的 chrome 插件 API,实现跨域请求、网页截屏、弹出 chrome 通知消息等功能。相当于在一个隐藏的浏览器页面内默默运行。
功能页面:包括点击插件图标弹出的页面(简称 popup)、插件的配置页面(简称 options)。
content script:早期也被称为 injected script,是插件注入到页面的脚本,但是不会体现在页面 DOM 结构里。content script 可以操作 DOM,但是它和页面其他的脚本是隔离的,访问不到其他脚本定义的变量、函数等,相当于运行在单独的沙盒里。content script 可以调用有限的 chrome 插件 API,网络请求收到同源策略限制。
插件的架构可以参考:https://developer.chrome.com/docs/extensions/mv2/architecture-overview/
重点说明以下几点:
browser action 和 page action:这俩我们可以理解为插件的按钮。browser action 会固定在 chrome 的工具栏。 而 page action 可以设置特定的网页才显示图标,在地址栏的右端,如下图: 大部分插件点击之后会显示 UI,也就是上文描述的插件功能页面部分,一般称为 popup 页面,如下图: popup 无法通过程序打开,只能由用户点击打开。点击 popup 之外的区域会导致 popup 收起。
page action 和 browser action 分别由 manifest.json 的 page_action 和 browser_action 字段配置。
由于 content script 受到同源策略的限制,所以一般网络请求都交给 background script 处理。
content script、插件功能页面、background script 之间的通信架构如下图:
创建自己的第一个谷歌插件
新建文件夹,命名CE-Demo,增加一个manifest.json的文件,内容如下:
{
"name": "CE-Demo",
"description": "CE-Demo's description shows here!",
"version": "0.0.1",
"manifest_version": 3
}
该文件描述了插件的基本属性信息、代码的运行路径等。后面我会不断地丰富其内容。
加载插件 这里我们直接载入整个目录(尚未打包):
地址栏输入chrome://extensions进入插件管理页面。 选中界面右上角的开发者模式 点击左上角的加载已解压的扩展程序,并选中刚才的插件文件夹
可以点击扩展程序按钮,鼠标移动到插件右侧的固定按钮,固定到标签栏里。
插件不会热更新,记得每次修改代码后点击刷新icon载入最新代码
好了,这样就安装好了我们自己的一个谷歌插件,只不过这个插件什么功能都没有。下面我们需要一点一点加功能。
注册 background.js
它是一种后台脚本,浏览器会在插件安装或重新加载时扫描它并初始化(事件的监听等)。它是整个插件的重要组成部分。必须在manifest里配置。
{
"name": "CE-Demo",
"description": "I am a demo",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"persistent": true
}
}
persistent属性定义了插件常驻后台的方式;当其值为true时,表示插件将一直在后台运行,无论其是否正在工作; 当其值为false时,表示插件在后台按需运行,这就是Chrome后来提出的Event Page(非持久性后台)。 Event Page是基于事件驱动运行的,只有在事件发生的时候才可以访问;这样做的目的是为了能够有效减小插件对内存的消耗, 如非必要,请将persistent设置为false。 persistent属性的默认值为true
同时在插件目录添加background.js文件,包含以下代码:
当脚本安装成功的时候,就会执行下面这个事件
chrome.runtime.onInstalled.addListener(() => {
console.log('后台脚本运行成功!')
});
添加popup弹框页面
popup如果要写js逻辑的话,必须要采用新建js,然后引入的方式来写!!!
popup页面是你点击工具栏的图标的时候,展示的弹窗。 我们需要覆盖默认的popup界面,修改manifest:
{
...
"action": {
"default_popup": "popup.html"
},
...
}
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="width: 200px;height: 100px">
我是popup弹窗
</div>
</body>
</html>
内容脚本 Content Scripts
插件提供了内容脚本 Content Scripts(CS)的概念,当用户打开并访问某个网站时,浏览器将CS注入网站的文档里执行。
因此,我们需在CS脚本里编写记录的逻辑。
往manifest里添加CS:
{
// 在此处使用数组往页面中引入JS或者CSS。
"content_scripts":
[
{
// 当matches返回true时才会注入
// 比如 ["http://foo.com/bar/*", "https://foobar.com/bar/*"]
// 表示在foobar.com下的bar路径下会发生注入
// 一个特殊的值: "<all_urls>" 表示匹配所有地址 *://*/* 也是同样的效果
"matches": ["<all_urls>"],
// 多个JS会按照配置的顺序引入到页面
"js": ["content/index.js", "content/index2.js"],
// 多个CSS会按照配置的顺序引入到页面
"css": ["foo/bar.css"],
// 在什么时机引入到页面, 默认document_idle。 三个可选择的值: "document_start"、"document_end"、"document_idle"
"run_at": "document_idle",
// 是否运行在页面所有的frame中
"all_frames": true
},
{
// 因为是数组,所以可以配置多项
}
],
}
*://*/*
表示 匹配全部的url- document_start:所有css加载完毕,但DOM尚未创建时
- document_end:DOM创建完成,但图片及frame等子资源尚未加载时
- document_idle:document_end之后,window.onload之前
设置图标
插件在工具栏上显示的图标,也由 action 对象指定,具体为 default_icon 字段。插件在管理页面、权限警示框等场景也会显示图标,可以通过 manifest 配置 icons 字段指定
{
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
},
"icons": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
}
右键菜单
方法一
这种写法的一级菜单默认就是插件的名字
manifest.json
{
"permissions": ["contextMenus"]
}
background.js
const menuList = [
{
id: 'element-plus',
title: 'element-plus',
onclick: function () {
chrome.tabs.create({
url: 'https://element-plus.gitee.io/zh-CN/component/table-v2.html',
});
},
},
{
id: 'vue-api',
title: 'vue-api',
onclick: function () {
chrome.tabs.create({
url: 'https://cn.vuejs.org/api/',
});
},
},
];
// 批量创建菜单
chrome.runtime.onInstalled.addListener(function () {
menuList.forEach((item) => {
chrome.contextMenus.create({
id: item.id,
title: item.title,
contexts: ['page'],
});
});
});
// 实现点击事件监听
chrome.contextMenus.onClicked.addListener(function (info, tab) {
let selectItem = menuList.filter((item) => item.id === info.menuItemId)[0];
if (selectItem) {
selectItem.onclick(info, tab);
}
});
方法二
这种写法,可以自定义一级菜单
chrome.contextMenus.create({
title: '右键快捷菜单', //菜单的名称
id: '10', //一级菜单的id
contexts: ['page'], // 选中文字时用:selection
});
chrome.contextMenus.create({
title: '百度', //菜单的名称
id: '1101', //二级菜单的id
parentId: '10', //父级id
contexts: ['page'],
});
chrome.contextMenus.create 函数创建的参数解释
【参数】
createProperties ( object )
type ( optional enumerated string ["normal", "checkbox", "radio", "separator"] )
右键菜单项的类型。默认为“normal”。
title ( optional string )
右键菜单项的显示文字;除非为“separator”类型,否则此参数是必须的。如果类型为“selection”,您可以在字符串中使用%s显示选定的文本。例如,如果参数的值为 "Translate '%s' to Pig Latin",而用户还选中了文本“cool”,那么显示在菜单中的将会是 "Translate 'cool' to Pig Latin"。
checked ( optional boolean )
Checkbox或者radio的初始状态:true代表选中,false代表未选中。在给定的radio中只能有一个处于选中状态。
contexts ( optional array of string ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"] )
右键菜单项将会在这个列表指定的上下文类型中显示。默认为“page”。
onclick ( optional function )
当菜单项被点击时触发的函数。
【参数】
info ( OnClickData )
右键菜单项被点击时相关的上下文信息。
tab ( Tab )
右键菜单项被点击时,当前标签的详细信息。
parentId ( optional integer )
右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单。
documentUrlPatterns ( optional array of string )
这使得右键菜单只在匹配此模式的url页面上生效(这个对框架也适用)。详细的匹配格式见:模式匹配页面。
targetUrlPatterns ( optional array of string )
类似于documentUrlPatterns,但是您可以针对img/audio/video标签的src属性和anchor标签的href做过滤。
enabled ( optional boolean )
启用或者禁用此菜单项,启用为true,禁用为false。默认为true。
callback ( optional function )
在创建完菜单项后触发。如果创建过程中有错误产生,其详细信息在Chrome.extension.lastError中。
插件右键菜单点击插件名跳转主页设置
{
"name": "CE-Demo",
"homepage_url": "https://blog.share888.top/"
}