chromium/chrome/browser/ui/views/frame/immersive_mode_browser_view_test.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/chromeos/test_util.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/immersive_mode_controller.h"
#include "chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h"
#include "chrome/browser/ui/views/frame/immersive_mode_tester.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/fullscreen_control/fullscreen_control_host.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.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 "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "ui/aura/window.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/ui_base_types.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/caption_button_layout_constants.h"
#include "url/gurl.h"
#include "url/url_constants.h"

namespace {

class ImmersiveModeBrowserViewTest
    : public TopChromeMdParamTest<ChromeOSBrowserUITest> {
 public:
  ImmersiveModeBrowserViewTest() = default;
  ImmersiveModeBrowserViewTest(const ImmersiveModeBrowserViewTest&) = delete;
  ImmersiveModeBrowserViewTest& operator=(const ImmersiveModeBrowserViewTest&) =
      delete;
  ~ImmersiveModeBrowserViewTest() override = default;

  // TopChromeMdParamTest<ChromeOSBrowserUITest>:
  void PreRunTestOnMainThread() override {
    InProcessBrowserTest::PreRunTestOnMainThread();

    BrowserView::SetDisableRevealerDelayForTesting(true);

    chromeos::ImmersiveFullscreenControllerTestApi(
        static_cast<ImmersiveModeControllerChromeos*>(
            BrowserView::GetBrowserViewForBrowser(browser())
                ->immersive_mode_controller())
            ->controller())
        .SetupForTest();
  }
};

}  // namespace

using ImmersiveModeBrowserViewTestNoWebUiTabStrip =
    WebUiTabStripOverrideTest<false, ImmersiveModeBrowserViewTest>;

// This test does not make sense for the webUI tabstrip, since the frame is not
// painted in that case.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/40199989): Reveal does not end until mouse is moved. Find out
// if this is a product or test issue and fix it.
#define MAYBE_ImmersiveFullscreen DISABLED_ImmersiveFullscreen
#else
#define MAYBE_ImmersiveFullscreen ImmersiveFullscreen
#endif
IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTestNoWebUiTabStrip,
                       MAYBE_ImmersiveFullscreen) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  content::WebContents* web_contents = browser_view->GetActiveWebContents();
  BrowserNonClientFrameViewChromeOS* frame_view =
      GetFrameViewChromeOS(browser_view);

  ImmersiveModeController* immersive_mode_controller =
      browser_view->immersive_mode_controller();

  // Immersive fullscreen starts disabled.
  ASSERT_FALSE(browser_view->GetWidget()->IsFullscreen());
  EXPECT_FALSE(immersive_mode_controller->IsEnabled());

  // Frame paints by default.
  EXPECT_TRUE(frame_view->GetShouldPaint());
  EXPECT_LT(0, frame_view
                   ->GetBoundsForTabStripRegion(
                       browser_view->tab_strip_region_view()->GetMinimumSize())
                   .bottom());

  // Enter both browser fullscreen and tab fullscreen. Entering browser
  // fullscreen should enable immersive fullscreen.
  ui_test_utils::ToggleFullscreenModeAndWait(browser());
  EnterTabFullscreenMode(browser(), web_contents);
  EXPECT_TRUE(immersive_mode_controller->IsEnabled());
  // Caption button container is hidden.
  EXPECT_FALSE(frame_view->caption_button_container_->GetVisible());

  // An immersive reveal shows the buttons and the top of the frame.
  std::unique_ptr<ImmersiveRevealedLock> revealed_lock =
      immersive_mode_controller->GetRevealedLock(
          ImmersiveModeController::ANIMATE_REVEAL_NO);
  EXPECT_TRUE(immersive_mode_controller->IsRevealed());
  EXPECT_TRUE(frame_view->GetShouldPaint());
  // Caption button container is visible again.
  EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());

  // End the reveal. When in both immersive browser fullscreen and tab
  // fullscreen.
  revealed_lock.reset();
  EXPECT_FALSE(immersive_mode_controller->IsRevealed());
  EXPECT_FALSE(frame_view->GetShouldPaint());
  EXPECT_EQ(0, frame_view
                   ->GetBoundsForTabStripRegion(
                       browser_view->tab_strip_region_view()->GetMinimumSize())
                   .bottom());
  EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());

  // Repeat test but without tab fullscreen.
  EnterTabFullscreenMode(browser(), web_contents);

  // Immersive reveal should have same behavior as before.
  revealed_lock = immersive_mode_controller->GetRevealedLock(
      ImmersiveModeController::ANIMATE_REVEAL_NO);
  EXPECT_TRUE(immersive_mode_controller->IsRevealed());
  EXPECT_TRUE(frame_view->GetShouldPaint());
  EXPECT_LT(0, frame_view
                   ->GetBoundsForTabStripRegion(
                       browser_view->tab_strip_region_view()->GetMinimumSize())
                   .bottom());
  EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());

  // Ending the reveal. Immersive browser should have the same behavior as full
  // screen, i.e., having an origin of (0,0).
  revealed_lock.reset();
  EXPECT_FALSE(frame_view->GetShouldPaint());
  EXPECT_EQ(0, frame_view
                   ->GetBoundsForTabStripRegion(
                       browser_view->tab_strip_region_view()->GetMinimumSize())
                   .bottom());
  EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());

  // Exiting immersive fullscreen should make the caption buttons and the frame
  // visible again.
  {
    ui_test_utils::FullscreenWaiter waiter(
        browser(), ui_test_utils::FullscreenWaiter::kNoFullscreen);
    browser_view->ExitFullscreen();
    waiter.Wait();
  }
  EXPECT_FALSE(immersive_mode_controller->IsEnabled());
  EXPECT_TRUE(frame_view->GetShouldPaint());
  EXPECT_LT(0, frame_view
                   ->GetBoundsForTabStripRegion(
                       browser_view->tab_strip_region_view()->GetMinimumSize())
                   .bottom());
  EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
}

// Tests IDC_SELECT_TAB_0, IDC_SELECT_NEXT_TAB, IDC_SELECT_PREVIOUS_TAB and
// IDC_SELECT_LAST_TAB when the browser is in immersive fullscreen mode.
IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                       TabNavigationAcceleratorsFullscreenBrowser) {
  ImmersiveModeTester tester(browser());

  // Make sure that the focus is on the webcontents rather than on the omnibox,
  // because if the focus is on the omnibox, the tab strip will remain revealed
  // in the immersive fullscreen mode and will interfere with this test waiting
  // for the revealer to be dismissed.
  browser()->tab_strip_model()->GetActiveWebContents()->Focus();

  // Create three more tabs plus the existing one that browser tests start with.
  const GURL about_blank(url::kAboutBlankURL);
  ASSERT_TRUE(AddTabAtIndex(0, about_blank, ui::PAGE_TRANSITION_TYPED));
  browser()->tab_strip_model()->GetActiveWebContents()->Focus();
  ASSERT_TRUE(AddTabAtIndex(0, about_blank, ui::PAGE_TRANSITION_TYPED));
  browser()->tab_strip_model()->GetActiveWebContents()->Focus();
  ASSERT_TRUE(AddTabAtIndex(0, about_blank, ui::PAGE_TRANSITION_TYPED));
  browser()->tab_strip_model()->GetActiveWebContents()->Focus();

  EnterImmersiveFullscreenMode(browser());

  // Wait for the end of the initial reveal which results from adding the new
  // tabs and changing the focused tab.
  tester.VerifyTabIndexAfterReveal(0);

  // Groups the browser command ID and its corresponding active tab index that
  // will result when the command is executed in this test.
  struct TestData {
    int command;
    int expected_index;
  };
  constexpr TestData test_data[] = {{IDC_SELECT_LAST_TAB, 3},
                                    {IDC_SELECT_TAB_0, 0},
                                    {IDC_SELECT_NEXT_TAB, 1},
                                    {IDC_SELECT_PREVIOUS_TAB, 0}};
  for (const auto& datum : test_data)
    tester.RunCommand(datum.command, datum.expected_index);
}

// This test does not make sense for the webUI tabstrip, since the window layout
// is different in that case.
IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTestNoWebUiTabStrip,
                       TestCaptionButtonsReceiveEventsInBrowserImmersiveMode) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());

  // Make sure that the focus is on the webcontents rather than on the omnibox,
  // because if the focus is on the omnibox, the tab strip will remain revealed
  // in the immersive fullscreen mode and will interfere with this test waiting
  // for the revealer to be dismissed.
  browser()->tab_strip_model()->GetActiveWebContents()->Focus();

  EnterImmersiveFullscreenMode(browser());
  EXPECT_FALSE(browser()->window()->IsMaximized());
  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsRevealed());

  std::unique_ptr<ImmersiveRevealedLock> revealed_lock =
      browser_view->immersive_mode_controller()->GetRevealedLock(
          ImmersiveModeController::ANIMATE_REVEAL_NO);
  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsRevealed());

  // Clicking the "restore" caption button should exit the immersive mode.
  aura::Window* window = browser()->window()->GetNativeWindow();
  ui::test::EventGenerator event_generator(window->GetRootWindow());
  gfx::Size button_size = views::GetCaptionButtonLayoutSize(
      views::CaptionButtonLayoutSize::kBrowserCaptionMaximized);
  gfx::Point point_in_restore_button(window->GetBoundsInScreen().top_right());
  point_in_restore_button.Offset(-button_size.width() * 3 / 2,
                                 button_size.height() / 2);

  event_generator.MoveMouseTo(point_in_restore_button);
  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsRevealed());
  event_generator.ClickLeftButton();
  ImmersiveModeTester(browser()).WaitForFullscreenToExit();
  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
  EXPECT_FALSE(browser()->window()->IsFullscreen());
}

IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                       TestCaptionButtonsReceiveEventsInAppImmersiveMode) {
  // Open a new app window.
  Browser* app_browser =
      CreateBrowserForApp("test_browser_app", browser()->profile());
  // TODO(neis): Move this into the CreateBrowser* functions.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  ui_test_utils::CreateAsyncWidgetRequestWaiter(*browser()).Wait();
#endif

  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser);
  chromeos::ImmersiveFullscreenControllerTestApi(
      static_cast<ImmersiveModeControllerChromeos*>(
          app_view->immersive_mode_controller())
          ->controller())
      .SetupForTest();

  EnterImmersiveFullscreenMode(app_browser);
  EXPECT_TRUE(app_browser->window()->IsFullscreen());
  EXPECT_FALSE(app_browser->window()->IsMaximized());
  EXPECT_FALSE(app_view->GetTabStripVisible());
  EXPECT_FALSE(app_view->immersive_mode_controller()->IsRevealed());

  std::unique_ptr<ImmersiveRevealedLock> revealed_lock =
      app_view->immersive_mode_controller()->GetRevealedLock(
          ImmersiveModeController::ANIMATE_REVEAL_NO);
  EXPECT_TRUE(app_view->immersive_mode_controller()->IsRevealed());

  AddBlankTabAndShow(app_browser);

  // Clicking the "restore" caption button should exit the immersive mode.
  aura::Window* app_window = app_browser->window()->GetNativeWindow();
  ui::test::EventGenerator event_generator(app_window->GetRootWindow(),
                                           app_window);
  gfx::Size button_size = views::GetCaptionButtonLayoutSize(
      views::CaptionButtonLayoutSize::kBrowserCaptionMaximized);
  gfx::Point point_in_restore_button(
      app_window->GetBoundsInRootWindow().top_right());
  point_in_restore_button.Offset(-2 * button_size.width(),
                                 button_size.height() / 2);

  event_generator.MoveMouseTo(point_in_restore_button);
  EXPECT_TRUE(app_view->immersive_mode_controller()->IsRevealed());
  event_generator.ClickLeftButton();
  ImmersiveModeTester(app_browser).WaitForFullscreenToExit();
  EXPECT_FALSE(app_view->immersive_mode_controller()->IsEnabled());
  EXPECT_FALSE(app_browser->window()->IsFullscreen());
}

// Regression test for crbug.com/796171.  Make sure that going from regular
// fullscreen to locked fullscreen does not cause a crash.
// Also test that the immersive mode is disabled afterwards (and the shelf is
// hidden, and the fullscreen control popup doesn't show up).
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/40948963): Reenable test when bug is fixed.
#define MAYBE_RegularToLockedFullscreenDisablesImmersive \
  DISABLED_RegularToLockedFullscreenDisablesImmersive
#else
#define MAYBE_RegularToLockedFullscreenDisablesImmersive \
  RegularToLockedFullscreenDisablesImmersive
#endif
IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                       MAYBE_RegularToLockedFullscreenDisablesImmersive) {
  if (!IsIsShelfVisibleSupported()) {
    GTEST_SKIP() << "Ash is too old.";
  }

  EnterImmersiveFullscreenMode(browser());

  // Set locked fullscreen state.
  PinWindow(browser()->window()->GetNativeWindow(), /*trusted=*/true);

  // We're fullscreen, immersive is disabled in locked fullscreen, and while
  // we're at it, also make sure that the shelf is hidden.
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
  EXPECT_FALSE(IsShelfVisible());

  // Make sure the fullscreen control popup doesn't show up.
  ui::MouseEvent mouse_move(ui::EventType::kMouseMoved, gfx::Point(1, 1),
                            gfx::Point(), base::TimeTicks(), 0, 0);
  ASSERT_TRUE(browser_view->fullscreen_control_host_for_test());
  browser_view->fullscreen_control_host_for_test()->OnMouseEvent(mouse_move);
  EXPECT_FALSE(browser_view->fullscreen_control_host_for_test()->IsVisible());
}

// Regression test for crbug.com/883104.  Make sure that immersive fullscreen is
// disabled in locked fullscreen mode (also the shelf is hidden, and the
// fullscreen control popup doesn't show up).
IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                       LockedFullscreenDisablesImmersive) {
  if (!IsIsShelfVisibleSupported()) {
    GTEST_SKIP() << "Ash is too old.";
  }

  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  EXPECT_FALSE(browser_view->GetWidget()->IsFullscreen());

  // Set locked fullscreen state.
  PinWindow(browser()->window()->GetNativeWindow(), /*trusted=*/true);

  // We're fullscreen, immersive is disabled in locked fullscreen, and while
  // we're at it, also make sure that the shelf is hidden.
  EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  // TODO(crbug.com/40276379): Enable this assertion once the bug is fixed (at
  // the moment PinWindow returns too early).
#else
  EXPECT_FALSE(IsShelfVisible());
#endif

  // Make sure the fullscreen control popup doesn't show up.
  ui::MouseEvent mouse_move(ui::EventType::kMouseMoved, gfx::Point(1, 1),
                            gfx::Point(), base::TimeTicks(), 0, 0);
  ASSERT_TRUE(browser_view->fullscreen_control_host_for_test());
  browser_view->fullscreen_control_host_for_test()->OnMouseEvent(mouse_move);
  EXPECT_FALSE(browser_view->fullscreen_control_host_for_test()->IsVisible());
}

// Test the shelf visibility affected by entering and exiting tab fullscreen and
// immersive fullscreen.
IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest, TabAndBrowserFullscreen) {
  if (!IsIsShelfVisibleSupported()) {
    GTEST_SKIP() << "Ash is too old.";
  }

  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());

  ASSERT_TRUE(
      AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  // The shelf should start out as visible.
  EXPECT_TRUE(IsShelfVisible());

  // 1) Test that entering tab fullscreen from immersive fullscreen hides the
  // shelf.
  EnterImmersiveFullscreenMode(browser());
  EXPECT_FALSE(IsShelfVisible());
  content::WebContents* web_contents = browser_view->GetActiveWebContents();
  EnterTabFullscreenMode(browser(), web_contents);
  ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
  EXPECT_FALSE(IsShelfVisible());

  // 2) Test that exiting tab fullscreen autohides the shelf.
  ExitTabFullscreenMode(browser(), web_contents);
  ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
  EXPECT_FALSE(IsShelfVisible());

  // 3) Test that exiting tab fullscreen and immersive fullscreen correctly
  // updates the shelf visibility.
  EnterTabFullscreenMode(browser(), web_contents);
  ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
  ui_test_utils::ToggleFullscreenModeAndWait(browser());
  ASSERT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
  EXPECT_TRUE(IsShelfVisible());
}

#define INSTANTIATE_TEST_SUITE(name) \
  INSTANTIATE_TEST_SUITE_P(All, name, ::testing::Values(false, true))

INSTANTIATE_TEST_SUITE(ImmersiveModeBrowserViewTest);
INSTANTIATE_TEST_SUITE(ImmersiveModeBrowserViewTestNoWebUiTabStrip);