📜 ⬆️ ⬇️

Add to Chromium selector: focus-within



This article is about the introduction of a new feature in Chromium / Blink . Namely - pseudo - class: focus-within of the Selectors 4 specification . We will also talk about different things that we have to deal with when developing.

Pseudo-class :focus-within


This is a new selector that allows you to modify the style of an element while focusing on this element or any of its child elements (descendant). This is similar to the action of the :focus selector, only applies to parent elements, so it works something like :active and :hover .

Everything will be clear from the example:
')
 <style> form:focus-within { background-color: green; } </style> <form> <input /> </form> 

When the user selects an input form, its background turns green.

Delivery intention


Although the specification is still in Editor's Draft (editorial draft) , it is already implemented in Firefox 52 and Safari 10.1, so it is a good candidate for adding to Chromium.

To do this, you need to send a letter of intent to blink-dev . The task seemed simple and easy, and after a little thought, I sent the letter Intent to Implement and Ship: CSS Selectors Level 4:: focus-within pseudo-class (Intention to implement: CSS Selectors Level 4: pseudo-class : focus-within) .

But then the first difficulty arose ...

Specification issues


At first glance it seems that the feature is very simple. But the Web Platform is a complex thing, with a lot of internal relationships.

In my case, I was able to quickly identify the problem with the specification text associated with using this selector (as well as :active and :hover ) with the Shadow DOM . The old specification text reads as follows:

An element also matches (matches) :focus-within , if one of its child elements, including Shadow, corresponds to :focus .

It looks like the specification is ready for the Shadow DOM, but there is an error in it. It may not be so easy to understand, but if you're interested, take a look at an example:

 <div id="shadowHost"> <input /> </div> <script> shadowHost.attachShadow({ mode: "open"}).innerHTML = "<style>" + " #shadowDiv:focus-within { border: thick solid green; }" + "</style>" + "<div id='shadowDiv'>" + " <slot></slot>" + "</div>"; </script> 

If not understood: the input element is inserted into the slot tag (a quick and simplified explanation of this particular example with the Shadow DOM).

In this example, the flat tree will look like this:

 <div id="shadowHost"> #shadow-root <div id="shadowDiv"> <slot> <input /> </slot> </div> </div> 

The problem is that when you focus on the input, which is now inside the slot , you expect the frame around shadowDiv turn green. However, input is not a shadowDiv child, including Shadow . Instead, the specification should talk about the children of the flat tree.

The problem was reported to the CSS WG GitHub repository , the specification now states:

An element also matches (matches) :focus-within , if one of its child elements in a flat tree (including non-element nodes, such as text) corresponds to the following conditions :focus .

Implementation :focus-within


After solving the specification problem, the intent was approved. My implant was given a green light.

A patch that adds support for a feature mainly consists of a boilerplate code needed to add a new selector to Blink. For the most part, it does the same thing as :focus , but then the interesting part goes: looping through the children using the flat tree:

 for (ContainerNode* node = this; node; node = FlatTreeTraversal::Parent(*node)) { node->SetHasFocusWithin(received); node->FocusWithinStateChanged(); } 

What about the tests?


Of course, any changes to Blink need to be tested. I was lucky, there were already several tests in the W3C Web Platform Tests (WPT) repository for this new selector.

I imported the tests ( not without problems ) into Blink and checked that my patch passes them (including Mozilla tests that are already upstream). I also went through the tests in the WebKit repository, since they already implemented this feature, and upstream one of them , who tried some interesting combinations. Finally, I wrote a few more tests to cover additional situations (like the specification problem described above).

Focus and display:none


During the code review they helped me find another controversial issue. What happens to the selected item when it is marked as display: none ? At first glance, it seems that the focus should be removed, and this is indeed the case ( in the HTML specification there is a rule describing this situation ).

But here we are talking about the compatibility issue, because this rule in Blink is observed only by the engine. With respect to the rest of the browsers published reports of bugs, that is, the problem seems to be known. However, it has not been solved yet. Here is one of the reports: Chromium bug # 491828 .

If you use the :focus selector to change, for example, the background color in input, then it is not particularly important what happens when this input receives display: none and disappears. What does it matter what is done with the background of what you no longer see. But in the case of focus-within this problem is more important. Imagine that you changed the background color of a form when you selected one of the input fields. If it is marked as display: none , then there will be no focus on any of the form fields, and the background color should be changed. But now it only happens in Chromium.

Parenting strategy


Initially, the support patch: focus-within was implemented in Chrome 59, but was flagged as experimental. The main reason: it still needs to be finalized so that it is enabled by default.

One of the improvements was related to style recalculations. The initial implementation led to an excessive amount of computation.

Take a new example:

 <style> *:focus-within { background-color: green; } </style> <form> <ul> <li id="li1"><input id="input1" /></li> <li id="li2"><input id="input2" /></li> </ul> </form> 

What happens when you select input2 instead of input1 ?

Consider step by step how this works in the original patch:

  1. At first, input1 is chosen, so this element and all its child elements - including input1 , li1 , ul and form (in fact, even body and html , but here we omit them) - receive the flag :focus-within (everyone has green frame).
  2. Then go to input2 . The first element selected - input1 - loses focus. And here we go through the chain of parent elements, removing the flag :focus-within input1 , li1 , ul and form .
  3. Now input2 really selected. Go back through the chain of parent elements and add a flag at input2 , li2 , ul and form .

We removed and added a flag to the form and ul elements, although this was a redundant operation, because as a result they came to the same state.

The new version of the patch in paragraph 2 made a change: now the elements that lose and receive focus, are looking for common parent elements. In our case, when going from input1 to input2 it will be ul . Passing through the chain of parent elements to add / remove a flag :focus-within , the system skips the common parent element and leaves it (and all its parents) unchanged. So we save the amount of computation.

Now in paragraph 2, the flag will be cleared only for input1 and li1 , and in paragraph 3 it will be added only for input2 and li2 . The ul and form elements will remain intact.

And in addition ...


After finishing work in Chromium, I realized that WebKit does not comply with the specification in the case of flat tree. Therefore, I imported WPT tests into WebKit and wrote a patch that allows using flat tree in WebKit .

Adding a new selector may look like a simple task. But let me show you the number of commits to different repositories that were associated with this work:


And there will be more commits, because I make new modifications of tests, so that you can easily use them in Blink and WebKit.

Application


Now everything is ready :focus-within will be available by default in Chrome 60 . You can use it.

I wrote a simple demo showing what a new feature allows you to make. But you can come up with much more interesting options.



The new selector is important to increase the availability of web applications and sites, especially when working with the keyboard. For example, if you have only :hover involved, then some users who are accustomed to button navigation will not be able to use your product. And if you add :focus-within - you will avoid such problems.

I created a typical menu that uses: hover and: focus-within , take a look at how button navigation now works.



Please note that there is a bug in Firefox , because of which this example does not work.

Source: https://habr.com/ru/post/328478/


All Articles