1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/web_apps/web_app_frame_toolbar_view.h"
6 
7 #include <memory>
8 
9 #include "base/feature_list.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/numerics/ranges.h"
12 #include "base/scoped_observer.h"
13 #include "base/task_runner.h"
14 #include "base/threading/sequenced_task_runner_handle.h"
15 #include "base/timer/timer.h"
16 #include "chrome/app/chrome_command_ids.h"
17 #include "chrome/app/vector_icons/vector_icons.h"
18 #include "chrome/browser/command_observer.h"
19 #include "chrome/browser/ui/browser_command_controller.h"
20 #include "chrome/browser/ui/browser_commands.h"
21 #include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
22 #include "chrome/browser/ui/content_settings/content_setting_image_model.h"
23 #include "chrome/browser/ui/layout_constants.h"
24 #include "chrome/browser/ui/ui_features.h"
25 #include "chrome/browser/ui/view_ids.h"
26 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
27 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
28 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
29 #include "chrome/browser/ui/views/frame/browser_view.h"
30 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
31 #include "chrome/browser/ui/views/location_bar/content_setting_image_view.h"
32 #include "chrome/browser/ui/views/page_action/page_action_icon_container.h"
33 #include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
34 #include "chrome/browser/ui/views/page_action/page_action_icon_params.h"
35 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
36 #include "chrome/browser/ui/views/toolbar/back_forward_button.h"
37 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
38 #include "chrome/browser/ui/views/toolbar/reload_button.h"
39 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
40 #include "chrome/browser/ui/views/web_apps/web_app_menu_button.h"
41 #include "chrome/browser/ui/views/web_apps/web_app_origin_text.h"
42 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
43 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
44 #include "components/content_settings/core/browser/cookie_settings.h"
45 #include "components/content_settings/core/common/features.h"
46 #include "components/vector_icons/vector_icons.h"
47 #include "ui/base/hit_test.h"
48 #include "ui/base/pointer/touch_ui_controller.h"
49 #include "ui/base/window_open_disposition.h"
50 #include "ui/compositor/layer_animation_element.h"
51 #include "ui/compositor/layer_animation_sequence.h"
52 #include "ui/compositor/scoped_layer_animation_settings.h"
53 #include "ui/events/event.h"
54 #include "ui/gfx/color_utils.h"
55 #include "ui/gfx/geometry/insets.h"
56 #include "ui/gfx/geometry/rect.h"
57 #include "ui/gfx/geometry/rect_f.h"
58 #include "ui/gfx/paint_vector_icon.h"
59 #include "ui/gfx/vector_icon_types.h"
60 #include "ui/views/animation/ink_drop.h"
61 #include "ui/views/border.h"
62 #include "ui/views/controls/button/button.h"
63 #include "ui/views/controls/button/menu_button.h"
64 #include "ui/views/controls/label.h"
65 #include "ui/views/layout/box_layout.h"
66 #include "ui/views/layout/flex_layout.h"
67 #include "ui/views/layout/flex_layout_types.h"
68 #include "ui/views/layout/layout_provider.h"
69 #include "ui/views/layout/layout_types.h"
70 #include "ui/views/view.h"
71 #include "ui/views/view_class_properties.h"
72 #include "ui/views/widget/widget.h"
73 #include "ui/views/widget/widget_observer.h"
74 #include "ui/views/window/custom_frame_view.h"
75 #include "ui/views/window/hit_test_utils.h"
76 
77 #if defined(OS_WIN)
78 #include "base/win/windows_version.h"
79 #endif
80 
81 namespace {
82 
83 bool g_animation_disabled_for_testing = false;
84 
85 constexpr base::TimeDelta kContentSettingsFadeInDuration =
86     base::TimeDelta::FromMilliseconds(500);
87 
88 constexpr int kPaddingBetweenNavigationButtons = 9;
89 
90 #if defined(OS_CHROMEOS)
91 constexpr int kWebAppFrameLeftMargin = 4;
92 #else
93 constexpr int kWebAppFrameLeftMargin = 9;
94 #endif
95 
96 class WebAppToolbarActionsBar : public ToolbarActionsBar {
97  public:
98   using ToolbarActionsBar::ToolbarActionsBar;
99 
GetIconAreaInsets() const100   gfx::Insets GetIconAreaInsets() const override {
101     // TODO(calamity): Unify these toolbar action insets with other clients once
102     // all toolbar button sizings are consolidated. https://crbug.com/822967.
103     return gfx::Insets(2);
104   }
105 
GetIconCount() const106   size_t GetIconCount() const override {
107     // Only show an icon when an extension action is popped out due to
108     // activation, and none otherwise.
109     return GetPoppedOutAction() ? 1 : 0;
110   }
111 
GetMinimumWidth() const112   int GetMinimumWidth() const override {
113     // Allow the BrowserActionsContainer to collapse completely and be hidden
114     return 0;
115   }
116 
117  private:
118   DISALLOW_COPY_AND_ASSIGN(WebAppToolbarActionsBar);
119 };
120 
121 template <class BaseClass>
122 class WebAppToolbarButton : public BaseClass {
123  public:
124   using BaseClass::BaseClass;
125   WebAppToolbarButton(const WebAppToolbarButton&) = delete;
126   WebAppToolbarButton& operator=(const WebAppToolbarButton&) = delete;
127   ~WebAppToolbarButton() override = default;
128 
129 #if defined(OS_WIN)
ShouldUseWindowsIconsForMinimalUI() const130   bool ShouldUseWindowsIconsForMinimalUI() const {
131     return base::win::GetVersion() >= base::win::Version::WIN10;
132   }
133 #endif
134 
SetIconColor(SkColor icon_color)135   void SetIconColor(SkColor icon_color) {
136     if (icon_color_ == icon_color)
137       return;
138 
139     icon_color_ = icon_color;
140     UpdateIcon();
141   }
142 
GetAlternativeIcon() const143   virtual const gfx::VectorIcon* GetAlternativeIcon() const { return nullptr; }
144 
145   // ToolbarButton:
UpdateIcon()146   void UpdateIcon() override {
147     if (const auto* icon = GetAlternativeIcon()) {
148       BaseClass::UpdateIconsWithStandardColors(*icon);
149       return;
150     }
151 
152     BaseClass::UpdateIcon();
153   }
154 
155  protected:
156   // ToolbarButton:
GetForegroundColor(views::Button::ButtonState state) const157   SkColor GetForegroundColor(views::Button::ButtonState state) const override {
158     if (state == views::Button::STATE_DISABLED)
159       return SkColorSetA(icon_color_, gfx::kDisabledControlAlpha);
160 
161     return icon_color_;
162   }
163 
164  private:
165   SkColor icon_color_ = gfx::kPlaceholderColor;
166 };
167 
168 class WebAppToolbarBackButton : public WebAppToolbarButton<BackForwardButton> {
169  public:
170   WebAppToolbarBackButton(PressedCallback callback, Browser* browser);
171   WebAppToolbarBackButton(const WebAppToolbarBackButton&) = delete;
172   WebAppToolbarBackButton& operator=(const WebAppToolbarBackButton&) = delete;
173   ~WebAppToolbarBackButton() override = default;
174 
175   // WebAppToolbarButton:
176   const gfx::VectorIcon* GetAlternativeIcon() const override;
177 };
178 
WebAppToolbarBackButton(PressedCallback callback,Browser * browser)179 WebAppToolbarBackButton::WebAppToolbarBackButton(PressedCallback callback,
180                                                  Browser* browser)
181     : WebAppToolbarButton<BackForwardButton>(
182           BackForwardButton::Direction::kBack,
183           std::move(callback),
184           browser) {}
185 
GetAlternativeIcon() const186 const gfx::VectorIcon* WebAppToolbarBackButton::GetAlternativeIcon() const {
187 #if defined(OS_WIN)
188   if (ShouldUseWindowsIconsForMinimalUI()) {
189     return ui::TouchUiController::Get()->touch_ui()
190                ? &kBackArrowWindowsTouchIcon
191                : &kBackArrowWindowsIcon;
192   }
193 #endif
194   return nullptr;
195 }
196 
197 class WebAppToolbarReloadButton : public WebAppToolbarButton<ReloadButton> {
198  public:
199   using WebAppToolbarButton<ReloadButton>::WebAppToolbarButton;
200   WebAppToolbarReloadButton(const WebAppToolbarReloadButton&) = delete;
201   WebAppToolbarReloadButton& operator=(const WebAppToolbarReloadButton&) =
202       delete;
203   ~WebAppToolbarReloadButton() override = default;
204 
205   // WebAppToolbarButton:
206   const gfx::VectorIcon* GetAlternativeIcon() const override;
207 };
208 
GetAlternativeIcon() const209 const gfx::VectorIcon* WebAppToolbarReloadButton::GetAlternativeIcon() const {
210 #if defined(OS_WIN)
211   if (ShouldUseWindowsIconsForMinimalUI()) {
212     const bool is_reload = visible_mode() == ReloadButton::Mode::kReload;
213     if (ui::TouchUiController::Get()->touch_ui()) {
214       return is_reload ? &kReloadWindowsTouchIcon
215                        : &kNavigateStopWindowsTouchIcon;
216     }
217     return is_reload ? &kReloadWindowsIcon : &kNavigateStopWindowsIcon;
218   }
219 #endif
220   return nullptr;
221 }
222 
HorizontalPaddingBetweenPageActionsAndAppMenuButtons()223 int HorizontalPaddingBetweenPageActionsAndAppMenuButtons() {
224   return views::LayoutProvider::Get()->GetDistanceMetric(
225       views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
226 }
227 
WebAppFrameRightMargin()228 int WebAppFrameRightMargin() {
229 #if defined(OS_MAC)
230   return kWebAppMenuMargin;
231 #else
232   return HorizontalPaddingBetweenPageActionsAndAppMenuButtons();
233 #endif
234 }
235 
236 // An ink drop with round corners in shown when the user hovers over the button.
237 // Insets are kept small to avoid increasing web app frame toolbar height.
SetInsetsForWebAppToolbarButton(ToolbarButton * toolbar_button,bool is_browser_focus_mode)238 void SetInsetsForWebAppToolbarButton(ToolbarButton* toolbar_button,
239                                      bool is_browser_focus_mode) {
240   if (!is_browser_focus_mode)
241     toolbar_button->SetLayoutInsets(gfx::Insets(2));
242 }
243 
244 }  // namespace
245 
246 const char WebAppFrameToolbarView::kViewClassName[] = "WebAppFrameToolbarView";
247 
248 constexpr base::TimeDelta WebAppFrameToolbarView::kTitlebarAnimationDelay;
249 constexpr base::TimeDelta WebAppFrameToolbarView::kOriginFadeInDuration;
250 constexpr base::TimeDelta WebAppFrameToolbarView::kOriginPauseDuration;
251 constexpr base::TimeDelta WebAppFrameToolbarView::kOriginFadeOutDuration;
252 
253 // static
OriginTotalDuration()254 base::TimeDelta WebAppFrameToolbarView::OriginTotalDuration() {
255   // TimeDelta.operator+ uses time_internal::SaturatedAdd() which isn't
256   // constexpr, so this needs to be a function to not introduce a static
257   // initializer.
258   return kOriginFadeInDuration + kOriginPauseDuration + kOriginFadeOutDuration;
259 }
260 
261 class WebAppFrameToolbarView::ContentSettingsContainer : public views::View {
262  public:
263   ContentSettingsContainer(
264       IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
265       ContentSettingImageView::Delegate* content_setting_image_delegate);
266   ~ContentSettingsContainer() override = default;
267 
UpdateContentSettingViewsVisibility()268   void UpdateContentSettingViewsVisibility() {
269     for (auto* v : content_setting_views_)
270       v->Update();
271   }
272 
273   // Sets the color of the content setting icons.
SetIconColor(SkColor icon_color)274   void SetIconColor(SkColor icon_color) {
275     for (auto* v : content_setting_views_)
276       v->SetIconColor(icon_color);
277   }
278 
SetUpForFadeIn()279   void SetUpForFadeIn() {
280     SetVisible(false);
281     SetPaintToLayer();
282     layer()->SetFillsBoundsOpaquely(false);
283     layer()->SetOpacity(0);
284   }
285 
FadeIn()286   void FadeIn() {
287     if (GetVisible())
288       return;
289     SetVisible(true);
290     DCHECK_EQ(layer()->opacity(), 0);
291     ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
292     settings.SetTransitionDuration(kContentSettingsFadeInDuration);
293     layer()->SetOpacity(1);
294   }
295 
EnsureVisible()296   void EnsureVisible() {
297     SetVisible(true);
298     if (layer())
299       layer()->SetOpacity(1);
300   }
301 
get_content_setting_views() const302   const std::vector<ContentSettingImageView*>& get_content_setting_views()
303       const {
304     return content_setting_views_;
305   }
306 
307  private:
308   // views::View:
GetClassName() const309   const char* GetClassName() const override {
310     return "WebAppFrameToolbarView::ContentSettingsContainer";
311   }
312 
313   // Owned by the views hierarchy.
314   std::vector<ContentSettingImageView*> content_setting_views_;
315 
316   DISALLOW_COPY_AND_ASSIGN(ContentSettingsContainer);
317 };
318 
ContentSettingsContainer(IconLabelBubbleView::Delegate * icon_label_bubble_delegate,ContentSettingImageView::Delegate * content_setting_image_delegate)319 WebAppFrameToolbarView::ContentSettingsContainer::ContentSettingsContainer(
320     IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
321     ContentSettingImageView::Delegate* content_setting_image_delegate) {
322   views::BoxLayout& layout =
323       *SetLayoutManager(std::make_unique<views::BoxLayout>(
324           views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
325           views::LayoutProvider::Get()->GetDistanceMetric(
326               views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
327   // Right align to clip the leftmost items first when not enough space.
328   layout.set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
329 
330   std::vector<std::unique_ptr<ContentSettingImageModel>> models =
331       ContentSettingImageModel::GenerateContentSettingImageModels();
332   for (auto& model : models) {
333     auto image_view = std::make_unique<ContentSettingImageView>(
334         std::move(model), icon_label_bubble_delegate,
335         content_setting_image_delegate,
336         views::CustomFrameView::GetWindowTitleFontList());
337     // Padding around content setting icons.
338     constexpr auto kContentSettingIconInteriorPadding = gfx::Insets(4);
339     image_view->SetBorder(
340         views::CreateEmptyBorder(kContentSettingIconInteriorPadding));
341     image_view->disable_animation();
342     views::SetHitTestComponent(image_view.get(), static_cast<int>(HTCLIENT));
343     content_setting_views_.push_back(image_view.get());
344     AddChildView(image_view.release());
345   }
346 }
347 
348 // Holds controls in the far left of the toolbar.
349 class WebAppFrameToolbarView::NavigationButtonContainer
350     : public views::View,
351       public CommandObserver {
352  public:
353   explicit NavigationButtonContainer(BrowserView* browser_view);
354   ~NavigationButtonContainer() override;
355 
back_button()356   WebAppToolbarBackButton* back_button() { return back_button_; }
reload_button()357   WebAppToolbarReloadButton* reload_button() { return reload_button_; }
358 
SetIconColor(SkColor icon_color)359   void SetIconColor(SkColor icon_color) {
360     back_button_->SetIconColor(icon_color);
361     reload_button_->SetIconColor(icon_color);
362   }
363 
364  protected:
365   // CommandObserver:
EnabledStateChangedForCommand(int id,bool enabled)366   void EnabledStateChangedForCommand(int id, bool enabled) override {
367     switch (id) {
368       case IDC_BACK:
369         back_button_->SetEnabled(enabled);
370         break;
371       case IDC_RELOAD:
372         reload_button_->SetEnabled(enabled);
373         break;
374       default:
375         NOTREACHED();
376     }
377   }
378 
379  private:
380   // views::View:
GetClassName() const381   const char* GetClassName() const override {
382     return "WebAppFrameToolbarView::NavigationButtonContainer";
383   }
384 
385   // The containing browser.
386   Browser* const browser_;
387 
388   // These members are owned by the views hierarchy.
389   WebAppToolbarBackButton* back_button_ = nullptr;
390   WebAppToolbarReloadButton* reload_button_ = nullptr;
391 };
392 
NavigationButtonContainer(BrowserView * browser_view)393 WebAppFrameToolbarView::NavigationButtonContainer::NavigationButtonContainer(
394     BrowserView* browser_view)
395     : browser_(browser_view->browser()) {
396   views::BoxLayout& layout =
397       *SetLayoutManager(std::make_unique<views::BoxLayout>(
398           views::BoxLayout::Orientation::kHorizontal,
399           gfx::Insets(0, kWebAppFrameLeftMargin),
400           kPaddingBetweenNavigationButtons));
401   // Right align to clip the leftmost items first when not enough space.
402   layout.set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
403   layout.set_cross_axis_alignment(
404       views::BoxLayout::CrossAxisAlignment::kCenter);
405 
406   back_button_ = AddChildView(std::make_unique<WebAppToolbarBackButton>(
407       base::BindRepeating(
408           [](Browser* browser, const ui::Event& event) {
409             chrome::ExecuteCommandWithDisposition(
410                 browser, IDC_BACK,
411                 ui::DispositionFromEventFlags(event.flags()));
412           },
413           browser_),
414       browser_));
415   back_button_->set_tag(IDC_BACK);
416   reload_button_ = AddChildView(std::make_unique<WebAppToolbarReloadButton>(
417       browser_->command_controller()));
418   reload_button_->set_tag(IDC_RELOAD);
419 
420   const bool is_browser_focus_mode = browser_->is_focus_mode();
421   SetInsetsForWebAppToolbarButton(back_button_, is_browser_focus_mode);
422   SetInsetsForWebAppToolbarButton(reload_button_, is_browser_focus_mode);
423 
424   views::SetHitTestComponent(back_button_, static_cast<int>(HTCLIENT));
425   views::SetHitTestComponent(reload_button_, static_cast<int>(HTCLIENT));
426 
427   chrome::AddCommandObserver(browser_, IDC_BACK, this);
428   chrome::AddCommandObserver(browser_, IDC_RELOAD, this);
429 }
430 
431 WebAppFrameToolbarView::NavigationButtonContainer::
~NavigationButtonContainer()432     ~NavigationButtonContainer() {
433   chrome::RemoveCommandObserver(browser_, IDC_BACK, this);
434   chrome::RemoveCommandObserver(browser_, IDC_RELOAD, this);
435 }
436 
437 // Holds controls in the far right of the toolbar.
438 // Forces a layout of the toolbar (and hence the window text) whenever a control
439 // changes visibility.
440 class WebAppFrameToolbarView::ToolbarButtonContainer
441     : public views::View,
442       public BrowserActionsContainer::Delegate,
443       public IconLabelBubbleView::Delegate,
444       public ContentSettingImageView::Delegate,
445       public ImmersiveModeController::Observer,
446       public PageActionIconView::Delegate,
447       public PageActionIconContainer,
448       public views::WidgetObserver {
449  public:
450   ToolbarButtonContainer(views::Widget* widget,
451                          BrowserView* browser_view,
452                          ToolbarButtonProvider* toolbar_button_provider);
453   ~ToolbarButtonContainer() override;
454 
UpdateStatusIconsVisibility()455   void UpdateStatusIconsVisibility() {
456     if (content_settings_container_)
457       content_settings_container_->UpdateContentSettingViewsVisibility();
458     page_action_icon_controller_->UpdateAll();
459   }
460 
SetColors(SkColor foreground_color,SkColor background_color)461   void SetColors(SkColor foreground_color, SkColor background_color) {
462     foreground_color_ = foreground_color;
463     background_color_ = background_color;
464     if (web_app_origin_text_)
465       web_app_origin_text_->SetTextColor(foreground_color_);
466     if (content_settings_container_)
467       content_settings_container_->SetIconColor(foreground_color_);
468     if (extensions_container_)
469       extensions_container_->OverrideIconColor(foreground_color_);
470     page_action_icon_controller_->SetIconColor(foreground_color_);
471     if (web_app_menu_button_)
472       web_app_menu_button_->SetColor(foreground_color_);
473   }
474 
GetFlexRule() const475   views::FlexRule GetFlexRule() const {
476     // Prefer height consistency over accommodating edge case icons that may
477     // bump up the container height (e.g. extension action icons with badges).
478     // TODO(https://crbug.com/889745): Fix the inconsistent icon sizes found in
479     // the right-hand container and turn this into a DCHECK that the container
480     // height is the same as the app menu button height.
481     const auto* const layout =
482         static_cast<views::FlexLayout*>(GetLayoutManager());
483     return base::BindRepeating(
484         [](ToolbarButtonProvider* toolbar_button_provider,
485            views::FlexRule input_flex_rule, const views::View* view,
486            const views::SizeBounds& available_size) {
487           const gfx::Size preferred = input_flex_rule.Run(view, available_size);
488           return gfx::Size(
489               preferred.width(),
490               toolbar_button_provider->GetToolbarButtonSize().height());
491         },
492         base::Unretained(toolbar_button_provider_),
493         layout->GetDefaultFlexRule());
494   }
495 
content_settings_container()496   ContentSettingsContainer* content_settings_container() {
497     return content_settings_container_;
498   }
499 
page_action_icon_controller()500   PageActionIconController* page_action_icon_controller() {
501     return page_action_icon_controller_.get();
502   }
503 
browser_actions_container()504   BrowserActionsContainer* browser_actions_container() {
505     return browser_actions_container_;
506   }
507 
extensions_container()508   ExtensionsToolbarContainer* extensions_container() {
509     return extensions_container_;
510   }
511 
web_app_menu_button()512   WebAppMenuButton* web_app_menu_button() { return web_app_menu_button_; }
513 
514  private:
515   // views::View:
GetClassName() const516   const char* GetClassName() const override {
517     return "WebAppFrameToolbarView::ToolbarButtonContainer";
518   }
519 
520   // PageActionIconContainer:
AddPageActionIcon(views::View * icon)521   void AddPageActionIcon(views::View* icon) override {
522     AddChildViewAt(icon, page_action_insertion_point_++);
523     views::SetHitTestComponent(icon, static_cast<int>(HTCLIENT));
524   }
525 
526   // PageActionIconView::Delegate:
GetPageActionIconSize() const527   int GetPageActionIconSize() const override {
528     return GetLayoutConstant(WEB_APP_PAGE_ACTION_ICON_SIZE);
529   }
530 
GetPageActionIconInsets(const PageActionIconView * icon_view) const531   gfx::Insets GetPageActionIconInsets(
532       const PageActionIconView* icon_view) const override {
533     const int icon_size =
534         icon_view->GetImageView()->GetPreferredSize().height();
535     if (icon_size == 0)
536       return gfx::Insets();
537 
538     const int height =
539         toolbar_button_provider_->GetToolbarButtonSize().height();
540     const int inset_size = std::max(0, (height - icon_size) / 2);
541     return gfx::Insets(inset_size);
542   }
543 
544   // Methods for coordinate the titlebar animation (origin text slide, menu
545   // highlight and icon fade in).
ShouldAnimate() const546   bool ShouldAnimate() const {
547     return !g_animation_disabled_for_testing &&
548            !browser_view_->immersive_mode_controller()->IsEnabled();
549   }
550 
StartTitlebarAnimation()551   void StartTitlebarAnimation() {
552     if (!ShouldAnimate())
553       return;
554 
555     if (web_app_origin_text_)
556       web_app_origin_text_->StartFadeAnimation();
557     if (web_app_menu_button_)
558       web_app_menu_button_->StartHighlightAnimation();
559     icon_fade_in_delay_.Start(FROM_HERE, OriginTotalDuration(), this,
560                               &WebAppFrameToolbarView::ToolbarButtonContainer::
561                                   FadeInContentSettingIcons);
562   }
563 
FadeInContentSettingIcons()564   void FadeInContentSettingIcons() {
565     if (content_settings_container_)
566       content_settings_container_->FadeIn();
567   }
568 
ChildPreferredSizeChanged(views::View * child)569   void ChildPreferredSizeChanged(views::View* child) override {
570     PreferredSizeChanged();
571   }
572 
573   // BrowserActionsContainer::Delegate:
GetOverflowReferenceView()574   views::LabelButton* GetOverflowReferenceView() override {
575     return web_app_menu_button_;
576   }
GetMaxBrowserActionsWidth() const577   base::Optional<int> GetMaxBrowserActionsWidth() const override {
578     // Our maximum size is 1 icon so don't specify a pixel-width max here.
579     return base::Optional<int>();
580   }
CanShowIconInToolbar() const581   bool CanShowIconInToolbar() const override { return false; }
CreateToolbarActionsBar(ToolbarActionsBarDelegate * delegate,Browser * browser,ToolbarActionsBar * main_bar) const582   std::unique_ptr<ToolbarActionsBar> CreateToolbarActionsBar(
583       ToolbarActionsBarDelegate* delegate,
584       Browser* browser,
585       ToolbarActionsBar* main_bar) const override {
586     DCHECK_EQ(browser_view_->browser(), browser);
587     return std::make_unique<WebAppToolbarActionsBar>(delegate, browser,
588                                                      main_bar);
589   }
590 
591   // IconLabelBubbleView::Delegate:
GetIconLabelBubbleSurroundingForegroundColor() const592   SkColor GetIconLabelBubbleSurroundingForegroundColor() const override {
593     return foreground_color_;
594   }
GetIconLabelBubbleBackgroundColor() const595   SkColor GetIconLabelBubbleBackgroundColor() const override {
596     return background_color_;
597   }
598 
599   // ContentSettingImageView::Delegate:
ShouldHideContentSettingImage()600   bool ShouldHideContentSettingImage() override { return false; }
GetContentSettingWebContents()601   content::WebContents* GetContentSettingWebContents() override {
602     return browser_view_->GetActiveWebContents();
603   }
GetContentSettingBubbleModelDelegate()604   ContentSettingBubbleModelDelegate* GetContentSettingBubbleModelDelegate()
605       override {
606     return browser_view_->browser()->content_setting_bubble_model_delegate();
607   }
OnContentSettingImageBubbleShown(ContentSettingImageModel::ImageType type) const608   void OnContentSettingImageBubbleShown(
609       ContentSettingImageModel::ImageType type) const override {
610     UMA_HISTOGRAM_ENUMERATION(
611         "HostedAppFrame.ContentSettings.ImagePressed", type,
612         ContentSettingImageModel::ImageType::NUM_IMAGE_TYPES);
613   }
614 
615   // ImmersiveModeController::Observer:
OnImmersiveRevealStarted()616   void OnImmersiveRevealStarted() override {
617     // Don't wait for the fade in animation to make content setting icons
618     // visible once in immersive mode.
619     if (content_settings_container_)
620       content_settings_container_->EnsureVisible();
621   }
622 
623   // PageActionIconView::Delegate:
GetWebContentsForPageActionIconView()624   content::WebContents* GetWebContentsForPageActionIconView() override {
625     return browser_view_->GetActiveWebContents();
626   }
627 
628   // views::WidgetObserver:
629   void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
630 
631   // Whether we're waiting for the widget to become visible.
632   bool pending_widget_visibility_ = true;
633 
634   ScopedObserver<views::Widget, views::WidgetObserver> scoped_widget_observer_{
635       this};
636 
637   // Timers for synchronising their respective parts of the titlebar animation.
638   base::OneShotTimer animation_start_delay_;
639   base::OneShotTimer icon_fade_in_delay_;
640 
641   // The containing browser view.
642   BrowserView* const browser_view_;
643   ToolbarButtonProvider* const toolbar_button_provider_;
644 
645   SkColor foreground_color_ = gfx::kPlaceholderColor;
646   SkColor background_color_ = gfx::kPlaceholderColor;
647 
648   std::unique_ptr<PageActionIconController> page_action_icon_controller_;
649   int page_action_insertion_point_ = 0;
650 
651   // All remaining members are owned by the views hierarchy.
652   WebAppOriginText* web_app_origin_text_ = nullptr;
653   ContentSettingsContainer* content_settings_container_ = nullptr;
654   BrowserActionsContainer* browser_actions_container_ = nullptr;
655   ExtensionsToolbarContainer* extensions_container_ = nullptr;
656   WebAppMenuButton* web_app_menu_button_ = nullptr;
657 };
658 
ToolbarButtonContainer(views::Widget * widget,BrowserView * browser_view,ToolbarButtonProvider * toolbar_button_provider)659 WebAppFrameToolbarView::ToolbarButtonContainer::ToolbarButtonContainer(
660     views::Widget* widget,
661     BrowserView* browser_view,
662     ToolbarButtonProvider* toolbar_button_provider)
663     : browser_view_(browser_view),
664       toolbar_button_provider_(toolbar_button_provider),
665       page_action_icon_controller_(
666           std::make_unique<PageActionIconController>()) {
667   views::FlexLayout* const layout =
668       SetLayoutManager(std::make_unique<views::FlexLayout>());
669   layout->SetOrientation(views::LayoutOrientation::kHorizontal)
670       .SetInteriorMargin(gfx::Insets(0, WebAppFrameRightMargin()))
671       .SetDefault(
672           views::kMarginsKey,
673           gfx::Insets(0,
674                       HorizontalPaddingBetweenPageActionsAndAppMenuButtons()))
675       .SetCollapseMargins(true)
676       .SetIgnoreDefaultMainAxisMargins(true)
677       .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
678       .SetDefault(views::kFlexBehaviorKey,
679                   views::FlexSpecification(
680                       views::LayoutOrientation::kHorizontal,
681                       views::MinimumFlexSizeRule::kPreferredSnapToZero)
682                       .WithWeight(0))
683       .SetFlexAllocationOrder(views::FlexAllocationOrder::kReverse);
684 
685   const auto* app_controller = browser_view_->browser()->app_controller();
686 
687   if (app_controller->HasTitlebarAppOriginText()) {
688     web_app_origin_text_ = AddChildView(
689         std::make_unique<WebAppOriginText>(browser_view_->browser()));
690   }
691 
692   if (app_controller->HasTitlebarContentSettings()) {
693     content_settings_container_ =
694         AddChildView(std::make_unique<ContentSettingsContainer>(this, this));
695     views::SetHitTestComponent(content_settings_container_,
696                                static_cast<int>(HTCLIENT));
697   }
698 
699   // This is the point where we will be inserting page action icons.
700   page_action_insertion_point_ = int{children().size()};
701 
702   // Insert the default page action icons.
703   PageActionIconParams params;
704   params.types_enabled = app_controller->GetTitleBarPageActions();
705   params.icon_color = gfx::kPlaceholderColor;
706   params.between_icon_spacing =
707       HorizontalPaddingBetweenPageActionsAndAppMenuButtons();
708   params.browser = browser_view_->browser();
709   params.command_updater = browser_view_->browser()->command_controller();
710   params.icon_label_bubble_delegate = this;
711   params.page_action_icon_delegate = this;
712   page_action_icon_controller_->Init(params, this);
713 
714   // Do not create the extensions or browser actions container if it is a
715   // System Web App.
716   if (!web_app::IsSystemWebApp(browser_view_->browser())) {
717     // Extensions toolbar area with pinned extensions is lower priority than,
718     // for example, the menu button or other toolbar buttons, and pinned
719     // extensions should hide before other toolbar buttons.
720     constexpr int kLowPriorityFlexOrder = 2;
721     if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu)) {
722       extensions_container_ =
723           AddChildView(std::make_unique<ExtensionsToolbarContainer>(
724               browser_view_->browser(),
725               ExtensionsToolbarContainer::DisplayMode::kCompact));
726       extensions_container_->SetProperty(
727           views::kFlexBehaviorKey,
728           views::FlexSpecification(
729               extensions_container_->animating_layout_manager()
730                   ->GetDefaultFlexRule())
731               .WithOrder(kLowPriorityFlexOrder));
732       views::SetHitTestComponent(extensions_container_,
733                                  static_cast<int>(HTCLIENT));
734     } else {
735       browser_actions_container_ =
736           AddChildView(std::make_unique<BrowserActionsContainer>(
737               browser_view_->browser(), nullptr, this,
738               false /* interactive */));
739       browser_actions_container_->SetProperty(
740           views::kFlexBehaviorKey,
741           views::FlexSpecification(browser_actions_container_->GetFlexRule())
742               .WithOrder(kLowPriorityFlexOrder));
743       views::SetHitTestComponent(browser_actions_container_,
744                                  static_cast<int>(HTCLIENT));
745     }
746   }
747 
748   if (app_controller->HasTitlebarMenuButton()) {
749     web_app_menu_button_ =
750         AddChildView(std::make_unique<WebAppMenuButton>(browser_view_));
751     web_app_menu_button_->SetID(VIEW_ID_APP_MENU);
752     const bool is_browser_focus_mode =
753         browser_view_->browser()->is_focus_mode();
754     SetInsetsForWebAppToolbarButton(web_app_menu_button_,
755                                     is_browser_focus_mode);
756     web_app_menu_button_->SetMinSize(
757         toolbar_button_provider_->GetToolbarButtonSize());
758     web_app_menu_button_->SetProperty(views::kFlexBehaviorKey,
759                                       views::FlexSpecification());
760   }
761 
762   browser_view_->immersive_mode_controller()->AddObserver(this);
763   scoped_widget_observer_.Add(widget);
764 }
765 
~ToolbarButtonContainer()766 WebAppFrameToolbarView::ToolbarButtonContainer::~ToolbarButtonContainer() {
767   ImmersiveModeController* immersive_controller =
768       browser_view_->immersive_mode_controller();
769   if (immersive_controller)
770     immersive_controller->RemoveObserver(this);
771 }
772 
OnWidgetVisibilityChanged(views::Widget * widget,bool visible)773 void WebAppFrameToolbarView::ToolbarButtonContainer::OnWidgetVisibilityChanged(
774     views::Widget* widget,
775     bool visible) {
776   if (!visible || !pending_widget_visibility_)
777     return;
778   pending_widget_visibility_ = false;
779   if (ShouldAnimate()) {
780     if (content_settings_container_)
781       content_settings_container_->SetUpForFadeIn();
782     animation_start_delay_.Start(
783         FROM_HERE, kTitlebarAnimationDelay, this,
784         &WebAppFrameToolbarView::ToolbarButtonContainer::
785             StartTitlebarAnimation);
786   }
787 }
788 
WebAppFrameToolbarView(views::Widget * widget,BrowserView * browser_view)789 WebAppFrameToolbarView::WebAppFrameToolbarView(views::Widget* widget,
790                                                BrowserView* browser_view)
791     : browser_view_(browser_view) {
792   DCHECK(browser_view_);
793   DCHECK(web_app::AppBrowserController::IsForWebAppBrowser(
794       browser_view_->browser()));
795   SetID(VIEW_ID_WEB_APP_FRAME_TOOLBAR);
796 
797   {
798     // TODO(tluk) fix the need for both LayoutInContainer() and a layout
799     // manager for frame layout.
800     views::FlexLayout* layout =
801         SetLayoutManager(std::make_unique<views::FlexLayout>());
802     layout->SetOrientation(views::LayoutOrientation::kHorizontal);
803     layout->SetMainAxisAlignment(views::LayoutAlignment::kEnd);
804     layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
805   }
806 
807   const auto* app_controller = browser_view_->browser()->app_controller();
808 
809   if (app_controller->HasMinimalUiButtons()) {
810     left_container_ = AddChildView(
811         std::make_unique<NavigationButtonContainer>(browser_view_));
812     left_container_->SetProperty(
813         views::kFlexBehaviorKey,
814         views::FlexSpecification(
815             views::LayoutOrientation::kHorizontal,
816             views::MinimumFlexSizeRule::kScaleToMinimumSnapToZero)
817             .WithOrder(2));
818   }
819 
820   center_container_ = AddChildView(std::make_unique<views::View>());
821   center_container_->SetProperty(
822       views::kFlexBehaviorKey,
823       views::FlexSpecification(views::LayoutOrientation::kHorizontal,
824                                views::MinimumFlexSizeRule::kScaleToZero,
825                                views::MaximumFlexSizeRule::kUnbounded)
826           .WithOrder(3));
827 
828   right_container_ = AddChildView(
829       std::make_unique<ToolbarButtonContainer>(widget, browser_view, this));
830   right_container_->SetProperty(
831       views::kFlexBehaviorKey,
832       views::FlexSpecification(right_container_->GetFlexRule()).WithOrder(1));
833 
834   UpdateStatusIconsVisibility();
835 
836   DCHECK(!browser_view_->toolbar_button_provider() ||
837          browser_view_->toolbar_button_provider()
838                  ->GetAsAccessiblePaneView()
839                  ->GetClassName() == GetClassName())
840       << "This should be the first ToolbarButtorProvider or a replacement for "
841          "an existing instance of this class during a window frame refresh.";
842   browser_view_->SetToolbarButtonProvider(this);
843 }
844 
845 WebAppFrameToolbarView::~WebAppFrameToolbarView() = default;
846 
UpdateStatusIconsVisibility()847 void WebAppFrameToolbarView::UpdateStatusIconsVisibility() {
848   right_container_->UpdateStatusIconsVisibility();
849 }
850 
UpdateCaptionColors()851 void WebAppFrameToolbarView::UpdateCaptionColors() {
852   const BrowserNonClientFrameView* frame_view =
853       browser_view_->frame()->GetFrameView();
854   DCHECK(frame_view);
855 
856   active_background_color_ =
857       frame_view->GetFrameColor(BrowserFrameActiveState::kActive);
858   active_foreground_color_ =
859       frame_view->GetCaptionColor(BrowserFrameActiveState::kActive);
860   inactive_background_color_ =
861       frame_view->GetFrameColor(BrowserFrameActiveState::kInactive);
862   inactive_foreground_color_ =
863       frame_view->GetCaptionColor(BrowserFrameActiveState::kInactive);
864   UpdateChildrenColor();
865 }
866 
SetPaintAsActive(bool active)867 void WebAppFrameToolbarView::SetPaintAsActive(bool active) {
868   if (paint_as_active_ == active)
869     return;
870   paint_as_active_ = active;
871   UpdateChildrenColor();
872 }
873 
LayoutInContainer(int leading_x,int trailing_x,int y,int available_height)874 std::pair<int, int> WebAppFrameToolbarView::LayoutInContainer(
875     int leading_x,
876     int trailing_x,
877     int y,
878     int available_height) {
879   SetVisible(available_height > 0);
880 
881   if (available_height == 0) {
882     SetSize(gfx::Size());
883     return std::pair<int, int>(0, 0);
884   }
885 
886   gfx::Size preferred_size = GetPreferredSize();
887   const int width = std::max(trailing_x - leading_x, 0);
888   const int height = preferred_size.height();
889   DCHECK_LE(height, available_height);
890   SetBounds(leading_x, y + (available_height - height) / 2, width, height);
891   Layout();
892 
893   if (!center_container_->GetVisible())
894     return std::pair<int, int>(0, 0);
895 
896   // Bounds for remaining inner space, in parent container coordinates.
897   gfx::Rect center_bounds = center_container_->bounds();
898   DCHECK(center_bounds.x() == 0 || left_container_);
899   center_bounds.Offset(bounds().OffsetFromOrigin());
900 
901   return std::pair<int, int>(center_bounds.x(), center_bounds.right());
902 }
903 
GetBrowserActionsContainer()904 BrowserActionsContainer* WebAppFrameToolbarView::GetBrowserActionsContainer() {
905   CHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
906   return right_container_->browser_actions_container();
907 }
908 
909 ExtensionsToolbarContainer*
GetExtensionsToolbarContainer()910 WebAppFrameToolbarView::GetExtensionsToolbarContainer() {
911   return right_container_->extensions_container();
912 }
913 
GetToolbarButtonSize() const914 gfx::Size WebAppFrameToolbarView::GetToolbarButtonSize() const {
915   constexpr int kFocusModeButtonSize = 34;
916   int size = browser_view_->browser()->is_focus_mode()
917                  ? kFocusModeButtonSize
918                  : GetLayoutConstant(WEB_APP_MENU_BUTTON_SIZE);
919   return gfx::Size(size, size);
920 }
921 
GetDefaultExtensionDialogAnchorView()922 views::View* WebAppFrameToolbarView::GetDefaultExtensionDialogAnchorView() {
923   if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu))
924     return right_container_->extensions_container()->extensions_button();
925   return GetAppMenuButton();
926 }
927 
GetPageActionIconView(PageActionIconType type)928 PageActionIconView* WebAppFrameToolbarView::GetPageActionIconView(
929     PageActionIconType type) {
930   return right_container_->page_action_icon_controller()->GetIconView(type);
931 }
932 
GetAppMenuButton()933 AppMenuButton* WebAppFrameToolbarView::GetAppMenuButton() {
934   return right_container_->web_app_menu_button();
935 }
936 
GetFindBarBoundingBox(int contents_bottom)937 gfx::Rect WebAppFrameToolbarView::GetFindBarBoundingBox(int contents_bottom) {
938   if (!IsDrawn())
939     return gfx::Rect();
940 
941   // If LTR find bar will be right aligned so align to right edge of app menu
942   // button. Otherwise it will be left aligned so align to the left edge of the
943   // app menu button.
944   views::View* anchor_view = GetAnchorView(PageActionIconType::kFind);
945   gfx::Rect anchor_bounds =
946       anchor_view->ConvertRectToWidget(anchor_view->GetLocalBounds());
947   int x_pos = 0;
948   int width = anchor_bounds.right();
949   if (base::i18n::IsRTL()) {
950     x_pos = anchor_bounds.x();
951     width = GetWidget()->GetRootView()->width() - anchor_bounds.x();
952   }
953   return gfx::Rect(x_pos, anchor_bounds.bottom(), width,
954                    contents_bottom - anchor_bounds.bottom());
955 }
956 
FocusToolbar()957 void WebAppFrameToolbarView::FocusToolbar() {
958   SetPaneFocus(nullptr);
959 }
960 
GetAsAccessiblePaneView()961 views::AccessiblePaneView* WebAppFrameToolbarView::GetAsAccessiblePaneView() {
962   return this;
963 }
964 
GetAnchorView(PageActionIconType type)965 views::View* WebAppFrameToolbarView::GetAnchorView(PageActionIconType type) {
966   views::View* anchor = GetAppMenuButton();
967   return anchor ? anchor : this;
968 }
969 
ZoomChangedForActiveTab(bool can_show_bubble)970 void WebAppFrameToolbarView::ZoomChangedForActiveTab(bool can_show_bubble) {
971   right_container_->page_action_icon_controller()->ZoomChangedForActiveTab(
972       can_show_bubble);
973 }
974 
GetAvatarToolbarButton()975 AvatarToolbarButton* WebAppFrameToolbarView::GetAvatarToolbarButton() {
976   return nullptr;
977 }
978 
GetBackButton()979 ToolbarButton* WebAppFrameToolbarView::GetBackButton() {
980   return left_container_ ? left_container_->back_button() : nullptr;
981 }
982 
GetReloadButton()983 ReloadButton* WebAppFrameToolbarView::GetReloadButton() {
984   return left_container_ ? left_container_->reload_button() : nullptr;
985 }
986 
DisableAnimationForTesting()987 void WebAppFrameToolbarView::DisableAnimationForTesting() {
988   g_animation_disabled_for_testing = true;
989 }
990 
GetLeftContainerForTesting()991 views::View* WebAppFrameToolbarView::GetLeftContainerForTesting() {
992   return left_container_;
993 }
994 
GetRightContainerForTesting()995 views::View* WebAppFrameToolbarView::GetRightContainerForTesting() {
996   return right_container_;
997 }
998 
999 PageActionIconController*
GetPageActionIconControllerForTesting()1000 WebAppFrameToolbarView::GetPageActionIconControllerForTesting() {
1001   return right_container_->page_action_icon_controller();
1002 }
1003 
GetClassName() const1004 const char* WebAppFrameToolbarView::GetClassName() const {
1005   return kViewClassName;
1006 }
1007 
ChildPreferredSizeChanged(views::View * child)1008 void WebAppFrameToolbarView::ChildPreferredSizeChanged(views::View* child) {
1009   PreferredSizeChanged();
1010 }
1011 
OnThemeChanged()1012 void WebAppFrameToolbarView::OnThemeChanged() {
1013   views::AccessiblePaneView::OnThemeChanged();
1014   UpdateCaptionColors();
1015 }
1016 
GetContentSettingContainerForTesting()1017 views::View* WebAppFrameToolbarView::GetContentSettingContainerForTesting() {
1018   return right_container_->content_settings_container();
1019 }
1020 
1021 const std::vector<ContentSettingImageView*>&
GetContentSettingViewsForTesting() const1022 WebAppFrameToolbarView::GetContentSettingViewsForTesting() const {
1023   return right_container_->content_settings_container()
1024       ->get_content_setting_views();
1025 }
1026 
UpdateChildrenColor()1027 void WebAppFrameToolbarView::UpdateChildrenColor() {
1028   const SkColor foreground_color =
1029       paint_as_active_ ? active_foreground_color_ : inactive_foreground_color_;
1030   if (left_container_)
1031     left_container_->SetIconColor(foreground_color);
1032   right_container_->SetColors(
1033       foreground_color,
1034       paint_as_active_ ? active_background_color_ : inactive_background_color_);
1035 }
1036