Devtools架构浅析

写此文章的起源是我目前正在作的一个开源项目miniblink(欢迎访问miniblink.net来了解,这是个精简chromium内核),一直没有devtools功能,由于被精简了嘛。因此打算把这功能加上。html

但要加devtools,以前的想法是可能会有巨大困难。由于miniblink把chromium的基本除了blink以外的全部组件都裁剪掉了,包括content层。了解chromium的人都知道,content是chromium最重要的组件,构建了整个体系。以前很早的时候我就研究了下devtools的架构,发现除了blink外,其余全是content层的东西。这让我一时研究受阻。前端

不过此次决心不同了,非实现不可。我翻出同事以前研究过的资料,有个架构图画的颇有收获(建议保存下来慢慢看):node


这里类咋看之下不少,结构庞大。但我调试后发现,其实devtools架构简单的不得了!c++

对我有用的类其实就两个,DevToolsClient和DevToolsAgent,其余都是辅助性,或者由于chromium多进程架构带来的类。web

咱们来仔细分析下结构:chrome

Devtools分红两部分,一个叫front_end,前端部分。这个其实就是devtools的调试页面。也就是说,咱们平时看到的devtools,以下图所示:json


整个就是个网页而已。代码路径在third_party\WebKit\Source\devtools\front_end。入口是inspector.html。这个inspector是老的叫法,webkit时代,devtools就是叫inspector。bash


负责和inspector.html加载和对接的第一个类,就是以前我提到的content::DevToolsClient。因此chromium看来,front_end又是client。微信

DevToolsClient下面管着blink::WebDevToolsFrontend。DevToolsClient表明content层的devtools部分和blink层对接的最底层的类,而WebDevToolsFrontend就是blink层和content层最上层的类了。这两个货到底有啥用呢?这就涉及到devtools是如何工做的了。websocket

Devtools页面是html,而实际逻辑全都是js完成的。那就遇到个问题,devtools如何获取被调试页面发送来的消息?Devtools又是如何发消息给被调试页面?二者发的都是什么格式的消息?带着这三个问题,咱们拿个devtools最基本的功能,节点区域高亮来讲明。


如图,咱们在devtools里划到一个节点,如<i>上,被调试页面就会有个阴影,恰好盖住<i>所在的区域。这个devtools叫作DOM.highlightNode。

那么就来回答上面三个问题。首先,devtools和被调试页面通讯,全部协议都是走的json格式。

下面就是DOM.highlightNode的json请求样子:

{
   "id": 61,
   "method": "DOM.highlightNode",
   "params": {
       "highlightConfig": {
           "showInfo": true,
           "showRulers": false,
           "showExtensionLines": false,
           "contentColor": {
               "r": 111,
               "g": 168,
               "b": 220,
               "a": 0.66
           },
           "paddingColor": {
               "r": 147,
               "g": 196,
               "b": 125,
               "a": 0.55
           },
           "borderColor": {
               "r": 255,
               "g": 229,
               "b": 153,
               "a": 0.66
           },
           "marginColor": {
               "r": 246,
               "g": 178,
               "b": 107,
               "a": 0.66
           },
           "eventTargetColor": {
               "r": 255,
               "g": 196,
               "b": 196,
               "a": 0.66
           },
           "shapeColor": {
               "r": 96,
               "g": 82,
               "b": 177,
               "a": 0.8
           },
           "shapeMarginColor": {
               "r": 96,
               "g": 82,
               "b": 127,
               "a": 0.6
           },
           "showLayoutEditor": false
       },
       "nodeId": 7
   }
}复制代码

这是由devtools页面发送给被调试页面,要求被调试页面把nodeId=7的节点高亮。

这个消息是从

third_party\WebKit\Source\devtools\front_end\devtools.js的

DevToolsAPIImpl.prototype.sendMessageToEmbedder发出。

sendMessageToEmbedder: function(method, args, callback)
   {
       var callId = ++this._lastCallId;
       if (callback)
           this._callbacks[callId] = callback;
       var message = { "id": callId, "method": method };
       if (args.length)
           message.params = args;
       DevToolsHost.sendMessageToEmbedder(JSON.stringify(message));
}复制代码

这里的DevToolsHost.sendMessageToEmbedder就是个原生方法了。这就是由上文说的

blink::WebDevToolsFrontendImpl注册。

WebDevToolsFrontendImpl注册了v8的DevToolsHost对象和sendMessageToEmbedder方法。但这个方法在c++里是个虚函数,须要继承blink::WebDevToolsFrontendClient去实现。哪谁来实现呢?就是content::DevToolsClient了。

因此blink::WebDevToolsFrontendImpl和content::DevToolsClient就是这样被关联起来了。

从上面代码还能够看出有个calback,这个表示有些devtools发出的请求,须要返回数据。例如获取整个页面的node数据就是。

这个接收返回数据的地方,就是

DevToolsFrontendHostDelegateImpl::HandleMessageFromDevToolsFrontend。这个类我感受和content::DevToolsClient做用是差很少的,没搞明白为啥又要开个新类。反正我直接把这个和DevToolsClient合并了。

DevToolsFrontendHostDelegateImpl收到返回数据后,经过执行

"DevToolsAPI.dispatchMessage(" + *response + ");" 这句js,把被调试页面的返回数据,又通知了devtools页面。

Devtools页面的js,拿到返回数据后,又执行了以下堆栈:

at callback [143]:[21]at callback [937]:[58]at InspectorBackendClass.AgentPrototype.dispatchResponse [978]:[18]at InspectorBackendClass.Connection.dispatch [483]:[41]at InspectorBackendClass.MainConnection._dispatchMessage [637]:[14]at WebInspector.Object.dispatchEventToListeners [108]:[35]at innerDispatch [496]:[54]at InspectorFrontendAPIImpl._dispatch [489]:[17]at DevToolsAPIImpl._dispatchOnInspectorFrontendAPI [61]:[21]at DevToolsAPIImpl.dispatchMessage [128]:[14]:[devtools.js]at (anonymous function) [1]:[13]:[]复制代码

说完了devtools页面,那么被调试页面,又是如何响应devtools页面发来的json请求呢?

咱们先省略devtools到被调试页面之间复杂的通讯层,直接看最终接受消息的地方。这个在blink层就是content::DevToolsAgent类。此类有个blink::WebDevToolsAgent* webAgent 成员,并重载了

blink::WebDevToolsAgentClient

接口,也就是要实现

blink::WebDevToolsAgentClient::sendProtocolMessage、createClientMessageLoop等接口。

正是这个blink::WebDevToolsAgent* webAgent 成员,把来自devtools的消息,如上面的DOM.highlightNode json字符串,派发给了blink层。

webAgent->dispatchOnInspectorBackend正是这个派发的函数。

下面看个堆栈:

content.dll!content::DevToolsAgent::SendChunkedProtocolMessagecontent.dll!content::DevToolsAgent::sendProtocolMessageblink_web.dll!blink::WebDevToolsAgentImpl::sendProtocolResponsewebcore_shared.dll!blink::InspectorBackendDispatcherImpl::sendResponsewebcore_shared.dll!blink::InspectorBackendDispatcherImpl::sendResponsewebcore_shared.dll!blink::InspectorBackendDispatcherImpl::Console_enablewebcore_shared.dll!blink::InspectorBackendDispatcherImpl::dispatchblink_web.dll!blink::WebDevToolsAgentImpl::dispatchMessageFromFrontendblink_web.dll!blink::WebDevToolsAgentImpl::dispatchOnInspectorBackendcontent.dll!content::DevToolsAgent::OnDispatchOnInspectorBackend复制代码

这个堆栈,是devtools发出了console.enable消息,通知被调试页面开启console功能。

此消息由WebDevToolsAgentImpl::dispatchOnInspectorBackend收到后,派发给了

InspectorBackendDispatcherImpl::Console_enable。这个InspectorBackendDispatcherImpl就是个统一的派发和处理类。console.enable是个比较简单的消息,直接处理了,复杂的好比获取node相关信息,就会直接给blink内部的类,如Node\Element等来处理。

处理完毕后,就会调用咱们上面提到的WebDevToolsAgentImpl来发送返回消息。


正是这个blink::WebDevToolsAgentClient::sendProtocolMessage接口的实现类,DevToolsAgent::SendChunkedProtocolMessage,把反馈消息又经过一系列复杂的通讯机制,传给了devtools页面。Devtools接受这个返回消息的正是上文提到的DevToolsFrontendHostDelegateImpl::DispatchProtocolMessage。

因此看完这些,就知道若是不记中间复杂的通讯,devtools和被调试页面,互相启动一个DevToolsClient、DevToolsAgent就彻底搞定了两边消息的执行流程。那么剩下那一堆

DevToolsAgentHostImpl、RenderFrameDevToolsAgentHost就只是为了实现chromium多进程架构而搞出的各类代理、转发类。固然,还有些功能须要content给出必要信息,这个也须要content里开几个类来实现。

至于中间那堆复杂的通讯,其实也没那么可怕。Chromium支持跨平台调试。你在安卓上打开webview,是能在pc上用chrome调试的。这个过程是在图中AwDevtoolsServer\

DevToolsHttpHandle等类里实现的。原理是用websocket把刚才那堆json字符串发送和接收。这个我没继续研究下去了。咱们组的qingmei大师改造了后,能够实现远程调试了,你在本机使用微信的x5内核,他在别的城市能够直接调试你的webview。

另外,devtools里的js其实挺庞大的,这个我大体调试了下,记录了一堆零散笔记。有空的人能够去csdn上个人博客看下。

相关文章
相关标签/搜索