webkit事件處理

1,概述javascript

原文地址:http://blog.csdn.net/awebkit/article/details/8493716html

瀏覽器處理事件通常有兩個過程,捕獲過程和冒泡過程,這是由addEventListener的第三個參數決定的。java

    基本事件流
  • 每一個事件都對應一個事件目標(EventTarget)(也是一個node 節點),EventTarget 有event 的target 屬性指定。 每一個事件目標註冊有若干事件監聽者(EventListerner), 這些監聽者在事件到達後激活,激活的順序在DOM規範中沒有定義。若是沒有定義事件capture或者bubbling,則當事件目標上的全部事件監聽者 響應完畢後,則這個事件處理完畢。node

  • 事件捕獲
  • 事件捕獲發生在以下狀況下: 一個事件監聽者註冊在某個事件的目標節點的祖先節點上,當該事件發生後,在其到達目標節點以前,祖先節點的事件監聽者首先捕獲並處理,而後將事件逐級下傳,直到目標節點。c++

  • 事件冒泡
  • 事件冒泡初始階段同基本事件流相同,然而在事件的目標節點全部事件監聽者響應完畢後,是將將會沿着節點向其祖先方向上傳,直到document點,上傳過程當中將會逐級激發在遇到節點上註冊的的全部事件監聽者(捕獲事件除外)。web

  • 事件取消
  • 一些事件能夠規定爲可取消的,這些事件通常都會應有一個缺省的動做。當此類事件發生時,首先傳遞給目標節點上的事件監聽者,事件監聽者能夠選擇是否取消該事件的缺省動做。算法

當用戶在瀏覽器裏觸發了點擊鼠標,敲鍵盤等事件後,瀏覽器須要處理這些事件,那麼整個的流程如何呢?數組

首先,WebKit外部事件處理:瀏覽器

這些事件被GUI得到,而後調用WebView的對應處理函數,而這些處理函數會調用當前frame的 EventHandler 來處理對應的事件。架構

WebKit內部事件處理和保存:

EventHalder的處理函數通常還會調用到Node的dispatchGenericEvent,而這個函數會調用到EventTarget。EventTarget類是Node類的父類,裏面使用map保存了事件的處理函數。

對於js事件,對應的有JSEventListener,繼承於 EventListener,在解析script標籤的時候,遇到event屬性,就會建立一個JSEventListener,並調用 EventTarget的事件處理map中。這樣,就完成了對應事件到對應結點的map工做。


2 JavaScript事件處理

詳細介紹參考這篇文章:http://www.cnblogs.com/binyong/articles/1750263.html

這篇文章分享的也很好:http://www.cnblogs.com/diligenceday/p/4175721.html

 

3 JavaScript事件再WebKit中的處理流程

原文地址:http://blog.csdn.net/codigger/article/details/40620721

 

本文主要探討了JavaScript事件在WebKit中的註冊和觸發機制。

JS事件有兩種註冊方式: 經過DOM節點的屬性添加或者經過node.addEventListener()函數註冊;

經過DOM節點的屬性添加以下所示,節點的屬性採用on後面緊接event name的形式,好比onclick, onload; 

 1 <html>
 2 <head>
 3 <script type="text/javascript">
 4   function listener(e){  5  alert("hello world!");  6  }  7 </script>
 8 </head>
 9 <body>
10 <button onclick="listener(event)">click</button>
11 </body>
12 </html>

 

經過addEventListener()函數註冊的形式以下, 其完整的形式是:target.addEventListener(type, listener[, useCapture]);其中type爲事件類型,listener爲響應函數, useCapture表示是否在capture階段觸發,若是不指定,則爲false; 

1 <div>
2 <button id="button">button</button>
3 <script type="text/javascript">
4  document.getElementById('button').addEventListener("click", listener); 5 </script>
6 </div>

 

 

 WebKit中事件相關的類關係如上圖所示:

1. EventTargetDatatMap: 全局映射表,創建了Node與EventTargetData之間的映射關係 ;

2. EventTargetData:   成員變量firingEventIterators是Vector, 用於記錄正在觸發的事件類型,當該Vector非空時,也表示當前正處於firing階段; 成員變量eventListenerMap是EventlListenerMap類型;

3. EventlListenerMap:按事件類型分類保存了EventListeners;  成員變量m_entires是Vector,其中每一項能夠簡化爲std::pair<EventType, EventListenerVector>類型;

4. JSLazyEventListener: 最終響應事件觸發的對象; 保存了JS執行的基本信息(源碼或者JSObject類型的函數對象); 

 

 第一種狀況下,開始事件註冊的時機是發生在頁面解析階段,當建立了button元素之後,解析到onclick屬性時,會根據屬性值建立對應的EventListener; 這種狀況下的EventListener僅保存了JS源碼(尚未轉換成JSC虛擬機內部的函數對象), 並將EventListener添加到全局Hash表中; 

 

第二種狀況下,JS在虛擬機中執行到」addEventListener()"時,會根據JSBindings創建的映射關係,最終調用到WebCore中的native實現Node::addEventListener(), 該函數會根據虛擬機中傳遞過來的函數對象建立EventListener,並在全局Hash表中創建起target node與EventListener(即這裏的button)的映射關係; 

 

 下圖是兩種狀況下,事件註冊的流程對比:

 

事件觸發流程有如下幾個步驟:

1. 找到響應事件的target node: 若是是用戶交互事件,經過Hit Test算法肯定;  若是是瀏覽器內部生成的事件,通常有固定的響應節點,好比load事件的target node是body節點;

2. 事件分發:事件在document與target之間按照(capture, at_target, bubble)的順序進行分發,capture按照從根節點document到子節點target的路徑,而bubble則相反;

3. 事件響應:分發流程中,若是事件分發到的當前節點註冊了該類型的事件,而且useCapure與事件的分發的順序一致(即capture階段時,當前節點註冊了useCapture == true的事件), 則進行事件響應;

    事件響應分紅兩步:

        (1) 從全局映射表中找到當前node對應的EventListeners;

        (2)將EventListeners封裝的JS(源碼或者JSC的函數對象)拋到JS虛擬機中執行(下圖是mouseup事件的觸發時序):

 

 

如前所述,屬性中註冊的事件在EventListener中僅保存了源碼,因此開始執行以前會對源碼進行必要的轉換,格式化成以下形式:

"(function(event) {listener(event)\n})"

 簡單來說,事件註冊是創建node與響應函數的映射關係的過程 ,這種映射關係基於事件類型進行分類; 而事件觸發則是基於這種映射關係,在不一樣階段(capture, bubble)響應註冊函數的過程; 

 

4 webkit DOM事件分析

原文地址:http://blog.csdn.net/shunzi__1984/article/details/6281631 和 百度文庫,做者:upcshu

 

 

 Dom事件模型能夠分爲dom0 和dom2兩種事件模型,因此支持JavaScript的瀏覽器都都會支持dom0事件模型,DOM2定義了高級的事件處理API,和DOM0的API相比,有着使人矚目的不一樣(並且功能更強大).雖然DOM2標準並無把已有的API收入其中,可是DOM0級API也沒有被去除.對於基本的事件處理任務,你會以爲使用這些簡單的API更自由一些.

 

DOM2事件模型被除了IE之外的全部瀏覽器支持。

 webkit在這部分的設計中,較好的這兩種事件模型統一了起來,在註冊的部分,稍有不一樣,咱們知道,dom0的事件監聽器是經過html屬性註冊,而dom2是經過相似js elem.addEventListener()的方式 ,下面是一個相關的類圖。

 

EventTarget直接依賴於EventListener,EventListener是一個抽象類,而後具體的監聽器在下面派生,注意,JSEventListener,以及JSLazeEventListener是與具體的js解釋引擎有關係的。那麼事件監聽器的註冊的流程是怎麼樣的了?下面以body的onload爲例進行說明 。

解析文檔階段:

對應代碼:

 

在這裏,首先會使用ScriptEventListener.cpp中的

createAttributeEventListener函數建立事件監聽器(JSLazyEventListener)。

其次,會調用Document.cpp文件中的setWindowAttributeEventListener函數,在該函數中,會使用文檔(Document)的DOMWindow對象(其實是一個EventTarget)的setAttributeEventListener。

若是裏面註冊得有,會先清除這個事件類型的註冊。而後再調用addEventListener。

添加到EventListenerMap的成員EventListenerVector成員中了

 

註冊流程

 

【跟監聽器相關的類圖】

【EventTarget類中3個重要的方法】

      fireEventListeners響應會根據EventListener來調用handleEvent
      addEventListener添加 會去操做EventListenerMap
      removeEventListener刪除 會去操做EventListenerMap

 

【一切來自於頁面】

當建立節點後,會去執行屬性解析,若是有事件,會建立屬性監聽器(其中一種監聽器),其實是向保存監聽器類型的vector中加入了該類型,以備響應的時候查詢是否須要響應。(有則響應,無則用默認事件處理方式響應,最後這一句不知道對不?)

事件觸發與響應

不是正規流程哦,很奇怪吧!

 

響應流程

針對一個頁面而言,當有事件發生時,會先找到該頁面所屬frame,再傳入該Frame下的EventHandler,首先會取得當前點中的節點,其實由EventDispatcher來按照dom事件傳遞標準 傳遞該事件,在某些節點會處理該事件

實現邏輯(以鼠標事件爲例):

• 鼠標事件發生
• 根據鼠標事件發生的位置, 找到對應的EventTarget 節點
• 在EventDispatcher的 ensureEventAncestors函數中,獲取到全部的父節點,保存到Vector<EventContext>中;
• 進入事件捕獲階段
• 觸發當前EventTarget的當前事件的EventListen
• 進入事件冒泡階段

設置階段後的操做爲該操做。什麼冒泡呀,捕獲呀,target階段

1 windowContext.handleLocalEvents(event.get()) 2 m_ancestors[i - 1].handleLocalEvents(event.get()); 3 m_node->handleLocalEvents(event.get()); 4 m_ancestors[i].handleLocalEvents(event.get()); 5 windowContext.handleLocalEvents(event.get());

 

在winlauncher中最終會調用jsc來處理,暫時不往下看!

執行了兩次EventDispatchMediator::dispatchEvent,可是在這兩次之間,在執行EventDispatchMediator::dispatchEvent中增長一次調整的機會(做用何在呢?)

最終在Target階段(說明,事件處理分爲三個階段:捕獲,target,冒泡),會執行響應。如圖能夠知道:

 

到底怎麼響應呢?須要送到事件監聽器中去,由事件監聽器決定。在EventTarget ::fireEventListeners函數中,先找到相應的事件監聽器,接着纔是處理該事件。從函數實現中,能夠知道,應該使用了jsc來處理。(winlauncher)。

 

實例

 

 

這裏須要補充說明的是:這裏的事件是標籤屬性中,若是變爲js代碼中增長監聽,這種狀況下的執行路徑是怎麼樣呢?諮詢sekerao,應該是js引擎來調用(回調)webkit 的C++註冊監聽器,迴歸到當前上面討論的思路

 

 Dom2

 

在DOM2高級事件模型中,當一個文檔元素(被叫作事件的目標(target)對象)觸發了一個事件,這個目標對象的事件處理程序被觸發

除此以外,該目標對象的每個祖輩元素都有一個或者兩個機會去處理該事件.事件傳播的過程包括三個階段.

在DOM2模型中,你能夠爲一個特定對象的一個特定類型事件註冊任意數量的事件處理程序.

 

【備註】

其實webkit實現了兩個標準,dom0與dom2只是標準而已,從標準角度來講,區別很大,能夠說webkit實現的dom2標準。

 

註冊流程

響應流程

默認處理

點擊一個select框,這裏主要想知道 事件處理流程(與js事件處理的思路 作比較)

 

Ø  Dom事件傳遞

Dom事件傳遞  主要關注「事件流」。事件流的三個階段,以下圖:

 

默認狀況下,仍是在bubbling phase來處理祖先節點的事件響應!

測試用例:file:///C:/Users/jackyshu/Desktop/js_study/js_event_handle.html(參考前面)

是目標節點先響應,接着經過冒泡過程,祖先節點接着處理事件!

 

【代碼走讀】

dom事件傳遞過程,主要在以下函數中完成:

1 bool EventDispatcher::dispatchEvent(PassRefPtr<Event> event)

第一步:設置目標節點

1 event->setTarget(eventTargetRespectingSVGTargetRules(m_node.get()));

第二步:求以該目標節點的祖先數組(vector,最後一個節點爲樹根)。

ensureEventAncestors(event.get());具體實現以下:

 1 while (true) {  2         bool isSVGShadowRoot = ancestor->isSVGShadowRoot();  3         if (isSVGShadowRoot || ancestor->isShadowRoot()) {  4             if (determineDispatchBehavior(event, ancestor) == StayInsideShadowDOM)  5                 return;  6 #if ENABLE(SVG)
 7             ancestor = isSVGShadowRoot ? ancestor->svgShadowHost() : ancestor->shadowHost();  8 #else
 9             ancestor = ancestor->shadowHost(); 10 #endif
11             if (!shouldSkipNextAncestor) 12                 target = ancestor; 13         } else
14             ancestor = ancestor->parentNodeGuaranteedHostFree(); 15 
16         if (!ancestor) 17             return; 18 
19 #if ENABLE(SVG)
20         // Skip SVGShadowTreeRootElement.
21         shouldSkipNextAncestor = ancestor->isSVGShadowRoot(); 22         if (shouldSkipNextAncestor) 23             continue; 24 #endif
25         // FIXME: Unroll the extra loop inside eventTargetRespectingSVGTargetRules into this loop.
26  m_ancestors.append(EventContext(ancestor, eventTargetRespectingSVGTargetRules(ancestor), target)); 27 }

第三步:進入捕獲階段,進行相應事件處理(從上至下)

 1 event->setEventPhase(Event::CAPTURING_PHASE);  2 
 3     if (windowContext.handleLocalEvents(event.get()) && event->propagationStopped())  4         goto doneDispatching;  5 
 6     for (size_t i = m_ancestors.size(); i; --i) {  7         m_ancestors[i - 1].handleLocalEvents(event.get());  8         if (event->propagationStopped())  9             goto doneDispatching; 10     }

第四步:進入目標階段,進行相應事件處理

1 event->setEventPhase(Event::AT_TARGET); 2     event->setTarget(originalTarget.get()); 3     event->setCurrentTarget(eventTargetRespectingSVGTargetRules(m_node.get())); 4     m_node->handleLocalEvents(event.get()); 5     if (event->propagationStopped()) 6         goto doneDispatching;

第五步:進入冒泡階段,進行相應事件處理(從下至上)

 1 if (event->bubbles() && !event->cancelBubble()) {  2         // Trigger bubbling event handlers, starting at the bottom and working our way up.
 3         event->setEventPhase(Event::BUBBLING_PHASE);  4 
 5         size_t size = m_ancestors.size();  6         for (size_t i = 0; i < size; ++i) {  7             m_ancestors[i].handleLocalEvents(event.get());  8             if (event->propagationStopped() || event->cancelBubble())  9                 goto doneDispatching; 10  } 11         windowContext.handleLocalEvents(event.get()); 12     }

 

捕獲階段  爲何沒有作什麼事情呢,由什麼決定呢?

跟蹤發現,因爲RareData爲空,因此直接不執行了!

什麼是RareData呢?

 

代碼中,還可以發現另一個奇怪的問題:

事件流中採用的事件處理函數爲:handleLocalEvents。

另外,若是該過程不處理的話,還有默認事件處理函數爲:defaultEventHandler

還有一個windowContext.handleLocalEvents 這個函數到底在幹什麼呢?

 

跟蹤

file:///C:/Users/jackyshu/Desktop/js_study/js_event_handle.html  點擊第一幅圖,堆棧圖

 

 

從最後這個截圖知道,Node::handleLocalEvents 是當前按下節點的響應。

JSEventListener::handleEvent最終響應該點擊事件,彈出一個警告框。涉及js代碼的執行,跟js引擎相關了!在此不深刻!

 

隨後是祖先節點對點擊事件的響應,按照冒泡的順序執行!

 

 

注意EventDispatcher::dispatchEvent是一個靜態方法哦,Node類在傳遞事件的時候,都是調用的這個,因此可以反映什麼呢?

這個EventContent是幹啥的呢?

還須要作一個測試頁面,測試捕獲和冒泡的問題!

 

Dom事件響應

事件響應在捕獲仍是冒泡階段,由addEventListener的第三個參數來決定的!

若是useCapture爲false,則冒泡階段響應;反之,在捕獲階段響應!
Webkit內部,默認就是在冒泡階段響應,請看代碼:

 

難道說咱們不可以在網頁中來控制麼?讓它在捕獲階段來響應?

 

把事件捕獲和冒泡的過程統稱爲事件的傳播

事件的傳播是能夠阻止的

  • 在W3c中,使用stopPropagation()方法
  • 在IE下設置cancelBubble = true;

在捕獲的過程當中stopPropagation();後,後面的冒泡過程也不會發生了~ 在IE中則使用cancelBubble(IE中只有冒泡,沒有捕獲)

3.阻止事件的默認行爲,例如click <a>後的跳轉~

  • 在W3c中,使用preventDefault()方法;
  • 在IE下設置window.event.returnValue = false;

不是全部的事件都能冒泡,例如:blur、focus、load、unload,(這個測試過load事件哦)還有堆棧圖呢,哈哈!

 

在跟蹤捕獲過程處理事件的時候,發現基本上是在這個過程就返回了。

 

對比冒泡過程

 

就是一個條件的差異而已,致使是否響應!hasRareData()

RareData究竟是什麼意思呢?
NodeFlags 第17個

該堆棧圖是爲了說明何時設置RareData的,這個會影響捕獲的問題。

可以看到凡有事件註冊的地方  應該都會去設置這個標識!

 

EventTarget::fireEventListeners

在這個裏面處理的時候,也會去專門繞過捕獲階段,最終纔可以到冒泡階段!  不知道這樣作的意義是什麼?

 

參考資料:

(1)http://blog.csdn.net/shunzi__1984/article/details/6281631
(2)http://www.starwd.com/?p=340
(3)http://www.cnblogs.com/eoiioe/archive/2009/02/10/1387442.html
(4)http://en.wikipedia.org/wiki/DOM_events 一篇英文 值得學習
(5)http://blog.csdn.net/bestlxm/article/details/7450630

 

 

DOMWindow 中,addEventListener實現以下:

 1 bool DOMWindow::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)  2 {  3     if (!EventTarget::addEventListener(eventType, listener, useCapture))  4         return false;  5 
 6     if (Document* document = this->document())  7         document->addListenerTypeIfNeeded(eventType);  8 
 9     if (eventType == eventNames().unloadEvent) 10         addUnloadEventListener(this); 11     else if (eventType == eventNames().beforeunloadEvent && allowsBeforeUnloadListeners(this)) 12         addBeforeUnloadEventListener(this); 13 #if ENABLE(DEVICE_ORIENTATION)
14     else if (eventType == eventNames().devicemotionEvent && frame() && frame()->page() && frame()->page()->deviceMotionController()) 15         frame()->page()->deviceMotionController()->addListener(this); 16     else if (eventType == eventNames().deviceorientationEvent && frame() && frame()->page() && frame()->page()->deviceOrientationController()) 17         frame()->page()->deviceOrientationController()->addListener(this); 18 #endif
19 
20     return true; 21 }

 

5 QT WebKit 鼠標引起事件處理

轉自:http://mobile.51cto.com/symbian-287629.htm

 

QT WebKit鼠標引起事件處理是本文要介紹的內容,主要是來學習QT WebKit事件處理的機制,以鼠標事件爲案例,具體內容的詳解來看本文。先來貼個圖,來看:

                           Figure 1. JavaScript onclick event

 

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

 1 <html> 
 2   <script type="text/javascript"> 
 3     function myfunction(v)  4  {  5  alert(v)  6  }  7   </script> 
 8  
 9   <body onclick="myfunction('Hello')"> 
10     <p> 
11     <img onclick="myfunction('World')" height="250" width="290" 
12  src="http://www.dirjournal.com/info/wp-content/uploads/2009/02/antarctica_mountain_mirrored.jpg"> 
13     <p> 
14     <img height="206" width="275" 
15  src="http://media-cdn.tripadvisor.com/media/photo-s/01/26/f4/eb/hua-shan-hua-mountain.jpg"> 
16   </body> 
17 </html> 

 

這段HTML文件沒有什麼特別之處,全部略知一點HTML的人,估計都會寫。可是耳熟能詳,未必等於深刻了解。不妨反問本身幾個問題,

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

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

三、Onclick 是HTML的元素屬性(Element attribute),仍是JavaScript的事件偵聽器(EventListener)?換而言之,當用戶點擊鼠標之後,負責處理onclick事件的,是Webkit 仍是JavaScript Engine?

四、Alert() 是HTML定義的方法,仍是JavaScript提供的函數?誰負責生成那兩個彈出的窗口,是Webkit仍是JavaScript Engine?

五、注意到有兩個onclick="myfunction(...)",當用戶在第一張照片裏點擊鼠標的時候,爲何是前後彈出,而不是同時彈出?

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

七、爲何須要深刻了解這些問題? 除了知足好奇心之外,還有沒有其它目的?

 

                   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事件的截獲和響應。

一、微軟的Windows操做系統之上的GUI Toolkit,是MFC(Microsoft Fundation Classes)。

二、Linux操做系統GNOME環境的GUI Toolkit,是GTK+.

三、Linux KDE環境的,是QT。

四、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不一樣

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

二、Firefox瀏覽器,使用的JavaScript Engine是TraceMonkey,TraceMonkey的前身是SpiderMonkey,渲染機是Gecko。TraceMonkey JavaScript Engine借用了Adobe的Tamarin的部分代碼,尤爲是Just-In-Time即時編譯機的代碼。而Tamarin也被用在Adobe Flash的Action Engine中。

三、Opera瀏覽器,使用的JavaScript Engine是Futhark,它的前身是Linear_b,渲染機是Presto。

四、Apple的Safari瀏覽器,使用的JavaScript Engine是SquirrelFish,渲染機是Webkit。

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

六、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。

之因此說這個實現方式有趣,是由於有幾個問題須要特別留意:

一、一個HTMLElement節點,若是有多個相似於onclick的事件屬性,那麼就須要多個相應的EventListener object instances與之綁定。

二、每一個節點的每一個事件屬性,都對應一個獨立的EventListener object instance。不一樣節點不共享同一個 EventListener object instance。

    即使同一個節點中,不一樣的事件屬性,對應的也是不一樣的EventListener object instances。

    這是一個值得批評的地方。

    不一樣節點不一樣事件對應彼此獨立的EventListener object instances,這種作法給不一樣節點之間的信息傳遞,形成了很大障礙。

    反過來設想一下,若是可以有一種機制,讓同一個object instance,穿梭於多個HTMLElement Nodes之間,那麼瀏覽器的表現能力將會大大加強

    屆時,將會出現大量的史無前例的匪夷所思的應用。

三、DOM Tree的根節點,Document,統一規定了用什麼工具,去解析事件屬性的值,以及執行這個屬性值所定義的事件處理邏輯

    如前文所述,事件屬性的值,分紅HTML DOM methods 和JavaScript 兩類。

    可是無論某個HTMLElement節點的某個事件屬性的值屬於哪一類,Document 一概讓 kjsProxy代理器,生成一個 EventListener。

    看看這個代理器的名字就知道,kjsProxy生成的 EventListener,必定是依託JavaScriptCore Engine,也就是之前的KJS JavaScript Engine,來執行事件處理邏輯的。

    覈實一下源代碼,這個猜測果真正確。

四、若是想把JavaScriptCore 替換成其它JavaScript Engine,例如Google 的V8,不能簡單地更改configuration file,而須要修改一部分源代碼。

    所幸的是,Webkit的架構設計至關清晰,因此須要改動部分很少,關鍵部位是把 Document.{h,cpp} 以及其它少數源代碼中,涉及kjsProxy 的部分,改爲其它Proxy便可。

五、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所描述。整個流程分紅兩個階段,

一、尋找 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是那個最靠近葉子的中間節點。

 

二、執行事件處理邏輯

若是對於同一個事件,有多個響應節點,那麼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」 的窗口。

 

 

6 webkit裏 JS 和DOM事件處理

 文庫的文章,文庫做者:yujiawang2008

1,event傳遞到 js

全部的事件都是以WebViewWndProc做爲入口點。

咱們以鼠標事件爲例來分析,其它事件基本相似
在WebView裏又對不一樣類型的事件處理作了分類主要有

鼠標事件:handleMouseEvent
鍵盤事件:keyDown, keyUp

在EventHandler類裏開始對Event進行派發

EventHandler::dispatchMouseEvent

在這裏EventHandler 是frame的一個對象,見frame.h文件 mutable EventHandler m_eventHandler;
在EventHandler記錄了當前dom樹中關於事件的結點全部信息,例如,當前處於鼠標下面的結點,最後處於鼠標下面的結點,最後處於鼠標下面的Scrollbar等。EventHandler裏要作的事情就是在有事件發生的時候找到註冊了該事件的結點,而後更新這些結點,並調用相應結點的事件處理函數。這些事情是在dom結點自己結構的支持下完成的,凡是支持事件的dom結點都是繼承於EventNode,而全部的dom結點類型都繼承與Node。
在Node裏有這樣一個方法dispatchGenericEvent將事件進一步派發到EventTarget在EventTarget裏會觸發RegisteredEventListener 裏註冊的結點的事件處理函數
對於js事件,到了這一步又有一個js事件的入口點:
JSEventListener::handleEvent
JSEventListener從其類型的命名能夠看出它是一個js事件監聽者對象,既然有js事件監聽者,那能夠想象就有更通常的事件監聽者,在webcore裏也確實是這樣。

上面是從處理事件的流程分析了這個過程,可能你們還會有疑問,事件是怎麼派發到js監聽者的?下面分析事件監聽者註冊的過程。

 

在html解析的時候即 HTMLParser::parseToken(Token* t),分析到一個token有事件屬性,就會將該屬性添加到相應的存儲結構裏,在這裏咱們只分析事件屬性,在分析到該token有event屬性的時候(形如<button onclick="myfunction('World')">)會建立一個EventListener,見

WebKit\WebCore\html\HTMLElement.cpp HTMLElement::parseMappedAttribute(MappedAttribute *attr)方法,
setAttributeEventListener(eventNames().clickEvent, createAttributeEventListener(this, attr));
這裏是js事件監聽者,在attr裏保存了js代碼(myfunction('World'))。
接着會將建立出來的事件監聽者加入到eventListenerMap,見EventTarget::addEventListener方法。
這樣該結點就對該事件具備了監聽的能力。

結合上面的事件處理流程的分析知道在事件處理的時候是從該結點去取得其監聽器,而後調用相應的處理方法的,這樣事件到js的事件處理入口點的過程是分析清楚了。
再看js引擎是怎麼處理這些事件並執行相應的js代碼的,繼續看JSEventListener::handleEvent方法:
在該方法的開始有這樣一行代碼
JSObject* jsFunction = this->jsFunction(scriptExecutionContext);
這裏的jsFunction取的就是其成員變量mutable JSC::JSObject* m_jsFunction;
能夠看到它是一個JSObject對象,若是清楚了它是怎麼被建立的話那也就清楚了事件是怎麼觸發js的代碼執行的了,(若是還不清楚,那得熟悉jscore了)。
咱們繼續看m_jsFunction的建立,在\WebCore\bindings\js\ScriptEventListener.cpp 的方法createAttributeEventListener(這個方法就是在上面分析token的時候會調用的)裏能夠找到其建立的過程這裏涉及到JSLazyEventListener這個類,熟悉jscore的人看到這裏應該就啥都清楚了,我把其定義貼在這裏

 

1 mutable String m_functionName; 2 mutable String m_eventParameterName; 3 mutable String m_code; 4 mutable bool m_parsed; 5 mutable String m_sourceURL; 6 int m_lineNumber; 7 Node* m_originalNode;

 

m_jsFunction 裏就包含了這些數據,既然有了這些數據,那麼執行js代碼只是一個call的調用了,這在JSEventListener::handleEvent裏有使用實例。在這就不贅述了。

 

2.Js處理dom節點

js是怎麼操做dom樹裏的這些結點呢,其實咱們實現了js調car,其下面的實現基本原理是同樣的,car具備反射的能力,因此從腳本里去調一個car裏的方法,要先對car進行反射,而後取其類,方法及參數等信息,而後構造一個js腳本對象。
而在webcore裏dom對象並不沒有反射的機制,所以也就不具有反射的能力,那它是腳本是怎麼認識dom這些對象的呢,既然反射是爲了獲得這些信息再去構造js對象,那若是我直接拿這些系信息構建js對象行不行呢,那是確定行的,jscoer的demo就這樣寫的。而此時確定有人會有疑問js怎麼知道這些類信息的呢,回答很簡單,dom標準定義了,dom樹必須提供這些類和接口,想一想看這些類和接口也很多呢,作引擎的人以爲這樣一個一個本身去寫,也太傻冒了吧,因而就寫了一個per腳本程序來生成包含這些信息的類,並一面是調用dom樹的方法,一面爲js提供信息的一些類,者就是webkit裏的綁定。
下面咱們具體來看一個綁定的實例,那document來分析吧:
Document 是dom樹的根結點,因而操做dom樹的基本接口,在webcore裏其實先就在document.cpp文件裏。
Per腳本我不是很熟,因此這裏就不分析其怎麼用腳本生成上面所述的包含類型信息和調dom方法的類的過程,以避免誤人子弟。我就直接拿生成好的類來分析他們的關係了。
與document.cpp 對應的文件就是JSDocument.cpp ,這個裏面也包含了一百多個方法,我就只拿最具備表明性的 GetElementById方法來分析了:
先分析這個文件的結構,
在這個文件裏有兩個表:

1 static const HashTableValue JSDocumentTableValues[70]; 2 static const HashTableValue JSDocumentPrototypeTableValues[38];

兩個比較重要的函數:

1 bool JSDocumentPrototype::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) 2 bool JSDocumentPrototype::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)

 

這兩個函數的做用是提供給js引擎,在註冊document對象的時候使用的,也就是描述了document對象有哪些方法的表。

在js裏註冊一個c/c++對象的過程這裏就不贅述了,jscore demo裏有一個比較簡單的例子。也就是說js調到JSDocumentPrototype裏的方法的過程和一個普通的腳本調c方法沒有什麼區別,再看一下在JSDocumentPrototype方法裏作了一些什麼事情:

 1 JSValue JSC_HOST_CALL jsDocumentPrototypeFunctionGetElementById(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args)  2 {  3  UNUSED_PARAM(args);  4     if (!thisValue.inherits(&JSDocument::s_info))  5         return throwError(exec, TypeError);  6     JSDocument* castedThisObj = static_cast<JSDocument*>(asObject(thisValue));  7     Document* imp = static_cast<Document*>(castedThisObj->impl());  8     const UString& elementId = args.at(0).toString(exec);  9 
10 
11     JSC::JSValue result = toJS(exec, castedThisObj->globalObject(), WTF::getPtr(imp->getElementById(elementId))); 12     return result; 13 }

 

看看這個方法裏作的事情也很簡單,就是一些強制轉換,而後調用document對象的裏的方法。

其實簡單點理解就是jsobject包裝了一個指針在js引擎裏使用,當要調用webcoer裏的方法時候就強制轉換成dom裏的類型對象。

C++裏要這樣使用,繼承是最好的實現方式了,因此能夠看到webkit裏繼承關係是比較複雜的。

 

 

7 問題總結

主要是這兩篇博客的介紹,你們參考下:

http://www.cnblogs.com/hustskyking/p/problem-javascript-event.html

http://www.cnblogs.com/yexiaochai/p/3477715.html

相關文章
相關標籤/搜索