1. 介绍
Chrome 插件可用于在谷歌浏览器上控制当前页面的一些操作,可自主控制网页,提升效率。
平常我们可在谷歌应用商店中下载谷歌插件来增强浏览器功能,作为开发者,我们也可以自己开发一个浏览器插件来配合我们的日常学习工作,提升幸福感。
在谷歌插件开发者官网,可以阅读插件开发文档来写插件,插件开发主要是 JavaScript,因此对前端开发者很友好
2. 快速入门
2.1 manifest.json 配置
-
新建一个文件夹,就叫 ChromePlugin
-
在 ChromePlugin 根目录下新建 manifest.json 文件
manifest 文件是插件的配置文件,只有在该 json 文件中增加了配置项,才允许增加自定义的内容,下面是常见的 json 文件配置
{"manifest_version": 3,"name": "chromePlugin","version": "1.0.0","description": "我的第一个谷歌插件","icons": {"16": "./assets/images/icon16.png","32": "./assets/images/icon32.png","48": "./assets/images/icon48.png","128": "./assets/images/icon128.png"},
}
基本配置:
- manifest_version:manifest 的版本,是谷歌针对插件开发的版本配置,目前主流都是 3 版本,2 版本已经停止更新
- name:插件的名称,即插件在谷歌商店以及使用时的名称
- version:插件的版本
- description:插件的描述信息
- icons:描述插件图标的大小和文件路径,目前提供 4 种规格的图片,按照上述配置分别传入不同大小的图片即可,图片地址可在根目录下创建一个 assets文件放置静态资源
其他配置可见下文中的详细解释
到这里,一个 chrome 插件已经可以被加载出来了
打开谷歌浏览器,输入 chrome://extensions ,启用开发者模式(右上角),再点击「加载已解压的扩展程序」然后找到 ChromePlugin 这个文件夹打开,便可以将这个插件加载进来了,只不过当前插件没有任何功能,所以也是灰色的
2.2 action 配置
在 manifest.json 中除了上述的基础配置外,还可配置 action,action 字面上意思就是我们点击右上角的插件图标时的反应,比如打开一个交互面板等等。这种交互的集合就是 action。
首先先在 manifest.json 中配置 action,可见官网针对 action 的配置信息,action配置。
default_popup 可在点击插件图片时弹出一个弹层,弹层的内容就是 default_popup 所对应的 html 文件。
{"manifest_version": 3,"name": "chromePlugin","version": "1.0.0","description": "我的第一个谷歌插件","icons": {"16": "./assets/images/icon16.png","32": "./assets/images/icon32.png","48": "./assets/images/icon48.png","128": "./assets/images/icon128.png"},"action": {"default_title": "Click Me","default_popup": "./action/index.html","default_icon": "./assets/images/icon32.png"}
}
可在根目录下新建一个 action 文件夹,然后再创建一个 index.html 文件,写一个简单的测试文件
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.hello {width: 100px;}</style>
</head>
<body><div class="hello">hello simon</div><script src="./index.js"></script>
</body>
</html>
再次加载该插件,点击插件图标,可在图标下方看到一个弹层,弹层显示的是 html 文件中的内容
如果想写 js 代码,不能直接在 html 中创建一个 script 标签,然后写代码,插件中不允许 html 中有内联脚本,因此需要将脚本代码作为一个单独的文件,再通过 script 标签来引入
2.3 content_scripts
如果我们想给插件添加功能,那就需要再配置 content_scripts(内容脚本),插件content_scripts。
不是所有的 chrome 的 api 方法都能在 content_scripts 中使用
content_scripts 的特性:
可在页面打开、或页面加载结束、或者页面空闲时注入
共享页面的 dom,因此脚本文件可以操作页面上的 dom
JS 隔离,脚本文件的 js 不会影响到页面中的 js,也无法引用页面 js 中的函数或变量
content_scripts 的注入方式也有多种:静态注入、动态注入、编码注入
- 静态注入,即在 manifest.json 文件中指定 content_scripts 中的 js 文件,比较常用
- 动态注入,通过 chrome.scripting.registerContentScripts 方法注入
- 编码注入,通过 chrome.scripting.executeScript
在 manifest.json 中可传入的属性
属性 | 类型 | 描述 |
---|---|---|
matches | 字符串数组 | 必填,指定将内容脚本注入到哪些网页 |
css | 字符串数组 | 可选,要注入到匹配页面的 css 文件 |
js | 字符串数组 | 可选,要注入到匹配页面的 js 文件 |
run_at | document_start |document_end |document_idle | 可选,指定何时将脚本注入到网页中,默认值document_idle document_idle: 页面空闲时 document_end:在dom完成之后,图片等资源加载前注入脚本 document_start:在dom完成前就注入脚本 |
在 manifest.json 中配置 content_scripts
{"manifest_version": 3,"name": "chromePlugin","version": "1.0.0","description": "我的第一个谷歌插件","icons": {"16": "./assets/images/icon16.png","32": "./assets/images/icon32.png","48": "./assets/images/icon48.png","128": "./assets/images/icon128.png"},"action": {"default_title": "Click Me","default_popup": "./action/index.html","default_icon": "./assets/images/icon32.png"}, "content_scripts": [{"matches": ["https://*/*"],"css": ["./src/index.css"],"js": ["./src/main.js"]}]
}
// 计算图片大小
function getByte(src){return fetch(src).then(function(res){return res.blob()}).then(function(data){return (data.size/(1024)).toFixed(2)+'kB';})
}
// 在页面上的图片的title属性上显示出来
function showInfo(el,byte){const html=`真实尺寸:${el.naturalWidth}*${el.naturalHeight}\n显示尺寸:${el.width}*${el.height}\n存储大小:${byte}`;el.title=html;
}
document.addEventListener('mouseover',function(e){//移动到图片元素上时、则显示信息if(e.target.tagName=='IMG'){getByte(e.target.src).then(byte=>{showInfo(e.target,byte)});}
},true)
2.4 background
配置后台脚本,用于处理插件的主要事件的处理,后台脚本指定了属性 service_worker,其实后台脚本是 Service Worker 上处理的。我们插件上的一些主要逻辑的处理都可以放到这个脚本文件中
{"manifest_version": 3,"name": "chromePlugin","version": "1.0.0","description": "我的第一个谷歌插件","icons": {"16": "./assets/images/icon16.png","32": "./assets/images/icon32.png","48": "./assets/images/icon48.png","128": "./assets/images/icon128.png"},"action": {"default_title": "Click Me","default_popup": "./action/index.html","default_icon": "./assets/images/icon32.png"}, "content_scripts": [{"matches": ["https://*/*"],"css": ["./src/index.css"],"js": ["./src/main.js"]}],"background": {"service_worker": "./src/server_worker.js"}
}
2.5 右键菜单
当我们点击鼠标右键时,可打开一个菜单,其中有复制粘贴等功能,插件也可以自定义右键的菜单,同样需要在 manifest.json 中添加权限配置
{"manifest_version": 3,"name": "chromePlugin","version": "1.0.0","description": "我的第一个谷歌插件","icons": {"16": "./assets/images/icon16.png","32": "./assets/images/icon32.png","48": "./assets/images/icon48.png","128": "./assets/images/icon128.png"},"action": {"default_title": "Click Me","default_popup": "./action/index.html","default_icon": "./assets/images/icon32.png"}, "content_scripts": [{"matches": ["https://*/*"],"css": ["./src/index.css"],"js": ["./src/main.js"]}],"permissions": ["contextMenus"]
}
permissions 是允许增加权限配置,右键菜单就是一种权限配置,开启右键菜单权限后,需要再通过代码新增右键的菜单,这个时候就需要用到上一步的 background 中的配置了
在 service_worker.js 文件中新增一个右键菜单,可使用 chrome.contextMenus.create 方法来创建
chrome.contextMenus.create({type: 'normal',title: '插件的右键菜单',contexts:['all'],id:'menu-1'
});
保存后,就能在右键看到一个菜单选项了
2.6 permissions
在上一步创建的右键菜单中,涉及到权限设置,只有在 permissions 添加了对应权限后,才允许去创建菜单,权限除了右键菜单外,还有其他的权限,权限列表,常见的有:
- audio: chrome.audio,控制连接到系统的音频设备
- bookmarks: chrome.bookmarks,读取和更改书签的权限
- clipboardRead:通过 clipboard API 读取剪切板上的数据去粘贴
- clipboardWrite: 通过 clipboard API 剪切内容并复制到剪切板
- contextMenus: chrome.contextMenus 控制右键菜单的权限
- cookies: chrome.cookies 查询和更改 cookies 的权限
- desktopCapture: chrome.desktopCapture 截取屏幕内容的权限
- downloads: chrome.downloads 管理下载内容的权限
- favicon:通过 Favicon 读取网站页面的图标的权限
- history: chrome.history 读取和更改浏览记录的权限
- idle: chrome.idle 读取浏览器是否处于空闲状态的权限
- proxy: chrome.proxy 管理浏览器的代理设置
- sidePanel:chrome.sidePanel 打开侧边栏的权限
- storage: chrome.storage 存储、检索和跟踪用户数据的更改的权限
- system.cpu: chrome.system.cpu,查询 CPU 数据的权限
- system.memory: chrome.system.memory,获取物理内存的权限
当涉及到权限问题时,就需要先在 permissions 添加了对应权限后,才允许使用 chrome.xxx 再使用对应的功能。
2.7 options
options 就是选项页面,可用于在插件的右键选项中进行配置跳转,也可以在插件管理页面点击详情后打开插件选项,当增加该配置项后,选中插件再右键时,会增加一个「选项」的菜单,点击后会跳转到配置的页面。要展示该选项需要在 manifest.json 中配置 options_ui
{"manifest_version": 3,"name": "chromePlugin","version": "1.0.0","description": "我的第一个谷歌插件","icons": {"16": "./assets/images/icon16.png","32": "./assets/images/icon32.png","48": "./assets/images/icon48.png","128": "./assets/images/icon128.png"},"action": {"default_title": "Click Me","default_popup": "./action/index.html","default_icon": "./assets/images/icon32.png"}, "content_scripts": [{"matches": ["https://*/*"],"css": ["./src/index.css"],"js": ["./src/main.js"]}],"permissions": ["contextMenus"],"options_page": "./options/index.html", // chrome40 之前的写法"options_ui": { // chrome40 之后的写法"page": "./options/index.html","chrome_style": true // 添加一些默认样式}
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div>这是 options 点击后打开的页面</div>
</body>
</html>
3. 页面通信
在 manifest.json 中可通过不同配置来实现插件的扩展能力,比如 content_scripts、background、options、actions 都有脚本文件,那如何在不同的脚本文件中传递数据呢,比如在 content_scripts 是在网页中运行的,其中的数据如何共享给 background,可以通过页面通信来传递数据。
官网文档
3.1 发送数据
// 从内容脚本 content_scripts 发送数据到扩展页面(background、options、actions)
(async () => {const response = await chrome.runtime.sendMessage({greeting: "hello"});console.log(response);
})();
// 从扩展页面(background、options、actions) 发送数据到内容脚本(content_scripts)
(async () => {// 需要先获取当前的tab页面const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});console.log(response);
})();
不同的发送方,发送数据的方式也是不同的,在使用时需要有所区别
3.2 接收数据
接收数据的方式是一致的,都是通过 chrome.runtime.onMessage
// 接收数据
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");if (request.greeting === "hello")//处理完消息后、通知发送方sendResponse({farewell: "goodbye"});}
);