Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does addJavascriptInterface() rely upon getClass()?

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().

like image 266
CommonsWare Avatar asked Sep 26 '13 23:09

CommonsWare


2 Answers

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.

like image 132
devunwired Avatar answered Sep 21 '22 03:09

devunwired


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 :)

like image 42
Eng.Fouad Avatar answered Sep 21 '22 03:09

Eng.Fouad