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

#include "build/build_config.h"
#include "chrome/browser/printing/print_preview_dialog_controller.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/display/screen_base.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/shell.h"
#include "ui/display/test/display_manager_test_api.h"  // nogncheck
#endif                                                 // BUILDFLAG(IS_CHROMEOS)

class WindowManagementTest : public InProcessBrowserTest {
 public:
  void SetUp() override {
#if !BUILDFLAG(IS_CHROMEOS)
    display::Screen::SetScreenInstance(&screen_);
    screen_.display_list().AddDisplay({1, gfx::Rect(0, 0, 803, 600)},
                                      display::DisplayList::Type::PRIMARY);
#endif
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // Window management features are only available on secure contexts.
    https_test_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_test_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
    ASSERT_TRUE(https_test_server_->Start());
  }

  void TearDown() override {
    InProcessBrowserTest::TearDown();
#if !BUILDFLAG(IS_CHROMEOS)
    display::Screen::SetScreenInstance(nullptr);
#endif
  }

 protected:
  std::unique_ptr<net::EmbeddedTestServer> https_test_server_;
#if !BUILDFLAG(IS_CHROMEOS)
  display::ScreenBase screen_;
#endif
};

// TODO(crbug.com/40115071): Windows crashes static casting to ScreenWin.
// TODO(crbug.com/433855037): Disabling on Mac due to flakiness.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#define MAYBE_NoCrashOnEventsDuringHandlerPrint \
  DISABLED_NoCrashOnEventsDuringHandlerPrint
#else
#define MAYBE_NoCrashOnEventsDuringHandlerPrint \
  NoCrashOnEventsDuringHandlerPrint
#endif
// Test that screen change events occurring while an event handler is running
// a nested event loop (i.e. via window.print()) do not cause a crash.
// Regression test for crbug.com/40058028
IN_PROC_BROWSER_TEST_F(WindowManagementTest,
                       MAYBE_NoCrashOnEventsDuringHandlerPrint) {
  // Update the display configuration to mock display changes.
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("0+0-803x600");
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(1, display::Screen::Get()->GetNumDisplays());

  // Navigate in a new tab to observe the test screen instance as needed.
  const GURL url(https_test_server_->GetURL("/simple.html"));
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
          ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);

  // Auto-accept the Window Placement permission request.
  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
  permissions::PermissionRequestManager* permission_request_manager_tab =
      permissions::PermissionRequestManager::FromWebContents(tab);
  permission_request_manager_tab->set_auto_response_for_test(
      permissions::PermissionRequestManager::ACCEPT_ALL);

  // Add a currentscreenchange event handler that will freeze JS via print().
  auto* script = R"(
      function freezeJS(e) {
        self.print();
        screenDetails.removeEventListener('currentscreenchange', freezeJS);
      }

      let screenDetails;
      self.getScreenDetails().then(s => {
        screenDetails = s;
        screenDetails.addEventListener('currentscreenchange', freezeJS);
      });
  )";
  content::WebContentsAddedObserver web_contents_added_observer;
  ASSERT_TRUE(EvalJs(tab, script).is_ok());

  // Alter the display to trigger the currentscreenchange event and print().
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("0+0-807x600");
#else
  screen_.display_list().UpdateDisplay({1, gfx::Rect(0, 0, 807, 600)},
                                       display::DisplayList::Type::PRIMARY);
  EXPECT_EQ(screen_.display_list().displays().size(), 1u);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(1, display::Screen::Get()->GetNumDisplays());

  content::WebContents* print_preview =
      web_contents_added_observer.GetWebContents();
  EXPECT_EQ(print_preview, printing::PrintPreviewDialogController::GetInstance()
                               ->GetPrintPreviewForContents(tab));
  content::AwaitDocumentOnLoadCompleted(print_preview);

  // Add a second display while the print preview dialog is showing.
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("0+0-807x600,1000+0-804x600");
#else
  screen_.display_list().AddDisplay({2, gfx::Rect(1000, 0, 804, 600)},
                                    display::DisplayList::Type::NOT_PRIMARY);
  EXPECT_EQ(screen_.display_list().displays().size(), 2u);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());

  // Cancel print to unfreeze the tab's JS and check that no crash occurred.
  content::WebContentsDestroyedWatcher destroyed_watcher(print_preview);
  // Note: This could be a browser_test if cancelling the print preview dialog
  // was more broadly possible without simulating a key press.
  // PrintViewManager::PrintPreviewDone() worked on Linux, but not Chrome OS.
  EXPECT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_ESCAPE, false,
                                              false, false, false));
  destroyed_watcher.Wait();
  ASSERT_FALSE(tab->IsCrashed());
  EXPECT_EQ(true, EvalJs(tab, "true"));
}
