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
31namespace content {
32
33namespace {
34// The JavaBridge needs to use a Java thread so the callback
35// will happen on a thread with a prepared Looper.
36class JavaBridgeThread : public base::android::JavaHandlerThread {
37 public:
38  JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") {
39    Start();
40  }
41  virtual ~JavaBridgeThread() {
42    Stop();
43  }
44  static bool CurrentlyOn();
45};
46
47base::LazyInstance<JavaBridgeThread> g_background_thread =
48    LAZY_INSTANCE_INITIALIZER;
49
50// static
51bool 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.
59base::StaticAtomicSequenceNumber g_next_object_id;
60
61}  // namespace
62
63GinJavaBridgeDispatcherHost::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
76GinJavaBridgeDispatcherHost::~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.
85void 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
97void 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
109void 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
124GinJavaBoundObject::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
163bool 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
180JavaObjectWeakGlobalRef 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
189JavaObjectWeakGlobalRef
190GinJavaBridgeDispatcherHost::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
208void 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
219void 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
248void 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
274void 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
286void 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
313base::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
353bool 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
374void 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
382int GinJavaBridgeDispatcherHost::GetCurrentRoutingID() const {
383  DCHECK(JavaBridgeThread::CurrentlyOn());
384  return current_routing_id_;
385}
386
387void GinJavaBridgeDispatcherHost::SetCurrentRoutingID(int32 routing_id) {
388  DCHECK(JavaBridgeThread::CurrentlyOn());
389  current_routing_id_ = routing_id;
390}
391
392scoped_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
402void 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
416void 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
429void 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
476void 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