前言
当你看到这篇文章说明你已经很好奇 Chrome 的各种扩展(插件)是如何开发创造出来的,你或许也在思考自己该从何下手才能掌握浏览器扩展的开发。那么,这篇文章将用一个 Demo 尝试满足你的好奇并解答你的疑惑。
基本知识
- HTML 标签的基本认识
- CSS 选择器和基本布局样式
- JavaScript 基本语法知识以及 Dom 操作
前端基础十分简单,如果你暂时没有前端开发的基本知识可以到 MDN 进行学习。
浏览器扩展 WebExtensions
扩展的能力
- 提升或补充网站的功能,比如稀土掘金为各位开发者提供的工具插件,提供包括记笔记等功能。
- 操控网页内容,诸如去除页面广告类扩展。
- 添加开发工具,有 Vue 和 React 的 devTools,以及前端较为广泛使用的 FeHelper。
- 为网页注入脚本,有用户脚本管理工具 Tampermonkey。
扩展中的文件
.
├── icon
│ ├── icon_120x120.png // 不同大小的图片,用于在工具栏等处显示
│ ├── icon_48x48.png
│ └── icon_80x80.png
├── manifest.json // 扩展必须包含的 json 文件,可以使用“//”写行注释
├── popup.html // 弹出页面
├── scripts
│ ├── background.js // 后台运行的脚本文件
│ └── popup.js // 用于操作 Dom
└── styles
└── inline.css // 样式
复制代码
manifest.json 这是唯一一个在每个扩展里面必须存在的文件。它包含了关于这个扩展插件基本的元数据(metadata),比如它的名字、版本和所需权限。并且,它也对扩展中其他文件进行了链接。
{
"manifest_version": 2, // 指定扩展使用的 manifest.json 的版本
"name": "文章浏览量增加器", // 扩展的名称
"short_name": "upCount",
"description": "自动刷新文章的浏览量", // 扩展的描述信息
"version": "0.1.0", //版本号
"background": { // 引入一个或者多个后台脚本文件,以及一个可选的后台页面文件
"scripts": [
"./scripts/background.js" // 引入的路径
],
"persistent": true // 是否持续运行
},
"browser_action": {
"default_icon": "./icon/icon_120x120.png",
"default_title": "文章浏览量增加器",
"default_popup": "./popup.html"
},
"icons": {
"120": "./icon/icon_120x120.png",
"80": "./icon/icon_80x80.png",
"48": "./icon/icon_48x48.png"
},
"permissions": [
"activeTab",
"tabs",
"http://*/*",
"https://*/*"
]
}
复制代码
popup.html 弹出的页面
inline.css 引入的样式文件
popup.js 用于操作 Dom 和与 background.js 通信
// 获取 Dom 以便对其操作
const saveEl = document.querySelector('#save');
const startEl = document.querySelector('#start');
const pauseEl = document.querySelector('#pause');
const urlField = document.querySelector('#urlField');
const tbody = document.querySelector('#urlTableData');
const resetEl = document.querySelector('#reset');
const tfootEl = document.querySelector('tfoot');
// 获取本地存储数据
function getSites() {
const str = window.localStorage.getItem('sites') || '[]';
return JSON.parse(str);
}
// URL 校验方法
function checkURL(url) {
const urlReg = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
return urlReg.test(url);
}
function buttonDisable(sites) {
if (sites.length) {
tfootEl.classList.add('hidden');
resetEl.removeAttribute('disabled');
startEl.removeAttribute('disabled');
pauseEl.removeAttribute('disabled');
} else {
tfootEl.classList.remove('hidden');
resetEl.setAttribute('disabled', 'disabled');
startEl.setAttribute('disabled', 'disabled');
pauseEl.setAttribute('disabled', 'disabled');
}
}
function createTbodyContent(sites) {
tbody.innerHTML = '';
for (let item of sites) {
const trEl = document.createElement('tr');
trEl.innerHTML = `
<td class='urlCol' title='${item}'>${item}</td>
<td class='operationCol'>
<div class='del' id='${item}' title='删除${item}'>删除</div>
</td>`
tbody.prepend(trEl)
}
}
// 接受 background 返回值并存储,同时确定按钮是否 disabled
function callbackSites(res) {
const sites = JSON.parse(res).sites;
createTbodyContent(sites);
window.localStorage.setItem('sites', JSON.stringify(sites));
buttonDisable(sites)
}
// 根据本地获取的 url 生成表格
createTbodyContent(getSites());
buttonDisable(getSites());
// 全局点击事件绑定
document.addEventListener("click", (e) => {
if (checkURL(e.target.id)) {
chrome.runtime.sendMessage({
type: 'del',
data: e.target.id
}, callbackSites);
}
switch (e.target.id) {
case 'save': {
if (urlField.value.trim() === '') {
alert('请输入 URL');
return;
}
let result = checkURL(urlField.value.trim())
if (result) {
chrome.extension.sendMessage({
type: 'save',
data: urlField.value.trim()
}, (res) => {
callbackSites(res);
urlField.value = '';
});
} else {
alert('你输入的 URL 不正确')
}
break;
}
case 'start': {
chrome.tabs.query({
active: true,
currentWindow: true
}, (tabs) => {
chrome.extension.sendMessage({
type: 'start',
windowId: tabs[0].windowId,
tabId: tabs[0].id,
}, (res) => {
window.localStorage.setItem('status', JSON.stringify(JSON.parse(res).status));
console.log(JSON.parse(res).status);
})
})
break;
}
case "pause": {
chrome.extension.sendMessage({
type: 'pause'
})
break;
}
case "reset": {
chrome.extension.sendMessage({
type: 'reset'
}, callbackSites)
}
}
})
复制代码