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()
.
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.
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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With