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/jni_android.h"
8 #include "base/android/scoped_java_ref.h"
9 #include "content/browser/android/java/gin_java_bound_object_delegate.h"
10 #include "content/browser/android/java/gin_java_bridge_message_filter.h"
11 #include "content/browser/android/java/java_bridge_thread.h"
12 #include "content/browser/android/java/jni_helper.h"
13 #include "content/common/android/gin_java_bridge_value.h"
14 #include "content/common/android/hash_set.h"
15 #include "content/common/gin_java_bridge_messages.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/render_frame_host.h"
18 #include "content/public/browser/render_process_host.h"
19 #include "content/public/browser/web_contents.h"
21 #if !defined(OS_ANDROID)
22 #error "JavaBridge only supports OS_ANDROID"
27 GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
28 WebContents
* web_contents
,
29 jobject retained_object_set
)
30 : WebContentsObserver(web_contents
),
32 retained_object_set_(base::android::AttachCurrentThread(),
34 allow_object_contents_inspection_(true) {
35 DCHECK(retained_object_set
);
38 GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
41 // GinJavaBridgeDispatcherHost gets created earlier than RenderProcessHost
42 // is initialized. So we postpone installing the message filter until we know
43 // that the RPH is in a good shape. Also, message filter installation is
44 // postponed until the first named object is created.
45 void GinJavaBridgeDispatcherHost::InstallFilterAndRegisterAllRoutingIds() {
46 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
47 if (named_objects_
.empty() ||
48 !web_contents()->GetRenderProcessHost()->GetChannel()) {
52 auto filter
= GinJavaBridgeMessageFilter::FromHost(this, true);
53 // ForEachFrame is synchronous.
54 web_contents()->ForEachFrame(
55 base::Bind(&GinJavaBridgeMessageFilter::AddRoutingIdForHost
, filter
,
56 base::Unretained(this)));
59 void GinJavaBridgeDispatcherHost::RenderFrameCreated(
60 RenderFrameHost
* render_frame_host
) {
61 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
62 if (auto filter
= GinJavaBridgeMessageFilter::FromHost(this, false)) {
63 filter
->AddRoutingIdForHost(this, render_frame_host
);
65 InstallFilterAndRegisterAllRoutingIds();
67 for (NamedObjectMap::const_iterator iter
= named_objects_
.begin();
68 iter
!= named_objects_
.end();
70 render_frame_host
->Send(new GinJavaBridgeMsg_AddNamedObject(
71 render_frame_host
->GetRoutingID(), iter
->first
, iter
->second
));
75 void GinJavaBridgeDispatcherHost::WebContentsDestroyed() {
76 scoped_refptr
<GinJavaBridgeMessageFilter
> filter
=
77 GinJavaBridgeMessageFilter::FromHost(this, false);
79 filter
->RemoveHost(this);
82 GinJavaBoundObject::ObjectID
GinJavaBridgeDispatcherHost::AddObject(
83 const base::android::JavaRef
<jobject
>& object
,
84 const base::android::JavaRef
<jclass
>& safe_annotation_clazz
,
87 // Can be called on any thread. Calls come from the UI thread via
88 // AddNamedObject, and from the background thread, when injected Java
89 // object's method returns a Java object.
90 DCHECK(is_named
|| holder
);
91 JNIEnv
* env
= base::android::AttachCurrentThread();
92 JavaObjectWeakGlobalRef
ref(env
, object
.obj());
93 scoped_refptr
<GinJavaBoundObject
> new_object
=
94 is_named
? GinJavaBoundObject::CreateNamed(ref
, safe_annotation_clazz
)
95 : GinJavaBoundObject::CreateTransient(ref
, safe_annotation_clazz
,
97 GinJavaBoundObject::ObjectID object_id
= next_object_id_
++;
99 base::AutoLock
locker(objects_lock_
);
100 objects_
[object_id
] = new_object
;
104 GinJavaBoundObject::ObjectID added_object_id
;
105 DCHECK(FindObjectId(object
, &added_object_id
));
106 DCHECK_EQ(object_id
, added_object_id
);
108 #endif // DCHECK_IS_ON()
109 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
110 retained_object_set_
.get(env
);
111 if (!retained_object_set
.is_null()) {
112 base::AutoLock
locker(objects_lock_
);
113 JNI_Java_HashSet_add(env
, retained_object_set
, object
);
118 bool GinJavaBridgeDispatcherHost::FindObjectId(
119 const base::android::JavaRef
<jobject
>& object
,
120 GinJavaBoundObject::ObjectID
* object_id
) {
121 // Can be called on any thread.
122 JNIEnv
* env
= base::android::AttachCurrentThread();
123 base::AutoLock
locker(objects_lock_
);
124 for (const auto& pair
: objects_
) {
125 if (env
->IsSameObject(
127 pair
.second
->GetLocalRef(env
).obj())) {
128 *object_id
= pair
.first
;
135 JavaObjectWeakGlobalRef
GinJavaBridgeDispatcherHost::GetObjectWeakRef(
136 GinJavaBoundObject::ObjectID object_id
) {
137 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
139 return object
->GetWeakRef();
141 return JavaObjectWeakGlobalRef();
144 JavaObjectWeakGlobalRef
145 GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked(
147 ObjectMap::iterator
* iter_ptr
) {
148 objects_lock_
.AssertAcquired();
149 JavaObjectWeakGlobalRef result
;
150 scoped_refptr
<GinJavaBoundObject
> object((*iter_ptr
)->second
);
151 bool object_erased
= false;
152 if (!object
->IsNamed()) {
153 object
->RemoveHolder(holder
);
154 if (!object
->HasHolders()) {
155 result
= object
->GetWeakRef();
156 objects_
.erase((*iter_ptr
)++);
157 object_erased
= true;
160 if (!object_erased
) {
166 void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
167 const JavaObjectWeakGlobalRef
& ref
) {
168 objects_lock_
.AssertAcquired();
169 JNIEnv
* env
= base::android::AttachCurrentThread();
170 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
171 retained_object_set_
.get(env
);
172 if (!retained_object_set
.is_null()) {
173 JNI_Java_HashSet_remove(env
, retained_object_set
, ref
.get(env
));
177 void GinJavaBridgeDispatcherHost::AddNamedObject(
178 const std::string
& name
,
179 const base::android::JavaRef
<jobject
>& object
,
180 const base::android::JavaRef
<jclass
>& safe_annotation_clazz
) {
181 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
182 GinJavaBoundObject::ObjectID object_id
;
183 NamedObjectMap::iterator iter
= named_objects_
.find(name
);
184 bool existing_object
= FindObjectId(object
, &object_id
);
185 if (existing_object
&& iter
!= named_objects_
.end() &&
186 iter
->second
== object_id
) {
190 if (iter
!= named_objects_
.end()) {
191 RemoveNamedObject(iter
->first
);
193 if (existing_object
) {
194 base::AutoLock
locker(objects_lock_
);
195 objects_
[object_id
]->AddName();
197 object_id
= AddObject(object
, safe_annotation_clazz
, true, 0);
199 named_objects_
[name
] = object_id
;
201 // As GinJavaBridgeDispatcherHost can be created later than WebContents has
202 // notified the observers about new RenderFrame, it is necessary to ensure
203 // here that all render frame IDs are registered with the filter.
204 InstallFilterAndRegisterAllRoutingIds();
205 web_contents()->SendToAllFrames(
206 new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE
, name
, object_id
));
209 void GinJavaBridgeDispatcherHost::RemoveNamedObject(
210 const std::string
& name
) {
211 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
212 NamedObjectMap::iterator iter
= named_objects_
.find(name
);
213 if (iter
== named_objects_
.end())
216 // |name| may come from |named_objects_|. Make a copy of name so that if
217 // |name| is from |named_objects_| it'll be valid after the remove below.
218 const std::string
copied_name(name
);
221 base::AutoLock
locker(objects_lock_
);
222 objects_
[iter
->second
]->RemoveName();
224 named_objects_
.erase(iter
);
226 // As the object isn't going to be removed from the JavaScript side until the
227 // next page reload, calls to it must still work, thus we should continue to
228 // hold it. All the transient objects and removed named objects will be purged
229 // during the cleansing caused by DocumentAvailableInMainFrame event.
231 web_contents()->SendToAllFrames(
232 new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE
, copied_name
));
235 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow
) {
236 if (!JavaBridgeThread::CurrentlyOn()) {
237 JavaBridgeThread::GetTaskRunner()->PostTask(
240 &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection
,
244 allow_object_contents_inspection_
= allow
;
247 void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
248 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
249 // Called when the window object has been cleared in the main frame.
250 // That means, all sub-frames have also been cleared, so only named
252 JNIEnv
* env
= base::android::AttachCurrentThread();
253 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
254 retained_object_set_
.get(env
);
255 base::AutoLock
locker(objects_lock_
);
256 if (!retained_object_set
.is_null()) {
257 JNI_Java_HashSet_clear(env
, retained_object_set
);
259 auto iter
= objects_
.begin();
260 while (iter
!= objects_
.end()) {
261 if (iter
->second
->IsNamed()) {
262 if (!retained_object_set
.is_null()) {
263 JNI_Java_HashSet_add(
264 env
, retained_object_set
, iter
->second
->GetLocalRef(env
));
268 objects_
.erase(iter
++);
273 scoped_refptr
<GinJavaBoundObject
> GinJavaBridgeDispatcherHost::FindObject(
274 GinJavaBoundObject::ObjectID object_id
) {
275 // Can be called on any thread.
276 base::AutoLock
locker(objects_lock_
);
277 auto iter
= objects_
.find(object_id
);
278 if (iter
!= objects_
.end())
280 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
284 void GinJavaBridgeDispatcherHost::OnGetMethods(
285 GinJavaBoundObject::ObjectID object_id
,
286 std::set
<std::string
>* returned_method_names
) {
287 DCHECK(JavaBridgeThread::CurrentlyOn());
288 if (!allow_object_contents_inspection_
)
290 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
292 *returned_method_names
= object
->GetMethodNames();
295 void GinJavaBridgeDispatcherHost::OnHasMethod(
296 GinJavaBoundObject::ObjectID object_id
,
297 const std::string
& method_name
,
299 DCHECK(JavaBridgeThread::CurrentlyOn());
300 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
302 *result
= object
->HasMethod(method_name
);
305 void GinJavaBridgeDispatcherHost::OnInvokeMethod(
307 GinJavaBoundObject::ObjectID object_id
,
308 const std::string
& method_name
,
309 const base::ListValue
& arguments
,
310 base::ListValue
* wrapped_result
,
311 content::GinJavaBridgeError
* error_code
) {
312 DCHECK(JavaBridgeThread::CurrentlyOn());
313 DCHECK(routing_id
!= MSG_ROUTING_NONE
);
314 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
316 wrapped_result
->Append(base::Value::CreateNullValue());
317 *error_code
= kGinJavaBridgeUnknownObjectId
;
320 scoped_refptr
<GinJavaMethodInvocationHelper
> result
=
321 new GinJavaMethodInvocationHelper(
322 make_scoped_ptr(new GinJavaBoundObjectDelegate(object
)),
327 *error_code
= result
->GetInvocationError();
328 if (result
->HoldsPrimitiveResult()) {
329 scoped_ptr
<base::ListValue
> result_copy(
330 result
->GetPrimitiveResult().DeepCopy());
331 wrapped_result
->Swap(result_copy
.get());
332 } else if (!result
->GetObjectResult().is_null()) {
333 GinJavaBoundObject::ObjectID returned_object_id
;
334 if (FindObjectId(result
->GetObjectResult(), &returned_object_id
)) {
335 base::AutoLock
locker(objects_lock_
);
336 objects_
[returned_object_id
]->AddHolder(routing_id
);
338 returned_object_id
= AddObject(result
->GetObjectResult(),
339 result
->GetSafeAnnotationClass(),
343 wrapped_result
->Append(
344 GinJavaBridgeValue::CreateObjectIDValue(
345 returned_object_id
).release());
347 wrapped_result
->Append(base::Value::CreateNullValue());
351 void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
353 GinJavaBoundObject::ObjectID object_id
) {
354 DCHECK(JavaBridgeThread::CurrentlyOn());
355 DCHECK(routing_id
!= MSG_ROUTING_NONE
);
356 base::AutoLock
locker(objects_lock_
);
357 auto iter
= objects_
.find(object_id
);
358 if (iter
== objects_
.end())
360 JavaObjectWeakGlobalRef ref
=
361 RemoveHolderAndAdvanceLocked(routing_id
, &iter
);
362 if (!ref
.is_empty()) {
363 RemoveFromRetainedObjectSetLocked(ref
);
367 } // namespace content