blob: 3366c1362946d2e9f0854c7fcec2abeaf2e6a696 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/glic/public/glic_enabling.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/frame/window_frame_util.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/tab_menu_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_prefs.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/commerce/product_specifications_button.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tab_search_bubble_host.h"
#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/dragging/tab_drag_controller.h"
#include "chrome/browser/ui/views/tabs/glic_button.h"
#include "chrome/browser/ui/views/tabs/new_tab_button.h"
#include "chrome/browser/ui/views/tabs/tab_search_button.h"
#include "chrome/browser/ui/views/tabs/tab_search_container.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab_strip_action_container.h"
#include "chrome/browser/ui/views/tabs/tab_strip_control_button.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip_nudge_button.h"
#include "chrome/browser/ui/views/tabs/tab_strip_scroll_container.h"
#include "chrome/browser/ui/views/tabs/tab_style_views.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/vector_icons/vector_icons.h"
#include "tab_strip_region_view.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/accessible_pane_view.h"
#include "ui/views/border.h"
#include "ui/views/cascading_property.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
namespace {
class FrameGrabHandle : public views::View {
METADATA_HEADER(FrameGrabHandle, views::View)
public:
FrameGrabHandle() {
SetProperty(views::kElementIdentifierKey,
kTabStripFrameGrabHandleElementId);
}
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override {
// Reserve some space for the frame to be grabbed by, even if the tabstrip
// is full.
// TODO(tbergquist): Define this relative to the NTB insets again.
return gfx::Size(42, 0);
}
};
BEGIN_METADATA(FrameGrabHandle)
END_METADATA
bool ShouldShowNewTabButton(BrowserWindowInterface* browser) {
// `browser` can be null in tests and `app_controller` will be null if
// the browser is not for an app.
if (browser) {
auto* const controller = web_app::AppBrowserController::From(browser);
if (controller && controller->ShouldHideNewTabButton()) {
return false;
}
}
return true;
}
// Updates the border of `view` if the insets need to be updated.
void UpdateBorderInsetsIfNeeded(views::View* view,
const gfx::Insets& new_border_insets) {
CHECK(view);
if (!view->GetBorder() ||
view->GetBorder()->GetInsets() != new_border_insets) {
view->SetBorder(views::CreateEmptyBorder(new_border_insets));
}
}
std::unique_ptr<TabStrip> CreateTabStrip(BrowserView* browser_view) {
std::unique_ptr<TabMenuModelFactory> tab_menu_model_factory;
if (browser_view && browser_view->browser()->app_controller()) {
tab_menu_model_factory =
browser_view->browser()->app_controller()->GetTabMenuModelFactory();
}
auto tabstrip_controller = std::make_unique<BrowserTabStripController>(
browser_view->browser()->GetTabStripModel(), browser_view,
std::move(tab_menu_model_factory));
BrowserTabStripController* tabstrip_controller_ptr =
tabstrip_controller.get();
auto tab_strip = std::make_unique<TabStrip>(std::move(tabstrip_controller));
tabstrip_controller_ptr->InitFromModel(tab_strip.get());
return tab_strip;
}
} // namespace
// Logger that periodically saves the tab search position. There should be 1
// instance per tabstrip.
class TabSearchPositionMetricsLogger {
public:
explicit TabSearchPositionMetricsLogger(
const Profile* profile,
base::TimeDelta logging_interval = base::Hours(1))
: profile_(profile),
logging_interval_(logging_interval),
weak_ptr_factory_(this) {
LogMetrics();
ScheduleNextLog();
}
~TabSearchPositionMetricsLogger() = default;
void LogMetricsForTesting() { LogMetrics(); }
private:
// Logs the UMA metric for the tab search position.
void LogMetrics() {
base::UmaHistogramEnumeration(
"Tabs.TabSearch.PositionInTabstrip",
tabs::GetTabSearchTrailingTabstrip(profile_)
? TabStripRegionView::TabSearchPositionEnum::kTrailing
: TabStripRegionView::TabSearchPositionEnum::kLeading);
}
// Sets up a task runner that calls back into the logging data.
void ScheduleNextLog() {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&TabSearchPositionMetricsLogger::LogMetricAndReschedule,
weak_ptr_factory_.GetWeakPtr()),
logging_interval_);
}
// Helper method for posting the task which logs and schedules the next log.
void LogMetricAndReschedule() {
LogMetrics();
ScheduleNextLog();
}
// Profile for checking the pref value.
const raw_ptr<const Profile> profile_;
// Time in which this metric should be logged. Default is hourly.
const base::TimeDelta logging_interval_;
base::WeakPtrFactory<TabSearchPositionMetricsLogger> weak_ptr_factory_;
};
TabStripRegionView::TabStripRegionView(BrowserView* browser_view)
: TabStripRegionView(CreateTabStrip(browser_view)) {}
TabStripRegionView::TabStripRegionView(std::unique_ptr<TabStrip> tab_strip)
: profile_(tab_strip->GetBrowserWindowInterface()
? tab_strip->GetBrowserWindowInterface()->GetProfile()
: nullptr),
render_tab_search_before_tab_strip_(
!tabs::GetTabSearchTrailingTabstrip(profile_) &&
!features::HasTabSearchToolbarButton()),
tab_search_position_metrics_logger_(
std::make_unique<TabSearchPositionMetricsLogger>(profile_)) {
views::SetCascadingColorProviderColor(
this, views::kCascadingBackgroundColor,
kColorTabBackgroundInactiveFrameInactive);
SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kHorizontal);
GetViewAccessibility().SetRole(ax::mojom::Role::kTabList);
GetViewAccessibility().SetIsMultiselectable(true);
tab_strip_ = tab_strip.get();
BrowserWindowInterface* browser = tab_strip->GetBrowserWindowInterface();
// Add and configure the TabSearchContainer, TabStripComboButton, and
// ProductSpecificationsButton.
std::unique_ptr<TabSearchContainer> tab_search_container;
std::unique_ptr<TabStripActionContainer> tab_strip_action_container;
std::unique_ptr<ProductSpecificationsButton> product_specifications_button;
if (browser &&
(browser->GetType() == BrowserWindowInterface::Type::TYPE_NORMAL)) {
if (features::HasTabSearchToolbarButton()) {
tab_strip_action_container = std::make_unique<TabStripActionContainer>(
tab_strip_->controller(),
browser->GetFeatures().tab_declutter_controller(),
browser->GetFeatures().glic_nudge_controller());
tab_strip_action_container->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kStart);
} else {
tab_search_container = std::make_unique<TabSearchContainer>(
tab_strip_->controller(), browser->GetTabStripModel(),
render_tab_search_before_tab_strip_, this, browser,
browser->GetFeatures().tab_declutter_controller(), tab_strip_);
tab_search_container->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
if (base::FeatureList::IsEnabled(commerce::kProductSpecifications)) {
product_specifications_button =
std::make_unique<ProductSpecificationsButton>(
tab_strip_->controller(), browser->GetTabStripModel(),
commerce::ProductSpecificationsEntryPointController::From(
browser),
render_tab_search_before_tab_strip_, this);
product_specifications_button->SetProperty(
views::kCrossAxisAlignmentKey, views::LayoutAlignment::kCenter);
}
}
}
if (tab_search_container && render_tab_search_before_tab_strip_) {
tab_search_container->SetPaintToLayer();
tab_search_container->layer()->SetFillsBoundsOpaquely(false);
tab_search_container_ = AddChildView(std::move(tab_search_container));
// Inset between the tabsearch and tabstrip should be reduced to account for
// extra spacing.
tab_search_container_->SetProperty(views::kViewIgnoredByLayoutKey, true);
if (product_specifications_button) {
product_specifications_button->SetPaintToLayer();
product_specifications_button->layer()->SetFillsBoundsOpaquely(false);
product_specifications_button_ =
AddChildView(std::move(product_specifications_button));
product_specifications_button_->SetProperty(
views::kViewIgnoredByLayoutKey, true);
}
}
if (base::FeatureList::IsEnabled(tabs::kScrollableTabStrip)) {
std::unique_ptr<TabStripScrollContainer> scroll_container =
std::make_unique<TabStripScrollContainer>(std::move(tab_strip));
tab_strip_scroll_container_ = scroll_container.get();
tab_strip_container_ = AddChildView(std::move(scroll_container));
// Allow the |tab_strip_container_| to grow into the free space available in
// the TabStripRegionView.
const views::FlexSpecification tab_strip_container_flex_spec =
views::FlexSpecification(views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kPreferred);
tab_strip_container_->SetProperty(views::kFlexBehaviorKey,
tab_strip_container_flex_spec);
} else {
tab_strip_container_ = AddChildView(std::move(tab_strip));
// Allow the |tab_strip_container_| to grow into the free space available in
// the TabStripRegionView.
const views::FlexSpecification tab_strip_container_flex_spec =
views::FlexSpecification(views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kPreferred);
tab_strip_container_->SetProperty(views::kFlexBehaviorKey,
tab_strip_container_flex_spec);
}
if (ShouldShowNewTabButton(browser)) {
std::unique_ptr<TabStripControlButton> tab_strip_control_button =
std::make_unique<NewTabButton>(
tab_strip_->controller(),
base::BindRepeating(&TabStrip::NewTabButtonPressed,
base::Unretained(tab_strip_)),
vector_icons::kAddIcon, Edge::kNone, Edge::kNone, browser);
new_tab_button_ = AddChildView(std::move(tab_strip_control_button));
new_tab_button_->SetTooltipText(
l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
new_tab_button_->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
}
reserved_grab_handle_space_ =
AddChildView(std::make_unique<FrameGrabHandle>());
reserved_grab_handle_space_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded)
.WithOrder(3));
SetProperty(views::kElementIdentifierKey, kTabStripRegionElementId);
if (browser && tab_search_container && !render_tab_search_before_tab_strip_) {
if (product_specifications_button) {
product_specifications_button_ =
AddChildView(std::move(product_specifications_button));
}
tab_search_container_ = AddChildView(std::move(tab_search_container));
tab_search_container_->SetProperty(
views::kMarginsKey,
gfx::Insets::TLBR(0, 0, 0, GetLayoutConstant(TAB_STRIP_PADDING)));
}
if (tab_strip_action_container) {
tab_strip_action_container_ =
AddChildView(std::move(tab_strip_action_container));
}
UpdateTabStripMargin();
}
TabStripRegionView::~TabStripRegionView() {
// These objects have pointers to TabStripController, which is also destoroyed
// by this class. Remove child views that hold raw_ptr to TabStripController.
if (tab_strip_action_container_) {
RemoveChildViewT(std::exchange(tab_strip_action_container_, nullptr));
}
if (new_tab_button_) {
RemoveChildViewT(std::exchange(new_tab_button_, nullptr));
}
if (tab_search_container_) {
RemoveChildViewT(std::exchange(tab_search_container_, nullptr));
}
if (product_specifications_button_) {
RemoveChildViewT(std::exchange(product_specifications_button_, nullptr));
}
}
bool TabStripRegionView::IsRectInWindowCaption(const gfx::Rect& rect) {
const auto get_target_rect = [&](views::View* target) {
gfx::RectF rect_in_target_coords_f(rect);
View::ConvertRectToTarget(this, target, &rect_in_target_coords_f);
return gfx::ToEnclosingRect(rect_in_target_coords_f);
};
// Perform checks for buttons that should be rendered above the tabstrip.
views::View* button_painted_to_layer = new_tab_button_;
if (button_painted_to_layer &&
button_painted_to_layer->GetLocalBounds().Intersects(
get_target_rect(button_painted_to_layer))) {
return !button_painted_to_layer->HitTestRect(
get_target_rect(button_painted_to_layer));
}
if (render_tab_search_before_tab_strip_ && tab_search_container_ &&
tab_search_container_->GetLocalBounds().Intersects(
get_target_rect(tab_search_container_))) {
return !tab_search_container_->HitTestRect(
get_target_rect(tab_search_container_));
}
if (render_tab_search_before_tab_strip_ && product_specifications_button_ &&
product_specifications_button_->GetLocalBounds().Intersects(
get_target_rect(product_specifications_button_))) {
return !product_specifications_button_->HitTestRect(
get_target_rect(product_specifications_button_));
}
// Perform a hit test against the |tab_strip_container_| to ensure that the
// rect is within the visible portion of the |tab_strip_| before calling the
// tab strip's |IsRectInWindowCaption()| for scrolling disabled. Defer to
// scroll container if scrolling is enabled.
// TODO(tluk): Address edge case where |rect| might partially intersect with
// the |tab_strip_container_| and the |tab_strip_| but not over the same
// pixels. This could lead to this returning false when it should be returning
// true.
if (tab_strip_container_->HitTestRect(
get_target_rect(tab_strip_container_))) {
if (base::FeatureList::IsEnabled(tabs::kScrollableTabStrip)) {
TabStripScrollContainer* scroll_container =
views::AsViewClass<TabStripScrollContainer>(tab_strip_container_);
return scroll_container->IsRectInWindowCaption(
get_target_rect(scroll_container));
} else {
return tab_strip_->IsRectInWindowCaption(get_target_rect(tab_strip_));
}
}
// The child could have a non-rectangular shape, so if the rect is not in the
// visual portions of the child view we treat it as a click to the caption.
for (View* const child : children()) {
if (child != tab_strip_container_ && child != reserved_grab_handle_space_ &&
child->GetVisible() &&
child->GetLocalBounds().Intersects(get_target_rect(child))) {
return !child->HitTestRect(get_target_rect(child));
}
}
return true;
}
bool TabStripRegionView::IsPositionInWindowCaption(const gfx::Point& point) {
return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
}
views::Button* TabStripRegionView::GetNewTabButton() {
return new_tab_button_;
}
views::View::Views TabStripRegionView::GetChildrenInZOrder() {
views::View::Views children;
if (tab_strip_container_) {
children.emplace_back(tab_strip_container_.get());
}
if (new_tab_button_) {
children.emplace_back(new_tab_button_.get());
}
if (tab_search_container_) {
children.emplace_back(tab_search_container_.get());
}
if (product_specifications_button_) {
children.emplace_back(product_specifications_button_.get());
}
if (tab_strip_action_container_) {
children.emplace_back(tab_strip_action_container_.get());
}
if (reserved_grab_handle_space_) {
children.emplace_back(reserved_grab_handle_space_.get());
}
return children;
}
// The TabSearchButton need bounds that overlap the TabStripContainer, which
// FlexLayout doesn't currently support. Because of this the TSB bounds are
// manually calculated.
void TabStripRegionView::Layout(PassKey) {
const bool tab_search_container_before_tab_strip =
tab_search_container_ && render_tab_search_before_tab_strip_;
if (tab_search_container_before_tab_strip) {
UpdateTabStripMargin();
}
LayoutSuperclass<views::AccessiblePaneView>(this);
if (tab_search_container_before_tab_strip) {
// Manually adjust x-axis position of the UI components. Currently the
// components are `tab_search_container_` and
// `product_specifications_button` if it's available.
if (product_specifications_button_) {
AdjustViewBoundsRect(product_specifications_button_, 0);
}
int product_specifications_button_width =
product_specifications_button_
? product_specifications_button_->GetPreferredSize().width()
: 0;
AdjustViewBoundsRect(tab_search_container_,
product_specifications_button_width);
}
views::View* button_to_paint_to_layer = new_tab_button_;
if (button_to_paint_to_layer) {
// The button needs to be layered on top of the tabstrip to achieve
// negative margins.
gfx::Size button_size = button_to_paint_to_layer->GetPreferredSize();
// The y position is measured from the bottom of the tabstrip, and then
// padding and button height are removed.
int x = tab_strip_container_->bounds().right() -
TabStyle::Get()->GetBottomCornerRadius() +
GetLayoutConstant(TAB_STRIP_PADDING) +
GetLayoutConstant(NEW_TAB_BUTTON_LEADING_MARGIN);
gfx::Point button_new_position = gfx::Point(x, 0);
gfx::Rect button_new_bounds = gfx::Rect(button_new_position, button_size);
// If the tabsearch button is before the tabstrip container, then manually
// set the bounds.
button_to_paint_to_layer->SetBoundsRect(button_new_bounds);
}
}
bool TabStripRegionView::CanDrop(const OSExchangeData& data) {
return TabDragController::IsSystemDnDSessionRunning() &&
data.HasCustomFormat(ui::ClipboardFormatType::CustomPlatformType(
ui::kMimeTypeWindowDrag));
}
bool TabStripRegionView::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
format_types->insert(
ui::ClipboardFormatType::CustomPlatformType(ui::kMimeTypeWindowDrag));
return true;
}
void TabStripRegionView::OnDragEntered(const ui::DropTargetEvent& event) {
CHECK(TabDragController::IsSystemDnDSessionRunning());
TabDragController::OnSystemDnDUpdated(event);
}
int TabStripRegionView::OnDragUpdated(const ui::DropTargetEvent& event) {
// This can be false because we can still receive drag events after
// TabDragController is destroyed due to the asynchronous nature of the
// platform DnD.
if (TabDragController::IsSystemDnDSessionRunning()) {
TabDragController::OnSystemDnDUpdated(event);
return ui::DragDropTypes::DRAG_MOVE;
}
return ui::DragDropTypes::DRAG_NONE;
}
void TabStripRegionView::OnDragExited() {
// See comment in OnDragUpdated().
if (TabDragController::IsSystemDnDSessionRunning()) {
TabDragController::OnSystemDnDExited();
}
}
void TabStripRegionView::ChildPreferredSizeChanged(views::View* child) {
PreferredSizeChanged();
}
gfx::Size TabStripRegionView::GetMinimumSize() const {
gfx::Size tab_strip_min_size = tab_strip_->GetMinimumSize();
// Cap the tabstrip minimum width to a reasonable value so browser windows
// aren't forced to grow arbitrarily wide.
const int max_min_width = 520;
tab_strip_min_size.set_width(
std::min(max_min_width, tab_strip_min_size.width()));
return tab_strip_min_size;
}
gfx::Size TabStripRegionView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
return GetLayoutManager()->GetPreferredSize(this, available_size);
}
views::View* TabStripRegionView::GetDefaultFocusableChild() {
auto* focusable_child = tab_strip_->GetDefaultFocusableChild();
return focusable_child ? focusable_child
: AccessiblePaneView::GetDefaultFocusableChild();
}
bool TabStripRegionView::IsTabStripEditable() const {
return tab_strip_->IsTabStripEditable();
}
void TabStripRegionView::SetTabStripNotEditableForTesting() const {
tab_strip_->SetTabStripNotEditableForTesting(); // IN-TEST
}
bool TabStripRegionView::IsTabStripCloseable() const {
return tab_strip_->IsTabStripCloseable();
}
bool TabStripRegionView::IsAnimating() const {
return tab_strip_->IsAnimatingInTabStrip();
}
void TabStripRegionView::StopAnimating() {
return tab_strip_->StopAnimating();
}
void TabStripRegionView::UpdateLoadingAnimations(
const base::TimeDelta& elapsed_time) {
tab_strip_->UpdateLoadingAnimations(elapsed_time);
}
std::optional<int> TabStripRegionView::GetFocusedTabIndex() const {
for (int i = 0; i < tab_strip_->GetTabCount(); ++i) {
if (tab_strip_->tab_at(i)->HasFocus()) {
return i;
}
}
return std::nullopt;
}
Tab* TabStripRegionView::GetTabAnchorViewAt(int tab_index) {
return tab_strip_->tab_at(tab_index);
}
views::View* TabStripRegionView::GetTabGroupAnchorView(
const tab_groups::TabGroupId& group) {
return tab_strip_->group_header(group);
}
TabDragContext* TabStripRegionView::GetDragContext() {
return tab_strip_->GetDragContext();
}
void TabStripRegionView::SetTabStripObserver(TabStripObserver* observer) {
tab_strip_->SetTabStripObserver(observer);
}
void TabStripRegionView::LogTabSearchPositionForTesting() {
tab_search_position_metrics_logger_->LogMetricsForTesting(); // IN-TEST
}
void TabStripRegionView::UpdateButtonBorders() {
const int extra_vertical_space = GetLayoutConstant(TAB_STRIP_HEIGHT) -
GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP) -
NewTabButton::kButtonSize.height();
const int top_inset = extra_vertical_space / 2;
const int bottom_inset = extra_vertical_space - top_inset +
GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP);
// The new tab button is placed vertically exactly in the center of the
// tabstrip. Extend the border of the button such that it extends to the top
// of the tabstrip bounds. This is essential to ensure it is targetable on the
// edge of the screen when in fullscreen mode and ensures the button abides
// by the correct Fitt's Law behavior (https://crbug.com/1136557).
// TODO(crbug.com/40727472): The left border is 0 in order to abut the NTB
// directly with the tabstrip. That's the best immediately available
// approximation to the prior behavior of aligning the NTB relative to the
// trailing separator (instead of the right bound of the trailing tab). This
// still isn't quite what we ideally want in the non-scrolling case, and
// definitely isn't what we want in the scrolling case, so this naive approach
// should be improved, likely by taking the scroll state of the tabstrip into
// account.
const auto border_insets = gfx::Insets::TLBR(top_inset, 0, bottom_inset, 0);
if (tab_strip_action_container_) {
tab_strip_action_container_->UpdateButtonBorders(border_insets);
}
if (new_tab_button_) {
UpdateBorderInsetsIfNeeded(new_tab_button_, border_insets);
}
if (tab_search_container_) {
UpdateBorderInsetsIfNeeded(tab_search_container_->tab_search_button(),
border_insets);
if (tab_search_container_->auto_tab_group_button()) {
UpdateBorderInsetsIfNeeded(tab_search_container_->auto_tab_group_button(),
border_insets);
}
if (tab_search_container_->tab_declutter_button()) {
UpdateBorderInsetsIfNeeded(tab_search_container_->tab_declutter_button(),
border_insets);
}
}
}
void TabStripRegionView::UpdateTabStripMargin() {
// The new tab button overlaps the tabstrip. Render it to a layer and adjust
// the tabstrip right margin to reserve space for it.
std::optional<int> tab_strip_right_margin;
views::View* button_to_paint_to_layer = new_tab_button_;
if (button_to_paint_to_layer) {
button_to_paint_to_layer->SetPaintToLayer();
button_to_paint_to_layer->layer()->SetFillsBoundsOpaquely(false);
// Inset between the tabstrip and new tab button should be reduced to
// account for extra spacing.
button_to_paint_to_layer->SetProperty(views::kViewIgnoredByLayoutKey, true);
tab_strip_right_margin =
button_to_paint_to_layer->GetPreferredSize().width() +
GetLayoutConstant(TAB_STRIP_PADDING);
}
// If the tab search button is before the tab strip, it also overlaps the
// tabstrip, so give it the same treatment.
std::optional<int> tab_strip_left_margin;
if (tab_search_container_ && render_tab_search_before_tab_strip_) {
// The `tab_search_container_` is being laid out manually.
CHECK(tab_search_container_->GetProperty(views::kViewIgnoredByLayoutKey));
// When tab search container shows before tab strip, add a margin to the
// tab_strip_container_ to leave the correct amount of space for UI
// components showing before tab strip. Currently the components are
// `tab_search_container_` and `product_specifications_button` if it's
// available.
int product_specifications_button_width =
product_specifications_button_
? product_specifications_button_->GetPreferredSize().width()
: 0;
tab_strip_left_margin = tab_search_container_->GetPreferredSize().width() +
product_specifications_button_width;
// The TabSearchContainer should be 6 pixels from the left and the tabstrip
// should have 6 px of padding between it and the tab_search button (not
// including the corner radius).
tab_strip_left_margin = tab_strip_left_margin.value() +
GetLayoutConstant(TAB_STRIP_PADDING) +
GetLayoutConstant(TAB_STRIP_PADDING) -
TabStyle::Get()->GetBottomCornerRadius();
}
UpdateButtonBorders();
if (tab_strip_left_margin.has_value() || tab_strip_right_margin.has_value()) {
tab_strip_container_->SetProperty(
views::kMarginsKey,
gfx::Insets::TLBR(0, tab_strip_left_margin.value_or(0), 0,
tab_strip_right_margin.value_or(0)));
}
}
void TabStripRegionView::AdjustViewBoundsRect(View* view, int offset) {
const gfx::Size view_size = view->GetPreferredSize();
const int x =
tab_strip_container_->x() + TabStyle::Get()->GetBottomCornerRadius() -
GetLayoutConstant(TAB_STRIP_PADDING) - view_size.width() - offset;
const gfx::Rect new_bounds = gfx::Rect(gfx::Point(x, 0), view_size);
view->SetBoundsRect(new_bounds);
}
BEGIN_METADATA(TabStripRegionView)
END_METADATA