SafeWebView Documentation

repository·master·Indexed 18 days ago

https://github.com/seven456/safewebview

A security-focused Android WebView wrapper that mitigates addJavascriptInterface vulnerabilities using a prompt-based reflection bridge. It provides enhanced support for JS-to-Java callbacks, resolves common WebView crashes, and implements proxy settings across different Android versions.

Tokens
1.6K
Snippets
4
Records
5
Agent score
13%

What's inside SafeWebView

  1. How SafeWebView works

    master

    SafeWebView addresses four main issues in Android WebView development:

    1. Security: Mitigates vulnerabilities associated with addJavascriptInterface.
    2. JS-to-Java Callbacks: Supports passing JavaScript functions from the web page to the Java layer for easy callbacks.
    3. Stability: Resolves various WebView-related crashes.
    4. Proxy Support: Implements proxy settings for different Android versions using Java reflection.

    Core Mechanism: Instead of direct injection which can be insecure, SafeWebView uses prompt as a bridge. It uses reflection to call methods in the Java interface class by passing the method name, parameter types, and encapsulated parameters as a JSON string via the prompt mechanism.

  2. Initialize SafeWebView

    master

    To use SafeWebView, extend the SafeWebView class (or use it as a base) and ensure you enable JavaScript in the WebSettings. You must also provide a JavaScriptInterface and a WebChromeClient (ideally extending SafeWebChromeClient) to enable the bridge functionality.

    Note on Numeric Types: Because JavaScript treats all numbers as 64-bit floats, Java methods defining int, long, or double parameters are treated as a single type (double) during the bridge process.

    WebView wv = new SafeWebView(this);
    WebSettings ws = wv.getSettings();
    ws.setJavaScriptEnabled(true);
    // 'Android' is the name used in JS to access the interface
    wv.addJavascriptInterface(new JavaScriptInterface(wv), "Android");
    wv.setWebChromeClient(new InnerChromeClient());
    wv.loadUrl("file:///android_asset/test.html");
  3. Use asynchronous JS callbacks from Java

    master

    SafeWebView allows you to pass a JavaScript function to Java, which can then be executed later (e.g., after an asynchronous operation).

    Usage Pattern:

    1. Java side: Define a method that accepts a JsCallback object.
    2. JS side: Pass a function as an argument to the Java method.

    Constraints:

    • Parameter Types: Callback arguments in JS must be types that can be converted to a String.
    • Lifecycle: By default, a passed JsCallback is single-use. Once jsCallback.apply(...) is called, it cannot be used again. To allow the function to be reused multiple times within the page lifecycle, you must call jsCallback.setPermanent(true) in the Java method.
    // Java: Define a method that uses the callback after a delay
    @android.webkit.JavascriptInterface
    public void delayJsCallBack(final int ms, final String backMsg, final JsCallback jsCallback) {
        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    jsCallback.apply(backMsg);
                } catch (JsCallback.JsCallbackException je) {
                    je.printStackTrace();
                }
            }
        }, ms);
    }
    
    // Java: Making a callback reusable
    @android.webkit.JavascriptInterface
    public void test (JsCallback jsCallback) {
        jsCallback.setPermanent(true);
        // ...
    }
    // JavaScript: Calling the Java method and providing a callback
    Android.delayJsCallBack(3 * 1000, 'call back haha', function (msg) {
      HostApp.alert(msg);
    });
  4. Extend SafeWebChromeClient

    master

    When customizing the WebChromeClient, extend SafeWebChromeClient. To ensure the bridge functions correctly, you must follow specific rules regarding when to call super in overridden methods:

    1. In onProgressChanged: Call super.onProgressChanged(...) as the first line of the method body.
    2. In onJsPrompt: Call super.onJsPrompt(...) as the last line of the method body (or use conditional logic).
    public class InnerChromeClient extends SafeWebChromeClient {
    
        @Override
        public void onProgressChanged (WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress); // MUST be the first line
            // your work
        }
    
        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            // your work
            return super.onJsPrompt(view, url, message, defaultValue, result); // MUST be the last line
        }
    }
  5. Configure ProGuard to prevent obfuscation

    master

    When releasing your app, you must add ProGuard rules to prevent the obfuscation of your JavaScript interface classes. If these classes are obfuscated, the bridge will fail to find the methods via reflection.

    Replace android.webkit.safe.sample.JavaScriptInterface with the actual full class name of the interface you are injecting into the page.

    // Prevent obfuscation of the interface class injected into the page
    -keepclassmembers class android.webkit.safe.sample.JavaScriptInterface{ *; }