Chrome Extension开发之Message passing
由于extension中包含的content scripts运行在web page的上下文(context),而不是extension的上下文,所以,content scripts经常需要与extension的其他组件通信(communicating)。例如一个RSS阅读器extension可能使用content script去网页中探测现有的RSS feed,再通知后台页,就可以展现对应的page action图标。
在content scripts和extension之间通信,就是通过message passing实现的。通信双方可以监听(listen)对方发送过来的消息(message),并可以使用同一渠道(channel)返回结果(respond)。一个message可以包含任何有效的JSON对象。
一次性请求(on-time requests)
如果只需要向对方发送一个简单的message,应该使用runtime.sendMessage()或tabs.sendMessage()方法,这两个方法发送一次性的JSON序列化后的message给对方,如果需要获得返回结果(response back),可以使用callback参数。
从一个content script发送请求,如下:
|
|
从extension向content script发送请求的代码类似,除了需要确定请求发到哪个tab。
|
|
在接受一端,需要设定一个message事件监听器来处理message请求,使用方法在content script和extension page是相同的。
|
|
在上面的代码中,sendResponse是同步执行的,如果需要异步执行,在方法结尾添加return true。
注意:如果多个page都在监听onMessage事件,同一个事件,只有第一个调用sendResponse()的会发送response成功。其他的responses都会被忽略。
注意:如果没有message处理器(handlers)返回true,或者sendResponse回调函数被垃圾回收,sendMessage函数的callback会自动执行。
持续性连接(Long-lived connections)
有时候,比起单次请求通信,更需要持续较长时间的通信对话。这时,你可以打开content script和extension page的持续性通信渠道(long-lived channel)。使用runtime.connet()或tabs.connect()方法。为了区分不同类型的连接,可以为通信渠道指定名字(name)。
一个应用实例是自动添加表单的extension。
content script可以打开一个channel与extension page通信,通过向extension发送message,把页面中每个输入元素的内容发送过去,并获取响应的返回数据来自动填充。这个共享的连接允许extension保持共享状态,链接来自content script的若干信息(messages)。
在建立连接的时候,每个终端获得一个runtime.Port对象,用来通过连接发送和接受message。
|
|
从extension向content script发送请求的代码与上面代码相似,除了需要指定连接哪个tab。只需要把上面代码中建立连接的方法换成tabs.connect()方法。
为了处理传入的连接,需要设置一个runtime.onConnect事件监听器。这个方法的使用,在content script和extension page使用方法是一样的。当extension的一个组件调用了connect()方法,这个事件就会被触发,可以通过这个连接,使用runtime.Port对象来发送和接受message。示例代码如下。
|
|
Port对象的生命周期
Port被设计为extension的不同部分之间双向通信方案,其中extension的最小部分单位是一个frame。在调用tabs.connect,runtime.connect,rutime.connectNative方法时,会创建Port对象。这个Port对象可以马上用来通过postMessage向extension另一部分发送消息。
如果一个tab中包含多个frame,当调用tabs.connect方法,会导致触发多次runtime.onConnect事件。同样的,如果runtime.connect被调用,onConnect事件也会触发多次。
可能有时需要知道一个connection是何时关闭的,例如当你为每个打开的Port单独维护它的状态时。这时,可以通过监听runtime.Port.onDisconnect事件来达到目的。当channel的另一端没有有效Port时,就会触发这个事件。通常在以下情况发生:
- 在另一端没有runtime.onConnect的监听器;
- 包含port的tab处于未载入状态,即tab导航到了其他页面;
- 调用connect方法的frame已经处于未载入状态;
- 所有接受这个port消息的frame都处于未载入状态;
- 另一端调用了runtime.Port.disconnect方法。注意:如果一次connect方法调用的接收方有多个port,当任意一个port调用了disconnect方法,那么onDisconnect事件只会在这个port触发,其他port不会触发。
扩展之间的通信(Cross-extension Messaging)
除了在扩展中的不同组件之间发送消息外,还可以使用消息传递 API 与其他扩展进行通信。这样可以把自己的API作为公共API给其他扩展利用。
监听消息请求和连接与扩展内部通信类似,不同之处在于调用的方法不同。需要调用runtime.onMessageExternal和runtime.onConnectExternal方法。
|
|
向另一个extension发送消息类似于在extension内部发送消息。唯一的区别是必须要传递与之通信的扩展程序ID。
|
|
网页中向extension发送消息
与跨extension通信类似,extension还可以接收来自常规网页的消息。要使用这个功能,必须在manifest.json中指定要与之通信的网站。
|
|
加入以上代码,就会将消息传递API暴露给指定的URL正则表达式对应页面。URL正则表达式中必须包含一个二级域名,即禁止使用“*”、“*.com”、“*.co.uk”和“*.appspot.com”等域名。在网页中,使用runtime.sendMessage或runtime.connect方法向特定app或extension发送message。
|
|
在扩展中,监听来自网页的message与扩展之间传递message的方法一样,使用runtime.onMessageExternal或runtime.onConnectExternal方法。
这种连接,只有从网页端发起才能建立。
原生应用消息(Native messaging)
Extensions can exchange messages with native applications that are registered as a native messaging host.
文章作者 没湿巾公爵
上次更新 2021-11-23