Hinter den Kulissen moderner Webbrowser
Vorwort
Diese umfassende Einführung zu den internen Abläufen von WebKit und Gecko ist der das Ergebnis zahlreicher Recherchen der israelischen Entwicklerin Tali Garsiel. Über ein paar sahen sich alle veröffentlichten Daten über Browser-Interna an und verbrachte einen und viel Zeit mit dem Lesen von Quellcode verbringen. Sie schrieb:
Als Webentwickler mit den internen Abläufen von Browser vertraut machen hilft Ihnen, bessere Entscheidungen zu treffen und die Begründungen hinter der Entwicklung zu kennen Best Practices. Dieses Dokument ist ziemlich lang, daher empfehlen wir Sie sich damit beschäftigen. Sie werden froh sein, dass Sie es getan haben.
Paul Irish, Chrome Developer Relations
Einführung
Webbrowser sind die am häufigsten verwendete Software. In diesem Leitfaden erkläre ich,
hinter den Kulissen. Mal sehen, was passiert, wenn du google.com
eingibst
, bis Sie die Google-Seite auf dem Browserbildschirm sehen.
Browser, über die wir sprechen
Auf dem Desktop werden heute fünf Hauptbrowser verwendet: Chrome, Internet Explorer, Firefox, Safari und Opera. Die wichtigsten Browser auf Mobilgeräten sind der Android-Browser, das iPhone, Opera Mini und Opera Mobile, der UC-Browser, die Nokia S40/S60-Browser und Google Chrome. Alle Browser, mit Ausnahme der Opera-Browser, basieren auf WebKit. Ich gebe Beispiele aus den Open-Source-Browsern Firefox und Chrome sowie Safari (teilweise Open Source). Laut StatCounterstatistiken (Stand: Juni 2013) machten Firefox und Safari rund 71% der weltweiten Desktop-Browsernutzung aus. Android-Browser, iPhone und Chrome machen etwa 54% der Nutzung von Mobilgeräten aus.
Hauptfunktionen des Browsers
Die Hauptfunktion eines Browsers besteht darin, die von Ihnen ausgewählte Webressource anzuzeigen, indem Sie sie vom Server anfordern und im Browserfenster anzeigen. Bei der Ressource handelt es sich normalerweise um ein HTML-Dokument, es kann sich aber auch um PDF-Dateien, Bilder oder andere Inhalte handeln. Der Standort der Ressource wird vom Nutzer mithilfe eines URI (Uniform Resource Identifier) angegeben.
Wie der Browser HTML-Dateien interpretiert und anzeigt, wird in den HTML- und CSS-Spezifikationen festgelegt. Diese Spezifikationen werden vom World Wide Web Consortium W3C verwaltet, der Normungsorganisation für das Web. Jahrelang haben Browser nur einen Teil der Spezifikationen befolgt und ihre eigenen Erweiterungen entwickelt. Das führte zu ernsthaften Kompatibilitätsproblemen für Webautoren. Heutzutage entsprechen die meisten Browser mehr oder weniger den Spezifikationen.
Browser-Benutzeroberflächen haben viel gemeinsam. Zu den gängigen Benutzeroberflächenelementen gehören:
- Adressleiste zum Einfügen eines URI
- Schaltflächen „Zurück“ und „Vorwärts“
- Lesezeichenoptionen
- Schaltflächen zum Aktualisieren und Stoppen, um das Laden aktueller Dokumente zu aktualisieren oder zu stoppen
- Schaltfläche „Startseite“, über die Sie zu Ihrer Startseite gelangen
Seltsamerweise ist die Benutzeroberfläche des Browsers in keiner formalen Spezifikation festgelegt, sondern aus bewährten Vorgehensweisen, die sich im Laufe der Jahre entwickelt haben und durch Browser, die sich gegenseitig imitieren. In der HTML5-Spezifikation werden keine UI-Elemente definiert, die ein Browser haben muss, aber einige häufig verwendete Elemente. Zu diesen gehören die Adressleiste, die Statusleiste und die Symbolleiste. Natürlich gibt es auch Funktionen, die nur für einen bestimmten Browser verfügbar sind, wie beispielsweise den Download-Manager von Firefox.
Gesamtinfrastruktur
Die Hauptkomponenten des Browsers sind:
- Die Benutzeroberfläche: Hierzu gehören die Adressleiste, die Schaltfläche „Zurück“ und „Weiter“, das Lesezeichenmenü usw. Alle Bereiche der Browseranzeige mit Ausnahme des Fensters, in dem die angeforderte Seite angezeigt wird.
- Das Browser-Modul: ordnet Aktionen zwischen der Benutzeroberfläche und dem Rendering-Modul.
- Das Rendering-Modul: verantwortlich für die Anzeige des angeforderten Inhalts Wenn es sich bei dem angeforderten Inhalt beispielsweise um HTML handelt, parst das Rendering-Modul HTML und CSS und zeigt den geparsten Inhalt auf dem Bildschirm an.
- Netzwerk: Für Netzwerkaufrufe wie HTTP-Anfragen mit unterschiedlichen Implementierungen für unterschiedliche Plattformen hinter einer plattformunabhängigen Schnittstelle.
- UI-Backend: Wird zum Zeichnen grundlegender Widgets wie Kombinationsfelder und Fenster verwendet. Dieses Backend stellt eine generische Schnittstelle zur Verfügung, die nicht plattformspezifisch ist. Darunter werden Benutzeroberflächenmethoden des Betriebssystems verwendet.
- JavaScript-Interpreter Wird zum Parsen und Ausführen von JavaScript-Code verwendet.
- Datenspeicherung: Dies ist eine Persistenzebene. Der Browser muss möglicherweise alle Arten von Daten lokal speichern, etwa Cookies. Browser unterstützen auch Speichermechanismen wie localStorage, IndexedDB, WebSQL und FileSystem.
![Browserkomponenten](https://web.dev/static/articles/howbrowserswork/image/browser-components-9cd8ff834cc9c.png?authuser=6&hl=de)
Beachten Sie, dass Browser wie Chrome mehrere Instanzen des Rendering-Moduls ausführen: eine für jeden Tab. Jeder Tab wird in einem separaten Prozess ausgeführt.
Rendering-Module
Die Aufgabe des Rendering-Moduls ist naja... das Rendern, d. h. die Anzeige der angeforderten Inhalte auf dem Browserbildschirm.
Standardmäßig kann das Rendering-Modul HTML- und XML-Dokumente sowie Bilder anzeigen. Sie kann über Plug-ins oder Erweiterungen auch andere Datentypen anzeigen. beispielsweise die Anzeige von PDF-Dokumenten mit einem PDF-Viewer-Plug-in. In diesem Kapitel konzentrieren wir uns jedoch auf den Hauptanwendungsfall: die Darstellung von HTML und Bildern, die mit CSS formatiert sind.
Verschiedene Browser verwenden unterschiedliche Rendering-Module: Internet Explorer verwendet Trident, Firefox verwendet Gecko und Safari verwendet WebKit. Chrome und Opera (ab Version 15) verwenden Blink, eine Fork von WebKit.
WebKit ist ein Open-Source-Rendering-Modul, das als Engine für die Linux-Plattform gestartet und von Apple modifiziert wurde, um Mac und Windows zu unterstützen.
Der Hauptablauf
Das Rendering-Modul beginnt mit dem Abrufen des Inhalts des angeforderten Dokuments aus der Netzwerkschicht. Dies erfolgt normalerweise in Blöcken von 8 KB.
Danach sieht der grundlegende Ablauf des Rendering-Moduls so aus:
<ph type="x-smartling-placeholder">![Grundlegender Ablauf des Rendering-Moduls](https://web.dev/static/articles/howbrowserswork/image/rendering-engine-basic-fl-2fba02b24e871.png?authuser=6&hl=de)
Das Rendering-Modul beginnt mit dem Parsen des HTML-Dokuments und konvertiert Elemente in DOM-Knoten in einer sogenannten „Inhaltsstruktur“. Die Suchmaschine parst die Stildaten sowohl in externen CSS-Dateien als auch in Stilelementen. Anhand der Stilinformationen und der visuellen Anweisungen im HTML-Code wird eine weitere Baumstruktur erstellt: die Rendering-Struktur.
Die Rendering-Struktur enthält Rechtecke mit visuellen Attributen wie Farbe und Abmessungen. Die Rechtecke befinden sich in der richtigen Reihenfolge, um auf dem Bildschirm angezeigt zu werden.
Nach der Konstruktion der Rendering-Struktur wird ein Layout durchlaufen . Dabei werden jedem Knoten die genauen Koordinaten zugewiesen, an denen er auf dem Bildschirm erscheinen soll. Die nächste Phase ist das Painting. Dabei wird die Rendering-Struktur durchlaufen und jeder Knoten mithilfe der UI-Back-End-Ebene dargestellt.
Es ist wichtig zu verstehen, dass dies ein schrittweiser Prozess ist. Für eine bessere Nutzererfahrung versucht das Rendering-Modul, Inhalte so schnell wie möglich auf dem Bildschirm darzustellen. Es wartet nicht, bis der gesamte HTML-Code geparst wurde, bevor mit dem Erstellen und dem Layout der Rendering-Struktur begonnen wird. Teile des Inhalts werden geparst und angezeigt, während der Prozess mit dem restlichen Inhalt fortgesetzt wird, der aus dem Netzwerk kommt.
Beispiele für den Hauptablauf
<ph type="x-smartling-placeholder">![WebKit-Hauptablauf](https://web.dev/static/articles/howbrowserswork/image/webkit-main-flow-b779d50c0cf28.png?authuser=6&hl=de)
![Hauptablauf der Rendering-Engine Gecko von Mozilla](https://web.dev/static/articles/howbrowserswork/image/mozillas-gecko-rendering-b18e445544965.jpg?authuser=6&hl=de)
In den Abbildungen 3 und 4 können Sie sehen, dass WebKit und Gecko zwar etwas unterschiedliche Terminologie verwenden, der Ablauf jedoch im Prinzip gleich ist.
Bei Gecko wird die Baumstruktur der visuell formatierten Elemente "Frame Tree" genannt. Jedes Element ist ein Frame. WebKit verwendet den Begriff "Render Tree", und besteht aus "Render-Objekten". WebKit verwendet den Begriff "Layout" für die Platzierung von Elementen. Gecko nennt dies „Reflow“. „Anhang“ ist der Begriff WebKit für das Verbinden von DOM-Knoten und visuellen Informationen zum Erstellen der Rendering-Struktur. Ein kleiner nicht semantischer Unterschied besteht darin, dass Gecko zwischen dem HTML-Code und dem DOM-Baum eine zusätzliche Ebene hat. Sie wird als „Inhaltssenke“ bezeichnet. und dient der Herstellung von DOM-Elementen. Wir werden jeden Teil des Ablaufs besprechen:
Parsing – allgemein
Da das Parsing ein sehr wichtiger Prozess innerhalb des Rendering-Moduls ist, werden wir uns etwas genauer damit befassen. Beginnen wir mit einer kurzen Einführung in das Parsing.
Beim Parsen eines Dokuments wird es in eine Struktur übersetzt, die der Code verwenden kann. Das Ergebnis des Parsings ist normalerweise eine Knotenstruktur, die die Struktur des Dokuments wiedergibt. Dies wird als Analysebaum oder Syntaxbaum bezeichnet.
Beim Parsen des Ausdrucks 2 + 3 - 1
könnte beispielsweise dieser Baum zurückgegeben werden:
![Baumknoten für mathematische Ausdrücke.](https://web.dev/static/articles/howbrowserswork/image/mathematical-expression-t-6681a2511ead2.png?authuser=6&hl=de)
Grammatik
Das Parsing basiert auf den Syntaxregeln, die das Dokument befolgt: die Sprache oder das Format, in der bzw. dem es geschrieben wurde. Jedes Format, das geparst werden kann, muss über eine deterministische Grammatik aus Vokabular und Syntaxregeln verfügen. Es wird als kontextfreie Grammatik. Menschliche Sprachen sind solche Sprachen nicht und können daher nicht mit herkömmlichen Parsing-Techniken geparst werden.
Parser-Lexer-Kombination
Das Parsing kann in zwei Unterprozesse unterteilt werden: die lexikalische Analyse und die Syntaxanalyse.
Bei der lexikalischen Analyse wird die Eingabe in Tokens aufgeteilt. Bei Tokens handelt es sich um das Vokabular der Sprache: die Sammlung gültiger Bausteine. In der menschlichen Sprache besteht er aus allen Wörtern, die im Wörterbuch der entsprechenden Sprache vorkommen.
Bei der Syntaxanalyse werden die Syntaxregeln der Sprache angewendet.
Parser teilen die Arbeit normalerweise zwischen zwei Komponenten auf: dem Lexer (manchmal auch Tokenizer genannt), der für die Aufteilung der Eingabe in gültige Tokens zuständig ist, und dem Parser, der für die Erstellung des Parserbaums verantwortlich ist. Dazu wird die Dokumentstruktur gemäß den Syntaxregeln der Sprache analysiert.
Der Lexer weiß, wie irrelevante Zeichen wie Leerzeichen und Zeilenumbrüche entfernt werden.
<ph type="x-smartling-placeholder">![Vom Quelldokument zur Parsing-Struktur](https://web.dev/static/articles/howbrowserswork/image/from-source-document-par-c9c8c59da1ef2.png?authuser=6&hl=de)
Der Parsing-Prozess ist iterativ. Der Parser fordert in der Regel vom Lexer ein neues Token an und versucht, das Token einer Syntaxregel zuzuordnen. Wenn eine Regel übereinstimmt, wird ein Knoten, der dem Token entspricht, dem Parser hinzugefügt und der Parser fordert ein anderes Token an.
Wenn keine Regel übereinstimmt, speichert der Parser das Token intern und fordert so lange Tokens an, bis eine Regel gefunden wird, die mit allen intern gespeicherten Tokens übereinstimmt. Wird keine Regel gefunden, löst der Parser eine Ausnahme aus. Dies bedeutet, dass das Dokument ungültig war und Syntaxfehler enthielt.
Übersetzung
In vielen Fällen ist die Parsing-Struktur nicht das Endprodukt. Parsing wird häufig bei der Übersetzung verwendet: Umwandlung des Eingabedokuments in ein anderes Format. Ein Beispiel hierfür ist die Kompilierung. Der Compiler, der Quellcode in Maschinencode kompiliert, parst ihn zuerst in einen Parsing-Baum und übersetzt diesen dann in ein Maschinencodedokument.
<ph type="x-smartling-placeholder">![Kompilierungsablauf](https://web.dev/static/articles/howbrowserswork/image/compilation-flow-57cfc3aa68a53.png?authuser=6&hl=de)
Parsing-Beispiel
In Abbildung 5 haben wir einen Parse-Baum auf der Grundlage eines mathematischen Ausdrucks erstellt. Lassen Sie uns versuchen, eine einfache mathematische Sprache zu definieren und uns den Analyseprozess anzusehen.
Syntax:
- Die Bausteine der Sprachsyntax sind Ausdrücke, Begriffe und Operationen.
- Unsere Sprache kann beliebig viele Ausdrücke enthalten.
- Ein Ausdruck ist als „Begriff“ definiert. gefolgt von einem „Vorgang“ gefolgt von einem weiteren Begriff
- Ein Vorgang ist ein Plus- oder Minus-Token.
- Ein Term ist ein ganzzahliges Token oder ein Ausdruck.
Lassen Sie uns die Eingabe-2 + 3 - 1
analysieren.
Der erste Teilstring, der einer Regel entspricht, ist 2
: Gemäß Regel 5 ist es ein Term.
Die zweite Übereinstimmung ist 2 + 3
: Sie entspricht der dritten Regel: ein Term gefolgt von einer Operation gefolgt von einem weiteren Term.
Die nächste Übereinstimmung erfolgt erst am Ende der Eingabe.
2 + 3 - 1
ist ein Ausdruck, da wir bereits wissen, dass 2 + 3
ein Term ist. Es gibt also einen Term gefolgt von einer Operation gefolgt von einem weiteren Term.
2 + +
stimmt mit keiner Regel überein und ist daher eine ungültige Eingabe.
Formale Definitionen für Vokabular und Syntax
Vokabular wird normalerweise durch reguläre Ausdrücke ausgedrückt.
Unsere Sprache wird beispielsweise so definiert:
INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -
Wie Sie sehen, werden Ganzzahlen durch einen regulären Ausdruck definiert.
Die Syntax wird normalerweise im Format BNF definiert. Unsere Sprache sieht dann so aus:
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
Wir haben gesagt, dass eine Sprache von regulären Parsern geparst werden kann, wenn ihre Grammatik eine kontextfreie Grammatik ist. Eine intuitive Definition einer kontextfreien Grammatik ist eine Grammatik, die vollständig in BNF ausgedrückt werden kann. Eine formelle Definition finden Sie in Wikipedia-Artikel zur kontextfreien Grammatik
Parsertypen
Es gibt zwei Arten von Parsern: Top-down-Parser und Bottom-up-Parser. Eine intuitive Erklärung besteht darin, dass Top-down-Parser die übergeordnete Struktur der Syntax untersuchen und nach einer Regelübereinstimmung suchen. Bottom-up-Parser beginnen mit der Eingabe und wandeln sie nach und nach in die Syntaxregeln um, angefangen bei den Low-Level-Regeln bis zur Erfüllung der übergeordneten Regeln.
Sehen wir uns an, wie die beiden Parsertypen unser Beispiel parsen.
Der Top-down-Parser beginnt mit der Regel auf höherer Ebene: Er identifiziert 2 + 3
als Ausdruck. Anschließend wird 2 + 3 - 1
als Ausdruck identifiziert. Der Prozess zur Identifizierung des Ausdrucks entwickelt sich weiter und entspricht den anderen Regeln, aber der Startpunkt ist die Regel auf der höchsten Ebene.
Der Bottom-up-Parser scannt die Eingabe, bis eine Regel abgeglichen wird. Dann wird die übereinstimmende Eingabe durch die Regel ersetzt. Dies wird bis zum Ende der Eingabe fortgesetzt. Der teilweise übereinstimmende Ausdruck wird im Stapel des Parsers platziert.
Diese Art von Bottom-up-Parser wird als Shift-Reduce-Parser bezeichnet, da die Eingabe nach rechts verschoben wird (Stellen Sie sich einen Zeiger vor, der zuerst auf den Anfang der Eingabe zeigt und sich nach rechts bewegt) und allmählich auf Syntaxregeln reduziert wird.
Automatisches Erstellen von Parsern
Es gibt Tools, die einen Parser generieren können. Sie füttern die Grammatik Ihrer Sprache - deren Vokabular und Syntaxregeln - und sie generieren einen funktionierenden Parser. Die Erstellung eines Parsers erfordert ein tiefgreifendes Verständnis des Parsings und es ist nicht einfach, manuell einen optimierten Parser zu erstellen, daher können Parser-Generatoren sehr nützlich sein.
WebKit verwendet zwei bekannte Parser-Generatoren: Flex zum Erstellen eines Lexers und Bison zum Erstellen eines Parsers (auch bekannt als Lex und Yacc). Bei der Flex-Eingabe handelt es sich um eine Datei, die reguläre Ausdrucksdefinitionen der Tokens enthält. Bison gibt die Syntaxregeln der Sprache im BNF-Format ein.
HTML-Parser
Der HTML-Parser hat die Aufgabe, das HTML-Markup in eine Parsing-Struktur zu parsen.
HTML-Grammatik
Das Vokabular und die Syntax von HTML sind in Spezifikationen definiert, die von der W3C-Organisation erstellt wurden.
Wie wir in der Einführung zum Parsen gesehen haben, kann die Grammatiksyntax formell mithilfe von Formaten wie BNF definiert werden.
Leider treffen alle konventionellen Parser-Themen nicht auf HTML (ich habe sie nicht nur aus Spaß erwähnt, sie werden beim Parsen von CSS und JavaScript verwendet). HTML kann nicht einfach durch eine kontextfreie Grammatik definiert werden, die Parser benötigen.
Es gibt ein formelles Format zum Definieren von HTML - DTD (Document Type Definition), aber es ist keine kontextfreie Grammatik.
Das sieht auf den ersten Blick seltsam aus. HTML ist dem von XML ziemlich ähnlich. XML-Parser sind viele verfügbar. Es gibt eine XML-Variante von HTML: XHTML. Was ist also der große Unterschied?
Der Unterschied besteht darin, dass der HTML-Ansatz eher umsichtig ist: Sie können bestimmte Tags (die dann implizit hinzugefügt werden) oder manchmal Start- oder End-Tags weglassen usw. Insgesamt ist es ein „weicher“ im Gegensatz zur steifen und anspruchsvollen Syntax von XML.
Dieses scheinbar kleine Detail macht einen großen Unterschied. Einerseits ist dies der Hauptgrund für die Beliebtheit von HTML: HTML verzeiht Ihnen Ihre Fehler und macht Webautoren das Leben einfacher. Andererseits wird es dadurch schwierig, eine formale Grammatik zu schreiben. Zusammenfassend lässt sich sagen, dass HTML von konventionellen Parsern nicht einfach geparst werden kann, da seine Grammatik nicht kontextfrei ist. HTML kann nicht von XML-Parsern geparst werden.
HTML-DTD
Die HTML-Definition erfolgt im DTD-Format. Mit diesem Format werden Sprachen der SGML-Familie definiert. Das Format enthält Definitionen für alle zulässigen Elemente, ihre Attribute und die Hierarchie. Wie bereits erwähnt, bildet die HTML-DTD keine kontextfreie Grammatik.
Die DTD verfügt über verschiedene Variationen. Der strikte Modus entspricht ausschließlich den Spezifikationen, aber andere Modi unterstützen auch Markups, die von Browsern in der Vergangenheit verwendet wurden. Der Zweck ist die Abwärtskompatibilität mit älteren Inhalten. Die aktuelle strikte DTD ist hier: www.w3.org/TR/html4/strict.dtd
DOM
Die Ausgabestruktur (der "Analysebaum") ist eine Baumstruktur aus DOM-Element- und Attributknoten. DOM steht für Document Object Model. Es ist die Objektdarstellung des HTML-Dokuments und die Schnittstelle von HTML-Elementen mit der Außenwelt, z. B. JavaScript.
Die Wurzel der Baumstruktur ist das Dokument -Objekt enthält.
Das DOM steht in einer fast 1:1-Beziehung zur Auszeichnung. Beispiel:
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
Dieses Markup würde in den folgenden DOM-Baum übersetzt:
<ph type="x-smartling-placeholder">![DOM-Baum des Beispiel-Markups](https://web.dev/static/articles/howbrowserswork/image/dom-tree-the-example-mar-70be67fe14c9a.png?authuser=6&hl=de)
Wie HTML wird DOM von der W3C-Organisation angegeben. Weitere Informationen finden Sie unter www.w3.org/DOM/DOMTR. Es handelt sich um eine allgemeine Spezifikation zum Bearbeiten von Dokumenten. HTML-spezifische Elemente werden in einem speziellen Modul beschrieben. Die HTML-Definitionen finden Sie hier: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.
Wenn ich sage, dass der Baum DOM-Knoten enthält, meine ich, dass er aus Elementen besteht, die eine der DOM-Schnittstellen implementieren. Browser verwenden konkrete Implementierungen mit anderen Attributen, die vom Browser intern verwendet werden.
Der Parsing-Algorithmus
Wie in den vorherigen Abschnitten gezeigt wurde, kann HTML nicht mit den regulären Top-down- oder Bottom-up-Parsern analysiert werden.
Hierfür gibt es folgende Gründe:
- Der nachsichtige Charakter der Sprache.
- Die Tatsache, dass Browser über eine herkömmliche Fehlertoleranz verfügen, um bekannte Fälle von ungültigem HTML zu unterstützen.
- Der Parsing-Prozess ist eintretend. Bei anderen Sprachen ändert sich die Quelle während des Parsings nicht. In HTML können jedoch durch dynamischen Code (z. B. Skriptelemente mit
document.write()
-Aufrufen) zusätzliche Tokens hinzugefügt werden, sodass die Eingabe durch den Parsing-Prozess geändert wird.
Da sie die regulären Parsing-Techniken nicht verwenden können, erstellen Browser benutzerdefinierte Parser zum Parsen von HTML.
Der Parsing-Algorithmus wird in der HTML5-Spezifikation ausführlich beschrieben. Der Algorithmus besteht aus zwei Phasen: Tokenisierung und Baumkonstruktion.
Die Tokenisierung ist die lexikalische Analyse, bei der die Eingabe in Tokens geparst wird. Zu den HTML-Tokens gehören Start-Tags, End-Tags, Attributnamen und Attributwerte.
Der Tokenizer erkennt das Token, gibt es an den Baumkonstruktor weiter und verarbeitet das nächste Zeichen, um das nächste Token zu erkennen, usw. bis zum Ende der Eingabe.
<ph type="x-smartling-placeholder">![HTML-Parsing-Ablauf (aus der HTML5-Spezifikation)](https://web.dev/static/articles/howbrowserswork/image/html-parsing-flow-taken-6118c51b92b56.png?authuser=6&hl=de)
Der Tokenisierungsalgorithmus
Der Algorithmus gibt ein HTML-Token aus. Der Algorithmus wird als Zustandsautomat ausgedrückt. Jeder Zustand verbraucht ein oder mehrere Zeichen des Eingabestreams und aktualisiert den nächsten Status entsprechend diesen Zeichen. Die Entscheidung wird vom aktuellen Tokenisierungszustand und vom Zustand der Baumkonstruktion beeinflusst. Das bedeutet, dass dasselbe Zeichen je nach aktuellem Zustand unterschiedliche Ergebnisse für den nächsten richtigen Zustand liefert. Der Algorithmus ist zu komplex, um ihn vollständig zu beschreiben. Sehen wir uns daher ein einfaches Beispiel an, das das Prinzip verdeutlichen soll.
Einfaches Beispiel – Tokenisierung des folgenden HTML-Codes:
<html>
<body>
Hello world
</body>
</html>
Der Anfangszustand ist der „Data“-Zustand.
Wenn das Zeichen <
vorkommt, ändert sich der Status in „Tag open“.
Die Aufnahme eines a-z
-Zeichens führt zur Erstellung eines Start-Tag-Tokens und der Status ändert sich in "Tag name state" (Tag-Name-Status).
Dieser Zustand bleibt so lange erhalten, bis das Zeichen >
verbraucht ist. Jedes Zeichen wird an den neuen Tokennamen angehängt. In unserem Fall ist das erstellte Token ein html
-Token.
Wenn das Tag >
erreicht ist, wird das aktuelle Token ausgegeben und der Status ändert sich wieder in "Data".
Das <body>
-Tag wird mit denselben Schritten behandelt.
Bisher wurden die Tags html
und body
ausgegeben. Wir befinden uns jetzt wieder im „Data“-Zustand.
Die Aufnahme des H
-Zeichens von Hello world
führt zum Erstellen und Ausgeben eines Zeichentokens. Dies wird so lange fortgesetzt, bis der <
von </body>
erreicht ist. Für jedes Zeichen von Hello world
wird ein Zeichentoken ausgegeben.
Wir befinden uns jetzt wieder im Status Tag open.
Die Übernahme der nächsten Eingabe-/
führt zum Erstellen eines end tag token
und zum Wechsel in den Status „Tag name“. Auch hier bleiben wir in diesem Zustand, bis >
erreicht ist.Dann wird das neue Tag-Token ausgegeben und wir kehren zum Datenstatus zurück.
Die </html>
-Eingabe wird wie der vorherige Fall behandelt.
![Beispieleingabe tokenisieren](https://web.dev/static/articles/howbrowserswork/image/tokenizing-example-input-9d0cc36689681.png?authuser=6&hl=de)
Algorithmus für die Baumkonstruktion
Wenn der Parser erstellt wird, wird auch das Document-Objekt erstellt. Während der Baumkonstruktion wird die DOM-Baumstruktur mit dem Dokument im Stamm geändert und es werden Elemente hinzugefügt. Jeder vom Tokenizer ausgegebene Knoten wird vom Baumkonstruktor verarbeitet. Die Spezifikation definiert für jedes Token, welches DOM-Element für dieses Token relevant ist und für dieses Token erstellt wird. Das Element wird dem DOM-Baum und dem Stapel offener Elemente hinzugefügt. Dieser Stapel wird verwendet, um verschachtelte Abweichungen und nicht geschlossene Tags zu korrigieren. Der Algorithmus wird auch als Zustandsautomat beschrieben. Die Zustände werden als „Einfügemodi“ bezeichnet.
Sehen wir uns den Ablauf der Baumkonstruktion für die Beispieleingabe an:
<html>
<body>
Hello world
</body>
</html>
Die Eingabe für die Baumkonstruktionsphase ist eine Abfolge von Tokens aus der Tokenisierungsphase. Der erste Modus ist der "initial mode". Empfangen von „html“ führt dazu, dass das Token in den Modus before html verschoben und in diesem Modus noch einmal verarbeitet wird. Dies führt zur Erstellung des HTMLHTMLElement-Elements, das an das Stammobjekt „Document“ angehängt wird.
Der Status wird in "before head" geändert. Der „Body“ wird dann empfangen. Ein HTMLHeadElement wird implizit erstellt, obwohl kein "head"-Element vorhanden ist. und es wird der Baumstruktur hinzugefügt.
Es folgt der "in head"-Modus und dann der "after head"-Modus. Das "body"-Token wird erneut verarbeitet, ein "HTMLBodyElement" wird erstellt und eingefügt und der Modus wechselt zu "in body".
Die Zeichen-Tokens von „Hello World“ wurden empfangen. Bei der ersten Option wird ein „Text“ erstellt und eingefügt. und die anderen Zeichen werden an diesen Knoten angehängt.
Der Empfang des body-End-Tokens löst einen Wechsel in den "after body"-Modus aus. Sie erhalten nun das "html"-End-Tag, das den Modus "after after body" ändert. Durch den Empfang des Dateiende-Tokens wird das Parsing beendet.
<ph type="x-smartling-placeholder">![Baumkonstruktion des HTML-Beispiels](https://web.dev/static/articles/howbrowserswork/image/tree-construction-exampl-4e9757a851f96.gif?authuser=6&hl=de)
Aktionen nach dem Parsing
In dieser Phase markiert der Browser das Dokument als interaktiv und beginnt mit dem Parsen von Skripts, die sich im Status „Ausgesetzt“ befinden. mode: diejenigen, die nach dem Parsen des Dokuments ausgeführt werden sollen. Der Dokumentstatus wird dann auf „Abgeschlossen“ gesetzt und „Laden“ ausgelöst.
Die vollständigen Algorithmen für die Tokenisierung und Baumkonstruktion finden Sie in der HTML5-Spezifikation.
Browser Fehlertoleranz
Sie erhalten nie eine ungültige Syntax. -Fehler auf einer HTML-Seite. Browser korrigieren ungültige Inhalte und fahren fort.
Nehmen wir als Beispiel diesen HTML-Code:
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</p>
</html>
Ich habe vermutlich eine Million Regeln verletzt (z. B. ist „mytag“ kein Standard-Tag, die „p“- und „div“-Elemente sind falsch verschachtelt usw.), aber der Browser zeigt es immer noch richtig an und beschwert sich nicht. Ein Großteil des Parsercodes behebt also die Fehler des HTML-Autors.
Die Fehlerbehandlung erfolgt in allen Browsern relativ konsistent, ist jedoch erstaunlicherweise nicht Teil der HTML-Spezifikationen. Wie das Speichern von Lesezeichen und die Zurück-/Vorwärts-Schaltflächen ist dies eine Sache, die sich im Laufe der Jahre in Browsern entwickelt hat. Es gibt bekannte ungültige HTML-Konstrukte, die auf vielen Websites wiederholt werden, und die Browser versuchen, sie auf eine Weise zu korrigieren, die mit anderen Browsern kompatibel ist.
In der HTML5-Spezifikation werden jedoch einige dieser Anforderungen definiert. WebKit fasst dies im Kommentar zu Beginn der HTML-Parserklasse gut zusammen.
Der Parser parst die tokenisierte Eingabe in das Dokument und erstellt so die Dokumentstruktur. Wenn das Dokument wohlgeformt ist, ist das Parsen unkompliziert.
Leider müssen wir viele HTML-Dokumente verarbeiten, die nicht wohlgeformt sind. Daher muss der Parser gegenüber Fehlern tolerant sein.
Wir müssen zumindest die folgenden Fehlerbedingungen berücksichtigen:
- Das hinzugefügte Element ist innerhalb eines äußeren Tags ausdrücklich nicht zulässig. In diesem Fall sollten wir alle Tags bis zu dem Tag schließen, in dem das Element nicht zulässig ist, und es anschließend hinzufügen.
- Wir dürfen das Element nicht direkt hinzufügen. Es könnte sein, dass die Person, die das Dokument verfasst hat, ein Tag dazwischen vergessen hat oder dass das dazwischen liegende Tag optional ist. Dies könnte bei den folgenden Tags der Fall sein: HTML HEAD BODY TBODY TR TD LI (habe ich eines vergessen?).
- Wir möchten ein Block-Element innerhalb eines Inline-Elements hinzufügen. Schließen Sie alle Inline-Elemente bis zum nächsthöheren Blockelement.
- Falls das nicht hilft, schließen Sie die Elemente, bis wir das Element hinzufügen oder das Tag ignorieren dürfen.
Hier einige Beispiele für die WebKit-Fehlertoleranz:
</br>
statt <br>
Einige Websites verwenden </br>
anstelle von <br>
. Zur Kompatibilität mit IE und Firefox behandelt WebKit dies wie <br>
.
Der Code:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
reportError(MalformedBRError);
t->beginTag = true;
}
Beachten Sie, dass die Fehlerbehandlung intern erfolgt und dem Nutzer nicht angezeigt wird.
Eine verirrte Tabelle
Eine verirrte Tabelle ist eine Tabelle in einer anderen Tabelle, aber nicht in einer Tabellenzelle.
Beispiel:
<table>
<table>
<tr><td>inner table</td></tr>
</table>
<tr><td>outer table</td></tr>
</table>
WebKit ändert die Hierarchie in zwei gleichgeordnete Tabellen:
<table>
<tr><td>outer table</td></tr>
</table>
<table>
<tr><td>inner table</td></tr>
</table>
Der Code:
if (m_inStrayTableContent && localName == tableTag)
popBlock(tableTag);
WebKit verwendet einen Stapel für den aktuellen Elementinhalt: Die innere Tabelle wird aus dem Stapel der äußeren Tabelle herausgenommen. Die Tabellen sind jetzt gleichgeordnet.
Verschachtelte Formularelemente
Wenn der Nutzer ein Formular in ein anderes Formular einfügt, wird das zweite Formular ignoriert.
Der Code:
if (!m_currentFormElement) {
m_currentFormElement = new HTMLFormElement(formTag, m_document);
}
Zu tiefe Tag-Hierarchie
Der Kommentar spricht für sich selbst.
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;
}
Falsch platzierte HTML- oder Body-End-Tags
Auch hier spricht der Kommentar für sich selbst.
if (t->tagName == htmlTag || t->tagName == bodyTag )
return;
Webautoren sollten also aufgepasst sein, wenn Sie nicht als Beispiel in einem Code-Snippet für die Fehlertoleranz von WebKit erscheinen möchten, sollten Sie wohlgeformten HTML-Code schreiben.
CSS-Parsing
Erinnern Sie sich an die Parsing-Konzepte aus der Einführung? Im Gegensatz zu HTML ist CSS eine kontextfreie Grammatik und kann mit den in der Einführung beschriebenen Parsertypen geparst werden. In der CSS-Spezifikation wird sogar die lexikalische Grammatik und die Syntaxgrammatik von CSS definiert.
Hier einige Beispiele:
Die lexikalische Grammatik (Vokabular) wird durch reguläre Ausdrücke für jedes Token definiert:
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" steht für Identifier, z. B. einen Klassennamen. „Name“ ist eine Element-ID (auf die durch "#" verwiesen wird)
Die Syntaxgrammatik wird in BNF beschrieben.
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*] ')' ]
;
Erklärung:
Ein Regelsatz hat folgende Struktur:
div.error, a.error {
color:red;
font-weight:bold;
}
div.error
und a.error
sind Selektoren. Der Teil innerhalb der geschweiften Klammern enthält die Regeln, die von diesem Regelsatz angewendet werden.
Diese Struktur wird in der folgenden Definition formell definiert:
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
Das bedeutet, dass ein Regelsatz ein Selector oder optional eine Reihe von Selektoren ist, die durch ein Komma und Leerzeichen getrennt sind (S steht für Leerraum). Ein Regelsatz enthält geschweifte Klammern, in denen sich eine Deklaration oder optional mehrere durch ein Semikolon getrennte Deklarationen befinden. „Erklärung“ und „Auswahl“ werden in den folgenden BNF-Definitionen definiert.
WebKit-CSS-Parser
WebKit verwendet die Parsergeneratoren Flex und Bison, um automatisch Parser aus den CSS-Grammatikdateien zu erstellen. Wie Sie sich in der Einführung zu Parsern erinnern, erstellt Bison einen Bottom-up-Shift-Reduce-Parser. Firefox verwendet einen manuell erstellten Top-down-Parser. In beiden Fällen wird jede CSS-Datei in ein Stylesheet-Objekt geparst. Jedes Objekt enthält CSS-Regeln. Die CSS-Regelobjekte enthalten Selektor- und Deklarationsobjekte sowie andere Objekte, die der CSS-Grammatik entsprechen.
<ph type="x-smartling-placeholder">![Parsen von CSS.](https://web.dev/static/articles/howbrowserswork/image/parsing-css-4531ebee58764.png?authuser=6&hl=de)
Verarbeitungsreihenfolge für Skripts und Stylesheets
Skripts
Das Modell des Webs ist synchron. Autoren erwarten, dass Skripts sofort geparst und ausgeführt werden, wenn der Parser ein <script>
-Tag erreicht.
Das Parsen des Dokuments wird angehalten, bis das Skript ausgeführt wurde.
Wenn das Skript extern ist, muss die Ressource zuerst aus dem Netzwerk abgerufen werden. Dies erfolgt ebenfalls synchron und das Parsen wird angehalten, bis die Ressource abgerufen ist.
Dies war viele Jahre lang das Modell und wird auch in den HTML4- und 5-Spezifikationen beschrieben.
Autoren können den „Aussetzen“ hinzufügen zu einem Skript hinzu. In diesem Fall wird das Parsen des Dokuments nicht angehalten und es wird ausgeführt, nachdem das Dokument geparst wurde. HTML5 bietet eine Option zum Markieren des Skripts als asynchron, damit es von einem anderen Thread geparst und ausgeführt wird.
Spekulatives Parsing
Sowohl WebKit als auch Firefox führen diese Optimierung durch. Beim Ausführen von Skripts parst ein anderer Thread den Rest des Dokuments und findet heraus, welche anderen Ressourcen aus dem Netzwerk geladen werden müssen, und lädt sie. Auf diese Weise können Ressourcen bei parallelen Verbindungen geladen werden und die Gesamtgeschwindigkeit wird verbessert. Hinweis: Der spekulative Parser parst nur Verweise auf externe Ressourcen wie externe Skripts, Stylesheets und Bilder. Er ändert nicht den DOM-Baum, der dem Hauptparser überlassen wird.
Style sheets
Stylesheets haben dagegen ein anderes Modell. Da Stylesheets den DOM-Baum nicht verändern, scheint es grundsätzlich keinen Grund zu geben, auf sie zu warten und das Parsen des Dokuments zu stoppen. Es gibt jedoch das Problem, dass Skripts beim Parsen des Dokuments Stilinformationen anfordern. Wenn der Stil noch nicht geladen und geparst wurde, erhält das Skript falsche Antworten, was offensichtlich zu einer Vielzahl von Problemen führt. Dies scheint ein Grenzfall zu sein, kommt aber recht häufig vor. Firefox blockiert alle Skripts, wenn ein Stylesheet noch geladen und geparst wird. WebKit blockiert Skripts nur, wenn sie versuchen, auf bestimmte Stileigenschaften zuzugreifen, die von nicht geladenen Stylesheets betroffen sein können.
Konstruktion der Rendering-Baumstruktur
Während der DOM-Baumstruktur erstellt der Browser einen weiteren Baum, die Rendering-Struktur. Diese Baumstruktur enthält visuelle Elemente in der Reihenfolge, in der sie angezeigt werden. Dies ist die visuelle Darstellung des Dokuments. Zweck dieser Baumstruktur ist es, den Inhalt in der richtigen Reihenfolge darzustellen.
Bei Firefox werden die Elemente in der Rendering-Struktur als "Frames" bezeichnet. WebKit verwendet den Begriff Renderer oder Rendering-Objekt.
Ein Renderer kann sich selbst und seine untergeordneten Elemente gestalten und malen.
Die RenderObject-Klasse von WebKit, die Basisklasse der Renderer, hat folgende Definition:
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
}
Jeder Renderer repräsentiert einen rechteckigen Bereich, der normalerweise der CSS-Box eines Knotens entspricht, wie in der CSS2-Spezifikation beschrieben. Sie enthält geometrische Informationen wie Breite, Höhe und Position.
Der Boxtyp wird durch die Einstellung Wert des Stilattributs, das für den Knoten relevant ist (siehe Abschnitt Stilberechnung). Mit dem folgenden WebKit-Code wird entschieden, welcher Renderer-Typ für einen DOM-Knoten gemäß dem Anzeigeattribut erstellt werden soll:
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;
}
Der Elementtyp wird ebenfalls berücksichtigt: Formularsteuerelemente und Tabellen haben beispielsweise spezielle Frames.
Wenn in WebKit ein Element einen speziellen Renderer erstellen möchte, wird die Methode createRenderer()
überschrieben.
Die Renderer verweisen auf Stilobjekte, die nicht geometrische Informationen enthalten.
Die Rendering-Struktur im Verhältnis zur DOM-Struktur
Die Renderer entsprechen DOM-Elementen, es besteht jedoch kein 1:1-Verhältnis. Nicht visuelle DOM-Elemente werden nicht in die Rendering-Struktur eingefügt. Ein Beispiel hierfür ist der „head“- -Elements. Auch Elemente, deren Anzeigewert "none" zugewiesen wurde werden nicht in der Baumstruktur angezeigt, während Elemente mit der Sichtbarkeit „ausgeblendet“ in der Baumstruktur angezeigt werden.
Es gibt DOM-Elemente, die mehreren visuellen Objekten entsprechen. Dies sind normalerweise Elemente mit einer komplexen Struktur, die nicht durch ein einzelnes Rechteck beschrieben werden können. Beispiel: Die Schaltfläche „select“ hat drei Renderer: einen für den Anzeigebereich, einen für das Dropdown-Listenfeld und einen für die Schaltfläche. Wenn Text in mehrere Zeilen aufgeteilt wird, weil die Breite für eine Zeile nicht ausreicht, werden die neuen Zeilen als zusätzliche Renderer hinzugefügt.
Ein weiteres Beispiel für mehrere Renderer ist fehlerhafte HTML. Laut CSS-Spezifikation darf ein Inline-Element entweder nur Blockelemente oder nur Inline-Elemente enthalten. Bei gemischten Inhalten werden anonyme Block-Renderer erstellt, um die Inline-Elemente zu umschließen.
Einige Rendering-Objekte entsprechen einem DOM-Knoten, aber nicht an derselben Stelle in der Baumstruktur. Gleitkommazahlen und absolut positionierte Elemente stehen außerhalb des Ablaufs, werden in einem anderen Teil der Struktur platziert und dem tatsächlichen Frame zugeordnet. Ein Platzhalter-Frame ist an der Stelle, an der sie hätten sein sollen.
<ph type="x-smartling-placeholder">![Die Rendering-Struktur und die entsprechende DOM-Struktur.](https://web.dev/static/articles/howbrowserswork/image/the-render-tree-the-corr-f699894ef4c75.png?authuser=6&hl=de)
Ablauf der Baumstruktur
In Firefox wird die Präsentation als Listener für DOM-Updates registriert.
Die Präsentation delegiert die Frame-Erstellung an FrameConstructor
und der Konstruktor löst den Stil auf (siehe Stilberechnung) und erstellt einen Frame.
In WebKit wird der Vorgang zum Auflösen des Stils und zum Erstellen eines Renderers als "Anhängen" bezeichnet. Jeder DOM-Knoten hat einen „attach“ . Das Anhängen erfolgt synchron. Beim Einfügen von Knoten in den DOM-Baum wird der neue Knoten als "Anhängen" bezeichnet. .
Bei der Verarbeitung der HTML- und body-Tags wird der Stamm der Rendering-Struktur konstruiert.
Das Stamm-Rendering-Objekt entspricht dem beinhaltenden Block in der CSS-Spezifikation: dem obersten Block, der alle anderen Blöcke enthält. Seine Abmessungen entsprechen dem Darstellungsbereich, d. h. den Abmessungen des Anzeigebereichs des Browserfensters.
In Firefox wird dies ViewPortFrame
und bei WebKit RenderView
genannt.
Dies ist das Rendering-Objekt, auf das das Dokument verweist.
Der Rest der Baumstruktur wird durch Einfügen von DOM-Knoten erstellt.
Weitere Informationen finden Sie in der CSS2-Spezifikation zum Verarbeitungsmodell.
Stilberechnung
Zum Erstellen der Rendering-Struktur müssen die visuellen Eigenschaften jedes Rendering-Objekts berechnet werden. Dazu werden die Stileigenschaften jedes Elements berechnet.
Der Stil umfasst Stylesheets verschiedener Ursprünge, Inline-Stilelemente und visuelle Eigenschaften im HTML-Code (z. B. die "bgcolor"-Eigenschaft). Der spätere Stil wird in übereinstimmende CSS-Stileigenschaften übersetzt.
Die Style Sheets sind die Standard-Style Sheets des Browsers, die vom Seitenautor bereitgestellten Style Sheets und User Style Sheets. Dabei handelt es sich um Style Sheets, die vom Nutzer des Browsers bereitgestellt werden. In Browsern können Sie Ihre bevorzugten Stile festlegen. In Firefox erfolgt dies beispielsweise, indem ein Style Sheet in das "Firefox-Profil" eingefügt wird. Ordner).
Die Stilberechnung bringt einige Schwierigkeiten mit sich:
- Stildaten sind ein sehr umfangreiches Konstrukt und enthalten zahlreiche Stileigenschaften. Dies kann zu Speicherproblemen führen.
Die Suche nach den passenden Regeln für jedes Element kann zu Leistungsproblemen führen, wenn es nicht optimiert ist. Das Durchlaufen der gesamten Regelliste für jedes Element, um Übereinstimmungen zu finden, ist eine mühsame Aufgabe. Selektoren können eine komplexe Struktur haben, die dazu führen kann, dass der Zuordnungsprozess auf einem vielversprechenden Pfad beginnt, der sich jedoch als zwecklos erwiesen hat und ein anderer Pfad ausprobiert werden muss.
Beispiel: diese Kombinationsauswahl:
div div div div{ ... }
Das bedeutet, dass die Regeln für einen
<div>
gelten, der Nachfolger von drei div-Elementen ist. Angenommen, Sie möchten prüfen, ob die Regel für ein bestimmtes<div>
-Element gilt. Zur Überprüfung wählen Sie einen bestimmten Pfad in der Baumstruktur aus. Möglicherweise müssen Sie den Knotenbaum nach oben durchlaufen, nur um festzustellen, dass es nur zwei div-Elemente gibt und die Regel nicht zutrifft. Anschließend müssen Sie andere Pfade in der Baumstruktur ausprobieren.Die Anwendung der Regeln erfordert ziemlich komplexe Kaskadenregeln, die die Hierarchie der Regeln definieren.
Sehen wir uns an, wie die Browser mit diesen Problemen umgehen:
Stildaten teilen
WebKit-Knoten verweisen auf Stilobjekte (RenderStyle). Diese Objekte können unter bestimmten Bedingungen von Knoten gemeinsam genutzt werden. Die Knoten sind gleichgeordnete oder verwandte Knoten und:
- Die Elemente müssen sich im selben Mausstatus befinden (d. h., eines der Elemente darf sich nicht in :hover befinden, das andere nicht).
- Keines der Elemente sollte eine ID haben.
- Die Tag-Namen sollten übereinstimmen
- Die Klassenattribute sollten übereinstimmen
- Die zugeordneten Attribute müssen identisch sein
- Die Linkstatus müssen übereinstimmen.
- Die Fokuszustände müssen übereinstimmen.
- Keines der Elemente sollte von Attributselektoren beeinflusst werden, wenn davon eine Selektorübereinstimmung definiert wird, bei der ein Attributselektor an einer beliebigen Position innerhalb des Selektors verwendet wird.
- Die Elemente dürfen keine Inline-Style-Attribute haben.
- Es dürfen keine gleichgeordneten Selektoren in Verwendung sein. WebCore löst einfach einen globalen Wechsel aus, wenn ein gleichgeordneter Selektor gefunden wird, und deaktiviert die Stilfreigabe für das gesamte Dokument, sofern diese vorhanden sind. Dazu gehören der +-Selektor und Selektoren wie :first-child und :last-child.
Firefox-Regelbaum
Firefox verfügt über zwei zusätzliche Baumstrukturen für eine einfachere Stilberechnung: den Regelbaum und den Stilkontextbaum. WebKit verfügt ebenfalls über Stilobjekte, diese sind jedoch nicht in einem Baum wie dem Stilkontextbaum gespeichert. Nur der DOM-Knoten verweist auf den relevanten Stil.
<ph type="x-smartling-placeholder">![Firefox-Stilkontextbaum](https://web.dev/static/articles/howbrowserswork/image/firefox-style-context-tre-f578b75b74df7.png?authuser=6&hl=de)
Die Stilkontexte enthalten Endwerte. Die Werte werden berechnet, indem alle übereinstimmenden Regeln in der richtigen Reihenfolge angewendet und Änderungen vorgenommen werden, durch die sie von logischen in konkrete Werte umgewandelt werden. Wenn der logische Wert beispielsweise ein Prozentsatz des Bildschirms ist, wird er berechnet und in absolute Einheiten umgewandelt. Die Idee eines Regelbaums ist wirklich clever. Es ermöglicht die gemeinsame Nutzung dieser Werte zwischen Knoten, um eine erneute Berechnung zu vermeiden. Das spart auch Speicherplatz.
Alle zutreffenden Regeln werden in einer Baumstruktur gespeichert. Die unteren Knoten in einem Pfad haben eine höhere Priorität. Die Baumstruktur enthält alle Pfade für gefundene Regelübereinstimmungen. Das Speichern der Regeln erfolgt verzögert. Die Baumstruktur wird nicht zu Beginn für jeden Knoten berechnet, aber immer dann, wenn ein Knotenstil berechnet werden muss, werden die berechneten Pfade dem Baum hinzugefügt.
Die Idee ist, die Baumpfade als Wörter in einem Lexikon zu betrachten. Nehmen wir an, wir haben diesen Regelbaum bereits berechnet:
<ph type="x-smartling-placeholder">![Berechneter Regelbaum](https://web.dev/static/articles/howbrowserswork/image/computed-rule-tree-f874f412bbaf.png?authuser=6&hl=de)
Angenommen, wir müssen Regeln für ein anderes Element im Inhaltsbaum abgleichen und finden heraus, dass die übereinstimmenden Regeln (in der richtigen Reihenfolge) B-E-I sind. Wir haben diesen Pfad bereits im Baum, da wir den Pfad A-B-E-I-L bereits berechnet haben. Wir haben jetzt weniger Arbeit vor uns.
Sehen wir uns an, wie uns der Baum Arbeit spart.
Unterteilung in Strukturen
Die Stilkontexte sind in Strukturen unterteilt. Diese Strukturen enthalten Stilinformationen für eine bestimmte Kategorie wie Rahmen oder Farbe. Alle Eigenschaften in einer Struktur sind entweder geerbt oder nicht geerbt. Geerbte Eigenschaften sind Eigenschaften, die vom übergeordneten Element übernommen werden, sofern sie nicht vom Element selbst definiert werden. Nicht übernommene Eigenschaften, die als „reset“-Eigenschaften bezeichnet werden, verwenden Standardwerte, wenn sie nicht definiert sind.
Der Baum hilft uns, indem er ganze Strukturen (mit den berechneten Endwerten) im Baum zwischenspeichert. Die Idee dahinter ist, dass, wenn der untere Knoten keine Definition für eine Struktur bereitgestellt hat, eine zwischengespeicherte Struktur in einem oberen Knoten verwendet werden kann.
Stilkontexte mithilfe des Regelbaums berechnen
Bei der Berechnung des Stilkontexts für ein bestimmtes Element berechnen wir zunächst einen Pfad im Regelbaum oder verwenden einen vorhandenen Pfad. Anschließend beginnen wir, die Regeln im Pfad anzuwenden, um die Strukturen in unserem neuen Stilkontext zu füllen. Wir beginnen mit dem untersten Knoten des Pfads, demjenigen mit der höchsten Priorität (normalerweise der spezifischste Selektor), und durchlaufen den Baum nach oben, bis unsere Struktur vollständig ist. Wenn es keine Spezifikation für die Struktur in diesem Regelknoten gibt, können wir eine erhebliche Optimierung vornehmen. Wir wandern den Baum nach oben, bis wir einen Knoten finden, der ihn vollständig angibt, und verweisen darauf. Das ist die beste Optimierung. Die gesamte Struktur wird gemeinsam genutzt. Dies spart die Berechnung von Endwerten und den Arbeitsspeicher.
Wenn wir Teildefinitionen finden, wandern wir den Baum nach oben, bis die Struktur gefüllt ist.
Wenn wir keine Definitionen für unsere Struktur finden, falls die Struktur eine „übernommene“ ist verweisen wir auf die Struktur des übergeordneten Elements im Kontextbaum. In diesem Fall konnten wir auch Strukturen teilen. Wenn es sich um eine Zurücksetzen-Struktur handelt, werden Standardwerte verwendet.
Wenn der spezifischste Knoten Werte hinzufügt, müssen wir einige zusätzliche Berechnungen durchführen, um ihn in tatsächliche Werte umzuwandeln. Anschließend wird das Ergebnis im Baumknoten zwischengespeichert, damit es von untergeordneten Elementen verwendet werden kann.
Falls ein Element ein gleichgeordnetes oder verwandtes Element hat, das auf denselben Baumknoten verweist, kann der gesamte Stilkontext zwischen ihnen verwendet werden.
Beispiel: Angenommen, wir haben diese 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>
Und die folgenden Regeln:
div {margin: 5px; color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}
Nehmen wir zur Vereinfachung an, wir müssen nur zwei Strukturen ausfüllen: die Farbstruktur und die Randstruktur. Die Farbstruktur enthält nur ein Mitglied: die Farbe Die Randstruktur enthält die vier Seiten.
Der resultierende Regelbaum sieht wie folgt aus (die Knoten sind mit dem Knotennamen gekennzeichnet, also der Nummer der Regel, auf die sie verweisen):
<ph type="x-smartling-placeholder">![Regelbaum](https://web.dev/static/articles/howbrowserswork/image/the-rule-tree-23f05b0dac33f.png?authuser=6&hl=de)
Der Kontextbaum sieht so aus (Knotenname: Regelknoten, auf den sie verweisen):
<ph type="x-smartling-placeholder">![Kontextbaum](https://web.dev/static/articles/howbrowserswork/image/the-context-tree-771124b7cb80d.png?authuser=6&hl=de)
Angenommen, wir parsen den HTML-Code und gelangen zum zweiten <div>
-Tag. Wir müssen einen Stilkontext für diesen Knoten erstellen und seine Stilstrukturen ausfüllen.
Wir ordnen die Regeln zu und finden heraus, dass für <div>
die Regeln 1, 2 und 6 gelten.
Das bedeutet, dass im Baum bereits ein Pfad vorhanden ist, den unser Element verwenden kann, und wir nur einen weiteren Knoten für Regel 6 hinzufügen müssen (Knoten F im Regelbaum).
Wir erstellen einen Stilkontext und fügen ihn in den Kontextbaum ein. Der neue Stilkontext verweist auf Knoten F im Regelbaum.
Nun müssen wir die Stilstrukturen füllen. Wir beginnen mit der Randstruktur. Da der letzte Regelknoten (F) nicht zur Randstruktur beiträgt, können wir den Baum nach oben wandern, bis wir eine zwischengespeicherte Struktur aus einer vorherigen Knoteneinfügung finden und diese verwenden. Wir finden sie auf Knoten B, dem obersten Knoten, der Randregeln angibt.
Wir haben eine Definition für die Farbstruktur, sodass wir keine im Cache gespeicherte Struktur verwenden können. Da die Farbe nur ein Attribut hat, müssen wir den Baum nicht durchlaufen, um weitere Attribute zu füllen. Wir berechnen den Endwert (String in RGB konvertieren usw.) und speichern die berechnete Struktur auf diesem Knoten im Cache.
Die Arbeit am zweiten <span>
-Element ist noch einfacher. Wir ordnen die Regeln zu und kommen zu dem Schluss, dass sie wie das vorherige span-Element auf Regel G verweist.
Da es gleichgeordnete Elemente gibt, die auf denselben Knoten verweisen, können wir den gesamten Stilkontext teilen und einfach auf den Kontext des vorherigen Spans verweisen.
Bei Strukturen, die Regeln enthalten, die vom übergeordneten Element übernommen werden, erfolgt das Caching im Kontextbaum (die Farbeigenschaft wird übernommen, aber Firefox behandelt sie als zurückgesetzt und speichert sie im Regelbaum).
Wenn wir beispielsweise Regeln für Schriftarten in einem Absatz hinzufügen:
p {font-family: Verdana; font size: 10px; font-weight: bold}
Dann könnte das Absatzelement, das dem div-Element im Kontextbaum untergeordnet ist, dieselbe Schriftartstruktur wie sein übergeordnetes Element verwendet haben. Dies ist der Fall, wenn keine Schriftartregeln für den Absatz angegeben wurden.
In WebKit, das über keinen Regelbaum verfügt, werden die übereinstimmenden Deklarationen viermal durchlaufen. Zuerst werden unwichtige Eigenschaften mit hoher Priorität angewendet (Eigenschaften, die zuerst angewendet werden sollten, weil andere davon abhängen, wie Anzeige), dann wichtige Regeln mit hoher Priorität, dann nicht wichtige Regeln mit normaler Priorität und schließlich wichtige Regeln mit normaler Priorität. Eigenschaften, die mehrfach auftreten, werden also gemäß der richtigen Kaskadenreihenfolge aufgelöst. Der Letzte gewinnt.
Zusammenfassend lässt sich also sagen: Die gemeinsame Nutzung der Stilobjekte (alles oder einige der darin enthaltenen Strukturen) löst die Probleme 1 und 3. Der Firefox-Regelbaum hilft auch dabei, die Eigenschaften in der richtigen Reihenfolge anzuwenden.
Bearbeiten der Regeln für einen einfachen Abgleich
Für Stilregeln gibt es mehrere Quellen:
- CSS-Regeln, entweder in externen Stylesheets oder in Stilelementen.
css p {color: blue}
- Inline-Stilattribute wie
html <p style="color: blue" />
- Visuelle HTML-Attribute (die relevanten Stilregeln zugeordnet sind)
html <p bgcolor="blue" />
Die letzten beiden können dem Element leicht zugeordnet werden, da er der Inhaber der Stilattribute ist und HTML-Attribute mithilfe des Elements als Schlüssel zugeordnet werden können.
Wie bereits in Problem 2 erwähnt, kann der Abgleich von CSS-Regeln schwieriger sein. Um diese Schwierigkeit zu lösen, werden die Regeln bearbeitet, um den Zugriff zu erleichtern.
Nach dem Parsen des Stylesheets werden die Regeln entsprechend dem Selektor einer von mehreren Hashmaps hinzugefügt. Es gibt Zuordnungen nach ID, Klassenname und Tag-Name sowie eine allgemeine Map für alles, was nicht in diese Kategorien passt. Handelt es sich bei dem Selektor um eine ID, wird die Regel zur ID-Zuordnung hinzugefügt, wenn es sich um eine Klasse handelt, wird sie der Klassen-Map hinzugefügt usw.
Diese Bearbeitung erleichtert die Zuordnung von Regeln erheblich. Es muss nicht jede Deklaration überprüft werden: Wir können die relevanten Regeln für ein Element aus den Maps extrahieren. Durch diese Optimierung werden mehr als 95% der Regeln ausgeschlossen, sodass sie während des Zuordnungsprozesses nicht einmal berücksichtigt werden müssen(4.1).
Sehen wir uns zum Beispiel die folgenden Stilregeln an:
p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}
Die erste Regel wird in die Klassen-Map eingefügt. Das zweite Element wird in die ID-Map und das dritte in die Tag-Map eingefügt.
Bei dem folgenden HTML-Fragment:
<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>
Zuerst versuchen wir, Regeln für das p-Element zu finden. Die Klassen-Map enthält einen „Fehler“, Schlüssel, unter dem die Regel für "p.error" gefunden wird. Das div-Element verfügt über relevante Regeln in der ID-Zuordnung (der Schlüssel ist die ID) und der Tag-Zuordnung. Wir müssen also nur noch herausfinden, welche der durch die Schlüssel extrahierten Regeln wirklich übereinstimmen.
Angenommen, die Regel für das div-Element lautet:
table div {margin: 5px}
Er wird trotzdem aus der Tag-Zuordnung extrahiert, da der Schlüssel der Selektor ganz rechts ist, aber er würde nicht mit unserem div-Element übereinstimmen, das keinen Tabellen-Ancestor hat.
Sowohl WebKit als auch Firefox nehmen diese Manipulation vor.
Kaskadenreihenfolge von Stylesheets
Das Stilobjekt verfügt über Eigenschaften, die jedem visuellen Attribut entsprechen (allen CSS-Attributen, aber allgemeiner). Wird die Eigenschaft durch keine der übereinstimmenden Regeln definiert, können einige Eigenschaften vom Stilobjekt des übergeordneten Elements übernommen werden. Andere Eigenschaften haben Standardwerte.
Das Problem beginnt, wenn es mehr als eine Definition gibt. Hier kommt die Kaskadenreihenfolge, um das Problem zu lösen.
Eine Deklaration für eine Stileigenschaft kann in mehreren Stylesheets vorkommen sowie mehrmals innerhalb eines Stylesheets. Daher ist die Reihenfolge, in der die Regeln angewendet werden, sehr wichtig. Dies wird als „Kaskade“ bezeichnet. Reihenfolge. Laut CSS2-Spezifikation sieht die Kaskadenreihenfolge so aus (von niedrig nach hoch):
- Browserdeklarationen
- Normale Nutzerdeklarationen
- Normale Autorendeklarationen
- Wichtige Autorendeklarationen
- Wichtige Nutzerdeklarationen
Die Browserdeklarationen sind am wenigsten wichtig und der Nutzer überschreibt den Autor nur, wenn die Deklaration als wichtig markiert wurde. Deklarationen mit derselben Reihenfolge werden nach Spezifität und dann nach ihrer angegebenen Reihenfolge sortiert. Die visuellen HTML-Attribute werden in entsprechende CSS-Deklarationen übersetzt . Sie werden als Autorenregeln mit niedriger Priorität behandelt.
Spezifität
Die Selektorspezifität wird in der CSS2-Spezifikation so definiert:
- count 1, wenn die entsprechende Deklaration ein „style“ ist Attribut statt einer Regel mit einem Selektor, andernfalls 0 (= a)
- Anzahl der ID-Attribute im Selector (= b)
- Anzahl der anderen Attribute und Pseudoklassen im Selektor (= c)
- Anzahl der Elementnamen und Pseudoelemente im Selektor (= d)
Die Verkettung der vier Zahlen a-b-c-d (in einem Zahlensystem mit einer großen Basis) ergibt die Spezifität.
Die Zahlenbasis, die Sie verwenden müssen, wird durch die höchste Anzahl in einer der Kategorien definiert.
Wenn z. B. a=14 ist, können Sie eine Hexadezimalbasis verwenden. Im unwahrscheinlichen Fall, dass a=17 ist, benötigen Sie eine 17-stellige Zahlenbasis. Die spätere Situation kann bei einem Selektor wie diesem auftreten: html body div div p... (17 Tags in Ihrer Auswahl... eher unwahrscheinlich).
Beispiele:
* {} /* 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 */
Regeln sortieren
Nachdem die Regeln abgeglichen wurden, werden sie entsprechend den Kaskadenregeln sortiert.
WebKit verwendet die Blasensortierung für kleine Listen und die Merge-Sortierung für große.
WebKit implementiert die Sortierung, indem der Operator >
für die Regeln überschrieben wird:
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;
}
Schrittweiser Prozess
WebKit verwendet ein Flag, das anzeigt, ob alle Top-Level-Stylesheets (einschließlich @imports) geladen wurden. Wenn der Stil beim Anhängen nicht vollständig geladen wird, werden Platzhalter verwendet und im Dokument markiert. Sobald die Style Sheets geladen wurden, werden sie neu berechnet.
Layout
Wenn der Renderer erstellt und zur Baumstruktur hinzugefügt wird, hat er keine Position und Größe. Die Berechnung dieser Werte wird als Layout oder Reflow bezeichnet.
HTML verwendet ein flussbasiertes Layoutmodell. Dies bedeutet, dass es meistens möglich ist, die Geometrie in einem einzigen Durchlauf zu berechnen. Elemente, die sich später im Ablauf befinden wirken sich in der Regel nicht auf die Geometrie von Elementen aus, die "im Fluss" früher sind, sodass das Layout von links nach rechts und von oben nach unten durch das Dokument gehen kann. Es gibt Ausnahmen: Beispielsweise kann für HTML-Tabellen mehr als ein Durchlauf erforderlich sein.
Das Koordinatensystem ist relativ zum Stamm-Frame. Die Koordinaten oben und links werden verwendet.
Das Layout ist ein rekursiver Prozess. Es beginnt im Stamm-Renderer, der dem <html>
-Element des HTML-Dokuments entspricht. Das Layout durchläuft rekursiv einen Teil oder die gesamte Frame-Hierarchie und berechnet geometrische Informationen für jeden Renderer, der sie erfordert.
Die Position des Stamm-Renderers ist 0,0 und seine Abmessungen entsprechen dem Darstellungsbereich, also dem sichtbaren Teil des Browserfensters.
Alle Renderer haben ein „Layout“ oder „dynamischer Umbruch“ ruft jeder Renderer die Layout-Methode seiner untergeordneten Elemente auf, die ein Layout benötigen.
Dirty Bit-System
Um nicht für jede kleine Änderung ein komplettes Layout vornehmen zu müssen, System. Ein Renderer, der geändert oder hinzugefügt wird, markiert sich selbst und seine untergeordneten Elemente als „dirty“ (schmutzig: Layout erforderlich).
Es gibt zwei Flags: „dirty“ und „children are dirty“ Das bedeutet, dass zwar der Renderer selbst in Ordnung sein kann, aber mindestens ein untergeordnetes Element hat, das ein Layout benötigt.
Globales und inkrementelles Layout
Das Layout kann für die gesamte Rendering-Struktur ausgelöst werden. Dies ist "global". Layout. Das kann folgende Gründe haben:
- Eine globale Stiländerung, die alle Renderer betrifft, z. B. eine Änderung der Schriftgröße.
- Wenn die Bildschirmgröße angepasst wird
Das Layout kann inkrementell sein. Nur die dirty Renderer werden dargestellt. Dies kann zu Schäden führen, die zusätzliche Layouts erfordern.
Das inkrementelle Layout wird (asynchron) ausgelöst, wenn Renderer als "dirty" markiert sind. Dies ist beispielsweise der Fall, wenn neue Renderer an die Rendering-Struktur angehängt werden, nachdem zusätzliche Inhalte aus dem Netzwerk bezogen und der DOM-Struktur hinzugefügt wurden.
<ph type="x-smartling-placeholder">![Inkrementelles Layout.](https://web.dev/static/articles/howbrowserswork/image/incremental-layout-da3da0a148135.png?authuser=6&hl=de)
Asynchrones und synchrones Layout
Das inkrementelle Layout erfolgt asynchron. „Reflow-Befehle“ werden in Firefox in die Warteschlange gestellt und ein Planer löst die Batchausführung dieser Befehle aus. WebKit verfügt auch über einen Timer, der ein inkrementelles Layout ausführt – die Baumstruktur wird durchlaufen und "dirty" (schmutzig) haben die Renderer ohne Layout.
Skripts fordern Stilinformationen wie "offsetHeight" an kann inkrementelles Layout synchron auslösen.
Das globale Layout wird normalerweise synchron ausgelöst.
Manchmal wird das Layout als Callback nach einem anfänglichen Layout ausgelöst, weil sich einige Attribute, z. B. die Scrollposition, geändert haben.
Optimierungen
Wenn ein Layout durch eine Größenänderung ausgelöst wird oder eine Änderung der Renderer-Position(und nicht der Größe) hat, werden die Rendering-Größen aus einem Cache entnommen und nicht neu berechnet...
In einigen Fällen wird nur eine Unterstruktur geändert und das Layout beginnt nicht vom Stamm aus. Dies kann passieren, wenn die Änderung lokal erfolgt und sich nicht auf ihre Umgebung auswirkt, z. B. wenn Text in Textfelder eingefügt wird. Andernfalls würde jeder Tastenanschlag ein Layout auslösen, das vom Stamm aus gestartet wird.
Der Layoutprozess
Das Layout weist normalerweise das folgende Muster auf:
- Der übergeordnete Renderer bestimmt seine eigene Breite.
- Das übergeordnete Element hat Vorrang vor untergeordneten Elementen und:
<ph type="x-smartling-placeholder">
- </ph>
- Platzieren Sie den untergeordneten Renderer (legt x und y fest).
- Ruft ein untergeordnetes Layout auf, falls erforderlich. Entweder sie sind "dirty", wir befinden uns in einem globalen Layout oder aus einem anderen Grund, wodurch die Höhe des untergeordneten Elements berechnet wird.
- Das übergeordnete Element verwendet die Gesamthöhe der untergeordneten Elemente sowie die Höhe der Ränder und des Innenrands, um die eigene Höhe festzulegen. Diese wird vom übergeordneten Renderer verwendet.
- Legt den „Dirty Bit“-Wert auf „false“ fest.
Firefox verwendet einen "Status" Objekt(nsHTMLReflowState) als Parameter für das Layout (auch "Reflow" genannt). Der Status umfasst unter anderem die Breite der übergeordneten Elemente.
Das Firefox-Layout ist ein "Messwerte" Objekt(nsHTMLReflowMetrics). Sie enthält die vom Renderer berechnete Höhe.
Breitenberechnung
Die Breite des Renderers wird anhand der Breite des Containerblocks berechnet, also anhand des Stils „width“ des Renderers. die Ränder und Rahmenlinien.
Zum Beispiel die Breite des folgenden div-Elements:
<div style="width: 30%"/>
würde von WebKit wie folgt berechnet(Klasse der RenderBox-Methode calcWidth):
- Die Containerbreite ist das Maximum aus der „availableWidth“ des Containers und 0. Die "availableWidth" ist in diesem Fall die "contentWidth", die wie folgt berechnet wird:
clientWidth() - paddingLeft() - paddingRight()
„clientWidth“ und „clientHeight“ stellen das Innere eines Objekts dar außer dem Rahmen und der Bildlaufleiste.
Die Breite der Elemente style [Stil] an. Sie wird als absoluter Wert berechnet, indem der Prozentsatz der Containerbreite berechnet wird.
Die horizontalen Rahmen und Abstände werden jetzt hinzugefügt.
Bisher haben wir die „bevorzugte Breite“ berechnet. Jetzt werden die minimale und maximale Breite berechnet.
Wenn die bevorzugte Breite größer als die maximale Breite ist, wird die maximale Breite verwendet. Wenn sie kleiner als die Mindestbreite ist, also die kleinste unveränderliche Einheit, wird die Mindestbreite verwendet.
Die Werte werden im Cache gespeichert, falls ein Layout benötigt wird. Die Breite ändert sich jedoch nicht.
Zeilenumbruch
Wenn ein Renderer mitten in einem Layout entscheidet, dass er unterbrochen werden muss, stoppt der Renderer und teilt dem übergeordneten Layout des Layouts mit, dass er unterbrochen werden muss. Das übergeordnete Element erstellt die zusätzlichen Renderer und ruft für sie Layout auf.
Malerei
In der Painting-Phase wird die Rendering-Struktur durchlaufen und die "paint()"-Methode des Renderers verwendet. wird aufgerufen, um Inhalte auf dem Bildschirm anzuzeigen. Painting verwendet die UI-Infrastrukturkomponente.
Global und inkrementell
Wie das Layout kann auch das Painting global, d. h. die gesamte Struktur dargestellt, oder inkrementell sein. Beim inkrementellen Painting ändern sich einige Renderer auf eine Weise, die sich nicht auf die gesamte Struktur auswirkt. Durch den geänderten Renderer wird sein Rechteck auf dem Bildschirm ungültig. Dies führt dazu, dass das Betriebssystem die Region als „schmutzige Region“ betrachtet. und generieren eine Paint- . Das Betriebssystem geht intelligent vor und fasst mehrere Regionen in einer zusammen. In Chrome ist dies etwas komplizierter, da sich der Renderer in einem anderen Prozess befindet als der Hauptprozess. Chrome simuliert das Betriebssystemverhalten bis zu einem gewissen Grad. Die Präsentation überwacht diese Ereignisse und delegiert die Nachricht an den Rendering-Stamm. Die Baumstruktur wird durchlaufen, bis der relevante Renderer erreicht ist. Er aktualisiert sich selbst (und normalerweise seine untergeordneten Elemente).
Painting-Reihenfolge
CSS2 definiert die Reihenfolge des Painting-Prozesses. Tatsächlich ist dies die Reihenfolge, in der die Elemente in den Stapelkontexten gestapelt sind. Diese Reihenfolge wirkt sich auf das Painting aus, da die Stapel von hinten nach vorne dargestellt werden. Die Stapelreihenfolge eines Block-Renderers ist:
- Hintergrundfarbe
- Hintergrundbild
- border
- Kinder
- Outline
Firefox-Displayliste
Firefox durchläuft die Rendering-Struktur und erstellt eine Anzeigeliste für das dargestellte Rechteck. Sie enthält die für das Rechteck relevanten Renderer in der richtigen Painting-Reihenfolge (Hintergründe der Renderer, dann Rahmen usw.).
Auf diese Weise muss der Baum nur einmal durchlaufen werden, um eine Darstellungsaktualisierung zu erhalten. Es werden also alle Hintergründe, dann alle Bilder, dann alle Rahmen usw. gemalt.
Firefox optimiert den Prozess, indem keine Elemente hinzugefügt werden, die ausgeblendet werden, zum Beispiel Elemente, die vollständig unter anderen undurchsichtigen Elementen erscheinen.
Rechteckiger WebKit-Speicher
Vor der Neumalung speichert WebKit das alte Rechteck als Bitmap. Anschließend wird nur das Delta zwischen den neuen und den alten Rechtecken dargestellt.
Dynamische Änderungen
Die Browser versuchen, als Reaktion auf eine Änderung die minimal möglichen Aktionen auszuführen. Bei Änderungen an der Farbe eines Elements wird daher nur die Darstellung des Elements aktualisiert. Bei Änderungen an der Position des Elements werden das Layout und die Darstellung des Elements, seiner untergeordneten Elemente und möglicherweise gleichgeordneter Elemente aktualisiert. Durch das Hinzufügen eines DOM-Knotens werden das Layout und die Darstellung des Knotens aktualisiert. Größere Änderungen, z. B. das Erhöhen der Schriftgröße des "HTML"-Elements führt zur Entwertung der Caches sowie zu einer Neugestaltung des Layouts und der Darstellung des gesamten Baums.
Rendering-Modul-Threads
Das Rendering-Modul ist ein Single-Thread-Modul. Fast alles, mit Ausnahme von Netzwerkvorgängen, passiert in einem einzigen Thread. In Firefox und Safari ist dies der Hauptthread des Browsers. In Chrome ist es der Hauptthread des Tab-Prozesses.
Netzwerkvorgänge können von mehreren parallelen Threads ausgeführt werden. Die Anzahl der parallelen Verbindungen ist begrenzt (normalerweise 2–6 Verbindungen).
Ereignisschleife
Der Hauptthread des Browsers ist eine Ereignisschleife. Es ist eine Endlosschleife, die den Prozess am Leben erhält. Sie wartet auf Ereignisse (z. B. Layout- und Paint-Ereignisse) und verarbeitet sie. Dies ist der Firefox-Code für die Hauptereignisschleife:
while (!mExiting)
NS_ProcessNextEvent(thread);
Visuelles CSS2-Modell
Canvas
Gemäß der CSS2-Spezifikation Der Begriff Canvas beschreibt den Bereich, in dem die Formatierungsstruktur gerendert wird, also den Bereich, in dem der Browser den Inhalt darstellt.
Der Canvas ist für jede Dimension des Bereichs unendlich, aber Browser wählen eine Anfangsbreite basierend auf den Abmessungen des Darstellungsbereichs aus.
Laut www.w3.org/TR/CSS2/zindex.html Der Canvas ist transparent, wenn er in einem anderen Canvas enthalten ist. Andernfalls erhält er eine vom Browser definierte Farbe.
CSS-Box-Modell
Das CSS-Feld-Modell beschreibt die rechteckigen Felder, die für Elemente in der Dokumentstruktur generiert und nach dem visuellen Formatierungsmodell dargestellt werden.
Jedes Feld hat einen Inhaltsbereich (z. B. Text oder ein Bild) und optional umgebende Abstände, Rahmen und Randbereiche.
<ph type="x-smartling-placeholder">![CSS2-Boxmodell](https://web.dev/static/articles/howbrowserswork/image/css2-box-model-9c2ab852d1fb4.jpg?authuser=6&hl=de)
Jeder Knoten generiert 0...n solche Felder.
Alle Elemente haben ein „display“ die den Typ des Felds bestimmt, das erstellt wird.
Beispiele:
block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.
Die Standardeinstellung ist Inline, aber das Browser-Style-Sheet legt möglicherweise andere Standardeinstellungen fest. Beispiel: Die Standardanzeige für „div“ ist „block“.
Ein Beispiel für ein Standard-Stylesheet finden Sie hier: www.w3.org/TR/CSS2/sample.html.
Positionierungsschema
Es gibt drei Schemas:
- Normal: Das Objekt wird entsprechend seiner Position im Dokument positioniert. Das bedeutet, dass sein Platz in der Rendering-Struktur genau wie sein Platz im DOM-Baum ist und entsprechend seinem Boxtyp und den Abmessungen dargestellt wird.
- Gleitkommazahl: Das Objekt wird zuerst wie im normalen Fluss angeordnet und dann so weit nach links oder rechts wie möglich verschoben.
- Absolut: Das Objekt wird in der Rendering-Struktur an einer anderen Stelle als im DOM-Baum platziert
Das Positionierungsschema wird durch die "position" und die Eigenschaft "float" .
- „Statisch“ und „relative“ lösen einen normalen Fluss aus.
- „absolut“ und „fest“ führen zu absoluter Positionierung
Bei der statischen Positionierung ist keine Position definiert. Stattdessen wird die Standardpositionierung verwendet. Bei den anderen Schemas gibt der Autor die Position an: oben, unten, links, rechts.
Die Anordnung der Box wird durch Folgendes bestimmt:
- Boxtyp
- Boxabmessungen
- Positionierungsschema
- Externe Informationen wie die Bildgröße und die Bildschirmgröße
Boxtypen
Block-Box: bildet einen Block mit einem eigenen Rechteck im Browserfenster.
<ph type="x-smartling-placeholder">![Block-Feld.](https://web.dev/static/articles/howbrowserswork/image/block-box-b5c0bff4a44d2.png?authuser=6&hl=de)
Inline-Box: hat keinen eigenen Block, sondern befindet sich in einem übergeordneten Block.
<ph type="x-smartling-placeholder">![Inline-Boxen.](https://web.dev/static/articles/howbrowserswork/image/inline-boxes-a9ff03002e7e2.png?authuser=6&hl=de)
Blöcke werden vertikal nacheinander formatiert. Inlines sind horizontal formatiert.
<ph type="x-smartling-placeholder">![Block- und Inline-Formatierung](https://web.dev/static/articles/howbrowserswork/image/block-inline-formatting-a8450c63cf457.png?authuser=6&hl=de)
Inline-Boxen werden innerhalb von Linien oder „Zeilenfeldern“ platziert. Die Zeilen sind mindestens so hoch wie die höchste Box, können aber auch höher sein, wenn die Boxen an der Referenzlinie ausgerichtet sind - Das bedeutet, dass der untere Teil eines Elements an einem anderen Punkt als dem unteren Teil ausgerichtet ist. Wenn die Containerbreite nicht ausreicht, werden die Inlines auf mehrere Zeilen aufgeteilt. Das passiert normalerweise in einem Absatz.
<ph type="x-smartling-placeholder">![Linien.](https://web.dev/static/articles/howbrowserswork/image/lines-68c55a378a7ff.png?authuser=6&hl=de)
Positionierung
Verwandter
Relative Positionierung - positioniert wie gewohnt und dann um das erforderliche Delta verschoben.
<ph type="x-smartling-placeholder">![Relative Positionierung.](https://web.dev/static/articles/howbrowserswork/image/relative-positioning-fceed0670b8c5.png?authuser=6&hl=de)
Float
Eine Float-Box wird an die linke oder rechte Seite einer Linie verschoben. Interessant ist, dass die anderen Boxen um ihn herum fließen. Der HTML-Code:
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
Beispiel:
<ph type="x-smartling-placeholder">![Gleitkommawert.](https://web.dev/static/articles/howbrowserswork/image/float-dd9b790210284.png?authuser=6&hl=de)
Absolut und fest
Das Layout wird genau definiert, unabhängig vom normalen Fluss. Das Element ist nicht Teil des normalen Ablaufs. Die Abmessungen beziehen sich auf den Container. Bei festen Elementen ist der Container der Darstellungsbereich.
<ph type="x-smartling-placeholder">![Feste Positionierung.](https://web.dev/static/articles/howbrowserswork/image/fixed-positioning-881933fcb1a2f.png?authuser=6&hl=de)
Ebenendarstellung
Dieser wird von der CSS-Eigenschaft "z-index" angegeben. Sie stellt die dritte Dimension des Felds dar: seine Position entlang der Z-Achse.
Die Boxen werden in Stapel unterteilt (Stapelkontexte genannt). In jedem Stapel werden zuerst die hinteren Elemente und darauf die vorderen Elemente gezeichnet, die näher am Nutzenden sind. Bei einer Überschneidung wird das vorherige Element ausgeblendet.
Die Stapel werden entsprechend der z-index-Eigenschaft geordnet. Felder mit „Z-Index“ bilden einen lokalen Stack. Der Darstellungsbereich hat den äußeren Stapel.
Beispiel:
<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>
Das Ergebnis sieht so aus:
<ph type="x-smartling-placeholder">![Feste Positionierung.](https://web.dev/static/articles/howbrowserswork/image/fixed-positioning-84a46b366dc9a.png?authuser=6&hl=de)
Obwohl das rote div-Element im Markup vor dem grünen steht und im regulären Ablauf davor dargestellt worden wäre, ist die z-index-Eigenschaft höher, sodass es im Stapel des Stammfelds weiter vorn ist.
Ressourcen
Browserarchitektur
- Grosskurth, Alan. Referenzarchitektur für Webbrowser (PDF)
- Gupta, Vineet. Funktionsweise von Browsern – Teil 1 – Architektur
Parsen
- Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (auch „Dragon Book“), Addison-Wesley, 1986
- Rick Jelliffe. Zwei neue Entwürfe für HTML 5
Firefox
- L. David Baron, Rapid HTML and CSS: Layout Engine Internals for Web Developers.
- L. David Baron, Accelerate HTML and CSS: Layout Engine Internals for Web Developers (Google Tech Talk-Video)
- L. David Baron, Layout Engine von Mozilla
- L. David Baron, Dokumentation zu Mozilla Style System
- Chris Waterson, Notizen zu HTML Reflow
- Chris Waterson, Gecko Overview (in englischer Sprache)
- Alexander Larsson, The life of an HTML HTTP request
WebKit
- David Hyatt, CSS implementieren(Teil 1)
- David Hyatt, An Overview of WebCore
- David Hyatt, WebCore Rendering
- David Hyatt, The FOUC Problem
W3C-Spezifikationen
Erstellungsanleitung für Browser
Übersetzungen
Diese Seite wurde ins Japanische übersetzt - zweimal:
- Funktionsweise von Browsern – Hinter den Kulissen moderner Webbrowser (ja) von @kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 von @ikeike443 und @kiyoto01
Sie können die extern gehosteten Übersetzungen von Koreanisch und Türkisch.
Vielen Dank an alle!