<!DOCTYPE html>
<title>Test Script-Based Focus for Fenced Frames</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/common/utils.js"></script>
<script src="resources/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>

<script src="/common/get-host-info.sub.js"></script>

<body>
<script>
async function AttemptButtonFocus(frame, expecting_focus) {
  await frame.execute(async (expecting_focus) => {
    const button = document.createElement("button");
    document.body.append(button);
    button.focus();
    assert_equals(document.activeElement == button, expecting_focus,
        "Button's focus should match expected focus");
  }, [expecting_focus]);
}

async function ClickOn(element, actions) {
  // Wait until the window size is initialized.
  while (window.innerWidth == 0) {
    await new Promise(resolve => requestAnimationFrame(resolve));
  }
  await actions.pointerMove(0, 0, {origin: element})
               .pointerDown()
               .pointerUp()
               .send();
}

async function SetupTest(click=true) {
  // Clean up any leftover frames from prior tests.
  document.querySelectorAll("fencedframe").forEach(e => {
    e.remove();
  })

  const actions = new test_driver.Actions();

  const frame = attachFencedFrameContext();
  const fencedframe_element = frame.element;

  if (click)
    await ClickOn(document.body, actions);

  return [actions, frame, fencedframe_element];
}

promise_test(async () => {
  const [actions, ff1, ff1_element] = await SetupTest(false);

  await ClickOn(ff1_element, actions);
  await AttemptButtonFocus(ff1, true);

  const button = document.createElement("button");
  document.body.append(button);
  button.focus();
  assert_true(document.activeElement == button,
      "The button should have focus");
  assert_false(navigator.userActivation.isActive,
      "Window should not have user activation");
}, "An embedder can focus out of a fenced frame");

promise_test(async () => {
  const [actions, frame, fencedframe_element] = await SetupTest();

  await AttemptButtonFocus(frame, false);
  await ClickOn(fencedframe_element, actions);
  await AttemptButtonFocus(frame, true);
}, "Fenced frames can't pull script focus until getting user activation");

promise_test(async t => {
  const [actions, frame, fencedframe_element] = await SetupTest();

  await ClickOn(fencedframe_element, actions);
  await ClickOn(document.body, actions);

  await AttemptButtonFocus(frame, true);

  // Give the browser time to receive the focus event before attempting
  // another focus.
  await t.step_timeout(async () => {await AttemptButtonFocus(frame, true);}, 20);
}, "Focused fenced frames can move programmatic focus within frame");

promise_test(async () => {
  const [actions, frame, fencedframe_element] = await SetupTest();

  await ClickOn(fencedframe_element, actions);
  await ClickOn(document.body, actions);

  // This will pull focus across a frame boundary and consume user activation.
  await AttemptButtonFocus(frame, true);

  await ClickOn(document.body, actions);
  await AttemptButtonFocus(frame, false);
}, "Script focus into a fenced frame consumes user activation");

promise_test(async () => {
  const [actions, ff1, ff1_element] = await SetupTest();

  const ff2 = attachFencedFrameContext();
  const ff2_element = ff2.element;

  await ClickOn(ff1_element, actions);

  await AttemptButtonFocus(ff1, true);
  await AttemptButtonFocus(ff2, false);
}, "Another fenced frame cannot pull focus out of a focused fenced frame");

promise_test(async () => {
  const [actions, ff1, ff1_element] = await SetupTest();

  await ClickOn(ff1_element, actions);
  await AttemptButtonFocus(ff1, true);

  await ff1.execute(async () => {
    const ff2 = attachFencedFrameContext();

    await ff2.execute(async () => {
      const button = document.createElement("button");
      document.body.append(button);
      button.focus();
      assert_false(document.activeElement == button,
          "The button should not have focus");
      assert_false(navigator.userActivation.isActive,
          "The fenced frame should not have user activation");
    });
  });
}, "A fenced frame nested in another fenced frame cannot pull focus");

promise_test(async () => {
  const [actions, ff1, ff1_element] = await SetupTest();

  await ClickOn(document.body, actions);

  const button = document.createElement("button");
  document.body.append(button);
  button.focus();
  assert_equals(document.activeElement, button,
      "The button in the main page should have focus.");

  await ff1.execute(async () => {
    assert_false(navigator.userActivation.isActive,
        "The fenced frame should not have user activation.");
    window.focus();
  });

  assert_equals(document.activeElement, button,
      "The button in the main page should still have focus.");
}, "A fenced frame cannot pull window.focus() without user activation");

promise_test(async () => {
  const [actions, ff1, ff1_element] = await SetupTest();

  await ClickOn(ff1_element, actions);
  await ClickOn(document.body, actions);

  const button = document.createElement("button");
  document.body.append(button);
  button.focus();
  assert_equals(document.activeElement, button,
      "The button should have focus.");

  await ff1.execute(async () => {
    assert_true(navigator.userActivation.isActive,
        "The fenced frame should have user activation.");
    window.focus();
    assert_false(navigator.userActivation.isActive,
        "The fenced frame's user activation should be consumed by the focus");
  });

  assert_equals(document.activeElement, document.body,
      "The main page's focus should be pulled away from the button.");
}, "A fenced frame can pull window.focus() after user activation");

promise_test(async () => {
  var actions = new test_driver.Actions();

  const frame = attachIFrameContext(
      {origin: get_host_info().HTTPS_REMOTE_ORIGIN});
  const iframe_element =
      document.body.getElementsByTagName('iframe')[0];

  await frame.execute(async () => {
    const button = document.createElement("button");
    document.body.append(button);
    button.focus();
    assert_equals(document.activeElement, button,
        "The button in the iframe should have focus.");
  }, [true]);

  const button = document.createElement("button");
  document.body.append(button);
  button.focus();
  assert_equals(document.activeElement, button,
      "The button in the main page should have focus.");
}, "An cross-origin iframe can pull focus back and forth without activation");

</script>
</body>
