【Chrome】开发一个Chrome扩展以及常见问题的解决方案

前言

本文介绍开发chrome扩展很重要的几种操作,如:操作网页dom、发送请求、渲染弹层、不同沙盒环境的通信方式、扩展与网页的通信方式、遇到iframe时的操作等。最终会提供一个简单的案例,其中涵盖了上述操作。

还有一些本人相关文章,有兴趣也可以看一下,如:判断是否安装了某个Chrome插件background.js中log打印未出现在控制台Edge扩展程序上架流程Chrome扩展程序上架流程

本文都是在该页面下测试扩展

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script>
    localStorage.setItem("tag", "123456") // 标识是否需要执行插件
  </script>
</head>

<body>
  <input id="test-chrome-extensions" type="text">
</body>

</html>
  • 需要一个标识用于确认是否需要在该页面下运行扩展,本文以本地存储举例,也可能是下面几种方式,需要根据实际场景判断使用。
    • 根据域名判断,可以通过扩展的 manifest.json 中 content_scripts 的 matches 字段来定义允许执行的页面(下文会提及)。
    • 根据url路径参数
    • 根据页面中的特殊标签
    • 根据本地存储
  • 需要一个input输入框用于测试扩展对网页dom元素的直接操作

一、目录结构

开发一个Chrome扩展,项目目录大致如下,下文统一以标准命名介绍。

在这里插入图片描述

  • background.js:后台脚本
  • content.js:内容脚本
  • manifest.json:插件配置文件(清单列表)
  • popup.html 和 popup.js:插件弹窗页面及其脚本

二、配置清单(manifest.json)

  • 该文件必须位于项目的根目录下。
  • 必需的键只有 “manifest_version”、“name” 和 “version”。
  • 开发过程中支持注释 (//),但在将代码上传到 Chrome 应用商店之前,必须先移除这些注释。

基本配置如下

{
  "manifest_version": 3,
  "name": "My Chrome Extension",
  "version": "1.0",
  "permissions": ["storage", "activeTab", "scripting"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

Chrome官方文档中明确说明,v2已经弃用了。

在这里插入图片描述

在这里插入图片描述

三、沙盒环境

Chrome 插件的每个模块(background.js、popup.js、content.js)都运行在自己的沙盒环境中,意味着它们的 JavaScript 变量和函数是相互隔离的,避免了相互干扰。唯一的通信方式是通过 Chrome 提供的 API(如 chrome.runtime.sendMessagechrome.storage)。

  • background.js:运行在插件的后台,它运行在独立的沙盒环境中,无法与网页内容直接交互
  • popup.html:弹窗页面,用户点击浏览器工具栏的插件图标时弹出。
  • popup.js:只能在弹窗页面存在时运行,弹窗页对应的脚本。
  • content.js:内容脚本,运行在网页的环境中可以直接访问网页的 DOM

浏览器运行环境:与普通的网页脚本不同,Chrome 插件模块无法直接访问网页的全局变量和 DOM,必须通过 content.js 作为桥梁进行交互。同时,插件可以使用 chrome 提供的扩展 API,而网页中的 JavaScript 代码则没有这些权限。

四、通信方式

1. chrome.runtime API

由于处于不同沙盒环境,只能通过chrome.runtime APIchrome.storage通信

chrome.runtime.sendMessage发送消息,接收两个参数

  • message: 要发送的消息内容,可以是一个对象。
  • callback (可选): 当消息成功发送或遇到错误时调用的回调函数。
chrome.runtime.sendMessage({ type: "test", message: "Hello World!" }, (response) => {
  console.log("回应:", response);
});

chrome.runtime.onMessage.addListener监听消息,参数为回调函数,其参数:

  • message:chrome.runtime.sendMessage发送的数据(第一个参数)。
  • sender:发送消息的上下文,包含有关消息来源的信息,如 tab、url、扩展id。
  • sendResponse:返回消息(chrome.runtime.sendMessage中callback可以接收)
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  console.log("接收到的消息:", message);
  sendResponse({ type: "responseData", data: "返回数据" })
});

2. chrome.storage

不同于 localStoragechrome.storage是异步的,所有存储操作都会接收一个回调函数,以处理操作完成后的逻辑。

chrome.storage 主要有两个存储区域:

  • chrome.storage.local:在本地存储数据,不同步到其他设备,存储容量最大为 5MB。
  • chrome.storage.sync:同步数据到 Chrome 登录账户的所有设备,存储容量为 100KB,适合存储轻量配置或设置。

基本用法

存储数据

// 存储数据
chrome.storage.local.set({ key: "value" }, function() {
  console.log('数据已存储');
});

// 或者使用多个键值对
chrome.storage.local.set({ key1: "value1", key2: "value2" }, function() {
  console.log('多组数据已存储');
});

获取数据

// 获取存储的数据
chrome.storage.local.get(['key'], function(result) {
  console.log('获取到的值:', result.key);
});

// 获取多个键值对
chrome.storage.local.get(['key1', 'key2'], function(result) {
  console.log('key1 的值:', result.key1);
  console.log('key2 的值:', result.key2);
});

删除数据

// 删除指定键的数据
chrome.storage.local.remove('key', function() {
  console.log('数据已删除');
});

// 删除多个键的数据
chrome.storage.local.remove(['key1', 'key2'], function() {
  console.log('多个数据已删除');
});

清除所有数据

// 清除存储中的所有数据
chrome.storage.local.clear(function() {
  console.log('所有数据已清除');
});

测试

进入扩展程序管理页,或者直接访问chrome://extensions/

在这里插入图片描述

确保开启开发者模式

在这里插入图片描述

直接将文件夹拖入即可

注意:background.js不同于content.js,他并不与页面共享相同的 JavaScript 环境,所以需要单独打开一个控制台。

点击检查视图,会弹出background.js对应的控制台

在这里插入图片描述

v2版本的扩展有所不同,名为background.html,新项目不建议使用v2。

在这里插入图片描述

五、案例演示

案例中,content使用chrome.runtime API通信,popup使用chrome.storage获取数据,实际场景中可以使用任意通信方式,没有限制,流程如下:

  1. content 判断该页面是否需要扩展运行
  2. 如果需要则通知 background 发送请求
  3. background 将返回数据通过 chrome.runtime API 交予 content,然后content将该值赋给页面中input元素
  4. background 将返回数据保存到 chrome.storage 供 popup 渲染

在这里插入图片描述

content.js

console.log("content.js - 开始运行...")

const tag = localStorage.getItem("tag")
console.log("标识:", tag)

if (tag) {
  console.log("该页面需要使用插件,准备接收消息...")

  // 通知 background.js 可以继续执行接口调用
  chrome.runtime.sendMessage(
    { type: "pageCheck", valid: true, tag },
    (response) => {
      console.log("收到消息:", response)
      if (response.type === "responseData") {
        const inputElement = document.getElementById("test-chrome-extensions")
        if (inputElement) {
          inputElement.value = response.data.name
        }
      }
    }
  )
} else {
  console.log("该页面不需要使用插件,不执行扩展逻辑。")

  // 通知 background.js 不用调用接口
  chrome.runtime.sendMessage({ type: "pageCheck", valid: false, tag })
}

background.js

console.log("background.js - 开始运行...")

// 模拟请求并返回数据
function fetchData(tag) {
  console.log("标识为", tag) // 可能需要该标识作为请求参数
  const responseData = { name: "田本初", age: 23 }
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(responseData)
    }, 1000) // 模拟接口异步返回
  })
}

// 监听 content.js 发来的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "pageCheck") {
    if (message.valid) {
      console.log("页面需要接口调用...")
      fetchData(message.tag).then((response) => {
        chrome.storage.local.set({ data: response }) // 存储数据
        sendResponse({ type: "responseData", data: response }) // 发送消息
        console.log(
          "接口调用成功,向content发送消息,并将data存储到storage供popup使用",
          response
        )
      })
      // 返回 true 表示异步使用 sendResponse,否则content.js 不会等待异步返回结果
      return true
    } else {
      console.log("页面不需要接口调用...")
      return
    }
  }
})

popup.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Popup</title>
  <script src="popup.js"></script>
  <style>
    .container {
      width: 600px;
      height: 600px;
      background-color: #efefef;
    }
  </style>
</head>

<body>
  <div class="container">
    <div id="show_name"></div>
    <div id="show_age"></div>
  </div>
</body>

</html>

popup.js

console.log("popup.js - 开始运行...")

// 获取数据
chrome.storage.local.get(["data"], (result) => {
  if (chrome.runtime.lastError) {
    console.error("获取数据时发生错误:", chrome.runtime.lastError)
    return
  }

  console.log("获取到的值:", result)

  // 处理数据
  const data = result.data

  // 更新 popup.html 中的内容
  const nameDiv = document.getElementById("show_name")
  const ageDiv = document.getElementById("show_age")

  if (nameDiv && ageDiv && data) {
    nameDiv.textContent = `姓名:${data.name}`
    ageDiv.textContent = `年龄:${data.age}`
  }
})

六、分析manifest.json

上文提过一次 manifest.json,通过案例后详细分析一下清单:

{
  "manifest_version": 3,
  "name": "My Chrome Extension",
  "version": "1.0",
  "permissions": ["storage", "activeTab", "scripting"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}
  • manifest_version:Manifest 文件的版本,官方已经不再支持发布v2版,目前都是3。
  • name:插件名
  • version:版本号,后续更新时必须修改在这里插入图片描述
  • permissions:权限,本文只用到了storage
    • activeTab:允许扩展在用户点击扩展图标时访问当前活动标签页的内容
    • scripting:允许扩展使用 chrome.scripting API 注入脚本到网页中
  • background:在后台运行,负责处理扩展的主要逻辑和事件
  • action:用户点击扩展图标时,会显示指定的弹出页面
  • content_scripts:定义要注入的内容脚本及其匹配规则
    • matches:定义哪些网页 URL 匹配规则,以决定哪些页面将注入指定的内容脚本。

      "http://\*/\*":匹配所有 HTTP 协议的网页。
      "https://\*/\*":匹配所有 HTTPS 协议的网页。
      "*://example.com/*":匹配 example.com 域下的所有网页。
      "*://*.example.com/*":匹配 example.com 域下的所有子域网页。
      

修改matches为"matches": ["https://www.baidu.com/"], 可以发现只有在该域名下才执行了 content.js

在这里插入图片描述

配置扩展图标,其中action下的default_icon对应工具栏中的图标,icons对应管理扩展页面中的图标。

"action": {
  "default_popup": "popup.html",
  "default_icon": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
},
"icons": {
  "16": "icons/icon16.png",
  "48": "icons/icon48.png",
  "128": "icons/icon128.png"
},

在这里插入图片描述

在这里插入图片描述

更多详细使用请参考:官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

田本初

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值