Fonctionnement des navigateurs

Coulisses des navigateurs Web modernes

Préface

Cette présentation complète des opérations internes de WebKit et Gecko est le fruit de nombreuses recherches menées par le développeur israélien Tali Garsiel. Pendant quelques années, elle a passé en revue 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 a écrit:

En tant que développeur Web, apprendre les rouages des opérations de navigateur vous aide à prendre de meilleures décisions et à identifier les raisons qui sous-tendent les bonnes pratiques de développement. Bien qu'il s'agisse d'un document assez long, nous vous recommandons de prendre le temps de l'approfondir. Vous serez content de l'avoir fait.

Paul Irish, Chrome Developer Relations

Introduction

Les navigateurs Web sont les logiciels les plus utilisés. Dans cette introduction, j'explique comment ils fonctionnent en coulisse. Nous verrons 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

Les cinq principaux navigateurs utilisés actuellement sur les ordinateurs sont: Chrome, Internet Explorer, Firefox, Safari et Opera. Sur les mobiles, les principaux navigateurs sont le navigateur Android, l'iPhone, Opera Mini, Opera Mobile, UC Browser, les navigateurs Nokia S40/S60 et Chrome, qui sont tous basés sur WebKit, à l'exception des navigateurs Opera. Je vais citer des exemples issus des navigateurs Open Source Firefox et Chrome, et Safari (qui est partiellement Open Source). Selon les statistiques StatCounter (données de juin 2013), Chrome, Firefox et Safari représentent environ 71% de l'utilisation mondiale des navigateurs pour ordinateur. Sur les mobiles, le navigateur Android, 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 de votre choix, 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 il peut également s'agir d'un PDF, d'une image ou d'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 l'organisme W3C (World Wide Web Consortium), qui est l'organisme de normalisation du Web. Pendant des années, les navigateurs ne respectaient qu'une partie des spécifications et ont développé leurs propres extensions. Cela entraînait 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. Parmi les éléments d'interface utilisateur courants, on trouve:

  1. Barre d'adresse pour insérer un URI
  2. Boutons "Précédent" et "Suivant"
  3. Options d'ajout aux favoris
  4. Boutons d'actualisation et d'arrêt permettant d'actualiser ou d'arrêter le chargement des documents actuels
  5. Bouton d'accueil qui vous permet d'accéder à votre page d'accueil

Étrangement, l'interface utilisateur du navigateur n'est spécifiée dans aucune spécification formelle. Elle résulte simplement de bonnes pratiques façonnées au fil des années d'expérience et par des navigateurs qui s'imitent les uns les autres. La spécification HTML5 ne définit pas les éléments d'interface utilisateur requis par le navigateur, mais répertorie quelques éléments communs. par exemple la barre d'adresse, la barre d'état et la barre d'outils. Il existe, bien évidemment, des fonctionnalités propres à un navigateur particulier, comme le gestionnaire de téléchargements de Firefox.

Infrastructure de haut niveau

Les principaux composants du navigateur sont les suivants:

  1. Interface utilisateur: elle inclut la barre d'adresse, le bouton "Précédent/Suivant", le menu des favoris, etc. Toutes les parties du navigateur s'affichent, à l'exception de la fenêtre dans laquelle vous voyez la page demandée.
  2. Moteur de navigateur: regroupe les actions entre l'interface utilisateur et le moteur de rendu.
  3. Moteur de rendu: responsable de l'affichage du contenu demandé. Par exemple, si le contenu demandé est au format HTML, le moteur de rendu analyse les codes 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, différentes implémentations sont utilisées pour différentes plates-formes derrière une interface indépendante.
  5. Backend de l'interface utilisateur: 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 à la plate-forme. En dessous, il utilise les méthodes de l'interface utilisateur du système d'exploitation.
  6. Interpréteur JavaScript : Permet d'analyser et d'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 en local, comme les cookies. Les navigateurs sont également compatibles avec les 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

La responsabilité du moteur de rendu est 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 et des images HTML et XML. Elle peut afficher d'autres types de données via des plug-ins ou des extensions, comme l'affichage de documents PDF à l'aide d'un plug-in de visionneuse de PDF. Cependant, dans ce chapitre, nous nous concentrerons sur le cas d'utilisation principal: l'affichage de code HTML et d'images au format CSS.

Les moteurs de rendu varient selon les navigateurs: Internet Explorer utilise Trident, Firefox sur Gecko et Safari sur WebKit. Chrome et Opera (à partir de la version 15) utilisent Blink, une copie de WebKit.

WebKit est un moteur de rendu Open Source qui était au départ un moteur pour la plate-forme Linux. Il a été modifié par Apple pour être compatible avec Mac et Windows.

Flux principal

Le moteur de rendu commence à récupérer le contenu du document demandé à partir de la couche réseau. Cela se fait généralement par blocs de 8 Ko.

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 à convertir les éléments en nœuds DOM dans une arborescence appelée "arborescence de contenu". Le moteur analyse 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 permettront de 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 s'afficher à l'écran.

Après la construction de l'arborescence de rendu, elle est soumise à un processus de mise en page. Cela signifie donner à chaque nœud les coordonnées exactes où il doit apparaître à l'écran. L'étape suivante consiste à peindre : l'arborescence de rendu est balayée, et chaque nœud est peint à l'aide de la couche 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 tente d'afficher le contenu à l'écran dès que possible. Il n'est pas nécessaire d'attendre que tout le code HTML soit analysé pour 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 de provenir du réseau.

Exemples de flux principal

Flux principal de 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 globalement le même.

Gecko appelle l'arborescence d'éléments mis en forme visuellement une "arborescence de cadres". Chaque élément est un cadre. WebKit utilise le terme "Render Tree" (Arborescence de rendu) et se compose de "Render Objects". 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 connecter des nœuds DOM et des informations visuelles pour créer l'arborescence de rendu. Une différence non sémantique mineure est que Gecko comporte une couche supplémentaire entre le code HTML et l'arborescence DOM. C'est ce qu'on appelle le "récepteur de contenu". Il s'agit d'une fabrique permettant de créer des éléments DOM. Nous allons parler de chaque partie du flux:

Analyser - général

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

Analyser un document consiste à le traduire en une structure pouvant être utilisée dans le code. Le résultat de l'analyse est généralement une arborescence de nœuds qui représente la structure du document. C'est ce qu'on appelle une arborescence syntaxique ou syntaxique.

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

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

Grammaire

L'analyse est basée sur les règles de syntaxe du document: la langue ou le format dans lequel il a été rédigé. Chaque format que vous pouvez analyser doit avoir une grammaire déterministe composée de règles de vocabulaire et de syntaxe. C'est ce qu'on appelle une grammaire sans contexte. Les langues humaines ne sont pas de tels langages et ne peuvent donc pas être analysées avec les techniques d'analyse conventionnelles.

Combinaison Analyseur/Lexer

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

L'analyse lexicale consiste à diviser les entrées en jetons. Les jetons représentent le vocabulaire du langage, c'est-à-dire l'ensemble des éléments de base valides. En langage humain, il est composé de tous les mots qui figurent dans le dictionnaire de la langue en question.

L’analyse syntaxique est l’application des règles de syntaxe du langage.

Les analyseurs répartissent généralement le travail entre deux composants: le lexer (parfois appelé "tokenizer") qui est chargé de décomposer les entrées en jetons valides, et l'analyseur chargé de construire l'arborescence d'analyse en analysant la structure du document selon les règles de syntaxe du langage.

Le lexer sait comment supprimer les caractères non pertinents tels que les espaces blancs et les sauts de ligne.

Du document source à l'analyse d'arbres
Figure 6: Du document source à l'analyse des arborescences

Le processus d'analyse est itératif. L'analyseur demande généralement un nouveau jeton au lexer et essaie de le faire correspondre à l'une des règles de syntaxe. Si une règle est mise en correspondance, un nœud correspondant au jeton est ajouté à l'arborescence 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 syntaxique n'est pas le produit final. L'analyse est souvent utilisée en traduction, c'est-à-dire la transformation du 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 dans une arborescence d'analyse, puis traduit l'arborescence en un document de code machine.

Flux de compilation
Figure 7: flux de compilation

Exemple d'analyse

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

Syntaxe :

  1. Les expressions, les termes et les opérations sont les principaux composants de la syntaxe du langage.
  2. Notre langage peut inclure un nombre illimité d'expressions.
  3. Une expression est définie comme un "terme", suivi d'une "opération" suivi d'un autre terme
  4. Une opération est un jeton plus ou 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 n°5, il s'agit d'un terme. La deuxième correspondance est 2 + 3: cela correspond à la troisième règle: un terme suivi d'une opération suivi d'un autre terme. La correspondance suivante ne sera appelé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 suivi d'un autre terme. 2 + + ne correspond à aucune règle. Il s'agit donc d'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 langue sera définie 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 langue sera définie comme suit:

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

Nous avons vu qu'une langue peut être analysée par des analyseurs standards si sa grammaire est une grammaire sans contexte. La définition intuitive d'une grammaire sans contexte est une grammaire qui peut être entièrement exprimée dans BNF. Pour une définition formelle, consultez l'article de Wikipédia sur la grammaire sans contexte.

Types d'analyseurs

Il existe deux types d'analyseurs: les analyseurs descendants et les analyseurs ascendants. L'explication intuitive est que les analyseurs descendants examinent la structure de haut niveau de la syntaxe et essaient de trouver une correspondance de règle. Les analyseurs ascendants commencent par l'entrée et la transforment progressivement en règles de syntaxe, des règles de niveau inférieur jusqu'à ce que les règles de niveau supérieur soient respectées.

Voyons comment les deux types d'analyseurs vont analyser notre exemple.

L'analyseur de haut en bas commencera à partir de la règle de niveau supérieur: il identifiera 2 + 3 en tant qu'expression. Il identifie ensuite 2 + 3 - 1 en tant qu'expression (le processus d'identification de l'expression évolue pour correspondre aux 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 corresponde. Elle remplacera ensuite l'entrée correspondante par la règle. La lecture se poursuit jusqu'à la fin de l'entrée. L'expression partiellement correspondante est placée sur la pile de l'analyseur.

Pile Entrée
2 + 3 - 1
term + 3 – 1
opération à terme 3 – 1
expression – 1
opération d'expression 1
expression -

Ce type d'analyseur ascendant est appelé analyseur décalage/réduction, 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 elle est progressivement réduite aux règles de syntaxe.

Générer automatiquement des analyseurs

Certains outils permettent de générer un analyseur. Vous leur fournissez la grammaire de votre langue (vocabulaire et règles de syntaxe) pour qu'ils génèrent un analyseur fonctionnel. La création d'un analyseur nécessite une connaissance approfondie de l'analyse. Il n'est pas facile de créer manuellement un analyseur optimisé. Les générateurs d'analyseur peuvent donc s'avérer très utiles.

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

Analyseur HTML

Le rôle de l'analyseur HTML est d'analyser le balisage HTML dans un arbre syntaxique.

Grammaire HTML

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

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

Malheureusement, tous les thèmes classiques de l'analyseur ne s'appliquent pas au langage HTML (je ne les ai pas mentionnés juste pour m'amuser, ils seront utilisés pour analyser les fichiers CSS et JavaScript). HTML ne peut pas être facilement défini par une grammaire sans contexte dont les analyseurs ont besoin.

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

Cela peut paraître étrange à première vue ; le langage HTML est assez proche du XML. Il existe de nombreux analyseurs XML disponibles. Il existe une variante XML du langage HTML, le XHTML. Quelle est donc la principale différence ?

La différence réside dans le fait que l'approche HTML est plus tolérante : elle vous permet d'omettre certaines balises (qui sont ensuite ajoutées implicitement), ou d'omettre parfois des balises de début ou de fin, etc. Dans l'ensemble, il s'agit d'une syntaxe "soft", par opposition à la syntaxe stricte et exigeante du langage XML.

Ce petit détail en apparence fait toute la différence. D'un côté, c'est la raison principale pour laquelle le langage HTML est si populaire: il tolère vos erreurs et facilite la vie de l'auteur Web. En revanche, il est difficile d'écrire une grammaire formelle. Pour résumer, le code HTML ne peut pas être analysé facilement par des analyseurs conventionnels, car sa grammaire n'est pas sans 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, leurs attributs et leur hiérarchie. Comme nous l'avons vu précédemment, la DTD HTML ne forme pas une grammaire sans contexte.

Il existe quelques variantes de la DTD. Le mode strict respecte uniquement les spécifications, mais d'autres modes prennent en charge le balisage utilisé par les navigateurs auparavant. L'objectif est d'assurer la rétrocompatibilité avec les anciens contenus. La DTD stricte actuelle se trouve ici : www.w3.org/TR/html4/strict.dtd

DOM

L'arborescence de sortie ("parse tree") est une arborescence de nœuds d'éléments et d'attributs DOM. DOM est l'abréviation de Document Object Model (Modèle d'objet de document). Il s'agit de la présentation des objets 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 dans l'arborescence DOM suivante:

Arborescence DOM de l&#39;exemple de balisage
Figure 8: Arborescence DOM de l'exemple de balisage

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

Lorsque je dis que l'arborescence contient des nœuds DOM, cela signifie qu'elle est constituée d'éléments qui implémentent l'une des interfaces DOM. Les navigateurs utilisent des implémentations concrètes dont d'autres attributs sont utilisés en interne.

L'algorithme d'analyse

Comme nous l'avons vu dans les sections précédentes, il n'est pas possible d'analyser le code HTML à l'aide des analyseurs descendants ou ascendants classiques.

Pour les raisons suivantes :

  1. La nature indulgente du langage.
  2. Les navigateurs ont habituellement une tolérance aux erreurs afin de prendre en charge les cas bien connus de codes HTML incorrects.
  3. Le processus d'analyse est réentrant. Pour les autres langues, la source ne change pas lors de l'analyse. Toutefois, 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.

Il est impossible d'utiliser les techniques d'analyse habituelles. 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 d'arborescence.

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

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

Flux d&#39;analyse HTML (extrait de la spécification HTML5)
Figure 9: Flux d'analyse HTML (issu 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 dépend de l'état de tokenisation actuel et de l'état de construction de l'arbre. Cela signifie que le même caractère consommé produit des résultats différents pour l'état suivant correct, en fonction de l'état actuel. Cet algorithme est trop complexe pour être décrit de manière exhaustive. Voyons donc un exemple simple qui nous aidera à comprendre le principe.

Exemple de base - tokenisation du code HTML suivant:

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

L'état initial est "État des données". Lorsque le caractère < est détecté, l'état passe à "Tag ouvert". Si vous utilisez un caractère a-z, un "jeton de balise de début" est créé, l'état passe à "État du nom de balise". Nous restons dans cet état jusqu'à ce que le caractère > soit utilisé. Chaque caractère est ajouté au nom du nouveau jeton. Dans notre cas, le jeton créé est html.

Lorsque la balise > est atteinte, le jeton actuel est émis et l'état repasse à l'état des données. La balise <body> sera traitée de la même manière. Jusqu'à présent, les balises html et body ont été émises. Nous sommes de retour à l'état des données. L'utilisation du caractère H de Hello world entraîne la création et l'émission d'un jeton de caractère. Cela se poursuit jusqu'à ce que la < de </body> soit atteinte. Nous allons émettre un jeton de caractère pour chaque caractère de Hello world.

Nous sommes de retour à l'état "Tag ouvert". L'utilisation de l'entrée suivante / entraînera la création d'une end tag token et un passage au "État du nom de tag". Là encore, nous restons dans cet état jusqu'à atteindre >.Ensuite, le nouveau jeton de balise est émis et nous revenons à l'état des données. L'entrée </html> sera traitée comme la précédente.

Tokenisation de l&#39;exemple d&#39;entrée
Figure 10: Tokenisation de l'exemple d'entrée

Algorithme de construction d'arbres

Une fois l'analyseur créé, l'objet Document est créé. Au cours de l'étape de construction de l'arborescence, l'arborescence DOM dont la racine contient le document 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 qui lui est pertinent et sera créée pour ce jeton. L'élément est ajouté à l'arborescence DOM, ainsi qu'à la pile d'éléments ouverts. Cette pile permet de corriger les incohérences d'imbrication et les tags non fermés. L'algorithme est également décrit comme une machine à états. Les états sont appelés "modes d'insertion".

Examinons le processus de construction de l'arborescence pour cet exemple d'entrée:

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

L'entrée à l'étape de construction de l'arbre est une séquence de jetons provenant de l'étape de tokenisation. Le premier 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. Cette opération entraîne la création de l'élément HTMLHTMLElement, qui sera ajouté à l'objet Document racine.

L'état passera à "before head". Le jeton "body" est ensuite reçu. Un élément HTMLHeadElement sera créé implicitement, bien que nous ne disposions pas de jeton "head" et qu'il soit ajouté à l'arborescence.

Nous passons maintenant au mode in head (dans la tête), puis au mode "after head". Le jeton du corps est retraité, un élément HTMLBodyElement est créé et inséré, et le mode est transféré vers "in body".

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

La réception du jeton de fin du corps entraîne un transfert en mode "after body". Nous allons maintenant recevoir la balise HTML de fin qui nous fera passer en mode "after after body". La réception de la fin du jeton de fichier mettra fin à l'analyse.

Construction d&#39;arborescence de l&#39;exemple HTML.
Figure 11: Construction d'une arborescence pour un 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é", c'est-à-dire ceux qui doivent être exécutés après l'analyse du document. L'état du document est alors défini sur "complete" et un événement "load" est déclenché.

Vous pouvez consulter les algorithmes complets de tokenisation et de construction d'arborescences dans la spécification HTML5.

Tolérance des navigateurs aux erreurs

L'erreur "Syntaxe non valide" ne s'affiche jamais sur une page HTML. Les navigateurs corrigent tout contenu incorrect, puis se poursuivent.

Prenons ce code HTML par exemple:

<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 un tag standard, une imbrication incorrecte des éléments "p" et "div", etc.), mais le navigateur l'affiche toujours correctement et ne me plaint pas. Une grande partie du code de l'analyseur corrige donc les erreurs de l'auteur HTML.

La gestion des erreurs est assez cohérente dans les navigateurs, mais, assez étonnamment, elle ne fait pas partie des spécifications HTML. Tout comme les favoris et les boutons "Précédent" et "Suivant", c'est un phénomène qui s'est développé dans les navigateurs au fil des années. Il existe des constructions HTML incorrectes connues et répétées sur de nombreux sites, et les navigateurs essaient de les corriger d'une manière conforme à celle des autres navigateurs.

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

L'analyseur analyse l'entrée tokenisée dans le document, créant ainsi l'arborescence du document. Si le document est correctement formaté, il est facile de l'analyser.

Malheureusement, nous devons traiter de nombreux documents HTML qui ne sont pas bien formés. L'analyseur doit donc être tolérant aux erreurs.

Nous devons traiter 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 par la suite.
  2. Nous ne sommes pas autorisés à ajouter l'élément directement. Il se peut que la personne qui écrit le document ait oublié une balise entre les deux (ou que la balise située entre les deux soit facultative). Cela peut être le cas avec les balises suivantes: HTML HEAD BODY TBODY TR TD LI (Ai-je oublié certaines ?).
  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'à l'élément de bloc situé au-dessus.
  4. Si le problème persiste, fermez les éléments jusqu'à ce que nous soyons autorisés à les ajouter ou ignorez la balise.

Examinons quelques exemples de tolérance aux erreurs 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 n'est pas présentée à l'utilisateur.

Une table errante

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

Exemple :

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

WebKit va modifier 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 actuel de l'élément: il fait sortir le tableau interne de la pile du tableau externe. Les tables seront désormais frères.

Éléments de formulaire imbriqués

Si l'utilisateur place un formulaire dans un autre formulaire, ce dernier est ignoré.

Le code:

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

Hiérarchie des 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 HTML ou body de la fin mal placées

Encore une fois, le commentaire parle de lui-même.

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

Les auteurs Web doivent donc être prudents (sauf si vous voulez apparaître comme exemple dans un extrait de code de tolérance aux erreurs WebKit) écrivent un code HTML correct.

Analyse CSS

Vous vous souvenez des concepts d'analyse de l'introduction ? Contrairement au langage HTML, CSS est une grammaire sans contexte qui peut être analysée à 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 de "ident", comme un nom de classe. "name" est l'identifiant de l'élément (identifié par "#" ).

La grammaire de la syntaxe est décrite dans 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 est 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 formellement définie 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 nombre de sélecteurs séparés par une virgule et des espaces (S signifie espace blanc). Un ensemble 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. "déclaration" et "sélecteur" seront définis dans les définitions BNF suivantes.

Analyseur CSS WebKit

WebKit utilise des générateurs d'analyseur Flex et Bison pour créer automatiquement des analyseurs à partir des fichiers de grammaire CSS. Comme nous l'avons vu dans l'introduction de l'analyseur, Bison crée un analyseur ascendant Shift-reduce. 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 sélecteur et déclaration, ainsi que d'autres objets correspondant à la grammaire CSS.

Analyse CSS...
Figure 12: Analyse 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'à l'exécution du script. Si le script est externe, la ressource doit d'abord être extraite du réseau. Cette opération s'effectue également de manière synchrone, et l'analyse s'interrompt jusqu'à ce que la ressource soit récupérée. Ce modèle a été utilisé pendant de nombreuses années, et il est également spécifié dans les spécifications HTML4 et 5. Les auteurs peuvent ajouter l'attribut "defer" à un script, auquel cas il n'interrompt pas l'analyse du document et s'exécute après l'analyse du document. HTML5 ajoute une option permettant de marquer le script comme asynchrone afin qu'il soit analysé et exécuté par un autre thread.

Analyse spéculative

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

Feuilles de style

En revanche, les feuilles de style ont un modèle différent. D'un point de vue conceptuel, il semble que, comme 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. Toutefois, les scripts demandent des informations de style lors de la phase d'analyse du document. Si le style n'est pas encore chargé et analysé, le script obtiendra de mauvaises réponses, ce qui aurait entraîné de nombreux problèmes. Cela semble être un cas limite, mais il est assez courant. Firefox bloque tous les scripts lorsqu'une feuille de style est toujours 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.

Afficher la construction de l'arborescence

Pendant la construction de l'arborescence DOM, le navigateur en construit une autre : 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 de rendu "frames". WebKit utilise le terme moteur de rendu ou objet de rendu.

Un moteur de rendu sait comment se mettre en page et peindre ses propres enfants.

La classe RenderObject de WebKit, la classe de base des moteurs de rendu, a la définition suivante:

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 zone 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 zone dépend de la valeur "display" de l'attribut de style correspondant au nœud (voir la section sur le 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 display:

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 comportent des cadres spéciaux.

Dans WebKit, si un élément souhaite créer un moteur de rendu spécial, il remplacera la méthode createRenderer(). Les moteurs de rendu pointent vers les objets stylisés 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 un à un. Les éléments DOM non visuels ne seront pas insérés dans l'arborescence de rendu. L'élément "head" en est un exemple. De plus, les éléments dont la valeur d'affichage est définie sur "none" n'apparaîtront pas dans l'arborescence (contrairement aux éléments dont la visibilité est "masquée").

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 rectangle unique. Par exemple, l'élément "select" a 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 d'une ligne est insuffisante, les nouvelles lignes sont ajoutées en tant que moteurs de rendu supplémentaires.

Autre exemple de plusieurs moteurs de rendu : le code HTML corrompu. Conformément à la spécification CSS, un élément intégré ne doit contenir que des éléments de bloc ou uniquement des éléments intégrés. 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 flottants et les éléments positionnés de manière absolue sont hors du flux, placés dans une autre partie de l'arborescence et mappés sur le cadre réel. Un cadre d’espace réservé est là où ils auraient dû être.

L&#39;arborescence de rendu et l&#39;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'agira de l'objet "RenderView".

Le déroulement de la construction de l'arbre

Dans Firefox, la présentation est enregistrée en tant qu'écouteur pour les mises à jour DOM. La présentation délègue la création de frames à FrameConstructor. Le constructeur résout le style (voir la section sur le calcul de 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é "pièce jointe". Chaque nœud DOM possède une méthode "attach". L'association est synchrone : l'insertion de nœuds dans l'arborescence DOM appelle la nouvelle méthode "attach".

Le traitement des balises html et body entraîne la construction de la racine de l'arborescence de rendu. L'objet de rendu racine correspond à ce que la spécification CSS appelle le bloc qui le contient: le bloc le plus haut qui contient tous les autres blocs. Ses dimensions correspondent à la fenêtre d'affichage, c'est-à-dire aux dimensions 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 prend la forme d'une insertion de nœuds DOM.

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

Calcul des styles

Pour créer l'arborescence de rendu, vous devez calculer les propriétés visuelles de chaque objet de rendu. Pour ce faire, les propriétés de style de chaque élément sont calculées.

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").La version ultérieure 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. Ce sont des feuilles de style fournies par l'utilisateur du navigateur (les navigateurs vous permettent de définir vos styles préférés. (dans Firefox, par exemple, vous devez placer une feuille de style dans le dossier "Profil Firefox").

Le calcul des styles pose quelques difficultés:

  1. Les données de style sont une construction très volumineuse qui contiennent de nombreuses propriétés de style. Cela peut entraîner des problèmes de mémoire.
  2. Trouver les règles de correspondance pour chaque élément peut entraîner des problèmes de performances s'il n'est pas optimisé. Parcourir toute la liste de règles pour chaque élément pour trouver des correspondances est une tâche fastidieuse. Les sélecteurs peuvent avoir une structure complexe qui peut entraîner le démarrage du processus de mise en correspondance sur un chemin apparemment prometteur qui s'est avéré inutile et une autre voie doit être essayée.

    Par exemple, ce sélecteur composé:

    div div div div{
    ...
    }
    

    Cela signifie que les règles s'appliquent à un élément <div> qui est le descendant de trois éléments div. Supposons que vous souhaitiez vérifier si la règle s'applique à un élément <div> donné. Vous choisissez un chemin spécifique vers le haut de l'arbre à vérifier. Vous devrez peut-être balayer l'arborescence de nœuds vers le haut pour découvrir qu'il n'y a que deux div et que la règle ne s'applique pas. Vous devez ensuite essayer d'autres chemins dans l'arborescence.

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

Voyons comment les navigateurs sont confrontés à 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 frères ou cousins, et:

  1. Les éléments doivent avoir le même état de souris (par exemple, l'un ne peut pas être dans :hover alors que l'autre ne l'est pas).
  2. Aucun de ces éléments ne doit avoir d'identifiant.
  3. Les noms des balises doivent correspondre.
  4. Les attributs de classe doivent correspondre
  5. L'ensemble des attributs mappés doit être identique
  6. Les états des liens doivent être identiques.
  7. Les états de sélection doivent correspondre
  8. Aucun de ces éléments ne doit être affecté par les sélecteurs d'attribut. En effet, ces éléments sont définis comme ayant une correspondance de sélecteur utilisant un sélecteur d'attribut à n'importe quelle position dans le sélecteur.
  9. Aucun attribut de style ne doit être intégré aux éléments.
  10. Aucun sélecteur frère ne doit être utilisé. WebCore lance simplement une transition globale lorsqu'un sélecteur frère est détecté et désactive le partage des styles 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 arborescences supplémentaires pour faciliter le calcul des styles: l'arborescence des règles et l'arborescence de contexte des styles. WebKit dispose également d'objets de style, mais ils ne sont pas stockés dans une arborescence comme l'arborescence de contexte, seul le nœud DOM pointe vers le style correspondant.

Arborescence de contexte de style Firefox.
Figure 14: Arborescence de contexte des styles 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'arborescence de règles est très intelligente. Il permet de partager ces valeurs entre les nœuds pour éviter de les calculer à nouveau. Cela permet également d'économiser de l'espace.

Toutes les règles correspondantes sont stockées dans une arborescence. Les nœuds situés en bas d'un chemin ont une priorité plus élevée. L'arborescence contient tous les chemins d'accès des correspondances de règles trouvées. Le stockage des règles s'effectue 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 tracés calculés sont ajoutés à l'arborescence.

L'idée est de voir les chemins dans les arbres comme des mots dans un lexique. Imaginons que nous ayons déjà calculé cette arborescence de règles:

Arborescence de règles calculée
Figure 15: Arborescence des règles calculées
.

Supposons que nous devions faire correspondre les règles d'un autre élément de l'arborescence de contenu et découvrir que les règles correspondantes (dans le bon ordre) sont B-E-I. Ce tracé figure déjà dans l'arborescence, car nous avons déjà calculé le chemin A-B-E-I-L. La tâche sera donc réduite.

Voyons comment l'arbre nous sauve le travail.

Division en structs

Les contextes de style sont divisés en structures. Ces structs 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 héritées ou non. 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 "reset") utilisent des valeurs par défaut si elles ne sont pas définies.

L'arborescence nous aide en mettant en cache des structs entiers (contenant les valeurs finales calculées) dans l'arborescence. 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.

Calcul des contextes de style à l'aide de l'arborescence de règles

Lorsque nous calculons le contexte de style d'un élément donné, nous calculons d'abord un tracé dans l'arborescence de règles ou nous utilisons un tracé existant. Nous commençons ensuite à appliquer les règles du chemin pour remplir les structures dans notre nouveau contexte de style. Nous commençons par le nœud inférieur du chemin (celui ayant la priorité la plus élevée (généralement le sélecteur le plus spécifique)) et nous remontons l'arborescence jusqu'à ce que notre structure soit pleine. S'il n'existe aucune spécification pour l'objet struct dans ce nœud de règle, nous pouvons l'optimiser considérablement. Nous montrons dans l'arborescence jusqu'à ce que nous trouvions un nœud qui le spécifie entièrement et pointe vers celui-ci. C'est la meilleure optimisation - l'ensemble de la structure est partagé. Cela permet d'économiser la mémoire et les calculs des valeurs de fin.

Si nous trouvons des définitions partielles, nous montons dans l'arborescence jusqu'à ce que le struct soit rempli.

Si nous n'avons trouvé aucune définition pour notre structure, si celle-ci est de type "hérité", nous pointerons vers la structure de notre parent dans l'arborescence de contexte. Dans ce cas, nous avons également réussi à partager des structs. S'il s'agit d'une structure réinitialisée, 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 d'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é entre eux.

Prenons un exemple : si nous possédons 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>

Respectez 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 devions remplir uniquement deux structs: color struct et margin struct. La structure color ne contient qu'un seul membre: la structure color La structure margin contient les quatre côtés.

L'arborescence de règles ainsi obtenue se présentera comme suit (les nœuds sont indiqués par le nom du nœud, c'est-à-dire le numéro de la règle vers laquelle ils pointent) :

Arborescence de règles
Figure 16: Arborescence des règles

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

Arborescence de contexte.
Figure 17: Arborescence de contexte

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

Nous allons faire correspondre les règles et découvrir que celles de <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 et que nous devons simplement y ajouter un autre nœud pour la règle 6 (nœud F dans l'arborescence de règles).

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

Nous devons maintenant remplir les structs de style. Nous allons commencer par remplir la structure de la marge. Comme le dernier nœud de règle (F) n'ajoute aucune structure à la marge, nous pouvons remonter dans l'arborescence jusqu'à ce que nous trouvions une structure mise en cache calculée dans une insertion de nœud précédente et l'utilisons. Nous la trouverons sur le nœud B, qui est le nœud supérieur pour spécifier les règles de marge.

Nous avons une définition de struct color, nous ne pouvons donc pas utiliser un struct mis en cache. La couleur ayant un attribut, il n'est pas nécessaire de remonter dans l'arborescence pour remplir les 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 facile. Nous nous conformerons aux règles et arriverons à la conclusion qu'elle renvoie à la règle G, comme pour l'intervalle précédent. Étant donné que les frères et sœurs pointent vers le même nœud, nous pouvons partager l'intégralité du contexte du style et se contenter de pointer vers le contexte du segment précédent.

Pour les structs qui contiennent des règles héritées du parent, la mise en cache s'effectue sur l'arborescence de contexte (la propriété color est en fait héritée, mais Firefox la considère comme réinitialisée et la met en cache dans l'arborescence des règles).

Par exemple, si nous avons ajouté des règles pour les polices d'un paragraphe:

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

Ainsi, 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 se produit si aucune règle de police n'a été spécifiée pour le paragraphe.

Dans WebKit, qui ne possède pas d'arborescence de règles, les déclarations correspondantes sont balayées quatre fois. Les premières propriétés de priorité élevée non importantes sont appliquées (les propriétés qui doivent être appliquées en premier, car d'autres en dépendent, telles que l'affichage), puis les règles de priorité élevée à priorité élevée, puis les règles de priorité normale non importante, puis les règles de priorité normale à priorité normale. Cela signifie que les propriétés qui apparaissent plusieurs fois sont résolues selon l'ordre correct de la cascade. La dernière victoire.

En résumé, partager les objets de style (l'intégralité ou certains des structs qu'ils contiennent) permet de résoudre les problèmes 1 et 3. L'arborescence de règles 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 pour les règles de style:

  1. Règles CSS, dans des feuilles de style externes ou 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 (mappés aux règles de style pertinentes) html <p bgcolor="blue" /> Les deux derniers peuvent facilement être associés à l'élément, car il est le propriétaire des attributs de style, tandis que 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 mise en correspondance des règles CSS peut s'avérer plus délicate. Pour résoudre ce problème, les règles sont manipulées afin d'y accéder plus facilement.

Après avoir analysé la feuille de style, les règles sont ajoutées à l'une des cartes de hachage, en fonction du sélecteur. Il existe des mappages par ID, par nom de classe et par nom de tag, ainsi qu'une carte générale pour tous les éléments qui ne rentrent pas dans ces catégories. Si le sélecteur est un ID, la règle sera ajoutée au mappage d'ID. S'il s'agit d'une classe, elle sera ajoutée au mappage de classes, etc.

Cette manipulation facilite grandement la mise en correspondance des règles. Il n'est pas nécessaire de consulter chaque déclaration: nous pouvons extraire les règles pertinentes pour un élément des cartes. 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).

Examinons par exemple les 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 des classes. Le deuxième dans le mappage d'ID et le troisième dans le mappage de tags.

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. Le mappage de classes contient une clé "error" sous laquelle se trouve la règle "p.error". L'élément div comportera des règles pertinentes dans le mappage d'ID (la clé correspond à l'ID) et dans le mappage de tags. Il ne reste donc qu'à identifier les règles extraites par les clés qui correspondent réellement.

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

table div {margin: 5px}

Elle sera tout de même extraite du mappage de tags, car la clé est le sélecteur le plus à droite, mais elle ne correspondrait pas à notre élément div, qui n'a pas d'ancêtre de table.

WebKit et Firefox effectuent cette manipulation.

Ordre en cascade de la feuille 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 par 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 y a plusieurs définitions ; c'est ici l'ordre en cascade qui permet de le résoudre.

Une déclaration de 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 "cascade". Selon la spécification CSS2, l'ordre en cascade est (du plus faible au plus élevé):

  1. Déclarations du navigateur
  2. Déclarations normales de l'utilisateur
  3. Déclarations normales de l'auteur
  4. Déclarations importantes de l'auteur
  5. Déclarations importantes de l'utilisateur

Les déclarations de 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 sont triées par spécificité, puis dans 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 avec une priorité faible.

Spécificité

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

  1. "count 1" si la déclaration dont il provient est un attribut "style" et non une règle avec un sélecteur, et 0 dans le cas contraire (= a)
  2. Compter le nombre d'attributs d'identifiant 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 numérique à base importante) 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 une base hexadécimale. Dans le cas peu probable où a=17, vous aurez besoin d'une base numérique à 17 chiffres. La situation ultérieure peut se produire avec un sélecteur comme celui-ci : html body div div p... (17 tags 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 en fonction des règles de cascade d'annonces. WebKit utilise le tri par bulle pour les petites listes et le tri fusionné pour les longues listes. 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'attachement, des espaces réservés sont utilisés, il est marqué dans le document. Ils sont ensuite recalculés une fois les feuilles de style chargées.

Mise en page

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

HTML utilise un modèle de mise en page basé sur les flux, ce qui signifie que la plupart du temps, il est possible de calculer la géométrie en un seul passage. Les éléments situés plus tard 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 poursuivre de gauche à droite, 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 supérieure et 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 sur une partie ou la totalité de la hiérarchie des frames, en calculant des 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 correspondent à la fenêtre d'affichage, c'est-à-dire à la partie visible de la fenêtre du navigateur.

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

Système de bits sale

Afin de ne pas appliquer une mise en page complète à chaque petite modification, les navigateurs utilisent un système "dirty bit". Un moteur de rendu modifié ou ajouté se marque lui-même et ses enfants comme "sales": il a besoin d'une mise en page.

Il existe deux indicateurs: "sales" (sale) et "children are dirty" (les enfants sont sales). Cela signifie que même si le moteur de rendu lui-même est correct, il comporte au moins un enfant qui a besoin d'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 de rendu : 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. Suite au redimensionnement d'un écran

La mise en page peut être incrémentielle. Seuls les moteurs de rendu incorrects sont disposés (cela peut causer des dommages qui nécessiteront 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 sales. (par exemple, lorsque de nouveaux moteurs de rendu sont ajoutés à l'arborescence de rendu après que du contenu supplémentaire provient du réseau et a été ajouté à l'arborescence DOM).

Mise en page incrémentielle.
Figure 18: Mise en page incrémentielle – Seuls les moteurs de rendu incorrects et leurs enfants sont disposés

Mises en page asynchrones et synchrones

La mise en page incrémentielle est effectuée de manière asynchrone. Firefox met en file d'attente des "commandes reflow" pour les mises en page incrémentielles et un planificateur déclenche l'exécution par lots de ces commandes. WebKit dispose également d'un minuteur qui exécute une mise en page incrémentielle : l'arborescence est balayée et les moteurs de rendu "sale" 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, tels que la position de défilement, ont changé.

Optimisations

Lorsqu'une mise en page est déclenchée par un "redimensionnement" ou un changement de la position du moteur de rendu(et non de la taille), 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 à la racine. Cela peut se produire lorsque le changement est local et n'affecte pas son environnement, comme du texte inséré dans des champs de texte (dans le cas contraire, chaque frappe déclenche une mise en page à partir de la racine).

Le processus de mise en page

La mise en page se présente généralement comme suit:

  1. Le moteur de rendu parent détermine sa propre largeur.
  2. Le parent passe sur les enfants et :
    1. Placez le moteur de rendu enfant (définit ses x et y).
    2. Appelle la mise en page enfant si nécessaire (s'il est sale, si nous utilisons 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 des marges intérieures pour définir sa propre hauteur. Elle sera utilisée par le parent du moteur de rendu parent.
  4. Définit la valeur "dirty" de l'élément sur "false".

Firefox utilise un objet "state" (nsHTMLReflowState) pour la mise en page (appelé "reflow"). Entre autres, l'état inclut la largeur des parents.

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

Calcul de la largeur

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

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

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

Il serait calculé par WebKit comme suit(class RenderBox méthode calcWidth):

  • La largeur du conteneur correspond au nombre maximal de conteneurs availableWidth, qui est égal à 0. Dans ce cas, la valeur availableWidth correspond à contentWidth, qui est calculé comme suit:
clientWidth() - paddingLeft() - paddingRight()

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

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

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

Jusqu'à présent, il s'agissait du calcul de la "largeur préférée". Les largeurs minimale et maximale seront maintenant calculées.

Si la largeur souhaité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é incassable), la largeur minimale est utilisée.

Les valeurs sont mises en cache au cas où une mise en page est 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 être interrompu, il s'arrête et se propage au parent de la mise en page pour indiquer qu'il doit être interrompu. Le parent crée les moteurs de rendu supplémentaires et appelle leur mise en page.

Peinture

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

Globales et incrémentielles

Tout comme la disposition, la peinture peut être globale (l'arbre entier est peint) ou incrémentiel. Avec le rendu incrémentiel, certains moteurs de rendu changent d'une manière qui n'affecte pas l'ensemble de l'arborescence. Le moteur de rendu modifié invalide son rectangle à l'écran. L'OS la considère alors comme une "zone sale" et génère un événement "peindre". L'OS le fait intelligemment et fusionne plusieurs régions en une seule. Dans Chrome, c'est plus compliqué, car le moteur de rendu n'a pas le même processus que le processus principal. Chrome simule le comportement du système d'exploitation dans une certaine mesure. La présentation écoute ces événements et délègue le message à la racine de rendu. L'arborescence est balayée jusqu'à ce que le moteur de rendu correspondant soit atteint. Elle se repeigne (et généralement ses enfants).

Ordre des peintures

Le code CSS2 définit l'ordre du processus de rendu. Il s'agit en fait de l'ordre dans lequel les éléments sont empilés dans les contextes d'empilement. Cet ordre affecte la peinture puisque les piles sont peintes du dos au recto. L'ordre d'empilement d'un moteur de rendu de blocs 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'arborescence de rendu et crée une liste d'affichage pour le rectangle peint. Il contient les moteurs de rendu pertinents pour le rectangle, dans l'ordre de peinture approprié (arrière-plans des moteurs de rendu, puis bordures, etc.).

Ainsi, l'arbre ne doit être traversé qu'une seule fois pour un repeindre, et non plusieurs fois (tous les arrière-plans, toutes les images, toutes les bordures, etc.).

Firefox optimise ce processus en n'ajoutant pas d'éléments qui seront masqués, tels que des éléments complètement situés sous d'autres éléments opaques.

Espace de stockage rectangulaire WebKit

Avant de repeindre, WebKit enregistre l'ancien rectangle en tant que bitmap. Il ne peigne ensuite que le delta entre le nouveau et l'ancien rectangle.

Modifications dynamiques

En réponse à un changement, les navigateurs essaient d'effectuer le moins d'actions possible. Ainsi, les modifications apportées à la couleur d'un élément ne feront que repeindre l'élément. Toute modification de la position de l'élément entraîne sa mise en page et son repeindre, ses enfants et éventuellement ses frères et sœurs. L'ajout d'un nœud DOM entraîne la mise en page et le repeinture du nœud. Des modifications majeures, telles que l'augmentation de la taille de police de l'élément "html", entraîneront l'invalidation des caches, et la remise en page et le repeinture de l'intégralité de l'arborescence.

Threads du moteur de rendu

Le moteur de rendu est monothread. Presque tout, à l'exception des opérations réseau, se déroule 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 par 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 entre 2 et 6 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 (tels que 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

La toile

Selon la spécification CSS2, le terme "canevas" décrit "l'espace dans lequel la structure de mise en forme est affichée", c'est-à-dire l'endroit où le navigateur affiche 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. Dans le cas contraire, une couleur définie par le navigateur est attribuée.

Modèle CSS Box

Le modèle de zone CSS décrit les zones rectangulaires générées pour les éléments de l'arborescence de documents et disposées selon le modèle de mise en forme visuelle.

Chaque zone comporte une zone de contenu (texte, image, etc.) et, éventuellement, des marges intérieures, des bordures et des marges.

Modèle de zone CSS2
Figure 19: Modèle de zone CSS2

Chaque nœud génère 0...n de ces cases.

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

Exemples :

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

La valeur par défaut est intégrée, 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 le bloc.

Vous trouverez un exemple de feuille de style par défaut à l'adresse suivante: www.w3.org/TR/CSS2/sample.html.

Schéma de positionnement

Il existe trois schémas:

  1. Normale: l'objet est positionné conformément à sa position dans le document. Cela signifie que son emplacement dans l'arborescence de rendu est identique à celui dans l'arborescence DOM, et qu'il est disposé selon son type de zone et ses dimensions.
  2. Flottant: l'objet est d'abord disposé comme un flux normal, puis déplacé le plus à gauche ou à droite que possible
  3. Absolu: l'objet est placé dans l'arborescence de rendu à un autre endroit que dans l'arborescence DOM.

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

  • statiques et relatives provoquent un flux normal
  • position absolue et cause fixe

En cas de 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: haut, bas, gauche, droite.

La disposition de la boîte dépend des éléments suivants:

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

Types de zones

Bloc: forme un bloc ; possède son propre rectangle dans la fenêtre du navigateur.

Zone de blocage.
Figure 20: Zone de blocs

Boîte intégrée: ne possède pas son propre bloc, mais se trouve à l'intérieur d'un bloc conteneur.

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

Les volumes sont formatés verticalement l'un après l'autre. Les annonces intégrées sont mises en forme horizontalement.

Mise en forme par bloc et mise en forme intégrée.
Figure 22: Mise en forme par blocs et intégrée

Les cases intégrées sont placées à l'intérieur des lignes ou "cases de ligne". Les lignes sont au moins aussi hautes que la boîte la plus haute, mais peuvent être plus grandes lorsque les boîtes sont alignées sur le "référence", c'est-à-dire 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 est insuffisante, les lignes de délimitation s'affichent sur plusieurs lignes. C'est généralement ce qui se passe dans un paragraphe.

Lignes.
Figure 23: Lignes

La

Relatif

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

Positionnement relatif
Figure 24: Positionnement relatif

Nombres décimaux

Une boîte flottante est décalée vers la gauche ou la droite d'une ligne. Ce qui est intéressant, c'est que les autres cases circulent 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>

Se présentera comme suit:

Propriété « float ».
Figure 25: float

Absolue 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 un modèle fixe, le conteneur est la fenêtre d'affichage.

Positionnement fixe.
Figure 26: Positionnement fixe

Représentation multicouche

Cet élément est spécifié par la propriété CSS z-index. Il s'agit de 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 "Retour" seront peints en premier et les éléments placés avant, au-dessus, le plus près possible de l'utilisateur. En cas de chevauchement, le premier élément masque le premier.

Les piles sont classées en fonction de la propriété z-index. Les zones contenant une propriété "z-index" forment une pile locale. La fenêtre d'affichage comporte 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 le div rouge soit antérieur au vert dans le balisage et aurait été auparavant peint dans le flux standard, la propriété z-index est plus élevée. Elle apparaît donc plus en avant dans la pile contenue par le cadre racine.

Ressources

  1. Architecture du navigateur

    1. Grosskurth, Alan. Architecture de référence pour les navigateurs Web (pdf)
    2. Gupta, Vineet. Fonctionnement des navigateurs – Partie 1 – Architecture
  2. analyse

    1. Aho, Sethi, Ullman, "Les compilateurs: principes, techniques et outils" (aussi appelé "livre dragon"), Addison-Wesley, 1986
    2. Rick Jelliffe. L'audace et l'esthétique: deux nouveaux brouillons pour le format HTML5
  3. Firefox

    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
    2. L. David Baron, Accelerate HTML and CSS: Layout Engine Internals for Web Developers (Google Tech talk video)
    3. L. David Baron, Le moteur de mise en page de Mozilla
    4. L. David Baron, documentation du système de style Mozilla
    5. Chris Waterson, Notes on HTML Reflow
    6. Chris Waterson, Gecko Overview
    7. Alexander Larsson, The life of an HTML HTTP request
  4. WebKit

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

    1. Spécifications HTML 4.01
    2. Spécification W3C pour HTML5
    3. Spécification CSS 2.1 (Cascading Style Sheets, Level 2 Révision 1)
  6. Instructions pour créer des navigateurs

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

Traductions

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

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

Bravo à tous !