webkit事件處理


1,概述

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

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

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

  • 事件捕獲
  • 事件捕獲發生在如下情況下: 一個事件監聽者注冊在某個事件的目標節點的祖先節點上,當該事件發生后,在其到達目標節點之前,祖先節點的事件監聽者首先捕獲並處理,然后將事件逐級下傳,直到目標節點。

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

  • 事件取消
  • 一些事件可以規定為可取消的,這些事件一般都會應有一個缺省的動作。當此類事件發生時,首先傳遞給目標節點上的事件監聽者,事件監聽者可以選擇是否取消該事件的缺省動作。

當用戶在瀏覽器里觸發了點擊鼠標,敲鍵盤等事件后,瀏覽器需要處理這些事件,那么整個的流程如何呢?

首先,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的人,估計都會寫。但是耳熟能詳,未必等於深入了解。不妨反問自己幾個問題,

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” 的窗口。

 

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM