Chrome Extension开发入门
JRQZ
本篇主要是开发的一些实践,可读性还比较粗糙,有待进一步施工
参考:
https://developer.chrome.com/docs/extensions/get-started (官方教程)
https://github.com/sxei/chrome-plugin-demo
https://www.pipipi.net/24804.html
方法调研:background模块(chrome extension模块),Message Passing API(chrome extension api),Broadcast Channel(发布-订阅模式),SharedWorker API(多TAB共享后台线程),localStorage/sessionStorage(本地浏览器存储)
HelloWorld
manifest.json
{
"manifest_version": 3,
"name": "Hello Extensions",
"description": "Base Level Extension",
"version": "1.0",
"action": {
"default_popup": "hello.html",
"default_icon": "hello_extensions.png"
}
}
hello.html
<html>
<body>
<h1>Hello Extensions</h1>
</body>
</html>
chrome://extensions->Enable Developer Mode->Load unpacked button and select the extension directory
Reload
Extension component | Requires extension reload |
---|---|
The manifest | Yes |
Service worker | Yes |
Content scripts | Yes (plus the host page) |
The popup | No |
Options page | No |
Other extension HTML pages | No |
ReadTime
manifest.json
{
"manifest_version": 3,
"name": "Reading time",
"version": "1.0",
"description": "Add the reading time to Chrome Extension documentation articles",
"icons": {
"16": "images/icon-16.png",
"32": "images/icon-32.png",
"48": "images/icon-48.png",
"128": "images/icon-128.png"
},
"content_scripts": [
{
"js": ["scripts/content.js"],
"matches": [
"https://developer.chrome.com/docs/extensions/*",
"https://developer.chrome.com/docs/webstore/*",
"https://jrqz-wu.com/*"
]
}
]
}
Content scripts can use the standard Document Object Model (DOM) to read and change the content of a page. The extension will first check if the page contains the <article>
element. Then, it will count all the words within this element and create a paragraph that displays the total reading time.
content.js
const article = document.querySelector("article");
// `document.querySelector` may return null if the selector doesn't match anything.
if (article) {
const text = article.textContent;
const wordMatchRegExp = /[^\s]+/g; // Regular expression
const words = text.matchAll(wordMatchRegExp);
// matchAll returns an iterator, convert to array to get word count
const wordCount = [...words].length;
const readingTime = Math.round(wordCount / 200);
const badge = document.createElement("p");
// Use the same styling as the publish information in an article's header
badge.classList.add("color-secondary-text", "type--caption");
badge.textContent = `⏱️ ${readingTime} min read`;
// Support for API reference docs
const heading = article.querySelector("h1");
// Support for article docs with date
const date = article.querySelector("time")?.parentNode;
(date ?? heading).insertAdjacentElement("afterend", badge);
}
跨页面调度
内容脚本(content.js),背景脚本(background.js)
内容脚本是在特定的网页加载时触发并注入的。具体的执行时机取决于 manifest.json
中 content_scripts
字段的配置,包括 matches
和 run_at
属性。
-
matches:定义内容脚本应注入到哪些页面中。
-
run_at
:定义内容脚本在页面加载的什么阶段注入。可选值包括:
"document_start"
:在HTML文档开始加载时。"document_end"
:在HTML文档完全加载和解析时(默认)。"document_idle"
:在页面的其余资源(如图片)加载完成时。
后台脚本在扩展安装或浏览器启动时执行,并在整个浏览器会话期间保持运行。Manifest V3 中使用的是后台服务工作者(Service Worker),这与传统的持久性后台页面有所不同。
- Service Worker:后台服务工作者在需要时启动,并在空闲时自动终止。这有助于节省资源。
Message Passing API:
- 插件的不同组件之间的通信: 它可以让插件的不同组件之间进行通信,包括 background page、content script、popup 窗口等。
- 插件和网页之间的通信: 通过 Message Passing API,插件可以和网页之间进行通信,实现数据传递和功能调用等操作。
- 插件和服务器之间的通信: 通过 Message Passing API,插件可以和服务器之间进行通信,实现数据传递和功能调用等操作。
Message Passing API 主要提供了以下内容 :
字段名 | 描述 |
---|---|
chrome.runtime.sendMessage() | 用于向指定的扩展程序或 content script 发送消息 |
chrome.runtime.onMessage() | 用于监听来自指定的扩展程序或 content script 发送的消息 |
chrome.runtime.connect() | 用于在扩展程序或 content script 之间建立长连接,以便进行双向通信 |
chrome.runtime.onConnect() | 用于监听来自扩展程序或 content script 建立的连接请求,并返回连接对象以便进行通信 |
demo:
manifest.json
{
"manifest_version": 3,
"name": "Cross Tab Messaging Example",
"version": "1.0",
"permissions": ["tabs", "activeTab", "scripting"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://jrqz-wu.com/*"],
"js": ["contentA.js"],
"run_at": "document_end"
},
{
"matches": ["https://www.google.com/*"],
"js": ["contentB.js"],
"run_at": "document_end"
}
]
}
background.js
,插件加载时执行
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'sendToGoogle') {
// 查找所有标签页
chrome.tabs.query({ url: "https://www.google.com/*" }, (tabs) => {
for (let tab of tabs) {
// 将消息发送到 Google 标签页
chrome.tabs.sendMessage(tab.id, { action: 'messageFromTabA', data: message.data });
}
});
sendResponse({ status: 'message sent to Google tab' });
}
});
contentA.js
,打开/刷新https://jrqz-wu.com/*时执行
console.log('hello from contentA');
// 获取 h1 标签的内容
const h1Content = document.querySelector('h1').innerText;
console.log('sent h1Content' + h1Content);
// 发送消息到后台脚本
chrome.runtime.sendMessage({ action: 'sendToGoogle', data: h1Content }, (response) => {
console.log(response.status);
});
console.log('sent h1Content' + h1Content);
contentB.js
,打开/刷新https://www.google.com/*时执行
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'messageFromTabA') {
console.log('Received message from Tab A:', message.data);
// 写入到 Google 搜索框
const searchBox = document.querySelector('textarea[name="q"]') || document.querySelector('input[name="q"]');
if (searchBox) {
searchBox.value = message.data;
searchBox.dispatchEvent(new Event('input', { bubbles: true })); // 触发输入事件
}
}
});
contentscript日志直接在对应tab看console,backgroundscript日志在chrome扩展程序页面对应插件检查service worker