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