[Sidebar V2] Update panel contents border radius (#36834)

Applied rounded corners to panel's contents view.
Introduced brave::GetPanelContentsRoundedCorners(PrefService*, bool has_header)
which derives corners from the pref and current header presence.
Route GetRoundedCorners() through it via patch, reading header_view_ state
(set before AddChildView) instead of the stale coordinator entry ID.
Call UpdateContentWrapperChildCorners() from UpdateBorder() overrides so
corners are re-applied on panel open and pref change.

Resolves no issue - use umbrella issue (brave/brave-browser#51462)

TEST=SidePanelUtilsTest.*, SidebarBrowserTest.SidebarV2ContentCornersUpdateOnStateChange
This commit is contained in:
Simon Hong
2026-06-05 08:06:19 +09:00
committed by GitHub
parent 967ecbcfdd
commit 9c51b5e6e2
10 changed files with 343 additions and 20 deletions
+108
View File
@@ -65,6 +65,7 @@
#include "chrome/browser/ui/tabs/features.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/frame/multi_contents_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/side_panel/side_panel.h"
@@ -78,6 +79,7 @@
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/display/test/test_screen.h"
#include "ui/events/base_event_utils.h"
@@ -86,6 +88,7 @@
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/widget/widget_utils.h"
#if BUILDFLAG(ENABLE_AI_CHAT)
@@ -99,6 +102,7 @@
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
#include "brave/browser/ui/views/side_panel/brave_side_panel_header.h"
#include "brave/browser/ui/views/side_panel/brave_side_panel_resize_area.h"
#include "brave/browser/ui/views/side_panel/side_panel_utils.h"
#endif
using ::testing::Eq;
@@ -2179,6 +2183,110 @@ IN_PROC_BROWSER_TEST_F(SidebarBrowserTest, SidebarV2BraveHeaderTest) {
<< "BraveSidePanelHeader should not be attached for CustomizeChrome";
}
namespace {
// Returns the corners that GetRoundedCorners() inside ContentParentView would
// compute for the panel's current header and pref state.
gfx::RoundedCornersF ExpectedContentCorners(SidePanel* panel,
PrefService* prefs) {
return brave::GetPanelContentsRoundedCorners(
prefs, panel->GetHeaderView<views::View>() != nullptr);
}
// Asserts layer-backed content children carry the expected corner radii.
// Children without compositor layers (the typical WebView holder path) are
// silently skipped; their corners can't be observed via public API.
void ExpectContentChildLayerCorners(SidePanel* panel,
const gfx::RoundedCornersF& expected,
const base::Location& loc = FROM_HERE) {
SCOPED_TRACE(loc.ToString());
for (const auto child : panel->GetContentParentView()->children()) {
if (child->layer()) {
EXPECT_EQ(expected, child->layer()->rounded_corner_radii());
}
}
}
} // namespace
// Verify that content corner radii stay correct across three triggers:
// (a) panel type change (header ↔ no-header entry),
// (b) rounded-corners pref toggle while the panel is open,
// (c) panel reopened after the pref changed while it was closed.
IN_PROC_BROWSER_TEST_F(SidebarBrowserTest,
SidebarV2ContentCornersUpdateOnStateChange) {
auto* panel_ui = browser()->GetFeatures().side_panel_ui();
auto* side_panel =
BrowserView::GetBrowserViewForBrowser(browser())->side_panel();
auto* prefs = browser()->profile()->GetPrefs();
side_panel->DisableAnimationsForTesting();
prefs->SetBoolean(kWebViewRoundedCorners, true);
const int r = views::LayoutProvider::Get()->GetDistanceMetric(
ChromeDistanceMetric::DISTANCE_SIDE_PANEL_CONTENT_RADIUS);
const gfx::RoundedCornersF flat_top(0, 0, r, r);
const gfx::RoundedCornersF all_round(r);
const gfx::RoundedCornersF none;
auto wait_for_entry = [&](SidePanelEntryId id,
const base::Location& loc = FROM_HERE) {
SCOPED_TRACE(loc.ToString());
ASSERT_TRUE(base::test::RunUntil([&]() {
return panel_ui->IsSidePanelEntryShowing(SidePanelEntry::Key(id));
}));
};
// (a) Panel type change -----------------------------------------------
// Bookmarks has a Brave header → top corners must be flat.
panel_ui->Show(SidePanelEntryId::kBookmarks);
wait_for_entry(SidePanelEntryId::kBookmarks);
EXPECT_NE(nullptr, side_panel->GetHeaderView<views::View>());
EXPECT_EQ(flat_top, ExpectedContentCorners(side_panel, prefs));
ExpectContentChildLayerCorners(side_panel, flat_top);
// CustomizeChrome has no Brave header → all corners must be round.
panel_ui->Show(SidePanelEntryId::kCustomizeChrome);
wait_for_entry(SidePanelEntryId::kCustomizeChrome);
EXPECT_EQ(nullptr, side_panel->GetHeaderView<views::View>());
EXPECT_EQ(all_round, ExpectedContentCorners(side_panel, prefs));
ExpectContentChildLayerCorners(side_panel, all_round);
// Back to bookmarks → flat top again.
panel_ui->Show(SidePanelEntryId::kBookmarks);
wait_for_entry(SidePanelEntryId::kBookmarks);
EXPECT_NE(nullptr, side_panel->GetHeaderView<views::View>());
EXPECT_EQ(flat_top, ExpectedContentCorners(side_panel, prefs));
ExpectContentChildLayerCorners(side_panel, flat_top);
// (b) Pref change while panel is open ---------------------------------
// Pref OFF: no corners regardless of header (UpdateBorder() path).
prefs->SetBoolean(kWebViewRoundedCorners, false);
EXPECT_EQ(none, ExpectedContentCorners(side_panel, prefs));
ExpectContentChildLayerCorners(side_panel, none);
// Pref ON again: flat top (bookmarks has header).
prefs->SetBoolean(kWebViewRoundedCorners, true);
EXPECT_EQ(flat_top, ExpectedContentCorners(side_panel, prefs));
ExpectContentChildLayerCorners(side_panel, flat_top);
// (c) Panel reopened after pref changed while closed ------------------
panel_ui->Close();
ASSERT_TRUE(
base::test::RunUntil([&]() { return !side_panel->GetVisible(); }));
// Toggle pref while the panel is hidden.
prefs->SetBoolean(kWebViewRoundedCorners, false);
// Reopen — Open() must re-apply corners with the new pref value.
panel_ui->Show(SidePanelEntryId::kBookmarks);
wait_for_entry(SidePanelEntryId::kBookmarks);
EXPECT_EQ(none, ExpectedContentCorners(side_panel, prefs));
ExpectContentChildLayerCorners(side_panel, none);
}
// Verify that the resize area is positioned correctly for both border states.
// In both cases the strip starts at the panel's outer edge with width
// kResizeStripWidth. Border state only affects z-order: without rounded
+13 -2
View File
@@ -13,6 +13,7 @@ source_set("side_panel") {
public_deps = [
"//base",
"//chrome/browser/ui/side_panel",
"//chrome/browser/ui/side_panel:side_panel_views_dependent",
"//chrome/browser/ui/views/side_panel",
"//components/prefs",
@@ -28,6 +29,7 @@ source_set("side_panel") {
"brave_side_panel_header_controller.h",
"brave_side_panel_resize_area.cc",
"brave_side_panel_resize_area.h",
"side_panel_utils.h",
]
}
}
@@ -37,7 +39,10 @@ source_set("side_panel_impl") {
deps = []
if (enable_sidebar_v2) {
sources += [ "brave_side_panel_header_controller.cc" ]
sources += [
"brave_side_panel_header_controller.cc",
"side_panel_utils.cc",
]
deps += [
":side_panel",
@@ -49,6 +54,7 @@ source_set("side_panel_impl") {
"//chrome/browser/ui/browser_window",
"//chrome/browser/ui/color:mixers",
"//chrome/browser/ui/side_panel:side_panel_views_dependent",
"//chrome/browser/ui/views/side_panel",
"//chrome/common",
"//components/prefs",
"//ui/base",
@@ -63,7 +69,10 @@ source_set("unit_tests") {
deps = []
if (enable_sidebar_v2) {
sources += [ "brave_side_panel_header_controller_unittest.cc" ]
sources += [
"brave_side_panel_header_controller_unittest.cc",
"side_panel_utils_unittest.cc",
]
deps += [
":side_panel",
@@ -73,10 +82,12 @@ source_set("unit_tests") {
"//chrome/browser/ui/browser_window/test:test_support",
"//chrome/browser/ui/side_panel",
"//chrome/browser/ui/side_panel:side_panel_views_dependent",
"//chrome/browser/ui/views/side_panel",
"//chrome/test:test_support",
"//components/prefs",
"//testing/gmock",
"//testing/gtest",
"//ui/gfx",
"//ui/views",
]
}
@@ -31,6 +31,7 @@
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
#include "brave/browser/ui/views/side_panel/brave_side_panel_header.h"
#include "brave/browser/ui/views/side_panel/brave_side_panel_header_controller.h"
#include "brave/browser/ui/views/side_panel/side_panel_utils.h"
#endif
namespace {
@@ -205,7 +206,7 @@ void BraveSidePanelCoordinator::PopulateSidePanel(
std::move(content_view));
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
if (ShouldShowBraveHeader(entry)) {
if (brave::ShouldShowSidePanelHeader(entry->key().id())) {
SidePanel* side_panel = browser_view_->side_panel();
CHECK(side_panel);
side_panel->AddHeaderView(std::make_unique<BraveSidePanelHeader>(
@@ -215,15 +216,6 @@ void BraveSidePanelCoordinator::PopulateSidePanel(
#endif
}
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
bool BraveSidePanelCoordinator::ShouldShowBraveHeader(
SidePanelEntry* entry) const {
const SidePanelEntry::Id id = entry->key().id();
return id == SidePanelEntry::Id::kReadingList ||
id == SidePanelEntry::Id::kBookmarks;
}
#endif
BraveBrowserView* BraveSidePanelCoordinator::GetBraveBrowserView() {
return static_cast<BraveBrowserView*>(browser_view_);
}
@@ -9,7 +9,6 @@
#include <memory>
#include <optional>
#include "brave/browser/ui/sidebar/buildflags/buildflags.h"
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_helper.h"
@@ -52,11 +51,6 @@ class BraveSidePanelCoordinator : public SidePanelCoordinator {
std::optional<SidePanelEntry::Key> GetLastActiveEntryKey() const;
void UpdateToolbarButtonHighlight(bool side_panel_visible);
BraveBrowserView* GetBraveBrowserView();
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
// Returns true when we want to show brave panel header view for |entry|.
bool ShouldShowBraveHeader(SidePanelEntry* entry) const;
#endif
};
#endif // BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_BRAVE_SIDE_PANEL_COORDINATOR_H_
@@ -0,0 +1,41 @@
/* Copyright (c) 2026 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 https://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/views/side_panel/side_panel_utils.h"
#include "brave/common/pref_names.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "components/prefs/pref_service.h"
#include "ui/views/layout/layout_provider.h"
namespace brave {
bool ShouldShowSidePanelHeader(SidePanelEntryId id) {
return id == SidePanelEntryId::kReadingList ||
id == SidePanelEntryId::kBookmarks;
}
gfx::RoundedCornersF GetPanelContentsRoundedCorners(PrefService* prefs,
bool has_header) {
// When Brave's rounded corners are off, the panel has no rounded border, so
// the content shouldn't be rounded either.
if (!prefs->GetBoolean(kWebViewRoundedCorners)) {
return gfx::RoundedCornersF();
}
gfx::RoundedCornersF radii(views::LayoutProvider::Get()->GetDistanceMetric(
ChromeDistanceMetric::DISTANCE_SIDE_PANEL_CONTENT_RADIUS));
// When a Brave header is attached it paints the rounded top, so flatten the
// content's top corners to avoid double-rounding.
if (has_header) {
radii.set_upper_left(0);
radii.set_upper_right(0);
}
return radii;
}
} // namespace brave
@@ -0,0 +1,29 @@
/* Copyright (c) 2026 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 https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTILS_H_
#define BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTILS_H_
#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
class PrefService;
namespace brave {
// Returns the rounded corners for the web content hosted inside the side panel.
// Returns empty corners when Brave's rounded corners are disabled. When
// `has_header` is true the Brave header owns the top rounding, so the
// content's top corners are flattened to avoid double-rounding.
gfx::RoundedCornersF GetPanelContentsRoundedCorners(PrefService* prefs,
bool has_header);
// Returns true when the entry with `id` should show Brave's custom side panel
// header view.
bool ShouldShowSidePanelHeader(SidePanelEntryId id);
} // namespace brave
#endif // BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTILS_H_
@@ -0,0 +1,72 @@
/* Copyright (c) 2026 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 https://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/views/side_panel/side_panel_utils.h"
#include <memory>
#include "brave/common/pref_names.h"
#include "chrome/browser/ui/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/prefs/pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/layout/layout_provider.h"
namespace brave {
class SidePanelUtilsTest : public ChromeViewsTestBase {
public:
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
ChromeViewsTestBase::SetUp();
}
void TearDown() override {
profile_.reset();
ChromeViewsTestBase::TearDown();
}
protected:
PrefService* prefs() { return profile_->GetPrefs(); }
int ContentRadius() const {
return views::LayoutProvider::Get()->GetDistanceMetric(
ChromeDistanceMetric::DISTANCE_SIDE_PANEL_CONTENT_RADIUS);
}
std::unique_ptr<TestingProfile> profile_;
};
TEST_F(SidePanelUtilsTest, ShouldShowSidePanelHeaderOnlyForBraveEntries) {
EXPECT_TRUE(ShouldShowSidePanelHeader(SidePanelEntryId::kReadingList));
EXPECT_TRUE(ShouldShowSidePanelHeader(SidePanelEntryId::kBookmarks));
EXPECT_FALSE(ShouldShowSidePanelHeader(SidePanelEntryId::kCustomizeChrome));
}
TEST_F(SidePanelUtilsTest, RoundedCornersEmptyWhenPrefDisabled) {
prefs()->SetBoolean(kWebViewRoundedCorners, false);
EXPECT_EQ(gfx::RoundedCornersF(),
GetPanelContentsRoundedCorners(prefs(), false));
EXPECT_EQ(gfx::RoundedCornersF(),
GetPanelContentsRoundedCorners(prefs(), true));
}
TEST_F(SidePanelUtilsTest, RoundedCornersAllCornersWhenNoHeader) {
prefs()->SetBoolean(kWebViewRoundedCorners, true);
EXPECT_EQ(gfx::RoundedCornersF(ContentRadius()),
GetPanelContentsRoundedCorners(prefs(), false));
}
TEST_F(SidePanelUtilsTest, RoundedCornersFlatTopWhenHasHeader) {
prefs()->SetBoolean(kWebViewRoundedCorners, true);
const int r = ContentRadius();
EXPECT_EQ(gfx::RoundedCornersF(0, 0, r, r),
GetPanelContentsRoundedCorners(prefs(), true));
}
} // namespace brave
@@ -7,12 +7,14 @@
#include "build/buildflag.h"
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
#include "brave/browser/ui/views/side_panel/side_panel_utils.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/views/side_panel/side_panel.h"
#include "ui/compositor/layer.h"
// Rename the upstream Add/RemoveHeaderView implementation so we can provide
// a thin wrapper that reapplies border state.
// Rename upstream methods so we can provide thin wrappers that reapply
// rounded-corner and border state.
#define AddHeaderView AddHeaderView_ChromiumImpl
#define RemoveHeaderView RemoveHeaderView_ChromiumImpl
@@ -25,6 +27,41 @@
#if BUILDFLAG(ENABLE_SIDEBAR_V2)
namespace {
// Applies the current rounded-corner values to every direct child of the
// content parent view. Used when the pref changes or the panel opens with
// existing content (so OnChildViewAdded never fired with the new values).
void UpdateContentWrapperChildCorners(views::View* content_parent_view,
PrefService* prefs,
bool has_header) {
// ContentParentView hosts multiple content view and shows at once.
CHECK(content_parent_view->GetUseDefaultFillLayout());
auto corners = brave::GetPanelContentsRoundedCorners(prefs, has_header);
for (views::View* child : content_parent_view->children()) {
// If the child is a WebView or paints to a layer, round its corners.
if (views::IsViewClass<views::WebView>(child)) {
views::AsViewClass<views::WebView>(child)->holder()->SetCornerRadii(
corners);
}
// Try to detect if the child is a views::View wrapper of a WebView. If so,
// round its corners.
if (child->children().size() == 1 &&
views::IsViewClass<views::WebView>(child->children()[0])) {
views::AsViewClass<views::WebView>(child->children()[0])
->holder()
->SetCornerRadii(corners);
}
if (child->layer()) {
child->layer()->SetIsFastRoundedCorner(true);
child->layer()->SetRoundedCornerRadius(corners);
}
}
}
} // namespace
void SidePanel::SetResizeArea(std::unique_ptr<views::View> resize_area) {
CHECK(resize_area);
auto old_resize_area = RemoveChildViewT(resize_area_);
@@ -52,6 +89,11 @@ void SidePanel::UpdateBorder() {
// IsRightAligned().
UpdateHorizontalAlignment();
// Re-apply corners to existing content: the pref or header state changed.
UpdateContentWrapperChildCorners(GetContentParentView(),
browser_view_->GetProfile()->GetPrefs(),
GetHeaderView<views::View>());
const int header_top_inset =
header_view_ ? header_view_->GetPreferredSize().height() : 0;
gfx::Insets insets;
@@ -0,0 +1,14 @@
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index ef7fce6750ec533b14fa1e1719406167d9063942..554e6a357ad81e1411d01c4da8b08cb2e065b844 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -223,6 +223,9 @@ class ContentParentView : public views::View, public views::ViewObserver {
}
gfx::RoundedCornersF GetRoundedCorners() {
+ return brave::GetPanelContentsRoundedCorners(
+ browser_view_->GetProfile()->GetPrefs(),
+ browser_view_->side_panel()->GetHeaderView<views::View>());
ChromeDistanceMetric corner_radius =
ChromeDistanceMetric::DISTANCE_SIDE_PANEL_CONTENT_RADIUS;
return GetLayoutProvider()
@@ -0,0 +1,20 @@
# Copyright (c) 2026 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 https://mozilla.org/MPL/2.0/.
substitutions:
- description: |
Route ContentParentView::GetRoundedCorners() through Brave's corner helper.
Calls brave::GetPanelContentsRoundedCorners() with the current prefs and
whether the side panel has a Brave header attached. Reading header_view_
state (set before AddChildView in PopulateSidePanel) avoids the timing
issue where coordinator->GetCurrentEntryId() still returns the previous
entry when OnChildViewAdded fires.
re_pattern: '( gfx::RoundedCornersF GetRoundedCorners\(\) \{)'
replace: |-
\1
return brave::GetPanelContentsRoundedCorners(
browser_view_->GetProfile()->GetPrefs(),
browser_view_->side_panel()->GetHeaderView<views::View>());