Open Pines-Cheng opened 4 years ago
主要内容:
由于内容脚本(content scripts)运行在 web 页面的上下文中,而不是在扩展(extension)中,因此它们通常需要某种方式与扩展(extension)的其余部分进行通信。 例如,RSS 阅读器扩展可能使用内容脚本(content scripts)检测页面上是否存在 RSS feed,然后通知后台页面以显示该页面的页面操作图标。
扩展(extension)和它们的内容脚本(content scripts)之间的通信是通过消息传递(message passing)进行的。 任何一方都可以 监听(listen) 从另一端发送的消息,并在同一个通道(channel)上作出响应。 消息可以包含任何有效的 JSON 对象(null、boolean、number、string、array 或 object)。 有一个用于 一次性请求(one-time requests)的简单 API ,还有一个更复杂的 API,它们允许你使用 长连接(long-lived connections) 通过共享上下文(shared context)交换(exchanging)多个消息。 如果您知道另一个扩展的 ID,也可以将消息发送到该扩展,这在 跨插件消息(cross-extension messages)消息部分中有介绍。
另外还有 Sending messages from web pages 以及 Native messaging。
如果您只需要向扩展的另一部分发送一条消息(并且可以选择获得回复) ,那么您应该使用简化的 runtime.sendMessage
或 tabs.sendMessage
。 这允许您分别将一次性的 JSON-serializable
消息从内容脚本(content script)发送到扩展,或者反之亦然。 可选的回调参数允许您处理来自另一端的响应(如果有的话)。
从内容脚本发送请求如下:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
从扩展向内容脚本发送请求看起来非常相似。
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
有时候,比起单一的请求和响应,有一个持续时间更长的对话是有用的。 在这种情况下,您可以分别使用 runtime.connect
或 tabs.connect
打开从内容脚本(content script)到扩展页面的 long-lived channel,反之亦然。 通道可以有选择地有一个名称,允许您区分不同类型的连接。
下面是如何从内容脚本中打开一个频道,并发送和监听消息:
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question == "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question == "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});
为了处理传入的连接,需要设置 runtime.onConnect
事件侦听器。 从内容脚本或扩展页面来看,这看起来是一样的。
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke == "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer == "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer == "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
需要注意 Port lifetime。
除了在扩展(extension)中的不同组件之间发送消息外,还可以使用消息传递 API 与其他扩展进行通信。 这使您可以公开其他扩展可以利用的公共 API。
侦听传入的请求和连接与内部情况类似,只是使用 runtime.onMessageExternal
或 runtime.onConnectExternal
方法。 下面是每种方法的一个例子:
// For simple requests:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id == blocklistedExtension)
return; // don't allow this extension access
else if (request.getTargetData)
sendResponse({targetData: targetData});
else if (request.activateLasers) {
var success = activateLasers();
sendResponse({activateLasers: success});
}
});
// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
同样,向另一个扩展发送消息类似于在扩展中发送消息。 唯一的区别是,您必须传递要与之通信的扩展的 ID。 例如:
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
});
// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
与跨扩展消息传递(cross-extension messaging,)类似,你的应用程序或扩展程序可以接收和响应来自常规网页的消息。 要使用这个功能,你必须首先在你的 manifest.json 中指定你想要与哪些网站通信。 例如:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
这将向任何与你指定的 URL 模式匹配(patterns matches)的页面公开消息传递 API。 URL 模式(patterns)必须至少包含一个二级域(second-level domain),即主机名模式(hostname patterns),如 “ * ”、“ * .com”、“ *.co.uk” 和 “ *.appspot.com”
是禁止的。 在网页上,使用 runtime.sendMessage
或 runtime.connect
API 向特定的应用程序或扩展发送消息。 例如:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
从您的应用程序(app)或扩展(extension),您可以通过 runtime.onMessageExternal
或 runtime.onConnectExternal
API 监听来自网页的消息,类似于跨扩展消息传递(cross-extension messaging)。 只有网页可以启动连接。 下面是一个例子:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blocklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
扩展和应用程序可以与注册为 [原生消息主机(native messaging host)] (https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host)的 原生应用(native applications) 交换消息 exchange messages 。 要了解关于此特性的更多信息,请参见 Native messaging。
你可以在 examples/api/messaging 目录中找到通过消息进行通信的简单示例。 native messaging sample演示了 Chrome 应用程序如何与本地应用程序通信。 有关更多示例和查看源代码的帮助,请参见 示例。
我想问题,devtools页面要和content注入到webpage的脚本进行交互,
在devtoos页面使用api chrome.devtools.inspectedWindow.eval
能够和注入脚本进行通讯
除了这种方式,还有其他的没有?
DevTools 原理
DevTools 本质上可以看成是一个前端小应用,代码在这里: ChromeDevTools/devtools-frontend,当然,你也可以在 Chrome 浏览器直接打开:
devtools://devtools/bundled/inspector.html
查看运行效果。DevTools 是通过 Chrome 远程调试协议(Remote Debugger Protocal) 来和后端进行交互和调试的,这里说的后端一般指的是:Chrome 的远程调试功能,可以通过
在指定端口开启,然后在浏览器地址栏输入
http://localhost:${port}
能看到一个列表页面,列出了当前所有可调试的页面和插件。点击Example Page,会导向到
http://localhost:9222/devtools/inspector.html?ws=localhost:9222/devtools/page/55A4F84F6A66845F72388146E3B8F986
。长得和内嵌devtools 一样的 html 页面。inspector.html 和 Chrome Host 之间通过 WebSocket 建立连接,这个 WebSocket 地址就是 url 中 ws参数的值。其中
55A4F84F6A66845F72388146E3B8F986
是page id
,每个页面都有一个唯一的page id
,Chrome 就是通过这个 id 确定哪个是目标页面。页面和 Chrome 内核之间就是通过这个连接交换数据的。Chrome 调试器实例和目标页面实例之间是进程通信,所以inspector.html
可以通过Chrome 调试器实例加载目标页面的 Source 文件,还可以操作目标页面,例如加断点、刷新、记录Network 信息等。通过 Chrome 远程调试协议(Remote Debugger Protocal) 建立连接之后,就会向调试后台发送很多请求来展现数据并进行交互。
此外,你还可以使用 Chrome Remote Debugger Protocal,通过 Node 与 Chrome 调试后台进行交互,并直接控制 Chrome。
Chrome Debugging Protocol
简单来说,远程调试协议就是利用 WebSocket 建立连接 DevTools 和浏览器内核的快速数据通道。那么我们也可以自己打开这个 WebSocket,遵从它的协议来发送消息。
在前面 inspector.html 和 Chrome Host 之间通过 WebSocket 建立连接后,从整个调试过程中的 Websocket 通讯可以看出,这个接口里面有两种通讯模式:Cmmand 和 Event。
远程调试协议把操作划分为不同的域 domain ,比如
可以理解为 DevTools 中的不同功能模块。每个域(domain)定义了它所支持的 command 和它所产生的 event(就是上面讲的两种通讯方式)。每个 command 包含 request 和 response 两部分,request 部分指定所要进行的操作以及操作说要的参数,response 部分表明操作状态,成功或失败。command 和 event 中可能涉及到非基本数据类型,在 domain 中被归为 Type,比如:'frameId': ,其中 FrameId 为非基本数据类型。
至此,不难理解: domain = command + event + type
很多工具都使用了Chrome Debugging Protocol,包括 PhantomJS,Selenium 的 ChromeDriver,本质都是一样的实现,它就相当于 Chrome 内核提供的 API 让应用调用。官网列出了很多有意思的工具:awesome-chrome-devtools/Developing with the protocol,因为 API 丰富,所以才有了这么多的 Chrome 插件。
协议调试
使用 "Protocol Monitor":
打开 devTools-on-devTools,然后在内部 DevTools 窗口中,使用 Main。控制台中的
MainImpl.sendOverProtocol()
:DevTools 拓展与整合
Chrome DevTools 本身就具备很好的拓展性。如果 DevTools 缺少一个你需要的特性,你可以找一找现成的扩展(extension),或者干脆自己写一个,同时你也可以选择将 DevTools 功能集成到你的应用中。
使用 DevTools 构建自定义解决方案有两种基本方式:
下面的小节将讨论这两种方法。
DevTools Chrome extensions
DevTools UI 是一个嵌入在 Chrome 中的 web 应用程序。 Devtools 扩展使用 Chrome extensions system 为 DevTools 添加功能。DevTools 扩展可以向 DevTools 添加新的面板(panels),向 Elements 和 Sources 面板侧边栏(panel sidebar)添加新的窗格(panes),检查 resources 和 network 事件,以及在被 inspected 的浏览器选项卡(tab)中执行 JavaScript 表达式。
如果你想开发一个 DevTools 扩展:
有关 DevTools 扩展的实例列表,请参考 Sample DevTools Extensions。 这些示例包括许多可供参考的 Extensions 源码。
Debugging protocol clients
第三方应用程序,如 IDE、编辑器、持续集成框架和测试框架都可以与 Chrome 调试器集成,以调试代码、实时预览代码和 CSS 更改,并控制浏览器。 客户端使用 Chrome debugging protocol 与 Chrome 实例进行交互,该实例既可以在同一个系统上运行,也可以远程运行。
注意:目前,Chrome debugging protocol 每个 Page 只支持一个客户端。 因此,您可以使用 DevTools inspect 页面,或者使用第三方客户端,但两者不能同时 inspect。
有两种方法可以与调试协议集成:
有关一些集成示例,请参考:Sample Debugging Protocol Clients
参考