Pandorym

Pandorym


Wlcm to Quasi-reality Another Dimension.

Pandorym
Author

A freeLander, full stack developer. Love traveling, photographing and coding.

Share


Our Newsletter


Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Tags


avrt

Twitter


chrome-remote-interface

Chrome 調試協議 的接口,他提供一個使用 JavaScript API 的簡單的命令和通知抽象,幫助使用 Chrome(或任何其他合適的實現)。

PandorymPandorym

本文翻譯自:chrome-remote-interface
原文更新時間:July 21,2017
譯者:Pandorym

Chrome 調試協議 的接口,他提供一個使用 JavaScript API 的簡單的命令和通知抽象,幫助使用 Chrome(或任何其他合適的實現)。

這個模塊是眾多 第三方協議客戶端 之一。

簡單的 API 用法

下列片段加載https://github.com,并轉儲每個請求:

const CDP = require('chrome-remote-interface');

CDP((client) => {
    // extract domains
    const {Network, Page} = client;
    // setup handlers
    Network.requestWillBeSent((params) => {
        console.log(params.request.url);
    });
    Page.loadEventFired(() => {
        client.close();
    });
    // enable events then start!
    Promise.all([
        Network.enable(),
        Page.enable()
    ]).then(() => {
        return Page.navigate({url: 'https://github.com'});
    }).catch((err) => {
        console.error(err);
        client.close();
    });
}).on('error', (err) => {
    // cannot connect to the remote endpoint
    console.error(err);
});

維基 中尋找更多的例子,特別注意,上面的例子可以改寫成 async / await 原型。

你可能還想查看 FAQ。

安裝

npm install chrome-remote-interface

全局安裝(-g)可以使用附隨的客戶端。

實現

這個模塊應該和每個實現了 Chrome 調試協議 的應用程序一起工作。特別是,它已經對以下實現進行了測試:

Implementation Protocol version Protocol List New Activate Close Version
Google Chrome tip-of-tree yes yes yes yes yes yes
Microsoft Edge partial yes yes no no no yes
Node.js (v6.3.0+) node yes no no no no yes
Safari (iOS) partial no yes no no no no

目標(target)的含義根據實現而差異,例如,每個 Chrome 標籤代表一個目標,而對於 Node.js 一個目標是當前視察的腳本。

設置

為了使用這個模塊,Chrome 自身或其他實現的實例 需要運行在一個已知端口(默認為localhost:9222)。

Choreme/Chromium

Desktop

啟動 Chrome 時,使用--remote-debugging-port選項,例如:

google-chrome --remote-debugging-port=9222

Headless

在 59 之後的版本,額外使用--headless選項,例如:

google-chrome --headless --remote-debugging-port=9222

Android

插入設備,并啟動 端口轉發, 例如:

adb forward tcp:9222 localabstract:chrome_devtools_remote

WebView

為了視察,WebView 必需 已配置調試,並且對應的進程 ID 必需是已知的。有幾種方法可以獲得它,例如:

adb shell grep -a webview_devtools_remote /proc/net/unix

最後,可以使用如下方式啟動端口轉發:

adb forward tcp:9222 localabstract:webview_devtools_remote_<pid>

Edge

安裝并運行 Edge 診斷配適器(Diagnostics Adapter)

Node.js

啟動 Node.js 使附加--inspect選項,例如:

node --inspect=9222 script.js

Safari (iOS)

安裝并運行 iOS WebKit 調試代理(Debug Proxy)

附隨的客戶端

這個模塊有一個附隨的客戶端應用程序,他可用於交互式的控制遠程實例。

目標管理

該附隨的客戶端提供用於與 HTTP 前端交互的子命令(例如,List、New 等),運行--help以顯示一個可用選項的列表。

下面是一些例子:

$ chrome-remote-interface new 'http://example.com'
{
    "description": "",
    "devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/b049bb56-de7d-424c-a331-6ae44cf7ae01",
    "id": "b049bb56-de7d-424c-a331-6ae44cf7ae01",
    "thumbnailUrl": "/thumb/b049bb56-de7d-424c-a331-6ae44cf7ae01",
    "title": "",
    "type": "page",
    "url": "http://example.com/",
    "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/b049bb56-de7d-424c-a331-6ae44cf7ae01"
}
$ chrome-remote-interface close 'b049bb56-de7d-424c-a331-6ae44cf7ae01'

查閱

使用子命令inspect可以以 REPL 方式進行 命令執行 和 事件綁定。不像常規的 API,該回調被重寫了,以方便顯示命令的返回值和事件的信息。還有,這里的事件綁定是簡化的,執行一個速寫方法(例如Page.loadEventFired())切換事件註冊。

記住,該 REPL 接口提供了實現。

這是一個簡單的片段:

$ chrome-remote-interface inspect
>>> Runtime.evaluate({expression: 'window.location.toString()'})
{ result:
   { result:
      { type: 'string',
        value: 'https://www.google.it/_/chrome/newtab?espv=2&ie=UTF-8' },
     wasThrown: false } }
>>> Page.enable()
{ result: {} }
>>> Page.loadEventFired() // registered
{ 'Page.loadEventFired': true }
>>> Page.loadEventFired() // unregistered
{ 'Page.loadEventFired': false }
>>> Page.loadEventFired() // registered
{ 'Page.loadEventFired': true }
>>> Page.navigate({url: 'https://github.com'})
{ result: { frameId: '28677.1' } }
{ 'Page.loadEventFired': { timestamp: 21385.383076 } }
>>> Runtime.evaluate({expression: 'window.location.toString()'})
{ result:
   { result: { type: 'string', value: 'https://github.com/' },
     wasThrown: false } }

事件過濾

為了減少來自事件監聽器的數據顯示量,可以提供一個過濾函數。在這個例子中,只顯示資源的 URL:

$ chrome-remote-interface inspect
>>> Network.enable()
{ result: {} }
>>> Network.requestWillBeSent(params => params.request.url)
{ 'Network.requestWillBeSent': 'params => params.request.url' }
>>> Page.navigate({url: 'https://www.wikipedia.org'})
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/' }
{ result: { frameId: '5530.1' } }
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia_wordmark.png' }
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png' }
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/js/index-3b68787aa6.js' }
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/js/gt-ie9-c84bf66d33.js' }
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-bookshelf_icons.png?16ed124e8ca7c5ce9d463e8f99b2064427366360' }
{ 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-project-logos.png?9afc01c5efe0a8fb6512c776955e2ad3eb48fbca' }

內置文檔

在 REPL和常規 API 中的每個協議對象都擁有在元信息中找到的描述符。此外,還添加了一個category字段,譯確定成員是commandeventtype

例如,學習如何調用Page.navigate

>>> Page.navigate
{ [Function]
  category: 'command',
  parameters: { url: { type: 'string', description: 'URL to navigate the page to.' } },
  returns:
   [ { name: 'frameId',
       '$ref': 'FrameId',
       hidden: true,
       description: 'Frame id that will be navigated.' } ],
  description: 'Navigates current page to the given URL.',
  handlers: [ 'browser', 'renderer' ] }

學習來自Network.requestWillBeSent事件的參數:

>>> Network.requestWillBeSent
{ [Function]
  category: 'event',
  description: 'Fired when page is about to send HTTP request.',
  parameters:
   { requestId: { '$ref': 'RequestId', description: 'Request identifier.' },
     frameId:
      { '$ref': 'Page.FrameId',
        description: 'Frame identifier.',
        hidden: true },
     loaderId: { '$ref': 'LoaderId', description: 'Loader identifier.' },
     documentURL:
      { type: 'string',
        description: 'URL of the document this request is loaded for.' },
     request: { '$ref': 'Request', description: 'Request data.' },
     timestamp: { '$ref': 'Timestamp', description: 'Timestamp.' },
     wallTime:
      { '$ref': 'Timestamp',
        hidden: true,
        description: 'UTC Timestamp.' },
     initiator: { '$ref': 'Initiator', description: 'Request initiator.' },
     redirectResponse:
      { optional: true,
        '$ref': 'Response',
        description: 'Redirect response data.' },
     type:
      { '$ref': 'Page.ResourceType',
        optional: true,
        hidden: true,
        description: 'Type of this resource.' } } }

視察Network.Request類型(注意,不同於命令和時間,類型使用大駝峰拼寫法):

>>> Network.Request
{ category: 'type',
  id: 'Request',
  type: 'object',
  description: 'HTTP request data.',
  properties:
   { url: { type: 'string', description: 'Request URL.' },
     method: { type: 'string', description: 'HTTP request method.' },
     headers: { '$ref': 'Headers', description: 'HTTP request headers.' },
     postData:
      { type: 'string',
        optional: true,
        description: 'HTTP POST request data.' },
     mixedContentType:
      { optional: true,
        type: 'string',
        enum: [Object],
        description: 'The mixed content status of the request, as defined in http://www.w3.org/TR/mixed-content/' },
     initialPriority:
      { '$ref': 'ResourcePriority',
        description: 'Priority of the resource request at the time request is sent.' } } }

Chrome 調試協議版本

chrome-remote-interface默認使用本地版本的協議描述符。不停的更新這個文件,使用scripts/update-protocol.sh並且推送到這個倉庫。

這種行為可以改變在 connectionremote選項為true,在這種情況,遠程實例要求自己提供協議描述符。

Chrome < 60.0.3097.0 不支持這麼做,所以當這種情況,將從源倉庫獲取協議描述符。

有三個選項可以覆蓋上述行為:

  • 使用特定的協議描述符 connection (protocol 選項);
  • 用原始版本的命令和事件接口來使用前沿特性,這不會出現在 本地版本 的協議描述符;
  • 更新scripts/update-protocol.sh的本地副本(當使用npm install獲取時不存在)。

瀏覽器用法

這個模塊可以運行在 Web 上下文,但有明顯的局限性,即外部 HTTP請求(List,New 等)不能直接執行,因此為了使用它 用戶必須提供一個全局 criRequest

function criRequest(options, callback) {}

options與在 Node.js http模塊中的寫法一樣,並且callback是一個接受兩個參數的函數:err(JavaScript Error對象,或null)和data(字符串返回值)。

使用 webpack

只需要使用這個模塊,他就會工作:

const CDP = require('chrome-remote-interface');

使用 non-minified 版本手動運行 webpack :

DEBUG=true npm run webpack

使用 Vanilla JavaScript

生成一個 JavaScript 文件,這可以使用 <script> 標籤。

  1. 在根目錄運行npm install
  2. 手動運行 webpack:
  TARGET=var npm run webpack
  TARGET=var DEBUG=true npm run webpack
  1. 像這樣使用:
  <script>
    function criRequest(options, callback) { /*...*/ }
  </script>
  <script src="chrome-remote-interface.js"></script>

API

該 API 由三部分組成:

  • DevTools 方法(對於那些 實現 的支持,例如,List、New 等);
  • 建立連接;
  • 該實際上的協議接口;

CDP([options], [callback])

使用 Chrome 調試協議 連接一個遠程實例。
options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost

  • port:HTTP 前端端口。默認為9222

  • secure:HTTPS/WSS 前端。默認為false

  • target:確定這個客戶端應該連接到哪個目標。行為根據類型的變化而變化:

    • 一個function,該函數以List方法返回的數組作為參數,并返回一個目標、或目標在數組中的數值索引;
    • 一個目標object,如NewList方法的返回值;
    • 一個string,他表示原始 WebSocket URL,在這種情況,hostport不用於獲取目標列表,但是他們將用於使相對 URL 變得完整。
    • 一個string,他表示目標 id。

    默認是一個實現了返回第一個可用目標的函數。(注意,最多可在同一目標上建立一個連接);

  • protocolChrome 調試協議 描述符對象。默認使用根據遠程選項選擇的協議;

  • remote:一個布爾值,指示協議是否必須遠程獲取,或者必須使用本地版本。如果設置了protocol選項,這個選擇不會生效。默認值為false

這些選項在該CDP類實例的所有操作都是有效屬性。除此之外, webSocketURL字段包含當前使用的 WebSocket URL。

callback是一個監聽器,將自動添加到返回EventEmitterconnect事件。當callback缺省時,將返回一個Promise對象,如果觸發connect事件即為成功(fulfilled),如果觸發error事件即為失敗(rejected)。

EventEmitter支持下列事件:

Event: 'connetn'

function (client) {}

當到 WebSocket 的連接已建立時觸發。

client是一個CDP類的實例。

Event: 'error'

function (err) {}

當不能訪問http://host:port/json,或不能連接到 WebSocket 時觸發。

err是一個Error的實例。

CDP.Protocol([options], [callback])

取得該 Chrome 調試協議 描述符。

options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost
  • port:HTTP 前端端口。默認為9222
  • secure:HTTPS/WSS 前端。默認為false
  • remote:一個布爾值,指示協議是否必須遠程獲取,或者必須使用本地版本。如果不能完成請求,則使用本地版本。默認值為false

callback在取得協議后執行,他將獲得下列參數:

  • err:一個Error對象,指明成功狀態;
  • protocol:一個對象,擁有下列屬性:
    • remote:一個布爾值,指明返回的描述符是否是遠程版本(取決于用戶選擇或錯誤);
    • descriptor:該 Chrome 調試協議 描述符。

callback缺省時,將返回一個Promise對象。

For example:

const CDP = require('chrome-remote-interface');
CDP.Protocol(function (err, protocol) {
    if (!err) {
        console.log(JSON.stringify(protocol.descriptor, null, 4));
    }
});

CDP.List([options], [callback])

請求遠程實例的打開的可用目標 / 標籤的列表。

options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost
  • port:HTTP 前端端口。默認為9222
  • secure:HTTPS/WSS 前端。默認為false

callback在正確取得列表后執行,他將獲得下列參數:

  • err:一個Error對象,指明成功狀態;
  • targets:該數組返回`http://host:port/json/list中包含的目標列表。

callback缺省時,將返回一個Promise對象。

For example:

const CDP = require('chrome-remote-interface');
CDP.List(function (err, targets) {
    if (!err) {
        console.log(targets);
    }
});

CDP.New([options], [callback])

在遠程實例中創建一個新的目標 / 標籤。

options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost
  • port:HTTP 前端端口。默認為9222
  • secure:HTTPS/WSS 前端。默認為false
  • url:URL 用於載入新目標 / 標籤。默認為about:blank

callback在創建目標后執行,他將獲得下列參數:

  • err:一個Error對象,指明成功狀態;
  • targets:該對象,來自http://host:port/json/new中包含的目標。

callback缺省時,將返回一個Promise對象。

For example:

const CDP = require('chrome-remote-interface');
CDP.New(function (err, target) {
    if (!err) {
        console.log(target);
    }
});

CDP.Activate([options], [callback])

在遠程實例中激活一個目標 / 標籤。

options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost
  • port:HTTP 前端端口。默認為9222
  • secure:HTTPS/WSS 前端。默認為false
  • id:目標 id。必填,無默認值。

callback在激活請求收到響應后執行,他將獲得下列參數:

  • err:一個Error對象,指明成功狀態;

callback缺省時,將返回一個Promise對象。

For example:

const CDP = require('chrome-remote-interface');
CDP.Activate({'id': 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, function (err) {
    if (!err) {
        console.log('target is activated');
    }
});

CDP.Close([options], [callback])

關閉一個在遠程實例中打開的目標 / 標籤。

options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost
  • port:HTTP 前端端口。默認為9222
  • secure:HTTPS/WSS 前端。默認為false
  • id:目標 id。必填,無默認值。

callback在關閉請求收到響應后執行,他將獲得下列參數:

  • err:一個Error對象,指明成功狀態;

callback缺省時,將返回一個Promise對象。

For example:

const CDP = require('chrome-remote-interface');
CDP.Close({'id': 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, function (err) {
    if (!err) {
        console.log('target is closing');
    }
});

注意,當目標排隊等待刪除時,該回調將被觸發,但真正的刪除將異步執行。

CDP.Version([options], [callback])

從遠程實例請求版本信息。

options是一個對象,并有下列可選屬性:

  • host:HTTP 前端主機。默認為localhost
  • port:HTTP 前端端口。默認為9222
  • secure:HTTPS/WSS 前端。默認為false

callback在正確獲得版本信息后執行,他將獲得下列參數:

  • err:一個Error對象,指明成功狀態;
  • info:一個 JSON 對象,來自http://host:port/json/version包含的版本信息。

callback缺省時,將返回一個Promise對象。

For example:

const CDP = require('chrome-remote-interface');
CDP.Version(function (err, info) {
    if (!err) {
        console.log(info);
    }
});

Class: CDP

Event: 'event'

function (message) {}

當遠程實例通過 WebSocket 發送任何通知時觸發。

message是接收的對象,他有下列屬性:

  • method:一個字符串,描述該通知(例如)'Network.requestWillBeSent');
  • params:一個對象,包含有效載荷(payload)。

參考 Chrome 調試協議 規格獲取更多信息。

For example:

client.on('event', function (message) {
    if (message.method === 'Network.requestWillBeSent') {
        console.log(message.params);
    }
});

Event: '.'

function (params) {}

當遠程實例通過 WebSocket 發送關於<domain>.<method>的通知時觸發。

params:一個對象,包含有效載荷(payload)。

這是一個實用的時間,他可以很容易的偵聽特定的時間(看 'event'),for example:

client.on('Network.requestWillBeSent', console.log);

Event: 'ready'

function () {}

每次不存在 等待遠程實例響應的命令 時觸發。交互是異步的,序列化命令隊列的唯一方式是使用 send 方法提供的回調。這個時間起到一個屏障作用,並且在某些簡單情況下,可以避免回調地獄。

鼓勵使用者廣泛的檢查每個方法的響應,並且在處理複雜的異步程序流是應該更喜歡 promises API。

例如,僅在啟用了NetworkPage域的通知后才加載 URL:

client.Network.enable();
client.Page.enable();
client.once('ready', function () {
    client.Page.navigate({'url': 'https://github.com'});
});

在這個特殊的情況下,不強制序列化執行可能導致遠程實例不能正確的交付通知該客戶端。

Event: 'disconnect'

function () {}

當實例關閉該 WebSocket 連接時觸發。

這可能發生在當用戶打開 DevTools 或關閉標籤。

client.send(method, [params], [callback])

向遠程實例發出一個命令。

method 是一個描述命令的字符串。

params 是一個對象,包含有效載荷。

callback 是在收到遠程實例關於這個命令的響應時執行,他將獲得下列參數:

  • error:一個布爾值,指明成功狀態,來自遠程實例;
  • response:一個對象,包含響應(result 字段,如果error === false)或者錯誤知識(error字段,如果`error === true)。

callback缺省時,將返回一個Promise對象,並且 fulfilled/rejected 狀態取決于error屬性。

在低等級 WebSocket 錯誤的情況下,該error包含產生的Error對象,並且沒有response返回。

注意,他的字段idChrome 調試協議 中提到是內部管理的,並且不向用戶揭露。

For example:

client.send('Page.navigate', {'url': 'https://github.com'}, console.log);

client..([params], [callback])

就是一個速寫:

client.send('<domain>.<method>', params, callback);

For example:

client.Page.navigate({'url': 'https://github.com'}, console.log);

client..([callback])

就是一個速寫:

client.on('<domain>.<event>', callback);

唯一的區別是,當callback省卻時,事件只註冊一次,并返回一個Promise對象。

For example:

client.Network.requestWillBeSent(console.log);

client.close([callback])

關閉到遠程實例的連接。

callback當該 WebSocket 成功關閉時執行。

callback缺省時,將返回一個Promise對象。

FAQ

調用Domain.method,我獲得提示Domain.method is not a function

這意味著你正在使用的協議描述符中不包含Domain.method。如果你確信你的 Chrome 實例支持這種方法,你可以直接調用它:

client.send('Domain.method', ...);

或者詢問 Chrome 正確的協議描述符是什麼:

CDP({remote: true});

這裡,獲得更多信息。

調用Domain.method,我獲得提示Domain.method wasn't found

這意味著你正在使用的協議描述符包含一個不受 Chrome 實例支持的方法。最有可能的原因是,你正在嘗試使用一個最前沿的功能,試試升級到新的 Chrome 版本吧。

這裡,獲得更多信息。

檢查正確的協議描述符:

$ chrome-remote-interface inspect --remote

Headless Chrome 問題?

記住,--headless Chrome 是相對較新的,他的操作指南(in Chrome)還在制定中。如果你認為遇到了一個 Bug,那麼就看看這些開放性 issues,尤其是 外部 issues

為什麼當我在 Docker 容器中運行 Chrome 時,我的程序會停滯或表現得出乎意料呢?

這是因為在 Docker 的默認設置中,/dev/shm的大小被設置為 64MB,這可能不足以讓 Chrome 跳轉到某些網頁。

你可以改變這個值,在運行你的容器時附帶說,--shm-size=256m

投稿者

資源

Pandorym
Author

Pandorym

A freeLander, full stack developer. Love traveling, photographing and coding.

Comments