Chrome DevTools Protocol 协议详解
- RPC 的概念在前端应用还是比较广泛的
- VSCode 的 Debug Adapter Protocol 的协议定义上更加丰富
- 大部分并没有严格遵守:JSON-RPC 2.0 Specification
- 源码及相关的可以参考:Chrome DevTools Frontend 运行原理浅析,写得很详细,不想重复了。
默认是 http://localhost:9222/devtools/inspector.html ,带模拟器;http://localhost:9222/devtools/devtools_app.html 不带模拟器。
DevTools 本质上可以看成是一个前端小应用,代码在这里: ChromeDevTools/devtools-frontend,当然,你也可以在 Chrome 浏览器直接打开:devtools://devtools/bundled/inspector.html 查看运行效果。

DevTools 是通过 Chrome 远程调试协议(Remote Debugger Protocal) 来和后端进行交互和调试的,
使用 DevTools 作为 protocol client
Developer Tools front-end 可以附加到远程运行的 Chrome 实例以进行调试。 为了让这个方案起作用,你应该使用 remote-debugging-port 命令行开关启动你的 host Chrome 实例:
Windows :chrome.exe --remote-debugging-port=9222
Mac : /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
如果碰到证书问题,添加:--ignore-certificate-errors
然后你可以启动一个独立的 client Chrome 实例,使用一个不同的用户配置文件:
chrome.exe --user-data-dir=<some directory>
在这个场景中,您可以用自己的实现替换 Developer Tools 前端。你的应用程序可以通过请求: google http://localhost:9222 和获取一个 JSON 对象来发现可用的页面,这个 JSON 对象包含可检查页面的信息和 WebSocket 地址,你可以使用这些信息来开始检测它们。
GET /json or /json/list 获取可用的 websocket targets:
[ {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734",
"id": "DAB7FB6187B554E10B0BD18821265734",
"title": "Yahoo",
"type": "page",
"url": "https://www.yahoo.com/",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734"
} ]
DevTools protocol 通过 Chrome extension
参考:chrome.debugger extension API
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
很多工具都使用了Chrome Debugging Protocol,包括 PhantomJS,Selenium 的 ChromeDriver,本质都是一样的实现,它就相当于 Chrome 内核提供的 API 让应用调用。官网列出了很多有意思的工具:awesome-chrome-devtools/Developing with the protocol,因为 API 丰富,所以才有了这么多的 Chrome 插件。
- chrome-debug-protocol 使用了ES6和TypeScript
- chrome-remote-interface 官网推荐的
- chrome-har-capturer 传入url,直接获取har format文件
协议调试
使用 "Protocol Monitor":

- 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-frontend 使用 JSON 定义。
TS 类型文件也是通过脚本读取 JSON 文件生成的:protocol_dts_generator.ts
简单概览:
{
"domain": "DOM",
"description": "This domain exposes DOM read/write operations",
"dependencies": ["Runtime"],
"types": [
{
"id": "NodeId",
"description": "Unique DOM node identifier.",
"type": "integer"
}
......
],
"commands": [
{
"name": "getAttributes",
"description": "Returns attributes for the specified node.",
"parameters": [
{
"name": "nodeId",
"description": "Id of the node to retrieve attibutes for.",
"$ref": "NodeId"
}
],
"returns": [
{
"name": "attributes",
"description": "An interleaved array of node attribute names and values.",
"type": "array",
"items": {
"type": "string"
}
}
]
}
......
],
"events": [
{
"name": "childNodeRemoved",
"description": "Mirrors `DOMNodeRemoved` event.",
"parameters": [
{
"name": "parentNodeId",
"description": "Parent id.",
"$ref": "NodeId"
},
{
"name": "nodeId",
"description": "Id of the node that has been removed.",
"$ref": "NodeId"
}
]
}
......
]
}
关键字解析:
- domain:协议内容分为若干域 Domain(DOM、Debugger、Network等)。每个域定义了它所支持的许多命令(commands)和它所生成的事件(events)。命令和事件都是固定结构的序列化 JSON 对象。
- experimental:是否试验性
- description:描述
- dependencies:依赖
- types:Cmmand 和 Event 中涉及到的数据类型的定义
- commands:如同一个异步调用,通过请求的信息,获取相应的返回结果。这样的通讯必然有一个message id,否则两方都无法正确的判断请求和返回的匹配状况。
- events:用于由一方单方面的通知另一方某个信息。
Command / Event 命名规范:
- 动作:enable、disable、focus
- 动词+名词:getAttributes、highlightNode、removeNode、copyTo、
协议示例
🔝{"id":1,"method":"Network.enable","params":{"maxPostDataSize":65536}}
🔝{"id":2,"method":"Page.enable"}
🔝{"id":3,"method":"Page.getResourceTree"}
🔝{"id":4,"method":"Runtime.enable"}
🔝{"id":5,"method":"Profiler.enable"}
🔝{"id":6,"method":"Debugger.enable","params":{"maxScriptsCacheSize":10000000}}
🔝{"id":9,"method":"DOM.enable"}
🔝{"id":10,"method":"CSS.enable"}
⬇️{"method":"Target.attachedToTarget","params":{"sessionId":"93AFA6399DA8A8623D82C166AF8094A2","targetInfo":{"targetId":"9F45931CC38889806267A00B8BB40478","type":"iframe","title":"chrome-extension://react-perf/devtools.html","url":"chrome-extension://react-perf/devtools.html","attached":true,"browserContextId":"35183A5B763F607EACECFD0BBA9421A0"},"waitingForDebugger":false}}
🔝{"id":29,"method":"Page.getResourceTree","sessionId":"93AFA6399DA8A8623D82C166AF8094A2"}
⬇️{"id":1,"result":{}}
⬇️{"id":2,"result":{}}
⬇️{"id":3,"result":{"frameTree":{"frame":{"id":"1071A0A3CD4026CF3910F3BCF58D1171","loaderId":"AF4B4B66A0E7C141C845309F85599A47","url":"devtools://devtools/bundled/devtools_app.html?remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/@36cc3c133da6270352f9b7ccb712eeacee0dd458/&can_dock=true&toolbarColor=rgba(223,223,223,1)&textColor=rgba(0,0,0,1)&experiments=true","securityOrigin":"devtools://devtools","mimeType":"text/html"},"resources":[{"url":"devtools://devtools/bundled/devtools_app.js","type":"Script","mimeType":"application/javascript","contentSize":195937},{"url":"devtools://devtools/bundled/Images/largeIcons.svg","type":"Image","mimeType":"image/svg+xml","contentSize":20597},{"url":"devtools://devtools/bundled/Images/treeoutlineTriangles.svg","type":"Image","mimeType":"image/svg+xml","contentSize":120},{"url":"devtools://devtools/bundled/Images/smallIcons.svg","type":"Image","mimeType":"image/svg+xml","contentSize":15394},{"url":"devtools://devtools/bundled/shell.js","type":"Script","mimeType":"application/javascript","contentSize":195282}]}}}
⬇️{"method":"CSS.mediaQueryResultChanged","params":{},"sessionId":"93AFA6399DA8A8623D82C166AF8094A2"}
使用 Chrome Protocol monitor Panel 查看:

error
⬇️{"error":{"code":-32000,"message":"Not supported"},"id":20}
错误码定义
未找到相关规范。
协议使用
前端
参考官方推荐的 chrome-remote-interface,接口优雅,异步编程。
const CDP = require('chrome-remote-interface');
async function example() {
let client;
try {
// connect to endpoint
client = await CDP();
// extract domains
const {Network, Page} = client;
// setup handlers
Network.requestWillBeSent((params) => { // events
console.log(params.request.url);
});
// enable events then start!
await Network.enable(); // command
await Page.enable();
await Page.navigate({url: 'https://github.com'});
await Page.loadEventFired();
} catch (err) {
console.error(err);
} finally {
if (client) {
await client.close();
}
}
}
example();
总的来说,Chrome DevTools Protocol 是 基于 Websocket 的 JSON RPC 协议规范,整个协议的定义、使用及生态还是非常不错,如果有类似的需求,参照着进行设计能够大大提升项目规范性,少踩一些坑。
参考
这里是什么意思,没有明白
打开 devTools-on-devTools,然后在内部 DevTools 窗口中,使用 Main。控制台中的 MainImpl.sendOverProtocol()
这里是什么意思,没有明白
打开 devTools-on-devTools,然后在内部 DevTools 窗口中,使用 Main。控制台中的 MainImpl.sendOverProtocol()
@MinWest 笔误,已修正。