diff options
author | mnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-27 10:47:30 +0000 |
---|---|---|
committer | mnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-27 10:47:30 +0000 |
commit | 197e294b2219a52d2e01ab80b05bf610e0b13f1c (patch) | |
tree | 7120fd7bb7da4e5b0a5a3b89dee2807d3ba93700 | |
parent | 76dfe8860ddbfc23e727a0ebc14f714c35044808 (diff) | |
download | chromium_src-197e294b2219a52d2e01ab80b05bf610e0b13f1c.zip chromium_src-197e294b2219a52d2e01ab80b05bf610e0b13f1c.tar.gz chromium_src-197e294b2219a52d2e01ab80b05bf610e0b13f1c.tar.bz2 |
[Android] Java Bridge with Gin: implement Java Bridge dispatcher
This patch adds implementation for GinJavaBridgeDispatcherHost class, which
is responsible for serving JB requests from renderers, and fulfilling
them with help of GinJavaBoundObject instances.
This patch also enables passing a error message for method invocation
errors back to renderers, so they can use it when raising JavaScript
exceptions.
BUG=355644
Review URL: https://codereview.chromium.org/345753003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280298 0039d316-1c4b-4281-b951-d872f2087c98
14 files changed, 780 insertions, 40 deletions
diff --git a/content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.cc b/content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.cc new file mode 100644 index 0000000..6c6e86b --- /dev/null +++ b/content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.cc @@ -0,0 +1,497 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.h" + +#include "base/android/java_handler_thread.h" +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/lazy_instance.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task_runner_util.h" +#include "content/browser/renderer_host/java/gin_java_bound_object_delegate.h" +#include "content/browser/renderer_host/java/jni_helper.h" +#include "content/common/android/gin_java_bridge_value.h" +#include "content/common/android/hash_set.h" +#include "content/common/gin_java_bridge_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "ipc/ipc_message_utils.h" + +#if !defined(OS_ANDROID) +#error "JavaBridge only supports OS_ANDROID" +#endif + +namespace content { + +namespace { +// The JavaBridge needs to use a Java thread so the callback +// will happen on a thread with a prepared Looper. +class JavaBridgeThread : public base::android::JavaHandlerThread { + public: + JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") { + Start(); + } + virtual ~JavaBridgeThread() { + Stop(); + } +}; + +base::LazyInstance<JavaBridgeThread> g_background_thread = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost( + WebContents* web_contents, + jobject retained_object_set) + : WebContentsObserver(web_contents), + retained_object_set_(base::android::AttachCurrentThread(), + retained_object_set), + allow_object_contents_inspection_(true) { + DCHECK(retained_object_set); +} + +GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() { +} + +void GinJavaBridgeDispatcherHost::RenderFrameCreated( + RenderFrameHost* render_frame_host) { + renderers_.insert(render_frame_host); + for (NamedObjectMap::const_iterator iter = named_objects_.begin(); + iter != named_objects_.end(); + ++iter) { + render_frame_host->Send(new GinJavaBridgeMsg_AddNamedObject( + render_frame_host->GetRoutingID(), iter->first, iter->second)); + } +} + +void GinJavaBridgeDispatcherHost::RenderFrameDeleted( + RenderFrameHost* render_frame_host) { + renderers_.erase(render_frame_host); + RemoveHolder(render_frame_host, + GinJavaBoundObject::ObjectMap::iterator(&objects_), + objects_.size()); +} + +GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject( + const base::android::JavaRef<jobject>& object, + const base::android::JavaRef<jclass>& safe_annotation_clazz, + bool is_named, + RenderFrameHost* holder) { + DCHECK(is_named || holder); + GinJavaBoundObject::ObjectID object_id; + JNIEnv* env = base::android::AttachCurrentThread(); + JavaObjectWeakGlobalRef ref(env, object.obj()); + if (is_named) { + object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>( + GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz))); + } else { + object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>( + GinJavaBoundObject::CreateTransient( + ref, safe_annotation_clazz, holder))); + } +#if DCHECK_IS_ON + { + GinJavaBoundObject::ObjectID added_object_id; + DCHECK(FindObjectId(object, &added_object_id)); + DCHECK_EQ(object_id, added_object_id); + } +#endif // DCHECK_IS_ON + base::android::ScopedJavaLocalRef<jobject> retained_object_set = + retained_object_set_.get(env); + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_add(env, retained_object_set, object); + } + return object_id; +} + +bool GinJavaBridgeDispatcherHost::FindObjectId( + const base::android::JavaRef<jobject>& object, + GinJavaBoundObject::ObjectID* object_id) { + JNIEnv* env = base::android::AttachCurrentThread(); + for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd(); + it.Advance()) { + if (env->IsSameObject( + object.obj(), + it.GetCurrentValue()->get()->GetLocalRef(env).obj())) { + *object_id = it.GetCurrentKey(); + return true; + } + } + return false; +} + +JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef( + GinJavaBoundObject::ObjectID object_id) { + scoped_refptr<GinJavaBoundObject>* result = objects_.Lookup(object_id); + scoped_refptr<GinJavaBoundObject> object(result ? *result : NULL); + if (object.get()) + return object->GetWeakRef(); + else + return JavaObjectWeakGlobalRef(); +} + +void GinJavaBridgeDispatcherHost::RemoveHolder( + RenderFrameHost* holder, + const GinJavaBoundObject::ObjectMap::iterator& from, + size_t count) { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> retained_object_set = + retained_object_set_.get(env); + size_t i = 0; + for (GinJavaBoundObject::ObjectMap::iterator it(from); + !it.IsAtEnd() && i < count; + it.Advance(), ++i) { + scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue()); + if (object->IsNamed()) + continue; + object->RemoveHolder(holder); + if (!object->HasHolders()) { + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_remove( + env, retained_object_set, object->GetLocalRef(env)); + } + objects_.Remove(it.GetCurrentKey()); + } + } +} + +void GinJavaBridgeDispatcherHost::AddNamedObject( + const std::string& name, + const base::android::JavaRef<jobject>& object, + const base::android::JavaRef<jclass>& safe_annotation_clazz) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + GinJavaBoundObject::ObjectID object_id; + NamedObjectMap::iterator iter = named_objects_.find(name); + bool existing_object = FindObjectId(object, &object_id); + if (existing_object && iter != named_objects_.end() && + iter->second == object_id) { + // Nothing to do. + return; + } + if (iter != named_objects_.end()) { + RemoveNamedObject(iter->first); + } + if (existing_object) { + (*objects_.Lookup(object_id))->AddName(); + } else { + object_id = AddObject(object, safe_annotation_clazz, true, NULL); + } + named_objects_[name] = object_id; + + for (RendererSet::iterator iter = renderers_.begin(); + iter != renderers_.end(); ++iter) { + (*iter)->Send(new GinJavaBridgeMsg_AddNamedObject( + (*iter)->GetRoutingID(), name, object_id)); + } +} + +void GinJavaBridgeDispatcherHost::RemoveNamedObject( + const std::string& name) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + NamedObjectMap::iterator iter = named_objects_.find(name); + if (iter == named_objects_.end()) + return; + + scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(iter->second)); + named_objects_.erase(iter); + object->RemoveName(); + + // Not erasing from the objects map, as we can still receive method + // invocation requests for this object, and they should work until the + // java object is gone. + if (!object->IsNamed()) { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> retained_object_set = + retained_object_set_.get(env); + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_remove( + env, retained_object_set, object->GetLocalRef(env)); + } + } + + for (RendererSet::iterator iter = renderers_.begin(); + iter != renderers_.end(); ++iter) { + (*iter)->Send(new GinJavaBridgeMsg_RemoveNamedObject( + (*iter)->GetRoutingID(), name)); + } +} + +void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) { + allow_object_contents_inspection_ = allow; +} + +void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + // Called when the window object has been cleared in the main frame. + // That means, all sub-frames have also been cleared, so only named + // objects survived. + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> retained_object_set = + retained_object_set_.get(env); + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_clear(env, retained_object_set); + } + + // We also need to add back the named objects we have so far as they + // should survive navigations. + for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd(); + it.Advance()) { + scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue()); + if (object->IsNamed()) { + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_add( + env, retained_object_set, object->GetLocalRef(env)); + } + } else { + objects_.Remove(it.GetCurrentKey()); + } + } +} + +namespace { + +// TODO(mnaganov): Implement passing of a parameter into sync message handlers. +class MessageForwarder : public IPC::Sender { + public: + MessageForwarder(GinJavaBridgeDispatcherHost* gjbdh, + RenderFrameHost* render_frame_host) + : gjbdh_(gjbdh), render_frame_host_(render_frame_host) {} + void OnGetMethods(GinJavaBoundObject::ObjectID object_id, + IPC::Message* reply_msg) { + gjbdh_->OnGetMethods(render_frame_host_, + object_id, + reply_msg); + } + void OnHasMethod(GinJavaBoundObject::ObjectID object_id, + const std::string& method_name, + IPC::Message* reply_msg) { + gjbdh_->OnHasMethod(render_frame_host_, + object_id, + method_name, + reply_msg); + } + void OnInvokeMethod(GinJavaBoundObject::ObjectID object_id, + const std::string& method_name, + const base::ListValue& arguments, + IPC::Message* reply_msg) { + gjbdh_->OnInvokeMethod(render_frame_host_, + object_id, + method_name, + arguments, + reply_msg); + } + virtual bool Send(IPC::Message* msg) OVERRIDE { + NOTREACHED(); + return false; + } + private: + GinJavaBridgeDispatcherHost* gjbdh_; + RenderFrameHost* render_frame_host_; +}; + +} + +bool GinJavaBridgeDispatcherHost::OnMessageReceived( + const IPC::Message& message, + RenderFrameHost* render_frame_host) { + DCHECK(render_frame_host); + bool handled = true; + MessageForwarder forwarder(this, render_frame_host); + IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(GinJavaBridgeDispatcherHost, message, + render_frame_host) + IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_GetMethods, + &forwarder, + MessageForwarder::OnGetMethods) + IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_HasMethod, + &forwarder, + MessageForwarder::OnHasMethod) + IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_InvokeMethod, + &forwarder, + MessageForwarder::OnInvokeMethod) + IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted, + OnObjectWrapperDeleted) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void GinJavaBridgeDispatcherHost::SendReply( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (renderers_.find(render_frame_host) != renderers_.end()) { + render_frame_host->Send(reply_msg); + } else { + delete reply_msg; + } +} + +void GinJavaBridgeDispatcherHost::OnGetMethods( + RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id, + IPC::Message* reply_msg) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(render_frame_host); + if (!allow_object_contents_inspection_) { + IPC::WriteParam(reply_msg, std::set<std::string>()); + render_frame_host->Send(reply_msg); + return; + } + scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id)); + if (!object) { + LOG(ERROR) << "WebView: Unknown object: " << object_id; + IPC::WriteParam(reply_msg, std::set<std::string>()); + render_frame_host->Send(reply_msg); + return; + } + base::PostTaskAndReplyWithResult( + g_background_thread.Get().message_loop()->message_loop_proxy(), + FROM_HERE, + base::Bind(&GinJavaBoundObject::GetMethodNames, object), + base::Bind(&GinJavaBridgeDispatcherHost::SendMethods, + AsWeakPtr(), + render_frame_host, + reply_msg)); +} + +void GinJavaBridgeDispatcherHost::SendMethods( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + const std::set<std::string>& method_names) { + IPC::WriteParam(reply_msg, method_names); + SendReply(render_frame_host, reply_msg); +} + +void GinJavaBridgeDispatcherHost::OnHasMethod( + RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id, + const std::string& method_name, + IPC::Message* reply_msg) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(render_frame_host); + scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id)); + if (!object) { + LOG(ERROR) << "WebView: Unknown object: " << object_id; + IPC::WriteParam(reply_msg, false); + render_frame_host->Send(reply_msg); + return; + } + base::PostTaskAndReplyWithResult( + g_background_thread.Get().message_loop()->message_loop_proxy(), + FROM_HERE, + base::Bind(&GinJavaBoundObject::HasMethod, object, method_name), + base::Bind(&GinJavaBridgeDispatcherHost::SendHasMethodReply, + AsWeakPtr(), + render_frame_host, + reply_msg)); +} + +void GinJavaBridgeDispatcherHost::SendHasMethodReply( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + bool result) { + IPC::WriteParam(reply_msg, result); + SendReply(render_frame_host, reply_msg); +} + +void GinJavaBridgeDispatcherHost::OnInvokeMethod( + RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id, + const std::string& method_name, + const base::ListValue& arguments, + IPC::Message* reply_msg) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(render_frame_host); + scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id)); + if (!object) { + LOG(ERROR) << "WebView: Unknown object: " << object_id; + base::ListValue result; + result.Append(base::Value::CreateNullValue()); + IPC::WriteParam(reply_msg, result); + IPC::WriteParam(reply_msg, kGinJavaBridgeUnknownObjectId); + render_frame_host->Send(reply_msg); + return; + } + scoped_refptr<GinJavaMethodInvocationHelper> result = + new GinJavaMethodInvocationHelper( + make_scoped_ptr(new GinJavaBoundObjectDelegate(object)) + .PassAs<GinJavaMethodInvocationHelper::ObjectDelegate>(), + method_name, + arguments); + result->Init(this); + g_background_thread.Get() + .message_loop() + ->message_loop_proxy() + ->PostTaskAndReply( + FROM_HERE, + base::Bind(&GinJavaMethodInvocationHelper::Invoke, result), + base::Bind( + &GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult, + AsWeakPtr(), + render_frame_host, + reply_msg, + result)); +} + +void GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + scoped_refptr<GinJavaMethodInvocationHelper> result) { + if (result->HoldsPrimitiveResult()) { + IPC::WriteParam(reply_msg, result->GetPrimitiveResult()); + IPC::WriteParam(reply_msg, result->GetInvocationError()); + SendReply(render_frame_host, reply_msg); + } else { + ProcessMethodInvocationObjectResult(render_frame_host, reply_msg, result); + } +} + +void GinJavaBridgeDispatcherHost::ProcessMethodInvocationObjectResult( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + scoped_refptr<GinJavaMethodInvocationHelper> result) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (renderers_.find(render_frame_host) == renderers_.end()) { + delete reply_msg; + return; + } + base::ListValue wrapped_result; + if (!result->GetObjectResult().is_null()) { + GinJavaBoundObject::ObjectID returned_object_id; + if (FindObjectId(result->GetObjectResult(), &returned_object_id)) { + (*objects_.Lookup(returned_object_id))->AddHolder(render_frame_host); + } else { + returned_object_id = AddObject(result->GetObjectResult(), + result->GetSafeAnnotationClass(), + false, + render_frame_host); + } + wrapped_result.Append( + GinJavaBridgeValue::CreateObjectIDValue(returned_object_id).release()); + } else { + wrapped_result.Append(base::Value::CreateNullValue()); + } + IPC::WriteParam(reply_msg, wrapped_result); + IPC::WriteParam(reply_msg, result->GetInvocationError()); + render_frame_host->Send(reply_msg); +} + +void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted( + RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(render_frame_host); + if (objects_.Lookup(object_id)) { + GinJavaBoundObject::ObjectMap::iterator iter(&objects_); + while (!iter.IsAtEnd() && iter.GetCurrentKey() != object_id) + iter.Advance(); + DCHECK(!iter.IsAtEnd()); + RemoveHolder(render_frame_host, iter, 1); + } +} + +} // namespace content diff --git a/content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.h b/content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.h new file mode 100644 index 0000000..d77da3a --- /dev/null +++ b/content/browser/renderer_host/java/gin_java_bridge_dispatcher_host.h @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BRIDGE_DISPATCHER_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BRIDGE_DISPATCHER_HOST_H_ + +#include <map> +#include <set> + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/renderer_host/java/gin_java_bound_object.h" +#include "content/browser/renderer_host/java/gin_java_method_invocation_helper.h" +#include "content/public/browser/web_contents_observer.h" + +namespace base { +class ListValue; +} + +namespace IPC { +class Message; +} + +namespace content { + +// This class handles injecting Java objects into a single RenderView. The Java +// object itself lives in the browser process on a background thread, while a +// proxy object is created in the renderer. An instance of this class exists +// for each RenderFrameHost. +class GinJavaBridgeDispatcherHost + : public base::SupportsWeakPtr<GinJavaBridgeDispatcherHost>, + public WebContentsObserver, + public GinJavaMethodInvocationHelper::DispatcherDelegate { + public: + + GinJavaBridgeDispatcherHost(WebContents* web_contents, + jobject retained_object_set); + virtual ~GinJavaBridgeDispatcherHost(); + + void AddNamedObject( + const std::string& name, + const base::android::JavaRef<jobject>& object, + const base::android::JavaRef<jclass>& safe_annotation_clazz); + void RemoveNamedObject(const std::string& name); + void SetAllowObjectContentsInspection(bool allow); + + // WebContentsObserver + virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE; + virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE; + virtual void DocumentAvailableInMainFrame() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message, + RenderFrameHost* render_frame_host) OVERRIDE; + + // GinJavaMethodInvocationHelper::DispatcherDelegate + virtual JavaObjectWeakGlobalRef GetObjectWeakRef( + GinJavaBoundObject::ObjectID object_id) OVERRIDE; + + void OnGetMethods(RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id, + IPC::Message* reply_msg); + void OnHasMethod(RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id, + const std::string& method_name, + IPC::Message* reply_msg); + void OnInvokeMethod(RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id, + const std::string& method_name, + const base::ListValue& arguments, + IPC::Message* reply_msg); + + private: + typedef std::set<RenderFrameHost*> RendererSet; + void OnObjectWrapperDeleted(RenderFrameHost* render_frame_host, + GinJavaBoundObject::ObjectID object_id); + + void SendReply(RenderFrameHost* render_frame_host, IPC::Message* reply_msg); + void SendMethods(RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + const std::set<std::string>& method_names); + void SendHasMethodReply(RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + bool result); + void ProcessMethodInvocationResult( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + scoped_refptr<GinJavaMethodInvocationHelper> result); + void ProcessMethodInvocationObjectResult( + RenderFrameHost* render_frame_host, + IPC::Message* reply_msg, + scoped_refptr<GinJavaMethodInvocationHelper> result); + GinJavaBoundObject::ObjectID AddObject( + const base::android::JavaRef<jobject>& object, + const base::android::JavaRef<jclass>& safe_annotation_clazz, + bool is_named, + RenderFrameHost* holder); + bool FindObjectId(const base::android::JavaRef<jobject>& object, + GinJavaBoundObject::ObjectID* object_id); + void RemoveHolder(RenderFrameHost* holder, + const GinJavaBoundObject::ObjectMap::iterator& from, + size_t count); + + // Every time a GinJavaBoundObject backed by a real Java object is + // created/destroyed, we insert/remove a strong ref to that Java object into + // this set so that it doesn't get garbage collected while it's still + // potentially in use. Although the set is managed native side, it's owned + // and defined in Java so that pushing refs into it does not create new GC + // roots that would prevent ContentViewCore from being garbage collected. + JavaObjectWeakGlobalRef retained_object_set_; + bool allow_object_contents_inspection_; + RendererSet renderers_; + GinJavaBoundObject::ObjectMap objects_; + typedef std::map<std::string, GinJavaBoundObject::ObjectID> NamedObjectMap; + NamedObjectMap named_objects_; + + DISALLOW_COPY_AND_ASSIGN(GinJavaBridgeDispatcherHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BRIDGE_DISPATCHER_HOST_H_ diff --git a/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc b/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc index d3fb138..5c60ecf 100644 --- a/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc +++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc @@ -23,13 +23,6 @@ namespace content { namespace { -const char kObjectIsGone[] = "Java object is gone"; -const char kMethodNotFound[] = "Method not found"; -const char kAccessToObjectGetClassIsBlocked[] = - "Access to java.lang.Object.getClass is blocked"; -const char kJavaExceptionRaised[] = - "Java exception has been raised during method invocation"; - // See frameworks/base/core/java/android/webkit/EventLogTags.logtags const int kObjectGetClassInvocationAttemptLogTag = 70151; @@ -41,7 +34,8 @@ GinJavaMethodInvocationHelper::GinJavaMethodInvocationHelper( const base::ListValue& arguments) : object_(object.Pass()), method_name_(method_name), - arguments_(arguments.DeepCopy()) { + arguments_(arguments.DeepCopy()), + invocation_error_(kGinJavaBridgeNoError) { } GinJavaMethodInvocationHelper::~GinJavaMethodInvocationHelper() {} @@ -121,14 +115,14 @@ void GinJavaMethodInvocationHelper::Invoke() { const JavaMethod* method = object_->FindMethod(method_name_, arguments_->GetSize()); if (!method) { - SetInvocationFailure(kMethodNotFound); + SetInvocationError(kGinJavaBridgeMethodNotFound); return; } if (object_->IsObjectGetClassMethod(method)) { base::android::EventLogWriteInt(kObjectGetClassInvocationAttemptLogTag, getuid()); - SetInvocationFailure(kAccessToObjectGetClassIsBlocked); + SetInvocationError(kGinJavaBridgeAccessToObjectGetClassIsBlocked); return; } @@ -140,7 +134,7 @@ void GinJavaMethodInvocationHelper::Invoke() { obj = object_->GetLocalRef(env); } if (obj.is_null() && cls.is_null()) { - SetInvocationFailure(kObjectIsGone); + SetInvocationError(kGinJavaBridgeObjectIsGone); return; } @@ -166,11 +160,11 @@ void GinJavaMethodInvocationHelper::Invoke() { } } -void GinJavaMethodInvocationHelper::SetInvocationFailure( - const char* error_message) { +void GinJavaMethodInvocationHelper::SetInvocationError( + GinJavaBridgeError error) { holds_primitive_result_ = true; primitive_result_.reset(new base::ListValue()); - error_message_ = error_message; + invocation_error_ = error; } void GinJavaMethodInvocationHelper::SetPrimitiveResult( @@ -205,8 +199,8 @@ GinJavaMethodInvocationHelper::GetSafeAnnotationClass() { return safe_annotation_clazz_; } -const std::string& GinJavaMethodInvocationHelper::GetErrorMessage() { - return error_message_; +const GinJavaBridgeError GinJavaMethodInvocationHelper::GetInvocationError() { + return invocation_error_; } void GinJavaMethodInvocationHelper::InvokeMethod(jobject object, @@ -295,7 +289,7 @@ void GinJavaMethodInvocationHelper::InvokeMethod(jobject object, // methods. ScopedJavaLocalRef is liable to make such calls, so we test // first. if (base::android::ClearException(env)) { - SetInvocationFailure(kJavaExceptionRaised); + SetInvocationError(kGinJavaBridgeJavaExceptionRaised); return; } ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string); @@ -318,7 +312,7 @@ void GinJavaMethodInvocationHelper::InvokeMethod(jobject object, object ? env->CallObjectMethodA(object, id, parameters) : env->CallStaticObjectMethodA(clazz, id, parameters); if (base::android::ClearException(env)) { - SetInvocationFailure(kJavaExceptionRaised); + SetInvocationError(kGinJavaBridgeJavaExceptionRaised); return; } ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object); @@ -334,7 +328,7 @@ void GinJavaMethodInvocationHelper::InvokeMethod(jobject object, if (!base::android::ClearException(env)) { SetPrimitiveResult(result_wrapper); } else { - SetInvocationFailure(kJavaExceptionRaised); + SetInvocationError(kGinJavaBridgeJavaExceptionRaised); } } diff --git a/content/browser/renderer_host/java/gin_java_method_invocation_helper.h b/content/browser/renderer_host/java/gin_java_method_invocation_helper.h index 89805ca..4cdb09b 100644 --- a/content/browser/renderer_host/java/gin_java_method_invocation_helper.h +++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper.h @@ -13,6 +13,7 @@ #include "base/values.h" #include "content/browser/renderer_host/java/gin_java_bound_object.h" #include "content/browser/renderer_host/java/java_type.h" +#include "content/common/android/gin_java_bridge_errors.h" #include "content/common/content_export.h" namespace content { @@ -68,7 +69,7 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper const base::ListValue& GetPrimitiveResult(); const base::android::JavaRef<jobject>& GetObjectResult(); const base::android::JavaRef<jclass>& GetSafeAnnotationClass(); - const std::string& GetErrorMessage(); + const GinJavaBridgeError GetInvocationError(); private: friend class base::RefCountedThreadSafe<GinJavaMethodInvocationHelper>; @@ -89,7 +90,7 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper const JavaType& return_type, jmethodID id, jvalue* parameters); - void SetInvocationFailure(const char* error_message); + void SetInvocationError(GinJavaBridgeError error); void SetPrimitiveResult(const base::ListValue& result_wrapper); void SetObjectResult( const base::android::JavaRef<jobject>& object, @@ -104,7 +105,7 @@ class CONTENT_EXPORT GinJavaMethodInvocationHelper ObjectRefs object_refs_; bool holds_primitive_result_; scoped_ptr<base::ListValue> primitive_result_; - std::string error_message_; + GinJavaBridgeError invocation_error_; base::android::ScopedJavaGlobalRef<jobject> object_result_; base::android::ScopedJavaGlobalRef<jclass> safe_annotation_clazz_; diff --git a/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc b/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc index d763441..067a5e1 100644 --- a/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc +++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc @@ -5,6 +5,7 @@ #include "content/browser/renderer_host/java/gin_java_method_invocation_helper.h" #include "base/android/jni_android.h" +#include "content/browser/renderer_host/java/jni_helper.h" #include "content/common/android/gin_java_bridge_value.h" #include "testing/gtest/include/gtest/gtest.h" @@ -154,21 +155,73 @@ TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsHaveObjects) { counter.AssertInvocationsCount(1, 6); } +namespace { + +class ObjectIsGoneObjectDelegate : public NullObjectDelegate { + public: + ObjectIsGoneObjectDelegate() : + get_local_ref_called_(false) { + // We need a Java Method object to create a valid JavaMethod instance. + JNIEnv* env = base::android::AttachCurrentThread(); + jmethodID method_id = + GetMethodIDFromClassName(env, "java/lang/Object", "hashCode", "()I"); + EXPECT_TRUE(method_id); + base::android::ScopedJavaLocalRef<jobject> method_obj( + env, + env->ToReflectedMethod( + base::android::GetClass(env, "java/lang/Object").obj(), + method_id, + false)); + EXPECT_TRUE(method_obj.obj()); + method_.reset(new JavaMethod(method_obj)); + } + + virtual ~ObjectIsGoneObjectDelegate() {} + + virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef( + JNIEnv* env) OVERRIDE { + get_local_ref_called_ = true; + return NullObjectDelegate::GetLocalRef(env); + } + + virtual const JavaMethod* FindMethod(const std::string& method_name, + size_t num_parameters) OVERRIDE { + return method_.get(); + } + + bool get_local_ref_called() { return get_local_ref_called_; } + + const std::string& get_method_name() { return method_->name(); } + + protected: + scoped_ptr<JavaMethod> method_; + bool get_local_ref_called_; + + private: + DISALLOW_COPY_AND_ASSIGN(ObjectIsGoneObjectDelegate); +}; + +} // namespace + TEST_F(GinJavaMethodInvocationHelperTest, HandleObjectIsGone) { base::ListValue no_objects; + ObjectIsGoneObjectDelegate* object_delegate = + new ObjectIsGoneObjectDelegate(); scoped_refptr<GinJavaMethodInvocationHelper> helper = new GinJavaMethodInvocationHelper( scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>( - new NullObjectDelegate()), - "foo", + object_delegate), + object_delegate->get_method_name(), no_objects); NullDispatcherDelegate dispatcher; helper->Init(&dispatcher); - EXPECT_TRUE(helper->GetErrorMessage().empty()); + EXPECT_FALSE(object_delegate->get_local_ref_called()); + EXPECT_EQ(kGinJavaBridgeNoError, helper->GetInvocationError()); helper->Invoke(); + EXPECT_TRUE(object_delegate->get_local_ref_called()); EXPECT_TRUE(helper->HoldsPrimitiveResult()); EXPECT_TRUE(helper->GetPrimitiveResult().empty()); - EXPECT_FALSE(helper->GetErrorMessage().empty()); + EXPECT_EQ(kGinJavaBridgeObjectIsGone, helper->GetInvocationError()); } namespace { @@ -215,12 +268,12 @@ TEST_F(GinJavaMethodInvocationHelperTest, HandleMethodNotFound) { NullDispatcherDelegate dispatcher; helper->Init(&dispatcher); EXPECT_FALSE(object_delegate->find_method_called()); - EXPECT_TRUE(helper->GetErrorMessage().empty()); + EXPECT_EQ(kGinJavaBridgeNoError, helper->GetInvocationError()); helper->Invoke(); EXPECT_TRUE(object_delegate->find_method_called()); EXPECT_TRUE(helper->HoldsPrimitiveResult()); EXPECT_TRUE(helper->GetPrimitiveResult().empty()); - EXPECT_FALSE(helper->GetErrorMessage().empty()); + EXPECT_EQ(kGinJavaBridgeMethodNotFound, helper->GetInvocationError()); } namespace { @@ -273,13 +326,14 @@ TEST_F(GinJavaMethodInvocationHelperTest, HandleGetClassInvocation) { helper->Init(&dispatcher); EXPECT_FALSE(object_delegate->find_method_called()); EXPECT_FALSE(object_delegate->get_class_called()); - EXPECT_TRUE(helper->GetErrorMessage().empty()); + EXPECT_EQ(kGinJavaBridgeNoError, helper->GetInvocationError()); helper->Invoke(); EXPECT_TRUE(object_delegate->find_method_called()); EXPECT_TRUE(object_delegate->get_class_called()); EXPECT_TRUE(helper->HoldsPrimitiveResult()); EXPECT_TRUE(helper->GetPrimitiveResult().empty()); - EXPECT_FALSE(helper->GetErrorMessage().empty()); + EXPECT_EQ(kGinJavaBridgeAccessToObjectGetClassIsBlocked, + helper->GetInvocationError()); } } // namespace content diff --git a/content/browser/renderer_host/java/java_method.h b/content/browser/renderer_host/java/java_method.h index 1d59bac..6477263 100644 --- a/content/browser/renderer_host/java/java_method.h +++ b/content/browser/renderer_host/java/java_method.h @@ -11,12 +11,13 @@ #include "base/android/scoped_java_ref.h" #include "content/browser/renderer_host/java/java_type.h" +#include "content/common/content_export.h" namespace content { // Wrapper around java.lang.reflect.Method. This class must be used on a single // thread only. -class JavaMethod { +class CONTENT_EXPORT JavaMethod { public: explicit JavaMethod(const base::android::JavaRef<jobject>& method); ~JavaMethod(); diff --git a/content/common/android/gin_java_bridge_errors.cc b/content/common/android/gin_java_bridge_errors.cc new file mode 100644 index 0000000..7a80801 --- /dev/null +++ b/content/common/android/gin_java_bridge_errors.cc @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/common/android/gin_java_bridge_errors.h" + +#include "base/logging.h" + +namespace content { + +const char* GinJavaBridgeErrorToString(GinJavaBridgeError error) { + switch (error) { + case kGinJavaBridgeNoError: + return "No error"; + case kGinJavaBridgeUnknownObjectId: + return "Unknown Java object ID"; + case kGinJavaBridgeObjectIsGone: + return "Java object is gone"; + case kGinJavaBridgeMethodNotFound: + return "Method not found"; + case kGinJavaBridgeAccessToObjectGetClassIsBlocked: + return "Access to java.lang.Object.getClass is blocked"; + case kGinJavaBridgeJavaExceptionRaised: + return "Java exception was raised during method invocation"; + } + NOTREACHED(); + return "Unknown error"; +} + +} // namespace content diff --git a/content/common/android/gin_java_bridge_errors.h b/content/common/android/gin_java_bridge_errors.h new file mode 100644 index 0000000..75a8970 --- /dev/null +++ b/content/common/android/gin_java_bridge_errors.h @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_COMMON_ANDROID_GIN_JAVA_BRIDGE_ERRORS_H_ +#define CONTENT_COMMON_ANDROID_GIN_JAVA_BRIDGE_ERRORS_H_ + +#include "content/common/content_export.h" + +namespace content { + +enum GinJavaBridgeError { + kGinJavaBridgeNoError = 0, + kGinJavaBridgeUnknownObjectId, + kGinJavaBridgeObjectIsGone, + kGinJavaBridgeMethodNotFound, + kGinJavaBridgeAccessToObjectGetClassIsBlocked, + kGinJavaBridgeJavaExceptionRaised, +}; + +CONTENT_EXPORT const char* GinJavaBridgeErrorToString(GinJavaBridgeError error); + +} // namespace content + +#endif // CONTENT_COMMON_ANDROID_GIN_JAVA_BRIDGE_ERRORS_H_ diff --git a/content/common/gin_java_bridge_messages.h b/content/common/gin_java_bridge_messages.h index f9525b1..cbce413 100644 --- a/content/common/gin_java_bridge_messages.h +++ b/content/common/gin_java_bridge_messages.h @@ -7,6 +7,7 @@ // Multiply-included message file, hence no include guard. #include "base/basictypes.h" +#include "content/common/android/gin_java_bridge_errors.h" #include "content/common/content_export.h" #include "ipc/ipc_message_macros.h" @@ -16,6 +17,8 @@ // Messages for handling Java objects injected into JavaScript ----------------- +IPC_ENUM_TRAITS(content::GinJavaBridgeError) + // Sent from browser to renderer to add a Java object with the given name. // Object IDs are generated on the browser side. IPC_MESSAGE_ROUTED2(GinJavaBridgeMsg_AddNamedObject, @@ -45,14 +48,16 @@ IPC_SYNC_MESSAGE_ROUTED2_1(GinJavaBridgeHostMsg_HasMethod, // a container to work around immutability of base::Value. // Empty result list indicates that an error has happened on the Java side // (either bridge-induced error or an unhandled Java exception) and an exception -// must be thrown into JavaScript. +// must be thrown into JavaScript. |error_code| indicates the cause of +// the error. // Some special value types that are not supported by base::Value are encoded // as BinaryValues via GinJavaBridgeValue. -IPC_SYNC_MESSAGE_ROUTED3_1(GinJavaBridgeHostMsg_InvokeMethod, +IPC_SYNC_MESSAGE_ROUTED3_2(GinJavaBridgeHostMsg_InvokeMethod, int32 /* object_id */, std::string /* method_name */, base::ListValue /* arguments */, - base::ListValue /* result */) + base::ListValue /* result */, + content::GinJavaBridgeError /* error_code */) // Sent from renderer to browser in two cases: // diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 874f0f2..d8a00d3 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -1335,6 +1335,8 @@ 'browser/renderer_host/java/gin_java_bound_object.h', 'browser/renderer_host/java/gin_java_bound_object_delegate.cc', 'browser/renderer_host/java/gin_java_bound_object_delegate.h', + 'browser/renderer_host/java/gin_java_bridge_dispatcher_host.cc', + 'browser/renderer_host/java/gin_java_bridge_dispatcher_host.h', 'browser/renderer_host/java/gin_java_method_invocation_helper.cc', 'browser/renderer_host/java/gin_java_method_invocation_helper.h', 'browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc', diff --git a/content/content_common.gypi b/content/content_common.gypi index d9aace9f..2070af6 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -140,6 +140,8 @@ 'common/android/address_parser_internal.h', 'common/android/common_jni_registrar.cc', 'common/android/common_jni_registrar.h', + 'common/android/gin_java_bridge_errors.cc', + 'common/android/gin_java_bridge_errors.h', 'common/android/gin_java_bridge_value.cc', 'common/android/gin_java_bridge_value.h', 'common/android/hash_set.cc', diff --git a/content/renderer/java/gin_java_bridge_dispatcher.cc b/content/renderer/java/gin_java_bridge_dispatcher.cc index 88d502e..9b999f2 100644 --- a/content/renderer/java/gin_java_bridge_dispatcher.cc +++ b/content/renderer/java/gin_java_bridge_dispatcher.cc @@ -114,14 +114,16 @@ bool GinJavaBridgeDispatcher::HasJavaMethod(ObjectID object_id, scoped_ptr<base::Value> GinJavaBridgeDispatcher::InvokeJavaMethod( ObjectID object_id, const std::string& method_name, - const base::ListValue& arguments) { + const base::ListValue& arguments, + GinJavaBridgeError* error) { base::ListValue result_wrapper; render_frame()->Send( new GinJavaBridgeHostMsg_InvokeMethod(routing_id(), object_id, method_name, arguments, - &result_wrapper)); + &result_wrapper, + error)); base::Value* result; if (result_wrapper.Get(0, &result)) { return scoped_ptr<base::Value>(result->DeepCopy()); diff --git a/content/renderer/java/gin_java_bridge_dispatcher.h b/content/renderer/java/gin_java_bridge_dispatcher.h index 13c6777..efbd19f 100644 --- a/content/renderer/java/gin_java_bridge_dispatcher.h +++ b/content/renderer/java/gin_java_bridge_dispatcher.h @@ -12,6 +12,7 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/values.h" +#include "content/common/android/gin_java_bridge_errors.h" #include "content/public/renderer/render_frame_observer.h" namespace blink { @@ -50,7 +51,8 @@ class GinJavaBridgeDispatcher bool HasJavaMethod(ObjectID object_id, const std::string& method_name); scoped_ptr<base::Value> InvokeJavaMethod(ObjectID object_id, const std::string& method_name, - const base::ListValue& arguments); + const base::ListValue& arguments, + GinJavaBridgeError* error); GinJavaBridgeObject* GetObject(ObjectID object_id); void OnGinJavaBridgeObjectDeleted(ObjectID object_id); diff --git a/content/renderer/java/gin_java_bridge_object.cc b/content/renderer/java/gin_java_bridge_object.cc index a153f2a..d2a2b60 100644 --- a/content/renderer/java/gin_java_bridge_object.cc +++ b/content/renderer/java/gin_java_bridge_object.cc @@ -5,6 +5,7 @@ #include "content/renderer/java/gin_java_bridge_object.h" #include "base/strings/utf_string_conversions.h" +#include "content/common/android/gin_java_bridge_errors.h" #include "content/common/android/gin_java_bridge_value.h" #include "content/public/renderer/v8_value_converter.h" #include "content/renderer/java/gin_java_bridge_value_converter.h" @@ -125,11 +126,12 @@ v8::Handle<v8::Value> GinJavaBridgeObject::InvokeMethod( } } - scoped_ptr<base::Value> result = - dispatcher_->InvokeJavaMethod(object_id_, name, arguments); + GinJavaBridgeError error; + scoped_ptr<base::Value> result = dispatcher_->InvokeJavaMethod( + object_id_, name, arguments, &error); if (!result.get()) { args->isolate()->ThrowException(v8::Exception::Error(gin::StringToV8( - args->isolate(), kMethodInvocationErrorMessage))); + args->isolate(), GinJavaBridgeErrorToString(error)))); return v8::Undefined(args->isolate()); } if (!result->IsType(base::Value::TYPE_BINARY)) { |