blob: 2520aa5003e58e4245b69d26c8008b23e66772f4 [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/tabs/split_tab_highlight_controller.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/callback_list.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/interaction/browser_elements.h"
#include "chrome/browser/ui/tabs/split_tab_highlight_delegate.h"
#include "chrome/browser/ui/views/device_chooser_content_view.h"
#include "chrome/browser/ui/views/file_system_access/file_system_access_restore_permission_bubble_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view_base.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace {
const std::vector<ui::ElementIdentifier>& GetTrackedBubbleDialogs() {
static const base::NoDestructor<std::vector<ui::ElementIdentifier>>
kTrackedBubbleDialogs(
{DeviceChooserContentView::kDeviceChooserDialogBubbleElementId,
PageInfoBubbleViewBase::kPageInfoBubbleElementIdentifier,
FileSystemAccessRestorePermissionBubbleView::
kFileSystemAccessBubbleElementIdentifier});
return *kTrackedBubbleDialogs;
}
} // namespace
namespace split_tabs {
SplitTabHighlightController::SplitTabHighlightController(
BrowserView* browser_view)
: split_tab_highlight_delegate_(
std::make_unique<split_tabs::SplitTabHighlightDelegateImpl>(
browser_view)),
browser_window_interface_(browser_view->browser()) {
tracked_bubble_visibility_ = base::MakeFlatMap<ui::ElementIdentifier, bool>(
GetTrackedBubbleDialogs(), {},
[](ui::ElementIdentifier id) { return std::make_pair(id, false); });
browser_scoped_subscriptions_.emplace_back(
browser_window_interface_->RegisterActiveTabDidChange(
base::BindRepeating(&SplitTabHighlightController::OnActiveTabChange,
base::Unretained(this))));
chip_controller_observation_.Observe(
browser_view->toolbar()->location_bar()->GetChipController());
for (ui::ElementIdentifier identifier : GetTrackedBubbleDialogs()) {
AddShowHideElementSubscriptions(identifier);
}
}
SplitTabHighlightController::~SplitTabHighlightController() = default;
bool SplitTabHighlightController::ShouldHighlight() {
const bool is_any_tracked_bubbles_visible = std::any_of(
tracked_bubble_visibility_.begin(), tracked_bubble_visibility_.end(),
[](const auto& pair) { return pair.second; });
return is_omnibox_popup_showing_ || is_permission_prompt_showing_ ||
is_any_tracked_bubbles_visible;
}
void SplitTabHighlightController::OnOmniboxPopupVisibilityChanged(
bool popup_is_open) {
is_omnibox_popup_showing_ = popup_is_open;
UpdateHighlight();
}
void SplitTabHighlightController::OnPermissionPromptShown() {
is_permission_prompt_showing_ = true;
UpdateHighlight();
}
void SplitTabHighlightController::OnPermissionPromptHidden() {
is_permission_prompt_showing_ = false;
UpdateHighlight();
}
void SplitTabHighlightController::AddShowHideElementSubscriptions(
ui::ElementIdentifier element_identifier) {
ui::ElementContext context =
BrowserElements::From(browser_window_interface_)->GetContext();
browser_scoped_subscriptions_.emplace_back(
ui::ElementTracker::GetElementTracker()->AddElementShownCallback(
element_identifier, context,
base::BindRepeating(&SplitTabHighlightController::OnElementShown,
base::Unretained(this))));
browser_scoped_subscriptions_.emplace_back(
ui::ElementTracker::GetElementTracker()->AddElementHiddenCallback(
element_identifier, context,
base::BindRepeating(&SplitTabHighlightController::OnElementHidden,
base::Unretained(this))));
}
void SplitTabHighlightController::OnActiveTabChange(
BrowserWindowInterface* browser_window_interface) {
omnibox_tab_helper_observation_.Reset();
tabs::TabInterface* const active_tab =
browser_window_interface->GetActiveTabInterface();
if (active_tab) {
tab_will_detach_subscription_ = active_tab->RegisterWillDetach(
base::BindRepeating(&SplitTabHighlightController::OnTabWillDetach,
base::Unretained(this)));
OmniboxTabHelper* const tab_helper =
OmniboxTabHelper::FromWebContents(active_tab->GetContents());
CHECK(tab_helper);
omnibox_tab_helper_observation_.Observe(tab_helper);
tab_will_discard_subscription_ = active_tab->RegisterWillDiscardContents(
base::BindRepeating(&SplitTabHighlightController::OnTabWillDiscard,
base::Unretained(this)));
}
// Need to update the highlight because the omnibox focus state
// event might have already been triggered before the active tab change.
UpdateHighlight();
}
void SplitTabHighlightController::OnTabWillDetach(
tabs::TabInterface* tab_interface,
tabs::TabInterface::DetachReason reason) {
// Reset the omnibox tab helper observation to ensure that it doesn't live
// longer than the web contents it is observing.
omnibox_tab_helper_observation_.Reset();
tab_will_detach_subscription_ = base::CallbackListSubscription();
}
void SplitTabHighlightController::OnTabWillDiscard(
tabs::TabInterface* tab_interface,
content::WebContents* old_contents,
content::WebContents* new_contents) {
// Reset the observation of the omnibox tab helper since it is possible for
// the active tab to be discarded on CrOS.
omnibox_tab_helper_observation_.Reset();
is_omnibox_popup_showing_ = false;
UpdateHighlight();
}
void SplitTabHighlightController::OnElementShown(
ui::TrackedElement* tracked_element) {
tracked_bubble_visibility_[tracked_element->identifier()] = true;
UpdateHighlight();
}
void SplitTabHighlightController::OnElementHidden(
ui::TrackedElement* tracked_element) {
tracked_bubble_visibility_[tracked_element->identifier()] = false;
UpdateHighlight();
}
void SplitTabHighlightController::UpdateHighlight() {
split_tab_highlight_delegate_->SetHighlight(ShouldHighlight());
}
} // namespace split_tabs