Commit 980da35b authored by benm@chromium.org's avatar benm@chromium.org

[Android] Fix leak in Java Bridge.

First draft at moving the strong reference native side into a
weak reference and maintaining the strong reference on the
java side.

Android only change, android bots green.
NOTRY=true

Review URL: https://chromiumcodereview.appspot.com/11802002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176079 0039d316-1c4b-4281-b951-d872f2087c98
parent 9ed23c8c
...@@ -589,7 +589,7 @@ jvalue CoerceJavaScriptObjectToJavaValue(const NPVariant& variant, ...@@ -589,7 +589,7 @@ jvalue CoerceJavaScriptObjectToJavaValue(const NPVariant& variant,
// objects. Spec requires passing only Java objects which are // objects. Spec requires passing only Java objects which are
// assignment-compatibile. // assignment-compatibile.
result.l = AttachCurrentThread()->NewLocalRef( result.l = AttachCurrentThread()->NewLocalRef(
JavaBoundObject::GetJavaObject(object)); JavaBoundObject::GetJavaObject(object).obj());
} else { } else {
// LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec
// requires converting if the target type is // requires converting if the target type is
...@@ -742,7 +742,7 @@ NPObject* JavaBoundObject::Create( ...@@ -742,7 +742,7 @@ NPObject* JavaBoundObject::Create(
JavaBoundObject::JavaBoundObject( JavaBoundObject::JavaBoundObject(
const JavaRef<jobject>& object, const JavaRef<jobject>& object,
base::android::JavaRef<jclass>& safe_annotation_clazz) base::android::JavaRef<jclass>& safe_annotation_clazz)
: java_object_(object), : java_object_(AttachCurrentThread(), object.obj()),
are_methods_set_up_(false), are_methods_set_up_(false),
safe_annotation_clazz_(safe_annotation_clazz) { safe_annotation_clazz_(safe_annotation_clazz) {
// We don't do anything with our Java object when first created. We do it all // We don't do anything with our Java object when first created. We do it all
...@@ -752,10 +752,10 @@ JavaBoundObject::JavaBoundObject( ...@@ -752,10 +752,10 @@ JavaBoundObject::JavaBoundObject(
JavaBoundObject::~JavaBoundObject() { JavaBoundObject::~JavaBoundObject() {
} }
jobject JavaBoundObject::GetJavaObject(NPObject* object) { ScopedJavaLocalRef<jobject> JavaBoundObject::GetJavaObject(NPObject* object) {
DCHECK_EQ(&JavaNPObject::kNPClass, object->_class); DCHECK_EQ(&JavaNPObject::kNPClass, object->_class);
JavaBoundObject* jbo = reinterpret_cast<JavaNPObject*>(object)->bound_object; JavaBoundObject* jbo = reinterpret_cast<JavaNPObject*>(object)->bound_object;
return jbo->java_object_.obj(); return jbo->java_object_.get(AttachCurrentThread());
} }
bool JavaBoundObject::HasMethod(const std::string& name) const { bool JavaBoundObject::HasMethod(const std::string& name) const {
...@@ -795,10 +795,15 @@ bool JavaBoundObject::Invoke(const std::string& name, const NPVariant* args, ...@@ -795,10 +795,15 @@ bool JavaBoundObject::Invoke(const std::string& name, const NPVariant* args,
true); true);
} }
// Call ScopedJavaLocalRef<jobject> obj = java_object_.get(AttachCurrentThread());
bool ok = CallJNIMethod(java_object_.obj(), method->return_type(),
method->id(), &parameters[0], result, bool ok = false;
safe_annotation_clazz_); if (!obj.is_null()) {
// Call
ok = CallJNIMethod(obj.obj(), method->return_type(),
method->id(), &parameters[0], result,
safe_annotation_clazz_);
}
// Now that we're done with the jvalue, release any local references created // Now that we're done with the jvalue, release any local references created
// by CoerceJavaScriptValueToJavaValue(). // by CoerceJavaScriptValueToJavaValue().
...@@ -816,8 +821,14 @@ void JavaBoundObject::EnsureMethodsAreSetUp() const { ...@@ -816,8 +821,14 @@ void JavaBoundObject::EnsureMethodsAreSetUp() const {
are_methods_set_up_ = true; are_methods_set_up_ = true;
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> obj = java_object_.get(env);
if (obj.is_null()) {
return;
}
ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>( ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
env->CallObjectMethod(java_object_.obj(), GetMethodIDFromClassName( env->CallObjectMethod(obj.obj(), GetMethodIDFromClassName(
env, env,
kJavaLangObject, kJavaLangObject,
kGetClass, kGetClass,
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include "base/android/jni_helper.h"
#include "base/android/scoped_java_ref.h" #include "base/android/scoped_java_ref.h"
#include "base/memory/linked_ptr.h" #include "base/memory/linked_ptr.h"
#include "content/browser/renderer_host/java/java_method.h" #include "content/browser/renderer_host/java/java_method.h"
...@@ -37,10 +38,11 @@ class JavaBoundObject { ...@@ -37,10 +38,11 @@ class JavaBoundObject {
virtual ~JavaBoundObject(); virtual ~JavaBoundObject();
// Gets a global ref to the underlying JavaObject from a JavaBoundObject // Gets a local ref to the underlying JavaObject from a JavaBoundObject
// wrapped as an NPObject. Ownership of the global ref is retained by the // wrapped as an NPObject. May return null if the underlying object has
// JavaBoundObject: the caller must NOT release it. // been garbage collected.
static jobject GetJavaObject(NPObject* object); static base::android::ScopedJavaLocalRef<jobject> GetJavaObject(
NPObject* object);
// Methods to implement the NPObject callbacks. // Methods to implement the NPObject callbacks.
bool HasMethod(const std::string& name) const; bool HasMethod(const std::string& name) const;
...@@ -54,10 +56,9 @@ class JavaBoundObject { ...@@ -54,10 +56,9 @@ class JavaBoundObject {
void EnsureMethodsAreSetUp() const; void EnsureMethodsAreSetUp() const;
// The global ref to the underlying Java object that this JavaBoundObject // The weak ref to the underlying Java object that this JavaBoundObject
// instance represents. // instance represents.
base::android::ScopedJavaGlobalRef<jobject> java_object_; JavaObjectWeakGlobalRef java_object_;
// Map of public methods, from method name to Method instance. Multiple // Map of public methods, from method name to Method instance. Multiple
// entries will be present for overloaded methods. Note that we can't use // entries will be present for overloaded methods. Note that we can't use
// scoped_ptr in STL containers as we can't copy it. // scoped_ptr in STL containers as we can't copy it.
......
...@@ -49,6 +49,10 @@ import org.chromium.content.common.TraceEvent; ...@@ -49,6 +49,10 @@ import org.chromium.content.common.TraceEvent;
import org.chromium.ui.gfx.NativeWindow; import org.chromium.ui.gfx.NativeWindow;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
/** /**
* Provides a Java-side 'wrapper' around a WebContent (native) instance. * Provides a Java-side 'wrapper' around a WebContent (native) instance.
...@@ -75,6 +79,27 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { ...@@ -75,6 +79,27 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient {
// Personality of the ContentView. // Personality of the ContentView.
private final int mPersonality; private final int mPersonality;
// If the embedder adds a JavaScript interface object that contains an indirect reference to
// the ContentViewCore, then storing a strong ref to the interface object on the native
// side would prevent garbage collection of the ContentViewCore (as that strong ref would
// create a new GC root).
// For that reason, we store only a weak reference to the interface object on the
// native side. However we still need a strong reference on the Java side to
// prevent garbage collection if the embedder doesn't maintain their own ref to the
// interface object - the Java side ref won't create a new GC root.
// This map stores those refernces. We put into the map on addJavaScriptInterface()
// and remove from it in removeJavaScriptInterface().
private Map<String, Object> mJavaScriptInterfaces = new HashMap<String, Object>();
// Additionally, we keep track of all Java bound JS objects that are in use on the
// current page to ensure that they are not garbage collected until the page is
// navigated. This includes interface objects that have been removed
// via the removeJavaScriptInterface API and transient objects returned from methods
// on the interface object.
// TODO(benm): Implement the transient object retention - likely by moving the
// management of this set into the native Java bridge. (crbug/169228) (crbug/169228)
private Set<Object> mRetainedJavaScriptObjects = new HashSet<Object>();
/** /**
* Interface that consumers of {@link ContentViewCore} must implement to allow the proper * Interface that consumers of {@link ContentViewCore} must implement to allow the proper
* dispatching of view methods through the containing view. * dispatching of view methods through the containing view.
...@@ -494,6 +519,10 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { ...@@ -494,6 +519,10 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient {
public void didStartLoading(String url) { public void didStartLoading(String url) {
hidePopupDialog(); hidePopupDialog();
resetGestureDetectors(); resetGestureDetectors();
// TODO(benm): This isn't quite the right place to do this. Management
// of this set should be moving into the native java bridge in crbug/169228
// and until that's ready this will do.
mRetainedJavaScriptObjects.clear();
} }
}; };
} }
...@@ -625,6 +654,8 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { ...@@ -625,6 +654,8 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient {
} }
mNativeContentViewCore = 0; mNativeContentViewCore = 0;
mContentSettings = null; mContentSettings = null;
mJavaScriptInterfaces.clear();
mRetainedJavaScriptObjects.clear();
} }
/** /**
...@@ -2131,13 +2162,6 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { ...@@ -2131,13 +2162,6 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient {
return mZoomManager.getZoomControlsViewForTest(); return mZoomManager.getZoomControlsViewForTest();
} }
// TODO(joth): Remove in next patch.
@Deprecated
public void addJavascriptInterface(Object object, String name, boolean requireAnnotation) {
addPossiblyUnsafeJavascriptInterface(object, name,
requireAnnotation ? JavascriptInterface.class : null);
}
/** /**
* This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)} * This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
* and automatically pass in {@link JavascriptInterface} as the required annotation. * and automatically pass in {@link JavascriptInterface} as the required annotation.
...@@ -2195,6 +2219,7 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { ...@@ -2195,6 +2219,7 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient {
public void addPossiblyUnsafeJavascriptInterface(Object object, String name, public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
Class<? extends Annotation> requiredAnnotation) { Class<? extends Annotation> requiredAnnotation) {
if (mNativeContentViewCore != 0 && object != null) { if (mNativeContentViewCore != 0 && object != null) {
mJavaScriptInterfaces.put(name, object);
nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation); nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
} }
} }
...@@ -2205,6 +2230,10 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { ...@@ -2205,6 +2230,10 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient {
* @param name The name of the interface to remove. * @param name The name of the interface to remove.
*/ */
public void removeJavascriptInterface(String name) { public void removeJavascriptInterface(String name) {
// TODO(benm): Move the management of this retained object set
// into the native java bridge. (crbug/169228)
mRetainedJavaScriptObjects.add(mJavaScriptInterfaces.get(name));
mJavaScriptInterfaces.remove(name);
if (mNativeContentViewCore != 0) { if (mNativeContentViewCore != 0) {
nativeRemoveJavascriptInterface(mNativeContentViewCore, name); nativeRemoveJavascriptInterface(mNativeContentViewCore, name);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment