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"
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
{
38 JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
41 ~JavaBridgeThread() override
{ Stop(); }
42 static bool CurrentlyOn();
45 base::LazyInstance
<JavaBridgeThread
> g_background_thread
=
46 LAZY_INSTANCE_INITIALIZER
;
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
57 base::StaticAtomicSequenceNumber g_next_object_id
;
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(),
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
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();
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
,
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
,
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
;
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
);
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(
170 pair
.second
->GetLocalRef(env
).obj())) {
171 *object_id
= pair
.first
;
178 JavaObjectWeakGlobalRef
GinJavaBridgeDispatcherHost::GetObjectWeakRef(
179 GinJavaBoundObject::ObjectID object_id
) {
180 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
182 return object
->GetWeakRef();
184 return JavaObjectWeakGlobalRef();
187 JavaObjectWeakGlobalRef
188 GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked(
190 ObjectMap::iterator
* iter_ptr
) {
191 objects_lock_
.AssertAcquired();
192 JavaObjectWeakGlobalRef result
;
193 scoped_refptr
<GinJavaBoundObject
> object((*iter_ptr
)->second
);
194 if (!object
->IsNamed()) {
195 object
->RemoveHolder(holder
);
196 if (!object
->HasHolders()) {
197 result
= object
->GetWeakRef();
198 objects_
.erase((*iter_ptr
)++);
206 void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
207 const JavaObjectWeakGlobalRef
& ref
) {
208 objects_lock_
.AssertAcquired();
209 JNIEnv
* env
= base::android::AttachCurrentThread();
210 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
211 retained_object_set_
.get(env
);
212 if (!retained_object_set
.is_null()) {
213 JNI_Java_HashSet_remove(env
, retained_object_set
, ref
.get(env
));
217 void GinJavaBridgeDispatcherHost::AddNamedObject(
218 const std::string
& name
,
219 const base::android::JavaRef
<jobject
>& object
,
220 const base::android::JavaRef
<jclass
>& safe_annotation_clazz
) {
221 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
222 GinJavaBoundObject::ObjectID object_id
;
223 NamedObjectMap::iterator iter
= named_objects_
.find(name
);
224 bool existing_object
= FindObjectId(object
, &object_id
);
225 if (existing_object
&& iter
!= named_objects_
.end() &&
226 iter
->second
== object_id
) {
230 if (iter
!= named_objects_
.end()) {
231 RemoveNamedObject(iter
->first
);
233 if (existing_object
) {
234 base::AutoLock
locker(objects_lock_
);
235 objects_
[object_id
]->AddName();
237 object_id
= AddObject(object
, safe_annotation_clazz
, true, 0);
239 named_objects_
[name
] = object_id
;
241 AddBrowserFilterIfNeeded();
242 web_contents()->SendToAllFrames(
243 new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE
, name
, object_id
));
246 void GinJavaBridgeDispatcherHost::RemoveNamedObject(
247 const std::string
& name
) {
248 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
249 NamedObjectMap::iterator iter
= named_objects_
.find(name
);
250 if (iter
== named_objects_
.end())
253 // |name| may come from |named_objects_|. Make a copy of name so that if
254 // |name| is from |named_objects_| it'll be valid after the remove below.
255 const std::string
copied_name(name
);
258 base::AutoLock
locker(objects_lock_
);
259 objects_
[iter
->second
]->RemoveName();
261 named_objects_
.erase(iter
);
263 // As the object isn't going to be removed from the JavaScript side until the
264 // next page reload, calls to it must still work, thus we should continue to
265 // hold it. All the transient objects and removed named objects will be purged
266 // during the cleansing caused by DocumentAvailableInMainFrame event.
268 web_contents()->SendToAllFrames(
269 new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE
, copied_name
));
272 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow
) {
273 if (!JavaBridgeThread::CurrentlyOn()) {
274 g_background_thread
.Get().message_loop()->task_runner()->PostTask(
277 &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection
,
281 allow_object_contents_inspection_
= allow
;
284 void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
285 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
286 // Called when the window object has been cleared in the main frame.
287 // That means, all sub-frames have also been cleared, so only named
289 AddBrowserFilterIfNeeded();
290 JNIEnv
* env
= base::android::AttachCurrentThread();
291 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
292 retained_object_set_
.get(env
);
293 base::AutoLock
locker(objects_lock_
);
294 if (!retained_object_set
.is_null()) {
295 JNI_Java_HashSet_clear(env
, retained_object_set
);
297 auto iter
= objects_
.begin();
298 while (iter
!= objects_
.end()) {
299 if (iter
->second
->IsNamed()) {
300 if (!retained_object_set
.is_null()) {
301 JNI_Java_HashSet_add(
302 env
, retained_object_set
, iter
->second
->GetLocalRef(env
));
306 objects_
.erase(iter
++);
311 base::TaskRunner
* GinJavaBridgeDispatcherHost::OverrideTaskRunnerForMessage(
312 const IPC::Message
& message
) {
313 GinJavaBoundObject::ObjectID object_id
= 0;
314 // TODO(mnaganov): It's very sad that we have a BrowserMessageFilter per
315 // WebView instance. We should redesign to have a filter per RPH.
316 // Check, if the object ID in the message is known to this host. If not,
317 // this is a message for some other host. As all our IPC messages from the
318 // renderer start with object ID, we just fetch it directly from the
319 // message, considering sync and async messages separately.
320 switch (message
.type()) {
321 case GinJavaBridgeHostMsg_GetMethods::ID
:
322 case GinJavaBridgeHostMsg_HasMethod::ID
:
323 case GinJavaBridgeHostMsg_InvokeMethod::ID
: {
324 DCHECK(message
.is_sync());
325 PickleIterator message_reader
=
326 IPC::SyncMessage::GetDataIterator(&message
);
327 if (!IPC::ReadParam(&message
, &message_reader
, &object_id
))
331 case GinJavaBridgeHostMsg_ObjectWrapperDeleted::ID
: {
332 DCHECK(!message
.is_sync());
333 PickleIterator
message_reader(message
);
334 if (!IPC::ReadParam(&message
, &message_reader
, &object_id
))
343 base::AutoLock
locker(objects_lock_
);
344 if (objects_
.find(object_id
) != objects_
.end()) {
345 return g_background_thread
.Get().message_loop()->task_runner().get();
351 bool GinJavaBridgeDispatcherHost::OnMessageReceived(
352 const IPC::Message
& message
) {
353 // We can get here As WebContentsObserver also has OnMessageReceived,
354 // or because we have not provided a task runner in
355 // OverrideTaskRunnerForMessage. In either case, just bail out.
356 if (!JavaBridgeThread::CurrentlyOn())
358 SetCurrentRoutingID(message
.routing_id());
360 IPC_BEGIN_MESSAGE_MAP(GinJavaBridgeDispatcherHost
, message
)
361 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_GetMethods
, OnGetMethods
)
362 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_HasMethod
, OnHasMethod
)
363 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_InvokeMethod
, OnInvokeMethod
)
364 IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted
,
365 OnObjectWrapperDeleted
)
366 IPC_MESSAGE_UNHANDLED(handled
= false)
367 IPC_END_MESSAGE_MAP()
368 SetCurrentRoutingID(MSG_ROUTING_NONE
);
372 void GinJavaBridgeDispatcherHost::OnDestruct() const {
373 if (BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
376 BrowserThread::DeleteSoon(BrowserThread::UI
, FROM_HERE
, this);
380 int GinJavaBridgeDispatcherHost::GetCurrentRoutingID() const {
381 DCHECK(JavaBridgeThread::CurrentlyOn());
382 return current_routing_id_
;
385 void GinJavaBridgeDispatcherHost::SetCurrentRoutingID(int32 routing_id
) {
386 DCHECK(JavaBridgeThread::CurrentlyOn());
387 current_routing_id_
= routing_id
;
390 scoped_refptr
<GinJavaBoundObject
> GinJavaBridgeDispatcherHost::FindObject(
391 GinJavaBoundObject::ObjectID object_id
) {
392 // Can be called on any thread.
393 base::AutoLock
locker(objects_lock_
);
394 auto iter
= objects_
.find(object_id
);
395 if (iter
!= objects_
.end())
400 void GinJavaBridgeDispatcherHost::OnGetMethods(
401 GinJavaBoundObject::ObjectID object_id
,
402 std::set
<std::string
>* returned_method_names
) {
403 DCHECK(JavaBridgeThread::CurrentlyOn());
404 if (!allow_object_contents_inspection_
)
406 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
408 *returned_method_names
= object
->GetMethodNames();
410 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
414 void GinJavaBridgeDispatcherHost::OnHasMethod(
415 GinJavaBoundObject::ObjectID object_id
,
416 const std::string
& method_name
,
418 DCHECK(JavaBridgeThread::CurrentlyOn());
419 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
421 *result
= object
->HasMethod(method_name
);
423 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
427 void GinJavaBridgeDispatcherHost::OnInvokeMethod(
428 GinJavaBoundObject::ObjectID object_id
,
429 const std::string
& method_name
,
430 const base::ListValue
& arguments
,
431 base::ListValue
* wrapped_result
,
432 content::GinJavaBridgeError
* error_code
) {
433 DCHECK(JavaBridgeThread::CurrentlyOn());
434 DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE
);
435 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
437 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
438 wrapped_result
->Append(base::Value::CreateNullValue());
439 *error_code
= kGinJavaBridgeUnknownObjectId
;
442 scoped_refptr
<GinJavaMethodInvocationHelper
> result
=
443 new GinJavaMethodInvocationHelper(
444 make_scoped_ptr(new GinJavaBoundObjectDelegate(object
)),
449 *error_code
= result
->GetInvocationError();
450 if (result
->HoldsPrimitiveResult()) {
451 scoped_ptr
<base::ListValue
> result_copy(
452 result
->GetPrimitiveResult().DeepCopy());
453 wrapped_result
->Swap(result_copy
.get());
454 } else if (!result
->GetObjectResult().is_null()) {
455 GinJavaBoundObject::ObjectID returned_object_id
;
456 if (FindObjectId(result
->GetObjectResult(), &returned_object_id
)) {
457 base::AutoLock
locker(objects_lock_
);
458 objects_
[returned_object_id
]->AddHolder(GetCurrentRoutingID());
460 returned_object_id
= AddObject(result
->GetObjectResult(),
461 result
->GetSafeAnnotationClass(),
463 GetCurrentRoutingID());
465 wrapped_result
->Append(
466 GinJavaBridgeValue::CreateObjectIDValue(
467 returned_object_id
).release());
469 wrapped_result
->Append(base::Value::CreateNullValue());
473 void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
474 GinJavaBoundObject::ObjectID object_id
) {
475 DCHECK(JavaBridgeThread::CurrentlyOn());
476 DCHECK(GetCurrentRoutingID() != MSG_ROUTING_NONE
);
477 base::AutoLock
locker(objects_lock_
);
478 auto iter
= objects_
.find(object_id
);
479 if (iter
== objects_
.end())
481 JavaObjectWeakGlobalRef ref
=
482 RemoveHolderAndAdvanceLocked(GetCurrentRoutingID(), &iter
);
483 if (!ref
.is_empty()) {
484 RemoveFromRetainedObjectSetLocked(ref
);
488 } // namespace content