CefSharp 手動執行CDP(Chrome DevTools Protocol)和監聽執行CDP 方法消息(messageId)返回結果

CefSharp 提供了多種執行CDP(Chrome DevTools Protocol)方式,有高度封裝的DevToolsClient.Page、DevToolsClient.DOM等等,也有徹底手動執行的IBrowserHost下的SendDevToolsMessage,這裏咱們只討論手動執行方式。web

手動執行CDP方式目前我知道的有兩種:

  只傳入CDP方法名稱、參數,返回結果(Cefsharp維護 發送消息ID、接收消息ID; 有些方法也提供了消息ID入參),使用方便,可是因爲消息id是Cefsharp維護,頻繁發送時有時會拋出消息ID不匹配異常;json

  手動控制發送json,監聽返回結果(徹底控制,就剩下websocket連接等基本信息cefsharp維護),可是操做比較麻煩瀏覽器

手動執行CDP方法

DevToolsClient

Cefsharp提供的CDP封裝類,封裝了CDP各類方法模塊直接調用方法,可是使用姿式不對可能會執行後程序卡死,具體各類卡死狀況請跳轉stackoverflow服務器

能夠經過chromiumWebBrowser.GetBrowser().GetDevToolsClient() 得到DevToolsClient實例。websocket

最好不要頻繁調用GetDevToolsClient() 獲取DevToolsClient,由於聽說每次獲取會重置消息ID,頻繁獲取可能會致使 發送/接收消息ID衝突,因此最好聲明全局變量在ChromiumWebBrowser實例初始化完成時獲取一次:異步

DevToolsClient devTool = null;

private void Form1_Load(object sender, EventArgs e){
    //....
    ChromiumWebBrowser chromiumWebBrowser1 = new ChromiumWebBrowser();
    
    chromiumWebBrowser1.IsBrowserInitializedChanged+= new EventHandler(delegate {
        devTool = chromiumWebBrowser1.GetBrowser().GetDevToolsClient();
    });
}

DevToolsClient.ExecuteDevToolsMethodAsync

我感受相對比較簡單的手動調用CDP方式,CefSharp維護髮送消息ID,Cefsharp已經簡單封裝了消息結果類型socket

方法原型:函數

public class DevToolsClient : IDevToolsMessageObserver, IDisposable, IDevToolsClient
{
    //....

    public Task<DevToolsMethodResponse> ExecuteDevToolsMethodAsync(string method, IDictionary<string, object> parameters = null);
}
method: CDP 方法名稱
parameters: 方法參數
返回結果DevToolsMethodResponse:
public class DevToolsMethodResponse
{
    public DevToolsMethodResponse();

    public int MessageId { get; set; }
    public string ResponseAsJsonString { get; set; }
    public bool Success { get; set; }
}

MessageId: 消息IDui

ResponseAsJsonString: 返回消息內容(消息的result內容)this

Success: 是否執行成功

好比執行刷新頁面:

private void button8_Click(object sender, EventArgs e)
{
    devTool.ExecuteDevToolsMethodAsync("Page.reload").ContinueWith(delegate(Task<DevToolsMethodResponse> result) {
        Console.WriteLine(result.Result.ResponseAsJsonString);
    });
}

獲取頁面結構:

private void button8_Click(object sender, EventArgs e)
{
    devTool.ExecuteDevToolsMethodAsync("DOM.enable").ContinueWith(delegate(Task<DevToolsMethodResponse> result) {
        Dictionary<string, object> param = new Dictionary<string, object>() {
            { "depth", 10 },
            { "pierce", true }
        };
        devTool.ExecuteDevToolsMethodAsync("DOM.getDocument", param).ContinueWith(delegate(Task<DevToolsMethodResponse> resultA) {
            Console.WriteLine(resultA.Result.ResponseAsJsonString);
        });
    });
}
DOM.enable: 開啓DOM代理
DOM.getDocument: 獲取頁面結構(包含嵌套的iframe內容),有兩個可選參數(depth: 獲取結構深度,pierce: 是否遞歸向下查詢iframes)

 可是別使用Wait()奧- -,像這樣:

private void button8_Click(object sender, EventArgs e)
{
    devTool.ExecuteDevToolsMethodAsync("DOM.enable").Wait();
}

會發現程序卡死了...當初這個問題困擾很久,上邊的overflow上的問題就是我提出的,截止到如今,尚未大佬關注...o(╥﹏╥)o

DevToolsExtensions

另外一個執行CDP方法的靜態類,主要用來擴展實現IBrowserHost、IWebBrowser、IBrowser接口的實例能夠直接執行CDP方法,由於ChromiumWebBrowser實現了IWebBrowser和IBrowser,因此能夠在ChromiumWebBrowser實例中直接調用ExecuteDevToolsMethodAsync方法。

上邊chromiumWebBrowser1.GetBrowser().GetDevToolsClient()中GetDevToolsClient方法就是使用的此類中的擴展函數。

DevToolsExtensions.ExecuteDevToolsMethod

執行CDP方法,執行成功返回消息id,失敗則返回0。

和上邊不一樣的是,上邊執行CDP方法後,會異步返回方法執行結果,此方法沒有異步執行,而是返回了傳入的消息id,而且此方法必須在cefsharp線程中調用

public static class DevToolsExtensions
{
    public static int ExecuteDevToolsMethod(this IBrowserHost browserHost, int messageId, string method, JsonString parameters);
}
browserHost: 此處傳入 chromiumWebBrowser1.GetBrowserHost();
messageId: 消息ID
method: 方法名稱
parameters: 方法參數,傳入方法參數json字符串
好比獲取頁面結構:
int i = 1;
Cef.UIThreadTaskFactory.StartNew(delegate { chromiumWebBrowser1.GetBrowserHost().ExecuteDevToolsMethod(i, "DOM.enable"); i++; Console.WriteLine(chromiumWebBrowser1.GetBrowserHost().ExecuteDevToolsMethod(i, "DOM.getDocument", new JsonString("{\"pierce\": true, \"depth\": 1}"))); });

上邊方法只是發送指令,若是想要拿到指令對應的結果,就須要實現IDevToolsMessageObserver接口,至關於添加了一個監聽,監聽websocket發送過來的消息:

 class DevToolsMessageObserverHandler : IDevToolsMessageObserver
    {
        public void Dispose()
        {   
        }

        public void OnDevToolsAgentAttached(IBrowser browser)
        {
        }

        public void OnDevToolsAgentDetached(IBrowser browser)
        {
        }

        public void OnDevToolsEvent(IBrowser browser, string method, Stream parameters)
        {
        }

        public bool OnDevToolsMessage(IBrowser browser, Stream message)
        {
            return false;
        }

        public void OnDevToolsMethodResult(IBrowser browser, int messageId, bool success, Stream result)
        {
            byte[] bytes = new byte[result.Length];
            result.Read(bytes, 0, bytes.Length);
            StringBuilder sb = new StringBuilder();
            foreach (byte item in bytes)
            {
                sb.Append((char)item);
            }
            Console.WriteLine(sb.ToString());
        }
    }

這裏主要關注OnDevToolsMessage和OnDevToolsMethodResult方法,服務器發送一條消息時,先到OnDevToolsMessage方法,在到OnDevToolsMethodResult方法。

若是OnDevToolsMessage方法返回true,表示消息已處理,不會在執行後續的OnDevToolsMethodResult.

OnDevToolsMethodResult方法的messageId就是發送指令時傳入的消息ID.

把監聽類註冊到BrowserHost中:

chromiumWebBrowser1.IsBrowserInitializedChanged += new EventHandler(delegate {
    chromiumWebBrowser1.GetBrowserHost().AddDevToolsMessageObserver(new DevToolsMessageObserverHandler());
});

這樣每一條瀏覽器端的發送過來的消息,都會監聽到,可是這時就須要咱們本身來實現根據消息ID匹配CDP方法的返回結果了。

IBrowserHost.SendDevToolsMessage

徹底手動控制發送json,雖然自由度高但使用起來跟上邊比起來確實有些繁瑣

方法原型很簡單,只傳入一個json,CefSharp會直接給瀏覽器端發送這個json字符串,返回true表示執行成功,false表示執行失敗

bool SendDevToolsMessage(string messageAsJson);

發前須要咱們記一下消息id,發送後須要使用上邊監聽瀏覽器端消息內容方式匹配每一條消息ID,直到找到對應此條命令消息id對應返回結果。

和DevToolsExtensions.ExecuteDevToolsMethod函數同樣,須要在cefsharp線程中使用:

Cef.UIThreadTaskFactory.StartNew(delegate {
                IBrowserHost browserHose = chromiumWebBrowser1.GetBrowserHost();
                browserHose.SendDevToolsMessage("{\"id\": 1, \"method\": \"DOM.enable\"}");
                browserHose.SendDevToolsMessage("{\"id\": 2, \"method\": \"DOM.getDocument\", \"params\": {\"pierce\": true, \"depth\": 40}}");
            });
以上根據我的理解總結,若有錯誤的地方,歡迎前輩指出,很是感謝!
相關文章
相關標籤/搜索