在之前的 Chrome Extension 开发的初探文章当中,我对 消息传递 的认识还比较浅,最近又双叒叕学到了一点点这方便的新知识,也在一次又一次的功能实践当中也体会到了消息传递在实用当中重要性。所以还是再写一篇关于消息传递的内容,另外个人觉得这个方向非常不错,也推荐给各位。

重新认识 message

在Chrome扩展开发中,message 通信机制是核心技术之一,它允许扩展的不同组件(如内容脚本、后台脚本、弹出页面、选项页面等)通过发送和接收消息来进行互动和协作。这种通信方式打破了扩展组件之间的孤立状态,使得它们能够像团队一样高效地协同工作,共享数据,触发操作,甚至执行复杂的逻辑处理。

通过 message 通信,内容脚本可以向后台脚本请求数据或执行后台任务,而后台脚本则可以根据接收到的消息作出响应,并将结果传回给内容脚本或其他组件。此外,弹出页面可以通过消息向后台脚本发送用户指令,更新扩展的状态,或与当前网页内容进行交互。

这种机制不仅提高了扩展的灵活性和功能性,还增强了扩展在处理异步任务和用户交互时的响应能力。例如,当用户在弹出页面中点击按钮时,后台脚本可以即时接收消息,执行相关操作,如访问远程API、更新存储的数据或通知内容脚本在当前页面上进行某些操作。

message 通信机制在Chrome扩展开发中扮演了关键角色,使得复杂的扩展能够以模块化的方式构建,每个组件各司其职,又能够通过消息传递无缝协作,实现扩展整体功能的最大化。

使用场景

在Chrome扩展开发中,message 通信有多种常见的应用场景,每种场景都涉及扩展不同组件之间的消息传递。这些场景通常包括内容脚本与后台脚本的通信、弹出页面与后台脚本的交互、扩展各部分之间的广播消息等。以下是一些典型的message 通信场景:

1. 内容脚本与后台脚本的通信

  • 场景描述: 内容脚本通常用于与当前网页的DOM进行交互,但由于其在一个独立的环境中运行,无法直接访问后台脚本的数据或功能。因此,内容脚本需要通过消息传递与后台脚本通信。
  • 应用示例: 内容脚本检测到网页上的特定元素变化后,向后台脚本发送消息,请求后台脚本抓取或更新数据。内容脚本从后台脚本请求用户的设置数据,获取后根据设置动态修改网页内容。

2. 弹出页面与后台脚本的通信

  • 场景描述: 弹出页面是用户与扩展互动的入口,通常需要根据用户的操作触发后台脚本执行某些任务。由于弹出页面只在被打开时存在,因此需要与持久运行的后台脚本通信来保持状态和执行操作。
  • 应用示例: 用户在弹出页面中点击按钮后,向后台脚本发送消息,后台脚本接收后执行相应的操作,如保存数据或触发通知。弹出页面在加载时请求后台脚本获取当前状态信息,以便显示最新的应用状态或用户数据。

3. 后台脚本与多个内容脚本的通

  • 场景描述: 当扩展需要在多个标签页或窗口中的内容脚本之间同步信息时,后台脚本可以充当消息中心,接收一个内容脚本的消息并将其转发给其他内容脚本。
  • 应用示例: 一个内容脚本更新了网页上的某些数据,并将此更新通过后台脚本通知其他所有打开的标签页,使它们的内容同步。用户在某个标签页上执行操作后,后台脚本通过广播将此操作通知给所有相关的内容脚本,让它们根据需要做出相应更新。

4. 扩展选项页面与后台脚本的通信

  • 场景描述: 选项页面通常用于扩展的配置设置,用户在此页面上修改的设置需要持久保存并立即生效。通过消息通信,选项页面可以与后台脚本互动,实现设置的保存和应用。
  • 应用示例: 用户在选项页面修改扩展的配置后,通过消息通知后台脚本,后台脚本更新配置并将新设置应用到所有活动的内容脚本中。选项页面在加载时请求后台脚本提供当前设置数据,以便用户查看和修改。

5. 使用广播消息

  • 场景描述: 当扩展需要在其所有部分(内容脚本、弹出页面、后台脚本等)之间传递消息时,可以使用广播消息。这种方式可以将消息发送给扩展的所有组件,而不必指定特定的接收者。
  • 应用示例: 扩展在某个部分(如弹出页面)执行某个操作后,通过广播消息通知扩展的所有其他部分(如内容脚本、后台脚本),确保整个扩展的一致性。当后台脚本监测到某个全局事件(如网络连接变化)时,通过广播消息通知所有相关的扩展组件,以便它们采取相应的措施。

这些场景展示了message 通信机制在Chrome扩展开发中的广泛应用,它使得扩展能够有效地管理和协调其不同部分,确保功能的顺畅运行和用户体验的一致性。

message 类型

Chrome 扩展的 message 通信机制主要包括以下几种情况,每种情况都对应了扩展内部不同组件之间的消息传递方式。这些通信方式确保了扩展的各个部分能够高效协作,实现复杂功能和良好的用户体验。

单向消息传递

单向消息传递 是 Chrome 扩展中的一种基本通信方式,它指的是消息从一个组件发送到另一个组件后,不需要立即收到回复或反馈。这种方式适用于简单的通知或触发操作场景,在扩展的各个部分之间传递信息时非常常见。

单向消息传递 是 Chrome 扩展中的一种基本通信方式,它指的是消息从一个组件发送到另一个组件后,不需要立即收到回复或反馈。这种方式适用于简单的通知或触发操作场景,在扩展的各个部分之间传递信息时非常常见。

应用场景

  1. 内容脚本向后台脚本发送数据: 当内容脚本检测到某个事件(如用户点击按钮或网页元素变化)时,它可以通过单向消息将这个事件通知后台脚本。后台脚本接收到消息后可以执行相应的操作,如记录日志或更新扩展的状态。
  2. 弹出页面通知后台脚本执行操作: 用户在弹出页面中进行的操作(如点击某个按钮)可能需要触发后台脚本执行某些任务。在这种情况下,弹出页面可以通过单向消息将指令发送给后台脚本,然后继续处理用户的其他操作,而无需等待后台脚本的回应。
  3. 后台脚本向内容脚本广播信息: 如果后台脚本监测到某个全局事件(如扩展状态变化或用户设置更新),它可以通过单向消息通知所有活动的内容脚本,内容脚本可以根据需要更新页面显示或执行相关操作。

单向消息传递 的优点:

  • 简洁: 只需发送消息而不需要处理返回值,简化了通信逻辑。
  • 高效: 适合需要快速触发操作或发送通知的场景。

注意事项: 由于是单向消息传递,发送方无法确认接收方是否成功处理了消息。因此,这种方式更适用于无需确认的简单通信场景。

演示

下面是一个关于 Chrome 扩展中单向消息传递的简单例子,展示了内容脚本向后台脚本发送消息的过程。

1. 内容脚本 (content.js)

内容脚本侦听网页上的按钮点击事件,并在按钮被点击时向后台脚本发送一条消息。

// 侦听页面上按钮的点击事件
document.getElementById('myButton').addEventListener('click', function() {
    // 向后台脚本发送消息
    chrome.runtime.sendMessage({ action: 'buttonClicked', data: 'Hello from content script' });
});
2. 后台脚本 (background.js)

后台脚本接收内容脚本发送的消息,并处理该消息(例如,记录日志或执行其他操作)。

// 监听来自内容脚本的消息
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
    if (message.action === 'buttonClicked') {
        // 处理收到的消息
        console.log('Received message:', message.data);
        // 这里可以执行其他操作,例如更新扩展状态或发送通知
    }
});

在这个例子中,当用户点击网页上的按钮时,内容脚本通过 chrome.runtime.sendMessage 方法向后台脚本发送了一条消息。后台脚本接收到消息后,通过 chrome.runtime.onMessage.addListener 监听器处理该消息。

双向长连接通信

双向长连接通信 是 Chrome 扩展中一种高级的通信机制,它允许扩展的两个组件之间建立持久的连接,并进行持续的双向数据交换。这种方式适用于需要实时更新、持续通信的场景,如内容脚本与后台脚本之间的实时数据同步或状态监控。

应用场景

  1. 内容脚本与后台脚本的实时数据同步: 当用户在网页上进行连续操作时,内容脚本可以通过长连接实时将数据发送给后台脚本,后台脚本可以立即响应并将处理结果返回给内容脚本。
  2. 扩展的实时状态监控: 扩展需要持续监控某些状态变化并根据变化做出响应,长连接通信可以保证状态的实时同步,确保用户操作的即时反馈。
  3. 实时聊天功能: 内容脚本与后台脚本之间的长连接可以用于实现聊天应用的消息实时传输。
  4. 实时数据分析: 用户在网页上的操作可以通过长连接实时传送到后台脚本,进行数据分析和反馈。
  5. 游戏扩展: 可以使用长连接来实现网页游戏中的实时数据传输和状态同步。

双向长连接通信特点

  • 持久连接: 一旦连接建立,双方可以在不需要重新建立连接的情况下相互发送消息,直到连接被显式断开。
  • 双向通信: 两个端点可以在连接的生命周期内自由地发送和接收消息。
  • 低延迟: 适合实时性要求高的应用场景,如聊天应用、实时数据流等。

演示

内容脚本 (content.js)

内容脚本通过 chrome.runtime.connect 方法与后台脚本建立连接,并监听连接端口上的消息。

// 与后台脚本建立连接
const port = chrome.runtime.connect({ name: 'content-background' });

// 监听来自后台脚本的消息
port.onMessage.addListener(function(msg) {
    console.log('Received message from background:', msg);
    
    // 可以根据后台脚本的消息更新页面或执行操作
});

// 发送消息给后台脚本
port.postMessage({ action: 'init', data: 'Hello from content script' });
后台脚本 (background.js)

后台脚本通过 chrome.runtime.onConnect.addListener 监听内容脚本的连接请求,并在连接建立后监听消息和发送响应。

// 监听内容脚本的连接请求
chrome.runtime.onConnect.addListener(function(port) {
    console.log('Connected:', port.name);

    // 监听来自内容脚本的消息
    port.onMessage.addListener(function(msg) {
        console.log('Received message from content script:', msg);
        
        // 根据收到的消息执行操作,并发送回应
        if (msg.action === 'init') {
            port.postMessage({ response: 'Connection established' });
        }
    });

    // 连接关闭时执行操作
    port.onDisconnect.addListener(function() {
        console.log('Port disconnected');
    });
});

例子的详细解释:

  • 连接建立: 内容脚本通过 chrome.runtime.connect 方法与后台脚本建立连接,并指定一个连接名称name,以便区分不同的连接。
  • 消息发送与接收: 一旦连接建立,内容脚本和后台脚本可以通过连接端口 (port) 进行双向消息传递。port.postMessage 用于发送消息,port.onMessage.addListener 用于接收消息。
  • 连接断开: 当连接不再需要时,或者内容脚本所在的页面关闭时,连接会被自动断开。可以通过 port.onDisconnect.addListener 监听连接断开事件。

跨扩展/应用的消息传递

跨扩展/应用的消息传递 是指在不同的Chrome扩展、应用或网页之间传递消息。这种通信方式允许不同的扩展或应用之间进行数据交换或协作,实现更复杂的功能和集成。以下是跨扩展/应用消息传递的主要方式和应用场景:

这种方式允许一个扩展向另一个扩展发送消息,但前提是这两个扩展都需要声明适当的权限,并且消息传递是通过扩展的 ID 实现的。

跨扩展/应用的消息传递 有两种实现方式,总结为 消息模型连接模型 。这与前两者消息类型有相似的关系。

消息模型

发送消息

要向另一个扩展发送消息,您需要知道目标扩展的 ID。可以使用 chrome.runtime.sendMessage 发送消息到指定的扩展。

// 向指定 ID 的扩展发送消息
chrome.runtime.sendMessage('TARGET_EXTENSION_ID', { action: 'someAction', data: 'Hello from extension A' });
接收消息

目标扩展需要在其后台脚本或其他组件中监听消息,并作出相应的处理。

// 监听来自其他扩展的消息
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
    if (message.action === 'someAction') {
        console.log('Received message from another extension:', message.data);
        // 处理消息
    }
});

连接模型

这种方法适用于建立持久的连接,可以用于跨扩展的持久通信。

建立连接

扩展 A 通过 chrome.runtime.connect 建立与扩展 B 的连接(前提是扩展 B 允许此连接)。

// 扩展 A 中建立连接
const port = chrome.runtime.connect('TARGET_EXTENSION_ID', { name: 'extensionA-to-B' });
监听连接

扩展 B 监听来自扩展 A 的连接请求,并处理数据交换。

// 扩展 B 中监听连接
chrome.runtime.onConnect.addListener(function(port) {
    console.log('Connected with port name:', port.name);

    port.onMessage.addListener(function(message) {
        console.log('Received message from extension A:', message);
        // 处理消息
    });
});

权限和声明

要使跨扩展消息传递功能正常工作,目标扩展需要在其 manifest.json 文件中声明权限。通常需要声明 *://*/* 或特定的 URL 权限,以及 identity 权限。

{
  "manifest_version": 3,
  "name": "Extension B",
  "version": "1.0",
  "permissions": [
    "identity",
    "https://FunTester.com/*" // 指定允许的 URL 或域名
  ],
  "background": {
    "service_worker": "background.js"
  }
}

应用场景

  1. 跨扩展集成: 例如,扩展 A 可以与扩展 B 交换数据,实现扩展功能的扩展或集成。
  2. 跨应用通信: 一个扩展可以与网页应用或桌面应用进行通信,传递信息或触发操作。
  3. 插件协作: 不同的扩展或插件可以协同工作,共享状态或功能,以增强用户体验。

注意事项:

  • 权限: 跨扩展消息传递需要适当的权限配置,确保目标扩展的权限允许接收消息。
  • 安全性: 确保消息传递机制的安全性,避免恶意扩展通过跨扩展通信方式访问敏感数据。

页面与内容脚本之间的消息传递

页面与内容脚本之间的消息传递 是在 Chrome 扩展中实现网页(页面脚本)与内容脚本之间的数据交换和互动的机制。内容脚本运行在网页的隔离环境中,而页面脚本是网页本身的 JavaScript 代码,两者之间不能直接共享变量或函数。因此,需要使用消息传递机制进行通信。

应用场景

  • 数据交换: 在网页的页面脚本和内容脚本之间交换数据,比如从网页中获取用户输入并发送到内容脚本进行处理。
  • 实时更新: 实现网页内容的实时更新,例如当内容脚本修改了网页上的内容,页面脚本需要做出相应的调整。
  • 跨组件通信: 通过内容脚本作为中介实现页面脚本与后台脚本的通信。

通过这些机制,Chrome 扩展能够在网页和扩展的不同组件之间实现高效的数据交换和互动,增强用户体验和扩展功能。与 跨扩展/应用的消息传递 类似,也有两种不同风格的 API

window API

window.postMessage 是在页面脚本和内容脚本之间传递消息的主要方式。它允许不同的窗口(包括 iframe 和跨域窗口)之间发送消息。具体步骤如下:

内容脚本 (content.js)

内容脚本通过 window.postMessage 向网页中的页面脚本发送消息。

// 向页面脚本发送消息
window.postMessage({ type: 'FROM_CONTENT_SCRIPT', data: 'Hello from content script' }, '*');
页面脚本 (page.js)

页面脚本监听来自内容脚本的消息,并处理这些消息。

// 监听来自内容脚本的消息
window.addEventListener('message', function(event) {
    if (event.origin !== 'http://expected-origin.com') {
        // 验证消息来源
        return;
    }
    
    if (event.data.type === 'FROM_CONTENT_SCRIPT') {
        console.log('Received message from content script:', event.data.data);
        // 处理消息
    }
});

chrome Message API

这种方式用于内容脚本与背景脚本之间的消息传递,但在页面脚本和内容脚本之间需要依赖内容脚本作为中介。

内容脚本 (content.js)

内容脚本从页面脚本接收消息,并可以通过 chrome.runtime.sendMessage 将其转发给后台脚本。

// 监听来自页面脚本的消息
window.addEventListener('message', function(event) {
    if (event.data.type === 'FROM_PAGE_SCRIPT') {
        // 处理来自页面脚本的消息
        chrome.runtime.sendMessage({ type: 'FROM_CONTENT_SCRIPT', data: event.data.data });
    }
});
页面脚本 (page.js)

页面脚本通过 window.postMessage 向内容脚本发送消息。

// 向内容脚本发送消息
window.postMessage({ type: 'FROM_PAGE_SCRIPT', data: 'Hello from page script' }, '*');
后台脚本 (background.js)

后台脚本接收内容脚本转发的消息。

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
    if (message.type === 'FROM_CONTENT_SCRIPT') {
        console.log('Received message from content script:', message.data);
        // 处理消息
    }
});

消息传递的步骤

  1. 页面脚本向内容脚本发送消息: 使用 window.postMessage 从页面脚本发送消息到内容脚本。
  2. 内容脚本处理消息并转发: 内容脚本使用 window.addEventListener 监听来自页面脚本的消息。内容脚本可以选择直接处理消息,也可以通过 chrome.runtime.sendMessage 将消息转发到后台脚本。
  3. 内容脚本向页面脚本发送消息: 内容脚本使用 window.postMessage 向页面脚本发送消息。
  4. 页面脚本处理返回的消息: 页面脚本使用 window.addEventListener 监听来自内容脚本的消息。