zoukankan      html  css  js  c++  java
  • 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}}");
                });


    以上根据个人理解总结,如有错误的地方,欢迎前辈指出,非常感谢!
  • 相关阅读:
    knockout之ko if绑定和ifnot绑定
    inline-block去掉空白距离的方法
    DOM
    $(document).ready()与window.onload的区别
    white-space: nowrap
    商品列表属性过滤
    for循环与for in循环
    表单验证
    表单验证四个步骤
    ID和Name的区别
  • 原文地址:https://www.cnblogs.com/GengMingYan/p/14363718.html
Copyright © 2011-2022 走看看