chromium/chrome/browser/android/context_menu/context_menu_native_delegate_impl.cc

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

#include "chrome/browser/android/context_menu/context_menu_native_delegate_impl.h"

#include <utility>

#include "base/android/callback_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "chrome/browser/download/android/download_controller_base.h"
#include "chrome/browser/image_decoder/image_decoder.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "components/embedder_support/android/contextmenu/context_menu_builder.h"
#include "components/embedder_support/android/contextmenu/context_menu_image_format.h"
#include "components/lens/lens_metadata.mojom.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/gfx/android/java_bitmap.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/contextmenu/jni_headers/ContextMenuNativeDelegateImpl_jni.h"

using base::android::JavaParamRef;
using base::android::JavaRef;

namespace {

class ContextMenuImageRequest : public ImageDecoder::ImageRequest {
 public:
  ContextMenuImageRequest() = delete;
  ContextMenuImageRequest(const ContextMenuImageRequest&) = delete;
  ContextMenuImageRequest& operator=(const ContextMenuImageRequest&) = delete;

  static void Start(const JavaRef<jobject>& jcallback,
                    const std::vector<uint8_t>& thumbnail_data) {
    auto* request = new ContextMenuImageRequest(jcallback);
    ImageDecoder::Start(request, thumbnail_data);
  }

 protected:
  void OnImageDecoded(const SkBitmap& decoded_image) override {
    base::android::RunObjectCallbackAndroid(
        jcallback_, gfx::ConvertToJavaBitmap(decoded_image));
    delete this;
  }

  void OnDecodeImageFailed() override {
    base::android::ScopedJavaLocalRef<jobject> j_bitmap;
    base::android::RunObjectCallbackAndroid(jcallback_, j_bitmap);
    delete this;
  }

 private:
  explicit ContextMenuImageRequest(const JavaRef<jobject>& jcallback)
      : jcallback_(jcallback) {}

  const base::android::ScopedJavaGlobalRef<jobject> jcallback_;
};

chrome::mojom::ImageFormat ToChromeMojomImageFormat(int image_format) {
  auto format = static_cast<ContextMenuImageFormat>(image_format);
  switch (format) {
    case ContextMenuImageFormat::JPEG:
      return chrome::mojom::ImageFormat::JPEG;
    case ContextMenuImageFormat::PNG:
      return chrome::mojom::ImageFormat::PNG;
    case ContextMenuImageFormat::ORIGINAL:
      return chrome::mojom::ImageFormat::ORIGINAL;
  }

  NOTREACHED_IN_MIGRATION();
  return chrome::mojom::ImageFormat::JPEG;
}

void OnRetrieveImageForShare(
    mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
        chrome_render_frame,
    const JavaRef<jobject>& jcallback,
    const std::vector<uint8_t>& thumbnail_data,
    const gfx::Size& original_size,
    const gfx::Size& downscaled_size,
    const std::string& image_extension,
    const std::vector<lens::mojom::LatencyLogPtr>) {
  JNIEnv* env = base::android::AttachCurrentThread();
  auto j_data = base::android::ToJavaByteArray(env, thumbnail_data);
  auto j_extension =
      base::android::ConvertUTF8ToJavaString(env, image_extension);
  base::android::RunObjectCallbackAndroid(
      jcallback, Java_ContextMenuNativeDelegateImpl_createImageCallbackResult(
                     env, j_data, j_extension));
}

void OnRetrieveImageForContextMenu(
    mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
        chrome_render_frame,
    const JavaRef<jobject>& jcallback,
    const std::vector<uint8_t>& thumbnail_data,
    const gfx::Size& original_size,
    const gfx::Size& downscaled_size,
    const std::string& filename_extension,
    const std::vector<lens::mojom::LatencyLogPtr>) {
  ContextMenuImageRequest::Start(jcallback, thumbnail_data);
}

}  // namespace

ContextMenuNativeDelegateImpl::ContextMenuNativeDelegateImpl(
    content::WebContents* const web_contents,
    content::ContextMenuParams* const context_menu_params)
    : web_contents_(web_contents), context_menu_params_(context_menu_params) {}

void ContextMenuNativeDelegateImpl::StartDownload(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jboolean jis_link) {
  DownloadControllerBase::Get()->StartContextMenuDownload(
      *context_menu_params_, web_contents_, jis_link);
}

void ContextMenuNativeDelegateImpl::SearchForImage(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& jrender_frame_host) {
  auto* render_frame_host =
      content::RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host);
  if (!render_frame_host)
    return;

  CoreTabHelper::FromWebContents(web_contents_)
      ->SearchByImage(render_frame_host, context_menu_params_->src_url);
}

void ContextMenuNativeDelegateImpl::RetrieveImageForShare(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& jrender_frame_host,
    const JavaParamRef<jobject>& jcallback,
    jint max_width_px,
    jint max_height_px,
    jint jimage_format) {
  RetrieveImageInternal(env, base::BindOnce(&OnRetrieveImageForShare),
                        jrender_frame_host, jcallback, max_width_px,
                        max_height_px, ToChromeMojomImageFormat(jimage_format));
}

void ContextMenuNativeDelegateImpl::RetrieveImageForContextMenu(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& jrender_frame_host,
    const JavaParamRef<jobject>& jcallback,
    jint max_width_px,
    jint max_height_px) {
  // For context menu, Image needs to be PNG for receiving transparency pixels.
  RetrieveImageInternal(env, base::BindOnce(&OnRetrieveImageForContextMenu),
                        jrender_frame_host, jcallback, max_width_px,
                        max_height_px, chrome::mojom::ImageFormat::PNG);
}

void ContextMenuNativeDelegateImpl::RetrieveImageInternal(
    JNIEnv* env,
    ImageRetrieveCallback retrieve_callback,
    const JavaParamRef<jobject>& jrender_frame_host,
    const JavaParamRef<jobject>& jcallback,
    jint max_width_px,
    jint max_height_px,
    chrome::mojom::ImageFormat image_format) {
  auto* render_frame_host =
      content::RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host);
  if (!render_frame_host)
    return;
  mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame;
  render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
      &chrome_render_frame);

  // Bind the InterfacePtr into the callback so that it's kept alive
  // until there's either a connection error or a response.
  auto* thumbnail_capturer_proxy = chrome_render_frame.get();
  thumbnail_capturer_proxy->RequestImageForContextNode(
      max_width_px * max_height_px, gfx::Size(max_width_px, max_height_px),
      image_format, chrome::mojom::kDefaultQuality,
      base::BindOnce(
          std::move(retrieve_callback), std::move(chrome_render_frame),
          base::android::ScopedJavaGlobalRef<jobject>(env, jcallback)));
}

static jlong JNI_ContextMenuNativeDelegateImpl_Init(
    JNIEnv* env,
    const JavaParamRef<jobject>& jweb_contents,
    const JavaParamRef<jobject>& jcontext_menu_params) {
  if (jweb_contents.is_null())
    return reinterpret_cast<intptr_t>(nullptr);
  auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents);
  DCHECK(web_contents);
  auto* params =
      context_menu::ContextMenuParamsFromJavaObject(jcontext_menu_params);
  return reinterpret_cast<intptr_t>(
      new ContextMenuNativeDelegateImpl(web_contents, params));
}