一、什么是chrom插件?
就是 extensions 扩展,可以通过自定义界面、监听一些浏览器的事件和处理网络请求提升浏览器使用体验的工具。
二、chrom 浏览器插件是如何构建的?
就是使用web技术写 html、css、js 构建的
现在开发chrom 插件可以使用 vite/webpack 结合vue/react ,pinia/zustand(状态管理),native-ui/ant design(ui 库) 开发插件
三、chrom 浏览器插件可以做什么?
1、设计界面和用户交互
比如 chrome 工具栏、侧边栏、菜单等触发
一些相关api
侧边栏 chrome.slidePanel 可以把内容托管在浏览器侧边栏中的网页主要内容旁边。
操作项 action 控制插件图标在工具栏显示
菜单项 menus 在上下文菜单添加项
2、控制浏览器
借助浏览器的一些插件api 改变浏览器的工作方式
manifest.json 配置 chrom_settings_overrides 可以覆盖页面
显示通知 利用 chrome.notifications API
管理历史记录 chrome.history
控制标签页和窗口 chrome.tabs 、chrome.tanGroups 、chrome.windows
下载管理 chrom.dowloads
等等
3、控制网络等
可以通过注入脚本、拦截网络请求以及请用 web api 与 网页交互
比如 注入 js 和css、访问当前tab、监听网络请求、发起网络请求、录音录屏等
四、chrome 插件的核心概念
manifest
列出了有关该插件的结构和行为的重要信息。
{
"manifest_version": 3,
"name": "My Chrome Extension",
"version": "0.0.1",
"description": "My Chrome Extension Description"
}
manifest_version:用于指定插件使用的清单文件格式版本,目前是 3
name:插件名称
version:插件版本
如果需要发布插件市场需要
description:插件描述
icons:图标
Service Worker
Service worker 是一个基于事件的脚本,在浏览器后台运行,用于处理数据、协调扩展中的不同的任务。
示例
{
"background": {
"service_worker": "service_worker.js",
"type": "module"
}
}
将脚本导入 Service Worker 的方法有两种:import 语句和 importScripts() 方法。
如需使用 import 语句,请将 "type" 字段添加到 manifest.json 文件中并指定 "module"。
import { tldLocales } from './locales.js';
importScripts('locales.js');
常用的相关事件
chrome.action
当有用户与插件的工具栏图标互动时触发,无论该操作是针对特定网页(标签页)还是整个插件。
chrome.management
提供与安装、卸载、启用和停用插件相关的事件。
chrome.notifications
提供与用户与插件生成的系统通知互动相关的事件。
chrome.permissions
指示用户何时授予或撤消插件权限。
chrome.runtime
提供与插件生命周期相关的事件、插件的其他部分发送的消息,以及可用插件或 Chrome 更新的通知。
chrome.storage.onChanged
每当任何 StorageArea 对象被清除或某个键的值被更改或设置时触发。请注意,每个 StorageArea 实例都有自己的 onChanged 事件。
chrome.webNavigation
提供有关导航请求状态的信息。
Permissions 权限
插件在浏览器中获取的功能和数据访问权限,通过声明所需的权限,插件可以使用相关功能。
插件可以请求使用相应键指定的以下类别的权限:
permissions
包含已知字符串列表中的项
optional_permissions
由用户在运行时(而不是在安装时)授予
content_scripts.matches
包含一个或多个匹配模式,允许内容脚本注入到一个或多个主机中
host_permissions
包含一个或多个匹配格式,可授予对一个或多个主机的访问权限
optional_host_permissions
由用户在运行时(而不是在安装时)授予
示例
{
"permissions": [
"activeTab",
"contextMenus",
"storage"
],
"optional_permissions": [
"topSites",
],
"host_permissions": [
"https://www.developer.chrome.com/*"
],
"optional_host_permissions":[
"https://*/*",
"http://*/*"
]
}
Content script 内容脚本
内容脚本是在网页环境中运行的文件,可以操作 DOM,读取浏览器访问网页的信息和修改。
示例
"content_scripts": [
{
"matches": ["https://www.taobao.com/"],
"js": ["content_scripts.js"]
}
]
content_scripts.js 文件代码
console.log('this is content scripts')
打开这个网站 选择console 就可以看到输出
如果选择sources 选择 content scripts 就可以看到 文件代码
内容脚本相关api
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Action
浏览器工具栏中显示的图标或按钮,用户可以单击该图标或按钮来执行插件提供的功能或操作。
配置示例
{
"manifest_version": 3,
"name": "My Chrome Extension",
"version": "0.0.1",
"description": "My Chrome Extension Description",
"icons": {
"16": "icons/icon_16.png",
"32": "icons/icon_32.png",
"48": "icons/icon_48.png",
"128": "icons/icon_128.png"
},
"action": {
"default_icon": "icons/icon.png",
"default_title": "Popup Title",
"default_popup": "popup.html"
}
}
popup.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>
*{
padding: 0;
margin: 0;
}
div{
width: 300px;
height: 300px;
text-align: center;
line-height: 300px;
background: gray;
}
</style>
</head>
<body>
<div>popup html</div>
</body>
</html>
Messaging 消息传递
一般来说,消息传递是指 action、content script、service worker 三者之间进行消息传递。
任一端都可以监听另一端发送的消息,并在同一通道上做出响应
消息传递API
runtime API
runtime.sendMessage()
runtime.onMessage.addListener()
runtime.connect()
runtime.onConnect.addListener()
runtime.onMessageExternal
runtime.onConnectExternal
tabs API
tabs.sendMessage()
tabs.connect()
一次性请求
runtime.sendMessage() 或 tabs.sendMessage() 发送消息,runtime.onMessage 监听消息
长期有效的连接
runtime.connect() 或 tabs.connect() 发送消息,runtime.onConnect 监听消息
与其他插件进行通信
runtime.sendMessage 或 runtime.connect 发送消息,runtime.onMessageExternal 或 runtime.onConnectExternal 监听消息
接收从网页发送的消息
manifest.json 中使用 externally_connectable 指定与哪些网站通信
Action(popup) 和 background(service worker) 之间的通信
popup 中使用 chrome.runtime.sendMessage 进行消息发送
chrome.runtime.sendMessage({
action: 'fromPopup',
message: 'Hello from Popup!'
});
在 service_worker.js 中接收消息chrome.runtime.onMessage.addListener
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
if (message.action === 'fromPopup') {
chrome.notifications.create(
{
type: "basic",
title: "Notifications Title",
message: "Notifications message to display",
iconUrl: "../icons/icon.png"
},
(notificationId) => {
console.log('notificationId-->', notificationId)
}
);
}
});
Content script 和 background(Service Worker) 通信
在 content_scripts 中添加点击事件进行消息发送
content_scripts 中使用 chrome.runtime.sendMessage 进行消息发送
$('#contentBut').click(async (e) => {
// 发送消息
chrome.runtime.sendMessage({action: "fromContent"});
})
在 Service_worker.js 里面进行消息接收
service_worker 中使用 chrome.runtime.onMessage.addListener 进行消息监听,通过 .action 来判断来源
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
if (message.action === 'fromContent') {
chrome.notifications.create(
{
type: "basic",
title: "Notifications Title",
message: "Notifications message to display",
iconUrl: "../icons/icon.png"
},
(notificationId) => {
console.log('notificationId-->', notificationId)
}
);
}
});
Action(popup) 和 content 通信
因为 content 是注入页面的脚本,所以和 content 通信,需要获取当前 tab 信息
1. 获取当前 tab 信息
const [tab] = await chrome.tabs.query({
url: ["https://www.test.com/*"],
active: true,
currentWindow: true
});
console.log('tab', tab)
2、popup 向 content 发送消息,content 接收消息
popup 中使用 chrome.tabs.sendMessage 发送消息,content 中使用 chrome.runtime.onMessage.addListener 接收消息
1、popup 代码
plugin_search_but.onclick = async function () {
const [tab] = await chrome.tabs.query({
url: ["https://movie.douban.com/*"],
active: true,
currentWindow: true
});
console.log('tab', tab)
if (tab) {
// 使用 chrome.tabs.sendMessage 发送消息
chrome.tabs.sendMessage(tab.id, {
action: 'fromPopup2Content'
})
}
}
2、content 使用 chrome.runtime.onMessage.addListener 进行消息监听
chrome.runtime.onMessage.addListener((e) => {
console.log('e', e)
})
popup 中使用 chrome.tabs.connect 发送消息,content 使用 chrome.runtime.onConnect.addListener 来接收消息
1、popup 代码
plugin_search_but.onclick = async function () {
const [tab] = await chrome.tabs.query({
url: ["https://movie.douban.com/*"],
active: true,
currentWindow: true
});
console.log('tab', tab)
if (tab) {
const connect = chrome.tabs.connect(tab.id, {name: 'fromPopup2Content'});
console.log('connect', connect)
connect.postMessage('这里是弹出框页面,你是谁?')
connect.onMessage.addListener((mess) => {
console.log(mess)
})
}
}
2、content 中使用 chrome.runtime.onConnect.addListener 进行消息监听
chrome.runtime.onConnect.addListener((res) => {
console.log('contentjs中的 chrome.runtime.onConnect:',res)
if (res.name === 'fromPopup2Content') {
res.onMessage.addListener(mess => {
console.log('contentjs中的 res.onMessage.addListener:', mess)
res.postMessage('哈哈哈,我是contentjs')
})
}
})
与其他插件进行通信
如需监听传入请求和来自其他插件的连接,需使用 runtime.onMessageExternal 或 runtime.onConnectExternal 方法
// 一次性请求
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id === blocklistedExtension)
return; // don't allow this extension access
else if (request.getTargetData)
sendResponse({targetData: targetData});
else if (request.activateLasers) {
var success = activateLasers();
sendResponse({activateLasers: success});
}
});
// 长期连接
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
要向其他插件发送消息,需要其他插件的 ID
// 插件 ID
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// 一次性请求
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// 长期请求
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Storage 存储
Chrome 插件有一个专门的 storage API,用来进行数据存储。
"permissions": [
"storage"
]
功能
所有插件上下文都可以访问 Storage API
可序列化的 JSON 值存储为对象属性
Storage API 是异步的,支持批量读取和写入操作
即使用户清除缓存和浏览记录,这些数据仍会保留
即使在无痕模式拆分后,存储的设置也会保留
匹配模式
用于指定插件的内容脚本或页面操作脚本在哪些 URL 匹配模式下执行
五、插件安装
1、Chrome 应用商店安装
浏览器输入:https://chromewebstore.google.com/
搜索和安装自己需要的插件即可
2、安装包安装
浏览器输入:chrome://extensions/
点击【加载已解压的拓展程序】按钮
选择已解压的文件夹即可
六、Manifest.json 文件全字段示例
{
"manifest_version": 3,
"name": "My Chrome Ext Name",
"version": "0.0.1",
"description": "My Chrome Extension description",
"icons": {
"16": "public/icons/icon_16.png",
"32": "public/icons/icon_32.png",
"48": "public/icons/icon_48.png",
"128": "public/icons/icon_128.png"
},
"action": {
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
"default_title": "Click Me",
"default_popup": "popup.html"
},
"author": {
"email": "user@example.com"
},
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
"chrome_settings_overrides": {
"homepage": "https://www.homepage.com",
"search_provider": {
"name": "name.__MSG_url_domain__",
"keyword": "keyword.__MSG_url_domain__",
"search_url": "https://www.foo.__MSG_url_domain__/s?q={searchTerms}",
"favicon_url": "https://www.foo.__MSG_url_domain__/favicon.ico",
"suggest_url": "https://www.foo.__MSG_url_domain__/suggest?q={searchTerms}",
"instant_url": "https://www.foo.__MSG_url_domain__/instant?q={searchTerms}",
"image_url": "https://www.foo.__MSG_url_domain__/image?q={searchTerms}",
"search_url_post_params": "search_lang=__MSG_url_domain__",
"suggest_url_post_params": "suggest_lang=__MSG_url_domain__",
"instant_url_post_params": "instant_lang=__MSG_url_domain__",
"image_url_post_params": "image_lang=__MSG_url_domain__",
"alternate_urls": [
"https://www.moo.__MSG_url_domain__/s?q={searchTerms}",
"https://www.noo.__MSG_url_domain__/s?q={searchTerms}"
],
"encoding": "UTF-8",
"is_default": true
},
"startup_pages": ["https://www.startup.com"]
},
"chrome_url_overrides" : {
"bookmarks": "myBookmarks.html",
"history": "myHistory.html",
"newtab": "myNewtab.html"
},
"commands": {
"run-foo": {
"suggested_key": {
"default": "Ctrl+Shift+Y",
"mac": "Command+Shift+Y"
},
"description": "Run 'foo' on the current page."
}
},
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self';",
"sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
},
"cross_origin_embedder_policy": {
"value": "require-corp"
},
"cross_origin_opener_policy": {
"value": "same-origin"
},
"declarative_net_request" : {
"rule_resources" : [{
"id": "ruleset_1",
"enabled": true,
"path": "rules_1.json"
}, {
"id": "ruleset_2",
"enabled": false,
"path": "rules_2.json"
}]
},
"default_locale": "en",
"devtools_page": "devtools.html",
"export": {
"allowlist": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
]
},
"externally_connectable": {
"ids": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
],
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
],
"accepts_tls_channel_id": false
},
"homepage_url": "https://guoqiankun.blog.csdn.net/",
"permissions": [
"activeTab",
"contextMenus",
"storage"
],
"optional_permissions": [
"topSites"
],
"host_permissions": [
"https://www.developer.chrome.com/*"
],
"optional_host_permissions":[
"https://*/*",
"http://*/*"
],
"import": [
{
"id": "cccccccccccccccccccccccccccccccc"
},
{
"id": "dddddddddddddddddddddddddddddddd",
"minimum_version": "0.5"
}
],
"incognito": "not_allowed",
"key": "ThisKeyIsChromeKey",
"minimum_chrome_version": "120.0.6099.129",
"oauth2": {
"client_id": "yourExtensionOAuthClientIDWillGoHere.apps.googleusercontent.com",
"scopes":[""]
},
"omnibox": {
"keyword": "newTab"
},
"options_page": "index.html",
"options_ui": {
"page": "options.html",
"open_in_tab": false
},
"requirements": {
"3D": {
"features": ["webgl"]
}
},
"sandbox": {
"pages": [
"page1.html",
"directory/page2.html"
]
},
"short_name": "short name",
"side_panel": {
"default_path": "sidepanel.html"
},
"storage": {
"managed_schema": "schema.json"
},
"tts_engine": {
"voices": [
{
"voice_name": "Alice",
"lang": "en-US",
"event_types": ["start", "marker", "end"]
},
{
"voice_name": "Pat",
"lang": "en-US",
"event_types": ["end"]
}
]
},
"update_url": "https://myhost.com/mytestextension/updates.xml",
"version_name": "1.0 beta",
"web_accessible_resources": [
{
"resources": [ "test1.png", "test2.png" ],
"matches": [ "https://web-accessible-resources-1.glitch.me/*" ]
}, {
"resources": [ "test3.png", "test4.png" ],
"matches": [ "https://web-accessible-resources-2.glitch.me/*" ],
"use_dynamic_url": true
}
],
"file_browser_handlers": [
{
"id": "upload",
"default_title": "Save to Gallery",
"file_filters": [
"filesystem:*.jpg",
"filesystem:*.jpeg",
"filesystem:*.png"
]
}
],
"file_handlers": [
{
"action": "/open_text.html",
"name": "Plain text",
"accept": {
"text/plain": [".txt"]
},
"launch_type": "single-client"
}
],
"file_system_provider_capabilities": {
"configurable": true,
"watchable": false,
"multiple_mounts": true,
"source": "network"
},
"input_components": [{
"name": "ToUpperIME",
"id": "ToUpperIME",
"language": "en",
"layouts": ["us::eng"]
}]
}