Avez-vous remarqué que les propriétés CSS de l'onglet Styles de Chrome DevTools sont un peu plus soignées ces derniers temps ? Ces mises à jour, déployées entre Chrome 121 et 128, sont le résultat d'une amélioration significative de l'analyse et de la présentation des valeurs CSS. Dans cet article, nous allons vous présenter les détails techniques de cette transformation, en passant d'un système de correspondance d'expressions régulières à un analyseur plus robuste.
Comparons les outils de développement actuels avec ceux de la version précédente:
Une belle différence, non ? Voici le détail des principales améliorations:
color-mix
: aperçu pratique représentant visuellement les deux arguments de couleur dans la fonctioncolor-mix
.pink
: aperçu de la couleur cliquable pour la couleurpink
. Cliquez dessus pour ouvrir un sélecteur de couleur et effectuer facilement des réglages.var(--undefined, [fallback value])
. Amélioration de la gestion des variables non définies, avec la variable non définie grisée et la valeur de remplacement active (dans ce cas, une couleur HSL) affichée avec un aperçu de couleur cliquable.hsl(…)
: autre aperçu de couleur cliquable pour la fonction de couleurhsl
, permettant d'accéder rapidement au sélecteur de couleur.177deg
: cadran angulaire cliquable qui vous permet de faire glisser et de modifier interactivement la valeur de l'angle.var(--saturation, …)
: lien cliquable vers la définition de la propriété personnalisée, ce qui permet de passer facilement à la déclaration appropriée.
La différence est frappante. Pour ce faire, nous avons dû apprendre à DevTools à comprendre les valeurs des propriétés CSS beaucoup mieux qu'auparavant.
Ces aperçus n'étaient-ils pas déjà disponibles ?
Bien que ces icônes de prévisualisation puissent sembler familières, elles n'ont pas toujours été affichées de manière cohérente, en particulier dans une syntaxe CSS complexe comme dans l'exemple ci-dessus. Même dans les cas où elles fonctionnaient, des efforts importants étaient souvent nécessaires pour les faire fonctionner correctement.
En effet, le système d'analyse des valeurs a évolué de manière organique depuis les débuts de DevTools. Cependant, il n'a pas pu suivre les nouvelles fonctionnalités incroyables que nous offre le CSS et l'augmentation de la complexité du langage qui en découle. Le système nécessitait une refonte complète pour suivre l'évolution, et c'est exactement ce que nous avons fait.
Traitement des valeurs de propriété CSS
Dans DevTools, le processus de rendu et de décoration des déclarations de propriétés dans l'onglet Styles est divisé en deux phases distinctes:
- Analyse structurelle. Cette phase initiale analyse la déclaration de propriété pour identifier ses composants sous-jacents et leurs relations. Par exemple, dans la déclaration
border: 1px solid red
, il reconnaît1px
comme une longueur,solid
comme une chaîne etred
comme une couleur. - Rendu. En s'appuyant sur l'analyse structurelle, la phase de rendu transforme ces composants en une représentation HTML. Cela enrichit le texte de la propriété affichée avec des éléments interactifs et des repères visuels. Par exemple, la valeur de couleur
red
est affichée avec une icône de couleur cliquable qui, lorsqu'elle est cliquée, affiche un sélecteur de couleur permettant de la modifier facilement.
Expressions régulières
Auparavant, nous utilisions des expressions régulières pour analyser les valeurs des propriétés à des fins structurelles. Nous avons géré une liste d'expressions régulières pour faire correspondre les bits de valeurs de propriété que nous envisageons de décorer. Par exemple, il y avait des expressions correspondant à des couleurs, des longueurs, des angles CSS, des sous-expressions plus complexes telles que des appels de fonction var
, etc. Nous avons analysé le texte de gauche à droite pour effectuer une analyse des valeurs, en recherchant en permanence la première expression de la liste qui correspond à la partie suivante du texte.
Bien que cette approche fonctionne la plupart du temps, le nombre de cas où elle ne fonctionne pas ne cesse de croître. Au fil des ans, nous avons reçu un grand nombre de signalements de bugs pour lesquels la mise en correspondance n'était pas tout à fait correcte. Pour les résoudre (certaines solutions étaient simples, d'autres très complexes), nous avons dû repenser notre approche afin de limiter notre dette technique. Examinons quelques-uns de ces problèmes.
Correspondance color-mix()
L'expression régulière que nous avons utilisée pour la fonction color-mix()
était la suivante:
/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g
Ce qui correspond à sa syntaxe:
color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})
Essayez d'exécuter l'exemple suivant pour visualiser les correspondances.
const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;
// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);
re.exec('');
// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);
L'exemple, plus simple, convient bien. Toutefois, dans l'exemple plus complexe, la correspondance <firstColor>
est hsl(177deg var(--saturation
et la correspondance <secondColor>
est 100%) 50%))
, ce qui n'a aucun sens.
Nous savions que c'était un problème. Après tout, le CSS en tant que langage formel n'est pas standard. Nous avons donc déjà inclus un traitement spécial pour gérer des arguments de fonction plus complexes, comme les fonctions var
. Toutefois, comme vous pouvez le voir sur la première capture d'écran, cela ne fonctionne toujours pas dans tous les cas.
tan()
correspondant
L'un des bugs les plus hilarants signalés concernait la fonction trigonométrique tan()
. L'expression régulière que nous utilisions pour faire correspondre les couleurs incluait une sous-expression \b[a-zA-Z]+\b(?!-)
pour faire correspondre les couleurs nommées telles que le mot clé red
. Nous avons ensuite vérifié si la partie correspondante était en fait une couleur nommée, et devinez quoi : tan
est aussi une couleur nommée ! Nous avons donc interprété à tort les expressions tan()
comme des couleurs.
Correspondance var()
Prenons un autre exemple : les fonctions var()
avec un remplacement contenant d'autres références var()
: var(--non-existent, var(--margin-vertical))
.
Notre expression régulière pour var()
correspondrait parfaitement à cette valeur. Sauf qu'il s'arrêterait à la première parenthèse fermante. Le texte ci-dessus est donc mis en correspondance avec var(--non-existent, var(--margin-vertical)
. Il s'agit d'une limitation classique de la mise en correspondance d'expressions régulières. Les langages qui nécessitent des parenthèses correspondantes ne sont pas fondamentalement réguliers.
Passer à un analyseur CSS
Lorsque l'analyse de texte à l'aide d'expressions régulières ne fonctionne plus (parce que la langue analysée n'est pas régulière), il existe une étape canonique: utiliser un analyseur pour une grammaire de type supérieur. Pour le CSS, cela signifie un analyseur pour les langages sans contexte. En fait, un tel système d'analyseur existait déjà dans le code de base de DevTools: le Lezer de CodeMirror, qui sert de base, par exemple, à la mise en surbrillance de la syntaxe dans CodeMirror, l'éditeur que vous trouverez dans le panneau Sources. L'analyseur CSS de Lezer nous a permis de produire des arborescences syntaxiques (non abstraites) pour les règles CSS et était prêt à l'emploi. Victoire.
En revanche, il n'était pas possible, dès la première utilisation, de passer d'une correspondance basée sur des expressions régulières à une correspondance basée sur l'analyseur directement: les deux approches fonctionnent dans des directions opposées. Lorsque des éléments de valeur étaient mis en correspondance avec des expressions régulières, DevTools analysait l'entrée de gauche à droite, en essayant à plusieurs reprises de trouver la correspondance la plus ancienne dans une liste ordonnée de modèles. Avec un arbre syntaxique, la mise en correspondance commence par le bas vers le haut, par exemple en analysant d'abord les arguments d'un appel avant d'essayer de mettre en correspondance l'appel de fonction. Imaginez que vous évaluez une expression arithmétique, où vous devez d'abord tenir compte des expressions entre parenthèses, puis des opérateurs multiplicatifs, puis des opérateurs additifs. Dans ce contexte, la mise en correspondance basée sur une expression régulière correspond à l'évaluation de l'expression arithmétique de gauche à droite. Nous ne voulions vraiment pas réécrire l'intégralité du système de mise en correspondance à partir de zéro: il y avait 15 couples de paires de correspondances et de moteurs de rendu différents, avec des milliers de lignes de code, ce qui rendait peu probable que nous puissions le publier en une seule étape.
Nous avons donc trouvé une solution qui nous a permis d'apporter des modifications incrémentales, que nous décrirons plus en détail ci-dessous. En résumé, nous avons conservé l'approche en deux phases, mais dans la première phase, nous essayons de faire correspondre les sous-expressions de bas en haut (ce qui rompt avec le flux d'expression régulière), et dans la deuxième phase, nous effectuons le rendu de haut en bas. Dans les deux phases, nous avons pu utiliser les outils de mise en correspondance et les rendus basés sur des expressions régulières, pratiquement inchangés, et ainsi les migrer un par un.
Phase 1: Mise en correspondance ascendante
La première phase consiste plus ou moins exactement et exclusivement à faire ce qui est indiqué sur la couverture. Nous parcourons l'arborescence dans l'ordre, de bas en haut, et essayons de faire correspondre des sous-expressions à chaque nœud de l'arborescence syntaxique que nous visitons. Pour établir une correspondance avec une sous-expression spécifique, un outil de mise en correspondance peut utiliser une expression régulière de la même manière que dans le système existant. Depuis la version 128, nous le faisons encore dans certains cas, par exemple pour les longueurs correspondantes. Un outil de mise en correspondance peut également analyser la structure du sous-arbre dont le nœud racine est le nœud actuel. Il peut ainsi détecter les erreurs de syntaxe et enregistrer des informations structurelles en même temps.
Prenons l'exemple d'arbre syntaxique ci-dessus:
Pour cet arbre, nos outils de correspondance s'appliquent dans l'ordre suivant:
hsl(
177deg
var(--saturation, 100%) 50%)
: nous découvrons d'abord le premier argument de l'appel de fonctionhsl
, l'angle de teinte. Nous l'associons à un outil de mise en correspondance d'angles afin de pouvoir décorer la valeur d'angle avec l'icône d'angle.hsl(177deg
var(--saturation, 100%)
50%)
: nous découvrons ensuite l'appel de fonctionvar
avec un outil de mise en correspondance de variables. Pour ces appels, nous voulons principalement effectuer deux actions :- Recherchez la déclaration de la variable et calculez sa valeur, puis ajoutez un lien et un popover au nom de la variable pour vous y connecter, respectivement.
- Décorez l'appel avec une icône de couleur si la valeur calculée est une couleur. Il y a en fait un troisième élément, mais nous en reparlerons plus tard.
hsl(177deg var(--saturation, 100%) 50%)
: enfin, nous faisons correspondre l'expression d'appel de la fonctionhsl
afin de pouvoir la décorer avec l'icône de couleur.
En plus de rechercher des sous-expressions que nous aimerions décorer, il existe une deuxième fonctionnalité que nous exécutons dans le cadre du processus de mise en correspondance. Notez que lors de l'étape 2, nous avons indiqué que nous recherchions la valeur calculée pour un nom de variable. En fait, nous allons encore plus loin et propageons les résultats vers le haut de l'arborescence. Et pas seulement pour la variable, mais aussi pour la valeur de remplacement. Lors de la visite d'un nœud de fonction var
, ses enfants ont été consultés au préalable. Nous connaissons donc déjà les résultats de toutes les fonctions var
qui pourraient apparaître dans la valeur de remplacement. Nous pouvons donc remplacer facilement et à moindre coût les fonctions var
par leurs résultats instantanément, ce qui nous permet de répondre facilement à des questions telles que "Le résultat de cet appel var
est-il une couleur ?", comme nous l'avons fait à l'étape 2.
Phase 2: Affichage de haut en bas
Pour la deuxième phase, nous inverserons la direction. En utilisant les résultats de la phase 1, nous lisons l'arborescence au format HTML en la parcourant dans l'ordre, de haut en bas. Pour chaque nœud visité, nous vérifions s'il correspond et, le cas échéant, nous appelons le moteur de rendu correspondant de l'outil de mise en correspondance. Nous évitons d'avoir à traiter de manière spéciale les nœuds qui ne contiennent que du texte (comme le NumberLiteral
"50%") en incluant un outil de mise en correspondance et un moteur de rendu par défaut pour les nœuds de texte. Les moteurs de rendu génèrent simplement des nœuds HTML qui, une fois assemblés, produisent la représentation de la valeur de la propriété, y compris ses décorations.
Pour l'exemple d'arborescence, voici l'ordre dans lequel la valeur de la propriété est affichée:
- Consultez l'appel de fonction
hsl
. Il y a correspondance. Appelez donc le moteur de rendu de la fonction de couleur. Il a deux fonctions :- Calcule la valeur de couleur réelle à l'aide du mécanisme de substitution instantanée pour tous les arguments
var
, puis dessine une icône de couleur. - Affiche de manière récursive les enfants de
CallExpression
. Cela se charge automatiquement d'afficher le nom de la fonction, les parenthèses et les virgules, qui ne sont que du texte.
- Calcule la valeur de couleur réelle à l'aide du mécanisme de substitution instantanée pour tous les arguments
- Accédez au premier argument de l'appel
hsl
. Comme il correspond, appelez le moteur de rendu d'angle, qui dessine l'icône d'angle et le texte de l'angle. - Accédez au deuxième argument, qui est l'appel
var
. La correspondance a été établie. Appelez la variable renderer, qui affiche le résultat suivant :- Texte
var(
au début. - Le nom de la variable est décoré avec un lien vers la définition de la variable ou avec une couleur de texte grise pour indiquer qu'elle n'a pas été définie. Un popover est également ajouté à la variable pour afficher des informations sur sa valeur.
- La virgule affiche ensuite de manière récursive la valeur de remplacement.
- Parenthèse fermante.
- Texte
- Accédez au dernier argument de l'appel
hsl
. Il n'y a pas de correspondance. Affichez donc simplement son contenu textuel.
Avez-vous remarqué que, dans cet algorithme, un rendu contrôle entièrement la façon dont les enfants d'un nœud mis en correspondance sont affichés ? L'affichage récursif des enfants est proactif. C'est cette astuce qui a permis de migrer progressivement du rendu basé sur les expressions régulières vers le rendu basé sur l'arborescence syntaxique. Pour les nœuds correspondant à un ancien outil de correspondance d'expression régulière, le moteur de rendu correspondant peut être utilisé sous sa forme d'origine. En termes d'arborescence syntaxique, il serait responsable de l'affichage de l'ensemble du sous-arbre, et son résultat (un nœud HTML) pourrait être branché de manière claire dans le processus de rendu environnant. Cela nous a permis de porter les outils de mise en correspondance et les moteurs de rendu par paires, et de les remplacer un par un.
Les moteurs de rendu qui contrôlent le rendu des enfants de leur nœud correspondant présentent une autre fonctionnalité intéressante : ils nous permettent de raisonner sur les dépendances entre les icônes que nous ajoutons. Dans l'exemple ci-dessus, la couleur produite par la fonction hsl
dépend évidemment de sa valeur de teinte. Cela signifie que la couleur affichée par l'icône de couleur dépend de l'angle affiché par l'icône d'angle. Si l'utilisateur ouvre l'éditeur d'angle via cette icône et modifie l'angle, nous pouvons désormais modifier la couleur de l'icône de couleur en temps réel:
Comme vous pouvez le voir dans l'exemple ci-dessus, nous utilisons également ce mécanisme pour d'autres associations d'icônes, comme pour color-mix()
et ses deux canaux de couleur, ou les fonctions var
qui renvoient une couleur à partir de son remplacement.
Impact sur la performance
Lorsque nous avons commencé à examiner ce problème pour améliorer la fiabilité et résoudre des problèmes de longue date, nous nous attendions à une régression des performances, car nous avons commencé à exécuter un analyseur complet. Pour tester cela, nous avons créé un benchmark qui affiche environ 3 500 déclarations de propriétés et profilé les versions basées sur les expressions régulières et sur l'analyseur avec un débit limité à 6 fois sur une machine M1.
Comme nous nous y attendions, l'approche basée sur l'analyse s'est avérée 27% plus lente que l'approche basée sur les expressions régulières dans ce cas. L'approche basée sur les expressions régulières a nécessité 11 secondes pour l'affichage, et l'approche basée sur l'analyseur 15 secondes.
Compte tenu des avantages que présente cette nouvelle approche, nous avons décidé de l'adopter.
Remerciements
Nous remercions Sofia Emelianova et Jecelyn Yeen pour leur précieuse aide à la rédaction de ce post.
Télécharger les canaux de prévisualisation
Envisagez d'utiliser Chrome Canary, Dev ou Bêta comme navigateur de développement par défaut. Ces canaux de prévisualisation vous donnent accès aux dernières fonctionnalités de DevTools, vous permettent de tester les API de plates-formes Web de pointe et vous aident à détecter les problèmes sur votre site avant vos utilisateurs.
Contacter l'équipe des outils pour les développeurs Chrome
Utilisez les options suivantes pour discuter des nouvelles fonctionnalités, des mises à jour ou de tout autre sujet lié aux outils de développement.
- Envoyez-nous vos commentaires et vos demandes de fonctionnalités sur crbug.com.
- Signalez un problème dans les outils de développement à l'aide de l'icône Plus d'options > Aide > Signaler un problème dans les outils de développement dans les outils de développement.
- Tweetez à l'adresse @ChromeDevTools.
- Laissez des commentaires sur les vidéos YouTube sur les nouveautés des outils pour les développeurs ou sur les vidéos YouTube sur les conseils concernant les outils pour les développeurs.