blob: 442d93ab15b6a47dd40ca8c762f9acf0b0142dac [file] [log] [blame]
// Copyright 2019 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/views/web_apps/pwa_confirmation_bubble_view.h"
#include <string>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
#include "chrome/browser/ui/views/web_apps/web_app_info_image_source.h"
#include "chrome/browser/ui/views/web_apps/web_app_views_utils.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_prefs_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/event_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/common/content_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/text_elider.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/metrics/structured/event_logging_features.h"
#include "components/metrics/structured/structured_events.h"
#endif
namespace {
PWAConfirmationBubbleView* g_bubble_ = nullptr;
bool g_auto_accept_pwa_for_testing = false;
bool g_dont_close_on_deactivate = false;
// Returns an ImageView containing the app icon.
std::unique_ptr<views::ImageView> CreateIconView(
const WebAppInstallInfo& web_app_info) {
constexpr int kIconSize = 48;
gfx::ImageSkia image(std::make_unique<WebAppInfoImageSource>(
kIconSize, web_app_info.icon_bitmaps.any),
gfx::Size(kIconSize, kIconSize));
auto icon_image_view = std::make_unique<views::ImageView>();
icon_image_view->SetImage(image);
return icon_image_view;
}
#if BUILDFLAG(IS_CHROMEOS)
namespace cros_events = metrics::structured::events::v2::cr_os_events;
int64_t ToLong(web_app::WebAppInstallStatus web_app_install_status) {
return static_cast<int64_t>(web_app_install_status);
}
#endif
} // namespace
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(PWAConfirmationBubbleView,
kInstallButton);
DEFINE_CLASS_CUSTOM_ELEMENT_EVENT_TYPE(PWAConfirmationBubbleView,
kInstalledPWAEventId);
// static
bool PWAConfirmationBubbleView::IsShowing() {
return g_bubble_;
}
// static
PWAConfirmationBubbleView* PWAConfirmationBubbleView::GetBubble() {
return g_bubble_;
}
PWAConfirmationBubbleView::PWAConfirmationBubbleView(
views::View* anchor_view,
content::WebContents* web_contents,
PageActionIconView* highlight_icon_button,
std::unique_ptr<WebAppInstallInfo> web_app_info,
chrome::AppInstallationAcceptanceCallback callback,
chrome::PwaInProductHelpState iph_state,
PrefService* prefs,
feature_engagement::Tracker* tracker)
: LocationBarBubbleDelegateView(anchor_view, web_contents),
highlight_icon_button_(highlight_icon_button),
web_app_info_(std::move(web_app_info)),
callback_(std::move(callback)),
iph_state_(iph_state),
prefs_(prefs),
tracker_(tracker) {
SetCloseOnMainFrameOriginNavigation(true);
DCHECK(web_app_info_);
DCHECK(prefs_);
WidgetDelegate::SetShowCloseButton(true);
WidgetDelegate::SetTitle(
l10n_util::GetStringUTF16(IDS_INSTALL_TO_OS_LAUNCH_SURFACE_BUBBLE_TITLE));
SetButtonLabel(ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(IDS_INSTALL_PWA_BUTTON_LABEL));
base::TrimWhitespace(web_app_info_->title, base::TRIM_ALL,
&web_app_info_->title);
// PWAs should always be configured not to open in a browser tab.
if (web_app_info_->user_display_mode.has_value()) {
DCHECK_NE(*web_app_info_->user_display_mode,
web_app::mojom::UserDisplayMode::kBrowser);
}
const ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
// Use CONTROL insets, because the icon is non-text (see documentation for
// DialogContentType).
gfx::Insets margin_insets = layout_provider->GetDialogInsetsForContentType(
views::DialogContentType::kControl, views::DialogContentType::kControl);
set_margins(margin_insets);
int icon_label_spacing = layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
icon_label_spacing));
AddChildView(CreateIconView(*web_app_info_).release());
views::View* labels = new views::View();
AddChildView(labels);
labels->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
labels->AddChildView(
web_app::CreateNameLabel(web_app_info_->title).release());
labels->AddChildView(web_app::CreateOriginLabel(
url::Origin::Create(web_app_info_->start_url), false)
.release());
if (base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip) &&
base::FeatureList::IsEnabled(features::kDesktopPWAsTabStripSettings)) {
// This UI is only for prototyping and is not intended for shipping.
DCHECK_EQ(features::kDesktopPWAsTabStripSettings.default_state,
base::FEATURE_DISABLED_BY_DEFAULT);
tabbed_window_checkbox_ = labels->AddChildView(
std::make_unique<views::Checkbox>(l10n_util::GetStringUTF16(
IDS_BOOKMARK_APP_BUBBLE_OPEN_AS_TABBED_WINDOW)));
tabbed_window_checkbox_->SetChecked(
web_app_info_->user_display_mode ==
web_app::mojom::UserDisplayMode::kTabbed);
}
SetDefaultButton(ui::DIALOG_BUTTON_CANCEL);
SetHighlightedButton(highlight_icon_button_);
if (g_dont_close_on_deactivate) {
set_close_on_deactivate(false);
}
}
PWAConfirmationBubbleView::~PWAConfirmationBubbleView() = default;
void PWAConfirmationBubbleView::OnWidgetInitialized() {
auto* ok_button = GetOkButton();
if (ok_button) {
ok_button->SetProperty(views::kElementIdentifierKey,
PWAConfirmationBubbleView::kInstallButton);
}
}
bool PWAConfirmationBubbleView::OnCloseRequested(
views::Widget::ClosedReason close_reason) {
base::UmaHistogramEnumeration("WebApp.InstallConfirmation.CloseReason",
close_reason);
return LocationBarBubbleDelegateView::OnCloseRequested(close_reason);
}
views::View* PWAConfirmationBubbleView::GetInitiallyFocusedView() {
return nullptr;
}
void PWAConfirmationBubbleView::WindowClosing() {
DCHECK_EQ(g_bubble_, this);
g_bubble_ = nullptr;
if (highlight_icon_button_) {
highlight_icon_button_->Update();
}
// If |web_app_info_| is populated, then the bubble was not accepted.
if (web_app_info_) {
base::RecordAction(base::UserMetricsAction("WebAppInstallCancelled"));
if (iph_state_ == chrome::PwaInProductHelpState::kShown) {
web_app::AppId app_id =
web_app::GenerateAppIdFromManifestId(web_app_info_->manifest_id);
web_app::RecordInstallIphIgnored(prefs_, app_id, base::Time::Now());
#if BUILDFLAG(IS_CHROMEOS)
if (base::FeatureList::IsEnabled(
metrics::structured::kAppDiscoveryLogging)) {
cros_events::AppDiscovery_Browser_AppInstallDialogResult()
.SetWebAppInstallStatus(
ToLong(web_app::WebAppInstallStatus::kCancelled))
.SetAppId(app_id)
.Record();
}
#endif
}
} else {
base::RecordAction(base::UserMetricsAction("WebAppInstallAccepted"));
}
if (callback_) {
DCHECK(web_app_info_);
std::move(callback_).Run(false, std::move(web_app_info_));
}
}
bool PWAConfirmationBubbleView::Accept() {
DCHECK(web_app_info_);
web_app_info_->user_display_mode =
tabbed_window_checkbox_ && tabbed_window_checkbox_->GetChecked()
? web_app::mojom::UserDisplayMode::kTabbed
: web_app::mojom::UserDisplayMode::kStandalone;
web_app::AppId app_id =
web_app::GenerateAppIdFromManifestId(web_app_info_->manifest_id);
#if BUILDFLAG(IS_CHROMEOS)
if (base::FeatureList::IsEnabled(metrics::structured::kAppDiscoveryLogging)) {
cros_events::AppDiscovery_Browser_AppInstallDialogResult()
.SetWebAppInstallStatus(ToLong(web_app::WebAppInstallStatus::kAccepted))
.SetAppId(app_id)
.Record();
}
#endif
if (iph_state_ == chrome::PwaInProductHelpState::kShown) {
web_app::RecordInstallIphInstalled(prefs_, app_id);
tracker_->NotifyEvent(feature_engagement::events::kDesktopPwaInstalled);
}
auto* ok_button = GetOkButton();
auto* tracker_framework = ui::ElementTracker::GetFrameworkDelegate();
auto* tracker_views = views::ElementTrackerViews::GetInstance();
if (ok_button && tracker_framework && tracker_views) {
tracker_framework->NotifyCustomEvent(
tracker_views->GetElementForView(ok_button),
PWAConfirmationBubbleView::kInstalledPWAEventId);
}
std::move(callback_).Run(true, std::move(web_app_info_));
return true;
}
base::AutoReset<bool>
PWAConfirmationBubbleView::SetDontCloseOnDeactivateForTesting() {
return base::AutoReset<bool>(&g_dont_close_on_deactivate, true);
}
void PWAConfirmationBubbleView::OnBeforeBubbleWidgetInit(
views::Widget::InitParams* params,
views::Widget* widget) const {
params->name = "PWAConfirmationBubbleView";
}
namespace chrome {
void ShowPWAInstallBubble(content::WebContents* web_contents,
std::unique_ptr<WebAppInstallInfo> web_app_info,
AppInstallationAcceptanceCallback callback,
PwaInProductHelpState iph_state) {
if (g_bubble_) {
return;
}
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser) {
return;
}
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
views::View* anchor_view =
browser_view->toolbar_button_provider()->GetAnchorView(
PageActionIconType::kPwaInstall);
PageActionIconView* icon =
browser_view->toolbar_button_provider()->GetPageActionIconView(
PageActionIconType::kPwaInstall);
auto* browser_context = web_contents->GetBrowserContext();
PrefService* prefs = Profile::FromBrowserContext(browser_context)->GetPrefs();
#if BUILDFLAG(IS_CHROMEOS)
if (base::FeatureList::IsEnabled(metrics::structured::kAppDiscoveryLogging)) {
web_app::AppId app_id =
web_app::GenerateAppIdFromManifestId(web_app_info->manifest_id);
cros_events::AppDiscovery_Browser_AppInstallDialogShown()
.SetAppId(app_id)
.Record();
}
#endif
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserContext(browser_context);
g_bubble_ = new PWAConfirmationBubbleView(
anchor_view, web_contents, icon, std::move(web_app_info),
std::move(callback), iph_state, prefs, tracker);
views::BubbleDialogDelegateView::CreateBubble(g_bubble_)->Show();
base::RecordAction(base::UserMetricsAction("WebAppInstallShown"));
if (g_auto_accept_pwa_for_testing) {
g_bubble_->AcceptDialog();
}
if (icon) {
icon->Update();
DCHECK(icon->GetVisible());
}
}
void SetAutoAcceptPWAInstallConfirmationForTesting(bool auto_accept) {
g_auto_accept_pwa_for_testing = auto_accept;
}
} // namespace chrome