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

// 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/immersive_mode_controller_chromeos.h"

#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/chromeos/test_util.h"
#include "chrome/browser/ui/chromeos/window_pin_util.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/ui_features.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/immersive_mode_tester.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/frame/webui_tab_strip_container_view.h"
#include "chrome/browser/ui/views/fullscreen_control/fullscreen_control_host.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "ui/aura/window.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/events/event.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/controls/webview/webview.h"

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/ui/lacros/immersive_context_lacros.h"
#endif

class ImmersiveModeControllerChromeosTest : public TestWithBrowserView {
 public:
  ImmersiveModeControllerChromeosTest()
      : TestWithBrowserView(Browser::TYPE_NORMAL) {}

  ImmersiveModeControllerChromeosTest(
      const ImmersiveModeControllerChromeosTest&) = delete;
  ImmersiveModeControllerChromeosTest& operator=(
      const ImmersiveModeControllerChromeosTest&) = delete;

  ~ImmersiveModeControllerChromeosTest() override {}

  // TestWithBrowserView override:
  void SetUp() override {
    TestWithBrowserView::SetUp();

    browser()->window()->Show();

    controller_ = browser_view()->immersive_mode_controller();
    chromeos::ImmersiveFullscreenControllerTestApi(
        static_cast<ImmersiveModeControllerChromeos*>(controller_)
            ->controller())
        .SetupForTest();
  }

  // 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);
    }
  }

  // Attempt unrevealing the top-of-window views.
  void AttemptUnreveal() { revealed_lock_.reset(); }

  ImmersiveModeController* controller() { return controller_; }

 private:
  // Not owned.
  raw_ptr<ImmersiveModeController, DanglingUntriaged> controller_;

  std::unique_ptr<ImmersiveRevealedLock> revealed_lock_;

#if BUILDFLAG(IS_CHROMEOS_LACROS)
  ImmersiveContextLacros immersive_context_;
#endif
};

// Test the layout and visibility of the tabstrip, toolbar and TopContainerView
// in immersive fullscreen.
TEST_F(ImmersiveModeControllerChromeosTest, Layout) {
  AddTab(browser(), GURL("about:blank"));

  TabStrip* tabstrip = browser_view()->tabstrip();
  ToolbarView* toolbar = browser_view()->toolbar();
  views::WebView* contents_web_view = browser_view()->contents_web_view();

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

  // By default, the tabstrip and toolbar should be visible.
  EXPECT_TRUE(tabstrip->GetVisible());
  EXPECT_TRUE(toolbar->GetVisible());
  EXPECT_EQ(
      0, browser_view()->contents_web_view()->holder()->GetHitTestTopInset());

  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
  EXPECT_TRUE(browser_view()->GetWidget()->IsFullscreen());
  EXPECT_TRUE(controller()->IsEnabled());
  EXPECT_FALSE(controller()->IsRevealed());
  EXPECT_FALSE(toolbar->GetVisible());
  // The browser's top chrome is completely offscreen with tapstrip visible.
  EXPECT_TRUE(tabstrip->GetVisible());
  // Tabstrip and top container view should be completely offscreen.
  EXPECT_EQ(0, GetBoundsInWidget(tabstrip).bottom());
  EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).bottom());
  EXPECT_EQ(
      0, browser_view()->contents_web_view()->holder()->GetHitTestTopInset());

  // Since the tab strip and tool bar are both hidden in immersive fullscreen
  // mode, the web contents should extend to the edge of screen.
  EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());

  // Revealing the top-of-window views should set the tab strip back to the
  // normal style and show the toolbar.
  AttemptReveal();
  EXPECT_TRUE(controller()->IsRevealed());
  EXPECT_TRUE(tabstrip->GetVisible());
  EXPECT_TRUE(toolbar->GetVisible());
  EXPECT_NE(
      0, browser_view()->contents_web_view()->holder()->GetHitTestTopInset());

  // The TopContainerView should be flush with the top edge of the widget. If
  // it is not flush with the top edge the immersive reveal animation looks
  // wonky.
  EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).y());

  // The web contents should be at the same y position as they were when the
  // top-of-window views were hidden.
  EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());

  // Repeat the test for when in both immersive fullscreen and tab fullscreen.
  ChromeOSBrowserUITest::EnterTabFullscreenMode(
      browser(), browser_view()->contents_web_view()->GetWebContents());
  // Hide and reveal the top-of-window views so that they get relain out.
  AttemptUnreveal();
  AttemptReveal();

  // The tab strip and toolbar should still be visible and the TopContainerView
  // should still be flush with the top edge of the widget.
  EXPECT_TRUE(controller()->IsRevealed());
  EXPECT_TRUE(tabstrip->GetVisible());
  EXPECT_TRUE(toolbar->GetVisible());
  EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).y());

  // The web contents should be flush with the top edge of the widget when in
  // both immersive and tab fullscreen.
  EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());

  // Hide the top-of-window views. Tabstrip is still considered as visible.
  AttemptUnreveal();
  EXPECT_FALSE(controller()->IsRevealed());
  EXPECT_FALSE(toolbar->GetVisible());
  EXPECT_TRUE(tabstrip->GetVisible());

  // The web contents should still be flush with the edge of the widget.
  EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());

  // Exiting both immersive and tab fullscreen should show the tab strip and
  // toolbar.
  ChromeOSBrowserUITest::ExitImmersiveFullscreenMode(browser());
  EXPECT_EQ(
      0, browser_view()->contents_web_view()->holder()->GetHitTestTopInset());
  EXPECT_FALSE(browser_view()->GetWidget()->IsFullscreen());
  EXPECT_FALSE(controller()->IsEnabled());
  EXPECT_FALSE(controller()->IsRevealed());
  EXPECT_TRUE(tabstrip->GetVisible());
  EXPECT_TRUE(toolbar->GetVisible());
}

// Verifies that transitioning from fullscreen to trusted pinned disables the
// immersive controls.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(b/40276379): Currently, fullscreen flow on Lacros is not properly
// implemented in some edge cases, and this is hitting one of the cases.
// Re-enable the test once the flow is fixed.
#define MAYBE_FullscreenToLockedTransition DISABLED_FullscreenToLockedTransition
#else
#define MAYBE_FullscreenToLockedTransition FullscreenToLockedTransition
#endif
TEST_F(ImmersiveModeControllerChromeosTest,
       MAYBE_FullscreenToLockedTransition) {
  AddTab(browser(), GURL("about:blank"));
  // Start in fullscreen.
  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
  // ImmersiveController is enabled in fullscreen.
  EXPECT_TRUE(controller()->IsEnabled());

  // Transition to locked fullscreen.
  ChromeOSBrowserUITest::PinWindow(
      browser_view()->GetWidget()->GetNativeWindow(), /*trusted=*/true);
  // ImmersiveController is disabled in TrustedPinned so that it cannot be
  // exited.
  EXPECT_FALSE(controller()->IsEnabled());
}

// Test that the browser commands which are usually disabled in fullscreen are
// are enabled in immersive fullscreen.
TEST_F(ImmersiveModeControllerChromeosTest, EnabledCommands) {
  ASSERT_FALSE(controller()->IsEnabled());
  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL));
  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT));
  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FOCUS_LOCATION));

  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
  EXPECT_TRUE(controller()->IsEnabled());
  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL));
  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT));
  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FOCUS_LOCATION));
}

// Test that restoring a window properly exits immersive fullscreen.
TEST_F(ImmersiveModeControllerChromeosTest, ExitUponRestore) {
  ASSERT_FALSE(controller()->IsEnabled());
  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
  AttemptReveal();
  ASSERT_TRUE(controller()->IsEnabled());
  ASSERT_TRUE(controller()->IsRevealed());
  ASSERT_TRUE(browser_view()->GetWidget()->IsFullscreen());

  browser_view()->GetWidget()->Restore();
  ImmersiveModeTester(browser()).WaitForFullscreenToExit();
}

// Ensure the circular tab-loading throbbers are not painted as layers in
// immersive fullscreen, since the tab strip may animate in or out without
// moving the layers.
TEST_F(ImmersiveModeControllerChromeosTest, LayeredSpinners) {
  AddTab(browser(), GURL("about:blank"));

  TabStrip* tabstrip = browser_view()->tabstrip();

  // Immersive fullscreen starts out disabled; layers are OK.
  EXPECT_FALSE(browser_view()->GetWidget()->IsFullscreen());
  EXPECT_FALSE(controller()->IsEnabled());
  EXPECT_TRUE(tabstrip->CanPaintThrobberToLayer());

  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
  EXPECT_TRUE(browser_view()->GetWidget()->IsFullscreen());
  EXPECT_TRUE(controller()->IsEnabled());
  EXPECT_FALSE(tabstrip->CanPaintThrobberToLayer());

  ChromeOSBrowserUITest::ExitImmersiveFullscreenMode(browser());
  EXPECT_TRUE(tabstrip->CanPaintThrobberToLayer());
}

// Ensure SetEnable is called when needed even when the previous request is
// passed from different client.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(b/40942067): Port and enable when bug is fixed.
#define MAYBE_CallEnableForWidgetWhenNeeded \
  DISABLED_CallEnableForWidgetWhenNeeded
#else
#define MAYBE_CallEnableForWidgetWhenNeeded CallEnableForWidgetWhenNeeded
#endif
TEST_F(ImmersiveModeControllerChromeosTest,
       MAYBE_CallEnableForWidgetWhenNeeded) {
  ASSERT_FALSE(controller()->IsEnabled());
  chromeos::ImmersiveFullscreenController::EnableForWidget(
      browser_view()->frame(), /*enabled=*/true);
  ASSERT_TRUE(controller()->IsEnabled());
  controller()->SetEnabled(/*enabled=*/false);
  ASSERT_FALSE(controller()->IsEnabled());
}

class ImmersiveModeControllerChromeosWebUITabStripTest
    : public ImmersiveModeControllerChromeosTest {
 public:
  ImmersiveModeControllerChromeosWebUITabStripTest() {
    scoped_feature_list_.InitAndEnableFeature(features::kWebUITabStrip);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Ensures the WebUI tab strip can be opened during immersive reveal.
// Regression test for crbug.com/1096569 where it couldn't be opened.
TEST_F(ImmersiveModeControllerChromeosWebUITabStripTest, CanOpen) {
  AddTab(browser(), GURL("about:blank"));

  // The WebUI tab strip is only used in touch mode.
  ui::TouchUiController::TouchUiScoperForTesting touch_mode_override(true);

  WebUITabStripContainerView* const webui_tab_strip =
      browser_view()->webui_tab_strip();
  ASSERT_TRUE(webui_tab_strip);
  EXPECT_FALSE(webui_tab_strip->GetVisible());

  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
  EXPECT_FALSE(webui_tab_strip->GetVisible());

  AttemptReveal();
  EXPECT_FALSE(webui_tab_strip->GetVisible());

  webui_tab_strip->SetVisibleForTesting(true);

  // The WebUITabStrip should be layed out.
  browser_view()->GetWidget()->LayoutRootViewIfNecessary();
  EXPECT_TRUE(webui_tab_strip->GetVisible());
  EXPECT_FALSE(webui_tab_strip->size().IsEmpty());
}