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事件传递 主要关注“事件流”。事件流的三个阶段,以下图:
默认状况下,仍是在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内部,默认就是在冒泡阶段响应,请看代码:
难道说咱们不可以在网页中来控制么?让它在捕获阶段来响应?
把事件捕获和冒泡的过程统称为事件的传播
事件的传播是能够阻止的:
在捕获的过程当中stopPropagation();后,后面的冒泡过程也不会发生了~ 在IE中则使用cancelBubble(IE中只有冒泡,没有捕获)
3.阻止事件的默认行为,例如click <a>后的跳转~
不是全部的事件都能冒泡,例如: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