Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<meta charset="utf-8" />
<meta name="timeout" content="long">
<link rel="author" href="mailto:masonf@chromium.org">
<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(':has-interest'),expectHasInterest,`${msg}: :has-interest mismatch`);
assert_equals(target.matches(':target-of-interest'),expectTargetHasInterest,`${msg}: :target-of-interest mismatch`);
assert_false(invoker.matches(':target-of-interest'),'invoker should never match :target-of-interest');
assert_false(target.matches(':has-interest'),'target should never match :has-interest');
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(':has-interest') ||
target.matches(':target-of-interest');
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(':has-interest') &&
target.matches(':target-of-interest');
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>