Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE html>
<meta charset="utf-8">
<title>Focus fixup rule one (no &lt;dialog>s involved)</title>
<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div>
<button id="button1">Button 1</button>
<button id="button2">Button 2</button>
<button id="button3">Button 3</button>
<fieldset id="fieldset1"><button id="button4">Button 4</button></fieldset>
<fieldset id="fieldset2" disabled><legend><button id="button5">Button 5</button></legend></fieldset>
<div id="div" tabindex="0">Div</div>
<div id="editable" contenteditable=true>editor</div>
<button id="button6">Button 6</button>
</div>
<script>
"use strict";
function test_focus_fixup(selector, change, expectSync = false) {
promise_test(async function(t) {
// Make sure we're not running from a ResizeObserver / etc notification.
await new Promise(r => t.step_timeout(r, 0));
const el = document.querySelector(selector);
el.focus();
assert_equals(document.activeElement, el, `Sanity check: ${selector} must start focused`);
change(el);
{
const fn = expectSync ? assert_not_equals : assert_equals;
fn(document.activeElement, el, `active element ${expectSync ? "is" : "isn't"} fixed-up sync`);
}
// We don't expect blur events in the sync case per spec yet, at least.
if (!expectSync) {
// Fixup should run after animation frame callbacks, right before the end of
// "update the rendering", so after resize observations.
let ranFirstRaf = false;
let ranRO = false;
let resolveDone;
let done = new Promise(r => { resolveDone = r; });
requestAnimationFrame(t.step_func(() => {
ranFirstRaf = true;
assert_equals(document.activeElement, el, "activeElement shouldn't have changed yet (rAF)");
requestAnimationFrame(t.step_func(() => {
assert_true(ranRO, "ResizeObserver should've ran on the previous frame");
resolveDone();
}));
}));
let ro = new ResizeObserver(t.step_func(() => {
assert_true(ranFirstRaf, "requestAnimationFrame should run before ResizeObserver");
ranRO = true;
assert_equals(document.activeElement, el, "activeElement shouldn't have changed yet (ResizeObserver)");
}));
// TODO: Test IntersectionObserver timing. It's a bit trickier because it
// uses its own task source and so on.
ro.observe(document.documentElement);
await done;
ro.disconnect();
}
assert_not_equals(document.activeElement, el, "focus is fixed up");
assert_equals(document.activeElement, document.body, "Body is focused");
}, selector);
}
test_focus_fixup("#button1", function(button) {
button.disabled = true;
});
test_focus_fixup("#button2", function(button) {
button.hidden = true;
});
test_focus_fixup("#button3", function(button) {
button.remove();
}, /* expectSync = */ true);
test_focus_fixup("#button4", function(button) {
document.querySelector("#fieldset1").disabled = true;
});
test_focus_fixup("#button5", function(button) {
const fieldset = document.querySelector("#fieldset2");
fieldset.insertBefore(document.createElement("legend"), fieldset.firstChild);
});
test_focus_fixup("#div", function(div) {
div.removeAttribute("tabindex");
});
test_focus_fixup("#editable", function(div) {
div.contentEditable = false;
});
test_focus_fixup("#button6", function(button) {
button.style.visibility = "hidden";
});
</script>