Subido por Martin Morales

Cómo funcionan los navegadores

Anuncio
Cómo funcionan los navegadores: lo que hay detrás de los
navegadores web actuales
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
1. Los navegadores de los que hablaremos
2. La función principal de un navegador
3. Componentes principales del navegador
2. El motor de renderización
1. Motores de renderización
2. El flujo principal
3. Ejemplos del flujo principal
3. Análisis y construcción del árbol de DOM
1. Análisis (general)
1. Gramáticas
2. Analizador: combinación de analizadores léxicos
3. Traducción
4. Ejemplo de análisis
5. Definiciones formales de vocabulario y sintaxis
6. Tipos de analizadores
7. Cómo generar analizadores automáticamente
2. Analizador de HTML
1. Definición de gramática HTML
2. No es una gramática libre de contexto
3. DTD de HTML
4. DOM
5. El algoritmo de análisis
6. El algoritmo de tokenización
7. Algoritmo de construcción de árbol
8. Acciones al finalizar el análisis
9. Tolerancia a errores de los navegadores
3. Análisis de CSS
1. Analizador de CSS de WebKit
4. Orden de procesamiento de secuencias de comandos y hojas de estilo
1. Secuencias de comandos
2. Análisis especulativo
3. Hojas de estilo
4. Construcción del árbol de renderización
1. Relación del árbol de renderización con el árbol de DOM
2. El flujo de construcción del árbol
3. Computación de estilos
1. Datos de estilo compartidos
2. Árbol de reglas de Firefox
1. División en estructuras
2. Cómo computar los contextos de estilo con el árbol de reglas
3. Cómo manipular las reglas para obtener coincidencias fácilmente
4. Cómo aplicar las reglas en el orden de cascada correcto
1. Orden en cascada de la hoja de estilo
2. Especificidad
3. Cómo ordenar las reglas
4. Proceso gradual
5. Diseño
1. Sistema de bit de modifiación (dirty bit)
2. Diseño global e incremental
3. Diseño asíncrono y síncrono
4. Optimizaciones
5. El proceso de diseño
6. Cálculo del ancho
7. Salto de línea
6. Pintura
1. Global e incremental
2. Orden del proceso de pintura
3. Lista de visualización de Firefox
4. Almacenamiento de figuras rectangulares de WebKit
7. Cambios dinámicos
8. Subprocesos del motor de renderización
1. Bucle de eventos
9. Modelo de formato visual de CSS2
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
Los navegadores de los que hablaremos
En la actualidad se utilizan principalmente cinco navegadores: Internet Explorer, Firefox,
Safari, Chrome y Opera. Los ejemplos de este documento se refieren a navegadores de
código abierto, como Firefox, Chrome y Safari (este último es en parte de código abierto).
Según las estadísticas sobre navegadores de StatCounter, actualmente (agosto de 2011) el
uso conjunto de Firefox, Safari y Chrome representa el 60%. Por tanto, en estos momentos
los navegadores de código abierto constituyen una parte importante del mercado de los
navegadores.
La función principal de un navegador
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:





una barra de direcciones donde insertar las URI,
botones de avance y retroceso,
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.
Estas coincidencias resultan extrañas, ya que la interfaz de usuario de los navegadores no se
incluye en ninguna de las especificaciones formales, sino que procede de la experiencia
acumulada a lo largo de los años y de los elementos que los navegadores han imitado unos
de otros. La especificación de HTML5 no define los elementos que debe incluir la interfaz
de usuario de los navegadores, pero muestra algunos elementos comunes. Entre estos
elementos se encuentran la barra de direcciones, la barra de estado y la barra de
herramientas. Existen, por supuesto, características únicas de cada navegador, como el
administrador de descargas de Firefox.
Componentes principales del navegador
A continuación se especifican los componentes principales de un navegador (1.1).
1. Interfaz de usuario: incluye la barra de direcciones, el botón de avance/retroceso,
el menú de marcadores, etc. (en general, todas las partes visibles del navegador,
excepto la ventana principal donde se muestra la página solicitada).
2. Motor de búsqueda: coordina las acciones entre la interfaz y el motor de
renderización.
3. Motor de renderización: es responsable de mostrar el contenido solicitado. Por
ejemplo, si el contenido solicitado es HTML, será el responsable de analizar el
código HTML y CSS y de mostrar el contenido analizado en la pantalla.
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.
5. Servidor de la interfaz: permite presentar widgets básicos, como ventanas y
cuadros combinados. Muestra una interfaz genérica que no es específica de ninguna
plataforma. Utiliza métodos de la interfaz de usuario del sistema operativo en
segundo plano.
6. Intérprete de JavaScript: permite analizar y ejecutar el código JavaScript.
7. Almacenamiento de datos: es una capa de persistencia. El navegador necesita
guardar todo tipo de datos en el disco duro (por ejemplo, las cookies). La nueva
especificación de HTML (HTML5) define el concepto de "base de datos web", que
consiste en una completa (aunque ligera) base de datos del navegador.
Figura :
componentes principales del navegador
Es importante decir que Chrome, a diferencia de la mayoría de los navegadores,
implementa varias instancias del motor de renderización, una por cada pestaña. Cada
pestaña representa un proceso independiente.
El motor de renderización
La responsabilidad del motor de renderización es "renderizar", es decir, mostrar el
contenido solicitado en la pantalla del navegador.
De forma predeterminada, el motor de renderización puede mostrar imágenes y documentos
HTML y XML. Puede mostrar otros tipos mediante el uso de complementos (o
extensiones); por ejemplo, puede mostrar documentos PDF mediante un complemento
capaz de leer archivos PDF. Sin embargo, en este capítulo nos centraremos en su uso
principal: mostrar imágenes y código HTML con formato definido con CSS.
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.
WebKit es un motor de renderización de código abierto que empezó siendo un motor de la
plataforma Linux y que fue modificado posteriormente por Apple para hacerlo compatible
con Mac y Windows. Puedes obtener más información en webkit.org.
El flujo principal
El motor de renderización empieza recibiendo el contenido del documento solicitado desde
la capa de red, normalmente en fragmentos de 8.000 bytes.
A continuación, el motor de renderización realiza este flujo básico:
Figura : flujo básico del motor de renderización
El motor de renderización empieza a analizar el documento HTML y convierte las etiquetas
en nodos DOM en un árbol denominado "árbol de contenido". Analiza los datos de estilo,
tanto en los archivos CSS externos como en los elementos de estilo. Los datos de estilo,
junto con las instrucciones visuales del código HTML, se utilizan para crear otro árbol: el
árbol de renderización.
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.
Ejemplos del flujo principal
Figura : flujo principal de WebKit
Figura : flujo principal del motor de renderización Gecko de Mozilla (3.6)
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".
Ejemplo: el análisis de la expresión 2 + 3 - 1 podría dar como resultado este árbol:
Figura : nodo de árbol de
expresión matemática
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.
Analizador: combinación de analizadores léxicos
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.
El análisis sintáctico es la aplicación de las reglas sintácticas del lenguaje.
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
El proceso de análisis es iterativo. El analizador normalmente pide al analizador léxico un
nuevo token e intenta buscar coincidencias entre el token y una de las reglas de sintaxis. Si
se encuentra una coincidencia, se añade al árbol de análisis un nodo correspondiente al
token y el analizador solicita otro token.
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
Muchas veces, el árbol de análisis no es el producto final. El análisis se utiliza
frecuentemente en la traducción, es decir, en la conversión del documento de entrada a otro
formato. Un ejemplo sería la compilación. El compilador, que compila un código fuente en
código de máquina, en primer lugar lo convierte en un árbol de análisis y, a continuación,
traduce el árbol a un documento de código de máquina.
Figura : flujo de compilación
Ejemplo de análisis
En la figura 5 se observa un árbol de análisis creado a partir de una expresión matemática.
Intentemos definir un lenguaje matemático simple y veamos el proceso de análisis.
Vocabulario: nuestro lenguaje puede incluir números enteros, el signo más y el signo
menos.
Sintaxis:
1. Los bloques de construcción de la sintaxis del lenguaje son expresiones, términos y
operaciones.
2. Nuestro lenguaje puede incluir cualquier cantidad de expresiones.
3. Una expresión está formada por un "término" seguido de una "operación" y de otro
término.
4. Una operación es un token de suma o un token de resta.
5. Un término es un token de número entero o una expresión.
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.
Definiciones formales de vocabulario y sintaxis
El vocabulario se suele expresar mediante expresiones regulares.
Por ejemplo, nuestro lenguaje se definirá de la siguiente forma:
INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
Como se puede observar, los números enteros se definen mediante una expresión regular.
La sintaxis normalmente se define en un formato denominado notación de Backus-Naur
(BNF). Nuestro idioma se definirá de la siguiente forma:
expression := term operation
operation := PLUS | MINUS
term := INTEGER | expression
term
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 BackusNaur (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.
Veamos cómo analizan el contenido de ejemplo estos dos tipos de analizadores:
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
+ 3 - 1
3 - 1
- 1
1
2
+
término
3
operación del término
expresión
operación de la expresión 1
expresión
Este tipo de analizador ascendente se conoce como analizador de desplazamiento-reducción
debido a que los datos de entrada se desplazan hacia la derecha (imagina un puntero que
apunta primero al inicio de los datos de entrada y a continuación se desplaza hacia la
derecha) y gradualmente se reducen según las reglas sintácticas.
Cómo generar analizadores automáticamente
Existen herramientas capaces de generar analizadores automáticamente. Se denominan
generadores de analizadores. Estos generadores crean automáticamente un analizador
funcional utilizando la gramática del lenguaje (vocabulario y reglas sintácticas) establecida
por el desarrollador. Los generadores de analizadores son muy útiles, ya que, para crear un
analizador, es necesario disponer de un profundo conocimiento del proceso de análisis, y no
resulta fácil crear manualmente un analizador optimizado.
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.
Definición de gramática HTML
Es la sintaxis y el vocabulario definidos en las especificaciones creadas por la organización
W3C. La versión actual es HTML4 y actualmente se está trabajando en HTML5.
No es una gramática libre de contexto
Como ya vimos en la introducción al análisis, la sintaxis de la gramática se puede definir
formalmente mediante formatos como BNF.
Lamentablemente, no todos los temas sobre analizadores convencionales se pueden aplicar
al lenguaje HTML (los he incluido porque se utilizarán en el análisis de CSS y JavaScript).
El lenguaje HTML no se puede definir fácilmente mediante la gramática libre de contexto
que necesitan los analizadores.
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>
<div> <img src="example.png"/></div>
</body>
</html>
El marcado anterior se traduciría en el siguiente árbol de DOM:
Figura : árbol de DOM del
marcado de ejemplo
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.
Las razones son:
1. la naturaleza permisiva del lenguaje,
2. el hecho de que los navegadores presenten una tolerancia a errores tradicional para
admitir casos bien conocidos de HTML no válido,
3. la naturaleza iterativa del proceso de análisis. Normalmente, el código no cambia
durante el análisis. Sin embargo, en el caso del código HTML, las etiquetas de
secuencias de comandos que contienen document.write pueden añadir tokens
adicionales, por lo que el proceso de análisis llega a modificar los datos de entrada.
Al no poder utilizar las técnicas de análisis normales, los navegadores crean analizadores
personalizados para analizar el código HTML.
El algoritmo de análisis se describe de forma detallada en la especificación de HTML5. El
algoritmo presenta dos fases: la tokenización y la construcción del árbol.
La tokenización es el análisis léxico, es decir, el análisis y la conversión en tokens de los
datos de entrada. Entre los tokens HTML se encuentran las etiquetas iniciales, las etiquetas
finales y los valores de atributos.
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.
Figura : flujo de análisis de HTML
(tomado de la especificación de HTML5)
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
Algoritmo de construcción de árbol
Cuando se crea un analizador, se crea el objeto "Document". Durante la fase de
construcción, se modifica el árbol de DOM que incluye el objeto "Document" en su raíz y
se añaden elementos. El constructor del árbol procesa cada nodo emitido por el
tokenizador. Por cada token, la especificación define qué elementos de DOM son relevantes
y cuáles se deben crear para este token. Además de añadirse al árbol de DOM, el elemento
se añade a una pila de elementos abiertos. Esta pila permite corregir incidencias de
anidación y de etiquetas no cerradas. El algoritmo también se describe como una máquina
de estado. Los estados se denominan "modos de inserción".
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>
La entrada a la fase de construcción del árbol es una secuencia de tokens de la fase de
tokenización. El primer modo es el "modo inicial". Si se recibe el token html, se pasará al
modo "previo a html" y se volverá a procesar el token en ese modo. Esto hará que el
elemento "HTMLHtmlElement" se cree y se añada al objeto raíz "Document".
El estado cambiará a "antes del encabezado". Recibimos el token "body". Se creará
implícitamente un elemento "HTMLHeadElement", aunque no tengamos ningún token
"head", y ese elemento se añadirá al árbol.
Ahora pasamos al modo "en el encabezado" y, a continuación, al modo "después del
encabezado". El token del cuerpo se vuelve a procesar, se crea y se inserta un elemento
"HTMLBodyElement" y, por último, el modo pasa a "en el cuerpo".
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
Acciones al finalizar el análisis
En esta fase, el navegador marcará el documento como interactivo y empezará a analizar
las secuencias de comandos que se encuentren en modo "aplazado", es decir, aquellas que
se deben ejecutar una vez que se ha analizado el documento. A continuación, el estado del
documento se establecerá como "completado" y se activará un evento de "carga".
Se pueden ver los algoritmos completos para tokenización y la construcción del árbol en la
especificación de HTML5.
Tolerancia a errores de los navegadores
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.
Tomemos este código HTML como ejemplo:
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</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.
En cambio, en la especificación de HTML5 sí se definen algunos de estos requisitos.
WebKit lo resume en el comentario que se encuentra al principio de la clase de analizador
de HTML.
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.
2. No está permitido añadir el elemento directamente. Es posible que el autor
del documento haya olvidado incluir una etiqueta en medio (o que la
etiqueta sea opcional). Este podría ser el caso de estas etiquetas: HTML
HEAD BODY TBODY TR TD LI (¿he olvidado alguna?).
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.
Veamos algunos ejemplos de tolerancia a errores de WebKit:
</br> en lugar de <br>
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:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
reportError(MalformedBRError);
t->beginTag = true;
}
Nota: los errores se gestionan de forma interna, es decir, no se muestran al usuario.
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>
WebKit cambiará la jerarquía de dos tablas secundarias:
<table>
<tr><td>outer table</td></tr>
</table>
<table>
<tr><td>inner table</td></tr>
</table>
Código:
if (m_inStrayTableContent && localName == tableTag)
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.
Elementos de un formulario anidado
Si el usuario incluye un formulario dentro de otro, el segundo se ignorará.
Código:
if (!m_currentFormElement) {
m_currentFormElement = new HTMLFormElement(formTag,
m_document);
}
Jerarquía de etiquetas demasiado profunda
El comentario habla por sí solo.
www.liceo.edu.mx es un ejemplo de un sitio con un nivel de anidamiento de unas 1.500
etiquetas, todas ellas procedentes de una serie de etiquetas <b>s. No se permite utilizar más
de 20 etiquetas anidadas del mismo tipo. A partir de ese número, se ignoran todas.
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;
}
Etiquetas finales de cuerpo o HTML colocadas incorrectamente
De nuevo, el comentario habla por sí solo.
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.
if (t->tagName == htmlTag || t->tagName == bodyTag )
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.
Veamos algunos ejemplos:
La gramática léxica (el vocabulario) se define mediante expresiones regulares para cada
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" es la abreviatura de identificador, como el nombre de una clase. "name" es el
identificador de un elemento (y se hace referencia a él mediante "#").
La gramática sintáctica se describe en 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*] ')' ]
;
Explicación: un conjunto de reglas es una estructura como la que se muestra a
continuación.
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
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
Esto significa que un conjunto de reglas es un selector o un número opcional de selectores
separados por una coma y por espacios (S significa espacio en blanco). Un conjunto de
reglas contiene una declaración entre llaves u, opcionalmente, una serie de declaraciones
separadas por punto y coma. "declaration" y "selector" se definirán en las siguientes
definiciones de BNF.
Analizador de CSS de WebKit
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 desplazamientoreducció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
Orden de procesamiento de secuencias de comandos y hojas de estilo
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.
Construcción del árbol de renderización
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{
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
}
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* 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;
}
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.
Relación del árbol de renderización con el árbol de DOM
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".
El flujo de construcción del árbol
En Firefox, la presentación se registra como un detector de actualizaciones de DOM. La
presentación delega la creación de marcos en FrameConstructor y el constructor resuelve
el estilo (consulta la sección Computación de estilos) y crea un marco.
En WebKit, el proceso para resolver el estilo y crear un renderizador se denomina
"asociación". Cada nodo DOM dispone de un método de "asociación". El proceso de
asociación es síncrono, es decir, que la inserción de nodos en el árbol de DOM activa el
método de "asociación" del nuevo nodo.
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.
Consulta la especificación de CSS2 en el modelo de procesamiento.
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).
La computación de estilos conlleva algunas dificultades:
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.
2. La búsqueda de las reglas coincidentes de cada elemento puede afectar al
rendimiento si no se optimiza el proceso. Recorrer la lista completa de reglas de
cada elemento para encontrar coincidencias es una tarea muy pesada. Los selectores
pueden tener una estructura compleja, lo que puede hacer que se empiece a buscar
en una ruta que aparentemente sea buena, pero que finalmente no sea así y se deba
probar con otra ruta.
Pongamos como ejemplo este selector complejo:
div div div div{
...
}
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.
Veamos cómo se enfrentan los navegadores a estas dificultades:
Datos de estilo compartidos
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.
3. Los nombres de las etiquetas deben coincidir.
4. Los atributos de clase deben coincidir.
5. El conjunto de atributos asignados debe ser idéntico.
6. Los estados de enlace deben coincidir.
7. Los estados de enfoque deben coincidir.
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.
9. No debe haber ningún atributo de estilo integrado en los elementos.
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".
Árbol de reglas de Firefox
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.
Vamos a comprobar cómo guarda el árbol nuestro trabajo.
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.
Cómo computar los contextos de estilo con el árbol de reglas
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.
Veamos un ejemplo. Supongamos que tenemos el siguiente código 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>
Y las siguientes reglas:
1.
2.
3.
4.
5.
6.
div {margin:5px;color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#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 apunta):
de contextos
Figura : árbol
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.
Ahora necesitamos completar las estructuras de estilo. Empezaremos completando la
estructura de margen. Como el último nodo de regla (F) no se añade a la estructura de
margen, podemos ascender por el árbol hasta encontrar una estructura almacenada en caché
computada en la inserción de un nodo anterior y utilizarla. Encontraremos esta estructura en
el nodo B, que es el nodo de nivel más superior que especifica reglas de margen.
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.
Cómo manipular las reglas para obtener coincidencias fácilmente
A continuación se muestran las distintas fuentes de reglas de los modificadores de estilo.

Reglas de CSS, tanto en hojas de estilo internas como en elementos de estilo:
p {color:blue}

Atributos de estilo integrados, como el siguiente:
<p style="color:blue" />

Atributos visuales HTML (que se asignan a reglas de estilo relevantes):
<p bgcolor="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).
Analicemos las reglas de estilo que se incluyen a continuación:
p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
La primera regla se insertará en el mapa de clase, la segunda en el mapa de ID y la tercera
en el mapa de etiquetas.
Para el siguiente fragmento de HTML:
<p class="error">an error occurred </p>
<div id=" messageDiv">this is a message</div>
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:
table div {margin:5px}
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.
Tanto WebKit como Firefox utilizan esta manipulación.
Cómo aplicar las reglas en el orden de cascada correcto
El objeto de estilo tiene propiedades que se corresponden con cada atributo visual (todos
los atributos CSS, pero más genéricos). Si ninguna de las reglas que coinciden define la
propiedad, algunas propiedades se pueden heredar del elemento principal del objeto de
estilo. Otras propiedades tienen valores predeterminados.
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.
Orden en cascada de la hoja de estilo
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):
1.
2.
3.
4.
5.
declaraciones del navegador,
declaraciones normales del usuario,
declaraciones normales del autor,
declaraciones importantes del autor,
declaraciones importantes del usuario.
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 especificación de CSS2 define la especificidad del selector como se indica a
continuación:




"1" si la declaración es de un atributo "style" en lugar de una regla con un selector;
en caso contrario, "0" (= a),
número de atributos de ID del selector (= b),
número de otros atributos y pseudoclases del selector (= c),
número de nombres de elementos y de pseudoelementos del selector (= d).
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:
*
{}
li
{}
li:first-line {}
ul li
{}
ul ol+li
{}
h1 + *[rel=up]{}
ul ol li.red {}
li.red.level {}
#x34y
{}
style=""
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
a=0
a=0
a=0
a=0
a=0
a=0
a=0
a=0
a=0
a=1
b=0
b=0
b=0
b=0
b=0
b=0
b=0
b=0
b=1
b=0
c=0
c=0
c=0
c=0
c=0
c=1
c=1
c=2
c=0
c=0
d=0
d=1
d=2
d=2
d=3
d=1
d=3
d=1
d=0
d=0
->
->
->
->
->
->
->
->
->
->
specificity
specificity
specificity
specificity
specificity
specificity
specificity
specificity
specificity
specificity
=
=
=
=
=
=
=
=
=
=
0,0,0,0
0,0,0,1
0,0,0,2
0,0,0,2
0,0,0,3
0,0,1,1
0,0,1,3
0,0,2,1
0,1,0,0
1,0,0,0
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
Cómo ordenar las reglas
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:
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;
}
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).
El sistema de coordenadas se refiere al marco raíz. Se utilizan las coordenadas superior e
izquierda.
El diseño consiste en un proceso recurrente. Se inicia en el renderizador raíz, que
corresponde al elemento <html> del documento HTML. El diseño se aplica de forma
recurrente a través de toda la jerarquía de marcos o de una parte de ella, calculando
información geométrica para cada renderizador que lo requiere.
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.
Sistema de bit de modificación (dirty bit)
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.
Diseño global e incremental
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,
2. un cambio de tamaño de la pantalla.
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)
Diseño asíncrono y síncrono
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
El proceso de diseño suele seguir el patrón que se indica a continuación:
1. El renderizador principal determina su propio ancho.
2. El renderizador principal analiza los elementos secundarios y:
1. Sitúa el renderizador secundario (establece su valor x e y).
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.
4. Establece el bit de modificación (dirty bit) en "false".
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.
Cálculo del ancho
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%"/>
WebKit calcularía su ancho de la siguiente forma (clase "RenderBox", método
"calcWidth"):

El ancho del contenedor es el valor máximo de la propiedad "availableWidth" de los
contenedores y 0. En este caso, la propiedad "availableWidth" es la propiedad
"contentWidth", que se calcula así:
clientWidth() - paddingLeft() - paddingRight()
Las propiedades "clientWidth" y "clientHeight" representan el interior de un objeto,
excluyendo el borde y la barra de desplazamiento.


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.
A continuación, se añaden el relleno y los bordes horizontales.
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
En la fase de pintura, se recorre el árbol de renderización y se activa el método de "pintura"
de los renderizadores para que se muestre su contenido en la pantalla. En la fase de pintura,
se utiliza el componente de infraestructura de la interfaz.
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.
Orden del proceso de pintura
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.
2.
3.
4.
5.
color de fondo,
imagen de fondo,
borde,
elementos secundarios,
contorno.
Lista de visualización de Firefox
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.
Almacenamiento de figuras rectangulares de WebKit
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.
Subprocesos del motor de renderización
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);
Modelo de formato visual de CSS2
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.
Modelo de cajas de CSS
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
Cada nodo genera entre 0y n cajas de este tipo.
Todos los elementos tienen una propiedad "display" que determina el tipo de caja que se
generará. Ejemplos:
block - generates a block box.
inline - generates one or more inline boxes.
none - no box is generated.
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.
2. Flotante: el objeto se diseña primero según el flujo normal y, posteriormente, se
mueve hacia la derecha o hacia la izquierda todo lo posible.
3. Posicionamiento absoluto: el objeto se coloca en el árbol de renderización de una
forma diferente a la que se utiliza para colocarlo en el árbol de DOM.
La propiedad "position" y el atributo "float" determinan el esquema de posicionamiento.


Si se utilizan "static" y "relative", se genera un flujo normal.
Si se utilizan "absolute" y "fixed", se produce un posicionamiento absoluto.
Cuando el posicionamiento es estático, no se define ninguna posición, por lo que se utiliza
el posicionamiento predeterminado. En otros esquemas, el autor especifica la posición:
arriba, abajo, izquierda, derecha.
Los siguientes valores determinan el diseño de la caja:




tipo de caja,
dimensiones de la caja,
esquema de posicionamiento,
información externa, como el tamaño de las imágenes y el tamaño de la pantalla.
Tipos de cajas
Caja de bloque: forma un bloque (tiene su propio rectángulo en la ventana del navegador).
Figura : caja de bloque
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.
Figura : formato de cajas de bloque e
integradas
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
Posicionamiento relativo: el elemento se coloca normalmente y, a continuación, se mueve
según el diferencial correspondiente.
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>
<img style="float:right" src="images/image.gif" width="100"
height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
La apariencia sería la siguiente:
Figura : caja flotante
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 "zindex" 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
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>
Se obtendrá el siguiente resultado:
Figura : posicionamiento fijo
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
1. Arquitectura del navegador
1. Grosskurth, Alan. A Reference Architecture for Web Browsers (pdf)
2. Gupta, Vineet. How Browsers Work - Part 1 - Architecture
2. Análisis
1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools, también
conocido como "The Dragon Book" (El libro del dragón), Addison-Wesley,
1986
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)
3. L. David Baron, Mozilla's Layout Engine
4. L. David Baron, Mozilla Style System Documentation
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, Implementing CSS(part 1)
2. David Hyatt, An Overview of WebCore
3. David Hyatt, WebCore Rendering
4. David Hyatt, The FOUC Problem
5. Especificaciones de W3C
1. HTML 4.01 Specification
2. W3C HTML5 Specification
3. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification
6. Instrucciones de creación de navegadores
1. Firefox: https://developer.mozilla.org/en/Build_Documentation
2. WebKit: http://webkit.org/building/build.html
Tali Garsiel es una desarrolladora de Israel. Empezó su
andadura como desarrolladora web en el año 2000 y se
familiarizó con el "maléfico" modelo de capas de Netscape.
Al igual que Richard Feynmann, sentía fascinación por
descubrir cómo funcionaban las cosas, por lo que empezó a
investigar los mecanismos de funcionamiento interno de los
navegadores y a documentar todos sus descubrimientos. Tali
también ha publicado una pequeña guía sobre rendimiento
de aplicaciones cliente.
Traducciones
Descargar