Orlok: Rendering-Engine

Beitrag lesen

Hallo Torsten

Ich interessiere mich um den zeitlichen Ablauf bei der Darstellung von sehr langen Seiten. Aktuelle Webseiten nutzen ja dynamisches Nachladen mit Hilfe von Javascript. Wie sieht das aber aus, wenn kein Javascript eingesetzt wird. Rendern die Maschinen von Firefox und Chrome zuerst die komplette Seite, bevor alles dargestellt wird? Oder was wird zuerst dargestellt, Gibt es eine Reihenfolge?

Das ist ein einigermaßen komplexes Thema. :-)

Grundsätzlich gilt, dass Browser Webseiten seit langer Zeit inkrementell rendern. Das bedeutet, dass ein Teil der Seite bereits zur Anzeige gebracht werden kann, während der Rest der Seite noch über das Netzwerk geladen wird. Es wird also nicht darauf gewartet, bis das Dokument vollständig übertragen wurde, bevor mit dessen Verarbeitung begonnen wird. Diese Vorgehensweise hat den Vorteil, dass Inhalte schneller zugänglich gemacht werden und Benutzer früher mit der Seite interagieren können, als wenn erst nach dem vollständigen Laden des Dokuments mit der Verarbeitung begonnen würde.

Ein grobes Schema für den Ablauf beim Rendern eines Dokuments:

 +----------------------------+
 |                            |
 |          Network           |
 |                            |
 +-------------||-------------+
              _||_                  
              \  /
 +-------------\/-------------+
 |                            |
 |        HTML Parsing        |
 |                            |
 +-------------||-------------+
              _||_
              \  /
 +-------------\/-------------+
 |                            |
 |  Render Tree Construction  |
 |                            |
 +-------------||-------------+
              _||_
              \  /
 +-------------\/-------------+
 |                            |
 |           Layout           |
 |                            |
 +-------------||-------------+
              _||_
              \  /
 +-------------\/-------------+
 |                            |
 |          Painting          |
 |                            |
 +----------------------------+

Wird per HTTP Response ein Dokument empfangen, dann wird direkt mit der Verarbeitung begonnen. Dabei reicht die Netzwerk–Komponente des Browsers die eingehenden Daten an den HTML Parser weiter – und zwar sobald genug Bytes empfangen wurden, dass dieser sinnvoll damit arbeiten kann. Das Parsing selbst ist dann auch wieder in mehrere Abschnitte unterteilt. Welche Regeln dabei eingehalten werden müssen, insbesondere auch in dem Fall, dass das Dokument nicht den Anforderungen an ein valides HTML Dokument genügt, kannst du im Abschnitt Parsing HTML Documents der Spezifikation nachlesen.

Bevor mit dem eigentlichen Parsing begonnen werden kann, also der Transformation des Markups in eine entsprechende Objektrepräsentation, schlägt aber erstmal die Stunde des Decoders. Das ist die Komponente, die dafür zuständig ist herauszufinden, wie das Dokument kodiert wurde, um daraus dann wieder Text zu generieren den der Parser lesen kann. Im Idealfall sollte der Browser die Kodierung des Dokuments einem HTTP Header der Response vom Server entnehmen können, aber es gibt natürlich keine Garantie, dass eine solche Angabe auch hinzugefügt wurde. Gibt es auch kein Byte Order Mark am Anfang des Dokuments, dem Informationen entnommen werden können, dann muss der Browser raten. Wenn’s dumm läuft stellt er später fest, dass er falsch geraten hat, dann ist der Parsing–Fortschritt zu diesem Zeitpunkt nichtig und es muss von vorn begonnen werden.

Hat der Browser aufgrund entsprechender Angaben oder auf Basis einer Heuristik eine Entscheidung getroffen, wie das Dokument zu dekodieren ist, dann wird mit dem Lesen der empfangenen Daten begonnen. Wie üblich, ist der Prozess des Parsings dabei konzeptionell in zwei Hauptabschnitte aufgeteilt: Die lexikalische Analyse, bei der Zeichen gelesen und den Wörtern der Sprache zugeordnet werden, wobei Wort im Kontext formaler Sprachen einfach als eine endliche Folge von Symbolen zu verstehen ist, und syntaktische Analyse, bei der gemäß den Syntax–Regeln der Sprache ein Ableitungsbaum erzeugt wird. In modernen Browsern gibt es vor der lexikalischen Analyse noch einen vorgeschalteten Preparsing–Prozess, bei dem das Dokument nach Referenzen auf externe Ressourcen durchsucht wird. Preparsing und lexikalische Analyse können aber auch in einem Schritt erfolgen.

Das Parsing des Dokuments lässt sich schematisch etwa so darstellen:

 +----------------------------+
 |                            |
 |         Preparsing         |
 |                            |
 +-------------||-------------+
              _||_                  
              \  /
 +-------------\/-------------+
 |                            |
 |        Tokenization        |
 |                            |
 +-------------||-------------+
              _||_
              \  /
 +-------------\/-------------+
 |                            |
 |      DOM Construction      |
 |                            |
 +----------------------------+

Während des Preparsings werden die dekodierten Zeichen nach Elementen durchsucht, die weitere Dateien wie beispielsweise Bilder referenzieren und die gegebenenfalls Anweisungen enthalten, mit welcher Priorität diese Ressourcen vom Browser geladen werden sollen. Auch das ist wieder eine Maßnahme, um Benutzern Inhalte möglichst schnell zugänglich zu machen. Würde das Dokument nicht auf diese Weise vorab gescannt, könnte es passieren, dass sich während des regulären Parsings herausstellt, dass weitere Ressourcen vom Server geladen werden müssen, die eigentlich schon früher hätten geladen werden können.

Bei der lexikalischen Analyse transformiert der Browser den eingehenden Zeichenstrom in Token. Das sind Zeichenketten, die im Kontext der Sprache eine besondere Bedeutung haben, zugeordnet zu bestimmten Typen, von denen es in HTML aber nicht so furchtbar viele gibt. Das ist also eine Klassifizierung von erkannten Wörtern. Ein Beispiel für ein Token wäre die Zeichenkette <body> vom Typ 'start tag'. Außer dem jeweiligen Typ können auch weitere Metainformationen zu dem erkannten Wort gespeichert werden. Implementiert ist das Ganze als endlicher Automat mit einer beachtlichen Menge von Zuständen. Nachzulesen im Kapitel Tokenization der Spezifikation.

Aus den Token wird dann die Objektrepräsentation der Seite generiert, das bedeutet, es werden gemäß der Spezifikation des DOM verschiedene Objekte erzeugt, die beispielsweise Elemente, Attribute oder textuelle Inhalte repräsentieren. Diese Objekte werden dann in eine Baumstruktur eingefügt, deren Wurzel das HTML-Element darstellt. Auch dieser Vorgang ist inkrementell. Es wird also nicht erst darauf gewartet, dass der vorliegende Abschnitt des Dokuments in Token übersetzt wurde, sondern sobald ein Token zur Verfügung steht, wird es vom Tokenizer an die Komponente weitergereicht, die für die Konstruktion des DOM–Baums zuständig ist, damit die nötigen Objekte direkt erzeugt werden können. Zu diesem Zeitpunkt kann es dann auch passieren, dass Skripte in den Prozess eingreifen, die weiteres Markup produzieren, das geparst werden muss. In diesem Fall muss der ursprüngliche Vorgang natürlich unterbrochen werden.

Neben der Konstruktion des DOM wird außerdem eine Repräsentation der Styles der Seite erzeugt, das CSSOM. Hier wird im Prinzip erstmal genau so vorgegangen wie beim Lesen des HTML–Dokuments. Das heißt, eingebettete oder geladene Stylesheets werden erstmal in Token zerlegt und dann weiterverarbeitet. Hier wird der Browser alles was er an Stilen findet in eine gemeinsame Datenstruktur werfen, die dann auch Regeln aus dem Browser–Stylesheet und gegebenenfalls Stilangaben des Benutzers enthält. Das wird dann danach sortiert, welche Regeln Vorrang haben, nach Herkunft, Spezifität und Reihenfolge im Dokument. Dabei werden auch Werte berechnet beziehungsweise in andere Einheiten umgewandelt. Für Stylesheets und die darin notierten Regeln werden Objekte erzeugt, auf die dann mittels JavaScript zugegriffen werden kann, ebenso wie beim DOM der Seite.

 +---------------+   +---------------+
 |               |   |               |
 |      DOM      |   |     CSSOM     |
 |               |   |               |
 +-------||------+   +------||-------+
        _||_               _||_
        \  /               \  /
 +-------\/-----------------\/-------+
 |                                   |
 |            Render Tree            |
 |                                   |
 +-----------------------------------+

Aus DOM und CSSOM wird schließlich der Render Tree für die Seite gebaut, der Grundlage für Layout und Painting ist, also grob gesagt die Berechnung der Größe und die Anordnung von Boxen, sowie das anschließende Hinzufügen von Farbe, Schatten und was es sonst noch so an Gestaltungsmöglichkeiten gibt. Dabei werden nur solche Objekte eingefügt, die für die Darstellung der Seite relevant sind. Aus diesem Baum werden dann am Ende mehrere Bitmaps generiert, die auf dem Bildschirm angezeigt werden können, wobei auch berücksichtigt werden muss, ob und wenn ja welche Teile der Seite mehr als einmal gerendert werden müssen, etwa wegen Animationen. Auch der Render Tree wird inkrementell erzeugt.

Wir können also zusammenfassen, dass definitiv nicht die komplette Seite verarbeitet wird, bevor sie dem Benutzer angezeigt wird. Das wäre aus Sicht der Benutzer eine Katastrophe. Die Seite wird vielmehr Stück für Stück aufgebaut. Es ist aber zu beachten, dass es dabei keine strikte Reihenfolge gibt. Es ist nicht vorherzusehen, welche Elemente zuerst auf dem Bildschirm erscheinen. Zwar wird prinzipiell sichtbarer Inhalt relativ am Anfang des Dokuments mit großer Wahrscheinlichkeit vor Inhalten an dessen Ende zur Anzeige gebracht, aber es ist nicht gesagt, dass ein Element, das der direkter Nachfolger eines anderen Elements ist, auch in jedem Fall nach diesem gerendert wird. Jede Rendering–Engine hat ihre eigenen Strategien, wie mit unvollständigen Informationen umzugehen ist.

Viele Grüße,

Orlok