Can addJavascriptInterface () rely on getClass ()?

I tried to trace the code to see how addJavascriptInterface() on WebView , but it is immersed in the native code, which basically reduces my ability to check what is happening.

In particular, I am trying to determine whether the JNI (?) Tool, with which addJavascriptInterface() translates back to Java code, relies on getClass() as part of the reflection strategy, to display method references in JavaScript source code in a Java implementation . I would suggest that this is necessary, and maybe I'm looking in the wrong place , but I don't see it.

Can someone point me to a code that uses embedded Java objects so that we can see how this is implemented?

Thanks!


UPDATE

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

+6
source share
4 answers

The code that I think you need is in external/webkit/Source/WebCore/bridge/jni/ . There are two main subdirectories here: jsc and v8 , representing the two Javascript engines that used Android. Since the V8 is an engine that has been used recently and for some time, we will stick to this.

I assume that you were able to successfully track the Java part of the code to get from WebView.addJavascriptInterface() to BrowserFrame.nativeAddJavaScriptInterface() , I will leave this data. The native side is picked up by AddJavaScriptInterface() in external/webkit/Source/WebKit/android/jni/WebCoreFrameBridge.cpp , where the Java object passed by the application is finally bound to the WebKit frame using bindToWindowObject() .

I am trying to determine if the JNI by which addJavascriptInterface () organizes a callback in Java code means to rely on getClass () as part of the reflection strategy

The short answer is yes. They use many wrappers around the traditional JNI code, but if you look inside, there are accessors on JNIEnv for reflection. The wrappers created in V8 are as follows:

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

Returning to WebCoreFrameBridge.cpp , before this object is passed, attached, the jobject , originally passed to native code through the JNI, is wrapped in the JavaInstance class, and then converted to NPObject , which is the final object bound to WebKit. Source for V8 NPObject: external/webkit/Source/WebCore/bridge/jni/v8/JavaNPObjectV8.cpp

In the NPObject implementation NPObject we can see that calls always retrieve JavaInstance methods and call methods. If you look at examples like JavaNPObjectHasMethod() or JavaNPObjectInvoke , you will notice that the following line appears often:

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

This returns the JavaClass wrapper that they created, but if you look at the JavaClassJobjectV8 constructor and its associated methods, you will see the familiar reflection calls for the Java object using JNIEnv (including the > actual JNI getClass() call in Dalvik).

Therefore, when a method is called by the associated WebKit frame, it finds the associated NPObject , which retrieves its JavaInstance , which in turn uses JNI reflection to access Java methods. The storage chain here is a little more complicated, so let me know if what has already been provided is enough to answer your questions.

+5
source

Here is what I got:

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

this is:

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

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

 //------------------------------------------------------------------------- // Private internal stuff //------------------------------------------------------------------------- private WebViewProvider mProvider; 

The only method I can verify 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 he uses Reflection. If an exception occurs during the instantiation of the custom class, WebViewClassic.Factory() will be used WebViewClassic.Factory() . Here's 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 a regular class (by reflection) or WebViewClassic.Factory .

WebViewClassic.Factory#createWebView() returns WebViewClassic , which is a subtype of type mProvider .

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

+2
source

This is more of a comment than an answer, but I cannot add stacktrace to the comments. So here it is:

When setting a breakpoint in an object that acts as a server implementation of the JavaScript interface, this is an approximate stack trace that 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 with Java ( Thread.run handleMessage ). Then it disappears in Native code ( nativeServiceFuncPtrQueue ), and it reappears in Java ( nativeServiceFuncPtrQueue JSReady ).

This stack from Nexus 10 works 4.3.

Something happens in the Native Layer that moves execution from a call to nativeServiceFuncPtrQueue directly to the Java method of the JavaScriptInterface instance in Java.

Currently, JavaScriptInterface needs to annotate every method that it publishes in JavaScript ( @JavaScriptInterface method annotation). Perhaps this creates some JNI bridges on the fly, invoking from Native in Java.

I wonder how this stack trace would look on an older device, where @JavaScriptInterface annotations @JavaScriptInterface not needed.

0
source

from Understanding the Android interface addjavascriptinterface : "The WebView.addJavascriptInterface method sends a message to the WebViewCore instance:

mWebViewCore.sendMessage (EventHub.ADD_JS_INTERFACE, arg); There are many overloaded methods in WebViewCore.java called sendMessage, but we don’t need to know what exactly is being called, because they do almost the same thing. There is even a good comment to give us a hint that we are in the right place! They all delegate an instance of EventHub, which is some inner class. This method turns out to be synchronized and sends a message to the Handler instance, which is a good indication that it probably works in a different thread, but for completeness, let's find out!

This handler is created in EventHub.transferMessages, which is called from WebViewCore.initialize. There are a few more jumps here, but in the end I found out that it was called from a run in WebCoreThread (a subclass of Runnable), which is created along with a new thread right here. "Created with the new stream 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, it could be a 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 execution class of this object. The returned class object is an object that is locked by the static synchronized methods of the presented class. The actual type of result is the class where | X | is erasing the static type of the expression on which getClass is invoked. For example, a throw is not required in this code fragment:

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

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

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

From the point of view of Dalvik, I think you are just registering a JNI callback through 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 can be used instead of getClass ...

0
source

Source: https://habr.com/ru/post/954730/


All Articles