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