blob: e0b4d05f9c31a1721655ac0863dca9a584f5dad0 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/signin/signin_ui_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/extensions/extension_install_ui_default.h"
#include "chrome/browser/ui/extensions/extension_installed_bubble_model.h"
#include "chrome/browser/ui/extensions/extension_installed_waiter.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "extensions/common/extension.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/image_model_utils.h"
#include "ui/views/layout/box_layout.h"
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
#endif
namespace {
const int kRightColumnWidth = 285;
constexpr gfx::Size kMaxIconSize{43, 43};
views::Label* CreateLabel(const std::u16string& text) {
views::Label* label = new views::Label(text);
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SizeToFit(kRightColumnWidth);
return label;
}
views::View* AnchorViewForBrowser(const ExtensionInstalledBubbleModel* model,
Browser* browser) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
views::View* reference_view = nullptr;
if (model->anchor_to_action()) {
ExtensionsToolbarContainer* const container =
browser_view->toolbar_button_provider()
->GetExtensionsToolbarContainer();
if (container)
reference_view = container->GetViewForId(model->extension_id());
} else if (model->anchor_to_omnibox()) {
reference_view = browser_view->GetLocationBarView()->location_icon_view();
}
// Default case.
if (!reference_view || !reference_view->GetVisible()) {
return browser_view->toolbar_button_provider()
->GetDefaultExtensionDialogAnchorView();
}
return reference_view;
}
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
std::unique_ptr<views::View> CreateSigninPromoView(
Profile* profile,
BubbleSyncPromoDelegate* delegate) {
return std::make_unique<DiceBubbleSyncPromoView>(
profile, delegate,
signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE,
IDS_EXTENSION_INSTALLED_DICE_PROMO_SYNC_MESSAGE,
/*dice_signin_button_prominent=*/true);
}
#endif
} // namespace
// Provides feedback to the user upon successful installation of an
// extension. Depending on the type of extension, the Bubble will
// point to:
// OMNIBOX_KEYWORD-> The omnibox.
// BROWSER_ACTION -> The browserAction icon in the toolbar.
// PAGE_ACTION -> A preview of the pageAction icon in the location
// bar which is shown while the Bubble is shown.
// GENERIC -> The app menu. This case includes pageActions that don't
// specify a default icon.
class ExtensionInstalledBubbleView : public BubbleSyncPromoDelegate,
public views::BubbleDialogDelegateView {
public:
METADATA_HEADER(ExtensionInstalledBubbleView);
ExtensionInstalledBubbleView(
Browser* browser,
std::unique_ptr<ExtensionInstalledBubbleModel> model);
ExtensionInstalledBubbleView(const ExtensionInstalledBubbleView&) = delete;
ExtensionInstalledBubbleView& operator=(const ExtensionInstalledBubbleView&) =
delete;
~ExtensionInstalledBubbleView() override;
static void Show(Browser* browser,
std::unique_ptr<ExtensionInstalledBubbleModel> model);
// Recalculate the anchor position for this bubble.
void UpdateAnchorView();
const ExtensionInstalledBubbleModel* model() const { return model_.get(); }
private:
// views::BubbleDialogDelegateView:
void Init() override;
// BubbleSyncPromoDelegate:
void OnEnableSync(const AccountInfo& account_info) override;
void LinkClicked();
const raw_ptr<Browser> browser_;
const std::unique_ptr<ExtensionInstalledBubbleModel> model_;
};
// static
void ExtensionInstalledBubbleView::Show(
Browser* browser,
std::unique_ptr<ExtensionInstalledBubbleModel> model) {
auto delegate =
std::make_unique<ExtensionInstalledBubbleView>(browser, std::move(model));
auto* weak_delegate = delegate.get();
views::Widget* const widget =
views::BubbleDialogDelegateView::CreateBubble(std::move(delegate));
// When the extension is installed to the ExtensionsToolbarContainer, use the
// container to pop out the extension icon and show the widget.
if (weak_delegate->model()->anchor_to_action()) {
ExtensionsToolbarContainer* const container =
BrowserView::GetBrowserViewForBrowser(browser)
->toolbar_button_provider()
->GetExtensionsToolbarContainer();
container->ShowWidgetForExtension(widget,
weak_delegate->model()->extension_id());
} else {
widget->Show();
}
}
ExtensionInstalledBubbleView::ExtensionInstalledBubbleView(
Browser* browser,
std::unique_ptr<ExtensionInstalledBubbleModel> model)
: BubbleDialogDelegateView(AnchorViewForBrowser(model.get(), browser),
model->anchor_to_omnibox()
? views::BubbleBorder::TOP_LEFT
: views::BubbleBorder::TOP_RIGHT),
browser_(browser),
model_(std::move(model)) {
chrome::RecordDialogCreation(chrome::DialogIdentifier::EXTENSION_INSTALLED);
SetButtons(ui::DIALOG_BUTTON_NONE);
if (model_->show_sign_in_promo()) {
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Promo view requires DICE, so show it only if DICE support is enabled.
SetFootnoteView(CreateSigninPromoView(browser->profile(), this));
#endif
}
SetIcon(model_->MakeIconOfSize(kMaxIconSize));
SetShowIcon(true);
SetShowCloseButton(true);
std::u16string extension_name = base::UTF8ToUTF16(model_->extension_name());
base::i18n::AdjustStringForLocaleDirection(&extension_name);
SetTitle(l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALLED_HEADING,
extension_name));
}
ExtensionInstalledBubbleView::~ExtensionInstalledBubbleView() = default;
void ExtensionInstalledBubbleView::UpdateAnchorView() {
views::View* reference_view = AnchorViewForBrowser(model_.get(), browser_);
DCHECK(reference_view);
SetAnchorView(reference_view);
}
void ExtensionInstalledBubbleView::Init() {
UpdateAnchorView();
// The Extension Installed bubble takes on various forms, depending on the
// type of extension installed. In general, though, they are all similar:
//
// -------------------------
// | Icon | Title (x) |
// | Info |
// | Extra info |
// -------------------------
//
// Icon and Title are always shown (as well as the close button).
// Info is shown for browser actions, page actions and Omnibox keyword
// extensions and might list keyboard shorcut for the former two types.
// Extra info is...
// ... for other types, either a description of how to manage the extension
// or a link to configure the keybinding shortcut (if one exists).
// Extra info can include a promo for signing into sync.
ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL));
layout->set_minimum_cross_axis_size(kRightColumnWidth);
// Indent by the size of the icon.
layout->set_inside_border_insets(gfx::Insets(
0,
views::GetImageSkiaFromImageModel(GetWindowIcon(), nullptr).width() +
provider->GetDistanceMetric(DISTANCE_UNRELATED_CONTROL_HORIZONTAL),
0, 0));
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
SetLayoutManager(std::move(layout));
if (model_->show_how_to_use())
AddChildView(CreateLabel(model_->GetHowToUseText()));
if (model_->show_key_binding()) {
auto* manage_shortcut = AddChildView(std::make_unique<views::Link>(
l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_SHORTCUTS)));
manage_shortcut->SetCallback(base::BindRepeating(
&ExtensionInstalledBubbleView::LinkClicked, base::Unretained(this)));
}
if (model_->show_how_to_manage()) {
AddChildView(CreateLabel(
l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_INFO)));
}
}
void ExtensionInstalledBubbleView::OnEnableSync(const AccountInfo& account) {
signin_ui_util::EnableSyncFromSingleAccountPromo(
browser_, account,
signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE);
GetWidget()->Close();
}
void ExtensionInstalledBubbleView::LinkClicked() {
const GURL kUrl(base::StrCat({chrome::kChromeUIExtensionsURL,
chrome::kExtensionConfigureCommandsSubPage}));
NavigateParams params = GetSingletonTabNavigateParams(browser_, kUrl);
Navigate(&params);
GetWidget()->Close();
}
BEGIN_METADATA(ExtensionInstalledBubbleView, views::BubbleDialogDelegateView)
END_METADATA
void ShowUiOnToolbarMenu(scoped_refptr<const extensions::Extension> extension,
Browser* browser,
const SkBitmap& icon) {
ExtensionInstalledBubbleView::Show(
browser, std::make_unique<ExtensionInstalledBubbleModel>(
browser->profile(), extension.get(), icon));
}
void ExtensionInstallUIDefault::ShowPlatformBubble(
scoped_refptr<const extensions::Extension> extension,
Browser* browser,
const SkBitmap& icon) {
ExtensionInstalledWaiter::WaitForInstall(
extension, browser,
base::BindOnce(&ShowUiOnToolbarMenu, extension, browser, icon));
}