QT WebKit鼠標引發事件處理
QT WebKit鼠標引發事件處理是本文要介紹的內容,主要是來學習QT WebKit的事件處理的機制,以鼠標事件為案例,具體內容的詳解來看本文。先來貼個圖,來看:
Figure 1. JavaScript onclick event
先看一段簡單的HTML文件。在瀏覽器里打開這個文件,將看到兩張照片。把鼠標移動到第一張照片,點擊鼠標左鍵,將自動彈出一個窗口,上書“World”。但是當鼠標移動到第二張照片,或者其它任何區域,點擊鼠標,卻沒有反應。關閉“World”窗口,自動彈出第二個窗口,上書“Hello”。
- <html>
- <script type="text/javascript">
- function myfunction(v)
- {
- alert(v)
- }
- </script>
- <body onclick="myfunction('Hello')">
- <p>
- <img onclick="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的元素屬性(Element attribute),還是JavaScript的事件偵聽器(EventListener)?換而言之,當用戶點擊鼠標以后,負責處理onclick事件的,是Webkit 還是JavaScript Engine?
4、Alert() 是HTML定義的方法,還是JavaScript提供的函數?誰負責生成那兩個彈出的窗口,是Webkit還是JavaScript Engine?
5、注意到有兩個onclick="myfunction(...)",當用戶在第一張照片里點擊鼠標的時候,為什么是先后彈出,而不是同時彈出?
6、除了PC上的瀏覽器以外,手機是否也可以完成同樣的事件及其響應?假如手機上沒有鼠標,但是有觸摸屏,如何把onclick定義成用手指點擊屏幕?
7、為什么需要深入了解這些問題? 除了滿足好奇心以外,還有沒有其它目的?
Figure 2. Event callback stacks
當用戶點擊鼠標,在OS語匯里,這叫發生了一次中斷(interrupt)。系統內核(kernel) 如何偵聽以及處理interrupt,不妨參閱“Programming Embedded Systems” 一書,Chapter 8. Interrupts。這里不展開介紹,有兩個原因,1. 這些內容很龐雜,而且與本文主題不太相關。2. 從Webkit角度看,它不必關心interrupt 以及interrupt handling 的具體實現,因為Webkit建筑在GUI Toolkit之上,而GUI Toolkit已經把底層的interrupt handling,嚴密地封裝起來。Webkit只需要調用GUI Toolkit 的相關APIs,就可以截獲鼠標的點擊和移動,鍵盤的輸入等等諸多事件。所以,本文著重討論Figure 2 中,位于頂部的Webkit和JavaScript兩層。
不同的操作系統,有相應的GUI Toolkit。GUI Toolkit提供一系列APIs,方便應用程序去管理各色窗口和控件,以及鼠標和鍵盤等等UI事件的截獲和響應。
1、微軟的Windows操作系統之上的GUI Toolkit,是MFC(Microsoft Fundation Classes)。
2、Linux操作系統GNOME環境的GUI Toolkit,是GTK+.
3、Linux KDE環境的,是QT。
4、Java的GUI Toolkit有兩個,一個是Sun Microsystem的Java Swing,另一個是IBM Eclipse的SWT。
Swing對native的依賴較小,它依靠Java 2D來繪制窗口以及控件,而Java 2D對于native的依賴基本上只限于用native library畫點畫線著色。 SWT對native的依賴較大,很多人把SWT理解為Java通過JNI,對MFC,GTK+和QT進行的封裝。這種理解雖然不是百分之百準確,但是大體上也沒錯。
有了GUI Toolkit,應用程序處理鼠標和鍵盤等等UI事件的方式,就簡化了許多,只需要做兩件事情。1. 把事件來源(event source),與事件處理邏輯(event listener) 綁定。2. 實現事件處理邏輯的細節。
Figure 3 顯示的是Webkit如何綁定event source和event listener。Figure 4 顯示的是Webkit如何調用JavaScript Engine,解析并執行事件處理邏輯。首先看看event source,注意到在HTML文件里有這么一句,
- <img onclick="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,和一棵Render Tree。對應于這一句<img>語句,在DOM Tree里有一個HTMLElement節點,相應地,在Render Tree里有一個RenderImage節點。在layout() 過程結束后,根據<img>語句中規定的height和width,確定了RenderImage的大小和位置。由于 Render Tree的RenderImage節點,與DOM Tree的HTMLElement節點一一對應,所以HTMLElement節點所處的位置和大小也相應確定。
因為onclick事件與這個HTMLElement節點相關聯,所以這個HTMLElement節點的位置和大小確定了以后,點擊事件的觸發區域也就自動確定。假如修改了HTML 文件,替換了照片,經過layout() 過程以后,新照片對應的HTMLElement節點,它的位置和大小也自動相應變化,所以,點擊事件的觸發區域也就相應地自動變化。
在onclick屬性的值里,定義了如何處理這個事件的邏輯。有兩種處理事件的方式,1. 直接調用HTML DOM method,2. 間接調用外設的Script。onclick="alert('Hello')",是第一種方式。alert()是W3C制訂的標準的 HTML DOM methods之一。除此以外,也有稍微復雜一點的methods,譬如可以把這一句改成,<img onclick="document.write('Hello')">。本文的例子,onclick="myfunction('world')",是第二種方式,間接調用外設的Script。
外設的script有多種,最常見的是JavaScript,另外,微軟的VBScript和Adobe的ActionScript,在一些瀏覽器里也能用。即便是JavaScript,也有多種版本,各個版本之間,語法上存在一些差別。為了消弭這些差別,降低JavaScript使用者,以及 JavaScript Engine開發者的負擔,ECMA(歐洲電腦產聯)試圖制訂一套標準的JavaScript規范,稱為ECMAScript。
各個瀏覽器使用的JavaScript Engine不同。
1、微軟的IE瀏覽器,使用的JavaScript Engine是JScript Engine,渲染機是Trident。
2、Firefox瀏覽器,使用的JavaScript Engine是TraceMonkey,TraceMonkey的前身是SpiderMonkey,渲染機是Gecko。TraceMonkey JavaScript Engine借用了Adobe的Tamarin的部分代碼,尤其是Just-In-Time即時編譯機的代碼。而Tamarin也被用在Adobe Flash的Action Engine中。
3、Opera瀏覽器,使用的JavaScript Engine是Futhark,它的前身是Linear_b,渲染機是Presto。
4、Apple的Safari瀏覽器,使用的JavaScript Engine是SquirrelFish,渲染機是Webkit。
5、Google的Chrome瀏覽器,使用的JavaScript Engine是V8,渲染機也是Webkit。
6、Linux的KDE和GNOME環境中可以使用Konqueror瀏覽器,這個瀏覽器使用的JavaScript Engine是JavaScriptCore,前身是KJS,渲染機也是Webkit。
同樣是Webkit渲染機,可以調用不同的JavaScript Engine。之所以能做到這一點,是因為Webkit的架構設計,在設置JavaScript Engine的時候,利用代理器,采取了松散的調用方式
Figure 3. The listener binding of Webkit
Figure 3 詳細描繪了Webkit 設置JavaScript Engine 的全過程。在Webkit 解析HTML文件,生成DOM Tree 和Render Tree 的過程中,當解析到 <img onclick="..." src="..."> 這一句的時候,生成DOM Tree中的 HTMLElement 節點,以及Render Tree 中 RenderImage 節點。如前文所述。在生成HTMLElement 節點的過程中,因為注意到有onclick屬性,Webkit決定需要給 HTMLElement 節點綁定一個 EventListener,參見Figure 3 中第7步。
Webkit 把所有EventListener 的創建工作,交給Document 統一處理,類似于 Design Patterns中,Singleton 的用法。也就是說,DOM Tree的根節點 Document,掌握著這個網頁涉及的所有EventListeners。 有趣的是,當Document 接獲請求后,不管針對的是哪一類事件,一律讓代理器 (kjsProxy) 生成一個JSLazyEventListener。之所以說這個實現方式有趣,是因為有幾個問題需要特別留意,
1、一個HTMLElement節點,如果有多個類似于onclick的事件屬性,那么就需要多個相應的EventListener object instances與之綁定。
2、每個節點的每個事件屬性,都對應一個獨立的EventListener object instance。不同節點不共享同一個 EventListener object instance。即便同一個節點中,不同的事件屬性,對應的也是不同的EventListener object instances。
這是一個值得批評的地方。不同節點不同事件對應彼此獨立的EventListener object instances,這種做法給不同節點之間的信息傳遞,造成了很大障礙。反過來設想一下,如果能夠有一種機制,讓同一個object instance,穿梭于多個HTMLElement Nodes之間,那么瀏覽器的表現能力將會大大增強,屆時,將會出現大量的前所未有的匪夷所思的應用。
3、DOM Tree的根節點,Document,統一規定了用什么工具,去解析事件屬性的值,以及執行這個屬性值所定義的事件處理邏輯。如前文所述,事件屬性的值,分成HTML DOM methods 和JavaScript 兩類。但是不管某個HTMLElement節點的某個事件屬性的值屬于哪一類,Document 一律讓 kjsProxy代理器,生成一個 EventListener。
看看這個代理器的名字就知道,kjsProxy生成的 EventListener,一定是依托JavaScriptCore Engine,也就是以前的KJS JavaScript Engine,來執行事件處理邏輯的。核實一下源代碼,這個猜想果然正確。
4、如果想把JavaScriptCore 替換成其它JavaScript Engine,例如Google 的V8,不能簡單地更改configuration file,而需要修改一部分源代碼。所幸的是,Webkit的架構設計相當清晰,所以需要改動部分不多,關鍵部位是把 Document.{h,cpp} 以及其它少數源代碼中,涉及kjsProxy 的部分,改成其它Proxy即可。
5、kjsProxy 生成的EventListener,是JSLazyEventListener。解釋一下JSLazyEventListener 命名的寓意,JS容易理解,意思是把事件處理邏輯,交給JavaScript engine 負責。所謂 lazy 指的是,除非用戶在照片顯示區域點擊了鼠標,否則,JavaScript Engine 不主動處理事件屬性的值所規定的事件處理邏輯。
與 lazy做法相對應的是JIT即時編譯,譬如有一些JavaScript Engine,在用戶尚沒有觸發任何事件以前,預先編譯了所有與該網頁相關的JavaScript,這樣,當用戶觸發了一個特定事件,需要調用某些 JavaScript functions時,運行速度就會加快。當然,預先編譯會有代價,可能會有一些JavaScript functions,雖然編譯過了,但是從來沒有被真正執行過。
Figure 4. The event handling of Webkit
當解析完HTML文件,生成了完整的DOM Tree 和Render Tree 以后,Webkit就準備好去響應和處理用戶觸發的事件了。響應和處理事件的整個流程,如Figure 4所描述。整個流程分成兩個階段,
1、尋找 EventTargetNode。
當用戶觸發某個事件,例如點擊鼠標,根據鼠標所在位置,從Render Tree的根節點開始,一路搜索到鼠標所在位置對應的葉子節點。Render Tree根節點對應的是整個瀏覽器頁面,而葉子節點對應的區域面積最小。
從Render Tree根節點,到葉子節點,沿途每個Render Tree Node,都對應一個DOM Tree Node。這一串DOM Tree Nodes中,有些節點響應用戶觸發的事件,另一些不響應。例如在本文的例子中,<body> tag 對應的DOM Tree Node,和第一張照片的<img> tag 對應的DOM Tree Node,都對onclick事件有響應。
第一階段結束時,Webkit得到一個EventTargetNode,這個節點是一個DOM Tree Node,而且是對事件有響應的DOM Tree Node。如果存在多個DOM Tree Nodes對事件有響應,EventTargetNode是那個最靠近葉子的中間節點。
2、執行事件處理邏輯。
如果對于同一個事件,有多個響應節點,那么JavaScript Engine 依次處理這一串節點中,每一個節點定義的事件處理邏輯。事件處理邏輯,以字符串的形式定義在事件屬性的值中。在本文的例子中,HTML文件包含<img onclick="myfunction('World')">,和<body onclick="myfunction('Hello')">,這意味著,有兩個DOM Tree Nodes 對onclick事件有響應,它們的事件處理邏輯分別是myfunction('World') 和myfunction('Hello'),這兩個字符串。
當JavaScript Engine 獲得事件處理邏輯的字符串后,它把這個字符串,根據JavaScript的語法規則,解析為一棵樹狀結構,稱作Parse Tree。有了這棵Parse Tree,JavaScript Engine就可以理解這個字符串中,哪些是函數名,哪些是變量,哪些是變量值。理解清楚以后,JavaScript Engine 就可以執行事件處理邏輯了。本文例子的事件處理過程,如Figure 4中第16步,到第35步所示。
本文的例子中,“myfunction('World')" 這個字符串本身并沒有定義事件處理邏輯,而只是提供了一個JavaScript函數的函數名,以及函數的參數的值。當JavaScript Engine 得到這個字符串以后,解析,執行。執行的結果是得到函數實體的代碼。函數實體的代碼中,最重要的是alert(v) 這一句。JavaScript Engine 把這一句解析成Parse Tree,然后執行。
注意到本文例子中,對于同一個事件onclick,有兩個不同的DOM Tree Nodes 有響應。處理這兩個節點的先后順序要么由capture path,要么由bubbling path決定,如Figure 5所示。(Figure 5中對應的HTML文件,不是本文所引的例子)。在HTML文件中,可以規定event.bubbles屬性。如果沒有規定,那就按照bubbling的順序進行,所以本文的例子,是先執行<img>,彈出“World” 的窗口,關掉“World”窗口后,接著執行<body>,彈出“Hello” 的窗口。
小結:QT WebKit鼠標引發事件處理的內容介紹完了,希望通過本文的學習能對你有所幫助!