chromium/content/browser/android/java/gin_java_bridge_dispatcher_host.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"

#include "base/android/jni_android.h"
#include "base/android/scoped_java_ref.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "build/build_config.h"
#include "content/browser/android/java/gin_java_bound_object_delegate.h"
#include "content/browser/android/java/java_bridge_thread.h"
#include "content/browser/renderer_host/agent_scheduling_group_host.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/android/gin_java_bridge_value.h"
#include "content/common/android/hash_set.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"

#if !BUILDFLAG(IS_ANDROID)
#error "JavaBridge only supports OS_ANDROID"
#endif

namespace content {

GinJavaBridgeDispatcherHost::GinJavaBridgeDispatcherHost(
    WebContents* web_contents,
    const base::android::JavaRef<jobject>& retained_object_set)
    : RefCountedDeleteOnSequence<GinJavaBridgeDispatcherHost>(
          base::SequencedTaskRunner::GetCurrentDefault()),
      WebContentsObserver(web_contents),
      retained_object_set_(base::android::AttachCurrentThread(),
                           retained_object_set),
      mojo_skip_clear_on_main_document_(
          base::FeatureList::IsEnabled(
              features::
                  kGinJavaBridgeMojoSkipClearObjectsOnMainDocumentReady)) {
  DCHECK(!retained_object_set.is_null());
}

GinJavaBridgeDispatcherHost::~GinJavaBridgeDispatcherHost() {
}

void GinJavaBridgeDispatcherHost::BindNewHostOnBackgroundThread(
    GlobalRenderFrameHostId routing_id,
    mojo::PendingReceiver<mojom::GinJavaBridgeHost> host) {
  DCHECK(JavaBridgeThread::CurrentlyOn());
  receivers_.Add(this, std::move(host), routing_id);
}

void GinJavaBridgeDispatcherHost::ClearAllReceivers() {
  DCHECK(JavaBridgeThread::CurrentlyOn());
  receivers_.set_disconnect_handler({});
  receivers_.Clear();
  object_receivers_.set_disconnect_handler({});
  object_receivers_.Clear();
}

mojom::GinJavaBridge* GinJavaBridgeDispatcherHost::GetJavaBridge(
    RenderFrameHost* frame_host,
    bool should_create) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto routing_id = frame_host->GetGlobalId();
  auto it = remotes_.find(routing_id);
  if (it == remotes_.end()) {
    if (!should_create) {
      return nullptr;
    }
    CHECK(frame_host->IsRenderFrameLive());
    auto& bound_remote = remotes_[routing_id];
    frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
        bound_remote.BindNewEndpointAndPassReceiver());
    bound_remote.set_disconnect_handler(
        base::BindOnce(&GinJavaBridgeDispatcherHost::RemoteDisconnected,
                       base::Unretained(this), routing_id));

    mojo::PendingReceiver<mojom::GinJavaBridgeHost> host_receiver;
    bound_remote->SetHost(host_receiver.InitWithNewPipeAndPassRemote());
    JavaBridgeThread::GetTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &GinJavaBridgeDispatcherHost::BindNewHostOnBackgroundThread, this,
            routing_id, std::move(host_receiver)));

    // Initialize with all the current named objects.
    for (auto& object : named_objects_) {
      bound_remote->AddNamedObject(object.first, object.second);
    }

    return bound_remote.get();
  }
  return it->second.get();
}

void GinJavaBridgeDispatcherHost::RemoteDisconnected(
    const content::GlobalRenderFrameHostId& routing_id) {
  remotes_.erase(routing_id);

  auto* frame_host = RenderFrameHost::FromID(routing_id);
  // If the RenderHost is still alive try to reconnect.
  if (frame_host->IsRenderFrameLive()) {
    LOG(ERROR) << "Reconnecting to RenderFrame";
    GetJavaBridge(frame_host, true);
  }
}

WebContentsImpl* GinJavaBridgeDispatcherHost::web_contents() const {
  return static_cast<WebContentsImpl*>(WebContentsObserver::web_contents());
}

void GinJavaBridgeDispatcherHost::RenderFrameCreated(
    RenderFrameHost* render_frame_host) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (named_objects_.empty()) {
    return;
  }

  GetJavaBridge(render_frame_host, /*should_create=*/true);
  // Named objects will be sent in GetJavaBridge when it is first connected.
}

void GinJavaBridgeDispatcherHost::RenderFrameDeleted(
    RenderFrameHost* render_frame_host) {
  remotes_.erase(render_frame_host->GetGlobalId());
}

void GinJavaBridgeDispatcherHost::WebContentsDestroyed() {
  JavaBridgeThread::GetTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(&GinJavaBridgeDispatcherHost::ClearAllReceivers, this));
}

GinJavaBoundObject::ObjectID GinJavaBridgeDispatcherHost::AddObject(
    const base::android::JavaRef<jobject>& object,
    const base::android::JavaRef<jclass>& safe_annotation_clazz,
    std::optional<GlobalRenderFrameHostId> holder) {
  // Can be called on any thread. Calls come from the UI thread via
  // AddNamedObject, and from the background thread, when injected Java
  // object's method returns a Java object.
  JNIEnv* env = base::android::AttachCurrentThread();
  JavaObjectWeakGlobalRef ref(env, object);
  scoped_refptr<GinJavaBoundObject> new_object =
      !holder ? GinJavaBoundObject::CreateNamed(ref, safe_annotation_clazz)
              : GinJavaBoundObject::CreateTransient(ref, safe_annotation_clazz,
                                                    holder.value());
  GinJavaBoundObject::ObjectID object_id;
  {
    base::AutoLock locker(objects_lock_);
    object_id = next_object_id_++;
    objects_[object_id] = new_object;
  }
#if DCHECK_IS_ON()
  {
    GinJavaBoundObject::ObjectID added_object_id;
    DCHECK(FindObjectId(object, &added_object_id));
    DCHECK_EQ(object_id, added_object_id);
  }
#endif  // DCHECK_IS_ON()
  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
        retained_object_set_.get(env);
  if (!retained_object_set.is_null()) {
    base::AutoLock locker(objects_lock_);
    JNI_Java_HashSet_add(env, retained_object_set, object);
  }
  return object_id;
}

bool GinJavaBridgeDispatcherHost::FindObjectId(
    const base::android::JavaRef<jobject>& object,
    GinJavaBoundObject::ObjectID* object_id) {
  // Can be called on any thread.
  JNIEnv* env = base::android::AttachCurrentThread();
  base::AutoLock locker(objects_lock_);
  for (const auto& pair : objects_) {
    if (env->IsSameObject(
            object.obj(),
            pair.second->GetLocalRef(env).obj())) {
      *object_id = pair.first;
      return true;
    }
  }
  return false;
}

JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::GetObjectWeakRef(
    GinJavaBoundObject::ObjectID object_id) {
  scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
  if (object.get())
    return object->GetWeakRef();
  else
    return JavaObjectWeakGlobalRef();
}

JavaObjectWeakGlobalRef GinJavaBridgeDispatcherHost::RemoveHolderLocked(
    const GlobalRenderFrameHostId& holder,
    ObjectMap::iterator* iter_ptr) {
  objects_lock_.AssertAcquired();
  JavaObjectWeakGlobalRef result;
  scoped_refptr<GinJavaBoundObject> object((*iter_ptr)->second);
  if (!object->IsNamed()) {
    object->RemoveHolder(holder);
    if (!object->HasHolders()) {
      result = object->GetWeakRef();
      objects_.erase(*iter_ptr);
    }
  }
  return result;
}

void GinJavaBridgeDispatcherHost::RemoveFromRetainedObjectSetLocked(
    const JavaObjectWeakGlobalRef& ref) {
  objects_lock_.AssertAcquired();
  JNIEnv* env = base::android::AttachCurrentThread();
  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
      retained_object_set_.get(env);
  if (!retained_object_set.is_null()) {
    JNI_Java_HashSet_remove(env, retained_object_set, ref.get(env));
  }
}

void GinJavaBridgeDispatcherHost::AddNamedObject(
    const std::string& name,
    const base::android::JavaRef<jobject>& object,
    const base::android::JavaRef<jclass>& safe_annotation_clazz) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  GinJavaBoundObject::ObjectID object_id;
  NamedObjectMap::iterator iter = named_objects_.find(name);
  bool existing_object = FindObjectId(object, &object_id);
  if (existing_object && iter != named_objects_.end() &&
      iter->second == object_id) {
    // Nothing to do.
    return;
  }
  if (iter != named_objects_.end()) {
    RemoveNamedObject(iter->first);
  }
  if (existing_object) {
    base::AutoLock locker(objects_lock_);
    objects_[object_id]->AddName();
  } else {
    object_id = AddObject(object, safe_annotation_clazz, std::nullopt);
  }
  named_objects_[name] = object_id;

  web_contents()
      ->GetPrimaryMainFrame()
      ->ForEachRenderFrameHostIncludingSpeculative(
          [&name, object_id, this](RenderFrameHostImpl* render_frame_host) {
            if (!render_frame_host->IsRenderFrameLive()) {
              return;
            }
            GetJavaBridge(render_frame_host, /*should_create=*/true)
                ->AddNamedObject(name, object_id);
          });
}

void GinJavaBridgeDispatcherHost::RemoveNamedObject(
    const std::string& name) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  NamedObjectMap::iterator iter = named_objects_.find(name);
  if (iter == named_objects_.end())
    return;

  // |name| may come from |named_objects_|. Make a copy of name so that if
  // |name| is from |named_objects_| it'll be valid after the remove below.
  const std::string copied_name(name);

  {
    base::AutoLock locker(objects_lock_);
    objects_[iter->second]->RemoveName();
  }
  named_objects_.erase(iter);

  web_contents()
      ->GetPrimaryMainFrame()
      ->ForEachRenderFrameHostIncludingSpeculative(
          [&copied_name, this](RenderFrameHostImpl* render_frame_host) {
            if (!render_frame_host->IsRenderFrameLive()) {
              return;
            }

            auto* bridge =
                GetJavaBridge(render_frame_host, /*should_create=*/false);
            if (!bridge) {
              return;
            }
            bridge->RemoveNamedObject(copied_name);
          });
}

void GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection(bool allow) {
  if (!JavaBridgeThread::CurrentlyOn()) {
    JavaBridgeThread::GetTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &GinJavaBridgeDispatcherHost::SetAllowObjectContentsInspection,
            this, allow));
    return;
  }
  allow_object_contents_inspection_ = allow;
}

void GinJavaBridgeDispatcherHost::PrimaryMainDocumentElementAvailable() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // If we are skipping clearing on main document return early.
  if (mojo_skip_clear_on_main_document_) {
    return;
  }
  // Called when the window object has been cleared in the main frame.
  // That means, all sub-frames have also been cleared, so only named
  // objects survived.
  JNIEnv* env = base::android::AttachCurrentThread();
  base::android::ScopedJavaLocalRef<jobject> retained_object_set =
      retained_object_set_.get(env);
  base::AutoLock locker(objects_lock_);
  if (!retained_object_set.is_null()) {
    JNI_Java_HashSet_clear(env, retained_object_set);
  }
  auto iter = objects_.begin();
  while (iter != objects_.end()) {
    if (iter->second->IsNamed()) {
      if (!retained_object_set.is_null()) {
        JNI_Java_HashSet_add(
            env, retained_object_set, iter->second->GetLocalRef(env));
      }
      ++iter;
    } else {
      iter = objects_.erase(iter);
    }
  }
}

scoped_refptr<GinJavaBoundObject> GinJavaBridgeDispatcherHost::FindObject(
    GinJavaBoundObject::ObjectID object_id) {
  // Can be called on any thread.
  base::AutoLock locker(objects_lock_);
  auto iter = objects_.find(object_id);
  if (iter != objects_.end())
    return iter->second;
  LOG(ERROR) << "WebView: Unknown object: " << object_id;
  return nullptr;
}

void GinJavaBridgeDispatcherHost::OnGetMethods(
    GinJavaBoundObject::ObjectID object_id,
    std::vector<std::string>* returned_method_names) {
  DCHECK(JavaBridgeThread::CurrentlyOn());
  if (!allow_object_contents_inspection_)
    return;
  scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
  if (object.get()) {
    std::set<std::string> result = object->GetMethodNames();
    *returned_method_names = {result.begin(), result.end()};
  }
}

void GinJavaBridgeDispatcherHost::OnHasMethod(
    GinJavaBoundObject::ObjectID object_id,
    const std::string& method_name,
    bool* result) {
  DCHECK(JavaBridgeThread::CurrentlyOn());
  scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
  if (object.get())
    *result = object->HasMethod(method_name);
}

void GinJavaBridgeDispatcherHost::OnInvokeMethod(
    const GlobalRenderFrameHostId& routing_id,
    GinJavaBoundObject::ObjectID object_id,
    const std::string& method_name,
    const base::Value::List& arguments,
    base::Value::List* wrapped_result,
    content::mojom::GinJavaBridgeError* error_code) {
  DCHECK(JavaBridgeThread::CurrentlyOn());
  DCHECK(routing_id);
  scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
  if (!object.get()) {
    wrapped_result->Append(base::Value());
    *error_code = mojom::GinJavaBridgeError::kGinJavaBridgeUnknownObjectId;
    return;
  }
  auto result = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::make_unique<GinJavaBoundObjectDelegate>(object), method_name,
      arguments);
  result->Init(this);
  result->Invoke();
  *error_code = result->GetInvocationError();
  if (result->HoldsPrimitiveResult()) {
    *wrapped_result = result->GetPrimitiveResult().Clone();
  } else if (!result->GetObjectResult().is_null()) {
    GinJavaBoundObject::ObjectID returned_object_id;
    if (FindObjectId(result->GetObjectResult(), &returned_object_id)) {
      base::AutoLock locker(objects_lock_);
      objects_[returned_object_id]->AddHolder(routing_id);
    } else {
      returned_object_id = AddObject(result->GetObjectResult(),
                                     result->GetSafeAnnotationClass(),
                                     routing_id);
    }
    wrapped_result->Append(base::Value::FromUniquePtrValue(
        GinJavaBridgeValue::CreateObjectIDValue(returned_object_id)));
  } else {
    wrapped_result->Append(base::Value());
  }
}

void GinJavaBridgeDispatcherHost::DeleteObjectForRouteLocked(
    const GlobalRenderFrameHostId& routing_id,
    GinJavaBoundObject::ObjectID object_id) {
  objects_lock_.AssertAcquired();
  auto iter = objects_.find(object_id);
  if (iter == objects_.end())
    return;
  JavaObjectWeakGlobalRef ref = RemoveHolderLocked(routing_id, &iter);
  if (!ref.is_uninitialized()) {
    RemoveFromRetainedObjectSetLocked(ref);
  }
}

void GinJavaBridgeDispatcherHost::OnObjectWrapperDeleted(
    const GlobalRenderFrameHostId& routing_id,
    GinJavaBoundObject::ObjectID object_id) {
  CHECK(JavaBridgeThread::CurrentlyOn());
  DCHECK(routing_id);
  base::AutoLock locker(objects_lock_);
  DeleteObjectForRouteLocked(routing_id, object_id);
}

void GinJavaBridgeDispatcherHost::GetObject(
    int32_t object_id,
    mojo::PendingReceiver<mojom::GinJavaBridgeRemoteObject> receiver) {
  CHECK(JavaBridgeThread::CurrentlyOn());
  if (object_receivers_.empty()) {
    object_receivers_.set_disconnect_handler(base::BindRepeating(
        &GinJavaBridgeDispatcherHost::ObjectDisconnected, this));
  }
  object_receivers_.Add(
      this, std::move(receiver),
      std::make_pair(receivers_.current_context(), object_id));

  // Add a holder reference because the object may become unnamed
  // yet the renderer can still hold a reference to it. See
  // crbug.com/333171288.
  scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
  if (object.get()) {
    object->AddHolder(receivers_.current_context());
  }
}

void GinJavaBridgeDispatcherHost::ObjectWrapperDeleted(int32_t object_id) {
  CHECK(JavaBridgeThread::CurrentlyOn());
  base::AutoLock locker(objects_lock_);
  DeleteObjectForRouteLocked(receivers_.current_context(), object_id);
}

void GinJavaBridgeDispatcherHost::GetMethods(GetMethodsCallback callback) {
  CHECK(JavaBridgeThread::CurrentlyOn());
  if (!allow_object_contents_inspection_) {
    std::move(callback).Run({});
    return;
  }
  scoped_refptr<GinJavaBoundObject> object =
      FindObject(object_receivers_.current_context().second);
  if (object.get()) {
    std::set<std::string> result = object->GetMethodNames();
    std::move(callback).Run({result.begin(), result.end()});
  } else {
    std::move(callback).Run({});
  }
}

void GinJavaBridgeDispatcherHost::HasMethod(const std::string& method_name,
                                            HasMethodCallback callback) {
  CHECK(JavaBridgeThread::CurrentlyOn());
  scoped_refptr<GinJavaBoundObject> object =
      FindObject(object_receivers_.current_context().second);
  bool result = false;
  if (object.get()) {
    result = object->HasMethod(method_name);
  }
  std::move(callback).Run(result);
}

void GinJavaBridgeDispatcherHost::InvokeMethod(const std::string& method_name,
                                               base::Value::List arguments,
                                               InvokeMethodCallback callback) {
  CHECK(JavaBridgeThread::CurrentlyOn());
  base::Value::List wrapped_result;
  content::mojom::GinJavaBridgeError error_code;
  OnInvokeMethod(object_receivers_.current_context().first,
                 object_receivers_.current_context().second, method_name,
                 arguments, &wrapped_result, &error_code);
  std::move(callback).Run(error_code, std::move(wrapped_result));
}

void GinJavaBridgeDispatcherHost::ObjectDisconnected() {
  CHECK(JavaBridgeThread::CurrentlyOn());
  OnObjectWrapperDeleted(object_receivers_.current_context().first,
                         object_receivers_.current_context().second);
}

}  // namespace content