Cómo Funcionan Los Navegadores Web
Cómo Funcionan Los Navegadores Web
Prólogo
Este completo manual sobre las operaciones internas de WebKit y Gecko es el resultado de las
extensas investigaciones realizadas por la desarrolladora israelí Tali Garsiel. Durante varios años ha
estado revisando toda la información publicada sobre las características internas de los
navegadores web (consulta la sección Recursos) y ha pasado mucho tiempo leyendo su código
fuente. Tali escribió lo siguiente:
"En los años en los que Internet Explorer acaparaba el 90% del mercado, el navegador se podía
considerar poco más que "caja negra", pero ahora que los navegadores de código abierto dominan
más de la mitad del mercado, es un buen momento para echar un vistazo al interior de los
navegadores y ver lo que esconden. Bueno, lo que esconden millones de líneas de código en
C++..."
Tali ha publicado su investigación en su sitio web, pero como sabíamos que se merecía un público
más amplio, lo hemos publicado aquí también tras hacer algunas modificaciones.
Como desarrolladora web, conocer las características internas de las operaciones de los
navegadores sirve para tomar mejores decisiones y para conocer los motivos que justifican las
prácticas de desarrollo recomendadas. Aunque se trata de un documento bastante extenso, te
recomendamos que dediques un poco de tu tiempo a examinarlo. Ten por seguro que no te
arrepentirás. Paul Irish, Relaciones con desarrolladores de Chrome
Introducción
Los navegadores son probablemente el software más utilizado de todos. En este manual se explica
su funcionamiento interno. Veremos qué ocurre cuando escribes google.com en la barra de
direcciones hasta que la página de Google aparece en la pantalla.
Índice
1. Introducción
2. El motor de renderización
1. Motores de renderización
2. El flujo principal
3. Ejemplos del flujo principal
1. Análisis (general)
1. Gramáticas
3. Traducción
4. Ejemplo de análisis
6. Tipos de analizadores
2. Analizador de HTML
3. DTD de HTML
4. DOM
5. El algoritmo de análisis
6. El algoritmo de tokenización
3. Análisis de CSS
1. Secuencias de comandos
2. Análisis especulativo
3. Hojas de estilo
1. División en estructuras
2. Especificidad
4. Proceso gradual
5. Diseño
4. Optimizaciones
5. El proceso de diseño
7. Salto de línea
6. Pintura
1. Global e incremental
7. Cambios dinámicos
1. Bucle de eventos
1. El elemento canvas
2. Modelo de cajas de CSS
3. Esquema de posicionamiento
4. Tipos de cajas
5. Posicionamiento
1. Relativo
2. Flotante
3. Absoluto y fijo
6. Representación en capas
10. Recursos
La función principal de un navegador es solicitar al servidor los recursos web que elija el usuario y
mostrarlos en una ventana. El recurso suele ser un documento HTML, pero también puede ser un
archivo PDF, una imagen o un objeto de otro tipo. El usuario especifica la ubicación del recurso
mediante el uso de una URI (siglas de Uniform Resource Identifier, identificador uniforme de
recurso).
La forma en la que el navegador interpreta y muestra los archivos HTML se determina en las
especificaciones de CSS y HTML. Estas especificaciones las establece el consorcio W3C (World
Wide Web Consortium), que es la organización de estándares de Internet.
Durante años, los navegadores cumplían solo una parte de las especificaciones y desarrollaban sus
propias extensiones. Esto provocó graves problemas de compatibilidad para los creadores de
contenido web. En la actualidad, la mayoría de los navegadores cumplen las especificaciones en
mayor o menor grado.
Las interfaces de usuario de los distintos navegadores tienen muchos elementos en común. Estos
son algunos de los elementos comunes de las interfaces de usuario:
opciones de marcadores,
un botón para detener la carga de los documentos actuales y otro para volver a cargarlos,
un botón de inicio que permite volver a la página de inicio.
4. Red: es responsable de las llamadas de red, como las solicitudes HTTP. Tiene una interfaz
independiente de la plataforma y realiza implementaciones en segundo plano para cada
plataforma.
El motor de renderización
Motores de renderización
Nuestros navegadores de referencia (Firefox, Chrome y Safari) están basados en dos motores de
renderización. Firefox utiliza Gecko, un motor de renderización propio de Mozilla. Tanto Safari
como Chrome utilizan WebKit.
El flujo principal
El árbol de renderización contiene rectángulos con atributos visuales, como el color y las
dimensiones. Los rectángulos están organizados en el orden en el que aparecerán en la pantalla.
Una vez construido el árbol de renderización, se inicia un proceso de "diseño". Esto significa que a
cada nodo se le asignan las coordenadas exactas del lugar de la pantalla en el que debe aparecer.
La siguiente fase es la de pintura, en la que se recorre el árbol de renderización y se pinta cada uno
de los nodos utilizando la capa de servidor de la interfaz de usuario.
Es importante comprender que se trata de un proceso gradual. Para mejorar la experiencia del
usuario, el motor de renderización intentará mostrar el contenido en pantalla lo antes posible. No
esperará a que se analice el código HTML para empezar a crear y diseñar el árbol de renderización.
Se analizarán y se mostrarán algunas partes del contenido, mientras que se sigue procesando el
resto del contenido que llega de la red.
En las figuras 3 y 4 se puede ver que, aunque WebKit y Gecko utilizan una terminología
ligeramente diferente, el flujo es básicamente el mismo.
Gecko denomina al árbol de elementos formateados visualmente "árbol de marcos". Cada uno de
los elementos es un marco. WebKit utiliza los términos "árbol de renderización" y "objetos de
renderización" en lugar de los anteriores. WebKit utiliza el término "diseño" para colocar los
elementos, mientras que Gecko lo denomina "reflujo". WebKit utiliza el término "asociación" para
conectar los nodos DOM con información visual para crear el árbol de renderización. Una pequeña
diferencia no semántica es que Gecko dispone de una capa extra entre el código HTML y el árbol
de DOM. Esta capa se denomina "depósito de contenido" y está dedicada a la creación de
elementos DOM. Vamos a ver cada una de las partes del flujo:
Análisis (general)
Dado que el análisis es un proceso muy importante del motor del renderización, vamos a verlo de
una forma más detallada. Comencemos por una breve introducción a este proceso.
Analizar un documento significa traducirlo a una estructura que tenga sentido, es decir, algo que el
código pueda comprender y utilizar. El resultado del análisis suele ser un árbol de nodos que
representa la estructura del documento. Este árbol se denomina "árbol de análisis" o "árbol de
sintaxis".
Gramáticas
El análisis se basa en las reglas de sintaxis por las que se rige el documento, es decir, el lenguaje o
el formato en el que está escrito. Todos los formatos que se pueden analizar deben tener una
gramática determinista formada por un vocabulario y unas reglas de sintaxis. Esto se denomina
gramática libre de contexto. Los lenguajes humanos no son de este tipo y, por tanto, no se pueden
analizar con técnicas de análisis convencionales.
El proceso de análisis se puede dividir en dos subprocesos: análisis léxico y análisis sintáctico.
El análisis léxico es el proceso de descomponer los datos de entrada en tokens. Los tokens son el
vocabulario del lenguaje, un conjunto de bloques de construcción válidos. En el lenguaje humano,
equivaldría a todas las palabras que aparecen en el diccionario de un determinado idioma.
Los analizadores normalmente dividen el trabajo entre dos componentes: el analizador léxico (a
veces denominado "tokenizador"), responsable de descomponer los datos de entrada en tokens
válidos, y el analizador normal, responsable de construir el árbol tras analizar la estructura del
documento según las reglas sintácticas del lenguaje. El analizador léxico es capaz de ignorar
caracteres irrelevantes, como espacios en blanco y saltos de línea.
Figura : del documento original al árbol de análisis
Si no coincide con ninguna regla, el analizador almacena el token internamente y sigue solicitando
tokens hasta que encuentra una regla que coincide con todos los tokens almacenados
internamente. Si no encuentra ninguna regla, lanza una excepción. Esto significa que el
documento no se considera válido por tener errores de sintaxis.
Traducción
Ejemplo de análisis
Vocabulario: nuestro lenguaje puede incluir números enteros, el signo más y el signo menos.
Sintaxis:
3. Una expresión está formada por un "término" seguido de una "operación" y de otro
término.
Analicemos la entrada 2 + 3 - 1.
La primera subcadena que coincide con una regla es 2; según la regla 5, es un término. La segunda
coincidencia es 2 + 3, que coincide con la tercera regla (un término seguido de una operación y de
otro término). La siguiente coincidencia solo se utilizará al final de la entrada. 2 + 3 - 1 es una
expresión porque ya sabemos que 2+3 es un término, así que tenemos un término seguido de una
operación y de otro término. 2 + + no coincide con ninguna regla, por lo que no sería una entrada
válida.
INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
Como se puede observar, los números enteros se definen mediante una expresión regular.
Dijimos que un lenguaje se puede analizar mediante analizadores normales si su gramática es una
gramática libre de contexto. Una definición intuitiva de una gramática libre de contexto es una
gramática que se puede expresar completamente en notación de Backus-Naur (BNF). Puedes
consultar una definición formal en este artículo de Wikipedia sobre las gramáticas libres de
contexto.
Tipos de analizadores
Existen dos tipos básicos de analizadores: los descendentes y los ascendentes. Utilizando una
explicación intuitiva, podríamos decir que los analizadores descendentes comprueban la
estructura de nivel superior de la sintaxis e intentan buscar una coincidencia, mientras que los
analizadores ascendentes comienzan con los datos de entrada y los van transformando
gradualmente mediante las reglas sintácticas empezando por el nivel más bajo hasta que se
cumplen las reglas de nivel superior.
Un analizador descendente empieza desde la regla de nivel superior: identifica 2 + 3 como una
expresión. A continuación, identifica 2 + 3 - 1 como expresión (el proceso de identificar la
expresión se desarrolla buscando coincidencias con el resto de las reglas, pero se empieza por la
regla de nivel superior).
El analizador ascendente analiza los datos de entrada hasta que encuentra una coincidencia con
una regla y, a continuación, sustituye la entrada coincidente con la regla. Este proceso continúa
hasta que se analizan todos los datos de entrada. Las expresiones con coincidencias parciales se
colocan en la pila del analizador.
Pila Entrada
2+3-1
término +3-1
expresión -1
operación de la expresión 1
expresión
WebKit utiliza dos generadores de analizadores muy conocidos: Flex, para crear un analizador
léxico, y Bison, para crear un analizador normal (también se conocen como "Lex" y "Yacc"). La
entrada de Flex consiste en un archivo con definiciones de expresiones regulares de los tokens. La
entrada de Bison consiste en las reglas sintácticas del lenguaje en formato BNF.
Analizador de HTML
El trabajo del analizador de HTML es analizar las marcas HTML y organizarlas en un árbol de
análisis.
Existe un formato formal para definir el lenguaje HTML: DTD (definición de tipo de documento);
sin embargo, no es una gramática libre de contexto.
Parece algo extraño a primera vista: el lenguaje HTML es bastante similar al XML. Hay una gran
variedad de analizadores de XML disponibles. Existe una variación XML del lenguaje HTML, el
XHTML, así que, ¿cuál es la diferencia?
La diferencia radica en que el lenguaje HTML es más "permisivo", ya que permite omitir ciertas
etiquetas que se añaden de forma implícita, a veces se omite el principio o el final de las etiquetas,
etc. En conjunto, es una sintaxis "flexible", en oposición a la sintaxis rígida y exigente del lenguaje
XML.
Esta diferencia aparentemente pequeña es, en realidad, abismal. Por un lado, esta es la razón
principal por la que el HTML es tan popular: permite errores y facilita las cosas a los autores de
contenido web. Por otro lado, hace que resulte difícil escribir una gramática formal. En resumen:
el lenguaje HTML no se puede analizar fácilmente mediante analizadores convencionales (porque
no dispone de una gramática libre de contexto) ni mediante analizadores de XML.
DTD DE HTML
La definición de HTML está en formato DTD. Este formato se utiliza para definir lenguajes de la
familia SGML. Contiene definiciones de todos los elementos permitidos, de sus atributos y de su
jerarquía. Como vimos antes, la definición DTD del lenguaje HTML no forma una gramática libre de
contexto.
Existen algunas variaciones de la definición DTD. El modo estricto se ajusta únicamente a las
especificaciones, pero otros modos admiten el marcado utilizado anteriormente por los
navegadores. El objetivo es mantener la compatibilidad con el contenido más antiguo. La
definición DTD estricta actual se encuentra en la siguiente página:
www.w3.org/TR/html4/strict.dtd
DOM
El árbol de salida ("árbol de análisis") está formado por elementos DOM y nodos de atributo. DOM
son las siglas de "Document Object Model" (modelo de objetos del documento). Es la presentación
de objetos del documento HTML y la interfaz de los elementos HTML para el mundo exterior,
como JavaScript.
La raíz del árbol es el objeto Document.
El modelo DOM guarda una relación casi de uno a uno con el marcado. Veamos un ejemplo de
marcado:
<html>
<body>
<p>
Hello World
</p>
</body>
</html>
Al igual que el HTML, la organización W3C ha especificado el modelo DOM. Puedes consultarlo en
la página www.w3.org/DOM/DOMTR. Es una especificación genérica para la manipulación de
documentos. Un módulo específico describe elementos HTML específicos. Puedes consultar las
definiciones HTML en la página www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-
definitions.html.
Cuando digo que el árbol contiene nodos DOM, quiero decir que está construido con elementos
que implementan una de las interfaces DOM. Los navegadores utilizan implementaciones
concretas que tienen otros atributos que el navegador utiliza internamente.
El algoritmo de análisis
Como vimos en las secciones anteriores, el lenguaje HTML no se puede analizar mediante los
analizadores ascendentes y descendentes normales.
2. el hecho de que los navegadores presenten una tolerancia a errores tradicional para
admitir casos bien conocidos de HTML no válido,
Al no poder utilizar las técnicas de análisis normales, los navegadores crean analizadores
personalizados para analizar el código HTML.
El tokenizador reconoce el token, lo envía al constructor del árbol y consume el siguiente carácter
para reconocer el siguiente token, y así sucesivamente hasta llegar al final de los datos.
El algoritmo de tokenización
El algoritmo produce un token HTML. El algoritmo se expresa como una máquina de estado. Cada
estado consume uno o varios caracteres del flujo de entrada y actualiza el siguiente estado de
acuerdo con esos caracteres. La decisión está influenciada por el estado de tokenización actual y
por el estado de construcción del árbol. Esto significa que el mismo carácter consumido producirá
resultados diferentes para el siguiente estado correcto en función del estado actual. El algoritmo
es demasiado complejo para describirlo en su totalidad, así que veremos algunos ejemplos
sencillos que nos ayudarán a comprender el principio.
Ejemplo básico de tokenización del siguiente código HTML:
<html>
<body>
Hello world
</body>
</html>
El estado inicial es el de "estado de datos". Cuando se encuentra el carácter <, el estado cambia a
estado de etiqueta abierta. Al consumir un carácter a-z, se crea el estado "token de etiqueta
inicial" y el estado cambia a estado de nombre de etiqueta. Este estado se mantiene hasta que se
consume el carácter >. Todos los caracteres se añaden al nombre del nuevo token. En nuestro
caso, el nuevo token es un token html.
Al llegar a la etiqueta >, se emite el token actual y el estado cambia a estado de datos. Se siguen
los mismos pasos para la etiqueta <body>. Hasta ahora, se han emitido las etiquetas html y body.
Ahora volvemos al estado de datos. Al consumir el carácter H de Hello world, se crea y se emite un
token de carácter, y así sucesivamente hasta llegar al carácter < de </body>. Se emite un token de
carácter por cada uno de los caracteres de Hello world.
Ahora volvemos al estado de etiqueta abierta. Al consumir la siguiente entrada /, se crea un token
de etiqueta final y se pasa al estado de nombre de etiqueta. De nuevo, el estado se mantiene
hasta llegar a >. A continuación, se emite el token de la nueva etiqueta y se vuelve al estado de
datos. La entrada </html> se tratará como el caso anterior.
Figura : tokenización de la entrada de ejemplo
Veamos cuál sería el proceso de construcción del árbol para los datos de entrada del ejemplo:
<html>
<body>
Hello world
</body>
</html>
A continuación, se reciben los tokens de los caracteres de la cadena "Hello world". El primero hará
que se cree y se inserte el nodo "Text", al que se añadirá el resto de caracteres.
La recepción del token de finalización del cuerpo provocará una transferencia al modo "después
del cuerpo". A continuación, se recibirá la etiqueta HTML final, que hará que se pase al modo
después del cuerpo. Cuando se reciba el final del token del archivo, al análisis finalizará.
Figura :
construcción de árbol del código HTML de ejemplo
Se pueden ver los algoritmos completos para tokenización y la construcción del árbol en la
especificación de HTML5.
Nunca se obtiene un error por "sintaxis no válida" en una página HTML. Los navegadores corrigen
el contenido no válido y siguen.
<html>
<mytag>
</mytag>
<div>
<p>
</div>
</p>
</html>
He debido de infringir un millón de reglas ("mytag" no es una etiqueta estándar, los elementos "p"
y "div" están mal anidados, etc.), pero el navegador sigue mostrándolo correctamente y no
muestra ningún error. Por lo tanto, una gran parte del código del analizador corrige los errores del
autor de contenido HTML.
La gestión de errores es bastante consistente en los navegadores, pero lo más increíble es que no
forma parte de la especificación actual de HTML. Al igual que los marcadores y los botones de
avance y retroceso, es algo que se ha ido desarrollando a lo largo de los años. Hay algunas
construcciones HTML conocidas que no son válidas y que se repiten en muchos sitios. Los
navegadores intentan corregirlas de acuerdo con otros navegadores.
El analizador analiza la entrada tokenizada y la convierte en el documento, lo que crea el árbol del
documento. Si el documento está bien construido, el análisis resulta sencillo.
Lamentablemente, debemos tratar con muchos documentos HTML que no están bien construidos,
por lo que el analizador debe ser tolerante con estos errores.
Se debe tener cuidado, como mínimo, con los siguientes errores:
1. El elemento que se debe añadir está prohibido explícitamente en alguna etiqueta externa.
En este caso, debemos cerrar todas las etiquetas hasta llegar a la que prohíbe el elemento
y añadirlo a continuación.
3. Se quiere añadir un elemento de bloque dentro de un elemento integrado. Hay que cerrar
todos los elementos integrados hasta llegar al siguiente elemento de bloque superior.
4. Si esto no funciona, se deben cerrar elementos hasta que se pueda añadir el elemento o
ignorar la etiqueta.
Algunos sitios utilizan </br> en lugar de <br>. Para poder ser compatible con Internet Explorer y
Firefox, WebKit trata la etiqueta como si fuera <br>.
Código:
reportError(MalformedBRError);
t->beginTag = true;
Tabla perdida
Una tabla perdida es una tabla incluida en el contenido de otra tabla, pero no en una celda.
Ejemplo:
<table>
<table>
<tr><td>inner table</td></tr>
</table>
<tr><td>outer table</td></tr>
</table>
<tr><td>outer table</td></tr>
</table>
<table>
<tr><td>inner table</td></tr>
</table>
Código:
popBlock(tableTag);
WebKit utiliza una pila para el contenido del elemento actual y saca la tabla interna de la pila de la
tabla externa. Las tablas se encontrarán en el mismo nivel de la jerarquía.
if (!m_currentFormElement) {
unsigned i = 0;
return i != cMaxRedundantTagDepth;
}
Se tolera que HTML se rompa totalmente. Nunca cerramos la etiqueta del cuerpo (body), ya que
algunas páginas web cometen el error de cerrarla antes de que haya acabado el documento. Por
eso, nos fijamos en la llamada "end()" para cerrar elementos.
return;
Así que ya sabéis: a menos que queráis aparecer como ejemplo en un fragmento de código de
tolerancia a errores de WebKit, escribid el código HTML correctamente.
Análisis de CSS
¿Os acordáis de los conceptos de análisis que vimos en la introducción? A diferencia del lenguaje
HTML, CSS tiene una gramática libre de contexto y se puede analizar con los tipos de analizadores
descritos en la introducción. De hecho, la especificación del lenguaje CSS define su gramática
sintáctica y léxica.
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}*
ruleset
selector
: simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
simple_selector
class
: '.' IDENT
element_name
: IDENT | '*'
attrib
pseudo
div.error , a.error {
color:red;
font-weight:bold;
"div.error" y "a.error" son selectores. La parte entre llaves contiene las reglas que se aplican a este
conjunto de reglas. Esta estructura se define formalmente en esta definición:
ruleset
WebKit utiliza generadores de analizadores Flex y Bison para crear analizadores automáticamente
a partir de los archivos de gramática de CSS. Como ya dijimos en la introducción a los analizadores,
Bison crea un analizador ascendente de desplazamiento-reducción. Firefox utiliza un analizador
descendente escrito manualmente. En ambos casos, los archivos CSS se analizan y se convierten
en objetos "StyleSheet", cada uno de los cuales contiene reglas de CSS. Los objetos de regla de CSS
contienen objetos de declaraciones y selectores, así como otros objetos relacionados con la
gramática de CSS.
Figura :
análisis de CSS
Secuencias de comandos
El modelo de la Web es síncrono. Los autores esperan que las secuencias de comandos se analicen
y se ejecuten inmediatamente cuando el analizador llega a la etiqueta <script>. El análisis del
documento se detiene hasta que la secuencia de comandos se ejecuta. La secuencia de comandos
es externa, por lo que antes es necesario recuperar el recurso de la red. Esta acción se realiza
también de una forma síncrona, es decir, que el análisis se detiene hasta que se recupera el
recurso. Este modelo se utilizó durante muchos años y está incluido en las especificaciones de
HTML 4 y 5. Los autores pueden marcar la secuencia de comandos como "aplazada". De ese modo,
no se detiene el análisis del documento y la secuencia se ejecuta una vez que se ha completado el
análisis. HTML5 añade una opción que permite marcar la secuencia de comandos como asíncrona
para que se analice y se ejecute en un subproceso diferente.
Análisis especulativo
Tanto WebKit y Firefox utilizan esta optimización. Al ejecutar las secuencias de comandos, otro
subproceso analiza el resto del documento, busca en la red otros recursos necesarios y los carga.
De esta forma, los recursos se pueden cargar mediante conexiones paralelas, lo que mejora la
velocidad general. Nota: el analizador especulativo no modifica el árbol de DOM (de eso se
encarga el analizador principal), solo analiza las referencias a recursos externos, como secuencias
de comandos externas, hojas de estilo e imágenes.
Hojas de estilo
Las hojas de estilo, por otro lado, tienen un modelo diferente. Conceptualmente, parece que, dado
que las hojas de estilo no modifican el árbol de DOM, no hay razón para esperarlas y detener el
análisis del documento. Sin embargo, se produce una incidencia cuando las secuencias de
comandos solicitan información de estilo durante la fase de análisis del documento. Si el estilo aún
no se ha cargado ni analizado, la secuencia de comandos obtendrá respuestas incorrectas y,
aparentemente, esto causará muchas complicaciones. Parece que se trata de un caso extremo,
pero es bastante común. Firefox bloquea todas las secuencias de comandos si todavía se está
cargando y analizando una hoja de estilo. WebKit bloquea las secuencias de comandos solo
cuando intentan acceder a determinadas propiedades de estilo que se pueden ver afectadas por
las hojas de estilo descargadas.
Mientras se está construyendo el árbol DOM, el navegador construye otro árbol: el árbol de
renderización. Este árbol está formado por elementos visuales que se muestran en el mismo orden
en que aparecerán. Es la representación visual del documento. El propósito de este árbol es poder
representar el contenido en el orden correcto.
Firefox denomina a los elementos del árbol de renderización "marcos" (o "frames"). WebKit utiliza
el término "renderizador" u "objeto de renderización" (o "render").
Un renderizador es capaz de representarse a sí mismo y a sus elementos secundarios.
La clase "RenderObject" de WebKit, la clase básica de los renderizadores, tiene la siguiente
definición:
class RenderObject{
Cada renderizador representa un área rectangular que normalmente se corresponde con la caja de
CSS del nodo, según se describe en la especificación de CSS2. Contiene información geométrica
como el ancho, la altura y la posición.
El tipo de caja se ve afectado por el atributo de estilo "display" relevante para el nodo (consulta la
sección Computación de estilos). Este es el código de WebKit que se utiliza para decidir qué tipo
de renderizador se debe crear para un nodo DOM, según el atributo de visualización:
...
RenderObject* o = 0;
switch (style->display()) {
case NONE:
break;
case INLINE:
break;
case BLOCK:
break;
case INLINE_BLOCK:
break;
case LIST_ITEM:
...
return o;
El tipo de elemento también se tiene en cuenta. Por ejemplo, las tablas y los controles de los
formularios tienen marcos especiales.
En WebKit, si un elemento quiere crear un renderizador especial, sobrescribe el método
createRenderer. Los renderizadores apuntan a objetos de estilo que contienen la información no
geométrica.
Los renderizadores corresponden a elementos DOM, pero la relación no es de uno a uno. Los
elementos DOM no visuales no se insertan en el árbol de renderización. Un ejemplo sería el
elemento "head". Los elementos cuyo atributo de visualización se ha asignado a "none" tampoco
aparecen en el árbol (los elementos con el atributo de visibilidad "hidden" sí aparecen en el árbol).
Algunos elementos DOM corresponden a varios objetos visuales. Estos suelen ser elementos con
una estructura compleja que no se pueden describir en un solo rectángulo. Por ejemplo, el
elemento "select" tiene tres renderizadores: uno para el área de visualización, otro para el cuadro
de lista desplegable y otro para el botón. Además, cuando el texto se divide en varias líneas
porque el ancho no es suficiente para una línea, las nuevas líneas se añaden como renderizadores
adicionales.
Otro ejemplo con varios renderizadores sería un código HTML roto. Según la especificación de CSS,
un elemento integrado debe contener únicamente elementos de bloque o elementos integrados.
Si el contenido es mixto, se crean renderizadores de bloques anónimos para incluir los elementos
integrados.
Algunos objetos de renderización corresponden a un nodo DOM de un lugar del árbol diferente.
Los elementos flotantes y aquellos con posición absoluta quedan fuera del flujo, en un lugar
diferente del árbol y asignados al marco real. Deberían haber estado en un marco de marcador.
Figura : el árbol renderizador y el árbol de DOM correspondiente (3.1) La "ventana gráfica" es el
bloque contenedor inicial. En WebKit, será el objeto "RenderView".
Al procesar las etiquetas "html" y "body", se construye la raíz del árbol de renderización. El objeto
de renderización raíz se corresponde con lo que la especificación de CSS denomina "bloque
contenedor", es decir, el bloque superior que contiene el resto de los bloques. Sus dimensiones se
corresponden con la ventana gráfica, es decir, con las dimensiones del área de visualización de la
ventana del navegador. Firefox lo denomina ViewPortFrame y WebKit RenderView. Este es el
objeto de renderización al que apunta el documento. El resto del árbol se construye como una
inserción de nodos DOM.
Computación de estilos
Para crear el árbol de renderización, es necesario calcular las propiedades visuales de cada uno de
los objetos de renderización. Para hacerlo, hay que calcular las propiedades de estilo de cada
elemento.
El estilo incluye hojas de estilo de varios orígenes, elementos de estilo integrados y propiedades
visuales en el código HTML (como la propiedad "bgcolor"). Estas últimas se transforman en las
propiedades de estilo de CSS correspondientes.
Los orígenes de las hojas de estilo son las hojas de estilo predeterminadas del navegador, las
proporcionadas por el autor de la página y las proporcionadas por el usuario del navegador (los
navegadores permiten al usuario definir su estilo favorito. En Firefox, por ejemplo, se puede hacer
colocando una hoja de estilo en la carpeta de perfiles del navegador).
1. Al contener las numerosas propiedades de los estilos, los datos de estilo llegan a alcanzar
unas dimensiones considerables, lo que puede provocar un uso excesivo de memoria.
...
Significa que las reglas se aplican a un elemento <div> que es el descendiente de tres elementos
"div". Supongamos que quieres comprobar si la regla se aplica a un elemento <div> determinado.
Debes seleccionar una ruta superior del árbol para comprobarlo. Es posible que debas ascender
por todo el árbol de nodos solo para averiguar que únicamente existen dos elementos "div" y que
la regla no se aplica. A continuación, debes probar con otras rutas del árbol.
3. Para aplicar las reglas, es necesario utilizar reglas en cascada bastante complejas que
definen la jerarquía de las reglas.
Los nodos de WebKit hacen referencia los objetos de estilo (RenderStyle). Los nodos pueden
compartir estos objetos en algunas circunstancias. Los nodos son del mismo nivel, ya pertenezcan
al mismo nodo principal o a otro nodo del mismo nivel que este, y:
1. Los elementos deben tener el mismo estado de ratón (por ejemplo, uno no puede estar en
":hover" y el otro en otro estado).
2. Ninguno de los elementos debe tener un identificador.
8. Ningún elemento se debe ver afectado por selectores de atributos, es decir, no debe
presentar ninguna coincidencia con ningún selector que utilice un selector de atributo en
ninguna posición dentro del selector.
10. No debe haber ningún selector del mismo nivel en uso. WebCore simplemente aplica un
cambio global si detecta un selector del mismo nivel e inhabilita la opción de compartir
estilos en todo el documento cuando está presente. Esto incluye el selector + y selectores
como ":first-child" y ":last-child".
Firefox cuenta con dos árboles adicionales para una computación de estilos más sencilla: el árbol
de reglas y el árbol de contextos de estilo. WebKit también cuenta con objetos de estilo, pero no
se almacenan en un árbol como el árbol de contextos de estilo. Solo el nodo de DOM indica el
estilo pertinente.
Figura : árbol de contextos de estilo (2.2)
Los contextos de estilo incluyen valores finales. Para computar los valores, se aplican todas las
reglas con las que se hayan encontrado coincidencias en el orden correcto y se realizan
manipulaciones que transforman los valores lógicos en concretos. Por ejemplo, si el valor lógico es
un porcentaje de la pantalla, se calculará y se transformará en unidades absolutas. La idea del
árbol de reglas es muy ingeniosa, ya que permite compartir estos valores entre nodos para evitar
que se vuelvan a computar. Además, ahorra espacio.
Todas las reglas con las que se encuentra alguna coincidencia se almacenan en un árbol. Los nodos
inferiores de una ruta tienen mayor prioridad. El árbol incluye todas las rutas de las coincidencias
que se han encontrado para una regla. Las reglas se almacenan lentamente. El árbol no se
computa al principio de cada nodo, pero siempre que se debe computar el estilo de un nodo, las
rutas se añaden al árbol.
La idea es que las rutas del árbol se muestren como las palabras de un lexicón. Imaginemos, por
ejemplo, que ya hemos computado este árbol de reglas:
Supongamos que necesitamos
encontrar coincidencias para reglas de otros elementos del árbol de contenido y obtenemos las
siguientes reglas (en el orden correcto): B - E - I. Ya teníamos esta ruta del árbol porque ya
habíamos computado la ruta A - B - E - I - L, por lo que ahora tendremos menos trabajo que hacer.
División en estructuras
Los contextos de estilo se dividen en estructuras que incluyen información de estilo de una cierta
categoría, como el borde o el color. Todas las propiedades de un estructura pueden ser heredadas
o no heredadas. Las propiedades heredadas son propiedades que, a menos que las defina el
elemento, se heredan del elemento principal. Las propiedades no heredadas (denominadas
propiedades "reset") utilizan los valores predeterminados en caso de que no se definan.
El árbol guarda en caché estructuras completas (que incluyen los valores finales computados) del
árbol. De esa forma, si el nodo inferior no proporciona una definición para una estructura, se
puede utilizar una estructura guardada en caché de un nodo superior.
Cuando se computa el contexto de estilo de un elemento específico, primero se computa una ruta
del árbol de reglas o se utiliza una existente. A continuación, se aplican las reglas de la ruta para
completar las estructuras del nuevo contexto de estilo. Empezamos por el nodo inferior de la ruta,
es decir, el nodo de mayor prioridad (normalmente el selector más específico), y recorremos el
árbol en sentido ascendente hasta completar la estructura. Si este nodo de reglas no incluye
ninguna especificación para la estructura, podemos optimizarlo considerablemente (la mejor
forma es recorrer el árbol en sentido ascendente hasta encontrar un nodo que incluya una
especificación completa y apuntar después a este nodo) y compartir la estructura completa.
Gracias a este método ahorramos valores finales y memoria.
Si encontramos definiciones parciales, recorremos el árbol en sentido ascendente hasta completar
la estructura.
Si no encontramos definiciones para la estructura, en caso de que esta sea de tipo "heredada",
apuntamos a la estructura del elemento principal del árbol de contextos. En este caso, también
conseguimos compartir las estructuras. Si la estructura es de tipo "no heredada", se utilizarán los
valores predeterminados.
Si el nodo más específico no añade valores, tendremos que efectuar cálculos adicionales para
transformarlos en valores reales. Posteriormente, almacenamos en caché en el árbol el resultado
del nodo para que se pueda utilizar como elemento secundario.
En caso de que un elemento tenga un elemento de su mismo nivel que apunte al mismo nodo del
árbol, ambos elementos pueden compartir el contexto de estilo completo.
<html>
<body>
<p>
this is also a
</p>
</div>
</body>
</html>
1. div {margin:5px;color:black}
2. .err {color:red}
3. .big {margin-top:3px}
5. #div1 {color:blue}
6. #div2 {color:green}
Para simplificar la tarea, digamos que tenemos que completar solo dos estructuras: la estructura
de color y la estructura de margen. La estructura de color solo contiene un miembro, el color,
mientras que la estructura de margen contiene los cuatro lados.
El árbol de reglas que se obtiene tendrá la siguiente apariencia (los nodos se marcan así: "nombre
del nodo : número de la regla a la que apunta"):
Figura : árbol
de reglas
El árbol de contextos tendrá la siguiente apariencia (nombre del nodo : nodo de regla a la que
Supongamos que analizamos el código HTML y obtenemos la segunda etiqueta <div>. Tendremos
que crear un contexto de estilo para el nodo y completar sus estructuras de estilo.
Buscamos las reglas que coincidan con <div> y descubrimos que son 1, 2 y 6. Esto significa que ya
existe una ruta del árbol que podemos utilizar para nuestro elemento, por lo que solo necesitamos
añadir otro nodo para la regla 6 (nodo F del árbol de reglas).
Crearemos un contexto de estilo y lo incluiremos en el árbol de contextos. El nuevo contexto de
estilo apuntará al nodo F del árbol de reglas.
Ya tenemos una definición para la estructura de color, por lo que no podemos utilizar una
estructura almacenada en caché. Como el color tiene un atributo, no necesitamos ascender por el
árbol para completar otros atributos. Computamos el valor final (convertimos la cadena a RGB,
etc.) y almacenamos en caché en este nodo la estructura computada.
El trabajo relacionado con el elemento <span> es aún más sencillo. Buscamos las reglas que
coinciden con este elemento y llegamos a la conclusión de que la regla a la que apunta es G, como
el elemento "span" anterior. Como tenemos elementos del mismo nivel que apuntan al mismo
nodo, podemos compartir el contexto de estilo completo y apuntar solo al contexto del elemento
"span" anterior.
En el caso de las estructuras que incluyen reglas heredadas del elemento principal, el proceso de
almacenamiento en caché se lleva a cabo en el árbol de contextos (aunque la propiedad de color
se hereda, Firefox trata esta propiedad como no heredada y la guarda en caché en el árbol de
reglas).
Por ejemplo, imaginemos que añadimos reglas para las fuentes de un parágrafo:
p {font-family:Verdana;font size:10px;font-weight:bold}
El elemento de párrafo, que es un elemento secundario del elemento "div" del árbol de contextos,
podría compartir la misma estructura de fuente que el elemento principal. Este caso se podría
aplicar si no se especificasen reglas de fuente para el párrafo.
En WebKit, no existen los árboles de reglas, por lo que las declaraciones que coinciden se recorren
cuatro veces. En primer lugar, se aplican las propiedades de alta prioridad de menor importancia
(estas propiedades se deben aplicar primero porque hay otras que dependen de ellas, como
"display"); a continuación, se aplican las propiedades de alta prioridad de mayor importancia;
posteriormente, las propiedades de prioridad normal de menor importancia y, por último, las
reglas de prioridad normal de mayor importancia. Esto significa que las propiedades que
aparezcan varias veces se resolverán según el orden de cascada correcto. La última es la más
importante.
En resumen, compartir objetos de estilo (ya sea en su totalidad o compartiendo solamente algunas
de sus estructuras) soluciona las incidencias 1 y 3. El árbol de reglas de Firefox también ayuda a
aplicar las propiedades en el orden correcto.
p {color:blue}
Las dos últimas fuentes coinciden fácilmente con el elemento, ya que este incluye los atributos de
estilo, y los atributos HTML se pueden asignar utilizando el elemento como clave.
Como se comentó previamente en la incidencia 2, buscar una coincidencia con la regla de CSS
puede resultar bastante complicado. Para resolver esta dificultad, las reglas se manipulan para
facilitar el acceso.
Después de analizar la hoja de estilo, las reglas se añaden a uno de varios mapas hash, de acuerdo
con el selector. Existen mapas organizados por ID, nombre de clase y nombre de etiqueta, y un
mapa general para todo lo que no se puede incluir en estas categorías. Si el selector es un ID, la
regla se añadirá al mapa de ID; si es una clase, se añadirá al mapa de clase, etc.
Esta manipulación facilita considerablemente la tarea de asignación de reglas. No hace falta
revisar cada declaración, podemos extraer las reglas relevantes para un elemento de los mapas.
Esta optimización elimina más del 95% de las reglas, por lo que ni siquiera es necesario tenerlas en
cuenta durante el proceso de búsqueda de coincidencias (4.1).
p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
En primer lugar, intentaremos buscar reglas para el elemento "p". El mapa de clase incluirá una
clave "error" dentro de la que se encuentra la regla para "p.error". El elemento "div" tendrá reglas
relevantes en el mapa de ID (la clave es el ID) y el mapa de etiqueta. Por tanto, solo queda
averiguar qué reglas extraídas de las claves realmente coinciden.
Por ejemplo, si la regla del elemento "div" fuera la siguiente:
se extraería del mapa de etiqueta, porque la clave es el selector situado más a la derecha, pero no
coincidiría con el elemento "div", que no cuenta con un antecesor de tabla.
La incidencia se produce cuando existe más de una definición, y es entonces cuando se debe
utilizar el orden en cascada para resolverla.
Una declaración de una propiedad de estilo puede aparecer en varias hojas de estilo y varias veces
dentro de una misma hoja. Por ese motivo, el orden de aplicación de las reglas tiene una gran
importancia. Este orden se conoce como "cascada". De acuerdo con las especificaciones de CSS2,
el orden en cascada es el siguiente (de menor a mayor):
Las declaraciones del navegador son las que tienen menos importancia y las del usuario solo
tienen prioridad sobre las del autor si están marcadas como importantes. Las declaraciones con el
mismo orden se ordenan según la especificidad y, posteriormente, según el orden en el que se han
especificado. Los atributos visuales HTML se traducen en las declaraciones CSS correspondientes.
Se tratan como reglas de autor de prioridad baja.
Especificidad
La especificidad se obtiene al concatenar los cuatro números a-b-c-d (en un sistema de números
de base amplia).
La base numérica que se debe utilizar es el número de recuento más elevado de una de las
categorías.
Por ejemplo, si a=14, se puede utilizar una base hexadecimal. En el improbable caso de que a=17,
se deberá utilizar una base numérica de 17 dígitos. El último caso sería el de un selector como
html body div div p... con 17 etiquetas en el selector, pero esto es muy poco probable.
Algunos ejemplos:
Después de buscar coincidencias, las reglas se ordenan según las reglas de cascada. WebKit utiliza
el ordenamiento de burbuja para listas pequeñas y el ordenamiento por mezcla para listas
grandes. WebKit ordena las reglas sobrescribiendo el operador ">" para las reglas:
Proceso gradual
WebKit utiliza un indicador que muestra si se han cargado todas las hojas de estilo de nivel
superior (incluidas las de @imports). Si las hojas de estilo no se cargan por completo al asociarlas,
se utilizan marcadores de posición (indicándolo en el documento), que se vuelven a calcular una
vez que se han cargado las hojas de estilo.
Diseño
Cuando el renderizador se crea y se añade al árbol, no tiene posición ni tamaño. El cálculo de estos
valores se conoce como diseño o reflujo.
HTML utiliza un modelo de diseño basado en flujo, lo que significa que, la mayoría de las veces, los
cálculos geométricos se pueden realizar con una sola operación. Los elementos que entran
posteriormente "en el flujo" no suelen influir en la geometría de los elementos que ya se
encuentran en él, por lo que el diseño se puede aplicar de izquierda a derecha y de arriba a abajo
en todo el documento. Hay excepciones, como las tablas HTML, que pueden requerir más de un
cálculo (3.5).
La posición del renderizador raíz es 0,0 y su dimensión es la ventana gráfica, es decir, la parte
visible de la ventana del navegador.
Todos los renderizadores incluyen un método de "diseño" o de "reflujo" y cada uno activa el
método de diseño del elemento secundario al que se debe aplicar el diseño.
Para no iniciar un proceso de diseño completo con cada pequeña modificación, el navegador
utiliza un sistema de bit de modificación (dirty bit). Cuando se añade o se modifica un
renderizador, tanto el propio renderizador como su elemento secundario se marcan con el
indicador "dirty", lo que significa que se deben someter a un proceso de diseño.
Existen dos indicadores: "dirty" y "children are dirty". El indicador "children are dirty" especifica
que, aunque el renderizador no haya sufrido cambios, al menos uno de sus elementos secundarios
necesita someterse a un proceso de diseño.
El proceso de diseño se puede activar en todo el árbol de renderización, lo que se conoce como
diseño "global". A continuación se indican algunos motivos por los que puede ser necesario un
diseño global:
1. un cambio de estilo global que afecte a todos los renderizadores, como un cambio de
tamaño de fuente,
El diseño puede ser incremental, en cuyo caso solo se someterán a un proceso de diseño los
renderizadores marcados como "dirty" (este hecho puede provocar daños que pueden requerir
procesos de diseño adicionales).
Cuando los renderizadores están marcados como "dirty", se activa (de forma asíncrona) el diseño
incremental (por ejemplo, cuando se añaden renderizadores nuevos al árbol de renderización
después de incluir contenido adicional de la red en el árbol de DOM).
Figura : diseño incremental en el que solo
se someten a un proceso de diseño los renderizadores modificados y sus elementos secundarios
(3.6)
El diseño incremental se efectúa de forma asíncrona. Firefox almacena "comandos de reflujo" para
los diseños incrementales y un programador activa la ejecución en lote de estos comandos.
WebKit también incluye un temporizador que ejecuta el diseño incremental (se recorre el árbol y
se aplica diseño a los renderizadores marcados como "dirty").
Las secuencias de comandos que solicitan información de estilo, como "offsetHeight", pueden
activar el diseño incremental de forma síncrona.
El diseño global se suele activar de forma síncrona.
A veces, el diseño se activa como una devolución de llamada posterior a un diseño inicial debido a
los cambios que sufren algunos atributos, como la posición de desplazamiento.
Optimizaciones
Cuando se activa un proceso de diseño por un "cambio de tamaño" o por un cambio en la posición
del renderizador (no en su tamaño), el tamaño de los renderizadores se toma de una caché en
lugar de recalcularse.
En algunos casos, solo se modifica un subárbol, por lo que el proceso de diseño no se inicia desde
la raíz. Esto puede suceder en aquellos casos en los que el cambio es local y no afecta a los
elementos que lo rodean, como el texto insertado en campos de texto (de lo contrario, cada tecla
activaría un diseño desde la raíz) .
El proceso de diseño
2. Activa la aplicación del diseño del renderizador secundario en caso necesario (si
está marcado como "dirty", si se trata de un diseño global o por alguna otra
causa), lo que hace que se calcule la altura del renderizador secundario.
3. El renderizador principal utiliza las alturas acumulativas de los elementos secundarios y las
alturas de los márgenes y el relleno para establecer su propia altura, que utilizará el
elemento principal del renderizador principal.
Firefox utiliza un objeto "state" (nsHTMLReflowState) como parámetro de diseño (conocido como
"reflujo"). Entre otros valores, el objeto de estado incluye el ancho de los elementos principales.
El resultado del diseño de Firefox es un objeto "metrics" (nsHTMLReflowMetrics) que incluirá la
altura computada del renderizador.
El ancho del renderizador se calcula utilizando el ancho del bloque contenedor, la propiedad de
estilo "width" del renderizador, los márgenes y los bordes.
Utilicemos para nuestro ejemplo el siguiente elemento "div":
<div style="width:30%"/>
El ancho de los elementos es el atributo de estilo "width", que se calcula como un valor
absoluto computando el porcentaje del ancho del contenedor.
Hasta ahora, hemos calculado el "ancho preferente". Ahora vamos a calcular los anchos mínimo y
máximo.
Si el ancho preferente es superior al ancho máximo, se utiliza el ancho máximo. Si, por el
contrario, es inferior al ancho mínimo (la unidad indivisible más pequeña), se utiliza el ancho
mínimo.
Los valores se almacenan en caché en caso de que se necesite activar un proceso de diseño sin
que varíe el ancho.
Salto de línea
El salto de línea se produce cuando un renderizador decide que debe interrumpirse en mitad del
diseño. Se detiene y comunica el salto al renderizador principal. El renderizador principal crea
renderizadores adicionales y activa sus procesos de diseño.
Pintura
Global e incremental
Al igual que ocurre en la fase de diseño, la pintura también puede ser un proceso global (se pinta
el árbol de renderización completo) o incremental. En el caso de la pintura incremental, algunos
de los renderizadores se modifican de una forma que no afecta a la totalidad del árbol. El
renderizador modificado invalida su rectángulo correspondiente en la pantalla. Esto hace que el
sistema operativo considere esta región como modificada y que genere un evento de "pintura". El
sistema operativo fusiona ingeniosamente varias regiones en una. En Chrome, esta operación
resulta más complicada, ya que el renderizador se encuentra en un proceso diferente al proceso
principal. Chrome simula el comportamiento del sistema operativo hasta cierto punto. La
presentación escucha estos eventos y delega el mensaje en la raíz de la renderización. Se recorre
el árbol hasta llegar al renderizador correspondiente. En consecuencia, se vuelve a pintar el
renderizador y, normalmente, sus elementos secundarios.
Haz clic aquí para conocer el orden del proceso de pintura en CSS2. Es el orden en el que se apilan
los elementos en los contextos de pila. Este orden influye en la pintura, ya que las pilas se pintan
de atrás hacia delante. El orden de apilamiento de un renderizador de bloque es el siguiente:
1. color de fondo,
2. imagen de fondo,
3. borde,
4. elementos secundarios,
5. contorno.
Firefox analiza el árbol de renderización y crea una lista de visualización para el área rectangular
pintada que incluye los renderizadores relevantes para el área rectangular en el orden de pintura
correcto (primero los fondos de los renderizadores, luego los bordes, etc.). De esta forma, si se
quiere volver a pintar el árbol, solo se tendrá que recorrer una vez (primero se pintan todos los
fondos, después todas las imágenes, a continuación todos los bordes, etc.).
Para optimizar el proceso, Firefox no añade elementos que vayan a quedar ocultos, como los
elementos que quedan totalmente ocultos bajo otros elementos opacos.
Antes de volver a iniciar un proceso de pintura, WebKit guarda el rectángulo antiguo como un
mapa de bits. Posteriormente, solo pinta el área diferencial existente entre los rectángulos nuevo
y antiguo.
Cambios dinámicos
Los navegadores intentan ejecutar la menor cantidad posible de acciones cuando se produce un
cambio. Por tanto, si se producen cambios en el color de un elemento, solo se volverá a pintar ese
elemento. Si se producen cambios en la posición de un elemento, se volverá a diseñar y a pintar
ese elemento, sus elementos secundarios y, posiblemente, los elementos que estén a su mismo
nivel. Si se añade un nodo DOM, se activará un proceso de diseño y de nueva pintura del nodo. Si
se producen cambios de mayor importancia, como el aumento del tamaño de fuente del elemento
"html", se invalidarán las cachés y se activará un nuevo proceso de diseño y de pintura del árbol
completo.
El motor de renderización solo consta de un subproceso. Casi todas las operaciones, excepto las de
red, se desarrollan en un único subproceso. En Firefox y Safari, es el subproceso principal del
navegador. En Chrome, es el subproceso principal del proceso de pestaña.
Las operaciones de red se pueden realizar mediante varios subprocesos paralelos. El número de
conexiones paralelas es limitado (normalmente, de dos a seis conexiones. Por ejemplo, Firefox 3
utiliza seis).
Bucle de eventos
El subproceso principal del navegador es un bucle de eventos, que consiste en un bucle infinito
que mantiene activo el proceso. Espera a que se inicien eventos (como los de diseño y pintura) y
los procesa. Este es el código de Firefox para el bucle de eventos principal:
while (!mExiting)
NS_ProcessNextEvent(thread);
El elemento canvas
De acuerdo con la especificación de CSS2, el término canvas describe "el espacio en el que se
representa la estructura de formato", es decir, el lugar en el que el navegador pinta el contenido.
Aunque el elemento canvas es infinito para cada dimensión del espacio, los navegadores eligen un
ancho inicial en función de las dimensiones de la ventana gráfica.
De acuerdo con las indicaciones de la página www.w3.org/TR/CSS2/zindex.html, el elemento
canvas es transparente si se incluye dentro de otro elemento o tiene un color definido por el
navegador si no se incluye en ningún elemento.
El modelo de cajas de CSS describe las cajas rectangulares que se generan para los elementos del
árbol del documento y que se diseñan de acuerdo con el modelo de formato visual.
Cada caja consta de un área de contenido (por ejemplo, texto, una imagen, etc.) y de áreas
circundantes opcionales de margen, borde y relleno.
Figura :
modelo de cajas de CSS2
Aunque el tipo de caja predeterminado es "inline", la hoja de estilo del navegador establece otros
tipos predeterminados. Por ejemplo, el tipo de visualización predeterminado de un elemento "div"
es "block".
Puedes consultar un ejemplo de hoja de estilo predeterminada en la página
www.w3.org/TR/CSS2/sample.html.
Esquema de posicionamiento
A continuación se indican los tres tipos de esquemas disponibles.
1. Flujo normal: el objeto se coloca en función del lugar que ocupa en el documento (esto
significa que el lugar que ocupa en el árbol de renderización es similar al lugar que ocupa
en el árbol de DOM) y se diseña de acuerdo con sus dimensiones y con el tipo de caja.
tipo de caja,
dimensiones de la caja,
esquema de posicionamiento,
Tipos de cajas
Caja de bloque: forma un bloque (tiene su propio rectángulo en la ventana del navegador).
Caja integrada: no tiene bloque propio, sino que se incluye en un bloque de contención.
Figura : cajas integradas
Las cajas de bloque se colocan en vertical, una detrás de otra, mientras que las cajas integradas se
distribuyen horizontalmente.
Las cajas integradas se colocan dentro de líneas o "cajas de línea". Cuando las cajas se alinean
tomando como punto de referencia su base, es decir, la parte inferior de un elemento se alinea
con otra caja tomando como referencia una parte diferente a la inferior, las líneas tienen como
mínimo la misma altura que la caja más alta, aunque pueden ser más altas. En caso de que el
ancho del contenedor no sea suficiente, las cajas integradas se colocan en diferentes líneas. Esto
es lo que suele ocurrir en un párrafo.
Figura : líneas
Posicionamiento
Relativo
Figura :
posicionamiento relativo
Flotante
Una caja flotante se desplaza a la izquierda o a la derecha de una línea. Lo más interesante de este
posicionamiento es que las demás cajas fluyen a su alrededor. A continuación, se incluye un
ejemplo con código HTML:
<p>
</p>
Absoluto y fijo
El diseño se define con exactitud independientemente del flujo normal. El elemento no participa
en el flujo normal. Las dimensiones son relativas al contenedor. En el posicionamiento fijo, el
contenedor es la ventana gráfica.
Figura :
posicionamiento fijo
Nota: la caja fija no se moverá aunque el usuario se desplace por el documento.
Representación en capas
Las capas se especifican con la propiedad "z-index" de CSS. Representa la tercera dimensión de la
caja, es decir, su posición a lo largo del "eje z".
Las cajas se dividen en pilas (denominadas "contextos de pila"). En cada pila, los elementos que
quedan debajo se pintan en primer lugar, y los elementos que quedan encima se colocan en la
parte superior, más cerca del usuario. En caso de superposición, se oculta el elemento que queda
debajo.
Las pilas se ordenan según la propiedad "z-index". Las cajas que tienen la propiedad "z-index"
forman una pila local. La ventana gráfica forma la pila exterior.
Ejemplo:
<style type="text/css">
div {
position: absolute;
left: 2in;
top: 2in;
</style>
<p>
<div
</div>
<div
</div>
</p>
Aunque el elemento "div" rojo preceda al verde en el marcado y se pinte en primer lugar en un
flujo normal, el valor de la propiedad "z-index" es superior, por lo que se encuentra más
adelantado en la pila de la caja raíz.
Recursos
2. Análisis
2. Rick Jelliffe. The Bold and the Beautiful: two new drafts for 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
(vídeo de Google Tech Talks)
4. WebKit
5. Especificaciones de W3C
1. Firefox: https://developer.mozilla.org/en/Build_Documentation
2. WebKit: http://webkit.org/building/build.html