popover-focus-4.html (5268B)
1 <!DOCTYPE html> 2 <meta charset="utf-8" /> 3 <title>Popover focus behaviors</title> 4 <meta name="timeout" content="long"> 5 <link rel="author" title="Tim Nguyen" href="https://github.com/nt1m"> 6 <link rel="author" title="Luke Warlow" href="mailto:lwarlow@igalia.com"> 7 <link rel=help href="https://open-ui.org/components/popover.research.explainer"> 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 <script src="/resources/testdriver.js"></script> 11 <script src="/resources/testdriver-actions.js"></script> 12 <script src="/resources/testdriver-vendor.js"></script> 13 <script src="resources/popover-utils.js"></script> 14 15 <div id=no-focus-candidate> 16 <button tabindex="0">Toggle popover</button> 17 <div popover id=no-focus-candidate-p> 18 Popover with <button tabindex="0">focusable element</button> 19 <div popover id=no-focus-candidate-p2>Nested popover with <button tabindex="0">focusable element</button></div> 20 </div> 21 </div> 22 <button id="after"></button> 23 24 <script> 25 async function testNoFocusCandidate() { 26 const invoker = document.querySelector('#no-focus-candidate>button'); 27 const popover = document.querySelector('#no-focus-candidate>[popover]'); 28 const nestedPopover = document.querySelector('#no-focus-candidate>[popover]>[popover]'); 29 invoker.focus(); // Make sure button is focused. 30 assert_equals(document.activeElement,invoker); 31 invoker.click(); // Activate the invoker 32 assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker'); 33 assert_equals(document.activeElement,invoker, 'invoker should still be focused'); 34 await sendTab(); 35 assert_equals(document.activeElement,popover.querySelector('button'),'next up is the popover'); 36 await sendEnter(); // Show nested popover 37 assert_true(nestedPopover.matches(':popover-open'), 'nested popover should be invoked by invoker'); 38 await sendTab(); 39 assert_equals(document.activeElement, nestedPopover.querySelector('button'), 'focus on the nested popover button'); 40 popover.querySelector('button').disabled = true; // Make the invoker no longer a focus candidate. 41 await sendShiftTab(); 42 assert_equals(document.activeElement, invoker, 'initial invoker should be focused, nested popover invoker is skipped since it is disabled'); 43 nestedPopover.querySelector('button').focus(); 44 await sendTab(); 45 assert_equals(document.activeElement,after,'no more focusable elements after the button'); 46 } 47 promise_test(async t => { 48 const invoker = document.querySelector('#no-focus-candidate>button'); 49 const popover = document.querySelector('#no-focus-candidate>[popover]'); 50 const nestedButton = popover.querySelector('button'); 51 const nestedPopover = document.querySelector('#no-focus-candidate>[popover]>[popover]'); 52 invoker.setAttribute('popovertarget', 'no-focus-candidate-p'); 53 nestedButton.setAttribute('popovertarget', 'no-focus-candidate-p2'); 54 t.add_cleanup(() => { 55 invoker.removeAttribute('popovertarget'); 56 nestedButton.removeAttribute('popovertarget'); 57 nestedButton.disabled = false; 58 popover.hidePopover(); 59 nestedPopover.hidePopover(); 60 }); 61 await testNoFocusCandidate(); 62 }, "Cases where the next focus candidate isn't in the direct parent scope with popovertarget invocation"); 63 promise_test(async t => { 64 const invoker = document.querySelector('#no-focus-candidate>button'); 65 const popover = document.querySelector('#no-focus-candidate>[popover]'); 66 const nestedButton = popover.querySelector('button'); 67 const nestedPopover = document.querySelector('#no-focus-candidate>[popover]>[popover]'); 68 invoker.setAttribute('commandfor', 'no-focus-candidate-p'); 69 invoker.setAttribute('command', 'toggle-popover'); 70 nestedButton.setAttribute('commandfor', 'no-focus-candidate-p2'); 71 nestedButton.setAttribute('command', 'toggle-popover'); 72 t.add_cleanup(() => { 73 invoker.removeAttribute('command'); 74 invoker.removeAttribute('commandfor'); 75 nestedButton.removeAttribute('command'); 76 nestedButton.removeAttribute('commandfor'); 77 nestedButton.disabled = false; 78 popover.hidePopover(); 79 nestedPopover.hidePopover(); 80 }); 81 await testNoFocusCandidate(); 82 }, "Cases where the next focus candidate isn't in the direct parent scope with command/commandfor invocation"); 83 promise_test(async t => { 84 const invoker = document.querySelector('#no-focus-candidate>button'); 85 const popover = document.querySelector('#no-focus-candidate>[popover]'); 86 const nestedButton = popover.querySelector('button'); 87 const nestedPopover = document.querySelector('#no-focus-candidate>[popover]>[popover]'); 88 const invokerClick = () => { 89 popover.togglePopover({ source: invoker }); 90 }; 91 invoker.addEventListener('click', invokerClick); 92 const nestedButtonClick = () => { 93 nestedPopover.togglePopover({ source: nestedButton }); 94 }; 95 nestedButton.addEventListener('click', nestedButtonClick); 96 t.add_cleanup(() => { 97 invoker.removeEventListener('click', invokerClick); 98 nestedButton.removeEventListener('click', nestedButtonClick); 99 nestedButton.disabled = false; 100 popover.hidePopover(); 101 nestedPopover.hidePopover(); 102 }); 103 await testNoFocusCandidate(); 104 }, "Cases where the next focus candidate isn't in the direct parent scope with imperative invocation"); 105 </script>