blob: 2af1946db28bb6050645ab2f07ab61369ead322a [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include "base/optional.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_view.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
#include "chrome/browser/ui/views/frame/app_menu_button.h"
#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_test_helper.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_navigation_button_container.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
#include "chrome/browser/ui/web_applications/web_app_menu_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/page_zoom.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/theme_change_waiter.h"
#include "extensions/test/test_extension_dir.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"
#include "url/gurl.h"
namespace {
#if defined(OS_MAC)
// Keep in sync with browser_non_client_frame_view_mac.mm
constexpr double kTitlePaddingWidthFraction = 0.1;
#endif
template <typename T>
T* GetLastVisible(const std::vector<T*>& views) {
T* visible = nullptr;
for (auto* view : views) {
if (view->GetVisible())
visible = view;
}
return visible;
}
void LoadTestPopUpExtension(Profile* profile) {
extensions::TestExtensionDir test_extension_dir;
test_extension_dir.WriteManifest(
R"({
"name": "Pop up extension",
"version": "1.0",
"manifest_version": 2,
"browser_action": {
"default_popup": "popup.html"
}
})");
test_extension_dir.WriteFile(FILE_PATH_LITERAL("popup.html"), "");
extensions::ChromeTestExtensionLoader(profile).LoadExtension(
test_extension_dir.UnpackedPath());
}
} // namespace
class WebAppFrameToolbarBrowserTest : public InProcessBrowserTest {
public:
WebAppFrameToolbarBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
net::EmbeddedTestServer* https_server() { return &https_server_; }
// InProcessBrowserTest:
void SetUp() override {
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
InProcessBrowserTest::SetUp();
}
WebAppFrameToolbarTestHelper* helper() {
return &web_app_frame_toolbar_helper_;
}
bool IsMenuCommandEnabled(int command_id) {
auto app_menu_model = std::make_unique<WebAppMenuModel>(
/*provider=*/nullptr, helper()->app_browser());
app_menu_model->Init();
ui::MenuModel* model = app_menu_model.get();
int index = -1;
if (!app_menu_model->GetModelAndIndexForCommandId(command_id, &model,
&index)) {
return false;
}
return model->IsEnabledAt(index);
}
private:
net::EmbeddedTestServer https_server_;
WebAppFrameToolbarTestHelper web_app_frame_toolbar_helper_;
};
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest, SpaceConstrained) {
const GURL app_url("https://test.org");
helper()->InstallAndLaunchWebApp(browser(), app_url);
WebAppNavigationButtonContainer* const toolbar_left_container =
helper()->web_app_frame_toolbar()->get_left_container_for_testing();
EXPECT_EQ(toolbar_left_container->parent(),
helper()->web_app_frame_toolbar());
views::View* const window_title =
helper()->frame_view()->GetViewByID(VIEW_ID_WINDOW_TITLE);
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
EXPECT_FALSE(window_title);
#else
EXPECT_EQ(window_title->parent(), helper()->frame_view());
#endif
WebAppToolbarButtonContainer* const toolbar_right_container =
helper()->web_app_frame_toolbar()->get_right_container_for_testing();
EXPECT_EQ(toolbar_right_container->parent(),
helper()->web_app_frame_toolbar());
std::vector<const PageActionIconView*> page_actions =
helper()
->web_app_frame_toolbar()
->GetPageActionIconControllerForTesting()
->GetPageActionIconViewsForTesting();
for (const PageActionIconView* action : page_actions)
EXPECT_EQ(action->parent(), toolbar_right_container);
views::View* const menu_button =
helper()->browser_view()->toolbar_button_provider()->GetAppMenuButton();
EXPECT_EQ(menu_button->parent(), toolbar_right_container);
// Ensure we initially have abundant space.
helper()->frame_view()->SetSize(gfx::Size(1000, 1000));
EXPECT_TRUE(toolbar_left_container->GetVisible());
const int original_left_container_width = toolbar_left_container->width();
EXPECT_GT(original_left_container_width, 0);
#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
!BUILDFLAG(IS_CHROMEOS_LACROS))
const int original_window_title_width = window_title->width();
EXPECT_GT(original_window_title_width, 0);
#endif
// Initially the page action icons are not visible.
EXPECT_EQ(GetLastVisible(page_actions), nullptr);
const int original_menu_button_width = menu_button->width();
EXPECT_GT(original_menu_button_width, 0);
// Cause the zoom page action icon to be visible.
chrome::Zoom(helper()->app_browser(), content::PAGE_ZOOM_IN);
// The layout should be invalidated, but since we don't have the benefit of
// the compositor to immediately kick a layout off, we have to do it manually.
helper()->frame_view()->Layout();
// The page action icons should now take up width, leaving less space on
// Windows and Linux for the window title. (On Mac, the window title remains
// centered - not tested here.)
EXPECT_TRUE(toolbar_left_container->GetVisible());
EXPECT_EQ(toolbar_left_container->width(), original_left_container_width);
#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
!BUILDFLAG(IS_CHROMEOS_LACROS))
EXPECT_GT(window_title->width(), 0);
EXPECT_LT(window_title->width(), original_window_title_width);
#endif
EXPECT_NE(GetLastVisible(page_actions), nullptr);
EXPECT_EQ(menu_button->width(), original_menu_button_width);
// Resize the WebAppFrameToolbarView just enough to clip out the page action
// icons (and toolbar contents left of them).
const int original_toolbar_width = helper()->web_app_frame_toolbar()->width();
const int new_toolbar_width = toolbar_right_container->width() -
GetLastVisible(page_actions)->bounds().right();
const int new_frame_width = helper()->frame_view()->width() -
original_toolbar_width + new_toolbar_width;
helper()->web_app_frame_toolbar()->SetSize(
{new_toolbar_width, helper()->web_app_frame_toolbar()->height()});
helper()->frame_view()->SetSize(
{new_frame_width, helper()->frame_view()->height()});
// The left container (containing Back and Reload) should be hidden.
EXPECT_FALSE(toolbar_left_container->GetVisible());
// The window title should be clipped to 0 width.
#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
!BUILDFLAG(IS_CHROMEOS_LACROS))
EXPECT_EQ(window_title->width(), 0);
#endif
// The page action icons should be hidden while the app menu button retains
// its full width.
EXPECT_EQ(GetLastVisible(page_actions), nullptr);
EXPECT_EQ(menu_button->width(), original_menu_button_width);
}
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest, ThemeChange) {
ASSERT_TRUE(https_server()->Start());
const GURL app_url = https_server()->GetURL("/banners/theme-color.html");
helper()->InstallAndLaunchWebApp(browser(), app_url);
content::WebContents* web_contents =
helper()->app_browser()->tab_strip_model()->GetActiveWebContents();
content::AwaitDocumentOnLoadCompleted(web_contents);
// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
// of lacros-chrome is complete.
#if !(defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
// Avoid dependence on Linux GTK+ Themes appearance setting.
ToolbarButtonProvider* const toolbar_button_provider =
helper()->browser_view()->toolbar_button_provider();
AppMenuButton* const app_menu_button =
toolbar_button_provider->GetAppMenuButton();
const SkColor original_ink_drop_color =
app_menu_button->GetInkDropBaseColor();
{
content::ThemeChangeWaiter theme_change_waiter(web_contents);
EXPECT_TRUE(content::ExecJs(web_contents,
"document.getElementById('theme-color')."
"setAttribute('content', '#246')"));
theme_change_waiter.Wait();
EXPECT_NE(app_menu_button->GetInkDropBaseColor(), original_ink_drop_color);
}
{
content::ThemeChangeWaiter theme_change_waiter(web_contents);
EXPECT_TRUE(content::ExecJs(
web_contents, "document.getElementById('theme-color').remove()"));
theme_change_waiter.Wait();
EXPECT_EQ(app_menu_button->GetInkDropBaseColor(), original_ink_drop_color);
}
#endif
}
// Test that a tooltip is shown when hovering over a truncated title.
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest, TitleHover) {
const GURL app_url("https://test.org");
helper()->InstallAndLaunchWebApp(browser(), app_url);
WebAppNavigationButtonContainer* const toolbar_left_container =
helper()->web_app_frame_toolbar()->get_left_container_for_testing();
WebAppToolbarButtonContainer* const toolbar_right_container =
helper()->web_app_frame_toolbar()->get_right_container_for_testing();
auto* const window_title = static_cast<views::Label*>(
helper()->frame_view()->GetViewByID(VIEW_ID_WINDOW_TITLE));
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
// Chrome OS PWA windows do not display app titles.
EXPECT_EQ(nullptr, window_title);
return;
#endif
EXPECT_EQ(window_title->parent(), helper()->frame_view());
window_title->SetText(base::string16(30, 't'));
// Ensure we initially have abundant space.
helper()->frame_view()->SetSize(gfx::Size(1000, 1000));
helper()->frame_view()->Layout();
EXPECT_GT(window_title->width(), 0);
const int original_title_gap = toolbar_right_container->x() -
toolbar_left_container->x() -
toolbar_left_container->width();
// With a narrow window, we have insufficient space for the full title.
const int narrow_title_gap =
window_title->CalculatePreferredSize().width() * 3 / 4;
int narrow_frame_width =
helper()->frame_view()->width() - original_title_gap + narrow_title_gap;
#if defined(OS_MAC)
// Increase frame width to allow for title padding.
narrow_frame_width = base::checked_cast<int>(
std::ceil(narrow_frame_width / (1 - 2 * kTitlePaddingWidthFraction)));
#endif
helper()->frame_view()->SetSize(gfx::Size(narrow_frame_width, 1000));
helper()->frame_view()->Layout();
EXPECT_GT(window_title->width(), 0);
EXPECT_EQ(window_title->GetTooltipHandlerForPoint(gfx::Point(0, 0)),
window_title);
EXPECT_EQ(
helper()->frame_view()->GetTooltipHandlerForPoint(window_title->origin()),
window_title);
}
class WebAppFrameToolbarBrowserTest_ElidedExtensionsMenu
: public WebAppFrameToolbarBrowserTest {
public:
WebAppFrameToolbarBrowserTest_ElidedExtensionsMenu() {
scoped_feature_list_.InitAndEnableFeature(
features::kDesktopPWAsElidedExtensionsMenu);
}
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest_ElidedExtensionsMenu,
Test) {
helper()->InstallAndLaunchWebApp(browser(), GURL("https://test.org"));
// There should be no menu entry for opening the Extensions menu prior to
// installing Extensions.
EXPECT_FALSE(IsMenuCommandEnabled(WebAppMenuModel::kExtensionsMenuCommandId));
// Install test Extension.
LoadTestPopUpExtension(browser()->profile());
// There should be no visible Extensions icon.
WebAppToolbarButtonContainer* toolbar_button_container =
helper()->web_app_frame_toolbar()->get_right_container_for_testing();
EXPECT_FALSE(toolbar_button_container->extensions_container()->GetVisible());
// There should be a menu entry for opening the Extensions menu.
EXPECT_TRUE(IsMenuCommandEnabled(WebAppMenuModel::kExtensionsMenuCommandId));
// Trigger the Extensions menu entry.
auto app_menu_model = std::make_unique<WebAppMenuModel>(
/*provider=*/nullptr, helper()->app_browser());
app_menu_model->Init();
app_menu_model->ExecuteCommand(WebAppMenuModel::kExtensionsMenuCommandId,
/*event_flags=*/0);
// Extensions icon and menu should be visible.
EXPECT_TRUE(toolbar_button_container->extensions_container()->GetVisible());
EXPECT_TRUE(ExtensionsMenuView::IsShowing());
}
class WebAppFrameToolbarBrowserTest_NoElidedExtensionsMenu
: public WebAppFrameToolbarBrowserTest {
public:
WebAppFrameToolbarBrowserTest_NoElidedExtensionsMenu() {
scoped_feature_list_.InitAndDisableFeature(
features::kDesktopPWAsElidedExtensionsMenu);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest_NoElidedExtensionsMenu,
Test) {
helper()->InstallAndLaunchWebApp(browser(), GURL("https://test.org"));
WebAppToolbarButtonContainer* toolbar_button_container =
helper()->web_app_frame_toolbar()->get_right_container_for_testing();
// Extensions toolbar should be hidden while there are no Extensions
// installed.
EXPECT_FALSE(toolbar_button_container->extensions_container()->GetVisible());
// Install Extension and wait for Extensions toolbar to appear.
base::RunLoop run_loop;
ExtensionsToolbarContainer::SetOnVisibleCallbackForTesting(
run_loop.QuitClosure());
LoadTestPopUpExtension(browser()->profile());
run_loop.Run();
EXPECT_TRUE(toolbar_button_container->extensions_container()->GetVisible());
// There should be no menu entry for opening the Extensions menu.
EXPECT_FALSE(IsMenuCommandEnabled(WebAppMenuModel::kExtensionsMenuCommandId));
}