Ein Blick hinter die Kulissen eines modernen Webbrowsers (Teil 2)

Mariko Kosaka

Was passiert bei der Navigation?

Dies ist der zweite Teil einer vierteiligen Blog-Reihe, in der wir uns das Innenleben von Chrome näher ansehen. Im vorherigen Post haben wir uns angesehen, wie verschiedene Prozesse und Threads mit verschiedenen Teilen eines Browsers umgehen. In diesem Beitrag gehen wir näher darauf ein, wie jeder Prozess und Thread kommuniziert, um eine Website anzuzeigen.

Sehen wir uns einen einfachen Anwendungsfall des Surfens im Web an: Sie geben eine URL in einen Browser ein, der Browser ruft dann Daten aus dem Internet ab und zeigt eine Seite an. In diesem Beitrag konzentrieren wir uns auf den Teil, in dem ein Nutzer eine Website anfordert und sich der Browser auf das Rendern einer Seite vorbereitet – die sogenannte Navigation.

Es beginnt mit einem Browserprozess,

Browserprozesse
Abbildung 1: Oben auf der Browser-UI, Diagramm des Browserprozesses mit UI-, Netzwerk- und Speicherthread unten

Wie in Teil 1: CPU-, GPU-, Arbeitsspeicher- und Multi-Prozess-Architektur erläutert, wird alles außerhalb eines Tabs vom Browserprozess verarbeitet. Der Browserprozess besteht aus Threads wie dem UI-Thread, der Schaltflächen und Eingabefelder des Browsers zeichnet, den Netzwerkthread, der sich mit dem Netzwerkstack für den Empfang von Daten aus dem Internet befasst, dem Speicherthread, der den Zugriff auf die Dateien steuert, und vieles mehr. Wenn Sie eine URL in die Adressleiste eingeben, wird Ihre Eingabe vom UI-Thread des Browserprozesses verarbeitet.

Einfache Navigation

Schritt 1: Eingabe verarbeiten

Wenn ein Nutzer beginnt, Text in die Adressleiste einzugeben, wird im UI-Thread als Erstes gefragt: „Ist das eine Suchanfrage oder URL?“. In Chrome ist die Adressleiste auch ein Sucheingabefeld. Daher muss der UI-Thread parsen und entscheiden, ob Sie an eine Suchmaschine oder die angeforderte Website weitergeleitet werden.

Nutzereingaben verarbeiten
Abbildung 1: UI-Thread, in dem gefragt wird, ob die Eingabe eine Suchanfrage oder URL ist

Schritt 2: Navigation starten

Wenn ein Nutzer die Eingabetaste drückt, initiiert der UI-Thread einen Netzwerkaufruf zum Abrufen von Websiteinhalten. Das Ladesymbol wird in der Ecke eines Tabs angezeigt und der Netzwerkthread durchläuft entsprechende Protokolle, z. B. die DNS-Suche und die Herstellung einer TLS-Verbindung für die Anfrage.

Navigationsanfang
Abbildung 2: UI-Thread, der mit dem Netzwerkthread kommuniziert, um zu mysite.com zu gelangen

An dieser Stelle empfängt der Netzwerkthread einen Server-Weiterleitungs-Header wie HTTP 301. In diesem Fall kommuniziert der Netzwerkthread mit dem UI-Thread, den der Server um eine Weiterleitung anfordert. Daraufhin wird eine weitere URL-Anfrage initiiert.

Schritt 3: Antwort lesen

HTTP-Antwort
Abbildung 3: Antwortheader, der Content-Type und die Nutzlast enthält, also die tatsächlichen Daten.

Sobald der Antworttext (Nutzlast) eingeht, prüft der Netzwerkthread bei Bedarf die ersten Bytes des Streams. Im Content-Type-Header der Antwort sollte angegeben sein, um welchen Datentyp es sich handelt. Da diese jedoch möglicherweise fehlen oder falsch sind, wird hier das MIME-Typ-Sniffing durchgeführt. Wie im Quellcode kommentiert, handelt es sich um ein „kniffliges Geschäft“. Sie können den Kommentar lesen, um zu erfahren, wie verschiedene Browser Inhaltstypen/Nutzlastpaare behandeln.

Wenn die Antwort eine HTML-Datei ist, besteht der nächste Schritt darin, die Daten an den Renderer-Prozess zu übergeben. Wenn es sich jedoch um eine ZIP-Datei oder eine andere Datei handelt, bedeutet dies, dass es sich um eine Downloadanfrage handelt und die Daten an den Download-Manager übergeben werden müssen.

MIME-Typ-Sniffing
Abbildung 4: Netzwerkthread mit der Frage, ob die Antwortdaten HTML von einer sicheren Website sind

Hier wird auch die SafeBrowsing durchgeführt. Wenn die Domain und die Antwortdaten mit einer bekannten schädlichen Website übereinstimmen, wird vom Netzwerkthread eine Warnung angezeigt. Außerdem wird mithilfe der Prüfung Cross Origin Read Blocking (CORB) sichergestellt, dass sensible websiteübergreifende Daten nicht in den Rendererprozess gelangen.

Schritt 4: Renderer-Prozess finden

Sobald alle Prüfungen abgeschlossen sind und der Netzwerkthread sicher ist, dass der Browser die angeforderte Website aufrufen sollte, teilt der Netzwerkthread dem UI-Thread mit, dass die Daten bereit sind. UI-Thread findet dann einen Renderer-Prozess, um das Rendern der Webseite fortzusetzen.

Renderer-Prozess suchen
Abbildung 5: Netzwerkthread, das den UI-Thread anweist, den Renderer-Prozess zu suchen

Da es mehrere Hundert Millisekunden dauern kann, bis eine Netzwerkanfrage eine Antwort erhält, wird eine Optimierung zur Beschleunigung dieses Prozesses angewendet. Wenn der UI-Thread in Schritt 2 eine URL-Anfrage an den Netzwerkthread sendet, weiß er bereits, zu welcher Website er navigiert. Der UI-Thread versucht, parallel zur Netzwerkanfrage proaktiv einen Rendererprozess zu suchen oder zu starten. Auf diese Weise befindet sich ein Renderer-Prozess bereits in der Standby-Position, wenn der Netzwerkthread Daten empfangen hat, wenn alles wie erwartet funktioniert. Dieser Standby-Prozess wird möglicherweise nicht verwendet, wenn die Navigation auf mehrere Websites weiterleitet. In diesem Fall ist möglicherweise ein anderer Prozess erforderlich.

Schritt 5: Commit-Navigation

Nachdem die Daten und der Renderer-Prozess bereit sind, wird ein IPC vom Browserprozess an den Renderer-Prozess gesendet, um die Navigation zu übergeben. Er gibt auch den Datenstream weiter, sodass der Renderer-Prozess weiterhin HTML-Daten empfangen kann. Sobald der Browserprozess eine Bestätigung empfängt, dass der Commit im Renderer-Prozess ausgeführt wurde, ist die Navigation abgeschlossen und die Ladephase des Dokuments beginnt.

Zu diesem Zeitpunkt wird die Adressleiste aktualisiert und die Sicherheitsanzeige und die Benutzeroberfläche für die Websiteeinstellungen spiegeln die Websiteinformationen der neuen Seite wider. Der Sitzungsverlauf für den Tab wird aktualisiert, sodass Sie mit den Schaltflächen „Zurück“ und „Weiter“ die Seite aufrufen können, die gerade aufgerufen wurde. Der Sitzungsverlauf wird auf dem Laufwerk gespeichert, um die Wiederherstellung von Tabs bzw. Sitzungen beim Schließen eines Tabs oder Fensters zu erleichtern.

Commit für die Navigation durchführen
Abbildung 6: IPC zwischen dem Browser und den Rendererprozessen, die das Rendern der Seite anfordern

Zusätzlicher Schritt: Anfänglicher Ladevorgang abgeschlossen

Sobald für die Navigation ein Commit durchgeführt wurde, lädt der Renderer-Prozess die Ressourcen und rendert die Seite. Im nächsten Beitrag erfahren Sie, was in dieser Phase passiert. Sobald der Renderer-Prozess das Rendering beendet hat, sendet er einen IPC an den Browserprozess zurück. Dies erfolgt, nachdem alle onload-Ereignisse für alle Frames auf der Seite ausgelöst wurden und die Ausführung abgeschlossen ist. An dieser Stelle stoppt der UI-Thread das rotierende Ladesymbol für den Tab.

Ich sage „beendet“, weil clientseitiges JavaScript auch nach diesem Punkt zusätzliche Ressourcen laden und neue Ansichten rendern könnte.

Laden der Seite abgeschlossen
Abbildung 7: IPC vom Renderer an den Browserprozess, um zu benachrichtigen, dass die Seite „geladen“ wurde

Die einfache Navigation war vollständig! Aber was passiert, wenn ein Nutzer wieder eine andere URL in die Adressleiste eingibt? Der Browser durchläuft dieselben Schritte, um zu der anderen Website zu navigieren. Zuvor muss jedoch mit der derzeit gerenderten Website überprüft werden, ob das Ereignis beforeunload für sie relevant ist.

beforeunload kann die Benachrichtigung „Diese Website verlassen?“ anzeigen, wenn du versuchst, die Seite zu verlassen oder den Tab zu schließen. Alle Elemente auf einem Tab, einschließlich Ihres JavaScript-Codes, werden vom Renderer-Prozess verarbeitet. Der Browser-Prozess muss sich also mit dem aktuellen Renderer-Prozess überprüfen, wenn eine neue Navigationsanfrage eingeht.

beforeunload-Event-Handler
Abbildung 8: IPC vom Browserprozess zu einem Rendererprozess, das ihm mitteilt, dass eine andere Website aufgerufen werden soll

Wenn die Navigation über den Rendererprozess initiiert wurde, z. B. wenn ein Nutzer auf einen Link geklickt hat oder das clientseitige JavaScript window.location = "https://newsite.com" ausgeführt hat, prüft der Rendererprozess zuerst beforeunload-Handler. Dann durchläuft er den gleichen Prozess wie die vom Browserprozess initiierte Navigation. Der einzige Unterschied besteht darin, dass die Navigationsanfrage vom Renderer-Prozess an den Browserprozess gestartet wird.

Wenn die neue Navigation zu einer anderen Website als der aktuell gerenderten, wird ein separater Renderingprozess aufgerufen, um die neue Navigation zu verarbeiten, während der aktuelle Renderingprozess beibehalten wird, um Ereignisse wie unload zu verarbeiten. Weitere Informationen finden Sie in der Übersicht über die Status der Seitenlebenszyklus und darüber, wie Sie mit der Page Lifecycle API Ereignisse einbinden.

neue Navigation und Entladen
Abbildung 9: 2 IPCs von einem Browserprozess zu einem neuen Rendererprozess, die die Seite rendern und den alten Rendererprozess entladen lassen

Bei einem Service Worker

Eine aktuelle Änderung an diesem Navigationsprozess ist die Einführung des Service Worker. Mit einem Service Worker können Sie einen Netzwerk-Proxy in Ihren Anwendungscode schreiben. Dadurch haben Webentwickler mehr Kontrolle darüber, was lokal im Cache gespeichert wird und wann neue Daten aus dem Netzwerk abgerufen werden. Wenn der Service Worker so eingestellt ist, dass er die Seite aus dem Cache lädt, müssen die Daten nicht vom Netzwerk angefordert werden.

Beachten Sie, dass es sich beim Service Worker um JavaScript-Code handelt, der in einem Renderer-Prozess ausgeführt wird. Aber wie erkennt ein Browserprozess bei einer eingehenden Navigationsanfrage, dass die Website einen Service Worker hat?

Service Worker-Bereichssuche
Abbildung 10: Netzwerkthread im Browserprozess, der den Service Worker-Bereich sucht

Wenn ein Service Worker registriert wird, bleibt dessen Bereich als Referenz erhalten. Weitere Informationen finden Sie im Artikel Service Worker-Lebenszyklus. Bei einer Navigation prüft der Netzwerkthread die Domain anhand der registrierten Service Worker-Bereiche. Wenn ein Service Worker für diese URL registriert ist, findet der UI-Thread einen Rendererprozess, um den Service Worker-Code auszuführen. Der Service Worker kann Daten aus dem Cache laden, sodass keine Daten mehr aus dem Netzwerk angefordert werden müssen. Eventuell fordert er auch neue Ressourcen aus dem Netzwerk an.

Service Worker Navigation
Abbildung 11: UI-Thread in einem Browserprozess, der einen Rendererprozess zur Verarbeitung von Service Workern startet. Ein Worker-Thread in einem Rendererprozess fordert dann Daten vom Netzwerk an.

Wie Sie sehen, kann dieser Umlauf zwischen dem Browserprozess und dem Rendererprozess zu Verzögerungen führen, falls der Service Worker schließlich beschließt, Daten aus dem Netzwerk anzufordern. Mit Navigation Preload können Sie diesen Prozess beschleunigen, indem Ressourcen parallel zum Service-Worker-Start geladen werden. Diese Anfragen werden mit einem Header gekennzeichnet, sodass die Server entscheiden können, unterschiedliche Inhalte für diese Anfragen zu senden, beispielsweise nur aktualisierte Daten anstelle eines vollständigen Dokuments.

Navigations vorab laden
Abbildung 12: UI-Thread in einem Browserprozess, der einen Renderer-Prozess startet, um den Service Worker zu verarbeiten, während gleichzeitig eine Netzwerkanfrage gestartet wird

Zusammenfassung

In diesem Beitrag haben wir uns angesehen, was während der Navigation passiert und wie Ihr Webanwendungscode wie Antwortheader und clientseitiges JavaScript mit dem Browser interagiert. Wenn Sie wissen, welche Schritte der Browser beim Abrufen von Daten aus dem Netzwerk durchführt, ist es leichter zu verstehen, warum APIs wie das Vorabladen der Navigation entwickelt wurden. Im nächsten Post beschäftigen wir uns damit, wie der Browser HTML/CSS/JavaScript zum Rendern von Seiten auswertet.

Hat Ihnen der Beitrag gefallen? Falls ihr Fragen oder Anregungen für den zukünftigen Beitrag habt, könnt ihr uns gerne unten im Kommentarbereich oder unter @kosamari auf Twitter Beiträge von euch schreiben.

Nächster Schritt: Innere Funktionsweise eines Renderer-Prozesses