// Copyright 2017 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/renderer_host/render_frame_host_android.h"

#include <jni.h>

#include <utility>

#include "base/android/callback_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/unguessable_token_android.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/bad_message.h"
#include "content/browser/closewatcher/close_listener_host.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom.h"
#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
#include "url/android/gurl_android.h"
#include "url/origin.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/RenderFrameHostImpl_jni.h"

using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;

namespace content {

namespace {

void JavaScriptResultCallback(
    const base::android::ScopedJavaGlobalRef<jobject>& callback,
    base::Value result) {
  JNIEnv* env = base::android::AttachCurrentThread();
  std::string json = base::WriteJson(result).value_or("");
  base::android::ScopedJavaLocalRef<jstring> j_json =
      ConvertUTF8ToJavaString(env, json);
  Java_RenderFrameHostImpl_onEvaluateJavaScriptResult(env, j_json, callback);
}

}  // namespace

// static
RenderFrameHost* RenderFrameHost::FromJavaRenderFrameHost(
    const JavaRef<jobject>& jrender_frame_host_android) {
  CHECK_CURRENTLY_ON(BrowserThread::UI);
  if (jrender_frame_host_android.is_null())
    return nullptr;

  RenderFrameHostAndroid* render_frame_host_android =
      reinterpret_cast<RenderFrameHostAndroid*>(
          Java_RenderFrameHostImpl_getNativePointer(
              AttachCurrentThread(), jrender_frame_host_android));
  if (!render_frame_host_android)
    return nullptr;
  return render_frame_host_android->render_frame_host();
}

RenderFrameHostAndroid::RenderFrameHostAndroid(
    RenderFrameHostImpl* render_frame_host)
    : render_frame_host_(render_frame_host) {}

RenderFrameHostAndroid::~RenderFrameHostAndroid() {
  // Avoid unnecessarily creating the java object from the destructor.
  if (obj_.is_uninitialized())
    return;

  ScopedJavaLocalRef<jobject> jobj = GetJavaObject();
  if (!jobj.is_null()) {
    Java_RenderFrameHostImpl_clearNativePtr(AttachCurrentThread(), jobj);
    obj_.reset();
  }
}

base::android::ScopedJavaLocalRef<jobject>
RenderFrameHostAndroid::GetJavaObject() {
  JNIEnv* env = base::android::AttachCurrentThread();
  if (obj_.is_uninitialized()) {
    const bool is_incognito = render_frame_host_->GetSiteInstance()
                                  ->GetBrowserContext()
                                  ->IsOffTheRecord();
    const GlobalRenderFrameHostId rfh_id = render_frame_host_->GetGlobalId();
    // TODO(crbug.com/379869738) Remove GetUnsafeValue.
    ScopedJavaLocalRef<jobject> local_ref = Java_RenderFrameHostImpl_create(
        env, reinterpret_cast<intptr_t>(this),
        render_frame_host_->delegate()->GetJavaRenderFrameHostDelegate(),
        is_incognito, rfh_id.child_id.GetUnsafeValue(),
        rfh_id.frame_routing_id);
    obj_ = JavaObjectWeakGlobalRef(env, local_ref);
    return local_ref;
  }
  return obj_.get(env);
}

ScopedJavaLocalRef<jobject> RenderFrameHostAndroid::GetLastCommittedURL(
    JNIEnv* env) const {
  return url::GURLAndroid::FromNativeGURL(
      env, render_frame_host_->GetLastCommittedURL());
}

ScopedJavaLocalRef<jobject> RenderFrameHostAndroid::GetLastCommittedOrigin(
    JNIEnv* env) {
  return render_frame_host_->GetLastCommittedOrigin().ToJavaObject(env);
}

ScopedJavaLocalRef<jobject> RenderFrameHostAndroid::GetMainFrame(JNIEnv* env) {
  return render_frame_host_->GetMainFrame()->GetJavaRenderFrameHost();
}

bool RenderFrameHostAndroid::IsOutermostMainFrame(JNIEnv* env) const {
  return render_frame_host_->IsOutermostMainFrame();
}

void RenderFrameHostAndroid::GetCanonicalUrlForSharing(
    base::OnceCallback<void(const std::optional<GURL>&)> callback) const {
  render_frame_host_->GetCanonicalUrl(std::move(callback));
}

std::vector<ScopedJavaLocalRef<jobject>>
RenderFrameHostAndroid::GetAllRenderFrameHosts(JNIEnv* env) const {
  std::vector<ScopedJavaLocalRef<jobject>> ret;
  render_frame_host_->ForEachRenderFrameHostImpl(
      [&ret](RenderFrameHostImpl* rfh) {
        ret.push_back(rfh->GetJavaRenderFrameHost());
      });
  return ret;
}

bool RenderFrameHostAndroid::IsFeatureEnabled(JNIEnv* env,
                                              int32_t feature) const {
  return render_frame_host_->IsFeatureEnabled(
      static_cast<network::mojom::PermissionsPolicyFeature>(feature));
}

base::UnguessableToken RenderFrameHostAndroid::GetAndroidOverlayRoutingToken(
    JNIEnv* env) const {
  return render_frame_host_->GetOverlayRoutingToken();
}

void RenderFrameHostAndroid::NotifyUserActivation(JNIEnv* env) {
  render_frame_host_->GetAssociatedLocalFrame()->NotifyUserActivation(
      blink::mojom::UserActivationNotificationType::kVoiceSearch);
}

void RenderFrameHostAndroid::NotifyWebAuthnAssertionRequestSucceeded(
    JNIEnv* env) {
  render_frame_host_->WebAuthnAssertionRequestSucceeded();
}

bool RenderFrameHostAndroid::IsCloseWatcherActive(JNIEnv* env) const {
  auto* close_listener_host =
      CloseListenerHost::GetForCurrentDocument(render_frame_host_);
  return close_listener_host && close_listener_host->IsActive();
}

bool RenderFrameHostAndroid::SignalCloseWatcherIfActive(JNIEnv* env) const {
  auto* close_listener_host =
      CloseListenerHost::GetForCurrentDocument(render_frame_host_);
  return close_listener_host && close_listener_host->SignalIfActive();
}

bool RenderFrameHostAndroid::IsRenderFrameLive(JNIEnv* env) const {
  return render_frame_host_->IsRenderFrameLive();
}

void RenderFrameHostAndroid::GetInterfaceToRendererFrame(
    JNIEnv* env,
    const base::android::JavaRef<jstring>& interface_name,
    int64_t message_pipe_raw_handle) const {
  // TODO(https://crbug.com/526542746): CHECK-exclusion: Convert to CHECK once
  // we are sure this isn't hit.
  DCHECK(render_frame_host_->IsRenderFrameLive());
  render_frame_host_->GetRemoteInterfaces()->GetInterfaceByName(
      ConvertJavaStringToUTF8(env, interface_name),
      mojo::ScopedMessagePipeHandle(
          mojo::MessagePipeHandle(message_pipe_raw_handle)));
}

void RenderFrameHostAndroid::TerminateRendererDueToBadMessage(
    JNIEnv* env,
    int32_t reason) const {
  CHECK_LT(reason, bad_message::BAD_MESSAGE_MAX);
  ReceivedBadMessage(render_frame_host_->GetProcess(),
                     static_cast<bad_message::BadMessageReason>(reason));
}

bool RenderFrameHostAndroid::IsProcessBlocked(JNIEnv* env) const {
  return render_frame_host_->GetProcess()->IsBlocked();
}

void RenderFrameHostAndroid::PerformGetAssertionWebAuthSecurityChecks(
    JNIEnv* env,
    const base::android::JavaRef<jstring>& relying_party_id,
    const base::android::JavaRef<jobject>& effective_origin,
    bool is_payment_credential_get_assertion,
    const base::android::JavaRef<jobject>&
        remote_desktop_client_override_origin,
    const base::android::JavaRef<jstring>& app_id,
    const base::android::JavaRef<jobject>& callback) const {
  url::Origin origin = url::Origin::FromJavaObject(env, effective_origin);
  std::optional<url::Origin> remote_desktop_client_override_origin_optional;
  if (!remote_desktop_client_override_origin.is_null()) {
    remote_desktop_client_override_origin_optional =
        url::Origin::FromJavaObject(env, remote_desktop_client_override_origin);
  }
  std::optional<std::string> app_id_optional;
  if (!app_id.is_null()) {
    app_id_optional = ConvertJavaStringToUTF8(env, app_id);
  }
  render_frame_host_->PerformGetAssertionWebAuthSecurityChecks(
      ConvertJavaStringToUTF8(env, relying_party_id), origin,
      is_payment_credential_get_assertion,
      remote_desktop_client_override_origin_optional, app_id_optional,
      base::BindOnce(
          [](base::android::ScopedJavaGlobalRef<jobject> callback,
             blink::mojom::AuthenticatorStatus status, bool is_cross_origin) {
            base::android::RunObjectCallbackAndroid(
                callback,
                Java_RenderFrameHostImpl_createWebAuthSecurityChecksResults(
                    base::android::AttachCurrentThread(),
                    static_cast<int32_t>(status), is_cross_origin));
          },
          base::android::ScopedJavaGlobalRef<jobject>(callback)));
}

void RenderFrameHostAndroid::PerformMakeCredentialWebAuthSecurityChecks(
    JNIEnv* env,
    const base::android::JavaRef<jstring>& relying_party_id,
    const base::android::JavaRef<jobject>& effective_origin,
    bool is_payment_credential_creation,
    const base::android::JavaRef<jobject>&
        remote_desktop_client_override_origin,
    const base::android::JavaRef<jstring>& app_id,
    const base::android::JavaRef<jobject>& callback) const {
  url::Origin origin = url::Origin::FromJavaObject(env, effective_origin);
  std::optional<url::Origin> remote_desktop_client_override_origin_optional;
  if (!remote_desktop_client_override_origin.is_null()) {
    remote_desktop_client_override_origin_optional =
        url::Origin::FromJavaObject(env, remote_desktop_client_override_origin);
  }
  std::optional<std::string> app_id_optional;
  if (!app_id.is_null()) {
    app_id_optional = ConvertJavaStringToUTF8(env, app_id);
  }
  render_frame_host_->PerformMakeCredentialWebAuthSecurityChecks(
      ConvertJavaStringToUTF8(env, relying_party_id), origin,
      is_payment_credential_creation,
      remote_desktop_client_override_origin_optional, app_id_optional,
      base::BindOnce(
          [](base::android::ScopedJavaGlobalRef<jobject> callback,
             blink::mojom::AuthenticatorStatus status, bool is_cross_origin) {
            base::android::RunObjectCallbackAndroid(
                callback,
                Java_RenderFrameHostImpl_createWebAuthSecurityChecksResults(
                    base::android::AttachCurrentThread(),
                    static_cast<int32_t>(status), is_cross_origin));
          },
          base::android::ScopedJavaGlobalRef<jobject>(callback)));
}

void RenderFrameHostAndroid::PerformReportWebAuthSecurityChecks(
    JNIEnv* env,
    const base::android::JavaRef<jstring>& relying_party_id,
    const base::android::JavaRef<jobject>& effective_origin,
    const base::android::JavaRef<jobject>& callback) const {
  url::Origin origin = url::Origin::FromJavaObject(env, effective_origin);
  render_frame_host_->PerformReportWebAuthSecurityChecks(
      ConvertJavaStringToUTF8(env, relying_party_id), origin,
      base::BindOnce(
          [](base::android::ScopedJavaGlobalRef<jobject> callback,
             blink::mojom::AuthenticatorStatus status, bool is_cross_origin) {
            base::android::RunObjectCallbackAndroid(
                callback,
                Java_RenderFrameHostImpl_createWebAuthSecurityChecksResults(
                    base::android::AttachCurrentThread(),
                    static_cast<int32_t>(status), is_cross_origin));
          },
          base::android::ScopedJavaGlobalRef<jobject>(callback)));
}

int32_t RenderFrameHostAndroid::GetLifecycleState(JNIEnv* env) const {
  return static_cast<int32_t>(render_frame_host_->GetLifecycleState());
}

void RenderFrameHostAndroid::ExecuteJavaScriptInIsolatedWorld(
    JNIEnv* env,
    const base::android::JavaRef<jstring>& jscript,
    int32_t jworldId,
    const base::android::JavaRef<jobject>& jcallback) {
  if (!jcallback) {
    render_frame_host()->ExecuteJavaScriptInIsolatedWorld(
        ConvertJavaStringToUTF16(env, jscript), base::DoNothing(), jworldId);
    return;
  }
  // Secure the Java callback in a scoped object and give ownership of it to the
  // base::OnceCallback below.
  base::android::ScopedJavaGlobalRef<jobject> java_callback;
  java_callback.Reset(env, jcallback);

  render_frame_host()->ExecuteJavaScriptInIsolatedWorld(
      ConvertJavaStringToUTF16(env, jscript),
      base::BindOnce(&JavaScriptResultCallback, java_callback), jworldId);
}

void RenderFrameHostAndroid::InsertVisualStateCallback(
    base::OnceCallback<void(bool)> callback) {
  render_frame_host()->InsertVisualStateCallback(std::move(callback));
}

bool RenderFrameHostAndroid::HasHitTestDataForTesting(JNIEnv* env) {
  RenderWidgetHostViewBase* child_view =
      render_frame_host()->GetRenderWidgetHost()->GetView();
  if (!child_view) {
    return false;
  }
  const viz::FrameSinkId& frame_sink_id = child_view->GetFrameSinkId();

  for (auto& it : GetHostFrameSinkManager()->GetDisplayHitTestQuery()) {
    if (it.second->ContainsActiveFrameSinkId(frame_sink_id)) {
      const std::vector<viz::AggregatedHitTestRegion>& hit_test_data =
          it.second->GetHitTestData();

      for (auto& it2 : hit_test_data) {
        if (it2.frame_sink_id == frame_sink_id &&
            !(it2.flags & viz::HitTestRegionFlags::kHitTestNotActive)) {
          return true;
        }
      }
    }
  }

  return false;
}

void RenderFrameHostAndroid::ViewSource(JNIEnv* env) {
  render_frame_host()->ViewSource();
}

}  // namespace content

DEFINE_JNI(RenderFrameHostImpl)
