Cómo funcionan los navegadores: lo que hay detrás de los navegadores web actuales

HTML5 Rocks

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  term
operation :=  PLUS | MINUS
term := INTEGER | expression

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.

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
  2 + 3 - 1
término + 3 - 1
operación del término 3 - 1
expresión - 1
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 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

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:
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):
Figura : árbol de contextos

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. declaraciones del navegador,
  2. declaraciones normales del usuario,
  3. declaraciones normales del autor,
  4. declaraciones importantes del autor,
  5. 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:

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

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. color de fondo,
  2. imagen de fondo,
  3. borde,
  4. elementos secundarios,
  5. 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 "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
         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

Traducciones

Esta página se ha traducido al japonés ¡dos veces! Cómo funcionan los navegadores: lo que hay detrás de los navegadores web actuales (ja) por @_kosei_ y ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 por @ikeike443 y @kiyoto01. ¡Gracias a todos!

Comments

0