blob: fc8017f11e9ff332804cac894a8441f5b8b7b7e8 [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 <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.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/browser/ui/lens/lens_search_feature_flag_utils.h"
#include "chrome/browser/ui/lens/lens_string_utils.h"
#include "chrome/browser/ui/tabs/public/tab_dialog_manager.h"
#include "chrome/browser/ui/tabs/public/tab_features.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/tabs/public/tab_interface.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/bubble/bubble_dialog_model_host.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget.h"
namespace lens {
LensPermissionBubbleController::LensPermissionBubbleController(
tabs::TabInterface& tab_interface,
PrefService* pref_service,
LensOverlayInvocationSource invocation_source)
: invocation_source_(invocation_source),
tab_interface_(tab_interface),
pref_service_(pref_service) {
tab_will_detach_subscription_ = tab_interface_->RegisterWillDetach(
base::BindRepeating(&LensPermissionBubbleController::TabWillDetach,
base::Unretained(this)));
}
LensPermissionBubbleController::~LensPermissionBubbleController() {
if (HasOpenDialogWidget()) {
CloseDialogWidget(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::IsLensOverlayContextualSearchboxEnabled()) {
pref_observer_.Add(
prefs::kLensSharingPageContentEnabled,
base::BindRepeating(
&LensPermissionBubbleController::OnPermissionPreferenceUpdated,
weak_ptr_factory_.GetWeakPtr()));
} else {
pref_observer_.Add(
prefs::kLensSharingPageScreenshotEnabled,
base::BindRepeating(
&LensPermissionBubbleController::OnPermissionPreferenceUpdated,
weak_ptr_factory_.GetWeakPtr()));
}
dialog_widget_ = ShowDialogWidget(std::move(callback), 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<views::Widget> LensPermissionBubbleController::ShowDialogWidget(
RequestPermissionCallback callback,
content::WebContents* web_contents) {
// The widget will own `model_host` through DialogDelegate.
views::BubbleDialogModelHost* model_host =
views::BubbleDialogModelHost::CreateModal(
CreateLensPermissionDialogModel(std::move(callback)),
ui::mojom::ModalType::kChild)
.release();
model_host->SetOwnershipOfNewWidget(
views::Widget::InitParams::CLIENT_OWNS_WIDGET);
auto widget =
tab_interface_->GetTabFeatures()
->tab_dialog_manager()
->CreateAndShowDialog(
model_host, std::make_unique<tabs::TabDialogManager::Params>());
widget->MakeCloseSynchronous(
base::BindOnce(&LensPermissionBubbleController::CloseDialogWidget,
base::Unretained(this)));
views::View* focused_view = model_host->GetInitiallyFocusedView();
CHECK(focused_view);
focused_view->RequestFocus();
return widget;
}
void LensPermissionBubbleController::CloseDialogWidget(
views::Widget::ClosedReason reason) {
switch (reason) {
case views::Widget::ClosedReason::kAcceptButtonClicked:
RecordPermissionUserAction(LensPermissionUserAction::kAcceptButtonPressed,
invocation_source_);
break;
case views::Widget::ClosedReason::kCancelButtonClicked:
RecordPermissionUserAction(LensPermissionUserAction::kCancelButtonPressed,
invocation_source_);
break;
case views::Widget::ClosedReason::kEscKeyPressed:
RecordPermissionUserAction(LensPermissionUserAction::kEscKeyPressed,
invocation_source_);
break;
case views::Widget::ClosedReason::kUnspecified:
case views::Widget::ClosedReason::kCloseButtonClicked:
case views::Widget::ClosedReason::kLostFocus:
break;
}
dialog_widget_.reset();
}
std::unique_ptr<ui::DialogModel>
LensPermissionBubbleController::CreateLensPermissionDialogModel(
RequestPermissionCallback callback) {
ui::DialogModelLabel::TextReplacement link = ui::DialogModelLabel::CreateLink(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_LEARN_MORE_LINK,
base::BindRepeating(
&LensPermissionBubbleController::OnHelpCenterLinkClicked,
weak_ptr_factory_.GetWeakPtr()));
auto description_text =
lens::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(lens::GetLensOverlayEntrypointLabelAltIds(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_TITLE)))
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
.SetIcon(ui::ImageModel::FromImageSkia(
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_GOOGLE_G_GRADIENT_20)))
.SetBannerImage(ui::ImageModel::FromImageSkia(
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
lens::features::IsLensOverlayPermissionBubbleAltEnabled()
? IDR_LENS_PERMISSION_MODAL_IMAGE_ALT
: IDR_LENS_PERMISSION_MODAL_IMAGE)))
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
.AddParagraph(description_text)
.AddOkButton(
base::BindOnce(
&LensPermissionBubbleController::OnPermissionDialogAccept,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
ui::DialogModel::Button::Params()
.SetLabel(l10n_util::GetStringUTF16(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_CONTINUE_BUTTON))
.SetId(kLensPermissionDialogOkButtonElementId)
.SetStyle(ui::ButtonStyle::kProminent))
.AddCancelButton(base::DoNothing(),
ui::DialogModel::Button::Params()
.SetLabel(l10n_util::GetStringUTF16(
IDS_LENS_PERMISSION_BUBBLE_DIALOG_CANCEL_BUTTON))
.SetId(kLensPermissionDialogCancelButtonElementId)
.SetStyle(ui::ButtonStyle::kTonal))
.Build();
}
bool LensPermissionBubbleController::HasOpenDialogWidget() {
return dialog_widget_ && !dialog_widget_->IsClosed();
}
void LensPermissionBubbleController::OnHelpCenterLinkClicked(
const ui::Event& event) {
RecordPermissionUserAction(LensPermissionUserAction::kLinkOpened,
invocation_source_);
tab_interface_->GetBrowserWindowInterface()->OpenGURL(
GURL(lens::features::GetLensOverlayHelpCenterURL()),
ui::DispositionFromEventFlags(event.flags(),
WindowOpenDisposition::NEW_FOREGROUND_TAB));
}
void LensPermissionBubbleController::OnPermissionDialogAccept(
RequestPermissionCallback callback) {
// Pref observer is used to close background dialogs on other tabs. Observing
// the prefs is no longer necessary when the dialog is being closed because
// the user accepted the dialog.
pref_observer_.Reset();
if (lens::IsLensOverlayContextualSearchboxEnabled()) {
pref_service_->SetBoolean(prefs::kLensSharingPageContentEnabled, true);
}
pref_service_->SetBoolean(prefs::kLensSharingPageScreenshotEnabled, true);
// Must close dialog widget before running callback. This ensures that the
// overlay can show (it can't if there is another modal is showing).
CloseDialogWidget(views::Widget::ClosedReason::kAcceptButtonClicked);
callback.Run();
}
void LensPermissionBubbleController::OnPermissionPreferenceUpdated() {
if (HasOpenDialogWidget()) {
CloseDialogWidget(views::Widget::ClosedReason::kAcceptButtonClicked);
}
pref_observer_.Reset();
}
void LensPermissionBubbleController::TabWillDetach(
tabs::TabInterface* tab,
tabs::TabInterface::DetachReason reason) {
if (reason == tabs::TabInterface::DetachReason::kDelete &&
HasOpenDialogWidget()) {
CloseDialogWidget(views::Widget::ClosedReason::kUnspecified);
}
}
} // namespace lens