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