// 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/android/java/gin_java_bridge_dispatcher_host.h" #include "base/android/jni_android.h" #include "base/android/scoped_java_ref.h" #include "content/browser/android/java/gin_java_bound_object_delegate.h" #include "content/browser/android/java/gin_java_bridge_message_filter.h" #include "content/browser/android/java/java_bridge_thread.h" #include "content/browser/android/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 "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #if !defined(OS_ANDROID) #error "JavaBridge only supports OS_ANDROID" #endif namespace content { GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost( WebContents* web_contents, jobject retained_object_set) : WebContentsObserver(web_contents), next_object_id_(1), retained_object_set_(base::android::AttachCurrentThread(), retained_object_set), allow_object_contents_inspection_(true) { DCHECK(retained_object_set); } GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() { } // GinJavaBridgeDispatcherHost gets created earlier than RenderProcessHost // is initialized. So we postpone installing the message filter until we know // that the RPH is in a good shape. Also, message filter installation is // postponed until the first named object is created. void GinJavaBridgeDispatcherHost::InstallFilterAndRegisterAllRoutingIds() { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!named_objects_.empty()); if (!web_contents()->GetRenderProcessHost()->GetChannel()) return; DCHECK(!GinJavaBridgeMessageFilter::FromHost(this, false)); scoped_refptr filter = GinJavaBridgeMessageFilter::FromHost(this, true); // ForEachFrame is synchronous. web_contents()->ForEachFrame( base::Bind(&GinJavaBridgeMessageFilter::AddRoutingIdForHost, filter, base::Unretained(this))); } void GinJavaBridgeDispatcherHost::RenderFrameCreated( RenderFrameHost* render_frame_host) { DCHECK_CURRENTLY_ON(BrowserThread::UI); scoped_refptr filter = GinJavaBridgeMessageFilter::FromHost(this, false); if (filter) { filter->AddRoutingIdForHost(this, render_frame_host); } else if (!named_objects_.empty()) { InstallFilterAndRegisterAllRoutingIds(); } 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::WebContentsDestroyed() { scoped_refptr filter = GinJavaBridgeMessageFilter::FromHost(this, false); if (filter) filter->RemoveHost(this); } GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject( const base::android::JavaRef& object, const base::android::JavaRef& safe_annotation_clazz, bool is_named, int32 holder) { // Can be called on any thread. Calls come from the UI thread via // AddNamedObject, and from the background thread, when injected Java // object's method returns a Java object. DCHECK(is_named || holder); JNIEnv* env = base::android::AttachCurrentThread(); JavaObjectWeakGlobalRef ref(env, object.obj()); scoped_refptr new_object = is_named ? GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz) : GinJavaBoundObject::CreateTransient(ref, safe_annotation_clazz, holder); GinJavaBoundObject::ObjectID object_id = next_object_id_++; { base::AutoLock locker(objects_lock_); objects_[object_id] = new_object; } #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 retained_object_set = retained_object_set_.get(env); if (!retained_object_set.is_null()) { base::AutoLock locker(objects_lock_); JNI_Java_HashSet_add(env, retained_object_set, object); } return object_id; } bool GinJavaBridgeDispatcherHost::FindObjectId( const base::android::JavaRef& object, GinJavaBoundObject::ObjectID* object_id) { // Can be called on any thread. JNIEnv* env = base::android::AttachCurrentThread(); base::AutoLock locker(objects_lock_); for (const auto& pair : objects_) { if (env->IsSameObject( object.obj(), pair.second->GetLocalRef(env).obj())) { *object_id = pair.first; return true; } } return false; } JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef( GinJavaBoundObject::ObjectID object_id) { scoped_refptr object = FindObject(object_id); if (object.get()) return object->GetWeakRef(); else return JavaObjectWeakGlobalRef(); } JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked( int32 holder, ObjectMap::iterator* iter_ptr) { objects_lock_.AssertAcquired(); JavaObjectWeakGlobalRef result; scoped_refptr object((*iter_ptr)->second); bool object_erased = false; if (!object->IsNamed()) { object->RemoveHolder(holder); if (!object->HasHolders()) { result = object->GetWeakRef(); objects_.erase((*iter_ptr)++); object_erased = true; } } if (!object_erased) { ++(*iter_ptr); } return result; } void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked( const JavaObjectWeakGlobalRef& ref) { objects_lock_.AssertAcquired(); JNIEnv* env = base::android::AttachCurrentThread(); base::android::ScopedJavaLocalRef retained_object_set = retained_object_set_.get(env); if (!retained_object_set.is_null()) { JNI_Java_HashSet_remove(env, retained_object_set, ref.get(env)); } } void GinJavaBridgeDispatcherHost::AddNamedObject( const std::string& name, const base::android::JavaRef& object, const base::android::JavaRef& 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) { base::AutoLock locker(objects_lock_); objects_[object_id]->AddName(); } else { object_id = AddObject(object, safe_annotation_clazz, true, 0); } named_objects_[name] = object_id; scoped_refptr filter = GinJavaBridgeMessageFilter::FromHost(this, false); if (!filter) InstallFilterAndRegisterAllRoutingIds(); web_contents()->SendToAllFrames( new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE, 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; // |name| may come from |named_objects_|. Make a copy of name so that if // |name| is from |named_objects_| it'll be valid after the remove below. const std::string copied_name(name); { base::AutoLock locker(objects_lock_); objects_[iter->second]->RemoveName(); } named_objects_.erase(iter); // As the object isn't going to be removed from the JavaScript side until the // next page reload, calls to it must still work, thus we should continue to // hold it. All the transient objects and removed named objects will be purged // during the cleansing caused by DocumentAvailableInMainFrame event. web_contents()->SendToAllFrames( new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE, copied_name)); } void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) { if (!JavaBridgeThread::CurrentlyOn()) { JavaBridgeThread::GetTaskRunner()->PostTask( FROM_HERE, base::Bind( &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection, this, allow)); return; } 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 retained_object_set = retained_object_set_.get(env); base::AutoLock locker(objects_lock_); if (!retained_object_set.is_null()) { JNI_Java_HashSet_clear(env, retained_object_set); } auto iter = objects_.begin(); while (iter != objects_.end()) { if (iter->second->IsNamed()) { if (!retained_object_set.is_null()) { JNI_Java_HashSet_add( env, retained_object_set, iter->second->GetLocalRef(env)); } ++iter; } else { objects_.erase(iter++); } } } scoped_refptr GinJavaBridgeDispatcherHost::FindObject( GinJavaBoundObject::ObjectID object_id) { // Can be called on any thread. base::AutoLock locker(objects_lock_); auto iter = objects_.find(object_id); if (iter != objects_.end()) return iter->second; LOG(ERROR) << "WebView: Unknown object: " << object_id; return nullptr; } void GinJavaBridgeDispatcherHost::OnGetMethods( GinJavaBoundObject::ObjectID object_id, std::set* returned_method_names) { DCHECK(JavaBridgeThread::CurrentlyOn()); if (!allow_object_contents_inspection_) return; scoped_refptr object = FindObject(object_id); if (object.get()) *returned_method_names = object->GetMethodNames(); } void GinJavaBridgeDispatcherHost::OnHasMethod( GinJavaBoundObject::ObjectID object_id, const std::string& method_name, bool* result) { DCHECK(JavaBridgeThread::CurrentlyOn()); scoped_refptr object = FindObject(object_id); if (object.get()) *result = object->HasMethod(method_name); } void GinJavaBridgeDispatcherHost::OnInvokeMethod( int routing_id, GinJavaBoundObject::ObjectID object_id, const std::string& method_name, const base::ListValue& arguments, base::ListValue* wrapped_result, content::GinJavaBridgeError* error_code) { DCHECK(JavaBridgeThread::CurrentlyOn()); DCHECK(routing_id != MSG_ROUTING_NONE); scoped_refptr object = FindObject(object_id); if (!object.get()) { wrapped_result->Append(base::Value::CreateNullValue()); *error_code = kGinJavaBridgeUnknownObjectId; return; } scoped_refptr result = new GinJavaMethodInvocationHelper( make_scoped_ptr(new GinJavaBoundObjectDelegate(object)), method_name, arguments); result->Init(this); result->Invoke(); *error_code = result->GetInvocationError(); if (result->HoldsPrimitiveResult()) { scoped_ptr result_copy( result->GetPrimitiveResult().DeepCopy()); wrapped_result->Swap(result_copy.get()); } else if (!result->GetObjectResult().is_null()) { GinJavaBoundObject::ObjectID returned_object_id; if (FindObjectId(result->GetObjectResult(), &returned_object_id)) { base::AutoLock locker(objects_lock_); objects_[returned_object_id]->AddHolder(routing_id); } else { returned_object_id = AddObject(result->GetObjectResult(), result->GetSafeAnnotationClass(), false, routing_id); } wrapped_result->Append( GinJavaBridgeValue::CreateObjectIDValue( returned_object_id).release()); } else { wrapped_result->Append(base::Value::CreateNullValue()); } } void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted( int routing_id, GinJavaBoundObject::ObjectID object_id) { DCHECK(JavaBridgeThread::CurrentlyOn()); DCHECK(routing_id != MSG_ROUTING_NONE); base::AutoLock locker(objects_lock_); auto iter = objects_.find(object_id); if (iter == objects_.end()) return; JavaObjectWeakGlobalRef ref = RemoveHolderAndAdvanceLocked(routing_id, &iter); if (!ref.is_empty()) { RemoveFromRetainedObjectSetLocked(ref); } } } // namespace content