- PVSM.RU - https://www.pvsm.ru -
В этой статье рассказывается о внедрении в Chromium/Blink [1] новой фичи. А именно — псевдокласса :focus-within из спецификации Selectors 4 [2]. Также поговорим о разных вещах, с которыми приходится сталкиваться при разработке.
:focus-within
Это новый селектор, позволяющий модифицировать стиль элемента при фокусировке на этом элементе или любом из его дочерних элементов (descendant). Это аналогично действию селектора :focus [3]
, только применяется и по отношению к родительским элементам, так что работает примерно как :active [4]
и :hover [5]
.
Всё будет понятно из примера:
<style>
form:focus-within {
background-color: green;
}
</style>
<form>
<input />
</form>
Когда пользователь выбирает форму ввода, её фон становится зелёным.
Хотя спецификация всё ещё в состоянии Editor’s Draft (редакторский черновик), она уже реализована в Firefox 52 и Safari 10.1, так что она хороший кандидат и на добавление в Chromium.
Для этого вам нужно отправить письмо о намерении [6] в blink-dev [7]. Задача казалась простой и лёгкой, и после небольшого раздумья я отправил письмо Intent to Implement and Ship: CSS Selectors Level 4: :focus-within pseudo-class [8] (Намерение о внедрении: CSS Selectors Level 4: псевдокласс :focus-within).
Но тут возникло первое затруднение…
На первый взгляд кажется, что фича очень простая. Но Web Platform — вещь сложная, с большим количеством внутренних взаимосвязей.
В моём случае удалось быстро выявить проблему с текстом спецификации, связанную с использованием этого селектора (а также :active
и :hover
) с Shadow DOM [9]. Старый текст спецификации гласит:
Элемент также соответствует (matches)
:focus-within
, если один из его дочерних элементов, включающий в себя Shadow, соответствует:focus
.
Похоже, спецификация готова в отношении Shadow DOM, но в ней есть ошибка. Это может быть не так просто понять, но, если интересно, взгляните на пример:
<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>
Если не поняли: элемент input вставляется в тег slot
(быстрое и упрощённое объяснение этого конкретного примера с Shadow DOM).
В этом примере flat tree [10] будет выглядеть так:
<div id="shadowHost">
#shadow-root
<div id="shadowDiv">
<slot>
<input />
</slot>
</div>
</div>
Проблема в том, что когда фокусируешься на input, который сейчас внутри slot
, то ожидаешь, что рамка вокруг shadowDiv
станет зелёной. Однако input не является дочерним элементом shadowDiv, включающим в себя Shadow [11]. Вместо этого в спецификации должно говориться о дочерних элементах flat tree.
О проблеме было сообщено в GitHub-репозитории CSS WG [12], в спецификации теперь говорится:
Элемент также соответствует (matches)
:focus-within
, если один из его дочерних элементов в flat tree (включающий неэлементные узлы (non-element nodes) вроде текстовых) соответствует условиям соответствия:focus
.
:focus-within
После решения проблемы со спецификацией намерение было одобрено. Моему внедрению дали зелёный свет.
Патч, добавляющий поддержку фичи [13], в основном состоит из шаблонного кода (boilerplate code), необходимого для добавления в Blink нового селектора. По большей части он делает всё то же самое, что и :focus
, но затем идёт интересная часть: циклический проход по дочерним элементам с помощью flat tree:
for (ContainerNode* node = this; node;
node = FlatTreeTraversal::Parent(*node)) {
node->SetHasFocusWithin(received);
node->FocusWithinStateChanged();
}
Конечно, любые изменения в Blink нужно протестировать. Мне повезло, в репозитории W3C Web Platform Tests (WPT) [14] уже было несколько тестов для этого нового селектора.
Я импортировал тесты (не без проблем [15]) в Blink и проверил, что мой патч их проходит (включая тесты Mozilla, которые уже апстримлены). Также я прошерстил тесты в репозитории WebKit, поскольку они уже внедрили эту фичу, и апстримил один из них [16], проверявший некоторые интересные комбинации. Наконец, я написал ещё несколько тестов [17] для покрытия дополнительных ситуаций (вроде описанной выше проблемы со спецификацией).
display:none
Во время code review мне помогли найти ещё один спорный момент. Что произойдёт с выбранным элементом, когда он помечен как display: none
? На первый взгляд кажется, что фокусировка должна быть снята, и это действительно так (в спецификации HTML есть правило, описывающее такую ситуацию [18]).
Но здесь идёт речь о проблеме совместимости, потому что это правило в Blink соблюдается только движком. Применительно к остальным браузерам опубликованы отчёты о багах, то есть о проблеме, судя по всему, известно. Однако она пока никак не решена. Вот один из отчётов: Chromium bug #491828 [19].
Если воспользоваться селектором :focus
для изменения, например, цвета фона в input, то не особенно важно, что происходит, когда этот input получает display: none
и исчезает. Какая разница, что делается с фоном того, что вы больше не видите. Но в случае с focus-within
эта проблема более важна. Представьте, что вы изменили цвет фона в форме, когда выбрано какое-то из полей ввода. Если оно помечено как display: none
, то не будет фокусировки ни на одном из полей формы, а цвет фона должен быть изменён. Но сейчас это происходит только в Chromium.
Изначально патч с поддержкой :focus-within [13] внедрили в Chrome 59, но пометили флагом как экспериментальный. Основная причина: его ещё нужно доработать, чтобы он был включён по умолчанию.
Одна из доработок была связана с повторными вычислениями стилей (style recalculations). Начальная реализация приводила к избыточному количеству вычислений.
Возьмём новый пример:
<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>
Что произойдёт, когда вместо input1
вы выберете input2
?
Рассмотрим пошагово, как это работает в первоначальном патче:
input1
, так что этот элемент и все его дочерние элементы — в том числе input1
, li1
, ul
и form
(на самом деле даже body
и html
, но здесь мы их опустим) — получают флаг :focus-within
(у всех появляется зелёная рамка).input2
. Первый выбранный элемент — input1
— теряет фокусировку. И здесь мы проходим по цепочке родительских элементов, убирая флаг :focus-within
у input1
, li1
, ul
и form
.input2
действительно выбран. Снова идём по цепочке родительских элементов и добавляем флаг у input2
, li2
, ul
и form
.
Мы убрали и добавили флаг у элементов form
и ul
, хотя это была избыточная операция, ведь в результате они пришли к тому же состоянию.
В новой версии патча [20] в пункт 2 внесли изменение: теперь у элементов, теряющих и получающих фокусировку, ищутся общие родительские элементы. В нашем случае при переходе с input1
к input2
это будет ul
. Проходя по цепочке родительских элементов для добавления/удаления флага :focus-within
, система пропускает общий родительский элемент и оставляет его (и всех его родителей) неизменённым. Так мы экономим количество вычислений.
Теперь в пункте 2 флаг будет снят только у input1
и li1
, а в пункте 3 добавится только у input2
и li2
. Элементы ul
и form
останутся нетронутыми.
Закончив работу в Chromium, я сообразил, что WebKit не соблюдает спецификацию в случае с flat tree. Поэтому я импортировал в WebKit тесты WPT и написал патч, позволяющий использовать flat tree и в WebKit [21].
Добавление нового селектора может выглядеть простой задачей. Но позвольте показать вам количество коммитов в разные репозитории, которые были связаны с этой работой:
И будут ещё коммиты, потому что я делаю новые модификации тестов, чтобы можно было без проблем использовать их в Blink и WebKit.
Теперь всё готово, :focus-within
будет доступен по умолчанию в Chrome 60. Можно его использовать.
Я написал простую демку [32], показывающую, что позволяет сделать новая фича. Но вы сможете придумать куда более интересные варианты.
Новый селектор важен для повышения доступности веб-приложений и сайтов, особенно при работе с клавиатурой. Например, если у вас задействован только :hover
, то часть пользователей, которые привыкли к кнопочной навигации, не смогут воспользоваться вашим продуктом. А если вы добавите :focus-within
— вы избежите таких проблем.
Я создал типичное меню, использующее :hover и :focus-within [33], взгляните, как теперь работает кнопочная навигация.
Обратите внимание, что в Firefox есть баг [34], из-за которого этот пример не работает.
Автор: Mail.Ru Group
Источник [35]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/255225
Ссылки в тексте:
[1] Chromium/Blink: https://www.chromium.org/blink
[2] псевдокласса :focus-within из спецификации Selectors 4: https://drafts.csswg.org/selectors-4/#focus-within-pseudo
[3] :focus: https://drafts.csswg.org/selectors-4/#focus-pseudo
[4] :active: https://drafts.csswg.org/selectors-4/#active-pseudo
[5] :hover: https://drafts.csswg.org/selectors-4/#hover-pseudo
[6] письмо о намерении: https://www.chromium.org/blink/launching-features
[7] blink-dev: https://groups.google.com/a/chromium.org/forum/#%21forum/blink-dev
[8] Intent to Implement and Ship: CSS Selectors Level 4: :focus-within pseudo-class: https://groups.google.com/a/chromium.org/forum/#%21topic/blink-dev/V3RNBhQelSg
[9] Shadow DOM: https://dom.spec.whatwg.org/#shadow-trees
[10] flat tree: https://drafts.csswg.org/css-scoping/#flat-tree
[11] включающим в себя Shadow: https://dom.spec.whatwg.org/#concept-shadow-including-descendant
[12] GitHub-репозитории CSS WG: https://github.com/w3c/csswg-drafts/issues/1135
[13] Патч, добавляющий поддержку фичи: https://codereview.chromium.org/2795143004/
[14] W3C Web Platform Tests (WPT): https://github.com/w3c/web-platform-tests/tree/master/css/selectors4
[15] не без проблем: https://github.com/w3c/web-platform-tests/pull/5370
[16] апстримил один из них: https://github.com/w3c/web-platform-tests/pull/5261
[17] написал ещё несколько тестов: https://github.com/w3c/web-platform-tests/pulls?q=is%3Apr%20author%3Amrego%20label%3Aselectors4%20
[18] в спецификации HTML есть правило, описывающее такую ситуацию: https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule-one
[19] Chromium bug #491828: https://bugs.chromium.org/p/chromium/issues/detail?id=491828
[20] новой версии патча: https://codereview.chromium.org/2821303005/
[21] патч, позволяющий использовать flat tree и в WebKit: https://trac.webkit.org/changeset/215719/webkit
[22] 7: https://codereview.chromium.org/2795143004
[23] коммитов: https://codereview.chromium.org/2820063002
[24] в: https://codereview.chromium.org/2821303005
[25] Chromium : https://codereview.chromium.org/2840183002
[26] некоторые: https://codereview.chromium.org/2776693002
[27] относятся: https://codereview.chromium.org/2783663002
[28] к тестам: https://codereview.chromium.org/2803213002
[29] 2: https://trac.webkit.org/changeset/215457/webkit
[30] 1 в Firefox: https://hg.mozilla.org/mozilla-central/rev/83eeccc86bab
[31] 14 в WPT: https://github.com/w3c/web-platform-tests/commits/master?author=mrego
[32] простую демку: https://blogs.igalia.com/mrego/files/2017/05/focus-within-demo.html
[33] типичное меню, использующее :hover и :focus-within: https://blogs.igalia.com/mrego/files/2017/05/focus-within-menu.html
[34] в Firefox есть баг: https://bugzilla.mozilla.org/show_bug.cgi?id=1361301
[35] Источник: https://habrahabr.ru/post/328478/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.