Home | History | Annotate | Download | only in java
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"
      6 
      7 #include "base/android/java_handler_thread.h"
      8 #include "base/android/jni_android.h"
      9 #include "base/android/scoped_java_ref.h"
     10 #include "base/atomic_sequence_num.h"
     11 #include "base/lazy_instance.h"
     12 #include "base/pickle.h"
     13 #include "base/strings/string_number_conversions.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "base/task_runner_util.h"
     16 #include "content/browser/android/java/gin_java_bound_object_delegate.h"
     17 #include "content/browser/android/java/jni_helper.h"
     18 #include "content/common/android/gin_java_bridge_value.h"
     19 #include "content/common/android/hash_set.h"
     20 #include "content/common/gin_java_bridge_messages.h"
     21 #include "content/public/browser/browser_thread.h"
     22 #include "content/public/browser/render_frame_host.h"
     23 #include "content/public/browser/render_process_host.h"
     24 #include "content/public/browser/web_contents.h"
     25 #include "ipc/ipc_message_utils.h"
     26 
     27 #if !defined(OS_ANDROID)
     28 #error "JavaBridge only supports OS_ANDROID"
     29 #endif
     30 
     31 namespace content {
     32 
     33 namespace {
     34 // The JavaBridge needs to use a Java thread so the callback
     35 // will happen on a thread with a prepared Looper.
     36 class JavaBridgeThread : public base::android::JavaHandlerThread {
     37  public:
     38   JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
     39     Start();
     40   }
     41   virtual ~JavaBridgeThread() {
     42     Stop();
     43   }
     44   static bool CurrentlyOn();
     45 };
     46 
     47 base::LazyInstance<JavaBridgeThread> g_background_thread =
     48     LAZY_INSTANCE_INITIALIZER;
     49 
     50 // static
     51 bool JavaBridgeThread::CurrentlyOn() {
     52   return base::MessageLoop::current() ==
     53          g_background_thread.Get().message_loop();
     54 }
     55 
     56 // Object IDs are globally unique, so we can figure out the right
     57 // GinJavaBridgeDispatcherHost when dispatching messages on the background
     58 // thread.
     59 base::StaticAtomicSequenceNumber g_next_object_id;
     60 
     61 }  // namespace
     62 
     63 GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
     64     WebContents* web_contents,
     65     jobject retained_object_set)
     66     : WebContentsObserver(web_contents),
     67       BrowserMessageFilter(GinJavaBridgeMsgStart),
     68       browser_filter_added_(false),
     69       retained_object_set_(base::android::AttachCurrentThread(),
     70                            retained_object_set),
     71       allow_object_contents_inspection_(true),
     72       current_routing_id_(MSG_ROUTING_NONE) {
     73   DCHECK(retained_object_set);
     74 }
     75 
     76 GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
     77 }
     78 
     79 // GinJavaBridgeDispatcherHost gets created earlier than RenderProcessHost
     80 // is initialized. So we postpone installing the message filter until we know
     81 // that the RPH is in a good shape. Currently this means that we are calling
     82 // this function from any UI thread function that is about to communicate
     83 // with the renderer.
     84 // TODO(mnaganov): Redesign, so we only have a single filter for all hosts.
     85 void GinJavaBridgeDispatcherHost::AddBrowserFilterIfNeeded() {
     86   DCHECK_CURRENTLY_ON(BrowserThread::UI);
     87   // Transient objects can only appear after named objects were added. Thus,
     88   // we can wait until we have one, to avoid installing unnecessary filters.
     89   if (!browser_filter_added_ &&
     90       web_contents()->GetRenderProcessHost()->GetChannel() &&
     91       !named_objects_.empty()) {
     92     web_contents()->GetRenderProcessHost()->AddFilter(this);
     93     browser_filter_added_ = true;
     94   }
     95 }
     96 
     97 void GinJavaBridgeDispatcherHost::RenderFrameCreated(
     98     RenderFrameHost* render_frame_host) {
     99   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    100   AddBrowserFilterIfNeeded();
    101   for (NamedObjectMap::const_iterator iter = named_objects_.begin();
    102        iter != named_objects_.end();
    103        ++iter) {
    104     render_frame_host->Send(new GinJavaBridgeMsg_AddNamedObject(
    105         render_frame_host->GetRoutingID(), iter->first, iter->second));
    106   }
    107 }
    108 
    109 void GinJavaBridgeDispatcherHost::RenderFrameDeleted(
    110     RenderFrameHost* render_frame_host) {
    111   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    112   AddBrowserFilterIfNeeded();
    113   base::AutoLock locker(objects_lock_);
    114   auto iter = objects_.begin();
    115   while (iter != objects_.end()) {
    116     JavaObjectWeakGlobalRef ref =
    117         RemoveHolderAndAdvanceLocked(render_frame_host->GetRoutingID(), &iter);
    118     if (!ref.is_empty()) {
    119       RemoveFromRetainedObjectSetLocked(ref);
    120     }
    121   }
    122 }
    123 
    124 GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
    125     const base::android::JavaRef<jobject>& object,
    126     const base::android::JavaRef<jclass>& safe_annotation_clazz,
    127     bool is_named,
    128     int32 holder) {
    129   // Can be called on any thread. Calls come from the UI thread via
    130   // AddNamedObject, and from the background thread, when injected Java
    131   // object's method returns a Java object.
    132   DCHECK(is_named || holder);
    133   JNIEnv* env = base::android::AttachCurrentThread();
    134   JavaObjectWeakGlobalRef ref(env, object.obj());
    135   scoped_refptr<GinJavaBoundObject> new_object =
    136       is_named ? GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz)
    137                : GinJavaBoundObject::CreateTransient(ref, safe_annotation_clazz,
    138                                                      holder);
    139   // Note that we are abusing the fact that StaticAtomicSequenceNumber
    140   // uses Atomic32 as a counter, so it is guaranteed that it will not
    141   // overflow our int32 IDs. IDs start from 1.
    142   GinJavaBoundObject::ObjectID object_id = g_next_object_id.GetNext() + 1;
    143   {
    144     base::AutoLock locker(objects_lock_);
    145     objects_[object_id] = new_object;
    146   }
    147 #if DCHECK_IS_ON
    148   {
    149     GinJavaBoundObject::ObjectID added_object_id;
    150     DCHECK(FindObjectId(object, &added_object_id));
    151     DCHECK_EQ(object_id, added_object_id);
    152   }
    153 #endif  // DCHECK_IS_ON
    154   base::android::ScopedJavaLocalRef<jobject> retained_object_set =
    155         retained_object_set_.get(env);
    156   if (!retained_object_set.is_null()) {
    157     base::AutoLock locker(objects_lock_);
    158     JNI_Java_HashSet_add(env, retained_object_set, object);
    159   }
    160   return object_id;
    161 }
    162 
    163 bool GinJavaBridgeDispatcherHost::FindObjectId(
    164     const base::android::JavaRef<jobject>& object,
    165     GinJavaBoundObject::ObjectID* object_id) {
    166   // Can be called on any thread.
    167   JNIEnv* env = base::android::AttachCurrentThread();
    168   base::AutoLock locker(objects_lock_);
    169   for (const auto& pair : objects_) {
    170     if (env->IsSameObject(
    171             object.obj(),
    172             pair.second->GetLocalRef(env).obj())) {
    173       *object_id = pair.first;
    174       return true;
    175     }
    176   }
    177   return false;
    178 }
    179 
    180 JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef(
    181     GinJavaBoundObject::ObjectID object_id) {
    182   scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
    183   if (object.get())
    184     return object->GetWeakRef();
    185   else
    186     return JavaObjectWeakGlobalRef();
    187 }
    188 
    189 JavaObjectWeakGlobalRef
    190 GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked(
    191     int32 holder,
    192     ObjectMap::iterator* iter_ptr) {
    193   objects_lock_.AssertAcquired();
    194   JavaObjectWeakGlobalRef result;
    195   scoped_refptr<GinJavaBoundObject> object((*iter_ptr)->second);
    196   if (!object->IsNamed()) {
    197     object->RemoveHolder(holder);
    198     if (!object->HasHolders()) {
    199       result = object->GetWeakRef();
    200       objects_.erase((*iter_ptr)++);
    201     }
    202   } else {
    203     ++(*iter_ptr);
    204   }
    205   return result;
    206 }
    207 
    208 void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
    209     const JavaObjectWeakGlobalRef& ref) {
    210   objects_lock_.AssertAcquired();
    211   JNIEnv* env = base::android::AttachCurrentThread();
    212   base::android::ScopedJavaLocalRef<jobject> retained_object_set =
    213       retained_object_set_.get(env);
    214   if (!retained_object_set.is_null()) {
    215     JNI_Java_HashSet_remove(env, retained_object_set, ref.get(env));
    216   }
    217 }
    218 
    219 void GinJavaBridgeDispatcherHost::AddNamedObject(
    220     const std::string& name,
    221     const base::android::JavaRef<jobject>& object,
    222     const base::android::JavaRef<jclass>& safe_annotation_clazz) {
    223   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    224   GinJavaBoundObject::ObjectID object_id;
    225   NamedObjectMap::iterator iter = named_objects_.find(name);
    226   bool existing_object = FindObjectId(object, &object_id);
    227   if (existing_object && iter != named_objects_.end() &&
    228       iter->second == object_id) {
    229     // Nothing to do.
    230     return;
    231   }
    232   if (iter != named_objects_.end()) {
    233     RemoveNamedObject(iter->first);
    234   }
    235   if (existing_object) {
    236     base::AutoLock locker(objects_lock_);
    237     objects_[object_id]->AddName();
    238   } else {
    239     object_id = AddObject(object, safe_annotation_clazz, true, 0);
    240   }
    241   named_objects_[name] = object_id;
    242 
    243   AddBrowserFilterIfNeeded();
    244   web_contents()->SendToAllFrames(
    245       new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE, name, object_id));
    246 }
    247 
    248 void GinJavaBridgeDispatcherHost::RemoveNamedObject(
    249     const std::string& name) {
    250   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    251   NamedObjectMap::iterator iter = named_objects_.find(name);
    252   if (iter == named_objects_.end())
    253     return;
    254 
    255   // |name| may come from |named_objects_|. Make a copy of name so that if
    256   // |name| is from |named_objects_| it'll be valid after the remove below.
    257   const std::string copied_name(name);
    258 
    259   {
    260     base::AutoLock locker(objects_lock_);
    261     objects_[iter->second]->RemoveName();
    262   }
    263   named_objects_.erase(iter);
    264 
    265   // As the object isn't going to be removed from the JavaScript side until the
    266   // next page reload, calls to it must still work, thus we should continue to
    267   // hold it. All the transient objects and removed named objects will be purged
    268   // during the cleansing caused by DocumentAvailableInMainFrame event.
    269 
    270   web_contents()->SendToAllFrames(
    271       new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE, copied_name));
    272 }
    273 
    274 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) {
    275   if (!JavaBridgeThread::CurrentlyOn()) {
    276     g_background_thread.Get().message_loop()->task_runner()->PostTask(
    277         FROM_HERE,
    278         base::Bind(
    279             &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection,
    280             this, allow));
    281     return;
    282   }
    283   allow_object_contents_inspection_ = allow;
    284 }
    285 
    286 void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
    287   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    288   // Called when the window object has been cleared in the main frame.
    289   // That means, all sub-frames have also been cleared, so only named
    290   // objects survived.
    291   AddBrowserFilterIfNeeded();
    292   JNIEnv* env = base::android::AttachCurrentThread();
    293   base::android::ScopedJavaLocalRef<jobject> retained_object_set =
    294       retained_object_set_.get(env);
    295   base::AutoLock locker(objects_lock_);
    296   if (!retained_object_set.is_null()) {
    297     JNI_Java_HashSet_clear(env, retained_object_set);
    298   }
    299   auto iter = objects_.begin();
    300   while (iter != objects_.end()) {
    301     if (iter->second->IsNamed()) {
    302       if (!retained_object_set.is_null()) {
    303         JNI_Java_HashSet_add(
    304             env, retained_object_set, iter->second->GetLocalRef(env));
    305       }
    306       ++iter;
    307     } else {
    308       objects_.erase(iter++);
    309     }
    310   }
    311 }
    312 
    313 base::TaskRunner* GinJavaBridgeDispatcherHost::OverrideTaskRunnerForMessage(
    314     const IPC::Message& message) {
    315   GinJavaBoundObject::ObjectID object_id = 0;
    316   // TODO(mnaganov): It's very sad that we have a BrowserMessageFilter per
    317   // WebView instance. We should redesign to have a filter per RPH.
    318   // Check, if the object ID in the message is known to this host. If not,
    319   // this is a message for some other host. As all our IPC messages from the
    320   // renderer start with object ID, we just fetch it directly from the
    321   // message, considering sync and async messages separately.
    322   switch (message.type()) {
    323     case GinJavaBridgeHostMsg_GetMethods::ID:
    324     case GinJavaBridgeHostMsg_HasMethod::ID:
    325     case GinJavaBridgeHostMsg_InvokeMethod::ID: {
    326       DCHECK(message.is_sync());
    327       PickleIterator message_reader =
    328           IPC::SyncMessage::GetDataIterator(&message);
    329       if (!IPC::ReadParam(&message, &message_reader, &object_id))
    330         return NULL;
    331       break;
    332     }
    333     case GinJavaBridgeHostMsg_ObjectWrapperDeleted::ID: {
    334       DCHECK(!message.is_sync());
    335       PickleIterator message_reader(message);
    336       if (!IPC::ReadParam(&message, &message_reader, &object_id))
    337         return NULL;
    338       break;
    339     }
    340     default:
    341       NOTREACHED();
    342       return NULL;
    343   }
    344   {
    345     base::AutoLock locker(objects_lock_);
    346     if (objects_.find(object_id) != objects_.end()) {
    347         return g_background_thread.Get().message_loop()->task_runner().get();
    348     }
    349   }
    350   return NULL;
    351 }
    352 
    353 bool GinJavaBridgeDispatcherHost::OnMessageReceived(
    354     const IPC::Message& message) {
    355   // We can get here As WebContentsObserver also has OnMessageReceived,
    356   // or because we have not provided a task runner in
    357   // OverrideTaskRunnerForMessage. In either case, just bail out.
    358   if (!JavaBridgeThread::CurrentlyOn())
    359     return false;
    360   SetCurrentRoutingID(message.routing_id());
    361   bool handled = true;
    362   IPC_BEGIN_MESSAGE_MAP(GinJavaBridgeDispatcherHost, message)
    363     IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_GetMethods, OnGetMethods)
    364     IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_HasMethod, OnHasMethod)
    365     IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_InvokeMethod, OnInvokeMethod)
    366     IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted,
    367                         OnObjectWrapperDeleted)
    368     IPC_MESSAGE_UNHANDLED(handled = false)
    369   IPC_END_MESSAGE_MAP()
    370   SetCurrentRoutingID(MSG_ROUTING_NONE);
    371   return handled;
    372 }
    373 
    374 void GinJavaBridgeDispatcherHost::OnDestruct() const {
    375   if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    376     delete this;
    377   } else {
    378     BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
    379   }
    380 }
    381 
    382 int GinJavaBridgeDispatcherHost::GetCurrentRoutingID() const {
    383   DCHECK(JavaBridgeThread::CurrentlyOn());
    384   return current_routing_id_;
    385 }
    386 
    387 void GinJavaBridgeDispatcherHost::SetCurrentRoutingID(int32 routing_id) {
    388   DCHECK(JavaBridgeThread::CurrentlyOn());
    389   current_routing_id_ = routing_id;
    390 }
    391 
    392 scoped_refptr<GinJavaBoundObject> GinJavaBridgeDispatcherHost::FindObject(
    393     GinJavaBoundObject::ObjectID object_id) {
    394   // Can be called on any thread.
    395   base::AutoLock locker(objects_lock_);
    396   auto iter = objects_.find(object_id);
    397   if (iter != objects_.end())
    398     return iter->second;
    399   return NULL;
    400 }
    401 
    402 void GinJavaBridgeDispatcherHost::OnGetMethods(
    403     GinJavaBoundObject::ObjectID object_id,
    404     std::set<std::string>* returned_method_names) {
    405   DCHECK(JavaBridgeThread::CurrentlyOn());
    406   if (!allow_object_contents_inspection_)
    407     return;
    408   scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
    409   if (object.get()) {
    410     *returned_method_names = object->GetMethodNames();
    411   } else {
    412     LOG(ERROR) << "WebView: Unknown object: " << object_id;
    413   }
    414 }
    415 
    416 void GinJavaBridgeDispatcherHost::OnHasMethod(
    417     GinJavaBoundObject::ObjectID object_id,
    418     const std::string& method_name,
    419     bool* result) {
    420   DCHECK(JavaBridgeThread::CurrentlyOn());
    421   scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
    422   if (object.get()) {
    423     *result = object->HasMethod(method_name);
    424   } else {
    425     LOG(ERROR) << "WebView: Unknown object: " << object_id;
    426   }
    427 }
    428 
    429 void GinJavaBridgeDispatcherHost::OnInvokeMethod(
    430     GinJavaBoundObject::ObjectID object_id,
    431     const std::string& method_name,
    432     const base::ListValue& arguments,
    433     base::ListValue* wrapped_result,
    434     content::GinJavaBridgeError* error_code) {
    435   DCHECK(JavaBridgeThread::CurrentlyOn());
    436   DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE);
    437   scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
    438   if (!object.get()) {
    439     LOG(ERROR) << "WebView: Unknown object: " << object_id;
    440     wrapped_result->Append(base::Value::CreateNullValue());
    441     *error_code = kGinJavaBridgeUnknownObjectId;
    442     return;
    443   }
    444   scoped_refptr<GinJavaMethodInvocationHelper> result =
    445       new GinJavaMethodInvocationHelper(
    446           make_scoped_ptr(new GinJavaBoundObjectDelegate(object))
    447               .PassAs<GinJavaMethodInvocationHelper::ObjectDelegate>(),
    448           method_name,
    449           arguments);
    450   result->Init(this);
    451   result->Invoke();
    452   *error_code = result->GetInvocationError();
    453   if (result->HoldsPrimitiveResult()) {
    454     scoped_ptr<base::ListValue> result_copy(
    455         result->GetPrimitiveResult().DeepCopy());
    456     wrapped_result->Swap(result_copy.get());
    457   } else if (!result->GetObjectResult().is_null()) {
    458     GinJavaBoundObject::ObjectID returned_object_id;
    459     if (FindObjectId(result->GetObjectResult(), &returned_object_id)) {
    460       base::AutoLock locker(objects_lock_);
    461       objects_[returned_object_id]->AddHolder(GetCurrentRoutingID());
    462     } else {
    463       returned_object_id = AddObject(result->GetObjectResult(),
    464                                      result->GetSafeAnnotationClass(),
    465                                      false,
    466                                      GetCurrentRoutingID());
    467     }
    468     wrapped_result->Append(
    469         GinJavaBridgeValue::CreateObjectIDValue(
    470             returned_object_id).release());
    471   } else {
    472     wrapped_result->Append(base::Value::CreateNullValue());
    473   }
    474 }
    475 
    476 void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
    477     GinJavaBoundObject::ObjectID object_id) {
    478   DCHECK(JavaBridgeThread::CurrentlyOn());
    479   DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE);
    480   base::AutoLock locker(objects_lock_);
    481   auto iter = objects_.find(object_id);
    482   if (iter == objects_.end())
    483     return;
    484   JavaObjectWeakGlobalRef ref =
    485       RemoveHolderAndAdvanceLocked(GetCurrentRoutingID(), &iter);
    486   if (!ref.is_empty()) {
    487     RemoveFromRetainedObjectSetLocked(ref);
    488   }
    489 }
    490 
    491 }  // namespace content
    492