chromium/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html

<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Test focus is moved to the previously focused element when dialog is closed</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>

<body>
<input />
<dialog>
    <button id="button1">This is a button1</button>
    <button id="button2">This is a button2</button>
    <button id="button3">This is a button3</button>
</dialog>
<script>

// Test focus is moved to the previously focused element
function test_move_to_previously_focused(showModal) {
  const input = document.querySelector("input");
  input.focus();
  const dialog = document.querySelector("dialog");
  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }
  dialog.close();

  assert_equals(document.activeElement, input);
}

// Test focus is moved to the previously focused element with some complex dialog usage
async function test_move_to_previously_focused_with_complex_dialog_usage(showModal) {
  const input = document.querySelector("input");
  input.focus();
  const dialog = document.querySelector("dialog");
  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }

  const button1 = document.getElementById("button1");
  const button2 = document.getElementById("button2");
  const button3 = document.getElementById("button3");

  await test_driver.click(button1);
  await test_driver.click(button2);
  await test_driver.click(button3);

  dialog.close();

  assert_equals(document.activeElement, input);
}

// Test focus is moved to the previously focused element even if that element moved in between
function test_element_move_in_between_show_close(showModal) {
  const input = document.querySelector("input");
  input.focus();
  const dialog = document.querySelector("dialog");

  assert_equals(input.nextElementSibling, dialog, "Element is in correct position");

  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }

  document.body.appendChild(input);
  assert_not_equals(input.nextElementSibling, dialog, "Element should have moved");

  dialog.close();
  assert_equals(document.activeElement, input, "Focus should be restored to previously focused input");

  // Clean up
  document.body.insertBefore(input, dialog);
}

// Test focus is moved to the previously focused element even if that element moved to shadow root in between
function test_element_move_to_shadow_root_in_between_show_close(showModal) {
  const input = document.querySelector("input");
  input.focus();
  const dialog = document.querySelector("dialog");

  assert_equals(input.nextElementSibling, dialog, "Element is in correct position");

  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }

  const shadowHost = document.createElement("div");
  const shadowRoot = shadowHost.attachShadow({mode: "open"});
  shadowRoot.appendChild(input);
  document.body.appendChild(shadowHost);

  assert_not_equals(input.nextElementSibling, dialog, "Element should have moved");

  dialog.close();
  assert_equals(shadowRoot.activeElement, input, "Focus should be restored to previously focused input");
  assert_equals(document.activeElement, shadowHost, "document.activeElement should be previously focused input's shadow DOM host");

  // Clean up
  document.body.insertBefore(input, dialog);
  shadowHost.remove();
}

// Test focus is moved to <body> if the previously focused
// element can't be focused
function test_move_to_body_if_fails(showModal) {
  const input = document.querySelector("input");
  input.focus();
  const dialog = document.querySelector("dialog");
  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }
  dialog.close();
  input.remove();
  assert_equals(document.activeElement, document.body);

  // Clean up
  document.body.insertBefore(input, dialog);
}

// Test focus is moved to shadow host if the previously
// focused element is a shadow node.
function test_move_to_shadow_host(showModal) {
  const shadowHost = document.createElement("div");

  const shadowRoot = shadowHost.attachShadow({mode: "open"});
  shadowRoot.appendChild(document.createElement("input"));

  document.body.appendChild(shadowHost);
  const inputElement = shadowRoot.querySelector("input");
  inputElement.focus();

  assert_equals(document.activeElement, shadowHost);
  assert_equals(shadowRoot.activeElement, inputElement);

  const dialog = document.querySelector("dialog");
  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }
  dialog.close();

  assert_equals(document.activeElement, shadowHost);
  assert_equals(shadowRoot.activeElement, inputElement);

  // Clean up
  shadowHost.remove();
}

// Test moving the focus doesn't scroll the viewport
async function test_move_focus_dont_scroll_viewport(showModal) {
  const outViewPortButton = document.createElement("button");
  outViewPortButton.style.top = (window.innerHeight + 10).toString() + "px";
  outViewPortButton.style.position = "absolute";
  document.body.appendChild(outViewPortButton);

  await new Promise(resolve => {
    document.addEventListener("scroll", resolve, { once: true });
    outViewPortButton.focus();
  });

  // Since the outViewPortButton is focused, so the viewport should be
  // scrolled to it
  assert_true(document.documentElement.scrollTop > 0 );

  const dialog = document.querySelector("dialog");
  if (showModal) {
    dialog.showModal();
  } else {
    dialog.show();
  }

  window.scrollTo(0, 0);
  assert_equals(document.documentElement.scrollTop, 0);

  dialog.close();
  assert_equals(document.documentElement.scrollTop, 0);

  assert_equals(document.activeElement, outViewPortButton);
}

test(() => {
  test_move_to_previously_focused(true);
  test_move_to_previously_focused(false);
}, "Focus should be moved to the previously focused element (Simple dialog usage)");

promise_test(async () => {
  await test_move_to_previously_focused_with_complex_dialog_usage(true);
  await test_move_to_previously_focused_with_complex_dialog_usage(false);
}, "Focus should be moved to the previously focused element (Complex dialog usage)");

test(() => {
  test_element_move_in_between_show_close(true);
  test_element_move_in_between_show_close(false);
}, "Focus should be moved to the previously focused element even if it has moved in between show/close");

test(() => {
  test_element_move_to_shadow_root_in_between_show_close(true);
  test_element_move_to_shadow_root_in_between_show_close(false);
}, "Focus should be moved to the previously focused element even if it has moved to shadow DOM root in between show/close");

test(() => {
  test_move_to_body_if_fails(true);
  test_move_to_body_if_fails(false);
}, "Focus should be moved to the body if the previously focused element is removed");

test(() => {
  test_move_to_shadow_host(true);
  test_move_to_shadow_host(false);
}, "Focus should be moved to the shadow DOM host if the previouly focused element is a shadow DOM node");

promise_test(async () => {
  await test_move_focus_dont_scroll_viewport(true);
  await test_move_focus_dont_scroll_viewport(false);
}, "Focus should not scroll if the previously focused element is outside the viewport");
</script>
</body>