interestfor-pseudo-classes.tentative.html (7572B)
1 <!DOCTYPE html> 2 <meta charset="utf-8" /> 3 <meta name="timeout" content="long"> 4 <link rel="author" href="mailto:masonf@chromium.org"> 5 <link rel="help" href="https://open-ui.org/components/interest-invokers.explainer"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-actions.js"></script> 10 <script src="/resources/testdriver-vendor.js"></script> 11 <script src="resources/invoker-utils.js"></script> 12 <script src="/html/semantics/popovers/resources/popover-utils.js"></script> 13 14 <div id=unrelated tabindex=0>Unrelated</div> 15 <button id=invoker interestfor=target>Invoker</button> 16 <div id=target popover>Target popover with all kinds of focusable things 17 <button id=target_button>contained button</button> 18 <button id=target_button_2>contained button 2</button> 19 <a href=foo>Link</a> 20 <dialog open>Dialog</dialog> 21 <textarea></textarea> 22 <input type=text> 23 <input type=checkbox> 24 <input type=radio> 25 <input type=button> 26 <input type=range> 27 <map name="mymap"> 28 <area shape="circle" coords="75,75,75" href=foo> 29 </map> 30 <img usemap="#mymap" src="../../embedded-content/the-img-element/resources/green.png"> 31 <div tabindex=0>tabindex=0</div> 32 </div> 33 <button id=after>Button after</button> 34 <style> 35 button { 36 interest-delay: 0s; 37 } 38 </style> 39 <script> 40 function checkPseudos(invoker,target,expectHasInterest,expectTargetHasInterest,msg) { 41 msg = msg ?? 'Error'; 42 assert_equals(invoker.matches(':interest-source'),expectHasInterest,`${msg}: :interest-source mismatch`); 43 assert_equals(target.matches(':interest-target'),expectTargetHasInterest,`${msg}: :interest-target mismatch`); 44 assert_false(invoker.matches(':interest-target'),'invoker should never match :interest-target'); 45 assert_false(target.matches(':interest-source'),'target should never match :interest-source'); 46 assert_equals(target.matches(':popover-open'),expectTargetHasInterest,'Popover should be open if target has interest'); 47 } 48 // Note that add_cleanup does not wait for async functions. 49 async function do_cleanup(t) { 50 invoker.removeAttribute('style'); 51 await focusOn(unrelated); 52 await hoverOver(unrelated); 53 await sendLoseInterestHotkey(); 54 target.hidePopover(); 55 await waitForRender(); 56 } 57 58 promise_test(async (t) => { 59 let hasInterest = false; 60 target.addEventListener('interest',() => (hasInterest=true)); 61 target.addEventListener('loseinterest',() => (hasInterest=false)); 62 checkPseudos(invoker,target,false,false,'initial'); 63 assert_false(hasInterest); 64 await hoverOver(invoker); 65 checkPseudos(invoker,target,true,true,'hovering invoker shows full interest'); 66 assert_true(hasInterest,'event was fired'); 67 await hoverOver(target); 68 checkPseudos(invoker,target,true,true,'hovering the target maintains interest'); 69 assert_true(hasInterest,'loseinterest event was not yet fired'); 70 await hoverOver(unrelated); 71 checkPseudos(invoker,target,false,false,'hovering unrelated loses interest'); 72 assert_false(hasInterest,'loseinterest event was fired'); 73 await do_cleanup(); 74 },'Basic pseudo class function, with mouse hover triggering'); 75 76 promise_test(async (t) => { 77 let hasInterest = false; 78 target.addEventListener('interest',() => (hasInterest=true)); 79 target.addEventListener('loseinterest',() => (hasInterest=false)); 80 checkPseudos(invoker,target,false,false,'initial'); 81 assert_false(hasInterest); 82 await focusOn(invoker); 83 checkPseudos(invoker,target,true,true,'focusing invoker shows interest'); 84 assert_true(hasInterest,'event was fired'); 85 await focusOn(invoker); 86 checkPseudos(invoker,target,true,true,'focusing back on invoker keeps full interest'); 87 assert_true(hasInterest,'loseinterest event was not yet fired'); 88 await focusOn(unrelated); 89 checkPseudos(invoker,target,false,false,'focusing unrelated loses interest'); 90 assert_false(hasInterest,'loseinterest event was fired'); 91 await do_cleanup(); 92 },'Basic pseudo class function, with keyboard focus triggering'); 93 94 promise_test(async (t) => { 95 checkPseudos(invoker,target,false,false,'initial'); 96 await focusOn(invoker); 97 checkPseudos(invoker,target,true,true,'invoker now has full interest'); 98 await sendTab(); 99 assert_equals(document.activeElement,target_button,'focus should now be able to move within the target'); 100 await sendTab(); 101 assert_equals(document.activeElement,target_button_2,'focus should be able to move within the target'); 102 await sendShiftTab(); 103 await sendShiftTab(); 104 assert_equals(document.activeElement,invoker,'focus should go back to invoker'); 105 checkPseudos(invoker,target,true,true,'focusing back on invoker keeps full interest'); 106 await focusOn(unrelated); 107 checkPseudos(invoker,target,false,false,'focusing unrelated loses interest'); 108 await do_cleanup(); 109 },'Contents of target popover are keyboard focusable'); 110 111 promise_test(async (t) => { 112 checkPseudos(invoker,target,false,false,'initial'); 113 await focusOn(invoker); 114 checkPseudos(invoker,target,true,true,'focusing invoker shows interest'); 115 invoker.setAttribute('style',`interest-delay: 10000s`); 116 await sendLoseInterestHotkey(); 117 checkPseudos(invoker,target,false,false,'Hot key loses interest immediately (no delays)'); 118 await do_cleanup(); 119 },`Lose interest hotkey works`); 120 121 promise_test(async (t) => { 122 checkPseudos(invoker,target,false,false,'initial'); 123 await focusOn(invoker); 124 checkPseudos(invoker,target,true,true,'focusing invoker shows interest'); 125 invoker.setAttribute('style',`interest-delay: 10000s`); 126 target.hidePopover(); 127 checkPseudos(invoker,target,false,false,'closing the popover loses interest'); 128 assert_equals(document.activeElement,invoker,'focus does not move'); 129 await do_cleanup(); 130 },'Closing the target popover loses interest, without any delays (keyboard activation)'); 131 132 promise_test(async (t) => { 133 checkPseudos(invoker,target,false,false,'initial'); 134 await hoverOver(invoker); 135 checkPseudos(invoker,target,true,true,'hovering invoker shows full interest'); 136 invoker.setAttribute('style',`interest-delay: 10000s`); 137 target.hidePopover(); 138 checkPseudos(invoker,target,false,false,'closing the popover loses interest'); 139 await do_cleanup(); 140 },'Closing the target popover loses interest, without any delays (mouse activation)'); 141 142 const invokerDelayMs = 100; // The CSS delay setting. 143 const hoverWaitTime = 200; // How long to wait to cover the delay for sure. 144 promise_test(async (t) => { 145 invoker.setAttribute('style',`interest-delay: ${invokerDelayMs}ms`); 146 checkPseudos(invoker,target,false,false,'initial'); 147 const token1 = await mouseOverAndRecord(t,invoker); 148 const immediate_result = invoker.matches(':interest-source') || 149 target.matches(':interest-target'); 150 if (msSinceMouseOver(token1) < invokerDelayMs) { 151 assert_false(immediate_result,'No pseudos should match before the show delay elapses'); 152 } 153 await waitForHoverTime(hoverWaitTime); 154 checkPseudos(invoker,target,true,true,'pseudos should match after hover delay'); 155 const token2 = await mouseOverAndRecord(t,unrelated); 156 const immediate_result2 = invoker.matches(':interest-source') && 157 target.matches(':interest-target'); 158 if (msSinceMouseOver(token2) < invokerDelayMs) { 159 assert_true(immediate_result2,'all pseudos should still match before the hide delay elapses'); 160 } 161 await waitForHoverTime(hoverWaitTime); 162 checkPseudos(invoker,target,false,false,'no pseudos should match after de-hover delay'); 163 await do_cleanup(); 164 },'The pseudo classes only match after delays, once interest is shown'); 165 </script>