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

Mariko Kosaka

Was passiert bei der Navigation?

Dies ist Teil 2 einer vierteiligen Blogreihe, in der wir uns die Funktionsweise von Chrome genauer ansehen. Im vorherigen Beitrag haben wir uns angesehen, wie verschiedene Prozesse und Threads verschiedene Teile eines Browsers verarbeiten. In diesem Beitrag gehen wir genauer darauf ein, wie die einzelnen Prozesse und Threads kommunizieren, um eine Website anzuzeigen.

Sehen wir uns einen einfachen Anwendungsfall für das Surfen 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 der Browser sich darauf vorbereitet, eine Seite zu rendern. Dieser Vorgang wird auch als Navigation bezeichnet.

Es beginnt mit einem Browserprozess

Browserprozesse
Abbildung 1: Browser-Benutzeroberfläche oben, Diagramm des Browserprozesses mit UI-, Netzwerk- und Speicher-Thread unten

Wie wir in Teil 1: CPU, GPU, Arbeitsspeicher und mehrstufige Architektur besprochen haben, wird alles außerhalb eines Tabs vom Browserprozess verarbeitet. Der Browserprozess umfasst Threads wie den UI-Thread, der Schaltflächen und Eingabefelder des Browsers zeichnet, den Netzwerk-Thread, der sich um den Netzwerkstack zum Empfangen von Daten aus dem Internet kümmert, und den Speicher-Thread, der den Zugriff auf die Dateien steuert. Wenn Sie eine URL in die Adressleiste eingeben, wird Ihre Eingabe vom UI-Thread des Browserprozesses verarbeitet.

Eine einfache Navigation

Schritt 1: Eingabe verarbeiten

Wenn ein Nutzer etwas in die Adressleiste eingibt, wird zuerst im UI-Thread gefragt: „Ist das eine Suchanfrage oder eine URL?“ In Chrome ist die Adressleiste auch ein Sucheingabefeld. Daher muss der UI-Thread die Eingabe analysieren und entscheiden, ob Sie zu einer Suchmaschine oder zur von Ihnen angeforderten Website weitergeleitet werden.

Nutzereingabe verarbeiten
Abbildung 1: UI-Thread, in dem gefragt wird, ob es sich bei der Eingabe um eine Suchanfrage oder eine URL handelt

Schritt 2: Navigation starten

Wenn ein Nutzer die Eingabetaste drückt, initiiert der UI-Thread einen Netzwerkaufruf, um Websiteinhalte abzurufen. In der Ecke eines Tabs wird ein Ladebalken angezeigt und der Netzwerk-Thread führt entsprechende Protokolle durch, z. B. DNS-Lookup und TLS-Verbindung für die Anfrage.

Navigationsstart
Abbildung 2: Der UI-Thread kommuniziert mit dem Netzwerk-Thread, um zu mysite.com zu gelangen

An diesem Punkt kann der Netzwerk-Thread einen Server-Weiterleitungsheader wie HTTP 301 erhalten. In diesem Fall teilt der Netzwerk-Thread dem UI-Thread mit, dass der Server eine Weiterleitung anfordert. Dann wird eine weitere URL-Anfrage gestartet.

Schritt 3: Antwort lesen

HTTP-Antwort
Abbildung 3: Antwortheader mit Content-Type und Nutzlast, also den tatsächlichen Daten

Sobald der Antworttext (Nutzlast) eingeht, prüft der Netzwerk-Thread bei Bedarf die ersten Bytes des Streams. Der Content-Type-Header der Antwort sollte angeben, um welche Art von Daten es sich handelt. Da er jedoch möglicherweise fehlt oder falsch ist, wird hier MIME-Typ-Sniffing durchgeführt. Dies ist eine „schwierige Angelegenheit“, wie im Quellcode kommentiert. Im Kommentar erfahren Sie, wie verschiedene Browser Inhaltstyp-/Nutzlastpaare behandeln.

Wenn die Antwort eine HTML-Datei ist, werden die Daten im nächsten Schritt an den Rendering-Prozess übergeben. Bei einer ZIP-Datei oder einer anderen Datei handelt es sich jedoch um einen Downloadanfrage, sodass die Daten an den Downloadmanager übergeben werden müssen.

MIME-Typ-Sniffing
Abbildung 4: Netzwerk-Thread, in dem gefragt wird, ob Antwortdaten HTML von einer sicheren Website sind

Hier erfolgt auch die SafeBrowsing-Prüfung. Wenn die Domain und die Antwortdaten mit einer bekannten schädlichen Website übereinstimmen, wird über den Netzwerk-Thread eine Warnseite angezeigt. Außerdem wird Cross Origin Read Blocking (CORB) geprüft, um sicherzustellen, dass sensible websiteübergreifende Daten nicht an den Rendering-Prozess gelangen.

Schritt 4: Rendererprozess finden

Sobald alle Prüfungen abgeschlossen sind und der Netzwerk-Thread sicher ist, dass der Browser zur angeforderten Website wechseln soll, teilt der Netzwerk-Thread dem UI-Thread mit, dass die Daten bereit sind. Der UI-Thread sucht dann einen Renderer-Prozess, um das Rendering der Webseite fortzusetzen.

Renderer-Prozess finden
Abbildung 5: Netzwerk-Thread, der den UI-Thread auffordert, den Renderer-Prozess zu finden

Da es mehrere hundert Millisekunden dauern kann, bis eine Antwort auf die Netzwerkanfrage zurückgegeben wird, wird eine Optimierung angewendet, um diesen Vorgang zu beschleunigen. Wenn der UI-Thread in Schritt 2 eine URL-Anfrage an den Netzwerk-Thread sendet, weiß er bereits, auf welche Website er zugreift. Der UI-Thread versucht, parallel zur Netzwerkanfrage proaktiv einen Renderer-Prozess zu finden oder zu starten. So befindet sich ein Rendererprozess, wenn alles wie erwartet funktioniert, bereits im Standby, wenn der Netzwerk-Thread Daten empfängt. Dieser Standby-Prozess wird möglicherweise nicht verwendet, wenn die Navigation seitenübergreifend weiterleitet. In diesem Fall ist möglicherweise ein anderer Prozess erforderlich.

Schritt 5: Navigation committen

Nachdem die Daten und der Renderer-Prozess bereit sind, wird eine IPC vom Browserprozess an den Renderer-Prozess gesendet, um die Navigation zu bestätigen. Außerdem gibt er den Datenstream weiter, damit der Rendering-Prozess weiterhin HTML-Daten empfangen kann. Sobald der Browserprozess eine Bestätigung erhält, dass die Commit-Aktion im Rendererprozess stattgefunden hat, ist die Navigation abgeschlossen und die Phase des Dokumentladens beginnt.

In diesem Moment wird die Adressleiste aktualisiert und der Sicherheitsindikator und die Benutzeroberfläche der 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 „Vorwärts“ die Website aufrufen können, auf die Sie gerade zugegriffen haben. Damit Sie Tabs und Sitzungen leichter wiederherstellen können, wenn Sie einen Tab oder ein Fenster schließen, wird der Sitzungsverlauf auf dem Laufwerk gespeichert.

Navigation bestätigen
Abbildung 6: IPC zwischen dem Browser und den Renderer-Prozessen, die das Rendern der Seite anfordern

Zusätzlicher Schritt: Anfänglicher Ladevorgang abgeschlossen

Sobald die Navigation festgelegt wurde, lädt der Rendering-Prozess weiter Ressourcen und rendert die Seite. Im nächsten Beitrag gehen wir genauer darauf ein, was in dieser Phase passiert. Sobald der Rendering-Prozess abgeschlossen ist, sendet er eine IPC zurück an den Browserprozess. Dies geschieht, nachdem alle onload-Ereignisse für alle Frames auf der Seite ausgelöst und ausgeführt wurden. An diesem Punkt beendet der UI-Thread das Ladesymbol auf dem Tab.

Ich sage „beendet“, weil clientseitiges JavaScript nach diesem Punkt noch zusätzliche Ressourcen laden und neue Ansichten rendern kann.

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

Die einfache Navigation war fertig. Was passiert aber, wenn ein Nutzer wieder eine andere URL in die Adressleiste eingibt? Der Browserprozess durchläuft dieselben Schritte, um die andere Website aufzurufen. Bevor das möglich ist, muss er jedoch auf der aktuell gerenderten Website prüfen, ob das Ereignis beforeunload berücksichtigt werden soll.

beforeunload kann die Warnung „Diese Website verlassen?“ anzeigen, wenn Sie versuchen, die Seite zu verlassen oder den Tab zu schließen. Alles auf einem Tab, einschließlich Ihres JavaScript-Codes, wird vom Renderer-Prozess verarbeitet. Daher muss der Browserprozess den aktuellen Renderer-Prozess prüfen, wenn eine neue Navigationsanfrage eingeht.

Event-Handler „beforeunload“
Abbildung 8: IPC vom Browserprozess an einen Rendererprozess, der ihm mitteilt, dass er eine andere Website aufrufen wird

Wenn die Navigation vom Renderer-Prozess aus gestartet wurde (z. B. wenn der Nutzer auf einen Link geklickt oder clientseitiges JavaScript ausgeführt hat), prüft der Renderer-Prozess zuerst beforeunload-Handler.window.location = "https://newsite.com" Anschließend durchläuft sie denselben Prozess wie die vom Browser initiierte Navigation. Der einzige Unterschied besteht darin, dass die Navigationsanfrage vom Rendererprozess an den Browserprozess gesendet wird.

Wenn die neue Navigation zu einer anderen Website als der aktuell gerenderten Website führt, wird ein separater Rendering-Prozess aufgerufen, um die neue Navigation zu verarbeiten. Der aktuelle Rendering-Prozess wird beibehalten, um Ereignisse wie unload zu verarbeiten. Weitere Informationen finden Sie unter Übersicht über die Status des Seitenlebenszyklus und API für den Seitenlebenszyklus.

neue Navigation und Entladung
Abbildung 9: Zwei IPCs von einem Browserprozess an einen neuen Rendererprozess, die das Rendern der Seite und das Entladen des alten Rendererprozesses anweisen

Für Service Worker

Eine aktuelle Änderung an diesem Navigationsprozess ist die Einführung von Dienst-Workern. Mit Service Workern können Sie einen Netzwerkproxy in Ihren Anwendungscode schreiben. So haben Webentwickler mehr Kontrolle darüber, was lokal im Cache gespeichert werden soll und wann neue Daten aus dem Netzwerk abgerufen werden sollen. Wenn der Dienst-Worker so konfiguriert ist, dass die Seite aus dem Cache geladen wird, müssen die Daten nicht vom Netzwerk angefordert werden.

Wichtig ist, dass ein Dienst-Worker JavaScript-Code ist, der in einem Renderer-Prozess ausgeführt wird. Aber woher weiß ein Browserprozess, wenn die Navigationsanfrage eingeht, dass die Website einen Service Worker hat?

Suche im Service Worker-Bereich
Abbildung 10: Der Netzwerk-Thread im Browserprozess, der den Dienstanbieterbereich abruft

Wenn ein Dienstarbeiter registriert wird, wird sein Umfang als Referenz beibehalten. Weitere Informationen zum Umfang finden Sie im Artikel Der Dienstarbeiter-Lebenszyklus. Bei einer Navigation prüft der Netzwerk-Thread die Domain anhand der registrierten Service Worker-Bereiche. Wenn für diese URL ein Service Worker registriert ist, sucht der UI-Thread einen Renderer-Prozess, um den Service Worker-Code auszuführen. Der Service Worker kann Daten aus dem Cache laden, sodass keine Daten mehr vom Netzwerk angefordert werden müssen. Er kann aber auch neue Ressourcen vom Netzwerk anfordern.

Service Worker-Navigation
Abbildung 11: Der UI-Thread in einem Browserprozess startet einen Renderer-Prozess, um Dienst-Worker zu verarbeiten. Ein Worker-Thread in einem Renderer-Prozess fordert dann Daten vom Netzwerk an.

Dieser Hin- und Rückweg zwischen dem Browserprozess und dem Rendererprozess kann zu Verzögerungen führen, wenn der Service Worker schließlich entscheidet, Daten aus dem Netzwerk anzufordern. Navigations-Preload ist ein Mechanismus, mit dem dieser Vorgang beschleunigt wird, indem Ressourcen parallel zum Start des Dienstarbeiters geladen werden. Diese Anfragen werden mit einem Header gekennzeichnet, sodass Server entscheiden können, für diese Anfragen andere Inhalte zu senden, z. B. nur aktualisierte Daten anstelle eines vollständigen Dokuments.

Navigationsvorabladen
Abbildung 12: Der UI-Thread in einem Browserprozess startet einen Renderer-Prozess, um den Dienst-Worker zu verarbeiten, während parallel eine Netzwerkanfrage gestartet wird

Zusammenfassung

In diesem Beitrag haben wir uns angesehen, was bei einer Navigation passiert und wie der Code Ihrer Webanwendung, z. B. Antwortheader und clientseitiges JavaScript, mit dem Browser interagiert. Wenn Sie wissen, welche Schritte der Browser durchläuft, um Daten aus dem Netzwerk abzurufen, können Sie leichter nachvollziehen, warum APIs wie das Navigations-Preloading entwickelt wurden. Im nächsten Beitrag sehen wir uns an, wie der Browser unser HTML/CSS/JavaScript zum Rendern von Seiten auswertet.

Hat Ihnen der Beitrag gefallen? Wenn du Fragen oder Vorschläge für den nächsten Beitrag hast, kannst du dich unten in den Kommentaren oder auf Twitter unter @kosamari melden.

Nächster Schritt: Die Funktionsweise eines Renderer-Prozesses