focus-after-close.html (7538B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <meta name="viewport" content="width=device-width,initial-scale=1"> 4 <title>Test focus is moved to the previously focused element when dialog is closed</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/resources/testdriver.js"></script> 8 <script src="/resources/testdriver-actions.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 11 <body> 12 <input /> 13 <dialog> 14 <button id="button1">This is a button1</button> 15 <button id="button2">This is a button2</button> 16 <button id="button3">This is a button3</button> 17 </dialog> 18 <script> 19 20 // Test focus is moved to the previously focused element 21 function test_move_to_previously_focused(showModal) { 22 const input = document.querySelector("input"); 23 input.focus(); 24 const dialog = document.querySelector("dialog"); 25 if (showModal) { 26 dialog.showModal(); 27 } else { 28 dialog.show(); 29 } 30 dialog.close(); 31 32 assert_equals(document.activeElement, input); 33 } 34 35 // Test focus is moved to the previously focused element with some complex dialog usage 36 async function test_move_to_previously_focused_with_complex_dialog_usage(showModal) { 37 const input = document.querySelector("input"); 38 input.focus(); 39 const dialog = document.querySelector("dialog"); 40 if (showModal) { 41 dialog.showModal(); 42 } else { 43 dialog.show(); 44 } 45 46 const button1 = document.getElementById("button1"); 47 const button2 = document.getElementById("button2"); 48 const button3 = document.getElementById("button3"); 49 50 await test_driver.click(button1); 51 await test_driver.click(button2); 52 await test_driver.click(button3); 53 54 dialog.close(); 55 56 assert_equals(document.activeElement, input); 57 } 58 59 // Test focus is moved to the previously focused element even if that element moved in between 60 function test_element_move_in_between_show_close(showModal) { 61 const input = document.querySelector("input"); 62 input.focus(); 63 const dialog = document.querySelector("dialog"); 64 65 assert_equals(input.nextElementSibling, dialog, "Element is in correct position"); 66 67 if (showModal) { 68 dialog.showModal(); 69 } else { 70 dialog.show(); 71 } 72 73 document.body.appendChild(input); 74 assert_not_equals(input.nextElementSibling, dialog, "Element should have moved"); 75 76 dialog.close(); 77 assert_equals(document.activeElement, input, "Focus should be restored to previously focused input"); 78 79 // Clean up 80 document.body.insertBefore(input, dialog); 81 } 82 83 // Test focus is moved to the previously focused element even if that element moved to shadow root in between 84 function test_element_move_to_shadow_root_in_between_show_close(showModal) { 85 const input = document.querySelector("input"); 86 input.focus(); 87 const dialog = document.querySelector("dialog"); 88 89 assert_equals(input.nextElementSibling, dialog, "Element is in correct position"); 90 91 if (showModal) { 92 dialog.showModal(); 93 } else { 94 dialog.show(); 95 } 96 97 const shadowHost = document.createElement("div"); 98 const shadowRoot = shadowHost.attachShadow({mode: "open"}); 99 shadowRoot.appendChild(input); 100 document.body.appendChild(shadowHost); 101 102 assert_not_equals(input.nextElementSibling, dialog, "Element should have moved"); 103 104 dialog.close(); 105 assert_equals(shadowRoot.activeElement, input, "Focus should be restored to previously focused input"); 106 assert_equals(document.activeElement, shadowHost, "document.activeElement should be previously focused input's shadow DOM host"); 107 108 // Clean up 109 document.body.insertBefore(input, dialog); 110 shadowHost.remove(); 111 } 112 113 // Test focus is moved to <body> if the previously focused 114 // element can't be focused 115 function test_move_to_body_if_fails(showModal) { 116 const input = document.querySelector("input"); 117 input.focus(); 118 const dialog = document.querySelector("dialog"); 119 if (showModal) { 120 dialog.showModal(); 121 } else { 122 dialog.show(); 123 } 124 dialog.close(); 125 input.remove(); 126 assert_equals(document.activeElement, document.body); 127 128 // Clean up 129 document.body.insertBefore(input, dialog); 130 } 131 132 // Test focus is moved to shadow host if the previously 133 // focused element is a shadow node. 134 function test_move_to_shadow_host(showModal) { 135 const shadowHost = document.createElement("div"); 136 137 const shadowRoot = shadowHost.attachShadow({mode: "open"}); 138 shadowRoot.appendChild(document.createElement("input")); 139 140 document.body.appendChild(shadowHost); 141 const inputElement = shadowRoot.querySelector("input"); 142 inputElement.focus(); 143 144 assert_equals(document.activeElement, shadowHost); 145 assert_equals(shadowRoot.activeElement, inputElement); 146 147 const dialog = document.querySelector("dialog"); 148 if (showModal) { 149 dialog.showModal(); 150 } else { 151 dialog.show(); 152 } 153 dialog.close(); 154 155 assert_equals(document.activeElement, shadowHost); 156 assert_equals(shadowRoot.activeElement, inputElement); 157 158 // Clean up 159 shadowHost.remove(); 160 } 161 162 // Test moving the focus doesn't scroll the viewport 163 async function test_move_focus_dont_scroll_viewport(showModal) { 164 const outViewPortButton = document.createElement("button"); 165 outViewPortButton.style.top = (window.innerHeight + 10).toString() + "px"; 166 outViewPortButton.style.position = "absolute"; 167 document.body.appendChild(outViewPortButton); 168 169 await new Promise(resolve => { 170 document.addEventListener("scroll", () => { 171 if (resolve && document.documentElement.scrollTop) { 172 resolve(); 173 resolve = null; 174 } 175 }); 176 outViewPortButton.focus(); 177 }); 178 179 // Since the outViewPortButton is focused, so the viewport should be 180 // scrolled to it 181 assert_true(document.documentElement.scrollTop > 0 ); 182 183 const dialog = document.querySelector("dialog"); 184 if (showModal) { 185 dialog.showModal(); 186 } else { 187 dialog.show(); 188 } 189 190 window.scrollTo(0, 0); 191 assert_equals(document.documentElement.scrollTop, 0); 192 193 dialog.close(); 194 assert_equals(document.documentElement.scrollTop, 0); 195 196 assert_equals(document.activeElement, outViewPortButton); 197 } 198 199 test(() => { 200 test_move_to_previously_focused(true); 201 test_move_to_previously_focused(false); 202 }, "Focus should be moved to the previously focused element (Simple dialog usage)"); 203 204 promise_test(async () => { 205 await test_move_to_previously_focused_with_complex_dialog_usage(true); 206 await test_move_to_previously_focused_with_complex_dialog_usage(false); 207 }, "Focus should be moved to the previously focused element (Complex dialog usage)"); 208 209 test(() => { 210 test_element_move_in_between_show_close(true); 211 test_element_move_in_between_show_close(false); 212 }, "Focus should be moved to the previously focused element even if it has moved in between show/close"); 213 214 test(() => { 215 test_element_move_to_shadow_root_in_between_show_close(true); 216 test_element_move_to_shadow_root_in_between_show_close(false); 217 }, "Focus should be moved to the previously focused element even if it has moved to shadow DOM root in between show/close"); 218 219 test(() => { 220 test_move_to_body_if_fails(true); 221 test_move_to_body_if_fails(false); 222 }, "Focus should be moved to the body if the previously focused element is removed"); 223 224 test(() => { 225 test_move_to_shadow_host(true); 226 test_move_to_shadow_host(false); 227 }, "Focus should be moved to the shadow DOM host if the previouly focused element is a shadow DOM node"); 228 229 promise_test(async () => { 230 await test_move_focus_dont_scroll_viewport(true); 231 await test_move_focus_dont_scroll_viewport(false); 232 }, "Focus should not scroll if the previously focused element is outside the viewport"); 233 </script> 234 </body>