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


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

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

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

  手動控制發送json,監聽返回結果(完全控制,就剩下websocket鏈接等基本信息cefsharp維護),但是操作比較麻煩

手動執行CDP方法

DevToolsClient

Cefsharp提供的CDP封裝類,封裝了CDP各種方法模塊直接調用方法,但是使用姿勢不對可能會執行后程序卡死,具體各種卡死情況請跳轉stackoverflow

可以通過chromiumWebBrowser.GetBrowser().GetDevToolsClient() 獲得DevToolsClient實例。

最好不要頻繁調用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已經簡單封裝了消息結果類型

方法原型:

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: 消息ID

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

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中:

//全局變量
IRegistration reg = null;
chromiumWebBrowser1.IsBrowserInitializedChanged += new EventHandler(delegate {
    reg = chromiumWebBrowser1.GetBrowserHost().AddDevToolsMessageObserver(new DevToolsMessageObserverHandler());
});

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

這里需要注意AddDevToolsMessageObserver方法返回的IRegistration對象,這個對象控制監聽器是否繼續監聽,如果調用reg.Dispose(),監聽器將再也不起作用。

不要寫成這樣:

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

如果寫成這樣,將在第一次監聽到消息后,會隨着GC空閑時,把AddDevToolsMessageObserver返回的IRegistration對象回收,監聽器也就無效了

IBrowserHost.SendDevToolsMessage

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

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

bool SendDevToolsMessage(string messageAsJson);

發前需要我們記一下消息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}}");
            });


以上根據個人理解總結,如有錯誤的地方,歡迎前輩指出,非常感謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM