Chrome 插件学习(一)

790 阅读6分钟

基础知识

UI 主要提供以下几大功能:

  • popup.html 弹窗式窗口
  • sidePanel.html 侧边栏窗口
  • 右击菜单
  • 选项页(插件配置页面)

脚本:

  • content.js(运行在网页。只能访问Dom,和页面的javascript环境是隔离的,不能相互访问)
  • popup.js(运行在弹窗黑盒中)
  • sidePanel.js(运行在侧边栏黑盒中)
  • background.js(运行在Service worker)
  • inject.js (运行在网页)
  • options.js (选项页)

弹窗式窗口

image.png

第一步:配置popup.html

<html>
 <head>
   <title>hello world</title>
 </head>
 <body>
  <p>hello world</p>
   <script src="./public/popup.js"></script>
 </body>
</html>

弹出式窗口使用的任何 Javascript代码 都必须位于单独的文件中,不能使用 script标签。并且 Javascript代码 的作用域仅在弹窗式窗口中,与当前页面的 javascript 环境是 完全隔离的。

第二步:配置manifest.json

{
  "manifest_version": 3,
  "name": "Test Extension",
  "description": "Test Extension For dev",
  "version": "1.0",
  "action": {
    "default_popup": "./popup.html",
    "default_icon": "./assets/hello_extensions.png"
  }
}

⚠️注意:同时设置弹窗(action)和 侧边栏(sidePanel) 时候,弹窗失效

侧边栏窗口

image.png

第一步:配置sidePanel.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>SidePanel</title>
</head>
<body>
  hello world
  <script src="/public/sidePanel.js"></script>
</body>
</html>

第二步:配置manifest.json

{
  "manifest_version": 3,
  "name": "Side Panel Sample",
  "description": "Test Extension For dev",
  "version": "1.0",
  "action": {
    "default_icon": "./assets/hello_extensions.png"
  },
  "side_panel": {
    "default_path": "sidePanel.html"
  },
  "permissions": ["sidePanel"]
}

⚠️注意:需要开启 sidePanel 权限

菜单

image.png

background.js

chrome.contextMenus.onClicked.addListener((info) => {
  console.log('点击的菜单是:', info)
})

chrome.runtime.onInstalled.addListener(function () {
  const parent = chrome.contextMenus.create({
    title: 'Test parent item',
    id: '1'
  })

  chrome.contextMenus.create({
    title: 'child1',
    parentId: parent,
    id: '11'
  })

  chrome.contextMenus.create({
    title: 'child2',
    parentId: parent,
    id: '12'
  })
})

manifest.json

{
  "manifest_version": 3,
  "name": "Context Menus Sample",
  "description": "Uses the chrome.contextMenus API to customize the context menu.",
  "version": "1.0",
  "permissions": ["contextMenus"],
  "background": {
    "service_worker": "background.js"
  },
}

当我们点击某个 菜单选项时,会触发 chrome.contextMenus.onClicked 的事件监听,触发回调的如参打印如下:

image.png

⚠️注意:需要开启 contextMenus 权限

选项页

选项页的作用是为用户提供对插件进行 自定义配置 的地方。选项页通常用于存储用户的偏好设置,这些设置可以在插件的其他部分(如后台脚本、内容脚本等)中被访问和应用。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
    我最喜欢的颜色:
    <select id="color">
      <option value="red"></option>
      <option value="green">绿</option>
      <option value="blue"></option>
      <option value="yellow"></option>
    </select>

    <br>
    <div id="status"></div>
    <button id="save">保存</button>
  </div>
  <script src="./public/options.js"></script>
</body>

</html>

manifest.json

{
   "options_page": "options.html"
}

完成上述配置后,我们就可以打开选项页了

image.png

image.png

页面交互

上面我们只是配置了不同窗口,纯UI的展示;但是Chrome插件大部分场景是需要跟页面进行交互的,下面我们来聊一下这个问题。

popup.js/sidePanel.js 因为安全性问题,不能直接和 content.js 进行通信,必须要通过 background.js

通信传输路径:

pupup.js/sidePanel.js -> background.js -> content.js ⇋ inject.js

inject.js ⇋ content.js -> background.js -> popup.js/sidePanel.js

横着匹配竖着的看

popup.jsbackground.jscontent.jsinject.js
popup.js-chrome.runtime.sendMessage--
background.jschrome.runtime.sendMessage-sendResponse-
content.js-chrome.tabs.sendMessage-window.postMessage
inject.js--window.postMessage-

内容脚本(content.js)

运行在页面上,只能访问页面上的Dom,和页面的javascript环境是完全隔离的。需要通过chrome devtools进行调试(Sources/top/Cupshe Extension/public)

⚠️注意:修改后必须对页面进行刷新,否则不生效

manifest.json

{
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": [
      "scripts/content.js"
    ],
    "css": ["style.css"],
  }]
}

注入脚本(inject.js)

因为它被注入到页面中,而不是在 Chrome 扩展的沙箱环境中运行,它存在于页面的上下文执行中。content.js 使用 Dom 方法创建 script 标签将 inject.js 加载到页面中。

还需要在 manifest.json 文件中配置 web_accessible_resources。这样才能确保 inject.js 文件可以被网页访问和加载。

web_accessible_resources 是 Chrome 扩展中的一个配置项,指定哪些资源(如 JavaScript、CSS、图像等)可以被网页或内容脚本访问。默认情况下,Chrome 扩展的资源文件是封闭的,只有插件内部的脚本和页面可以直接访问。如果你想将某些文件(例如 inject.js)注入到网页中,就必须将其列入 web_accessible_resources。

content.js

const script = document.createElement('script');
script.src = chrome.runtime.getURL('inject.js');  // 获取 inject.js 的路径
(document.head || document.documentElement).appendChild(script);  // 注入到页面中
script.onload = function() {
    this.remove();  // 注入完成后删除 script 标签
};

manifest.json

{
  "web_accessible_resources": [{
    "resources": ["scripts/inject.js"],
    "matches": ["<all_urls>"]
  }],
}

content.js 和 inject.js 的区别:

content.js 直接注入网页的DOM,可以访问并修改网页的 DOM 元素,但它不能直接与页面的 Javascript 变量、函数等交互。内容脚本被隔离在自己的环境中,并通过消息传递与其他插件组件(如后台脚本)进行通信。如果需要与 Javascript 直接交互,就需要借助 inject.js

后台脚本(background.js)

可以使用扩展程序的服务在后台监控浏览器事件worker。Service Worker 是一种特殊的 Javascript 环境,用于处理事件,并在不需要时终止。

manifest.json 文件中注册 Service Worker

{
  "background": {
    "service_worker": "background.js",
    "type": "module", // 让 background.js 支持模块导入导出
  }
}

chrome

runtime

  • onInstalled

action

  • onClicked: 点击插件图标时触发; 与manifest.json 中的 action.default_popup 是互斥

scripting(permissions开启 'scripting'权限)

  • insertCSS 插入样式表。样式会在页面生效,但不会看到注入的css文件
  • removeCSS 移除式表
  • executeScript

sidePanel(permissions开启 'sidePanel'权限)

  • setOptions chrome 安全机制,无法自动弹出。和manifest.json 中配置 side_panel 作用一样

tabs(permissions开启 'tabs'权限)

  • onUpdated

长连接和一次性连接

在 Chrome 扩展开发中,长连接和一次性连接的区别主要体现在消息传递的持续性和用途。下面是对这两种连接方式的详细介绍:

一次性连接(短连接)

一次性连接使用 chrome.runtime.sendMessage 或 chrome.tabs.sendMessage,它用于短暂的通信,消息发送完毕后就断开连接。每次通信都需要重新建立连接。

适用场景:

  • 发送单次请求和接收响应的场景,例如查询数据、触发某个事件等。
  • 不需要持续的双向通信。

有两种方法可以实现短链接

  • chrome.runtime.sendMessage anywhere
  • chrome.tabs.sendMessage background -> content

实现: 发送端

chrome.runtime.sendMessage({ greeting: "hello" }, function(response) {
  console.log("Response:", response);
});

接受端:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.greeting === "hello") {
    sendResponse({ farewell: "goodbye" });
  }
});

长连接

长连接使用 chrome.runtime.connect 和 port 对象来建立持久通信通道,允许多次消息的传递。连接一旦建立,双方可以通过这个通道进行持续的双向通信,直到明确关闭连接为止。

适用场景:

  • 需要持续通信的场景,如监听某些状态、不断传输数据、WebSocket 类似的通信。
  • 更复杂的交互,例如内容脚本和背景脚本之间的长时间通信。

实现: 建立连接和持续通信:

const port = chrome.runtime.connect({ name: "content-background" });
port.postMessage({ greeting: "hello" });

port.onMessage.addListener(function(msg) {
  console.log("Received from background:", msg);
});

背景脚本端:

chrome.runtime.onConnect.addListener(function(port) {
  console.log("Connection established:", port.name);

  port.onMessage.addListener(function(msg) {
    console.log("Message from content script:", msg);
    if (msg.greeting === "hello") {
      port.postMessage({ farewell: "goodbye" });
    }
  });

  // 监听连接断开
  port.onDisconnect.addListener(function() {
    console.log("Connection closed.");
  });
});

区别:

  • 一次性连接:每次通信都需要重新发送消息,适合短时间的操作和事件驱动的行为。
  • 长连接:连接一旦建立,通信通道会一直保持打开状态,直到明确关闭,适合需要频繁传递信息的场景。 总结
  • 一次性连接 是基于请求-响应的模式,适合处理临时的通信需求。
  • 长连接 适用于持续、频繁的通信,通常用来实现复杂的实时交互,如状态监听、持续数据传输等。

manifest.json

字段解释
manifest_versionChrome 插件版本
name插件名称
description插件描述
version插件版本。
commands._execute_action当用户按下特定的快捷键时触发,等同于点击扩展图标