Fonctionnement interne d'un processus de rendu
Il s'agit de la troisième partie d'une série de quatre articles sur le fonctionnement des navigateurs. Dans les articles précédents, nous avons abordé l'architecture multiprocessus et le flux de navigation. Dans cet article, nous allons examiner ce qui se passe dans le processus de rendu.
Le processus de rendu touche de nombreux aspects des performances Web. Étant donné que de nombreux événements se produisent dans le processus de rendu, cet article n'est qu'une présentation générale. Pour en savoir plus, consultez la section "Performances" de Web Fundamentals.
Les processus de rendu gèrent les contenus Web
Le processus de rendu est responsable de tout ce qui se passe dans un onglet. Dans un processus de rendu, le thread principal gère la plupart du code que vous envoyez à l'utilisateur. Parfois, des parties de votre code JavaScript sont gérées par des threads de travail si vous utilisez un web worker ou un service worker. Les threads de composition et de rastérisation sont également exécutés dans des processus de rendu pour afficher une page de manière efficace et fluide.
La tâche principale du processus de rendu consiste à transformer le code HTML, CSS et JavaScript en une page Web avec laquelle l'utilisateur peut interagir.

Analyse
Construction d'un DOM
Lorsque le processus de rendu reçoit un message de validation pour une navigation et commence à recevoir des données HTML, le thread principal commence à analyser la chaîne de texte (HTML) et à la transformer en modèle d'objet Document Modèle (DOM).
Le DOM est la représentation interne de la page par le navigateur, ainsi que la structure de données et l'API avec lesquelles le développeur Web peut interagir via JavaScript.
L'analyse d'un document HTML dans un DOM est définie par la norme HTML. Vous avez peut-être remarqué que l'envoi de code HTML à un navigateur ne génère jamais d'erreur. Par exemple, une balise </p>
fermante manquante est un code HTML valide. Un balisage incorrect tel que Hi! <b>I'm <i>Chrome</b>!</i>
(la balise b est fermée avant la balise i) est traité comme si vous aviez écrit Hi! <b>I'm <i>Chrome</i></b><i>!</i>
. En effet, la spécification HTML est conçue pour gérer ces erreurs de manière élégante. Si vous souhaitez savoir comment ces éléments sont traités, consultez la section Introduction à la gestion des erreurs et aux cas étranges dans l'analyseur de la spécification HTML.
Chargement de sous-ressources
Un site Web utilise généralement des ressources externes telles que des images, du CSS et du JavaScript. Ces fichiers doivent être chargés à partir du réseau ou du cache. Le thread principal pourrait les demander un par un lorsqu'il les trouve lors de l'analyse pour créer un DOM, mais pour accélérer le processus, le "lecteur de préchargement" est exécuté simultanément.
Si des éléments tels que <img>
ou <link>
figurent dans le document HTML, l'analyseur de préchargement examine les jetons générés par l'analyseur HTML et envoie des requêtes au thread réseau dans le processus du navigateur.

JavaScript peut bloquer l'analyse
Lorsque l'analyseur HTML détecte une balise <script>
, il met en pause l'analyse du document HTML et doit charger, analyser et exécuter le code JavaScript. Pourquoi ? Parce que JavaScript peut modifier la forme du document à l'aide d'éléments tels que document.write()
, qui modifie l'ensemble de la structure DOM (présentation du modèle d'analyse dans la spécification HTML contient un bon diagramme). C'est pourquoi l'analyseur HTML doit attendre l'exécution du code JavaScript avant de pouvoir reprendre l'analyse du document HTML. Si vous souhaitez en savoir plus sur ce qui se passe lors de l'exécution JavaScript, l'équipe V8 propose des conférences et des articles de blog à ce sujet.
Indiquer au navigateur comment vous souhaitez charger les ressources
Les développeurs Web peuvent envoyer des indices au navigateur de plusieurs façons afin de charger les ressources de manière fluide.
Si votre code JavaScript n'utilise pas document.write()
, vous pouvez ajouter l'attribut async
ou defer
à la balise <script>
. Le navigateur charge et exécute ensuite le code JavaScript de manière asynchrone, sans bloquer l'analyse. Vous pouvez également utiliser un module JavaScript si cela convient. <link rel="preload">
permet d'indiquer au navigateur que la ressource est absolument nécessaire pour la navigation en cours et que vous souhaitez la télécharger dès que possible. Pour en savoir plus, consultez Priorisation des ressources : demander de l'aide au navigateur.
Calcul du style
Un DOM ne suffit pas à savoir à quoi ressemblera la page, car nous pouvons styliser les éléments de la page en CSS. Le thread principal analyse le CSS et détermine le style calculé pour chaque nœud DOM. Il s'agit d'informations sur le type de style appliqué à chaque élément en fonction des sélecteurs CSS. Vous pouvez consulter ces informations dans la section computed
de DevTools.

Même si vous ne fournissez pas de CSS, chaque nœud DOM possède un style calculé. La balise <h1>
est affichée plus grande que la balise <h2>
, et des marges sont définies pour chaque élément. En effet, le navigateur dispose d'une feuille de style par défaut. Si vous souhaitez savoir à quoi ressemble le CSS par défaut de Chrome, consultez le code source ici.
Mise en page
Le processus de rendu connaît désormais la structure d'un document et les styles de chaque nœud, mais cela ne suffit pas pour afficher une page. Imaginez que vous essayez de décrire un tableau à un ami par téléphone. "Il y a un grand cercle rouge et un petit carré bleu" n'est pas assez d'informations pour que votre ami sache exactement à quoi ressemblera le tableau.

La mise en page est un processus permettant de trouver la géométrie des éléments. Le thread principal parcourt le DOM et les styles calculés, puis crée l'arborescence de mise en page contenant des informations telles que les coordonnées x et y et les tailles de la zone de délimitation. L'arborescence de mise en page peut avoir une structure semblable à celle de l'arborescence DOM, mais elle ne contient que des informations liées à ce qui est visible sur la page. Si display: none
est appliqué, cet élément ne fait pas partie de l'arborescence de mise en page (cependant, un élément avec visibility: hidden
est dans l'arborescence de mise en page). De même, si un pseudo-élément avec un contenu tel que p::before{content:"Hi!"}
est appliqué, il est inclus dans l'arborescence de mise en page, même s'il ne figure pas dans le DOM.

Déterminer la mise en page d'une page est une tâche difficile. Même la mise en page la plus simple, comme un flux de blocs de haut en bas, doit tenir compte de la taille de la police et de l'emplacement des sauts de ligne, car ils affectent la taille et la forme d'un paragraphe, ce qui affecte ensuite l'emplacement du paragraphe suivant.
Le CSS peut faire flotter un élément d'un côté, masquer un élément en cas de dépassement et modifier les directions d'écriture. Comme vous pouvez l'imaginer, cette étape de mise en page est une tâche de taille. Dans Chrome, une équipe entière d'ingénieurs travaille sur la mise en page. Si vous souhaitez en savoir plus sur leur travail, quelques conférences de la conférence BlinkOn sont enregistrées et très intéressantes à regarder.
Peinture

Un DOM, un style et une mise en page ne suffisent toujours pas à afficher une page. Disons que vous essayez de reproduire un tableau. Vous connaissez la taille, la forme et l'emplacement des éléments, mais vous devez toujours déterminer dans quel ordre les peindre.
Par exemple, z-index
peut être défini pour certains éléments. Dans ce cas, le rendu dans l'ordre des éléments écrits en HTML entraînera un rendu incorrect.

À cette étape de peinture, le thread principal parcourt l'arborescence de mise en page pour créer des enregistrements de peinture. L'enregistrement de peinture est une note du processus de peinture, par exemple "arrière-plan d'abord, puis texte, puis rectangle". Si vous avez dessiné sur un élément <canvas>
à l'aide de JavaScript, ce processus peut vous sembler familier.

La mise à jour du pipeline de rendu est coûteuse
L'élément le plus important à comprendre dans le pipeline de rendu est qu'à chaque étape, le résultat de l'opération précédente est utilisé pour créer de nouvelles données. Par exemple, si quelque chose change dans l'arborescence de mise en page, l'ordre de peinture doit être régénéré pour les parties du document concernées.
Si vous animez des éléments, le navigateur doit exécuter ces opérations entre chaque frame. La plupart de nos écrans se rafraîchissent 60 fois par seconde (60 FPS). L'animation apparaît fluide aux yeux humains lorsque vous déplacez des éléments à l'écran à chaque frame. Toutefois, si l'animation manque les images intermédiaires, la page semblera "défectueuse".

Même si vos opérations de rendu suivent l'actualisation de l'écran, ces calculs s'exécutent sur le thread principal, ce qui signifie qu'il peut être bloqué lorsque votre application exécute JavaScript.

Vous pouvez diviser l'opération JavaScript en petits blocs et planifier son exécution à chaque frame à l'aide de requestAnimationFrame()
. Pour en savoir plus, consultez la section Optimiser l'exécution JavaScript. Vous pouvez également exécuter votre JavaScript dans des Web Workers pour éviter de bloquer le thread principal.

Composition
Comment dessiner une page ?
Maintenant que le navigateur connaît la structure du document, le style de chaque élément, la géométrie de la page et l'ordre de peinture, comment dessine-t-il une page ? La conversion de ces informations en pixels à l'écran s'appelle la rastérisation.
Une solution simple consisterait à rasteriser des parties dans la fenêtre d'affichage. Si un utilisateur fait défiler la page, déplacez le frame rasterisé et remplissez les parties manquantes en effectuant plus de rasterisation. C'est ainsi que Chrome gérait la rastérisation lors de sa première sortie. Toutefois, le navigateur moderne exécute un processus plus sophistiqué appelé "compositing".
Qu'est-ce que le compositing ?
Le compositing est une technique qui consiste à séparer les parties d'une page en calques, à les échantillonner séparément et à les composer en tant que page dans un thread distinct appelé thread de composition. Si le défilement se produit, étant donné que les calques sont déjà rastérisés, il suffit de composer un nouveau frame. Vous pouvez réaliser une animation de la même manière en déplaçant des calques et en composant un nouveau frame.
Vous pouvez voir comment votre site Web est divisé en calques dans DevTools à l'aide du panneau "Calques".
Diviser en calques
Pour déterminer les éléments qui doivent se trouver dans quelles couches, le thread principal parcourt l'arborescence de mise en page pour créer l'arborescence des calques (cette partie est appelée "Mettre à jour l'arborescence des calques" dans le panneau des performances de DevTools). Si certaines parties d'une page qui devraient être une couche distincte (comme un menu latéral coulissant) n'en reçoivent pas, vous pouvez en donner un indice au navigateur à l'aide de l'attribut will-change
en CSS.

Vous pourriez être tenté d'ajouter des calques à chaque élément, mais la composition sur un nombre excessif de calques peut ralentir l'opération par rapport à la rastérisation de petites parties d'une page à chaque frame. Il est donc essentiel de mesurer les performances de rendu de votre application. Pour en savoir plus, consultez S'en tenir aux propriétés réservées au moteur de composition et gérer le nombre de calques.
Réseau raster et composite en dehors du thread principal
Une fois l'arborescence des calques créée et les ordres de peinture déterminés, le thread principal valide ces informations auprès du thread du compositeur. Le thread du moteur de rendu effectue ensuite la rastérisation de chaque couche. Une couche peut être aussi grande que la longueur totale d'une page. Le thread du compositeur les divise donc en tuiles et envoie chaque tuile aux threads de rastérisation. Les threads de rastérisation rasterisent chaque tuile et les stockent dans la mémoire du GPU.

Le thread du compositeur peut hiérarchiser différents threads de rastérisation afin que les éléments situés dans le viewport (ou à proximité) puissent être rastérisés en premier. Une couche comporte également plusieurs cartes pour différentes résolutions afin de gérer des actions telles que le zoom avant.
Une fois les tuiles rastérisées, le thread du moteur de rendu rassemble les informations sur les tuiles appelées quads de dessin pour créer un cadre de moteur de rendu.
Dessiner des quads | Contient des informations telles que l'emplacement de la carte en mémoire et l'emplacement de la page où dessiner la carte en tenant compte de la composition de la page. |
Cadre du moteur de composition | Ensemble de quads de dessin représentant un cadre de page. |
Un frame de composition est ensuite envoyé au processus du navigateur via IPC. À ce stade, un autre frame de compositeur peut être ajouté à partir du thread d'UI pour le changement d'UI du navigateur ou à partir d'autres processus de rendu pour les extensions. Ces frames de composition sont envoyés au GPU pour être affichés à l'écran. Si un événement de défilement est reçu, le thread du moteur de composition crée un autre frame de moteur de composition à envoyer au GPU.

L'avantage de la composition est qu'elle est effectuée sans impliquer le thread principal. Le thread du moteur de rendu n'a pas besoin d'attendre le calcul des styles ni l'exécution JavaScript. C'est pourquoi la composition uniquement d'animations est considérée comme la meilleure option pour des performances fluides. Si la mise en page ou la peinture doit être calculée à nouveau, le thread principal doit être impliqué.
Conclusion
Dans cet article, nous avons examiné le pipeline de rendu de l'analyse à la composition. Nous espérons que vous pourrez désormais en savoir plus sur l'optimisation des performances d'un site Web.
Dans le prochain et dernier article de cette série, nous examinerons le thread du moteur de rendu plus en détail et verrons ce qui se passe lorsque des entrées utilisateur telles que mouse move
et click
sont reçues.
Avez-vous aimé cet article ? Si vous avez des questions ou des suggestions pour un prochain post, n'hésitez pas à me contacter dans la section des commentaires ci-dessous ou sur @kosamari sur Twitter.