blob: 2ff9ea81748decae71985260a859d3e6c0f19ec8 [file] [log] [blame]
// Copyright 2025 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/ui/android/extensions/extension_action_popup_contents.h"
#include "base/android/jni_string.h"
#include "base/notimplemented.h"
#include "chrome/browser/extensions/extension_view_host.h"
#include "chrome/browser/extensions/extension_view_host_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_action.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/extension_registry.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/ui/android/extensions/jni_headers/ExtensionActionPopupContents_jni.h"
using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
using content::RenderFrameHost;
using content::WebContents;
namespace extensions {
namespace {
// The minimum and maximum sizes for the extension popup.
// https://developer.chrome.com/docs/extensions/reference/api/action#popup
constexpr gfx::Size kMinSize = {25, 25};
constexpr gfx::Size kMaxSize = {800, 600};
} // namespace
ExtensionActionPopupContents::ExtensionActionPopupContents(
std::unique_ptr<ExtensionViewHost> host)
: host_(std::move(host)) {
java_object_ = Java_ExtensionActionPopupContents_Constructor(
AttachCurrentThread(), reinterpret_cast<jlong>(this),
host_->host_contents());
host_->set_view(this);
// Handle the containing view calling window.close();
// The base::Unretained() below is safe because this object owns `host_`, so
// the callback will never fire if `this` is deleted.
host_->SetCloseHandler(
base::BindOnce(&ExtensionActionPopupContents::HandleCloseExtensionHost,
base::Unretained(this)));
WebContentsObserver::Observe(host_->host_contents());
auto* primary_main_frame = host_->host_contents()->GetPrimaryMainFrame();
if (primary_main_frame->IsRenderFrameLive()) {
SetUpNewMainFrame(primary_main_frame);
}
}
ExtensionActionPopupContents::~ExtensionActionPopupContents() = default;
ScopedJavaLocalRef<jobject> ExtensionActionPopupContents::GetJavaObject() {
return java_object_.AsLocalRef(AttachCurrentThread());
}
void ExtensionActionPopupContents::RenderFrameHostChanged(
RenderFrameHost* old_host,
RenderFrameHost* new_host) {
// Since we skipped speculative main frames in RenderFrameCreated, we must
// watch for them being swapped in by watching for RenderFrameHostChanged().
if (new_host != host_->host_contents()->GetPrimaryMainFrame()) {
return;
}
// Ignore the initial main frame host, as there's no renderer frame for it
// yet. If the DCHECK fires, then we would need to handle the initial main
// frame when it its renderer frame is created.
if (!old_host) {
DCHECK(!new_host->IsRenderFrameLive());
return;
}
SetUpNewMainFrame(new_host);
}
void ExtensionActionPopupContents::ResizeDueToAutoResize(
content::WebContents* web_contents,
const gfx::Size& new_size) {
Java_ExtensionActionPopupContents_resizeDueToAutoResize(
AttachCurrentThread(), java_object_, new_size.width(), new_size.height());
}
void ExtensionActionPopupContents::RenderFrameCreated(
RenderFrameHost* render_frame_host) {
// Only handle the initial main frame, not speculative ones.
if (render_frame_host != host_->host_contents()->GetPrimaryMainFrame()) {
return;
}
SetUpNewMainFrame(render_frame_host);
}
bool ExtensionActionPopupContents::HandleKeyboardEvent(
content::WebContents* source,
const input::NativeWebKeyboardEvent& event) {
NOTIMPLEMENTED();
return false;
}
void ExtensionActionPopupContents::OnLoaded() {
Java_ExtensionActionPopupContents_onLoaded(AttachCurrentThread(),
java_object_);
}
void ExtensionActionPopupContents::Destroy(JNIEnv* env) {
delete this;
}
void ExtensionActionPopupContents::LoadInitialPage(JNIEnv* env) {
host_->CreateRendererSoon();
}
void ExtensionActionPopupContents::SetUpNewMainFrame(
RenderFrameHost* render_frame_host) {
render_frame_host->GetView()->EnableAutoResize(kMinSize, kMaxSize);
}
void ExtensionActionPopupContents::HandleCloseExtensionHost(
ExtensionHost* host) {
DCHECK_EQ(host, host_.get());
Java_ExtensionActionPopupContents_onClose(AttachCurrentThread(),
java_object_);
}
// JNI method to create an ExtensionActionPopupContents instance.
// This is called from the Java side to initiate the display of an extension
// popup.
static ScopedJavaLocalRef<jobject> JNI_ExtensionActionPopupContents_Create(
JNIEnv* env,
Profile* profile,
std::string& action_id,
int tab_id) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
DCHECK(registry);
ExtensionActionManager* manager = ExtensionActionManager::Get(profile);
DCHECK(manager);
const Extension* extension =
registry->enabled_extensions().GetByID(action_id);
DCHECK(extension);
ExtensionAction* action = manager->GetExtensionAction(*extension);
DCHECK(action);
GURL popup_url = action->GetPopupUrl(tab_id);
std::unique_ptr<ExtensionViewHost> host =
ExtensionViewHostFactory::CreatePopupHost(popup_url, profile);
DCHECK(host);
// The ExtensionActionPopupContents C++ object's lifetime is managed by its
// Java counterpart. The Java object holds a pointer to this C++ instance.
// When the Java side is finished with the popup, it will explicitly call
// a 'destroy()' method on its Java object, which in turn calls the native
// ExtensionActionPopupContents::Destroy() method, leading to the deletion
// of this C++ object. Therefore, 'new' is used here, and ownership is
// effectively passed to the Java-controlled lifecycle.
ExtensionActionPopupContents* popup =
new ExtensionActionPopupContents(std::move(host));
return popup->GetJavaObject();
}
} // namespace extensions