Move the focus to the previously focused element when <dialog> is closed

This change is based on the latest changes for the <dialog> element in
https://github.com/whatwg/html/pull/6531, such that closing
<dialog> should move the focus to the previously focused element.

Differential Revision: https://phabricator.services.mozilla.com/D109726

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1660271
gecko-commit: 130bfa5327532a2b188e4cc06f5e1de7183327e8
gecko-reviewers: smaug
diff --git a/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html b/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
new file mode 100644
index 0000000..4595961
--- /dev/null
+++ b/html/semantics/interactive-elements/the-dialog-element/focus-after-close.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<meta charset=urf-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 <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);
+  document.body.appendChild(input);
+}
+
+// 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);
+}
+
+// Test moving the focus doesn't scroll the viewport
+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);
+
+  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_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');
+
+test(() => {
+  test_move_focus_dont_scroll_viewport(true);
+  test_move_focus_dont_scroll_viewport(false);
+}, 'Focus should not scroll if the previously focused element is outside the viewport');
+</script>
+</body>