-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
What is the issue with the HTML Standard?
I read the section on focusing and created the following script locally:
index.html
<style>
body {
display: flex;
flex-direction: column;
align-items: flex-start;
}
iframe {
border-width: 5px;
}
</style>
<input id='input' type='text'>
<iframe src="main-iframe.html"></iframe>
<script>
setTimeout(() => {
let secondWindow = frames[0]
let thirdWindow = frames[0].frames[0];
let dictionary = {
window,
secondWindow,
thirdWindow
};
for (let [key, value] of Object.entries(dictionary)) {
value.addEventListener("focus", (e) => {
console.log(e.type, key, e);
})
value.frames[0]?.frameElement.addEventListener("focus", (e) => {
console.log(e.type, key+":iframeElement", e);
})
value.input.addEventListener("focus", (e) => {
console.log(e.type, key+":input", e);
})
}
let secondWindowFrameButton = document.createElement("button");
secondWindowFrameButton.onclick = () => secondWindow.frames[0].frameElement.focus();
secondWindowFrameButton.textContent = "secondWindow.frames[0].frameElement.focus()"
let thirdWindowFrameInputButton = document.createElement("button");
thirdWindowFrameInputButton.onclick = () => thirdWindow.input.focus();
thirdWindowFrameInputButton.textContent = "thirdWindow.input.focus()";
document.body.append(secondWindowFrameButton, thirdWindowFrameInputButton);
console.log("timeout fired");
}, 1000)
</script>main-frame.html
<style>
iframe {
border-width: 5px;
}
</style>
<input id='input' type='text'>
<iframe src="source.html"></iframe>source.html
<input id='input' type='text'>Expectation: When we click on the input in <iframe src="source.html"></iframe>, at least 5 "focus" events should appear in the console.
Reality: In the latest versions of both Firefox and Chrome, you only get 2 "focus" events. Why?
The specification contains a key algorithm called the focus chain:
The focus chain of a focusable area subject is the ordered list constructed as follows:
Let output be an empty list.
Let currentObject be subject.
While true:
Append currentObject to output.
If currentObject is an area element's shape, then append that area element to output.
Otherwise, if currentObject's DOM anchor is an element that is not currentObject itself, then append currentObject's DOM anchor to output.
If currentObject is a focusable area, then set currentObject to currentObject's DOM anchor's node document.
Otherwise, if currentObject is a Document whose node navigable's parent is non-null, then set currentObject to currentObject's node navigable's parent.
Otherwise, break.
Return output.
Note: The chain starts with subject and (if subject is or can be the currently focused area of a top-level traversable) continues up the focus hierarchy up to the Document of the top-level traversable.
Based on this algorithm, what would the focus chain be for input inside <iframe src="source.html"></iframe>?
My guess is what the focus chain looks like in this case: [input, document, iframe, document, iframe, document].
In the focus update steps algorithm:
- If the last entry in old chain and the last entry in new chain are the same, pop the last entry from old chain and the last entry from new chain and redo this step.
...
- For each entry entry in new chain, in reverse order, run these substeps:
If entry is a focusable area, and the focused area of the document is not entry:
- Set document's relevant global object's navigation API's focus changed during ongoing navigation to true.
- Designate entry as the focused area of the document.
If entry is an element, let focus event target be entry.
If entry is a Document object, let focus event target be that Document object's relevant global object.
Otherwise, let focus event target be null.
If entry is the last entry in new chain, and entry is an Element, and the last entry in old chain is also an Element, then let related focus target be the last entry in old chain. Otherwise, let related focus target be null.
If focus event target is not null, fire a focus event named focus at focus event target, with related focus target as the related target.
Note: In some cases, e.g. if entry is an area element's shape, a scrollable region, or a viewport, no event is fired.
The first step of the algorithm can shorten our new focus chain (if the old focus chain has matching chain elements from the end of the list).
Next, we look at the loop where a fire a focus event with the name "focus" occurs. The fire a focus event will only occur if the focus event target is not null (that is, it is either an element or a Document).
Thus, given a focus chain that looks like this: [input, document, iframe, document, iframe, document], we should receive one focus event on the input element (which will be the last one, since we use reverse enumeration of chain elements), two focus events on the window elements (since the document elements will be converted to window, specifically two, not three, due to the first step in the focus update steps) and two focus events on the iframe elements.
Call order:
iframewindowiframewindowinput
If I interpreted the spec correctly, neither Firefox nor Chrome implement this behavior. The most interesting thing is that they also invoke these events differently.
For example, in Chrome, if you click the secondWindow.frames[0].frameElement.focus() button, you will see:
secondWindowthirdWindowsecondWindow:iframeElement
In Firefox:
thirdWindow
How I think it should be:
window:iframesecondWindowthirdWindow(notsecondWindow:iframeElement)
If we click it again, no events will occur in Chrome, but in Firefox it will be the same as the first time:
thirdWindow
Now, if we refresh the page for the sake of clarity after the first test and check for clicks on the thirdWindow.input.focus() button, Chrome will display this:
thirdWindowthirdWindow:input
In Firefox:
thirdWindowthirdWindow:input
How I think it should be:
window:iframeElementsecondWindowsecondWindow:iframeElementthirdWindowthirdWindow:input
Clicking it again doesn't change anything in either browser.
Specifiers are needed to explain this.