chromium/content/browser/web_contents/web_contents_android.cc

// Copyright 2013 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/web_contents/web_contents_android.h"

#include <stdint.h>

#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/user_metrics.h"
#include "base/threading/scoped_blocking_call.h"
#include "cc/input/android/offset_tag_android.h"
#include "cc/input/browser_controls_offset_tags_info.h"
#include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/renderer_host/view_transition_opt_in_state.h"
#include "content/browser/web_contents/view_structure_builder_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view_android.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/back_forward_transition_animation_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/message_port_provider.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/isolated_world_ids.h"
#include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h"
#include "ui/accessibility/ax_assistant_structure.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/mojom/ax_assistant_structure.mojom.h"
#include "ui/android/overscroll_refresh_handler.h"
#include "ui/android/window_android.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/snapshot/snapshot.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"

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

using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::RunRunnableAndroid;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaArrayOfStringArray;
using base::android::ToJavaArrayOfStrings;
using base::android::ToJavaIntArray;

namespace content {

namespace {

// Track all WebContentsAndroid objects here so that we don't deserialize a
// destroyed WebContents object.
base::LazyInstance<std::unordered_set<WebContentsAndroid*>>::Leaky
    g_allocated_web_contents_androids = LAZY_INSTANCE_INITIALIZER;

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

void SmartClipCallback(const ScopedJavaGlobalRef<jobject>& callback,
                       const std::u16string& text,
                       const std::u16string& html,
                       const gfx::Rect& clip_rect) {
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaLocalRef<jstring> j_text = ConvertUTF16ToJavaString(env, text);
  ScopedJavaLocalRef<jstring> j_html = ConvertUTF16ToJavaString(env, html);
  Java_WebContentsImpl_onSmartClipDataExtracted(
      env, j_text, j_html, clip_rect.x(), clip_rect.y(), clip_rect.right(),
      clip_rect.bottom(), callback);
}

void CreateJavaAXSnapshot(JNIEnv* env,
                          const ui::AssistantTree* tree,
                          const ui::AssistantNode* node,
                          const JavaRef<jobject>& j_view_structure_node,
                          const JavaRef<jobject>& j_view_structure_builder,
                          bool is_root) {
  ScopedJavaLocalRef<jstring> j_text =
      ConvertUTF16ToJavaString(env, node->text);

  // The (fake) Android java class name.
  ScopedJavaLocalRef<jstring> j_class =
      ConvertUTF8ToJavaString(env, node->class_name);

  bool has_selection = node->selection.has_value();
  int sel_start = has_selection ? node->selection->start() : 0;
  int sel_end = has_selection ? node->selection->end() : 0;
  int child_count = static_cast<int>(node->children_indices.size());

  ViewStructureBuilder_populateViewStructureNode(
      env, j_view_structure_builder, j_view_structure_node, j_text,
      has_selection, sel_start, sel_end, node->color, node->bgcolor,
      node->text_size, node->bold, node->italic, node->underline,
      node->line_through, j_class, child_count);

  // Bounding box.
  ViewStructureBuilder_setViewStructureNodeBounds(
      env, j_view_structure_builder, j_view_structure_node, is_root,
      node->rect.x(), node->rect.y(), node->rect.width(), node->rect.height(),
      node->unclipped_rect.x(), node->unclipped_rect.y(),
      node->unclipped_rect.width(), node->unclipped_rect.height(),
      node->page_absolute_rect.x(), node->page_absolute_rect.y(),
      node->page_absolute_rect.width(), node->page_absolute_rect.height());

  // HTML/CSS attributes.
  ScopedJavaLocalRef<jstring> j_html_tag =
      ConvertUTF8ToJavaString(env, node->html_tag);
  ScopedJavaLocalRef<jstring> j_css_display =
      ConvertUTF8ToJavaString(env, node->css_display);
  std::vector<std::vector<std::u16string>> html_attrs;
  for (const auto& attr : node->html_attributes) {
    html_attrs.push_back(
        {base::UTF8ToUTF16(attr.first), base::UTF8ToUTF16(attr.second)});
  }
  ScopedJavaLocalRef<jobjectArray> j_attrs =
      ToJavaArrayOfStringArray(env, html_attrs);

  ViewStructureBuilder_setViewStructureNodeHtmlInfo(
      env, j_view_structure_builder, j_view_structure_node, j_html_tag,
      j_css_display, j_attrs);

  for (int child_index = 0; child_index < child_count; child_index++) {
    int child_id = node->children_indices[child_index];
    ScopedJavaLocalRef<jobject> j_child =
        ViewStructureBuilder_addViewStructureNodeChild(
            env, j_view_structure_builder, j_view_structure_node, child_index);
    CreateJavaAXSnapshot(env, tree, tree->nodes[child_id].get(), j_child,
                         j_view_structure_builder, false);
  }

  if (!is_root) {
    ViewStructureBuilder_commitViewStructureNode(env, j_view_structure_builder,
                                                 j_view_structure_node);
  }
}

void AddTreeLevelDataToViewStructure(
    JNIEnv* env,
    const JavaRef<jobject>& view_structure_root,
    const JavaRef<jobject>& view_structure_builder,
    const ui::AXTreeUpdate& ax_tree_update) {
  const auto& metadata_strings = ax_tree_update.tree_data.metadata;
  if (metadata_strings.empty())
    return;

  ScopedJavaLocalRef<jobjectArray> j_metadata_strings =
      ToJavaArrayOfStrings(env, metadata_strings);
  ViewStructureBuilder_setViewStructureNodeHtmlMetadata(
      env, view_structure_builder, view_structure_root, j_metadata_strings);
}

}  // namespace

// static
WebContents* WebContents::FromJavaWebContents(
    const JavaRef<jobject>& jweb_contents_android) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (jweb_contents_android.is_null())
    return NULL;

  WebContentsAndroid* web_contents_android =
      reinterpret_cast<WebContentsAndroid*>(
          Java_WebContentsImpl_getNativePointer(AttachCurrentThread(),
                                                jweb_contents_android));
  if (!web_contents_android)
    return NULL;
  return web_contents_android->web_contents();
}

// static
static void JNI_WebContentsImpl_DestroyWebContents(
    JNIEnv* env,
    jlong jweb_contents_android_ptr) {
  WebContentsAndroid* web_contents_android =
      reinterpret_cast<WebContentsAndroid*>(jweb_contents_android_ptr);
  if (!web_contents_android)
    return;

  WebContents* web_contents = web_contents_android->web_contents();
  if (!web_contents)
    return;

  delete web_contents;
}

// static
ScopedJavaLocalRef<jobject> JNI_WebContentsImpl_FromNativePtr(
    JNIEnv* env,
    jlong web_contents_ptr) {
  WebContentsAndroid* web_contents_android =
      reinterpret_cast<WebContentsAndroid*>(web_contents_ptr);

  if (!web_contents_android)
    return ScopedJavaLocalRef<jobject>();

  // Check to make sure this object hasn't been destroyed.
  if (g_allocated_web_contents_androids.Get().find(web_contents_android) ==
      g_allocated_web_contents_androids.Get().end()) {
    return ScopedJavaLocalRef<jobject>();
  }

  return web_contents_android->GetJavaObject();
}

WebContentsAndroid::WebContentsAndroid(WebContentsImpl* web_contents)
    : web_contents_(web_contents),
      navigation_controller_(&(web_contents->GetController())) {
  g_allocated_web_contents_androids.Get().insert(this);
  JNIEnv* env = AttachCurrentThread();
  obj_.Reset(env,
             Java_WebContentsImpl_create(env, reinterpret_cast<intptr_t>(this),
                                         navigation_controller_.GetJavaObject())
                 .obj());
}

WebContentsAndroid::~WebContentsAndroid() {
  DCHECK(g_allocated_web_contents_androids.Get().find(this) !=
      g_allocated_web_contents_androids.Get().end());
  g_allocated_web_contents_androids.Get().erase(this);
  offset_tag_mediator_ = nullptr;
  for (auto& observer : destruction_observers_)
    observer.WebContentsAndroidDestroyed(this);
  Java_WebContentsImpl_clearNativePtr(AttachCurrentThread(), obj_);
}

base::android::ScopedJavaLocalRef<jobject>
WebContentsAndroid::GetJavaObject() {
  return base::android::ScopedJavaLocalRef<jobject>(obj_);
}

void WebContentsAndroid::CaptureContentAsBitmapForTesting(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jcallback) {
  ui::GrabViewSnapshot(
      web_contents_->GetNativeView(), gfx::Rect(web_contents_->GetSize()),
      base::BindOnce(
          &WebContentsAndroid::OnFinishGetContentBitmapForTesting,
          weak_factory_.GetWeakPtr(),
          base::android::ScopedJavaGlobalRef<jobject>(env, jcallback)));
}

void WebContentsAndroid::OnFinishGetContentBitmapForTesting(
    const base::android::JavaRef<jobject>& callback,
    gfx::Image snapshot) {
  const SkBitmap bitmap = snapshot.AsBitmap();
  CHECK(!bitmap.isNull());
  CHECK(!bitmap.empty());
  base::android::RunObjectCallbackAndroid(
      callback,
      gfx::ConvertToJavaBitmap(bitmap, gfx::OomBehavior::kReturnNullOnOom));
}

void WebContentsAndroid::Init() {
  offset_tag_mediator_ = new BrowserControlsOffsetTagMediator(web_contents_);
  offset_tag_mediator_->Initialize();
}

void WebContentsAndroid::ClearNativeReference(JNIEnv* env) {
  return web_contents_->ClearWebContentsAndroid();
}

void WebContentsAndroid::AddDestructionObserver(DestructionObserver* observer) {
  destruction_observers_.AddObserver(observer);
}

void WebContentsAndroid::RemoveDestructionObserver(
    DestructionObserver* observer) {
  destruction_observers_.RemoveObserver(observer);
}

// static
void WebContentsAndroid::ReportDanglingPtrToBrowserContext(
    JNIEnv* env,
    WebContents* web_contents) {
  if (base::android::ScopedJavaLocalRef<jthrowable> java_creator =
          web_contents->GetJavaCreatorLocation()) {
    Java_WebContentsImpl_reportDanglingPtrToBrowserContext(env, java_creator);
  }
}

base::android::ScopedJavaLocalRef<jobject>
WebContentsAndroid::GetTopLevelNativeWindow(JNIEnv* env) {
  ui::WindowAndroid* window_android = web_contents_->GetTopLevelNativeWindow();
  if (!window_android)
    return nullptr;
  return window_android->GetJavaObject();
}

void WebContentsAndroid::SetTopLevelNativeWindow(
    JNIEnv* env,
    const JavaParamRef<jobject>& jwindow_android) {
  ui::WindowAndroid* window =
      ui::WindowAndroid::FromJavaWindowAndroid(jwindow_android);
  auto* old_window = web_contents_->GetTopLevelNativeWindow();
  if (window == old_window)
    return;

  auto* view = web_contents_->GetNativeView();
  if (old_window)
    view->RemoveFromParent();
  if (window)
    window->AddChild(view);
}

void WebContentsAndroid::SetViewAndroidDelegate(
    JNIEnv* env,
    const JavaParamRef<jobject>& jview_delegate) {
  ui::ViewAndroid* view_android = web_contents_->GetView()->GetNativeView();
  view_android->SetDelegate(jview_delegate);
}

ScopedJavaLocalRef<jobject> WebContentsAndroid::GetMainFrame(
    JNIEnv* env) const {
  return web_contents_->GetPrimaryMainFrame()->GetJavaRenderFrameHost();
}

ScopedJavaLocalRef<jobject> WebContentsAndroid::GetFocusedFrame(
    JNIEnv* env) const {
  RenderFrameHostImpl* rfh = web_contents_->GetFocusedFrame();
  if (!rfh)
    return nullptr;
  return rfh->GetJavaRenderFrameHost();
}

bool WebContentsAndroid::IsFocusedElementEditable(JNIEnv* env) {
  return web_contents_->IsFocusedElementEditable();
}

ScopedJavaLocalRef<jobject> WebContentsAndroid::GetRenderFrameHostFromId(
    JNIEnv* env,
    jint render_process_id,
    jint render_frame_id) const {
  RenderFrameHost* rfh =
      RenderFrameHost::FromID(render_process_id, render_frame_id);
  if (!rfh)
    return nullptr;
  return rfh->GetJavaRenderFrameHost();
}

ScopedJavaLocalRef<jobjectArray> WebContentsAndroid::GetAllRenderFrameHosts(
    JNIEnv* env) const {
  std::vector<RenderFrameHost*> frames;
  web_contents_->ForEachRenderFrameHost(
      [&frames](RenderFrameHostImpl* rfh) { frames.push_back(rfh); });
  ScopedJavaLocalRef<jobjectArray> jframes =
      Java_WebContentsImpl_createRenderFrameHostArray(env, frames.size());
  for (size_t i = 0; i < frames.size(); i++) {
    Java_WebContentsImpl_addRenderFrameHostToArray(
        env, jframes, i, frames[i]->GetJavaRenderFrameHost());
  }
  return jframes;
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetTitle(JNIEnv* env) const {
  return base::android::ConvertUTF16ToJavaString(env,
                                                 web_contents_->GetTitle());
}

ScopedJavaLocalRef<jobject> WebContentsAndroid::GetVisibleURL(
    JNIEnv* env) const {
  return url::GURLAndroid::FromNativeGURL(env, web_contents_->GetVisibleURL());
}

jint WebContentsAndroid::GetVirtualKeyboardMode(JNIEnv* env) const {
  return static_cast<jint>(web_contents_->GetVirtualKeyboardMode());
}

bool WebContentsAndroid::IsLoading(JNIEnv* env) const {
  return web_contents_->IsLoading();
}

bool WebContentsAndroid::ShouldShowLoadingUI(JNIEnv* env) const {
  return web_contents_->ShouldShowLoadingUI();
}

bool WebContentsAndroid::HasUncommittedNavigationInPrimaryMainFrame(
    JNIEnv* env) const {
  return web_contents_->HasUncommittedNavigationInPrimaryMainFrame();
}

void WebContentsAndroid::DispatchBeforeUnload(JNIEnv* env, bool auto_cancel) {
  web_contents_->DispatchBeforeUnload(auto_cancel);
}

void WebContentsAndroid::Stop(JNIEnv* env) {
  web_contents_->Stop();
}

void WebContentsAndroid::Cut(JNIEnv* env) {
  web_contents_->Cut();
}

void WebContentsAndroid::Copy(JNIEnv* env) {
  web_contents_->Copy();
}

void WebContentsAndroid::Paste(JNIEnv* env) {
  web_contents_->Paste();
}

void WebContentsAndroid::PasteAsPlainText(JNIEnv* env) {
  // Paste as if user typed the characters, which should match current style of
  // the caret location.
  web_contents_->PasteAndMatchStyle();
}

void WebContentsAndroid::Replace(JNIEnv* env,
                                 const JavaParamRef<jstring>& jstr) {
  web_contents_->Replace(base::android::ConvertJavaStringToUTF16(env, jstr));
}

void WebContentsAndroid::SelectAll(JNIEnv* env) {
  web_contents_->SelectAll();
}

void WebContentsAndroid::CollapseSelection(JNIEnv* env) {
  web_contents_->CollapseSelection();
}

ScopedJavaLocalRef<jobject> WebContentsAndroid::GetRenderWidgetHostView(
    JNIEnv* env) {
  RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
  if (!rwhva)
    return nullptr;
  return rwhva->GetJavaObject();
}

jint WebContentsAndroid::GetVisibility(JNIEnv* env) {
  return static_cast<jint>(web_contents_->GetVisibility());
}

void WebContentsAndroid::UpdateWebContentsVisibility(JNIEnv* env,
                                                     jint visibiity) {
  web_contents_->UpdateWebContentsVisibility(
      static_cast<Visibility>(visibiity));
}

RenderWidgetHostViewAndroid*
    WebContentsAndroid::GetRenderWidgetHostViewAndroid() {
  RenderWidgetHostView* rwhv = NULL;
  rwhv = web_contents_->GetRenderWidgetHostView();
  return static_cast<RenderWidgetHostViewAndroid*>(rwhv);
}

jint WebContentsAndroid::GetBackgroundColor(JNIEnv* env) {
  return web_contents_->GetBackgroundColor().value_or(SK_ColorTRANSPARENT);
}

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

jboolean WebContentsAndroid::IsIncognito(JNIEnv* env) {
  return web_contents_->GetBrowserContext()->IsOffTheRecord();
}

void WebContentsAndroid::ResumeLoadingCreatedWebContents(JNIEnv* env) {
  web_contents_->ResumeLoadingCreatedWebContents();
}

void WebContentsAndroid::OnHide(JNIEnv* env) {
  web_contents_->WasHidden();
}

void WebContentsAndroid::OnShow(JNIEnv* env) {
  web_contents_->WasShown();
}

void WebContentsAndroid::SetImportance(JNIEnv* env,
                                       jint primary_main_frame_importance) {
  web_contents_->SetPrimaryMainFrameImportance(
      static_cast<ChildProcessImportance>(primary_main_frame_importance));
}

void WebContentsAndroid::SuspendAllMediaPlayers(JNIEnv* env) {
  web_contents_->media_web_contents_observer()->SuspendAllMediaPlayers();
}

void WebContentsAndroid::SetAudioMuted(JNIEnv* env, jboolean mute) {
  web_contents_->SetAudioMuted(mute);
}

jboolean WebContentsAndroid::IsAudioMuted(JNIEnv* env) {
  return web_contents_->IsAudioMuted();
}

jboolean WebContentsAndroid::FocusLocationBarByDefault(JNIEnv* env) {
  return web_contents_->FocusLocationBarByDefault();
}

bool WebContentsAndroid::IsFullscreenForCurrentTab(JNIEnv* env) {
  return web_contents_->IsFullscreen();
}

void WebContentsAndroid::ExitFullscreen(JNIEnv* env) {
  web_contents_->ExitFullscreen(/*will_cause_resize=*/false);
}

void WebContentsAndroid::ScrollFocusedEditableNodeIntoView(JNIEnv* env) {
  auto* input_handler = web_contents_->GetFocusedFrameWidgetInputHandler();
  if (!input_handler)
    return;
  bool should_overlay_content =
      web_contents_->GetPrimaryPage().virtual_keyboard_mode() ==
      ui::mojom::VirtualKeyboardMode::kOverlaysContent;
  // TODO(bokan): Autofill is notified of focus changes at the end of the
  // scrollIntoView call using DidCompleteFocusChangeInFrame, see
  // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/web_local_frame_impl.cc;l=3047;drc=aeadb03c8553c39e88d5d11d10f706d42f06a1d7.
  // By avoiding this call in should_overlay_content, we never notify autofill
  // of changed focus so we don't e.g. show the keyboard accessory.
  if (!should_overlay_content)
    input_handler->ScrollFocusedEditableNodeIntoView();
}

void WebContentsAndroid::SelectAroundCaretAck(
    int startOffset,
    int endOffset,
    int surroundingTextLength,
    blink::mojom::SelectAroundCaretResultPtr result) {
  RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
  if (rwhva) {
    rwhva->SelectAroundCaretAck(startOffset, endOffset, surroundingTextLength,
                                std::move(result));
  }
}

void WebContentsAndroid::SelectAroundCaret(JNIEnv* env,
                                           jint granularity,
                                           jboolean should_show_handle,
                                           jboolean should_show_context_menu,
                                           jint startOffset,
                                           jint endOffset,
                                           jint surroundingTextLength) {
  auto* input_handler = web_contents_->GetFocusedFrameWidgetInputHandler();
  if (!input_handler)
    return;
  input_handler->SelectAroundCaret(
      static_cast<blink::mojom::SelectionGranularity>(granularity),
      should_show_handle, should_show_context_menu,
      base::BindOnce(&WebContentsAndroid::SelectAroundCaretAck,
                     weak_factory_.GetWeakPtr(), startOffset, endOffset,
                     surroundingTextLength));
}

void WebContentsAndroid::AdjustSelectionByCharacterOffset(
    JNIEnv* env,
    jint start_adjust,
    jint end_adjust,
    jboolean show_selection_menu) {
  web_contents_->AdjustSelectionByCharacterOffset(start_adjust, end_adjust,
                                                  show_selection_menu);
}

bool WebContentsAndroid::InitializeRenderFrameForJavaScript() {
  if (!web_contents_->GetPrimaryFrameTree()
           .root()
           ->render_manager()
           ->InitializeMainRenderFrameForImmediateUse()) {
    LOG(ERROR) << "Failed to initialize RenderFrame to evaluate javascript";
    return false;
  }
  return true;
}

void WebContentsAndroid::EvaluateJavaScript(
    JNIEnv* env,
    const JavaParamRef<jstring>& script,
    const JavaParamRef<jobject>& callback) {
  RenderViewHost* rvh = web_contents_->GetRenderViewHost();
  DCHECK(rvh);

  if (!InitializeRenderFrameForJavaScript())
    return;

  if (!callback) {
    // No callback requested.
    web_contents_->GetPrimaryMainFrame()->ExecuteJavaScript(
        ConvertJavaStringToUTF16(env, script), base::NullCallback());
    return;
  }

  // Secure the Java callback in a scoped object and give ownership of it to the
  // base::OnceCallback below.
  ScopedJavaGlobalRef<jobject> j_callback;
  j_callback.Reset(env, callback);

  web_contents_->GetPrimaryMainFrame()->ExecuteJavaScript(
      ConvertJavaStringToUTF16(env, script),
      base::BindOnce(&JavaScriptResultCallback, j_callback));
}

void WebContentsAndroid::EvaluateJavaScriptForTests(
    JNIEnv* env,
    const JavaParamRef<jstring>& script,
    const JavaParamRef<jobject>& callback) {
  RenderViewHost* rvh = web_contents_->GetRenderViewHost();
  DCHECK(rvh);

  if (!InitializeRenderFrameForJavaScript())
    return;

  if (!callback) {
    // No callback requested.
    web_contents_->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
        ConvertJavaStringToUTF16(env, script), base::NullCallback(),
        ISOLATED_WORLD_ID_GLOBAL);
    return;
  }

  // Secure the Java callback in a scoped object and give ownership of it to the
  // base::OnceCallback below.
  ScopedJavaGlobalRef<jobject> j_callback;
  j_callback.Reset(env, callback);

  web_contents_->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
      ConvertJavaStringToUTF16(env, script),
      base::BindOnce(&JavaScriptResultCallback, j_callback),
      ISOLATED_WORLD_ID_GLOBAL);
}

void WebContentsAndroid::AddMessageToDevToolsConsole(
    JNIEnv* env,
    jint level,
    const JavaParamRef<jstring>& message) {
  DCHECK_GE(level, 0);
  DCHECK_LE(level, static_cast<int>(blink::mojom::ConsoleMessageLevel::kError));

  web_contents_->GetPrimaryMainFrame()->AddMessageToConsole(
      static_cast<blink::mojom::ConsoleMessageLevel>(level),
      ConvertJavaStringToUTF8(env, message));
}

void WebContentsAndroid::PostMessageToMainFrame(
    JNIEnv* env,
    const JavaParamRef<jobject>& jmessage,
    const JavaParamRef<jstring>& jsource_origin,
    const JavaParamRef<jstring>& jtarget_origin,
    const JavaParamRef<jobjectArray>& jports) {
  content::MessagePortProvider::PostMessageToFrame(
      web_contents_->GetPrimaryPage(), env, jsource_origin, jtarget_origin,
      jmessage, jports);
}

jboolean WebContentsAndroid::HasAccessedInitialDocument(JNIEnv* env) {
  return static_cast<WebContentsImpl*>(web_contents_)->
      HasAccessedInitialDocument();
}

jboolean WebContentsAndroid::HasViewTransitionOptIn(JNIEnv* env) {
  auto* opt_in_state = ViewTransitionOptInState::GetForCurrentDocument(
      web_contents_->GetPrimaryMainFrame());
  return opt_in_state &&
         opt_in_state->same_origin_opt_in() ==
             blink::mojom::ViewTransitionSameOriginOptIn::kEnabled;
}

jint WebContentsAndroid::GetThemeColor(JNIEnv* env) {
  return web_contents_->GetThemeColor().value_or(SK_ColorTRANSPARENT);
}

jfloat WebContentsAndroid::GetLoadProgress(JNIEnv* env) {
  return web_contents_->GetLoadProgress();
}

void WebContentsAndroid::RequestSmartClipExtract(
    JNIEnv* env,
    const JavaParamRef<jobject>& callback,
    jint x,
    jint y,
    jint width,
    jint height) {
  // Secure the Java callback in a scoped object and give ownership of it to the
  // base::OnceCallback below.
  ScopedJavaGlobalRef<jobject> j_callback;
  j_callback.Reset(env, callback);

  web_contents_->GetPrimaryMainFrame()->RequestSmartClipExtract(
      base::BindOnce(&SmartClipCallback, j_callback),
      gfx::Rect(x, y, width, height));
}

void WebContentsAndroid::AXTreeSnapshotCallback(
    const JavaRef<jobject>& view_structure_root,
    const JavaRef<jobject>& view_structure_builder,
    const JavaRef<jobject>& callback,
    ui::AXTreeUpdate& result) {
  JNIEnv* env = base::android::AttachCurrentThread();
  if (result.nodes.empty()) {
    RunRunnableAndroid(callback);
    return;
  }
  std::unique_ptr<ui::AssistantTree> assistant_tree =
      ui::CreateAssistantTree(result);
  CreateJavaAXSnapshot(env, assistant_tree.get(),
                       assistant_tree->nodes.front().get(), view_structure_root,
                       view_structure_builder, true);
  AddTreeLevelDataToViewStructure(env, view_structure_root,
                                  view_structure_builder, result);
  RunRunnableAndroid(callback);
}

void WebContentsAndroid::RequestAccessibilitySnapshot(
    JNIEnv* env,
    const JavaParamRef<jobject>& view_structure_root,
    const JavaParamRef<jobject>& view_structure_builder,
    const JavaParamRef<jobject>& callback) {
  // Secure the Java objects in scoped objects and give ownership of them to the
  // base::OnceCallback below.
  ScopedJavaGlobalRef<jobject> j_callback;
  j_callback.Reset(env, callback);
  ScopedJavaGlobalRef<jobject> j_view_structure_root;
  j_view_structure_root.Reset(env, view_structure_root);
  ScopedJavaGlobalRef<jobject> j_view_structure_builder;
  j_view_structure_builder.Reset(env, view_structure_builder);

  // Set a timeout of 2.0 seconds to compute the snapshot of the
  // accessibility tree because Google Assistant ignores results that
  // don't come back within 3.0 seconds.
  static_cast<WebContentsImpl*>(web_contents_)
      ->RequestAXTreeSnapshot(
          base::BindOnce(
              &WebContentsAndroid::AXTreeSnapshotCallback,
              weak_factory_.GetWeakPtr(), std::move(j_view_structure_root),
              std::move(j_view_structure_builder), std::move(j_callback)),
          ui::AXMode(ui::kAXModeComplete.flags() | ui::AXMode::kHTMLMetadata),
          /* max_nodes= */ 5000,
          /* timeout= */ base::Seconds(2),
          WebContents::AXTreeSnapshotPolicy::kAll);
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetEncoding(JNIEnv* env) const {
  return base::android::ConvertUTF8ToJavaString(env,
                                                web_contents_->GetEncoding());
}

void WebContentsAndroid::SetOverscrollRefreshHandler(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& overscroll_refresh_handler) {
  WebContentsViewAndroid* view =
      static_cast<WebContentsViewAndroid*>(web_contents_->GetView());
  view->SetOverscrollRefreshHandler(
      std::make_unique<ui::OverscrollRefreshHandler>(
          overscroll_refresh_handler));
}

void WebContentsAndroid::SetSpatialNavigationDisabled(JNIEnv* env,
                                                      bool disabled) {
  web_contents_->SetSpatialNavigationDisabled(disabled);
}

void WebContentsAndroid::SetStylusHandwritingEnabled(JNIEnv* env,
                                                     bool enabled) {
  web_contents_->SetStylusHandwritingEnabled(enabled);
}

int WebContentsAndroid::DownloadImage(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jurl,
    jboolean is_fav_icon,
    jint max_bitmap_size,
    jboolean bypass_cache,
    const base::android::JavaParamRef<jobject>& jcallback) {
  const gfx::Size preferred_size;
  return web_contents_->DownloadImage(
      url::GURLAndroid::ToNativeGURL(env, jurl), is_fav_icon, preferred_size,
      max_bitmap_size, bypass_cache,
      base::BindOnce(&WebContentsAndroid::OnFinishDownloadImage,
                     weak_factory_.GetWeakPtr(), obj_,
                     ScopedJavaGlobalRef<jobject>(env, jcallback)));
}

void WebContentsAndroid::SetHasPersistentVideo(JNIEnv* env, jboolean value) {
  web_contents_->SetHasPersistentVideo(value);
}

bool WebContentsAndroid::HasActiveEffectivelyFullscreenVideo(JNIEnv* env) {
  return web_contents_->HasActiveEffectivelyFullscreenVideo();
}

bool WebContentsAndroid::IsPictureInPictureAllowedForFullscreenVideo(
    JNIEnv* env) {
  return web_contents_->IsPictureInPictureAllowedForFullscreenVideo();
}

base::android::ScopedJavaLocalRef<jobject>
WebContentsAndroid::GetFullscreenVideoSize(JNIEnv* env) {
  if (!web_contents_->GetFullscreenVideoSize())
    return ScopedJavaLocalRef<jobject>();  // Return null.

  gfx::Size size = web_contents_->GetFullscreenVideoSize().value();
  return Java_WebContentsImpl_createSize(env, size.width(), size.height());
}

void WebContentsAndroid::SetSize(JNIEnv* env, jint width, jint height) {
  web_contents_->GetNativeView()->OnSizeChanged(width, height);
}

int WebContentsAndroid::GetWidth(JNIEnv* env) {
  return web_contents_->GetNativeView()->GetSize().width();
}

int WebContentsAndroid::GetHeight(JNIEnv* env) {
  return web_contents_->GetNativeView()->GetSize().height();
}

ScopedJavaLocalRef<jobject> WebContentsAndroid::GetOrCreateEventForwarder(
    JNIEnv* env) {
  gfx::NativeView native_view = web_contents_->GetView()->GetNativeView();
  return native_view->GetEventForwarder();
}

void WebContentsAndroid::OnFinishDownloadImage(
    const JavaRef<jobject>& obj,
    const JavaRef<jobject>& callback,
    int id,
    int http_status_code,
    const GURL& url,
    const std::vector<SkBitmap>& bitmaps,
    const std::vector<gfx::Size>& sizes) {
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaLocalRef<jobject> jbitmaps =
      Java_WebContentsImpl_createBitmapList(env);
  ScopedJavaLocalRef<jobject> jsizes =
      Java_WebContentsImpl_createSizeList(env);
  ScopedJavaLocalRef<jobject> jurl = url::GURLAndroid::FromNativeGURL(env, url);

  for (const SkBitmap& bitmap : bitmaps) {
    // WARNING: convering to java bitmaps results in duplicate memory
    // allocations, which increases the chance of OOMs if DownloadImage() is
    // misused.
    ScopedJavaLocalRef<jobject> jbitmap = gfx::ConvertToJavaBitmap(bitmap);
    Java_WebContentsImpl_addToBitmapList(env, jbitmaps, jbitmap);
  }
  for (const gfx::Size& size : sizes) {
    Java_WebContentsImpl_createSizeAndAddToList(env, jsizes, size.width(),
                                                size.height());
  }
  Java_WebContentsImpl_onDownloadImageFinished(
      env, obj, callback, id, http_status_code, jurl, jbitmaps, jsizes);
}

void WebContentsAndroid::SetMediaSession(
    const ScopedJavaLocalRef<jobject>& j_media_session) {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_WebContentsImpl_setMediaSession(env, obj_, j_media_session);
}

void WebContentsAndroid::SendOrientationChangeEvent(JNIEnv* env,
                                                    jint orientation) {
  base::RecordAction(base::UserMetricsAction("ScreenOrientationChange"));
  WebContentsViewAndroid* view =
      static_cast<WebContentsViewAndroid*>(web_contents_->GetView());
  view->set_device_orientation(orientation);
  RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
  if (rwhva)
    rwhva->UpdateScreenInfo();

  web_contents_->OnScreenOrientationChange();
}

void WebContentsAndroid::OnScaleFactorChanged(JNIEnv* env) {
  RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
  if (rwhva) {
    // |SendScreenRects()| indirectly calls GetViewSize() that asks Java layer.
    web_contents_->SendScreenRects();
    rwhva->SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
                                       std::nullopt);
  }
}

void WebContentsAndroid::SetFocus(JNIEnv* env, jboolean focused) {
  WebContentsViewAndroid* view =
      static_cast<WebContentsViewAndroid*>(web_contents_->GetView());
  view->SetFocus(focused);
}

bool WebContentsAndroid::IsBeingDestroyed(JNIEnv* env) {
  return web_contents_->IsBeingDestroyed();
}

void WebContentsAndroid::SetDisplayCutoutSafeArea(JNIEnv* env,
                                                  int top,
                                                  int left,
                                                  int bottom,
                                                  int right) {
  web_contents()->SetDisplayCutoutSafeArea(
      gfx::Insets::TLBR(top, left, bottom, right));
}

void WebContentsAndroid::NotifyRendererPreferenceUpdate(JNIEnv* env) {
  web_contents_->OnWebPreferencesChanged();
}

void WebContentsAndroid::NotifyBrowserControlsHeightChanged(JNIEnv* env) {
  web_contents_->GetNativeView()->OnBrowserControlsHeightChanged();
}

bool WebContentsAndroid::NeedToFireBeforeUnloadOrUnloadEvents(JNIEnv* env) {
  return web_contents_->NeedToFireBeforeUnloadOrUnloadEvents();
}

void WebContentsAndroid::OnContentForNavigationEntryShown(JNIEnv* env) {
  if (auto* animation =
          web_contents_->GetBackForwardTransitionAnimationManager()) {
    animation->OnContentForNavigationEntryShown();
  }
}

jint WebContentsAndroid::GetCurrentBackForwardTransitionStage(JNIEnv* env) {
  auto stage = BackForwardTransitionAnimationManager::AnimationStage::kNone;
  if (auto* animation =
          web_contents_->GetBackForwardTransitionAnimationManager()) {
    stage = animation->GetCurrentAnimationStage();
  }
  return static_cast<jint>(stage);
}

void WebContentsAndroid::SetLongPressLinkSelectText(JNIEnv* env,
                                                    jboolean enabled) {
  web_contents_->SetLongPressLinkSelectText((bool)enabled);
}

void WebContentsAndroid::NotifyControlsConstraintsChanged(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& jold_tags_info,
    const base::android::JavaParamRef<jobject>& jtags_info) {
  cc::BrowserControlsOffsetTagsInfo tags_info =
      cc::android::FromJavaBrowserControlsOffsetTagsInfo(env, jtags_info);
  if (!offset_tag_mediator_) {
    Init();
  }
  offset_tag_mediator_->SetOffsetTagsInfo(tags_info);
}

WebContentsAndroid::BrowserControlsOffsetTagMediator::
    BrowserControlsOffsetTagMediator(WebContents* web_contents)
    : RenderWidgetHostConnector(web_contents) {}

WebContentsAndroid::BrowserControlsOffsetTagMediator::
    ~BrowserControlsOffsetTagMediator() = default;

void WebContentsAndroid::BrowserControlsOffsetTagMediator::SetOffsetTagsInfo(
    const cc::BrowserControlsOffsetTagsInfo& new_offset_tags_info) {
  if (rwhva_) {
    rwhva_->UnregisterOffsetTags(offset_tags_info_);
    rwhva_->RegisterOffsetTags(new_offset_tags_info);
  }

  offset_tags_info_ = new_offset_tags_info;
}

void WebContentsAndroid::BrowserControlsOffsetTagMediator::
    UpdateRenderProcessConnection(RenderWidgetHostViewAndroid* old_rwhva,
                                  RenderWidgetHostViewAndroid* new_rwhva) {
  if (old_rwhva) {
    old_rwhva->UnregisterOffsetTags(offset_tags_info_);
  }
  if (new_rwhva) {
    new_rwhva->RegisterOffsetTags(offset_tags_info_);
  }
  rwhva_ = new_rwhva;
}

}  // namespace content