Fonctionnement des navigateurs

Dans les coulisses des navigateurs Web modernes

Préface

Cette présentation complète du fonctionnement interne de WebKit et de Gecko est le résultat de nombreuses recherches effectuées par le développeur israélien Tali Garsiel. Pendant quelques années, elle a examiné toutes les données publiées sur les composants internes des navigateurs et a passé beaucoup de temps à lire le code source des navigateurs Web. Elle écrit :

En tant que développeur Web, comprendre le fonctionnement interne des opérations du navigateur vous aide à prendre de meilleures décisions et à comprendre les justifications des bonnes pratiques de développement. Bien qu'il s'agisse d'un document assez long, nous vous recommandons de prendre le temps de l'étudier. Vous ne le regretterez pas.

Paul Irish, Chrome Developer Relations

Introduction

Les navigateurs Web sont les logiciels les plus utilisés. Dans cette introduction, je vais vous expliquer comment ils fonctionnent en coulisses. Nous allons voir ce qui se passe lorsque vous saisissez google.com dans la barre d'adresse jusqu'à ce que la page Google s'affiche sur l'écran du navigateur.

Navigateurs dont nous allons parler

Cinq navigateurs principaux sont utilisés sur ordinateur aujourd'hui : Chrome, Internet Explorer, Firefox, Safari et Opera. Sur mobile, les principaux navigateurs sont Android Browser, iPhone, Opera Mini et Opera Mobile, UC Browser, les navigateurs Nokia S40/S60 et Chrome. Tous, à l'exception des navigateurs Opera, sont basés sur WebKit. Je vais donner des exemples de navigateurs Open Source Firefox et Chrome, ainsi que de Safari (qui est en partie Open Source). Selon les statistiques de StatCounter (en juin 2013), Chrome, Firefox et Safari représentent environ 71 % de l'utilisation mondiale des navigateurs pour ordinateur. Sur mobile, le navigateur Android, l'iPhone et Chrome représentent environ 54 % de l'utilisation.

La fonctionnalité principale du navigateur

La fonction principale d'un navigateur est de présenter la ressource Web que vous choisissez, en la demandant au serveur et en l'affichant dans la fenêtre du navigateur. La ressource est généralement un document HTML, mais peut également être un PDF, une image ou un autre type de contenu. L'emplacement de la ressource est spécifié par l'utilisateur à l'aide d'un URI (Uniform Resource Identifier).

La façon dont le navigateur interprète et affiche les fichiers HTML est spécifiée dans les spécifications HTML et CSS. Ces spécifications sont gérées par le W3C (World Wide Web Consortium), l'organisme de normalisation du Web. Pendant des années, les navigateurs ne se sont conformés qu'à une partie des spécifications et ont développé leurs propres extensions. Cela a entraîné de graves problèmes de compatibilité pour les auteurs Web. Aujourd'hui, la plupart des navigateurs sont plus ou moins conformes aux spécifications.

Les interfaces utilisateur des navigateurs ont beaucoup de points communs. Voici quelques-uns des éléments d'interface utilisateur courants :

  1. Barre d'adresse pour insérer un URI
  2. Boutons "Précédent" et "Suivant"
  3. Options de favoris
  4. Boutons d'actualisation et d'arrêt pour actualiser ou arrêter le chargement des documents actuels
  5. Bouton Accueil qui vous redirige vers votre page d'accueil

Curieusement, l'interface utilisateur du navigateur n'est spécifiée dans aucune spécification formelle. Elle provient simplement de bonnes pratiques façonnées au fil des années d'expérience et de l'imitation des navigateurs entre eux. La spécification HTML5 ne définit pas les éléments d'interface utilisateur qu'un navigateur doit posséder, mais liste certains éléments courants. Parmi ceux-ci figurent la barre d'adresse, la barre d'état et la barre d'outils. Bien sûr, certaines fonctionnalités sont propres à un navigateur spécifique, comme le gestionnaire de téléchargements de Firefox.

Infrastructure de haut niveau

Les principaux composants du navigateur sont les suivants :

  1. L'interface utilisateur : elle inclut la barre d'adresse, les boutons Précédent/Suivant, le menu des favoris, etc. Elle comprend toutes les parties de l'affichage du navigateur, à l'exception de la fenêtre dans laquelle vous voyez la page demandée.
  2. Le moteur du navigateur : gère les actions entre l'UI et le moteur de rendu.
  3. Le moteur de rendu : responsable de l'affichage du contenu demandé. Par exemple, si le contenu demandé est du code HTML, le moteur de rendu analyse le code HTML et CSS, puis affiche le contenu analysé à l'écran.
  4. Mise en réseau : pour les appels réseau tels que les requêtes HTTP, en utilisant différentes implémentations pour différentes plates-formes derrière une interface indépendante de la plate-forme.
  5. Backend d'UI : utilisé pour dessiner des widgets de base tels que des boîtes combinées et des fenêtres. Ce backend expose une interface générique qui n'est pas spécifique à une plate-forme. En arrière-plan, il utilise les méthodes d'interface utilisateur du système d'exploitation.
  6. Interpréteur JavaScript. Utilisé pour analyser et exécuter du code JavaScript.
  7. Stockage de données. Il s'agit d'une couche de persistance. Le navigateur peut avoir besoin d'enregistrer toutes sortes de données localement, comme des cookies. Les navigateurs sont également compatibles avec des mécanismes de stockage tels que localStorage, IndexedDB, WebSQL et FileSystem.
Composants du navigateur
Figure 1 : Composants du navigateur

Il est important de noter que les navigateurs tels que Chrome exécutent plusieurs instances du moteur de rendu : une pour chaque onglet. Chaque onglet s'exécute dans un processus distinct.

Moteurs de rendu

Le rôle du moteur de rendu est, eh bien… le rendu, c'est-à-dire l'affichage du contenu demandé sur l'écran du navigateur.

Par défaut, le moteur de rendu peut afficher des documents HTML et XML, ainsi que des images. Il peut afficher d'autres types de données via des plug-ins ou des extensions, par exemple des documents PDF à l'aide d'un plug-in de visionneuse PDF. Toutefois, dans ce chapitre, nous nous concentrerons sur le cas d'utilisation principal : l'affichage de code HTML et d'images formatés à l'aide de CSS.

Les différents navigateurs utilisent différents moteurs de rendu : Internet Explorer utilise Trident, Firefox utilise Gecko et Safari utilise WebKit. Chrome et Opera (à partir de la version 15) utilisent Blink, un fork de WebKit.

WebKit est un moteur de rendu Open Source qui a d'abord été conçu pour la plate-forme Linux, puis modifié par Apple pour être compatible avec Mac et Windows.

Le flux principal

Le moteur de rendu commence à récupérer le contenu du document demandé à partir de la couche réseau. Cette opération est généralement effectuée par fragments de 8 ko.

Ensuite, voici le flux de base du moteur de rendu :

Flux de base du moteur de rendu
Figure 2 : Flux de base du moteur de rendu

Le moteur de rendu commence à analyser le document HTML et convertit les éléments en nœuds DOM dans une arborescence appelée "arborescence de contenu". Le moteur analysera les données de style, à la fois dans les fichiers CSS externes et dans les éléments de style. Les informations de style et les instructions visuelles du code HTML seront utilisées pour créer une autre arborescence : l'arborescence de rendu.

L'arborescence de rendu contient des rectangles avec des attributs visuels tels que la couleur et les dimensions. Les rectangles sont dans le bon ordre pour être affichés à l'écran.

Une fois l'arbre de rendu construit, il passe par un processus de mise en page. Cela signifie que vous devez attribuer à chaque nœud les coordonnées exactes de l'endroit où il doit apparaître à l'écran. L'étape suivante est la mise en couleur : l'arborescence de rendu est parcourue et chaque nœud est mis en couleur à l'aide de la couche de backend de l'interface utilisateur.

Il est important de comprendre qu'il s'agit d'un processus progressif. Pour améliorer l'expérience utilisateur, le moteur de rendu essaie d'afficher le contenu à l'écran dès que possible. Il n'attend pas que tout le code HTML soit analysé avant de commencer à créer et à mettre en page l'arborescence de rendu. Certaines parties du contenu seront analysées et affichées, tandis que le processus se poursuivra avec le reste du contenu qui continue d'arriver du réseau.

Exemples de flux principaux

Flux principal WebKit.
Figure 3 : Flux principal de WebKit
Flux principal du moteur de rendu Gecko de Mozilla.
Figure 4 : Flux principal du moteur de rendu Gecko de Mozilla

Les figures 3 et 4 montrent que, même si WebKit et Gecko utilisent une terminologie légèrement différente, le flux est fondamentalement le même.

Gecko appelle l'arborescence des éléments formatés visuellement une "arborescence de cadres". Chaque élément est un frame. WebKit utilise le terme "Render Tree" (arbre de rendu), qui se compose de "Render Objects" (objets de rendu). WebKit utilise le terme "mise en page" pour le placement des éléments, tandis que Gecko l'appelle "Reflow". "Attachment" est le terme utilisé par WebKit pour désigner la connexion des nœuds DOM et des informations visuelles afin de créer l'arbre de rendu. Une différence mineure non sémantique est que Gecko possède une couche supplémentaire entre le code HTML et l'arborescence DOM. Il s'agit du "récepteur de contenu", qui est une fabrique permettant de créer des éléments DOM. Nous allons aborder chaque partie du flux :

Analyse – Généralités

L'analyse étant un processus très important dans le moteur de rendu, nous allons l'examiner un peu plus en détail. Commençons par une brève introduction à l'analyse syntaxique.

L'analyse d'un document consiste à le traduire dans une structure que le code peut utiliser. Le résultat de l'analyse est généralement une arborescence de nœuds qui représentent la structure du document. C'est ce qu'on appelle un arbre d'analyse ou un arbre syntaxique.

Par exemple, l'analyse de l'expression 2 + 3 - 1 peut renvoyer l'arbre suivant :

Nœud de l'arborescence d'expressions mathématiques.
Figure 5 : Nœud d'arborescence d'expression mathématique

Grammaire

L'analyse syntaxique est basée sur les règles de syntaxe auxquelles le document obéit : la langue ou le format dans lequel il a été écrit. Chaque format que vous pouvez analyser doit avoir une grammaire déterministe composée de règles de vocabulaire et de syntaxe. On parle alors de grammaire non contextuelle. Les langues humaines ne sont pas de ce type et ne peuvent donc pas être analysées avec des techniques d'analyse syntaxique conventionnelles.

Combinaison analyseur-analyseur lexical

L'analyse syntaxique peut être divisée en deux sous-processus : l'analyse lexicale et l'analyse syntaxique.

L'analyse lexicale est le processus qui consiste à diviser l'entrée en jetons. Les jetons constituent le vocabulaire du langage : l'ensemble des composants valides. Dans le langage humain, il s'agit de tous les mots qui apparaissent dans le dictionnaire de cette langue.

L'analyse syntaxique consiste à appliquer les règles de syntaxe du langage.

Les analyseurs syntaxiques divisent généralement le travail entre deux composants : l'analyseur lexical (parfois appelé "générateur de jetons"), qui est chargé de diviser l'entrée en jetons valides, et l'analyseur syntaxique, qui est chargé de construire l'arbre d'analyse en analysant la structure du document selon les règles de syntaxe du langage.

L'analyseur lexical sait comment supprimer les caractères non pertinents tels que les espaces blancs et les sauts de ligne.

Du document source aux arbres d'analyse syntaxique
Figure 6 : Du document source aux arbres d'analyse

Le processus d'analyse est itératif. L'analyseur demande généralement un nouveau jeton à l'analyseur lexical et tente de le faire correspondre à l'une des règles de syntaxe. Si une règle correspond, un nœud correspondant au jeton est ajouté à l'arbre d'analyse, et l'analyseur demande un autre jeton.

Si aucune règle ne correspond, l'analyseur stocke le jeton en interne et continue de demander des jetons jusqu'à ce qu'une règle correspondant à tous les jetons stockés en interne soit trouvée. Si aucune règle n'est trouvée, l'analyseur génère une exception. Cela signifie que le document n'était pas valide et contenait des erreurs de syntaxe.

Traduction

Dans de nombreux cas, l'arbre d'analyse n'est pas le produit final. L'analyse syntaxique est souvent utilisée pour la traduction : elle transforme le document d'entrée dans un autre format. La compilation en est un exemple. Le compilateur qui compile le code source en code machine l'analyse d'abord pour créer un arbre d'analyse, puis traduit l'arbre en un document de code machine.

Flux de compilation
Figure 7 : Flux de compilation

Exemple d'analyse syntaxique

Dans la figure 5, nous avons créé un arbre d'analyse à partir d'une expression mathématique. Essayons de définir un langage mathématique simple et de voir le processus d'analyse.

Syntaxe :

  1. Les composants de la syntaxe du langage sont les expressions, les termes et les opérations.
  2. Notre langage peut inclure un nombre illimité d'expressions.
  3. Une expression est définie comme un "terme" suivi d'une "opération" suivie d'un autre terme.
  4. Une opération est un jeton plus ou un jeton moins.
  5. Un terme est un jeton entier ou une expression

Analysons l'entrée 2 + 3 - 1.

La première sous-chaîne qui correspond à une règle est 2 : selon la règle 5, il s'agit d'un terme. La deuxième correspondance est 2 + 3 : elle correspond à la troisième règle, à savoir un terme suivi d'une opération, elle-même suivie d'un autre terme. La correspondance suivante ne sera trouvée qu'à la fin de l'entrée. 2 + 3 - 1 est une expression, car nous savons déjà que 2 + 3 est un terme. Nous avons donc un terme suivi d'une opération, suivie d'un autre terme. 2 + + ne correspondra à aucune règle et constitue donc une entrée non valide.

Définitions formelles du vocabulaire et de la syntaxe

Le vocabulaire est généralement exprimé par des expressions régulières.

Par exemple, notre langage sera défini comme suit :

INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -

Comme vous pouvez le voir, les entiers sont définis par une expression régulière.

La syntaxe est généralement définie dans un format appelé BNF. Notre langage sera défini comme suit :

expression :=  term  operation  term
operation :=  PLUS | MINUS
term := INTEGER | expression

Nous avons dit qu'un langage peut être analysé par des analyseurs standards si sa grammaire est une grammaire non contextuelle. Une définition intuitive d'une grammaire non contextuelle est une grammaire qui peut être entièrement exprimée en BNF. Pour obtenir une définition formelle, consultez l'article de Wikipédia sur la grammaire non contextuelle.

Types d'analyseurs syntaxiques

Il existe deux types d'analyseurs : les analyseurs descendants et les analyseurs ascendants. Pour faire simple, les analyseurs syntaxiques descendants examinent la structure de haut niveau de la syntaxe et essaient de trouver une règle correspondante. Les analyseurs ascendants commencent par l'entrée et la transforment progressivement en règles de syntaxe, en commençant par les règles de bas niveau jusqu'à ce que les règles de haut niveau soient respectées.

Voyons comment les deux types d'analyseurs analysent notre exemple.

L'analyseur descendant commence par la règle de niveau supérieur : il identifie 2 + 3 comme une expression. Il identifiera ensuite 2 + 3 - 1 comme une expression (le processus d'identification de l'expression évolue, en fonction des autres règles, mais le point de départ est la règle de niveau le plus élevé).

L'analyseur ascendant analyse l'entrée jusqu'à ce qu'une règle soit mise en correspondance. Elle remplacera ensuite l'entrée correspondante par la règle. Ce processus se poursuit jusqu'à la fin de l'entrée. L'expression partiellement mise en correspondance est placée sur la pile de l'analyseur.

Pile Entrée
2 + 3 - 1
terme + 3 - 1
opération sur un terme 3 - 1
expression - 1
opération d'expression 1
expression -

Ce type d'analyseur ascendant est appelé "analyseur shift-reduce", car l'entrée est décalée vers la droite (imaginez un pointeur pointant d'abord vers le début de l'entrée et se déplaçant vers la droite) et est progressivement réduite à des règles de syntaxe.

Générer des analyseurs automatiquement

Il existe des outils permettant de générer un analyseur. Vous leur fournissez la grammaire de votre langage (son vocabulaire et ses règles de syntaxe), et ils génèrent un analyseur syntaxique fonctionnel. La création d'un analyseur nécessite une compréhension approfondie de l'analyse, et il n'est pas facile de créer un analyseur optimisé manuellement. Les générateurs d'analyseurs peuvent donc être très utiles.

WebKit utilise deux générateurs d'analyseurs bien connus : Flex pour créer un analyseur lexical et Bison pour créer un analyseur (vous pouvez les rencontrer sous les noms Lex et Yacc). L'entrée Flex est un fichier contenant les définitions des jetons sous forme d'expressions régulières. L'entrée de Bison correspond aux règles de syntaxe du langage au format BNF.

Analyseur HTML

Le rôle de l'analyseur HTML est d'analyser le balisage HTML pour créer un arbre d'analyse.

Grammaire HTML

Le vocabulaire et la syntaxe du HTML sont définis dans des spécifications créées par l'organisation W3C.

Comme nous l'avons vu dans l'introduction à l'analyse syntaxique, la syntaxe d'une grammaire peut être définie formellement à l'aide de formats tels que BNF.

Malheureusement, aucun des sujets d'analyse conventionnels ne s'applique au HTML (je ne les ai pas évoqués juste pour le plaisir : ils seront utilisés pour analyser le CSS et le JavaScript). Le code HTML ne peut pas être facilement défini par une grammaire non contextuelle dont les analyseurs ont besoin.

Il existe un format formel pour définir le HTML : la DTD (Document Type Definition), mais il ne s'agit pas d'une grammaire non contextuelle.

Cela peut sembler étrange à première vue, car le HTML est assez proche du XML. Il existe de nombreux analyseurs XML. Il existe une variante XML du HTML, le XHTML. Alors, quelle est la grande différence ?

La différence est que l'approche HTML est plus "permissive" : elle vous permet d'omettre certaines balises (qui sont ensuite ajoutées implicitement), ou parfois d'omettre les balises de début ou de fin, etc. Dans l'ensemble, il s'agit d'une syntaxe "souple", par opposition à la syntaxe rigide et exigeante de XML.

Ce détail, qui peut sembler minime, fait toute la différence. D'un côté, c'est la principale raison pour laquelle le HTML est si populaire : il pardonne vos erreurs et facilite la vie du créateur Web. En revanche, il est difficile d'écrire une grammaire formelle. En résumé, le code HTML ne peut pas être analysé facilement par les analyseurs conventionnels, car sa grammaire n'est pas indépendante du contexte. Le code HTML ne peut pas être analysé par les analyseurs XML.

DTD HTML

La définition HTML est au format DTD. Ce format est utilisé pour définir les langages de la famille SGML. Le format contient les définitions de tous les éléments autorisés, de leurs attributs et de leur hiérarchie. Comme nous l'avons vu précédemment, la DTD HTML ne forme pas une grammaire non contextuelle.

Il existe plusieurs variantes de la DTD. Le mode strict est conforme uniquement aux spécifications, mais d'autres modes sont compatibles avec le balisage utilisé par les navigateurs par le passé. L'objectif est d'assurer la rétrocompatibilité avec les anciens contenus. La DTD stricte actuelle est disponible à l'adresse suivante : www.w3.org/TR/html4/strict.dtd.

DOM

L'arborescence de sortie (l'"arbre d'analyse") est une arborescence de nœuds d'éléments et d'attributs DOM. "DOM" est l'abréviation de "Document Object Model". Il s'agit de la représentation de l'objet du document HTML et de l'interface des éléments HTML avec le monde extérieur, comme JavaScript.

La racine de l'arborescence est l'objet "Document".

Le DOM a une relation presque un à un avec le balisage. Exemple :

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>

Ce balisage serait traduit par l'arborescence DOM suivante :

Arborescence DOM de l'exemple de balisage
Figure 8 : Arborescence DOM de l'exemple de balisage

Comme le HTML, le DOM est spécifié par l'organisation W3C. Consultez www.w3.org/DOM/DOMTR. Il s'agit d'une spécification générique pour manipuler des documents. Un module spécifique décrit des éléments HTML spécifiques. Les définitions HTML sont disponibles sur la page www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.

Quand je dis que l'arborescence contient des nœuds DOM, je veux dire qu'elle est constituée d'éléments qui implémentent l'une des interfaces DOM. Les navigateurs utilisent des implémentations concrètes qui ont d'autres attributs utilisés par le navigateur en interne.

L'algorithme d'analyse syntaxique

Comme nous l'avons vu dans les sections précédentes, le code HTML ne peut pas être analysé à l'aide des analyseurs standards de haut en bas ou de bas en haut.

Pour les raisons suivantes :

  1. La nature tolérante du langage.
  2. Le fait que les navigateurs tolèrent traditionnellement les erreurs pour prendre en charge les cas bien connus de code HTML non valide.
  3. Le processus d'analyse est réentrant. Pour les autres langages, la source ne change pas pendant l'analyse, mais en HTML, le code dynamique (comme les éléments de script contenant des appels document.write()) peut ajouter des jetons supplémentaires. Le processus d'analyse modifie donc l'entrée.

Comme ils ne peuvent pas utiliser les techniques d'analyse standards, les navigateurs créent des analyseurs personnalisés pour analyser le code HTML.

L'algorithme d'analyse est décrit en détail dans la spécification HTML5. L'algorithme se compose de deux étapes : la tokenisation et la construction de l'arbre.

La tokenisation est l'analyse lexicale, qui consiste à analyser l'entrée en jetons. Les jetons HTML incluent les balises de début, les balises de fin, les noms d'attributs et les valeurs d'attributs.

Le tokeniseur reconnaît le jeton, le transmet au constructeur d'arborescence et consomme le caractère suivant pour reconnaître le jeton suivant, et ainsi de suite jusqu'à la fin de l'entrée.

Flux d'analyse HTML (extrait de la spécification HTML5)
Figure 9 : Flux d'analyse HTML (extrait de la spécification HTML5)

L'algorithme de tokenisation

La sortie de l'algorithme est un jeton HTML. L'algorithme est exprimé sous la forme d'une machine à états. Chaque état consomme un ou plusieurs caractères du flux d'entrée et met à jour l'état suivant en fonction de ces caractères. La décision est influencée par l'état actuel de la tokenisation et par l'état de la construction de l'arborescence. Cela signifie que le même caractère consommé donnera des résultats différents pour l'état suivant correct, en fonction de l'état actuel. L'algorithme est trop complexe pour être décrit en détail. Voyons donc un exemple simple qui nous aidera à en comprendre le principe.

Exemple de base : tokenisation du code HTML suivant :

<html>
  <body>
    Hello world
  </body>
</html>

L'état initial est l'état "Données". Lorsque le caractère < est rencontré, l'état passe à "Tag open state" (État d'ouverture de la balise). La consommation d'un caractère a-z entraîne la création d'un "jeton de balise de début", et l'état passe à "État du nom de la balise". Nous restons dans cet état jusqu'à ce que le caractère > soit consommé. Chaque caractère est ajouté au nouveau nom du jeton. Dans notre cas, le jeton créé est un jeton html.

Lorsque la balise > est atteinte, le jeton actuel est émis et l'état revient à "Data state". La balise <body> sera traitée de la même manière. Jusqu'à présent, les tags html et body ont été émis. Nous sommes de retour à l'"état des données". La consommation du caractère H de Hello world entraîne la création et l'émission d'un jeton de caractère, ce qui se poursuit jusqu'à ce que le < de </body> soit atteint. Nous émettrons un jeton de caractère pour chaque caractère de Hello world.

Nous sommes maintenant de retour à l'"état d'ouverture de la balise". La consommation de l'entrée suivante / entraîne la création d'un end tag token et un passage à l'"état du nom de la balise". Là encore, nous restons dans cet état jusqu'à ce que nous atteignions >.Ensuite, le nouveau jeton de balise sera émis et nous reviendrons à l'"état des données". L'entrée </html> sera traitée comme dans le cas précédent.

Tokeniser l'exemple d'entrée
Figure 10 : Tokenisation de l'exemple d'entrée

Algorithme de création d'arbre

Lorsque l'analyseur est créé, l'objet Document est créé. Lors de l'étape de construction de l'arborescence, l'arborescence DOM avec le document à sa racine sera modifiée et des éléments y seront ajoutés. Chaque nœud émis par le tokenizer sera traité par le constructeur d'arborescence. Pour chaque jeton, la spécification définit l'élément DOM pertinent qui sera créé pour ce jeton. L'élément est ajouté à l'arborescence DOM, ainsi qu'à la pile des éléments ouverts. Cette pile permet de corriger les incohérences d'imbrication et les balises non fermées. L'algorithme est également décrit comme une machine à états. Les états sont appelés "modes d'insertion".

Voyons le processus de construction de l'arbre pour l'exemple d'entrée :

<html>
  <body>
    Hello world
  </body>
</html>

L'entrée de l'étape de construction de l'arborescence est une séquence de jetons provenant de l'étape de tokenisation. Le premier mode est le mode initial. La réception du jeton "html" entraîne le passage au mode "before html" et le retraitement du jeton dans ce mode. Cela entraîne la création de l'élément HTMLHtmlElement, qui sera ajouté à l'objet Document racine.

L'état passe à "before head". Le jeton "body" est ensuite reçu. Un élément HTMLHeadElement sera créé implicitement, même si nous n'avons pas de jeton "head", et il sera ajouté à l'arborescence.

Nous passons maintenant au mode "in head", puis au mode "after head". Le jeton body est retraité, un HTMLBodyElement est créé et inséré, et le mode est transféré vers "in body".

Les jetons de caractères de la chaîne "Hello world" sont maintenant reçus. Le premier entraîne la création et l'insertion d'un nœud "Text", et les autres caractères sont ajoutés à ce nœud.

La réception du jeton de fin de corps entraîne un transfert vers le mode "after body". Nous recevons maintenant la balise de fin HTML, qui nous fait passer en mode "after after body". La réception du jeton de fin de fichier met fin à l'analyse.

Construction de l'arborescence de l'exemple de code HTML.
Figure 11 : Construction de l'arborescence de l'exemple de code HTML

Actions à effectuer une fois l'analyse terminée

À ce stade, le navigateur marque le document comme interactif et commence à analyser les scripts en mode "différé" (ceux qui doivent être exécutés après l'analyse du document). L'état du document sera alors défini sur "complete" et un événement "load" sera déclenché.

Vous pouvez consulter les algorithmes complets de segmentation en jetons et de construction d'arborescence dans la spécification HTML5.

Tolérance aux erreurs des navigateurs

Vous n'obtenez jamais d'erreur "Syntaxe non valide" sur une page HTML. Les navigateurs corrigent tout contenu non valide et continuent.

Prenons l'exemple de ce code HTML :

<html>
  <mytag>
  </mytag>
  <div>
  <p>
  </div>
    Really lousy HTML
  </p>
</html>

J'ai dû enfreindre environ un million de règles ("mytag" n'est pas une balise standard, l'imbrication des éléments "p" et "div" est incorrecte, etc.), mais le navigateur l'affiche toujours correctement et ne se plaint pas. Une grande partie du code de l'analyseur consiste donc à corriger les erreurs de l'auteur du code HTML.

La gestion des exceptions est assez cohérente dans les navigateurs, mais étonnamment, elle ne fait pas partie des spécifications HTML. Comme les favoris et les boutons Précédent/Suivant, il s'agit d'une fonctionnalité qui s'est développée dans les navigateurs au fil des ans. Certaines constructions HTML non valides sont connues pour être répétées sur de nombreux sites, et les navigateurs tentent de les corriger de manière à ce qu'elles soient conformes aux autres navigateurs.

La spécification HTML5 définit certaines de ces exigences. (WebKit résume bien cela dans le commentaire au début de la classe de l'analyseur HTML.)

L'analyseur analyse l'entrée tokenisée dans le document, en créant l'arborescence du document. Si le document est bien formé, son analyse est simple.

Malheureusement, nous devons gérer de nombreux documents HTML qui ne sont pas bien formés. L'analyseur doit donc tolérer les erreurs.

Nous devons gérer au moins les conditions d'erreur suivantes :

  1. L'élément ajouté est explicitement interdit dans une balise externe. Dans ce cas, nous devons fermer toutes les balises jusqu'à celle qui interdit l'élément, puis l'ajouter.
  2. Nous ne sommes pas autorisés à ajouter l'élément directement. Il est possible que la personne qui a rédigé le document ait oublié une balise entre les deux (ou que la balise entre les deux soit facultative). Cela peut être le cas avec les balises suivantes : HTML HEAD BODY TBODY TR TD LI (en ai-je oublié ?).
  3. Nous voulons ajouter un élément de bloc à l'intérieur d'un élément intégré. Fermez tous les éléments intégrés jusqu'au prochain élément de bloc supérieur.
  4. Si cela ne fonctionne pas, fermez les éléments jusqu'à ce que nous soyons autorisés à ajouter l'élément, ou ignorez la balise.

Voici quelques exemples de tolérance aux erreurs de WebKit :

</br> au lieu de <br>

Certains sites utilisent </br> au lieu de <br>. Pour être compatible avec IE et Firefox, WebKit traite cela comme <br>.

Le code :

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
     reportError(MalformedBRError);
     t->beginTag = true;
}

Notez que la gestion des erreurs est interne : elle ne sera pas présentée à l'utilisateur.

Une table isolée

Un tableau isolé est un tableau situé à l'intérieur d'un autre tableau, mais pas dans une cellule de tableau.

Exemple :

<table>
  <table>
    <tr><td>inner table</td></tr>
  </table>
  <tr><td>outer table</td></tr>
</table>

WebKit modifiera la hiérarchie en deux tables sœurs :

<table>
  <tr><td>outer table</td></tr>
</table>
<table>
  <tr><td>inner table</td></tr>
</table>

Le code :

if (m_inStrayTableContent && localName == tableTag)
        popBlock(tableTag);

WebKit utilise une pile pour le contenu de l'élément actuel : il extrait la table interne de la pile de la table externe. Les tables seront désormais sœurs.

Éléments de formulaire imbriqués

Si l'utilisateur place un formulaire dans un autre, le second formulaire est ignoré.

Le code :

if (!m_currentFormElement) {
        m_currentFormElement = new HTMLFormElement(formTag,    m_document);
}

Une hiérarchie de tags trop profonde

Le commentaire parle de lui-même.

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
{

unsigned i = 0;
for (HTMLStackElem* curr = m_blockStack;
         i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
     curr = curr->next, i++) { }
return i != cMaxRedundantTagDepth;
}

Balises de fin html ou body mal placées

Là encore, le commentaire parle de lui-même.

if (t->tagName == htmlTag || t->tagName == bodyTag )
        return;

Les auteurs Web doivent donc faire attention : à moins de vouloir apparaître comme exemple dans un extrait de code de tolérance aux erreurs WebKit, ils doivent écrire du code HTML bien formé.

Analyse syntaxique CSS

Vous vous souvenez des concepts d'analyse de l'introduction ? Contrairement au HTML, le CSS est une grammaire non contextuelle et peut être analysé à l'aide des types d'analyseurs décrits dans l'introduction. En fait, la spécification CSS définit la grammaire lexicale et syntaxique CSS.

Voici quelques exemples :

La grammaire lexicale (vocabulaire) est définie par des expressions régulières pour chaque jeton :

comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num       [0-9]+|[0-9]*"."[0-9]+
nonascii  [\200-\377]
nmstart   [_a-z]|{nonascii}|{escape}
nmchar    [_a-z0-9-]|{nonascii}|{escape}
name      {nmchar}+
ident     {nmstart}{nmchar}*

"ident" est l'abréviation d'identifiant, comme un nom de classe. "name" est un ID d'élément (auquel il est fait référence par "#").

La grammaire de la syntaxe est décrite en BNF.

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
selector
  : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
  ;
simple_selector
  : element_name [ HASH | class | attrib | pseudo ]*
  | [ HASH | class | attrib | pseudo ]+
  ;
class
  : '.' IDENT
  ;
element_name
  : IDENT | '*'
  ;
attrib
  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
    [ IDENT | STRING ] S* ] ']'
  ;
pseudo
  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
  ;

Explication :

Un ensemble de règles présente la structure suivante :

div.error, a.error {
  color:red;
  font-weight:bold;
}

div.error et a.error sont des sélecteurs. La partie entre accolades contient les règles appliquées par cet ensemble de règles. Cette structure est définie formellement dans cette définition :

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;

Cela signifie qu'un ensemble de règles est un sélecteur ou, éventuellement, un certain nombre de sélecteurs séparés par une virgule et des espaces (S signifie espace blanc). Un jeu de règles contient des accolades et, à l'intérieur, une déclaration ou, éventuellement, un certain nombre de déclarations séparées par un point-virgule. "declaration" et "selector" seront définis dans les définitions BNF suivantes.

Analyseur CSS WebKit

WebKit utilise les générateurs d'analyseurs Flex et Bison pour créer automatiquement des analyseurs à partir des fichiers de grammaire CSS. Comme vous vous en souvenez peut-être, Bison crée un analyseur syntaxique ascendant de type décalage-réduction. Firefox utilise un analyseur descendant écrit manuellement. Dans les deux cas, chaque fichier CSS est analysé dans un objet StyleSheet. Chaque objet contient des règles CSS. Les objets de règle CSS contiennent des objets de sélecteur et de déclaration, ainsi que d'autres objets correspondant à la grammaire CSS.

Analyse du CSS…
Figure 12 : Analyse du CSS

Ordre de traitement des scripts et des feuilles de style

Scripts

Le modèle du Web est synchrone. Les auteurs s'attendent à ce que les scripts soient analysés et exécutés immédiatement lorsque l'analyseur atteint une balise <script>. L'analyse du document est interrompue jusqu'à ce que le script ait été exécuté. Si le script est externe, la ressource doit d'abord être récupérée sur le réseau. Cette opération est également effectuée de manière synchrone, et l'analyse s'arrête jusqu'à ce que la ressource soit récupérée. C'était le modèle pendant de nombreuses années, et il est également spécifié dans les spécifications HTML4 et HTML5. Les auteurs peuvent ajouter l'attribut "defer" à un script. Dans ce cas, l'analyse du document ne sera pas interrompue et le script sera exécuté une fois le document analysé. Le langage HTML5 ajoute une option permettant de marquer le script comme asynchrone afin qu'il soit analysé et exécuté par un thread différent.

Analyse spéculative

WebKit et Firefox effectuent tous deux cette optimisation. Pendant l'exécution des scripts, un autre thread analyse le reste du document, identifie les autres ressources à charger depuis le réseau et les charge. De cette façon, les ressources peuvent être chargées sur des connexions parallèles, et la vitesse globale est améliorée. Remarque : L'analyseur spéculatif n'analyse que les références à des ressources externes telles que des scripts, des feuilles de style et des images externes. Il ne modifie pas l'arborescence DOM, qui est laissée à l'analyseur principal.

Feuilles de style

Les feuilles de style, en revanche, ont un modèle différent. En théorie, étant donné que les feuilles de style ne modifient pas l'arborescence DOM, il n'y a aucune raison de les attendre et d'arrêter l'analyse du document. Cependant, il peut arriver que des scripts demandent des informations de style pendant l'étape d'analyse du document. Si le style n'est pas encore chargé et analysé, le script obtiendra des réponses incorrectes, ce qui semble avoir causé de nombreux problèmes. Il semble s'agir d'un cas limite, mais il est assez courant. Firefox bloque tous les scripts lorsqu'une feuille de style est encore en cours de chargement et d'analyse. WebKit ne bloque les scripts que lorsqu'ils tentent d'accéder à certaines propriétés de style susceptibles d'être affectées par des feuilles de style non chargées.

Construction de l'arborescence de rendu

Pendant la construction de l'arborescence DOM, le navigateur construit une autre arborescence, l'arborescence de rendu. Cette arborescence contient des éléments visuels dans l'ordre dans lequel ils seront affichés. Il s'agit de la représentation visuelle du document. L'objectif de cette arborescence est de permettre de peindre le contenu dans le bon ordre.

Firefox appelle les éléments de l'arborescence d'affichage des "frames". WebKit utilise le terme "moteur de rendu" ou "objet de rendu".

Un moteur de rendu sait comment se disposer et se peindre, ainsi que ses enfants.

La classe RenderObject de WebKit, la classe de base des moteurs de rendu, est définie comme suit :

class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}

Chaque moteur de rendu représente une zone rectangulaire correspondant généralement à la boîte CSS d'un nœud, comme décrit dans la spécification CSS2. Il inclut des informations géométriques telles que la largeur, la hauteur et la position.

Le type de boîte est affecté par la valeur "display" de l'attribut de style pertinent pour le nœud (voir la section Calcul du style). Voici le code WebKit permettant de décider du type de moteur de rendu à créer pour un nœud DOM, en fonction de l'attribut d'affichage :

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;

    switch (style->display()) {
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM:
            o = new (arena) RenderListItem(node);
            break;
       ...
    }

    return o;
}

Le type d'élément est également pris en compte : par exemple, les commandes de formulaire et les tableaux ont des cadres spéciaux.

Dans WebKit, si un élément souhaite créer un moteur de rendu spécial, il remplace la méthode createRenderer(). Les moteurs de rendu pointent vers des objets de style contenant des informations non géométriques.

Relation entre l'arborescence de rendu et l'arborescence DOM

Les moteurs de rendu correspondent aux éléments DOM, mais la relation n'est pas de type un à un. Les éléments DOM non visuels ne seront pas insérés dans l'arborescence de rendu. Par exemple, l'élément "head". De plus, les éléments dont la valeur d'affichage a été définie sur "none" n'apparaîtront pas dans l'arborescence (alors que les éléments dont la visibilité est définie sur "hidden" apparaîtront dans l'arborescence).

Il existe des éléments DOM qui correspondent à plusieurs objets visuels. Il s'agit généralement d'éléments à la structure complexe qui ne peuvent pas être décrits par un seul rectangle. Par exemple, l'élément "select" comporte trois moteurs de rendu : un pour la zone d'affichage, un pour la zone de liste déroulante et un pour le bouton. De plus, lorsque le texte est divisé en plusieurs lignes parce que la largeur n'est pas suffisante pour une seule ligne, les nouvelles lignes sont ajoutées en tant que moteurs de rendu supplémentaires.

Un autre exemple de moteurs de rendu multiples est le code HTML défectueux. Selon la spécification CSS, un élément inline ne doit contenir que des éléments de bloc ou que des éléments inline. Dans le cas d'un contenu mixte, des moteurs de rendu de blocs anonymes sont créés pour encapsuler les éléments intégrés.

Certains objets de rendu correspondent à un nœud DOM, mais pas au même endroit dans l'arborescence. Les éléments flottants et positionnés de manière absolue sont hors flux, placés dans une autre partie de l'arborescence et mappés au frame réel. Un cadre d'espace réservé se trouve à l'emplacement où ils auraient dû se trouver.

L'arborescence d'affichage et l'arborescence DOM correspondante.
Figure 13 : Arborescence de rendu et arborescence DOM correspondante. La "fenêtre d'affichage" est le bloc conteneur initial. Dans WebKit, il s'agit de l'objet "RenderView".

Le flux de construction de l'arbre

Dans Firefox, la présentation est enregistrée en tant qu'écouteur pour les mises à jour du DOM. La présentation délègue la création du frame à FrameConstructor, et le constructeur résout le style (voir Calcul du style) et crée un frame.

Dans WebKit, le processus de résolution du style et de création d'un moteur de rendu est appelé "association". Chaque nœud DOM dispose d'une méthode "attach". L'association est synchrone. L'insertion d'un nœud dans l'arborescence DOM appelle la méthode "attach" du nouveau nœud.

Le traitement des balises html et body entraîne la construction de la racine de l'arbre de rendu. L'objet de rendu racine correspond à ce que la spécification CSS appelle le bloc conteneur : le bloc le plus haut qui contient tous les autres blocs. Ses dimensions sont celles de la fenêtre d'affichage, c'est-à-dire de la zone d'affichage de la fenêtre du navigateur. Firefox l'appelle ViewPortFrame et WebKit l'appelle RenderView. Il s'agit de l'objet de rendu vers lequel pointe le document. Le reste de l'arborescence est construit comme une insertion de nœuds DOM.

Consultez la spécification CSS2 sur le modèle de traitement.

Calcul du style

La création de l'arbre de rendu nécessite de calculer les propriétés visuelles de chaque objet de rendu. Pour ce faire, il calcule les propriétés de style de chaque élément.

Le style inclut des feuilles de style de différentes origines, des éléments de style intégrés et des propriétés visuelles dans le code HTML (comme la propriété "bgcolor").Cette dernière est traduite en propriétés de style CSS correspondantes.

Les feuilles de style proviennent des feuilles de style par défaut du navigateur, des feuilles de style fournies par l'auteur de la page et des feuilles de style de l'utilisateur (feuilles de style fournies par l'utilisateur du navigateur, qui vous permettent de définir vos styles préférés). Dans Firefox, par exemple, cela se fait en plaçant une feuille de style dans le dossier "Profil Firefox").

Le calcul du style présente quelques difficultés :

  1. Les données de style sont une construction très volumineuse, qui contient de nombreuses propriétés de style. Cela peut entraîner des problèmes de mémoire.
  2. Si elle n'est pas optimisée, la recherche des règles correspondantes pour chaque élément peut entraîner des problèmes de performances. Parcourir l'intégralité de la liste de règles pour chaque élément afin de trouver des correspondances est une tâche lourde. Les sélecteurs peuvent avoir une structure complexe qui peut amener le processus de mise en correspondance à démarrer sur un chemin apparemment prometteur, mais qui s'avère inutile, et un autre chemin doit être essayé.

    Par exemple, ce sélecteur composé :

    div div div div{
    ...
    }
    

    Signifie que les règles s'appliquent à un <div> qui est le descendant de trois divs. Supposons que vous souhaitiez vérifier si la règle s'applique à un élément <div> donné. Vous choisissez un certain chemin dans l'arborescence pour la vérification. Vous devrez peut-être parcourir l'arborescence des nœuds pour découvrir qu'il n'y a que deux divs et que la règle ne s'applique pas. Vous devez alors essayer d'autres chemins dans l'arbre.

  3. L'application des règles implique des règles en cascade assez complexes qui définissent la hiérarchie des règles.

Voyons comment les navigateurs gèrent ces problèmes :

Partager des données de style

Les nœuds WebKit font référence à des objets de style (RenderStyle). Ces objets peuvent être partagés par des nœuds dans certaines conditions. Les nœuds sont des frères et sœurs ou des cousins, et :

  1. Les éléments doivent se trouver dans le même état de souris (par exemple, l'un ne peut pas être dans l'état :hover alors que l'autre ne l'est pas).
  2. Aucun élément ne doit avoir d'ID
  3. Les noms des balises doivent correspondre
  4. Les attributs de classe doivent correspondre
  5. L'ensemble d'attributs mappés doit être identique
  6. Les états des liens doivent correspondre
  7. Les états de sélection doivent correspondre
  8. Aucun élément ne doit être affecté par les sélecteurs d'attributs, où "affecté" est défini comme ayant une correspondance de sélecteur qui utilise un sélecteur d'attributs à n'importe quelle position dans le sélecteur.
  9. Les éléments ne doivent pas comporter d'attribut de style intégré.
  10. Aucun sélecteur de frères et sœurs ne doit être utilisé. WebCore active simplement un commutateur global lorsqu'un sélecteur frère est rencontré et désactive le partage de style pour l'ensemble du document lorsqu'il est présent. Cela inclut le sélecteur + et les sélecteurs tels que :first-child et :last-child.

Arborescence des règles Firefox

Firefox dispose de deux arbres supplémentaires pour faciliter le calcul des styles : l'arbre des règles et l'arbre de contexte de style. WebKit comporte également des objets de style, mais ils ne sont pas stockés dans une arborescence comme l'arborescence de contexte de style. Seul le nœud DOM pointe vers le style pertinent.

Arborescence de contexte de style Firefox.
Figure 14 : Arborescence de contexte de style Firefox.

Les contextes de style contiennent des valeurs de fin. Les valeurs sont calculées en appliquant toutes les règles de correspondance dans le bon ordre et en effectuant des manipulations qui les transforment de valeurs logiques en valeurs concrètes. Par exemple, si la valeur logique est un pourcentage de l'écran, elle sera calculée et transformée en unités absolues. L'idée de l'arborescence de règles est vraiment astucieuse. Il permet de partager ces valeurs entre les nœuds pour éviter de les recalculer. Cela permet également de gagner de l'espace.

Toutes les règles correspondantes sont stockées dans une arborescence. Les nœuds inférieurs d'un chemin d'accès ont une priorité plus élevée. L'arborescence contient tous les chemins d'accès aux correspondances de règles trouvées. Le stockage des règles est effectué de manière différée. L'arborescence n'est pas calculée au début pour chaque nœud, mais chaque fois qu'un style de nœud doit être calculé, les chemins calculés sont ajoutés à l'arborescence.

L'idée est de voir les chemins de l'arbre comme des mots dans un lexique. Supposons que nous ayons déjà calculé cet arbre de règles :

Arborescence de règles calculée
Figure 15 : Arbre de règles calculé

Supposons que nous devions faire correspondre des règles pour un autre élément de l'arborescence de contenu et que nous découvrions que les règles correspondantes (dans le bon ordre) sont B-E-I. Nous avons déjà ce chemin dans l'arbre, car nous avons déjà calculé le chemin A-B-E-I-L. Nous aurons désormais moins de travail à faire.

Voyons comment l'arborescence nous fait gagner du temps.

Division en structures

Les contextes de style sont divisés en structures. Ces structures contiennent des informations de style pour une certaine catégorie, comme la bordure ou la couleur. Toutes les propriétés d'une structure sont soit héritées, soit non héritées. Les propriétés héritées sont des propriétés qui, sauf si elles sont définies par l'élément, sont héritées de son parent. Les propriétés non héritées (appelées propriétés de "réinitialisation") utilisent des valeurs par défaut si elles ne sont pas définies.

L'arborescence nous aide en mettant en cache des structures entières (contenant les valeurs finales calculées). L'idée est que si le nœud inférieur n'a pas fourni de définition pour une structure, une structure mise en cache dans un nœud supérieur peut être utilisée.

Calculer les contextes de style à l'aide de l'arbre de règles

Lors du calcul du contexte de style d'un élément, nous calculons d'abord un chemin d'accès dans l'arborescence de règles ou nous en utilisons un existant. Nous commençons ensuite à appliquer les règles du chemin d'accès pour remplir les structures dans notre nouveau contexte de style. Nous commençons par le nœud inférieur du chemin d'accès (celui qui a la priorité la plus élevée, généralement le sélecteur le plus spécifique) et nous parcourons l'arborescence jusqu'à ce que notre structure soit complète. S'il n'y a pas de spécification pour la structure dans ce nœud de règle, nous pouvons l'optimiser considérablement : nous remontons l'arborescence jusqu'à trouver un nœud qui la spécifie entièrement et nous pointons vers lui. C'est la meilleure optimisation : l'ensemble de la structure est partagé. Cela permet d'économiser le calcul des valeurs finales et de la mémoire.

Si nous trouvons des définitions partielles, nous remontons l'arborescence jusqu'à ce que la structure soit remplie.

Si nous ne trouvons aucune définition pour notre structure, et si celle-ci est de type "hérité ", nous pointons vers la structure de son parent dans l'arborescence de contexte. Dans ce cas, nous avons également réussi à partager des structures. S'il s'agit d'une structure de réinitialisation, les valeurs par défaut seront utilisées.

Si le nœud le plus spécifique ajoute des valeurs, nous devons effectuer des calculs supplémentaires pour les transformer en valeurs réelles. Nous mettons ensuite en cache le résultat dans le nœud de l'arborescence afin qu'il puisse être utilisé par les enfants.

Si un élément a un frère ou une sœur qui pointe vers le même nœud d'arborescence, l'intégralité du contexte de style peut être partagée entre eux.

Prenons un exemple : Supposons que nous ayons ce code HTML

<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>

Et les règles suivantes :

div {margin: 5px; color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

Pour simplifier les choses, supposons que nous n'ayons besoin de remplir que deux structs : le struct de couleur et le struct de marge. La structure de couleur ne contient qu'un seul membre : la couleur. La structure de marge contient les quatre côtés.

L'arborescence de règles obtenue se présente comme suit (les nœuds sont marqués avec le nom du nœud : le numéro de la règle vers laquelle ils pointent) :

L'arborescence des règles
Figure 16 : Arborescence des règles

L'arborescence de contexte se présente comme suit (nom du nœud : nœud de règle vers lequel il pointe) :

L'arborescence de contexte.
Figure 17 : Arborescence de contexte

Supposons que nous analysions le code HTML et que nous arrivions à la deuxième balise <div>. Nous devons créer un contexte de style pour ce nœud et remplir ses structures de style.

Nous allons mettre en correspondance les règles et découvrir que les règles correspondantes pour <div> sont 1, 2 et 6. Cela signifie qu'il existe déjà un chemin d'accès dans l'arborescence que notre élément peut utiliser. Il nous suffit d'y ajouter un autre nœud pour la règle 6 (nœud F dans l'arborescence des règles).

Nous allons créer un contexte de style et le placer dans l'arborescence de contexte. Le nouveau contexte de style pointera vers le nœud F dans l'arborescence des règles.

Nous devons maintenant remplir les structures de style. Nous allons commencer par remplir la structure de marge. Comme le dernier nœud de règle (F) n'ajoute rien à la structure de marge, nous pouvons remonter l'arborescence jusqu'à trouver une structure mise en cache calculée lors d'une insertion de nœud précédente et l'utiliser. Nous le trouverons sur le nœud B, qui est le nœud le plus élevé ayant spécifié des règles de marge.

Nous avons une définition pour la structure de couleur, nous ne pouvons donc pas utiliser de structure mise en cache. Comme la couleur n'a qu'un seul attribut, nous n'avons pas besoin de remonter dans l'arborescence pour renseigner d'autres attributs. Nous allons calculer la valeur finale (convertir la chaîne en RVB, etc.) et mettre en cache la structure calculée sur ce nœud.

Le travail sur le deuxième élément <span> est encore plus simple. Nous allons faire correspondre les règles et en conclure qu'il pointe vers la règle G, comme le segment précédent. Comme nous avons des frères et sœurs qui pointent vers le même nœud, nous pouvons partager l'intégralité du contexte de style et simplement pointer vers le contexte de l'élément span précédent.

Pour les structures contenant des règles héritées du parent, la mise en cache est effectuée sur l'arborescence de contexte (la propriété de couleur est en fait héritée, mais Firefox la traite comme réinitialisée et la met en cache sur l'arborescence de règles).

Par exemple, si nous ajoutons des règles pour les polices dans un paragraphe :

p {font-family: Verdana; font size: 10px; font-weight: bold}

Ensuite, l'élément de paragraphe, qui est un enfant de l'élément div dans l'arborescence de contexte, aurait pu partager la même structure de police que son parent. Cela s'applique si aucune règle de police n'a été spécifiée pour le paragraphe.

Dans WebKit, qui n'a pas d'arborescence de règles, les déclarations correspondantes sont parcourues quatre fois. Les propriétés non importantes à priorité élevée sont appliquées en premier (propriétés qui doivent être appliquées en premier, car d'autres en dépendent, comme l'affichage), puis les propriétés importantes à priorité élevée, puis les propriétés non importantes à priorité normale, puis les règles importantes à priorité normale. Cela signifie que les propriétés qui apparaissent plusieurs fois seront résolues selon l'ordre de cascade correct. La dernière l'emporte.

En résumé, le partage des objets de style (en totalité ou certaines des structures qu'ils contiennent) résout les problèmes 1 et 3. L'arborescence des règles de Firefox permet également d'appliquer les propriétés dans le bon ordre.

Manipuler les règles pour une correspondance facile

Il existe plusieurs sources de règles de style :

  1. Règles CSS, soit dans des feuilles de style externes, soit dans des éléments de style. css p {color: blue}
  2. Attributs de style intégrés tels que html <p style="color: blue" />
  3. Attributs visuels HTML (qui sont mappés aux règles de style pertinentes) html <p bgcolor="blue" /> Les deux derniers sont facilement mis en correspondance avec l'élément, car il possède les attributs de style, et les attributs HTML peuvent être mappés en utilisant l'élément comme clé.

Comme indiqué précédemment dans le problème 2, la correspondance des règles CSS peut être plus complexe. Pour résoudre ce problème, les règles sont manipulées pour faciliter l'accès.

Après l'analyse de la feuille de style, les règles sont ajoutées à l'une des différentes tables de hachage, en fonction du sélecteur. Il existe des mappages par ID, par nom de classe, par nom de balise et un mappage général pour tout ce qui n'entre pas dans ces catégories. Si le sélecteur est un ID, la règle sera ajoutée à la carte des ID. S'il s'agit d'une classe, elle sera ajoutée à la carte des classes, etc.

Cette manipulation facilite grandement la mise en correspondance des règles. Il n'est pas nécessaire de parcourir chaque déclaration : nous pouvons extraire les règles pertinentes pour un élément à partir des mappages. Cette optimisation élimine plus de 95 % des règles, de sorte qu'elles n'ont même pas besoin d'être prises en compte lors du processus de mise en correspondance(4.1).

Prenons l'exemple des règles de style suivantes :

p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}

La première règle sera insérée dans le mappage de classe. La deuxième dans le mappage d'ID et la troisième dans le mappage de balises.

Pour le fragment HTML suivant :

<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>

Nous allons d'abord essayer de trouver des règles pour l'élément p. La carte de classe contiendra une clé "error" sous laquelle se trouve la règle pour "p.error". L'élément div aura des règles pertinentes dans le mappage d'ID (la clé est l'ID) et le mappage de balises. Il ne reste donc plus qu'à déterminer lesquelles des règles extraites par les clés correspondent réellement.

Par exemple, si la règle pour la div était :

table div {margin: 5px}

Il sera toujours extrait du mappage de balises, car la clé est le sélecteur le plus à droite, mais il ne correspondra pas à notre élément div, qui n'a pas d'ancêtre table.

WebKit et Firefox effectuent tous deux cette manipulation.

Ordre de cascade des feuilles de style

L'objet de style possède des propriétés correspondant à chaque attribut visuel (tous les attributs CSS, mais plus génériques). Si la propriété n'est définie par aucune des règles correspondantes, certaines propriétés peuvent être héritées de l'objet de style de l'élément parent. Les autres propriétés ont des valeurs par défaut.

Le problème commence lorsqu'il existe plusieurs définitions. C'est là qu'intervient l'ordre en cascade pour résoudre le problème.

Une déclaration pour une propriété de style peut apparaître dans plusieurs feuilles de style et plusieurs fois dans une feuille de style. Cela signifie que l'ordre d'application des règles est très important. C'est ce qu'on appelle l'ordre "en cascade". Selon la spécification CSS2, l'ordre de cascade est le suivant (du plus bas au plus élevé) :

  1. Déclarations du navigateur
  2. Déclarations normales de l'utilisateur
  3. Rédiger des déclarations normales
  4. Rédiger des déclarations importantes
  5. Déclarations importantes de l'utilisateur

Les déclarations du navigateur sont les moins importantes, et l'utilisateur ne remplace l'auteur que si la déclaration a été marquée comme importante. Les déclarations ayant le même ordre seront triées par spécificité, puis par l'ordre dans lequel elles sont spécifiées. Les attributs visuels HTML sont traduits en déclarations CSS correspondantes . Elles sont traitées comme des règles d'auteur de faible priorité.

Spécificité

La spécificité du sélecteur est définie par la spécification CSS2 comme suit :

  1. Comptez 1 si la déclaration provient d'un attribut "style" plutôt que d'une règle avec un sélecteur, sinon 0 (= a).
  2. Comptez le nombre d'attributs d'ID dans le sélecteur (= b).
  3. compter le nombre d'autres attributs et pseudo-classes dans le sélecteur (= c)
  4. compter le nombre de noms d'éléments et de pseudo-éléments dans le sélecteur (= d)

La concaténation des quatre nombres a-b-c-d (dans un système de numération à base élevée) donne la spécificité.

La base numérique que vous devez utiliser est définie par le nombre le plus élevé que vous avez dans l'une des catégories.

Par exemple, si a=14, vous pouvez utiliser la base hexadécimale. Dans le cas peu probable où a=17, vous aurez besoin d'une base de nombres à 17 chiffres. La deuxième situation peut se produire avec un sélecteur comme celui-ci : html body div div p… (17 balises dans votre sélecteur… peu probable).

Voici quelques exemples :

 *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
 li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
 h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
 ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
 li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
 #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
 style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

Tri des règles…

Une fois les règles mises en correspondance, elles sont triées selon les règles de cascade. WebKit utilise le tri à bulles pour les petites listes et le tri fusion pour les grandes. WebKit implémente le tri en remplaçant l'opérateur > pour les règles :

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) ? r1.position() > r2.position() : spec1 > spec2;
}

Processus progressif

WebKit utilise un indicateur qui indique si toutes les feuilles de style de premier niveau (y compris @imports) ont été chargées. Si le style n'est pas entièrement chargé lors de l'association, des espaces réservés sont utilisés et marqués dans le document. Ils seront recalculés une fois les feuilles de style chargées.

Disposition

Lorsque le moteur de rendu est créé et ajouté à l'arborescence, il n'a ni position, ni taille. Le calcul de ces valeurs est appelé "mise en page" ou "reflow".

Le langage HTML utilise un modèle de mise en page basé sur le flux, ce qui signifie que la plupart du temps, il est possible de calculer la géométrie en une seule passe. Les éléments situés plus loin dans le flux n'affectent généralement pas la géométrie des éléments situés plus tôt dans le flux. La mise en page peut donc se faire de gauche à droite et de haut en bas dans le document. Il existe des exceptions : par exemple, les tableaux HTML peuvent nécessiter plusieurs passes.

Le système de coordonnées est relatif au cadre racine. Les coordonnées du haut et de la gauche sont utilisées.

La mise en page est un processus récursif. Il commence au niveau du moteur de rendu racine, qui correspond à l'élément <html> du document HTML. La mise en page se poursuit de manière récursive dans une partie ou l'ensemble de la hiérarchie des frames, en calculant les informations géométriques pour chaque moteur de rendu qui en a besoin.

La position du moteur de rendu racine est 0,0 et ses dimensions sont celles de la fenêtre d'affichage (la partie visible de la fenêtre du navigateur).

Tous les moteurs de rendu disposent d'une méthode "layout" ou "reflow". Chacun d'eux appelle la méthode de mise en page de ses enfants qui en ont besoin.

Système de bits modifiés

Pour éviter d'effectuer une mise en page complète à chaque petite modification, les navigateurs utilisent un système de "bit de modification". Un moteur de rendu modifié ou ajouté se marque lui-même et ses enfants comme "dirty" (nécessitant une mise en page).

Il existe deux indicateurs : "dirty" et "children are dirty", ce qui signifie que même si le moteur de rendu lui-même est OK, il comporte au moins un enfant qui nécessite une mise en page.

Mise en page globale et incrémentielle

La mise en page peut être déclenchée sur l'ensemble de l'arborescence d'affichage. Il s'agit d'une mise en page "globale". Cela peut se produire pour les raisons suivantes :

  1. Un changement de style global qui affecte tous les moteurs de rendu, comme un changement de taille de police.
  2. En raison du redimensionnement d'un écran

La mise en page peut être incrémentielle, seuls les moteurs de rendu modifiés seront mis en page (ce qui peut entraîner des dommages nécessitant des mises en page supplémentaires).

La mise en page incrémentielle est déclenchée (de manière asynchrone) lorsque les moteurs de rendu sont modifiés. Par exemple, lorsque de nouveaux moteurs de rendu sont ajoutés à l'arbre de rendu après que du contenu supplémentaire provenant du réseau a été ajouté à l'arbre DOM.

Mise en page incrémentielle.
Figure 18 : Mise en page incrémentielle : seuls les moteurs de rendu modifiés et leurs enfants sont mis en page

Mise en page asynchrone et synchrone

La mise en page incrémentielle est effectuée de manière asynchrone. Firefox met en file d'attente les "commandes de redistribution" pour les mises en page incrémentielles, et un planificateur déclenche l'exécution par lot de ces commandes. WebKit dispose également d'un minuteur qui exécute une mise en page incrémentielle : l'arborescence est parcourue et les moteurs de rendu "dirty" sont mis en page.

Les scripts demandant des informations de style, comme "offsetHeight", peuvent déclencher une mise en page incrémentielle de manière synchrone.

La mise en page globale est généralement déclenchée de manière synchrone.

Parfois, la mise en page est déclenchée en tant que rappel après une mise en page initiale, car certains attributs, comme la position de défilement, ont changé.

Optimisations

Lorsqu'une mise en page est déclenchée par un redimensionnement ou un changement de position(et non de taille) du moteur de rendu, les tailles de rendu sont extraites d'un cache et ne sont pas recalculées…

Dans certains cas, seule une sous-arborescence est modifiée et la mise en page ne commence pas à partir de la racine. Cela peut se produire lorsque la modification est locale et n'affecte pas son environnement, comme du texte inséré dans des champs de texte (sinon, chaque frappe déclencherait une mise en page à partir de la racine).

Le processus de mise en page

La mise en page suit généralement le modèle suivant :

  1. Le moteur de rendu parent détermine sa propre largeur.
  2. Le parent passe en revue les enfants et :
    1. Placez le moteur de rendu enfant (définissez ses valeurs x et y).
    2. Appelle la mise en page enfant si nécessaire (si elle est incorrecte, si nous sommes dans une mise en page globale ou pour une autre raison), ce qui calcule la hauteur de l'enfant.
  3. Le parent utilise les hauteurs cumulées des enfants, ainsi que les hauteurs des marges et de la marge intérieure pour définir sa propre hauteur, qui sera utilisée par le parent du moteur de rendu parent.
  4. Définit son bit modifié sur "false".

Firefox utilise un objet d'état(nsHTMLReflowState) comme paramètre de mise en page (appelé "reflow"). L'état inclut, entre autres, la largeur du parent.

La sortie de la mise en page Firefox est un objet "metrics" (nsHTMLReflowMetrics). Il contiendra la hauteur calculée du moteur de rendu.

Calcul de la largeur

La largeur du moteur de rendu est calculée à l'aide de la largeur du bloc de conteneur, de la propriété "width" du style du moteur de rendu, des marges et des bordures.

Par exemple, la largeur de l'élément div suivant :

<div style="width: 30%"/>

Serait calculé par WebKit comme suit(méthode calcWidth de la classe RenderBox) :

  • La largeur du conteneur est la valeur maximale entre la largeur disponible du conteneur et 0. Dans ce cas, availableWidth correspond à contentWidth, qui est calculé comme suit :
clientWidth() - paddingLeft() - paddingRight()

clientWidth et clientHeight représentent l'intérieur d'un objet, à l'exclusion de la bordure et de la barre de défilement.

  • La largeur de l'élément est l'attribut de style "width". Elle sera calculée en tant que valeur absolue en calculant le pourcentage de la largeur du conteneur.

  • Les bordures et marges intérieures horizontales sont maintenant ajoutées.

Jusqu'à présent, nous avons calculé la "largeur préférée". Les largeurs minimale et maximale seront maintenant calculées.

Si la largeur préférée est supérieure à la largeur maximale, la largeur maximale est utilisée. Si elle est inférieure à la largeur minimale (la plus petite unité insécable), la largeur minimale est utilisée.

Les valeurs sont mises en cache au cas où une mise en page serait nécessaire, mais la largeur ne change pas.

Saut de ligne

Lorsqu'un moteur de rendu au milieu d'une mise en page décide qu'il doit s'interrompre, il s'arrête et indique au parent de la mise en page qu'il doit être interrompu. Le parent crée les moteurs de rendu supplémentaires et appelle la mise en page sur ceux-ci.

Peinture

Lors de l'étape de peinture, l'arborescence de rendu est parcourue et la méthode "paint()" du moteur de rendu est appelée pour afficher le contenu à l'écran. Le dessin utilise le composant d'infrastructure de l'UI.

Global et incrémental

Comme la mise en page, la peinture peut également être globale (l'arborescence entière est peinte) ou incrémentielle. Dans le rendu incrémental, certains moteurs de rendu changent d'une manière qui n'affecte pas l'arborescence entière. Le moteur de rendu modifié invalide son rectangle à l'écran. L'OS la considère alors comme une "région sale" et génère un événement "paint". L'OS le fait intelligemment et fusionne plusieurs régions en une seule. Dans Chrome, c'est plus compliqué, car le moteur de rendu se trouve dans un processus différent du processus principal. Chrome simule le comportement de l'OS dans une certaine mesure. La présentation écoute ces événements et délègue le message à la racine de rendu. L'arborescence est parcourue jusqu'à ce que le moteur de rendu approprié soit atteint. Il se redessine (et généralement ses enfants).

L'ordre de peinture

CSS2 définit l'ordre du processus de peinture. Il s'agit en fait de l'ordre dans lequel les éléments sont empilés dans les contextes d'empilement. Cet ordre affecte l'affichage, car les piles sont affichées de l'arrière vers l'avant. L'ordre d'empilement d'un moteur de rendu de bloc est le suivant :

  1. couleur de l'arrière-plan
  2. image de fond
  3. border
  4. enfants
  5. vue générale

Liste d'affichage Firefox

Firefox parcourt l'arbre de rendu et crée une liste d'affichage pour le rectangle peint. Il contient les moteurs de rendu pertinents pour le rectangle, dans le bon ordre de peinture (arrière-plans des moteurs de rendu, puis bordures, etc.).

De cette façon, l'arborescence n'a besoin d'être parcourue qu'une seule fois pour un repeint au lieu de plusieurs fois (peindre tous les arrière-plans, puis toutes les images, puis toutes les bordures, etc.).

Firefox optimise le processus en n'ajoutant pas d'éléments qui seront masqués, comme les éléments complètement situés sous d'autres éléments opaques.

Stockage de rectangles WebKit

Avant de repeindre, WebKit enregistre l'ancien rectangle sous forme de bitmap. Il ne peint ensuite que la différence entre les nouveaux et les anciens rectangles.

Modifications dynamiques

Les navigateurs essaient d'effectuer le moins d'actions possible en réponse à une modification. Par conséquent, les modifications apportées à la couleur d'un élément n'entraînent que le repeint de l'élément. Les modifications apportées à la position de l'élément entraînent la mise en page et le rafraîchissement de l'élément, de ses enfants et éventuellement de ses frères et sœurs. L'ajout d'un nœud DOM entraîne la mise en page et le rafraîchissement du nœud. Les modifications majeures, comme l'augmentation de la taille de la police de l'élément "html", entraînent l'invalidation des caches, la réorganisation et le rafraîchissement de l'arborescence entière.

Threads du moteur de rendu

Le moteur de rendu est monothread. Presque tout, à l'exception des opérations réseau, se produit dans un seul thread. Dans Firefox et Safari, il s'agit du thread principal du navigateur. Dans Chrome, il s'agit du thread principal du processus de l'onglet.

Les opérations réseau peuvent être effectuées par plusieurs threads parallèles. Le nombre de connexions parallèles est limité (généralement de deux à six connexions).

Boucle d'événements

Le thread principal du navigateur est une boucle d'événements. Il s'agit d'une boucle infinie qui maintient le processus actif. Il attend les événements (comme les événements de mise en page et de peinture) et les traite. Voici le code Firefox pour la boucle d'événements principale :

while (!mExiting)
    NS_ProcessNextEvent(thread);

Modèle visuel CSS2

Le canevas

Selon la spécification CSS2, le terme "canevas" décrit "l'espace où la structure de mise en forme est affichée" : l'endroit où le navigateur peint le contenu.

Le canevas est infini pour chaque dimension de l'espace, mais les navigateurs choisissent une largeur initiale en fonction des dimensions de la fenêtre d'affichage.

Selon www.w3.org/TR/CSS2/zindex.html, le canevas est transparent s'il est contenu dans un autre, et une couleur définie par le navigateur lui est attribuée si ce n'est pas le cas.

Modèle de boîte CSS

Le modèle de boîte CSS décrit les boîtes rectangulaires générées pour les éléments de l'arborescence du document et disposées selon le modèle de mise en forme visuelle.

Chaque boîte comporte une zone de contenu (par exemple, du texte, une image, etc.) et des zones de marge intérieure, de bordure et de marge extérieure facultatives.

Modèle de boîte CSS2
Figure 19 : Modèle de boîte CSS2

Chaque nœud génère de 0 à n boîtes de ce type.

Tous les éléments ont une propriété "display" qui détermine le type de boîte qui sera généré.

Exemples :

block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.

La valeur par défaut est "inline", mais la feuille de style du navigateur peut définir d'autres valeurs par défaut. Par exemple, l'affichage par défaut de l'élément "div" est "block".

Vous trouverez un exemple de feuille de style par défaut sur la page www.w3.org/TR/CSS2/sample.html.

Schéma de positionnement

Il existe trois schémas :

  1. Normal : l'objet est positionné en fonction de sa place dans le document. Cela signifie que sa place dans l'arborescence de rendu est semblable à sa place dans l'arborescence DOM, et qu'il est disposé en fonction de son type de boîte et de ses dimensions.
  2. Float : l'objet est d'abord disposé comme un flux normal, puis déplacé le plus à gauche ou à droite possible.
  3. Absolu : l'objet est placé dans l'arborescence de rendu à un emplacement différent de celui de l'arborescence DOM.

Le schéma de positionnement est défini par la propriété "position" et l'attribut "float".

  • "static" et "relative" entraînent un flux normal
  • "absolute" et "fixed" entraînent un positionnement absolu

Dans le positionnement statique, aucune position n'est définie et le positionnement par défaut est utilisé. Dans les autres schémas, l'auteur spécifie la position : en haut, en bas, à gauche ou à droite.

La disposition de la boîte est déterminée par :

  • Type de boîte
  • Dimensions de la boîte
  • Schéma de positionnement
  • Informations externes telles que la taille de l'image et la taille de l'écran

Types de boîtes

Boîte de bloc : forme un bloc, possède son propre rectangle dans la fenêtre du navigateur.

Zone de bloc.
Figure 20 : Boîte de bloc

Zone intégrée : n'a pas son propre bloc, mais se trouve à l'intérieur d'un bloc conteneur.

Zones intégrées.
Figure 21 : Boîtes intégrées

Les blocs sont formatés verticalement les uns après les autres. Les éléments intégrés sont formatés horizontalement.

Mise en forme de bloc et intégrée.
Figure 22 : Mise en forme de bloc et intégrée

Les boîtes intégrées sont placées à l'intérieur de lignes ou de "boîtes de ligne". Les lignes sont au moins aussi hautes que la boîte la plus haute, mais elles peuvent être plus hautes lorsque les boîtes sont alignées sur la "ligne de base", ce qui signifie que la partie inférieure d'un élément est alignée sur un point d'une autre boîte, autre que le bas. Si la largeur du conteneur n'est pas suffisante, les éléments intégrés seront placés sur plusieurs lignes. C'est généralement ce qui se passe dans un paragraphe.

Lignes.
Figure 23 : Lignes

Positionnement

Relative

Positionnement relatif : l'élément est positionné comme d'habitude, puis déplacé selon le delta requis.

Emplacement relatif.
Figure 24 : Emplacement relatif

Nombres décimaux

Une boîte flottante est décalée vers la gauche ou la droite d'une ligne. La particularité intéressante est que les autres boîtes s'écoulent autour d'elle. Le code HTML :

<p>
  <img style="float: right" src="images/image.gif" width="100" height="100">
  Lorem ipsum dolor sit amet, consectetuer...
</p>

S'affichera comme suit :

Valeur float.
Figure 25 : Float

Absolu et fixe

La mise en page est définie exactement, quel que soit le flux normal. L'élément ne participe pas au flux normal. Les dimensions sont relatives au conteneur. Dans le cas de "fixed", le conteneur est la fenêtre d'affichage.

Positionnement fixe.
Figure 26 : Positionnement fixe

Représentation en couches

Ceci est spécifié par la propriété CSS z-index. Il représente la troisième dimension de la boîte : sa position le long de l'axe Z.

Les boîtes sont divisées en piles (appelées contextes d'empilement). Dans chaque pile, les éléments situés à l'arrière seront peints en premier, et les éléments situés à l'avant seront peints par-dessus, plus près de l'utilisateur. En cas de chevauchement, l'élément au premier plan masque l'élément précédent.

Les piles sont classées selon la propriété z-index. Les zones avec la propriété "z-index" forment une pile locale. La fenêtre d'affichage contient la pile externe.

Exemple :

<style type="text/css">
  div {
    position: absolute;
    left: 2in;
    top: 2in;
  }
</style>

<p>
  <div
    style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
  </div>
  <div
    style="z-index: 1;background-color:green;width: 2in; height: 2in;">
  </div>
</p>

Le résultat sera le suivant :

Positionnement fixe.
Figure 27 : Positionnement fixe

Bien que la div rouge précède la div verte dans le balisage et qu'elle aurait été peinte avant dans le flux standard, la propriété z-index est plus élevée. Elle est donc plus en avant dans la pile détenue par la boîte racine.

Ressources

  1. Architecture du navigateur

    1. Grosskurth, Alan. Architecture de référence pour les navigateurs Web (PDF)
    2. Gupta, Vineet. How Browsers Work - Part 1 - Architecture
  2. Analyse

    1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (aka the "Dragon book"), Addison-Wesley, 1986
    2. Rick Jelliffe. The Bold and the Beautiful: two new drafts for HTML 5.
  3. Firefox

    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
    2. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers (vidéo de présentation technique Google)
    3. L. David Baron, Moteur de mise en page de Mozilla
    4. L. David Baron, Mozilla Style System Documentation
    5. Chris Waterson, Notes on HTML Reflow
    6. Chris Waterson, Gecko Overview
    7. The life of an HTML HTTP request, par Alexander Larsson
  4. WebKit

    1. David Hyatt, Implementing CSS(part 1)
    2. An Overview of WebCore, David Hyatt
    3. WebCore Rendering, David Hyatt
    4. David Hyatt, The FOUC Problem
  5. Spécifications du W3C

    1. Spécification HTML 4.01
    2. Spécification HTML5 du W3C
    3. Spécification Cascading Style Sheets Level 2 Revision 1 (CSS 2.1)
  6. Instructions de compilation des navigateurs

    1. https://developer.mozilla.org/Build_Documentation (Firefox)
    2. http://webkit.org/building/build.html

Traductions

Cette page a été traduite deux fois en japonais :

Vous pouvez consulter les traductions hébergées en externe pour le coréen et le turc.

Bravo à tous !