Files
brave-core/browser/ui/brave_browser.cc
T
Claudio DeSouza abbf8cc947 [cr149] IsWebContentsVisible cannot be override through Browser
Browser doesn't inherit from `ChromeWebModalDialogManagerDelegate`
anymore. This change fixes our code by directly patching
`ChromeWebModalDialogManagerDelegate` to have an override for this
method.

Chromium changes:
https://chromium.googlesource.com/chromium/src/+/083c2a8b08fff93b74aac4f4acd89c5d1fb206ea

commit 083c2a8b08fff93b74aac4f4acd89c5d1fb206ea
Author: Qikai <qikaizhong@microsoft.com>
Date:   Tue Apr 21 22:53:26 2026 -0700

    [bedrock] Move ChromeWebModalDialogManagerDelegate to BrowserWindowFeatures

    Extract ChromeWebModalDialogManagerDelegate from Browser into a new
    BrowserWindowModalDialogDelegate owned by BrowserWindowFeatures. The
    new class manages per-tab WebContentsModalDialogManager delegate
    registration via TabStripModelObserver and handles tab blocking,
    fullscreen exit, and dialog host lookup.

    Browser retains a thin SetWebContentsBlocked() override for
    DesktopBrowserWindowCapabilitiesDelegate, forwarding to the feature.
    DevTools scrim visibility uses a callback pattern subscribed by
    BrowserView.

    Bug: 496674143
    Change-Id: Ia8aea00b733e113c5ca9e9e79d28a37061eb8ced
    Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7738543
    Reviewed-by: Thomas Lukaszewicz <tluk@chromium.org>
    Commit-Queue: Qikai Zhong <qikaizhong@microsoft.com>
    Cr-Commit-Position: refs/heads/main@{#1618668}
2026-05-22 16:25:09 -04:00

393 lines
15 KiB
C++

/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/brave_browser.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "brave/browser/brave_browser_features.h"
#include "brave/browser/ui/brave_browser_window.h"
#include "brave/browser/ui/brave_file_select_utils.h"
#include "brave/browser/ui/sidebar/sidebar.h"
#include "brave/browser/ui/sidebar/sidebar_controller.h"
#include "brave/browser/ui/split_view/split_view_link_redirect_utils.h"
#include "brave/browser/ui/tabs/brave_tab_prefs.h"
#include "brave/browser/ui/views/tabs/vertical_tab_utils.h"
#include "brave/components/constants/pref_names.h"
#include "chrome/browser/lifetime/browser_close_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.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/browser/ui/webui_browser/webui_browser.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/url_constants.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "ui/base/window_open_disposition.h"
#include "url/gurl.h"
namespace {
bool g_suppress_dialog_for_testing = false;
} // namespace
// static
void BraveBrowser::SuppressBrowserWindowClosingDialogForTesting(bool suppress) {
g_suppress_dialog_for_testing = suppress;
}
BraveBrowser::BraveBrowser(const CreateParams& params) : Browser(params) {
if (auto* sidebar_controller = GetFeatures().sidebar_controller()) {
// TODO(https://github.com/brave/brave-browser/issues/45633): Cleanup this.
// Below call order is important.
// When reaches here, Sidebar UI is setup in BraveBrowserView but
// not initialized. It's just empty because sidebar controller/model is not
// ready yet. BraveBrowserView is instantiated by the ctor of Browser.
// So, initializing sidebar controller/model here and then ask to initialize
// sidebar UI. After that, UI will be updated for model's change.
sidebar_controller->SetSidebar(brave_window()->InitSidebar());
}
if (webui_browser::IsWebUIBrowserEnabled() && is_type_normal()) {
// WebUIBrowserWindow was created in Browser's c'tor (in
// BrowserWindow::CreateBrowserWindow), not a BraveBrowserWindow.
return;
}
// As browser window(BrowserView) is initialized before fullscreen controller
// is ready, it's difficult to know when browsr window can listen.
// Notify exact timing to do it.
CHECK(GetFeatures().exclusive_access_manager());
brave_window()->ReadyToListenFullscreenChanges();
}
BraveBrowser::~BraveBrowser() = default;
void BraveBrowser::ScheduleUIUpdate(content::WebContents* source,
unsigned changed_flags) {
Browser::ScheduleUIUpdate(source, changed_flags);
if (tab_strip_model_->GetIndexOfWebContents(source) ==
TabStripModel::kNoTab) {
return;
}
// We need to update sidebar UI only when current active tab state is changed.
if (changed_flags & content::INVALIDATE_TYPE_URL) {
if (source == tab_strip_model_->GetActiveWebContents()) {
// sidebar() can return a nullptr in unit tests.
if (auto* sidebar_controller = GetFeatures().sidebar_controller()) {
if (sidebar_controller->sidebar()) {
sidebar_controller->sidebar()->UpdateSidebarItemsState();
} else {
CHECK_IS_TEST();
}
}
}
}
}
void BraveBrowser::OnTabClosing(content::WebContents* contents) {
Browser::OnTabClosing(contents);
if (!AreAllTabsSharedPinnedTabs()) {
return;
}
bool more_than_one = false;
ProfileBrowserCollection::GetForProfile(profile())->ForEach(
[this, &more_than_one](BrowserWindowInterface* browser) {
if (!more_than_one) {
more_than_one = true;
return true;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(
[](base::WeakPtr<BraveBrowser> browser) {
if (browser) {
// We don't want close confirm dialog to show up.
// In this case, Shared pinned tabs will be moved
// to another window, so we don't have to warn
// users.
browser->confirmed_to_close_ = true;
chrome::CloseWindow(browser.get());
}
},
weak_ptr_factory_.GetWeakPtr()));
return false;
});
}
void BraveBrowser::TabStripEmpty() {
if (unload_controller_.is_attempting_to_close_browser() ||
!is_type_normal() || ignore_enable_closing_last_tab_pref_) {
Browser::TabStripEmpty();
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(
[](base::WeakPtr<BraveBrowser> browser) {
if (browser) {
chrome::AddTabAt(browser.get(),
browser->GetNewTabURL(),
/*index=*/-1, /*foreground=*/true,
/*group=*/std::nullopt,
/*pinned=*/false);
}
},
weak_ptr_factory_.GetWeakPtr()));
}
void BraveBrowser::RunFileChooser(
content::RenderFrameHost* render_frame_host,
scoped_refptr<content::FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) {
#if BUILDFLAG(IS_ANDROID)
Browser::RunFileChooser(render_frame_host, listener, params);
#else
auto new_params = params.Clone();
if (new_params->title.empty()) {
// Fill title of file chooser with origin of the frame.
// Note that save mode param is for PPAPI. 'Save As...' or downloading
// something doesn't reach here. They show 'select file dialog' from
// DownloadFilePicker::DownloadFilePicker directly.
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/mojom/choosers/file_chooser.mojom;l=27;drc=047c7dc4ee1ce908d7fea38ca063fa2f80f92c77
CHECK(render_frame_host);
const url::Origin& origin = render_frame_host->GetLastCommittedOrigin();
new_params->title = brave::GetFileSelectTitle(
content::WebContents::FromRenderFrameHost(render_frame_host), origin,
origin,
params.mode == blink::mojom::FileChooserParams::Mode::kSave
? brave::FileSelectTitleType::kSave
: brave::FileSelectTitleType::kOpen);
}
Browser::RunFileChooser(render_frame_host, listener, *new_params);
#endif
}
content::WebContents* BraveBrowser::AddNewContents(
content::WebContents* source,
std::unique_ptr<content::WebContents> new_contents,
const GURL& target_url,
WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& window_features,
bool user_gesture,
bool* was_blocked) {
// For NEW_FOREGROUND_TAB disposition (target="_blank" links) from a source
// in the left pane of a linked split view, set the split tab ID on the new
// contents so that SplitViewLinkNavigationThrottle can redirect it to the
// right pane. If the split tab ID was set, change the disposition to
// NEW_BACKGROUND_TAB to prevent the empty tab from becoming visible.
// It'll be closed immediately right after navigation is routed to right
// pane. By routing at SplitViewLinkNavigationThrottle, proper referrer/opener
// could be set. These are set after navigation starts. So, can't get it here.
if (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB && source &&
user_gesture && new_contents.get() && !target_url.is_empty()) {
if (split_view::SetSplitTabIdForRedirect(source, new_contents.get())) {
disposition = WindowOpenDisposition::NEW_BACKGROUND_TAB;
}
}
return Browser::AddNewContents(source, std::move(new_contents), target_url,
disposition, window_features, user_gesture,
was_blocked);
}
void BraveBrowser::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
Browser::OnTabStripModelChanged(tab_strip_model, change, selection);
if (!profile()->GetPrefs()->GetBoolean(kEnableClosingLastTab) &&
change.type() == TabStripModelChange::kRemoved) {
for (const auto& contents : change.GetRemove()->contents) {
// If there is no tab after this change for inserting them to
// another window, this browser should be closed.
if (contents.remove_reason ==
TabRemovedReason::kInsertedIntoOtherTabStrip &&
tab_strip_model->empty()) {
// Each removed can only have same reason. so safe to early return here.
ignore_enable_closing_last_tab_pref_ = true;
break;
}
}
}
// sidebar() can return a nullptr in unit tests.
auto* sidebar_controller = GetFeatures().sidebar_controller();
if (!sidebar_controller || !sidebar_controller->sidebar()) {
return;
}
// We need to update sidebar UI whenever active tab is changed or
// inactive tab is added/removed.
if (change.type() == TabStripModelChange::Type::kInserted ||
change.type() == TabStripModelChange::Type::kRemoved ||
selection.active_tab_changed()) {
sidebar_controller->sidebar()->UpdateSidebarItemsState();
}
// Check if all tabs we set to ignore onbeforeunload handler are closed.
if (!tabs_closing_with_onbeforeunload_ignore_.empty() &&
change.type() == TabStripModelChange::Type::kRemoved) {
for (auto& removed_tab : change.GetRemove()->contents) {
tabs_closing_with_onbeforeunload_ignore_.erase(
removed_tab.tab->GetHandle());
}
}
}
void BraveBrowser::FinishWarnBeforeClosing(WarnBeforeClosingResult result) {
// Clear user's choice because user cancelled window closing by some
// warning(ex, download is in-progress).
if (result == WarnBeforeClosingResult::kDoNotClose) {
confirmed_to_close_ = false;
}
Browser::FinishWarnBeforeClosing(result);
}
void BraveBrowser::BeforeUnloadFired(content::WebContents* source,
bool proceed,
bool* proceed_to_fire_unload) {
// Clear user's choice when user cancelled window closing by beforeunload
// handler.
if (!proceed) {
confirmed_to_close_ = false;
}
Browser::BeforeUnloadFired(source, proceed, proceed_to_fire_unload);
}
bool BraveBrowser::TryToCloseWindow(
bool skip_beforeunload,
const base::RepeatingCallback<void(bool)>& on_close_confirmed) {
// Window closing could be asked directly to browser object by this method.
// For example, when user tries to delete profile, this method is called on
// all its browser object. After all handlers are done, its all browser window
// start to close. In this case, we should not ask to users about this
// closing. So, treats like user confirmed closing. If this try blocked by
// user, |confirmed_to_close_| is set to false by ResetTryToCloseWindow().
confirmed_to_close_ = true;
return Browser::TryToCloseWindow(skip_beforeunload,
std::move(on_close_confirmed));
}
void BraveBrowser::ResetTryToCloseWindow() {
confirmed_to_close_ = false;
Browser::ResetTryToCloseWindow();
}
bool BraveBrowser::NormalBrowserSupportsWindowFeature(
WindowFeature feature,
bool check_can_support) const {
#if BUILDFLAG(IS_WIN)
if (feature == WindowFeature::kFeatureTitleBar) {
// In case of vertical tab strip is allowed, we need to have ability to
// show title bar on Windows.
return tabs::utils::ShouldShowBraveVerticalTabs(this) &&
tabs::utils::ShouldShowWindowTitleForVerticalTabs(this);
}
#endif
return Browser::NormalBrowserSupportsWindowFeature(feature,
check_can_support);
}
void BraveBrowser::UpdateTargetURL(content::WebContents* source,
const GURL& url) {
GURL target_url = url;
if (url.SchemeIs(content::kChromeUIScheme)) {
GURL::Replacements replacements;
replacements.SetSchemeStr(content::kBraveUIScheme);
target_url = target_url.ReplaceComponents(replacements);
}
Browser::UpdateTargetURL(source, target_url);
}
bool BraveBrowser::ShouldAskForBrowserClosingBeforeHandlers() {
if (g_suppress_dialog_for_testing) {
return false;
}
// Don't need to ask when application closing is in-progress.
if (BrowserCloseManager::BrowserClosingStarted()) {
return false;
}
if (confirmed_to_close_) {
return false;
}
PrefService* prefs = profile()->GetPrefs();
if (!prefs->GetBoolean(kEnableWindowClosingConfirm)) {
return false;
}
// Only launch confirm dialog while closing when browser has multiple tabs.
return tab_strip_model()->count() > 1;
}
bool BraveBrowser::AreAllTabsSharedPinnedTabs() {
if (!base::FeatureList::IsEnabled(tabs::kBraveSharedPinnedTabs)) {
return false;
}
if (!is_type_normal()) {
return false;
}
if (!profile()->GetPrefs()->GetBoolean(brave_tabs::kSharedPinnedTab)) {
return false;
}
return tab_strip_model()->count() > 0 &&
tab_strip_model()->count() ==
tab_strip_model()->IndexOfFirstNonPinnedTab();
}
BraveBrowserWindow* BraveBrowser::brave_window() {
return static_cast<BraveBrowserWindow*>(window_.get());
}
void BraveBrowser::SetTabsToIgnoreBeforeUnloadHandlers(
const base::flat_set<tabs::TabHandle>& for_contents) {
tabs_closing_with_onbeforeunload_ignore_ = for_contents;
}
bool BraveBrowser::ShouldSuppressDialogs(content::WebContents* source) {
auto* tab = tabs::TabInterface::MaybeGetFromContents(source);
return (tab && tabs_closing_with_onbeforeunload_ignore_.contains(
tab->GetHandle())) ||
content::WebContentsDelegate::ShouldSuppressDialogs(source);
}