blob: 5b7b94f79ea27d7a2278485eff3546c09ba288e4 [file] [log] [blame]
// Copyright 2024 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/lens/lens_permission_bubble_controller.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "build/branding_buildflags.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/lens/lens_overlay_theme_utils.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/lens/lens_features.h"
#include "components/lens/lens_overlay_metrics.h"
#include "components/lens/lens_overlay_permission_utils.h"
#include "components/lens/lens_permission_user_action.h"
#include "components/prefs/pref_service.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/common/referrer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/models/dialog_model_field.h"
#include "ui/base/models/image_model.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_types.h"
#include "ui/base/window_open_disposition.h"
#include "ui/base/window_open_disposition_utils.h"
#include "ui/compositor/layer.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget.h"
namespace lens {
LensPermissionBubbleController::LensPermissionBubbleController(
BrowserWindowInterface* browser_window_interface,
PrefService* pref_service,
LensOverlayInvocationSource invocation_source)
: invocation_source_(invocation_source),
browser_window_interface_(browser_window_interface),
pref_service_(pref_service) {}
LensPermissionBubbleController::~LensPermissionBubbleController() {
if (HasOpenDialogWidget()) {
dialog_widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
}
void LensPermissionBubbleController::RequestPermission(
content::WebContents* web_contents,
RequestPermissionCallback callback) {
// Return early if there is already an open dialog widget. Show the dialog if
// it is not currently visible for some reason.
if (HasOpenDialogWidget()) {
if (!dialog_widget_->IsVisible()) {
dialog_widget_->Show();
}
RecordPermissionRequestedToBeShown(false, invocation_source_);
return;
}
RecordPermissionRequestedToBeShown(true, invocation_source_);
// Observe pref changes. Reset the pref observer in case this method called
// several times in succession.
pref_observer_.Reset();
pref_observer_.Init(pref_service_);
if (lens::features::IsLensOverlayContextualSearchboxEnabled()) {
pref_observer_.Add(
prefs::kLensSharingPageContentEnabled,
base::BindRepeating(
&LensPermissionBubbleController::OnPermissionPreferenceUpdated,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
} else {
pref_observer_.Add(
prefs::kLensSharingPageScreenshotEnabled,
base::BindRepeating(
&LensPermissionBubbleController::OnPermissionPreferenceUpdated,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
// Show a tab-modal dialog and keep a reference to its widget.
dialog_widget_ = constrained_window::ShowWebModal(
CreateLensPermissionDialogModel(), web_contents);
// Clip layers to root layer bounds so that they don't render outside of the
// dialog boundary when the dialog is small.
// TODO(crbug.com/358379367): this should live in the framework and should
// clip to the window opaque area. Currently child layers will bleed into the
// window shadow area.
dialog_widget_->GetLayer()->SetMasksToBounds(true);
}
std::unique_ptr<ui::DialogModel>
LensPermissionBubbleController::CreateLensPermissionDialogModel() {
ui::DialogModelLabel::TextReplacement link = ui::DialogModelLabel::CreateLink(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_LEARN_MORE_LINK,
base::BindRepeating(
&LensPermissionBubbleController::OnHelpCenterLinkClicked,
weak_ptr_factory_.GetWeakPtr()));
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
bool dark_mode =
lens::LensOverlayShouldUseDarkMode(ThemeServiceFactory::GetForProfile(
browser_window_interface_->GetProfile()));
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
auto description_text =
lens::features::IsLensOverlayContextualSearchboxEnabled()
? ui::DialogModelLabel::CreateWithReplacement(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_CSB_DESCRIPTION, link)
: ui::DialogModelLabel::CreateWithReplacement(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_DESCRIPTION, link);
return ui::DialogModel::Builder()
.SetInternalName(kLensPermissionDialogName)
.SetTitle(
l10n_util::GetStringUTF16(IDS_LENS_PERMISSION_BUBBLE_DIALOG_TITLE))
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
.SetIcon(ui::ImageModel::FromVectorIcon(
dark_mode ? vector_icons::kGoogleGLogoMonochromeIcon
: vector_icons::kGoogleColorIcon,
dark_mode ? ui::kColorRefPrimary100 : ui::kColorIcon, 20))
.SetBannerImage(ui::ImageModel::FromImageSkia(
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_LENS_PERMISSION_MODAL_IMAGE)))
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
.AddParagraph(description_text)
.AddOkButton(
base::BindOnce(
&LensPermissionBubbleController::OnPermissionDialogAccept,
weak_ptr_factory_.GetWeakPtr()),
ui::DialogModel::Button::Params()
.SetLabel(l10n_util::GetStringUTF16(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_CONTINUE_BUTTON))
.SetId(kLensPermissionDialogOkButtonElementId)
.SetStyle(ui::ButtonStyle::kProminent))
.AddCancelButton(
base::BindOnce(
&LensPermissionBubbleController::OnPermissionDialogCancel,
weak_ptr_factory_.GetWeakPtr()),
ui::DialogModel::Button::Params()
.SetLabel(l10n_util::GetStringUTF16(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_CANCEL_BUTTON))
.SetId(kLensPermissionDialogCancelButtonElementId)
.SetStyle(ui::ButtonStyle::kTonal))
.SetCloseActionCallback(base::BindOnce(
&LensPermissionBubbleController::OnPermissionDialogClose,
weak_ptr_factory_.GetWeakPtr()))
.Build();
}
bool LensPermissionBubbleController::HasOpenDialogWidget() {
return dialog_widget_ && !dialog_widget_->IsClosed();
}
void LensPermissionBubbleController::OnHelpCenterLinkClicked(
const ui::Event& event) {
RecordPermissionUserAction(LensPermissionUserAction::kLinkOpened,
invocation_source_);
browser_window_interface_->OpenGURL(
GURL(lens::features::GetLensOverlayHelpCenterURL()),
ui::DispositionFromEventFlags(event.flags(),
WindowOpenDisposition::NEW_BACKGROUND_TAB));
}
void LensPermissionBubbleController::OnPermissionDialogAccept() {
RecordPermissionUserAction(LensPermissionUserAction::kAcceptButtonPressed,
invocation_source_);
pref_service_->SetBoolean(prefs::kLensSharingPageScreenshotEnabled, true);
if (lens::features::IsLensOverlayContextualSearchboxEnabled()) {
pref_service_->SetBoolean(prefs::kLensSharingPageContentEnabled, true);
}
dialog_widget_ = nullptr;
}
void LensPermissionBubbleController::OnPermissionDialogCancel() {
RecordPermissionUserAction(LensPermissionUserAction::kCancelButtonPressed,
invocation_source_);
dialog_widget_ = nullptr;
}
void LensPermissionBubbleController::OnPermissionDialogClose() {
if (dialog_widget_->closed_reason() ==
views::Widget::ClosedReason::kEscKeyPressed) {
RecordPermissionUserAction(LensPermissionUserAction::kEscKeyPressed,
invocation_source_);
}
dialog_widget_ = nullptr;
}
void LensPermissionBubbleController::OnPermissionPreferenceUpdated(
RequestPermissionCallback callback) {
// If sharing page content pref is enabled, the screenshot pref will also be
// enabled. Only need to check for the latter when a pref gets updated.
if (CanSharePageScreenshotWithLensOverlay(pref_service_)) {
if (HasOpenDialogWidget()) {
dialog_widget_->CloseWithReason(
views::Widget::ClosedReason::kAcceptButtonClicked);
}
pref_observer_.Reset();
callback.Run();
}
}
} // namespace lens