blob: 6428eb4f1c9b322ed5084dfb8c339aedd752d2f6 [file] [log] [blame]
// Copyright 2013 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/views/frame/browser_view.h"
#include <memory>
#include <string_view>
#include "base/byte_count.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser_actions.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h"
#include "chrome/browser/ui/tabs/tab_activity_simulator.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h"
#include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model_factory.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
#include "chrome/browser/ui/views/frame/browser_frame_view.h"
#include "chrome/browser/ui/views/frame/browser_view_layout.h"
#include "chrome/browser/ui/views/frame/browser_widget.h"
#include "chrome/browser/ui/views/frame/tab_strip_view_interface.h"
#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/infobars/infobar_container_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/vector_icons/vector_icons.h"
#include "components/version_info/channel.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "ui/actions/actions.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/text/bytes_formatting.h"
#include "ui/gfx/scrollbar_size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/bubble/bubble_dialog_model_host.h"
#include "ui/views/controls/webview/webview.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/ui/recently_audible_helper.h"
#endif
#if defined(USE_AURA)
#include "ui/aura/client/aura_constants.h"
#endif
namespace {
// Tab strip bounds depend on the window frame sizes.
gfx::Point ExpectedTabStripRegionOrigin(BrowserView* browser_view) {
gfx::Rect tabstrip_bounds(
browser_view->browser_widget()
->GetFrameView()
->GetBoundsForTabStripRegion(
browser_view->tab_strip_view()->GetMinimumSize()));
gfx::Point tabstrip_region_origin(tabstrip_bounds.origin());
views::View::ConvertPointToTarget(browser_view->parent(), browser_view,
&tabstrip_region_origin);
return tabstrip_region_origin;
}
// Helper function to take a prefix and suffix and insert the browser name (like
// "Chromium" or "Google Chrome") in the middle.
std::u16string SubBrowserName(std::u16string_view prefix,
std::u16string_view suffix) {
return base::StrCat(
{prefix, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), suffix});
}
} // namespace
class BrowserViewTest : public TestWithBrowserView {
public:
BrowserViewTest() = default;
BrowserViewTest(const BrowserViewTest&) = delete;
BrowserViewTest& operator=(const BrowserViewTest&) = delete;
~BrowserViewTest() override = default;
TestingProfile::TestingFactories GetTestingFactories() override {
TestingProfile::TestingFactories factories =
TestWithBrowserView::GetTestingFactories();
factories.emplace_back(
PinnedToolbarActionsModelFactory::GetInstance(),
base::BindRepeating(&BrowserViewTest::BuildPinnedToolbarActionsModel));
return factories;
}
static std::unique_ptr<KeyedService> BuildPinnedToolbarActionsModel(
content::BrowserContext* context) {
return std::make_unique<PinnedToolbarActionsModel>(
Profile::FromBrowserContext(context));
}
};
// Test basic construction and initialization.
TEST_F(BrowserViewTest, BrowserView) {
// The window is owned by the native widget, not the test class.
EXPECT_FALSE(window());
EXPECT_TRUE(browser_view()->browser());
// Test initial state.
EXPECT_TRUE(browser_view()->GetTabStripVisible());
EXPECT_FALSE(browser_view()->GetIncognito());
EXPECT_FALSE(browser_view()->GetGuestSession());
EXPECT_TRUE(browser_view()->GetIsNormalType());
EXPECT_FALSE(browser_view()->IsFullscreen());
EXPECT_FALSE(browser_view()->IsBookmarkBarVisible());
EXPECT_FALSE(browser_view()->IsBookmarkBarAnimating());
// Test action item creation.
BrowserActions* browser_actions = browser()->browser_actions();
ASSERT_NE(browser_actions->root_action_item(), nullptr);
EXPECT_GE(
browser_actions->root_action_item()->GetChildren().children().size(),
1UL);
actions::ActionItemVector actions;
auto& manager = actions::ActionManager::GetForTesting();
manager.GetActions(actions);
actions::ActionItem* customize_chrome_action = manager.FindAction(
kActionSidePanelShowCustomizeChrome, browser_actions->root_action_item());
EXPECT_EQ(customize_chrome_action->GetText(),
l10n_util::GetStringUTF16(IDS_SIDE_PANEL_CUSTOMIZE_CHROME_TITLE));
EXPECT_EQ(customize_chrome_action->GetImage(),
ui::ImageModel::FromVectorIcon(vector_icons::kEditChromeRefreshIcon,
ui::kColorIcon));
EXPECT_EQ(customize_chrome_action->GetEnabled(), true);
}
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(BrowserViewTest, OnTaskLockedBrowserView) {
ASSERT_TRUE(browser_view()->browser());
browser_view()->browser()->SetLockedForOnTask(true);
EXPECT_FALSE(browser_view()->CanMinimize());
EXPECT_FALSE(browser_view()->ShouldShowCloseButton());
}
TEST_F(BrowserViewTest, OnTaskUnlockedBrowserView) {
ASSERT_TRUE(browser_view()->browser());
browser_view()->browser()->SetLockedForOnTask(false);
EXPECT_TRUE(browser_view()->CanMinimize());
EXPECT_TRUE(browser_view()->ShouldShowCloseButton());
}
#endif
namespace {
// A thin wrapper around `Browser` to ensure that it's destructed in the right
// order.
class ScopedBrowser {
public:
explicit ScopedBrowser(Profile* profile) {
Browser::CreateParams params(profile, true);
browser_view_ =
BrowserView::GetBrowserViewForBrowser(Browser::Create(params));
}
ScopedBrowser(const ScopedBrowser&) = delete;
ScopedBrowser& operator=(const ScopedBrowser&) = delete;
~ScopedBrowser() {
browser_view_->browser()->tab_strip_model()->CloseAllTabs();
browser_view_.ExtractAsDangling()->GetWidget()->CloseNow();
content::RunAllTasksUntilIdle();
}
Browser* browser() { return browser_view_->browser(); }
private:
raw_ptr<BrowserView> browser_view_;
};
} // namespace
// TODO(crbug.com/326199292): Flaky on Linux.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_UpdateActiveBrowser DISABLED_UpdateActiveBrowser
#else
#define MAYBE_UpdateActiveBrowser UpdateActiveBrowser
#endif
// Test that calling `BrowserView::Activate()` or `BrowserView::Show()` sets
// the last active browser synchronously.
TEST_F(BrowserViewTest, MAYBE_UpdateActiveBrowser) {
// On platforms like Ash-Chrome, for `BrowserView::Activate()` to actually
// activate the browser, it has to be made visible first. Thus
// `BrowserView::Show()` has to be called first.
ScopedBrowser scoped_browser(profile());
Browser* browser2 = scoped_browser.browser();
EXPECT_EQ(2u, BrowserList::GetInstance()->size());
EXPECT_EQ(browser(), GetLastActiveBrowserWindowInterfaceWithAnyProfile());
browser2->window()->Show();
EXPECT_EQ(browser2, GetLastActiveBrowserWindowInterfaceWithAnyProfile());
browser()->window()->Show();
EXPECT_EQ(browser(), GetLastActiveBrowserWindowInterfaceWithAnyProfile());
browser2->window()->Activate();
EXPECT_EQ(browser2, GetLastActiveBrowserWindowInterfaceWithAnyProfile());
browser()->window()->Activate();
EXPECT_EQ(browser(), GetLastActiveBrowserWindowInterfaceWithAnyProfile());
browser2 = nullptr;
}
// Test layout of the top-of-window UI.
TEST_F(BrowserViewTest, DISABLED_BrowserViewLayout) {
BookmarkBarView::DisableAnimationsForTesting(true);
// |browser_view_| owns the Browser, not the test class.
Browser* browser = browser_view()->browser();
TopContainerView* top_container = browser_view()->top_container();
TabStrip* tabstrip = browser_view()->tabstrip();
views::View* tabstrip_region = browser_view()->tabstrip()->parent();
ToolbarView* toolbar = browser_view()->toolbar();
views::View* contents_container = browser_view()->contents_container();
views::WebView* contents_web_view = browser_view()->contents_web_view();
views::WebView* devtools_web_view =
browser_view()->GetActiveContentsContainerView()->devtools_web_view();
// Start with a single tab open to a normal page.
AddTab(browser, GURL("about:blank"));
// Verify the view hierarchy.
EXPECT_EQ(top_container, tabstrip_region->parent());
EXPECT_EQ(tabstrip_region, tabstrip->parent());
EXPECT_EQ(top_container, browser_view()->toolbar()->parent());
EXPECT_EQ(top_container, browser_view()->GetBookmarkBarView()->parent());
EXPECT_EQ(browser_view(), browser_view()->infobar_container()->parent());
// Find bar host is at the front of the view hierarchy, followed by the
// infobar container and then top container.
ASSERT_GE(browser_view()->children().size(), 2U);
auto child = browser_view()->children().crbegin();
EXPECT_EQ(browser_view()->find_bar_host_view(), *child++);
EXPECT_EQ(browser_view()->infobar_container(), *child);
// Verify basic layout.
EXPECT_EQ(0, top_container->x());
EXPECT_EQ(0, top_container->y());
EXPECT_EQ(browser_view()->width(), top_container->width());
// Tabstrip layout varies based on window frame sizes.
gfx::Point expected_tabstrip_region_origin =
ExpectedTabStripRegionOrigin(browser_view());
EXPECT_EQ(expected_tabstrip_region_origin.x(), tabstrip_region->x());
EXPECT_EQ(expected_tabstrip_region_origin.y(), tabstrip_region->y());
EXPECT_EQ(0, toolbar->x());
EXPECT_EQ(tabstrip_region->bounds().bottom() -
GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP),
toolbar->y());
EXPECT_EQ(0, contents_container->x());
EXPECT_EQ(toolbar->bounds().bottom(), contents_container->y());
EXPECT_EQ(top_container->bounds().bottom(), contents_container->y());
EXPECT_EQ(0, devtools_web_view->x());
EXPECT_EQ(0, devtools_web_view->y());
EXPECT_EQ(0, contents_web_view->x());
EXPECT_EQ(0, contents_web_view->y());
// Verify bookmark bar visibility.
BookmarkBarView* bookmark_bar = browser_view()->GetBookmarkBarView();
EXPECT_FALSE(bookmark_bar->GetVisible());
EXPECT_EQ(devtools_web_view->y(), bookmark_bar->height());
EXPECT_EQ(GetLayoutConstant(BOOKMARK_BAR_HEIGHT),
bookmark_bar->GetMinimumSize().height());
chrome::ExecuteCommand(browser, IDC_SHOW_BOOKMARK_BAR);
EXPECT_TRUE(bookmark_bar->GetVisible());
chrome::ExecuteCommand(browser, IDC_SHOW_BOOKMARK_BAR);
EXPECT_FALSE(bookmark_bar->GetVisible());
// The NTP should be treated the same as any other page.
NavigateAndCommitActiveTabWithTitle(browser, GURL(chrome::kChromeUINewTabURL),
std::u16string());
EXPECT_FALSE(bookmark_bar->GetVisible());
EXPECT_EQ(top_container, bookmark_bar->parent());
// Find bar host is still at the front of the view hierarchy, followed by the
// infobar container and then top container.
ASSERT_GE(browser_view()->children().size(), 2U);
child = browser_view()->children().crbegin();
EXPECT_EQ(browser_view()->find_bar_host_view(), *child++);
EXPECT_EQ(browser_view()->infobar_container(), *child);
// Bookmark bar layout on NTP.
EXPECT_EQ(0, bookmark_bar->x());
EXPECT_EQ(tabstrip_region->bounds().bottom() + toolbar->height() -
GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP),
bookmark_bar->y());
EXPECT_EQ(bookmark_bar->height() + bookmark_bar->y(),
contents_container->y());
EXPECT_EQ(contents_web_view->y(), devtools_web_view->y());
BookmarkBarView::DisableAnimationsForTesting(false);
}
// TODO(crbug.com/40656637): Flaky on Linux.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_FindBarBoundingBoxLocationBar \
DISABLED_FindBarBoundingBoxLocationBar
#else
#define MAYBE_FindBarBoundingBoxLocationBar FindBarBoundingBoxLocationBar
#endif
// Test the find bar's bounding box when the location bar is visible.
TEST_F(BrowserViewTest, MAYBE_FindBarBoundingBoxLocationBar) {
ASSERT_FALSE(base::i18n::IsRTL());
const views::View* location_bar = browser_view()->GetLocationBarView();
const views::View* contents_container = browser_view()->contents_container();
// Make sure we are testing the case where the location bar is visible.
EXPECT_TRUE(location_bar->GetVisible());
const gfx::Rect find_bar_bounds = browser_view()->GetFindBarBoundingBox();
const gfx::Rect location_bar_bounds =
location_bar->ConvertRectToWidget(location_bar->GetLocalBounds());
const gfx::Rect contents_bounds = contents_container->ConvertRectToWidget(
contents_container->GetLocalBounds());
const gfx::Rect target(
location_bar_bounds.x(), location_bar_bounds.bottom(),
location_bar_bounds.width(),
contents_bounds.bottom() - location_bar_bounds.bottom());
EXPECT_EQ(target.ToString(), find_bar_bounds.ToString());
}
// Test the find bar's bounding box when the location bar is not visible.
TEST_F(BrowserViewTest, FindBarBoundingBoxNoLocationBar) {
ASSERT_FALSE(base::i18n::IsRTL());
const views::View* location_bar = browser_view()->GetLocationBarView();
const views::View* contents_container = browser_view()->contents_container();
// Make sure we are testing the case where the location bar is absent.
browser_view()->GetLocationBarView()->SetVisible(false);
EXPECT_FALSE(location_bar->GetVisible());
const gfx::Rect find_bar_bounds = browser_view()->GetFindBarBoundingBox();
gfx::Rect contents_bounds = contents_container->ConvertRectToWidget(
contents_container->GetLocalBounds());
contents_bounds.Inset(gfx::Insets::TLBR(0, 0, 0, gfx::scrollbar_size()));
EXPECT_EQ(contents_bounds.ToString(), find_bar_bounds.ToString());
}
// Tests that a browser window is correctly associated to a WebContents that
// belongs to that window's UI hierarchy.
TEST_F(BrowserViewTest, FindBrowserWindowWithWebContents) {
auto web_view = std::make_unique<views::WebView>(browser()->profile());
ASSERT_NE(nullptr, web_view->GetWebContents());
// If the web contents does not belong browser's UI hierarchy there should not
// be a browser window associated with the contents.
EXPECT_EQ(nullptr, BrowserWindow::FindBrowserWindowWithWebContents(
web_view->GetWebContents()));
// After adding the web contents to the browser's UI hierarchy the browser
// window should be correctly associated with the contents.
auto* web_view_ptr = browser_view()->AddChildView(std::move(web_view));
EXPECT_EQ(browser()->window(),
BrowserWindow::FindBrowserWindowWithWebContents(
web_view_ptr->GetWebContents()));
// Removing the web contents from the browser's UI hierarchy should
// disassociate it with the browser window.
web_view = browser_view()->RemoveChildViewT(web_view_ptr);
EXPECT_EQ(nullptr, BrowserWindow::FindBrowserWindowWithWebContents(
web_view->GetWebContents()));
}
// Tests that tab contents are correctly associated with their browser window,
// even when non-active.
TEST_F(BrowserViewTest, FindBrowserWindowWithWebContentsTabSwitch) {
AddTab(browser_view()->browser(), GURL("about:blank"));
content::WebContents* original_active_contents =
browser_view()->GetActiveWebContents();
EXPECT_EQ(browser()->window(),
BrowserWindow::FindBrowserWindowWithWebContents(
original_active_contents));
// Inactive tabs (aka tabs with their web contents not currently embedded in
// the browser's ContentWebView) should still be associated with their hosting
// browser window.
AddTab(browser_view()->browser(), GURL("about:blank"));
content::WebContents* new_active_contents =
browser_view()->GetActiveWebContents();
EXPECT_NE(original_active_contents, browser_view()->GetActiveWebContents());
EXPECT_EQ(new_active_contents, browser_view()->GetActiveWebContents());
EXPECT_EQ(browser()->window(),
BrowserWindow::FindBrowserWindowWithWebContents(
original_active_contents));
EXPECT_EQ(
browser()->window(),
BrowserWindow::FindBrowserWindowWithWebContents(new_active_contents));
}
// On macOS, most accelerators are handled by CommandDispatcher.
#if !BUILDFLAG(IS_MAC)
// Test that repeated accelerators are processed or ignored depending on the
// commands that they refer to. The behavior for different commands is dictated
// by IsCommandRepeatable() in chrome/browser/ui/accelerator_table.h.
TEST_F(BrowserViewTest, DISABLED_RepeatedAccelerators) {
// A non-repeated Ctrl-L accelerator should be processed.
const ui::Accelerator kLocationAccel(ui::VKEY_L, ui::EF_PLATFORM_ACCELERATOR);
EXPECT_TRUE(browser_view()->AcceleratorPressed(kLocationAccel));
// If the accelerator is repeated, it should be ignored.
const ui::Accelerator kLocationRepeatAccel(
ui::VKEY_L, ui::EF_PLATFORM_ACCELERATOR | ui::EF_IS_REPEAT);
EXPECT_FALSE(browser_view()->AcceleratorPressed(kLocationRepeatAccel));
// A repeated Ctrl-Tab accelerator should be processed.
const ui::Accelerator kNextTabRepeatAccel(
ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_IS_REPEAT);
EXPECT_TRUE(browser_view()->AcceleratorPressed(kNextTabRepeatAccel));
}
#endif // !BUILDFLAG(IS_MAC)
// Test that bookmark bar view becomes invisible when closing the browser.
// TODO(crbug.com/40097152): Flaky on Linux.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_BookmarkBarInvisibleOnShutdown \
DISABLED_BookmarkBarInvisibleOnShutdown
#else
#define MAYBE_BookmarkBarInvisibleOnShutdown BookmarkBarInvisibleOnShutdown
#endif
TEST_F(BrowserViewTest, MAYBE_BookmarkBarInvisibleOnShutdown) {
BookmarkBarView::DisableAnimationsForTesting(true);
Browser* browser = browser_view()->browser();
TabStripModel* tab_strip_model = browser->tab_strip_model();
EXPECT_EQ(0, tab_strip_model->count());
AddTab(browser, GURL("about:blank"));
EXPECT_EQ(1, tab_strip_model->count());
BookmarkBarView* bookmark_bar = browser_view()->GetBookmarkBarView();
chrome::ExecuteCommand(browser, IDC_SHOW_BOOKMARK_BAR);
EXPECT_TRUE(bookmark_bar->GetVisible());
tab_strip_model->CloseWebContentsAt(tab_strip_model->active_index(), 0);
EXPECT_EQ(0, tab_strip_model->count());
EXPECT_FALSE(bookmark_bar->GetVisible());
BookmarkBarView::DisableAnimationsForTesting(false);
}
TEST_F(BrowserViewTest, UpdateWindowTitle) {
AddTab(browser(), GURL("about:blank"));
AddTab(browser(), GURL("about:blank"));
std::string user_title1 = "Test Title";
browser()->SetWindowUserTitle(user_title1);
auto window_title = browser_view()->GetAccessibleWindowTitle();
EXPECT_EQ(base::UTF8ToUTF16(user_title1),
window_title.substr(0, user_title1.size()));
std::string user_title2 = "Test Title 2";
browser()->SetWindowUserTitle(user_title2);
window_title = browser_view()->GetAccessibleWindowTitle();
EXPECT_EQ(base::UTF8ToUTF16(user_title2),
window_title.substr(0, user_title2.size()));
}
TEST_F(BrowserViewTest, WindowTitleOmitsLowMemoryUsage) {
scoped_refptr<TabResourceUsage> tab_resource_usage_ =
base::MakeRefCounted<TabResourceUsage>();
tab_resource_usage_->SetMemoryUsage(base::ByteCount(100));
TabRendererData memory_usage;
memory_usage.tab_resource_usage = tab_resource_usage_;
AddTab(browser(), GURL("about:blank"));
Tab* const tab = browser_view()->tabstrip()->tab_at(0);
tab->SetData(std::move(memory_usage));
// Expect that low memory usage isn't in the window title.
EXPECT_EQ(SubBrowserName(u"about:blank - ", u""),
browser_view()->GetAccessibleWindowTitle());
base::ByteCount memory_used =
TabResourceUsage::kHighMemoryUsageThreshold + base::ByteCount(1);
tab_resource_usage_->SetMemoryUsage(memory_used);
// Expect that high memory usage is in the window title.
EXPECT_TRUE(browser_view()->GetAccessibleWindowTitle().find(
u"High memory usage") != std::string::npos);
}
#if BUILDFLAG(IS_MAC)
// Tests that audio playing state is reflected in the "Window" menu on Mac.
TEST_F(BrowserViewTest, TitleAudioIndicators) {
std::u16string playing_icon = u"\U0001F50A";
std::u16string muted_icon = u"\U0001F507";
AddTab(browser_view()->browser(), GURL("about:blank"));
content::WebContents* contents = browser_view()->GetActiveWebContents();
RecentlyAudibleHelper* audible_helper =
RecentlyAudibleHelper::FromWebContents(contents);
audible_helper->SetNotRecentlyAudibleForTesting();
EXPECT_EQ(browser_view()->GetWindowTitle().find(playing_icon),
std::u16string::npos);
EXPECT_EQ(browser_view()->GetWindowTitle().find(muted_icon),
std::u16string::npos);
audible_helper->SetCurrentlyAudibleForTesting();
EXPECT_NE(browser_view()->GetWindowTitle().find(playing_icon),
std::u16string::npos);
EXPECT_EQ(browser_view()->GetWindowTitle().find(muted_icon),
std::u16string::npos);
audible_helper->SetRecentlyAudibleForTesting();
contents->SetAudioMuted(true);
EXPECT_EQ(browser_view()->GetWindowTitle().find(playing_icon),
std::u16string::npos);
EXPECT_NE(browser_view()->GetWindowTitle().find(muted_icon),
std::u16string::npos);
}
#endif
TEST_F(BrowserViewTest, RotatePaneFocusFromView) {
auto dialog_model = ui::DialogModel::Builder()
.SetTitle(u"test")
.SetIsAlertDialog()
.AddOkButton(base::DoNothing())
.Build();
auto* anchor = browser_view()->toolbar_button_provider()->GetAppMenuButton();
auto bubble = std::make_unique<views::BubbleDialogModelHost>(
std::move(dialog_model), anchor, views::BubbleBorder::TOP_RIGHT);
auto* bubble_ptr = bubble.get();
auto* widget = views::BubbleDialogDelegate::CreateBubble(std::move(bubble));
widget->Show();
// OK button cannot be retrieved until CreateBubble has been called.
auto* ok_button = bubble_ptr->GetOkButton();
auto* focus_manager = widget->GetFocusManager();
focus_manager->SetKeyboardAccessible(true);
// Initial rotation should return a "rotated" result.
EXPECT_TRUE(browser_view()->RotatePaneFocusFromView(nullptr, true, true));
EXPECT_EQ(ok_button, focus_manager->GetStoredFocusView());
// Next rotation should not return a "rotated" result and should not change
// the focus.
EXPECT_FALSE(browser_view()->RotatePaneFocusFromView(nullptr, true, false));
EXPECT_EQ(ok_button, focus_manager->GetStoredFocusView());
}
TEST_F(BrowserViewTest, AccessibleProperties) {
ui::AXNodeData data;
browser_view()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.role, ax::mojom::Role::kClient);
ui::AXNodeData root_view_data;
browser_view()
->GetWidget()
->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&root_view_data);
EXPECT_EQ(
root_view_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
browser_view()->GetAccessibleWindowTitle());
}
TEST_F(BrowserViewTest, UpdateAccessibleURL) {
const GURL before_url(u"data:text/html,before");
AddTab(browser(), before_url);
ui::AXNodeData node_data;
browser_view()
->GetWidget()
->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
before_url);
const GURL after_url(u"data:text/html,after");
AddTab(browser(), after_url);
browser_view()
->GetWidget()
->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
after_url);
}
TEST_F(BrowserViewTest, UpdateAccessibleURLOnTabSelection) {
// Create two tabs with different URLs
const GURL url1(u"data:text/html,tab1");
const GURL url2(u"data:text/html,tab2");
AddTab(browser(), url1);
AddTab(browser(), url2);
// Initially, the second tab should be active (most recently added)
// Note: AddTab inserts at index 0, so tab2 is at index 0, tab1 is at index 1
EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(),
url2);
ui::AXNodeData node_data;
browser_view()
->GetWidget()
->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
url2);
// Switch to the first tab (tab1 at index 1) by changing selection
browser()->tab_strip_model()->SelectTabAt(1);
EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(),
url1);
// Verify that the accessible URL was updated due to tab selection change
browser_view()
->GetWidget()
->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
url1);
// Switch back to the second tab (tab2 at index 0)
browser()->tab_strip_model()->SelectTabAt(0);
EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(),
url2);
// Verify that the accessible URL was updated again
browser_view()
->GetWidget()
->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
url2);
}
// Macs do not have fullscreen policy.
#if !BUILDFLAG(IS_MAC)
TEST_F(BrowserViewTest, CanFullscreenPolicyWatcher) {
auto* fullscreen_pref_path = prefs::kFullscreenAllowed;
EXPECT_TRUE(browser_view()->CanFullscreen());
browser_view()->GetProfile()->GetPrefs()->SetBoolean(fullscreen_pref_path,
false);
EXPECT_FALSE(browser_view()->CanFullscreen());
browser_view()->GetProfile()->GetPrefs()->SetBoolean(fullscreen_pref_path,
true);
EXPECT_TRUE(browser_view()->CanFullscreen());
}
class BrowserViewPipTest : public TestWithBrowserView {
public:
BrowserViewPipTest()
: TestWithBrowserView(Browser::TYPE_PICTURE_IN_PICTURE) {}
BrowserViewPipTest(const BrowserViewPipTest&) = delete;
BrowserViewPipTest& operator=(const BrowserViewPipTest&) = delete;
~BrowserViewPipTest() override = default;
};
// Pip is used to test reverting back to not allowed to fullscreen state.
TEST_F(BrowserViewPipTest, CanFullscreenPolicyDoesNotEnableFullscreen) {
auto* fullscreen_pref_path = prefs::kFullscreenAllowed;
EXPECT_FALSE(browser_view()->CanFullscreen());
browser_view()->GetProfile()->GetPrefs()->SetBoolean(fullscreen_pref_path,
false);
EXPECT_FALSE(browser_view()->CanFullscreen());
// This should have no effect, because pip is not allowed to enter fullscreen.
browser_view()->GetProfile()->GetPrefs()->SetBoolean(fullscreen_pref_path,
true);
EXPECT_FALSE(browser_view()->CanFullscreen());
}
#endif // !BUILDFLAG(IS_MAC)
class BrowserViewHostedAppTest : public TestWithBrowserView {
public:
BrowserViewHostedAppTest()
: TestWithBrowserView(Browser::TYPE_POPUP,
BrowserWithTestWindowTest::HostedApp()) {}
BrowserViewHostedAppTest(const BrowserViewHostedAppTest&) = delete;
BrowserViewHostedAppTest& operator=(const BrowserViewHostedAppTest&) = delete;
~BrowserViewHostedAppTest() override = default;
};
// Test basic layout for hosted apps.
TEST_F(BrowserViewHostedAppTest, Layout) {
// Add a tab because the browser starts out without any tabs at all.
AddTab(browser(), GURL("about:blank"));
const int contents_container_y = browser_view()->main_container()->y() -
browser_view()->contents_container()->y();
// The tabstrip, toolbar and bookmark bar should not be visible for hosted
// apps.
EXPECT_FALSE(browser_view()->tabstrip()->GetVisible());
EXPECT_FALSE(browser_view()->toolbar()->GetVisible());
EXPECT_FALSE(browser_view()->IsBookmarkBarVisible());
gfx::Point header_offset;
views::View::ConvertPointToTarget(
browser_view(),
browser_view()->browser_widget()->non_client_view()->frame_view(),
&header_offset);
// The position of the bottom of the header (the bar with the window
// controls) in the coordinates of BrowserView.
const int top_inset =
browser_view()->browser_widget()->GetFrameView()->GetTopInset(false);
const int bottom_of_header = top_inset - header_offset.y();
// The web contents should be flush with the bottom of the header.
EXPECT_EQ(bottom_of_header, contents_container_y);
// The find bar should butt against the 1px header/web-contents separator at
// the bottom of the header.
EXPECT_EQ(top_inset, browser_view()->GetFindBarBoundingBox().y());
}
using BrowserViewWindowTypeTest = BrowserWithTestWindowTest;
TEST_F(BrowserViewWindowTypeTest, TestWindowIsNotReturned) {
// Check that BrowserView::GetBrowserViewForBrowser does not return a
// non-BrowserView BrowserWindow instance - in this case, a TestBrowserWindow.
EXPECT_NE(nullptr, browser()->window());
EXPECT_EQ(nullptr, BrowserView::GetBrowserViewForBrowser(browser()));
}
// Tests Feature to ensure that the loading animation is not rendered after the
// window changes to hidden.
TEST_F(TestWithBrowserView, LoadingAnimationNotRenderedWhenWindowHidden) {
TabActivitySimulator tab_activity_simulator;
content::WebContents* web_contents =
tab_activity_simulator.AddWebContentsAndNavigate(
browser()->tab_strip_model(), GURL("about:blank"));
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
GURL("about:blank"), web_contents);
navigation->SetKeepLoading(true);
browser_view()->browser_widget()->Show();
EXPECT_TRUE(browser()->tab_strip_model()->TabsNeedLoadingUI());
EXPECT_TRUE(browser_view()->IsLoadingAnimationRunning());
browser_view()->browser_widget()->Hide();
EXPECT_TRUE(browser()->tab_strip_model()->TabsNeedLoadingUI());
EXPECT_FALSE(browser_view()->IsLoadingAnimationRunning());
}