post

Chrome Extension开发入门

· 3 分钟阅读 · 524 字

本篇主要是开发的一些实践,可读性还比较粗糙,有待进一步施工

参考:

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.jsoncontent_scripts 字段的配置,包括 matchesrun_at 属性。

  • matches:定义内容脚本应注入到哪些页面中。

  • run_at

    :定义内容脚本在页面加载的什么阶段注入。可选值包括:

    • "document_start":在HTML文档开始加载时。
    • "document_end":在HTML文档完全加载和解析时(默认)。
    • "document_idle":在页面的其余资源(如图片)加载完成时。

后台脚本在扩展安装或浏览器启动时执行,并在整个浏览器会话期间保持运行。Manifest V3 中使用的是后台服务工作者(Service Worker),这与传统的持久性后台页面有所不同。

  • Service Worker:后台服务工作者在需要时启动,并在空闲时自动终止。这有助于节省资源。

Message Passing API:

  1. 插件的不同组件之间的通信: 它可以让插件的不同组件之间进行通信,包括 background page、content script、popup 窗口等。
  2. 插件和网页之间的通信: 通过 Message Passing API,插件可以和网页之间进行通信,实现数据传递和功能调用等操作。
  3. 插件和服务器之间的通信: 通过 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