Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chrome DevTools 原理、拓展与整合 #78

Open
Pines-Cheng opened this issue Mar 17, 2020 · 2 comments
Open

Chrome DevTools 原理、拓展与整合 #78

Pines-Cheng opened this issue Mar 17, 2020 · 2 comments
Labels

Comments

@Pines-Cheng
Copy link
Owner

Pines-Cheng commented Mar 17, 2020

DevTools 原理

DevTools 本质上可以看成是一个前端小应用,代码在这里: ChromeDevTools/devtools-frontend,当然,你也可以在 Chrome 浏览器直接打开:devtools://devtools/bundled/inspector.html 查看运行效果。

image

DevTools 是通过 Chrome 远程调试协议(Remote Debugger Protocal) 来和后端进行交互和调试的,这里说的后端一般指的是:Chrome 的远程调试功能,可以通过

sudo /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

在指定端口开启,然后在浏览器地址栏输入 http://localhost:${port} 能看到一个列表页面,列出了当前所有可调试的页面和插件。

image

点击Example Page,会导向到 http://localhost:9222/devtools/inspector.html?ws=localhost:9222/devtools/page/55A4F84F6A66845F72388146E3B8F986。长得和内嵌devtools 一样的 html 页面。

image

inspector.html 和 Chrome Host 之间通过 WebSocket 建立连接,这个 WebSocket 地址就是 url 中 ws参数的值。其中 55A4F84F6A66845F72388146E3B8F986page id,每个页面都有一个唯一的page id,Chrome 就是通过这个 id 确定哪个是目标页面。页面和 Chrome 内核之间就是通过这个连接交换数据的。Chrome 调试器实例和目标页面实例之间是进程通信,所以 inspector.html 可以通过Chrome 调试器实例加载目标页面的 Source 文件,还可以操作目标页面,例如加断点、刷新、记录Network 信息等。

通过 Chrome 远程调试协议(Remote Debugger Protocal) 建立连接之后,就会向调试后台发送很多请求来展现数据并进行交互。

image

此外,你还可以使用 Chrome Remote Debugger Protocal,通过 Node 与 Chrome 调试后台进行交互,并直接控制 Chrome。

Chrome Debugging Protocol

简单来说,远程调试协议就是利用 WebSocket 建立连接 DevTools 和浏览器内核的快速数据通道。那么我们也可以自己打开这个 WebSocket,遵从它的协议来发送消息。

在前面 inspector.html 和 Chrome Host 之间通过 WebSocket 建立连接后,从整个调试过程中的 Websocket 通讯可以看出,这个接口里面有两种通讯模式:Cmmand 和 Event。

  • Command:包含 request/response ,就如同一个异步调用,通过请求的信息,获取相应的返回结果。这样的通讯必然有一个message id,否则两方都无法正确的判断请求和返回的匹配状况。
request: {"id":1,"method":"Page.canScreencast"}
response: {"id":1,"result":{"result":false}}
  • Event:类似于 notification ,和第一种不同,这种模式用于由一方单方面的通知另一方某个信息。和“事件”的概念类似。
{"method":"Network.loadingFinished","params:{"requestId":"14307.143","timestamp":1424097364.31611,"encodedDataLength":0}}

远程调试协议把操作划分为不同的域 domain ,比如

  • DOM
  • Debugger
  • Network
  • Console
  • Timeline

可以理解为 DevTools 中的不同功能模块。每个域(domain)定义了它所支持的 command 和它所产生的 event(就是上面讲的两种通讯方式)。每个 command 包含 request 和 response 两部分,request 部分指定所要进行的操作以及操作说要的参数,response 部分表明操作状态,成功或失败。command 和 event 中可能涉及到非基本数据类型,在 domain 中被归为 Type,比如:'frameId': ,其中 FrameId 为非基本数据类型。

至此,不难理解: domain = command + event + type

image

很多工具都使用了Chrome Debugging Protocol,包括 PhantomJS,Selenium 的 ChromeDriver,本质都是一样的实现,它就相当于 Chrome 内核提供的 API 让应用调用。官网列出了很多有意思的工具:awesome-chrome-devtools/Developing with the protocol,因为 API 丰富,所以才有了这么多的 Chrome 插件。

协议调试

使用 "Protocol Monitor":

image

  • DevTools-on-DevTools

打开 devTools-on-devTools,然后在内部 DevTools 窗口中,使用 Main。控制台中的 MainImpl.sendOverProtocol() :

let Main = await import('./main/main.js');
await Main.MainImpl.sendOverProtocol('Emulation.setDeviceMetricsOverride', {
  mobile: true,
  width: 412,
  height: 732,
  deviceScaleFactor: 2.625,
});

const data = await Main.MainImpl.sendOverProtocol("Page.captureScreenshot");

DevTools 拓展与整合

Chrome DevTools 本身就具备很好的拓展性。如果 DevTools 缺少一个你需要的特性,你可以找一找现成的扩展(extension),或者干脆自己写一个,同时你也可以选择将 DevTools 功能集成到你的应用中。

使用 DevTools 构建自定义解决方案有两种基本方式:

  • DevTools Extension:一个插入到 DevTools 中的 Chrome extension,可以增加功能和扩展用户界面。
  • Debugging Protocol Client:使用 Chrome remote debugging protocol 插入 Chrome 的底层调试支持的第三方应用程序。

下面的小节将讨论这两种方法。

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 扩展:

image

image

有关 DevTools 扩展的实例列表,请参考 Sample DevTools Extensions。 这些示例包括许多可供参考的 Extensions 源码。

Debugging protocol clients

第三方应用程序,如 IDE、编辑器、持续集成框架和测试框架都可以与 Chrome 调试器集成,以调试代码、实时预览代码和 CSS 更改,并控制浏览器。 客户端使用 Chrome debugging protocol 与 Chrome 实例进行交互,该实例既可以在同一个系统上运行,也可以远程运行。

注意:目前,Chrome debugging protocol 每个 Page 只支持一个客户端。 因此,您可以使用 DevTools inspect 页面,或者使用第三方客户端,但两者不能同时 inspect。

有两种方法可以与调试协议集成:

  • 运行在 Chrome 中的应用程序(比如基于 Web 的 IDE)可以使用调试器模块 chrome.debugger 创建 Chrome 扩展,此模块允许扩展与调试器直接交互,绕过 DevTools 用户界面。参见:Using the debugger extension API
  • 其他应用程序可以使用 wire protocol 和 debugger 直接交互,该协议需要通过 WebSocket 连接交换 JSON 消息。

image

有关一些集成示例,请参考:Sample Debugging Protocol Clients

参考

@Pines-Cheng Pines-Cheng changed the title Integrating with DevTools Chrome DevTools 原理、拓展与整合 Mar 18, 2020
@Pines-Cheng
Copy link
Owner Author

Pines-Cheng commented Mar 23, 2020

主要内容:

  1. 内容脚本(content scripts)和 扩展(extension)
  2. 跨插件通信(chrome.runtime.sendMessage(laserExtensionId,)
  3. 从网页发送信息( chrome.runtime.sendMessage(editorExtensionId,)
  4. Native messaging

Message Passing

由于内容脚本(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

Simple one-time requests

如果您只需要向扩展的另一部分发送一条消息(并且可以选择获得回复) ,那么您应该使用简化的 runtime.sendMessagetabs.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);
  });
});

Long-lived connections

有时候,比起单一的请求和响应,有一个持续时间更长的对话是有用的。 在这种情况下,您可以分别使用 runtime.connecttabs.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。

Cross-extension messaging

除了在扩展(extension)中的不同组件之间发送消息外,还可以使用消息传递 API 与其他扩展进行通信。 这使您可以公开其他扩展可以利用的公共 API。

侦听传入的请求和连接与内部情况类似,只是使用 runtime.onMessageExternalruntime.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(...);

Sending messages from web pages

与跨扩展消息传递(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.sendMessageruntime.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.onMessageExternalruntime.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

扩展和应用程序可以与注册为 [原生消息主机(native messaging host)] (https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host)的 原生应用(native applications) 交换消息 exchange messages 。 要了解关于此特性的更多信息,请参见 Native messaging

Security considerations

  • Content scripts are less trustworthy
  • Cross-site scripting

Examples

你可以在 examples/api/messaging 目录中找到通过消息进行通信的简单示例。 native messaging sample演示了 Chrome 应用程序如何与本地应用程序通信。 有关更多示例和查看源代码的帮助,请参见 示例

参考

@tidys
Copy link

tidys commented Apr 6, 2021

我想问题,devtools页面要和content注入到webpage的脚本进行交互,
在devtoos页面使用api chrome.devtools.inspectedWindow.eval能够和注入脚本进行通讯
除了这种方式,还有其他的没有?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants