面向初学者的Chrome扩展程序开发教程:理论部分

面向初学者的Chrome扩展程序开发教程:理论部分

欢迎阅读 Chrome 浏览器扩展开发新手入门!本系列的主要目的是介绍 Chrome 扩展的基本概念以及如何创建扩展。在本系列结束时,你将使用 Javascript 制作一个 Pomodoro 计时器扩展。

要创建任何 Chrome 扩展,我们都必须依赖 Chrome 开发人员的文档。无论是 Chrome API 还是在 Chrome 网站上发布,这都是与扩展相关的唯一真相来源。Chrome API 是特殊的 Javascript 函数或特殊的  manifest 文件(稍后详述)字段,允许我们通过 Chrome 扩展与 Chrome 浏览器进行交互。它们由 chrome 关键字和 API 名称  chrome.[API name]表示。

Chrome 开发者页面

Manifest 文件

manifest 文件是每个扩展的核心。通过它,你可以告诉网络浏览器你的 Chrome 浏览器扩展应该做什么,以及扩展由哪些 Javascript、Html 和 Css 文件组成。

根据 Chrome 浏览器开发人员的文档

扩展的 manifest 是唯一必需的文件,它必须有一个特定的文件名: manifest.json。它还必须位于扩展的根目录中。清单记录了重要的元数据、定义了资源、声明了权限,并确定了要在后台和页面上运行的文件。

这意味着 manifest 文件是一个 JSON(Javascript 对象符号)格式的文件。

让我们来看一些代码示例:

从本文的 GitHub 代码库中下载代码,在代码编辑器中打开  chrome extension basics 文件夹。它应该是这样的

📦 Chrome-Extension-Series
┣ 🎨 icon.png

创建新的 manifest.json 文件

 📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 **manifest.json**

添加新的 manifest.json 文件后,在其中添加以下代码。

{
"manifest_version": 3,
"name": "First Extension",
"version": "1.0.0",
"description": "My first extension",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
}
}

以上是 manifest 文件的基本定义。 manifest_version 告诉浏览器使用哪个版本(3 是最新版本);如何定义 manifest 文件取决于 manifest 版本。最佳做法是使用最新版本,因为旧版本已经过时。名称字段只是我们扩展的名称。version 字段代表 Chrome 浏览器扩展的版本。description 字段只是 Chrome 扩展的描述。icons 字段是一个对象,包含扩展图标在需要时可缩放的不同尺寸。

manifest 文件准备就绪后,就可以在浏览器上加载 Chrome 扩展了。

Chrome浏览器扩展入口

  1. 打开 Chrome 浏览器
  2. 点击最右侧的汉堡包图标(如上图所示)
  3. 点击扩展程序 > 管理扩展程序
  4. 然后打开开发者模式
  5. 点击 “加载已解压的扩展程序”
  6. 最后,选择包含 manifest.json 文件的  chrome extension basics 文件夹

按照上述步骤操作后,你的 Chrome 浏览器扩展应该已经加载到浏览器中了。如果我们查看 Chrome 浏览器的扩展页面,就会看到已加载的扩展。

Chrome扩展列表

点击此处了解有关 manifest 文件的更多信息

既然我们已经加载了基本扩展,那么就来添加一些交互功能吧。popup 是每个扩展都有的交互元素;当你点击扩展图标时,它就会出现在工具栏上。

根据文档:

使用 chrome.action API 控制 Google Chrome 浏览器工具栏中的扩展图标。

扩展图标

在上面的演示中,你可以看到我们的扩展图标(钉住它)是灰色的,点击它时什么也不会显示,这是因为弹出页面尚未创建。要创建弹出页面,我们必须在 manifest 文件中执行操作设置(action setting)。

  {
"manifest_version": 3,
"name": "First Extension",
"version": "1.0.0",
"description": "My first extension",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
+ "action": {
+    "default_icon": {
+     "16": "icon.png",
+        "24": "icon.png",
+        "32": "icon.png"
+      },
+      "default_title": "My Extension Action Title",
+  "default_popup": "popup.html"
+  }
}

传递给 default_popup 字段的值就是点击扩展时将加载的 HTML 文件。现在创建一个名为 popup.html 的新文件。

 📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 manifest.json
┣ 📄 **popup.html**
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>First Extension</title>
</head>
<body>
<h1>My First Extension</h1>
</body>
</html>

现在我们可以在浏览器中查看弹出页面了。注:在更改 manifest 文件时,必须始终重新加载扩展,如下面的演示所示。

重新加载扩展

现在,我们已经在弹出页面上加载了 Html 文件,可以使用 Javascript 使其互动,并使用 css 为其设计样式。创建 popup.css 文件,并将 popup.html 连接到该文件。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>First Extension</title>
+   <link rel="stylesheet" href="popup.css">
......
</html>
📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 manifest.json
┣ 📄 popup.html
┣ 📄 **popup.css**
body {
width: 400px;
height: 400px;
}
h1 {
color: blue;
}

用前面的代码设置了 popup.html 的样式后,弹出文字现在是蓝色的了。

现在,让我们使用 Javascript 显示当前时间,使弹出窗口更具互动性。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>First Extension</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<h1>My First Extension</h1>
<h2 id="time"></h2>
</body>
<script src="popup.js"></script>
</html>

现在创建 popup.js 文件。

📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 manifest.json
┣ 📄 popup.html
┣ 📄 popup.css
┣ 📄 **popup.js**
const timeElement = document.getElementById('time')
const currentTime = new Date().toLocaleTimeString()
timeElement.textContent = `The time is: ${currentTime}`

现在,当你点击弹出窗口时,扩展程序就会显示当前时间。

Chrome扩展demo

让我们使用 chrome.action API 方法之一来增强扩展的交互性。让我们在显示文本 TIME 的弹出窗口上设置一个徽章。我们将使用 setBadgeText 方法。

 setBadgeText 方法

popup.js 中添加以下代码。

const timeElement = document.getElementById('time')
const currentTime = new Date().toLocaleTimeString()
timeElement.textContent = `The time is: ${currentTime}`
chrome.action.setBadgeText({
text: "TIME",
}, () => {
console.log('Finished setting badge text')
})

现在,我们的扩展有了一个显示文本 TIME 的 icon,控制台也会显示该信息。这样就能正常工作了。

Dcidug9

Options 页面

选项页面是用户与 Chrome 浏览器扩展互动的另一种方式,因此让我们为扩展创建一个选项页面。如果右键单击扩展图标,就会显示 options,但由于尚未创建选项页面,所以选项是禁用的。

要创建选项页面,我们必须在 manifest.json 中添加 options 字段。

{
"manifest_version": 3,
"name": "First Extension",
"version": "1.0.0",
"description": "My first extension",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"action": {
"default_icon": {
"16": "icon.png",
"24": "icon.png",
"32": "icon.png"
},
"default_title": "My Extension Action Title",
"default_popup": "popup.html"
},
+  "options_page": "options.html"
}

现在创建一个 options.html 文件,这将是我们的扩展选项页面文件。

📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 manifest.json
┣ 📄 popup.html
┣ 📄 popup.css
┣ 📄 popup.js
┣ 📄 options.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Extension Options</title>
</head>
<body>
<h1>My Extension Options</h1>
</body>
</html>

现在,我们可以右键单击图标,选择 “options” 选项,查看扩展的选项页面。

扩展的选项页面

让我们为 options 页面设计风格并添加互动性。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="options.css">
<title>My Extension Options</title>
</head>
<body>
<h1>My Extension Options</h1>
<input id="name-input" type="text" placeholder="Enter your name!" />
<button id="save-btn">Save Options</button>
</body>
+ <script src="options.js"></script>
</html>

我们的项目目录结构如下:

📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 manifest.json
┣ 📄 popup.html
┣ 📄 popup.css
┣ 📄 popup.js
┣ 📄 options.html
┣ 📄 options.css
┣ 📄 options.js

options.css 中添加以下代码。

h1 {
color: green;
}

options.js 中添加以下代码。

const nameInput = document.getElementById("name-input")
const saveBtn = document.getElementById("save-btn")
saveBtn.addEventListener("click", () => {
console.log(nameInput.value)
})

当点击 save Options 按钮时,options.js 中的代码会获取输入框中的文本并登录到控制台。

Chrome扩展选项页面

Chrome 存储 API

我们可以与用户输入选项页面的数据进行交互,但目前还没有办法在选项页面上存储任何数据。我们需要一个存储系统来存储选项页面上的数据,并在弹出页面上访问这些数据。为此,我们使用了 chrome 存储 API

根据文档:

使用 chrome.storage API 来存储、检索和跟踪用户数据的更改。

存储 API 有一些注意事项,但您可以认为它的工作原理与任何使用 Javascript 的基本本地存储 API 类似。

在使用存储 API 之前,有两件事需要了解:

  1. 使用存储 API 需要用户许可(添加到 manifest 文件中)。
  2. 主要有两种存储 API
    • chrome.storage.sync API
    • chrome.storage.local API两者的主要区别在于,chrome.storage.sync API 会在不同的浏览器会话中同步所有 Chrome 浏览器存储数据,而 chrome.storage.local 则只针对单个会话。不过,在处理用户选项时,使用 chrome.storage.sync 是个不错的做法,因为您希望数据能在不同的浏览器实例中保存。

在使用存储 API 之前,我们必须在 manifest.json 文件的 permissions 字段中添加存储 API。

{
"manifest_version": 3,
"name": "First Extension",
"version": "1.0.0",
"description": "My first extension",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"action": {
"default_icon": {
"16": "icon.png",
"24": "icon.png",
"32": "icon.png"
},
"default_title": "My Extension Action Title",
"default_popup": "popup.html"
},
"options_page": "options.html",
+  "permissions": ["storage"]
}

然后在 options.js 中添加以下代码。

const nameInput = document.getElementById("name-input")
const saveBtn = document.getElementById("save-btn")
saveBtn.addEventListener("click", () => {
const name = nameInput.value;
chrome.storage.sync.set({
name,
}, () => {
console.log(`Name is set to ${name}`)
})
})

在前面的代码中,我们通过 set 方法使用 Chrome 存储 API 来存储用户输入,该方法接受要存储的值和回调。

现在,如果你刷新扩展,进入选项页面并检查控制台,存储的值就会被记录下来。

进入选项页面并检查控制台

如果我们刷新页面,你会发现即使我们在存储中设置了输入值,也没有将 name 输入的内部值更新为保存的值。

为此,我们将使用 get 方法来获取值(set 用于存储值,get 用于获取值)。

const nameInput = document.getElementById('name-input')
const saveBtn = document.getElementById('save-btn')
.....
chrome.storage.sync.get(['name'], (res) => {
nameInput.value = res.name ?? "???"
})

get 方法需要以下参数:

  • 一个包含要检索的键值([name])的数组(键值应与 set 方法中的键值一致)。
  • 回调函数,其参数代表一个对象,其中包含最初用 set 方法存储的值的键值绑定。

例如:假设存储在 name 键中的值是 john

    chrome.storage.sync.get(['name'], (res) => {
console.log(res)
})
//logs
{name: 'john'}

我们已经成功地在选项页面上存储并获取了用户输入。让我们在弹出页面上显示用户输入。在 popup.html 中添加。

......
<body>
<h1>My First Extension</h1>
<h2 id="time"></h2>
+    <h2 id="name"></h2>
</body>
<script src="popup.js"></script>
</html>

popup.js 中添加。

    const timeElement = document.getElementById('time')
const nameElement = document.getElementById("name");
const currentTime = new Date().toLocaleTimeString()
timeElement.textContent = `The time is: ${currentTime}`
chrome.action.setBadgeText({
text: "TIME",
}, () => {
console.log('Finished setting badge text')
}) 
chrome.storage.sync.get(["name"], (res) => {
const name = res.name ?? "???"
nameElement.textContent = `Your name is: ${res.name}`
})

在前面的代码中,我们选择了显示存储值的元素( nameElement ),从存储中获取值,并将其设置为所选元素的 textContent

现在,我们可以在弹出页面的选项页中查看输入的值。

弹出页面的选项页

后台脚本和 Service Workers

根据文件

扩展是基于事件的程序,用于修改或增强 Chrome 浏览器的浏览体验。事件是浏览器触发器,例如导航到新页面、删除书签或关闭标签页。扩展程序会在后台脚本中监控这些事件,然后根据指定指令做出反应。

后台脚本是一个 Javascript 文件,在安装 Chrome 浏览器扩展时在后台运行。服务工作者在运行一段时间后总是处于空闲状态;点击此处了解有关服务工作者的更多信息。

让我们为扩展实现后台脚本:

在我们的 manifest.json 文件中添加 ” background” 字段。

{
"manifest_version": 3,
"name": "First Extension",
"version": "1.0.0",
"description": "My first extension",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"action": {
"default_icon": {
"16": "icon.png",
"24": "icon.png",
"32": "icon.png"
},
"default_title": "My Extension Action Title",
"default_popup": "popup.html"
},
"options_page": "options.html",
"permissions": ["storage"],
"background": {
"service_worker": "lbackground.js"
}
}

创建 background.js 文件

📦 chrome extension basics
┣ 🎨 icon.png
┣ 📄 manifest.json
┣ 📄 popup.html
┣ 📄 popup.css
┣ 📄 popup.js
┣ 📄 options.html
┣ 📄 options.css
┣ 📄 options.js
┣ 📄 background.js
    consol.log("Hello from the background script!");

进入扩展页面并刷新扩展

上面的演示显示,添加后台脚本后,当你进入扩展页面并刷新扩展时,就会看到 inspect views:service worker 选项。点击 inspect 时,我们会看到它显示了从 background.js 记录到控制台的信息。

关于后台脚本,需要注意以下几点:

  • 点击 service worker 选项时显示的 devtools 环境与检查弹出式页面时看到的 devtools 环境并无不同,只是与我们的弹出式页面相比,我们没有 Elements 标签,因为后台脚本全部是 Javascript(只有 Javascript 相关的 devtools 可用)。
  •  this 关键字并不指向后台脚本中的 window 对象,而是指向 ServiceWorkerGlobalScope 对象(这意味着它是一个服务工作者)。因此请注意,服务工作者的功能与 HTML 文档中的普通 Javascript 文件不同。
/// background.js
console.log(this)
// logs
> ServiceWorkerGlobalScope

 background.js

现在,让我们在后台脚本中设置一个计时器功能。

/// background.js
let time = 0
setInterval(() => {
time +=  1
console.log(time)
}, 1000)

前面的代码通过 setInterval() 函数每秒增加一个 time 变量。

Chrome Alarms API

根据文档

使用 chrome.alarms API 可安排代码定期或在未来某个指定时间运行。

这意味着,即使服务 Worker 处于休眠状态, chrome.alarms API 也允许代码在后台脚本中运行。

让我们在 manifest 文件中启用此 API 的权限。

/// manifest.json
......
"options_page": "options.html",
"permissions": ["storage", "alarms"]  
"background": {
"service_worker": "background.js"   
}
.....

现在,让我们在 background.js 中使用 chrome.alarms API 重新实现计时器功能。

chrome.alarms.create({
periodInMinutes: 1 / 60, 
})
chrome.alarms.onAlarm.addListener((alarm) => {
chrome.storage.local.get(["timer"], (res) => {
const time = res.timer ?? 0
chrome.storage.local.set({
timer: time + 1
})
console.log(time)
})
})

前面的代码使用 chrome.alarms.create 方法通过 periodInMinutes 属性创建了一个每秒触发一次的警报。然后使用 onAlarm 方法监听并响应警报。最后,我们使用 chrome.storage 设置和增加计时器,并将其记录到控制台。

Chrome Notifications API

根据文档

使用 chrome.notifications API 使用模板创建丰富的通知,并在系统托盘中向用户显示这些通知。

这意味着 chrome.notifications API 可用于为我们的扩展创建桌面通知。不过,需要注意的是,在 manifest V3 中,后台脚本是一个服务工作者。在 service Worker 中,有一个名为 ServiceWorkerRegistration 的对象。它有一个内置的显示通知函数( ServiceWorkerRegistration.showNotification() ),可以在桌面上向用户显示通知。

现在,让我们使用 API 在特定时间段过去后通知用户。让我们在 manifest 文件中启用此 API 的权限。

/// manifest.json
......
"options_page": "options.html",
+ "permissions": ["storage", "alarms", "notifications"]  
"background": {
"service_worker": "background.js"   
}
.....

在 background.js

 chrome.alarms.create({
periodInMinutes: 1 / 60, 
})
chrome.alarms.onAlarm.addListener((alarm) => {
chrome.storage.local.get(["timer"], (res) => {
const time = res.timer ?? 0
chrome.storage.local.set({
timer: time + 1
})
if(time % 5 == 0) {
this.registration.showNotification('My first Extension', {
body: '1 second has passed',
icon: 'icon.png',
})
}
})
})

在前面的代码中,我们通过 serviceWorker 对象( this )访问 showNotification() 函数。该函数接受两个参数–一个表示通知标题的字符串和一个包含不同通知配置选项的对象。在我们的例子中,通知标题是 “My first Extension“;配置对象包含 body 属性(在通知正文中显示的文本)和 icon(通知图标)。我们的通知被包裹在一个 if 语句中,该语句每 5 秒触发一次通知。

Chrome浏览器扩展通知

现在刷新扩展页面并等待 5 秒钟,你就会在桌面上看到通知。

待续…

这就是本两部分系列的第一部分。在下一部分中,我们将构建一个 Pomodoro 计时器扩展。

你可以从 GitHub 代码库中下载本文的示例代码文件。

同时,如果你想更深入地了解本文中的概念,请查看下面的 “参考阅读” 部分。

参考阅读

评论留言