Chrome
插件Chrome
浏览器插件?Chrome
浏览器插件可通过自定义界面、观察浏览器事件和修改网络来提升浏览体验。
Chrome
浏览器插件是如何构建的?使用 Web
技术开构建 Chrome
插件:HTML、CSS、JS
。
Chrome
浏览器插件可以做什么?大多数扩展程序都需要某种类型的用户互动才能正常运行。扩展程序平台提供了多种方式来向扩展程序添加互动。这些方法包括从 Chrome
工具栏、侧边栏、上下文菜单等触发的弹出式窗口:
Side panel
)Action
)Menus
)借助 Chrome
的扩展程序 API
,可以改变浏览器的工作方式:
Chrome
页面和设置项:Manifest.json
配置 chrome_settings_overrides
Manifest.json
配置 devtools_page
chrome.notifications API
chrome.history API
chrome.tabs、chrome.tabGroups
和 chrome.windows
等 API
chrome.commands API
chrome.identity API
chrome.management API
chrome.omnibox API
Chrome
设置:chrome.proxy API
chrome.downloads API
chrome.bookmarks API
可以通过注入脚本、拦截网络请求以及使用 Web API
与网页进行交互,来控制和修改 Web
:
JS
和 CSS
文件Tab
页Web
请求Chrome
插件术语Manifest
)Chrome
插件的清单是唯一且必须具有特定文件名的必需文件:manifest.json
;
该文件列出了有关该扩展程序的结构和行为的重要信息。
{
"manifest_version": 3,
"name": "My Chrome Extension",
"version": "0.0.1",
"description": "My Chrome Extension Description"
}
manifest.json
文件必需的字段manifest_version
:用于指定扩展程序使用的清单文件格式版本,目前是 3name
:插件名称,一般情况下 hover
插件图标展示的文案也是 name
version
:插件版本Chrome
应用商店需要的字段description
:插件描述icons
:图标name
version
description
icons
Action
)控制扩展程序在 Chrome
浏览器工具栏中的图标。
{
"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"
}
}
<!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>
Action
字段default_icon
:工具栏展示的图片default_title
:hover
插件图标展示的文案default_popup
:点击图标弹出的页面Background - Service Worker
)Service Worker
在后台运行并处理浏览器事件
在
V3
中使用service worker
替换background
页面
Background
在 manifest V2 V3
版本中的不同之处V2
版本 background
{
"background": {
"scripts": [
"backgroundContextMenus.js",
"backgroundOauth.js"
],
"persistent": false
},
}
V3
版本 background
{
"background": {
"service_worker": "service_worker.js",
"type": "module"
}
}
在上面的 Action
示例中添加以下代码
"background": {
"service_worker": "service_worker.js"
}
Service Worker
Service Worker
Service Worker
fetch
响应的传递时间超过 30 秒时。Service Worker DevTools
点击 Chrome Service Worker
即可弹出 DevTools
Content scripts
)在网页环境中运行 JavaScript
或 CSS
。
通过 content_scripts
进行注册。
Service Worker
示例中添加以下代码"content_scripts": [
{
"matches": ["https://lkcoffee.com/"],
"js": ["content_scripts.js"]
}
]
content_scripts.js
文件代码console.log('this is content scripts')
content_scripts.js
Chrome
插件manifest.json
文件{
"manifest_version": 3,
"name": "My Chrome Extension",
"version": "0.0.1",
"description": "My Chrome Extension Description"
}
浏览器输入:chrome://extensions/
选择刚刚创建的文件夹即可
包含名称、版本、描述、默认图标
到了这一步,一个最最基础的插件已经完成了
action
中的 popup html
页面以谷歌翻译模版为例
action icons
创建 icons
文件夹,并添加图片
.
├── icons
│ └── icon.png
├── manifest.json
popup
文件夹并创建 index.html
页面index.css
和 index.js
文件目前的目录结构
.
├── icons
│ └── icon.png
├── manifest.json
└── popup
├── index.css
├── index.html
└── index.js
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>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div class="plugin_search">
<div class="plugin_search_bar">
<input id="plugin_search_inp" />
<input id="plugin_search_but" type="button" value="翻译" />
</div>
<div class="plugin_span">
<span>翻译此页面</span>
</div>
</div>
<div class="plugin_footer">
<span>? 2015 Google -</span>
<a target="_blank" href="https://google.com/">扩展程序选项</a>
<a target="_blank" href="https://translate.google.com/?source=gtx">Google 翻译</a>
</div>
</body>
<script src="./index.js"></script>
</html>
index.css
*{
padding: 0;
margin: 0;
list-style: none;
text-decoration: none;
}
.plugin_search{
margin: 15px 30px 15px 30px;
}
.plugin_search_bar{
margin: 8px 0 16px 0;
}
#plugin_search_inp{
font-size: 14px;
margin: 2px;
height: 23px;
width: 70%;
}
#plugin_search_but{
box-shadow: none;
background-color: #4d90fe;
background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed);
border: 1px solid #3079ed;
color: #fff;
border-radius: 2px;
cursor: default;
font-size: 11px;
font-weight: bold;
text-align: center;
white-space: nowrap;
margin-right: 16px;
height: 27px;
line-height: 27px;
width: 54px;
outline: 0px;
padding: 0 8px;
box-sizing: content-box;
}
.plugin_span{
color: #4285f4;
}
.plugin_footer{
background-color: #f0f0f0;
color: #636363;
font-family: 'Roboto',sans-serif;
font-size: 11px;
margin-top: 9px;
padding: 10px;
text-align: center;
width: 400px;
}
.plugin_footer a{
color: #4285f4;
}
index.js
const plugin_search_but = document.getElementById('plugin_search_but')
const plugin_search_inp = document.getElementById('plugin_search_inp')
plugin_search_but.onclick = function () {
alert('plugin_search_inp的值为:' + plugin_search_inp.value.trim())
}
Manifest.json
文件添加 action
字段Manifest.json
文件内容{
"manifest_version": 3,
"name": "My Chrome Extension",
"version": "0.0.1",
"description": "My Chrome Extension Description",
"action": {
"default_icon": "icons/icon.png",
"default_title": "Popup Title",
"default_popup": "popup/index.html"
}
}
可以看到小图标已经展示出来了
Hover
图标展示 default_title
字段可以看到我们的 html
和 css
已经生效了
根据我们 index.js 文件中的代码,我们可以在输入框中输入文案,并点击翻译按钮
原来这就是惊喜
Action popup
控制台点击图标,弹出 popup
页面,右键,会有【检查】选项,点击检查按钮
到了这一步,我们可以开发一个有点击动作的插件了
background
(service worker
)popup index.js
中添加一个 setInterval
,每隔几秒弹出一个 alert
setInterval(() => {
alert('哈哈哈哈,我又出来了')
}, 3000)
保存代码之后,刷新插件,点击插件图标弹出 popup
页面,就会定时弹出 alert
框了
但是,当我们点击页面某处,把插件 popup
页面隐藏的时候,就会发现 alert
弹框不会再弹出了,只有在此展现 popup
页面的时候才会重新执行
那如果我想的是不展现 popup
的时候,也执行 alert
弹框,那就需要 background
了
background
文件夹并创建 service_worker.js
文件popup index.js
中的 alert
删除service_worker.js
中写入代码setInterval(() => {
alert('我是 background service worker 中弹出来的')
}, 3000)
.
├── background
│ └── service_worker.js
├── icons
│ └── icon.png
├── manifest.json
└── popup
├── index.css
├── index.html
Manifest.json
文件中添加 background
action
的基础上添加代码"background": {
"service_worker": "background/service_worker.js"
}
Service Worker
弹出控制台这是因为 Service Workers
在插件中主要用于后台任务、推送通知等,而不是直接与用户交互。因此,alert
这类弹窗通常无法直接在 Service Worker
中使用。
service_worker.js
中的代码30s
之后,会发现不刷新了,这个时候我们点击返回,可以看到 service worker
已经被终止了这个时候如果继续点击 service worker
则会继续触发当前程序
alert
,那我们就换个 notifications
用一用借助
chrome.notifications API
,可以通过模板创建内容丰富的通知,并在系统任务栏中向用户显示这些通知。
service worker
中的代码setInterval(() => {
chrome.notifications.create(
{
type: "basic",
title: "Notifications Title",
message: "Notifications message to display",
iconUrl: "../icons/icon.png"
},
(notificationId) => {
console.log('notificationId-->', notificationId)
}
);
}, 3000)
会发现还是会报错
说是 notifications
未定义
这个时候就涉及到 manifest.json
中的另一个字段了,权限(permissions
)字段,使用 Chrome.XXX
的 API
绝大多数的时候都需要在 permissions
字段中添加,permissions
是一个 string[]
manifest.json
文件中添加 permissions
字段添加如下代码
"permissions": [
"notifications"
]
service worker
控制台可以看到控制台中已经有通知的回调返回值了
那通知呢?
平台差异:对于
Mac OS X
用户,通知的显示方式有所不同。用户看到的不是Chrome
自己的通知,而是原生Mac OS X
通知。
点开通知中心,即可看到通知
JS
需要引入怎么办?如果还需要引入 module_1.js
和 module_2.js
manifest.json
中 background
字段添加 type
为 module
"background": {
"service_worker": "background/service_worker.js",
"type": "module"
},
service_worker.js
中 import
引入import './module_1.js'
import './module_2.js'
Service worker
控制台每个
js
里面只是输出了一句话
到这一步,我们已经可以操作后台了
Action(popup)
和 background(service worker)
之间的通信点击翻译的时候,发一条信息
popup.js
中的 Chrome
数据plugin_search_but.onclick = function () {
// alert('plugin_search_inp的值为:' + plugin_search_inp.value.trim())
console.log('chrome', chrome)
}
popup.js
中的点击事件修改下改成点击之后向 Service Worker
发送消息
plugin_search_but.onclick = function () {
// alert('plugin_search_inp的值为:' + plugin_search_inp.value.trim())
console.log('chrome', chrome)
// 向 Service Worker 发送消息
chrome.runtime.sendMessage({
action: 'fromPopup',
message: 'Hello from Popup!'
});
}
service_worker.js
中接收消息即可把之前的代码注释/删除即可
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)
}
);
}
});
点击图标,弹出 popup
页面,点击翻译按钮,点击消息中心
Content Script
)注入如果需要在某个页面中注入脚本进去,那就用到了 content_scripts
更新 content script
之后需要刷新对应的页面
content
文件夹,并创建 index.js
文件console.log('this is content js')
console.log('document', document)
console.log('location', location)
console.log('window', window)
.
├── background
│ ├── module_1.js
│ ├── module_2.js
│ └── service_worker.js
├── content
│ └── index.js
├── icons
│ └── icon.png
├── manifest.json
└── popup
├── index.css
├── index.html
└── index.js
manifest.json
文件中添加 content_scripts
"content_scripts": [
{
"matches": ["https://lkcoffee.com/"],
"js": ["content/index.js"]
}
]
可以看出我们引入了 js
文件和匹配了域名
使用
jquery
content
中创建 index.css
和 jquery.js
(可在 CDN
上下载)index.css
上写入面板样式index.js
上创建 dom
和监听鼠标事件manifest.json
中引入manifest.json
文件
"content_scripts": [
{
"matches": ["https://lkcoffee.com/"],
"css": ["content/index.css"],
"js": ["content/jquery.js", "content/index.js"]
}
]
JS/CSS
注入多个时,按照数组顺序注入,所以如果 JS/CSS
有多个的话,需要注意引用关系。
index.css
文件
#cj_move_page{
width: 400px;
user-select: none;
background: white;
border: 1px solid;
height: 400px;
position: fixed;
right: 0;
bottom: 0;
z-index: 1000001;
}
#cj_move_h3{
text-align: center;
line-height: 40px;
cursor: move;
}
index.js
文件
console.log('this is content js')
console.log('document', document)
console.log('location', location)
console.log('window', window)
//创建页面函数
function createPage () {
const page = $('<div id="cj_move_page"></div>')
const h3 = $('<h3 id="cj_move_h3">my Plugin</h3>')
page.append(h3)
$('body').append(page)
//拖拽
drag(cj_move_h3)
}
createPage()
//拖拽
function drag(ele) {
let oldX, oldY, newX, newY
ele.onmousedown = function (e) {
if (!cj_move_page.style.right && !cj_move_page.style.bottom) {
cj_move_page.style.right = 0
cj_move_page.style.bottom = 0
}
oldX = e.clientX
oldY = e.clientY
document.onmousemove = function (e) {
newX = e.clientX
newY = e.clientY
cj_move_page.style.right = parseInt(cj_move_page.style.right) - newX + oldX + 'px'
cj_move_page.style.bottom = parseInt(cj_move_page.style.bottom) - newY + oldY + 'px'
oldX = newX
oldY = newY
}
document.onmouseup = function () {
document.onmousemove = null
document.onmouseup = null
}
}
}
Chrome
插件,并刷新匹配的页面可以看到右下角有个 content
页面
到这一步我们已经可以注入自己想要的东西了
Content
和 background(Service Worker)
通信如果我想在 content
中点击按钮来触发消息通知要怎么做呢?
button
,再加一个 click
事件先打印下 Chrome
数据,看看有啥
//创建页面函数
function createPage () {
const page = $('<div id="cj_move_page"></div>')
const h3 = $('<h3 id="cj_move_h3">My Chrome Ext Content Page</h3>')
const but1 = $('<button id="cj_but1">消息通知</button>')
page.append(h3)
page.append(but1)
$('body').append(page)
$('#cj_but1').click(async (e) => {
console.log('e', e, chrome)
})
//拖拽
drag(cj_move_h3)
}
button
触发事件可以看到 chrome
有个 runtime
,里面有 sendMessage
可以发送消息,有 onMessage
可以接收消息
content_scripts
里面进行消息发送把 click
事件改一下,加一个消息发送
$('#cj_but1').click(async (e) => {
console.log('e', e, chrome)
// 发送消息
chrome.runtime.sendMessage({action: "fromContent"});
})
Service_worker.js
里面进行消息接收和上面 action
和 service-worker
的通信一样,在一个 if
判断就行
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
信息
就需要权限(permissions
)
tab
权限(permissions
)添加权限的方式有两种
permissions
中添加 tabs
"permissions": [
"notifications",
"tabs"
],
host_permissions
)添加匹配的域名"host_permissions": [
"https://movie.douban.com/"
]
推荐使用第二种,这种符合权限最小化
tab
数据需要在 popup js
中获取 tab
数据
const [tab] = await chrome.tabs.query({
url: ["https://movie.douban.com/*"],
active: true,
currentWindow: true
});
console.log('tab', tab)
tab
信息
popup
向 content
发送消息,content
接收消息popup
中使用 chrome.tabs.sendMessage
发送消息,content
中使用 chrome.runtime.onMessage.addListener
接收消息popup
代码const plugin_search_but = document.getElementById('plugin_search_but')
const plugin_search_inp = document.getElementById('plugin_search_inp')
plugin_search_but.onclick = async function () {
// alert
// alert('plugin_search_inp的值为:' + plugin_search_inp.value.trim())
// console.log('chrome', chrome)
// 向 Service Worker 发送消息
// chrome.runtime.sendMessage({
// action: 'fromPopup',
// message: 'Hello from Popup!'
// });
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'
})
}
}
content
监听代码chrome.runtime.onMessage.addListener((e) => {
console.log('e', e)
})
popup
中使用 chrome.tabs.connect
发送消息,content
使用 chrome.runtime.onConnect.addListener
来接收消息popup
代码if (tab) {
// 使用 chrome.tabs.sendMessage 发送消息
// chrome.tabs.sendMessage(tab.id, {
// action: 'fromPopup2Content'
// })
const connect = chrome.tabs.connect(tab.id, {name: 'fromPopup2Content'});
console.log('connect', connect)
connect.postMessage('这里是弹出框页面,你是谁?')
connect.onMessage.addListener((mess) => {
console.log(mess)
})
}
content
代码// chrome.runtime.onMessage.addListener((e) => {
// console.log('e', e)
// })
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')
})
}
})
popup
,点击翻译按钮content
页面的日志
popup
页面的日志
Fetch
请求V3 版本已经禁止使用
XMLHttpRequest
,所有的请求走的都是fetch
content_scripts
中的 matches
中加入豆瓣域名(www.douban.com)用于获取数据
*: 通配符匹配
"content_scripts": [
{
"matches": ["https://lkcoffee.com/", "https://movie.douban.com/*"],
"css": ["content/index.css"],
"js": ["content/jquery.js", "content/index.js"]
}
]
index.js
文件中加入按钮,点击的时候触发 fetch
请求只更改了 createPage
函数,增加了 but2
、but3
按钮和 click
事件
//创建页面函数
function createPage () {
const page = $('<div id="cj_move_page"></div>')
const h3 = $('<h3 id="cj_move_h3">My Chrome Ext Content Page</h3>')
const but1 = $('<button id="cj_but1">消息通知</button>')
const but2 = $('<button id="cj_but2">content 加载更多</button>')
const but3 = $('<button id="cj_but3">service worker 加载</button>')
page.append(h3)
page.append(but1)
page.append(but2)
page.append(but3)
$('body').append(page)
// 消息通知按钮事件
$('#cj_but1').click(async (e) => {
console.log('e', e, chrome)
chrome.runtime.sendMessage({action: "fromContent"});
})
// content 加载更多按钮事件
$('#cj_but2').click(async (e) => {
const response = await fetch("https://movie.douban.com/j/tv/recommend_groups")
if (!response.ok) {
throw new Error('Network response was not ok')
}
const allData = await response.json()
console.log('content index allData', allData)
})
// service worker 加载按钮事件
$('#cj_but3').click(async (e) => {
console.log('e', e, chrome)
chrome.runtime.sendMessage({action: "fromContentFetch"});
})
//拖拽
drag(cj_move_h3)
}
service_worker.js
里面进行消息监听if (message.action === 'fromContentFetch') {
const response = await fetch("https://movie.douban.com/j/tv/recommend_groups")
if (!response.ok) {
throw new Error('Network response was not ok')
}
const allData = await response.json()
console.log('service worker allData', allData)
}
content
加载更多】service worker
控制台,点击【service worker
加载】到这一步,我们已经可以进行接口请求了,整体的链路已经通了
Cookie
获取
web
开发少不了获取cookie
使用
chrome.cookies API
查询和修改Cookie
,并在这些Cookie
发生更改时收到通知。
这就需要在权限(permissions
)里面添加 cookies
字段以及在主机权限(host_permissions
)里面添加 host
Manifest.json
文件"permissions": [
"notifications",
"cookies"
],
"host_permissions": [
"https://*.douban.com/*",
"https://*.lkcoffee.com/*"
]
Popup
页面获取 cookies
const cookies = await chrome.cookies.getAll({ domain: '.lkcoffee.com'})
console.log('popup cookies--->', cookies)
const urlCookies = await chrome.cookies.getAll({ url: 'https://leaptest03.lkcoffee.com/' })
console.log("popup urlCookies", urlCookies);
Service Worker
获取 cookies
const cookies = await chrome.cookies.getAll({ domain: '.lkcoffee.com'})
console.log('service worker cookies--->', cookies)
Content
获取 cookies
这个直接用 document.cookie
获取即可
Chrome
插件Chrome
应用商店直接压缩包就行,安装的时候加载已解压的扩展程序即可
Chrome
应用商店Chrome
开发者信息中心。ZIP
文件 > 上传。gg
Chrome
插件了,但是这篇文章只是一个基础Manifest.json
文件字段解析