• JS调用JCEF方法


    坐下写这篇文章的时候,内心还是有一点点小激动的,折腾了一个多星期,踩了一个又一个的坑,终于找到一条可以走通的路,内心的喜悦相信经历过的人都会明白~~~~~今儿个老百姓啊,真呀个真高兴啊,哈哈,好了,废话不多说,上正文

    首先是问题的需求:

    我用JCEF实现了java程序中嵌入浏览器,不了解JCEF的同学请戳这里,那么问题就来了,如果我想要打开一个原生的文件选择对话框怎么办?因为在页面上,我们用的最多的就是js,而在示例demo里面全是后台的java代码,以及JNI的调用,而我想要的效果是如何用js去打开文件选择对话框,当时我的第一反应就是ajax请求,但是这样的话,我每次发送请求的时候都会需要带上我外部框架的对象CefBrowser,CefFrame等,并且我要一直在我的客户端持有这些对象,这个方法理论上是走得通的,但是如果能有更好的办法,何乐而不为呢?

    于是英语水平稀烂的我开始研究网上的各种英文资料,也有幸找到了几篇中文的资料,下面就简单谈谈我踩坑的经历吧

    我一个想到的就是,jcef打开文件选择对话框的本质还是通过JNI调用c++的接口来实现的,于是我想到了能不能通过js调用c++的方法来实现,正好官网上也有wiki文档:JavaScriptIntegration,参照官网的介绍,我开始一步一步按部就班的敲代码,敲好以后编译,重启,然后满怀期待的看着界面,然后就没然后了,不满足于失败的我开始寻找原因,由于水平有限,我折腾了三四天,始终没有解决这个问题,哎,我这暴脾气,就跟他杠上了,结果,还是以我的失败告终,悲哀~~如果有幸有大神看到这里,麻烦告知原因,小弟感激不尽。

    既然直接用js调用c++的方式没走通,咱也不能真的在一棵树上吊死不是,于是我想到了能不能用js直接调用java代码,于是DWR出现在了我的视野里,这里就不再详细描述了,我只知道以我的水平,短时间内又败给了DWR,这就让人头痛了啊,搞不定,任务就没办法推进,总不能一直这么晾着哈。

    于是我开始用我很中文的英文去网上提问,如果大家搜索时搜到了类似:How to open a filedialog using javascript或者How to bind javascript with java?这样的问题,很有可能就是我问的,然后问题放在好几天了,也没人回答,宝宝心里急啊,可是一点办法也没有,最后实在是不知道怎么办的时候,我开始静下心来看jecf的源码,试图从源码中找到点蛛丝马迹,还真别说,真就找到了。

    当我看到cef/handler/CefMessageRouterHandler的时候,详细代码如下:

    public interface CefMessageRouterHandler extends CefNative {
    
        /**
         * Called when the browser receives a JavaScript query.
         * @param browser The browser generating the event.
         * @param query_id The unique ID for the query.
         * @param persistent True if the query is persistent.
         * @param callback Object used to continue or cancel the query asynchronously.
         * 
         * @return true to handle the query or false to propagate the query to other 
         * registered handlers, if any. If no handlers return true from this method 
         * then the query will be automatically canceled with an error code of -1
         * delivered to the JavaScript onFailure callback.
         */
        public boolean onQuery(CefBrowser browser, long query_id, String request, boolean persistent,
                CefQueryCallback callback);
    
        /**
         * Called when a pending JavaScript query is canceled.
         * @param browser The browser generating the event.
         * @param query_id The unique ID for the query.
         */
        public void onQueryCanceled(CefBrowser browser, long query_id);
    }
    View Code

    大家看到什么没有?Javascript有木有?抱着怀疑的态度,我看了该接口的实现类,啥都没有啊,但是好不容易找到一点蛛丝马迹,怎么可能轻易放弃呢,于是我又开始Google,果然,

    Correct way to use window.cefQuery 和 Asynchronous JavaScript Bindings

    终于觉得自己找到点有用的了,于是我开始研究这个接口的实现,写了一个简单的实现类如下:

    public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {
    
        @Override
        public boolean onQuery(CefBrowser browser, long query_id, String request, boolean persistent,
                final CefQueryCallback callback) {
            if (StringUtils.isNotEmpty(request) && StringUtils.indexOf(request, "cmd:") != -1) {
                String cmd = StringUtils.substring(request, 4);
                if (StringUtils.equals(cmd, "open")) {
                    this.openAppFile(browser, callback);
                }
            }
            return true;
        }
    
        /**
         * 打开文件
         */
        private void openAppFile(CefBrowser browser, final CefQueryCallback callback) {
            CefRunFileDialogCallback dialogCallBack = new CefRunFileDialogCallback() {
                @Override
                public void onFileDialogDismissed(int selectedAcceptFilter, Vector<String> filePaths) {
                    if (filePaths.size() == 0) {
                        return;
                    }
                    File selectedFile = new File(filePaths.get(0));
                    if (selectedFile != null) {
                        String selectedPath = selectedFile.getAbsolutePath();
                        callback.success(selectedPath);
                    }
                }
            };
            browser.runFileDialog(FileDialogMode.FILE_DIALOG_OPEN, "test", null, null, 0, dialogCallBack);
        }
    }
    View Code

    并在主窗口中加入该实现:

    //绑定js和jcef后台代码,window.cefQuery();
            CefMessageRouter msgRouter = CefMessageRouter.create();
            msgRouter.addHandler(new MessageRouterHandler(), true);
            client.addMessageRouter(msgRouter);
    View Code

    然后在界面调用window.cefQuery,代码如下:

    window.cefQuery({
                    request : "cmd:open",
                    onSuccess : function(response) {
                        alert(response);
                    },
                    onFailure : function(response) {
                        alert(response);
                    }
                });
    View Code

    一切准备就绪,重启程序,然后兴奋的等着结果出现,但是天不遂人愿啊,等了半天,界面一片空白,FUCK,根据不算丰富的前端经验,这种情况多半是js异常,于是打开dev窗口,果然:

    Uncaught TypeError: window.cefQuery is not a function

    一下子又懵逼了,于是我开始在源码里面找CefMessageRouterHandler相关的代码,然后让我找到了CefMessageRouter类:

    /**
     * The below classes implement support for routing aynchronous messages between
     * JavaScript running in the renderer process and C++ running in the browser
     * process. An application interacts with the router by passing it data from
     * standard CEF C++ callbacks (OnBeforeBrowse, OnProcessMessageRecieved,
     * OnContextCreated, etc). The renderer-side router supports generic JavaScript
     * callback registration and execution while the browser-side router supports
     * application-specific logic via one or more application-provided Handler
     * instances.
     *
     * The renderer-side router implementation exposes a query function and a cancel
     * function via the JavaScript 'window' object:
     *
     *    // Create and send a new query.
     *    var request_id = window.cefQuery({
     *        request: 'my_request',
     *        persistent: false,
     *        onSuccess: function(response) {},
     *        onFailure: function(error_code, error_message) {}
     *    });
     *
     *    // Optionally cancel the query.
     *    window.cefQueryCancel(request_id);
     *
     * When |window.cefQuery| is executed the request is sent asynchronously to one
     * or more C++ Handler objects registered in the browser process. Each C++
     * Handler can choose to either handle or ignore the query in the
     * Handler::OnQuery callback. If a Handler chooses to handle the query then it
     * should execute Callback::Success when a response is available or
     * Callback::Failure if an error occurs. This will result in asynchronous
     * execution of the associated JavaScript callback in the renderer process. Any
     * queries unhandled by C++ code in the browser process will be automatically
     * canceled and the associated JavaScript onFailure callback will be executed
     * with an error code of -1.
     *
     * Queries can be either persistent or non-persistent. If the query is
     * persistent than the callbacks will remain registered until one of the
     * following conditions are met:
     *
     * A. The query is canceled in JavaScript using the |window.cefQueryCancel|
     *    function.
     * B. The query is canceled in C++ code using the Callback::Failure function.
     * C. The context associated with the query is released due to browser
     *    destruction, navigation or renderer process termination.
     *
     * If the query is non-persistent then the registration will be removed after
     * the JavaScript callback is executed a single time. If a query is canceled for
     * a reason other than Callback::Failure being executed then the associated
     * Handler's OnQueryCanceled method will be called.
     *
     * Some possible usage patterns include:
     *
     * One-time Request. Use a non-persistent query to send a JavaScript request.
     *    The Handler evaluates the request and returns the response. The query is
     *    then discarded.
     *
     * Broadcast. Use a persistent query to register as a JavaScript broadcast
     *    receiver. The Handler keeps track of all registered Callbacks and executes
     *    them sequentially to deliver the broadcast message.
     *
     * Subscription. Use a persistent query to register as a JavaScript subscription
     *    receiver. The Handler initiates the subscription feed on the first request
     *    and delivers responses to all registered subscribers as they become
     *    available. The Handler cancels the subscription feed when there are no
     *    longer any registered JavaScript receivers.
     *
     * Message routing occurs on a per-browser and per-context basis. Consequently,
     * additional application logic can be applied by restricting which browser or
     * context instances are passed into the router. If you choose to use this
     * approach do so cautiously. In order for the router to function correctly any
     * browser or context instance passed into a single router callback must then
     * be passed into all router callbacks.
     *
     * There is generally no need to have multiple renderer-side routers unless you
     * wish to have multiple bindings with different JavaScript function names. It
     * can be useful to have multiple browser-side routers with different client-
     * provided Handler instances when implementing different behaviors on a per-
     * browser basis.
     *
     * This implementation places no formatting restrictions on payload content.
     * An application may choose to exchange anything from simple formatted
     * strings to serialized XML or JSON data.
     *
     *
     * EXAMPLE USAGE
     *
     * 1. Define the router configuration. You can optionally specify settings
     *    like the JavaScript function names. The configuration must be the same in
     *    both the browser and renderer processes. If using multiple routers in the
     *    same application make sure to specify unique function names for each
     *    router configuration.
     *
     *    // Example config object showing the default values.
     *    CefMessageRouterConfig config = new CefMessageRouterConfig();
     *    config.jsQueryFunction = "cefQuery";
     *    config.jsCancelFunction = "cefQueryCancel";
     *
     * 2. Create an instance of CefMessageRouter in the browser process.
     *
     *    messageRouter_ = CefMessageRouter.create(config);
     *
     * 3. Register one or more Handlers. The Handler instances must either outlive
     *    the router or be removed from the router before they're deleted.
     *
     *    messageRouter_.addHandler(myHandler);
     *
     * 4. Add your message router to all CefClient instances you want to get your
     *    JavaScript code be handled.
     *
     *    myClient.addMessageRouter(messageRouter_);
     *
     * 4. Execute the query function from JavaScript code.
     *
     *    window.cefQuery({request: 'my_request',
     *                     persistent: false,
     *                     onSuccess: function(response) { print(response); },
     *                     onFailure: function(error_code, error_message) {} });
     *
     * 5. Handle the query in your CefMessageRouterHandler.onQuery implementation
     *    and execute the appropriate callback either immediately or asynchronously.
     *
     *    public boolean onQuery(CefBrowser browser,
     *                           long query_id,
     *                           String request,
     *                           boolean persistent,
     *                           CefQueryCallback callback) {
     *      if (request.indexOf("my_request") == 0) {
     *        callback.success("my_response");
     *        return true;
     *      }
     *      return false;  // Not handled.
     *    }
     *
     * 6. Notice that the success callback is executed in JavaScript.
     */
    public abstract class CefMessageRouter {
    
        private final CefMessageRouterConfig routerConfig_;
    
        /**
         * Used to configure the query router. If using multiple router pairs make
         * sure to choose values that do not conflict.
         */
        public static class CefMessageRouterConfig {
            /**
             * Name of the JavaScript function that will be added to the 'window' object
             * for sending a query. The default value is "cefQuery".
             */
            public String jsQueryFunction;
    
            /**
             * Name of the JavaScript function that will be added to the 'window' object
             * for canceling a pending query. The default value is "cefQueryCancel".
             */
            public String jsCancelFunction;
    
            public CefMessageRouterConfig() {
                this("cefQuery", "cefQueryCancel");
            }
    
            public CefMessageRouterConfig(String queryFunction, String cancelFunction) {
                jsQueryFunction = queryFunction;
                jsCancelFunction = cancelFunction;
            }
        }
    
        // This CTOR can't be called directly. Call method create() instead.
        CefMessageRouter(CefMessageRouterConfig routerConfig) {
            routerConfig_ = routerConfig;
        }
    
        /**
         * Create a new router with the specified configuration.
         *
         * @param config router configuration
         * @return
         */
        public static final CefMessageRouter create() {
            return CefMessageRouter.create(null, null);
        }
    
        public static final CefMessageRouter create(CefMessageRouterConfig config) {
            return CefMessageRouter.create(config, null);
        }
    
        public static final CefMessageRouter create(CefMessageRouterHandler handler) {
            return CefMessageRouter.create(null, handler);
        }
    
        public static final CefMessageRouter create(CefMessageRouterConfig config, CefMessageRouterHandler handler) {
            CefMessageRouter router = CefMessageRouter_N.createNative(config);
            if (router != null && handler != null)
                router.addHandler(handler, true);
            return router;
        }
    
        /**
         * Must be called if the CefMessageRouter instance isn't used any more
         */
        public abstract void dispose();
    
        public final CefMessageRouterConfig getMessageRouterConfig() {
            return routerConfig_;
        }
    
        /**
         * Add a new query handler. If |first| is true it will be added as the first
         * handler, otherwise it will be added as the last handler. Returns true if
         * the handler is added successfully or false if the handler has already been
         * added. Must be called on the browser process UI thread. The Handler object
         * must either outlive the router or be removed before deletion.
         *
         * @param handler the according handler to be added
         * @param first if If set to true it will be added as the first handler
         * @return true if the handler is added successfully
         */
        public abstract boolean addHandler(CefMessageRouterHandler handler, boolean first);
    
        /**
         * Remove an existing query handler. Any pending queries associated with the
         * handler will be canceled. Handler.OnQueryCanceled will be called and the
         * associated JavaScript onFailure callback will be executed with an error
         * code of -1. Returns true if the handler is removed successfully or false
         * if the handler is not found. Must be called on the browser process UI
         * thread.
         *
         * @param handler the according handler to be removed
         * @return true if the handler is removed successfully
         */
        public abstract boolean removeHandler(CefMessageRouterHandler handler);
    
        /**
         * Cancel all pending queries associated with either |browser| or |handler|.
         * If both |browser| and |handler| are NULL all pending queries will be
         * canceled. Handler::OnQueryCanceled will be called and the associated
         * JavaScript onFailure callback will be executed in all cases with an error
         * code of -1.
         *
         * @param browser may be empty
         * @param handler may be empty
         */
        public abstract void cancelPending(CefBrowser browser, CefMessageRouterHandler handler);
    
        /**
         * Returns the number of queries currently pending for the specified |browser|
         * and/or |handler|. Either or both values may be empty. Must be called on the
         * browser process UI thread.
         *
         * @param browser may be empty
         * @param handler may be empty
         * @return the number of queries currently pending
         */
        public abstract int getPendingCount(CefBrowser browser, CefMessageRouterHandler handler);
    }
    View Code

    我们别的都不看,就先看最开始的类注释,写得清清楚楚,小弟我就不逐字翻译了,大概意思就是这个就是我找了好几天了的东西,然后我开始看里面的方法以及实现,多的也不说了,我们就看重点吧:

    /**
         * Used to configure the query router. If using multiple router pairs make
         * sure to choose values that do not conflict.
         */
        public static class CefMessageRouterConfig {
            /**
             * Name of the JavaScript function that will be added to the 'window' object
             * for sending a query. The default value is "cefQuery".
             */
            public String jsQueryFunction;
    
            /**
             * Name of the JavaScript function that will be added to the 'window' object
             * for canceling a pending query. The default value is "cefQueryCancel".
             */
            public String jsCancelFunction;
    
            public CefMessageRouterConfig() {
                this("cefQuery", "cefQueryCancel");
            }
    
            public CefMessageRouterConfig(String queryFunction, String cancelFunction) {
                jsQueryFunction = queryFunction;
                jsCancelFunction = cancelFunction;
            }
        }

    这里就是初始化js的window对象下面cefQuery,cefQueryCancel方法的地方,而你在设置CefMessageRouter的时候需要带上参数CefMessageRouterConfig,于是我将上面主窗口中的实现修改如下:

    //绑定js和jcef后台代码,window.cefQuery();
            CefMessageRouter msgRouter = CefMessageRouter.create(new CefMessageRouterConfig());
            msgRouter.addHandler(new DIDesktopDocmd(), true);
            client.addMessageRouter(msgRouter);

    这一次总不会错了,重启,然后开始等待,终于,原生的文件选择框出现在了我的眼前,NICE,折腾了这么久,终于有一点点的进步了,上图,让各位看官都瞧一瞧:

    至此,用js打开文件选择对话框告一段落,得出的结论如下:

    1,一定要看源码,尤其是源码的类注释很重要

    2,不要怕麻烦,也不要觉得头晕,你想到的问题很多都已经是别人想到过的,如果真没有,那你就当一回第一个吃螃蟹的人,挺好

    本文为原创文章,转载请注明出处,谢谢!

  • 相关阅读:
    request:fail parameter error: parameter.url should be String instead of Undefined;
    高性能Mysql笔记 — 索引
    机器学习 — 构建价格模型
    机器学习 — 决策树建模
    机器学习 — 文档过滤
    机器学习 — 优化
    机器学习 — 搜索及排名
    机器学习 — 发现群组
    机器学习 — 提供推荐
    docker
  • 原文地址:https://www.cnblogs.com/minteliu/p/5727040.html
Copyright © 2020-2023  润新知