美文网首页
将Libcef打造为win32控件之三:资源拦截替换、JS调用C

将Libcef打造为win32控件之三:资源拦截替换、JS调用C

作者: 天下第九九八十一 | 来源:发表于2020-10-11 20:41 被阅读0次

    实现资源拦截器

    cefclient 示例代码自带一些资源拦截替换的实现,名为某某Provider。比如“查看网页源码的”的菜单功能,就是通过 test_runner.cc 下的类 StringResourceProvider 实现。各种 Provider 通过 test_runner::SetupResourceManager 组装。我呢后继实现不在去看它,而是直接在已有的 StringResourceProvider 之上加入回调函数:

    typedef url_intercept_result* (__cdecl * BC_URLInterceptor)(std::string);
    

    回调结果放在结构体内,方便以后拓展:

    struct url_intercept_result{
        CHAR* data;
        size_t length;
        int status_code;
        CHAR* status_text;
        CHAR mime[92]={0};
        bool delete_internal=false;
    };
    

    值得一提的是,结构体在exe定义的回调中构建,然后传给cefclient.dll。若直接在后者中用完了销毁,会在debug模式报错:__acrt_first_block == header。当然release模式下没有问题,不过为以后更新埋下了隐患。作为拖妥协,只好把回调函数再用一次,将结构体传回exe,exe端delete、释放内存。

    示例,访问百度首页返回HAPPY

    
            url_intercept_result* Test_InterceptBaidu(std::string url){ // 资源拦截的回调
                if(url=="https://www.baidu.com/") {
                    return new url_intercept_result{"HAPPY", 5, 200, "OK"};
                }
                return nullptr;
            }
    

    使用:

    
            struct BWCreateOptions{
                HWND hParent=0; //嵌入至的父窗口句柄。若空,则使用默认的窗口模式
                const CHAR* URL=nullptr; //初始化网址
                BC_BrowserCallback bcCallback=0; // 创建完成的回调
                BC_URLInterceptor bcInterceptor=0; //资源拦截器回调
            };
    
                BWCreateOptions args = {hwnd, "www.baidu.com", Test_browsercallback, Test_InterceptBaidu};
                bwCreateBrowser(args);
    

    实现简而言之就是想方设法使资源拦截器回调可以在 StringResourceProvider 中访问。一开始我是想将资源拦截器赋给CefBrowser对象,然后像这样:

    class StringResourceProvider : public CefResourceManager::Provider {
    public:
        StringResourceProvider(StringResourceMap* string_resource_map, StrResourceMap* str_resource_map)
        ……
    
        bool OnRequest(scoped_refptr<CefResourceManager::Request> request) OVERRIDE {
            CEF_REQUIRE_IO_THREAD();
    
            const std::string& url = request->url();
            if (url.compare(0, 13, kTestOrigin)) { //try_intercept
    
                理想中的代码:
                if(request->browser()->_resource_interceptor)
                {
                    const url_intercept_result = request->browser()->_resource_interceptor(url);
    

    奈何每次 OnRequest 回调中获得的 request->browser() 对象都是不同的,尽管实际上只有一个浏览器控件。只能退而求其次将资源拦截器赋给ClientHandler了:

    实际的代码:
    if(((ClientHandler*)request->browser()->GetHost()->GetClient().get())->_resource_interceptor)
    {
        const url_intercept_result = ((ClientHandler*)request->browser()->GetHost()->GetClient().get())->_resource_interceptor(url);
    

    https://github.com/KnIfER/BrowserWidget/blob/a11fdd7d89e2fd7b2faac6974ee15d0c27cc7629/tests/cefclient/browser/test_runner.cc#L524

    这就完了。可以在实际使用中进行测试了。不过那之前,需要修理一下乱糟糟的代码。几年前传承下来的c++代码都是两个空格作为缩进,还是用工具改写成一个Tab吧。

    选择使用Textrument的TextFx插件进行“reindent c++ code”。前置又要修复一下TextFx这个同样古董级别的c++项目。里面有许多奇奇怪怪的函数名,比如标准里有strcspn、wcscspn、strchr,他就来许多memcspn_chr、testmemcspn、memstr、strmstrinit、strmstr、strmstrclose、memchrX、memmovearmtest、memcqspn等等等等,再加上wchar_t和char宽度的不同,这里漏一个cspn的c,那里少一个sizeof(TCHAR),BUG一大堆,简直是噩梦一般的编程体验。

    幸好不负期望,完成了一部分的修复工作。现在,可以回到浏览器组件的主题,实现JS调用C++!


    JS调用C++ Native,处理JS传来的参数及C++返回值

    这一点实现较为复杂,尤其是涉及多进程模式时。

    多进程模式下大概只能像chrome插件那样全局注入Native代码,然后根据当前网址的不同决定是否执行。CEF的多进程太复杂,难以实现“将这段Native代码注入到此浏览控件以及由该控件弹出的一系列新窗口”这样基于控件的设计理念,干脆放弃。单进程不香么,又不是单线程。

    ( 看client_handler.cc,可能CefMessageRouterBrowserSide是处理多进程的。嫌麻烦没看。反正之后会兼容Webview2,Libcef的集成怎么简单就怎么来好了。 )

    处理参数及返回值:用一个结构体概括各种数据类型

    参考:
    https://blog.csdn.net/foruok/article/details/50573612

    …… (不重复了)


    C++ 调用 JS

    extern "C" __declspec(dllexport) void* bwExecuteJavaScript(CefRefPtr<CefBrowser>* pBrowser, CHAR* JS)
    {
        if(pBrowser)
        {
            (*pBrowser)->GetMainFrame()->ExecuteJavaScript(JS, "", 0);
        }
    }
    

    完成JS调用C++与C++调用JS后,就可以具体运用了!来看第一个实例的详细介绍。


    为Textrument文本编辑器开发Markdown实时预览插件

    构成规划:集成多个可以互相替换的浏览器内核,采用md.html将markdown文本渲染为html。

    md.html集成了以下js项目:

    可选用的浏览器内核以及各自的优缺点:

    • miniblink : 体积小(~30MB),开源免费。但是单线程,JS会卡UI。
    • miniblink vip : 在miniblink的基础上追加两MB的付费拓展,支持多线程渲染、基于插件的视频播放。但是价格昂贵,而且虽然JS不卡UI了,但加载新的页面无法中断旧的正在运行的JS。
    • Libcef : 开源免费,用有chromium的诸多特性,保持更新。但是体积臃肿,配置复杂,剔除了播放MP4视频的功能。
    • Webview2 : 微软自家维护,基于edge chromium,功能齐全。但仍处于测试阶段,需要下载Edge的开发者预览版才能使用。

    同类插件:

    • Markdown Panel (C#, IE)
    • Markdown Viewer ++ (C#, HTML Renderer)
      两者均不支持数学公式与实时预览。

    如何开发 MarkdownText Plugin:

    1. 任选一带有dock panel的Notepad++插件作为模板代码。
    2. 集成览器控件,显示最基本的 hello world
    3. 利用资源拦截技术集成md.html,加载位于插件dll文件夹的js代码。
      md.html略有修改,"导出" 了window.APMD方法。改动、以及调用如下:
    //修改
    window.APMD=async function(md_text){
    //方法体照抄源库正下方代码。
    }
    //调用
    // main.js 是JS库编译后的文件。
    <script src="http://mdbr/main.js">
    
    window.APMD("# hello world "); // Append and render Markdown text.
    

    md.html如何编译:同saladict,安装vscode、yarn、node,运行yarn install然后yarn build,编译时间16s。

    1. 利用JS调用C++ Native技术,将Notepad加载的文本传给JS层。
    // char* GetDocTex : 获取 Notepad 当前显示的文本
    
    BJSCV* GetDocText1(LONG_PTR funcName, int argc, LONG_PTR argv, int sizeofBJSCV)
    {
        int len;
        return new BJSCV{typeString, 0, GetDocTex(len)};
    }
    
    void onBrowserPrepared(bwWebView browserPtr)
    {
        bwInstallJsNativeToWidget(browserPtr, "GetDocText1", GetDocText1);
    }
    
    bwLoadStrData(mWebView_2, "123.html", "<!doctype html><meta charset=\"utf-8\"> <script src=\"http://mdbr/main.js\"></script><body><script>window.APMD(GetDocText1(''));</script></body>", 0);
    

    完成!


    小结

    已实现将Libcef打造为win32控件,供 Notepad 的 Markdown 预览插件——MarkdownText使用。

    此次实现专注于要用到的功能,故而尚有欠缺,比如并未为“C++调用JS”设计“处理JS返回值”的回调。

    插件代码:https://github.com/NotMad-Text-Editor-Plugins/MarkdownText

    项目描述:Notepad 插件,要与 Textrument 的头文件一起编译。不仅仅是个预览性质的插件,还加入了Markdown语法的加粗、斜体、下划线这些快捷入口,以后再添加Latex语法的加粗、斜体、下划线等等。然后去兼容html的实时预览。甚至还可以发展出小程序,比如内置一个Saladict。

    BrowserWidget - win32控件代码:https://github.com/KnIfER/BrowserWidget

    项目描述:将Libcef的demo程序 cefclient.exe 改写为 cefclient.dll,导出以下函数,全部在BrowserUI.h中对接处理:

    bwCreateBrowser : 创建浏览器。exe端在创建完成的回调中保存CefRefPtr<CefBrowser>* pBrowser指针,作为以下浏览器操作的句柄。

    bwGetHWNDForBrowser : 获得浏览器窗口句柄
    bwLoadStrData : 加载字符串
    bwInstallJsNativeToWidget : Js调用C++ Native
    bwParseCefV8Args : 分析“Js调用C++ Native”时JS传来的参数(将cefclient中的数据结构)

    bwGetUrl : 获得url
    bwExecuteJavaScript : Native调用Js
    bwCanGoBack :可以后退
    bwGoBack : 后退网页
    bwCanGoForward : 可以前进
    bwGoForward : 前进网页
    bwDestroyWebview : 销毁浏览器实例
    bwGetZoomLevel : 获取页面缩放
    bwSetZoomLevel : 设置页面缩放
    bwZoomLevelDelta : 为页面缩放应用一个Δ增量/减量

    完结。 ★,°:.☆( ̄▽ ̄)/$:.°★

    上一篇 初次封装,拿到浏览器HWND
    题外话 兼容微软Webview2

    相关文章

      网友评论

          本文标题:将Libcef打造为win32控件之三:资源拦截替换、JS调用C

          本文链接:https://www.haomeiwen.com/subject/hkpuuktx.html