gin_java_bridge_dispatcher_host.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/lazy_instance.h" 11#include "base/strings/string_number_conversions.h" 12#include "base/strings/utf_string_conversions.h" 13#include "base/task_runner_util.h" 14#include "content/browser/android/java/gin_java_bound_object_delegate.h" 15#include "content/browser/android/java/jni_helper.h" 16#include "content/common/android/gin_java_bridge_value.h" 17#include "content/common/android/hash_set.h" 18#include "content/common/gin_java_bridge_messages.h" 19#include "content/public/browser/browser_thread.h" 20#include "content/public/browser/render_frame_host.h" 21#include "content/public/browser/web_contents.h" 22#include "ipc/ipc_message_utils.h" 23 24#if !defined(OS_ANDROID) 25#error "JavaBridge only supports OS_ANDROID" 26#endif 27 28namespace content { 29 30namespace { 31// The JavaBridge needs to use a Java thread so the callback 32// will happen on a thread with a prepared Looper. 33class JavaBridgeThread : public base::android::JavaHandlerThread { 34 public: 35 JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") { 36 Start(); 37 } 38 virtual ~JavaBridgeThread() { 39 Stop(); 40 } 41}; 42 43base::LazyInstance<JavaBridgeThread> g_background_thread = 44 LAZY_INSTANCE_INITIALIZER; 45 46} // namespace 47 48GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost( 49 WebContents* web_contents, 50 jobject retained_object_set) 51 : WebContentsObserver(web_contents), 52 retained_object_set_(base::android::AttachCurrentThread(), 53 retained_object_set), 54 allow_object_contents_inspection_(true) { 55 DCHECK(retained_object_set); 56} 57 58GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() { 59 DCHECK(pending_replies_.empty()); 60} 61 62void GinJavaBridgeDispatcherHost::RenderFrameCreated( 63 RenderFrameHost* render_frame_host) { 64 for (NamedObjectMap::const_iterator iter = named_objects_.begin(); 65 iter != named_objects_.end(); 66 ++iter) { 67 render_frame_host->Send(new GinJavaBridgeMsg_AddNamedObject( 68 render_frame_host->GetRoutingID(), iter->first, iter->second)); 69 } 70} 71 72void GinJavaBridgeDispatcherHost::RenderFrameDeleted( 73 RenderFrameHost* render_frame_host) { 74 DCHECK_CURRENTLY_ON(BrowserThread::UI); 75 IPC::Message* reply_msg = TakePendingReply(render_frame_host); 76 if (reply_msg != NULL) { 77 base::ListValue result; 78 result.Append(base::Value::CreateNullValue()); 79 IPC::WriteParam(reply_msg, result); 80 IPC::WriteParam(reply_msg, kGinJavaBridgeRenderFrameDeleted); 81 render_frame_host->Send(reply_msg); 82 } 83 RemoveHolder(render_frame_host, 84 GinJavaBoundObject::ObjectMap::iterator(&objects_), 85 objects_.size()); 86} 87 88GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject( 89 const base::android::JavaRef<jobject>& object, 90 const base::android::JavaRef<jclass>& safe_annotation_clazz, 91 bool is_named, 92 RenderFrameHost* holder) { 93 DCHECK(is_named || holder); 94 GinJavaBoundObject::ObjectID object_id; 95 JNIEnv* env = base::android::AttachCurrentThread(); 96 JavaObjectWeakGlobalRef ref(env, object.obj()); 97 if (is_named) { 98 object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>( 99 GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz))); 100 } else { 101 object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>( 102 GinJavaBoundObject::CreateTransient( 103 ref, safe_annotation_clazz, holder))); 104 } 105#if DCHECK_IS_ON 106 { 107 GinJavaBoundObject::ObjectID added_object_id; 108 DCHECK(FindObjectId(object, &added_object_id)); 109 DCHECK_EQ(object_id, added_object_id); 110 } 111#endif // DCHECK_IS_ON 112 base::android::ScopedJavaLocalRef<jobject> retained_object_set = 113 retained_object_set_.get(env); 114 if (!retained_object_set.is_null()) { 115 JNI_Java_HashSet_add(env, retained_object_set, object); 116 } 117 return object_id; 118} 119 120bool GinJavaBridgeDispatcherHost::FindObjectId( 121 const base::android::JavaRef<jobject>& object, 122 GinJavaBoundObject::ObjectID* object_id) { 123 JNIEnv* env = base::android::AttachCurrentThread(); 124 for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd(); 125 it.Advance()) { 126 if (env->IsSameObject( 127 object.obj(), 128 it.GetCurrentValue()->get()->GetLocalRef(env).obj())) { 129 *object_id = it.GetCurrentKey(); 130 return true; 131 } 132 } 133 return false; 134} 135 136JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef( 137 GinJavaBoundObject::ObjectID object_id) { 138 scoped_refptr<GinJavaBoundObject>* result = objects_.Lookup(object_id); 139 scoped_refptr<GinJavaBoundObject> object(result ? *result : NULL); 140 if (object.get()) 141 return object->GetWeakRef(); 142 else 143 return JavaObjectWeakGlobalRef(); 144} 145 146void GinJavaBridgeDispatcherHost::RemoveHolder( 147 RenderFrameHost* holder, 148 const GinJavaBoundObject::ObjectMap::iterator& from, 149 size_t count) { 150 JNIEnv* env = base::android::AttachCurrentThread(); 151 base::android::ScopedJavaLocalRef<jobject> retained_object_set = 152 retained_object_set_.get(env); 153 size_t i = 0; 154 for (GinJavaBoundObject::ObjectMap::iterator it(from); 155 !it.IsAtEnd() && i < count; 156 it.Advance(), ++i) { 157 scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue()); 158 if (object->IsNamed()) 159 continue; 160 object->RemoveHolder(holder); 161 if (!object->HasHolders()) { 162 if (!retained_object_set.is_null()) { 163 JNI_Java_HashSet_remove( 164 env, retained_object_set, object->GetLocalRef(env)); 165 } 166 objects_.Remove(it.GetCurrentKey()); 167 } 168 } 169} 170 171void GinJavaBridgeDispatcherHost::AddNamedObject( 172 const std::string& name, 173 const base::android::JavaRef<jobject>& object, 174 const base::android::JavaRef<jclass>& safe_annotation_clazz) { 175 DCHECK_CURRENTLY_ON(BrowserThread::UI); 176 GinJavaBoundObject::ObjectID object_id; 177 NamedObjectMap::iterator iter = named_objects_.find(name); 178 bool existing_object = FindObjectId(object, &object_id); 179 if (existing_object && iter != named_objects_.end() && 180 iter->second == object_id) { 181 // Nothing to do. 182 return; 183 } 184 if (iter != named_objects_.end()) { 185 RemoveNamedObject(iter->first); 186 } 187 if (existing_object) { 188 (*objects_.Lookup(object_id))->AddName(); 189 } else { 190 object_id = AddObject(object, safe_annotation_clazz, true, NULL); 191 } 192 named_objects_[name] = object_id; 193 194 web_contents()->SendToAllFrames( 195 new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE, name, object_id)); 196} 197 198void GinJavaBridgeDispatcherHost::RemoveNamedObject( 199 const std::string& name) { 200 DCHECK_CURRENTLY_ON(BrowserThread::UI); 201 NamedObjectMap::iterator iter = named_objects_.find(name); 202 if (iter == named_objects_.end()) 203 return; 204 205 // |name| may come from |named_objects_|. Make a copy of name so that if 206 // |name| is from |named_objects_| it'll be valid after the remove below. 207 const std::string copied_name(name); 208 209 scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(iter->second)); 210 named_objects_.erase(iter); 211 object->RemoveName(); 212 213 // Not erasing from the objects map, as we can still receive method 214 // invocation requests for this object, and they should work until the 215 // java object is gone. 216 if (!object->IsNamed()) { 217 JNIEnv* env = base::android::AttachCurrentThread(); 218 base::android::ScopedJavaLocalRef<jobject> retained_object_set = 219 retained_object_set_.get(env); 220 if (!retained_object_set.is_null()) { 221 JNI_Java_HashSet_remove( 222 env, retained_object_set, object->GetLocalRef(env)); 223 } 224 } 225 226 web_contents()->SendToAllFrames( 227 new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE, copied_name)); 228} 229 230void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) { 231 allow_object_contents_inspection_ = allow; 232} 233 234void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() { 235 DCHECK_CURRENTLY_ON(BrowserThread::UI); 236 // Called when the window object has been cleared in the main frame. 237 // That means, all sub-frames have also been cleared, so only named 238 // objects survived. 239 JNIEnv* env = base::android::AttachCurrentThread(); 240 base::android::ScopedJavaLocalRef<jobject> retained_object_set = 241 retained_object_set_.get(env); 242 if (!retained_object_set.is_null()) { 243 JNI_Java_HashSet_clear(env, retained_object_set); 244 } 245 246 // We also need to add back the named objects we have so far as they 247 // should survive navigations. 248 for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd(); 249 it.Advance()) { 250 scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue()); 251 if (object->IsNamed()) { 252 if (!retained_object_set.is_null()) { 253 JNI_Java_HashSet_add( 254 env, retained_object_set, object->GetLocalRef(env)); 255 } 256 } else { 257 objects_.Remove(it.GetCurrentKey()); 258 } 259 } 260} 261 262namespace { 263 264// TODO(mnaganov): Implement passing of a parameter into sync message handlers. 265class MessageForwarder : public IPC::Sender { 266 public: 267 MessageForwarder(GinJavaBridgeDispatcherHost* gjbdh, 268 RenderFrameHost* render_frame_host) 269 : gjbdh_(gjbdh), render_frame_host_(render_frame_host) {} 270 void OnGetMethods(GinJavaBoundObject::ObjectID object_id, 271 IPC::Message* reply_msg) { 272 gjbdh_->OnGetMethods(render_frame_host_, 273 object_id, 274 reply_msg); 275 } 276 void OnHasMethod(GinJavaBoundObject::ObjectID object_id, 277 const std::string& method_name, 278 IPC::Message* reply_msg) { 279 gjbdh_->OnHasMethod(render_frame_host_, 280 object_id, 281 method_name, 282 reply_msg); 283 } 284 void OnInvokeMethod(GinJavaBoundObject::ObjectID object_id, 285 const std::string& method_name, 286 const base::ListValue& arguments, 287 IPC::Message* reply_msg) { 288 gjbdh_->OnInvokeMethod(render_frame_host_, 289 object_id, 290 method_name, 291 arguments, 292 reply_msg); 293 } 294 virtual bool Send(IPC::Message* msg) OVERRIDE { 295 NOTREACHED(); 296 return false; 297 } 298 private: 299 GinJavaBridgeDispatcherHost* gjbdh_; 300 RenderFrameHost* render_frame_host_; 301}; 302 303} 304 305bool GinJavaBridgeDispatcherHost::OnMessageReceived( 306 const IPC::Message& message, 307 RenderFrameHost* render_frame_host) { 308 DCHECK(render_frame_host); 309 bool handled = true; 310 MessageForwarder forwarder(this, render_frame_host); 311 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(GinJavaBridgeDispatcherHost, message, 312 render_frame_host) 313 IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_GetMethods, 314 &forwarder, 315 MessageForwarder::OnGetMethods) 316 IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_HasMethod, 317 &forwarder, 318 MessageForwarder::OnHasMethod) 319 IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_InvokeMethod, 320 &forwarder, 321 MessageForwarder::OnInvokeMethod) 322 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted, 323 OnObjectWrapperDeleted) 324 IPC_MESSAGE_UNHANDLED(handled = false) 325 IPC_END_MESSAGE_MAP() 326 return handled; 327} 328 329namespace { 330 331class IsValidRenderFrameHostHelper 332 : public base::RefCounted<IsValidRenderFrameHostHelper> { 333 public: 334 explicit IsValidRenderFrameHostHelper(RenderFrameHost* rfh_to_match) 335 : rfh_to_match_(rfh_to_match), rfh_found_(false) {} 336 337 bool rfh_found() { return rfh_found_; } 338 339 void OnFrame(RenderFrameHost* rfh) { 340 if (rfh_to_match_ == rfh) rfh_found_ = true; 341 } 342 343 private: 344 friend class base::RefCounted<IsValidRenderFrameHostHelper>; 345 346 ~IsValidRenderFrameHostHelper() {} 347 348 RenderFrameHost* rfh_to_match_; 349 bool rfh_found_; 350 351 DISALLOW_COPY_AND_ASSIGN(IsValidRenderFrameHostHelper); 352}; 353 354} // namespace 355 356bool GinJavaBridgeDispatcherHost::IsValidRenderFrameHost( 357 RenderFrameHost* render_frame_host) { 358 scoped_refptr<IsValidRenderFrameHostHelper> helper = 359 new IsValidRenderFrameHostHelper(render_frame_host); 360 web_contents()->ForEachFrame( 361 base::Bind(&IsValidRenderFrameHostHelper::OnFrame, helper)); 362 return helper->rfh_found(); 363} 364 365void GinJavaBridgeDispatcherHost::OnGetMethods( 366 RenderFrameHost* render_frame_host, 367 GinJavaBoundObject::ObjectID object_id, 368 IPC::Message* reply_msg) { 369 DCHECK_CURRENTLY_ON(BrowserThread::UI); 370 DCHECK(render_frame_host); 371 if (!allow_object_contents_inspection_) { 372 IPC::WriteParam(reply_msg, std::set<std::string>()); 373 render_frame_host->Send(reply_msg); 374 return; 375 } 376 scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id)); 377 if (!object) { 378 LOG(ERROR) << "WebView: Unknown object: " << object_id; 379 IPC::WriteParam(reply_msg, std::set<std::string>()); 380 render_frame_host->Send(reply_msg); 381 return; 382 } 383 DCHECK(!HasPendingReply(render_frame_host)); 384 pending_replies_[render_frame_host] = reply_msg; 385 base::PostTaskAndReplyWithResult( 386 g_background_thread.Get().message_loop()->message_loop_proxy(), 387 FROM_HERE, 388 base::Bind(&GinJavaBoundObject::GetMethodNames, object), 389 base::Bind(&GinJavaBridgeDispatcherHost::SendMethods, 390 AsWeakPtr(), 391 render_frame_host)); 392} 393 394void GinJavaBridgeDispatcherHost::SendMethods( 395 RenderFrameHost* render_frame_host, 396 const std::set<std::string>& method_names) { 397 IPC::Message* reply_msg = TakePendingReply(render_frame_host); 398 if (!reply_msg) { 399 return; 400 } 401 IPC::WriteParam(reply_msg, method_names); 402 render_frame_host->Send(reply_msg); 403} 404 405void GinJavaBridgeDispatcherHost::OnHasMethod( 406 RenderFrameHost* render_frame_host, 407 GinJavaBoundObject::ObjectID object_id, 408 const std::string& method_name, 409 IPC::Message* reply_msg) { 410 DCHECK_CURRENTLY_ON(BrowserThread::UI); 411 DCHECK(render_frame_host); 412 scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id)); 413 if (!object) { 414 LOG(ERROR) << "WebView: Unknown object: " << object_id; 415 IPC::WriteParam(reply_msg, false); 416 render_frame_host->Send(reply_msg); 417 return; 418 } 419 DCHECK(!HasPendingReply(render_frame_host)); 420 pending_replies_[render_frame_host] = reply_msg; 421 base::PostTaskAndReplyWithResult( 422 g_background_thread.Get().message_loop()->message_loop_proxy(), 423 FROM_HERE, 424 base::Bind(&GinJavaBoundObject::HasMethod, object, method_name), 425 base::Bind(&GinJavaBridgeDispatcherHost::SendHasMethodReply, 426 AsWeakPtr(), 427 render_frame_host)); 428} 429 430void GinJavaBridgeDispatcherHost::SendHasMethodReply( 431 RenderFrameHost* render_frame_host, 432 bool result) { 433 IPC::Message* reply_msg = TakePendingReply(render_frame_host); 434 if (!reply_msg) { 435 return; 436 } 437 IPC::WriteParam(reply_msg, result); 438 render_frame_host->Send(reply_msg); 439} 440 441void GinJavaBridgeDispatcherHost::OnInvokeMethod( 442 RenderFrameHost* render_frame_host, 443 GinJavaBoundObject::ObjectID object_id, 444 const std::string& method_name, 445 const base::ListValue& arguments, 446 IPC::Message* reply_msg) { 447 DCHECK_CURRENTLY_ON(BrowserThread::UI); 448 DCHECK(render_frame_host); 449 scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id)); 450 if (!object) { 451 LOG(ERROR) << "WebView: Unknown object: " << object_id; 452 base::ListValue result; 453 result.Append(base::Value::CreateNullValue()); 454 IPC::WriteParam(reply_msg, result); 455 IPC::WriteParam(reply_msg, kGinJavaBridgeUnknownObjectId); 456 render_frame_host->Send(reply_msg); 457 return; 458 } 459 DCHECK(!HasPendingReply(render_frame_host)); 460 pending_replies_[render_frame_host] = reply_msg; 461 scoped_refptr<GinJavaMethodInvocationHelper> result = 462 new GinJavaMethodInvocationHelper( 463 make_scoped_ptr(new GinJavaBoundObjectDelegate(object)) 464 .PassAs<GinJavaMethodInvocationHelper::ObjectDelegate>(), 465 method_name, 466 arguments); 467 result->Init(this); 468 g_background_thread.Get() 469 .message_loop() 470 ->message_loop_proxy() 471 ->PostTaskAndReply( 472 FROM_HERE, 473 base::Bind(&GinJavaMethodInvocationHelper::Invoke, result), 474 base::Bind( 475 &GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult, 476 AsWeakPtr(), 477 render_frame_host, 478 result)); 479} 480 481void GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult( 482 RenderFrameHost* render_frame_host, 483 scoped_refptr<GinJavaMethodInvocationHelper> result) { 484 if (result->HoldsPrimitiveResult()) { 485 IPC::Message* reply_msg = TakePendingReply(render_frame_host); 486 if (!reply_msg) { 487 return; 488 } 489 IPC::WriteParam(reply_msg, result->GetPrimitiveResult()); 490 IPC::WriteParam(reply_msg, result->GetInvocationError()); 491 render_frame_host->Send(reply_msg); 492 } else { 493 ProcessMethodInvocationObjectResult(render_frame_host, result); 494 } 495} 496 497void GinJavaBridgeDispatcherHost::ProcessMethodInvocationObjectResult( 498 RenderFrameHost* render_frame_host, 499 scoped_refptr<GinJavaMethodInvocationHelper> result) { 500 DCHECK_CURRENTLY_ON(BrowserThread::UI); 501 502 if (!IsValidRenderFrameHost(render_frame_host)) { 503 // In this case, we must've already sent the reply when the render frame 504 // was destroyed. 505 DCHECK(!HasPendingReply(render_frame_host)); 506 return; 507 } 508 509 base::ListValue wrapped_result; 510 if (!result->GetObjectResult().is_null()) { 511 GinJavaBoundObject::ObjectID returned_object_id; 512 if (FindObjectId(result->GetObjectResult(), &returned_object_id)) { 513 (*objects_.Lookup(returned_object_id))->AddHolder(render_frame_host); 514 } else { 515 returned_object_id = AddObject(result->GetObjectResult(), 516 result->GetSafeAnnotationClass(), 517 false, 518 render_frame_host); 519 } 520 wrapped_result.Append( 521 GinJavaBridgeValue::CreateObjectIDValue( 522 returned_object_id).release()); 523 } else { 524 wrapped_result.Append(base::Value::CreateNullValue()); 525 } 526 IPC::Message* reply_msg = TakePendingReply(render_frame_host); 527 if (!reply_msg) { 528 return; 529 } 530 IPC::WriteParam(reply_msg, wrapped_result); 531 IPC::WriteParam(reply_msg, result->GetInvocationError()); 532 render_frame_host->Send(reply_msg); 533} 534 535void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted( 536 RenderFrameHost* render_frame_host, 537 GinJavaBoundObject::ObjectID object_id) { 538 DCHECK_CURRENTLY_ON(BrowserThread::UI); 539 DCHECK(render_frame_host); 540 if (objects_.Lookup(object_id)) { 541 GinJavaBoundObject::ObjectMap::iterator iter(&objects_); 542 while (!iter.IsAtEnd() && iter.GetCurrentKey() != object_id) 543 iter.Advance(); 544 DCHECK(!iter.IsAtEnd()); 545 RemoveHolder(render_frame_host, iter, 1); 546 } 547} 548 549IPC::Message* GinJavaBridgeDispatcherHost::TakePendingReply( 550 RenderFrameHost* render_frame_host) { 551 if (!IsValidRenderFrameHost(render_frame_host)) { 552 DCHECK(!HasPendingReply(render_frame_host)); 553 return NULL; 554 } 555 556 PendingReplyMap::iterator it = pending_replies_.find(render_frame_host); 557 // There may be no pending reply if we're called from RenderFrameDeleted and 558 // we already sent the reply through the regular route. 559 if (it == pending_replies_.end()) { 560 return NULL; 561 } 562 563 IPC::Message* reply_msg = it->second; 564 pending_replies_.erase(it); 565 return reply_msg; 566} 567 568bool GinJavaBridgeDispatcherHost::HasPendingReply( 569 RenderFrameHost* render_frame_host) const { 570 return pending_replies_.find(render_frame_host) != pending_replies_.end(); 571} 572 573} // namespace content 574