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}}");
            });
以上根据我的理解总结,若有错误的地方,欢迎前辈指出,很是感谢!
相关文章
相关标签/搜索