ホームページ > ウェブフロントエンド > htmlチュートリアル > ブラウザの仕組み: 新しい Web ブラウザの舞台裏_html/css_WEB-ITnose

ブラウザの仕組み: 新しい Web ブラウザの舞台裏_html/css_WEB-ITnose

WBOY
リリース: 2016-06-21 08:48:16
オリジナル
1333 人が閲覧しました

序文

これは、WebKit と Gecko の内部操作についての包括的な入門書であり、イスラエルの開発者 Tali Gahir による広範な研究の結果です。過去数年にわたり、彼女はブラウザの内部に関する公開されているすべてのデータを調査し、Web ブラウザのソース コードを何時間もかけて精査してきました。彼女はこう書きました:

IE が市場シェアの 90% を占めていた時代、私たちはブラウザを「ブラック ボックス」として扱う以外に何もできませんでした。しかし現在、オープンソース ブラウザーが市場シェアの半分以上を占めているため、ベールを剥がして Web ブラウザーの内部で何が起こっているのかを見てみる時期が来ています。そうですね、そこには数百万行の C++ コードが含まれています...

タリーは研究結果を自身の Web サイト

で公開しましたが、より多くの人に知ってもらう価値があると感じたため、再構成して公開しましたここ。

Web 開発者として、ブラウザーの内部動作を学ぶことは、より賢明な決定を下し、ベストな開発プラクティスが機能する理由を理解するのに役立ちます。これはかなり長い文書ですが、時間をかけてじっくり読むことをお勧めします。時間を費やす価値は十分にあったと感じていただけると思います。 Paul Irish、Chrome Developer Affairs

はじめに

Web ブラウザはおそらく最も広く使用されているソフトウェアです。この紹介記事では、それらが舞台裏でどのように機能するかを見ていきます。アドレス バーに google.com と入力してから、ブラウザ画面に Google ホームページが表示されるまでに何が起こるかを学びます。

目次

これから説明するブラウザ

現在使用されている主流ブラウザは、Internet Explorer、Firefox、Safari、Chrome、Opera の 5 つです。 。この記事では、例としてオープン ソース ブラウザ、つまり Firefox、Chrome、Safari (一部オープン ソース) を使用します。 StatCounter Browser Statistics によると、現在 (2011 年 8 月) Firefox、Safari、Chrome の合計市場シェアは 60% 近くです。オープンソース ブラウザが現在、ブラウザ市場の非常に強固な部分を占めていることがわかります。

ブラウザの主な機能

ブラウザの主な機能は、サーバーにリクエストを送信し、選択したネットワーク リソースをブラウザ ウィンドウに表示することです。ここで説明するリソースは通常 HTML ドキュメントを指しますが、PDF、画像、またはその他のタイプのリソースである場合もあります。リソースの場所は、ユーザーが URI (Uniform Resource Identifier) を使用して指定します。

ブラウザーが HTML ファイルを解釈して表示する方法は、HTML および CSS の仕様で指定されています。これらの仕様は、Web 標準化団体である W3C (World Wide Web Consortium) によって維持されています。

長年にわたり、ブラウザーは独自の拡張機能を開発する際にこれらの仕様に完全には準拠していないため、Web 開発者に深刻な互換性の問題が発生してきました。現在、ほとんどのブラウザは多かれ少なかれ準拠しています。

ブラウザのユーザー インターフェイスは、次のような多くの要素を相互に共有します。

  • URI を入力するためのアドレス バー
  • 進むボタンと戻るボタン
  • ブックマーク設定オプション
  • 現在のドキュメントを更新および読み込みを停止するための更新ボタンと停止ボタン
  • ホーム ページに戻るためのホーム ボタン

奇妙なことに、次のようなものがあります。これはブラウザのユーザー インターフェイスの正式な仕様ではなく、長年にわたるベスト プラクティスの自然な進化と相互の模倣です。また、HTML5 ではブラウザーに必須のユーザー インターフェイス要素は定義されていませんが、アドレス バー、ステータス バー、ツールバーなどのいくつかの一般的な要素がリストされています。もちろん、Firefox のダウンロード マネージャーなど、各ブラウザーが独自の機能を備えている場合もあります。

ブラウザの高レベル構造

ブラウザの主なコンポーネントは次のとおりです ():

  1. ユーザー インターフェイス - アドレス バー、進む/戻るボタン、ブックマーク メニューなどが含まれます。メイン ブラウザ ウィンドウで要求したページの表示を除けば、表示の他のすべての部分はユーザー インターフェイスに属します。
  2. ブラウザ エンジン - ユーザー インターフェイスとレンダリング エンジンの間で命令を渡します。
  3. レンダリング エンジン - 要求されたコンテンツの表示を担当します。要求されたコンテンツが HTML の場合、HTML および CSS コンテンツを解析し、解析されたコンテンツを画面に表示します。
  4. ネットワーク - HTTP リクエストなどのネットワーク呼び出し用。そのインターフェイスはプラットフォームに依存せず、すべてのプラットフォームに基盤となる実装を提供します。
  5. ユーザー インターフェイス バックエンド - コンボ ボックスやウィンドウなどの基本的なウィジェットを描画するために使用されます。内部ではオペレーティング システムのユーザー インターフェイス メソッドを使用しながら、プラットフォームに依存しない共通インターフェイスを公開します。
  6. JavaScript インタプリタ 。 JavaScript コードを解析して実行するために使用されます。
  7. データストレージ。これは永続化レイヤーです。ブラウザは Cookie などのさまざまなデータをハード ドライブに保存する必要があります。新しい HTML 仕様 (HTML5) は、完全な (ただし軽量の) ブラウザ内データベースである「Web データベース」を定義しています。
図: ブラウザの主要コンポーネント。

ほとんどのブラウザとは異なり、Chrome ブラウザの各タブがレンダリング エンジン インスタンスに対応していることは注目に値します。各タブは独立したプロセスです。

プレゼンテーション エンジン

プレゼンテーション エンジンの役割は…もちろん「レンダリング」、つまり要求されたコンテンツをブラウザ画面に表示することです。

デフォルトでは、レンダリング エンジンは HTML および XML ドキュメントと画像を表示できます。プラグイン (またはブラウザ拡張機能) を通じて、他のタイプのコンテンツを表示することもできます。たとえば、PDF ビューア プラグインを使用すると、PDF ドキュメントを表示できます。ただし、この章では、CSS を使用してフォーマットされた HTML コンテンツと画像を表示するという主な用途に焦点を当てます。

レンダリング エンジン

この記事で説明するブラウザ (Firefox、Chrome、Safari) は 2 つのレンダリング エンジンで構築されています。 Firefox は、Mozilla の「自家製」レンダリング エンジンである Gecko を使用しています。 Safari ブラウザと Chrome ブラウザはどちらも WebKit を使用します。

WebKit は、もともと Linux プラットフォームで使用されていたオープンソースのレンダリング エンジンで、後に Mac と Windows をサポートするために Apple によって変更されました。詳細については、webkit.org を参照してください。

メインプロセス

レンダリング エンジンは、最初にネットワーク層から要求されたドキュメントのコンテンツを取得します。コンテンツのサイズは通常 8000 ブロックに制限されます。

次に、以下に示す基本プロセスを進めます。

図: レンダリング エンジンの基本プロセス。

レンダリング エンジンは HTML ドキュメントの解析を開始し、各タグを「コンテンツ ツリー」上のノードに 1 つずつ変換します。外部 CSS ファイルおよびスタイル要素内のスタイル データも解析されます。 HTML の視覚的な指示を含むこれらのスタイル情報は、別のツリー構造を作成するために使用されます。

レンダリング ツリーには、色やサイズなどの視覚的プロパティを持つ複数の四角形が含まれています。これらの四角形の配置順序が、画面上に表示される順序になります。

プレゼンテーション ツリーが構築された後、「」処理段階に入ります。この段階では、各ノードに画面上で表示される正確な座標を割り当てます。次の段階は、レンダリング エンジンがレンダリング ツリーを横断し、各ノードがユーザー インターフェイス バックエンド層によって描画されることです。

これは段階的なプロセスであることに注意することが重要です。より良いユーザー エクスペリエンスを実現するために、レンダリング エンジンはコンテンツをできるだけ早く画面に表示するよう努めます。レンダリング ツリーの構築とレイアウトの設定を開始する前に、HTML ドキュメント全体が解析されるまで待つ必要はありません。レンダリング エンジンは、コンテンツの一部を解析して表示すると同時に、残りのコンテンツをネットワークから受信して処理し続けます。

メインプロセスの例

図: WebKit のメインフローチャート: Mozilla の Gecko レンダリング エンジンのメインプロセス ()

図 3 と図 4 からわかるように、WebKit と Gecko で使用される用語は次のとおりです。わずかに異なりますが、全体的なプロセスは基本的に同じです。

Gecko では、視覚的な書式設定要素のツリーを「フレーム ツリー」と呼びます。すべての要素がフレームです。 WebKit で使用される用語は「レンダリング ツリー」であり、これは「レンダリング オブジェクト」で構成されます。要素の配置について、WebKit では「レイアウト」という用語が使用されますが、Gecko ではそれを「リフロー」と呼びます。 WebKit では、DOM ノードと視覚情報を接続してレンダリング ツリーを作成するプロセスに対して「追加」という用語が使用されます。セマンティックではない微妙な違いの 1 つは、Gecko には DOM 要素を生成するための HTML と DOM ツリーの間に「コンテンツ スロット」と呼ばれるレイヤーがあることです。プロセスの各部分を 1 つずつ説明します。

解析 - 概要

解析はレンダリング エンジンの非常に重要な部分であるため、さらに詳しく説明します。まず、解析を紹介します。

ドキュメントの解析とは、ドキュメントをコードで理解して使用できる意味のある構造に変換することを意味します。解析の結果は通常、文書構造を表すノード ツリーであり、これは解析ツリーまたは構文ツリーと呼ばれます。

例 - 式 2 + 3 - 1 を解析すると、次のツリーが返されます。

図: 数学式ツリー ノード

構文

解析は、文書に続く文法規則に基づいて行われます (文書が書かれている言語または形式)。解析できるすべての形式は、特定の文法 (語彙と文法規則で構成される) に対応している必要があります。これをこう呼びます。人間の言語はそのような言語ではないため、従来の解析技術を使用して解析することはできません。

パーサーと字句アナライザーの組み合わせ

解析プロセスは、字句分析と構文分析の 2 つのサブプロセスに分割できます。

字句解析は、入力を多数のトークンに分割するプロセスです。トークンは言語の単語であり、コンテンツを構成する単位です。人間の言語では、言語辞書の単語に相当します。

文法分析は、言語の文法規則を適用するプロセスです。

パーサーは通常、解析作業を次の 2 つのコンポーネントに分割します: 字句解析器 (トークン ジェネレーターとも呼ばれる)。入力コンテンツを有効なトークンに分解します。 パーサー は、言語の文法規則に従って文書の構造を分析し、解析ツリーを構築する役割を果たします。字句アナライザーは、スペースや改行などの無関係な文字を分離する方法を知っています。

図: ソース文書から解析ツリーまで

解析は反復プロセスです。通常、パーサーはレクサーに新しいトークンを要求し、それを何らかの文法規則と照合しようとします。一致するルールが見つかった場合、パーサーはそのトークンに対応するノードを解析ツリーに追加し、次のトークンの要求に進みます。

一致するルールがない場合、パーサーはトークンを内部に保存し、内部に保存されているすべてのトークンに一致するルールが見つかるまでトークンの要求を続けます。一致するルールが見つからない場合、パーサーは例外を発生させます。これは、ドキュメントが無効であり、構文エラーが含まれていることを意味します。

翻訳

多くの場合、解析ツリーはまだ最終製品ではありません。解析は通常、入力ドキュメントを別の形式に変換する翻訳中に使用されます。コンパイルはその一例です。コンパイラは、まずソース コードを解析ツリーに解析し、次に解析ツリーをマシン コード ドキュメントに変換することによって、ソース コードをマシン コードにコンパイルできます。

図: コンパイル プロセス

解析の例

図 5 では、数式を使用して解析ツリーを構築します。ここで、解析プロセスを示すために単純な数学言語を定義してみましょう。

語彙: 整数、プラス記号、マイナス記号を含む言語を使用します。

構文:

  1. 言語を構成する文法単位は、式、用語、演算子です。
  2. 私たちが使用する言語には、任意の数の表現を含めることができます。
  3. 式の定義は、「項」の後に「演算子」が続き、さらに別の「項」が続きます。
  4. 演算子はプラスまたはマイナス記号です。
  5. 項は整数または式です。

2 + 3 - 1 を分析しましょう。

文法規則に一致する最初の部分文字列は 2 で、これは文法規則 5 に従った項目です。構文ルールに一致する 2 番目の部分文字列は 2 + 3 で、これはルール 3 (用語の後に演算子、その後に用語) に従った式です。次の一致は入力の終わりに達しました。 2 + 3 - 1 は式です。2 + 3 が項であることはすでにわかっており、「項の後に演算子、次に項」のルールに従います。 2++ はどのルールにも一致しないため、無効な入力です。

語彙と文法の正式な定義

語彙は通常、正規表現で表されます。

たとえば、サンプル言語は次のように定義できます。

INTEGER :0|[1-9][0-9]*PLUS : +MINUS: -
ログイン後にコピー

ご覧のとおり、ここでは正規表現を使用して整数の定義が与えられています。

文法は通常、BNF と呼ばれる形式を使用して定義されます。この例の言語は次のように定義できます。

expression :=  term  operation  termoperation :=  PLUS | MINUSterm := INTEGER | expression
ログイン後にコピー

前に、言語の文法が文脈自由文法であれば、通常のパーサーで解析できると言いました。文脈自由文法の直感的な定義は、BNF 形式で完全に表現できる文法です。正式な定義については、文脈自由文法に関するウィキペディアの記事を参照してください。

パーサーの種類

パーサーには、トップダウン パーサーとボトムアップ パーサーの 2 つの基本的な種類があります。直感的には、トップダウン パーサーは文法の高レベル構造から開始し、そこで一致する構造を見つけようとします。ボトムアップ パーサーは、低レベルのルールから開始し、高レベルのルールが満たされるまで、入力コンテンツを徐々に文法ルールに変換します。

両方のパーサーがこの例をどのように解析するかを見てみましょう:

トップダウン パーサーは高レベルのルールから開始します。最初に 2 + 3 を式として識別し、次に 2 + 3 を識別します。 - 式として 1 (式を識別するプロセスには他のルールの照合が含まれますが、開始点は最上位のルールです)。

ボトムアップ パーサーは入力コンテンツをスキャンし、一致するルールを見つけて、一致する入力コンテンツをルールで置き換えます。このように入力内容が終わるまで置換を続けます。部分的に一致した式はパーサーのスタックに保存されます。

堆栈 输入
2 + 3 - 1
+ 3 - 1
项运算 3 - 1
表达式 - 1
表达式运算符 1
表达式

这种自下而上的解析器称为移位归约解析器,因为输入在向右移位(设想有一个指针从输入内容的开头移动到结尾),并且逐渐归约到语法规则上。

自动生成解析器

有一些工具可以帮助您生成解析器,它们称为解析器生成器。您只要向其提供您所用语言的语法(词汇和语法规则),它就会生成相应的解析器。创建解析器需要对解析有深刻理解,而人工创建并优化解析器并不是一件容易的事情,所以解析器生成器是非常实用的。

WebKit 使用了两种非常有名的解析器生成器:用于创建词法分析器的 Flex 以及用于创建解析器的 Bison (您也可能遇到 Lex 和 Yacc 这样的别名)。Flex 的输入是包含标记的正则表达式定义的文件。Bison 的输入是采用 BNF 格式的语言语法规则。

HTML 解析器

HTML 解析器的任务是将 HTML 标记解析成解析树。

HTML 语法定义

HTML 的词汇和语法在 W3C 组织创建的中进行了定义。当前的版本是 HTML4,HTML5 正在处理过程中。

非与上下文无关的语法

正如我们在解析过程的简介中已经了解到的,语法可以用 BNF 等格式进行正式定义。

很遗憾,所有的常规解析器都不适用于 HTML(我并不是开玩笑,它们可以用于解析 CSS 和 JavaScript)。HTML 并不能很容易地用解析器所需的与上下文无关的语法来定义。

有一种可以定义 HTML 的正规格式:DTD(Document Type Definition,文档类型定义),但它不是与上下文无关的语法。

这初看起来很奇怪:HTML 和 XML 非常相似。有很多 XML 解析器可以使用。HTML 存在一个 XML 变体 (XHTML),那么有什么大的区别呢?

区别在于 HTML 的处理更为“宽容”,它允许您省略某些隐式添加的标记,有时还能省略一些起始或者结束标记等等。和 XML 严格的语法不同,HTML 整体来看是一种“软性”的语法。

显然,这种看上去细微的差别实际上却带来了巨大的影响。一方面,这是 HTML 如此流行的原因:它能包容您的错误,简化网络开发。另一方面,这使得它很难编写正式的语法。概括地说,HTML 无法很容易地通过常规解析器解析(因为它的语法不是与上下文无关的语法),也无法通过 XML 解析器来解析。

HTML DTD

HTML 的定义采用了 DTD 格式。此格式可用于定义 SGML 族的语言。它包括所有允许使用的元素及其属性和层次结构的定义。如上文所述,HTML DTD 无法构成与上下文无关的语法。

DTD 存在一些变体。严格模式完全遵守 HTML 规范,而其他模式可支持以前的浏览器所使用的标记。这样做的目的是确保向下兼容一些早期版本的内容。最新的严格模式 DTD 可以在这里找到: www.w3.org/TR/html4/strict.dtd

DOM

解析器的输出“解析树”是由 DOM 元素和属性节点构成的树结构。DOM 是文档对象模型 (Document Object Model) 的缩写。它是 HTML 文档的对象表示,同时也是外部内容(例如 JavaScript)与 HTML 元素之间的接口。

解析树的根节点是“ Document ”对象。

DOM 与标记之间几乎是一一对应的关系。比如下面这段标记:

<html>  <body>    <p>      Hello World    </p>    <div> <img src="example.png"/></div>  </body></html>
ログイン後にコピー

可翻译成如下的 DOM 树:

图 :示例标记的 DOM 树

和 HTML 一样,DOM 也是由 W3C 组织指定的。请参见 www.w3.org/DOM/DOMTR 。这是关于文档操作的通用规范。其中一个特定模块描述针对 HTML 的元素。HTML 的定义可以在这里找到: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html 。

我所说的树包含 DOM 节点,指的是树是由实现了某个 DOM 接口的元素构成的。浏览器在具体的实现中会有一些供内部使用的其他属性。

解析算法

我们在之前章节已经说过,HTML 无法用常规的自上而下或自下而上的解析器进行解析。

原因在于:

  1. 语言的宽容本质。
  2. 浏览器历来对一些常见的无效 HTML 用法采取包容态度。
  3. 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在 HTML 中,脚本标记如果包含 document.write ,就会添加额外的标记,这样解析过程实际上就更改了输入内容。

由于不能使用常规的解析技术,浏览器就创建了自定义的解析器来解析 HTML。

HTML5 规范详细地描述了解析算法 。此算法由两个阶段组成:标记化和树构建。

标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。

标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。

图 :HTML 解析流程(摘自 HTML5 规范)

标记化算法

该算法的输出结果是 HTML 标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。该算法相当复杂,无法在此详述,所以我们通过一个简单的示例来帮助大家理解其原理。

基本示例 - 将下面的 HTML 代码标记化:

<html>  <body>    Hello world  </body></html>
ログイン後にコピー
ログイン後にコピー

初始状态是数据状态。遇到字符 < 时,状态更改为 “标记打开状态” 。接收一个 a-z 字符会创建“起始标记”,状态更改为 “标记名称状态” 。这个状态会一直保持到接收 > 字符。在此期间接收的每个字符都会附加到新的标记名称上。在本例中,我们创建的标记是 html 标记。

遇到 > 标记时,会发送当前的标记,状态改回 “数据状态” 。 标记也会进行同样的处理。目前 html 和 body 标记均已发出。现在我们回到 “数据状态” 。接收到 Hello world 中的 H 字符时,将创建并发送字符标记,直到接收 中的 < 。我们将为 Hello world 中的每个字符都发送一个字符标记。

现在我们回到 “标记打开状态” 。接收下一个输入字符 / 时,会创建 end tag token 并改为 “标记名称状态” 。我们会再次保持这个状态,直到接收 > 。然后将发送新的标记,并回到 “数据状态” 。 输入也会进行同样的处理。

图 :对示例输入进行标记化

树构建算法

在创建解析器的同时,也会创建 Document 对象。在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式”。

让我们来看看示例输入的树构建过程:

<html>  <body>    Hello world  </body></html>
ログイン後にコピー
ログイン後にコピー

树构建阶段的输入是一个来自标记化阶段的标记序列。第一个模式是 “initial mode” 。接收 HTML 标记后转为 “before html” 模式,并在这个模式下重新处理此标记。这样会创建一个 HTMLHtmlElement 元素,并将其附加到 Document 根对象上。

然后状态将改为 “before head” 。此时我们接收“body”标记。即使我们的示例中没有“head”标记,系统也会隐式创建一个 HTMLHeadElement,并将其添加到树中。

现在我们进入了 “in head” 模式,然后转入 “after head” 模式。系统对 body 标记进行重新处理,创建并插入 HTMLBodyElement,同时模式转变为 “in body”

现在,接收由“Hello world”字符串生成的一系列字符标记。接收第一个字符时会创建并插入“Text”节点,而其他字符也将附加到该节点。

接收 body 结束标记会触发 “after body” 模式。现在我们将接收 HTML 结束标记,然后进入 “after after body” 模式。接收到文件结束标记后,解析过程就此结束。

图 :示例 HTML 的树构建

解析结束后的操作

在此阶段,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态将设置为“完成”,一个“加载”事件将随之触发。

您可以 在 HTML5 规范中查看标记化和树构建的完整算法

浏览器的容错机制

您在浏览 HTML 网页时从来不会看到“语法无效”的错误。这是因为浏览器会纠正任何无效内容,然后继续工作。

以下面的 HTML 代码为例:

<html>  <mytag>  </mytag>  <div>  <p>  </div>    Really lousy HTML  </p></html>
ログイン後にコピー

在这里,我已经违反了很多语法规则(“mytag”不是标准的标记,“p”和“div”元素之间的嵌套有误等等),但是浏览器仍然会正确地显示这些内容,并且毫无怨言。因为有大量的解析器代码会纠正 HTML 网页作者的错误。

不同浏览器的错误处理机制相当一致,但令人称奇的是,这种机制并不是 HTML 当前规范的一部分。和书签管理以及前进/后退按钮一样,它也是浏览器在多年发展中的产物。很多网站都普遍存在着一些已知的无效 HTML 结构,每一种浏览器都会尝试通过和其他浏览器一样的方式来修复这些无效结构。

HTML5 规范定义了一部分这样的要求。WebKit 在 HTML 解析器类的开头注释中对此做了很好的概括。

解析器对标记化输入内容进行解析,以构建文档树。如果文档的格式正确,就直接进行解析。

遗憾的是,我们不得不处理很多格式错误的 HTML 文档,所以解析器必须具备一定的容错性。

我们至少要能够处理以下错误情况:

  1. 明显不能在某些外部标记中添加的元素。在此情况下,我们应该关闭所有标记,直到出现禁止添加的元素,然后再加入该元素。
  2. 我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)。
  3. 向 inline 元素内添加 block 元素。关闭所有 inline 元素,直到出现下一个较高级的 block 元素。
  4. 如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。

让我们看一些 WebKit 容错的示例:

使用了
而不是

有些网站使用了
而不是
。为了与 IE 和 Firefox 兼容,WebKit 将其与
做同样的处理。代码如下:

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {     reportError(MalformedBRError);     t->beginTag = true;}
ログイン後にコピー

请注意,错误处理是在内部进行的,用户并不会看到这个过程。

离散表格

离散表格是指位于其他表格内容中,但又不在任何一个单元格内的表格。比如以下的示例:

<table>    <table>        <tr><td>inner table</td></tr>    </table>    <tr><td>outer table</td></tr></table>
ログイン後にコピー

WebKit 会将其层次结构更改为两个同级表格:

<table>    <tr><td>outer table</td></tr></table><table>    <tr><td>inner table</td></tr></table>
ログイン後にコピー

代码如下:

if (m_inStrayTableContent && localName == tableTag)        popBlock(tableTag);
ログイン後にコピー

WebKit 使用一个堆栈来保存当前的元素内容,它会从外部表格的堆栈中弹出内部表格。现在,这两个表格就变成了同级关系。

嵌套的表单元素

如果用户在一个表单元素中又放入了另一个表单,那么第二个表单将被忽略。代码如下:

if (!m_currentFormElement) {        m_currentFormElement = new HTMLFormElement(formTag,    m_document);}
ログイン後にコピー

过于复杂的标记层次结构

代码的注释已经说得很清楚了。

示例网站 www.liceo.edu.mx 嵌套了约 1500 个标记,全都来自一堆 标记。我们只允许最多 20 层同类型标记的嵌套,如果再嵌套更多,就会全部忽略。

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;}
ログイン後にコピー

放错位置的 html 或者 body 结束标记

同样,代码的注释已经说得很清楚了。

支持格式非常糟糕的 HTML 代码。我们从不关闭 body 标记,因为一些愚蠢的网页会在实际文档结束之前就关闭。我们通过调用 end() 来执行关闭操作。

if (t->tagName == htmlTag || t->tagName == bodyTag )        return;
ログイン後にコピー

所以网页作者需要注意,除非您想作为反面教材出现在 WebKit 容错代码段的示例中,否则还请编写格式正确的 HTML 代码。

CSS 解析

还记得简介中解析的概念吗?和 HTML 不同,CSS 是上下文无关的语法,可以使用简介中描述的各种解析器进行解析。事实上, CSS 规范定义了 CSS 的词法和语法 。

让我们来看一些示例:词法语法(词汇)是针对各个标记用正则表达式定义的:

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”是标识符 (identifier) 的缩写,比如类名。“name”是元素的 ID(通过“#”来引用)。

语法是采用 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*] ')' ]  ;
ログイン後にコピー

解释:这是一个规则集的结构:

div.error , a.error {  color:red;  font-weight:bold;}
ログイン後にコピー

div.error 和 a.error 是选择器。大括号内的部分包含了由此规则集应用的规则。此结构的正式定义是这样的:

ruleset  : selector [ ',' S* selector ]*    '{' S* declaration [ ';' S* declaration ]* '}' S*  ;
ログイン後にコピー

这表示一个规则集就是一个选择器,或者由逗号和空格(S 表示空格)分隔的多个(数量可选)选择器。规则集包含了大括号,以及其中的一个或多个(数量可选)由分号分隔的声明。“声明”和“选择器”将由下面的 BNF 格式定义。

WebKit CSS 解析器

WebKit 使用解析器生成器,通过 CSS 语法文件自动创建解析器。正如我们之前在解析器简介中所说,Bison 会创建自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。这两种解析器都会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。

图 :解析 CSS

处理脚本和样式表的顺序

脚本

网络的模型是同步的。网页作者希望解析器遇到

人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート