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 Protocol 协议详解 #82

Open
Pines-Cheng opened this issue Jul 21, 2020 · 2 comments
Open

Chrome DevTools Protocol 协议详解 #82

Pines-Cheng opened this issue Jul 21, 2020 · 2 comments
Labels

Comments

@Pines-Cheng
Copy link
Owner

Pines-Cheng commented Jul 21, 2020

默认是 http://localhost:9222/devtools/inspector.html ,带模拟器;http://localhost:9222/devtools/devtools_app.html 不带模拟器。

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

image

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 插件。

协议调试

使用 "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-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 命名规范:

  1. 动作:enable、disable、focus
  2. 动词+名词: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 协议规范,整个协议的定义、使用及生态还是非常不错,如果有类似的需求,参照着进行设计能够大大提升项目规范性,少踩一些坑。

参考

@MinWest
Copy link

MinWest commented May 19, 2021

这里是什么意思,没有明白

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

@Pines-Cheng
Copy link
Owner Author

这里是什么意思,没有明白

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

@MinWest 笔误,已修正。

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