// 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 "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.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/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/features.h"
#include "ui/display/screen_base.h"

#if BUILDFLAG(IS_MAC)
#include "content/browser/renderer_host/test_render_widget_host_view_mac_factory.h"
#endif

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

// Fake screen that allows tests to dynamically override the "current" display
// for a window or view even if the underlying OS does not signal a screen
// change.
class FakeScreen : public display::ScreenBase {
 public:
  display::Display GetDisplayNearestWindow(
      gfx::NativeWindow window) const override {
    return GetCurrentDisplayOverride().value_or(
        display::ScreenBase::GetDisplayNearestWindow(window));
  }
  display::Display GetDisplayNearestView(gfx::NativeView view) const override {
    return GetCurrentDisplayOverride().value_or(
        display::ScreenBase::GetDisplayNearestView(view));
  }

  void SetCurrentDisplayOverride(uint32_t display_id) {
    current_display_override_ = display_id;
  }

 private:
  int64_t current_display_override_ = display::kInvalidDisplayId;
  // Return the overridden display object if set and exists, nullopt otherwise.
  std::optional<display::Display> GetCurrentDisplayOverride() const {
    if (display::Display display;
        current_display_override_ != display::kInvalidDisplayId &&
        GetDisplayWithDisplayId(current_display_override_, &display)) {
      return display;
    }
    return std::nullopt;
  }
};

// TODO(crbug.com/40111905): Windows downcasts Screen to ScreenWin and fails.
#if BUILDFLAG(IS_WIN)
#define MAYBE_WindowManagementTest DISABLED_WindowManagementTest
#else
#define MAYBE_WindowManagementTest WindowManagementTest
#endif

class MAYBE_WindowManagementTest : public InProcessBrowserTest {
 public:
  void SetUp() override {
    feature_list_.InitWithFeatures(
        {blink::features::kScreenDetailedHdrHeadroom}, {});

#if !BUILDFLAG(IS_CHROMEOS)
    screen_.display_list().AddDisplay({1, gfx::Rect(100, 1, 801, 802)},
                                      display::DisplayList::Type::PRIMARY);
    display::Screen::SetScreenInstance(&screen_);
#endif  // !BUILDFLAG(IS_CHROMEOS)
    InProcessBrowserTest::SetUp();
  }

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

  void SetUpOnMainThread() override {
    // Support multiple sites on the test server.
    host_resolver()->AddRule("*", "127.0.0.1");

    // Window management features are only available on secure contexts, and so
    // we need to create an HTTPS test server here to serve those pages rather
    // than using the default https_test_server_.
    https_test_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    // Support sites like a.test, b.test, c.test etc
    https_test_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_test_server_->ServeFilesFromSourceDirectory("chrome/test/data");
    net::test_server::RegisterDefaultHandlers(https_test_server_.get());
    content::SetupCrossSiteRedirector(https_test_server_.get());
    ASSERT_TRUE(https_test_server_->Start());
#if BUILDFLAG(IS_CHROMEOS)
    display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
        .UpdateDisplay("100+1-801x802");
#endif
  }

  void SetupTwoIframes() {
    const GURL url(
        https_test_server_->GetURL("a.test", "/two_iframes_blank.html"));
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    auto* tab = browser()->tab_strip_model()->GetActiveWebContents();

    EXPECT_TRUE(ExecJs(tab,
                       R"(
      const frame1 = document.getElementById('iframe1');
      frame1.setAttribute('allow', 'window-management');
      const frame2 = document.getElementById('iframe2');
      frame2.setAttribute('allow', 'window-management');)",
                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));

    GURL subframe_url1(https_test_server_->GetURL("a.test", "/title1.html"));
    GURL subframe_url2(https_test_server_->GetURL("b.test", "/title1.html"));
    content::NavigateIframeToURL(tab, /*iframe_id=*/"iframe1", subframe_url1);
    content::NavigateIframeToURL(tab, /*iframe_id=*/"iframe2", subframe_url2);

    // TODO(crbug.com/40145721): this test could be in content_browsertests
    // and not browser_tests if permission controls were supported.

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

 protected:
  std::unique_ptr<net::EmbeddedTestServer> https_test_server_;
  base::test::ScopedFeatureList feature_list_;
#if !BUILDFLAG(IS_CHROMEOS)
  FakeScreen screen_;
#endif
};

IN_PROC_BROWSER_TEST_F(MAYBE_WindowManagementTest, OnScreensChangeEvent) {
  ASSERT_EQ(1, display::Screen::Get()->GetNumDisplays());
  SetupTwoIframes();
  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::RenderFrameHost* local_child =
      ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
  content::RenderFrameHost* remote_child =
      ChildFrameAt(tab->GetPrimaryMainFrame(), 1);

  auto initial_result = content::ListValueOf(801);
  auto* initial_script = R"(
      var screenDetails;
      var promiseForEvent = (target, evt) => {
        return new Promise((resolve) => {
          const handler = (e) => {
            target.removeEventListener(evt, handler);
            resolve(e);
          };
          target.addEventListener(evt, handler);
        });
      }
      var makeScreensChangePromise = () => {
        if (!screenDetails) return undefined;
        return promiseForEvent(screenDetails, 'screenschange');
      };
      var getScreenWidths = () => {
        return screenDetails.screens.map((d) => d.width).sort();
      };

      (async () => {
          screenDetails = await self.getScreenDetails();
          return getScreenWidths();
      })();
  )";

  ASSERT_EQ(initial_result, EvalJs(tab, initial_script));
  ASSERT_EQ(initial_result, EvalJs(local_child, initial_script));
  ASSERT_EQ(initial_result, EvalJs(remote_child, initial_script));

  // Add a second display.
  auto* add_screens_change_promise =
      R"(var screensChange = makeScreensChangePromise();)";
  EXPECT_TRUE(ExecJs(tab, add_screens_change_promise));
  EXPECT_TRUE(ExecJs(local_child, add_screens_change_promise));
  EXPECT_TRUE(ExecJs(remote_child, add_screens_change_promise));
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-801x802,901+100-803x804");
#else
  screen_.display_list().AddDisplay({2, gfx::Rect(901, 100, 803, 804)},
                                    display::DisplayList::Type::PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());

  auto* await_screens_change = R"(
      (async () => {
          if (!screensChange) return -1;
          await screensChange;
          return getScreenWidths();
      })();
  )";

  {
    auto result = content::ListValueOf(801, 803);

    EXPECT_EQ(result, EvalJs(tab, await_screens_change));
    EXPECT_EQ(result, EvalJs(local_child, await_screens_change));
    EXPECT_EQ(result, EvalJs(remote_child, await_screens_change));
  }

  // Remove the first display.
  EXPECT_TRUE(ExecJs(tab, add_screens_change_promise));
  EXPECT_TRUE(ExecJs(local_child, add_screens_change_promise));
  EXPECT_TRUE(ExecJs(remote_child, add_screens_change_promise));
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("901+100-803x804");
#else
  // Make the second display primary so we can remove the first.
  EXPECT_EQ(screen_.display_list().displays().size(), 2u);
  screen_.display_list().RemoveDisplay(1);
  EXPECT_EQ(screen_.display_list().displays().size(), 1u);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(1, display::Screen::Get()->GetNumDisplays());

  {
    auto result = content::ListValueOf(803);

    EXPECT_EQ(result, EvalJs(tab, await_screens_change));
    EXPECT_EQ(result, EvalJs(local_child, await_screens_change));
    EXPECT_EQ(result, EvalJs(remote_child, await_screens_change));
  }

  // Remove one display, add two displays.
  // TODO(enne): we add two displays here because DisplayManagerTestApi
  // would consider remove+add to just be an update (with the same id).
  // An alternative is to modify DisplayManagerTestApi to let us set ids.
  EXPECT_TRUE(ExecJs(tab, add_screens_change_promise));
  EXPECT_TRUE(ExecJs(local_child, add_screens_change_promise));
  EXPECT_TRUE(ExecJs(remote_child, add_screens_change_promise));
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("0+0-803x600,1000+0-804x600");
#else
  screen_.display_list().RemoveDisplay(2);
  screen_.display_list().AddDisplay({3, gfx::Rect(0, 4, 803, 600)},
                                    display::DisplayList::Type::PRIMARY);
  screen_.display_list().AddDisplay({4, gfx::Rect(0, 4, 804, 600)},
                                    display::DisplayList::Type::NOT_PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());

  {
    auto result = content::ListValueOf(803, 804);

    EXPECT_EQ(result, EvalJs(tab, await_screens_change));
    EXPECT_EQ(result, EvalJs(local_child, await_screens_change));
    EXPECT_EQ(result, EvalJs(remote_child, await_screens_change));
  }
}

// Test that the oncurrentscreenchange handler fires correctly for screen
// changes and property updates.  It also verifies that window.screen.onchange
// also fires in the same scenarios.  (This is not true in all cases, e.g.
// isInternal changing, but is true for width/height tests here.)
IN_PROC_BROWSER_TEST_F(MAYBE_WindowManagementTest, OnCurrentScreenChangeEvent) {
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-801x802,901+100-803x804");
#else
  screen_.display_list().UpdateDisplay({1, gfx::Rect(100, 100, 801, 802)},
                                       display::DisplayList::Type::PRIMARY);
  screen_.display_list().AddDisplay({2, gfx::Rect(901, 100, 803, 802)},
                                    display::DisplayList::Type::NOT_PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());

  SetupTwoIframes();
  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::RenderFrameHost* local_child =
      ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
  content::RenderFrameHost* remote_child =
      ChildFrameAt(tab->GetPrimaryMainFrame(), 1);

  auto* initial_script = R"(
      var screenDetails;
      var promiseForEvent = (target, evt) => {
        return new Promise((resolve) => {
          const handler = (e) => {
            target.removeEventListener(evt, handler);
            resolve(e);
          };
          target.addEventListener(evt, handler);
        });
      }
      var makeCurrentScreenChangePromise = () => {
        if (!screenDetails) return undefined;
        return promiseForEvent(screenDetails, 'currentscreenchange');
      };
      var makeWindowScreenChangePromise = () => {
        return promiseForEvent(window.screen, 'change');
      };
      (async () => {
          screenDetails = await self.getScreenDetails();
          if (screenDetails.currentScreen.width != window.screen.width)
            return -1;
          return screenDetails.currentScreen.width;
      })();
  )";
  EXPECT_EQ(801, EvalJs(tab, initial_script));
  EXPECT_EQ(801, EvalJs(local_child, initial_script));
  EXPECT_EQ(801, EvalJs(remote_child, initial_script));

  // Switch to a second display.  This should fire an event.
  auto* add_current_screen_change_promise = R"(
      var currentScreenChange = makeCurrentScreenChangePromise();
      var windowScreenChange = makeWindowScreenChangePromise();
  )";
  EXPECT_TRUE(ExecJs(tab, add_current_screen_change_promise));
  EXPECT_TRUE(ExecJs(local_child, add_current_screen_change_promise));
  EXPECT_TRUE(ExecJs(remote_child, add_current_screen_change_promise));

#if !BUILDFLAG(IS_CHROMEOS)
  screen_.SetCurrentDisplayOverride(2);
#if BUILDFLAG(IS_MAC)
  // On Mac, ScreenInfo will not update if the window does change displays
  // according to the OS.
  content::SetScreenInfosForTesting(
      browser()
          ->tab_strip_model()
          ->GetActiveWebContents()
          ->GetRenderWidgetHostView(),
      display::Screen::Get()->GetScreenInfosNearestDisplay(2));

#endif
#endif
  const gfx::Rect new_bounds(1000, 150, 600, 500);
  browser()->GetWindow()->SetBounds(new_bounds);

  auto* await_change_width = R"(
      (async () => {
          if (!currentScreenChange || !windowScreenChange)
              return -2;
          await currentScreenChange;
          await windowScreenChange;
          if (screenDetails.currentScreen.width != window.screen.width)
            return -1;
          return screenDetails.currentScreen.width;
      })();
  )";
  EXPECT_EQ(803, EvalJs(tab, await_change_width));
  EXPECT_EQ(803, EvalJs(local_child, await_change_width));
#if !BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/40246863): Fix flaky timeouts on ChromeOS.
  EXPECT_EQ(803, EvalJs(remote_child, await_change_width));
#endif

  // Update the second display to have a height of 300.  Validate that a change
  // event is fired when attributes of the current screen change.
  EXPECT_TRUE(ExecJs(tab, add_current_screen_change_promise));
  EXPECT_TRUE(ExecJs(local_child, add_current_screen_change_promise));
  EXPECT_TRUE(ExecJs(remote_child, add_current_screen_change_promise));

#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-801x802,901+100-802x300");
#else
  screen_.display_list().UpdateDisplay({2, gfx::Rect(901, 100, 802, 300)},
                                       display::DisplayList::Type::NOT_PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)

  auto* await_change_height = R"(
      (async () => {
          if (!currentScreenChange || !windowScreenChange)
              return -2;
          await currentScreenChange;
          await windowScreenChange;
          if (screenDetails.currentScreen.height != window.screen.height)
            return -1;
          return screenDetails.currentScreen.height;
      })();
  )";
  EXPECT_EQ(300, EvalJs(tab, await_change_height));
  EXPECT_EQ(300, EvalJs(local_child, await_change_height));
#if !BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/40246863): Fix flaky timeouts on ChromeOS.
  EXPECT_EQ(300, EvalJs(remote_child, await_change_height));
#endif
}

// Test that onchange events for individual screens in the screen list are
// supported.
IN_PROC_BROWSER_TEST_F(MAYBE_WindowManagementTest, ScreenDetailedOnChange) {
#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-801x802,901+100-802x803");
#else
  screen_.display_list().UpdateDisplay({1, gfx::Rect(100, 100, 801, 802)},
                                       display::DisplayList::Type::PRIMARY);
  screen_.display_list().AddDisplay({2, gfx::Rect(901, 100, 802, 803)},
                                    display::DisplayList::Type::NOT_PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)
  ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());

  SetupTwoIframes();
  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::RenderFrameHost* local_child =
      ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
  content::RenderFrameHost* remote_child =
      ChildFrameAt(tab->GetPrimaryMainFrame(), 1);

  auto* initial_script = R"(
      var screenDetails;
      var promiseForEvent = (target, evt) => {
        return new Promise((resolve) => {
          const handler = (e) => {
            target.removeEventListener(evt, handler);
            resolve(e);
          };
          target.addEventListener(evt, handler);
        });
      }
      var screenChanges0 = 0;
      var screenChanges1 = 0;
      (async () => {
        screenDetails = await self.getScreenDetails();
        if (screenDetails.screens.length !== 2)
          return false;
        // Add some event listeners for individual screens.
        screenDetails.screens[0].addEventListener('change', () => {
          screenChanges0++;
        });
        screenDetails.screens[1].addEventListener('change', () => {
          screenChanges1++;
        });
        return true;
      })();
  )";
  EXPECT_EQ(true, EvalJs(tab, initial_script));
  EXPECT_EQ(true, EvalJs(local_child, initial_script));
  EXPECT_EQ(true, EvalJs(remote_child, initial_script));

  // Update only the first display to have a different height.
  auto* add_change0 = R"(
    var change0 = promiseForEvent(screenDetails.screens[0], 'change');
  )";
  EXPECT_TRUE(ExecJs(tab, add_change0));
  EXPECT_TRUE(ExecJs(local_child, add_change0));
  EXPECT_TRUE(ExecJs(remote_child, add_change0));

#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-801x301,901+100-802x803");
#else
  screen_.display_list().UpdateDisplay({1, gfx::Rect(100, 100, 801, 301)},
                                       display::DisplayList::Type::PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)

  auto* await_change0_height = R"(
      (async () => {
          if (!change0) return -3;
          await change0;
          // Only screen[0] should have changed.
          if (screenChanges0 !== 1)
            return -1;
          if (screenChanges1 !== 0)
            return -2;
          return screenDetails.screens[0].height;
      })();
  )";
  EXPECT_EQ(301, EvalJs(tab, await_change0_height));
  EXPECT_EQ(301, EvalJs(local_child, await_change0_height));
  EXPECT_EQ(301, EvalJs(remote_child, await_change0_height));

  // Update only the second display to have a different height.
  auto* add_change1 = R"(
    var change1 = promiseForEvent(screenDetails.screens[1], 'change');
  )";
  EXPECT_TRUE(ExecJs(tab, add_change1));
  EXPECT_TRUE(ExecJs(local_child, add_change1));
  EXPECT_TRUE(ExecJs(remote_child, add_change1));

#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-801x301,901+100-802x302");
#else
  screen_.display_list().UpdateDisplay({2, gfx::Rect(901, 100, 802, 302)},
                                       display::DisplayList::Type::NOT_PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)

  auto* await_change1_height = R"(
      (async () => {
          if (!change1)
            return -3;
          await change1;
          // Both screens have one change.
          if (screenChanges0 !== 1)
            return -1;
          if (screenChanges1 !== 1)
            return -2;
          return screenDetails.screens[1].height;
      })();
  )";
  EXPECT_EQ(302, EvalJs(tab, await_change1_height));
  EXPECT_EQ(302, EvalJs(local_child, await_change1_height));
  EXPECT_EQ(302, EvalJs(remote_child, await_change1_height));

  // Change the width of both displays at the same time.
  auto* add_both_changes = R"(
    var change0 = promiseForEvent(screenDetails.screens[0], 'change');
    var change1 = promiseForEvent(screenDetails.screens[1], 'change');
  )";
  EXPECT_TRUE(ExecJs(tab, add_both_changes));
  EXPECT_TRUE(ExecJs(local_child, add_both_changes));
  EXPECT_TRUE(ExecJs(remote_child, add_both_changes));

#if BUILDFLAG(IS_CHROMEOS)
  display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
      .UpdateDisplay("100+100-401x301,901+100-402x302");
#else
  screen_.display_list().UpdateDisplay({1, gfx::Rect(100, 100, 401, 301)},
                                       display::DisplayList::Type::PRIMARY);
  screen_.display_list().UpdateDisplay({2, gfx::Rect(901, 100, 402, 302)},
                                       display::DisplayList::Type::NOT_PRIMARY);
#endif  // BUILDFLAG(IS_CHROMEOS)

  auto* await_both_changes_width = R"(
      (async () => {
          if (!change0 || !change1)
            return -3;
          await change0;
          await change1;
          // Both screens have two changes
          if (screenChanges0 !== 2)
            return false;
          if (screenChanges1 !== 2)
            return false;
          if (screenDetails.screens[0].width !== 401)
            return false;
          if (screenDetails.screens[1].width !== 402)
            return false;
          return true;
      })();
  )";
  EXPECT_EQ(true, EvalJs(tab, await_both_changes_width));
  EXPECT_EQ(true, EvalJs(local_child, await_both_changes_width));
  EXPECT_EQ(true, EvalJs(remote_child, await_both_changes_width));
}

// Test that onchange events for individual screens in the screen list are
// supported.
#if !BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(MAYBE_WindowManagementTest,
                       ScreenDetailedOnHdrHeadroomChange) {
  display::Display display(1, gfx::Rect(100, 100, 801, 802));
  auto display_cs = display.GetColorSpaces();
  // Transitions between HDR headroom 0 and HDR headroom >0 will result in the
  // bit depth from 8 to 10. Start at 10 bit, to avoid extra change events.
  display.set_color_depth(30);
  display.set_depth_per_component(10);
  screen_.display_list().UpdateDisplay(display,
                                       display::DisplayList::Type::PRIMARY);
  ASSERT_EQ(1, display::Screen::Get()->GetNumDisplays());

  SetupTwoIframes();
  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();

  EXPECT_EQ(true, EvalJs(tab, R"(
      var screenDetails;
      var promiseForEvent = (target, evt) => {
        return new Promise((resolve) => {
          const handler = (e) => {
            target.removeEventListener(evt, handler);
            resolve(e);
          };
          target.addEventListener(evt, handler);
        });
      }
      var screenChanges = 0;
      var screenHdrHeadroomChanges = 0;
      (async () => {
        screenDetails = await self.getScreenDetails();
        if (screenDetails.screens.length !== 1)
          return false;
        // Add some event listeners for individual screens.
        screenDetails.screens[0].addEventListener('change', () => {
          screenChanges++;
        });
        screenDetails.screens[0].addEventListener('hdrheadroomchange', () => {
          screenHdrHeadroomChanges++;
        });
        return true;
      })();
  )"));

  // Update the display to have a different height. Ensure that the HDR headroom
  // event does not fire.
  {
    EXPECT_TRUE(ExecJs(tab, R"(
      var change0 = promiseForEvent(screenDetails.screens[0], 'change');
    )"));
    display.set_bounds(gfx::Rect(100, 100, 801, 301));
    screen_.display_list().UpdateDisplay(display,
                                         display::DisplayList::Type::PRIMARY);
    EXPECT_EQ(301, EvalJs(tab, R"(
        (async () => {
            if (!change0) return -1;
            await change0;
            if (screenChanges !== 1)
              return -2;
            if (screenHdrHeadroomChanges !== 0)
              return -3;
            return screenDetails.screens[0].height;
        })();
    )"));
  }

  // Update the display's HDR headroom. Ensure that the HDR headroom event
  // fires, but the change event does not fire.
  {
    EXPECT_TRUE(ExecJs(tab, R"(
      var change1 = promiseForEvent(screenDetails.screens[0],
                                    'hdrheadroomchange');
    )"));
    display_cs.SetHDRMaxLuminanceRelative(2.f);
    display.SetColorSpaces(display_cs);
    screen_.display_list().UpdateDisplay(display,
                                         display::DisplayList::Type::PRIMARY);
    EXPECT_EQ(1, EvalJs(tab, R"(
        (async () => {
            if (!change1) return -4;
            await change1;
            if (screenChanges !== 1)
              return -5;
            if (screenHdrHeadroomChanges !== 1)
              return -6;
            return screenDetails.screens[0].hdrHeadroom;
        })();
    )"));
  }

  // Update the display's HDR headroom again. Ensure that the HDR headroom event
  // fires, but the change event does not fire.
  {
    EXPECT_TRUE(ExecJs(tab, R"(
      var change2 = promiseForEvent(screenDetails.screens[0],
                                    'hdrheadroomchange');
    )"));
    display_cs.SetHDRMaxLuminanceRelative(4.f);
    display.SetColorSpaces(display_cs);
    screen_.display_list().UpdateDisplay(display,
                                         display::DisplayList::Type::PRIMARY);
    EXPECT_EQ(2, EvalJs(tab, R"(
        (async () => {
            if (!change2) return -7;
            await change2;
            if (screenChanges !== 1)
              return -8;
            if (screenHdrHeadroomChanges !== 2)
              return -9;
            return screenDetails.screens[0].hdrHeadroom;
        })();
    )"));
  }
}
#endif

// Tests that the old alias for window-management throws an error.
// See: https://chromestatus.com/feature/5137018030391296
IN_PROC_BROWSER_TEST_F(MAYBE_WindowManagementTest, WindowPlacementAliasFails) {
  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_FALSE(content::ExecJs(
      tab, "navigator.permissions.query({name:'window-placement'})"));
}
