• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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