WebKit,鼠標引發的故事

Figure 1. JavaScript onclick event
Courtesy http://farm4.static.flickr.com/3302/3640149734_3268bf297f_o.jpg

先看一段簡單的HTML文件。在瀏覽器裏打開這個文件,將看到兩張照片。把鼠標移動到第一張照片,點擊鼠標左鍵,將自動彈出一個窗口,上書“World”。但是當鼠標移動到第二張照片,或者其它任何區域,點擊鼠標,卻沒有反應。關閉“World”窗口,自動彈出第二個窗口,上書“Hello”。

<html>
  <scripttype="text/javascript">
    functionmyfunction(v)
    {
     alert(v)
    }
  </script>

  <bodyοnclick="myfunction('Hello')">
   <p>
   <img οnclick="myfunction('World')"height="250" width="290"src="http://www.dirjournal.com/info/wp-content/uploads/2009/02/antarctica_mountain_mirrored.jpg">
   <p>
   <img height="206" width="275"src="http://media-cdn.tripadvisor.com/media/photo-s/01/26/f4/eb/hua-shan-hua-mountain.jpg">
  </body>
</html>

這段HTML文件沒有什麼特別之處,所有略知一點HTML的人,估計都會寫。但是耳熟能詳,未必等於深入瞭解。不妨反問自己幾個問題,

1. 瀏覽器如何知道,是否鼠標的位置,在第一個照片的範圍內?

2.假如修改一下HTML文件,把第一張照片替換成另一張照片,前後兩張照片的尺寸不同。在瀏覽器裏打開修改後的文件,我們會發現,能夠觸發彈出窗口事件的區域面積,隨着照片的改變而自動改變。瀏覽器內部,是通過什麼樣的機制,自動識別事件觸發區域的?

3. Onclick 是HTML的元素屬性(Elementattribute),還是JavaScript的事件偵聽器(EventListener)?換而言之,當用戶點擊鼠標以後,負責處理onclick事件的,是Webkit 還是JavaScript Engine?

4. Alert()是HTML定義的方法,還是JavaScript提供的函數?誰負責生成那兩個彈出的窗口,是Webkit還是JavaScriptEngine?

5.注意到有兩個οnclick="myfunction(...)",當用戶在第一張照片裏點擊鼠標的時候,爲什麼是先後彈出,而不是同時彈出?

6.除了PC上的瀏覽器以外,手機是否也可以完成同樣的事件及其響應?假如手機上沒有鼠標,但是有觸摸屏,如何把onclick定義成用手指點擊屏幕?

7. 爲什麼需要深入瞭解這些問題? 除了滿足好奇心以外,還有沒有其它目的?

[轉載]新時代新潮流WebOS <wbr>【22】WebKit,鼠標引發的故事

Figure 2. Event callback stacks
Courtesy http://farm4.static.flickr.com/3611/3640149728_bc64397f60_o.gif

當用戶點擊鼠標,在OS語彙裏,這叫發生了一次中斷(interrupt)。系統內核(kernel)如何偵聽以及處理interrupt,不妨參閱“Programming Embedded Systems” 一書,Chapter 8.Interrupts。這裏不展開介紹,有兩個原因,1. 這些內容很龐雜,而且與本文主題不太相關。2.從Webkit角度看,它不必關心interrupt 以及interrupt handling 的具體實現,因爲Webkit建築在GUIToolkit之上,而GUI Toolkit已經把底層的interrupthandling,嚴密地封裝起來。Webkit只需要調用GUI Toolkit的相關APIs,就可以截獲鼠標的點擊和移動,鍵盤的輸入等等諸多事件。所以,本文着重討論Figure 2中,位於頂部的Webkit和JavaScript兩層。

不同的操作系統,有相應的GUI Toolkit。GUIToolkit提供一系列APIs,方便應用程序去管理各色窗口和控件,以及鼠標和鍵盤等等UI事件的截獲和響應。

1. 微軟的Windows操作系統之上的GUI Toolkit,是MFC(Microsoft FundationClasses)。

2. Linux操作系統GNOME環境的GUI Toolkit,是GTK+.

3. Linux KDE環境的,是QT。

4. Java的GUI Toolkit有兩個,一個是Sun Microsystem的Java Swing,另一個是IBMEclipse的SWT。
 
 
   Swing對native的依賴較小,它依靠Java2D來繪製窗口以及控件,而Java 2D對於native的依賴基本上只限於用native library畫點畫線着色。SWT對native的依賴較大,很多人把SWT理解爲Java通過JNI,對MFC,GTK+和QT進行的封裝。這種理解雖然不是百分之百準確,但是大體上也沒錯。

有了GUI Toolkit,應用程序處理鼠標和鍵盤等等UI事件的方式,就簡化了許多,只需要做兩件事情。1. 把事件來源(eventsource),與事件處理邏輯(event listener) 綁定。2. 解析並執行事件處理邏輯。

Figure 3 顯示的是Webkit如何綁定event source和event listener。Figure 4顯示的是Webkit如何調用JavaScript Engine,解析並執行事件處理邏輯。首先看看eventsource,注意到在HTML文件裏有這麼一句,
   <imgοnclick="myfunction('World')" height="250"width="290" src=".../antarctica_mountain_mirrored.jpg">

這句話裏“<img>”標識告訴Webkit,需要在瀏覽器頁面裏擺放一張照片,“src”屬性明確了照片的來源,“height,width”明確了照片的尺寸。“onclick”屬性提醒Webkit,當用戶把鼠標移動到照片顯示的區域,並點擊鼠標時(onclick),需要有所響應。響應的方式定義在“onclick”屬性的值裏面,也就是“myfunction('World')”。

當Webkit解析這個HTML文件時,它依據這個HTML文件生成一棵DOM Tree,和一棵RenderTree。對應於這一句<img>語句,在DOMTree裏有一個HTMLElement節點,相應地,在Render Tree裏有一個RenderImage節點。在layout()過程結束後,根據<img>語句中規定的height和width,確定了RenderImage的大小和位置。由於Render Tree的RenderImage節點,與DOMTree的HTMLElement節點一一對應,所以HTMLElement節點所處的位置和大小也相應確定。

因爲onclick事件與這個HTMLElement節點相關聯,所以這個HTMLElement節點的位置和大小確定了以後,點擊事件的觸發區域也就自動確定。假如修改了HTML文件,替換了照片,經過layout()過程以後,新照片對應的HTMLElement節點,它的位置和大小也自動相應變化,所以,點擊事件的觸發區域也就相應地自動變化。

在onclick屬性的值裏,定義瞭如何處理這個事件的邏輯。有兩種處理事件的方式,1. 直接調用HTML DOM method,2.間接調用外設的Script。οnclick="alert('Hello')",是第一種方式。alert()是W3C制訂的標準的HTML DOMmethods之一。除此以外,也有稍微複雜一點的methods,譬如可以把這一句改成,<imgοnclick="document.write('Hello')">。本文的例子,οnclick="myfunction('world')",是第二種方式,間接調用外設的Script。

外設的script有多種,最常見的是JavaScript,另外,微軟的VBScript和Adobe的ActionScript,在一些瀏覽器裏也能用。即便是JavaScript,也有多種版本,各個版本之間,語法上存在一些差別。爲了消弭這些差別,降低JavaScript使用者,以及JavaScriptEngine開發者的負擔,ECMA(歐洲電腦產聯)試圖制訂一套標準的JavaScript規範,稱爲ECMAScript。

各個瀏覽器使用的JavaScript Engine不同。

1. 微軟的IE瀏覽器,使用的JavaScript Engine是JScript Engine,渲染機是Trident。

2. Firefox瀏覽器,使用的JavaScriptEngine是TraceMonkey,TraceMonkey的前身是SpiderMonkey,渲染機是Gecko。TraceMonkeyJavaScriptEngine借用了Adobe的Tamarin的部分代碼,尤其是Just-In-Time即時編譯機的代碼。而Tamarin也被用在AdobeFlash的Action Engine中。

3. Opera瀏覽器,使用的JavaScriptEngine是Futhark,它的前身是Linear_b,渲染機是Presto。

4. Apple的Safari瀏覽器,使用的JavaScriptEngine是SquirrelFish,渲染機是Webkit。

5. Google的Chrome瀏覽器,使用的JavaScript Engine是V8,渲染機也是Webkit。

6. Linux的KDE和GNOME環境中可以使用Konqueror瀏覽器,這個瀏覽器使用的JavaScriptEngine是JavaScriptCore,前身是KJS,渲染機也是Webkit。

同樣是Webkit渲染機,可以調用不同的JavaScriptEngine。之所以能做到這一點,是因爲Webkit的架構設計,在設置JavaScriptEngine的時候,利用代理器,採取了鬆散的調用方式。

[轉載]新時代新潮流WebOS <wbr>【22】WebKit,鼠標引發的故事
Figure 3. The listener binding of Webkit
Courtesy http://farm4.static.flickr.com/3659/3640149732_e55446f6b3_b.jpg

Figure 3 詳細描繪了Webkit 設置JavaScript Engine 的全過程。在Webkit解析HTML文件,生成DOM Tree 和Render Tree 的過程中,當解析到 <imgοnclick="..." src="..."> 這一句的時候,生成DOM Tree中的HTMLElement 節點,以及Render Tree 中 RenderImage 節點。如前文所述。在生成HTMLElement節點的過程中,因爲注意到有onclick屬性,Webkit決定需要給 HTMLElement 節點綁定一個EventListener,參見Figure 3 中第7步。

Webkit 把所有EventListener 的創建工作,交給Document 統一處理,類似於 DesignPatterns中,Singleton 的用法。也就是說,DOM Tree的根節點Document,掌握着這個網頁涉及的所有EventListeners。 有趣的是,當Document接獲請求後,不管針對的是哪一類事件,一律讓代理器 (kjsProxy)生成一個JSLazyEventListener。之所以說這個實現方式有趣,是因爲有幾個問題需要特別留意,

1. 一個HTMLElement節點,如果有多個類似於onclick的事件屬性,那麼就需要多個相應的EventListenerobject instances與之綁定。

2. 每個節點的每個事件屬性,都對應一個獨立的EventListener object instance。不同節點不共享同一個EventListener objectinstance。即便同一個節點中,不同的事件屬性,對應的也是不同的EventListener objectinstances。

  這是一個值得商榷的地方。不同節點不同事件對應彼此獨立的EventListener objectinstances,這種做法給不同節點之間的信息傳遞,造成了很大障礙。反過來設想一下,如果能夠有一種機制,讓同一個objectinstance,穿梭於多個HTMLElementNodes之間,那麼瀏覽器的表現能力將會大大增強,屆時,將會出現大量的前所未有的匪夷所思的應用。

3. DOMTree的根節點,Document,統一規定了用什麼工具,去解析事件屬性的值,以及執行這個屬性值所定義的事件處理邏輯。如前文所述,事件屬性的值,分成HTMLDOM methods 和JavaScript兩類。但是不管某個HTMLElement節點的某個事件屬性的值屬於哪一類,Document一律讓 kjsProxy代理器,生成一個EventListener。

   看看這個代理器的名字就知道,kjsProxy生成的EventListener,一定是依託JavaScriptCore Engine,也就是以前的KJS JavaScriptEngine,來執行事件處理邏輯的。覈實一下源代碼,這個猜想果然正確。

4. 如果想把JavaScriptCore 替換成其它JavaScriptEngine,例如Google的V8,不能簡單地更改configurationfile,而需要修改一部分源代碼。所幸的是,Webkit的架構設計相當清晰,所以需要改動部分不多,關鍵部位是把Document.{h,cpp}以及其它少數源代碼中,涉及kjsProxy 的部分,改成其它Proxy即可。

5. kjsProxy生成的EventListener,是JSLazyEventListener。解釋一下JSLazyEventListener命名的寓意,JS容易理解,意思是把事件處理邏輯,交給JavaScript engine 負責。所謂 lazy指的是,除非用戶在照片顯示區域點擊了鼠標,否則,JavaScript Engine不主動處理事件屬性的值所規定的事件處理邏輯。

   與lazy做法相對應的是JIT即時編譯,譬如有一些JavaScriptEngine,在用戶尚沒有觸發任何事件以前,預先編譯了所有與該網頁相關的JavaScript,這樣,當用戶觸發了一個特定事件,需要調用某些JavaScript functions時,運行速度就會加快。當然,預先編譯會有代價,可能會有一些JavaScriptfunctions,雖然編譯過了,但是從來沒有被真正執行過。


[轉載]新時代新潮流WebOS <wbr>【22】WebKit,鼠標引發的故事
Figure 4. The event handling of Webkit
Courtesy http://farm4.static.flickr.com/3390/3640149730_0c98f0218d_b.jpg

當解析完HTML文件,生成了完整的DOM Tree 和Render Tree以後,Webkit就準備好去響應和處理用戶觸發的事件了。響應和處理事件的整個流程,如Figure4所描述。整個流程分成兩個階段,

1. 尋找 EventTargetNode。

  當用戶觸發某個事件,例如點擊鼠標,根據鼠標所在位置,從RenderTree的根節點開始,一路搜索到鼠標所在位置對應的葉子節點。RenderTree根節點對應的是整個瀏覽器頁面,而葉子節點對應的區域面積最小。

   從RenderTree根節點,到葉子節點,沿途每個Render Tree Node,都對應一個DOM Tree Node。這一串DOM TreeNodes中,有些節點響應用戶觸發的事件,另一些不響應。例如在本文的例子中,<body>tag 對應的DOM Tree Node,和第一張照片的<img> tag對應的DOM Tree Node,都對onclick事件有響應。

  第一階段結束時,Webkit得到一個EventTargetNode,這個節點是一個DOM TreeNode,而且是對事件有響應的DOM Tree Node。如果存在多個DOM TreeNodes對事件有響應,EventTargetNode是那個最靠近葉子的中間節點。

2. 執行事件處理邏輯。

   如果對於同一個事件,有多個響應節點,那麼JavaScriptEngine依次處理這一串節點中,每一個節點定義的事件處理邏輯。事件處理邏輯,以字符串的形式定義在事件屬性的值中。在本文的例子中,HTML文件包含<imgοnclick="myfunction('World')">,和<bodyοnclick="myfunction('Hello')">,這意味着,有兩個DOM TreeNodes 對onclick事件有響應,它們的事件處理邏輯分別是myfunction('World')和myfunction('Hello'),這兩個字符串。

   當JavaScript Engine獲得事件處理邏輯的字符串後,它把這個字符串,根據JavaScript的語法規則,解析爲一棵樹狀結構,稱作ParseTree。有了這棵Parse Tree,JavaScriptEngine就可以理解這個字符串中,哪些是函數名,哪些是變量,哪些是變量值。理解清楚以後,JavaScript Engine就可以執行事件處理邏輯了。本文例子的事件處理過程,如Figure 4中第16步,到第35步所示。

   本文的例子中,“myfunction('World')"這個字符串本身並沒有定義事件處理邏輯,而只是提供了一個JavaScript函數的函數名,以及函數的參數的值。當JavaScriptEngine 得到這個字符串以後,解析,執行。執行的結果是得到函數實體的代碼。函數實體的代碼中,最重要的是alert(v)這一句。JavaScript Engine 把這一句解析成Parse Tree,然後執行。

  注意到本文例子中,對於同一個事件onclick,有兩個不同的DOM Tree Nodes有響應。處理這兩個節點的先後順序要麼由capture path,要麼由bubbling path決定,如Figure5所示。(Figure5中對應的HTML文件,不是本文所引的例子)。在HTML文件中,可以規定event.bubbles屬性。如果沒有規定,那就按照bubbling的順序進行,所以本文的例子,是先執行<img>,彈出“World”的窗口,關掉“World”窗口後,接着執行<body>,彈出“Hello”的窗口。

[轉載]新時代新潮流WebOS <wbr>【22】WebKit,鼠標引發的故事

Figure 5. The capture and bubbling of event by the DOM tree.
Courtesyhttp://www.w3.org/TR/DOM-Level-3-Events/images/eventflow.png


這一節比較枯燥,因爲涉及了太多的源代碼細節。之所以這麼不厭其煩地說明細節,是爲了解決如何更有效率地處理事件,以及提供更豐富的手段去處理事件。待續。

發佈了3 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章