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