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)
})"
简单来讲,事件注册是建立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,见
WebKitWebCorehtmlHTMLElement.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的创建,在WebCoreindingsjsScriptEventListener.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