Open dbates-wk opened 5 years ago
Shoutout @garykac @travisleithead @rniwa @annevk @hober
@smfr
Firefox and WebKit don't fire blur
event when the element is removed from the DOM either. For consistency, it's probably better if we didn't fire blur
event in the case.
Also, firing blur
event in this scenario poses a question as to when the event needs to be fired. We definitely don't want to expose the timing at which the style resolution had happened so that would mean the event needs to be dispatched when the rendering is updated.
I'm pretty sure Chrome sets up some kind of 0s timer to fire blur
event in this case. But this approach is problematic in that it would mean we'd have to update the style immediately after running each task to detect that the focused element is no longer rendered. Ideally, we want to delay such a style resolution up until the next rendering opportunity occurs.
<!DOCTYPE html>
<html>
<body>
<pre id="log"></pre>
<script>
log = (text) => document.getElementById('log').textContent += text + '\n';
window.onload = () => {
const input = document.createElement('input');
document.body.appendChild(input);
input.focus();
input.addEventListener('blur', () => log('blur event fired'));
input.style.display = 'none';
setTimeout(() => {
log('setTimeout fired');
}, 0);
}
</script>
</body>
</html>
@rakina @tkent-google @domenic
As far as specifying this goes, I think it should go into HTML as that largely defines the focus model for the web. (There's various open issues here and in whatwg/html to make it better.)
As far as behavior goes, treating it equivalently to tree removal seems reasonable. We don't really have a "loses its CSS box" primitive at the moment, but adding that seems somewhat reasonable. Even if the blur
event is not fired, there are other ways to observe whether the element lost focus, right? E.g., activeElement
? So depending on what accessors flush layout this might still be observable before a more ideal point in time.
Even if the
blur
event is not fired, there are other ways to observe whether the element lost focus, right? E.g.,activeElement
?
Looking at activeElement does not seem be a way to observe this (and I can't help be think: why would it be? Last I recall it represented the focused element ignoring window activation <-- the AppKit/UIKit/GUI toolkit concept, haven't checked the spec, yet. FYI, In Safari and Firefox document.activeElement
is still the <input>
after the display:none'ing in the example. Only Chrome changes the activeElement, but then it also dispatches a blur 😕
As far as specifying this goes, I think it should go into HTML as that largely defines the focus model for the web. (There's various open issues here and in whatwg/html to make it better.)
Great! Let's add some spec text!
I did a bit of digging in the HTML spec. I believe the Chrome behavior (dispatch blur
on an element that becomes display: none
) is currently required by the HTML Living Standard.
The focus fixup rule says:
When the designated focused area of the document is removed from that Document in some way (e.g. it stops being a focusable area, it is removed from the DOM, it becomes expressly inert, etc.), designate the Document's viewport to be the new focused area of the document.
The requirements for being a focusable area include being rendered for most elements, which requires producing CSS boxes. Thus a display:none
element would not be a focusable area. This will run the focus update steps which dispatch the blur
event.
This behavior logically makes sense to me, because a display: none
element can’t be focused in the first place, so an element that becomes display: none
shouldn’t retain focus, and everything that happens upon loss of focus should happen.
Note that WebKit's behavior to not lose focus (blur) the element which loses CSS box due to display:none
was a compatibility requirement on facebook's mobile site as recently as five years ago. This is also the behavior IE had and WebKit deliberately decided not to remove focus from such an element seven years ago.
It's possible that the compatibility story has changed in recent years due to Blink's market share but I wouldn't discount the major Web compatibility risk Gecko and WebKi would face if we were to implement Blink's relatively new behavior. In fact, Blink is literally the only major engine which exhibits this behavior so I'm inclined to say the spec needs to be updated to match WebKit/Gecko/IE behavior instead assuming Blink is willing to change its behavior back.
<!DOCTYPE html>
<html>
<body>
<pre id="log"></pre>
<script>
onload = () => {
const input = document.createElement('input');
document.body.appendChild(input);
input.focus();
input.addEventListener('blur', () => log.textContent += 'blur');
input.style.display = 'none';
document.body.getBoundingClientRect();
input.style.display = null;
}
</script>
</body>
</html>
Doesn't log blur in Chrome regardless of document.body.getBoundingClientRect()
is executed or not so this seems to indicate that Chrome is indeed waiting for some time to decide whether the element has become invisible or not. The following example also seems to always logs blur and this is problematic because it implies that we'd have to resolve the style after each task. We don't want to do that.
<!DOCTYPE html>
<html>
<body>
<pre id="log"></pre>
<script>
onload = () => {
const input = document.createElement('input');
document.body.appendChild(input);
input.focus();
input.addEventListener('blur', () => log.textContent += 'blur');
input.style.display = 'none';
setTimeout(() => {
input.style.display = null;
}, 0);
}
</script>
</body>
</html>
I agree that the blur behavior in this case is not important for site compatibility, and we can change the HTML specification. However, dispatching no events and keeping activeElement
aren't logical and such behavior would be a pitfall for web developers. So I wonder if we can define an algorithm which is easily implementable.
A wild idea is to add something like activeElement-focsusable-check step to Update the rendering. It can handle many cases such as disconnecting focused element, changing tabindex/contenteditable attributes, changing disabled state as well as loosing CSS box.
A wild idea is to add something like activeElement-focsusable-check step to Update the rendering. It can handle many cases such as disconnecting focused element, changing tabindex/contenteditable attributes, changing disabled state as well as loosing CSS box.
That's probably the only sane way to define this behavior.
Consider a page with the following markup:
Load the page, wait one second. What events do you see printed? In Safari on macOS Mojave and Firefox for Mac 67.0.4 I see:
In Chrome for Mac version 75.0.3770.100 and Chrome Canary for Mac version 77.0.3835.0 I see, emphasis mine:
So, should a blur event be fired?
So that was the most important question. Many things fall out from the answer to that question. Just to give a sample of things that fall out try this: press a key, like 'a'. Then in Safari I see, emphasis mine:
Both Firefox and Chrome don't fire such keydowns. But remember, Firefox never fired a blur event (above) so the field is still focused? So it should get key events?
The spec does not seem to specify what the behavior should be when an element's display property is changed to "none". Can we please get some spec. text for this?