iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 3
4

暖身完畢!本文開始進入本系列重點。
接下來要深入探討渲染引擎的運作原理以及實作方式。

目前普及的瀏覽器引擎

最常聽到的莫過於 Mozilla 的 Gecko,最主要被用在 Firefox 上,也是第一款被設計為可作為單一模組存在的瀏覽器引擎。另外就是 WebKit,起初用在 Linux 平台,後來經由 Apple 公司進行修改後,Windows 和 macOS 也支援,主要用在 Chrome 和 Safari。這兩款都是開源專案。

關於兩者的工作流程,可以參考 how browser work 這篇文章的示意圖。
Webkit 長這樣:

Gecko 長這樣:

可以看到雖然兩者採用的術語不同,但概念其實是很像的。當然,更深入的實作方式就完全不同了,各種小細節造就一個引擎的厲害程度,也是各家比拼的重點!

解析

在上面兩張圖中,我們都可以看到 HTML Parser,功用就是把 HTML 的原始碼解析成 DOM tree。HTML 在解析的時候不能用一般的解析方式,因為 HTML 本身可以容錯,例如說原本應該是 <div></div>,但如果我們不小心漏掉 </div>,依舊可以看到正確地顯示。雖然說容錯對於使用者來說很方便,但也就使得解析的方式比較特別了。事實上,W3C 有完整定義如何去做這個步驟,通常我們在開發引擎核心時,就是直接照著 SPEC 來做。

同理, CSS Parser,功用就是把 CSS 的原始碼解析成 CSS tree。而一樣 W3C 也定義好規範了。CSS 因為不像 HTML 有上下關係(有<div>就會有</div>),所以可以使用一般常見解析的解析方式。例如:

WebKit 使用了兩種非常有名的解析器生成器:用於創建詞法分析器的 Flex 以及用於創建解析器的 Bison(您也可能遇到 Lex和 Yacc這樣的別名)。Flex 的輸入是包含標記的正則表達式定義的檔案。Bison 的輸入是採用 BNF 格式的語言語法規則。

關於瀏覽器引擎的處理方式,how browser work 這篇經典文章講的太清楚了,我認為這篇已經非常精簡也很清楚,因此不打算重複闡述原理。如果大家在閱讀這篇上有遇到問題,歡迎和我討論!

開發

當我們在設計一個瀏覽器的時候,寫處理 HTML 和 CSS 的部分是相對容易的(這邊不探討 JS,那又是另一個引擎了),因為 W3C 都有明確規範,例如我們常聽到的 HTML5、CSS3,因為要讓各家品牌做出來的東西可以呈現一樣,才不會發生同一份原始碼,在 Chrome 上跟在 Firefox 上差非常多,這樣開發者大概會瘋掉吧!所以 W3C 才都有完整的定義,甚至除了規則以外,連實作的細項步驟都寫得一清二楚。閒來無事的話可以點開看看 https://html.spec.whatwg.org

例如擷取 Servo 專案 components/script/dom/document.rs 的其中一段,可以看到這目前這個函式,就是根據註解起來的 SPEC 文件中的 current-document-readiness 來寫的。


// https://html.spec.whatwg.org/multipage/#current-document-readiness
pub fn set_ready_state(&self, state: DocumentReadyState) {
    match state {
        DocumentReadyState::Loading => {
            // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserconnected
            self.trigger_mozbrowser_event(MozBrowserEvent::Connected);
            update_with_current_time_ms(&self.dom_loading);
        },
        DocumentReadyState::Complete => {
            // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadend
            self.trigger_mozbrowser_event(MozBrowserEvent::LoadEnd);
            update_with_current_time_ms(&self.dom_complete);
        },
        DocumentReadyState::Interactive => update_with_current_time_ms(&self.dom_interactive),
    };

    self.ready_state.set(state);

    self.upcast::<EventTarget>().fire_event(atom!("readystatechange"));
}

關於作者

劉安齊

軟體工程師,熱愛寫程式,更喜歡推廣程式讓更多人學會


上一篇
先來看看瀏覽器內核長怎樣吧!
下一篇
瀏覽器引擎處理 DOM 的簡易版
系列文
來做個網路瀏覽器吧!Let's build a web browser!35

尚未有邦友留言

立即登入留言