Dev Collection
HomeGitHubAI Web
  • ReadMe
  • How to use patch-package
  • Add GCC for node-canvas in Centos7.x
  • Create Server-Sent Events in Nextjs
  • TimeZone in Jest
  • Common Git Commit Message Types
  • Node v8 memory leak
  • minIO + picGO self-hosted image hosting service
  • Preventing SSH remote brute-force attacks
  • Ubuntu安装Netflix-Proxy
  • SNIProxy+Dnsmasq实现Netflix-Proxy
  • Simultaneously Pushing a Repository to Two Different Remotes: GitHub and GitLab
  • Typescript中的类型谓词
  • Mac Sonoma 无法安装 Charles 的解决办法
  • Git 多账号管理:在不同文件夹区分Git账号
  • localStorage VS IndexedDB
  • Chrome Extension 开发指南
Powered by GitBook
On this page
  • 基本概念
  • manifest.json (必须)
  • Extension Service Worker:service-worker.js (backgournd.js)
  • Content Scripts: content-script.js
  • Popup Page 和 Option Page
  • 开发实践
  • 推荐开发工具:NextExtension-Starter
  • 发布插件

Chrome Extension 开发指南

PreviouslocalStorage VS IndexedDB

Last updated 1 year ago

在本教程中,我们将深入了解并掌握基于最新Manifest V3(MV3)标准的Chrome插件开发。通过逐步探索和丰富的代码示例,全面理解插件开发的每个环节。

基本概念

通常,一个 Chrome 插件是一个完整的应用项目,它包含了一系列主要文件。其本身的开发语言为 javascript 。因此对于前端开发工程师来说,只要熟悉了整个结构以及 API,开发一个 Chrome 插件就没有那么困难了。

先来认识一下插件中的几个主要文件

manifest.json (必须)

manifest.json 文件定义了插件的基本信息和行为。目前 manifest 的版本已经升级为 v3,并且将在 2024年6月禁止再使用 v2版本。

以下是一些关键字段的介绍和示例用法 ():

字段名

示例

说明

manifest_version (必填)

"manifest_version": 3,

指定了插件使用的Manifest版本。对于Manifest V3,你应该设置为3。

name 和 version (必填)

"name": "My New Extension", "version": "1.0.0",

name 字段定义了插件的名称, version 字段定义了插件的版本号。

action

"action": {

"default_popup": "popup.html", "default_title": "My New Extension"

},

action 字段定义了插件的用户界面,通常是浏览器工具栏上的一个按钮。

options_page

"options_page": "options.html",

指定 options.html 文件的路径,以供扩展用作选项页面。

background

"background": {"service_worker": "service-worker.js"}

background 字段定义了一个后台脚本,它会在插件安装后一直运行。 (在 v2 版本中,这个字段为 background.js,v3已经变更为 service_worker)

permissions

"permissions": [ "activeTab", "storage"],

permissions 字段列出了插件所需的所有权限。

content_scripts

"content_scripts": [ { "js": ["content-script.js"],

"matches": [ "https://*.ctrip.com/*" ] } ],

content_scripts字段定义了要在特定网页上运行的脚本路径。

Extension Service Worker:service-worker.js (backgournd.js)

Service Worker 是扩展程序的核心事件处理脚本。Service Worker 在需要时加载,并在其进入休眠状态时取消加载。只要 Service Worker 在加载后还会主动接收事件,它就会运行。

因此在 service-worker.js 中,我们通常会定义一些监听事件。service-worker 作为一个chrome 后台脚本,它可以使用 Chrome 的一些 API, 但无法无法访问 DOM

生命周期

触发事件

说明

安装

ServiceWorkerRegistration.install

addEventListener("install", (event) => {});

当用户从 Chrome 应用商店安装或更新 Service Worker,或者用户使用 chrome://extensions 页面加载或更新已解压的扩展程序时。

安装完成

chrome.runtime.onInstalled

当该扩展程序(而不是 Service Worker)首次安装时、该扩展程序更新到新版本以及 Chrome 更新到新版本时,都会触发该事件。使用此事件来设置状态或一次性初始化。

激活

ServiceWorkerRegistration.active

addEventListener("activate", (event) => {});

在上一步安装完毕之后,会触发此项

启动

chrome.runtime.onStartup

当用户个人资料启动时,会触发 chrome.runtime.onStartup 事件,但不会调用任何 Service Worker 事件。

// 当点击插件图标时,判断当前页面是否符合需要修改的页面
chrome.action.onClicked.addListener(async tab => {
    if (!tab.url) return
    const url = new URL(tab.url)
    const tabId = tab.id
    const isInMatchUrl = Match_URL.some(function (matchurl) {
        return url.origin.includes(matchurl)
    })
 
    if (isInMatchUrl) {
        // inject script in page first
    }
})
// 当页面更新时
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab)=>{
    if(changeInfo.status === 'complete'){
        // your code here ...
    }
}

Content Scripts: content-script.js

Content Scripts 可以读取和更改网页内容,但其本身被限制在沙盒中,无法更改和注入页面的 js。

我们可以通过 content script 来插入一些和页面元素融合的功能。也可以用于调整页面的样式布局,比如移除侧边栏的广告。

另外如果想让 content script 和其他脚本通信,可以使用 chrome.runtime.sendMessage 和 chrome.runtime.onMessage:

// 在content script中发送消息
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});
 
// 在 service worker 中监听消息
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.greeting === "hello"){
      sendResponse({farewell: "goodbye"});
    }
  }
);

Popup Page 和 Option Page

当用户与Chrome插件进行交互时,Popup Page是他们点击插件图标后首先看到的页面。Popup Page应该简洁直观,同时提供用户所需的核心功能或信息。

Option Page是插件的设置中心,允许用户根据个人偏好调整插件的行为。在这里,用户可以修改插件的配置,如通知设置、外观偏好或功能开关。

为了增强用户体验,插件可以在Popup 和 Option 中利用chrome.storage API来存储和读取用户的个性化设置。无需使用复杂的服务器端存储解决方案。

注意,Popup 和 Option 本身并不是必须的。这取决于插件本身是否需要。

开发实践

在本节中,我们将通过一个具体的开发案例来深入Chrome插件的开发实践。目标是创建一个插件,该插件能够读取页面上发起的fetch请求,并允许用户修改请求URL中的subEnv查询参数。

功能实现

插件的核心功能将围绕以下两个关键点展开:

  1. 读取fetch请求:监测页面上的fetch请求,寻找包含subEnv参数的URL。

  2. 用户交互:提供用户界面,使用户能够修改subEnv的值。

技术对比

在实现上述功能时,我们有两种主要的技术途径:

劫持 fetch 在插件开发中可以有两种形式,

  1. 使用declarativeNetRequest API 优势:作为Chrome内核提供的API,declarativeNetRequest能够确保拦截所有fetch请求。 局限:相比于直接注入JS,这种方法在灵活性上有所欠缺,并且存在一定的使用限制。

  2. 注入JS以Hook fetch方法 优势:提供了更高的灵活性,允许开发者自由地修改请求(Request)和响应(Response)。

    局限:注入脚本的时机可能会影响其对所有fetch请求的覆盖能力。

权衡与选择

开发者在选择实现方式时,需要根据项目的具体需求进行权衡。如果需要确保捕获所有请求,且对修改的灵活性要求不高,那么使用declarativeNetRequest可能是更好的选择。反之,如果项目需要高度定制化的请求处理逻辑,那么注入JS可能是更合适的方案。

manifest.json

假设我们以第一种方式来实现。首先需要修改 manifest 中的 permisssions

"permissions": [ "declarativeNetRequest", "scripting", "storage"],
"declarative_net_request" : {
    "rule_resources" : []
},
"background": {
    "service_worker": "service-worker.js"
}

在 manifest 中,我们可以指定 rule_resources 为空,之后在插件让用户手动的选择需要被修改的 fetch URL,这样可以更灵活劫持需要处理的路径。

同时也需要 storage 权限来保存用户选择的 URL。

service-worker.js

在 service-worker.js 中,可以通过监听 storage 变化来更新 rules

// 监听 storage 变化
chrome.storage.onChanged.addListener((changes, namespace) => {
    const { subEvnItemList} = changes || {}
    const { newValue } = subEvnItemList || {}
    updateRulesForSubEvnItemList(newValue)
})

// 更新 rules
function updateRulesForSubEvnItemList(subEvnItemList){
    let newRules = []
    if(subEvnItemList?.length === 0) return
    // 对用户的选择进行处理以符合规则 Syntax
    subEvnItemList.map((item, index) => {
        const { enabled, subEvn, urlMatch } = item
        if(enabled && urlMatch){
            newRules.push({
                id: (+index + 1),
                priority: 1,
                action: {
                    type: 'redirect',
                    redirect: {
                        regexSubstitution: `\\1subEnv=${subEvn}&\\4`
                    }
                },
                condition: {
                    regexFilter: `(ctrip.com\/[^\t\n\f\r].*${urlMatch}[^\t\n\f\r].*)(subEnv=[^&]+($|&))(.*$)`
                }
            })
        } 
    })
    chrome.declarativeNetRequest.getDynamicRules(previousRules => {
        const previousRuleIds = previousRules.map(rule => rule.id);
        chrome.declarativeNetRequest.updateDynamicRules({
          removeRuleIds: previousRuleIds,
          addRules: newRules
        });
    });
}

Popup Page

为了提供一个便捷的用户交互方式,我们将创建一个Popup Page,允许用户输入特定的URL和调整所需的subEnv参数。此外,我们将利用Chrome的存储机制来保存用户的输入,以便在用户下次访问Popup Page时能够恢复之前的设置。

存储机制

  1. 数据保存:当用户输入URL和subEnv参数后,我们将使用chrome.storage API来保存这些数据。这确保了用户设置的持久化,即使在浏览器关闭后也能保持。

  2. 数据提取:在Popup Page加载时,我们将从chrome.storage中提取之前保存的URL和subEnv值,并将其自动填充到相应的输入框中,提升用户体验。

关键代码实现

以下是实现上述存储和提取功能的JavaScript代码示例:

const saveSubEvnItemList = async (subEvnItemList: ISubEvnItem[]) => {
    let msg = ''
    // @ts-ignore
    if (typeof chrome === 'undefined' || !chrome?.tabs) {
        msg = 'Please use as chrome extension'
        return
    }
    return new Promise((resolve, reject) => {
        // @ts-ignore
        chrome.storage.sync.set({ subEvnItemList }).then(() => {
            resolve(true)
        })
    })
}

const restoreSubEvnItemList = async (): Promise<ISubEvnItem[]> => {
    let msg = ''
    // @ts-ignore
    if (typeof chrome === 'undefined' || !chrome?.tabs) {
        msg = 'Please use as chrome extension'
        return []
    }
    return new Promise<ISubEvnItem[]>((resolve, reject) => {
        // @ts-ignore
        chrome.storage.sync.get(null, (items: any) => {
            const subEvnItemList = items?.subEvnItemList || []
            resolve(subEvnItemList)
        })
    }).catch(e => {
        console.error(e)
        return []
    })
}

这里设计了2个方法用于保存和读取 chrome.storage。同时每当点击确认按钮 saveSubEvnItemList 之后,就会触发 service-worker.js 中的 chrome.storage.onChanged

至此,一个用于修改页面请求中,subEvn query 的Chrome插件现已开发完成。

推荐开发工具:NextExtension-Starter

使用NextExtension-Starter的好处 :

  • 快速开发:利用React的组件化特性,构建可复用的UI组件,实现的Popup和Option页面。

  • 集成打包:该模板集成了打包工具,可以一步生成插件所需的Zip文件,简化部署流程。

  • 自定义图标:轻松生成和集成插件icon,增强插件的视觉效果和用户识别度。

发布插件

Service worker 有几个生命周期 ():

Service Worker 中可能用到的一些事件 ():

declarativeNetRequest 用于获取页面上的请求,并且需要声明对应的 rule 规则。()

declarativeNetRequest API 接受的规则可以通过正则表达式来命中需要的 URL。() 但由于限制,所支持的正则表达式并非完全态,贪婪方法并不适用。()

declarativeNetRequest API 通过 updateDynamicRules 方法来动态修改 rules。()

为了简化开发流程并提高开发效率,这里推荐使用 。一个基于Next.js框架的Chrome插件开发模板。

read more
read more
read more
read more
read more
RE2 Syntax
read more
NextExtension-Starter