8

I have tried to trace through the code to see how addJavascriptInterface() on WebView is implemented, but it dives into native code, which basically cripples my ability to grok what is going on.

Specifically, I am trying to determine if the JNI(?) means by which addJavascriptInterface() arranges to call back into Java code relies upon getClass() as part of a reflection strategy, to map method references in JavaScript source to the implementations in Java. I would assume that it has to, and maybe I am searching in the wrong place, but I am not seeing it.

Can anyone point me to the code where the injected Java objects are used, so we can see how that is implemented?

Thanks!


UPDATE

To clarify, I mean using getClass() on the object passed to addJavascriptInterface().

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    Another hint that it probably does use reflection: it requires a -keep ProGuard rule. – James Wald Sep 27 '13 at 01:29
  • Have you tried to implement a JavaScriptInterface instance, and set a breakpoint in one of its methods and have JavaScript in the browser call that method? Examining the stack trace of that call when the breakpoint hits may be enlightening. – Streets Of Boston Sep 27 '13 at 03:51
  • @StreetsOfBoston: Well, I can't set a breakpoint on `getClass()` itself, unless I rig things up with the Android source attached to Eclipse (which is possible, just not something I've needed to mess with). Setting a breakpoint elsewhere might lead me in the direction of where this stuff is in the code, but it won't provide an answer directly. Good ideas, though -- I'll try that stuff today. – CommonsWare Sep 27 '13 at 10:37
  • @CommonsWare, Btw, what does [your location](http://stackoverflow.com/users/115145/commonsware) mean that you are in "Space"? – Pacerier Jun 17 '15 at 12:55

4 Answers4

7

The code that I think you're after is found in external/webkit/Source/WebCore/bridge/jni/. There are two main subdirectories there, jsc and v8 representing the two Javascript engines Android has used. Since V8 is the engine that's been used most recently and for some time, we'll stick with that.

I'm assuming you were able to successfully trace the Java side of the code to get from WebView.addJavascriptInterface() down to BrowserFrame.nativeAddJavaScriptInterface(), I'll leave those details out. The native side is picked up by AddJavaScriptInterface() in external/webkit/Source/WebKit/android/jni/WebCoreFrameBridge.cpp, where the Java object passed in by the application is finally bound to the WebKit frame with bindToWindowObject().

I am trying to determine if the JNI means by which addJavascriptInterface() arranges to call back into Java code relies upon getClass() as part of a reflection strategy

The short answer is yes. They use a lot of wrappers around traditional JNI code, but if you look inside them the accessors on the JNIEnv for doing reflection are present. The wrappers they've created in V8 are:

external/webkit/Source/WebCore/bridge/jni/v8/JavaInstanceJobjectV8.cpp external/webkit/Source/WebCore/bridge/jni/v8/JavaClassJobjectV8.cpp external/webkit/Source/WebCore/bridge/jni/v8/JavaMethodJobjectV8.cpp

Going back to WebCoreFrameBridge.cpp, before that object the application passed in is bound, the jobject originally handed into the native code via JNI is wrapped in a JavaInstance class, and then converted to an NPObject, which is the final object bound to WebKit. The source for the V8 NPObject is at: external/webkit/Source/WebCore/bridge/jni/v8/JavaNPObjectV8.cpp

We can see in the NPObject implementation that the calls always extract the JavaInstance back out and call methods there. If you look at examples like JavaNPObjectHasMethod() or JavaNPObjectInvoke, you'll notice the following line appear frequently:

instance->getClass()->methodsNamed(name)

This returns the JavaClass wrapper they've created, but if you look into the JavaClassJobjectV8 constructor and associated methods you'll see those familiar reflection calls to the Java object using the JNIEnv (including the actual JNI getClass() call into Dalvik).

So when a method is called by the bound WebKit frame, it finds the associated NPObject, which extracts its JavaInstance wrapper, which in turn uses JNI reflection to get access to the Java methods. The chain of custody here is a little harder to follow, so let me know if what's already provided is sufficient to answer your questions.

devunwired
  • 62,780
  • 12
  • 127
  • 139
  • Excellent writeup! I hadn't realized that the `external/` projects contained Android-specific hacks like this -- I thought they were closer to pure upstream code. Unfortunately, this means that my current "Hail Mary" attempt to close the `addJavascriptInterface()` security leak is shot as a result. But, better to find that out sooner rather than later, to rummage around my hat again and see if I can find a rabbit. Many thanks for your help! – CommonsWare Sep 27 '13 at 16:04
  • @CommonsWare did you ever find a rabbit here? Looking around to see if anyone came up with a solution for plugging the security leak on versions of android before 4.2 – cottonBallPaws Dec 19 '13 at 20:10
  • 1
    @littleFluffyKitty: "did you ever find a rabbit here?" -- I live a rabbit-free existence, unfortunately. – CommonsWare Dec 19 '13 at 23:41
2

Here is what I got:

WebView wv = ...;
wv.addJavascriptInterface(object, name);

this goes to:

public void addJavascriptInterface(Object object, String name) {
    checkThread();
    mProvider.addJavascriptInterface(object, name);
}

mProvider is an interface of type WebViewProvider as it is declared in in WebView class:

//-------------------------------------------------------------------------
// Private internal stuff
//-------------------------------------------------------------------------

private WebViewProvider mProvider;

The only method I can see that instantiates it is ensureProviderCreated():

private void ensureProviderCreated() {
    checkThread();
    if (mProvider == null) {
        // As this can get called during the base class constructor chain, pass the minimum
        // number of dependencies here; the rest are deferred to init().
        mProvider = getFactory().createWebView(this, new PrivateAccess());
    }
}

getFactory() is implemented as:

private static synchronized WebViewFactoryProvider getFactory() {
    return WebViewFactory.getProvider();
}

getProvider() is implemented as:

static synchronized WebViewFactoryProvider getProvider() {
    // For now the main purpose of this function (and the factory abstraction) is to keep
    // us honest and minimize usage of WebViewClassic internals when binding the proxy.
    if (sProviderInstance != null) return sProviderInstance;

    sProviderInstance = getFactoryByName(DEFAULT_WEB_VIEW_FACTORY);
    if (sProviderInstance == null) {
        if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage");
        sProviderInstance = new WebViewClassic.Factory();
    }
    return sProviderInstance;
}

getFactoryByName() is implemented as:

private static WebViewFactoryProvider getFactoryByName(String providerName) {
    try {
        if (DEBUG) Log.v(LOGTAG, "attempt to load class " + providerName);
        Class<?> c = Class.forName(providerName);
        if (DEBUG) Log.v(LOGTAG, "instantiating factory");
        return (WebViewFactoryProvider) c.newInstance();
    } catch (ClassNotFoundException e) {
        Log.e(LOGTAG, "error loading " + providerName, e);
    } catch (IllegalAccessException e) {
        Log.e(LOGTAG, "error loading " + providerName, e);
    } catch (InstantiationException e) {
        Log.e(LOGTAG, "error loading " + providerName, e);
    }
    return null;
}

and here is where it uses Reflection. If an exception occurs during instantiating the custom class, WebViewClassic.Factory() will be used instead. Here is how it is implemented:

static class Factory implements WebViewFactoryProvider,  WebViewFactoryProvider.Statics {
    @Override
    public String findAddress(String addr) {
        return WebViewClassic.findAddress(addr);
    }
    @Override
    public void setPlatformNotificationsEnabled(boolean enable) {
        if (enable) {
            WebViewClassic.enablePlatformNotifications();
        } else {
            WebViewClassic.disablePlatformNotifications();
        }
    }

    @Override
    public Statics getStatics() { return this; }

    @Override
    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
        return new WebViewClassic(webView, privateAccess);
    }

    @Override
    public GeolocationPermissions getGeolocationPermissions() {
        return GeolocationPermissionsClassic.getInstance();
    }

    @Override
    public CookieManager getCookieManager() {
        return CookieManagerClassic.getInstance();
    }

    @Override
    public WebIconDatabase getWebIconDatabase() {
        return WebIconDatabaseClassic.getInstance();
    }

    @Override
    public WebStorage getWebStorage() {
        return WebStorageClassic.getInstance();
    }

    @Override
    public WebViewDatabase getWebViewDatabase(Context context) {
        return WebViewDatabaseClassic.getInstance(context);
    }
}

Now go back to mProvider = getFactory().createWebView(this, new PrivateAccess()); where getFactory() is either the custom class (by reflection) or WebViewClassic.Factory.

WebViewClassic.Factory#createWebView() returns WebViewClassic which is a sub-type of mProvider's type.

WebViewClassic#addJavascriptInterface is implemented as:

/**
 * See {@link WebView#addJavascriptInterface(Object, String)}
 */
@Override
public void addJavascriptInterface(Object object, String name) {
    if (object == null) {
        return;
    }
    WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
    arg.mObject = object;
    arg.mInterfaceName = name;
    mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
}

I think, this is what you are looking for :)

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • 1
    My interpretation of the question: Is reflection used when javascript communicates back, after a query is floated using `BrowserFrame#stringByEvaluatingJavaScriptFromString(String)`? Looking at your answer, I think my interpretation is way off. – Vikram Sep 27 '13 at 02:20
  • Sorry, I should have clarified more originally (and just did in an edit): I am interested in knowing if `getClass()` will be called on the object passed into `addJavascriptInterface()`. – CommonsWare Sep 27 '13 at 10:35
0

This is more of a comment than an answer, but I can't add a stacktrace in comments. So here it goes:

When setting a breakpoint in an Object that servers as a JavaScript Interface implementation, this is a sample stack-trace I get:

16> WebViewCoreThread@830034675584, prio=5, in group 'main', status: 'RUNNING'
      at com.mediaarc.player.books.model.pagesource.service.EPubPageSourceService$JS.JSReady(EPubPageSourceService.java:1752)
      at android.webkit.JWebCoreJavaBridge.nativeServiceFuncPtrQueue(JWebCoreJavaBridge.java:-1)
      at android.webkit.JWebCoreJavaBridge.nativeServiceFuncPtrQueue(JWebCoreJavaBridge.java:-1)
      at android.webkit.JWebCoreJavaBridge.handleMessage(JWebCoreJavaBridge.java:113)
      at android.os.Handler.dispatchMessage(Handler.java:99)
      at android.os.Looper.loop(Looper.java:137)
      at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:814)
      at java.lang.Thread.run(Thread.java:841)

It starts in Java (Thread.run --> handleMessage). Then it disappears into Native code (nativeServiceFuncPtrQueue) and it comes out again in Java (nativeServiceFuncPtrQueue --> JSReady).

This stack is from a Nexus 10 running 4.3.

There is something going on in the Native Layer that moves the execution from within a call to nativeServiceFuncPtrQueue directly to the Java method of the JavaScriptInterface instance in Java.

Nowadays, the JavaScriptInterface need to annotate each method that it publishes to JavaScript (@JavaScriptInterface method annotation). Maybe this generates some JNI bridges on the fly calling from Native into Java.

I wonder how this stack-trace would have looked like on an older device where the @JavaScriptInterface annotations were not necessary.

Streets Of Boston
  • 12,576
  • 2
  • 25
  • 28
  • On Android 4.1, the stack trace is even less informative, jumping from `WebViewCore.nativeMouseClick()` straight into my method. And, while I seem to be able to tell Eclipse that I want a breakpoint on `getClass()`, it doesn't actually suspend the thread -- it just slows things down. – CommonsWare Sep 27 '13 at 15:45
  • Setting a breakpoint on a method makes everything wicked slow. There is no way of overriding the 'public Class> getClass()', even when using interfaces/Proxy/InvocationHandler classes. To my knowledge, there is no 'clean' way to get information about an instance without calling its getClass() method. From then on, you could then generate, based on Method information, the appropriate JNI bindings to call into Java directly. – Streets Of Boston Sep 27 '13 at 23:16
0

from Understanding Android's webview addjavascriptinterface : "The method WebView.addJavascriptInterface sends a message to an instance of WebViewCore:

mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); In WebViewCore.java there are a bunch of overloaded methods called sendMessage , but we don't really need to know which exactly is being called, since they do pretty much the same thing. There's even a nice comment to give us a hint that we're in the right place! All of them are delegating to an instance of EventHub which is some inner class. This method turns out to be synchronized, and is sending a message to an instance of Handler, which is a good indication that this is probably running in another thread, but for completeness sake, let's find out!

That Handler is instantiated in EventHub.transferMessages which is called from WebViewCore.initialize . There are a few more hops here, but eventually I found out that this is called from run in WebCoreThread (subclass of Runnable), which is instantiated along with a new Thread right here ." instantiated along with a new Thread right here ."

  synchronized (WebViewCore.class) {
            if (sWebCoreHandler == null) {
                // Create a global thread and start it.
                Thread t = new Thread(new WebCoreThread());
                t.setName(THREAD_NAME);
                t.start();
                try {
                    WebViewCore.class.wait();
                } catch (InterruptedException e) {
                    Log.e(LOGTAG, "Caught exception while waiting for thread " +
                           "creation.");
                    Log.e(LOGTAG, Log.getStackTraceString(e));
                }
            }
        }

In other words, this could be the chain of calls in my opinion:

android.webkit.WebViewClassic

 4159    @Override
4160    public void More ...addJavascriptInterface(Object object, String name) {
4161
4162        if (object == null) {
4163            return;
4164        }
4165        WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
4166
4167        arg.mObject = object;
4168        arg.mInterfaceName = name;
4169
4170        // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
4171        // methods that are accessible from JS.
4172        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
4173            arg.mRequireAnnotation = true;
4174        } else {
4175            arg.mRequireAnnotation = false;
4176        }
4177        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
4178    }

android.webkit.WebViewCore

 static class JSInterfaceData {
827         Object mObject;
828         String mInterfaceName;
829         boolean mRequireAnnotation;
830     }

java.lang.Object

 37 public class Object {
38 
39     private static native void registerNatives();
40     static {
41         registerNatives();
42     }

Returns the runtime class of this Object. The returned Class object is the object that is locked by static synchronized methods of the represented class. The actual result type is Class where |X| is the erasure of the static type of the expression on which getClass is called. For example, no cast is required in this code fragment:

 Number n = 0; 
Class<? extends Number> c = n.getClass();

Returns: The Class object that represents the runtime class of this object. See also: The Java Language Specification, Third Edition (15.8.2 Class Literals)

 64 
65     public final native Class<?> getClass();

From a Dalvik's perspective I think you are just registering a JNI callback via findClass like this from JNIHelp.c :

 /*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s', aborting\n",
            className);
        abort();
    }

    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s', aborting\n", className);
        abort();
    }

    (*env)->DeleteLocalRef(env, clazz);
    return 0;
}

In conclusion my idea is derived from Native Libraries:

//Get jclass with env->FindClass

so maybe FindClass could be used instead of getClass...

Community
  • 1
  • 1