👆 这是第 140 篇不掺水的原创,想要了解更多,请戳下方卡片关注我们吧~
CDP 远程调试方案 https://www.zoo.team/article/cdp
cdp 协议简称 chrome 调试协议,是基于 scoket(websocket、usb、adb )消息的 json rpc 协议。用来调用 chrome 内部的方法实现 js、css 、dom 的开发调试。 可以将实现了 cdp 协议的应用看做 rpc 调用的服务端( chrome 、puppeteer), 将调试面板看做 rpc 调用的客户端(devtools)。
完整的调试系统分别由前端,后端,协议,通道四部分组成
如上,所有能够消费发送 cdp 消息的应用都可以被 chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 调试。chrome 集成了 cdp 协议,所以只需要通过消息通道与 chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 应用建立 socket 链接就可以被调试端调试。调试启动分为三步:
以 chrome、puppetter 为例启动 backend 应用,设置调试端口 9222。
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=./test
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
args: ['--remote-debugging-port=9222', '--remote-debugging-address=0.0.0.0'],
}); const page = await browser.newPage();
// let res= await page.goto('https://www.baidu.com');
console.log(browser.wsEndpoint());
// output -> ws://127.0.0.1:57724/devtools/browser/705082a5-19e6-4e9a-b8a6-477b7b6e1bd6
})();
此时访问 http://0.0.0.0:9222/json 此时可以获取调试页面 id 和页面的 webSocketDebuggerUrl ,代表 backend 端启动成功。
[ {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/659E9C174913FC5670B5F6E78B6B91FD",
"id": "659E9C174913FC5670B5F6E78B6B91FD",
"title": "欢迎使用 Chrome",
"type": "page",
"url": "chrome://welcome/",
"webSocketDebuggerUrl": "ws://0.0.0.0:9222/devtools/page/659E9C174913FC5670B5F6E78B6B91FD"
}, {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/9902C5D5F55923DFC457B7AA423F957E",
"faviconUrl": "https://cn.bing.com/sa/simg/favicon-2x.ico",
"id": "9902C5D5F55923DFC457B7AA423F957E",
"title": "必应",
"type": "page",
"url": "https://cn.bing.com/",
"webSocketDebuggerUrl": "ws://0.0.0.0:9222/devtools/page/9902C5D5F55923DFC457B7AA423F957E"
}, {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/428C9AC3F9B6AF7235CC41D849DD7589",
"faviconUrl": "https://www.baidu.com/favicon.ico",
"id": "428C9AC3F9B6AF7235CC41D849DD7589",
"title": "百度一下,你就知道",
"type": "page",
"url": "https://www.baidu.com/",
"webSocketDebuggerUrl": "ws://0.0.0.0:9222/devtools/page/428C9AC3F9B6AF7235CC41D849DD7589"
} ]
chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 作为一个纯 web 应用,可以通过 koa 部署访问。同时 chrome 默认也集成了 chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 服务。在 cdp 服务端启动后可以直接访问 http://0.0.0.0:9222/devtools/inspector.html。
传入 socket_backend_url ,http://0.0.0.0:9222/devtools/inspector.html?ws=${webSocketDebuggerUrl}
或者直接访问 http://0.0.0.0:9222/ 可以看到浏览器中的的 tab 线程页,点击需要调试的页面,http://0.0.0.0:9222/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/9902C5D5F55923DFC457B7AA423F957E , 就可以连接 chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 调试了。
此时打开 network,查看 ws,可以看到 backend 端在接收 chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 发送的 cdp 命令,并将 dom、network、资源信息返回给远程调试端
cdp 协议按域「Domain」划分能力,每个域下有 Method、Event 和 Types。
([https://chromedevtools.github.io/devtools-protocol/tot/Debugger/](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/) )
Method 对应 socket 通信的请求 / 响应模式,Events 对应 socket 通信的发布 / 订阅模式,Types 为交互中使用到的实体。
用远程执行一段 js 为例,在 chrome-devtools-frontend(https://github.com/ChromeDevTools/devtools-frontend) 控制台输入下面代码,backend 端分别收到执行三个 cdp 命令。Runtime.compileScript -> Runtime.evaluate -> Runtime.compileScript
pp=function(){
alert(244)
}
pp()
{
"id": 47,
"method": "Runtime.evaluate",
"params": {
"expression": "pp=function(){alert(244)}",
"includeCommandLineAPI": true,
"generatePreview": true,
"userGesture": false,
"awaitPromise": false,
"throwOnSideEffect": true,
"timeout": 500,
"disableBreaks": true,
"replMode": true,
"uniqueContextId": "-3083772769491583108.6833303176535544523"
}
}
{
"id": 178,
"method": "Runtime.compileScript",
"params": {
"expression": "pp=function(){alert(244)}",
"sourceURL": "",
"persistScript": false,
"executionContextId": 7
}
}
{
"id": 100,
"method": "Runtime.compileScript",
"params": {
"expression": "pp()",
"sourceURL": "",
"persistScript": false,
"executionContextId": 7
}
}
var ws = new WebSocket('ws://127.0.0.1:9222/devtools/page/620F91C22D41B614947001C52AC55E53');
window.ppp=function(){
// 调用 Command
debugger
ws.onmessage = function(event) {
console.log(event.data);
// 获取数据:{"method": "Page.loadEventFired", "params": {"timestamp": 1402317772.874949}}
};
ws.send('{"id": 1, "method": "Page.navigate", "params": {"url": "http://www.github.com"}}');
}
chrome 社区提供了基础的远程调试方案 :devtools-remote(https://github.com/auchenberg/devtools-remote)。分别提供 websocket 服务做消息转发和 chrome 插件在 backend 端来监听执行发送 cdp 消息。
devtools-remote(https://github.com/auchenberg/devtools-remote) 调试插件在 background 层实现 cdp 消息监听, 响应和执行。主要依赖下面几个 api。Chrome Extensions Api(chrome.debugger)(https://developer.chrome.com/docs/extensions/reference/debugger/#type-Debuggee)
插件内部逻辑实现
socket_proxy 逻辑代理服务器的逻辑很简单,将来自插件端 'data.response' 、 'data.event' 的事件消息转发到调试应用上。将来自调试应用 'data.request' 事件消息转发到 backend 插件端。
此外社区还有 chobitsu 这样的 cdp 解析库,提供了 Storage、Runtime、Network、DOM、DOMDebugger、DOMStorage 的能力。与需要依赖在插件 background 层执行 chrome.debugger API 的方案不同。chobitsu 在浏览器运行时环境中手动实现了cdp 协议。 基于chobitsu 的能力可以实现待调试页面通过加载 backend.js 监听浏览器事件 执行 cdp 命令,实现 dom css network 的远程调试。具体用例可以参考 devtools-pro(https://github.com/wanwu/devtools-pro.git) 。由此方案进一步联想到 xss 安全的重要性。
同理 react-devtools 的实现方案,也与 cdp 方案类似, 在调试页面中引入或者通过插件插入 backend.js,监听变化发送到调试应用。
PS:之前我们有输出过在线调试的方法,链接是 前端工程师生产环境 debugger 技巧,感兴趣的同学可以点开看一下。
参考文档
chrome DevTools Protocol(https://chromedevtools.github.io/devtools-protocol/tot/Debugger/) chrome-remote-interface(https://github.com/cyrus-and/chrome-remote-interface) 深入理解 Chrome DevTools(https://zhaomenghuan.js.org/blog/chrome-devtools.html)