Come funzionano i browser

Dietro le quinte dei browser web moderni

Paul Irish
Tali Garsiel
Tali Garsiel

Prefazione

Questa introduzione completa sulle operazioni interne di WebKit e Gecko è il risultato di molte ricerche condotte dallo sviluppatore israeliano Tali Garsiel. Nel corso di alcuni anni, ha esaminato tutti i dati pubblicati sugli elementi interni dei browser e ha dedicato molto tempo a leggere il codice sorgente del browser web. Ha scritto:

In qualità di sviluppatore web, conoscere i componenti interni delle operazioni del browser ti aiuta a prendere decisioni migliori e a comprendere le giustificazioni alla base delle best practice per lo sviluppo. Anche se si tratta di un documento piuttosto lungo, ti consigliamo di approfondirlo ulteriormente. Sarai felice di averlo fatto.

Paul Ireland, Relazioni con gli sviluppatori di Chrome

Introduzione

I browser web sono i software più utilizzati. In questa introduzione, spiegherò come funzionano dietro le quinte. Vedremo cosa succede quando digiti google.com nella barra degli indirizzi finché non visualizzi la pagina Google nella schermata del browser.

Browser di cui parleremo

Attualmente esistono cinque browser principali utilizzati sui computer: Chrome, Internet Explorer, Firefox, Safari e Opera. Sui dispositivi mobili, i browser principali sono Android Browser, iPhone, Opera Mini e Opera Mobile, UC Browser, i browser Nokia S40/S60 e Chrome, tutti basati su WebKit, ad eccezione dei browser Opera. Fornirò esempi dei browser open source Firefox, Chrome e Safari (che è in parte open source). Secondo le statistiche di StatCounter (dati di giugno 2013), Chrome, Firefox e Safari rappresentano circa il 71% dell'utilizzo globale dei browser desktop. Sui dispositivi mobili, il browser Android, gli iPhone e Chrome costituiscono circa il 54% dell'utilizzo.

Le funzionalità principali del browser

La funzione principale di un browser è presentare la risorsa web che hai scelto, richiedendola al server e visualizzandola nella finestra del browser. La risorsa è in genere un documento HTML, ma può anche essere un PDF, un'immagine o un altro tipo di contenuti. La posizione della risorsa è specificata dall'utente utilizzando un URI (Uniform Resource Identifier).

Il modo in cui il browser interpreta e visualizza i file HTML viene specificato nelle specifiche HTML e CSS. Queste specifiche sono gestite dal W3C (World Wide Web Consortium), l'organizzazione standard per il web. Per anni i browser sono stati conformi solo a una parte delle specifiche e hanno sviluppato le proprie estensioni. Ciò ha causato gravi problemi di compatibilità per gli autori web. Oggi la maggior parte dei browser è più o meno conforme alle specifiche.

Le interfacce utente del browser hanno molto in comune tra loro. Tra gli elementi più comuni dell'interfaccia utente ci sono:

  1. Barra degli indirizzi per l'inserimento di un URI
  2. Pulsanti Avanti e Indietro
  3. Opzioni di aggiunta dei preferiti
  4. Pulsanti di aggiornamento e interruzione per aggiornare o interrompere il caricamento dei documenti correnti
  5. Pulsante Home che ti porta alla home page

Stranamente, l’interfaccia utente del browser non è specificata in nessuna specifica formale, è solo una pratica che si forma nel corso di anni di esperienza e che i browser si imitano a vicenda. La specifica HTML5 non definisce gli elementi dell'interfaccia utente che un browser deve avere, ma elenca alcuni elementi comuni. Tra questi ci sono la barra degli indirizzi, la barra di stato e la barra degli strumenti. Esistono, naturalmente, funzioni specifiche per un browser specifico, come il gestore dei download di Firefox.

Infrastruttura di alto livello

I componenti principali del browser sono:

  1. Interfaccia utente. Include la barra degli indirizzi, il pulsante Indietro/Avanti, il menu per i preferiti e così via. Viene visualizzata ogni parte del browser, ad eccezione della finestra in cui viene visualizzata la pagina richiesta.
  2. Il motore del browser: combina azioni tra l'interfaccia utente e il motore di rendering.
  3. Il motore di rendering: responsabile della visualizzazione dei contenuti richiesti. Ad esempio, se i contenuti richiesti sono HTML, il motore di rendering analizza i contenuti HTML e CSS e visualizza i contenuti analizzati sullo schermo.
  4. Networking: per chiamate di rete come le richieste HTTP, utilizzando implementazioni diverse per piattaforme diverse con un'interfaccia indipendente dalla piattaforma.
  5. Backend UI: utilizzato per disegnare widget di base come caselle combinate e finestre. Questo backend espone un'interfaccia generica non specifica della piattaforma. Sotto utilizza metodi di interfaccia utente del sistema operativo.
  6. Interprete JavaScript. Utilizzato per analizzare ed eseguire il codice JavaScript.
  7. Archiviazione dei dati. Questo è un livello di persistenza. Il browser potrebbe dover salvare tutti i tipi di dati localmente, come i cookie. I browser supportano anche meccanismi di archiviazione come localStorage, IndexedDB, WebSQL e FileSystem.
Componenti del browser
Figura 1: componenti del browser

È importante notare che i browser come Chrome eseguono più istanze del motore di rendering: una per ogni scheda. Ogni scheda viene eseguita in un processo separato.

Motori di rendering

La responsabilità del motore di rendering è bene... Rendering, ovvero la visualizzazione dei contenuti richiesti sulla schermata del browser.

Per impostazione predefinita, il motore di rendering può visualizzare documenti e immagini HTML e XML. Può visualizzare altri tipi di dati tramite plug-in o estensioni; ad esempio, può mostrare documenti PDF utilizzando un plug-in per il visualizzatore di PDF. Tuttavia, in questo capitolo ci concentreremo sul caso d'uso principale: la visualizzazione di codice HTML e immagini formattate mediante CSS.

Browser diversi utilizzano motori di rendering diversi: Internet Explorer utilizza Trident, Firefox utilizza Gecko, Safari utilizza WebKit. Chrome e Opera (dalla versione 15) utilizzano Blink, una forchetta di WebKit.

WebKit è un motore di rendering open source avviato come motore per la piattaforma Linux ed è stato modificato da Apple per supportare Mac e Windows.

Flusso principale

Il motore di rendering inizierà a recuperare i contenuti del documento richiesto dal livello di networking. Questa operazione viene generalmente eseguita in blocchi da 8 kB.

Dopodiché, questo è il flusso di base del motore di rendering:

Flusso di base del motore di rendering
Figura 2: flusso di base del motore di rendering

Il motore di rendering inizierà ad analizzare il documento HTML e convertirà gli elementi in nodi DOM in una struttura chiamata "albero dei contenuti". Il motore analizzerà i dati degli stili sia nei file CSS esterni che negli elementi di stile. Le informazioni sullo stile insieme alle istruzioni visive nel codice HTML verranno utilizzate per creare un altro albero: l'albero di rendering.

L'albero di rendering contiene rettangoli con attributi visivi come colore e dimensioni. I rettangoli sono nell'ordine corretto per essere visualizzati sullo schermo.

Una volta costruito, l'albero di rendering viene sottoposto a un processo di "layout". Ciò significa fornire a ogni nodo le coordinate esatte in cui dovrebbe apparire sullo schermo. La fase successiva è painting: l'albero di rendering verrà attraversato e ogni nodo verrà dipinto utilizzando il livello di backend dell'interfaccia utente.

È importante capire che si tratta di un processo graduale. Per migliorare l'esperienza utente, il motore di rendering cercherà di visualizzare i contenuti sullo schermo il prima possibile. Non attende che venga analizzato tutto il codice HTML prima di iniziare a creare e impostare il layout dell'albero di rendering. Parti dei contenuti verranno analizzate e visualizzate, mentre il processo prosegue con il resto dei contenuti che continuano a provenire dalla rete.

Esempi di flussi principali

Flusso principale di WebKit.
Figura 3: flusso principale di WebKit
Flusso principale del motore di rendering Gecko di Mozilla.
Figura 4: flusso principale del motore di rendering Gecko di Mozilla

Dalle figure 3 e 4 si può vedere che sebbene WebKit e Gecko utilizzino una terminologia leggermente diversa, il flusso è sostanzialmente lo stesso.

Gecko chiama l'albero degli elementi visivamente formattati "Frame Tree". Ogni elemento è un frame. WebKit utilizza il termine "Rendering dell'albero" ed è composto da "Visualizza oggetti". WebKit utilizza il termine "layout" per posizionare gli elementi, mentre Gecko lo chiama "Adattamento dinamico". "Allegato" è il termine di WebKit per connettere i nodi DOM e le informazioni visive per creare l'albero di rendering. Una piccola differenza non semantica è che Gecko ha un livello in più tra l'HTML e l'albero DOM. È chiamato "sink di contenuti" ed è una fabbrica per la produzione di elementi DOM. Parleremo di ogni parte del flusso:

Analisi - generale

Poiché l'analisi è un processo molto significativo all'interno del motore di rendering, ne esamineremo un po' più in dettaglio. Iniziamo con una piccola introduzione all'analisi.

L'analisi di un documento comporta la sua traduzione in una struttura utilizzabile dal codice. Il risultato dell'analisi è in genere un albero di nodi che rappresenta la struttura del documento. In questo caso si parla di albero di analisi o albero della sintassi.

Ad esempio, l'analisi dell'espressione 2 + 3 - 1 potrebbe restituire questo albero:

Nodo ad albero delle espressioni matematica.
Figura 5: nodo ad albero delle espressioni matematiche

Grammatica

L'analisi si basa sulle regole di sintassi rispettate dal documento, ovvero la lingua o il formato in cui è stato scritto. Ogni formato che puoi analizzare deve avere una grammatica deterministica composta da regole di vocabolario e sintassi. Si chiama grammatica senza contesto. I linguaggi umani non sono questi e pertanto non possono essere analizzati con le tecniche di analisi convenzionali.

Analizzatore sintattico - Combinazione Lexer

L'analisi può essere suddivisa in due sottoprocessi: analisi lessicale e analisi della sintassi.

L'analisi lessicale è il processo di scomposizione dell'input in token. I token sono il vocabolario linguistico, ovvero la raccolta di componenti di base validi. Nella lingua umana, è composta da tutte le parole presenti nel dizionario di quella lingua.

L'analisi della sintassi è l'applicazione delle regole di sintassi del linguaggio.

I analizzatori di solito dividono il lavoro tra due componenti: il lexer (a volte chiamato tokenizzatore), che è responsabile della suddivisione dell'input in token validi, e il parser, che è responsabile della creazione dell'albero di analisi analizzando la struttura del documento in base alle regole di sintassi del linguaggio.

Il lexer sa come eliminare i caratteri non pertinenti come gli spazi bianchi e le interruzioni di riga.

Dal documento di origine all'analisi degli alberi
Figura 6: dal documento di origine all'analisi degli alberi

Il processo di analisi è iterativo. Di solito il parser chiede al lexer un nuovo token e tenta di abbinare il token a una delle regole di sintassi. Se viene soddisfatta una regola, un nodo corrispondente al token verrà aggiunto all'albero di analisi e il parser richiederà un altro token.

Se non esiste una regola corrispondente, il parser archivierà il token internamente e continuerà a richiedere token fino a quando non viene trovata una regola corrispondente a tutti i token archiviati internamente. Se non viene trovata alcuna regola, il parser genererà un'eccezione. Ciò significa che il documento non era valido e conteneva errori di sintassi.

Traduzione

In molti casi, l'albero di analisi non è il prodotto finale. L'analisi viene spesso utilizzata nella traduzione: trasforma il documento di input in un altro formato. Un esempio è la compilazione. Il compilatore che compila il codice sorgente nel codice della macchina lo analizza innanzitutto in un albero di analisi, quindi lo traduce in un documento di codice macchina.

Flusso di compilazione
Figura 7: flusso di compilazione

Esempio di analisi

Nella figura 5 abbiamo creato un albero di analisi da un'espressione matematica. Proviamo a definire un semplice linguaggio matematico e vediamo il processo di analisi.

Sintassi:

  1. I componenti di base della sintassi del linguaggio sono espressioni, termini e operazioni.
  2. Il nostro linguaggio può includere un numero illimitato di espressioni.
  3. Un'espressione è definita come "termine" seguito da un'"operazione" seguita da un altro termine
  4. Un'operazione è un token più o meno
  5. Un termine è un token intero o un'espressione

Analizziamo l'input 2 + 3 - 1.

La prima sottostringa che corrisponde a una regola è 2: secondo la regola 5 è un termine. La seconda corrispondenza è 2 + 3: corrisponde alla terza regola, ovvero un termine seguito da un'operazione seguita da un altro termine. La corrispondenza successiva verrà raggiunta solo alla fine dell'input. 2 + 3 - 1 è un'espressione perché sappiamo già che 2 + 3 è un termine, quindi abbiamo un termine seguito da un'operazione seguita da un altro termine. 2 + + non corrisponderà a nessuna regola e, di conseguenza, non è valido.

Definizioni formali del vocabolario e della sintassi

Il vocabolario è generalmente espresso da espressioni regolari.

Ad esempio, la nostra lingua verrà definita come:

INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -

Come puoi vedere, i numeri interi sono definiti da un'espressione regolare.

In genere, la sintassi viene definita in un formato chiamato BNF. La nostra lingua sarà definita come:

expression :=  term  operation  term
operation :=  PLUS | MINUS
term := INTEGER | expression

Abbiamo detto che un linguaggio può essere analizzato da normali parser se la sua grammatica è una grammatica senza contesto. Una definizione intuitiva di una grammatica senza contesto è una grammatica che può essere interamente espressa in BNF. Per una definizione formale, consulta l'articolo di Wikipedia sulla grammatica senza contesto

Tipi di parser

Esistono due tipi di parser: i parser dall'alto verso il basso e dal basso verso l'alto. Una spiegazione intuitiva è che i parser dall'alto verso il basso esaminano la struttura di alto livello della sintassi e provano a trovare una corrispondenza con una regola. I parser di livello inferiore iniziano con l'input e lo trasformano gradualmente nelle regole di sintassi, a partire dalle regole di basso livello fino a quando non vengono soddisfatte le regole di alto livello.

Vediamo come i due tipi di parser analizzeranno il nostro esempio.

Il parser dall'alto verso il basso inizierà dalla regola di livello superiore e identificherà 2 + 3 come espressione. Quindi identificherà 2 + 3 - 1 come espressione (il processo di identificazione dell'espressione cambia abbinando le altre regole, ma il punto iniziale è la regola di livello più alto).

Il parser dal basso verso l'alto eseguirà la scansione dell'input finché non viene soddisfatta una regola. e sostituirà l'input corrispondente con la regola. Questa operazione andrà avanti fino alla fine dell'input. L'espressione parzialmente corrispondente viene inserita nello stack del parser.

In pila di inupt
2 + 3 - 1
term + 3 - 1
operazione a termine 3 - 1
espressione - 1
operazione di espressione 1
espressione -

Questo tipo di parser dal basso verso l'alto è chiamato parser con riduzione Maiusc, perché l'input viene spostato verso destra (immaginiamo un puntatore che punta prima all'inizio dell'input e che si sposta a destra) e viene gradualmente ridotto alle regole di sintassi.

Generazione automatica di parser

Esistono strumenti in grado di generare un parser. Fornisci loro la grammatica della tua lingua, il suo vocabolario e le sue regole di sintassi, e generano un parser funzionante. La creazione di un parser richiede una profonda comprensione dell'analisi e non è facile creare manualmente un parser ottimizzato, quindi questi generatori possono essere molto utili.

WebKit utilizza due noti generatori di parser: Flex per creare un lexer e Bison per creare un parser (potresti incontrarli con i nomi Lex e Yacc). L'input flessibile è un file contenente le definizioni di espressioni regolari dei token. L'input di Bisonte indica le regole di sintassi della lingua in formato BNF.

Analizzatore HTML

Il compito dell'analizzatore sintattico HTML è analizzare il markup HTML in un albero di analisi.

Grammatica HTML

Il vocabolario e la sintassi del codice HTML sono definiti nelle specifiche create dall'organizzazione W3C.

Come abbiamo visto nell'introduzione all'analisi, la sintassi grammaticale può essere definita formalmente utilizzando formati come BNF.

Purtroppo tutti gli argomenti del parser convenzionali non si applicano al codice HTML (non ne ho parlato solo per divertimento, verranno utilizzati nell'analisi di CSS e JavaScript). L'HTML non può essere facilmente definito da una grammatica senza contesto necessaria ai parser.

Esiste un formato formale per la definizione del codice HTML, DTD (Document Type Definition), ma non è una grammatica senza contesto.

A prima vista sembra strano; il linguaggio HTML è piuttosto simile a quello di XML. Ci sono molti parser XML disponibili. Esiste una variante XML del linguaggio HTML, ossia lo standard HTML, ma la differenza importante?

La differenza è che l'approccio HTML è più tollerante: permette di omettere alcuni tag (che vengono poi aggiunti implicitamente), a volte di omettere i tag di inizio o di fine e così via. Nel complesso è una sintassi "soft", a differenza della sintassi rigida ed esigente di XML.

Questo piccolo dettaglio fa la differenza. Da un lato questo è il motivo principale per cui l'HTML è così popolare: ci aiuta a perdonare gli errori e semplifica la vita all'autore web. D'altra parte, rende difficile scrivere una grammatica formale. Quindi, per riepilogare, il codice HTML non può essere analizzato facilmente dai parser convenzionali, poiché la sua grammatica non è priva di contesto. Impossibile analizzare il codice HTML dai parser XML.

DTD HTML

La definizione HTML è in formato DTD. Questo formato viene utilizzato per definire i linguaggi della famiglia SGML. Il formato contiene le definizioni di tutti gli elementi consentiti, dei relativi attributi e gerarchia. Come abbiamo visto in precedenza, la DTD HTML non forma una grammatica senza contesto.

Esistono alcune varianti del DTD. La modalità restrittiva è conforme esclusivamente alle specifiche, mentre altre supportano il markup utilizzato in passato dai browser. Lo scopo è la compatibilità con le versioni precedenti di contenuti. L'attuale DTD rigoroso è disponibile qui: www.w3.org/TR/html4/strict.dtd

DOM (DOM)

La struttura di output (l'"albero di analisi") è una struttura di elementi DOM e nodi degli attributi. DOM è l'acronimo di Document Object Model. È la presentazione oggetto del documento HTML e l'interfaccia degli elementi HTML al mondo esterno come JavaScript.

La radice dell'albero è l'oggetto "Document".

Il DOM ha una relazione quasi one-to-one con il markup. Ad esempio:

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>

Questo markup verrà tradotto nel seguente albero DOM:

Albero DOM del markup di esempio
Figura 8: albero DOM del markup di esempio

Come per l'HTML, il DOM è specificato dall'organizzazione W3C. Visita la pagina www.w3.org/DOM/DOMTR. È una specifica generica per manipolare i documenti. Un modulo specifico descrive elementi specifici del codice HTML. Le definizioni HTML sono disponibili qui: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.

Quando dico che l'albero contiene nodi DOM, vuol dire che è costruito con elementi che implementano una delle interfacce DOM. I browser utilizzano implementazioni concrete che hanno altri attributi utilizzati internamente dal browser.

L'algoritmo di analisi

Come abbiamo visto nelle sezioni precedenti, non è possibile analizzare l'HTML utilizzando i normali parser dall'alto verso il basso o dal basso verso l'alto.

I motivi sono:

  1. La natura tollerante del linguaggio.
  2. Il fatto che i browser abbiano una tolleranza di errore tradizionale per supportare casi ben noti di HTML non valido.
  3. Il processo di analisi è rientrato. Per altri linguaggi, l'origine non cambia durante l'analisi, ma nel codice HTML il codice dinamico (ad esempio gli elementi di script contenenti chiamate document.write()) può aggiungere ulteriori token, quindi il processo di analisi modifica effettivamente l'input.

I browser non possono utilizzare le normali tecniche di analisi, pertanto i browser creano parser personalizzati per l'analisi del codice HTML.

L'algoritmo di analisi è descritto in dettaglio dalla specifica HTML5. L'algoritmo si compone di due fasi: tokenizzazione e creazione dell'albero.

La tokenizzazione è l'analisi lessicale, che analizza l'input in token. Tra i token HTML ci sono i tag di inizio, i tag finali, i nomi degli attributi e i valori degli attributi.

Il tokenizzatore riconosce il token, lo fornisce al costruttore dell'albero e utilizza il carattere successivo per riconoscere il token successivo e così via fino alla fine dell'input.

Flusso di analisi HTML (tratto dalle specifiche HTML5)
Figura 9: flusso di analisi HTML (tratto dalle specifiche HTML5)

L'algoritmo di tokenizzazione

L'output dell'algoritmo è un token HTML. L'algoritmo è espresso come una macchina a stati. Ogni stato utilizza uno o più caratteri del flusso di input e aggiorna lo stato successivo in base a questi caratteri. La decisione è influenzata dall'attuale stato di tokenizzazione e dallo stato di costruzione dell'albero. Ciò significa che lo stesso carattere utilizzato restituirà risultati diversi per lo stato successivo corretto, a seconda dello stato attuale. L'algoritmo è troppo complesso per essere descritto in modo completo, quindi vediamo un semplice esempio che ci aiuterà a capire il principio.

Esempio di base: tokenizzazione del seguente codice HTML:

<html>
  <body>
    Hello world
  </body>
</html>

Lo stato iniziale è lo "stato dei dati". Quando viene rilevato il carattere <, lo stato viene modificato in "Stato tag aperto". L'utilizzo di un carattere a-z determina la creazione di un "Token tag avvio", lo stato viene modificato in "Stato nome tag". Rimaniamo in questo stato fino a quando non viene consumato il carattere >. Ogni carattere viene aggiunto al nuovo nome del token. Nel nostro caso, il token creato è un token html.

Quando viene raggiunto il tag >, viene emesso il token corrente e lo stato viene ripristinato su "Stato dei dati". Il tag <body> verrà trattato con gli stessi passaggi. Finora sono stati emessi i tag html e body. Ora torniamo allo "stato dei dati". Il consumo del carattere H di Hello world determina la creazione e l'emissione di un token del carattere, fino al raggiungimento del valore < di </body>. Emetteremo un token per ogni carattere di Hello world.

Ora siamo tornati allo "stato di apertura dei tag". L'utilizzo del prossimo input / comporterà la creazione di un end tag token e lo spostamento allo "stato del nome del tag". Rimaniamo di nuovo in questo stato fino a quando non raggiungiamo >.Successivamente, verrà emesso il nuovo token del tag e torneremo allo "stato dei dati". L'input </html> verrà trattato come il caso precedente.

Tokenizzazione dell&#39;input di esempio
Figura 10: tokenizzazione dell'input di esempio

Algoritmo di costruzione degli alberi

Alla creazione dell'analizzatore sintattico, viene creato l'oggetto Document. Durante la fase di creazione dell'albero, verrà modificato l'albero DOM con il documento nella directory radice e verranno aggiunti gli elementi. Ogni nodo emesso dal tokenizzatore verrà elaborato dal costruttore dell'albero. Per ogni token, la specifica definisce l'elemento DOM pertinente e verrà creato per questo token. L'elemento viene aggiunto all'albero DOM e allo stack di elementi aperti. Questo stack viene utilizzato per correggere le mancate corrispondenze nidificate e i tag non chiusi. L'algoritmo viene anche descritto come una macchina a stati. Gli stati sono chiamati "modalità di inserimento".

Vediamo il processo di creazione dell'albero per l'input di esempio:

<html>
  <body>
    Hello world
  </body>
</html>

L'input della fase di creazione dell'albero è una sequenza di token dalla fase di tokenizzazione. La prima è la "modalità iniziale". La ricezione del token "html" determina il passaggio alla modalità "before html" e una rielaborazione del token in questa modalità. Ciò causerà la creazione dell'elemento HTMLHTMLElement, che verrà aggiunto all'oggetto Document principale.

Lo stato verrà modificato in "before head". Viene quindi ricevuto il token "body". Un HTMLHeadElement verrà creato implicitamente, anche se non abbiamo un token "head" e verrà aggiunto alla struttura ad albero.

Ora passiamo alla modalità "in head" e poi a "after head". Il token del corpo viene rielaborato, un HTMLBodyElement viene creato e inserito e la modalità viene trasferita in "in body".

I token di caratteri della stringa "Hello world" vengono ora ricevuti. Il primo causerà la creazione e l'inserimento di un nodo "Text" e gli altri caratteri verranno aggiunti a questo nodo.

La ricezione del token Body-end determina un trasferimento in modalità "after body". Ora riceveremo il tag di fine html che ci sposterà in modalità "after after body". La ricezione del token della fine del file determina l'interruzione dell'analisi.

Struttura ad albero del codice HTML di esempio.
Figura 11: costruzione ad albero dell'HTML di esempio

Azioni al termine dell'analisi

In questa fase il browser contrassegnerà il documento come interattivo e inizierà l'analisi degli script in modalità "differita", ovvero gli script che devono essere eseguiti dopo l'analisi del documento. Lo stato del documento verrà quindi impostato su "complete" e verrà attivato un evento "load".

Puoi vedere gli algoritmi completi per la tokenizzazione e la struttura ad albero nella specifica HTML5.

Tolleranza agli errori del browser

Non ricevi mai l'errore "Sintassi non valida" in una pagina HTML. I browser correggono i contenuti non validi e possono continuare.

Prendiamo ad esempio questo codice HTML:

<html>
  <mytag>
  </mytag>
  <div>
  <p>
  </div>
    Really lousy HTML
  </p>
</html>

Devo aver violato circa un milione di regole ("mytag" non è un tag standard, nidificazione errata degli elementi "p" e "div" e altro ancora), ma il browser lo mostra comunque correttamente e non si lamenta. Gran parte del codice del parser consente di correggere gli errori di creazione dei contenuti HTML.

La gestione degli errori è abbastanza coerente nei browser, ma sorprendentemente non ha fatto parte delle specifiche HTML. Come i preferiti e i pulsanti Indietro/Avanti, è solo una cosa che si è sviluppata nei browser nel corso degli anni. Esistono costrutti HTML non validi noti che vengono ripetuti su molti siti e i browser tentano di correggerli in modo conforme agli altri browser.

La specifica HTML5 definisce alcuni di questi requisiti. WebKit riassume bene questo aspetto nel commento all'inizio della classe del parser HTML.

Il parser analizza l'input tokenizzato nel documento, creando un albero dei documenti. Se il documento è strutturato correttamente, l'analisi è semplice.

Purtroppo dobbiamo gestire molti documenti HTML che non hanno una formattazione corretta, quindi il parser deve tollerare gli errori.

Dobbiamo gestire almeno le seguenti condizioni di errore:

  1. L'elemento aggiunto è espressamente vietato all'interno di un tag esterno. In questo caso, dobbiamo chiudere tutti i tag fino a quello che vieta l'elemento e aggiungerlo in seguito.
  2. Non siamo autorizzati ad aggiungere l'elemento direttamente. È possibile che la persona che ha redatto il documento abbia dimenticato un tag intermedio (o che il tag intermedio sia facoltativo). Questo potrebbe verificarsi con i seguenti tag: HTML HEAD BODY TBODY TR TD LI (ne ho dimenticato?).
  3. Vogliamo aggiungere un elemento blocco all'interno di un elemento in linea. Chiudi tutti gli elementi incorporati fino all'elemento di blocco successivo più alto.
  4. Se il problema persiste, chiudi gli elementi finché non possiamo aggiungerli oppure ignora il tag.

Vediamo alcuni esempi di tolleranza agli errori di WebKit:

</br> anziché <br>

Alcuni siti utilizzano </br> anziché <br>. Per essere compatibile con IE e Firefox, WebKit tratta questo aspetto come <br>.

Il codice:

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
     reportError(MalformedBRError);
     t->beginTag = true;
}

Tieni presente che la gestione degli errori è interna: non verrà presentata all'utente.

Una tabella vagante

Una tabella falsa è una tabella all'interno di un'altra tabella, ma non all'interno di una cella di tabella.

Ad esempio:

<table>
  <table>
    <tr><td>inner table</td></tr>
  </table>
  <tr><td>outer table</td></tr>
</table>

WebKit modificherà la gerarchia in due tabelle di pari livello:

<table>
  <tr><td>outer table</td></tr>
</table>
<table>
  <tr><td>inner table</td></tr>
</table>

Il codice:

if (m_inStrayTableContent && localName == tableTag)
        popBlock(tableTag);

WebKit utilizza uno stack per i contenuti degli elementi correnti: la tabella interna verrà estratta dalla pila esterna. Le tabelle ora saranno gemelle.

Elementi dei moduli nidificati

Se l'utente inserisce un modulo all'interno di un altro, il secondo modulo viene ignorato.

Il codice:

if (!m_currentFormElement) {
        m_currentFormElement = new HTMLFormElement(formTag,    m_document);
}

Gerarchia tag troppo profonda

Il commento parla da solo.

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
{

unsigned i = 0;
for (HTMLStackElem* curr = m_blockStack;
         i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
     curr = curr->next, i++) { }
return i != cMaxRedundantTagDepth;
}

HTML o tag di chiusura del corpo non inseriti

Di nuovo: il commento parla da solo.

if (t->tagName == htmlTag || t->tagName == bodyTag )
        return;

Pertanto, se non vuoi apparire come esempio in uno snippet di codice di tolleranza agli errori di WebKit, fai attenzione se scrivi codice HTML con una formattazione corretta.

Analisi CSS

Ricordi i concetti di analisi presenti nell'introduzione? A differenza dell'HTML, il CSS è una grammatica senza contesto e può essere analizzato utilizzando i tipi di parser descritti nell'introduzione. Infatti la specifica CSS definisce la grammatica lessicale e della sintassi del CSS.

Vediamo alcuni esempi:

La grammatica lessicale (vocabolario) è definita da espressioni regolari per ciascun token:

comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num       [0-9]+|[0-9]*"."[0-9]+
nonascii  [\200-\377]
nmstart   [_a-z]|{nonascii}|{escape}
nmchar    [_a-z0-9-]|{nonascii}|{escape}
name      {nmchar}+
ident     {nmstart}{nmchar}*

"ident" è l'acronimo di identificatore, come il nome di una classe. "name" è un ID elemento (indicato da "#")

La grammatica della sintassi è descritta in BNF.

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
selector
  : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
  ;
simple_selector
  : element_name [ HASH | class | attrib | pseudo ]*
  | [ HASH | class | attrib | pseudo ]+
  ;
class
  : '.' IDENT
  ;
element_name
  : IDENT | '*'
  ;
attrib
  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
    [ IDENT | STRING ] S* ] ']'
  ;
pseudo
  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
  ;

Spiegazione:

Un set di regole ha la seguente struttura:

div.error, a.error {
  color:red;
  font-weight:bold;
}

div.error e a.error sono selettori. La parte all'interno delle parentesi graffe contiene le regole applicate da questa serie di regole. Questa struttura è definita formalmente in questa definizione:

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;

Questo significa che un set di regole è un selettore o, facoltativamente, un numero di selettori separati da virgola e spazi (S sta per lo spazio vuoto). Una serie di regole contiene parentesi graffe e al suo interno una dichiarazione o, facoltativamente, un numero di dichiarazioni separate da punto e virgola. "declaration" e "selector" saranno definiti nelle seguenti definizioni di BNF.

Parser CSS WebKit

WebKit utilizza generatori di parser Flex and Bison per creare automaticamente parser dai file grammaticali CSS. Come ricorderai dall'introduzione del parser, Bison crea un parser con riduzione Maiusc e Bison dal basso. Firefox utilizza un parser dall'alto verso il basso scritto manualmente. In entrambi i casi, ogni file CSS viene analizzato in un oggetto StyleSheet. Ogni oggetto contiene regole CSS. Gli oggetti delle regole CSS contengono oggetti selettore e dichiarazione e altri oggetti corrispondenti alla grammatica CSS.

Analisi del CSS.
Figura 12: analisi dei CSS

Ordine di elaborazione per script e fogli di stile

Script

Il modello del web è sincrono. Gli autori si aspettano che gli script vengano analizzati ed eseguiti immediatamente quando il parser raggiunge un tag <script>. L'analisi del documento si interrompe finché lo script non viene eseguito. Se lo script è esterno, la risorsa deve prima essere recuperata dalla rete. L'operazione viene eseguita in modalità sincrona e l'analisi si interrompe finché non viene recuperata la risorsa. Questo è stato il modello per molti anni ed è specificato anche nelle specifiche HTML4 e 5. Gli autori possono aggiungere l'attributo "defer" a uno script. In questo caso, l'analisi del documento non verrà interrotta e verrà eseguita al termine dell'analisi del documento. HTML5 aggiunge un'opzione per contrassegnare lo script come asincrono in modo che venga analizzato ed eseguito da un altro thread.

Analisi speculativa

Sia WebKit che Firefox eseguono questa ottimizzazione. Durante l'esecuzione degli script, un altro thread analizza il resto del documento, scopre quali altre risorse devono essere caricate dalla rete e le carica. In questo modo è possibile caricare le risorse su connessioni parallele e migliorare la velocità complessiva. Nota: il parser speculativo analizza solo i riferimenti a risorse esterne come script esterni, fogli di stile e immagini e non modifica l'albero DOM, lasciato all'analizzatore sintattico principale.

Fogli di stile

I fogli di stile, invece, hanno un modello diverso. A livello concettuale, sembra che, poiché i fogli di stile non modificano l'albero DOM, non c'è motivo di aspettare e interrompere l'analisi del documento. Tuttavia, si è verificato un problema con gli script che richiedono informazioni sullo stile durante la fase di analisi del documento. Se lo stile non è stato ancora caricato e analizzato, lo script riceverà risposte errate e apparentemente questo ha causato molti problemi. Sembra essere un caso limite, ma è abbastanza comune. Firefox blocca tutti gli script se è ancora presente un foglio di stile in fase di caricamento e analisi. WebKit blocca gli script solo quando tentano di accedere a determinate proprietà di stile che potrebbero essere interessate dai fogli di stile non caricati.

Esegui il rendering della costruzione di alberi

Durante la creazione dell'albero DOM, il browser genera un altro albero, quello di rendering. Questa struttura ad albero contiene elementi visivi nell'ordine in cui verranno visualizzati. È la rappresentazione visiva del documento. Lo scopo di questo albero è consentire di dipingere i contenuti nell'ordine corretto.

Firefox chiama gli elementi nell'albero di rendering "frame". WebKit utilizza il termine renderer o oggetto di rendering.

Un renderer sa come impaginare e dipingere se stesso e i relativi figli.

La classe RenderObject di WebKit, la classe base dei renderer, ha la seguente definizione:

class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}

Ogni renderer rappresenta un'area rettangolare di solito corrispondente alla casella CSS di un nodo, come descritto dalla specifica CSS2. Include informazioni geometriche come larghezza, altezza e posizione.

Il tipo di casella è influenzato dal valore "display" dell'attributo di stile pertinente per il nodo (consulta la sezione calcolo dello stile). Ecco il codice WebKit per decidere quale tipo di renderer creare per un nodo DOM, in base all'attributo di visualizzazione:

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;

    switch (style->display()) {
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM:
            o = new (arena) RenderListItem(node);
            break;
       ...
    }

    return o;
}

Viene preso in considerazione anche il tipo di elemento: ad esempio, i controlli del modulo e le tabelle hanno frame speciali.

Se in WebKit un elemento vuole creare un renderer speciale, sostituisce il metodo createRenderer(). I renderer puntano agli oggetti di stile che contengono informazioni non geometriche.

La relazione dell'albero di rendering con l'albero DOM

I renderer corrispondono a elementi DOM, ma la relazione non è uno a uno. Gli elementi DOM non visivi non verranno inseriti nell'albero di rendering. Un esempio è l'elemento "head". Inoltre, gli elementi il cui valore di visualizzazione è stato assegnato a "nessuno" non verranno visualizzati nella struttura ad albero (mentre gli elementi con visibilità "nascosto" verranno visualizzati nella struttura).

Esistono elementi DOM che corrispondono a diversi oggetti visivi. Di solito si tratta di elementi con una struttura complessa che non può essere descritta da un singolo rettangolo. Ad esempio, l'elemento "select" ha tre renderer: uno per l'area di visualizzazione, uno per la casella di elenco a discesa e uno per il pulsante. Inoltre, quando il testo è suddiviso in più righe perché la larghezza non è sufficiente per una riga, le nuove righe vengono aggiunte come renderer extra.

Un altro esempio di più renderer è l'HTML non funzionante. In base alla specifica CSS, un elemento in linea deve contenere solo elementi di blocco o solo elementi in linea. Nel caso di contenuti misti, verranno creati renderer a blocchi anonimi per aggregare gli elementi incorporati.

Alcuni oggetti di rendering corrispondono a un nodo DOM ma non nella stessa posizione nell'albero. I Float e gli elementi posizionati in modo assoluto non vengono visualizzati, vengono posizionati in una parte diversa dell'albero e mappati al frame reale. Un frame segnaposto è il punto in cui avrebbe dovuto essere.

L&#39;albero di rendering e il corrispondente albero DOM.
Figura 13: l'albero di rendering e l'albero DOM corrispondente. Il "Viewport" è il blocco contenitore iniziale. In WebKit sarà l'oggetto "RenderView".

Il flusso di costruzione dell'albero

In Firefox, la presentazione viene registrata come listener per gli aggiornamenti DOM. La presentazione delega la creazione di frame a FrameConstructor e il costruttore risolve lo stile (vedi calcolo dello stile) e crea un frame.

In WebKit, il processo di risoluzione dello stile e di creazione di un renderer è chiamato "allegato". Ogni nodo DOM ha un metodo "attach". L'allegato è sincrono, l'inserimento dei nodi nell'albero DOM chiama il nuovo metodo "attach" del nodo.

L'elaborazione dei tag HTML e body determina la costruzione della radice dell'albero di rendering. L'oggetto di rendering principale corrisponde a ciò che la specifica CSS chiama il blocco contenente: il blocco più in alto che contiene tutti gli altri blocchi. Le sue dimensioni corrispondono all'area visibile, ovvero le dimensioni dell'area di visualizzazione della finestra del browser. Firefox la chiama ViewPortFrame, mentre WebKit la chiama RenderView. Si tratta dell'oggetto di rendering a cui rimanda il documento. Il resto dell'albero è costruito come inserimento di nodi DOM.

Consulta le specifiche CSS2 sul modello di elaborazione.

Calcolo dello stile

Per creare l'albero di rendering è necessario calcolare le proprietà visive di ciascun oggetto di rendering. Ciò viene fatto calcolando le proprietà di stile di ciascun elemento.

Lo stile include fogli di stile di varie origini, elementi di stile incorporati e proprietà visive nel codice HTML (come la proprietà "bgcolor").Successivamente viene tradotto in proprietà di stile CSS corrispondenti.

Le origini dei fogli di stile sono i fogli di stile predefiniti del browser, i fogli di stile forniti dall'autore della pagina e i fogli di stile utente. Si tratta di fogli di stile forniti dall'utente del browser (i browser consentono di definire gli stili preferiti). In Firefox, ad esempio, questo viene fatto inserendo un foglio di stile nella cartella "Profilo Firefox".

Il calcolo dello stile presenta alcune difficoltà:

  1. I dati di stile sono una struttura di grandi dimensioni che include numerose proprietà di stile e questo può causare problemi di memoria.
  2. Individuare le regole di corrispondenza per ogni elemento può causare problemi di rendimento se non è ottimizzato. Controllare l'intero elenco di regole per ogni elemento al fine di trovare corrispondenze è un'attività complessa. I selettori possono avere una struttura complessa che può far sì che il processo di corrispondenza inizi su un percorso apparentemente promettente che è dimostrato inutile e occorre provare un altro percorso.

    Ad esempio, questo selettore composto:

    div div div div{
    ...
    }
    

    Significa che le regole si applicano a un <div> che è il discendente di 3 div. Supponi di voler verificare se la regola si applica a un determinato elemento <div>. Scegli un determinato percorso fino all'albero da controllare. Potrebbe essere necessario attraversare la struttura ad albero dei nodi per scoprire che sono presenti solo due div e la regola non si applica. Poi devi provare altri percorsi nell'albero.

  3. L'applicazione delle regole comporta l'applicazione di regole a cascata piuttosto complesse che definiscono la gerarchia delle regole.

Vediamo in che modo i browser affrontano questi problemi:

Condivisione dei dati relativi allo stile

I nodi WebKit fanno riferimento a oggetti di stile (RenderStyle). Questi oggetti possono essere condivisi dai nodi in alcune condizioni. I nodi sono fratelli o cugini e:

  1. Gli elementi devono trovarsi nello stesso stato del mouse (ad es.uno non può essere in :hover e l'altro no).
  2. Nessuno dei due elementi deve avere un ID
  3. I nomi dei tag devono corrispondere
  4. Gli attributi di classe devono corrispondere
  5. L'insieme di attributi mappati deve essere identico
  6. Gli stati del collegamento devono corrispondere
  7. Gli stati di impostazione dello stato attivo devono corrispondere
  8. Nessuno dei due elementi deve essere interessato dai selettori di attributi, dove interessato è definito come la corrispondenza di un selettore che utilizza un selettore di attributo in qualsiasi posizione all'interno del selettore.
  9. Gli elementi non devono avere attributi di stile incorporati
  10. Non devono essere in uso selettori di pari livello. WebCore attiva semplicemente un'opzione globale quando viene rilevato un selettore di pari livello e, se presente, disattiva la condivisione dello stile per l'intero documento. Sono inclusi il selettore + e selettori come :primo figlio e :ultimo figlio.

Albero delle regole di Firefox

Firefox ha due alberi aggiuntivi per semplificare il calcolo dello stile: l'albero delle regole e l'albero di contesto degli stili. Inoltre, WebKit contiene oggetti di stile che però non sono archiviati in una struttura ad albero come la struttura ad albero del contesto degli stili, ma solo il nodo DOM punta allo stile pertinente.

Albero di contesto in stile Firefox.
Figura 14: struttura ad albero del contesto in stile Firefox.

I contesti di stile contengono valori finali. I valori vengono calcolati applicando tutte le regole di corrispondenza nell'ordine corretto ed eseguendo manipolazioni che li trasformano da valori logici in valori concreti. Ad esempio, se il valore logico è una percentuale della schermata, verrà calcolato e trasformato in unità assolute. L'idea dell'albero delle regole è davvero intelligente. Consente la condivisione di questi valori tra i nodi per evitare di calcolarli di nuovo. In questo modo, puoi risparmiare spazio.

Tutte le regole corrispondenti vengono archiviate in una struttura ad albero. I nodi inferiori di un percorso hanno una priorità più elevata. La struttura ad albero contiene tutti i percorsi per le corrispondenze delle regole trovate. La memorizzazione delle regole avviene pigramente. La struttura ad albero non viene calcolata all'inizio per ogni nodo, ma ogni volta che è necessario calcolare uno stile di nodo, i percorsi calcolati vengono aggiunti alla struttura.

L'idea è quella di vedere i percorsi degli alberi come parole in un lessico. Supponiamo di aver già calcolato questo albero delle regole:

Albero delle regole calcolato
Figura 15: albero delle regole calcolate.

Supponiamo di dover trovare una corrispondenza con le regole per un altro elemento della struttura ad albero dei contenuti e di scoprire che le regole corrispondenti (nell'ordine corretto) sono B-E-I. Questo percorso è già presente nell'albero perché abbiamo già calcolato il percorso A-B-E-I-L. Ora avremo meno lavoro da fare.

Vediamo come l'albero ci fa risparmiare lavoro.

Divisione in struct

I contesti degli stili sono divisi in struct. Questi struct contengono informazioni sullo stile di una determinata categoria, ad esempio bordo o colore. Tutte le proprietà di uno struct vengono ereditate o non ereditate. Le proprietà ereditate sono proprietà che, a meno che non siano definite dall'elemento, vengono ereditate dall'elemento principale. Le proprietà non ereditate (chiamate proprietà di "reimpostazione") utilizzano valori predefiniti se non sono definite.

La struttura ad albero ci aiuta memorizzando nella cache interi struct (contenenti i valori finali calcolati) nell'albero. L'idea è che se il nodo inferiore non ha fornito una definizione per uno struct, è possibile utilizzare uno struct memorizzato nella cache in un nodo superiore.

Calcolo dei contesti degli stili mediante l'albero delle regole

Per calcolare il contesto di stile di un determinato elemento, innanzitutto calcoliamo un percorso nell'albero delle regole oppure ne utilizziamo uno esistente. Dopodiché iniziamo ad applicare le regole nel percorso per compilare gli struct nel nuovo contesto dello stile. Iniziamo dal nodo inferiore del percorso, quello con la precedenza più alta (di solito il selettore più specifico) e attraversiamo l'albero fino a quando lo struct è pieno. Se non esiste alcuna specifica per lo struct in quel nodo della regola, possiamo ottimizzare notevolmente il processo, risalendo l'albero finché non troviamo un nodo che lo specifica completamente e lo puntiamo su di esso. Questa è l'ottimizzazione migliore, in quanto viene condiviso l'intero struct. Ciò consente di risparmiare il calcolo dei valori finali e della memoria.

Se troviamo definizioni parziali, saliamo nell'albero fino a quando lo struct non viene riempito.

Se non abbiamo trovato alcuna definizione per lo struct, nel caso in cui lo struct sia di tipo "ereditato", indirizziamo lo struct dello struct padre nella struttura del contesto. In questo caso siamo riusciti anche a condividere gli struct. Se si tratta di uno struct reimpostato, verranno utilizzati i valori predefiniti.

Se il nodo più specifico aggiunge valori, dobbiamo fare alcuni calcoli aggiuntivi per trasformarlo in valori effettivi. Quindi, il risultato viene memorizzato nella cache nel nodo della struttura ad albero in modo che possa essere utilizzato dagli elementi secondari.

Nel caso in cui un elemento abbia un elemento di pari livello o un elemento brother che punta allo stesso nodo dell'albero, l'intero contesto di stile può essere condiviso tra loro.

Vediamo un esempio: supponiamo di avere questo codice HTML

<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>

E le seguenti regole:

div {margin: 5px; color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

Per semplificare le cose, supponiamo di dover compilare solo due struct: il colore struct e il margine struct. Lo struct colore contiene un solo membro: lo struct margine contiene i quattro lati.

La struttura ad albero delle regole risultante sarà simile a questa (i nodi sono contrassegnati con il nome del nodo: il numero della regola a cui puntano):

L&#39;albero delle regole
Figura 16: albero delle regole

La struttura ad albero del contesto sarà simile a questa (nome nodo: nodo della regola a cui puntano):

La struttura ad albero del contesto.
Figura 17: struttura ad albero del contesto

Supponiamo di analizzare il codice HTML e di arrivare al secondo tag <div>. Dobbiamo creare un contesto di stile per questo nodo e compilare i relativi struct di stile.

Abbiniamo le regole e scopriremo che le regole di corrispondenza per <div> sono 1, 2 e 6. Questo significa che nell'albero è già presente un percorso che l'elemento può utilizzare e che dobbiamo solo aggiungere un altro nodo per la regola 6 (nodo F nell'albero delle regole).

Creeremo un contesto di stile e lo inseriremo nella struttura ad albero. Il nuovo contesto dello stile punterà al nodo F nell'albero delle regole.

Ora dobbiamo compilare gli struct dello stile. Inizieremo compilando lo struct margin. Poiché l'ultimo nodo della regola (F) non si aggiunge allo struct margin, possiamo salire nella struttura ad albero finché non troviamo uno struct memorizzato nella cache calcolato in un inserimento del nodo precedente e lo utilizziamo. Lo troveremo sul nodo B, ovvero il nodo più alto che specificava le regole di margine.

Abbiamo una definizione dello struct color, quindi non possiamo utilizzare uno struct memorizzato nella cache. Poiché il colore ha un attributo, non è necessario salire sull'albero per compilare altri attributi. Calcoleremo il valore finale (converti la stringa in RGB e così via) e memorizzeremo nella cache lo struct calcolato su questo nodo.

Il lavoro sul secondo elemento <span> è ancora più facile. Corrisponderemo alle regole e giungeremo alla conclusione che punta alla regola G, come nell'intervallo precedente. Dato che abbiamo dei fratelli che puntano allo stesso nodo, possiamo condividere l'intero contesto dello stile e semplicemente puntare a quello dell'intervallo precedente.

Per gli struct che contengono regole ereditate dall'elemento padre, la memorizzazione nella cache viene eseguita nella struttura ad albero del contesto (la proprietà colore viene in realtà ereditata, ma Firefox la considera reimpostata e la memorizza nella cache nella struttura ad albero delle regole).

Ad esempio, se abbiamo aggiunto regole per i caratteri in un paragrafo:

p {font-family: Verdana; font size: 10px; font-weight: bold}

Quindi l'elemento paragrafo, che è un elemento secondario del div nell'albero del contesto, avrebbe potuto condividere lo stesso struct di carattere del suo elemento principale. Questo avviene se non è stata specificata alcuna regola per i caratteri per il paragrafo.

In WebKit, che non dispone di un albero delle regole, le dichiarazioni con corrispondenze vengono elaborate quattro volte. Vengono applicate prima le proprietà con priorità elevata non importanti (proprietà che devono essere applicate per prime perché le altre dipendono da esse, ad esempio gli annunci display), poi le regole con priorità elevata importante, infine le regole con priorità normale non importante e infine le regole con priorità normale importante. Ciò significa che le proprietà che appaiono più volte verranno risolte secondo l'ordine a cascata corretto. Vince gli ultimi.

Quindi, per riassumere: la condivisione degli oggetti di stile (tutti o alcuni degli struct al loro interno) risolve i problemi 1 e 3. Anche la struttura ad albero delle regole di Firefox aiuta ad applicare le proprietà nell'ordine corretto.

Modifica delle regole per una facile corrispondenza

Esistono diverse origini per le regole di stile:

  1. Regole CSS, in fogli di stile esterni o in elementi di stile. css p {color: blue}
  2. Attributi di stile incorporati come html <p style="color: blue" />
  3. Attributi visivi HTML (che sono mappati a regole di stile pertinenti) html <p bgcolor="blue" /> Gli ultimi due vengono abbinati facilmente all'elemento, poiché quest'ultimo è proprietario degli attributi di stile, mentre quelli HTML possono essere mappati utilizzando l'elemento come chiave.

Come indicato in precedenza nel numero 2, la corrispondenza della regola CSS può essere più complicata. Per risolvere il problema, le regole vengono manipolate per facilitare l'accesso.

Dopo aver analizzato il foglio di stile, le regole vengono aggiunte a una delle numerose mappe hash, in base al selettore. Esistono mappe per ID, per nome classe e per nome tag, oltre a una mappa generale per tutti gli elementi che non rientrano in quelle categorie. Se il selettore è un ID, la regola verrà aggiunta alla mappa degli ID, se si tratta di una classe verrà aggiunta alla mappa delle classi e così via.

Questa manipolazione semplifica molto l'abbinamento delle regole. Non è necessario cercare in ogni dichiarazione: possiamo estrarre le regole pertinenti per un elemento dalle mappe. Questa ottimizzazione elimina più del 95% delle regole, in modo che non debbano essere prese in considerazione durante il processo di corrispondenza(4.1).

Vediamo ad esempio le seguenti regole di stile:

p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}

La prima regola verrà inserita nella mappa della classe. Il secondo nella mappa ID e il terzo nella mappa tag.

Per il seguente frammento HTML;

<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>

Innanzitutto, cercheremo di trovare le regole per l'elemento p. La mappa delle classi conterrà una chiave "error" in base alla quale viene trovata la regola per "p.error". L'elemento div avrà regole pertinenti nella mappa ID (la chiave è l'ID) e nella mappa tag. Quindi l'unico lavoro che rimane da fare è scoprire quali regole estratte dalle chiavi corrispondono davvero.

Ad esempio, se la regola per il div era:

table div {margin: 5px}

Verrà comunque estratto dalla mappa tag perché la chiave è il selettore più a destra, ma non corrisponderà al nostro elemento div, che non ha un predecessore della tabella.

Sia WebKit che Firefox eseguono questa manipolazione.

Ordine a cascata dei fogli di stile

L'oggetto style ha proprietà che corrispondono a ogni attributo visivo (tutti gli attributi CSS, ma più generici). Se la proprietà non è definita da nessuna delle regole corrispondenti, alcune proprietà possono essere ereditate dall'oggetto di stile dell'elemento principale. Altre proprietà hanno valori predefiniti.

Il problema inizia quando c'è più di una definizione: ecco l'ordine a cascata per risolverlo.

Una dichiarazione per una proprietà di stile può essere visualizzata in diversi fogli di stile e diverse volte all'interno di un foglio di stile. Ciò significa che l'ordine di applicazione delle regole è molto importante. Questo è chiamato ordine delle "cascate". Secondo le specifiche CSS2, l'ordine di sequenza è (dal più basso al più alto):

  1. Dichiarazioni del browser
  2. Dichiarazioni normali dell'utente
  3. Dichiarazioni normali dell'autore
  4. Autore dichiarazioni importanti
  5. Dichiarazioni importanti dell'utente

Le dichiarazioni del browser sono meno importanti e l'utente sostituisce l'autore solo se la dichiarazione è stata contrassegnata come importante. Le dichiarazioni con lo stesso ordine verranno ordinate in base alla specificità e successivamente all'ordine in cui vengono specificate. Gli attributi visivi HTML vengono tradotti in dichiarazioni CSS corrispondenti . Vengono trattate come regole di autore con priorità bassa.

Specificità

La specificità del selettore è definita dalla specifica CSS2 come segue:

  1. conteggio 1 se la dichiarazione da cui proviene è un attributo "style" anziché una regola con un selettore, altrimenti 0 (= a)
  2. conteggia il numero di attributi ID nel selettore (= b)
  3. contare il numero di altri attributi e pseudo-classi nel selettore (= c)
  4. contare il numero di nomi di elementi e pseudo-elementi nel selettore (= d)

La concatenazione dei quattro numeri a-b-c-d (in un sistema numerico con una base grande) fornisce la specificità.

La base numerica che devi utilizzare è definita dal numero più alto che hai in una delle categorie.

Ad esempio, se a=14 puoi utilizzare la base esadecimale. Nell'improbabile caso in cui a=17 sia necessaria una base di numeri di 17 cifre. La situazione successiva può verificarsi con un selettore come questo: html body div div p... (17 tag nel tuo selettore... non molto probabile).

Ecco alcuni esempi:

 *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
 li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
 h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
 ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
 li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
 #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
 style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

Ordinamento delle regole

Dopo aver trovato le corrispondenze, le regole vengono ordinate secondo le regole a cascata. WebKit utilizza l'ordinamento a bolle per gli elenchi piccoli e l'ordinamento di unione per quelli più grandi. WebKit implementa l'ordinamento sostituendo l'operatore > per le regole:

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
}

Processo graduale

WebKit utilizza un flag che segnala se tutti i fogli di stile di primo livello (inclusi @imports) sono stati caricati. Se lo stile non è stato caricato completamente durante l'aggiunta, vengono utilizzati segnaposto e questo viene contrassegnato nel documento e verrà ricalcolato una volta caricati i fogli di stile.

Layout

Quando viene creato e aggiunto alla struttura ad albero, il renderer non dispone di posizione e dimensioni. Il calcolo di questi valori si chiama layout o adattamento dinamico del contenuto.

L'HTML utilizza un modello di layout basato sul flusso, il che significa che il più delle volte è possibile calcolare la geometria in un'unica passaggio. Gli elementi successivi "nel flusso" di solito non influenzano la geometria degli elementi precedenti "nel flusso ", quindi il layout può procedere da sinistra a destra e dall'alto verso il basso nel documento. Ci sono eccezioni: ad esempio, le tabelle HTML possono richiedere più di un passaggio.

Il sistema di coordinate è relativo al frame principale. Vengono utilizzate le coordinate in alto e a sinistra.

Layout è un processo ricorsivo. Inizia dal renderer principale, che corrisponde all'elemento <html> del documento HTML. Il layout continua in modo ricorsivo in parte o in tutta la gerarchia dei frame, elaborando informazioni geometriche per ogni renderer che la richiede.

La posizione del renderer principale è 0,0 e le sue dimensioni corrispondono all'area visibile, ovvero la parte visibile della finestra del browser.

Tutti i renderer utilizzano un metodo "layout" o "reflow" e ogni renderer richiama il metodo di layout dei file secondari che necessitano di un layout.

Sistema di bit sporchi

Per non realizzare un layout completo per ogni piccola modifica, i browser utilizzano un sistema "sporco". Un renderer modificato o aggiunto contrassegna se stesso e i relativi elementi secondari come "sporco": richiede layout.

Sono presenti due flag: "dirty" e "children are dirty": questo significa che, sebbene il renderer stesso possa essere corretto, ha almeno un elemento secondario che necessita di un layout.

Layout globale e incrementale

Il layout può essere attivato sull'intero albero di rendering e si tratta di un layout "globale". Ciò può verificarsi a causa di:

  1. Una modifica globale dello stile che interessa tutti i renderer, ad esempio una modifica delle dimensioni del carattere.
  2. In seguito al ridimensionamento dello schermo

Il layout può essere incrementale, verranno configurati solo i renderer sporchi (ciò può causare alcuni danni che richiedono layout aggiuntivi).

Il layout incrementale viene attivato (in modo asincrono) quando i renderer sono "sporchi". Ad esempio, quando vengono aggiunti nuovi renderer all'albero di rendering dopo che i contenuti aggiuntivi provengono dalla rete e sono stati aggiunti all'albero DOM.

Layout incrementale.
Figura 18: layout incrementale (mostra solo i renderer non sporchi e i relativi elementi secondari)

Layout asincrono e sincrono

Il layout incrementale viene eseguito in modo asincrono. Firefox mette in coda i "comandi di ripetizione flusso" per i layout incrementali e uno scheduler attiva l'esecuzione batch di questi comandi. WebKit dispone inoltre di un timer che esegue un layout incrementale: l'albero viene attraversato e i renderer "sporchi" vengono distribuiti all'esterno.

Gli script che richiedono informazioni sullo stile, come "offsetHeight" possono attivare il layout incrementale in modo sincrono.

Il layout globale viene solitamente attivato in modo sincrono.

A volte il layout viene attivato come callback dopo un layout iniziale perché alcuni attributi, come la posizione dello scorrimento, sono cambiati.

Ottimizzazioni

Quando un layout viene attivato da un "ridimensionamento" o da una modifica della posizione del renderer(e non della dimensione), le dimensioni di rendering vengono ricavate dalla cache e non ricalcolate...

In alcuni casi viene modificato solo una struttura secondaria e il layout non inizia dalla directory principale. Questo può accadere nei casi in cui la modifica sia locale e non influisca sull'ambiente circostante, ad esempio nel caso di testo inserito nei campi di testo (altrimenti ogni sequenza di tasti attiverà un layout a partire dalla radice).

Il processo di layout

Il layout solitamente presenta il seguente pattern:

  1. Il renderer principale determina la propria larghezza.
  2. Il genitore esamina i figli e:
    1. Inserisci il renderer secondario (imposta x e y).
    2. Richiama il layout del bambino se necessario (sono sporchi o siamo in un layout globale o per qualche altro motivo) che calcola l'altezza del bambino.
  3. L'elemento principale utilizza le altezze cumulative dei figli, le altezze dei margini e la spaziatura interna per impostare la propria altezza, che verrà utilizzata dal renderer principale.
  4. Imposta il bit dirty su false.

Firefox utilizza un oggetto "state" (nsHTMLReflowState) come parametro per il layout (denominato "reflow"). Tra gli altri, lo stato include la larghezza principali.

L'output del layout di Firefox è un oggetto "metrics" (nsHTMLReflowMetrics). Conterrà l'altezza calcolata dal renderer.

Calcolo della larghezza

La larghezza del renderer viene calcolata utilizzando la larghezza del blocco contenitore, la proprietà "width" dello stile del renderer, i margini e i bordi.

Ad esempio, la larghezza del seguente div:

<div style="width: 30%"/>

Verrà calcolato da WebKit come segue(metodo RenderBox calcLarghezza della classe):

  • La larghezza del contenitore è la larghezza massima dei contenitori disponibili, che è pari a 0. In questo caso il parametro_availablewidth è il parametro contentwidth, che viene calcolato come segue:
clientWidth() - paddingLeft() - paddingRight()

clientLarghezza e clientHeight rappresentano l'interno di un oggetto, esclusi bordo e barra di scorrimento.

  • La larghezza degli elementi è l'attributo di stile "width". Verrà calcolata come valore assoluto calcolando la percentuale della larghezza del contenitore.

  • Vengono aggiunti i bordi e le spaziature orizzontali.

Finora questo è stato il calcolo della "larghezza preferita". Ora vengono calcolate le larghezze minima e massima.

Se la larghezza preferita è maggiore di quella massima, viene utilizzata la larghezza massima. Se è inferiore alla larghezza minima (l'unità infrangibile più piccola), viene utilizzata la larghezza minima.

I valori vengono memorizzati nella cache nel caso sia necessario un layout, ma la larghezza non cambia.

Interruzione di riga

Quando un renderer al centro di un layout decide di interrompersi, il renderer si interrompe e si propaga all'elemento principale del layout affinché venga interrotto. L'elemento principale crea i renderer aggiuntivi e richiama il layout su di essi.

Quadro

Nella fase di disegno, viene attraversato l'albero di rendering e viene chiamato il metodo "paint()" del renderer per visualizzare i contenuti sullo schermo. La pittura utilizza il componente di infrastruttura dell'interfaccia utente.

Globale e incrementale

Come per il layout, anche la pittura può essere globale (l'intero albero è dipinto) o incrementale. Nella visualizzazione incrementale, alcuni renderer cambiano in modo da non influire sull'intero albero. Il renderer modificato rende non valido il proprio rettangolo sullo schermo. In questo modo il sistema operativo la visualizza come "regione sporca" e genera un evento "paint". Il sistema operativo lo fa in modo intelligente e riunisce diverse regioni in una sola. In Chrome è più complicato perché il renderer ha un processo diverso rispetto a quello principale. Chrome simula il comportamento del sistema operativo in una certa misura. La presentazione ascolta questi eventi e delega il messaggio alla radice di rendering. La struttura ad albero viene attraversata fino a raggiungere il renderer pertinente. Ridipingerà da solo (e solitamente i suoi figli).

L'ordine di pittura

CSS2 definisce l'ordine del processo di colorazione. Questo è l'ordine in cui gli elementi vengono impilati nei contesti di stack. Questo ordine interessa la pittura poiché le pile sono dipinte da dietro in fronte. L'ordine di sovrapposizione di un renderer a blocchi è:

  1. background color
  2. Immagine di sfondo
  3. border
  4. bambini
  5. struttura

Elenco di visualizzazioni di Firefox

Firefox esamina l'albero di rendering e crea un elenco di visualizzazione per il rettangolare dipinto. Contiene i renderer pertinenti per il rettangolare, nell'ordine di visualizzazione corretto (sfondi dei renderer, bordi e così via).

In questo modo, l'albero deve essere attraversato una sola volta per una ricolorazione anziché più volte, ad esempio dipingendo tutti gli sfondi, poi tutte le immagini, poi tutti i bordi e così via.

Firefox ottimizza il processo non aggiungendo elementi nascosti, ad esempio elementi sotto altri elementi opachi.

Spazio di archiviazione rettangolo WebKit

Prima di dipingere, WebKit salva il vecchio rettangolo come bitmap. Dipinge quindi solo il delta tra i rettangoli nuovi e vecchi.

Modifiche dinamiche

I browser tentano di compiere le azioni minime possibili in risposta a un cambiamento. Pertanto, le modifiche al colore di un elemento comportano solo la ricolorazione dell'elemento. Le modifiche alla posizione dell'elemento ne causeranno il layout e la ricomposizione dell'elemento, dei suoi elementi secondari ed eventualmente degli elementi di pari livello. L'aggiunta di un nodo DOM comporterà il layout e la ricolorazione del nodo. Modifiche di grande entità, come l'aumento delle dimensioni dei caratteri dell'elemento "html", comporteranno l'annullamento della convalida delle cache, il relayout e la modifica del layout dell'intero albero.

I thread del motore di rendering

Il motore di rendering è a thread unico. Quasi tutto, ad eccezione delle operazioni di rete, si verifica in un singolo thread. In Firefox e Safari, si tratta del thread principale del browser. In Chrome si tratta del thread principale del processo scheda.

Le operazioni di rete possono essere eseguite da più thread paralleli. Il numero di connessioni parallele è limitato (di solito da 2 a 6).

Loop di eventi

Il thread principale del browser è un loop di eventi. È un ciclo infinito che mantiene attivo il processo. Attende gli eventi, ad esempio eventi di layout e colorazione, e li elabora. Questo è il codice Firefox per il loop dell'evento principale:

while (!mExiting)
    NS_ProcessNextEvent(thread);

Modello visivo CSS2

La tela

Secondo la specifica CSS2, il termine canvas descrive "lo spazio in cui viene visualizzata la struttura di formattazione", ovvero dove il browser colora i contenuti.

Il canvas è infinito per ogni dimensione dello spazio, ma i browser scelgono una larghezza iniziale in base alle dimensioni dell'area visibile.

Secondo www.w3.org/TR/CSS2/zindex.html, la tela è trasparente se contenuta all'interno di un altro e, in caso contrario, viene assegnato un colore definito dal browser.

Modello CSS Box

Il modello di caselle CSS descrive le caselle rettangolari generate per gli elementi nell'albero dei documenti e disposte in base al modello di formattazione visiva.

Ogni casella include un'area dei contenuti (ad es. un testo, un'immagine e così via) e, facoltativamente, spaziatura interna, bordo e margine.

Modello scatola CSS2
Figura 19: modello di casella CSS2

Ogni nodo genera 0...n di queste caselle.

Tutti gli elementi hanno una proprietà "display" che determina il tipo di casella che verrà generato.

Esempi:

block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.

L'impostazione predefinita è in linea, ma il foglio di stile del browser potrebbe impostare altri valori predefiniti. Ad esempio, la visualizzazione predefinita per l'elemento "div" è a blocchi.

Puoi trovare un esempio di foglio di stile predefinito qui: www.w3.org/TR/CSS2/sample.html.

Schema di posizionamento

Sono disponibili tre schemi:

  1. Normale: l'oggetto viene posizionato in base alla sua posizione nel documento. Ciò significa che la sua posizione nell'albero di rendering è quella nell'albero DOM e sarà strutturata in base al tipo e alle dimensioni del riquadro di rendering
  2. Mobile: l'oggetto viene prima posizionato come un flusso normale, quindi spostato il più a sinistra o a destra possibile.
  3. Assoluto: l'oggetto viene posizionato nell'albero di rendering in una posizione diversa rispetto all'albero DOM.

Lo schema di posizionamento è impostato dalla proprietà "position" e dall'attributo "float".

  • statici e relativi causano un flusso normale
  • assoluti e fissi causano un posizionamento assoluto

Nel posizionamento statico non viene definita alcuna posizione; viene utilizzato il posizionamento predefinito. Negli altri schemi, l'autore specifica la posizione: in alto, in basso, a sinistra, a destra.

La disposizione della scatola dipende da:

  • Tipo di casella
  • Dimensioni della confezione
  • Schema di posizionamento
  • Informazioni esterne come le dimensioni dell'immagine e le dimensioni dello schermo

Tipi di caselle

Casella blocco: forma un blocco e ha un proprio rettangolo nella finestra del browser.

Casella di blocco.
Figura 20: casella di blocco

Casella in linea: non ha un blocco proprio, ma si trova all'interno di un blocco contenitore.

Caselle in linea.
Figura 21: caselle incorporate

I blocchi vengono formattati verticalmente uno dopo l'altro. Le righe in linea sono formattate orizzontalmente.

Formattazione a blocchi e in linea.
Figura 22: formattazione a blocchi e in linea

Le caselle in linea vengono inserite all'interno di linee o "caselle di linea". Le linee sono alte almeno quanto il riquadro più alto, ma possono essere più alte, quando i riquadri sono allineati "di base", il che significa che la parte inferiore di un elemento è allineata in un punto di un altro riquadro oltre il fondo. Se la larghezza del contenitore non è sufficiente, le righe verranno posizionate su più righe. Di solito si tratta di ciò che accade in un paragrafo.

Linee.
Figura 23: linee

Posizionamento della

Parente

Posizionamento relativo: posizionato come al solito e poi spostato del delta richiesto.

Posizionamento relativo.
Figura 24: posizionamento relativo

Float

Un riquadro mobile viene spostato a sinistra o a destra di una linea. La caratteristica interessante è che gli altri riquadri scorrono intorno al dispositivo. Il codice HTML:

<p>
  <img style="float: right" src="images/image.gif" width="100" height="100">
  Lorem ipsum dolor sit amet, consectetuer...
</p>

Sarà simile a questo:

Mobile.
Figura 25: numero in virgola mobile

Assoluti e fissi

Il layout viene definito esattamente indipendentemente dal flusso normale. L'elemento non partecipa al flusso normale. Le dimensioni sono relative al contenitore. Nel valore fisso, il contenitore è l'area visibile.

Posizionamento fisso.
Figura 26: posizionamento fisso

Rappresentazione a livelli

Questo valore è specificato dalla proprietà CSS z-index. Rappresenta la terza dimensione della casella: la sua posizione lungo l'"asse z".

Le caselle sono suddivise in stack (chiamati contesti di impilamento). In ogni pila, gli elementi posteriori verranno dipinti per primi e quelli posteriori in alto, più vicini all'utente. In caso di sovrapposizione, l'elemento più importante nasconderà quello precedente.

Gli stack sono ordinati in base alla proprietà z-index. Le caselle con la proprietà "z-index" formano uno stack locale. L'area visibile ha lo stack esterno.

Esempio:

<style type="text/css">
  div {
    position: absolute;
    left: 2in;
    top: 2in;
  }
</style>

<p>
  <div
    style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
  </div>
  <div
    style="z-index: 1;background-color:green;width: 2in; height: 2in;">
  </div>
</p>

Il risultato sarà questo:

Posizionamento fisso.
Figura 27: posizionamento fisso

Sebbene il div rosso preceda quello verde nel markup e sarebbe stato già dipinto nel flusso regolare, la proprietà z-index è più alta, quindi è più avanti nella pila trattenuta dalla root box.

Risorse

  1. Architettura del browser

    1. Grosskurth, Alan. Architettura di riferimento per i browser web (pdf)
    2. Gupta, Vineet. Come funzionano i browser - Parte 1 - Architettura
  2. Analisi

    1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (noto anche come "Libro del drago"), Addison-Wesley, 1986
    2. Rick Jelliffe. "Bold" e "Beautiful": due nuove bozze di HTML 5.
  3. Firefox

    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
    2. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers (video tech talk di Google)
    3. L. David Baron, Motore di layout di Mozilla
    4. L. David Baron, Documentazione sul sistema Mozilla Style
    5. Chris Waterson, Notes on HTML Reflow
    6. Chris Waterson, Gecko Overview
    7. Alexander Larsson, The life of an HTML HTTP request
  4. WebKit

    1. David Hyatt, Implementazione di CSS(parte 1)
    2. David Hyatt, An Overview of WebCore
    3. David Hyatt, WebCore Rendering
    4. David Hyatt, The FOUC Problem
  5. Specifiche W3C

    1. Specifiche HTML 4.01
    2. Specifiche W3C HTML5
    3. Specifiche dei fogli di stile Cascading Level 2 Revision 1 (CSS 2.1)
  6. Istruzioni per la creazione dei browser

    1. Firefox. https://developer.mozilla.org/Build_Documentation
    2. WebKit. http://webkit.org/building/build.html

Traduzioni

Questa pagina è stata tradotta in giapponese due volte:

Puoi visualizzare le traduzioni ospitate esternamente per coreano e turco.

Grazie a tutti.