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/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"
24 #if !defined(OS_ANDROID)
25 #error "JavaBridge only supports OS_ANDROID"
31 // The JavaBridge needs to use a Java thread so the callback
32 // will happen on a thread with a prepared Looper.
33 class JavaBridgeThread
: public base::android::JavaHandlerThread
{
35 JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
38 virtual ~JavaBridgeThread() {
43 base::LazyInstance
<JavaBridgeThread
> g_background_thread
=
44 LAZY_INSTANCE_INITIALIZER
;
48 GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
49 WebContents
* web_contents
,
50 jobject retained_object_set
)
51 : WebContentsObserver(web_contents
),
52 retained_object_set_(base::android::AttachCurrentThread(),
54 allow_object_contents_inspection_(true) {
55 DCHECK(retained_object_set
);
58 GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
59 DCHECK(pending_replies_
.empty());
62 void GinJavaBridgeDispatcherHost::RenderFrameCreated(
63 RenderFrameHost
* render_frame_host
) {
64 for (NamedObjectMap::const_iterator iter
= named_objects_
.begin();
65 iter
!= named_objects_
.end();
67 render_frame_host
->Send(new GinJavaBridgeMsg_AddNamedObject(
68 render_frame_host
->GetRoutingID(), iter
->first
, iter
->second
));
72 void 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
);
83 RemoveHolder(render_frame_host
,
84 GinJavaBoundObject::ObjectMap::iterator(&objects_
),
88 GinJavaBoundObject::ObjectID
GinJavaBridgeDispatcherHost::AddObject(
89 const base::android::JavaRef
<jobject
>& object
,
90 const base::android::JavaRef
<jclass
>& safe_annotation_clazz
,
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());
98 object_id
= objects_
.Add(new scoped_refptr
<GinJavaBoundObject
>(
99 GinJavaBoundObject::CreateNamed(ref
, safe_annotation_clazz
)));
101 object_id
= objects_
.Add(new scoped_refptr
<GinJavaBoundObject
>(
102 GinJavaBoundObject::CreateTransient(
103 ref
, safe_annotation_clazz
, holder
)));
107 GinJavaBoundObject::ObjectID added_object_id
;
108 DCHECK(FindObjectId(object
, &added_object_id
));
109 DCHECK_EQ(object_id
, added_object_id
);
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
);
120 bool 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();
126 if (env
->IsSameObject(
128 it
.GetCurrentValue()->get()->GetLocalRef(env
).obj())) {
129 *object_id
= it
.GetCurrentKey();
136 JavaObjectWeakGlobalRef
GinJavaBridgeDispatcherHost::GetObjectWeakRef(
137 GinJavaBoundObject::ObjectID object_id
) {
138 scoped_refptr
<GinJavaBoundObject
>* result
= objects_
.Lookup(object_id
);
139 scoped_refptr
<GinJavaBoundObject
> object(result
? *result
: NULL
);
141 return object
->GetWeakRef();
143 return JavaObjectWeakGlobalRef();
146 void GinJavaBridgeDispatcherHost::RemoveHolder(
147 RenderFrameHost
* holder
,
148 const GinJavaBoundObject::ObjectMap::iterator
& from
,
150 JNIEnv
* env
= base::android::AttachCurrentThread();
151 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
152 retained_object_set_
.get(env
);
154 for (GinJavaBoundObject::ObjectMap::iterator
it(from
);
155 !it
.IsAtEnd() && i
< count
;
157 scoped_refptr
<GinJavaBoundObject
> object(*it
.GetCurrentValue());
158 if (object
->IsNamed())
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
));
166 objects_
.Remove(it
.GetCurrentKey());
171 void 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
) {
184 if (iter
!= named_objects_
.end()) {
185 RemoveNamedObject(iter
->first
);
187 if (existing_object
) {
188 (*objects_
.Lookup(object_id
))->AddName();
190 object_id
= AddObject(object
, safe_annotation_clazz
, true, NULL
);
192 named_objects_
[name
] = object_id
;
194 web_contents()->SendToAllFrames(
195 new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE
, name
, object_id
));
198 void 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())
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
);
209 scoped_refptr
<GinJavaBoundObject
> object(*objects_
.Lookup(iter
->second
));
210 named_objects_
.erase(iter
);
211 object
->RemoveName();
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
));
226 web_contents()->SendToAllFrames(
227 new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE
, copied_name
));
230 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow
) {
231 allow_object_contents_inspection_
= allow
;
234 void 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
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
);
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();
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
));
257 objects_
.Remove(it
.GetCurrentKey());
264 // TODO(mnaganov): Implement passing of a parameter into sync message handlers.
265 class MessageForwarder
: public IPC::Sender
{
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_
,
276 void OnHasMethod(GinJavaBoundObject::ObjectID object_id
,
277 const std::string
& method_name
,
278 IPC::Message
* reply_msg
) {
279 gjbdh_
->OnHasMethod(render_frame_host_
,
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_
,
294 virtual bool Send(IPC::Message
* msg
) OVERRIDE
{
299 GinJavaBridgeDispatcherHost
* gjbdh_
;
300 RenderFrameHost
* render_frame_host_
;
305 bool GinJavaBridgeDispatcherHost::OnMessageReceived(
306 const IPC::Message
& message
,
307 RenderFrameHost
* render_frame_host
) {
308 DCHECK(render_frame_host
);
310 MessageForwarder
forwarder(this, render_frame_host
);
311 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(GinJavaBridgeDispatcherHost
, message
,
313 IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_GetMethods
,
315 MessageForwarder::OnGetMethods
)
316 IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_HasMethod
,
318 MessageForwarder::OnHasMethod
)
319 IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_InvokeMethod
,
321 MessageForwarder::OnInvokeMethod
)
322 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted
,
323 OnObjectWrapperDeleted
)
324 IPC_MESSAGE_UNHANDLED(handled
= false)
325 IPC_END_MESSAGE_MAP()
331 class IsValidRenderFrameHostHelper
332 : public base::RefCounted
<IsValidRenderFrameHostHelper
> {
334 explicit IsValidRenderFrameHostHelper(RenderFrameHost
* rfh_to_match
)
335 : rfh_to_match_(rfh_to_match
), rfh_found_(false) {}
337 bool rfh_found() { return rfh_found_
; }
339 void OnFrame(RenderFrameHost
* rfh
) {
340 if (rfh_to_match_
== rfh
) rfh_found_
= true;
344 friend class base::RefCounted
<IsValidRenderFrameHostHelper
>;
346 ~IsValidRenderFrameHostHelper() {}
348 RenderFrameHost
* rfh_to_match_
;
351 DISALLOW_COPY_AND_ASSIGN(IsValidRenderFrameHostHelper
);
356 bool 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();
365 void 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
);
376 scoped_refptr
<GinJavaBoundObject
> object(*objects_
.Lookup(object_id
));
378 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
379 IPC::WriteParam(reply_msg
, std::set
<std::string
>());
380 render_frame_host
->Send(reply_msg
);
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(),
388 base::Bind(&GinJavaBoundObject::GetMethodNames
, object
),
389 base::Bind(&GinJavaBridgeDispatcherHost::SendMethods
,
394 void GinJavaBridgeDispatcherHost::SendMethods(
395 RenderFrameHost
* render_frame_host
,
396 const std::set
<std::string
>& method_names
) {
397 IPC::Message
* reply_msg
= TakePendingReply(render_frame_host
);
401 IPC::WriteParam(reply_msg
, method_names
);
402 render_frame_host
->Send(reply_msg
);
405 void 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
));
414 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
415 IPC::WriteParam(reply_msg
, false);
416 render_frame_host
->Send(reply_msg
);
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(),
424 base::Bind(&GinJavaBoundObject::HasMethod
, object
, method_name
),
425 base::Bind(&GinJavaBridgeDispatcherHost::SendHasMethodReply
,
430 void GinJavaBridgeDispatcherHost::SendHasMethodReply(
431 RenderFrameHost
* render_frame_host
,
433 IPC::Message
* reply_msg
= TakePendingReply(render_frame_host
);
437 IPC::WriteParam(reply_msg
, result
);
438 render_frame_host
->Send(reply_msg
);
441 void 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
));
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
);
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
>(),
468 g_background_thread
.Get()
470 ->message_loop_proxy()
473 base::Bind(&GinJavaMethodInvocationHelper::Invoke
, result
),
475 &GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult
,
481 void 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
);
489 IPC::WriteParam(reply_msg
, result
->GetPrimitiveResult());
490 IPC::WriteParam(reply_msg
, result
->GetInvocationError());
491 render_frame_host
->Send(reply_msg
);
493 ProcessMethodInvocationObjectResult(render_frame_host
, result
);
497 void GinJavaBridgeDispatcherHost::ProcessMethodInvocationObjectResult(
498 RenderFrameHost
* render_frame_host
,
499 scoped_refptr
<GinJavaMethodInvocationHelper
> result
) {
500 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
502 if (!IsValidRenderFrameHost(render_frame_host
)) {
503 // In this case, we must've already sent the reply when the render frame
505 DCHECK(!HasPendingReply(render_frame_host
));
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
);
515 returned_object_id
= AddObject(result
->GetObjectResult(),
516 result
->GetSafeAnnotationClass(),
520 wrapped_result
.Append(
521 GinJavaBridgeValue::CreateObjectIDValue(
522 returned_object_id
).release());
524 wrapped_result
.Append(base::Value::CreateNullValue());
526 IPC::Message
* reply_msg
= TakePendingReply(render_frame_host
);
530 IPC::WriteParam(reply_msg
, wrapped_result
);
531 IPC::WriteParam(reply_msg
, result
->GetInvocationError());
532 render_frame_host
->Send(reply_msg
);
535 void 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
)
544 DCHECK(!iter
.IsAtEnd());
545 RemoveHolder(render_frame_host
, iter
, 1);
549 IPC::Message
* GinJavaBridgeDispatcherHost::TakePendingReply(
550 RenderFrameHost
* render_frame_host
) {
551 if (!IsValidRenderFrameHost(render_frame_host
)) {
552 DCHECK(!HasPendingReply(render_frame_host
));
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()) {
563 IPC::Message
* reply_msg
= it
->second
;
564 pending_replies_
.erase(it
);
568 bool GinJavaBridgeDispatcherHost::HasPendingReply(
569 RenderFrameHost
* render_frame_host
) const {
570 return pending_replies_
.find(render_frame_host
) != pending_replies_
.end();
573 } // namespace content