chrome/browser/extensions: Remove use of MessageLoopProxy and deprecated MessageLoop...
[chromium-blink-merge.git] / content / browser / android / java / gin_java_bridge_dispatcher_host.cc
blob69a08b431b69ef454be5b87c84e8acd97b8ec256
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.
5 #include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"
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"
27 #if !defined(OS_ANDROID)
28 #error "JavaBridge only supports OS_ANDROID"
29 #endif
31 namespace content {
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();
41 ~JavaBridgeThread() override { Stop(); }
42 static bool CurrentlyOn();
45 base::LazyInstance<JavaBridgeThread> g_background_thread =
46 LAZY_INSTANCE_INITIALIZER;
48 // static
49 bool JavaBridgeThread::CurrentlyOn() {
50 return base::MessageLoop::current() ==
51 g_background_thread.Get().message_loop();
54 // Object IDs are globally unique, so we can figure out the right
55 // GinJavaBridgeDispatcherHost when dispatching messages on the background
56 // thread.
57 base::StaticAtomicSequenceNumber g_next_object_id;
59 } // namespace
61 GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
62 WebContents* web_contents,
63 jobject retained_object_set)
64 : WebContentsObserver(web_contents),
65 BrowserMessageFilter(GinJavaBridgeMsgStart),
66 browser_filter_added_(false),
67 retained_object_set_(base::android::AttachCurrentThread(),
68 retained_object_set),
69 allow_object_contents_inspection_(true),
70 current_routing_id_(MSG_ROUTING_NONE) {
71 DCHECK(retained_object_set);
74 GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
77 // GinJavaBridgeDispatcherHost gets created earlier than RenderProcessHost
78 // is initialized. So we postpone installing the message filter until we know
79 // that the RPH is in a good shape. Currently this means that we are calling
80 // this function from any UI thread function that is about to communicate
81 // with the renderer.
82 // TODO(mnaganov): Redesign, so we only have a single filter for all hosts.
83 void GinJavaBridgeDispatcherHost::AddBrowserFilterIfNeeded() {
84 DCHECK_CURRENTLY_ON(BrowserThread::UI);
85 // Transient objects can only appear after named objects were added. Thus,
86 // we can wait until we have one, to avoid installing unnecessary filters.
87 if (!browser_filter_added_ &&
88 web_contents()->GetRenderProcessHost()->GetChannel() &&
89 !named_objects_.empty()) {
90 web_contents()->GetRenderProcessHost()->AddFilter(this);
91 browser_filter_added_ = true;
95 void GinJavaBridgeDispatcherHost::RenderFrameCreated(
96 RenderFrameHost* render_frame_host) {
97 DCHECK_CURRENTLY_ON(BrowserThread::UI);
98 AddBrowserFilterIfNeeded();
99 for (NamedObjectMap::const_iterator iter = named_objects_.begin();
100 iter != named_objects_.end();
101 ++iter) {
102 render_frame_host->Send(new GinJavaBridgeMsg_AddNamedObject(
103 render_frame_host->GetRoutingID(), iter->first, iter->second));
107 void GinJavaBridgeDispatcherHost::RenderFrameDeleted(
108 RenderFrameHost* render_frame_host) {
109 DCHECK_CURRENTLY_ON(BrowserThread::UI);
110 AddBrowserFilterIfNeeded();
111 base::AutoLock locker(objects_lock_);
112 auto iter = objects_.begin();
113 while (iter != objects_.end()) {
114 JavaObjectWeakGlobalRef ref =
115 RemoveHolderAndAdvanceLocked(render_frame_host->GetRoutingID(), &iter);
116 if (!ref.is_empty()) {
117 RemoveFromRetainedObjectSetLocked(ref);
122 GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
123 const base::android::JavaRef<jobject>& object,
124 const base::android::JavaRef<jclass>& safe_annotation_clazz,
125 bool is_named,
126 int32 holder) {
127 // Can be called on any thread. Calls come from the UI thread via
128 // AddNamedObject, and from the background thread, when injected Java
129 // object's method returns a Java object.
130 DCHECK(is_named || holder);
131 JNIEnv* env = base::android::AttachCurrentThread();
132 JavaObjectWeakGlobalRef ref(env, object.obj());
133 scoped_refptr<GinJavaBoundObject> new_object =
134 is_named ? GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz)
135 : GinJavaBoundObject::CreateTransient(ref, safe_annotation_clazz,
136 holder);
137 // Note that we are abusing the fact that StaticAtomicSequenceNumber
138 // uses Atomic32 as a counter, so it is guaranteed that it will not
139 // overflow our int32 IDs. IDs start from 1.
140 GinJavaBoundObject::ObjectID object_id = g_next_object_id.GetNext() + 1;
142 base::AutoLock locker(objects_lock_);
143 objects_[object_id] = new_object;
145 #if DCHECK_IS_ON()
147 GinJavaBoundObject::ObjectID added_object_id;
148 DCHECK(FindObjectId(object, &added_object_id));
149 DCHECK_EQ(object_id, added_object_id);
151 #endif // DCHECK_IS_ON()
152 base::android::ScopedJavaLocalRef<jobject> retained_object_set =
153 retained_object_set_.get(env);
154 if (!retained_object_set.is_null()) {
155 base::AutoLock locker(objects_lock_);
156 JNI_Java_HashSet_add(env, retained_object_set, object);
158 return object_id;
161 bool GinJavaBridgeDispatcherHost::FindObjectId(
162 const base::android::JavaRef<jobject>& object,
163 GinJavaBoundObject::ObjectID* object_id) {
164 // Can be called on any thread.
165 JNIEnv* env = base::android::AttachCurrentThread();
166 base::AutoLock locker(objects_lock_);
167 for (const auto& pair : objects_) {
168 if (env->IsSameObject(
169 object.obj(),
170 pair.second->GetLocalRef(env).obj())) {
171 *object_id = pair.first;
172 return true;
175 return false;
178 JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef(
179 GinJavaBoundObject::ObjectID object_id) {
180 scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
181 if (object.get())
182 return object->GetWeakRef();
183 else
184 return JavaObjectWeakGlobalRef();
187 JavaObjectWeakGlobalRef
188 GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked(
189 int32 holder,
190 ObjectMap::iterator* iter_ptr) {
191 objects_lock_.AssertAcquired();
192 JavaObjectWeakGlobalRef result;
193 scoped_refptr<GinJavaBoundObject> object((*iter_ptr)->second);
194 bool object_erased = false;
195 if (!object->IsNamed()) {
196 object->RemoveHolder(holder);
197 if (!object->HasHolders()) {
198 result = object->GetWeakRef();
199 objects_.erase((*iter_ptr)++);
200 object_erased = true;
203 if (!object_erased) {
204 ++(*iter_ptr);
206 return result;
209 void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
210 const JavaObjectWeakGlobalRef& ref) {
211 objects_lock_.AssertAcquired();
212 JNIEnv* env = base::android::AttachCurrentThread();
213 base::android::ScopedJavaLocalRef<jobject> retained_object_set =
214 retained_object_set_.get(env);
215 if (!retained_object_set.is_null()) {
216 JNI_Java_HashSet_remove(env, retained_object_set, ref.get(env));
220 void GinJavaBridgeDispatcherHost::AddNamedObject(
221 const std::string& name,
222 const base::android::JavaRef<jobject>& object,
223 const base::android::JavaRef<jclass>& safe_annotation_clazz) {
224 DCHECK_CURRENTLY_ON(BrowserThread::UI);
225 GinJavaBoundObject::ObjectID object_id;
226 NamedObjectMap::iterator iter = named_objects_.find(name);
227 bool existing_object = FindObjectId(object, &object_id);
228 if (existing_object && iter != named_objects_.end() &&
229 iter->second == object_id) {
230 // Nothing to do.
231 return;
233 if (iter != named_objects_.end()) {
234 RemoveNamedObject(iter->first);
236 if (existing_object) {
237 base::AutoLock locker(objects_lock_);
238 objects_[object_id]->AddName();
239 } else {
240 object_id = AddObject(object, safe_annotation_clazz, true, 0);
242 named_objects_[name] = object_id;
244 AddBrowserFilterIfNeeded();
245 web_contents()->SendToAllFrames(
246 new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE, name, object_id));
249 void GinJavaBridgeDispatcherHost::RemoveNamedObject(
250 const std::string& name) {
251 DCHECK_CURRENTLY_ON(BrowserThread::UI);
252 NamedObjectMap::iterator iter = named_objects_.find(name);
253 if (iter == named_objects_.end())
254 return;
256 // |name| may come from |named_objects_|. Make a copy of name so that if
257 // |name| is from |named_objects_| it'll be valid after the remove below.
258 const std::string copied_name(name);
261 base::AutoLock locker(objects_lock_);
262 objects_[iter->second]->RemoveName();
264 named_objects_.erase(iter);
266 // As the object isn't going to be removed from the JavaScript side until the
267 // next page reload, calls to it must still work, thus we should continue to
268 // hold it. All the transient objects and removed named objects will be purged
269 // during the cleansing caused by DocumentAvailableInMainFrame event.
271 web_contents()->SendToAllFrames(
272 new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE, copied_name));
275 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) {
276 if (!JavaBridgeThread::CurrentlyOn()) {
277 g_background_thread.Get().message_loop()->task_runner()->PostTask(
278 FROM_HERE,
279 base::Bind(
280 &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection,
281 this, allow));
282 return;
284 allow_object_contents_inspection_ = allow;
287 void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
288 DCHECK_CURRENTLY_ON(BrowserThread::UI);
289 // Called when the window object has been cleared in the main frame.
290 // That means, all sub-frames have also been cleared, so only named
291 // objects survived.
292 AddBrowserFilterIfNeeded();
293 JNIEnv* env = base::android::AttachCurrentThread();
294 base::android::ScopedJavaLocalRef<jobject> retained_object_set =
295 retained_object_set_.get(env);
296 base::AutoLock locker(objects_lock_);
297 if (!retained_object_set.is_null()) {
298 JNI_Java_HashSet_clear(env, retained_object_set);
300 auto iter = objects_.begin();
301 while (iter != objects_.end()) {
302 if (iter->second->IsNamed()) {
303 if (!retained_object_set.is_null()) {
304 JNI_Java_HashSet_add(
305 env, retained_object_set, iter->second->GetLocalRef(env));
307 ++iter;
308 } else {
309 objects_.erase(iter++);
314 base::TaskRunner* GinJavaBridgeDispatcherHost::OverrideTaskRunnerForMessage(
315 const IPC::Message& message) {
316 GinJavaBoundObject::ObjectID object_id = 0;
317 // TODO(mnaganov): It's very sad that we have a BrowserMessageFilter per
318 // WebView instance. We should redesign to have a filter per RPH.
319 // Check, if the object ID in the message is known to this host. If not,
320 // this is a message for some other host. As all our IPC messages from the
321 // renderer start with object ID, we just fetch it directly from the
322 // message, considering sync and async messages separately.
323 switch (message.type()) {
324 case GinJavaBridgeHostMsg_GetMethods::ID:
325 case GinJavaBridgeHostMsg_HasMethod::ID:
326 case GinJavaBridgeHostMsg_InvokeMethod::ID: {
327 DCHECK(message.is_sync());
328 base::PickleIterator message_reader =
329 IPC::SyncMessage::GetDataIterator(&message);
330 if (!IPC::ReadParam(&message, &message_reader, &object_id))
331 return NULL;
332 break;
334 case GinJavaBridgeHostMsg_ObjectWrapperDeleted::ID: {
335 DCHECK(!message.is_sync());
336 base::PickleIterator message_reader(message);
337 if (!IPC::ReadParam(&message, &message_reader, &object_id))
338 return NULL;
339 break;
341 default:
342 NOTREACHED();
343 return NULL;
346 base::AutoLock locker(objects_lock_);
347 if (objects_.find(object_id) != objects_.end()) {
348 return g_background_thread.Get().message_loop()->task_runner().get();
351 return NULL;
354 bool GinJavaBridgeDispatcherHost::OnMessageReceived(
355 const IPC::Message& message) {
356 // We can get here As WebContentsObserver also has OnMessageReceived,
357 // or because we have not provided a task runner in
358 // OverrideTaskRunnerForMessage. In either case, just bail out.
359 if (!JavaBridgeThread::CurrentlyOn())
360 return false;
361 SetCurrentRoutingID(message.routing_id());
362 bool handled = true;
363 IPC_BEGIN_MESSAGE_MAP(GinJavaBridgeDispatcherHost, message)
364 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_GetMethods, OnGetMethods)
365 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_HasMethod, OnHasMethod)
366 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_InvokeMethod, OnInvokeMethod)
367 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted,
368 OnObjectWrapperDeleted)
369 IPC_MESSAGE_UNHANDLED(handled = false)
370 IPC_END_MESSAGE_MAP()
371 SetCurrentRoutingID(MSG_ROUTING_NONE);
372 return handled;
375 void GinJavaBridgeDispatcherHost::OnDestruct() const {
376 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
377 delete this;
378 } else {
379 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
383 int GinJavaBridgeDispatcherHost::GetCurrentRoutingID() const {
384 DCHECK(JavaBridgeThread::CurrentlyOn());
385 return current_routing_id_;
388 void GinJavaBridgeDispatcherHost::SetCurrentRoutingID(int32 routing_id) {
389 DCHECK(JavaBridgeThread::CurrentlyOn());
390 current_routing_id_ = routing_id;
393 scoped_refptr<GinJavaBoundObject> GinJavaBridgeDispatcherHost::FindObject(
394 GinJavaBoundObject::ObjectID object_id) {
395 // Can be called on any thread.
396 base::AutoLock locker(objects_lock_);
397 auto iter = objects_.find(object_id);
398 if (iter != objects_.end())
399 return iter->second;
400 return NULL;
403 void GinJavaBridgeDispatcherHost::OnGetMethods(
404 GinJavaBoundObject::ObjectID object_id,
405 std::set<std::string>* returned_method_names) {
406 DCHECK(JavaBridgeThread::CurrentlyOn());
407 if (!allow_object_contents_inspection_)
408 return;
409 scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
410 if (object.get()) {
411 *returned_method_names = object->GetMethodNames();
412 } else {
413 LOG(ERROR) << "WebView: Unknown object: " << object_id;
417 void GinJavaBridgeDispatcherHost::OnHasMethod(
418 GinJavaBoundObject::ObjectID object_id,
419 const std::string& method_name,
420 bool* result) {
421 DCHECK(JavaBridgeThread::CurrentlyOn());
422 scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
423 if (object.get()) {
424 *result = object->HasMethod(method_name);
425 } else {
426 LOG(ERROR) << "WebView: Unknown object: " << object_id;
430 void GinJavaBridgeDispatcherHost::OnInvokeMethod(
431 GinJavaBoundObject::ObjectID object_id,
432 const std::string& method_name,
433 const base::ListValue& arguments,
434 base::ListValue* wrapped_result,
435 content::GinJavaBridgeError* error_code) {
436 DCHECK(JavaBridgeThread::CurrentlyOn());
437 DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE);
438 scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
439 if (!object.get()) {
440 LOG(ERROR) << "WebView: Unknown object: " << object_id;
441 wrapped_result->Append(base::Value::CreateNullValue());
442 *error_code = kGinJavaBridgeUnknownObjectId;
443 return;
445 scoped_refptr<GinJavaMethodInvocationHelper> result =
446 new GinJavaMethodInvocationHelper(
447 make_scoped_ptr(new GinJavaBoundObjectDelegate(object)),
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());
468 wrapped_result->Append(
469 GinJavaBridgeValue::CreateObjectIDValue(
470 returned_object_id).release());
471 } else {
472 wrapped_result->Append(base::Value::CreateNullValue());
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);
491 } // namespace content