74ccaa21aaf6b0352edd3d971c6d1630524292ae
[platform/framework/web/crosswalk.git] / src / content / browser / android / java / gin_java_bridge_dispatcher_host.cc
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/lazy_instance.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/task_runner_util.h"
14 #include "content/browser/android/java/gin_java_bound_object_delegate.h"
15 #include "content/browser/android/java/jni_helper.h"
16 #include "content/common/android/gin_java_bridge_value.h"
17 #include "content/common/android/hash_set.h"
18 #include "content/common/gin_java_bridge_messages.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/web_contents.h"
22 #include "ipc/ipc_message_utils.h"
23
24 #if !defined(OS_ANDROID)
25 #error "JavaBridge only supports OS_ANDROID"
26 #endif
27
28 namespace content {
29
30 namespace {
31 // The JavaBridge needs to use a Java thread so the callback
32 // will happen on a thread with a prepared Looper.
33 class JavaBridgeThread : public base::android::JavaHandlerThread {
34  public:
35   JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
36     Start();
37   }
38   virtual ~JavaBridgeThread() {
39     Stop();
40   }
41 };
42
43 base::LazyInstance<JavaBridgeThread> g_background_thread =
44     LAZY_INSTANCE_INITIALIZER;
45
46 }  // namespace
47
48 GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
49     WebContents* web_contents,
50     jobject retained_object_set)
51     : WebContentsObserver(web_contents),
52       retained_object_set_(base::android::AttachCurrentThread(),
53                            retained_object_set),
54       allow_object_contents_inspection_(true) {
55   DCHECK(retained_object_set);
56 }
57
58 GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
59 }
60
61 void GinJavaBridgeDispatcherHost::RenderFrameCreated(
62     RenderFrameHost* render_frame_host) {
63   for (NamedObjectMap::const_iterator iter = named_objects_.begin();
64        iter != named_objects_.end();
65        ++iter) {
66     render_frame_host->Send(new GinJavaBridgeMsg_AddNamedObject(
67         render_frame_host->GetRoutingID(), iter->first, iter->second));
68   }
69 }
70
71 void GinJavaBridgeDispatcherHost::RenderFrameDeleted(
72     RenderFrameHost* render_frame_host) {
73   RemoveHolder(render_frame_host,
74                GinJavaBoundObject::ObjectMap::iterator(&objects_),
75                objects_.size());
76 }
77
78 GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
79     const base::android::JavaRef<jobject>& object,
80     const base::android::JavaRef<jclass>& safe_annotation_clazz,
81     bool is_named,
82     RenderFrameHost* holder) {
83   DCHECK(is_named || holder);
84   GinJavaBoundObject::ObjectID object_id;
85   JNIEnv* env = base::android::AttachCurrentThread();
86   JavaObjectWeakGlobalRef ref(env, object.obj());
87   if (is_named) {
88     object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>(
89         GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz)));
90   } else {
91     object_id = objects_.Add(new scoped_refptr<GinJavaBoundObject>(
92         GinJavaBoundObject::CreateTransient(
93             ref, safe_annotation_clazz, holder)));
94   }
95 #if DCHECK_IS_ON
96   {
97     GinJavaBoundObject::ObjectID added_object_id;
98     DCHECK(FindObjectId(object, &added_object_id));
99     DCHECK_EQ(object_id, added_object_id);
100   }
101 #endif  // DCHECK_IS_ON
102   base::android::ScopedJavaLocalRef<jobject> retained_object_set =
103         retained_object_set_.get(env);
104   if (!retained_object_set.is_null()) {
105     JNI_Java_HashSet_add(env, retained_object_set, object);
106   }
107   return object_id;
108 }
109
110 bool GinJavaBridgeDispatcherHost::FindObjectId(
111     const base::android::JavaRef<jobject>& object,
112     GinJavaBoundObject::ObjectID* object_id) {
113   JNIEnv* env = base::android::AttachCurrentThread();
114   for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd();
115        it.Advance()) {
116     if (env->IsSameObject(
117             object.obj(),
118             it.GetCurrentValue()->get()->GetLocalRef(env).obj())) {
119       *object_id = it.GetCurrentKey();
120       return true;
121     }
122   }
123   return false;
124 }
125
126 JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef(
127     GinJavaBoundObject::ObjectID object_id) {
128   scoped_refptr<GinJavaBoundObject>* result = objects_.Lookup(object_id);
129   scoped_refptr<GinJavaBoundObject> object(result ? *result : NULL);
130   if (object.get())
131     return object->GetWeakRef();
132   else
133     return JavaObjectWeakGlobalRef();
134 }
135
136 void GinJavaBridgeDispatcherHost::RemoveHolder(
137     RenderFrameHost* holder,
138     const GinJavaBoundObject::ObjectMap::iterator& from,
139     size_t count) {
140   JNIEnv* env = base::android::AttachCurrentThread();
141   base::android::ScopedJavaLocalRef<jobject> retained_object_set =
142       retained_object_set_.get(env);
143   size_t i = 0;
144   for (GinJavaBoundObject::ObjectMap::iterator it(from);
145        !it.IsAtEnd() && i < count;
146        it.Advance(), ++i) {
147     scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue());
148     if (object->IsNamed())
149       continue;
150     object->RemoveHolder(holder);
151     if (!object->HasHolders()) {
152       if (!retained_object_set.is_null()) {
153         JNI_Java_HashSet_remove(
154             env, retained_object_set, object->GetLocalRef(env));
155       }
156       objects_.Remove(it.GetCurrentKey());
157     }
158   }
159 }
160
161 void GinJavaBridgeDispatcherHost::AddNamedObject(
162     const std::string& name,
163     const base::android::JavaRef<jobject>& object,
164     const base::android::JavaRef<jclass>& safe_annotation_clazz) {
165   DCHECK_CURRENTLY_ON(BrowserThread::UI);
166   GinJavaBoundObject::ObjectID object_id;
167   NamedObjectMap::iterator iter = named_objects_.find(name);
168   bool existing_object = FindObjectId(object, &object_id);
169   if (existing_object && iter != named_objects_.end() &&
170       iter->second == object_id) {
171     // Nothing to do.
172     return;
173   }
174   if (iter != named_objects_.end()) {
175     RemoveNamedObject(iter->first);
176   }
177   if (existing_object) {
178     (*objects_.Lookup(object_id))->AddName();
179   } else {
180     object_id = AddObject(object, safe_annotation_clazz, true, NULL);
181   }
182   named_objects_[name] = object_id;
183
184   web_contents()->SendToAllFrames(
185       new GinJavaBridgeMsg_AddNamedObject(MSG_ROUTING_NONE, name, object_id));
186 }
187
188 void GinJavaBridgeDispatcherHost::RemoveNamedObject(
189     const std::string& name) {
190   DCHECK_CURRENTLY_ON(BrowserThread::UI);
191   NamedObjectMap::iterator iter = named_objects_.find(name);
192   if (iter == named_objects_.end())
193     return;
194
195   // |name| may come from |named_objects_|. Make a copy of name so that if
196   // |name| is from |named_objects_| it'll be valid after the remove below.
197   const std::string copied_name(name);
198
199   scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(iter->second));
200   named_objects_.erase(iter);
201   object->RemoveName();
202
203   // Not erasing from the objects map, as we can still receive method
204   // invocation requests for this object, and they should work until the
205   // java object is gone.
206   if (!object->IsNamed()) {
207     JNIEnv* env = base::android::AttachCurrentThread();
208     base::android::ScopedJavaLocalRef<jobject> retained_object_set =
209         retained_object_set_.get(env);
210     if (!retained_object_set.is_null()) {
211       JNI_Java_HashSet_remove(
212           env, retained_object_set, object->GetLocalRef(env));
213     }
214   }
215
216   web_contents()->SendToAllFrames(
217       new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE, copied_name));
218 }
219
220 void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) {
221   allow_object_contents_inspection_ = allow;
222 }
223
224 void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
225   DCHECK_CURRENTLY_ON(BrowserThread::UI);
226   // Called when the window object has been cleared in the main frame.
227   // That means, all sub-frames have also been cleared, so only named
228   // objects survived.
229   JNIEnv* env = base::android::AttachCurrentThread();
230   base::android::ScopedJavaLocalRef<jobject> retained_object_set =
231       retained_object_set_.get(env);
232   if (!retained_object_set.is_null()) {
233     JNI_Java_HashSet_clear(env, retained_object_set);
234   }
235
236   // We also need to add back the named objects we have so far as they
237   // should survive navigations.
238   for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd();
239        it.Advance()) {
240     scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue());
241     if (object->IsNamed()) {
242       if (!retained_object_set.is_null()) {
243         JNI_Java_HashSet_add(
244             env, retained_object_set, object->GetLocalRef(env));
245       }
246     } else {
247       objects_.Remove(it.GetCurrentKey());
248     }
249   }
250 }
251
252 namespace {
253
254 // TODO(mnaganov): Implement passing of a parameter into sync message handlers.
255 class MessageForwarder : public IPC::Sender {
256  public:
257   MessageForwarder(GinJavaBridgeDispatcherHost* gjbdh,
258                    RenderFrameHost* render_frame_host)
259       : gjbdh_(gjbdh), render_frame_host_(render_frame_host) {}
260   void OnGetMethods(GinJavaBoundObject::ObjectID object_id,
261                     IPC::Message* reply_msg) {
262     gjbdh_->OnGetMethods(render_frame_host_,
263                          object_id,
264                          reply_msg);
265   }
266   void OnHasMethod(GinJavaBoundObject::ObjectID object_id,
267                    const std::string& method_name,
268                    IPC::Message* reply_msg) {
269     gjbdh_->OnHasMethod(render_frame_host_,
270                         object_id,
271                         method_name,
272                         reply_msg);
273   }
274   void OnInvokeMethod(GinJavaBoundObject::ObjectID object_id,
275                       const std::string& method_name,
276                       const base::ListValue& arguments,
277                       IPC::Message* reply_msg) {
278     gjbdh_->OnInvokeMethod(render_frame_host_,
279                            object_id,
280                            method_name,
281                            arguments,
282                            reply_msg);
283   }
284   virtual bool Send(IPC::Message* msg) OVERRIDE {
285     NOTREACHED();
286     return false;
287   }
288  private:
289   GinJavaBridgeDispatcherHost* gjbdh_;
290   RenderFrameHost* render_frame_host_;
291 };
292
293 }
294
295 bool GinJavaBridgeDispatcherHost::OnMessageReceived(
296     const IPC::Message& message,
297     RenderFrameHost* render_frame_host) {
298   DCHECK(render_frame_host);
299   bool handled = true;
300   MessageForwarder forwarder(this, render_frame_host);
301   IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(GinJavaBridgeDispatcherHost, message,
302                                    render_frame_host)
303     IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_GetMethods,
304                                     &forwarder,
305                                     MessageForwarder::OnGetMethods)
306     IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_HasMethod,
307                                     &forwarder,
308                                     MessageForwarder::OnHasMethod)
309     IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_InvokeMethod,
310                                     &forwarder,
311                                     MessageForwarder::OnInvokeMethod)
312     IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted,
313                         OnObjectWrapperDeleted)
314     IPC_MESSAGE_UNHANDLED(handled = false)
315   IPC_END_MESSAGE_MAP()
316   return handled;
317 }
318
319 namespace {
320
321 class IsValidRenderFrameHostHelper
322     : public base::RefCounted<IsValidRenderFrameHostHelper> {
323  public:
324   explicit IsValidRenderFrameHostHelper(RenderFrameHost* rfh_to_match)
325       : rfh_to_match_(rfh_to_match), rfh_found_(false) {}
326
327   bool rfh_found() { return rfh_found_; }
328
329   void OnFrame(RenderFrameHost* rfh) {
330     if (rfh_to_match_ == rfh) rfh_found_ = true;
331   }
332
333  private:
334   friend class base::RefCounted<IsValidRenderFrameHostHelper>;
335
336   ~IsValidRenderFrameHostHelper() {}
337
338   RenderFrameHost* rfh_to_match_;
339   bool rfh_found_;
340
341   DISALLOW_COPY_AND_ASSIGN(IsValidRenderFrameHostHelper);
342 };
343
344 }  // namespace
345
346 bool GinJavaBridgeDispatcherHost::IsValidRenderFrameHost(
347     RenderFrameHost* render_frame_host) {
348   scoped_refptr<IsValidRenderFrameHostHelper> helper =
349       new IsValidRenderFrameHostHelper(render_frame_host);
350   web_contents()->ForEachFrame(
351       base::Bind(&IsValidRenderFrameHostHelper::OnFrame, helper));
352   return helper->rfh_found();
353 }
354
355 void GinJavaBridgeDispatcherHost::SendReply(
356     RenderFrameHost* render_frame_host,
357     IPC::Message* reply_msg) {
358   DCHECK_CURRENTLY_ON(BrowserThread::UI);
359   if (IsValidRenderFrameHost(render_frame_host)) {
360     render_frame_host->Send(reply_msg);
361   } else {
362     delete reply_msg;
363   }
364 }
365
366 void GinJavaBridgeDispatcherHost::OnGetMethods(
367     RenderFrameHost* render_frame_host,
368     GinJavaBoundObject::ObjectID object_id,
369     IPC::Message* reply_msg) {
370   DCHECK_CURRENTLY_ON(BrowserThread::UI);
371   DCHECK(render_frame_host);
372   if (!allow_object_contents_inspection_) {
373     IPC::WriteParam(reply_msg, std::set<std::string>());
374     render_frame_host->Send(reply_msg);
375     return;
376   }
377   scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
378   if (!object) {
379     LOG(ERROR) << "WebView: Unknown object: " << object_id;
380     IPC::WriteParam(reply_msg, std::set<std::string>());
381     render_frame_host->Send(reply_msg);
382     return;
383   }
384   base::PostTaskAndReplyWithResult(
385       g_background_thread.Get().message_loop()->message_loop_proxy(),
386       FROM_HERE,
387       base::Bind(&GinJavaBoundObject::GetMethodNames, object),
388       base::Bind(&GinJavaBridgeDispatcherHost::SendMethods,
389                  AsWeakPtr(),
390                  render_frame_host,
391                  reply_msg));
392 }
393
394 void GinJavaBridgeDispatcherHost::SendMethods(
395     RenderFrameHost* render_frame_host,
396     IPC::Message* reply_msg,
397     const std::set<std::string>& method_names) {
398   IPC::WriteParam(reply_msg, method_names);
399   SendReply(render_frame_host, reply_msg);
400 }
401
402 void GinJavaBridgeDispatcherHost::OnHasMethod(
403     RenderFrameHost* render_frame_host,
404     GinJavaBoundObject::ObjectID object_id,
405     const std::string& method_name,
406     IPC::Message* reply_msg) {
407   DCHECK_CURRENTLY_ON(BrowserThread::UI);
408   DCHECK(render_frame_host);
409   scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
410   if (!object) {
411     LOG(ERROR) << "WebView: Unknown object: " << object_id;
412     IPC::WriteParam(reply_msg, false);
413     render_frame_host->Send(reply_msg);
414     return;
415   }
416   base::PostTaskAndReplyWithResult(
417       g_background_thread.Get().message_loop()->message_loop_proxy(),
418       FROM_HERE,
419       base::Bind(&GinJavaBoundObject::HasMethod, object, method_name),
420       base::Bind(&GinJavaBridgeDispatcherHost::SendHasMethodReply,
421                  AsWeakPtr(),
422                  render_frame_host,
423                  reply_msg));
424 }
425
426 void GinJavaBridgeDispatcherHost::SendHasMethodReply(
427     RenderFrameHost* render_frame_host,
428     IPC::Message* reply_msg,
429     bool result) {
430   IPC::WriteParam(reply_msg, result);
431   SendReply(render_frame_host, reply_msg);
432 }
433
434 void GinJavaBridgeDispatcherHost::OnInvokeMethod(
435     RenderFrameHost* render_frame_host,
436     GinJavaBoundObject::ObjectID object_id,
437     const std::string& method_name,
438     const base::ListValue& arguments,
439     IPC::Message* reply_msg) {
440   DCHECK_CURRENTLY_ON(BrowserThread::UI);
441   DCHECK(render_frame_host);
442   scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
443   if (!object) {
444     LOG(ERROR) << "WebView: Unknown object: " << object_id;
445     base::ListValue result;
446     result.Append(base::Value::CreateNullValue());
447     IPC::WriteParam(reply_msg, result);
448     IPC::WriteParam(reply_msg, kGinJavaBridgeUnknownObjectId);
449     render_frame_host->Send(reply_msg);
450     return;
451   }
452   scoped_refptr<GinJavaMethodInvocationHelper> result =
453       new GinJavaMethodInvocationHelper(
454           make_scoped_ptr(new GinJavaBoundObjectDelegate(object))
455               .PassAs<GinJavaMethodInvocationHelper::ObjectDelegate>(),
456           method_name,
457           arguments);
458   result->Init(this);
459   g_background_thread.Get()
460       .message_loop()
461       ->message_loop_proxy()
462       ->PostTaskAndReply(
463           FROM_HERE,
464           base::Bind(&GinJavaMethodInvocationHelper::Invoke, result),
465           base::Bind(
466               &GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult,
467               AsWeakPtr(),
468               render_frame_host,
469               reply_msg,
470               result));
471 }
472
473 void GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult(
474     RenderFrameHost* render_frame_host,
475     IPC::Message* reply_msg,
476     scoped_refptr<GinJavaMethodInvocationHelper> result) {
477   if (result->HoldsPrimitiveResult()) {
478     IPC::WriteParam(reply_msg, result->GetPrimitiveResult());
479     IPC::WriteParam(reply_msg, result->GetInvocationError());
480     SendReply(render_frame_host, reply_msg);
481   } else {
482     ProcessMethodInvocationObjectResult(render_frame_host, reply_msg, result);
483   }
484 }
485
486 void GinJavaBridgeDispatcherHost::ProcessMethodInvocationObjectResult(
487     RenderFrameHost* render_frame_host,
488     IPC::Message* reply_msg,
489     scoped_refptr<GinJavaMethodInvocationHelper> result) {
490   DCHECK_CURRENTLY_ON(BrowserThread::UI);
491   if (!IsValidRenderFrameHost(render_frame_host)) {
492     delete reply_msg;
493     return;
494   }
495   base::ListValue wrapped_result;
496   if (!result->GetObjectResult().is_null()) {
497     GinJavaBoundObject::ObjectID returned_object_id;
498     if (FindObjectId(result->GetObjectResult(), &returned_object_id)) {
499       (*objects_.Lookup(returned_object_id))->AddHolder(render_frame_host);
500     } else {
501       returned_object_id = AddObject(result->GetObjectResult(),
502                                      result->GetSafeAnnotationClass(),
503                                      false,
504                                      render_frame_host);
505     }
506     wrapped_result.Append(
507         GinJavaBridgeValue::CreateObjectIDValue(returned_object_id).release());
508   } else {
509     wrapped_result.Append(base::Value::CreateNullValue());
510   }
511   IPC::WriteParam(reply_msg, wrapped_result);
512   IPC::WriteParam(reply_msg, result->GetInvocationError());
513   render_frame_host->Send(reply_msg);
514 }
515
516 void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
517     RenderFrameHost* render_frame_host,
518     GinJavaBoundObject::ObjectID object_id) {
519   DCHECK_CURRENTLY_ON(BrowserThread::UI);
520   DCHECK(render_frame_host);
521   if (objects_.Lookup(object_id)) {
522     GinJavaBoundObject::ObjectMap::iterator iter(&objects_);
523     while (!iter.IsAtEnd() && iter.GetCurrentKey() != object_id)
524       iter.Advance();
525     DCHECK(!iter.IsAtEnd());
526     RemoveHolder(render_frame_host, iter, 1);
527   }
528 }
529
530 }  // namespace content