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

Mariko Kosaka

Eingabe kommt an den Compositor

Dies ist der letzte Teil einer vierteiligen Blogreihe, in der wir uns Chrome näher ansehen und untersuchen, wie der Code zum Anzeigen einer Website verarbeitet wird. Im vorherigen Post haben wir uns den Renderingprozess angesehen und etwas über den Compositor erfahren. In diesem Beitrag erfahren Sie, wie der Compositor bei einer Nutzereingabe eine reibungslose Interaktion ermöglicht.

Eingabeereignisse aus Browsersicht

Wenn Sie "Eingabeereignisse" hören, denken Sie vielleicht nur an eine Eingabe in einem Textfeld oder einen Mausklick. Aus Browsersicht bedeutet Eingabe jedoch jede Bewegung des Nutzers. Das Scrollen mit dem Mausrad ist ein Eingabeereignis, während Berühren oder Mouseover ebenfalls ein Eingabeereignis sind.

Wenn der Nutzer eine Touch-Geste wie eine Berührung auf einem Bildschirm ausführt, wird die Bewegung zuerst über den Browserprozess empfangen. Der Browserprozess erkennt jedoch nur, wo die Bewegung aufgetreten ist, da Inhalte in einem Tab vom Rendererprozess verarbeitet werden. Daher sendet der Browserprozess den Ereignistyp (z. B. touchstart) und seine Koordinaten an den Rendererprozess. Der Renderer-Prozess verarbeitet das Ereignis entsprechend, indem das Ereignisziel ermittelt und die zugehörigen Ereignis-Listener ausgeführt werden.

Eingabeereignis
Abbildung 1: Eingabeereignis, das über den Browserprozess an den Rendererprozess weitergeleitet wird

Compositor empfängt Eingabeereignisse

Abbildung 2: Darstellungsbereich, der über Seitenebenen schwebt

Im vorherigen Post haben wir uns angesehen, wie der Compositor das Scrollen reibungslos handhaben kann, indem Rasterebenen zusammengesetzt werden. Wenn der Seite keine Eingabeereignis-Listener zugeordnet sind, kann der Compositor-Thread einen neuen zusammengesetzten Frame erstellen, der völlig unabhängig vom Hauptthread ist. Aber was wäre, wenn einige Event-Listener an die Seite angehängt wären? Wie würde der Compositor-Thread herausfinden, ob das Ereignis verarbeitet werden muss?

Bereich mit nicht schnellem Scrollen verstehen

Da das Ausführen von JavaScript die Aufgabe des Hauptthreads ist, markiert der Compositor-Thread beim Zusammensetzen einer Seite einen Bereich der Seite, dem Event-Handler als „Non-Fast Scrollable Region“ angehängt sind. Anhand dieser Informationen kann der Compositor-Thread sicherstellen, dass das Eingabeereignis an den Hauptthread gesendet wird, wenn das Ereignis in dieser Region auftritt. Wenn das Eingabeereignis von außerhalb dieser Region stammt, setzt der Compositor-Thread einen neuen Frame zusammen, ohne auf den Hauptthread zu warten.

eingeschränkter Bereich für nicht schnelles Scrollen
Abbildung 3: Diagramm der beschriebenen Eingabe für den nicht schnellen scrollbaren Bereich

Vorsicht beim Schreiben von Event-Handlern

Ein gängiges Muster für die Ereignisverarbeitung in der Webentwicklung ist die Ereignisdelegation. Da Ereignisse als Infofeld angezeigt werden, können Sie einen Event-Handler an das oberste Element anhängen und Aufgaben basierend auf dem Ereignisziel delegieren. Vielleicht haben Sie Code wie den folgenden gesehen oder geschrieben.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Da Sie nur einen Event-Handler für alle Elemente schreiben müssen, ist die Ergonomie dieses Ereignisdelegierungsmusters ansprechend. Wenn Sie diesen Code jedoch aus der Perspektive des Browsers betrachten, wird die gesamte Seite als nicht schnell scrollbarer Bereich gekennzeichnet. Dies bedeutet, dass der Compositor-Thread mit dem Hauptthread kommunizieren und bei jedem Eingang eines Eingabeereignisses darauf warten muss, selbst wenn Ihre Anwendung die Eingabe von bestimmten Teilen der Seite nicht interessiert. Daher ist das reibungslose Scrollen des Compositors nicht möglich.

Bereich für nicht schnelles Scrollen ganzer Seiten
Abbildung 4: Diagramm der beschriebenen Eingabe für den nicht schnellen scrollbaren Bereich, der eine gesamte Seite abdeckt

Um dies zu verhindern, können Sie passive: true-Optionen an den Event-Listener übergeben. Dies weist den Browser darauf hin, dass Sie das Ereignis weiterhin im Hauptthread überwachen möchten. Der Compositor kann aber auch einen neuen Frame zusammensetzen.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Prüfen, ob der Termin abgesagt werden kann

Scrollen
Abbildung 5: Webseite, auf der ein Teil der Seite horizontal gescrollt ist

Stellen Sie sich vor, Sie haben ein Feld auf einer Seite, in dem Sie die Scrollrichtung auf horizontales Scrollen beschränken möchten.

Wenn Sie die Option passive: true in Ihrem Mauszeigerereignis verwenden, kann das Scrollen der Seite flüssig sein, aber möglicherweise bereits vor dem gewünschten preventDefault begonnen, um die Scrollrichtung einzuschränken. Sie können das mit der Methode event.cancelable prüfen.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Alternativ können Sie eine CSS-Regel wie touch-action verwenden, um den Event-Handler vollständig zu eliminieren.

#area {
  touch-action: pan-x;
}

Ereignisziel finden

Treffertest
Abbildung 6: Hauptthread mit Blick auf die Paint-Datensätze, in dem gefragt wird, was auf dem Punkt x.y gezeichnet wird

Wenn der Compositor-Thread ein Eingabeereignis an den Hauptthread sendet, muss zuerst ein Treffertest zum Auffinden des Ereignisziels ausgeführt werden. Bei einem Treffertest werden Paint-Datensatzdaten verwendet, die beim Renderingprozess generiert wurden, um herauszufinden, was sich unter den Punktkoordinaten befindet, in denen das Ereignis aufgetreten ist.

Ereignisweiterleitungen an den Hauptthread minimieren

Im vorherigen Post haben wir erörtert, wie unser typischer Bildschirm den Bildschirm 60-mal pro Sekunde aktualisiert und wie wir mit dem Rhythmus für eine flüssige Animation Schritt halten müssen. Zur Eingabe führt ein typisches Touchscreen-Gerät 60- bis 120-mal pro Sekunde ein Touch-Ereignis aus, bei einer typischen Maus 100-mal pro Sekunde. Das Eingabeereignis hat eine höhere Genauigkeit, als unser Bildschirm aktualisieren kann.

Wenn ein kontinuierliches Ereignis wie touchmove 120 Mal pro Sekunde an den Hauptthread gesendet wird, kann es zu einer übermäßigen Anzahl von Treffertests und zur JavaScript-Ausführung im Vergleich zur langsamen Aktualisierung des Bildschirms kommen.

nicht gefilterte Ereignisse
Abbildung 7: Ereignisse, die die Frame-Zeitachse überfluten und zu Seitenverzögerungen führen

Um übermäßige Aufrufe an den Hauptthread zu minimieren, fasst Chrome kontinuierliche Ereignisse (z. B. wheel, mousewheel, mousemove, pointermove, touchmove) zusammen und Verzögerungen bei der Weiterleitung bis direkt vor dem nächsten requestAnimationFrame.

zusammengeführte Ereignisse
Abbildung 8: Derselbe Zeitplan wie zuvor, aber das Ereignis wird zusammengeführt und verzögert

Alle diskreten Ereignisse wie keydown, keyup, mouseup, mousedown, touchstart und touchend werden sofort ausgelöst.

getCoalescedEvents verwenden, um Ereignisse im Frame abzurufen

Bei den meisten Webanwendungen sollten zusammengefasste Ereignisse ausreichen, um eine gute Nutzererfahrung zu bieten. Wenn Sie jedoch beispielsweise Anwendungen zeichnen und einen Pfad erstellen, der auf touchmove-Koordinaten basiert, können zwischen den Koordinaten verloren gehen und eine glatte Linie gezeichnet werden. In diesem Fall können Sie die Methode getCoalescedEvents im Zeigerereignis verwenden, um Informationen zu diesen zusammengeführten Ereignissen abzurufen.

getCoalescedEvents
Abbildung 9: Pfad über flüssige Touch-Geste links, zusammengeführter begrenzter Pfad rechts
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Nächste Schritte

In dieser Reihe haben wir das Innenleben eines Webbrowsers behandelt. Wenn Sie noch nie darüber nachgedacht haben, warum DevTools Ihrem Event-Handler das Hinzufügen von {passive: true} empfiehlt oder warum Sie das async-Attribut in Ihr Skript-Tag schreiben sollten, hoffe ich, dass diese Reihe etwas darüber aufgeklärt hat, warum ein Browser diese Informationen benötigt, um eine schnellere und reibungslosere Webnutzung zu ermöglichen.

Lighthouse verwenden

Wenn Sie Ihren Code für den Browser anpassen möchten, aber keine Ahnung haben, wo Sie anfangen sollen, ist Lighthouse ein Tool, das Website-Prüfungen durchführt und Ihnen einen Bericht dazu liefert, was richtig gemacht wurde und was verbessert werden muss. Die Liste der Prüfungen vermittelt Ihnen einen Eindruck davon, was für einen Browser wichtig ist.

Weitere Informationen zum Messen der Leistung

Die Leistungsoptimierung kann je nach Website variieren. Daher ist es wichtig, dass Sie die Leistung Ihrer Website messen und entscheiden, was für Ihre Website am besten geeignet ist. Das Chrome DevTools-Team bietet mehrere Tutorials zur Messung der Websiteleistung.

Funktionsrichtlinie zu Ihrer Website hinzufügen

Wenn Sie einen zusätzlichen Schritt gehen möchten, ist die Funktionsrichtlinie eine neue Webplattformfunktion, die Ihnen beim Erstellen Ihres Projekts als Schutzmaßnahmen dienen kann. Durch das Aktivieren von Funktionsrichtlinien wird das bestimmte Verhalten Ihrer App garantiert und verhindert, dass Sie Fehler machen. Wenn Sie beispielsweise dafür sorgen möchten, dass Ihre Anwendung das Parsen nie blockiert, können Sie sie gemäß der Richtlinie für synchrone Skripts ausführen. Wenn sync-script: 'none' aktiviert ist, wird die Ausführung von JavaScript verhindert, das den Parser blockiert. Dadurch wird verhindert, dass Code den Parser blockiert und der Browser muss den Parser nicht pausieren.

Zusammenfassung

Danke

Als ich anfing, Websites zu erstellen, war mir fast nur wichtig, wie ich meinen Code schreibe und was mir helfen würde, produktiver zu sein. Das ist wichtig, aber wir sollten auch überlegen, wie der Browser den von uns geschriebenen Code aufnimmt. Moderne Browser arbeiten kontinuierlich daran, das Web für Nutzer zu verbessern. Eine positive Nutzererfahrung durch den Code zu verbessern, verbessert wiederum die Nutzererfahrung. Ich hoffe, Sie unterstützen mich auch dabei, nett zu den Browsern zu sein.

Ein großes Dankeschön an alle, die erste Entwürfe dieser Reihe gelesen haben. Dazu gehören unter anderem: Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko und Chasrudask.

Hat Ihnen die Reihe gefallen? Wenn ihr Fragen oder Vorschläge für den zukünftigen Beitrag habt, könnt ihr uns gerne unten im Kommentarbereich oder unter @kosamari auf Twitter kontaktieren.