blob: 0b4442e04fa15b92c6dc3d83defffc58eb915d9d [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/glic/host/context/glic_share_image_handler.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/glic/fre/glic_fre_controller.h"
#include "chrome/browser/glic/public/glic_keyed_service.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/toasts/api/toast_id.h"
#include "chrome/browser/ui/toasts/toast_controller.h"
#include "content/public/browser/render_frame_host.h"
namespace glic {
namespace {
constexpr int kShareThumbnailMinSize = 500 * 500;
constexpr int kShareThumbnailMaxWidth = 1000;
constexpr int kShareThumbnailMaxHeight = 1000;
constexpr base::TimeDelta kShareTimeoutSeconds = base::Seconds(60);
constexpr base::TimeDelta kGlicPanelPollIntervalMilliseconds =
base::Milliseconds(60);
mojom::AdditionalContextPtr CreateAdditionalContext(
const GURL& src_url,
const GURL& frame_url,
const url::Origin& frame_origin,
base::span<const uint8_t> thumbnail_data,
tabs::TabHandle handle) {
// TODO(b:448726704): update to use an Image part.
auto context = glic::mojom::AdditionalContext::New();
std::vector<glic::mojom::AdditionalContextPartPtr> parts;
auto context_data = mojom::ContextData::New();
context_data->mime_type = "image/png";
context_data->data = mojo_base::BigBuffer(thumbnail_data);
parts.push_back(
mojom::AdditionalContextPart::NewData(std::move(context_data)));
context->name = src_url.spec();
context->tab_id = handle.raw_value();
context->origin = frame_origin;
context->frameUrl = frame_url;
context->parts = std::move(parts);
return context;
}
} // namespace
GlicShareImageHandler::GlicShareImageHandler(GlicKeyedService& service)
: service_(service) {}
GlicShareImageHandler::~GlicShareImageHandler() = default;
void GlicShareImageHandler::ShareContextImage(
tabs::TabInterface* tab,
content::RenderFrameHost* render_frame_host,
const GURL& src_url) {
if (!tab) {
service_->metrics()->OnShareImageComplete(ShareImageResult::kFailedNoTab);
return;
}
if (!render_frame_host) {
MaybeShowErrorToast(tab);
service_->metrics()->OnShareImageComplete(ShareImageResult::kFailedNoFrame);
return;
}
if (is_share_in_progress_) {
// Cancel the previous attempt at sharing.
ShareComplete(ShareImageResult::kFailedReplacedByNewShare);
}
// Since we have no share in progress, we should not be waiting for the panel
// to be ready.
CHECK(!glic_panel_ready_timer_.IsRunning());
Reset();
is_share_in_progress_ = true;
service_->metrics()->OnShareImageStarted();
// Store the InterfacePtr into the callback so that it's kept alive until
// there's either a connection error or a response.
chrome_render_frame_remote_ = std::make_unique<
mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>>();
render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
chrome_render_frame_remote_.get());
chrome_render_frame_remote_->get()->RequestImageForContextNode(
kShareThumbnailMinSize,
gfx::Size(kShareThumbnailMaxWidth, kShareThumbnailMaxHeight),
// TODO(b:448715912): consider other formats.
chrome::mojom::ImageFormat::PNG, chrome::mojom::kDefaultQuality,
base::BindOnce(&GlicShareImageHandler::ShareCapturedImage,
base::Unretained(this), tab->GetHandle(), src_url,
render_frame_host->GetLastCommittedURL(),
render_frame_host->GetLastCommittedOrigin()));
}
void GlicShareImageHandler::ShareCapturedImage(
tabs::TabHandle tab_handle,
const GURL& src_url,
const GURL& frame_url,
const url::Origin& frame_origin,
const std::vector<uint8_t>& thumbnail_data,
const gfx::Size& original_size,
const gfx::Size& downscaled_size,
const std::string& image_extension,
std::vector<lens::mojom::LatencyLogPtr> log_data) {
// Close the remote since we've received our thumbnail.
chrome_render_frame_remote_.reset();
tab_handle_ = tab_handle;
if (thumbnail_data.empty()) {
ShareComplete(ShareImageResult::kFailedNoImage);
return;
}
tabs::TabInterface* tab = tab_handle.Get();
if (!tab) {
ShareComplete(ShareImageResult::kFailedNoTab);
return;
}
BrowserWindowInterface* browser = tab->GetBrowserWindowInterface();
if (!browser) {
ShareComplete(ShareImageResult::kFailedNoBrowser);
return;
}
additional_context_ = CreateAdditionalContext(
src_url, frame_url, frame_origin,
base::span<const uint8_t>(thumbnail_data), tab_handle);
auto* instance = service_->GetInstanceForTab(tab);
if (!instance || !instance->IsShowing()) {
glic_panel_open_time_ = base::TimeTicks::Now();
// Note: if the FRE was showing, this will just cause it to be reshown.
service_->ToggleUI(browser, /*prevent_close=*/true,
mojom::InvocationSource::kSharedImage);
}
SendAdditionalContextWhenReady();
}
bool GlicShareImageHandler::IsClientReady(tabs::TabInterface& tab) {
if (GlicInstance* instance = service_->GetInstanceForTab(&tab)) {
return instance->host().IsReady();
}
return false;
}
void GlicShareImageHandler::ShareComplete(ShareImageResult result) {
if (result == ShareImageResult::kSuccess) {
service_->SendAdditionalContext(tab_handle_,
std::move(additional_context_));
} else {
MaybeShowErrorToast(tab_handle_.Get());
}
service_->metrics()->OnShareImageComplete(result);
Reset();
}
void GlicShareImageHandler::MaybeShowErrorToast(tabs::TabInterface* tab) {
if (!tab) {
return;
}
if (BrowserWindowInterface* browser = tab->GetBrowserWindowInterface()) {
if (auto* controller = browser->GetFeatures().toast_controller()) {
controller->MaybeShowToast(ToastParams(ToastId::kGlicShareImageFailed));
}
}
}
void GlicShareImageHandler::SendAdditionalContextWhenReady() {
tabs::TabInterface* tab = tab_handle_.Get();
if (!tab) {
ShareComplete(ShareImageResult::kFailedNoTab);
} else if (IsClientReady(*tab)) {
ShareComplete(ShareImageResult::kSuccess);
} else if (base::TimeTicks::Now() - glic_panel_open_time_ >
kShareTimeoutSeconds) {
ShareComplete(ShareImageResult::kFailedTimedOut);
} else if (!glic_panel_ready_timer_.IsRunning()) {
glic_panel_ready_timer_.Start(
FROM_HERE, kGlicPanelPollIntervalMilliseconds,
base::BindRepeating(
&GlicShareImageHandler::SendAdditionalContextWhenReady,
base::Unretained(this)));
}
}
void GlicShareImageHandler::Reset() {
glic_panel_open_time_ = base::TimeTicks();
glic_panel_ready_timer_.Stop();
additional_context_.reset();
chrome_render_frame_remote_.reset();
tab_handle_ = tabs::TabHandle::Null();
is_share_in_progress_ = false;
}
} // namespace glic