blob: fde20bc312ed5e735c8477145ec8c666dbb6d41c [file] [log] [blame]
// Copyright 2025 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/bookmarks/bookmark_bar_controller.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/sad_tab.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h"
#include "chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.h"
#include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/saved_tab_groups/public/tab_group_sync_service.h"
#include "components/tabs/public/split_tab_data.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
namespace {
// TODO(crbug.com/382494946): Similar bespoke checks are used throughout the
// codebase. This should be factored out as a common util and other callsites
// converted to use this.
bool IsShowingNTP(content::WebContents* web_contents) {
if (SadTab::ShouldShow(web_contents->GetCrashedStatus())) {
return false;
}
// Use the committed entry (or the visible entry, if the committed entry is
// the initial NavigationEntry) so the bookmarks bar disappears at the same
// time the page does.
content::NavigationEntry* entry =
web_contents->GetController().GetLastCommittedEntry();
if (entry->IsInitialEntry()) {
entry = web_contents->GetController().GetVisibleEntry();
}
const GURL& url = entry->GetURL();
return NewTabUI::IsNewTab(url) || NewTabPageUI::IsNewTabPageOrigin(url) ||
NewTabPageThirdPartyUI::IsNewTabPageOrigin(url) ||
search::NavEntryIsInstantNTP(web_contents, entry);
}
} // namespace
DEFINE_USER_DATA(BookmarkBarController);
BookmarkBarController::BookmarkBarController(BrowserWindowInterface& browser,
TabStripModel& tab_strip_model)
: browser_(browser),
tab_strip_model_(tab_strip_model),
scoped_data_holder_(browser.GetUnownedUserDataHost(), *this) {
tab_strip_model_->AddObserver(this);
// Set up preference observer for bookmark bar visibility.
Profile* profile = browser_->GetProfile();
pref_change_registrar_.Init(profile->GetPrefs());
pref_change_registrar_.Add(
bookmarks::prefs::kShowBookmarkBar,
base::BindRepeating(&BookmarkBarController::UpdateBookmarkBarState,
base::Unretained(this),
StateChangeReason::kPrefChange));
// Initialize the bookmark bar state.
UpdateBookmarkBarState(StateChangeReason::kInit);
}
BookmarkBarController::~BookmarkBarController() = default;
BookmarkBarController* BookmarkBarController::From(
BrowserWindowInterface* browser_window_interface) {
return ui::ScopedUnownedUserData<BookmarkBarController>::Get(
browser_window_interface->GetUnownedUserDataHost());
}
const BookmarkBarController* BookmarkBarController::From(
const BrowserWindowInterface* browser_window_interface) {
return ui::ScopedUnownedUserData<BookmarkBarController>::Get(
browser_window_interface->GetUnownedUserDataHost());
}
void BookmarkBarController::SetForceShowBookmarkBarFlag(ForceShowFlag flag) {
force_show_bookmark_bar_flags_ |= flag;
UpdateBookmarkBarState(StateChangeReason::kForceShow);
}
void BookmarkBarController::ClearForceShowBookmarkBarFlag(ForceShowFlag flag) {
force_show_bookmark_bar_flags_ &= ~flag;
UpdateBookmarkBarState(StateChangeReason::kForceShow);
}
void BookmarkBarController::UpdateBookmarkBarState(StateChangeReason reason) {
BookmarkBar::State state =
ShouldShowBookmarkBar() ? BookmarkBar::SHOW : BookmarkBar::HIDDEN;
if (state == bookmark_bar_state_) {
return;
}
bookmark_bar_state_ = state;
if (reason == StateChangeReason::kTabSwitch) {
// Don't notify BrowserWindow on a tab switch as at the time this is invoked
// BrowserWindow hasn't yet switched tabs. The BrowserWindow implementations
// end up querying state once they process the tab switch.
return;
}
bool should_animate = reason == StateChangeReason::kPrefChange ||
reason == StateChangeReason::kForceShow;
Browser* browser = browser_->GetBrowserForMigrationOnly();
if (browser && browser->window()) {
browser->window()->BookmarkBarStateChanged(
should_animate ? BookmarkBar::ANIMATE_STATE_CHANGE
: BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
}
}
void BookmarkBarController::OnSplitTabChanged(const SplitTabChange& change) {
if (change.type == SplitTabChange::Type::kAdded ||
change.type == SplitTabChange::Type::kRemoved) {
UpdateBookmarkBarState(StateChangeReason::kSplitTabChange);
}
}
bool BookmarkBarController::ShouldShowBookmarkBar() const {
Profile* profile = browser_->GetProfile();
if (profile->IsGuestSession()) {
return false;
}
if (browser_defaults::bookmarks_enabled &&
profile->GetPrefs()->GetBoolean(bookmarks::prefs::kShowBookmarkBar) &&
!browser_->ShouldHideUIForFullscreen()) {
return true;
}
if (force_show_bookmark_bar_flags_ != ForceShowFlag::kNone) {
return true;
}
if (!browser_defaults::bookmarks_enabled) {
return false;
}
PrefService* prefs = profile->GetPrefs();
if (prefs->IsManagedPreference(bookmarks::prefs::kShowBookmarkBar) &&
!prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar)) {
return false;
}
std::vector<tabs::TabInterface*> tabs = tab_strip_model_->GetForegroundTabs();
if (tabs.empty()) {
return false;
}
bookmarks::BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(profile);
const bool has_bookmarks = bookmark_model && bookmark_model->HasBookmarks();
tab_groups::TabGroupSyncService* tab_group_service =
tab_groups::TabGroupSyncServiceFactory::GetForProfile(profile);
const bool has_saved_tab_groups =
tab_group_service && !tab_group_service->GetAllGroups().empty();
// The bookmark bar is only shown if the user has added something to it.
if (!has_bookmarks && !has_saved_tab_groups) {
return false;
}
// Prevent the bookmark bar from showing itself when entering fullscreen if
// fullscreen is entered through webview (TAB). This creates a consistent
// experience for split view fullscreen and the rest of the UI.
const tabs::TabInterface* active_tab = tab_strip_model_->GetActiveTab();
if (active_tab->GetContents()->IsFullscreen()) {
return false;
}
return std::any_of(
tabs.begin(), tabs.end(), [](const tabs::TabInterface* tab) {
return tab->GetContents() && IsShowingNTP(tab->GetContents());
});
}
void BookmarkBarController::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (selection.active_tab_changed()) {
if (web_contents()) {
content::WebContentsObserver::Observe(nullptr);
}
if (selection.new_contents) {
content::WebContentsObserver::Observe(selection.new_contents);
}
// Intentionally not updating the state to kTabSwitch. It is already updated
// in Browser::OnActiveTabChanged(), since the BrowserWindow may query the
// bookmark state there.
}
}
void BookmarkBarController::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsInPrimaryMainFrame() &&
navigation_handle->HasCommitted()) {
CHECK_EQ(web_contents(), tab_strip_model_->GetActiveWebContents());
UpdateBookmarkBarState(StateChangeReason::kTabState);
}
}