gin_java_bridge_dispatcher_host.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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
28namespace content {
29
30namespace {
31// The JavaBridge needs to use a Java thread so the callback
32// will happen on a thread with a prepared Looper.
33class JavaBridgeThread : public base::android::JavaHandlerThread {
34 public:
35  JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
36    Start();
37  }
38  virtual ~JavaBridgeThread() {
39    Stop();
40  }
41};
42
43base::LazyInstance<JavaBridgeThread> g_background_thread =
44    LAZY_INSTANCE_INITIALIZER;
45
46}  // namespace
47
48GinJavaBridgeDispatcherHost::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
58GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
59}
60
61void 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
71void GinJavaBridgeDispatcherHost::RenderFrameDeleted(
72    RenderFrameHost* render_frame_host) {
73  RemoveHolder(render_frame_host,
74               GinJavaBoundObject::ObjectMap::iterator(&objects_),
75               objects_.size());
76}
77
78GinJavaBoundObject::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
110bool 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
126JavaObjectWeakGlobalRef 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
136void 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
161void 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
188void 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  scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(iter->second));
196  named_objects_.erase(iter);
197  object->RemoveName();
198
199  // Not erasing from the objects map, as we can still receive method
200  // invocation requests for this object, and they should work until the
201  // java object is gone.
202  if (!object->IsNamed()) {
203    JNIEnv* env = base::android::AttachCurrentThread();
204    base::android::ScopedJavaLocalRef<jobject> retained_object_set =
205        retained_object_set_.get(env);
206    if (!retained_object_set.is_null()) {
207      JNI_Java_HashSet_remove(
208          env, retained_object_set, object->GetLocalRef(env));
209    }
210  }
211
212  web_contents()->SendToAllFrames(
213      new GinJavaBridgeMsg_RemoveNamedObject(MSG_ROUTING_NONE, name));
214}
215
216void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) {
217  allow_object_contents_inspection_ = allow;
218}
219
220void GinJavaBridgeDispatcherHost::DocumentAvailableInMainFrame() {
221  DCHECK_CURRENTLY_ON(BrowserThread::UI);
222  // Called when the window object has been cleared in the main frame.
223  // That means, all sub-frames have also been cleared, so only named
224  // objects survived.
225  JNIEnv* env = base::android::AttachCurrentThread();
226  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
227      retained_object_set_.get(env);
228  if (!retained_object_set.is_null()) {
229    JNI_Java_HashSet_clear(env, retained_object_set);
230  }
231
232  // We also need to add back the named objects we have so far as they
233  // should survive navigations.
234  for (GinJavaBoundObject::ObjectMap::iterator it(&objects_); !it.IsAtEnd();
235       it.Advance()) {
236    scoped_refptr<GinJavaBoundObject> object(*it.GetCurrentValue());
237    if (object->IsNamed()) {
238      if (!retained_object_set.is_null()) {
239        JNI_Java_HashSet_add(
240            env, retained_object_set, object->GetLocalRef(env));
241      }
242    } else {
243      objects_.Remove(it.GetCurrentKey());
244    }
245  }
246}
247
248namespace {
249
250// TODO(mnaganov): Implement passing of a parameter into sync message handlers.
251class MessageForwarder : public IPC::Sender {
252 public:
253  MessageForwarder(GinJavaBridgeDispatcherHost* gjbdh,
254                   RenderFrameHost* render_frame_host)
255      : gjbdh_(gjbdh), render_frame_host_(render_frame_host) {}
256  void OnGetMethods(GinJavaBoundObject::ObjectID object_id,
257                    IPC::Message* reply_msg) {
258    gjbdh_->OnGetMethods(render_frame_host_,
259                         object_id,
260                         reply_msg);
261  }
262  void OnHasMethod(GinJavaBoundObject::ObjectID object_id,
263                   const std::string& method_name,
264                   IPC::Message* reply_msg) {
265    gjbdh_->OnHasMethod(render_frame_host_,
266                        object_id,
267                        method_name,
268                        reply_msg);
269  }
270  void OnInvokeMethod(GinJavaBoundObject::ObjectID object_id,
271                      const std::string& method_name,
272                      const base::ListValue& arguments,
273                      IPC::Message* reply_msg) {
274    gjbdh_->OnInvokeMethod(render_frame_host_,
275                           object_id,
276                           method_name,
277                           arguments,
278                           reply_msg);
279  }
280  virtual bool Send(IPC::Message* msg) OVERRIDE {
281    NOTREACHED();
282    return false;
283  }
284 private:
285  GinJavaBridgeDispatcherHost* gjbdh_;
286  RenderFrameHost* render_frame_host_;
287};
288
289}
290
291bool GinJavaBridgeDispatcherHost::OnMessageReceived(
292    const IPC::Message& message,
293    RenderFrameHost* render_frame_host) {
294  DCHECK(render_frame_host);
295  bool handled = true;
296  MessageForwarder forwarder(this, render_frame_host);
297  IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(GinJavaBridgeDispatcherHost, message,
298                                   render_frame_host)
299    IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_GetMethods,
300                                    &forwarder,
301                                    MessageForwarder::OnGetMethods)
302    IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_HasMethod,
303                                    &forwarder,
304                                    MessageForwarder::OnHasMethod)
305    IPC_MESSAGE_FORWARD_DELAY_REPLY(GinJavaBridgeHostMsg_InvokeMethod,
306                                    &forwarder,
307                                    MessageForwarder::OnInvokeMethod)
308    IPC_MESSAGE_HANDLER(GinJavaBridgeHostMsg_ObjectWrapperDeleted,
309                        OnObjectWrapperDeleted)
310    IPC_MESSAGE_UNHANDLED(handled = false)
311  IPC_END_MESSAGE_MAP()
312  return handled;
313}
314
315namespace {
316
317class IsValidRenderFrameHostHelper
318    : public base::RefCounted<IsValidRenderFrameHostHelper> {
319 public:
320  explicit IsValidRenderFrameHostHelper(RenderFrameHost* rfh_to_match)
321      : rfh_to_match_(rfh_to_match), rfh_found_(false) {}
322
323  bool rfh_found() { return rfh_found_; }
324
325  void OnFrame(RenderFrameHost* rfh) {
326    if (rfh_to_match_ == rfh) rfh_found_ = true;
327  }
328
329 private:
330  friend class base::RefCounted<IsValidRenderFrameHostHelper>;
331
332  ~IsValidRenderFrameHostHelper() {}
333
334  RenderFrameHost* rfh_to_match_;
335  bool rfh_found_;
336
337  DISALLOW_COPY_AND_ASSIGN(IsValidRenderFrameHostHelper);
338};
339
340}  // namespace
341
342bool GinJavaBridgeDispatcherHost::IsValidRenderFrameHost(
343    RenderFrameHost* render_frame_host) {
344  scoped_refptr<IsValidRenderFrameHostHelper> helper =
345      new IsValidRenderFrameHostHelper(render_frame_host);
346  web_contents()->ForEachFrame(
347      base::Bind(&IsValidRenderFrameHostHelper::OnFrame, helper));
348  return helper->rfh_found();
349}
350
351void GinJavaBridgeDispatcherHost::SendReply(
352    RenderFrameHost* render_frame_host,
353    IPC::Message* reply_msg) {
354  DCHECK_CURRENTLY_ON(BrowserThread::UI);
355  if (IsValidRenderFrameHost(render_frame_host)) {
356    render_frame_host->Send(reply_msg);
357  } else {
358    delete reply_msg;
359  }
360}
361
362void GinJavaBridgeDispatcherHost::OnGetMethods(
363    RenderFrameHost* render_frame_host,
364    GinJavaBoundObject::ObjectID object_id,
365    IPC::Message* reply_msg) {
366  DCHECK_CURRENTLY_ON(BrowserThread::UI);
367  DCHECK(render_frame_host);
368  if (!allow_object_contents_inspection_) {
369    IPC::WriteParam(reply_msg, std::set<std::string>());
370    render_frame_host->Send(reply_msg);
371    return;
372  }
373  scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
374  if (!object) {
375    LOG(ERROR) << "WebView: Unknown object: " << object_id;
376    IPC::WriteParam(reply_msg, std::set<std::string>());
377    render_frame_host->Send(reply_msg);
378    return;
379  }
380  base::PostTaskAndReplyWithResult(
381      g_background_thread.Get().message_loop()->message_loop_proxy(),
382      FROM_HERE,
383      base::Bind(&GinJavaBoundObject::GetMethodNames, object),
384      base::Bind(&GinJavaBridgeDispatcherHost::SendMethods,
385                 AsWeakPtr(),
386                 render_frame_host,
387                 reply_msg));
388}
389
390void GinJavaBridgeDispatcherHost::SendMethods(
391    RenderFrameHost* render_frame_host,
392    IPC::Message* reply_msg,
393    const std::set<std::string>& method_names) {
394  IPC::WriteParam(reply_msg, method_names);
395  SendReply(render_frame_host, reply_msg);
396}
397
398void GinJavaBridgeDispatcherHost::OnHasMethod(
399    RenderFrameHost* render_frame_host,
400    GinJavaBoundObject::ObjectID object_id,
401    const std::string& method_name,
402    IPC::Message* reply_msg) {
403  DCHECK_CURRENTLY_ON(BrowserThread::UI);
404  DCHECK(render_frame_host);
405  scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
406  if (!object) {
407    LOG(ERROR) << "WebView: Unknown object: " << object_id;
408    IPC::WriteParam(reply_msg, false);
409    render_frame_host->Send(reply_msg);
410    return;
411  }
412  base::PostTaskAndReplyWithResult(
413      g_background_thread.Get().message_loop()->message_loop_proxy(),
414      FROM_HERE,
415      base::Bind(&GinJavaBoundObject::HasMethod, object, method_name),
416      base::Bind(&GinJavaBridgeDispatcherHost::SendHasMethodReply,
417                 AsWeakPtr(),
418                 render_frame_host,
419                 reply_msg));
420}
421
422void GinJavaBridgeDispatcherHost::SendHasMethodReply(
423    RenderFrameHost* render_frame_host,
424    IPC::Message* reply_msg,
425    bool result) {
426  IPC::WriteParam(reply_msg, result);
427  SendReply(render_frame_host, reply_msg);
428}
429
430void GinJavaBridgeDispatcherHost::OnInvokeMethod(
431    RenderFrameHost* render_frame_host,
432    GinJavaBoundObject::ObjectID object_id,
433    const std::string& method_name,
434    const base::ListValue& arguments,
435    IPC::Message* reply_msg) {
436  DCHECK_CURRENTLY_ON(BrowserThread::UI);
437  DCHECK(render_frame_host);
438  scoped_refptr<GinJavaBoundObject> object(*objects_.Lookup(object_id));
439  if (!object) {
440    LOG(ERROR) << "WebView: Unknown object: " << object_id;
441    base::ListValue result;
442    result.Append(base::Value::CreateNullValue());
443    IPC::WriteParam(reply_msg, result);
444    IPC::WriteParam(reply_msg, kGinJavaBridgeUnknownObjectId);
445    render_frame_host->Send(reply_msg);
446    return;
447  }
448  scoped_refptr<GinJavaMethodInvocationHelper> result =
449      new GinJavaMethodInvocationHelper(
450          make_scoped_ptr(new GinJavaBoundObjectDelegate(object))
451              .PassAs<GinJavaMethodInvocationHelper::ObjectDelegate>(),
452          method_name,
453          arguments);
454  result->Init(this);
455  g_background_thread.Get()
456      .message_loop()
457      ->message_loop_proxy()
458      ->PostTaskAndReply(
459          FROM_HERE,
460          base::Bind(&GinJavaMethodInvocationHelper::Invoke, result),
461          base::Bind(
462              &GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult,
463              AsWeakPtr(),
464              render_frame_host,
465              reply_msg,
466              result));
467}
468
469void GinJavaBridgeDispatcherHost::ProcessMethodInvocationResult(
470    RenderFrameHost* render_frame_host,
471    IPC::Message* reply_msg,
472    scoped_refptr<GinJavaMethodInvocationHelper> result) {
473  if (result->HoldsPrimitiveResult()) {
474    IPC::WriteParam(reply_msg, result->GetPrimitiveResult());
475    IPC::WriteParam(reply_msg, result->GetInvocationError());
476    SendReply(render_frame_host, reply_msg);
477  } else {
478    ProcessMethodInvocationObjectResult(render_frame_host, reply_msg, result);
479  }
480}
481
482void GinJavaBridgeDispatcherHost::ProcessMethodInvocationObjectResult(
483    RenderFrameHost* render_frame_host,
484    IPC::Message* reply_msg,
485    scoped_refptr<GinJavaMethodInvocationHelper> result) {
486  DCHECK_CURRENTLY_ON(BrowserThread::UI);
487  if (!IsValidRenderFrameHost(render_frame_host)) {
488    delete reply_msg;
489    return;
490  }
491  base::ListValue wrapped_result;
492  if (!result->GetObjectResult().is_null()) {
493    GinJavaBoundObject::ObjectID returned_object_id;
494    if (FindObjectId(result->GetObjectResult(), &returned_object_id)) {
495      (*objects_.Lookup(returned_object_id))->AddHolder(render_frame_host);
496    } else {
497      returned_object_id = AddObject(result->GetObjectResult(),
498                                     result->GetSafeAnnotationClass(),
499                                     false,
500                                     render_frame_host);
501    }
502    wrapped_result.Append(
503        GinJavaBridgeValue::CreateObjectIDValue(returned_object_id).release());
504  } else {
505    wrapped_result.Append(base::Value::CreateNullValue());
506  }
507  IPC::WriteParam(reply_msg, wrapped_result);
508  IPC::WriteParam(reply_msg, result->GetInvocationError());
509  render_frame_host->Send(reply_msg);
510}
511
512void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
513    RenderFrameHost* render_frame_host,
514    GinJavaBoundObject::ObjectID object_id) {
515  DCHECK_CURRENTLY_ON(BrowserThread::UI);
516  DCHECK(render_frame_host);
517  if (objects_.Lookup(object_id)) {
518    GinJavaBoundObject::ObjectMap::iterator iter(&objects_);
519    while (!iter.IsAtEnd() && iter.GetCurrentKey() != object_id)
520      iter.Advance();
521    DCHECK(!iter.IsAtEnd());
522    RemoveHolder(render_frame_host, iter, 1);
523  }
524}
525
526}  // namespace content
527