写此文章的起源是我目前正在作的一个开源项目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上个人博客看下。