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