Como os navegadores funcionam: bastidores dos navegadores modernos

HTML5 Rocks

Prefácio

Esta cartilha abrangente sobre as operações internas do WebKit e do Gecko é resultado de muita pesquisa feita pela desenvolvedora israelita Tali Garsiel. Ao longo de alguns anos, ela analisou todos os dados publicados sobre o funcionamento interno de navegadores (consulte Recursos) e dedicou muito tempo à leitura do código-fonte de navegadores da web. Ela escreveu:

Nos anos em que o IE tinha domínio de 90%, não havia muito a fazer além de considerar o navegador como uma "caixa preta", mas agora, com navegadores em código aberto que têm mais da metade da parcela de usuários (link em inglês), é um bom momento para dar uma olhada nas engrenagens e descobrir o que existe por dentro de um navegador da web. Na verdade, o que existe por dentro de milhões de linhas de código C++...
Tali publicou sua pesquisa em seu site, mas sabia que ela merecia ser vista por um público maior, então fizemos uma limpeza e a publicamos novamente aqui.

Como desenvolvedor da web, aprender sobre o funcionamento interno das operações de um navegador pode ajudar você a tomar melhores decisões e saber as explicações por trás das melhores práticas de desenvolvimento. Mesmo que este seja um documento bastante longo, recomendamos que você dedique algum tempo a seu estudo. Garantimos que vale a pena. Paul Irish, Chrome Developer Relations


Introdução

Navegadores da web são provavelmente os softwares mais amplamente utilizados. Nesta cartilha, explicarei como eles funcionam nos bastidores. Veremos o que acontece desde quando você digita google.com na barra de endereço até quando a página do Google é exibida na tela de seu navegador.

Sumário

  1. Introdução
    1. Os navegadores sobre os quais falaremos
    2. A principal funcionalidade do navegador
    3. A estrutura de nível superior do navegador
  2. O mecanismo de renderização
    1. Mecanismos de renderização
    2. O fluxo principal
    3. Exemplos de fluxo principal
  3. Análise e construção da árvore DOM
    1. Análise - geral
      1. Gramáticas
      2. Combinação Analisador - Analisador léxico
      3. Tradução
      4. Exemplo de análise
      5. Definições formais para vocabulário e sintaxe
      6. Tipos de analisador
      7. Geração automática de analisadores
    2. Analisador HTML
      1. Definição da gramática HTML
      2. Não é uma gramática livre de contexto
      3. HTML DTD
      4. DOM
      5. O algoritmo de análise
      6. O algoritmo de geração de tokens
      7. O algoritmo de construção de árvore
      8. Ações quando a análise é encerrada
      9. Tolerância a erros do navegador
    3. Análise CSS
      1. Analisador Webkit CSS
    4. Ordem de processamento de scripts e folhas de estilo
      1. Scripts
      2. Análise especulativa
      3. Folhas de estilo
  4. Construção da árvore de renderização
    1. Relação entre árvore de renderização e a árvore DOM
    2. O fluxo de construção da árvore
    3. Computação de estilo
      1. Compartilhamento de dados de estilo
      2. Árvore de regras do Firefox
        1. Divisão em estruturas
        2. Computação de contextos de estilo por meio da árvore de regras
      3. Manipulação de regras para obter uma fácil correspondência
      4. Aplicação de regras na ordem em cascata correta
        1. Ordem em cascata da folha de estilo
        2. Especificidade
        3. Classificação das regras
    4. Processo gradual
  5. Layout
    1. Sistema de bits incorretos
    2. Layout global e incremental
    3. Layout assíncrono e síncrono
    4. Otimizações
    5. O processo de layout
    6. Cálculo da largura
    7. Quebra de linhas
  6. Pintura
    1. Global e incremental
    2. Ordem de pintura
    3. Lista de exibição do Firefox
    4. Armazenamento em retângulo do Webkit
  7. Mudanças dinâmicas
  8. Sequências do mecanismo de renderização
    1. Loop de eventos
  9. Modelo visual CSS2
    1. O canvas
    2. Modelo de box CSS
    3. Esquema de posicionamento
    4. Tipos de box
    5. Posicionamento
      1. Relativo
      2. Floats
      3. Absoluto e fixo
    6. Representação em camadas
  10. Recursos

Os navegadores sobre os quais falaremos

Atualmente, existem quatro principais navegadores em uso: Internet Explorer, Firefox, Safari, Google Chrome e Opera. Os exemplos dados serão relacionados aos navegadores em código aberto – Firefox, Google Chrome e Safari (que é parcialmente em código aberto). Conforme as estatísticas de navegadores StatCounter (link em inglês), atualmente (em agosto de 2011), a parcela de uso do Firefox, do Safari e do Google Chrome em conjunto é de quase 60%. Isso significa que hoje em dia os navegadores em código aberto são parte substancial do negócio de navegação.

A funcionalidade principal do navegador

A funcionalidade principal de um navegador é apresentar o recurso da web escolhido por você por meio de uma solicitação ao servidor e exibição na janela do navegador. O recurso geralmente é um documento HTML, mas também pode ser um PDF, uma imagem ou outro tipo de arquivo. O local desses recursos é especificado pelo usuário por meio de um URI (Identificador Uniforme de Recursos).

A forma como o navegador interpreta e exibe arquivos HTML é apresentadas nas especificações de HTML e CSS. Essas especificações são mantidas pelo W3C (Consórcio World Wide Web), a organização que controla os padrões para a web.
Por muitos anos, os navegadores mantiveram-se parcialmente de acordo com as especificações e desenvolveram as próprias extensões. Isso causou sérios problemas de compatibilidade para autores da web. Hoje a maioria dos navegadores está relativamente de acordo com as especificações.

As interfaces do usuário dos navegadores têm muito em comum. Entre os elementos comuns às interfaces do usuário estão:

  • Barra de endereço para inserção do URI
  • Botões voltar e avançar
  • Opções para adicionar favoritos
  • Botões atualizar e parar para atualizar e parar o carregamento dos documentos atuais
  • Botão Início que o leva à página inicial

Estranhamente, a interface do usuário do navegador não está em nenhuma especificação formal. Ela é resultado de boas práticas moldadas ao longo de anos de experiência e da influência de um navegador sobre outro. A especificação HTML5 não define os elementos de interface do usuário que um navegador deve possuir, mas lista alguns elementos comuns. Entre eles estão a barra de endereço, a barra de status e a barra de ferramentas. Existem, é claro, recursos exclusivos de cada navegador, como o gerenciador de downloads do Firefox.

A estrutura de nível superior do navegador

Os principais componentes do navegador são (1.1):

  1. A interface do usuário , que inclui a barra de endereço, o botão voltar/avançar, o menu de favoritos, etc. Todas as áreas do display do navegador, exceto a janela principal em que você visualiza a página solicitada.
  2. O mecanismo de navegação , que faz a triagem das ações entre a interface do usuário e o mecanismo de renderização.
  3. O mecanismo de renderização, responsável pela exibição do conteúdo solicitado. Por exemplo, se o conteúdo solicitado estiver em HTML, ele é responsável pela análise do HTML e do CSS e pela exibição do conteúdo analisado na tela.
  4. Networking, utilizado para chamadas de rede, como solicitações HTTP. Possui interface independente de plataforma e sub-implementações para cada plataforma.
  5. Back-end da interface do usuário, utilizada para desenhar widgets básicos como caixas de combinação e janelas. Exibe uma interface genérica que não é específica à plataforma. Sob a interface, utiliza os métodos da interface do usuário do sistema operacional.
  6. Intérprete JavaScript Utilizado para analisar e executar o código JavaScript.
  7. Armazenamento de dados. Esta é uma camada persistente. O navegador precisa salvar dados de diversos tipos no disco rígido, como cookies. A nova especificação HTML (HTML5) define "banco de dados da web", que é um banco de dados completo (embora leve) no navegador.
Ilustração : principais componentes do navegador.

O Google Chrome, diferente da maioria dos navegadores, mantém múltiplas instâncias do mecanismo de renderização, uma para cada guia. Cada guia é um processo independente.

O mecanismo de renderização

A responsabilidade do mecanismo de renderização é, claro, renderizar, ou seja, exibir os conteúdos solicitados na tela do navegador.

Por padrão, o mecanismo de renderização pode exibir documentos e imagens HTML e XML. Ele pode exibir outros formatos por meio de plug-ins (ou extensões do navegador). Por exemplo, é possível exibir um PDF por meio de um plug-in do navegador para visualização de PDFs. No entanto, neste capítulo, nosso foco estará no uso principal: a exibição de HTML e de imagens formatadas com CSS.

Mecanismos de renderização

Nossos navegadores de referência – Firefox, Google Chrome e Safari – foram construídos com base em dois mecanismos de renderização. O Firefox utiliza o Gecko, um mecanismo de renderização criado pelo próprio Mozilla. O Safari e o Google Chrome usam o Webkit.

O Webkit é um mecanismo de renderização em código aberto que começou como um mecanismo para a plataforma Linux e foi modificado pela Apple para ser compatível com os sistemas Mac e Windows. Consulte webkit.org (link em inglês) para obter mais detalhes.

O fluxo principal

O mecanismo de renderização inicia pela obtenção do conteúdo do documento solicitado a partir da camada de rede. Isso geralmente é feito em parcelas de 8 KB.

Em seguida, o fluxo básico do mecanismo de renderização:

Ilustração : fluxo básico do mecanismo de renderização.

O mecanismo de renderização inicia a análise do documento HTML e transformará as tags em nós DOM de uma árvore chamada "árvore de conteúdo". Ele analisa os dados de estilo nos arquivos externos CSS e nos elementos de estilo. As informações de estilo, aliadas às instruções visuais no HTML são utilizadas para criar outra árvore, a árvore de renderização.

A árvore de renderização contém retângulos com atributos visuais como cor e dimensões. Os retângulos estão na ordem correta para serem exibidos na tela.

Após a construção da árvore de renderização, ela passa por um processo de "layout". Isso significa dar a cada nó as coordenadas exatas de onde ele deve ser exibido na tela. A próxima etapa é a pintura. A árvore de renderização será atravessada e cada nó será pintado usando a camada de back-end da interface do usuário.

É importante entender que este é um processo gradual. Para uma melhor experiência do usuário, o mecanismo de renderização tenta exibir conteúdos na tela assim que possível. Ele não espera que todo o HTML seja analisado para começar a construir e fazer o layout da árvore de renderização. Partes de conteúdo são analisadas e exibidas, enquanto o processo continua para o restante do conteúdo recebido da rede.

Principais exemplos de fluxo

Ilustração : fluxo principal do Webkit
Ilustração : fluxo principal do mecanismo de renderização Gecko do Mozilla(3.6)

As ilustrações 3 e 4 mostram que, embora o Webkit e o Gecko usem terminologias diferentes, o fluxo é basicamente o mesmo.

O Gecko nomeia a árvore de elementos formatados visualmente "Árvore de molduras". Cada elemento é uma moldura. O Webkit usa o termo "Árvore de renderização", que é formada por "Objetos de renderização". O Webkit utiliza o termo "layout" para a disposição dos elementos, enquanto o Gecko utiliza "Redimensionamento". "Attachment" é o termo do Webkit para conectar nós DOM e informações visuais para criar a árvore de renderização. Uma pequena diferença não-semântica é que o Gecko tem uma camada adicional entre o HTML e a árvore DOM. Essa camada se chama "coletor de conteúdo" e é uma fábrica para a criação de elementos DOM. Falaremos sobre cada parte do fluxo:

Análise - geral

Como a análise é um processo significativo no mecanismo de renderização, falaremos sobre ela com um pouco mais de profundidade. Começaremos com uma breve introdução sobre a análise.

A análise de um documento significa sua tradução para uma estrutura que faça sentido, algo que o código possa entender e usar. O resultado da análise geralmente é uma árvore de nós que representa a estrutura do documento. É chamada árvore de análise ou árvore de sintaxe.

Exemplo: a análise da expressão 2 + 3 - 1 pode ter como resultado esta árvore:

Ilustração : nó da árvore da expressão matemática

Gramática

A análise é baseada nas regras de sintaxe obedecidas pelo documento - a linguagem ou formato em que foi escrito. Todo formato que pode ser analisado deve possuir gramática determinista composta por regras de vocabulário e sintaxe. Ela é chamada gramática livre de contexto. As linguagens humanas são diferentes desta linguagem e, portanto, não podem ser analisadas por meio de técnicas de análise convencionais.

Combinação Analisador - Analisador léxico

A análise pode ser separada em dois processos - análise léxica e análise sintática.

A análise léxica é o processo de divisão das entradas em tokens. Os tokens são o vocabulário de uma linguagem, uma coleção de elementos estruturais válidos. Nas linguagens humanas, isso seria equivalente a todas as palavras que constam no dicionário de determinado idioma.

A análise sintática é a aplicação das regras de sintaxe da linguagem.

Analisadores costumam dividir o trabalho em dois componentes: oanalisador léxico (às vezes chamado tokenizador), responsável pela divisão da entrada em tokens válidos e o analisador, responsável pela construção da árvore de análise ao analisar a estrutura do documento conforme as regras de sintaxe da linguagem. O analisador léxico é capaz de eliminar caracteres irrelevantes como espaços em branco e quebras de linha.

Ilustração : do documento-fonte às árvores de análise

O processo de análise é iterativo. O analisador solicita ao analisador léxico um novo token e tenta encontrar alguma correspondência entre o token e uma das regras de sintaxe. Quando uma regra correspondente é encontrada, um nó correspondente ao token é adicionado à árvore de análise e o analisador solicita outro token.

Se nenhuma regra corresponde, o analisador armazena o token internamente e continua solicitando tokens até que uma regra correspondente a todos os tokens internamente armazenados seja encontrada. Se nenhuma regra é encontrada, o analisador gera uma exceção. Isso significa que o documento não é válido e contém erros de sintaxe.

Tradução

Muitas vezes a árvore de análise não é o produto final. A análise é utilizada com frequência em traduções, transformando o documento de entrada em outro formato. Um exemplo é a compilação. O compilador que compila um código-fonte em código de máquina começa por analisá-lo em uma árvore de análise e então traduz a árvore em um documento de código de máquina.

Ilustração : fluxo de compilação

Exemplo de análise

Na ilustração 5, construímos uma árvore de análise a partir de uma expressão matemática. Vamos tentar definir uma linguagem matemática simples e ver como funciona o processo de análise.

Vocabulário: nossa linguagem pode incluir números inteiros e sinais de adição e subtração.

Sintaxe:

  1. Os elementos estruturais da sintaxe de uma linguagem são expressões, termos e operações.
  2. Nossa linguagem pode incluir um número infinito de expressões.
  3. Uma expressão é definida como "termo" seguido por uma "operação", seguida por outro termo
  4. Uma operação é constituída por um token de adição ou subtração
  5. Um termo é um token representado por um número inteiro ou uma expressão

Vamos analisar a entrada 2 + 3 - 1.
A primeira substring que corresponde a uma regra é 2. Conforme a regra nº. 5 é um termo. A segunda correspondência é 2 + 3, que corresponde à terceira regra: um termo seguido por uma operação seguida por outro termo. A correspondência seguinte será atingida somente ao fim da entrada. 2 + 3 - 1 é uma expressão porque já sabemos que 2+3 é um termo, de modo que temos um termo seguido por uma operação, seguida por outro termo. 2 + + não corresponde a nenhuma regra e, portanto, é uma entrada inválida.

Definições formais para vocabulário e sintaxe

O vocabulário costuma ser formado por expressões regulares (link em inglês).

Nossa linguagem, por exemplo, pode ser definida como:

INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
Como pode ser visto, números inteiros são definidos por uma expressão regular.

A sintaxe costuma ser definida em um formato chamado BNF. Nossa linguagem é definida como:

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

Dissemos que a linguagem pode ser analisada por analisadores regulares se sua gramática for uma gramática livre de contexto. Uma definição intuitiva de gramática livre de contexto é uma gramática que pode ser inteiramente expressa em BNF. Para uma definição formal, consulte o artigo da Wikipédia sobre Gramática livre de contexto

Tipos de analisadores

Existem dois tipos básicos de analisadores: descendente e ascendente. Uma explicação intuitiva é a de que analisadores descendentes visualizam a estrutura de nível superior da sintaxe e tentam corresponder a uma delas. Analisadores ascendentes iniciam pela entrada e a transformam gradualmente nas regras de sintaxe, iniciando pelas regras de nível inferior até que as regras de nível superior encontrem correspondência.

Vejamos como os dois tipos de analisador analisam nosso exemplo:

Um analisador descendente inicia pelas regras de nível superior. Ele identifica 2 + 3 como uma expressão. Em seguida, identifica 2 + 3 - 1 como uma expressão (o processo de identificação da expressão evolui com a correspondência a outras regras, mas o ponto de partida é a regra de nível mais alto).

O analisador ascendente faz a leitura da entrada até que uma regra corresponda e, em seguida, substitui a entrada correspondente pela regra. Este processo continua até o término da entrada. A expressão com correspondência parcial é armazenada na pilha do analisador.

Pilha Entrada
  2 + 3 - 1
termo + 3 - 1
operação do termo 3 - 1
expressão - 1
operação da expressão 1
expressão  
Esse tipo de analisador ascendente é chamado analisador shift-reduce, porque a entrada é deslocada (shift) para a direita (imagine um ponteiro direcionado primeiro para o início da entrada e movendo-se para a direita) e é gradualmente reduzida (reduced) às regras de sintaxe.

Geração automática de analisadores

Existem ferramentas capazes de gerar um analisador para você. Elas são chamadas geradores de analisador. Você deve alimentá-las com a gramática de sua linguagem — suas regras de vocabulário e sintaxe — e elas produzirão um analisador em funcionamento. A criação de um analisador exige conhecimento profundo sobre o processo de análise e não é fácil criar um analisador otimizado à mão. Por isso, geradores de analisador podem ser muito úteis.

O Webkit utiliza dois geradores de analisador conhecidos: Flex (link em inglês) para a criação do analisador léxico e Bison (link em inglês) para a criação do analisador (você pode encontrá-los pelos nomes Lex e Yacc). A entrada do Flex é um arquivo com definições de expressões regulares dos tokens. A entrada do Bison são as regras de sintaxe da linguagem no formato BNF.

Analisador HTML

O trabalho do analisador HTML é analisar a marcação HTML em uma árvore de análise.

Definição de gramática HTML

O vocabulário e a sintaxe HTML são definidos nas especificações (link em inglês) criadas pela organização W3C. A versão atual é HTML4 e o trabalho para HTML5 já foi iniciado.

Não é uma gramática livre de contexto

Como vimos na introdução sobre a análise, a sintaxe de gramática pode ser definida formalmente com o uso de formatos como BNF.

Infelizmente, todos os tópicos convencionais sobre analisadores não são aplicáveis ao HTML. Não os mencionei apenas por diversão, eles serão utilizados na análise de CSS e JavaScript. O HTML não pode ser facilmente definido pela gramática livre de contexto exigida pelos analisadores.

Existe um formato formal de definição de HTML — DTD (Definição de Tipo de Documento) —, mas não é uma gramática livre de contexto.

Isso pode parecer estranho à primeira vista, mas o HTML é muito semelhante ao XML. Existem diversos analisadores XML. Existe uma variação XML do HTML, chamada XHTML, então qual a diferença?

A diferença é que a perspectiva HTML é mais adaptável, pois possibilita a você omitir certas tags adicionadas implicitamente, às vezes omitindo o início ou fim de uma tag, etc. Em geral, é uma sintaxe mais "leve" em oposição à sintaxe rígida e exigente do XML.

Aparentemente, essas diferenças que parecem mínimas fazem grande diferença. Por um lado, esta é a razão principal para a popularidade do HTML: ele perdoa seus erros e facilita a vida de autores da web. Por outro lado, dificulta a construção de uma gramática formal. Em suma, o HTML não pode ser analisado facilmente, ao menos por analisadores convencionais, já que sua gramática não é livre de contexto, nem por analisadores XML.

DTD HTML

A definição de HTML está em um formato DTD. Esse formato é utilizado para definir as linguagens da família SGML. O formato contém definições para todos os elementos permitidos, seus atributos e hierarquia. Como vimos anteriormente, o DTD HTML não forma uma gramática livre de contexto.

Existem algumas variações do DTD. O modo restrito é adequado somente às especificações, mas outros modos oferecem suporte para marcações utilizadas por navegadores no passado. O motivo disso é oferecer compatibilidade reversa com conteúdos mais antigos. O DTD restrito atual pode ser encontrado aqui: www.w3.org/TR/html4/strict.dtd (link em inglês)

DOM

A árvore de saída: a "árvore de análise" é uma árvore de elementos DOM e nós de atributo. DOM é a sigla para Modelo de Objeto de Documentos. Ele é a representação em objeto do documento HTML e da interface de elementos HTML para o mundo externo, como o JavaScript.
A raiz da árvore é o objeto "Document" (link em inglês).

O DOM tem uma relação praticamente direta com a marcação. Por exemplo, esta marcação:

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>
Seria traduzida para a seguinte árvore DOM:

Ilustração : árvore DOM da marcação de exemplo

Como HTML, o DOM é especificado pela organização W3C. Consulte www.w3.org/DOM/DOMTR (link em inglês). É uma especificação genérica para a manipulação de documentos. Um módulo específico descreve elementos específicos do HTML. As definições de HTML podem ser encontradas aqui: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html (link em inglês).

Afirmar que a árvore contém nós DOM significa que ela é construída a partir de elementos que implementam uma das interfaces DOM. Navegadores utilizam implementações concretas que possuem outros atributos usados internamente pelo navegador.

O algoritmo de análise

Como vimos nas seções anteriores, o HTML não pode ser analisado utilizando os analisadores descendente e ascendente padrão.

Os motivos para isso são:

  1. A natureza adaptável da linguagem.
  2. O fato de navegadores tradicionalmente terem tolerância a erros para suportar casos conhecidos de HTML inválido.
  3. O processo de análise na reentrância. A fonte geralmente não é modificada durante a análise, mas, em HTML, tags de script que contêmdocument.write podem incluir tokens adicionais. Assim, o processo de análise acaba por modificar a entrada.

Como não são capazes de usar as técnicas de análise regulares, os navegadores criam analisadores personalizados para a análise do HTML.

O algoritmo de análise é descrito em detalhes na especificação do HTML5. O algoritmo é formado por duas etapas: tokenização e construção da árvore.

A tokenização é a análise léxica, ou seja, a análise da entrada em tokens. Entre os tokens HTML estão as tags de início (start), fim (end), nomes de atributo e valores de atributos.

O tokenizador reconhece o token, o direciona para o construtor da árvore e consome o caractere seguinte para reconhecer o token seguinte e prossegue até o final da entrada.

Ilustração : fluxo de análise HTML (tirado das especificações do HTML5)

O algoritmo de tokenização

A saída do algoritmo é um token HTML. O algoritmo é expresso como uma máquina de estados. Cada estado consome um ou mais caracteres do fluxo de entrada e atualiza o estado seguinte de acordo com esses caracteres. A decisão é influenciada pelo estado atual da tokenização e pelo estado de construção da árvore. Isso significa que o mesmo caractere consumido produz diferentes resultados para o estado correto seguinte, dependendo do estado atual. Como o algoritmo é complexo demais para ser descrito integralmente, vejamos um exemplo que pode ajudar a compreender o princípio.

Exemplo básico: como tokenizar o seguinte HTML:

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

O estado inicial é "Estado de dados". Quando o caractere < é encontrado, o estado é modificado para "Estado de tag aberta". O consumo de um caractere a-z causa a criação de um "Token de tag de início" e o estado é modificado para "Estado de nome de tag". Esse estado permanece até que o caractere > seja consumido. Cada caractere é anexado ao nome do novo token. Em nosso caso, o token criado é um token html.

Quando a tag > é alcançada, o token atual é emitido e o estado retorna ao "Estado de dados". A tag <body> é tratada seguindo as mesmas etapas. Até agora, as tags html e body foram geradas. Voltamos ao "Estado de dados". O consumo do caractere H em "Hello world" causa a criação e emissão de um token de caractere e o processo continua até que < em </body> seja alcançado. Emitimos um token de caractere para cada caractere em "Hello world".

Agora voltamos ao "Estado de tag aberta". Consumir a entrada seguinte / provoca a criação de um token de tag de fim e uma transferência ao "Estado de nome de tag". Novamente, permanecemos nesse estado até atingirmos >. Em seguida, o token da nova tag é emitido e retornamos ao "Estado de dados". A entrada </html> será tratada como o caso anterior.

Ilustração : como tokenizar o exemplo de entrada

Algoritmo de construção de árvore

Quando o analisador é criado, o objeto Document é criado. Durante a fase de construção da árvore, a árvore DOM com o Documento em sua raiz é modificada e elementos são adicionados a ela. Cada nó emitido pelo tokenizador é processado pelo construtor da árvore. Para cada token, a especificação define qual elemento DOM é relevante e deve ser criado para o token. Além da adição do elemento à arvore DOM, ele também é adicionado a uma pilha de elementos abertos. Essa pilha é utilizada para corrigir incompatibilidade em aninhamentos e tag que não foram fechadas. O algoritmo é expresso como uma máquina de estados. Os estados são chamados "modos de inserção".

Este é o processo de construção da árvore para a entrada de exemplo:

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

A entrada para a etapa de construção da árvore é uma sequência de tokens da etapa de tokenização. O primeiro modo é o "modo inicial". O recebimento de um token html causa uma transferência ao modo"pré-html" e um reprocessamento do token nesse modo. Isso causa a criação do elemento HTMLHtmlElement, que será anexado ao objeto Document raiz.

O estado é modificado para "pré-head". Recebemos o token "body". Um elemento HTMLHeadElement é criado implicitamente, embora não tenhamos um token "head", e é adicionado à arvore.

Agora passamos para o modo "em head" e, em seguida"pós-head". O token "body" é reprocessado, um elemento HTMLBodyElement é criado e inserido e o modo é transferido para "em body".

Os tokens de caractere do trecho "Hello World" agora são recebidos. O primeiro causa a criação e inserção de um nó "Text" e os outros caracteres são anexados a esse nó.

O recebimento do token de fim do body causa a transferência para o modo "pós-body". Agora recebemos a tag de término do html, que nos leva para o modo "posterior ao pós-body". O recebimento do fim do token de arquivo termina a análise.

Ilustração : construção da árvore do html de exemplo

Ações quando a análise é terminada

Nesta etapa, o navegador marca o documento como interativo e inicia a análise de scripts em modo "adiado", aqueles que devem ser executados após o término da análise do documento. O estado do documento é configurado como "completo" e um evento "carregar" é acionado.

Você pode ver os algoritmos completos de tokenização e construção de árvore na especificação do HTML5

Tolerância a erros dos navegadores

Você nunca recebe um erro "Sintaxe inválida" em uma página HTML. Os navegadores consertam qualquer conteúdo inválido e prosseguem suas funções.

Tome este HTML como exemplo:

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

Devo ter violado um milhão de regras ("mytag" não é uma tag padrão, aninhamento incorreto dos elementos "p" e "div" e mais), mas o navegador ainda exibe o conteúdo corretamente. Portanto, grande parte do código do analisador é feito para consertar erros de autores HTML.

O erro de manipulação é consistente em navegadores, mas incrivelmente não é parte das especificações atuais de HTML. Como os botões adicionar aos favoritos e voltar/avançar, é algo que foi desenvolvido nos navegadores com o passar dos anos. Existem construções inválidas em HTML que são conhecidas e repetidas em muitos sites e que os navegadores tentar consertar de maneiras que estejam de acordo com outros navegadores.

A especificação HTML5 define alguns desses requisitos. O Webkit tem um bom resumo no comentário no início da classe analisador de HTML

O analisador analisa entradas tokenizadas no documento, construindo a árvore do documento. Se o documento tiver uma boa formação, a análise é simples.

Infelizmente, temos que lidar com muitos documentos HTML que não têm boa formação, então o analisador deve ser tolerante com os erros.

Temos que tratar, no mínimo, das seguintes condições de erro:

  1. O elemento adicionado é expressamente proibido caso esteja dentro de uma tag externa. Neste caso, devemos fechar todas as tag até aquela que proíbe o elemento e adicioná-lo em seguida.
  2. Não é permitido adicionar o elemento diretamente. É possível que o autor do documento tenha esquecido de alguma tag no meio (ou que a tag no meio seja opcional). Este pode ser o caso das tags a seguir: HTML HEAD BODY TBODY TR TD LI (esqueci alguma?).
  3. Queremos adicionar um elemento de bloco no interior de um elemento in-line. Feche todos os elementos in-line até o segundo maior elemento de bloco.
  4. Se isso não funcionar, feche os elementos até que possamos adicionar o elemento ou ignorar a tag.

Vejamos alguns exemplos de tolerância a erros no Webkit:

</br> em vez de <br>

Alguns itens utilizam </br> em vez de <br>. Para ser compatível com IE e Firefox, o Webkit trata este marcador como <br>.
O código:

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
     reportError(MalformedBRError);
     t->beginTag = true;
}
Observação: a manipulação de erros é interna e não será exibida para o usuário.

Uma stray table

Uma stray table é uma tabela dentro do conteúdo de outra tabela, mas não dentro de uma célula da tabela.
Como este exemplo:

<table>
    <table>
        <tr><td>inner table</td></tr>
    </table>
    <tr><td>outer table</td></tr>
</table>
O Webkit muda a hierarquia para duas tabelas derivadas:
<table>
    <tr><td>outer table</td></tr>
</table>
<table>
    <tr><td>inner table</td></tr>
</table>
O código:
if (m_inStrayTableContent && localName == tableTag)
        popBlock(tableTag);
O Webkit utiliza uma pilha para o conteúdo atual do elemento – ele faz com que a tabela interna saia da pilha da tabela externa. Assim, as tabelas se tornam derivadas.

Elementos de formulário aninhados

Caso o usuário inclua um formulário dentro de outro, o segundo será ignorado.
O código:

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

Uma hierarquia de tags muito profunda

O comentário é auto-explicativo.

www.liceo.edu.mx é um exemplo de site que atinge um nível de aninhamento de cerca de 1.500 tags, todas de um conjunto de <b>s. Nós permitimosno máximo 20 tags aninhadas do mesmo tipo antes de ignorá-las por completo.
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;
}

Tags html ou body end mal posicionadas

Novamente, o comentário é auto-explicativo.

Suporte para html realmente corrompido. Nunca fechamos a tag body, já que algumas páginas da web estúpidas a fecham antes do final efetivo do documento. Vamos confiar na chamada end() para o fechamento.
if (t->tagName == htmlTag || t->tagName == bodyTag )
        return;

Portanto, autores da web, fiquem atentos: a menos que queiram aparecer como exemplo de tolerância a erros no snippet de código do Webkit, criem HTML bem formado.

Análise CSS

Você lembra dos conceitos de análise mencionados na introdução? Bem, ao contrário do HTML, o CSS é uma gramática livre de contexto e pode ser analisada utilizando os tipos de analisador descritos na introdução. Na verdade, a especificação do CSS define a gramática léxica e sintática do CSS (link em inglês).

Vejamos alguns exemplos:
A gramática léxica (vocabulário) é definida por expressões 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" é a abreviação de identificador, como um nome da classe. "name" é um ID de elemento (referido por "#")

A gramática de sintaxe é descrita em 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*] ')' ]
  ;
Explicação: um conjunto de regras tem esta estrutura:
div.error , a.error {
  color:red;
  font-weight:bold;
}
div.error e a.error são seletores. A parte interna às chaves contém as regras aplicadas por este conjunto de regras. Esta estrutura é formalmente definida nesta definição:
ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
Isso significa que um conjunto de regras é um seletor ou um conjunto de seletores separados por uma vírgula e espaços (o "S" representa espaço em branco). Um conjunto de regras contém chaves e, dentro destas, uma declaração ou, opcionalmente, várias declarações separadas por ponto e vírgula. Os termos "declaração" e "seletor" serão definidos nas definições BNF a seguir.

Analisador de CSS do Webkit

O Webkit utiliza os geradores de analisador Flex e Bison para criar analisadores automaticamente a partir de arquivos de gramática CSS. Como você deve lembrar da introdução sobre analisadores, o Bison cria um analisador shift-reduce ascendente. O Firefox utiliza um analisador descendente criado manualmente. Nos dois casos, cada arquivo CSS é analisado em um objeto StyleSheet e cada objeto contém regras CSS. Os objetos de regra CSS contêm objetos seletores e de declaração e outros objetos correspondentes à gramática CSS.

Ilustração : análise de CSS

A ordem de processamento dos scripts e folhas de estilo

Scripts

O modelo da web é síncrono. Autores esperam que scripts sejam analisados e executados imediatamente quando o analisador atinge uma tag <script>. A análise do documento é interrompida até que o script seja executado. Se o script for externo, o recurso deve ser buscado na rede – o que também é feito de maneira síncrona – e a análise é interrompida até que o recurso seja encontrado. Este foi o modelo durante muitos anos e também está nas especificações de HTML4 e HTML5. Autores podem marcar o script como "adiar" para que ele não interrompa a análise do documento e seja executado depois da análise. O HTML5 adiciona a opção de marcar o script como assíncrono para que ele possa ser analisado e executado por uma sequência diferente.

Análise especulativa

O Webkit e o Firefox fazem esta otimização. Ao executar scripts, outra sequência analisa o restante do documento e descobre que outros recursos devem ser carregados da rede e faz seu carregamento. Assim os recursos podem ser carregados em conexões paralelas e a velocidade total é melhorada. Observação: o analisador especulativo não modifica a árvore DOM, deixando a tarefa para o analisador principal. Ele apenas analisa as referências a recursos externos como scripts externos, folhas de estilo e imagens.

Folhas de estilo

As folhas de estilo, por outro lado, têm um modelo diferente. Conceitualmente parece que desde que folhas de estilo não modifiquem a árvore DOM, não há motivo para esperar por elas e parar a análise do documento. Existe uma questão, no entanto, de scripts que solicitam informações de estilo durante a etapa de análise do documento. Se o estilo ainda não tiver sido carregado e analisado, o script receberá respostas erradas e isso pode causar diversos problemas. Parece ser uma exceção, mas na verdade é bastante comum. O Firefox bloqueia todos os scripts quando o carregamento e a análise da folha de estilo ainda não foram concluídos. O Webkit bloqueia scripts somente quando eles tentam acessar determinadas propriedades de estilo que podem ser afetadas por folhas de estilo não carregadas.

Construção da árvore de renderização

Enquanto a árvore DOM é construída, o navegador constrói outra árvore, a árvore de renderização. Essa árvore contém elementos visuais na ordem em que devem ser exibidos. É a representação visual do documento. A finalidade da árvore é possibilitar a pintura do conteúdo na ordem correta.

O Firefox nomeia os elementos na árvore de renderização como "molduras". O Webkit utiliza o termo renderizador ou objeto de renderização.
Um renderizador sabe como pintar e organizar no layout a si mesmo e a seus filhos.
A classe RenderObject do Webkit, a classe básica dos renderizadores, tem a seguinte definição:

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 uma área retangular que geralmente corresponde ao box CSS do nó, como descrito nas especificações do CSS2. Ele contém informações geométricas como largura, altura e posição.
O tipo de box é afetado pelo atributo de estilo "display" relevante para o nó (consulte a seção computação de estilo). Aqui está o código do Webkit para decidir que tipo de renderizador deve ser criado para um nó DOM, de acordo com o atributo display.

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;
}
O tipo de elemento também é considerado. Controles de quadros e tabelas, por exemplo, possuem molduras especiais.
No Webkit, se um elemento quiser criar um renderizador especial, ele irá ignorar o método createRenderer. Os renderizadores apontam para objetos de estilo que contêm informação não geométrica.

A relação da árvore de renderização com a árvore DOM
Os renderizadores correspondem aos elementos DOM, mas a relação não é direta. Elementos DOM não visuais não serão inseridos na árvore de renderização. Um exemplo é o elemento "head". Os elementos cujo atributo display foi definido como "none" também não aparecerão na árvore (elementos com atributo de visibilidade "hidden" aparecerão na árvore).

Existem elementos DOM que correspondem a vários objetos visuais. Estes costumam ser elementos com estrutura complexa que não podem ser representados por um único retângulo. Por exemplo, o elemento "select" possui 3 renderizadores: um para a área de exibição, outro para o menu suspenso e outro para o botão. Além disso, quando o texto é dividido em várias linhas porque a largura não é suficiente para uma linha, as novas linhas são adicionadas como renderizadores adicionais.
Outro exemplo que possui diversos renderizadores é HTML corrompido. De acordo com as especificações CSS, um elemento in-line deve conter apenas elementos de bloco ou outros elementos in-line. Em casos de conteúdo misto, renderizadores de bloco anônimos são criados para efetuar quebras de linha nos elementos in-line.

Alguns objetos de renderização correspondem a um nó DOM, mas não no mesmo lugar da árvore. Floats e elementos posicionados de forma absoluta estão fora do fluxo, posicionados em um local diferente na árvore e mapeados para a moldura real. Uma moldura de espaço reservado é onde eles deveriam ser posicionados.

Ilustração : a árvore de renderização e a árvore DOM correspondente (3.1). A "Janela de visualização" é o bloco que inicialmente contém as informações. No Webkit, ela será o objeto "RenderView".
O fluxo de construção da árvore

No Firefox, a apresentação é registrada como uma escuta para atualizações do DOM. A apresentação delega a criação de molduras ao FrameConstructor e o construtor resolve o estilo (consulte computação de estilo) e cria uma moldura.

No Webkit o processo de resolver o estilo e criar um renderizador é chamado "attachment". Cada nó do DOM tem um método "attach". O attachment é síncrono e a inserção de nós na árvore DOM solicita o novo método de "attach" (anexação) de nós.

O processamento de tags html e body resulta na construção da raiz da árvore de renderização. O objeto de renderização raiz corresponde ao que a especificação do CSS chama de bloco contentor, o bloco superior que contém todos os outros blocos. Suas dimensões são a janela de visualização - as dimensões da área da janela de exibição do navegador. O Firefox a chama de ViewPortFrame e o Webkit de RenderView. Este é o objeto de renderização para o qual o documento aponta. O restante da árvore é construído como uma inserção de nós DOM.

Consulte as especificações do CSS2 sobre o modelo de processamento (link em inglês).

Computação de estilo

A construção da árvore de renderização exige o cálculo das propriedades visuais de cada objeto de renderização. Isso é feito por meio do cálculo das propriedades de estilo de cada elemento.

O estilo inclui folhas de estilo de diversas origens, elementos de estilo in-line e propriedades visuais no HTML (como a propriedade "bgcolor"). Esta é traduzida para as propriedades de estilo CSS correspondentes.

As origens das folhas de estilo são as folhas de estilo padrão do navegador, as folhas de estilo fornecidas pelo autor da página e as folhas de estilo do usuário, que são folhas de estilo fornecidas pelo usuário do navegador, uma vez que os navegadores permitem que você defina seu estilo preferido. No Firefox, por exemplo, isso é feito pelo posicionamento de uma folha de estilo na pasta "Perfil Firefox").

A computação de estilo produz novas dificuldades:

  1. Dados de estilo são uma construção extensa que armazena diversas propriedades de estilo e isso pode causar problemas de memória.
  2. Encontrar as regras correspondentes a cada elemento pode causar problemas de desempenho se o processo não estiver otimizado. Transferir a lista de regras integralmente para cada elemento para encontrar correspondências é um trabalho pesado. Seletores podem possuir estruturas complexas que fazem com que o processo de correspondência inicie por um caminho que parece promissor, mas que acaba por ser fútil, tornando necessário tentar outro caminho.

    Por exemplo, este seletor composto:

    div div div div{
      ...
    }
    
    Significa que as regras são aplicáveis a <div>, que é descendente de 3 divs. Imagine que você queira verificar se a regra é aplicável a um elemento <div>. Você escolhe determinado caminho pela árvore para verificar. Você pode precisar atravessar toda a árvore de nós para descobrir que existem apenas duas divs e que a regra não é aplicável. Você precisaria então tentar outros caminhos na árvore.
  3. A aplicação das regras envolve regras complexas em cascata que definem a hierarquia das regras.
Vejamos como os navegadores lidam com estas questões:
Compartilhando dados de estilo

Os nós do Webkit fazem referência a objetos de estilo (RenderStyle) que podem ser compartilhados por nós em algumas condições. Os nós são associados ou derivados e:

  1. Os elementos devem estar no mesmo estado do mouse (por exemplo, um não pode estar em :hover enquanto o outro não está)
  2. Nenhum elemento pode possuir um id
  3. Os nomes de tags devem ser correspondentes
  4. Os atributos de classe devem ser correspondentes
  5. O conjunto de atributos mapeados deve ser idêntico
  6. Os estados de link devem ser correspondentes
  7. Os estados de foco devem ser correspondentes
  8. Nenhum elemento pode ser afetado por seletores de atributo, em que "ser afetado" significa ter qualquer correspondência de seletores que utilize um seletor de atributo em qualquer posição dentro do seletor
  9. Não deve haver atributo de estilo in-line nos elementos
  10. Não deve haver seletores derivados em uso. O WebCore simplesmente efetua uma mudança global quando qualquer seletor derivado é encontrado e desativa o compartilhamento de estilo para todo o documento quando os seletores estão presentes. Isso inclui o seletor + e seletores como :first-child e :last-child.
Árvore de regras do Firefox

O Firefox possui duas árvores adicionais para facilitar a computação de estilo: a árvore de regras e a árvore de contexto de estilo. O Webkit também possui objetos de estilo, mas eles não são armazenados em uma árvore como a árvore de contexto de estilo; apenas o nó DOM aponta para o estilo relevante.

Ilustração : árvore de contexto de estilo Firefox (2.2)

Os contextos de estilo contêm valores finais. Os valores são computados pela aplicação de todas as regras correspondentes na ordem correta e pela execução de manipulações que os transformam de valores lógicos em valores concretos. Por exemplo, se o valor lógico for a porcentagem da tela, ele será calculado e transformado em unidades absolutas. A ideia da árvore de regras é uma criação muito inteligente. Ela possibilita o compartilhamento desses valores entre nós para evitar que eles precisem ser computados novamente. Isso também diminui o consumo de espaço.

Todas as regras correspondentes são armazenadas em uma árvore. Os nós inferiores em um caminho têm prioridade mais alta. A árvore contém todos os caminhos para correspondência de regras encontradas. O armazenamento dessas regras é feito com economia de recursos. A árvore não é calculada no início para cada nó, mas cada vez que um estilo de nó precisa ser computado, os caminhos computados são adicionados à árvore.

A ideia é visualizar os caminhos da árvore como palavras em um léxico. Digamos que esta árvore de regras já tenha sido computada:

Imagine que precisamos fazer a correspondência de regras para um novo elemento na árvore de conteúdo e descobrir que regras compatíveis (na ordem correta) são B - E - I. Nós já possuímos esse caminho na árvore porque já computamos o caminho A - B - E - I - L. Teremos menos trabalho a fazer.

Vejamos como a árvore diminui nosso trabalho.

Divisão em estruturas

Os contextos de estilo são divididos em estruturas. Essas estruturas contêm informação de estilo para determinada categoria como borda ou cor. As propriedades em uma estrutura podem ser herdadas ou não herdadas. Propriedades herdadas são propriedades que, a menos que definidas pelo elemento, são herdadas do pai. Propriedades não herdadas (chamadas propriedades "redefinidas") utilizam valores padrão, se não são definidas.

A árvore nos ajuda a fazer o armazenamento em cache de estruturas inteiras (com valores finais computados) na árvore. A ideia é que, se o nó inferior não oferecer uma definição para a estrutura, uma estrutura em cache em um nó superior possa ser utilizada.

Computação de contextos de estilo por meio da árvore de regras

Ao computar o contexto de estilo para determinado elemento, primeiro computamos um caminho na árvore de regras ou usamos um já existente. Em seguida, começamos a aplicar as regras no caminho para preencher a estrutura em nosso novo contexto de estilo. Começamos pelo nó inferior do caminho - aquele com a prioridade mais alta (normalmente o seletor mais específico) e atravessamos a árvore até que nossa estrutura esteja completa. Se não houver especificação para a estrutura neste nó de regra, podemos fazer uma boa otimização: subimos na árvore até encontrarmos um nó que a especifique completamente e simplesmente aponte para ela. Esta é a melhor otimização: a estrutura inteira é compartilhada. Isso economiza em computação de valores finais e memória.
Se encontramos definições parciais, ascendemos na árvore até que a estrutura esteja completa.

Se não encontramos nenhuma definição para nossa estrutura, caso a estrutura seja do tipo "herdado", apontamos para a estrutura do elemento original na árvore de contexto. Neste caso, também tivemos êxito em compartilhar estruturas. Se esta for uma estrutura redefinida, os valores padrão serão utilizados.

Se o nó mais específico adicionar valores, teremos que fazer alguns cálculos adicionais para transformá-los em valores reais. Armazenamos o resultado em cache então no nó da árvore para que ele possa ser utilizado pelos filhos.

Se um elemento possuir um irmão correspondente que aponte para o mesmo nó na árvore, todo o contexto de estilo pode ser compartilhado entre eles.

Vejamos um exemplo: imagine que tenhamos este HTML

<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>
E as seguintes regras:
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 as coisas, digamos que precisemos preencher apenas duas estruturas: a estrutura de cor e a de margem. A estrutura de cor contém apenas um membro: a cor. A estrutura de margem contém as quatro extremidades.
A árvore de regras resultante terá esta aparência (os nós estão marcados com o nome do nó: o número da regra para a qual apontam):

Ilustração : a árvore de regras

A árvore de contexto terá esta aparência (nome do nó : o nó da regra para a qual apontam):
Ilustração : a árvore de contexto

Suponha que analisemos o HTML e cheguemos à segunda tag <div>. Teremos que criar um contexto de estilo para este nó e preencher suas estruturas de estilo.
Corresponderemos as regras e descobriremos que as regras correspondentes para <div> são 1, 2 e 6. Isso significa que já existe um caminho na árvore que nosso elemento pode utilizar e só precisamos adicionar outro nó a ele para a regra 6 (o nó F na árvore de regras).
Criaremos um contexto de estilo e o colocaremos na árvore de contexto. O novo contexto de estilo apontará para o nó F na árvore de regras.

Agora devemos preencher as estruturas de estilo. Começaremos preenchendo a estrutura de margem. Já que o último nó de regra (F) não adiciona à estrutura de margem, podemos ascender na árvore até encontrar uma estrutura em cache computada em uma inserção de nó anterior e a utilizá-la. Nós a encontraremos no nó B, que é o nó superior entre os que especificam regras de margem.

Não temos uma definição para a estrutura de cor, então não podemos usar uma estrutura armazenada em cache. Já que a cor tem apenas um atributo, não precisamos ascender na árvore para preencher outros atributos. Computaremos o valor final (converter a string para RGB etc.) e armazenaremos a estrutura em cache neste nó.

O trabalho no segundo elemento <span> é ainda mais simples. Corresponderemos as regras e chegaremos à conclusão que ele aponta para a regra G, como o período anterior. Já que temos filhos que apontam para o mesmo nó, podemos compartilhar todo o contexto de estilo e apenas apontar para o contexto do período anterior.

Para estruturas com regras herdadas do original, o armazenamento em cache é feito na árvore de contexto (a propriedade de cor na verdade é herdada, mas o Firefox a considera redefinida e a armazena em cache na árvore de regras).
Por exemplo, se tivéssemos adicionado regras para fontes em um parágrafo:

p {font-family:Verdana;font size:10px;font-weight:bold}
Então o elemento de parágrafo, que é filho da div na árvore de contexto, poderia ter compartilhado a estrutura de fonte de seu pai. Isso caso nenhuma regra de fonte tenha sido especificada para o parágrafo.

No Webkit, que não possui uma árvore de regras, as declarações com correspondência são transferidas 4 vezes. Em primeiro lugar, as propriedades não importantes de alta prioridade (propriedades que devem ser aplicadas em primeiro lugar porque outras dependem delas, como display) são aplicadas, em seguida as importantes de alta prioridade, depois aquelas não importantes de prioridade normal e por último as regras importantes de prioridade normal. Isso significa que as propriedades que aparecem várias vezes são resolvidas de acordo com a ordem em cascata correta. Os últimos serão os primeiros.

Em resumo, compartilhar os objetos de estilo (inteiramente ou algumas das estruturas internas e eles) resolve os problemas 1 e 3. A árvore de regras do Firefox também ajuda na aplicação de propriedades na ordem correta.

Manipulação de regras para obter uma fácil correspondência

Existem diversas fontes para regras de estilo:

  • As regras CSS, em folhas de estilo externas ou em elementos de estilo.
    p {color:blue}
    
  • Atributos de estilo in-line como
    <p style="color:blue" />
    
  • atributos visuais HTML (mapeados para regras de estilo relevantes)
    <p bgcolor="blue" />
    

As duas últimas são facilmente correspondidas ao elemento, já que ele possui os atributos de estilo e os atributos HTML podem ser mapeados utilizando o elemento como chave.

Como observado anteriormente no problema 2, a correspondência de regras CSS pode ser mais complicada. Para resolver a dificuldade, as regras são manipuladas para facilitar o acesso.

Após a análise da folha de estilo, um de diversos mapas hash é adicionado às regras, de acordo com o seletor. Existem mapas por id, nome de classe, nome de tag e um mapa geral para tudo que se encontre nessas categorias. Se um seletor for uma id, a regra será adicionada ao mapa de id; se for uma classe, será adicionado ao mapa de classe, etc.
Essa manipulação facilita muito a correspondência de regras. Não há necessidade de verificar cada declaração, já que podemos extrair as regras relevantes para cada elemento a partir dos mapas. Essa otimização elimina 95% ou mais das regras, para que elas não precisem ser consideradas durante o processo de correspondência (4.1).

Vejamos, por exemplo, as seguintes regras de estilo:

p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
A primeira regra será inserida no mapa de classe. A segunda no mapa de id e a terceira no mapa de tag.
Para o seguinte fragmento HTML;
<p class="error">an error occurred </p>
<div id=" messageDiv">this is a message</div>

Tentaremos, em primeiro lugar, encontrar regras para o elemento p. O mapa de classe conterá uma chave "erro" sob a qual a regra para "p.error" será encontrada. O elemento div terá regras relevantes no mapa de id (a chave é o id) e o mapa de tag. Assim, resta descobrir qual regra extraída pelas chaves realmente é correspondente.
Por exemplo, caso a regra para a div seja

table div {margin:5px}
ela ainda será extraída do mapa de tag, pois a chave é o seletor à extrema direita, mas ela não corresponderia a nosso elemento div que não possui um ancestral de tabela.

O Webkit e o Firefox fazem essa manipulação.

Aplicação de regras na ordem em cascata correta

O objeto de estilo tem propriedades que correspondem a todos os atributos visuais (todos os atributos css, mas de forma mais genérica). Se a propriedade não for definida por nenhuma das regras correspondidas, algumas propriedades podem ser herdadas pelo objeto de estilo do elemento pai. Outras propriedades têm valores padrão.

O problema começa quando existe mais de uma definição. E eis que chega a ordem em cascata para resolver a questão.

Ordem em cascata da folha de estilo
Uma declaração para uma propriedade de estilo pode aparecer em diversas folhas de estilo e várias vezes em uma folha de estilo. Isso significa que a ordem de aplicação das regras é muito importante. Isso se chama ordem "em cascata". De acordo com a especificação do CSS2, a ordem em cascata é (do inferior ao superior):
  1. Declarações do navegador
  2. Declarações normais do usuário
  3. Declarações normais do autor
  4. Declarações importantes do autor
  5. Declarações importantes do usuário

As declarações do navegador são menos importantes e o usuário prevalece sobre o autor somente se a declaração for marcada como importante. Declarações com a mesma ordem são classificadas por especificidade e em seguida pela ordem em que foram especificadas. Os atributos visuais do HTML são traduzidos em declarações correspondentes em CSS. Elas são tratadas como regras de autor com prioridade baixa.

Especificidade

A especificidade do seletor é definida pela especificação do CSS2 (link em inglês) da seguinte maneira:

  • conte 1 se declaração for originada de um atributo de "estilo" e não de uma regra com seletor; caso contrário, o valor deve ser 0 (= a)
  • conte o número de atributos de ID no seletor (= b)
  • conte o número de outros atributos e pseudoclasses no seletor (= c)
  • conte o número de nomes de elementos e pseudoelementos no seletor (= d)
Ao concatenar os quatro números a-b-c-d (em um sistema de números com base ampla), o resultado é a especificidade.

A base numérica que você deve utilizar é definida pela contagem mais alta encontrada em uma das categorias.
Por exemplo, se a=14 você pode usar a base hexadecimal. No caso improvável de a=17, você precisará de uma base numérica de 17 dígitos. Isso pode acontecer com um seletor como este: html body div div p... (17 tags em seu seletor... pouco provável)

Alguns exemplos:

 *             {}  /* 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 */

Classificação das regras

Após a correspondência das regras, elas são classificadas de acordo com as regras em cascata. O Webkit usa a classificação em balão para pequenas listas e a classificação em mescla para as grandes. O Webkit implementa a classificação ao sobrepor o operador ">" para as regras:

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

Processo gradual

O Webkit utiliza um sinalizador que marca se todas as folhas de estilo superiores (incluindo @imports) foram carregadas. Caso um estilo não tenha sido inteiramente carregado durante a anexação, placeholders são utilizados e ele é marcado no documento. Eles serão recalculados quando as folhas de estilo forem inteiramente carregadas.

Layout

Quando o renderizador é criado e adicionado à arvore, ele não possui posição e tamanho. O cálculo desses valores é chamado layout ou redimensionamento.

O HTML utiliza um fluxo baseado no modelo layout, o que significa que quase sempre é possível computar a geometria em um único envio. Elementos que entrem posteriormente "no fluxo" não costumam afetar a geometria dos elementos que estavam anteriormente "no fluxo", então o layout pode ser processado da esquerda para a direita e de cima para baixo no documento. Há exceções, como tabelas HTML, que podem requerer mais de um envio (3.5).

O sistema de coordenadas é relativo à moldura raiz. As coordenadas superior e da esquerda são utilizadas.

O layout é um processo recursivo. Ele inicia no renderizador raiz, que corresponde ao elemento <html> do documento HTML. O layout prossegue recursivamente por toda a hierarquia de molduras ou por parte dela, fazendo a computação de informações geométricas para cada renderizador que a solicite.

A posição do renderizador raiz é 0,0 e suas dimensões são a janela de visualização, que é a parte visível da janela do navegador.

Todos os processadores possuem um método de "layout" ou "redimensionamento". Cada processador invoca o método de layout dos filhos que precisam de layout.

Sistema de bits incorretos

Para não haver a necessidade de produzir um layout completo para cada pequena mudança, o navegador utiliza o sistema de "bits incorretos". Um renderizador modificado ou adicionado marca a si mesmo e a seus filhos como "incorretos", precisando de layout.

Existem dois sinalizadores: "incorreto" e "filhos incorretos". "Filhos incorretos" significa que, embora não haja problemas com o renderizador, ele possui no mínimo um filho que precisa de layout.

Layout global e incremental

O layout pode ser ativado em toda a árvore de renderização; este é o layout "global". Isso pode acontecer como efeito de:

  1. Uma mudança de estilo global que afeta todos os processadores, como uma mudança de tamanho de fonte.
  2. Como resultado do redimensionamento da tela

O layout pode ser incremental: apenas os processadores incorretos recebem layout. Isso pode danos que exigem layouts adicionais.
O layout incremental é acionado (de forma assíncrona) quando renderizadores são marcados como incorretos. Por exemplo, quando novos renderizadores são adicionados à árvore de renderização depois que conteúdo adicional é enviado da rede para a árvore DOM.

Ilustração : Layout incremental - apenas renderizadores incorretos e seus derivados passam por layout (3.6).

Layout assíncrono e síncrono

O layout incremental é feito de forma assíncrona. O Firefox organiza os "comandos de redimensionamento" em fila para executar o layout incremental e um programador aciona a execução em lote desses comandos. O Webkit também possui um timer que executa o layout de forma incremental: a árvore é atravessada e os processadores "incorretos" ficam de fora do layout.
Scripts que solicitam informações de estilo, como "offsetHeight", podem acionar o layout incremental de forma síncrona.
O layout global é normalmente acionado de forma síncrona.
Às vezes o layout é acionado como retorno de chamada após um layout inicial porque alguns atributos, como a posição de rolagem, foram modificados.

Otimizações

Quando um layout é acionado por um "redimensionamento" ou mudança na posição do renderizador (mas não em seu tamanho), os tamanhos dos processadores são solicitados ao cache e não recalculados.
Em alguns casos, apenas uma árvore inferior na hierarquia é modificada e o layout não é iniciado a partir da raiz. Isso pode acontecer nos casos em que a mudança é local e não afeta seus arredores - como texto inserido em campos de texto. Caso contrário, cada caractere digitado poderia acionar o processo de layout a partir da raiz.

O processo de layout

O layout costuma ter o seguinte padrão:

  1. O renderizador pai determina sua própria largura.
  2. O pai vai até seu filho e:
    1. Posiciona o renderizador filho (define seu x e y).
    2. Se necessário, chama o layout filho (caso sejam incorretos ou estejamos em um layout global ou por algum outro motivo), o que gera o cálculo da altura do filho.
  3. Pais utilizam as alturas cumulativas de seus filhos e as alturas de margens e preenchimentos para determinar a própria altura, que será utilizada pelo pai do renderizador pai.
  4. Define seu bit incorreto como falso.

O Firefox usa o objeto "estado"(nsHTMLReflowState) como parâmetro para o layout (chamado de "reposicionamento"). Entre outras informações, o estado inclui a largura do pai.
O resultado do layout do Firefox é um objeto "metrics" (nsHTMLReflowMetrics). Ele contém a altura computada do renderizador.

Cálculo da largura

A largura do renderizador é calculada usando a largura do bloco recipiente, a propriedade "width" de estilo do renderizador, as margens e bordas.
Por exemplo, a largura da seguinte div:

<div style="width:30%"/>
Seria calculada pelo Webkit da seguinte forma (classe RenderBox método calcWidth):
  • A largura do recipiente é o valor máximo da availableWidth dos recipientes e 0. A availableWidth neste caso é contentWidth, calculada como:
    clientWidth() - paddingLeft() - paddingRight()
    
    clientWidth e clientHeight representam o interior de um objeto, com exceção da borda e da barra de rolagem.
  • A largura dos elementos é o atributo de estilo "width". Ela será calculada como um valor absoluto pela computação da porcentagem da largura do recipiente.
  • As bordas horizontais e preenchimentos são adicionados agora.
Até então, este era o cálculo da "largura preferencial". Agora as larguras mínima e máxima serão calculadas.
Se a largura preferencial for maior que a largura máxima, a largura máxima será utilizada. Se for menor que a largura mínima (a menos unidade indivisível) a largura mínima será utilizada.

Os valores são armazenados em cache, caso seja necessário um layout, mas a largura não é modificada.

Quebra de linha

Quando um renderizador no meio de um layout decide que uma quebra é necessária. Ele para e propaga a seu pai a necessidade da quebra. O pai cria renderizadores adicionais e inicia o processo de layout para eles.

Pintura

Na fase de pintura, a árvore de renderização é atravessada e o método de "pintura" dos renderizadores é solicitado para exibir seu conteúdo na tela. A pintura utiliza o componente de infraestrutura da interface do usuário.

Global e incremental

Como o layout, a pintura também pode ser global, quando a árvore inteira é pintada, ou incremental. Na pintura incremental, alguns renderizadores são modificados de forma que não afete a árvore integralmente. O renderizador modificado invalida seu retângulo na tela. Isso faz com que o sistema operacional reconheça uma "região incorreta" e gere um evento "pintura". O sistema operacional o faz de forma inteligente e integra diversas regiões em uma. No Google Chrome, o processo é mais complicado porque o renderizador está em um processo diferente do principal. O Google Chrome simula o comportamento do sistema operacional até certo ponto. A apresentação escuta esses eventos e delega a mensagem à raiz do processador. A árvore é atravessada até que o renderizador relevante seja encontrado. Ele pintará a si mesmo (e geralmente também a seus filhos).

A ordem de pintura

O CSS2 define a ordem do processo de pintura (link em inglês). Esta é, na verdade, a ordem na qual os elementos são empilhados em pilhas de contexto. Essa ordem afeta a pintura, uma vez que as tarefas são pintadas de trás para a frente. A ordem de empilhamento de um renderizador em bloco é:
  1. cor do plano de fundo
  2. imagem de plano de fundo
  3. borda
  4. filhos
  5. contorno

Lista de exibição do Firefox

O Firefox repassa a árvore de renderização e constrói uma lista de exibição para o retângulo pintado. Ela contém os renderizadores relevantes ao retângulo, na ordem correta de pintura (planos de fundo dos renderizadores, depois bordas etc.). Assim, a árvore deve ser atravessada apenas uma vez para uma nova pintura em vez de diversas vezes, realizando a pintura de todos os planos de fundo, em seguida de todas as imagens, depois todas as bordas, etc.

O Firefox otimiza o processo por não adicionar elementos que serão escondidos, como elementos completamente cobertos por outros elementos opacos.

Armazenamento em retângulo do Webkit

Antes da nova pintura, o webkit salva o retângulo antigo como um bitmap. Em seguida pinta apenas o delta entre os retângulos antigo e novo.

Mudanças dinâmicas

Os navegadores tentam executar o mínimo de ações possível em resposta a uma mudança. Por isso mudanças na cor de um elemento causam apenas a nova pintura desse elemento. Modificações na posição de um elemento provocam um novo layout e a nova pintura do elemento, de seus filhos e possivelmente de seus elementos correspondentes. A adição de um nó DOM provoca um novo layout e a nova pintura do nó. Mudanças maiores, como o aumento do tamanho da fonte no elemento "html" causam a invalidação dos caches, um novo layout e a nova pintura de toda a árvore.

As sequências do mecanismo de renderização

O mecanismo de renderização tem uma sequência única. Quase tudo, exceto operações de rede, acontece em uma sequência única. No Firefox e no Safari, esta é a principal sequência do navegador. No Google Chrome, é a sequência principal da guia de processos.
Operações de rede podem ser executadas por diversas sequências paralelas. O número de conexões paralelas é limitado, geralmente de 2 a 6 conexões. O Firefox 3, por exemplo, usa 6.

Loop de eventos

A sequência principal do navegador é um loop de eventos. É um loop infinito que mantém o processo ativo. Ele aguarda eventos (como eventos de layout e pintura) e os processa. Este é o código do Firefox para o loop de eventos principal:
while (!mExiting)
    NS_ProcessNextEvent(thread);

Modelo visual CSS2

O canvas

De acordo com a especificação do CSS2 (link em inglês), o termo canvas descreve "o espaço onde a estrutura de formatação é processada". - onde o navegador pinta o conteúdo. O canvas é infinito para cada dimensão do espaço, mas os navegadores escolhem uma largura inicial baseada nas dimensões da janela de visualização.

De acordo com www.w3.org/TR/CSS2/zindex.html (link em inglês), o canvas é transparente se contido em outro ou, caso contrário, tem uma cor definida pelo navegador.

Modelo de box CSS

O Modelo de box CSS descreve os boxes retangulares gerados para elementos na árvore de documento e dispostos de acordo com o modelo de formatação visual.
Cada box tem uma área de conteúdo (por exemplo texto, uma imagem etc.) e preenchimento opcional ao redor, borda e áreas de margem.

Imagem : modelo de box CSS

Cada nó gera de 0..a n boxes como esses.
Todos os elementos têm uma propriedade "display" que determina o tipo de box que será gerado por elas. Exemplos:

block  - generates a block box.
inline - generates one or more inline boxes.
none - no box is generated.
O padrão é in-line, mas a folha de estilo do navegador determinou outros padrões. Por exemplo: a exibição padrão para o elemento "div" é bloco.
Você encontra um exemplo de folha de estilo padrão aqui: www.w3.org/TR/CSS2/sample.html (link em inglês)

Esquema de posicionamento

Existem três esquemas:

  1. Normal: o objeto é posicionado de acordo com seu lugar no documento. Isso significa que seu lugar na árvore de renderização é equivalente a seu lugar na árvore DOM e disposto de acordo com seu tipo e dimensão de box
  2. Float: o objeto inicialmente é disposto no fluxo normal e em seguida movido para a extrema direita ou esquerda
  3. Absoluto: o objeto é colocado na árvore de renderização de maneira diferente àquela como é colocado na árvore DOM

O esquema de posicionamento é definido pela propriedade "position" e pelo atributo "float".

  • estático e relativo geram um fluxo normal
  • absoluto e fixo geram um posicionamento absoluto

No posicionamento estático, nenhuma posição é definida e o posicionamento padrão é utilizado. Em outros esquemas, o autor especifica a posição: top,bottom,left,right.

A forma com que o box é disposto é determinada por:

  • Tipo de box
  • Dimensões do box
  • Esquema de posicionamento
  • Informações externas - como tamanhos de imagens e tela

Tipos de box

Box em bloco: forma um bloco - tem seu próprio retângulo na janela do navegador.

Ilustração : box em bloco

Box in-line: não tem seu próprio bloco, mas está dentro de um bloco recipiente.

Ilustração : boxes in-line

Blocos são formatados verticalmente um após o outro. In-lines são formatados horizontalmente.

Ilustração : formatação em bloco e in-line

Boxes in-line são dispostos dentro de linhas ou "boxes em linha". As linhas são no mínimo tão altas quanto o box mais alto, mas podem ser mais altas quando os boxes são dispostos em alinhamento "baseline", o que significa que a área da base de um elemento é alinhada a uma parte de outro box que não seja a base. Se a largura do recipiente não for suficiente, as in-lines serão dispostas em diversas linhas. Isso é o que costuma acontecer em um parágrafo.

Ilustração : linhas

Posicionamento

Relativo

Posicionamento relativo - posicionado como de costume e movido para o delta solicitado.

Ilustração : posicionamento relativo

Floats

Um box em float é posicionado à esquerda ou direita de uma linha. O recurso interessante é que os outros boxes são posicionados em torno dele. O HTML:

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

Ilustração : float

Absoluto e fixo

O layout é definido exatamente, independentemente do fluxo normal. O elemento não participa do fluxo normal. As dimensões são relativas ao recipiente. Caso seja fixo, o recipiente é a janela de visualização.

Ilustração : posicionamento fixo

Observação: o box fixo não é movido, nem mesmo quando o documento é rolado.

Representação em camadas

É especificada pela propriedade CSS z-index. Ela representa a terceira dimensão do box, sua posição no "eixo z".

Os boxes são divididos em pilhas (chamadas pilhas de contexto). Em cada pilha, os elementos de retorno são pintados em primeiro lugar e os elementos de avanço na parte superior, mais próximos ao usuário. Em caso de sobreposição, oculta o elemento anterior.
As pilhas são ordenadas de acordo com a propriedade z-index. Boxes com propriedade "z-index" de uma pilha local. A janela de visualização possui a pilha exterior.

Exemplo:

<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>
Este será o resultado:

Ilustração : posicionamento fixo

Embora a div vermelha preceda a verde na marcação e devesse ser pintada antes em um fluxo normal, a propriedade z-index tem prioridade mais alta, então é movida adiante na pilha utilizada pelo box raiz.

Recursos

  1. Arquitetura do navegador
    1. Grosskurth, Alan. A Reference Architecture for Web Browsers (pdf) (link em inglês)
    2. Gupta, Vineet. How Browsers Work - Part 1 - Architecture (link em inglês)
  2. Análise
    1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (também conhecido como "Dragon book"), Addison-Wesley, 1986
    2. Rick Jelliffe. The Bold and the Beautiful: two new drafts for HTML 5. (link em inglês)
  3. Firefox
    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers (link em inglês).
    2. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers (vídeo do Google tech talk) (link em inglês)
    3. L. David Baron, Mozilla's Layout Engine (link em inglês)
    4. L. David Baron, Mozilla Style System Documentation (link em inglês)
    5. Chris Waterson, Notes on HTML Reflow (link em inglês)
    6. Chris Waterson, Gecko Overview (link em inglês)
    7. Alexander Larsson, The life of an HTML HTTP request (link em inglês)
  4. Webkit
    1. David Hyatt, Implementing CSS (part 1) (link em inglês)
    2. David Hyatt, An Overview of WebCore (link em inglês)
    3. David Hyatt, WebCore Rendering (link em inglês)
    4. David Hyatt, The FOUC Problem (link em inglês)
  5. Especificações W3C
    1. HTML 4.01 Specification (link em inglês)
    2. W3C HTML5 Specification (link em inglês)
    3. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification (link em inglês)
  6. Instruções sobre construção nos navegadores
    1. Firefox. https://developer.mozilla.org/en/Build_Documentation (link em inglês)
    2. Webkit. http://webkit.org/building/build.html (link em inglês)

Traduções

Esta página foi traduzida para o japonês duas vezes. How Browsers Work - Behind the Scenes of Modern Web Browsers (link em japonês) por@_kosei_ e também ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 por @ikeike443 e @kiyoto01. Obrigado a todos!

Comments

0