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