Apply the focusing steps' get-the-focusable-area mapping before rejecting a non-focusable target. This preserves documentElement.focus() by mapping the non-focusable document element to the Document viewport. Also map rendered navigable containers with content navigables to their active document, while leaving hidden containers unfocused. Preserve Window focus events for child document viewports reached through iframe focus, while still suppressing the top-level viewport surrogate events. Treat rendered object elements as focusable through their default non-null tabindex, even when they show fallback or image content instead of a child navigable. Keep the spec focus-chain common-tail handling intact for viewport focus. The Document object is only our surrogate for the viewport, so designate viewport focus from the new focus target without dispatching Window focus/focusin events for that top-level surrogate. Pass that viewport surrogate as the fallback target for fragment scrolling and NavigateEvent focus reset, so unfocusable body or fragment targets still clear stale element focus. Cover documentElement.focus() in both the activeElement and focus-chain tests, including a tabindex document element that remains focused as an element. Cover object focus with and without a child navigable, hidden object focus attempts, iframe focus events, hidden iframe focus, and blurring a focused iframe after it becomes hidden. Also cover viewport fallback for intercepted navigation focus reset and fragment scrolling to an unfocusable target.
118 lines
4.8 KiB
HTML
118 lines
4.8 KiB
HTML
<!DOCTYPE html>
|
|
<script src="../include.js"></script>
|
|
<input id="before">
|
|
<input id="after">
|
|
<script>
|
|
function activeElementName() {
|
|
return document.activeElement.id || document.activeElement.localName;
|
|
}
|
|
|
|
function createIframe(id, style = "") {
|
|
return new Promise(resolve => {
|
|
const iframe = document.createElement("iframe");
|
|
iframe.id = id;
|
|
iframe.srcdoc = "<!DOCTYPE html><body>frame</body>";
|
|
if (style)
|
|
iframe.setAttribute("style", style);
|
|
iframe.onload = () => resolve(iframe);
|
|
document.body.appendChild(iframe);
|
|
});
|
|
}
|
|
|
|
function createObject() {
|
|
return new Promise(resolve => {
|
|
const object = document.createElement("object");
|
|
object.type = "text/html";
|
|
object.data = "about:srcdoc";
|
|
object.onload = () => resolve(object);
|
|
document.body.appendChild(object);
|
|
});
|
|
}
|
|
|
|
function createFallbackObject(id, style = "") {
|
|
const object = document.createElement("object");
|
|
object.id = id;
|
|
object.innerHTML = "<span>fallback</span>";
|
|
if (style)
|
|
object.setAttribute("style", style);
|
|
document.body.appendChild(object);
|
|
return object;
|
|
}
|
|
|
|
asyncTest(async done => {
|
|
const before = document.getElementById("before");
|
|
const after = document.getElementById("after");
|
|
const object = await createObject();
|
|
|
|
let blurredByObject = false;
|
|
before.addEventListener("blur", () => {
|
|
blurredByObject = true;
|
|
}, { once: true });
|
|
|
|
before.focus();
|
|
object.focus();
|
|
println(`before blurred after object.focus(): ${blurredByObject}`);
|
|
|
|
const fallbackObject = createFallbackObject("fallback-object");
|
|
let changeCommittedByFallbackObject = false;
|
|
before.addEventListener("change", () => {
|
|
changeCommittedByFallbackObject = true;
|
|
}, { once: true });
|
|
|
|
internals.sendText(before, "typed");
|
|
fallbackObject.focus();
|
|
println(`fallback object focus: active=${activeElementName()}, changeCommitted=${changeCommittedByFallbackObject}`);
|
|
|
|
const hiddenFallbackObject = createFallbackObject("hidden-fallback-object", "display: none");
|
|
let beforeBlurredByHiddenFallbackObject = false;
|
|
before.addEventListener("blur", () => {
|
|
beforeBlurredByHiddenFallbackObject = true;
|
|
}, { once: true });
|
|
|
|
before.focus();
|
|
hiddenFallbackObject.focus();
|
|
println(`hidden fallback object focus attempt: active=${activeElementName()}, beforeBlurred=${beforeBlurredByHiddenFallbackObject}`);
|
|
|
|
const frame = await createIframe("frame");
|
|
let frameFocusCount = 0;
|
|
let frameBlurCount = 0;
|
|
let frameWindowFocusCount = 0;
|
|
let frameWindowBlurCount = 0;
|
|
frame.addEventListener("focus", () => ++frameFocusCount);
|
|
frame.addEventListener("blur", () => ++frameBlurCount);
|
|
frame.contentWindow.addEventListener("focus", () => ++frameWindowFocusCount);
|
|
frame.contentWindow.addEventListener("blur", () => ++frameWindowBlurCount);
|
|
|
|
before.focus();
|
|
frame.focus();
|
|
after.focus();
|
|
println(`iframe focus/blur events: frame=${frameFocusCount}/${frameBlurCount}, window=${frameWindowFocusCount}/${frameWindowBlurCount}`);
|
|
|
|
const hiddenFrame = await createIframe("hidden-frame", "display: none");
|
|
let beforeBlurredByHiddenFrame = false;
|
|
let hiddenFrameFocusCount = 0;
|
|
let hiddenFrameWindowFocusCount = 0;
|
|
before.addEventListener("blur", () => {
|
|
beforeBlurredByHiddenFrame = true;
|
|
}, { once: true });
|
|
hiddenFrame.addEventListener("focus", () => ++hiddenFrameFocusCount);
|
|
hiddenFrame.contentWindow.addEventListener("focus", () => ++hiddenFrameWindowFocusCount);
|
|
|
|
before.focus();
|
|
hiddenFrame.focus();
|
|
println(`hidden iframe focus attempt: active=${activeElementName()}, beforeBlurred=${beforeBlurredByHiddenFrame}, frameFocus=${hiddenFrameFocusCount}, windowFocus=${hiddenFrameWindowFocusCount}`);
|
|
|
|
const hideAfterFocusFrame = await createIframe("hide-after-focus-frame");
|
|
let hideAfterFocusFrameBlurCount = 0;
|
|
let hideAfterFocusFrameWindowBlurCount = 0;
|
|
hideAfterFocusFrame.addEventListener("blur", () => ++hideAfterFocusFrameBlurCount);
|
|
hideAfterFocusFrame.contentWindow.addEventListener("blur", () => ++hideAfterFocusFrameWindowBlurCount);
|
|
|
|
hideAfterFocusFrame.focus();
|
|
hideAfterFocusFrame.style.display = "none";
|
|
hideAfterFocusFrame.blur();
|
|
println(`hidden focused iframe blur: active=${activeElementName()}, frameBlur=${hideAfterFocusFrameBlurCount}, windowBlur=${hideAfterFocusFrameWindowBlurCount}`);
|
|
|
|
done();
|
|
});
|
|
</script>
|