blob: 744b95a3a44cccbcabf3396212efddf6854daf31 [file] [log] [blame]
// Copyright 2017 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/immersive_mode_controller_chromeos.h"
#include "base/memory/raw_ptr.h"
#include "base/test/test_mock_time_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.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_menu_button.h"
#include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/permissions/permission_request_manager_test_api.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
#include "components/permissions/request_type.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/browser_test.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/test/ink_drop_host_test_api.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/window/frame_caption_button.h"
class ImmersiveModeControllerChromeosWebAppBrowserTest
: public web_app::WebAppBrowserTestBase {
public:
ImmersiveModeControllerChromeosWebAppBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
ImmersiveModeControllerChromeosWebAppBrowserTest(
const ImmersiveModeControllerChromeosWebAppBrowserTest&) = delete;
ImmersiveModeControllerChromeosWebAppBrowserTest& operator=(
const ImmersiveModeControllerChromeosWebAppBrowserTest&) = delete;
~ImmersiveModeControllerChromeosWebAppBrowserTest() override = default;
// WebAppBrowserTestBase override:
void SetUpOnMainThread() override {
WebAppBrowserTestBase::SetUpOnMainThread();
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server_.Start());
const GURL app_url = GetAppUrl();
auto web_app_info = std::make_unique<web_app::WebAppInstallInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url.GetWithoutFilename();
web_app_info->theme_color = SK_ColorBLUE;
app_id = InstallWebApp(std::move(web_app_info));
}
GURL GetAppUrl() { return https_server_.GetURL("/simple.html"); }
void LaunchAppBrowser(bool wait = true) {
ui_test_utils::UrlLoadObserver url_observer(
GetAppUrl(), content::NotificationService::AllSources());
browser_ = LaunchWebAppBrowser(app_id);
if (wait) {
// Wait for the URL to load so that the location bar end-state stabilizes.
url_observer.Wait();
}
controller_ = browser_view()->immersive_mode_controller();
// Disable animations in immersive fullscreen before we show the window,
// which triggers an animation.
chromeos::ImmersiveFullscreenControllerTestApi(
static_cast<ImmersiveModeControllerChromeos*>(controller_)
->controller())
.SetupForTest();
browser_->window()->Show();
}
// Returns the bounds of |view| in widget coordinates.
gfx::Rect GetBoundsInWidget(views::View* view) {
return view->ConvertRectToWidget(view->GetLocalBounds());
}
// Attempt revealing the top-of-window views.
void AttemptReveal() {
if (!revealed_lock_.get()) {
revealed_lock_ = controller_->GetRevealedLock(
ImmersiveModeControllerChromeos::ANIMATE_REVEAL_NO);
}
}
void VerifyButtonsInImmersiveMode(BrowserView* browser_view) {
WebAppFrameToolbarView* container =
browser_view->web_app_frame_toolbar_for_testing();
views::test::InkDropHostTestApi ink_drop_api(
views::InkDrop::Get(container->GetAppMenuButton()));
EXPECT_TRUE(container->GetContentSettingContainerForTesting()->layer());
EXPECT_EQ(views::InkDropHost::InkDropMode::ON,
ink_drop_api.ink_drop_mode());
}
Browser* browser() { return browser_; }
BrowserView* browser_view() {
return BrowserView::GetBrowserViewForBrowser(browser_);
}
ImmersiveModeController* controller() { return controller_; }
base::TimeDelta titlebar_animation_delay() {
return WebAppToolbarButtonContainer::kTitlebarAnimationDelay;
}
private:
webapps::AppId app_id;
raw_ptr<Browser, DanglingUntriaged> browser_ = nullptr;
raw_ptr<ImmersiveModeController, DanglingUntriaged> controller_ = nullptr;
std::unique_ptr<ImmersiveRevealedLock> revealed_lock_;
net::EmbeddedTestServer https_server_;
};
// Test the layout and visibility of the TopContainerView and web contents when
// a web app is put into immersive fullscreen.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
Layout) {
LaunchAppBrowser();
TabStrip* tabstrip = browser_view()->tabstrip();
ToolbarView* toolbar = browser_view()->toolbar();
views::WebView* contents_web_view = browser_view()->contents_web_view();
views::View* top_container = browser_view()->top_container();
// Immersive fullscreen starts out disabled.
ASSERT_FALSE(browser_view()->GetWidget()->IsFullscreen());
ASSERT_FALSE(controller()->IsEnabled());
// The tabstrip is not visible for web apps.
EXPECT_FALSE(tabstrip->GetVisible());
EXPECT_TRUE(toolbar->GetVisible());
// The window header should be above the web contents.
int header_height = GetBoundsInWidget(contents_web_view).y();
EnterImmersiveFullscreenMode(browser());
EXPECT_TRUE(browser_view()->GetWidget()->IsFullscreen());
EXPECT_TRUE(controller()->IsEnabled());
EXPECT_FALSE(controller()->IsRevealed());
// Entering immersive fullscreen should make the web contents flush with the
// top of the widget. The popup browser type doesn't support tabstrip and
// toolbar feature, thus invisible.
EXPECT_FALSE(tabstrip->GetVisible());
EXPECT_FALSE(toolbar->GetVisible());
EXPECT_TRUE(top_container->GetVisibleBounds().IsEmpty());
EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());
// Reveal the window header.
AttemptReveal();
// The tabstrip should still be hidden and the web contents should still be
// flush with the top of the screen.
EXPECT_FALSE(tabstrip->GetVisible());
EXPECT_TRUE(toolbar->GetVisible());
EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());
// During an immersive reveal, the window header should be painted to the
// TopContainerView. The TopContainerView should be flush with the top of the
// widget and have |header_height|.
gfx::Rect top_container_bounds_in_widget(GetBoundsInWidget(top_container));
EXPECT_EQ(0, top_container_bounds_in_widget.y());
EXPECT_EQ(header_height, top_container_bounds_in_widget.height());
// Exit immersive fullscreen. The web contents should be back below the window
// header.
ExitImmersiveFullscreenMode(browser());
EXPECT_FALSE(browser_view()->GetWidget()->IsFullscreen());
EXPECT_FALSE(controller()->IsEnabled());
EXPECT_FALSE(tabstrip->GetVisible());
EXPECT_TRUE(toolbar->GetVisible());
EXPECT_EQ(header_height, GetBoundsInWidget(contents_web_view).y());
}
// Verify the immersive mode status is as expected in tablet mode (titlebars are
// autohidden in tablet mode).
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(b/40946296): Port and enable when bug is fixed.
#define MAYBE_ImmersiveModeStatusTabletMode \
DISABLED_ImmersiveModeStatusTabletMode
#else
#define MAYBE_ImmersiveModeStatusTabletMode ImmersiveModeStatusTabletMode
#endif
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
MAYBE_ImmersiveModeStatusTabletMode) {
LaunchAppBrowser();
ASSERT_FALSE(controller()->IsEnabled());
aura::Window* aura_window = browser_view()->frame()->GetNativeWindow();
// Verify that after entering tablet mode, immersive mode is enabled, and the
// the associated window's top inset is 0 (the top of the window is not
// visible).
EnterTabletMode();
EXPECT_TRUE(controller()->IsEnabled());
EXPECT_EQ(0, aura_window->GetProperty(aura::client::kTopViewInset));
// Verify that after minimizing, immersive mode is disabled.
browser()->window()->Minimize();
EXPECT_TRUE(browser()->window()->IsMinimized());
EXPECT_FALSE(controller()->IsEnabled());
// Verify that after showing the browser, immersive mode is reenabled.
browser()->window()->Show();
EXPECT_TRUE(controller()->IsEnabled());
// Verify that immersive mode remains if fullscreen is toggled while in tablet
// mode.
ui_test_utils::ToggleFullscreenModeAndWait(browser());
EXPECT_TRUE(controller()->IsEnabled());
ExitTabletMode();
EXPECT_TRUE(controller()->IsEnabled());
// Verify that immersive mode remains if the browser was fullscreened when
// entering tablet mode.
EnterTabletMode();
EXPECT_TRUE(controller()->IsEnabled());
// Verify that if the browser is not fullscreened, upon exiting tablet mode,
// immersive mode is not enabled, and the associated window's top inset is
// greater than 0 (the top of the window is visible).
ui_test_utils::ToggleFullscreenModeAndWait(browser());
EXPECT_TRUE(controller()->IsEnabled());
ExitTabletMode();
EXPECT_FALSE(controller()->IsEnabled());
EXPECT_GT(aura_window->GetProperty(aura::client::kTopViewInset), 0);
}
// Verify that the frame layout is as expected when using immersive mode in
// tablet mode.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
FrameLayoutToggleTabletMode) {
LaunchAppBrowser();
ASSERT_FALSE(controller()->IsEnabled());
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
BrowserNonClientFrameViewChromeOS* frame_view =
static_cast<BrowserNonClientFrameViewChromeOS*>(
browser_view->GetWidget()->non_client_view()->frame_view());
chromeos::FrameCaptionButtonContainerView* caption_button_container =
frame_view->caption_button_container();
chromeos::FrameCaptionButtonContainerView::TestApi frame_test_api(
caption_button_container);
EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
// Verify the size button is hidden in tablet mode.
EnterTabletMode();
frame_test_api.EndAnimations();
EXPECT_FALSE(frame_test_api.size_button()->GetVisible());
VerifyButtonsInImmersiveMode(browser_view);
// Verify the size button is visible in clamshell mode, and that it does not
// cover the other two buttons.
ExitTabletMode();
frame_test_api.EndAnimations();
EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
EXPECT_FALSE(frame_test_api.size_button()->GetBoundsInScreen().Intersects(
frame_test_api.close_button()->GetBoundsInScreen()));
EXPECT_FALSE(frame_test_api.size_button()->GetBoundsInScreen().Intersects(
frame_test_api.minimize_button()->GetBoundsInScreen()));
VerifyButtonsInImmersiveMode(browser_view);
}
// Verify that the frame layout for new windows is as expected when using
// immersive mode in tablet mode.
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
FrameLayoutStartInTabletMode) {
// Start in tablet mode
EnterTabletMode();
// Launch app window while in tablet mode
LaunchAppBrowser(false);
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
{
// Skip the title bar animation.
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner);
task_runner->FastForwardBy(titlebar_animation_delay());
}
VerifyButtonsInImmersiveMode(browser_view);
// Verify the size button is visible in clamshell mode, and that it does not
// cover the other two buttons.
ExitTabletMode();
VerifyButtonsInImmersiveMode(browser_view);
}
// Tests that the permissions bubble dialog is anchored to the correct location.
// The dialog's anchor is normally the app menu button which is on the header.
// In immersive mode but not revealed, the app menu button is placed off screen
// but still drawn. In this case, we should have a null anchor view so that the
// bubble gets placed in the default top left corner. Regression test for
// https://crbug.com/1087143.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/329759044): Enable when bug is fixed.
#define MAYBE_PermissionsBubbleAnchor DISABLED_PermissionsBubbleAnchor
#else
#define MAYBE_PermissionsBubbleAnchor PermissionsBubbleAnchor
#endif
IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerChromeosWebAppBrowserTest,
MAYBE_PermissionsBubbleAnchor) {
LaunchAppBrowser();
auto test_api =
std::make_unique<test::PermissionRequestManagerTestApi>(browser());
EXPECT_TRUE(test_api->manager());
// Add a permission bubble using the test api.
test_api->AddSimpleRequest(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
permissions::RequestType::kGeolocation);
// The permission prompt is shown asynchronously. Without immersive mode
// enabled the anchor should exist.
// TODO(https://crbug.com/1317865): Change from RunUntilIdle to a more
// explicit notification.
base::RunLoop().RunUntilIdle();
views::Widget* prompt_widget = test_api->GetPromptWindow();
views::BubbleDialogDelegate* bubble_dialog =
prompt_widget->widget_delegate()->AsBubbleDialogDelegate();
ASSERT_TRUE(bubble_dialog);
EXPECT_TRUE(bubble_dialog->GetAnchorView());
// Turn on immersive, but do not reveal.
auto* immersive_mode_controller =
BrowserView::GetBrowserViewForBrowser(browser())
->immersive_mode_controller();
immersive_mode_controller->SetEnabled(true);
// Since a bubble was visible and anchored to the header, the header should
// have been automatically revealed.
EXPECT_TRUE(immersive_mode_controller->IsRevealed());
EXPECT_TRUE(bubble_dialog->GetAnchorView());
// Closing the bubble should cause the header to no longer be revealed.
bubble_dialog->AcceptDialog();
EXPECT_FALSE(immersive_mode_controller->IsRevealed());
// Make sure the old permission prompt fully goes away before opening a new
// prompt.
// TODO(https://crbug.com/1317865): Change from RunUntilIdle to a more
// explicit notification.
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(test_api->GetPromptWindow());
// Opening a new permission bubble should not cause the header to reveal.
test_api->AddSimpleRequest(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
permissions::RequestType::kMicStream);
// The permission prompt is shown asynchronously.
// TODO(https://crbug.com/1317865): Change from RunUntilIdle to a more
// explicit notification.
base::RunLoop().RunUntilIdle();
prompt_widget = test_api->GetPromptWindow();
ASSERT_TRUE(prompt_widget);
ASSERT_TRUE(prompt_widget->widget_delegate());
bubble_dialog = prompt_widget->widget_delegate()->AsBubbleDialogDelegate();
ASSERT_TRUE(bubble_dialog);
// The app menu button is hidden from
// sight so the anchor should be null. The bubble will get placed in the top
// left corner of the app.
EXPECT_FALSE(immersive_mode_controller->IsRevealed());
EXPECT_FALSE(bubble_dialog->GetAnchorView());
// Reveal the header. The anchor should exist since the app menu button is
// now visible.
{
std::unique_ptr<ImmersiveRevealedLock> focus_reveal_lock =
immersive_mode_controller->GetRevealedLock(
ImmersiveModeController::ANIMATE_REVEAL_YES);
EXPECT_TRUE(immersive_mode_controller->IsRevealed());
EXPECT_TRUE(bubble_dialog->GetAnchorView());
}
EXPECT_FALSE(immersive_mode_controller->IsRevealed());
EXPECT_FALSE(bubble_dialog->GetAnchorView());
}