Een kijkje in de moderne webbrowser (deel 3)

Mariko Kosaka

Innerlijke werking van een Renderer-proces

Dit is deel 3 van een 4-delige blogserie waarin wordt gekeken naar hoe browsers werken. Eerder behandelden we de architectuur met meerdere processen en de navigatiestroom . In dit bericht gaan we kijken naar wat er gebeurt in het rendererproces.

Het rendererproces raakt veel aspecten van webprestaties. Omdat er veel gebeurt binnen het rendererproces, is dit bericht slechts een algemeen overzicht. Als u dieper wilt graven, vindt u in het gedeelte Prestaties van Web Fundamentals nog veel meer bronnen.

Renderer-processen verwerken webinhoud

Het rendererproces is verantwoordelijk voor alles wat er binnen een tabblad gebeurt. In een rendererproces verwerkt de hoofdthread het grootste deel van de code die u naar de gebruiker verzendt. Soms worden delen van uw JavaScript afgehandeld door werkthreads als u een webwerker of een servicemedewerker gebruikt. Compositor- en rasterthreads worden ook in rendererprocessen uitgevoerd om een ​​pagina efficiënt en soepel weer te geven.

De kerntaak van het rendererproces is om HTML, CSS en JavaScript om te zetten in een webpagina waarmee de gebruiker kan communiceren.

Renderer-proces
Figuur 1: Rendererproces met een hoofdthread, werkthreads, een compositorthread en een rasterthread erin

Parseren

Bouw van een DOM

Wanneer het rendererproces een commit-bericht voor een navigatie ontvangt en HTML-gegevens begint te ontvangen, begint de hoofdthread de tekstreeks (HTML) te ontleden en deze om te zetten in een Document Object Model ( DOM ).

De DOM is de interne weergave van de pagina in een browser, evenals de datastructuur en API waarmee webontwikkelaars via JavaScript kunnen communiceren.

Het parseren van een HTML-document in een DOM wordt gedefinieerd door de HTML-standaard . Het is je misschien opgevallen dat het invoeren van HTML in een browser nooit een fout oplevert. De ontbrekende afsluitende </p> -tag is bijvoorbeeld een geldige HTML. Foutieve markeringen zoals Hi! <b>I'm <i>Chrome</b>!</i> (b-tag is gesloten voordat ik tag) wordt behandeld alsof je Hi! <b>I'm <i>Chrome</i></b><i>!</i> . Dit komt omdat de HTML-specificatie is ontworpen om deze fouten netjes af te handelen. Als je nieuwsgierig bent hoe deze dingen worden gedaan, kun je lezen in het gedeelte " Een inleiding tot foutafhandeling en vreemde gevallen in de parser " van de HTML-specificatie.

Subbronnen laden

Een website maakt meestal gebruik van externe bronnen zoals afbeeldingen, CSS en JavaScript. Deze bestanden moeten worden geladen vanuit het netwerk of de cache. De hoofdthread zou ze één voor één kunnen opvragen wanneer ze ze vinden tijdens het parseren om een ​​DOM te bouwen, maar om dit te versnellen wordt tegelijkertijd de "preload scanner" uitgevoerd. Als er zaken als <img> of <link> in het HTML-document staan, kijkt de vooraf geladen scanner naar tokens die zijn gegenereerd door de HTML-parser en verzendt verzoeken naar de netwerkthread in het browserproces.

DOM
Figuur 2: De rode draad die HTML parseert en een DOM-boom opbouwt

JavaScript kan het parseren blokkeren

Wanneer de HTML-parser een <script> -tag vindt, pauzeert hij het parseren van het HTML-document en moet hij de JavaScript-code laden, parseren en uitvoeren. Waarom? omdat JavaScript de vorm van het document kan veranderen met behulp van zaken als document.write() , waardoor de gehele DOM-structuur verandert ( overzicht van het parseermodel in de HTML-specificatie heeft een mooi diagram). Dit is de reden waarom de HTML-parser moet wachten tot JavaScript is uitgevoerd voordat hij het parseren van het HTML-document kan hervatten. Als je nieuwsgierig bent naar wat er gebeurt bij de uitvoering van JavaScript: het V8-team heeft hierover lezingen en blogposts .

Tip voor de browser hoe u bronnen wilt laden

Er zijn veel manieren waarop webontwikkelaars hints naar de browser kunnen sturen om bronnen netjes te laden. Als uw JavaScript geen gebruik maakt van document.write() , kunt u async of defer toevoegen aan de <script> -tag. De browser laadt en voert de JavaScript-code vervolgens asynchroon uit en blokkeert het parseren niet. U kunt ook de JavaScript-module gebruiken als dat geschikt is. <link rel="preload"> is een manier om de browser te informeren dat de bron zeker nodig is voor de huidige navigatie en dat u deze zo snel mogelijk wilt downloaden. U kunt hier meer over lezen op Bronprioriteit – De browser zover krijgen dat hij u helpt .

Stijlberekening

Het hebben van een DOM is niet voldoende om te weten hoe de pagina eruit zou zien, omdat we pagina-elementen in CSS kunnen opmaken. De hoofdthread ontleedt CSS en bepaalt de berekende stijl voor elk DOM-knooppunt. Dit is informatie over het soort stijl dat op elk element wordt toegepast op basis van CSS-selectors. U kunt deze informatie bekijken in het computed gedeelte van DevTools.

Berekende stijl
Figuur 3: De hoofdthread die CSS parseert om berekende stijl toe te voegen

Zelfs als u geen CSS levert, heeft elk DOM-knooppunt een berekende stijl. <h1> -tag wordt groter weergegeven dan <h2> -tag en voor elk element zijn marges gedefinieerd. Dit komt omdat de browser een standaardstijlblad heeft. Als je wilt weten hoe de standaard CSS van Chrome eruit ziet, kun je hier de broncode bekijken .

Indeling

Nu kent het rendererproces de structuur van een document en de stijlen voor elke knooppunten, maar dat is niet genoeg om een ​​pagina weer te geven. Stel je voor dat je via de telefoon een schilderij aan je vriend probeert te beschrijven. "Er is een grote rode cirkel en een klein blauw vierkant" is niet genoeg informatie voor je vriend om te weten hoe het schilderij er precies uit zou zien.

spel van menselijke faxmachine
Figuur 4: Een persoon die voor een schilderij staat, telefoonlijn verbonden met de andere persoon

De lay-out is een proces om de geometrie van elementen te vinden. De rode draad doorloopt de DOM en berekende stijlen en creëert de lay-outboom met informatie zoals xy-coördinaten en de afmetingen van de selectiekaders. De lay-outboom kan een soortgelijke structuur hebben als de DOM-boom, maar bevat alleen informatie die betrekking heeft op wat zichtbaar is op de pagina. Als display: none wordt toegepast, maakt dat element geen deel uit van de lay-outboom (een element met visibility: hidden bevindt zich echter wel in de lay-outboom). Op dezelfde manier wordt, als een pseudoklasse met inhoud als p::before{content:"Hi!"} wordt toegepast, deze opgenomen in de lay-outboom, ook al staat die niet in de DOM.

indeling
Figuur 5: De rode draad gaat over de DOM-boom met berekende stijlen en het produceren van een lay-outboom
Afbeelding 6: Kaderindeling voor een alinea die beweegt als gevolg van een wijziging in de regeleinden

Het bepalen van de lay-out van een pagina is een uitdagende taak. Zelfs bij de eenvoudigste pagina-indeling, zoals een blokstroom van boven naar beneden, moet rekening worden gehouden met hoe groot het lettertype is en waar de regels moeten worden afgebroken, omdat deze de grootte en vorm van een alinea beïnvloeden; wat dan van invloed is op waar de volgende paragraaf moet komen.

CSS kan elementen naar één kant laten zweven, overloopitems maskeren en de schrijfrichting wijzigen. Je kunt je voorstellen dat deze lay-outfase een enorme taak heeft. In Chrome werkt een heel team van engineers aan de lay-out. Als je details van hun werk wilt zien: er zijn maar weinig toespraken van de BlinkOn Conference opgenomen en best interessant om naar te kijken.

Verf

tekenspel
Figuur 7: Een persoon staat voor een canvas met een penseel in de hand en vraagt ​​zich af of hij of zij eerst een cirkel of een vierkant moet tekenen

Het hebben van een DOM, stijl en lay-out is nog steeds niet voldoende om een ​​pagina weer te geven. Stel dat u een schilderij probeert te reproduceren. Je kent de grootte, vorm en locatie van elementen, maar je moet nog steeds beoordelen in welke volgorde je ze schildert.

z-index kan bijvoorbeeld zijn ingesteld voor bepaalde elementen. In dat geval zal het schilderen in de volgorde van de in de HTML geschreven elementen resulteren in een onjuiste weergave.

z-index mislukt
Figuur 8: Pagina-elementen verschijnen in de volgorde van een HTML-opmaak, wat resulteert in een verkeerd weergegeven beeld omdat er geen rekening is gehouden met de z-index

Bij deze verfstap loopt de hoofdthread door de lay-outboom om verfrecords te maken. Verfrecord is een notitie van het schilderproces, zoals "eerst achtergrond, dan tekst, dan rechthoek". Als u met JavaScript op het <canvas> -element heeft getekend, is dit proces u wellicht bekend.

platen schilderen
Figuur 9: De rode draad die door de lay-outboom loopt en verfrecords produceert

Het bijwerken van de renderingpijplijn is kostbaar

Afbeelding 10: DOM+Style-, Layout- en Paint-bomen in de volgorde waarin deze worden gegenereerd

Het belangrijkste dat u moet weten bij het renderen van de pijplijn is dat bij elke stap het resultaat van de vorige bewerking wordt gebruikt om nieuwe gegevens te creëren. Als er bijvoorbeeld iets verandert in de lay-outstructuur, moet de verfvolgorde opnieuw worden gegenereerd voor de betreffende delen van het document.

Als u elementen animeert, moet de browser deze bewerkingen tussen elk frame uitvoeren. De meeste van onze beeldschermen verversen het scherm 60 keer per seconde (60 fps); animatie zal er vloeiend uitzien voor menselijke ogen wanneer u bij elk frame dingen over het scherm beweegt. Als de animatie echter de tussenliggende frames mist, zal de pagina er "janky" uitzien.

jage jank door frames te missen
Figuur 11: Animatieframes op een tijdlijn

Zelfs als uw weergavebewerkingen gelijke tred houden met de schermvernieuwing, worden deze berekeningen uitgevoerd op de hoofdthread, wat betekent dat deze kan worden geblokkeerd wanneer uw toepassing JavaScript uitvoert.

jage jank door JavaScript
Figuur 12: Animatieframes op een tijdlijn, maar één frame wordt geblokkeerd door JavaScript

U kunt de JavaScript-bewerking in kleine stukjes verdelen en plannen dat deze bij elk frame wordt uitgevoerd met requestAnimationFrame() . Voor meer informatie over dit onderwerp, zie JavaScript-uitvoering optimaliseren . U kunt uw JavaScript ook in Web Workers uitvoeren om te voorkomen dat de hoofdthread wordt geblokkeerd.

animatieframe aanvragen
Figuur 13: Kleinere stukjes JavaScript die op een tijdlijn met animatieframe worden uitgevoerd

Compositie

Hoe zou jij een pagina tekenen?

Figuur 14: Animatie van een naïef rasterproces

Nu de browser de structuur van het document, de stijl van elk element, de geometrie van de pagina en de verfvolgorde kent, hoe tekent hij dan een pagina? Het omzetten van deze informatie in pixels op het scherm wordt rasteren genoemd.

Misschien is een naïeve manier om hiermee om te gaan het rasteren van delen binnen de viewport. Als een gebruiker over de pagina scrolt, verplaats dan het gerasterde frame en vul de ontbrekende delen in door meer te rasteren. Dit is hoe Chrome omging met rasteren toen het voor het eerst werd uitgebracht. De moderne browser voert echter een geavanceerder proces uit dat compositing wordt genoemd.

Wat is compositie

Figuur 15: Animatie van het compositieproces

Compositie is een techniek om delen van een pagina in lagen te scheiden, deze afzonderlijk te rasteren en samen te stellen als een pagina in een aparte thread, de compositorthread. Als er sprake is van scrollen, hoeft u, aangezien de lagen al gerasterd zijn, alleen maar een nieuw frame samen te stellen. Animatie kan op dezelfde manier worden bereikt door lagen te verplaatsen en een nieuw frame samen te stellen.

U kunt zien hoe uw website in lagen is verdeeld in DevTools met behulp van het deelvenster Lagen .

Verdelen in lagen

Om erachter te komen welke elementen in welke lagen moeten staan, loopt de rode draad door de lay-outboom om de lagenboom te maken (dit onderdeel wordt "Update Layer Tree" genoemd in het prestatiepaneel van DevTools). Als bepaalde delen van een pagina die een aparte laag zouden moeten zijn (zoals een inschuifmenu aan de zijkant) er geen krijgen, dan kun je de browser een hint geven door will-change attribuut in CSS te gebruiken.

laag boom
Figuur 16: De rode draad die door de lay-outboom loopt en een lagenboom oplevert

U zou in de verleiding kunnen komen om lagen aan elk element toe te voegen, maar het samenstellen van een overmaat aan lagen kan resulteren in een langzamere werking dan het rasteren van kleine delen van een pagina per frame. Het is dus van cruciaal belang dat u de weergaveprestaties van uw toepassing meet. Voor meer informatie over dit onderwerp, zie Vasthouden aan eigenschappen die alleen voor de Compositor beschikbaar zijn en het aantal lagen beheren .

Raster en composiet van de hoofddraad

Zodra de lagenboom is gemaakt en de tekenvolgorde is bepaald, draagt ​​de hoofdthread die informatie over aan de compositorthread. De compositorthread rastert vervolgens elke laag. Een laag kan zo groot zijn als de gehele lengte van een pagina, dus de compositor-thread verdeelt ze in tegels en stuurt elke tegel naar rasterthreads. Rasterthreads rasteren elke tegel en slaan deze op in het GPU-geheugen.

rooster
Afbeelding 17: Rasterthreads die de bitmap van tegels maken en naar GPU verzenden

De compositorthread kan prioriteit geven aan verschillende rasterthreads, zodat dingen binnen de viewport (of dichtbij) eerst kunnen worden gerasterd. Een laag heeft ook meerdere herhalingen voor verschillende resoluties om zaken als inzoomen te verwerken.

Zodra de tegels zijn gerasterd, verzamelt de compositorthread tegelinformatie, genaamd draw quads, om een ​​compositorframe te maken.

Teken vierhoeken Bevat informatie zoals de locatie van de tegel in het geheugen en waar op de pagina de tegel moet worden getekend, rekening houdend met de samenstelling van de pagina.
Compositieframe Een verzameling tekenquads die een frame van een pagina vertegenwoordigen.

Een compositorframe wordt vervolgens via IPC aan het browserproces voorgelegd. Op dit punt zou een ander compositorframe kunnen worden toegevoegd vanuit de UI-thread voor de wijziging van de gebruikersinterface van de browser of vanuit andere rendererprocessen voor extensies. Deze compositorframes worden naar de GPU gestuurd om deze op een scherm weer te geven. Als er een scrollgebeurtenis binnenkomt, maakt de compositorthread een ander compositorframe aan dat naar de GPU wordt verzonden.

samenstelling
Afbeelding 18: Compositorthread die een compositieframe maakt. Frame wordt naar het browserproces en vervolgens naar GPU verzonden

Het voordeel van compositing is dat het gebeurt zonder dat de rode draad erbij betrokken is. Compositor-thread hoeft niet te wachten op stijlberekening of JavaScript-uitvoering. Dit is de reden waarom het samenstellen van alleen animaties als het beste wordt beschouwd voor vloeiende prestaties. Als er opnieuw layout of verf berekend moet worden dan moet de rode draad erbij betrokken worden.

Afronden

In dit bericht hebben we gekeken naar de weergavepijplijn van parseren tot compositing. Hopelijk bent u nu in staat om meer te lezen over de prestatie-optimalisatie van een website.

In het volgende en laatste bericht van deze serie zullen we de compositor-thread in meer details bekijken en kijken wat er gebeurt als gebruikersinvoer, zoals mouse move en click , binnenkomt.

Vond je het bericht leuk? Als je vragen of suggesties hebt voor het toekomstige bericht, hoor ik graag van je in het commentaargedeelte hieronder of via @kosamari op Twitter.

Volgende: Er komt invoer naar de compositor