blob: 3df3ac4b4fd04ea90a5fd7ffefd89a5fa777b16c [file] [log] [blame]
// Copyright 2016 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_frame_view.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/page_action/page_action_icon_type.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/custom_tab_bar_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/core/browser/form_import/form_data_importer.h"
#include "components/autofill/core/browser/form_import/form_data_importer_test_api.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#include "components/autofill/core/browser/foundations/test_autofill_manager_waiter.h"
#include "components/autofill/core/browser/payments/credit_card_save_manager.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/theme_change_waiter.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/mock_os_settings_provider.h"
namespace {
class TestAutofillManager : public autofill::BrowserAutofillManager {
public:
explicit TestAutofillManager(autofill::ContentAutofillDriver* driver)
: BrowserAutofillManager(driver) {}
[[nodiscard]] testing::AssertionResult WaitForFormsSeen(
int min_num_awaited_calls) {
return forms_seen_waiter_.Wait(min_num_awaited_calls);
}
private:
autofill::TestAutofillManagerWaiter forms_seen_waiter_{
*this,
{autofill::AutofillManagerEvent::kFormsSeen}};
};
} // namespace
class BrowserFrameViewBrowserTest : public extensions::ExtensionBrowserTest {
public:
BrowserFrameViewBrowserTest() = default;
BrowserFrameViewBrowserTest(const BrowserFrameViewBrowserTest&) = delete;
BrowserFrameViewBrowserTest& operator=(const BrowserFrameViewBrowserTest&) =
delete;
~BrowserFrameViewBrowserTest() override = default;
void SetUp() override {
embedded_test_server()->ServeFilesFromSourceDirectory(
"components/test/data");
ASSERT_TRUE(embedded_test_server()->Start());
extensions::ExtensionBrowserTest::SetUp();
}
// Note: A "bookmark app" is a type of hosted app. All of these tests apply
// equally to hosted and bookmark apps, but it's easier to install a bookmark
// app in a test.
// TODO: Add tests for non-bookmark hosted apps, as bookmark apps will no
// longer be hosted apps when BMO ships.
void InstallAndLaunchBookmarkApp(std::optional<GURL> app_url = std::nullopt) {
blink::mojom::Manifest manifest;
manifest.manifest_url = embedded_test_server()->GetURL("/manifest");
manifest.start_url = app_url.value_or(GetAppURL());
manifest.id = app_url.value_or(GetAppURL());
manifest.scope = manifest.start_url.GetWithoutFilename();
manifest.has_theme_color = true;
manifest.theme_color = app_theme_color_;
std::unique_ptr<web_app::WebAppInstallInfo> web_app_info =
web_app::test::GetInstallInfoForCurrentManifest(
browser()->tab_strip_model()->GetActiveWebContents()->GetWeakPtr(),
manifest);
webapps::AppId app_id =
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
app_browser_ = web_app::LaunchWebAppBrowser(profile(), app_id);
web_contents_ = app_browser_->tab_strip_model()->GetActiveWebContents();
// Ensure the main page has loaded and is ready for ExecJs DOM
// manipulation.
ASSERT_TRUE(content::NavigateToURL(web_contents_, manifest.start_url));
app_browser_view_ = BrowserView::GetBrowserViewForBrowser(app_browser_);
}
// Frame view may get reset after theme change, so always access from the
// browser view and don't retain the pointer.
// TODO(crbug.com/40656280): Make it not do this and only refresh the Widget.
BrowserFrameView* GetAppFrameView() {
return app_browser_view_->browser_widget()->GetFrameView();
}
protected:
SkColor app_theme_color_ = SK_ColorBLUE;
raw_ptr<Browser, AcrossTasksDanglingUntriaged> app_browser_ = nullptr;
raw_ptr<BrowserView, AcrossTasksDanglingUntriaged> app_browser_view_ =
nullptr;
raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> web_contents_ =
nullptr;
autofill::TestAutofillManagerInjector<TestAutofillManager>
autofill_manager_injector_;
private:
GURL GetAppURL() { return embedded_test_server()->GetURL("/empty.html"); }
web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
};
// Tests the frame color for a normal browser window.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest, BrowserFrameColorThemed) {
InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
const BrowserFrameView* frame_view =
browser_view->browser_widget()->GetFrameView();
const ui::ColorProvider* color_provider = frame_view->GetColorProvider();
const SkColor expected_active_color =
color_provider->GetColor(ui::kColorFrameActive);
const SkColor expected_inactive_color =
color_provider->GetColor(ui::kColorFrameInactive);
EXPECT_EQ(expected_active_color,
frame_view->GetFrameColor(BrowserFrameActiveState::kActive));
EXPECT_EQ(expected_inactive_color,
frame_view->GetFrameColor(BrowserFrameActiveState::kInactive));
}
// Tests the frame color for a bookmark app when a theme is applied.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
BookmarkAppFrameColorCustomTheme) {
// The theme color should not affect the window, but the theme must not be the
// default GTK theme for Linux so we install one anyway.
InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
InstallAndLaunchBookmarkApp();
// Note: This is checking for the bookmark app's theme color, not the user's
// theme color.
EXPECT_EQ(app_theme_color_,
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive));
}
// Tests the frame color for a bookmark app when a theme is applied, with the
// app itself having no theme color.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
BookmarkAppFrameColorCustomThemeNoThemeColor) {
InstallAndLaunchBookmarkApp();
const SkColor color_without_theme =
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive);
InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
// Bookmark apps are not affected by browser themes.
EXPECT_EQ(color_without_theme,
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive));
}
// Tests that an opaque frame color is used for a web app with a transparent
// theme color.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
OpaqueFrameColorForTransparentWebAppThemeColor) {
// Ensure we're not using the system theme on Linux.
ThemeService* theme_service =
ThemeServiceFactory::GetForProfile(browser()->profile());
theme_service->UseDefaultTheme();
app_theme_color_ = SkColorSetA(SK_ColorBLUE, 0x88);
InstallAndLaunchBookmarkApp();
EXPECT_EQ(GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive),
SK_ColorBLUE);
}
// Tests the frame color for a bookmark app when the system theme is applied.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
BookmarkAppFrameColorSystemTheme) {
ThemeService* theme_service =
ThemeServiceFactory::GetForProfile(browser()->profile());
// Should be using the system theme by default, but this assert was not true
// on the bots. Explicitly set.
theme_service->UseSystemTheme();
ASSERT_TRUE(theme_service->UsingSystemTheme());
InstallAndLaunchBookmarkApp();
#if BUILDFLAG(IS_LINUX)
// On Linux, the system theme is the GTK theme and should change the frame
// color to the system color (not the app theme color); otherwise the title
// and border would clash horribly with the GTK title bar.
// (https://crbug.com/878636)
const ui::ColorProvider* color_provider =
GetAppFrameView()->GetColorProvider();
const SkColor frame_color = color_provider->GetColor(ui::kColorFrameActive);
EXPECT_EQ(frame_color,
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive));
#else
EXPECT_EQ(app_theme_color_,
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive));
#endif
}
// Verifies that the incognito window frame is always the right color.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest, IncognitoIsCorrectColor) {
// Set the color that's expected to be ignored.
ui::MockOsSettingsProvider os_settings_provider;
os_settings_provider.SetAccentColor(gfx::kGoogleBlue400);
Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
BrowserView* view = BrowserView::GetBrowserViewForBrowser(incognito_browser);
BrowserWidget* widget = view->browser_widget();
BrowserFrameView* frame_view = widget->GetFrameView();
color_utils::HSL frame_color_hsl;
SkColorToHSL(frame_view->GetFrameColor(BrowserFrameActiveState::kActive),
&frame_color_hsl);
// Ensure that the frame color is very dark in Incognito.
EXPECT_LT(frame_color_hsl.l, 0.2);
incognito_browser->window()->Close();
}
// Checks that the title bar for hosted app windows is hidden when in fullscreen
// for tab mode.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
FullscreenForTabTitlebarHeight) {
InstallAndLaunchBookmarkApp();
static_cast<content::WebContentsDelegate*>(app_browser_)
->EnterFullscreenModeForTab(web_contents_->GetPrimaryMainFrame(), {});
EXPECT_EQ(GetAppFrameView()->GetTopInset(false), 0);
}
// Tests that the custom tab bar is visible in fullscreen mode.
// TODO(crbug.com/40855995): Flaky on linux-wayland-rel.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_CustomTabBarIsVisibleInFullscreen \
DISABLED_CustomTabBarIsVisibleInFullscreen
#else
#define MAYBE_CustomTabBarIsVisibleInFullscreen \
CustomTabBarIsVisibleInFullscreen
#endif
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
MAYBE_CustomTabBarIsVisibleInFullscreen) {
InstallAndLaunchBookmarkApp();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(app_browser_, GURL("http://example.com")));
static_cast<content::WebContentsDelegate*>(app_browser_)
->EnterFullscreenModeForTab(web_contents_->GetPrimaryMainFrame(), {});
EXPECT_TRUE(app_browser_view_->toolbar()->custom_tab_bar()->IsDrawn());
}
// Tests that hosted app frames reflect the theme color set by HTML meta tags.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest,
HTMLMetaThemeColorOverridesManifest) {
// Ensure we're not using the system theme on Linux.
ThemeService* theme_service =
ThemeServiceFactory::GetForProfile(browser()->profile());
theme_service->UseDefaultTheme();
InstallAndLaunchBookmarkApp();
ASSERT_EQ(app_theme_color_, SK_ColorBLUE);
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
app_theme_color_);
views::View* const window_title_view =
GetAppFrameView()->GetViewByID(VIEW_ID_WINDOW_TITLE);
views::Label* const window_title =
window_title_view ? static_cast<views::Label*>(window_title_view)
: nullptr;
if (window_title) {
EXPECT_EQ(window_title->GetBackgroundColor(), app_theme_color_);
}
{
// Add two meta theme color elements. The first element's color should be
// picked.
content::ThemeChangeWaiter waiter(web_contents_);
EXPECT_TRUE(content::ExecJs(
web_contents_.get(),
"document.documentElement.innerHTML = '"
"<meta id=\"first\" name=\"theme-color\" content=\"red\">"
"<meta id=\"second\" name=\"theme-color\" content=\"#00ff00\">'"));
waiter.Wait();
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
SK_ColorRED);
if (window_title) {
EXPECT_EQ(window_title->GetBackgroundColor(), SK_ColorRED);
}
}
{
// Change the color of the first element. The new color should be picked.
content::ThemeChangeWaiter waiter(web_contents_);
EXPECT_TRUE(content::ExecJs(
web_contents_.get(),
"document.getElementById('first').setAttribute('content', 'yellow')"));
waiter.Wait();
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
SK_ColorYELLOW);
if (window_title) {
EXPECT_EQ(window_title->GetBackgroundColor(), SK_ColorYELLOW);
}
}
{
// Set a non matching media query to the first element. The second element's
// color should be picked.
content::ThemeChangeWaiter waiter(web_contents_);
EXPECT_TRUE(content::ExecJs(web_contents_.get(),
"document.getElementById('first')."
"setAttribute('media', '(max-width: 0px)')"));
waiter.Wait();
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
SK_ColorGREEN);
}
{
// Remove the second element. The manifest color should be picked because
// the first element still does not match.
content::ThemeChangeWaiter waiter(web_contents_);
EXPECT_TRUE(content::ExecJs(web_contents_.get(),
"document.getElementById('second').remove()"));
waiter.Wait();
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
SK_ColorBLUE);
}
{
// Set a matching media query to the first element. The first element's
// color should be picked.
content::ThemeChangeWaiter waiter(web_contents_);
std::string width =
content::EvalJs(web_contents_.get(), "innerWidth.toString()")
.ExtractString();
EXPECT_TRUE(content::ExecJs(web_contents_.get(),
"document.getElementById('first')."
"setAttribute('media', '(max-width: " +
width + "px')"));
waiter.Wait();
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
SK_ColorYELLOW);
}
{
// Resize the window so that the media query on the first element does not
// match anymore. The manifest color should be picked.
content::ThemeChangeWaiter waiter(web_contents_);
EXPECT_TRUE(content::ExecJs(web_contents_.get(), "window.resizeBy(24, 0)"));
waiter.Wait();
EXPECT_EQ(
GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kUseCurrent),
SK_ColorBLUE);
}
}
class SaveCardOfferObserver
: public autofill::CreditCardSaveManager::ObserverForTest {
public:
explicit SaveCardOfferObserver(content::WebContents* web_contents) {
manager_ = autofill::ContentAutofillDriver::GetForRenderFrameHost(
web_contents->GetPrimaryMainFrame())
->GetAutofillManager()
.client()
.GetFormDataImporter()
->GetCreditCardSaveManager();
manager_->SetEventObserverForTesting(this);
}
~SaveCardOfferObserver() override {
manager_->SetEventObserverForTesting(nullptr);
}
// CreditCardSaveManager::ObserverForTest:
void OnOfferLocalSave() override { run_loop_.Quit(); }
void Wait() { run_loop_.Run(); }
private:
raw_ptr<autofill::CreditCardSaveManager> manager_ = nullptr;
base::RunLoop run_loop_;
};
// TODO(crbug.com/40866991): Test is flaky.
// Tests that hosted app frames reflect the theme color set by HTML meta tags.
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest, DISABLED_SaveCardIcon) {
InstallAndLaunchBookmarkApp(embedded_test_server()->GetURL(
"/autofill/credit_card_upload_form_address_and_cc.html"));
ASSERT_TRUE(autofill_manager_injector_[web_contents_]->WaitForFormsSeen(1));
ASSERT_TRUE(content::ExecJs(web_contents_.get(), "fill_form.click();"));
content::TestNavigationObserver nav_observer(web_contents_);
SaveCardOfferObserver offer_observer(web_contents_);
ASSERT_TRUE(content::ExecJs(web_contents_.get(), "submit.click();"));
nav_observer.Wait();
offer_observer.Wait();
PageActionIconView* icon =
app_browser_view_->toolbar_button_provider()->GetPageActionIconView(
PageActionIconType::kSaveCard);
EXPECT_TRUE(GetAppFrameView()->Contains(icon));
EXPECT_TRUE(icon->GetVisible());
}
#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(BrowserFrameViewBrowserTest, BrowserFrameWindowMask) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
BrowserFrameView* frame_view = browser_view->browser_widget()->GetFrameView();
SkPath path;
frame_view->GetWindowMask(frame_view->bounds().size(), &path);
EXPECT_TRUE(path.isEmpty());
}
#endif // BUILDFLAG(IS_CHROMEOS)