原生webview拦截弹框

SystemWebChromeClient.java

webview通过拦截alert,confirm,prompt等浏览器弹出框并截取弹出框中的数据达到接收数据的目的。以下粘帖部分代码 。

重点解释onJsPrompt

......

    /**
     * Tell the client to display a javascript alert dialog.
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
            @Override public void gotResult(boolean success, String value) {
                if (success) {
                    result.confirm();
                } else {
                    result.cancel();
                }
            }
        });
        return true;
    }

    /**
     * Tell the client to display a confirm dialog to the user.
     */
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
        dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
            @Override
            public void gotResult(boolean success, String value) {
                if (success) {
                    result.confirm();
                } else {
                    result.cancel();
                }
            }
        });
        return true;
    }

    /**
     * Tell the client to display a prompt dialog to the user.
     * If the client returns true, WebView will assume that the client will
     * handle the prompt dialog and call the appropriate JsPromptResult method.
     *
     * Since we are hacking prompts for our own purposes, we should not be using them for
     * this purpose, perhaps we should hack console.log to do this instead!
     * 参数含义:
       view:webview
       origin:网页请求源(网页所属的域名)
       message:显示在prompt对话框的内容
       defaultValue:显示在prompt输入框上的内容(cordova中的数据传递都是使用该字段值)
       result:A JsResult to confirm that the user hit enter.
       return true表示由原生端来处理弹出框,此时webview自带的弹出框效果将被禁止
     */
    @Override
    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
        //由CordovaBridge.java来处理数据
        String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
        if (handledRet != null) {
            //通知webview弹出框已经被处理,你需要调用result.confirm()或者result.cancel()来处理jsResult,否则会出问题.
            result.confirm(handledRet);
        } else {
            //使用原生API  AlertDialog.Builder来构建弹出框见CordovaDialogsHelper.java
            dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
                @Override
                public void gotResult(boolean success, String value) {
                    if (success) {
                        result.confirm(value);
                    } else {
                        result.cancel();
                    }
                }
            });
        }
        return true;
    }

......

CordovaBridge.java

在上面的onJsPrompt中弹出框数据的由该类负责处理(promptOnJsPrompt),以下说明该类。

String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue)

package org.apache.cordova;

import java.security.SecureRandom;

import org.json.JSONArray;
import org.json.JSONException;

/**
 * Contains APIs that the JS can call. All functions in here should also have
 * an equivalent entry in CordovaChromeClient.java, and be added to
 * cordova-js/lib/android/plugin/android/promptbasednativeapi.js
 */
public class CordovaBridge {
    private static final String LOG_TAG = "CordovaBridge";
    private PluginManager pluginManager;//该类用来负责调用具体插件的exec方法
    private NativeToJsMessageQueue jsMessageQueue;//该类用来负责向前端传输数据即传送插件的执行结果
    private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.

    public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
        this.pluginManager = pluginManager;
        this.jsMessageQueue = jsMessageQueue;
    }
    //js端传入对应server 和 action 再通过pluginManager执行对应的插件
    public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
        if (!verifySecret("exec()", bridgeSecret)) {
            return null;
        }
        // If the arguments weren't received, send a message back to JS.  It will switch bridge modes and try again.  See CB-2666.
        // We send a message meant specifically for this case.  It starts with "@" so no other message can be encoded into the same string.
        if (arguments == null) {
            return "@Null arguments.";
        }

        jsMessageQueue.setPaused(true);
        try {
            // Tell the resourceApi what thread the JS is running on.
            CordovaResourceApi.jsThread = Thread.currentThread();

            pluginManager.exec(service, action, callbackId, arguments);
            String ret = null;
            if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
                ret = jsMessageQueue.popAndEncode(false);
            }
            return ret;
        } catch (Throwable e) {
            e.printStackTrace();
            return "";
        } finally {
            jsMessageQueue.setPaused(false);
        }
    }
    //设置native到js的桥接模式 对应cordova.js 模块cordova/android/promptbasednativeapi 中的retrieveJsMessages方法 setNativeToJsBridgeMode
    public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
        if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
            return;
        }
        jsMessageQueue.setBridgeMode(value);
    }
    //检索消息 对应cordova.js 模块cordova/android/promptbasednativeapi 中的retrieveJsMessages方法
    public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
        if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
            return null;
        }
        return jsMessageQueue.popAndEncode(fromOnlineEvent);
    }

    //验证bridgeSecret是否相等,在cordova/exec模块中的init方法初始化native的bridgeSecret
    private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
        if (!jsMessageQueue.isBridgeEnabled()) {
            if (bridgeSecret == -1) {
                LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
            } else {
                LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
            }
            return false;
        }
        // Bridge secret wrong and bridge not due to it being from the previous page.
        if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
            LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
            clearBridgeSecret();
            throw new IllegalAccessException();
        }
        return true;
    }

    /** Called on page transitions */
    void clearBridgeSecret() {
        expectedBridgeSecret = -1;
    }
    //bridgeSecret是否初始化(桥接模式是否被设置)
    public boolean isSecretEstablished() {
        return expectedBridgeSecret != -1;
    }

    /** Called by cordova.js to initialize the bridge. */
    int generateBridgeSecret() {
        SecureRandom randGen = new SecureRandom();
        expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
        return expectedBridgeSecret;
    }

    public void reset() {
        jsMessageQueue.reset();
        clearBridgeSecret();
    }

    //处理cordova.js prompt弹出框数据
    public String promptOnJsPrompt(String origin, String message, String defaultValue) {
        //执行插件动作
        if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
            JSONArray array;
            try {
                array = new JSONArray(defaultValue.substring(4));
                int bridgeSecret = array.getInt(0);
                String service = array.getString(1);
                String action = array.getString(2);
                String callbackId = array.getString(3);
                String r = jsExec(bridgeSecret, service, action, callbackId, message);
                return r == null ? "" : r;
            } catch (JSONException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return "";
        }
        // 设置 native->JS 的桥接模式 
        else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
            try {
                int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
                jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
            } catch (NumberFormatException e){
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return "";
        }
        // Polling for JavaScript messages js通过轮寻获取原生数据(插件执行结果)
        else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
            int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
            try {
                String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
                return r == null ? "" : r;
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return "";
        }
        //初始化native -> js 桥接模式
        else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
            // Protect against random iframes being able to talk through the bridge.
            // Trust only pages which the app would have been allowed to navigate to anyway.
            if (pluginManager.shouldAllowBridgeAccess(origin)) {
                // Enable the bridge
                int bridgeMode = Integer.parseInt(defaultValue.substring(9));
                jsMessageQueue.setBridgeMode(bridgeMode);
                // Tell JS the bridge secret.
                int secret = generateBridgeSecret();
                return ""+secret;
            } else {
                LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
            }
            return "";
        }
        return null;
    }
}

results matching ""

    No results matching ""