Firefox: note sul reflow HTML di Gecko

Firefox: note sul reflow HTML di Gecko

Quella che segue è la traduzione del documento originale della Mozilla Foundation che allo stato attuale ha un valore puramente storico per comprendere l'evoluzione che ha portato Gecko, il motore di rendering di Firefox, ad assumere lo stato attuale nel corso degli anni.

Sguardo d'insieme

Il reflow è il processo con cui viene calcolata la geometria degli oggetti di formattazione del motore di layout. Gli oggetti di formattazione HTML vengono chiamati frame: un frame corrisponde all'informazione geometrica per un singolo elemento nel modello di contenuto. I frame vengono organizzati in una gerarchia che rispecchia la gerarchia di contenimento nel modello di contenuto. Un frame è rettangolare, con larghezza, altezza e un offset rispetto al frame genitore che lo contiene.

Possono essere necessari più frame per rappresentare un singolo elemento del modello di contenuto. Per esempio, il testo può essere diviso in diversi frame, uno per riga. In questo caso il frame primario è il frame che contiene la prima riga di testo, con frame di continuazione (o continuazioni) creati per le righe successive.

L'HTML usa un modello di layout basato sul flusso, il che significa che è possibile calcolare la geometria in un unico passaggio. Gli elementi che vengono dopo nel flusso di solito non influenzano la geometria degli elementi che li precedono, così il layout può procedere da sinistra a destra, dall'alto in basso per tutto il documento. Ci sono alcune eccezioni a questa regola: le tabelle HTML possono richiedere più di un passaggio.

Il modello di layout del box XUL, d'altra parte, è basato sulle restrizioni, il che significa che le preferenze e le restrizioni geometriche degli elementi vicini sono prese in considerazione prima del calcolo finale della geometria degli elementi. Il box è il tipo geometrico primitivo per il modello di layout XUL.

Tutto il reflow HTML, compreso il reflow iniziale, comincia dal frame radice, che corrisponde all'elemento <html> del documento HTML. Il reflow procede in modo ricorsivo attraverso una parte o tutta la gerarchia dei frame, calcolando le informazioni geometriche per ogni oggetto frame che lo richiede. Il reflow può avere l'effetto collaterale di creare una nuova continuazione di frame, per esempio per il testo.

Alcuni reflow sono immediati, in risposta alle azioni dell'utente o degli script (per esempio ridimensionando la finestra o modificando il font del documento). Questi vengono gestiti direttamente dalla Presentation Shell (per esempio nsIPresShell::StyleChangeReflow), influenzando l'intero albero dei frame. Altri reflow sono incrementali e sono gestiti in modo asincrono (per esempio quando il contenuto arriva dalla rete). I reflow incrementali sono messi in coda dalla Presentation Shell attraverso comandi successivi.

Stato del reflow

L'oggetto dello stato del reflow, nsHTMLReflowState, viene usato per passare informazioni sulle restrizioni dai frame genitori ai frame figli. Per esempio un <div> con una data larghezza (impostata attraverso la proprietà CSS width) lo notifica in questo oggetto prima di scendere ai suoi figli.

Quando il reflow comincia, lo stato del reflow radice viene inizializzato con le informazioni sul contenitore di livello superiore per la presentazione del documento (per esempio la larghezza e l'altezza della finestra dell'applicazione). Questo viene passato come argomento al metodo Reflow del frame radice nella gerarchia dei frame.

Ogni frame contenitore costruisce un nuovo oggetto dello stato del reflow (in base al proprio oggetto dello stato del reflow) nel quale il contenitore fa fluire i suoi figli. Molte delle restrizioni nel nuovo stato di reflow vengono calcolate quando viene creato lo stato (per esempio, lo spazio disponibile nel nuovo stato di reflow si calcola sottraendo il bordo e il padding del frame contenitore dallo spazio disponibile dello stato di reflow del genitore).

Motivi del reflow

Tutti i reflow hanno un motivo, che viene conservato dall'oggetto di stato del reflow (e può cambiare, come vedremo). Il motivo del reflow controlla il modo con cui un frame reagisce durante un reflow, ed è uno dei seguenti:

  • Initial, per la prima volta in cui viene creata la gerarchia dei frame. In questo caso, un frame sa che non vi è uno stato residuo da usare per semplificare il calcolo della geometria.

  • Incremental, quando qualcosa cambia nell'albero dei frame (per esempio quando arriva del contenuto dalla rete o uno script manipola il DOM). Un reflow incrementale viene indirizzato ad un singolo frame nella gerarchia dei frame. Durante un reflow incrementale, un frame può supporre che nessuna delle restrizioni calcolate dall'alto (per esempio, la larghezza disponibile) viene modificata. Al contrario, qualcosa all'interno del frame è cambiato, il che può avere un impatto bottom-up nella gerarchia dei frame.

  • Resize, quando il contenitore circostante della gerarchia dei frame cambia. Durante un reflow di ridimensionamento, il frame può supporre che nessuno degli stati interni del frame (per esempio, il testo in un frame di testo) sia cambiato. Al contrario, è accaduto un cambiamento top-down nelle restrizioni del layout.

  • StyleChange, quando l'intera gerarchia dei frame deve essere attraversata per riparare ad un cambio di stile (per esempio una modifica nela dimensione del font).

  • Dirty, quando un frame contenitore ha consolidato diversi reflow Incremental indirizzati ai suoi frame figli.

I reflow Initial, Incremental, Resize, e StyleChange possono essere eseguiti come un immediato reflow globale dalla Presentation Shell:

  • Un reflow iniziale viene eseguito quando la Presentation Shell viene inizializzata per far fluire la gerarchia di frame iniziale.

  • I reflow incrementali vengono eseguiti in massa quando la coda dei reflow incrementali della Presentation Shell viene elaborata in modo asincrono.

  • Un reflow di ridimensionamento viene eseguito quando cambiano le dimensioni della Presentation Shell (per esempio dopo che l'utente ha ridimensionato la finestra).

  • Un reflow del cambio di stile viene eseguito quando cambiano le informazioni di stile globali della Presentation Shell (per esempio con l'aggiunta o la rimozione di un foglio di stile o con una modifica al font).

Un reflow sporco non viene mai eseguito direttamente dalla Presentation Shell. Piuttosto, un reflow sporco viene rilevato quando un reflow incrementale raggiunge il suo frame di destinazione, descritto di seguito.

Metriche del reflow

L'oggetto delle metriche del reflow, nsHTMLReflowMetrics, viene usato per propagare informazioni dai frame figli al genitore. Per esempio, le dimensioni di ogni frame figlio di un <div> senza dimensioni verrebbero passate al frame del <div> attraverso l'oggetto the nsHTMLReflowMetrics.

Reflow incrementale

Sebbene tutti i reflow in Gecko cerchino di riutilizzare quanti più stati esistenti possibile (in questo senso "incrementale") un reflow Incremental corrisponde ad un reflow indirizzato ad un frame singolo nella gerarchia dei frame. Un frame richiede un reflow Incremental (o ne viene richiesto uno per un frame nel mezzo) quando qualcosa nel frame stesso viene modificato.

Scheduling. Per richiedere (o distribuire) un reflow incrementale (per esempio in risposta ad una modifica nel modello del contenuto) viene creato un oggetto comando e passato alla Presentation Shell tramite il metodo nsIPresShell::AppendReflowCommand. La Presentation Shell non elabora il comando immediatamente, ma mette in coda il comando e lo elabora in modo asincrono insieme ad altri comandi di reflow in coda.

Fusione. Come vedremo, il comando di reflow ha un tipo e un frame di destinazione. Comandi di reflow multipli dello stesso tipo e con lo stesso frame di destinazione vengono fusi: la Presentation Shell si rifiuta di aggiungere comandi consecutivi dello stesso tipo e per lo stesso frame alla sua coda. Un chiamante può anche cancellare un comando di reflow in coda (per esempio se il frame di destinazione viene distrutto).

Smistamento. La Presentation Shell elabora la coda di reflow rimuovendo un singolo comando di reflow dalla coda e smistandolo al suo frame di destinazione (ma si veda il lavoro sull'albero di reflow che rimuove diversi comandi dalla coda in una sola volta). Viene creato un percorso dal frame di destinazione a quello radice e memorizzato nel comando di reflow. Viene creato un oggetto di stato del reflow con motivo Incremental, il comando di reflow viene memorizzato nello stato e viene invocato il metodo Reflow del frame radice.

Elaborazione. Il frame radice nota il motivo di reflow Incremental specificato nello stato di reflow, e analizza il percorso contenuto nell'oggetto di comando del reflow. Nello specifico, estrae il frame successivo insieme con il percorso preso dall'oggetto del comando di reflow, crea il suo stato di reflow, anch'esso con motivo Incremental, e invoca il metodo Reflow del frame successivo.

Il reflow incrementale procede in modo ricorsivo lungo la gerarchia dei frame. Ogni frame con il percorso del reflow incrementale (come specificato nell'oggetto del comando di reflow) estrae il frame successivo e smista il reflow verso il basso. Al fine di smistare correttamente il reflow al frame figlio, il frame può aver bisogno di eseguire una riparazione di stato (per esempio, un frame di blocco attraverserà il suo elenco di riga per riparare allo spazio occupato dai frame flottati).

Ad un certo punto il reflow incrementale raggiunge il frame di destinazione. A quel punto il tipo di comando del reflow diventa importante.

  • ContentChanged indica che il contenuto che corrisponde al frame di destinazione è cambiato. Per esempio, il testo associato con un frame di testo è stato modificato. In realtà l'unico frame che risponde a questo tipo di reflow è il frame di blocco. Il frame di blocco tratta questo cambiamento come un 'reflow globale' (ossia come un ridimensionamento). Questo mi fa ritenere che potremmo probabilmente eliminare questa classe.

  • StyleChanged indica che le informazioni di stile relative al frame di destinazione sono cambiate, per esempio con l'aumento delle dimensioni del font. Questo fa in modo che il frame muti il motivo del reflow in StyleChange, che viene propagato in modo ricorsivo all'intero sottoalbero del frame di destinazione.

  • ReflowDirty indica che il frame contenitore ha deciso di fondere vari reflow incrementali indirizzati ai suoi figli in un unico reflow. Il frame contenitore conserva lo stato necessario a determinare quali figli debbano avere il reflow.

  • UserDefined, per "situazioni speciali". Attualmente viene usato solo dal frame del viewport per lo scheduling di un reflow di tutti i frame del viewport posizionati in modo fisso. Dovremmo cercare di eliminarlo.

Un reflow incrementale può danneggiare altre parti della gerarchia dei frame (per esempio, la modifica alla dimensione del font in un paragrafo può allargare o restringere il paragrafo). Un contenitore deve quindi propagare ogni danno causato dal reflow incrementale del frame figlio, possibilmente eseguendo il reflow degli altri figli.

Reflow sporchi

Se avvengono diverse modifiche incrementali nella stessa parte della gerarchia dei frame, è possibile avere diversi reflow Incremental indirizzati ai frame vicini. In questo caso è probabile che i singoli reflow incrementali finiranno col fare del lavoro ridondante. Per esempio, ogni carattere inserito in un input di testo potrebbe generare un reflow incrementale indirizzato al frame di testo. Se dovessimo elaborarli singolarmente, l'input di testo verrebbe renderizzato per ogni inserimento di testo, il che sarebbe dannoso se la progressione di un singolo reflow superasse la velocità di digitazione. Lo scopo del reflow Dirty è di permettere a questi reflow singoli di fondersi in modo intelligente.

Un frame che decide di aver bisogno di un reflow sporco imposta su se stesso lo stato NS_FRAME_IS_DIRTY e quindi chiama il metodo ReflowDirtyChild sul suo frame genitore. In ReflowDirtyChild il frame genitore imposta lo stato NS_FRAME_HAS_DIRTY_CHILD su se stesso, e svolge ogni lavoro necessario a ricordarsi quale figlio è sporco (per esempio, il frame di blocco marca il box di riga come sporco, il quale contiene il frame figlio). Il frame genitore può decidere di fare lo scheduling di un reflow ReflowDirty Incremental indirizzato a se stesso, o delegare il compito al suo genitore. Se decide di delegare, allora imposta lo stato NS_FRAME_IS_DIRTY su se stesso e chiama in modo ricorsivamente ReflowDirtyChild.

Alla fine il reflow ReflowDirty Incremental viene smistato, e arriva al frame contenitore che ne fa lo scheduling. Il frame di destinazione ripara le informazioni conservate (per esempio, il frame di blocco itera attraverso i box di riga sporchi) ed esegue il reflow dei frame figlio sporchi.

Non vi è perdita di informazioni se un reflow ReflowDirty Incremental fonde diversi tipi di reflow incrementali (per esempio un ContentChanged con un StyleChanged)? No, perché questi tipi di reflow non vengono fusi, ma vengono messi in coda nella Presentation Shell.

Interazione HTML e XUL

Come detto prima, HTML e XUL hanno diversi modelli di layout, il primo basato sul flusso e il secondo basato su un modello di restrizioni. Queste differenze vengono mediate da dua classi: nsBoxFrame e nsBoxToBlockAdaptor.

nsBoxFrame

nsBoxFrame è un frame HTML che "circonda" un box XUL. Il suo scopo è quello di convertire i reflow HTML nei loro analoghi box.

nsBoxToBlockAdaptor

nsBoxToBlockAdaptor è un box XUL che circonda un frame di blocco HTML, usato per convertire le modifiche nel layout del box in reflow HTML.

Torna su