focus-fixup-rule-one-no-dialogs.html (3778B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>Focus fixup rule one (no <dialog>s involved)</title> 4 <link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> 5 <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule"> 6 <link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#attr-fieldset-disabled"> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 10 <div> 11 <button id="button1">Button 1</button> 12 <button id="button2">Button 2</button> 13 <button id="button3">Button 3</button> 14 <fieldset id="fieldset1"><button id="button4">Button 4</button></fieldset> 15 <fieldset id="fieldset2" disabled><legend><button id="button5">Button 5</button></legend></fieldset> 16 <div id="div" tabindex="0">Div</div> 17 <div id="editable" contenteditable=true>editor</div> 18 <button id="button6">Button 6</button> 19 </div> 20 21 <script> 22 "use strict"; 23 24 function test_focus_fixup(selector, change, expectSync = false) { 25 promise_test(async function(t) { 26 // Make sure we're not running from a ResizeObserver / etc notification. 27 await new Promise(r => t.step_timeout(r, 0)); 28 29 const el = document.querySelector(selector); 30 el.focus(); 31 32 assert_equals(document.activeElement, el, `Sanity check: ${selector} must start focused`); 33 34 change(el); 35 36 { 37 const fn = expectSync ? assert_not_equals : assert_equals; 38 fn(document.activeElement, el, `active element ${expectSync ? "is" : "isn't"} fixed-up sync`); 39 } 40 41 // We don't expect blur events in the sync case per spec yet, at least. 42 if (!expectSync) { 43 // Fixup should run after animation frame callbacks, right before the end of 44 // "update the rendering", so after resize observations. 45 let ranFirstRaf = false; 46 let ranRO = false; 47 48 let resolveDone; 49 let done = new Promise(r => { resolveDone = r; }); 50 51 requestAnimationFrame(t.step_func(() => { 52 ranFirstRaf = true; 53 assert_equals(document.activeElement, el, "activeElement shouldn't have changed yet (rAF)"); 54 requestAnimationFrame(t.step_func(() => { 55 assert_true(ranRO, "ResizeObserver should've ran on the previous frame"); 56 resolveDone(); 57 })); 58 })); 59 60 let ro = new ResizeObserver(t.step_func(() => { 61 assert_true(ranFirstRaf, "requestAnimationFrame should run before ResizeObserver"); 62 ranRO = true; 63 assert_equals(document.activeElement, el, "activeElement shouldn't have changed yet (ResizeObserver)"); 64 })); 65 66 // TODO: Test IntersectionObserver timing. It's a bit trickier because it 67 // uses its own task source and so on. 68 ro.observe(document.documentElement); 69 70 await done; 71 72 ro.disconnect(); 73 } 74 75 assert_not_equals(document.activeElement, el, "focus is fixed up"); 76 assert_equals(document.activeElement, document.body, "Body is focused"); 77 }, selector); 78 } 79 80 test_focus_fixup("#button1", function(button) { 81 button.disabled = true; 82 }); 83 84 test_focus_fixup("#button2", function(button) { 85 button.hidden = true; 86 }); 87 88 test_focus_fixup("#button3", function(button) { 89 button.remove(); 90 }, /* expectSync = */ true); 91 92 test_focus_fixup("#button4", function(button) { 93 document.querySelector("#fieldset1").disabled = true; 94 }); 95 96 test_focus_fixup("#button5", function(button) { 97 const fieldset = document.querySelector("#fieldset2"); 98 fieldset.insertBefore(document.createElement("legend"), fieldset.firstChild); 99 }); 100 101 test_focus_fixup("#div", function(div) { 102 div.removeAttribute("tabindex"); 103 }); 104 105 test_focus_fixup("#editable", function(div) { 106 div.contentEditable = false; 107 }); 108 109 test_focus_fixup("#button6", function(button) { 110 button.style.visibility = "hidden"; 111 }); 112 </script>