blob: df3a23bfaaf3a87d6fc5e405c305ea62d2bed8d2 [file] [log] [blame]
// Copyright 2020 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 "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/view_ids.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/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_params.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_content_settings_container.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_utils.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_menu_button.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_origin_text.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "ui/base/hit_test.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/window/hit_test_utils.h"
namespace {
bool g_animation_disabled_for_testing = false;
} // namespace
constexpr base::TimeDelta WebAppToolbarButtonContainer::kTitlebarAnimationDelay;
constexpr base::TimeDelta WebAppToolbarButtonContainer::kOriginFadeInDuration;
constexpr base::TimeDelta WebAppToolbarButtonContainer::kOriginPauseDuration;
constexpr base::TimeDelta WebAppToolbarButtonContainer::kOriginFadeOutDuration;
// static
base::TimeDelta WebAppToolbarButtonContainer::OriginTotalDuration() {
// TimeDelta.operator+ uses time_internal::SaturatedAdd() which isn't
// constexpr, so this needs to be a function to not introduce a static
// initializer.
return kOriginFadeInDuration + kOriginPauseDuration + kOriginFadeOutDuration;
}
WebAppToolbarButtonContainer::WebAppToolbarButtonContainer(
views::Widget* widget,
BrowserView* browser_view,
ToolbarButtonProvider* toolbar_button_provider)
: browser_view_(browser_view),
toolbar_button_provider_(toolbar_button_provider),
page_action_icon_controller_(
std::make_unique<PageActionIconController>()) {
views::FlexLayout* const layout =
SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetInteriorMargin(gfx::Insets(0, WebAppFrameRightMargin()))
.SetDefault(
views::kMarginsKey,
gfx::Insets(0,
HorizontalPaddingBetweenPageActionsAndAppMenuButtons()))
.SetCollapseMargins(true)
.SetIgnoreDefaultMainAxisMargins(true)
.SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
.SetDefault(views::kFlexBehaviorKey,
views::FlexSpecification(
views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kPreferredSnapToZero)
.WithWeight(0))
.SetFlexAllocationOrder(views::FlexAllocationOrder::kReverse);
const auto* app_controller = browser_view_->browser()->app_controller();
if (app_controller->HasTitlebarAppOriginText()) {
web_app_origin_text_ = AddChildView(
std::make_unique<WebAppOriginText>(browser_view_->browser()));
}
if (app_controller->HasTitlebarContentSettings()) {
content_settings_container_ = AddChildView(
std::make_unique<WebAppContentSettingsContainer>(this, this));
views::SetHitTestComponent(content_settings_container_,
static_cast<int>(HTCLIENT));
}
// This is the point where we will be inserting page action icons.
page_action_insertion_point_ = int{children().size()};
// Insert the default page action icons.
PageActionIconParams params;
params.types_enabled = app_controller->GetTitleBarPageActions();
params.icon_color = gfx::kPlaceholderColor;
params.between_icon_spacing =
HorizontalPaddingBetweenPageActionsAndAppMenuButtons();
params.browser = browser_view_->browser();
params.command_updater = browser_view_->browser()->command_controller();
params.icon_label_bubble_delegate = this;
params.page_action_icon_delegate = this;
page_action_icon_controller_->Init(params, this);
// Do not create the extensions or browser actions container if it is a
// System Web App.
if (!web_app::IsSystemWebApp(browser_view_->browser())) {
// Extensions toolbar area with pinned extensions is lower priority than,
// for example, the menu button or other toolbar buttons, and pinned
// extensions should hide before other toolbar buttons.
constexpr int kLowPriorityFlexOrder = 2;
if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu)) {
extensions_container_ =
AddChildView(std::make_unique<ExtensionsToolbarContainer>(
browser_view_->browser(),
ExtensionsToolbarContainer::DisplayMode::kCompact));
extensions_container_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(
extensions_container_->animating_layout_manager()
->GetDefaultFlexRule())
.WithOrder(kLowPriorityFlexOrder));
views::SetHitTestComponent(extensions_container_,
static_cast<int>(HTCLIENT));
} else {
browser_actions_container_ =
AddChildView(std::make_unique<BrowserActionsContainer>(
browser_view_->browser(), nullptr, this,
false /* interactive */));
browser_actions_container_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(browser_actions_container_->GetFlexRule())
.WithOrder(kLowPriorityFlexOrder));
views::SetHitTestComponent(browser_actions_container_,
static_cast<int>(HTCLIENT));
}
}
if (app_controller->HasTitlebarMenuButton()) {
web_app_menu_button_ =
AddChildView(std::make_unique<WebAppMenuButton>(browser_view_));
web_app_menu_button_->SetID(VIEW_ID_APP_MENU);
const bool is_browser_focus_mode =
browser_view_->browser()->is_focus_mode();
SetInsetsForWebAppToolbarButton(web_app_menu_button_,
is_browser_focus_mode);
web_app_menu_button_->SetMinSize(
toolbar_button_provider_->GetToolbarButtonSize());
web_app_menu_button_->SetProperty(views::kFlexBehaviorKey,
views::FlexSpecification());
}
browser_view_->immersive_mode_controller()->AddObserver(this);
scoped_widget_observation_.Observe(widget);
}
WebAppToolbarButtonContainer::~WebAppToolbarButtonContainer() {
ImmersiveModeController* immersive_controller =
browser_view_->immersive_mode_controller();
if (immersive_controller)
immersive_controller->RemoveObserver(this);
}
void WebAppToolbarButtonContainer::UpdateStatusIconsVisibility() {
if (content_settings_container_)
content_settings_container_->UpdateContentSettingViewsVisibility();
page_action_icon_controller_->UpdateAll();
}
void WebAppToolbarButtonContainer::SetColors(SkColor foreground_color,
SkColor background_color) {
foreground_color_ = foreground_color;
background_color_ = background_color;
if (web_app_origin_text_)
web_app_origin_text_->SetTextColor(foreground_color_);
if (content_settings_container_)
content_settings_container_->SetIconColor(foreground_color_);
if (extensions_container_)
extensions_container_->OverrideIconColor(foreground_color_);
page_action_icon_controller_->SetIconColor(foreground_color_);
if (web_app_menu_button_)
web_app_menu_button_->SetColor(foreground_color_);
}
views::FlexRule WebAppToolbarButtonContainer::GetFlexRule() const {
// Prefer height consistency over accommodating edge case icons that may
// bump up the container height (e.g. extension action icons with badges).
// TODO(https://crbug.com/889745): Fix the inconsistent icon sizes found in
// the right-hand container and turn this into a DCHECK that the container
// height is the same as the app menu button height.
const auto* const layout =
static_cast<views::FlexLayout*>(GetLayoutManager());
return base::BindRepeating(
[](ToolbarButtonProvider* toolbar_button_provider,
views::FlexRule input_flex_rule, const views::View* view,
const views::SizeBounds& available_size) {
const gfx::Size preferred = input_flex_rule.Run(view, available_size);
return gfx::Size(
preferred.width(),
toolbar_button_provider->GetToolbarButtonSize().height());
},
base::Unretained(toolbar_button_provider_), layout->GetDefaultFlexRule());
}
const char* WebAppToolbarButtonContainer::GetClassName() const {
return "WebAppToolbarButtonContainer";
}
void WebAppToolbarButtonContainer::DisableAnimationForTesting() {
g_animation_disabled_for_testing = true;
}
void WebAppToolbarButtonContainer::AddPageActionIcon(views::View* icon) {
AddChildViewAt(icon, page_action_insertion_point_++);
views::SetHitTestComponent(icon, static_cast<int>(HTCLIENT));
}
int WebAppToolbarButtonContainer::GetPageActionIconSize() const {
return GetLayoutConstant(WEB_APP_PAGE_ACTION_ICON_SIZE);
}
gfx::Insets WebAppToolbarButtonContainer::GetPageActionIconInsets(
const PageActionIconView* icon_view) const {
const int icon_size = icon_view->GetImageView()->GetPreferredSize().height();
if (icon_size == 0)
return gfx::Insets();
const int height = toolbar_button_provider_->GetToolbarButtonSize().height();
const int inset_size = std::max(0, (height - icon_size) / 2);
return gfx::Insets(inset_size);
}
// Methods for coordinate the titlebar animation (origin text slide, menu
// highlight and icon fade in).
bool WebAppToolbarButtonContainer::ShouldAnimate() const {
return !g_animation_disabled_for_testing &&
!browser_view_->immersive_mode_controller()->IsEnabled();
}
void WebAppToolbarButtonContainer::StartTitlebarAnimation() {
if (!ShouldAnimate())
return;
if (web_app_origin_text_)
web_app_origin_text_->StartFadeAnimation();
if (web_app_menu_button_)
web_app_menu_button_->StartHighlightAnimation();
icon_fade_in_delay_.Start(
FROM_HERE, OriginTotalDuration(), this,
&WebAppToolbarButtonContainer::FadeInContentSettingIcons);
}
void WebAppToolbarButtonContainer::FadeInContentSettingIcons() {
if (content_settings_container_)
content_settings_container_->FadeIn();
}
void WebAppToolbarButtonContainer::ChildPreferredSizeChanged(
views::View* child) {
PreferredSizeChanged();
}
views::LabelButton* WebAppToolbarButtonContainer::GetOverflowReferenceView() {
return web_app_menu_button_;
}
base::Optional<int> WebAppToolbarButtonContainer::GetMaxBrowserActionsWidth()
const {
// Our maximum size is 1 icon so don't specify a pixel-width max here.
return base::Optional<int>();
}
bool WebAppToolbarButtonContainer::CanShowIconInToolbar() const {
return false;
}
std::unique_ptr<ToolbarActionsBar>
WebAppToolbarButtonContainer::CreateToolbarActionsBar(
ToolbarActionsBarDelegate* delegate,
Browser* browser,
ToolbarActionsBar* main_bar) const {
DCHECK_EQ(browser_view_->browser(), browser);
class WebAppToolbarActionsBar : public ToolbarActionsBar {
public:
using ToolbarActionsBar::ToolbarActionsBar;
gfx::Insets GetIconAreaInsets() const override {
// TODO(calamity): Unify these toolbar action insets with other clients
// once all toolbar button sizings are consolidated.
// https://crbug.com/822967.
return gfx::Insets(2);
}
size_t GetIconCount() const override {
// Only show an icon when an extension action is popped out due to
// activation, and none otherwise.
return GetPoppedOutAction() ? 1 : 0;
}
int GetMinimumWidth() const override {
// Allow the BrowserActionsContainer to collapse completely and be hidden
return 0;
}
private:
DISALLOW_COPY_AND_ASSIGN(WebAppToolbarActionsBar);
};
return std::make_unique<WebAppToolbarActionsBar>(delegate, browser, main_bar);
}
SkColor
WebAppToolbarButtonContainer::GetIconLabelBubbleSurroundingForegroundColor()
const {
return foreground_color_;
}
SkColor WebAppToolbarButtonContainer::GetIconLabelBubbleBackgroundColor()
const {
return background_color_;
}
bool WebAppToolbarButtonContainer::ShouldHideContentSettingImage() {
return false;
}
content::WebContents*
WebAppToolbarButtonContainer::GetContentSettingWebContents() {
return browser_view_->GetActiveWebContents();
}
ContentSettingBubbleModelDelegate*
WebAppToolbarButtonContainer::GetContentSettingBubbleModelDelegate() {
return browser_view_->browser()->content_setting_bubble_model_delegate();
}
void WebAppToolbarButtonContainer::OnContentSettingImageBubbleShown(
ContentSettingImageModel::ImageType type) const {
UMA_HISTOGRAM_ENUMERATION(
"HostedAppFrame.ContentSettings.ImagePressed", type,
ContentSettingImageModel::ImageType::NUM_IMAGE_TYPES);
}
// ImmersiveModeController::Observer:
void WebAppToolbarButtonContainer::OnImmersiveRevealStarted() {
// Don't wait for the fade in animation to make content setting icons
// visible once in immersive mode.
if (content_settings_container_)
content_settings_container_->EnsureVisible();
}
// PageActionIconView::Delegate:
content::WebContents*
WebAppToolbarButtonContainer::GetWebContentsForPageActionIconView() {
return browser_view_->GetActiveWebContents();
}
void WebAppToolbarButtonContainer::OnWidgetVisibilityChanged(
views::Widget* widget,
bool visible) {
if (!visible || !pending_widget_visibility_)
return;
pending_widget_visibility_ = false;
if (ShouldAnimate()) {
if (content_settings_container_)
content_settings_container_->SetUpForFadeIn();
animation_start_delay_.Start(
FROM_HERE, kTitlebarAnimationDelay, this,
&WebAppToolbarButtonContainer::StartTitlebarAnimation);
}
}