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 DCHECK(!named_objects_
.empty());
48 if (!web_contents()->GetRenderProcessHost()->GetChannel()) return;
50 DCHECK(!GinJavaBridgeMessageFilter::FromHost(this, false));
51 scoped_refptr
<GinJavaBridgeMessageFilter
> filter
=
52 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 scoped_refptr
<GinJavaBridgeMessageFilter
> filter
=
63 GinJavaBridgeMessageFilter::FromHost(this, false);
65 filter
->AddRoutingIdForHost(this, render_frame_host
);
66 } else if (!named_objects_
.empty()) {
67 InstallFilterAndRegisterAllRoutingIds();
69 for (NamedObjectMap::const_iterator iter
= named_objects_
.begin();
70 iter
!= named_objects_
.end();
72 render_frame_host
->Send(new GinJavaBridgeMsg_AddNamedObject(
73 render_frame_host
->GetRoutingID(), iter
->first
, iter
->second
));
77 void GinJavaBridgeDispatcherHost::WebContentsDestroyed() {
78 scoped_refptr
<GinJavaBridgeMessageFilter
> filter
=
79 GinJavaBridgeMessageFilter::FromHost(this, false);
81 filter
->RemoveHost(this);
84 GinJavaBoundObject::ObjectID
GinJavaBridgeDispatcherHost::AddObject(
85 const base::android::JavaRef
<jobject
>& object
,
86 const base::android::JavaRef
<jclass
>& safe_annotation_clazz
,
89 // Can be called on any thread. Calls come from the UI thread via
90 // AddNamedObject, and from the background thread, when injected Java
91 // object's method returns a Java object.
92 DCHECK(is_named
|| holder
);
93 JNIEnv
* env
= base::android::AttachCurrentThread();
94 JavaObjectWeakGlobalRef
ref(env
, object
.obj());
95 scoped_refptr
<GinJavaBoundObject
> new_object
=
96 is_named
? GinJavaBoundObject::CreateNamed(ref
, safe_annotation_clazz
)
97 : GinJavaBoundObject::CreateTransient(ref
, safe_annotation_clazz
,
99 GinJavaBoundObject::ObjectID object_id
= next_object_id_
++;
101 base::AutoLock
locker(objects_lock_
);
102 objects_
[object_id
] = new_object
;
106 GinJavaBoundObject::ObjectID added_object_id
;
107 DCHECK(FindObjectId(object
, &added_object_id
));
108 DCHECK_EQ(object_id
, added_object_id
);
110 #endif // DCHECK_IS_ON()
111 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
112 retained_object_set_
.get(env
);
113 if (!retained_object_set
.is_null()) {
114 base::AutoLock
locker(objects_lock_
);
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 // Can be called on any thread.
124 JNIEnv
* env
= base::android::AttachCurrentThread();
125 base::AutoLock
locker(objects_lock_
);
126 for (const auto& pair
: objects_
) {
127 if (env
->IsSameObject(
129 pair
.second
->GetLocalRef(env
).obj())) {
130 *object_id
= pair
.first
;
137 JavaObjectWeakGlobalRef
GinJavaBridgeDispatcherHost::GetObjectWeakRef(
138 GinJavaBoundObject::ObjectID object_id
) {
139 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
141 return object
->GetWeakRef();
143 return JavaObjectWeakGlobalRef();
146 JavaObjectWeakGlobalRef
147 GinJavaBridgeDispatcherHost::RemoveHolderAndAdvanceLocked(
149 ObjectMap::iterator
* iter_ptr
) {
150 objects_lock_
.AssertAcquired();
151 JavaObjectWeakGlobalRef result
;
152 scoped_refptr
<GinJavaBoundObject
> object((*iter_ptr
)->second
);
153 bool object_erased
= false;
154 if (!object
->IsNamed()) {
155 object
->RemoveHolder(holder
);
156 if (!object
->HasHolders()) {
157 result
= object
->GetWeakRef();
158 objects_
.erase((*iter_ptr
)++);
159 object_erased
= true;
162 if (!object_erased
) {
168 void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
169 const JavaObjectWeakGlobalRef
& ref
) {
170 objects_lock_
.AssertAcquired();
171 JNIEnv
* env
= base::android::AttachCurrentThread();
172 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
173 retained_object_set_
.get(env
);
174 if (!retained_object_set
.is_null()) {
175 JNI_Java_HashSet_remove(env
, retained_object_set
, ref
.get(env
));
179 void GinJavaBridgeDispatcherHost::AddNamedObject(
180 const std::string
& name
,
181 const base::android::JavaRef
<jobject
>& object
,
182 const base::android::JavaRef
<jclass
>& safe_annotation_clazz
) {
183 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
184 GinJavaBoundObject::ObjectID object_id
;
185 NamedObjectMap::iterator iter
= named_objects_
.find(name
);
186 bool existing_object
= FindObjectId(object
, &object_id
);
187 if (existing_object
&& iter
!= named_objects_
.end() &&
188 iter
->second
== object_id
) {
192 if (iter
!= named_objects_
.end()) {
193 RemoveNamedObject(iter
->first
);
195 if (existing_object
) {
196 base::AutoLock
locker(objects_lock_
);
197 objects_
[object_id
]->AddName();
199 object_id
= AddObject(object
, safe_annotation_clazz
, true, 0);
201 named_objects_
[name
] = object_id
;
203 scoped_refptr
<GinJavaBridgeMessageFilter
> filter
=
204 GinJavaBridgeMessageFilter::FromHost(this, false);
206 InstallFilterAndRegisterAllRoutingIds();
207 web_contents()->SendToAllFrames(
208 new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE
, name
, object_id
));
211 void GinJavaBridgeDispatcherHost::RemoveNamedObject(
212 const std::string
& name
) {
213 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
214 NamedObjectMap::iterator iter
= named_objects_
.find(name
);
215 if (iter
== named_objects_
.end())
218 // |name| may come from |named_objects_|. Make a copy of name so that if
219 // |name| is from |named_objects_| it'll be valid after the remove below.
220 const std::string
copied_name(name
);
223 base::AutoLock
locker(objects_lock_
);
224 objects_
[iter
->second
]->RemoveName();
226 named_objects_
.erase(iter
);
228 // As the object isn't going to be removed from the JavaScript side until the
229 // next page reload, calls to it must still work, thus we should continue to
230 // hold it. All the transient objects and removed named objects will be purged
231 // during the cleansing caused by DocumentAvailableInMainFrame event.
233 web_contents()->SendToAllFrames(
234 new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE
, copied_name
));
237 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow
) {
238 if (!JavaBridgeThread::CurrentlyOn()) {
239 JavaBridgeThread::GetTaskRunner()->PostTask(
242 &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection
,
246 allow_object_contents_inspection_
= allow
;
249 void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
250 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
251 // Called when the window object has been cleared in the main frame.
252 // That means, all sub-frames have also been cleared, so only named
254 JNIEnv
* env
= base::android::AttachCurrentThread();
255 base::android::ScopedJavaLocalRef
<jobject
> retained_object_set
=
256 retained_object_set_
.get(env
);
257 base::AutoLock
locker(objects_lock_
);
258 if (!retained_object_set
.is_null()) {
259 JNI_Java_HashSet_clear(env
, retained_object_set
);
261 auto iter
= objects_
.begin();
262 while (iter
!= objects_
.end()) {
263 if (iter
->second
->IsNamed()) {
264 if (!retained_object_set
.is_null()) {
265 JNI_Java_HashSet_add(
266 env
, retained_object_set
, iter
->second
->GetLocalRef(env
));
270 objects_
.erase(iter
++);
275 scoped_refptr
<GinJavaBoundObject
> GinJavaBridgeDispatcherHost::FindObject(
276 GinJavaBoundObject::ObjectID object_id
) {
277 // Can be called on any thread.
278 base::AutoLock
locker(objects_lock_
);
279 auto iter
= objects_
.find(object_id
);
280 if (iter
!= objects_
.end())
282 LOG(ERROR
) << "WebView: Unknown object: " << object_id
;
286 void GinJavaBridgeDispatcherHost::OnGetMethods(
287 GinJavaBoundObject::ObjectID object_id
,
288 std::set
<std::string
>* returned_method_names
) {
289 DCHECK(JavaBridgeThread::CurrentlyOn());
290 if (!allow_object_contents_inspection_
)
292 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
294 *returned_method_names
= object
->GetMethodNames();
297 void GinJavaBridgeDispatcherHost::OnHasMethod(
298 GinJavaBoundObject::ObjectID object_id
,
299 const std::string
& method_name
,
301 DCHECK(JavaBridgeThread::CurrentlyOn());
302 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
304 *result
= object
->HasMethod(method_name
);
307 void GinJavaBridgeDispatcherHost::OnInvokeMethod(
309 GinJavaBoundObject::ObjectID object_id
,
310 const std::string
& method_name
,
311 const base::ListValue
& arguments
,
312 base::ListValue
* wrapped_result
,
313 content::GinJavaBridgeError
* error_code
) {
314 DCHECK(JavaBridgeThread::CurrentlyOn());
315 DCHECK(routing_id
!= MSG_ROUTING_NONE
);
316 scoped_refptr
<GinJavaBoundObject
> object
= FindObject(object_id
);
318 wrapped_result
->Append(base::Value::CreateNullValue());
319 *error_code
= kGinJavaBridgeUnknownObjectId
;
322 scoped_refptr
<GinJavaMethodInvocationHelper
> result
=
323 new GinJavaMethodInvocationHelper(
324 make_scoped_ptr(new GinJavaBoundObjectDelegate(object
)),
329 *error_code
= result
->GetInvocationError();
330 if (result
->HoldsPrimitiveResult()) {
331 scoped_ptr
<base::ListValue
> result_copy(
332 result
->GetPrimitiveResult().DeepCopy());
333 wrapped_result
->Swap(result_copy
.get());
334 } else if (!result
->GetObjectResult().is_null()) {
335 GinJavaBoundObject::ObjectID returned_object_id
;
336 if (FindObjectId(result
->GetObjectResult(), &returned_object_id
)) {
337 base::AutoLock
locker(objects_lock_
);
338 objects_
[returned_object_id
]->AddHolder(routing_id
);
340 returned_object_id
= AddObject(result
->GetObjectResult(),
341 result
->GetSafeAnnotationClass(),
345 wrapped_result
->Append(
346 GinJavaBridgeValue::CreateObjectIDValue(
347 returned_object_id
).release());
349 wrapped_result
->Append(base::Value::CreateNullValue());
353 void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
355 GinJavaBoundObject::ObjectID object_id
) {
356 DCHECK(JavaBridgeThread::CurrentlyOn());
357 DCHECK(routing_id
!= MSG_ROUTING_NONE
);
358 base::AutoLock
locker(objects_lock_
);
359 auto iter
= objects_
.find(object_id
);
360 if (iter
== objects_
.end())
362 JavaObjectWeakGlobalRef ref
=
363 RemoveHolderAndAdvanceLocked(routing_id
, &iter
);
364 if (!ref
.is_empty()) {
365 RemoveFromRetainedObjectSetLocked(ref
);
369 } // namespace content