// Copyright 2016 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 <variant>

#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/tabs/tab_strip_model.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/security_state/core/security_state.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/focused_node_details.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/common/constants.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 "pdf/buildflags.h"
#include "ui/base/test/ui_controls.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_PDF)
#include "base/test/with_feature_override.h"
#include "chrome/browser/pdf/test_mime_handler_stream_manager.h"
#include "components/guest_view/browser/guest_view_base.h"
#include "components/guest_view/browser/guest_view_manager_delegate.h"
#include "components/guest_view/browser/test_guest_view_manager.h"
#include "extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h"
#include "pdf/pdf_features.h"
#endif  // BUILDFLAG(ENABLE_PDF)

namespace {

// Counts and returns the number of RenderWidgetHosts in the browser process.
size_t GetNumberOfRenderWidgetHosts() {
  std::unique_ptr<content::RenderWidgetHostIterator> all_widgets =
      content::RenderWidgetHost::GetRenderWidgetHosts();
  size_t count = 0;
  while (all_widgets->GetNextHost()) {
    count++;
  }
  return count;
}

// Waits and polls the current number of RenderWidgetHosts and stops when the
// number reaches |target_count|.
void WaitForRenderWidgetHostCount(size_t target_count) {
  EXPECT_TRUE(base::test::RunUntil(
      [&]() { return GetNumberOfRenderWidgetHosts() == target_count; }));
}

}  // namespace

class SitePerProcessInteractiveBrowserTest : public InProcessBrowserTest {
 public:
  SitePerProcessInteractiveBrowserTest() = default;

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

  ~SitePerProcessInteractiveBrowserTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    content::IsolateAllSitesForTesting(command_line);
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");

    // Add content/test/data for cross_site_iframe_factory.html
    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");

    ASSERT_TRUE(embedded_test_server()->Start());
  }

  gfx::Size GetScreenSize() {
    content::WebContents* web_contents =
        browser()->GetTabStripModel()->GetActiveWebContents();
    const display::Display display =
        display::Screen::Get()->GetDisplayNearestView(
            web_contents->GetRenderWidgetHostView()->GetNativeView());
    return display.bounds().size();
  }

  enum class FullscreenExitMethod {
    JS_CALL,
    ESC_PRESS,
  };

  void FullscreenElementInABA(FullscreenExitMethod exit_method);
};

class SitePerProcessInteractiveFencedFrameBrowserTest
    : public SitePerProcessInteractiveBrowserTest,
      public testing::WithParamInterface<const char*> {
 public:
  SitePerProcessInteractiveFencedFrameBrowserTest() = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");

    // Add content/test/data for cross_site_iframe_factory.html
    https_server()->ServeFilesFromSourceDirectory("content/test/data");
    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    content::SetupCrossSiteRedirector(https_server());
    net::test_server::RegisterDefaultHandlers(https_server());

    ASSERT_TRUE(https_server()->Start());
  }

  static std::string DescribeParams(
      const ::testing::TestParamInfo<ParamType>& info) {
    return info.param;
  }

  net::EmbeddedTestServer* https_server() { return &https_server_; }

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_test_helper_;
  }

  base::HistogramTester* histogram_tester() { return &histogram_tester_; }

 private:
  content::test::FencedFrameTestHelper fenced_frame_test_helper_;
  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  base::HistogramTester histogram_tester_;
};

// Check that document.hasFocus() works properly with out-of-process iframes.
// The test builds a page with four cross-site frames and then focuses them one
// by one, checking the value of document.hasFocus() in all frames.  For any
// given focused frame, document.hasFocus() should return true for that frame
// and all its ancestor frames.
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest, DocumentHasFocus) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b(c),d)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child1 = ChildFrameAt(main_frame, 0);
  ASSERT_NE(nullptr, child1);
  content::RenderFrameHost* child2 = ChildFrameAt(main_frame, 1);
  ASSERT_NE(nullptr, child2);
  content::RenderFrameHost* grandchild = ChildFrameAt(child1, 0);
  ASSERT_NE(nullptr, grandchild);

  EXPECT_NE(main_frame->GetSiteInstance(), child1->GetSiteInstance());
  EXPECT_NE(main_frame->GetSiteInstance(), child2->GetSiteInstance());
  EXPECT_NE(child1->GetSiteInstance(), grandchild->GetSiteInstance());

  // Helper function to check document.hasFocus() for a given frame.
  auto document_has_focus = [](content::RenderFrameHost* rfh) -> bool {
    return EvalJs(rfh, "document.hasFocus()").ExtractBool();
  };

  // The main frame should be focused to start with.
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  EXPECT_TRUE(document_has_focus(main_frame));
  EXPECT_FALSE(document_has_focus(child1));
  EXPECT_FALSE(document_has_focus(grandchild));
  EXPECT_FALSE(document_has_focus(child2));

  EXPECT_TRUE(ExecJs(child1, "window.focus();"));
  EXPECT_EQ(child1, web_contents->GetFocusedFrame());

  EXPECT_TRUE(document_has_focus(main_frame));
  EXPECT_TRUE(document_has_focus(child1));
  EXPECT_FALSE(document_has_focus(grandchild));
  EXPECT_FALSE(document_has_focus(child2));

  EXPECT_TRUE(ExecJs(grandchild, "window.focus();"));
  EXPECT_EQ(grandchild, web_contents->GetFocusedFrame());

  EXPECT_TRUE(document_has_focus(main_frame));
  EXPECT_TRUE(document_has_focus(child1));
  EXPECT_TRUE(document_has_focus(grandchild));
  EXPECT_FALSE(document_has_focus(child2));

  EXPECT_TRUE(ExecJs(child2, "window.focus();"));
  EXPECT_EQ(child2, web_contents->GetFocusedFrame());

  EXPECT_TRUE(document_has_focus(main_frame));
  EXPECT_FALSE(document_has_focus(child1));
  EXPECT_FALSE(document_has_focus(grandchild));
  EXPECT_TRUE(document_has_focus(child2));
}

// Ensure that a cross-process subframe can receive keyboard events when in
// focus.
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       SubframeKeyboardEventRouting) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/frame_tree/page_with_one_frame.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  GURL frame_url(
      embedded_test_server()->GetURL("b.com", "/page_with_input_field.html"));
  EXPECT_TRUE(NavigateIframeToURL(web_contents, "child0", frame_url));

  // Focus the subframe and then its input field.  The return value
  // "input-focus" will be sent once the input field's focus event fires.
  content::RenderFrameHost* child =
      ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
  std::string script =
      "function onInput(e) {"
      "  resultQueue.push(getInputFieldText());"
      "}"
      "inputField = document.getElementById('text-field');"
      "inputField.addEventListener('input', onInput, false);";
  EXPECT_TRUE(ExecJs(child, script));
  EXPECT_EQ("input-focus", EvalJs(child, "window.focus(); focusInputField();"));

  // The subframe should now be focused.
  EXPECT_EQ(child, web_contents->GetFocusedFrame());

  // Generate a few keyboard events and route them to currently focused frame.
  // We wait for replies to be sent back from the page, since keystrokes may
  // take time to propagate to the renderer's main thread.
  content::SimulateCharTyped(web_contents, 'F');

  EXPECT_EQ("F", EvalJs(child, "waitForInput();"));

  content::SimulateCharTyped(web_contents, 'O');
  EXPECT_EQ("FO", EvalJs(child, "waitForInput();"));

  content::SimulateCharTyped(web_contents, 'O');
  EXPECT_EQ("FOO", EvalJs(child, "waitForInput();"));
}

// Ensure that sequential focus navigation (advancing focused elements with
// <tab> and <shift-tab>) works across cross-process subframes. This has 2 test
// cases that check sequential focus navigation for both <iframe> and
// <fencedframe> elements.
// The test sets up six inputs fields in a page with two cross-process
// subframes:
//                 child1            child2
//             /------------\    /------------\.
//             | 2. <input> |    | 4. <input> |
//  1. <input> | 3. <input> |    | 5. <input> |  6. <input>
//             \------------/    \------------/.
//
// The test then presses <tab> six times to cycle through focused elements 1-6.
// The test then repeats this with <shift-tab> to cycle in reverse order.
IN_PROC_BROWSER_TEST_P(SitePerProcessInteractiveFencedFrameBrowserTest,
                       SequentialFocusNavigation) {
  GURL main_url(https_server()->GetURL(
      "a.test", GetParam() == std::string("iframe")
                    ? "/cross_site_iframe_factory.html?a.test(b.test,c.test)"
                    : "/cross_site_iframe_factory.html?a.test(b.test{fenced},c."
                      "test{fenced})"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child1 = nullptr;
  content::RenderFrameHost* child2 = nullptr;

  if (GetParam() == std::string("iframe")) {
    child1 = ChildFrameAt(main_frame, 0);
    ASSERT_NE(nullptr, child1);
    child2 = ChildFrameAt(main_frame, 1);
    ASSERT_NE(nullptr, child2);
  } else {
    std::vector<content::RenderFrameHost*> child_frames =
        fenced_frame_test_helper().GetChildFencedFrameHosts(main_frame);
    ASSERT_EQ(child_frames.size(), 2u);
    child1 = child_frames[0];
    child2 = child_frames[1];
  }

  content::WaitForHitTestData(child1);
  content::WaitForHitTestData(child2);

  // Assign a name to each frame.  This will be sent along in test messages
  // from focus events.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));
  EXPECT_TRUE(ExecJs(child1, "window.name = 'child1';"));
  EXPECT_TRUE(ExecJs(child2, "window.name = 'child2';"));

  // This script will insert two <input> fields in the document, one at the
  // beginning and one at the end.  For root frame, this means that we will
  // have an <input>, then two <iframe> elements, then another <input>.
  std::string script =
      "function onFocus(e) {"
      "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
      "}"
      "var input1 = document.createElement('input');"
      "input1.id = 'input1';"
      "var input2 = document.createElement('input');"
      "input2.id = 'input2';"
      "document.body.insertBefore(input1, document.body.firstChild);"
      "document.body.appendChild(input2);"
      "input1.addEventListener('focus', onFocus, false);"
      "input2.addEventListener('focus', onFocus, false);";

  // Add two input fields to each of the three frames.
  EXPECT_TRUE(ExecJs(main_frame, script));
  EXPECT_TRUE(ExecJs(child1, script));
  EXPECT_TRUE(ExecJs(child2, script));

  // Helper to simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message = [web_contents](bool reverse) {
    content::DOMMessageQueue msg_queue(web_contents);
    std::string reply;
    SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, reverse /* shift */, false, false);
    EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
    return reply;
  };

  // Press <tab> six times to focus each of the <input> elements in turn.
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"child1-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(child1, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"child1-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ("\"child2-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(child2, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"child2-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ("\"root-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  // Now, press <shift-tab> to navigate focus in the reverse direction.
  EXPECT_EQ("\"child2-focused-input2\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ(child2, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"child2-focused-input1\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ("\"child1-focused-input2\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ(child1, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"child1-focused-input1\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
}

// Similar to the test above, but check that sequential focus navigation works
// with <object> tags that contain OOPIFs.
//
// The test sets up four inputs fields in a page with a <object> that contains
// an OOPIF:
//                <object>
//             /------------\.
//             | 2. <input> |
//  1. <input> | 3. <input> |  4. <input>
//             \------------/.
//
// The test then presses <tab> 4 times to cycle through focused elements 1-4.
// The test then repeats this with <shift-tab> to cycle in reverse order.
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       SequentialFocusNavigationWithObject) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/page_with_object_fallback.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();

  content::TestNavigationObserver observer(web_contents);
  GURL object_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  EXPECT_TRUE(content::ExecJs(
      main_frame,
      content::JsReplace("document.querySelector('object').data = $1;",
                         object_url)));
  observer.Wait();
  content::RenderFrameHost* object = ChildFrameAt(main_frame, 0);
  ASSERT_TRUE(object);
  ASSERT_TRUE(object->IsCrossProcessSubframe());
  EXPECT_EQ(object_url, object->GetLastCommittedURL());

  // Assign a name to each frame.  This will be sent along in test messages
  // from focus events.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));
  EXPECT_TRUE(ExecJs(object, "window.name = 'object';"));

  // This script will insert two <input> fields in the document, one at the
  // beginning and one at the end.  For root frame, this means that we will
  // have an <input>, then an <object> element, then another <input>.
  std::string script =
      "function onFocus(e) {"
      "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
      "}"
      "var input1 = document.createElement('input');"
      "input1.id = 'input1';"
      "var input2 = document.createElement('input');"
      "input2.id = 'input2';"
      "document.body.insertBefore(input1, document.body.firstChild);"
      "document.body.appendChild(input2);"
      "input1.addEventListener('focus', onFocus, false);"
      "input2.addEventListener('focus', onFocus, false);";

  // Add two input fields to each of the two frames.
  EXPECT_TRUE(ExecJs(main_frame, script));
  EXPECT_TRUE(ExecJs(object, script));

  // Helper to simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message = [web_contents](bool reverse) {
    content::DOMMessageQueue msg_queue(web_contents);
    std::string reply;
    SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, reverse /* shift */, false, false);
    EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
    return reply;
  };

  // Press <tab> four times to focus each of the <input> elements in turn.
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"object-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(object, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"object-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ("\"root-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  // Now, press <shift-tab> to navigate focus in the reverse direction.
  EXPECT_EQ("\"object-focused-input2\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ(object, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"object-focused-input1\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
}

// Ensure that frames get focus when wrapping focus using <tab> or <shift-tab>.
// This has 2 test cases that check sequential focus navigation for both
// <iframe> and <fencedframe> elements.
// The test sets up two input fields in a page with one cross-process subframe:
//                  child
//              /------------\.
//  1. <input>  | 2. <input> |
//              \------------/.
//
// The test then presses <tab> to focus on elements 1, then <shift-tab> twice to
// focus on the omnibox followed by element 2. This tests that focus works as
// expected when wrapping through non-page UI elements. Specifically, this tests
// that fenced frames can properly get and verify focus if its RenderWidgetHost
// loses focus.
IN_PROC_BROWSER_TEST_P(SitePerProcessInteractiveFencedFrameBrowserTest,
                       SequentialFocusNavigationWrapAround) {
  GURL main_url(https_server()->GetURL(
      "a.test", GetParam() == std::string("fencedframe")
                    ? "/cross_site_iframe_factory.html?a.test(b.test{fenced})"
                    : "/cross_site_iframe_factory.html?a.test(b.test)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child = nullptr;

  if (GetParam() == std::string("iframe")) {
    child = ChildFrameAt(main_frame, 0);
    ASSERT_NE(nullptr, child);
  } else {
    child =
        fenced_frame_test_helper().GetMostRecentlyAddedFencedFrame(main_frame);
    ASSERT_NE(nullptr, child);
  }

  content::WaitForHitTestData(child);

  // Assign a name to each frame.  This will be sent along in test messages
  // from focus events.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));
  EXPECT_TRUE(ExecJs(child, "window.name = 'child';"));

  // This script will insert one <input> field at the beginning of the document.
  // For root frame, this means that we will have an <input> element followed by
  // an <iframe>.
  std::string script =
      "function onFocus(e) {"
      "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
      "}"
      "var input1 = document.createElement('input');"
      "input1.id = 'input1';"
      "document.body.insertBefore(input1, document.body.firstChild);"
      "input1.addEventListener('focus', onFocus, false);";

  // Add two input fields to each of the three frames.
  EXPECT_TRUE(ExecJs(main_frame, script));
  EXPECT_TRUE(ExecJs(child, script));

  // Helper to simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message = [web_contents](bool reverse) {
    content::DOMMessageQueue msg_queue(web_contents);
    std::string reply;
    SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, reverse /* shift */, false, false);
    EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
    return reply;
  };

  // Press <tab> to focus the <input> element in the main frame.
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  auto frame_focused = std::make_unique<content::FrameFocusedObserver>(child);
  // Press <shift-tab> twice to focus on the UI and then wrap around to the
  // child frame.
  SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                   ui::VKEY_TAB, false, /*shift=*/true, false, false);
  SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                   ui::VKEY_TAB, false, /*shift=*/true, false, false);

  // Wait for the child frame to get focus.
  frame_focused->Wait();
}

// Ensure that frames can pass focus to subsequent frames if there is nothing
// focusable left in their frame. The test sets up two input fields in a page
// with three cross-process subframes:
//                  child1          child3
//              /------------\. /------------\.
//  1. <input>  |   child2   |  | 2. <input> |
//              | /--------\ |  \------------/.
//              | \--------/ |
//              \------------/.
//
// The test then presses <tab> twice to focus on elements 1 and 2.
// TODO(crbug.com/40276413): Re-enable this test once this bug is fixed.
IN_PROC_BROWSER_TEST_P(SitePerProcessInteractiveFencedFrameBrowserTest,
                       SequentialFocusNavigationPassThrough) {
  GURL main_url(https_server()->GetURL(
      "a.test",
      GetParam() == std::string("fencedframe")
          ? "/cross_site_iframe_factory.html?a.test(b.test{fenced}(c.test{"
            "fenced}),d.test{fenced})"
          : "/cross_site_iframe_factory.html?a.test(b.test(c.test),d.test)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child1 = nullptr;
  content::RenderFrameHost* child2 = nullptr;
  content::RenderFrameHost* child3 = nullptr;

  if (GetParam() == std::string("iframe")) {
    child1 = ChildFrameAt(main_frame, 0);
    child2 = ChildFrameAt(child1, 0);
    child3 = ChildFrameAt(main_frame, 1);
  } else {
    std::vector<content::RenderFrameHost*> child_frames =
        fenced_frame_test_helper().GetChildFencedFrameHosts(main_frame);
    ASSERT_EQ(child_frames.size(), 2u);
    child1 = child_frames[0];
    child2 = fenced_frame_test_helper().GetMostRecentlyAddedFencedFrame(child1);
    child3 = child_frames[1];
  }

  ASSERT_NE(nullptr, child1);
  ASSERT_NE(nullptr, child2);
  ASSERT_NE(nullptr, child3);

  content::WaitForHitTestData(child1);
  content::WaitForHitTestData(child2);
  content::WaitForHitTestData(child3);

  // Assign a name to each frame.  This will be sent along in test messages
  // from focus events.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));
  EXPECT_TRUE(ExecJs(child1, "window.name = 'child1';"));
  EXPECT_TRUE(ExecJs(child2, "window.name = 'child2';"));
  EXPECT_TRUE(ExecJs(child3, "window.name = 'child3';"));

  // This script will insert one <input> field at the beginning of the document.
  // For root frame, this means that we will have an <input> element followed by
  // a <fencedframe>.
  std::string script =
      "function onFocus(e) {"
      "  console.log(window.name + '-focused-' + e.target.id);"
      "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
      "}"
      "var input1 = document.createElement('input');"
      "input1.id = 'input1';"
      "document.body.insertBefore(input1, document.body.firstChild);"
      "input1.addEventListener('focus', onFocus, false);";

  // Add one input field to the main frame and last fenced frame.
  EXPECT_TRUE(ExecJs(main_frame, script));
  EXPECT_TRUE(ExecJs(child3, script));

  // Helper to simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message = [web_contents](bool reverse) {
    content::DOMMessageQueue msg_queue(web_contents);
    std::string reply;
    SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, reverse /* shift */, false, false);
    EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
    return reply;
  };

  // Press <tab> to focus the <input> element in the main frame.
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  // Press <tab> to move focus to the <input> element in child3 fenced frame.
  EXPECT_EQ("\"child3-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(child3, web_contents->GetFocusedFrame());
}

// Ensure that frames can pass focus to subsequent frames if there is nothing
// focusable left in their frame. The test sets up two input fields in the last
// subframe loaded into a page:
//       child1          child3
//   /------------\. /-------------\.
//   |   child2   |  | 1. <input1> |
//   | /--------\ |  \ 2. <input2> /
//   | \--------/ |  \-------------/.
//   \------------/.
//
// The test then presses <tab> twice to focus on elements 1 and 2, <tab> to
// move focus to the UI, and <tab> one more time to focus on element 1 again.
// TODO(crbug.com/40276413): Re-enable this test once this bug is fixed.
IN_PROC_BROWSER_TEST_P(SitePerProcessInteractiveFencedFrameBrowserTest,
                       SequentialFocusWrapBackIntoChildFrame) {
  GURL main_url(https_server()->GetURL(
      "a.test",
      GetParam() == std::string("fencedframe")
          ? "/cross_site_iframe_factory.html?a.test(b.test{fenced}(c.test{"
            "fenced}),d.test{fenced})"
          : "/cross_site_iframe_factory.html?a.test(b.test(c.test),d.test)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child1 = nullptr;
  content::RenderFrameHost* child2 = nullptr;
  content::RenderFrameHost* child3 = nullptr;

  if (GetParam() == std::string("iframe")) {
    child1 = ChildFrameAt(main_frame, 0);
    child2 = ChildFrameAt(child1, 0);
    child3 = ChildFrameAt(main_frame, 1);
  } else {
    std::vector<content::RenderFrameHost*> child_frames =
        fenced_frame_test_helper().GetChildFencedFrameHosts(main_frame);
    ASSERT_EQ(child_frames.size(), 2u);
    child1 = child_frames[0];
    child2 = fenced_frame_test_helper().GetMostRecentlyAddedFencedFrame(child1);
    child3 = child_frames[1];
  }

  ASSERT_NE(nullptr, child1);
  ASSERT_NE(nullptr, child2);
  ASSERT_NE(nullptr, child3);

  content::WaitForHitTestData(child1);
  content::WaitForHitTestData(child2);
  content::WaitForHitTestData(child3);

  // Assign a name to each frame.  This will be sent along in test messages
  // from focus events.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));
  EXPECT_TRUE(ExecJs(child1, "window.name = 'child1';"));
  EXPECT_TRUE(ExecJs(child2, "window.name = 'child2';"));
  EXPECT_TRUE(ExecJs(child3, "window.name = 'child3';"));

  // This script will insert two <input> fields at the beginning of the
  // document.
  std::string script =
      "function onFocus(e) {"
      "  console.log(window.name + '-focused-' + e.target.id);"
      "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
      "}"
      "var input2 = document.createElement('input');"
      "input2.id = 'input2';"
      "document.body.insertBefore(input2, document.body.firstChild);"
      "input2.addEventListener('focus', onFocus, false);"
      "var input1 = document.createElement('input');"
      "input1.id = 'input1';"
      "document.body.insertBefore(input1, document.body.firstChild);"
      "input1.addEventListener('focus', onFocus, false);";

  // Add two input fields to the last fenced frame.
  EXPECT_TRUE(ExecJs(child3, script));

  // Helper to simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message = [web_contents](bool reverse) {
    content::DOMMessageQueue msg_queue(web_contents);
    std::string reply;
    SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, reverse /* shift */, false, false);
    EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
    return reply;
  };

  // Press <tab> to move focus to the <input> element in child3 fenced frame.
  EXPECT_EQ("\"child3-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ("\"child3-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(child3, web_contents->GetFocusedFrame());

  // Press <tab> twice to focus on the UI and then wrap around back to the
  // child frame.
  SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                   ui::VKEY_TAB, false, /*shift=*/true, false, false);

  EXPECT_EQ("\"child3-focused-input1\"", press_tab_and_wait_for_message(false));
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
// Ensures that renderers know to advance focus to sibling frames and parent
// frames in the presence of mouse click initiated focus changes.
// Verifies against regression of https://crbug.com/40511146
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       TabAndMouseFocusNavigation) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child1 = ChildFrameAt(main_frame, 0);
  ASSERT_NE(nullptr, child1);
  content::RenderFrameHost* child2 = ChildFrameAt(main_frame, 1);
  ASSERT_NE(nullptr, child2);

  // Needed to avoid flakiness with --enable-browser-side-navigation.
  content::WaitForHitTestData(child1);
  content::WaitForHitTestData(child2);

  // Assign a name to each frame.  This will be sent along in test messages
  // from focus events.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));
  EXPECT_TRUE(ExecJs(child1, "window.name = 'child1';"));
  EXPECT_TRUE(ExecJs(child2, "window.name = 'child2';"));

  // This script will insert two <input> fields in the document, one at the
  // beginning and one at the end.  For root frame, this means that we will
  // have an <input>, then two <iframe> elements, then another <input>.
  // The script will send back the coordinates to click for each <input>, in the
  // document's space. Additionally, the outer frame will return  the top left
  // point of each <iframe> to transform the coordinates of the inner <input>
  // elements. For example, main frame: 497,18;497,185:381,59;499,59 and each
  // iframe: 55,18;55,67
  std::string script = R"(
      /* Invariant: eventsOrGets is either all event records, or all
       * `resolve` functions.
       */
      var eventsOrGets = [];
      function waitForFocusEvent() {
        if (eventsOrGets.length && (typeof eventsOrGets.at(-1) !=
           'function')) {
          return eventsOrGets.pop();
        }
        return new Promise(resolve => {
          eventsOrGets.push(resolve);
        });
      }
      function onFocus(e) {
        var result = window.name + '-focused-' + e.target.id;
        console.log(result);
        if (eventsOrGets.length && (typeof eventsOrGets.at(-1) ==
            'function')) {
          eventsOrGets.pop()(result);
          return;
        }
        eventsOrGets.push(result);
      }

      function getElementCoords(element) {
        var rect = element.getBoundingClientRect();
        return Math.floor(rect.left + 0.5 * rect.width) +','+
               Math.floor(rect.top + 0.5 * rect.height);
      }
      function getIframeCoords(element) {
        var rect = element.getBoundingClientRect();
        return Math.floor(rect.left) +','+
               Math.floor(rect.top);
      }
      function onClick(e) {
       console.log('Click event ' + window.name + ' at: ' + e.x + ', ' + e.y
                   + ' screen: ' + e.screenX + ', ' + e.screenY);
      }

      window.addEventListener('click', onClick);
      console.log(document.location.origin);
      document.styleSheets[0].insertRule('input {width:100%;margin:0;}', 1);
      document.styleSheets[0].insertRule('h2 {margin:0;}', 1);
      var input1 = document.createElement('input');
      input1.id = 'input1';
      input1.addEventListener('focus', onFocus, false);
      var input2 = document.createElement('input');
      input2.id = 'input2';
      input2.addEventListener('focus', onFocus, false);
      document.body.insertBefore(input1, document.body.firstChild);
      document.body.appendChild(input2);

      var frames = document.querySelectorAll('iframe');
      frames = Array.prototype.map.call(frames, getIframeCoords).join(';');
      var inputCoords = [input1, input2].map(getElementCoords).join(';');
      if (frames) {
        inputCoords = inputCoords + ':' + frames;
      }
      inputCoords;
      )";

  auto parse_points = [](const std::string& input, const gfx::Point& offset) {
    base::StringPairs pieces;
    base::SplitStringIntoKeyValuePairs(input, ',', ';', &pieces);
    std::vector<gfx::Point> points;
    for (const auto& piece : pieces) {
      int x, y;
      EXPECT_TRUE(base::StringToInt(piece.first, &x));
      EXPECT_TRUE(base::StringToInt(piece.second, &y));
      points.push_back(gfx::Point(x + offset.x(), y + offset.y()));
    }
    return points;
  };
  auto parse_points_and_offsets = [parse_points](const std::string& input) {
    auto pieces = base::SplitString(input, ":", base::TRIM_WHITESPACE,
                                    base::SPLIT_WANT_NONEMPTY);
    gfx::Point empty_offset;
    return make_pair(parse_points(pieces[0], empty_offset),
                     parse_points(pieces[1], empty_offset));
  };

  // Add two input fields to each of the three frames and retrieve click
  // coordinates.
  std::string result = EvalJs(main_frame, script).ExtractString();
  auto parsed = parse_points_and_offsets(result);
  auto main_frame_input_coords = parsed.first;
  auto iframe1_offset = parsed.second[0];
  auto iframe2_offset = parsed.second[1];

  result = EvalJs(child1, script).ExtractString();
  auto child1_input_coords = parse_points(result, iframe1_offset);
  result = EvalJs(child2, script).ExtractString();
  auto child2_input_coords = parse_points(result, iframe2_offset);

  // Helper to simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message =
      [web_contents](content::RenderFrameHost* receiver, bool reverse) {
        SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                         ui::VKEY_TAB, false, reverse /* shift */, false,
                         false);
        LOG(INFO) << "Press tab";
        return EvalJs(receiver, "waitForFocusEvent()");
      };

  auto click_element_and_wait_for_message =
      [web_contents](content::RenderFrameHost* receiver,
                     const gfx::Point& point) {
        auto content_bounds = web_contents->GetContainerBounds();
        // Wait for the mouse move to be processed before sending the click.
        // On Wayland, SendMouseClick uses last_mouse_location() to find the
        // target window; if the move hasn't been processed yet,
        // last_mouse_location() is stale and the test clicks wrong point.
        base::RunLoop move_loop;
        ui_controls::SendMouseMoveNotifyWhenDone(point.x() + content_bounds.x(),
                                                 point.y() + content_bounds.y(),
                                                 move_loop.QuitClosure());
        move_loop.Run();
        ui_controls::SendMouseClick(ui_controls::LEFT);

        LOG(INFO) << "Click element";
        return EvalJs(receiver, "waitForFocusEvent()");
      };

  // Tab from child1 back to root.
  EXPECT_EQ("root-focused-input1", click_element_and_wait_for_message(
                                       main_frame, main_frame_input_coords[0]));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
  auto frame_focused = std::make_unique<content::FrameFocusedObserver>(child1);
  EXPECT_EQ("child1-focused-input1",
            click_element_and_wait_for_message(child1, child1_input_coords[0]));
  frame_focused->Wait();
  frame_focused = std::make_unique<content::FrameFocusedObserver>(main_frame);
  EXPECT_EQ("root-focused-input1",
            press_tab_and_wait_for_message(main_frame, true));
  frame_focused->Wait();

  // Tab from child2 forward to root.
  EXPECT_EQ("root-focused-input2", click_element_and_wait_for_message(
                                       main_frame, main_frame_input_coords[1]));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child2);
  EXPECT_EQ("child2-focused-input2",
            click_element_and_wait_for_message(child2, child2_input_coords[1]));
  frame_focused->Wait();
  frame_focused = std::make_unique<content::FrameFocusedObserver>(main_frame);
  EXPECT_EQ("root-focused-input2",
            press_tab_and_wait_for_message(main_frame, false));
  frame_focused->Wait();

  // Tab forward from child1 to child2.
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child2);
  EXPECT_EQ("child2-focused-input1",
            click_element_and_wait_for_message(child2, child2_input_coords[0]));
  frame_focused->Wait();
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child1);
  EXPECT_EQ("child1-focused-input2",
            click_element_and_wait_for_message(child1, child1_input_coords[1]));
  frame_focused->Wait();
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child2);
  EXPECT_EQ("child2-focused-input1",
            press_tab_and_wait_for_message(child2, false));
  frame_focused->Wait();

  // Tab backward from child2 to child1.
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child1);
  EXPECT_EQ("child1-focused-input2",
            click_element_and_wait_for_message(child1, child1_input_coords[1]));
  frame_focused->Wait();
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child2);
  EXPECT_EQ("child2-focused-input1",
            click_element_and_wait_for_message(child2, child2_input_coords[0]));
  frame_focused->Wait();
  frame_focused = std::make_unique<content::FrameFocusedObserver>(child1);
  EXPECT_EQ("child1-focused-input2",
            press_tab_and_wait_for_message(child1, true));
  // EXPECT_EQ(child1, web_contents->GetFocusedFrame());
  frame_focused->Wait();

  // Ensure there are no pending focus events after tabbing.
  EXPECT_EQ("root-focused-input1", click_element_and_wait_for_message(
                                       main_frame, main_frame_input_coords[0]))
      << "Unexpected extra focus events.";
}
#endif

// Ensure that tab traversal works with crashed frames. Start with
// a document with an OOPIF and two <input> fields. Crash the child
// frame renderer and ensure that pressing tab correctly skips the sad
// frame.
//                child(sad)
//             /------------\
//  1. <input> |            | 2. <input>
//             \------------/
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       TabFocusWithSadRemote) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child = ChildFrameAt(main_frame, 0);
  ASSERT_TRUE(child);

  content::WaitForHitTestData(child);

  // Assign a name to parent page.
  EXPECT_TRUE(ExecJs(main_frame, "window.name = 'root';"));

  // This script will insert two <input> fields in the document, one at the
  // beginning and one at the end. For root frame, this means that we will
  // have an <input>, <iframe> element and then another <input>.
  std::string script =
      "function onFocus(e) {"
      "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
      "}"
      "var input1 = document.createElement('input');"
      "input1.id = 'input1';"
      "var input2 = document.createElement('input');"
      "input2.id = 'input2';"
      "document.body.insertBefore(input1, document.body.firstChild);"
      "document.body.appendChild(input2);"
      "input1.addEventListener('focus', onFocus, false);"
      "input2.addEventListener('focus', onFocus, false);";

  EXPECT_TRUE(ExecJs(main_frame, script));

  // Simulate a tab press and wait for a focus message.
  auto press_tab_and_wait_for_message = [web_contents](bool reverse) {
    content::DOMMessageQueue msg_queue(web_contents);
    std::string reply;
    SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
                     ui::VKEY_TAB, false, reverse /* shift */, false, false);
    EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
    return reply;
  };

  // Press <tab> two times to focus each of the <input> elements in turn.
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
  EXPECT_EQ("\"root-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  {
    content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
    content::RenderProcessHostWatcher child_observer(
        child->GetProcess(),
        content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);

    // Kill the renderer process of child frame.
    child->GetProcess()->Shutdown(0);
    child_observer.Wait();
  }

  // Press <shift-tab> to navigate focus in the reverse direction.
  EXPECT_EQ("\"root-focused-input1\"", press_tab_and_wait_for_message(true));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());

  // Press <tab> to navigate focus to second input skipping sad iframe.
  EXPECT_EQ("\"root-focused-input2\"", press_tab_and_wait_for_message(false));
  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
}

namespace {

// Helper to retrieve the frame's (window.innerWidth, window.innerHeight).
gfx::Size GetFrameSize(content::RenderFrameHost* frame) {
  int width = EvalJs(frame, "window.innerWidth;").ExtractInt();

  int height = EvalJs(frame, "window.innerHeight;").ExtractInt();

  return gfx::Size(width, height);
}

// Helper to check |frame|'s document.webkitFullscreenElement and return its ID
// if it's defined (which is the case when |frame| is in fullscreen mode), or
// "none" otherwise.
std::string GetFullscreenElementId(content::RenderFrameHost* frame) {
  return EvalJs(frame,
                "document.webkitFullscreenElement ? "
                "    document.webkitFullscreenElement.id : 'none'")
      .ExtractString();
}

// Helper to check if an element with ID |element_id| has the
// :-webkit-full-screen style.
bool ElementHasFullscreenStyle(content::RenderFrameHost* frame,
                               const std::string& element_id) {
  std::string script = base::StringPrintf(
      "document.querySelectorAll('#%s:-webkit-full-screen').length == 1",
      element_id.c_str());
  return EvalJs(frame, script).ExtractBool();
}

// Helper to check if an element with ID |element_id| has the
// :-webkit-full-screen-ancestor style.
bool ElementHasFullscreenAncestorStyle(content::RenderFrameHost* host,
                                       const std::string& element_id) {
  std::string script = base::StringPrintf(
      "document.querySelectorAll("
      "    '#%s:-webkit-full-screen-ancestor').length == 1",
      element_id.c_str());
  return EvalJs(host, script).ExtractBool();
}

// Add a listener that will send back a message whenever the (prefixed)
// fullscreenchange event fires.  The message will be "fullscreenchange",
// followed by a space and the provided |id|.
void AddFullscreenChangeListener(content::RenderFrameHost* frame,
                                 const std::string& id) {
  std::string script = base::StringPrintf(
      "document.addEventListener('webkitfullscreenchange', function() {"
      "    domAutomationController.send('fullscreenchange %s');});",
      id.c_str());
  EXPECT_TRUE(ExecJs(frame, script));
}

// Helper to add a listener that will send back a "resize" message when the
// target |frame| is resized to |expected_size|.
void AddResizeListener(content::RenderFrameHost* frame,
                       const gfx::Size& expected_size) {
  std::string script =
      base::StringPrintf("addResizeListener(%d, %d);", expected_size.width(),
                         expected_size.height());
  EXPECT_TRUE(ExecJs(frame, script));
}

// Helper to wait for a toggle fullscreen operation to complete in all affected
// frames.  This means waiting for:
// 1. All fullscreenchange events with id's matching the list in
//    |expected_fullscreen_event_ids|.  Typically the list will correspond to
//    events from the actual fullscreen element and all of its ancestor
//    <iframe> elements.
// 2. A resize event.  This will verify that the frame containing the
//    fullscreen element is properly resized.  This assumes that the expected
//    size is already registered via AddResizeListener().
void WaitForMultipleFullscreenEvents(
    const std::set<std::string>& expected_fullscreen_event_ids,
    content::DOMMessageQueue& queue) {
  std::set<std::string> remaining_events(expected_fullscreen_event_ids);
  bool resize_validated = false;
  std::string response;
  while (queue.WaitForMessage(&response)) {
    base::TrimString(response, "\"", &response);
    std::vector<std::string> response_params = base::SplitString(
        response, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
    if (response_params[0] == "fullscreenchange") {
      EXPECT_TRUE(remaining_events.contains(response_params[1]));
      remaining_events.erase(response_params[1]);
    } else if (response_params[0] == "resize") {
      resize_validated = true;
    }
    if (remaining_events.empty() && resize_validated) {
      break;
    }
  }
}

}  // namespace

// Check that an element in a cross-process subframe can enter and exit
// fullscreen.  The test will verify that:
// - the subframe is properly resized
// - the WebContents properly enters/exits fullscreen.
// - document.webkitFullscreenElement is correctly updated in both frames.
// - fullscreenchange events fire in both frames.
// - fullscreen CSS is applied correctly in both frames.
//
#if BUILDFLAG(IS_MAC)
// https://crbug.com/41393319
#define MAYBE_FullscreenElementInSubframe DISABLED_FullscreenElementInSubframe
#else
#define MAYBE_FullscreenElementInSubframe FullscreenElementInSubframe
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       MAYBE_FullscreenElementInSubframe) {
  // Start on a page with one subframe (id "child-0") that has
  // "allowfullscreen" enabled.
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/page_with_allowfullscreen_frame.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Navigate the subframe cross-site to a page with a fullscreenable <div>.
  GURL frame_url(
      embedded_test_server()->GetURL("b.com", "/fullscreen_frame.html"));
  EXPECT_TRUE(NavigateIframeToURL(web_contents, "child-0", frame_url));

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child = ChildFrameAt(main_frame, 0);
  gfx::Size original_child_size = GetFrameSize(child);

  // Fullscreen the <div> inside the cross-site child frame.  Wait until:
  // (1) the fullscreenchange events in main frame and child send a response,
  // (2) the child frame is resized to fill the whole screen.
  // (3) the browser has finished the fullscreen transition.
  AddFullscreenChangeListener(main_frame, "main_frame");
  AddFullscreenChangeListener(child, "child");
  std::set<std::string> expected_events = {"main_frame", "child"};
  AddResizeListener(child, GetScreenSize());
  {
    content::DOMMessageQueue queue(web_contents);
    ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
    EXPECT_TRUE(ExecJs(child, "activateFullscreen()"));
    WaitForMultipleFullscreenEvents(expected_events, queue);
    waiter.Wait();
  }

  // Verify that the browser has entered fullscreen for the current tab.
  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
  EXPECT_TRUE(web_contents->IsFullscreen());

  // Verify that the <div> has fullscreen style (:-webkit-full-screen) in the
  // subframe.
  EXPECT_TRUE(ElementHasFullscreenStyle(child, "fullscreen-div"));

  // Verify that the main frame has applied proper fullscreen styles to the
  // <iframe> element (:-webkit-full-screen and :-webkit-full-screen-ancestor).
  // This is what causes the <iframe> to stretch and fill the whole viewport.
  EXPECT_TRUE(ElementHasFullscreenStyle(main_frame, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenAncestorStyle(main_frame, "child-0"));

  // Check document.webkitFullscreenElement.  For main frame, it should point
  // to the subframe, and for subframe, it should point to the fullscreened
  // <div>.
  EXPECT_EQ("child-0", GetFullscreenElementId(main_frame));
  EXPECT_EQ("fullscreen-div", GetFullscreenElementId(child));

  // Now exit fullscreen from the subframe.  Wait for two fullscreenchange
  // events from both frames, and also for the child to be resized to its
  // original size.
  AddResizeListener(child, original_child_size);
  {
    content::DOMMessageQueue queue(web_contents);
    ui_test_utils::FullscreenWaiter waiter(browser(),
                                           {.tab_fullscreen = false});
    EXPECT_TRUE(ExecJs(child, "exitFullscreen()"));
    WaitForMultipleFullscreenEvents(expected_events, queue);
    waiter.Wait();
  }

  EXPECT_FALSE(browser()->GetWindow()->IsFullscreen());

  // Verify that the fullscreen styles were removed from the <div> and its
  // container <iframe>.
  EXPECT_FALSE(ElementHasFullscreenStyle(child, "fullscreen-div"));
  EXPECT_FALSE(ElementHasFullscreenStyle(main_frame, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenAncestorStyle(main_frame, "child-0"));

  // Check that both frames cleared their document.webkitFullscreenElement.
  EXPECT_EQ("none", GetFullscreenElementId(main_frame));
  EXPECT_EQ("none", GetFullscreenElementId(child));
}

// Check that on a page with A-embed-B-embed-A frame hierarchy, an element in
// the bottom frame can enter and exit fullscreen.  |exit_method| specifies
// whether to use browser-initiated vs. renderer-initiated fullscreen exit
// (i.e., pressing escape vs. a JS call), since they trigger different code
// paths on the Blink side.
void SitePerProcessInteractiveBrowserTest::FullscreenElementInABA(
    FullscreenExitMethod exit_method) {
  GURL main_url(embedded_test_server()->GetURL("a.com",
                                               "/cross_site_iframe_factory."
                                               "html?a(b{allowfullscreen}(a{"
                                               "allowfullscreen}))"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child = ChildFrameAt(main_frame, 0);
  content::RenderFrameHost* grandchild = ChildFrameAt(child, 0);

  // Navigate the bottom frame to a page that has a fullscreenable <div>.
  content::TestNavigationObserver observer(web_contents);
  EXPECT_TRUE(ExecJs(grandchild, "location.href = '/fullscreen_frame.html'"));
  observer.Wait();
  grandchild = ChildFrameAt(child, 0);
  EXPECT_EQ(embedded_test_server()->GetURL("a.com", "/fullscreen_frame.html"),
            grandchild->GetLastCommittedURL());

  // Make fullscreenchange events in all three frames send a message.
  AddFullscreenChangeListener(main_frame, "main_frame");
  AddFullscreenChangeListener(child, "child");
  AddFullscreenChangeListener(grandchild, "grandchild");

  // Add a resize event handler that will send a message when the grandchild
  // frame is resized to the screen size.  Also save its original size.
  AddResizeListener(grandchild, GetScreenSize());
  gfx::Size original_grandchild_size = GetFrameSize(grandchild);

  // Fullscreen a <div> inside the bottom subframe.  This will block until
  // (1) the fullscreenchange events in all frames send a response, and
  // (2) the frame is resized to fill the whole screen.
  // (3) the browser has finished the fullscreen transition.
  std::set<std::string> expected_events = {"main_frame", "child", "grandchild"};
  {
    content::DOMMessageQueue queue(web_contents);
    ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
    EXPECT_TRUE(ExecJs(grandchild, "activateFullscreen()"));
    WaitForMultipleFullscreenEvents(expected_events, queue);
    waiter.Wait();
  }

  // Verify that the browser has entered fullscreen for the current tab.
  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
  EXPECT_TRUE(web_contents->IsFullscreen());

  // Verify that the <div> has fullscreen style in the bottom frame, and that
  // the proper <iframe> elements have fullscreen style in its ancestor frames.
  EXPECT_TRUE(ElementHasFullscreenStyle(grandchild, "fullscreen-div"));
  EXPECT_TRUE(ElementHasFullscreenStyle(child, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenAncestorStyle(child, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenStyle(main_frame, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenAncestorStyle(main_frame, "child-0"));

  // Check document.webkitFullscreenElement in all frames.
  EXPECT_EQ("child-0", GetFullscreenElementId(main_frame));
  EXPECT_EQ("child-0", GetFullscreenElementId(child));
  EXPECT_EQ("fullscreen-div", GetFullscreenElementId(grandchild));

  // Now exit fullscreen from the subframe.
  AddResizeListener(grandchild, original_grandchild_size);
  {
    content::DOMMessageQueue queue(web_contents);
    ui_test_utils::FullscreenWaiter waiter(browser(),
                                           {.tab_fullscreen = false});
    switch (exit_method) {
      case FullscreenExitMethod::JS_CALL:
        EXPECT_TRUE(ExecJs(grandchild, "exitFullscreen()"));
        break;
      case FullscreenExitMethod::ESC_PRESS:
        ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
            browser(), ui::VKEY_ESCAPE, false, false, false, false));
        break;
      default:
        NOTREACHED();
    }
    WaitForMultipleFullscreenEvents(expected_events, queue);
    waiter.Wait();
  }

  EXPECT_FALSE(browser()->GetWindow()->IsFullscreen());

  // Verify that the fullscreen styles were removed from the <div> and its
  // container <iframe>'s.
  EXPECT_FALSE(ElementHasFullscreenStyle(grandchild, "fullscreen-div"));
  EXPECT_FALSE(ElementHasFullscreenStyle(child, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenAncestorStyle(child, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenStyle(main_frame, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenAncestorStyle(main_frame, "child-0"));

  // Check that document.webkitFullscreenElement was cleared in all three
  // frames.
  EXPECT_EQ("none", GetFullscreenElementId(main_frame));
  EXPECT_EQ("none", GetFullscreenElementId(child));
  EXPECT_EQ("none", GetFullscreenElementId(grandchild));
}

// https://crbug.com/40694840: Flaky for ASAN and TSAN
#if BUILDFLAG(IS_MAC) || defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
#define MAYBE_FullscreenElementInABAAndExitViaEscapeKey \
  DISABLED_FullscreenElementInABAAndExitViaEscapeKey
#else
#define MAYBE_FullscreenElementInABAAndExitViaEscapeKey \
  FullscreenElementInABAAndExitViaEscapeKey
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       MAYBE_FullscreenElementInABAAndExitViaEscapeKey) {
  FullscreenElementInABA(FullscreenExitMethod::ESC_PRESS);
}

// This test is flaky on Linux (crbug.com/41393671) and also not working
// on Mac (crbug.com/41393319).
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       DISABLED_FullscreenElementInABAAndExitViaJS) {
  FullscreenElementInABA(FullscreenExitMethod::JS_CALL);
}

// Check that fullscreen works on a more complex page hierarchy with multiple
// local and remote ancestors.  The test uses this frame tree:
//
//             A (a_top)
//             |
//             A (a_bottom)
//            / \   .
// (b_first) B   B (b_second)
//               |
//               C (c_top)
//               |
//               C (c_middle) <- fullscreen target
//               |
//               C (c_bottom)
//
// The c_middle frame will trigger fullscreen for its <div> element.  The test
// verifies that its ancestor chain is properly updated for fullscreen, and
// that the b_first node that's not on the chain is not affected.
//
// The test also exits fullscreen by simulating pressing ESC rather than using
// document.webkitExitFullscreen(), which tests the browser-initiated
// fullscreen exit path.
// TODO(crbug.com/40535621): flaky on all platforms.
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       DISABLED_FullscreenElementInMultipleSubframes) {
  // Allow fullscreen in all iframes descending to |c_middle|.
  GURL main_url(embedded_test_server()->GetURL(
      "a.com",
      "/cross_site_iframe_factory.html?a(a{allowfullscreen}(b,b{"
      "allowfullscreen}(c{allowfullscreen}(c{allowfullscreen}))))"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* a_top = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* a_bottom = ChildFrameAt(a_top, 0);
  content::RenderFrameHost* b_first = ChildFrameAt(a_bottom, 0);
  content::RenderFrameHost* b_second = ChildFrameAt(a_bottom, 1);
  content::RenderFrameHost* c_top = ChildFrameAt(b_second, 0);
  content::RenderFrameHost* c_middle = ChildFrameAt(c_top, 0);

  // Navigate |c_middle| to a page that has a fullscreenable <div> and another
  // frame.
  content::TestNavigationObserver observer(web_contents);
  EXPECT_TRUE(ExecJs(c_middle, "location.href = '/fullscreen_frame.html'"));
  observer.Wait();
  c_middle = ChildFrameAt(c_top, 0);
  EXPECT_EQ(embedded_test_server()->GetURL("c.com", "/fullscreen_frame.html"),
            c_middle->GetLastCommittedURL());
  content::RenderFrameHost* c_bottom = ChildFrameAt(c_middle, 0);

  // Save the size of the frame to be fullscreened.
  gfx::Size c_middle_original_size = GetFrameSize(c_middle);

  // Add fullscreenchange and resize event handlers to all frames.
  AddFullscreenChangeListener(a_top, "a_top");
  AddFullscreenChangeListener(a_bottom, "a_bottom");
  AddFullscreenChangeListener(b_first, "b_first");
  AddFullscreenChangeListener(b_second, "b_second");
  AddFullscreenChangeListener(c_top, "c_top");
  AddFullscreenChangeListener(c_middle, "c_middle");
  AddFullscreenChangeListener(c_bottom, "c_bottom");
  AddResizeListener(c_middle, GetScreenSize());

  // Note that expected fullscreenchange events do NOT include |b_first| and
  // |c_bottom|, which aren't on the ancestor chain of |c_middle|.
  // WaitForMultipleFullscreenEvents() below will fail if it hears an
  // unexpected fullscreenchange from one of these frames.
  std::set<std::string> expected_events = {"a_top", "a_bottom", "b_second",
                                           "c_top", "c_middle"};

  // Fullscreen a <div> inside |c_middle|.  Block until (1) the
  // fullscreenchange events in |c_middle| and all its ancestors send a
  // response, (2) |c_middle| is resized to fill the whole screen, and (3) the
  // browser finishes the fullscreen transition.
  {
    content::DOMMessageQueue queue(web_contents);
    ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
    EXPECT_TRUE(ExecJs(c_middle, "activateFullscreen()"));
    WaitForMultipleFullscreenEvents(expected_events, queue);
    waiter.Wait();
  }

  // Verify that the browser has entered fullscreen for the current tab.
  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
  EXPECT_TRUE(web_contents->IsFullscreen());

  // Check document.webkitFullscreenElement.  It should point to corresponding
  // <iframe> element IDs on |c_middle|'s ancestor chain, and it should be null
  // in b_first and c_bottom.
  EXPECT_EQ("child-0", GetFullscreenElementId(a_top));
  EXPECT_EQ("child-1", GetFullscreenElementId(a_bottom));
  EXPECT_EQ("child-0", GetFullscreenElementId(b_second));
  EXPECT_EQ("child-0", GetFullscreenElementId(c_top));
  EXPECT_EQ("fullscreen-div", GetFullscreenElementId(c_middle));
  EXPECT_EQ("none", GetFullscreenElementId(b_first));
  EXPECT_EQ("none", GetFullscreenElementId(c_bottom));

  // Verify that the fullscreen element and all <iframe> elements on its
  // ancestor chain have fullscreen style, but other frames do not.
  EXPECT_TRUE(ElementHasFullscreenStyle(a_top, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenStyle(a_bottom, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenStyle(a_bottom, "child-1"));
  EXPECT_TRUE(ElementHasFullscreenStyle(b_second, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenStyle(c_top, "child-0"));
  EXPECT_TRUE(ElementHasFullscreenStyle(c_middle, "fullscreen-div"));
  EXPECT_FALSE(ElementHasFullscreenStyle(c_middle, "child-0"));

  // Now exit fullscreen by pressing escape.  Wait for all fullscreenchange
  // events fired for fullscreen exit and verify that the bottom frame was
  // resized back to its original size.
  AddResizeListener(c_middle, c_middle_original_size);
  {
    content::DOMMessageQueue queue(web_contents);
    ui_test_utils::FullscreenWaiter waiter(browser(),
                                           {.tab_fullscreen = false});
    ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_ESCAPE,
                                                false, false, false, false));
    WaitForMultipleFullscreenEvents(expected_events, queue);
    waiter.Wait();
  }

  EXPECT_FALSE(browser()->GetWindow()->IsFullscreen());

  // Check that document.webkitFullscreenElement has been cleared in all
  // frames.
  EXPECT_EQ("none", GetFullscreenElementId(a_top));
  EXPECT_EQ("none", GetFullscreenElementId(a_bottom));
  EXPECT_EQ("none", GetFullscreenElementId(b_first));
  EXPECT_EQ("none", GetFullscreenElementId(b_second));
  EXPECT_EQ("none", GetFullscreenElementId(c_top));
  EXPECT_EQ("none", GetFullscreenElementId(c_middle));
  EXPECT_EQ("none", GetFullscreenElementId(c_bottom));

  // Verify that all fullscreen styles have been cleared.
  EXPECT_FALSE(ElementHasFullscreenStyle(a_top, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenStyle(a_bottom, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenStyle(a_bottom, "child-1"));
  EXPECT_FALSE(ElementHasFullscreenStyle(b_second, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenStyle(c_top, "child-0"));
  EXPECT_FALSE(ElementHasFullscreenStyle(c_middle, "fullscreen-div"));
  EXPECT_FALSE(ElementHasFullscreenStyle(c_middle, "child-0"));
}

// Test that deleting a RenderWidgetHost that holds the mouse lock won't cause a
// crash. https://crbug.com/40472780.

// Flaky on multiple builders. https://crbug.com/40678582
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       DISABLED_RenderWidgetHostDeletedWhileMouseLocked) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child = ChildFrameAt(main_frame, 0);

  EXPECT_TRUE(ExecJs(child, "document.body.requestPointerLock()",
                     content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
  EXPECT_EQ(true,
            EvalJs(child, "document.body == document.pointerLockElement"));
  EXPECT_TRUE(main_frame->GetView()->IsPointerLocked());

  EXPECT_TRUE(ExecJs(main_frame,
                     "document.querySelector('iframe').parentNode."
                     "removeChild(document.querySelector('iframe'))"));
  EXPECT_FALSE(main_frame->GetView()->IsPointerLocked());
}

#if BUILDFLAG(ENABLE_PDF)
// Base test class for interactive tests which load and test PDF files.
class SitePerProcessInteractivePDFTest
    : public base::test::WithFeatureOverride,
      public SitePerProcessInteractiveBrowserTest {
 public:
  SitePerProcessInteractivePDFTest()
      : base::test::WithFeatureOverride(chrome_pdf::features::kPdfOopif) {}

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

  ~SitePerProcessInteractivePDFTest() override = default;

  void SetUpOnMainThread() override {
    SitePerProcessInteractiveBrowserTest::SetUpOnMainThread();
    if (UseOopif()) {
      factory_ = std::make_unique<pdf::TestMimeHandlerStreamManagerFactory>();
    } else {
      auto factory =
          std::make_unique<guest_view::TestGuestViewManagerFactory>();
      test_guest_view_manager_ = factory->GetOrCreateTestGuestViewManager(
          browser()->GetProfile(), extensions::ExtensionsAPIClient::Get()
                                       ->CreateGuestViewManagerDelegate());
      factory_ = std::move(factory);
    }
  }

  void TearDownOnMainThread() override {
    test_guest_view_manager_ = nullptr;
    factory_ = std::monostate();
    SitePerProcessInteractiveBrowserTest::TearDownOnMainThread();
  }

  bool UseOopif() const { return GetParam(); }

 protected:
  guest_view::TestGuestViewManager* GetTestGuestViewManager() const {
    return test_guest_view_manager_;
  }

  pdf::TestMimeHandlerStreamManager* GetTestMimeHandlerStreamManager() const {
    return std::get<std::unique_ptr<pdf::TestMimeHandlerStreamManagerFactory>>(
               factory_)
        ->GetTestMimeHandlerStreamManager(
            browser()->GetTabStripModel()->GetActiveWebContents());
  }

  void CreateTestMimeHandlerStreamManager() const {
    std::get<std::unique_ptr<pdf::TestMimeHandlerStreamManagerFactory>>(
        factory_)
        ->CreateMimeHandlerStreamManager(
            browser()->GetTabStripModel()->GetActiveWebContents());
  }

  void WaitUntilPdfLoaded(content::RenderFrameHost* embedder_host) {
    if (UseOopif()) {
      ASSERT_TRUE(
          GetTestMimeHandlerStreamManager()->WaitUntilPdfLoaded(embedder_host));
    } else {
      auto* guest_view =
          GetTestGuestViewManager()->WaitForSingleGuestViewCreated();
      ASSERT_TRUE(guest_view);
      auto* embedder_web_contents =
          browser()->GetTabStripModel()->GetActiveWebContents();
      EXPECT_NE(embedder_web_contents->GetPrimaryMainFrame(),
                guest_view->GetGuestMainFrame());

      extensions::TestMimeHandlerViewGuest::WaitForGuestLoadStartThenStop(
          guest_view);
    }
  }

 private:
  std::variant<std::monostate,
               std::unique_ptr<guest_view::TestGuestViewManagerFactory>,
               std::unique_ptr<pdf::TestMimeHandlerStreamManagerFactory>>
      factory_;
  raw_ptr<guest_view::TestGuestViewManager> test_guest_view_manager_;
};

// This test loads a PDF inside an OOPIF and then verifies that context menu
// shows up at the correct position.
// TODO(crbug.com/40897346, crbug.com/327338993): Fix flaky test.
#if BUILDFLAG(IS_WIN) || (BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER))
#define MAYBE_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame \
  DISABLED_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame
#else
#define MAYBE_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame \
  ContextMenuPositionForEmbeddedPDFInCrossOriginFrame
#endif  // BUILDFLAG(IS_WIN) || (BUILDFLAG(IS_LINUX) &&
        // defined(ADDRESS_SANITIZER))
IN_PROC_BROWSER_TEST_P(
    SitePerProcessInteractivePDFTest,
    MAYBE_ContextMenuPositionForEmbeddedPDFInCrossOriginFrame) {
  // Navigate to a page with an <iframe>.
  GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  if (!UseOopif()) {
    // Initially, no guests are created.
    EXPECT_EQ(0U, GetTestGuestViewManager()->num_guests_created());
  }

  content::WebContents* active_web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Change the position of the <iframe> inside the page.
  EXPECT_TRUE(ExecJs(active_web_contents,
                     "document.querySelector('iframe').style ="
                     " 'margin-left: 100px; margin-top: 100px;';"));

  // Navigate subframe to a cross-site page with an embedded PDF.
  GURL frame_url =
      embedded_test_server()->GetURL("b.com", "/page_with_embedded_pdf.html");

  // Ensure the page finishes loading without crashing.
  EXPECT_TRUE(NavigateIframeToURL(active_web_contents, "test", frame_url));
  content::RenderFrameHost* iframe_host =
      ChildFrameAt(active_web_contents->GetPrimaryMainFrame(), 0);
  ASSERT_TRUE(iframe_host);
  content::RenderFrameHost* embedder_host = ChildFrameAt(iframe_host, 0);
  ASSERT_TRUE(embedder_host);
  WaitUntilPdfLoaded(embedder_host);

  content::RenderWidgetHostView* child_view = iframe_host->GetView();

  ContextMenuWaiter menu_waiter;

  // Declaring a lambda to send a right-button mouse event to the embedder
  // frame.
  auto send_right_mouse_event = [](content::RenderWidgetHost* host, int x,
                                   int y, blink::WebInputEvent::Type type) {
    blink::WebMouseEvent event;
    event.SetTimeStamp(blink::WebInputEvent::GetStaticTimeStampForTests());
    event.SetPositionInWidget(x, y);
    event.button = blink::WebMouseEvent::Button::kRight;
    event.SetType(type);
    host->ForwardMouseEvent(event);
  };

  send_right_mouse_event(child_view->GetRenderWidgetHost(), 10, 20,
                         blink::WebInputEvent::Type::kMouseDown);
  send_right_mouse_event(child_view->GetRenderWidgetHost(), 10, 20,
                         blink::WebInputEvent::Type::kMouseUp);
  menu_waiter.WaitForMenuOpenAndClose();

  gfx::Point point_in_root_window =
      child_view->TransformPointToRootCoordSpace(gfx::Point(10, 20));

  EXPECT_EQ(point_in_root_window.x(), menu_waiter.params().x);
  EXPECT_EQ(point_in_root_window.y(), menu_waiter.params().y);
}

IN_PROC_BROWSER_TEST_P(SitePerProcessInteractivePDFTest,
                       LoadingPdfDoesNotStealFocus) {
  // Load test HTML, and verify the text area has focus.
  GURL main_url(embedded_test_server()->GetURL("/pdf/two_iframes.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  auto* embedder_web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Make sure we can see the iframe's document.
  ASSERT_TRUE(
      content::EvalJs(embedder_web_contents,
                      "new Promise((resolve) => {"
                      "  var iframe1 = document.getElementById('iframe1');"
                      "  var iframe1doc = iframe1.contentDocument;"
                      "  resolve(iframe1doc != null);"
                      "});")
          .ExtractBool());

  // Make sure the text area is focused. First, we must explicitly focus the
  // child iframe containing the text area.
  content::RenderFrameHost* main_frame =
      embedder_web_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* child_text_area = ChildFrameAt(main_frame, 0);
  content::RenderFrameHost* iframe_pdf = ChildFrameAt(main_frame, 1);
  ASSERT_TRUE(iframe_pdf);
  ASSERT_TRUE(content::ExecJs(child_text_area, "window.focus();"));
  bool starts_focused =
      content::EvalJs(
          embedder_web_contents,
          "new Promise((resolve) => {"
          "  iframe1doc = "
          "      document.getElementById('iframe1').contentDocument;"
          "  function timeoutFcn(n) {"
          "    if (n == 0 || iframe1doc.hasFocus()) {"
          "      resolve(iframe1doc.hasFocus());"
          "      return;"
          "    }"
          "    window.console.log('Recursing: n = ' + n);"
          "    setTimeout(() => { timeoutFcn(n-1); }, 1000);"
          "  };"
          "  timeoutFcn(5);"
          "});")
          .ExtractBool();
  if (!starts_focused) {
    LOG(ERROR) << "Embedder focused frame = "
               << embedder_web_contents->GetFocusedFrame()
               << ", main frame = " << main_frame
               << ", embedder_contents_focused = "
               << IsRenderWidgetHostFocused(
                      embedder_web_contents->GetRenderWidgetHostView()
                          ->GetRenderWidgetHost())
               << ", iframe_text = " << child_text_area
               << ", iframe_pdf = " << iframe_pdf;
  }
  ASSERT_TRUE(starts_focused);

  if (UseOopif()) {
    // Create the manager first, since the following script doesn't block until
    // navigation is complete.
    CreateTestMimeHandlerStreamManager();
  }

  GURL pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
  ASSERT_TRUE(content::ExecJs(
      embedder_web_contents,
      content::JsReplace("document.getElementById('iframe2').src = $1;",
                         pdf_url.spec())));

  WaitUntilPdfLoaded(iframe_pdf);

  // Make sure the text area still has focus.
  ASSERT_TRUE(
      content::EvalJs(
          embedder_web_contents,
          "new Promise((resolve) => {"
          "  iframe1doc = "
          "      document.getElementById('iframe1').contentDocument;"
          "  text_area = iframe1doc.getElementById('text_area');"
          "  text_area_is_active = iframe1doc.activeElement == text_area;"
          "  resolve(iframe1doc.hasFocus() && text_area_is_active);"
          "});")
          .ExtractBool());
}

// TODO(crbug.com/40268279): Stop testing both modes after OOPIF PDF viewer
// launches.
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(SitePerProcessInteractivePDFTest);
#endif  // BUILDFLAG(ENABLE_PDF)

class SitePerProcessAutofillTest : public SitePerProcessInteractiveBrowserTest {
 public:
  SitePerProcessAutofillTest() : SitePerProcessInteractiveBrowserTest() {}

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

  ~SitePerProcessAutofillTest() override = default;

  void SetupMainTab() {
    // Add a fresh new WebContents for which we add our own version of the
    // ChromePasswordManagerClient that uses a custom TestAutofillClient.
    std::unique_ptr<content::WebContents> new_contents =
        content::WebContents::Create(
            content::WebContents::CreateParams(browser()
                                                   ->GetTabStripModel()
                                                   ->GetActiveWebContents()
                                                   ->GetBrowserContext()));
    ASSERT_TRUE(new_contents);
    ASSERT_FALSE(
        ChromePasswordManagerClient::FromWebContents(new_contents.get()));

    // Create ChromePasswordManagerClient and verify it exists for the new
    // WebContents.
    ChromePasswordManagerClient::CreateForWebContents(new_contents.get());
    ASSERT_TRUE(
        ChromePasswordManagerClient::FromWebContents(new_contents.get()));

    browser()->GetTabStripModel()->AppendWebContents(std::move(new_contents),
                                                     true);
  }

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

// Waits until transforming |sample_point| from |render_frame_host| coordinates
// to its root frame's view's coordinates matches |transformed_point| within a
// reasonable error margin less than or equal to |bound|. This method is used to
// verify CSS changes on OOPIFs have been applied properly and the corresponding
// compositor frame is updated as well. This way we can rest assured that the
// future transformed and reported bounds for the elements inside
// |render_frame_host| are correct.
void WaitForFramePositionUpdated(content::RenderFrameHost* render_frame_host,
                                 const gfx::Point& sample_point,
                                 const gfx::Point& transformed_point,
                                 float bound) {
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return (transformed_point -
            render_frame_host->GetView()->TransformPointToRootCoordSpace(
                sample_point))
               .Length() <= bound;
  }));
}

// This test verifies that when clicking outside the bounds of a date picker
// associated with an <input> inside an OOPIF, the RenderWidgetHostImpl
// corresponding to the WebPagePopup is destroyed (see
// https://crbug.com/41289866).
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       ShowAndHideDatePopupInOOPIFMultipleTimes) {
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  content::RenderFrameHost* child_frame =
      ChildFrameAt(browser()
                       ->GetTabStripModel()
                       ->GetActiveWebContents()
                       ->GetPrimaryMainFrame(),
                   0);

  // Add <input type='date'> to the child frame. Adjust the positions that we
  // know where to click to dismiss the popup.
  ASSERT_TRUE(ExecJs(
      child_frame,
      "var input = document.createElement('input');"
      "input.type = 'date';"
      "input.value = '2008-09-02';"
      "document.body.appendChild(input);"
      "input.style = 'position: fixed; left: 0px; top: 10px; border: none;' +"
      "              'width: 120px; height: 20px;';"));

  // Cache current date value for a sanity check later.
  std::string cached_date = EvalJs(child_frame, "input.value;").ExtractString();

  // We use this to determine whether a new RenderWidgetHost is created or an
  // old one is removed.
  size_t default_widget_count = GetNumberOfRenderWidgetHosts();

  // Repeatedly invoke the date picker and then click outside the bounds of the
  // widget to dismiss it and each time verify that a new RenderWidgetHost is
  // added when showing the date picker and a RenderWidgetHost is destroyed when
  // it is dismissed.
  for (size_t tries = 0; tries < 3U; tries++) {
    // Focus the <input>.
    ASSERT_TRUE(
        ExecJs(child_frame, "document.querySelector('input').focus();"));

    // Alt + Down seems to be working fine on all platforms.
    ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_DOWN, false,
                                                false, true, false));

    // We should get one more widget on the screen.
    WaitForRenderWidgetHostCount(default_widget_count + 1U);

    content::RenderWidgetHost* child_widget_host =
        child_frame->GetView()->GetRenderWidgetHost();

    // Now simulate a click outside the bounds of the popup.
    blink::WebMouseEvent event;
    event.SetTimeStamp(blink::WebInputEvent::GetStaticTimeStampForTests());
    // Click a little bit to the right and top of the <input>.
    event.SetPositionInWidget(130, 10);
    event.button = blink::WebMouseEvent::Button::kLeft;

    // Send a mouse down event.
    event.SetType(blink::WebInputEvent::Type::kMouseDown);
    child_widget_host->ForwardMouseEvent(event);

    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
    run_loop.Run();

    // Now send a mouse up event.
    event.SetType(blink::WebMouseEvent::Type::kMouseUp);
    child_widget_host->ForwardMouseEvent(event);

    // Wait until the popup disappears and we go back to the normal
    // RenderWidgetHost count.
    WaitForRenderWidgetHostCount(default_widget_count);
  }

  // To make sure we never clicked into the date picker, get current date value
  // and make sure it matches the cached value.
  std::string date = EvalJs(child_frame, "input.value;").ExtractString();
  EXPECT_EQ(cached_date, date) << "Cached date was '" << cached_date
                               << "' but current date is '" << date << "'.";
}

// There is a problem of missing keyup events with the command key after
// the NSEvent is sent to NSApplication in ui/base/test/ui_controls_mac.mm .
// This test is disabled on only the Mac until the problem is resolved.
// See http://crbug.com/40390352 for more information.
#if BUILDFLAG(IS_MAC)
#define MAYBE_SubframeAnchorOpenedInBackgroundTab \
  DISABLED_SubframeAnchorOpenedInBackgroundTab
#else
#define MAYBE_SubframeAnchorOpenedInBackgroundTab \
  SubframeAnchorOpenedInBackgroundTab
#endif
// Tests that ctrl-click in a subframe results in a background, not a foreground
// tab - see https://crbug.com/40559397.  This test is somewhat similar to
// CtrlClickShouldEndUpIn*ProcessTest tests, but this test has to simulate an
// actual mouse click.
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       MAYBE_SubframeAnchorOpenedInBackgroundTab) {
  // Setup the test page - the ctrl-clicked link should be in a subframe.
  GURL main_url(embedded_test_server()->GetURL("foo.com", "/iframe.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  GURL subframe_url(embedded_test_server()->GetURL(
      "bar.com", "/frame_tree/anchor_to_same_site_location.html"));
  content::WebContents* old_contents =
      browser()->GetTabStripModel()->GetWebContentsAt(0);
  ASSERT_TRUE(NavigateIframeToURL(old_contents, "test", subframe_url));
  content::RenderFrameHost* subframe = ChildFrameAt(old_contents, 0);
  ASSERT_TRUE(subframe);
  EXPECT_EQ(subframe_url, subframe->GetLastCommittedURL());

  // Simulate the ctrl-return to open the anchor's link in a new background tab.
  EXPECT_TRUE(ExecJs(
      subframe, "document.getElementById('test-anchor-no-target').focus();"));
  content::WebContents* new_contents = nullptr;
  {
    content::WebContentsAddedObserver new_tab_observer;
#if BUILDFLAG(IS_MAC)
    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
        old_contents->GetTopLevelNativeWindow(), ui::VKEY_RETURN, false, false,
        false, true /* cmd */));
#else
    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
        old_contents->GetTopLevelNativeWindow(), ui::VKEY_RETURN,
        true /* ctrl */, false, false, false));
#endif
    new_contents = new_tab_observer.GetWebContents();
  }

  // Verify that the new content has loaded the expected contents.
  GURL target_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
  EXPECT_TRUE(WaitForLoadStop(new_contents));
  EXPECT_EQ(target_url,
            new_contents->GetPrimaryMainFrame()->GetLastCommittedURL());

  // Verify that the anchor opened in a new background tab.
  EXPECT_EQ(2, browser()->GetTabStripModel()->count());
  EXPECT_EQ(0, browser()->GetTabStripModel()->active_index());
  EXPECT_EQ(0,
            browser()->GetTabStripModel()->GetIndexOfWebContents(old_contents));
  EXPECT_EQ(1,
            browser()->GetTabStripModel()->GetIndexOfWebContents(new_contents));
}

// Check that window.focus works for cross-process popups.
// Flaky on ChromeOS debug and ASAN builds. https://crbug.com/40840456
// Flaky on Linux https://crbug.com/40847510.
// Flaky on Win https://crbug.com/40848559.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || \
    (BUILDFLAG(IS_CHROMEOS) &&                  \
     (!defined(NDEBUG) || defined(ADDRESS_SANITIZER)))
#define MAYBE_PopupWindowFocus DISABLED_PopupWindowFocus
#else
#define MAYBE_PopupWindowFocus PopupWindowFocus
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                       MAYBE_PopupWindowFocus) {
  GURL main_url(embedded_test_server()->GetURL("/page_with_focus_events.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  // Set window.name on main page.  This will be used to identify the page
  // later when it sends messages from its focus/blur events.
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_TRUE(ExecJs(web_contents, "window.name = 'main'",
                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));

  // Open a popup for a cross-site page.
  GURL popup_url =
      embedded_test_server()->GetURL("foo.com", "/page_with_focus_events.html");
  content::TestNavigationObserver popup_observer(nullptr);
  popup_observer.StartWatchingNewWebContents();
  EXPECT_TRUE(
      ExecJs(web_contents, "openPopup('" + popup_url.spec() + "','popup')"));
  popup_observer.Wait();
  ASSERT_EQ(2, browser()->GetTabStripModel()->count());
  content::WebContents* popup =
      browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_EQ(popup_url, popup->GetLastCommittedURL());
  EXPECT_NE(popup, web_contents);

  // Switch focus to the original tab, since opening a popup also focused it.
  web_contents->GetDelegate()->ActivateContents(web_contents);
  EXPECT_EQ(web_contents,
            browser()->GetTabStripModel()->GetActiveWebContents());

  // Focus the popup via window.focus(), this needs user gesture.
  content::DOMMessageQueue main_queue(web_contents);
  content::DOMMessageQueue popup_queue(popup);
  ExecuteScriptAsync(web_contents, "focusPopup()");

  // Wait for main page to lose focus and for popup to gain focus.  Each event
  // will send a message, and the two messages can arrive in any order.
  std::string status;
  while (main_queue.WaitForMessage(&status)) {
    if (status == "\"main-lost-focus\"") {
      break;
    }
  }
  while (popup_queue.WaitForMessage(&status)) {
    if (status == "\"popup-got-focus\"") {
      break;
    }
  }

  // The popup should be focused now.
  EXPECT_EQ(popup, browser()->GetTabStripModel()->GetActiveWebContents());
}

INSTANTIATE_TEST_SUITE_P(
    All,
    SitePerProcessInteractiveFencedFrameBrowserTest,
    ::testing::Values("fencedframe", "iframe"),
    &SitePerProcessInteractiveFencedFrameBrowserTest::DescribeParams);
