blob: d4c4305a3a62d7e6195f8595647b2becb2e9cb6e [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_types.h"
#include "chrome/browser/ui/views/frame/browser_root_view.h"
#include "chrome/browser/ui/views/tabs/dragging/tab_drag_context.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_container.h"
#include "chrome/browser/ui/views/tabs/tab_container_controller.h"
#include "chrome/browser/ui/views/tabs/tab_group_header.h"
#include "chrome/browser/ui/views/tabs/tab_group_views.h"
#include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/view.h"
#include "ui/views/view_model.h"
#include "ui/views/widget/widget_observer.h"
class Tab;
class TabGroup;
class TabHoverCardController;
class TabStripController;
class TabStripObserver;
class TabStyle;
namespace gfx {
class Rect;
}
namespace tab_groups {
class TabGroupId;
}
namespace ui {
class ListSelectionModel;
}
namespace tabs {
enum class TabAlert;
} // namespace tabs
// A View that represents the TabStripModel. The TabStrip has the
// following responsibilities:
//
// - It implements the TabStripModelObserver interface, and acts as a
// container for Tabs, and is also responsible for creating them.
//
// - It takes part in Tab Drag & Drop with Tab, TabDragHelper and
// DraggedTab, focusing on tasks that require reshuffling other tabs
// in response to dragged tabs.
class TabStrip : public views::View,
public views::ViewObserver,
public views::WidgetObserver,
public TabContainerController,
public TabSlotController,
public BrowserRootView::DropTarget {
METADATA_HEADER(TabStrip, views::View)
public:
explicit TabStrip(std::unique_ptr<TabStripController> controller);
TabStrip(const TabStrip&) = delete;
TabStrip& operator=(const TabStrip&) = delete;
~TabStrip() override;
void SetAvailableWidthCallback(
base::RepeatingCallback<int()> available_width_callback);
void NewTabButtonPressed(const ui::Event& event);
// Returns the size needed for the specified views. This is invoked during
// drag and drop to calculate offsets and positioning.
static int GetSizeNeededForViews(const std::vector<TabSlotView*>& views);
// Sets the observer to be notified of changes within this TabStrip.
void SetTabStripObserver(TabStripObserver* observer);
// Scroll the tabstrip towards the trailing tabs by an offset
void ScrollTowardsTrailingTabs(int offset);
// Scroll the tabstrip towards the leading tabs by an offset
void ScrollTowardsLeadingTabs(int offset);
// Returns true if the specified rect (in TabStrip coordinates) intersects
// the window caption area of the browser window.
bool IsRectInWindowCaption(const gfx::Rect& rect);
// Returns false when there is a drag operation in progress so that the frame
// doesn't close.
bool IsTabStripCloseable() const;
base::WeakPtr<TabStrip> AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
// Returns true if the tab strip is editable. Returns false if the tab strip
// is being dragged or animated to prevent extensions from messing things up
// while that's happening.
bool IsTabStripEditable() const;
// Returns information about tabs at given indices.
bool IsTabCrashed(int tab_index) const;
bool TabHasNetworkError(int tab_index) const;
std::optional<tabs::TabAlert> GetTabAlertState(int tab_index) const;
// Updates the loading animations displayed by tabs in the tabstrip to the
// next frame. The `elapsed_time` parameter is shared between tabs and used to
// keep the throbbers in sync.
void UpdateLoadingAnimations(const base::TimeDelta& elapsed_time);
// Adds tabs at the specified indices.
void AddTabsAt(std::vector<std::pair<int, TabRendererData>> tabs_datas);
// Moves a tab.
void MoveTab(int from_model_index, int to_model_index, TabRendererData data);
// Removes a tab at the specified index. If the tab with `contents` is being
// dragged then the drag is completed.
void RemoveTabAt(content::WebContents* contents,
int model_index,
bool was_active);
void OnTabWillBeRemoved(content::WebContents* contents, int model_index);
// Sets the tab data at the specified model index.
void SetTabData(int model_index, TabRendererData data);
// Sets the tab group at the specified model index.
void AddTabToGroup(std::optional<tab_groups::TabGroupId> group,
int model_index);
// Creates the views associated with a newly-created tab group.
void OnGroupCreated(const tab_groups::TabGroupId& group);
// Opens the editor bubble for the tab `group` as a result of an explicit user
// action to create the `group`.
void OnGroupEditorOpened(const tab_groups::TabGroupId& group);
// Updates the group's contents and metadata when its tab membership changes.
// This should be called when a tab is added to or removed from a group.
void OnGroupContentsChanged(const tab_groups::TabGroupId& group);
// Updates the group's tabs and header when its associated TabGroupVisualData
// changes. This should be called when the result of
// `TabStripController::GetGroupTitle(group)` or
// `TabStripController::GetGroupColorId(group)` changes.
void OnGroupVisualsChanged(const tab_groups::TabGroupId& group,
const tab_groups::TabGroupVisualData* old_visuals,
const tab_groups::TabGroupVisualData* new_visuals);
// Handles animations relating to toggling the collapsed state of a group.
void ToggleTabGroup(const tab_groups::TabGroupId& group,
bool is_collapsing,
ToggleTabGroupCollapsedStateOrigin origin);
// Updates the ordering of the group header when the whole group is moved.
// Needed to ensure display and focus order of the group header view.
void OnGroupMoved(const tab_groups::TabGroupId& group);
// Destroys the views associated with a recently deleted tab group.
void OnGroupClosed(const tab_groups::TabGroupId& group);
// Updates the tab slot view split state and animates to bounds.
void OnSplitCreated(const std::vector<int>& split_indices,
split_tabs::SplitTabId split_id);
// Updates the tab slot view split state and animates to bounds.
void OnSplitRemoved(const std::vector<int>& split_indices);
// Updates the tab slot view split state and animates to bounds.
void OnSplitContentsChanged(const std::vector<int>& split_indices);
// Returns whether or not strokes should be drawn around and under the tabs.
bool ShouldDrawStrokes() const;
// Invoked when the selection is updated.
void SetSelection(const ui::ListSelectionModel& new_selection);
// Invoked when a tab needs to show UI that it needs the user's attention.
void SetTabNeedsAttention(int model_index, bool attention);
// Invoked when a tab group needs to show UI that it needs the user's
// attention.
void SetTabGroupNeedsAttention(const tab_groups::TabGroupId& id,
bool attention);
// Returns the TabGroupHeader with ID `id`.
TabGroupHeader* group_header(const tab_groups::TabGroupId& id) const {
return tab_container_->GetGroupViews(id)->header();
}
// Returns the TabGroup with ID `id`.
TabGroup* GetTabGroup(const tab_groups::TabGroupId& id) const override;
// Returns the index of the specified view in the model coordinate system, or
// std::nullopt if view is closing not a tab, or is not in this tabstrip.
// TODO(tbergquist): This should return an optional<size_t>.
std::optional<int> GetModelIndexOf(const TabSlotView* view) const;
// Gets the number of Tabs in the tab strip.
int GetTabCount() const;
// Cover method for TabStripController::GetCount.
int GetModelCount() const;
// Returns the number of pinned tabs.
int GetModelPinnedTabCount() const;
TabStripController* controller() const { return controller_.get(); }
TabDragContext* GetDragContext();
// Stops any ongoing animations and lays out the tabstrip.
void StopAnimating();
// Returns a view for anchoring an in-product help promo. `index_hint`
// indicates at which tab the promo should be displayed, but is not
// binding.
views::View* GetTabViewForPromoAnchor(int index_hint);
// Gets the default focusable child view in the TabStrip.
views::View* GetDefaultFocusableChild();
// The browser window interface for the hosting browser.
BrowserWindowInterface* GetBrowserWindowInterface();
// TabContainerController:
bool IsValidModelIndex(int index) const override;
std::optional<int> GetActiveIndex() const override;
int NumPinnedTabsInModel() const override;
void OnDropIndexUpdate(std::optional<int> index, bool drop_before) override;
bool IsBrowserClosing() const override;
std::optional<int> GetFirstTabInGroup(
const tab_groups::TabGroupId& group) const override;
gfx::Range ListTabsInGroup(
const tab_groups::TabGroupId& group) const override;
bool CanExtendDragHandle() const override;
const views::View* GetTabClosingModeMouseWatcherHostView() const override;
bool IsAnimatingInTabStrip() const override;
void UpdateAnimationTarget(
TabSlotView* tab_slot_view,
gfx::Rect target_bounds_in_tab_container_coords) override;
// TabContainerController AND TabSlotController:
bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const override;
// TabSlotController:
const ui::ListSelectionModel& GetSelectionModel() const override;
Tab* tab_at(int index) const override;
void SelectTab(Tab* tab, const ui::Event& event) override;
void ExtendSelectionTo(Tab* tab) override;
void ToggleSelected(Tab* tab) override;
void AddSelectionFromAnchorTo(Tab* tab) override;
void CloseTab(Tab* tab, CloseTabSource source) override;
void ToggleTabAudioMute(Tab* tab) override;
void ShiftTabNext(Tab* tab) override;
void ShiftTabPrevious(Tab* tab) override;
void MoveTabFirst(Tab* tab) override;
void MoveTabLast(Tab* tab) override;
using TabSlotController::ToggleTabGroupCollapsedState;
void ToggleTabGroupCollapsedState(
const tab_groups::TabGroupId group,
ToggleTabGroupCollapsedStateOrigin origin) override;
void NotifyTabstripBubbleOpened() override;
void NotifyTabstripBubbleClosed() override;
void ShowContextMenuForTab(Tab* tab,
const gfx::Point& p,
ui::mojom::MenuSourceType source_type) override;
bool IsActiveTab(const Tab* tab) const override;
bool IsTabSelected(const Tab* tab) const override;
bool IsTabPinned(const Tab* tab) const override;
bool IsTabFirst(const Tab* tab) const override;
bool IsFocusInTabs() const override;
bool ShouldCompactLeadingEdge() const override;
void MaybeStartDrag(
TabSlotView* source,
const ui::LocatedEvent& event,
const ui::ListSelectionModel& original_selection) override;
[[nodiscard]] Liveness ContinueDrag(views::View* view,
const ui::LocatedEvent& event) override;
bool EndDrag(EndDragReason reason) override;
Tab* GetTabAt(const gfx::Point& point) override;
Tab* GetAdjacentTab(const Tab* tab, int offset) override;
std::vector<Tab*> GetTabsInSplit(const Tab* tab) override;
void OnMouseEventInTab(views::View* source,
const ui::MouseEvent& event) override;
void UpdateHoverCard(Tab* tab, HoverCardUpdateType update_type) override;
bool HoverCardIsShowingForTab(Tab* tab) override;
void ShowHover(Tab* tab, TabStyle::ShowHoverStyle style) override;
void HideHover(Tab* tab, TabStyle::HideHoverStyle style) override;
int GetStrokeThickness() const override;
bool CanPaintThrobberToLayer() const override;
bool HasVisibleBackgroundTabShapes() const override;
SkColor GetTabSeparatorColor() const override;
SkColor GetTabForegroundColor(TabActive active) const override;
std::u16string GetAccessibleTabName(const Tab* tab) const override;
std::optional<int> GetCustomBackgroundId(
BrowserFrameActiveState active_state) const override;
float GetHoverOpacityForTab(float range_parameter) const override;
float GetHoverOpacityForRadialHighlight() const override;
std::u16string GetGroupTitle(
const tab_groups::TabGroupId& group) const override;
std::u16string GetGroupContentString(
const tab_groups::TabGroupId& group) const override;
tab_groups::TabGroupColorId GetGroupColorId(
const tab_groups::TabGroupId& group) const override;
SkColor GetPaintedGroupColor(
const tab_groups::TabGroupColorId& color_id) const override;
void ShiftGroupLeft(const tab_groups::TabGroupId& group) override;
void ShiftGroupRight(const tab_groups::TabGroupId& group) override;
Browser* GetBrowser() override;
bool IsFrameCondensed() const override;
#if BUILDFLAG(IS_CHROMEOS)
bool IsLockedForOnTask() override;
#endif
// views::View:
views::SizeBounds GetAvailableSize(const View* child) const override;
gfx::Size GetMinimumSize() const override;
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override;
void Layout(PassKey) override;
void ChildPreferredSizeChanged(views::View* child) override;
// BrowserRootView::DropTarget:
// These methods handle link drag & drop.
// TODO(https://crbug.com/40828528): Use the standard views::View drag and
// drop methods instead.
std::optional<BrowserRootView::DropIndex> GetDropIndex(
const ui::DropTargetEvent& event) override;
BrowserRootView::DropTarget* GetDropTarget(
gfx::Point loc_in_local_coords) override;
views::View* GetViewForDrop() override;
void SetTabStripNotEditableForTesting();
TabHoverCardController* hover_card_controller_for_testing() {
return hover_card_controller_.get();
}
private:
class TabDragContextImpl;
friend class TabDragControllerTest;
friend class TabDragContextImpl;
friend class TabGroupEditorBubbleViewDialogBrowserTest;
friend class TabStripTestBase;
friend class TabStripRegionViewTestBase;
class TabContextMenuController : public views::ContextMenuController {
public:
explicit TabContextMenuController(TabStrip* parent);
// views::ContextMenuController:
void ShowContextMenuForViewImpl(
views::View* source,
const gfx::Point& point,
ui::mojom::MenuSourceType source_type) override;
private:
const raw_ptr<TabStrip> parent_;
};
void Init();
std::map<tab_groups::TabGroupId, TabGroupHeader*> GetGroupHeaders();
void MaybeUpdateGroupOnTabChanged(int model_index);
// Returns whether the close button should be highlighted after a remove.
bool ShouldHighlightCloseButtonAfterRemove();
// Returns whether the window background behind the tabstrip is transparent.
bool TitlebarBackgroundIsTransparent() const;
// Returns the last tab in the strip that's actually visible. This will be
// the actual last tab unless the strip is in the overflow node_data.
const Tab* GetLastVisibleTab() const;
// Closes the tab at `model_index`.
void CloseTabInternal(int model_index, CloseTabSource source);
// Computes and stores values derived from contrast ratios.
void UpdateContrastRatioValues();
// Determines whether a tab can be shifted by one in the direction of `offset`
// and moves it if possible.
void ShiftTabRelative(Tab* tab, int offset);
// Determines whether a group can be shifted by one in the direction of
// `offset` and moves it if possible.
void ShiftGroupRelative(const tab_groups::TabGroupId& group, int offset);
// views::View:
void OnMouseEntered(const ui::MouseEvent& event) override;
void OnMouseExited(const ui::MouseEvent& event) override;
void AddedToWidget() override;
void RemovedFromWidget() override;
void OnThemeChanged() override;
// ui::EventHandler:
void OnGestureEvent(ui::GestureEvent* event) override;
// views::ViewObserver:
void OnViewFocused(views::View* observed_view) override;
void OnViewBlurred(views::View* observed_view) override;
// views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
void OnTouchUiChanged();
// Screen-reader-only announcements that depend on tab group titles.
void AnnounceTabAddedToGroup(tab_groups::TabGroupId group_id);
void AnnounceTabRemovedFromGroup(tab_groups::TabGroupId group_id);
// -- Member Variables ------------------------------------------------------
raw_ptr<TabStripObserver> observer_;
std::unique_ptr<TabStripController> controller_;
std::unique_ptr<TabHoverCardController> hover_card_controller_;
raw_ref<TabDragContextImpl, AcrossTasksDanglingUntriaged> drag_context_;
// The View parent for the tabs and the various group views.
raw_ref<TabContainer, AcrossTasksDanglingUntriaged> tab_container_;
// Location of the mouse at the time of the last move.
gfx::Point last_mouse_move_location_;
// Used to track the time needed to create a new tab from the new tab button.
std::optional<base::TimeTicks> new_tab_button_pressed_start_time_;
// Used for seek time metrics from the time the mouse enters the tabstrip.
std::optional<base::TimeTicks> mouse_entered_tabstrip_time_;
// Used to track if the time from mouse entered to tab switch has been
// reported.
bool has_reported_time_mouse_entered_to_switch_ = false;
// Used to track if the tab dragging metrics have been reported.
bool has_reported_tab_drag_metrics_ = false;
// Used to track the time of last tab dragging.
std::optional<base::TimeTicks> last_tab_drag_time_;
// Used to count the number of tab dragging in the last 30 minutes and 5
// minutes.
int tab_drag_count_30min_ = 0;
int tab_drag_count_5min_ = 0;
std::unique_ptr<base::RepeatingTimer> tab_drag_count_timer_30min_;
std::unique_ptr<base::RepeatingTimer> tab_drag_count_timer_5min_;
const raw_ptr<const TabStyle> style_;
// Number of mouse moves.
int mouse_move_count_ = 0;
// This represents the Tabs in `tabs_` that have been selected.
//
// Each time tab selection should change, this class will receive a
// SetSelection() callback with the new tab selection. That callback only
// includes the new selection model. This keeps track of the previous
// selection model, and is always consistent with `tabs_`. This must be
// updated to account for tab insertions/removals/moves.
ui::ListSelectionModel selected_tabs_;
// When tabs are hovered, a radial highlight is shown and the tab opacity is
// adjusted using some value between `hover_opacity_min_` and
// `hover_opacity_max_` (depending on tab width). All these opacities depend
// on contrast ratios and are updated when colors or active state changes,
// so for efficiency's sake they are computed and stored once here instead
// of with each tab. Note: these defaults will be overwritten at construction
// except in cases where a unit test provides no controller_.
float hover_opacity_min_ = 1.0f;
float hover_opacity_max_ = 1.0f;
float radial_highlight_opacity_ = 1.0f;
SkColor separator_color_ = gfx::kPlaceholderColor;
// If true simulates a non-editable tab strip for testing.
bool tab_strip_not_editable_for_testing_ = false;
base::CallbackListSubscription paint_as_active_subscription_;
const base::CallbackListSubscription subscription_ =
ui::TouchUiController::Get()->RegisterCallback(
base::BindRepeating(&TabStrip::OnTouchUiChanged,
base::Unretained(this)));
TabContextMenuController context_menu_controller_{this};
base::WeakPtrFactory<TabStrip> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_