Chromium分发输入事件给WebKit处理的过程分析
Chromium的Render进程接收到Browser进程分发过来的输入事件之后,会在Compoistor线程中处理掉滑动和捏合手势这两种特殊的输入事件,其它类型的输入事件则交给Main线程处理。Main线程又会进一步将输入事件分发给WebKit处理。WebKit则根据输入事件发生的位置在网页中找到对应的HTML元素进行处理。本文接下来详细分析Chromium分发输入事件给WebKit处理的过程。
以Touch事件为例。WebKit接收到Chromium分发给它的Touch事件通知后,所做的第一件事情是做HitTest,也就是检测Touch事件发生在哪一个HTML元素上,然后再将接收到的Touch事件分发给该它处理,如图1所示:
HTML元素对应于网页DOMTree中的Node。要从DOMTree中找到输入事件的目标Node,必须要知道所有Node的Z轴位置大小,然后按照从上到下的顺序进行HitTest。从前面文章中析一文可以知道,DOMTree中的Node在Z轴上的实际位置,除了跟它们本身的Z-Index属性值有关之外,还与它们所处理的StackingContext的Z-Index值有关。网页的RenderLayerTree记录了所有的StackingContext,因此,WebKit要从网页的RenderLayerTree入手,才能在DOMTree中找到正确的目标Node处理当前发生的输入事件。
接下来,我们就从Compoistor线程将输入事件分发给Main线程开始,分析WebKit接收和处理输入事年的过程。从前面文章中一文可以知道,Compoistor线程是在InputEventFilter类的成员函数ForwardToHandler中将它不处理的输入事件发分给Main线程处理的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidInputEventFilter::ForwardToHandler(constIPC::Message&message){
......
introuting_id=message.routing_id();
InputMsg_HandleInputEvent::Paramparams;
if(!InputMsg_HandleInputEvent::Read(&message,¶ms))
return;
constWebInputEventevent=params.a;
ui::LatencyInfolatency_info=params.b;
.......
InputEventAckStateack_state=handler_.Run(routing_id,event,&latency_info);
if(ack_state==INPUT_EVENT_ACK_STATE_NOT_CONSUMED){
......
IPC::Messagenew_msg=InputMsg_HandleInputEvent(
routing_id,event,latency_info,is_keyboard_shortcut);
main_loop_->PostTask(
FROM_HERE,
base::Bind(&InputEventFilter::ForwardToMainListener,
this,new_msg));
return;
}
......
}
这个函数定义在文件external/chromium_org/content/renderer/input/input_event_filter.cc中。
InputEventFilter类的成员函数ForwardToHandler的详细分析可以参考前面文章中一文。对于Compositor线程不处理的输入事件,InputEventFilter类的成员函数ForwardToHandler会将它重新封装在一个InputMsg_HandleInputEvent消息中。这个InputMsg_HandleInputEvent消息又会进一步封装在一个Task中,并且发送给Main线程的消息队列。这个Task绑定了InputEventFilter类的成员函数ForwardToMainListener。
这意味着接下来InputEventFilter类的成员函数ForwardToMainListener就会在Main线程中被调用,用来处理Compositor线程分发过来的输入事件,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidInputEventFilter::ForwardToMainListener(constIPC::Message&message){
main_listener_->OnMessageReceived(message);
}
这个函数定义在文件external/chromium_org/content/renderer/input/input_event_filter.cc中。
从前面文章中一文可以知道,InputEventFilter类的成员变量main_listener_指向的是一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的就是Render进程中的RenderThread,也就是MainThread。InputEventFilter类的成员函数ForwardToMainListener调用这个RenderThreadImpl对象的成员函数OnMessageReceived处理参数message描述的输入事件。
RenderThreadImpl类的成员函数OnMessageReceived是从父类ChildThread继承下来的,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolChildThread::OnMessageReceived(constIPC::Message&msg){
.....
boolhandled=true;
IPC_BEGIN_MESSAGE_MAP(ChildThread,msg)
......
IPC_MESSAGE_UNHANDLED(handled=false)
IPC_END_MESSAGE_MAP()
if(handled)
returntrue;
......
returnrouter_.OnMessageReceived(msg);
}
这个函数定义在文件external/chromium_org/content/child/child_thread.cc中。
ChildThread类的成员函数OnMessageReceived首先检查参数msg描述的消息是否要由自己处理。如果不处理,那么就再分发给注册在它里面的Route进行处理。
从前面文章中一文可以知道,Render进程会为每一个网页创建一个RenderViewImpl对象,用来代表网页所加载在的控件。这个RenderViewImpl对象在初始化的过程中,会通过父类RenderWidget的成员函数DoInit将自己注册为RenderThread中的一个Route,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderWidget::DoInit(int32opener_id,
WebWidgetweb_widget,
IPC::SyncMessagecreate_widget_message){
......
boolresult=RenderThread::Get()->Send(create_widget_message);
if(result){
RenderThread::Get()->AddRoute(routing_id_,this);
.......
returntrue;
}
......
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
这意味着前面分析的RenderThreadImpl类的成员函数OnMessageReceived会将接收到的、自己又不处理的消息分发给RenderWidget类处理。RenderWidget类通过成员函数OnMessageReceived接收RenderThreadImpl类分发过来的消息,并且判断分发过来的消息是否与输入事件有关,也就是是否是一个类型为InputMsg_HandleInputEvent的消息。如果是的话,那么就会进行处理。如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderWidget::OnMessageReceived(constIPC::Message&message){
boolhandled=true;
IPC_BEGIN_MESSAGE_MAP(RenderWidget,message)
IPC_MESSAGE_HANDLER(InputMsg_HandleInputEvent,OnHandleInputEvent)
......
IPC_MESSAGE_UNHANDLED(handled=false)
IPC_END_MESSAGE_MAP()
returnhandled;
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
RenderWidget类通过成员函数OnMessageReceived会将类型为InputMsg_HandleInputEvent的消息分发给另外一个成员函数OnHandleInputEvent处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderWidget::OnHandleInputEvent(constblink::WebInputEventinput_event,
constui::LatencyInfo&latency_info,
boolis_keyboard_shortcut){
......
base::TimeTicksstart_time;
if(base::TimeTicks::IsHighResNowFastAndReliable())
start_time=base::TimeTicks::HighResNow();
......
boolprevent_default=false;
if(WebInputEvent::isMouseEventType(input_event->type)){
constWebMouseEvent&mouse_event=
static_cast(input_event);
......
prevent_default=WillHandleMouseEvent(mouse_event);
}
if(WebInputEvent::isKeyboardEventType(input_event->type)){
......
#ifdefined(OS_ANDROID)
//TheDPAD_CENTERkeyonAndroidhasadualsemantic:(1)inthegeneral
//caseitshouldbehavelikeaselectkey(i.e.causingaclickifabutton
//isfocused).However,ifatextfieldisfocused(2),itsintended
//behavioristojustshowtheIMEanddon''tpropagatethekey.
//Atypicalusecaseisawebform:theDPAD_CENTERshouldbringuptheIME
//whenclickedonaninputtextfieldandcausetheformsubmitifclicked
//whenthesubmitbuttonisfocused,butnotvice-versa.
//TheUIlayertakescareoftranslatingDPAD_CENTERintoaRETURNkey,
//butatthispointwehavetoswallowtheeventforthescenario(2).
constWebKeyboardEvent&key_event=
static_cast(input_event);
if(key_event.nativeKeyCode==AKEYCODE_DPAD_CENTER&&
GetTextInputType()!=ui::TEXT_INPUT_TYPE_NONE){
OnShowImeIfNeeded();
prevent_default=true;
}
#endif
}
if(WebInputEvent::isGestureEventType(input_event->type)){
constWebGestureEvent&gesture_event=
static_cast(input_event);
......
prevent_default=prevent_default||WillHandleGestureEvent(gesture_event);
}
boolprocessed=prevent_default;
if(input_event->type!=WebInputEvent::Char||!suppress_next_char_events_){
suppress_next_char_events_=false;
if(!processed&&webwidget_)
processed=webwidget_->handleInputEvent(input_event);
}
//IfthisRawKeyDowneventcorrespondstoabrowserkeyboardshortcutand
//it''snotprocessedbywebkit,thenweneedtosuppresstheupcomingChar
//events.
if(!processed&&is_keyboard_shortcut)
suppress_next_char_events_=true;
InputEventAckStateack_result=processed?
INPUT_EVENT_ACK_STATE_CONSUMED:INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
......
//dispatchcompositor-handledscrollgestures.
boolevent_type_can_be_rate_limited=
input_event->type==WebInputEvent::MouseMove||
input_event->type==WebInputEvent::MouseWheel||
(input_event->type==WebInputEvent::TouchMove&&
ack_result==INPUT_EVENT_ACK_STATE_CONSUMED);
boolframe_pending=compositor_&&compositor_->BeginMainFrameRequested();
//Ifwedon''thaveafastandaccurateHighResNow,weassumetheinput
//handlersareheavyandratelimitthem.
boolrate_limiting_wanted=true;
if(base::TimeTicks::IsHighResNowFastAndReliable()){
base::TimeTicksend_time=base::TimeTicks::HighResNow();
total_input_handling_time_this_frame_+=(end_time-start_time);
rate_limiting_wanted=
total_input_handling_time_this_frame_.InMicroseconds()>
kInputHandlingTimeThrottlingThresholdMicroseconds;
}
//Notethatwecan''tusehandling_event_type_heresinceitwillbeoverriden
//byreentrantcallsforeventsafterthepausedone.
boolno_ack=ignore_ack_for_mouse_move_from_debugger_&&
input_event->type==WebInputEvent::MouseMove;
if(!WebInputEventTraits::IgnoresAckDisposition(input_event)&&!no_ack){
InputHostMsg_HandleInputEvent_ACK_Paramsack;
ack.type=input_event->type;
ack.state=ack_result;
ack.latency=swap_latency_info;
scoped_ptrresponse(
newInputHostMsg_HandleInputEvent_ACK(routing_id_,ack));
if(rate_limiting_wanted&&event_type_can_be_rate_limited&&
frame_pending&&!is_hidden_){
//Wewanttoratelimittheinputeventsinthiscase,sowe''llwaitfor
//paintingtofinishbeforeACKingthismessage.
......
if(pending_input_event_ack_){
//Astwodifferentkindsofeventscouldcauseustopostponeanack
//wesenditnow,ifwehaveonepending.TheBrowsershouldnever
//sendusthesamekindofeventwearedelayingtheackfor.
Send(pending_input_event_ack_.release());
}
pending_input_event_ack_=response.Pass();
......
}else{
Send(response.release());
}
}
......
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
RenderWidget类的成员函数OnHandleInputEvent将参数input_event描述的输入事件分发给WebKit之前,会做以下三件事情:
1.检查参数input_event描述的输入事件是否是一个鼠标事件。如果是的话,那么就会调用另外一个成员函数WillHandleMouseEvent询问RenderWidget类是否要对它进行处理。如果处理,那么本地变量prevent_default的值就会被设置为true。
2.检查参数input_event描述的输入事件是否是一个DPAD键盘事件。如果是的话,并且当前获得焦点的是一个InputText控件,那么就调用另外一个成员函数OnShowImeIfNeeded弹出输入法,以便用户可以输入文本给InputText控件去。注意,这个检查是针对Android平台的,因为Android平台才会存在DPAD键。
3.检查参数input_event描述的输入事件是否是一个手势操作。如果是的话,那么就会调用另外一个成员函数WillHandleGestureEvent询问RenderWidget类是否要对它进行处理。如果处理,那么本地变量prevent_default的值也会被设置为true。
RenderWidget类的成员函数WillHandleMouseEvent和WillHandleGestureEvent的返回值均为false,这表明RenderWidget类不会处理鼠标和手势操作这两种输入事件。
RenderWidget类的成员函数OnHandleInputEvent接下来继续判断参数input_event描述的输入事件是否为键盘字符输入事件。如果不是,并且RenderWidget类不对它进行处理,那么RenderWidget类的成员函数OnHandleInputEvent就会将它分发给WebKit处理。这表明在RenderWidget类不处理的情况下,非键盘字符输入事件一定会分发给WebKit处理。
另一方面,对于键盘字符输入事件,它也会分发给WebKit处理。不过,分发给WebKit之后,如果WebKit决定不对它进行处理,并且它被设置为浏览器的快捷键,即参数is_keyboard_shortcut的值等于true,那么下一个键盘字符输入事件将不会再分发给WebKit处理。因为下一个键盘字符输入事件将会作为浏览器快捷键的另外一部分。在这种情况下,RenderWidget类的成员变量suppress_next_char_events_的值会被设置为true。
将参数input_event描述的输入事件分发给WebKit之后,RenderWidget类的成员函数OnHandleInputEvent考虑是否需要发Browser进程发送一个ACK。从前面文章中一文可以知道,Browser进程收到Render进程对上一个输入事件的ACK之后,才会给它分发下一个输入事件。因此,Render进程是否给Browser进程发送ACK,可以用来控制Browser进程分发输入事件给Render进程的节奏。
有三种类型的输入事件是需要控制分发节奏的:鼠标移动、鼠标中键滚动和触摸事件。这三类输入事件都有一个特点,它们会连续大量地输入。每一次输入Render进程都需要对它们作出响应,也就是对网页进行处理。如果它们发生得太过频繁,那么就会造成Render进程的负担很重。因此,对于这三类输入事件,RenderWidget类的成员函数OnHandleInputEvent并不会都马上对它们进行ACK。这里有一点需要注意,对于触摸事件,只有WebKit没有对它进行处理,那么RenderWidget类的成员函数OnHandleInputEvent会马上对它进行ACK。这是因为这个触摸事件和接下来发生的触摸事件,可能会触发手势操作,例如滑动手势和捏合手势。这些手势操作必须要迅速进行处理,否则的话,用户就会觉得浏览器没有反应了。
什么情况下不会马上进行ACK呢?如果网页的当前帧请求执行了一次Commit操作,也就是请求了重新绘制网页的CCLayerTree,但是网页的CCLayerTree还没有绘制完成,并且也没有同步到CCPendingLayerTree,那么就不会马上进行ACK。这个ACK会被缓存在RenderWidget类的成员变量pending_input_event_ack_中。等到网页的CCLayerTree重新绘制完成并且同步到CCPendingLayerTree之后,被缓存的ACK才会发送给Browser进程。之所以要这样做,是因为Main线程在重新绘制网页的CCLayerTree的时候,任务是相当重的,这时候不宜再分发新的输入事件给它处理。
此外,如果平台实现了高精度时钟,那么RenderWidget类的成员函数OnHandleInputEvent也不一定要等到网页的CCLayerTree重新绘制完成并且同步到CCPendingLayerTree之后,才将缓存的ACK才会发送给Browser进程。如果已经过去了一段时间,网页的CCLayerTree还没有绘制完成,也没有同步到CCPendingLayerTree,那么RenderWidget类的成员函数OnHandleInputEvent就会马上给Browser进程发送一个ACK。这个时间被设置为kInputHandlingTimeThrottlingThresholdMicroseconds(4166)微秒,大约等于1/4帧时间(假设一帧时间为16毫秒)。
还有一种特殊情况,造成RenderWidget类的成员函数OnHandleInputEvent不会缓存输入事件的ACK,那就是网页当前不可见。对于不可见的网页,Main线程不需要对它们进行处理,因为处理了也是白处理(用户看不到)。因此,可以认为此时分发给网页的任何输入都在瞬间完成,于是就可以安全地将它们ACK给Browser进程,不用担心Main线程的负载问题。
最后,我们还看到,如果浏览器连上了Debugger,并且Debugger希望不要对鼠标移动事件进行ACK,那么鼠标移动事件在任何情况下,都不会进行ACK。Browser进程是通过类型为InputHostMsg_HandleInputEvent的消息向Render进程分发输入事件的。相应地,Render进程通过向Browser进程发送类型为InputHostMsg_HandleInputEvent_ACK的消息对输入事件进行ACK。
接下来,我们就重点分析Chromium分发输入事件给WebKit处理的过程。从前面文章中一文可以知道,RenderWidget类的成员变量webwidget_指向的是一个WebViewImpl对象。RenderWidget类的成员函数OnHandleInputEvent就是通过调用这个WebViewImpl对象的成员函数handleInputEvent将参数input_event描述的输入事件分发给WebKit处理的。前面我们已经假设这是一个Touch事件。
WebViewImpl类的成员函数handleInputEvent的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolWebViewImpl::handleInputEvent(constWebInputEvent&inputEvent)
{
......
returnPageWidgetDelegate::handleInputEvent(m_page.get(),this,inputEvent);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
对于Touch事件,WebViewImpl类的成员函数handleInputEvent将会调用PageWidgetDelegate类的静态成员函数handleInputEvent对它进行处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolPageWidgetDelegate::handleInputEvent(Pagepage,PageWidgetEventHandler&handler,constWebInputEvent&event)
{
LocalFrameframe=page&&page->mainFrame()->isLocalFrame()?page->deprecatedLocalMainFrame():0;
switch(event.type){
......
caseWebInputEvent::TouchStart:
caseWebInputEvent::TouchMove:
caseWebInputEvent::TouchEnd:
caseWebInputEvent::TouchCancel:
if(!frame||!frame->view())
returnfalse;
returnhandler.handleTouchEvent(frame,static_cast(&event));
......
default:
returnfalse;
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。
从前面的调用过程可以知道,参数page是从WebViewImpl类的成员变量m_page传递过来的,它指向的是一个Page对象。PageWidgetDelegate类的静态成员函数handleInputEvent通过这个Page对象可以获得一个LocalFrame对象。这个LocalFrame对象用来在WebKit层描述在当前进程中加载的网页,它的创建过程可以参考前面文章中一文。
另外一个参数handler描述的是一个WebViewImpl对象。当第三个参数event描述的是一个Touch相关的事件时,PageWidgetDelegate类的静态成员函数handleInputEvent就会调用参数handler描述的WebViewImpl对象的成员函数handleTouchEvent对它进行处理。
WebViewImpl类的成员函数handleTouchEvent是从父类PageWidgetEventHandler继承下来的,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolPageWidgetEventHandler::handleTouchEvent(LocalFrame&mainFrame,constWebTouchEvent&event)
{
returnmainFrame.eventHandler().handleTouchEvent(PlatformTouchEventBuilder(mainFrame.view(),event));
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。
PageWidgetEventHandler类的成员函数handleTouchEvent首先调用参数mainFrame描述的一个LocalFrame对象的成员函数eventHandler获得一个EventHandler对象,接着再调用这个EventHandler对象的成员函数handleTouchEvent将处参数event描述的Touch事件,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolEventHandler::handleTouchEvent(constPlatformTouchEvent&event)
{
......
constVector&points=event.touchPoints();
unsignedi;
......
boolallTouchReleased=true;
for(i=0;i constPlatformTouchPoint&point=points[i];
......
if(point.state()!=PlatformTouchPoint::TouchReleased&&point.state()!=PlatformTouchPoint::TouchCancelled)
allTouchReleased=false;
}
......
//Firstdohittestsforanynewtouchpoints.
for(i=0;i constPlatformTouchPoint&point=points[i];
......
if(point.state()==PlatformTouchPoint::TouchPressed){
HitTestRequest::HitTestRequestTypehitType=HitTestRequest::TouchEvent|HitTestRequest::ReadOnly|HitTestRequest::Active;
LayoutPointpagePoint=roundedLayoutPoint(m_frame->view()->windowToContents(point.pos()));
HitTestResultresult;
if(!m_touchSequenceDocument){
result=hitTestResultAtPoint(pagePoint,hitType);
}elseif(m_touchSequenceDocument->frame()){
LayoutPointframePoint=roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->windowToContents(point.pos()));
result=hitTestResultInFrame(m_touchSequenceDocument->frame(),framePoint,hitType);
}else
continue;
Nodenode=result.innerNode();
......
if(!m_touchSequenceDocument){
//Keeptrackofwhichdocumentshouldreceivealltouchevents
//intheactivesequence.Thismustbeasingledocumentto
//ensurewedon''tleakNodesbetweendocuments.
m_touchSequenceDocument=&(result.innerNode()->document());
......
}
......
m_targetForTouchID.set(point.id(),node);
......
}
}
......
//Holdsthecompletesetoftouchesonthescreen.
RefPtrWillBeRawPtrtouches=TouchList::create();
//Adifferentviewonthe''touches''listabove,filteredandgroupedby
//eventtarget.Usedforthe''targetTouches''listintheJSevent.
typedefWillBeHeapHashMap>TargetTouchesHeapMap;
TargetTouchesHeapMaptouchesByTarget;
//Arrayoftouchesperstate,usedtoassemblethe''changedTouches''list.
typedefWillBeHeapHashSet>EventTargetSet;
struct{
//Thetouchescorrespondingtotheparticularchangestatethisstruct
//instancerepresents.
RefPtrWillBeMemberm_touches;
//Setoftargetsinvolvedinm_touches.
EventTargetSetm_targets;
}changedTouches[PlatformTouchPoint::TouchStateEnd];
for(i=0;i constPlatformTouchPoint&point=points[i];
PlatformTouchPoint::StatepointState=point.state();
RefPtrWillBeRawPtrtouchTarget=nullptr;
if(pointState==PlatformTouchPoint::TouchReleased||pointState==PlatformTouchPoint::TouchCancelled){
//Thetargetshouldbetheoriginaltargetforthistouch,soget
//itfromthehashmap.Asit''sareleaseorcancelwealsoremove
//itfromthemap.
touchTarget=m_targetForTouchID.take(point.id());
}else{
//Nohittestisperformedonmoveorstationary,sincethetarget
//isnotallowedtochangeanyway.
touchTarget=m_targetForTouchID.get(point.id());
}
LocalFrametargetFrame=0;
boolknownTarget=false;
if(touchTarget){
Document&doc=touchTarget->toNode()->document();
//Ifthetargetnodehasmovedtoanewdocumentwhileitwasbeingtouched,
//wecan''tsendeventstothenewdocumentbecausethatcouldleaknodes
//fromonedocumenttoanother.Seehttp://crbug.com/394339.
if(&doc==m_touchSequenceDocument.get()){
targetFrame=doc.frame();
knownTarget=true;
}
}
......
RefPtrWillBeRawPtrtouch=Touch::create(
targetFrame,touchTarget.get(),point.id(),point.screenPos(),adjustedPagePoint,adjustedRadius,point.rotationAngle(),point.force());
......
//Ensurethistarget''stouchlistexists,evenifitendsupempty,so
//itcanalwaysbepassedtoTouchEvent::Createbelow.
TargetTouchesHeapMap::iteratortargetTouchesIterator=touchesByTarget.find(touchTarget.get());
if(targetTouchesIterator==touchesByTarget.end()){
touchesByTarget.set(touchTarget.get(),TouchList::create());
targetTouchesIterator=touchesByTarget.find(touchTarget.get());
}
//touchesandtargetTouchesshouldonlycontaininformationabout
//touchesstillonthescreen,soifthispointisreleasedor
//cancelleditwillonlyappearinthechangedToucheslist.
if(pointState!=PlatformTouchPoint::TouchReleased&&pointState!=PlatformTouchPoint::TouchCancelled){
touches->append(touch);
targetTouchesIterator->value->append(touch);
}
//NowbuildupthecorrectlistforchangedTouches.
//NotethatanytouchesthatareintheTouchStationarystate(e.g.if
//theuserhadseveralpointstouchedbutdidnotmovethemall)should
//neverbeinthechangedToucheslistsowedonothandlethem
//explicitlyhere.Seehttps://bugs.webkit.org/show_bug.cgi?id=37609
//forfurtherdiscussionabouttheTouchStationarystate.
if(pointState!=PlatformTouchPoint::TouchStationary&&knownTarget){
......
if(!changedTouches[pointState].m_touches)
changedTouches[pointState].m_touches=TouchList::create();
changedTouches[pointState].m_touches->append(touch);
changedTouches[pointState].m_targets.add(touchTarget);
}
}
if(allTouchReleased){
m_touchSequenceDocument.clear();
......
}
......
//NowiteratethechangedToucheslistandm_targetswithinit,sending
//eventstothetargetsasrequired.
boolswallowedEvent=false;
for(unsignedstate=0;state!=PlatformTouchPoint::TouchStateEnd;++state){
......
constAtomicString&stateName(eventNameForTouchPointState(static_cast(state)));
constEventTargetSet&targetsForState=changedTouches[state].m_targets;
for(EventTargetSet::const_iteratorit=targetsForState.begin();it!=targetsForState.end();++it){
EventTargettouchEventTarget=it->get();
RefPtrWillBeRawPtrtouchEvent=TouchEvent::create(
touches.get(),touchesByTarget.get(touchEventTarget),changedTouches[state].m_touches.get(),
stateName,touchEventTarget->toNode()->document().domWindow(),
event.ctrlKey(),event.altKey(),event.shiftKey(),event.metaKey(),event.cancelable());
touchEventTarget->toNode()->dispatchTouchEvent(touchEvent.get());
swallowedEvent=swallowedEvent||touchEvent->defaultPrevented()||touchEvent->defaultHandled();
}
}
returnswallowedEvent;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/EventHandler.cpp中。
EventHandler类的成员函数handleTouchEvent主要是做三件事情:
1.对当前发生的Touch事件的每一个TouchPoint进行HitTest,分别找到它们的TargetNode。
2.对TouchPoint进行分类。还与屏幕接触是一类,不再与屏幕接触是另一类。与屏幕接触的一类又划分为两个子类。一个子类是静止不动的,另一个子类的正在移动的。另外,目标Node相同的TouchPoint也会被组织在同一个TouchList中。
3.将Touch事件分发给TargetNode处理。
接下来,我们就将EventHandler类的成员函数handleTouchEvent划分为三段进行分析,每一段对应于上述的一个事件。
第一段代码如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolEventHandler::handleTouchEvent(constPlatformTouchEvent&event)
{
......
constVector&points=event.touchPoints();
unsignedi;
......
boolallTouchReleased=true;
for(i=0;i constPlatformTouchPoint&point=points[i];
......
if(point.state()!=PlatformTouchPoint::TouchReleased&&point.state()!=PlatformTouchPoint::TouchCancelled)
allTouchReleased=false;
}
......
//Firstdohittestsforanynewtouchpoints.
for(i=0;i constPlatformTouchPoint&point=points[i];
......
if(point.state()==PlatformTouchPoint::TouchPressed){
HitTestRequest::HitTestRequestTypehitType=HitTestRequest::TouchEvent|HitTestRequest::ReadOnly|HitTestRequest::Active;
LayoutPointpagePoint=roundedLayoutPoint(m_frame->view()->windowToContents(point.pos()));
HitTestResultresult;
if(!m_touchSequenceDocument){
result=hitTestResultAtPoint(pagePoint,hitType);
}elseif(m_touchSequenceDocument->frame()){
LayoutPointframePoint=roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->windowToContents(point.pos()));
result=hitTestResultInFrame(m_touchSequenceDocument->frame(),framePoint,hitType);
}else
continue;
Nodenode=result.innerNode();
......
if(!m_touchSequenceDocument){
//Keeptrackofwhichdocumentshouldreceivealltouchevents
//intheactivesequence.Thismustbeasingledocumentto
//ensurewedon''tleakNodesbetweendocuments.
m_touchSequenceDocument=&(result.innerNode()->document());
......
}
......
m_targetForTouchID.set(point.id(),node);
......
}
}
这段代码首先获得参数event描述的Touch事件关联的所有TouchPoint,保存在本地变量points描述的一个Vector中。
每一个TouchPoint都用一个PlatformTouchPoint对象描述,它有四种状态,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classPlatformTouchPoint{
public:
enumState{
TouchReleased,
TouchPressed,
TouchMoved,
TouchStationary,
TouchCancelled,
TouchStateEnd//Placeholder:mustremainthelastitem.
};
......
};
这些状态定义在文件external/chromium_org/third_party/WebKit/Source/platform/PlatformTouchPoint.h中。
一个TouchPoint的一般状态变化过程为:TouchPressed->TouchMoved/TouchStationary->TouchReleased/TouchCancelled。
回到上面第一段代码中,它主要做的事情就是对那些状态为TouchPressed的TouchPoint进行HitTest,目的是找到它们的TargetNode,并且将这些TargetNode保存在EventHandler类的成员变量m_targetForTouchID描述的一个HashMap中,键值为TouchPoint对应的ID。有了这个HashMap,当一个TouchPoint从TouchPressed状态变为其它状态时,就可以轻松地知道它的TargetNode,避免做重复的HitTest。
一系列连续的TouchEvent只能发生在一个Document上。如果两个TouchEvent有两个或者两个以上的TouchPoint具有相同的ID,那么它们就是连续的TouchEvent。它们所发生在的Document由第一个连续的TouchEvent的第一个处于TouchPressed状态的TouchPoint确定,也就是这个TouchPoint的TargetNode所在的Document。这个Document一旦确定,就会维护在EventHandler类的成员变量m_touchSequenceDocument中。
当一个TouchEvent的所有TouchPoint的状态都处于TouchReleased或者TouchCancelled时,它就结束一个连续的TouchEvent系列。这时候EventHandler类的成员变量m_touchSequenceDocument就会设置为NULL,表示接下来发生的TouchEvent属于另外一个连续的系列。
当一个TouchEvent所在的Document所未确定时,EventHandler类的成员函数handleTouchEvent调用成员函数hitTestResultAtPoint对第一个TouchPoint做HitTest;当一个TouchEvent所在的Document确定时,则调用另外一个成员函数hitTestResultInFrame做HitTest。后者会将HitTest的范围限制在指定的Document中。后面我们将以EventHandler类的成员函数hitTestResultAtPoint为例,分析HitTest的执行过程。
第二段代码如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
//Holdsthecompletesetoftouchesonthescreen.
RefPtrWillBeRawPtrtouches=TouchList::create();
//Adifferentviewonthe''touches''listabove,filteredandgroupedby
//eventtarget.Usedforthe''targetTouches''listintheJSevent.
typedefWillBeHeapHashMap>TargetTouchesHeapMap;
TargetTouchesHeapMaptouchesByTarget;
//Arrayoftouchesperstate,usedtoassemblethe''changedTouches''list.
typedefWillBeHeapHashSet>EventTargetSet;
struct{
//Thetouchescorrespondingtotheparticularchangestatethisstruct
//instancerepresents.
RefPtrWillBeMemberm_touches;
//Setoftargetsinvolvedinm_touches.
EventTargetSetm_targets;
}changedTouches[PlatformTouchPoint::TouchStateEnd];
for(i=0;i constPlatformTouchPoint&point=points[i];
PlatformTouchPoint::StatepointState=point.state();
RefPtrWillBeRawPtrtouchTarget=nullptr;
if(pointState==PlatformTouchPoint::TouchReleased||pointState==PlatformTouchPoint::TouchCancelled){
//Thetargetshouldbetheoriginaltargetforthistouch,soget
//itfromthehashmap.Asit''sareleaseorcancelwealsoremove
//itfromthemap.
touchTarget=m_targetForTouchID.take(point.id());
}else{
//Nohittestisperformedonmoveorstationary,sincethetarget
//isnotallowedtochangeanyway.
touchTarget=m_targetForTouchID.get(point.id());
}
LocalFrametargetFrame=0;
boolknownTarget=false;
if(touchTarget){
Document&doc=touchTarget->toNode()->document();
//Ifthetargetnodehasmovedtoanewdocumentwhileitwasbeingtouched,
//wecan''tsendeventstothenewdocumentbecausethatcouldleaknodes
//fromonedocumenttoanother.Seehttp://crbug.com/394339.
if(&doc==m_touchSequenceDocument.get()){
targetFrame=doc.frame();
knownTarget=true;
}
}
......
RefPtrWillBeRawPtrtouch=Touch::create(
targetFrame,touchTarget.get(),point.id(),point.screenPos(),adjustedPagePoint,adjustedRadius,point.rotationAngle(),point.force());
......
//Ensurethistarget''stouchlistexists,evenifitendsupempty,so
//itcanalwaysbepassedtoTouchEvent::Createbelow.
TargetTouchesHeapMap::iteratortargetTouchesIterator=touchesByTarget.find(touchTarget.get());
if(targetTouchesIterator==touchesByTarget.end()){
touchesByTarget.set(touchTarget.get(),TouchList::create());
targetTouchesIterator=touchesByTarget.find(touchTarget.get());
}
//touchesandtargetTouchesshouldonlycontaininformationabout
//touchesstillonthescreen,soifthispointisreleasedor
//cancelleditwillonlyappearinthechangedToucheslist.
if(pointState!=PlatformTouchPoint::TouchReleased&&pointState!=PlatformTouchPoint::TouchCancelled){
touches->append(touch);
targetTouchesIterator->value->append(touch);
}
//NowbuildupthecorrectlistforchangedTouches.
//NotethatanytouchesthatareintheTouchStationarystate(e.g.if
//theuserhadseveralpointstouchedbutdidnotmovethemall)should
//neverbeinthechangedToucheslistsowedonothandlethem
//explicitlyhere.Seehttps://bugs.webkit.org/show_bug.cgi?id=37609
//forfurtherdiscussionabouttheTouchStationarystate.
if(pointState!=PlatformTouchPoint::TouchStationary&&knownTarget){
......
if(!changedTouches[pointState].m_touches)
changedTouches[pointState].m_touches=TouchList::create();
changedTouches[pointState].m_touches->append(touch);
changedTouches[pointState].m_targets.add(touchTarget);
}
}
if(allTouchReleased){
m_touchSequenceDocument.clear();
......
}
这段代码主要是对TouchPoint进行分门别类。
首先,那些还与屏幕有接触的TouchPoint将会保存在本地变量touches描述的一个TouchList中。那些状态不等于TouchReleased和TouchCancelled的TouchPoint即为还与屏幕有接触的TouchPoint。
其次,具有相同TargetNode的TouchPoint又会保存在相同的TouchList中。这些TouchList它们关联的TargetNode为键值,保存在本地变量touchesByTarget描述的一个HashMap中。
第三,那些位置或者状态发生变化,并且有TargetNode的TouchPoint会按照状态保存在本地变量changedTouches描述的一个数组中。相同状态的TouchPoint保存在同一个TouchList中,它们的TargetNode也会保存在同一个HashSet中。位置或者状态发生变化的TouchPoint,即为那些状态不等于TouchStationary的TouchPoint。另外,如果一个TouchPoint的TargetNode所在的Document与当前TouchEvent所发生在的Document不一致,那么该TouchPoint会被认为是没有TargetNode。
这段代码还会做另外两件事情:
1.如果一个TouchPoint的状态变为TouchReleased或者TouchCancelled,那么它就会从EventHandler类的成员变量m_targetForTouchID描述的一个HashMap中移除。结合前面对第一段代码的分析,我们就可以知道,一个连续的TouchEvent系列,它关联的TouchPoint是会动态增加和移除的。
2.如果当前发生的TouchEvent的所有TouchPoint的状态均为TouchReleased或者TouchCancelled,那么当前连续的TouchEvent系列就会结束。这时候EventHandler类的成员变量m_touchSequenceDocument将被设置为NULL。
第三段代码如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
//NowiteratethechangedToucheslistandm_targetswithinit,sending
//eventstothetargetsasrequired.
boolswallowedEvent=false;
for(unsignedstate=0;state!=PlatformTouchPoint::TouchStateEnd;++state){
......
constAtomicString&stateName(eventNameForTouchPointState(static_cast(state)));
constEventTargetSet&targetsForState=changedTouches[state].m_targets;
for(EventTargetSet::const_iteratorit=targetsForState.begin();it!=targetsForState.end();++it){
EventTargettouchEventTarget=it->get();
RefPtrWillBeRawPtrtouchEvent=TouchEvent::create(
touches.get(),touchesByTarget.get(touchEventTarget),changedTouches[state].m_touches.get(),
stateName,touchEventTarget->toNode()->document().domWindow(),
event.ctrlKey(),event.altKey(),event.shiftKey(),event.metaKey(),event.cancelable());
touchEventTarget->toNode()->dispatchTouchEvent(touchEvent.get());
swallowedEvent=swallowedEvent||touchEvent->defaultPrevented()||touchEvent->defaultHandled();
}
}
returnswallowedEvent;
}
这段代码将TouchEvent分发给TargetNode处理。注意,并不是所有的TargetNode都会被分发TouchEvent。只有那些TouchPoint位置或者状态发生了变化的TargetNode才会获得TouchEvent。
这段代码按照TouchPoint的状态分发TouchEvent给TargetNode处理,顺序为TouchReleased->TouchPressed->TouchMoved->TouchCancelled。如果具有相同状态的TouchPoint关联了不同的TargetNode,那么每一个TargetNode都会获得一个TouchEvent。
每一个TargetNode获得的TouchEvent是不同的TouchEvent对象,每一个TouchEvent对象包含了以下三种信息:
1.所有与屏幕接触的TouchPoint。这些TouchPoint有的位于TargetNode的范围内,有的可能位于TargetNode的范围外。
2.位于TargetNode的范围内的TouchPoint。
3.具有相同状态的TouchPoint。
注意,这些TouchPoint限定在当前发生的TouchEvent关联的TouchPoint中,也就是限定在参数event描述的TouchEvent关联的TouchPoint中。
EventHandler类的成员函数是通过TargetNode的成员函数handleTouchEvent给它们分发TouchEvent,也就是通过调用Node类的成员函数handleTouchEvent将TouchEvent分发给TargetNode处理。
接下来,我们就继续分析EventHandler类的成员函数hitTestResultAtPoint和Node类的成员函数handleTouchEvent的实现,以便了解WebKit根据TouchPoint找到TargetNode和将TouchEvent分发给TargetNode处理的过程。
EventHandler类的成员函数hitTestResultAtPoint的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
HitTestResultEventHandler::hitTestResultAtPoint(constLayoutPoint&point,HitTestRequest::HitTestRequestTypehitType,constLayoutSize&padding)
{
......
//WealwayssendhitTestResultAtPointtothemainframeifwehaveone,
//otherwisewemighthitareasthatareobscuredbyhigherframes.
if(Pagepage=m_frame->page()){
LocalFramemainFrame=page->mainFrame()->isLocalFrame()?page->deprecatedLocalMainFrame():0;
if(mainFrame&&m_frame!=mainFrame){
FrameViewframeView=m_frame->view();
FrameViewmainView=mainFrame->view();
if(frameView&&mainView){
IntPointmainFramePoint=mainView->rootViewToContents(frameView->contentsToRootView(roundedIntPoint(point)));
returnmainFrame->eventHandler().hitTestResultAtPoint(mainFramePoint,hitType,padding);
}
}
}
HitTestResultresult(point,padding.height(),padding.width(),padding.height(),padding.width());
......
//hitTestResultAtPointisspecificallyusedtohitTestintoallframes,thusitalwaysallowschildframecontent.
HitTestRequestrequest(hitType|HitTestRequest::AllowChildFrameContent);
m_frame->contentRenderer()->hitTest(request,result);
......
returnresult;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/EventHandler.cpp中。
EventHandler类的成员函数hitTestResultAtPoint首先会检查当前正在处理的网页的MainFrame是否是一个LocalFrame。如果是的话,那么就会在它上面做HitTest。否则的话,就在当前正在处理的网页所加载在的Frame上做HitTest,也就是EventHandler类的成员变量m_frame描述的Frame。这个Frame是一个LocalFrame。
前面文章中一文提到,在WebKit中,一个Frame有可能是Local的,也可能是Remote的。LocalFrame就在当前进程加载一个网页,而RemoteFrame在另外一个进程加载一个网页。Frame与Frame之间会形成一个FrameTree。FrameTree的根节点就是一个MainFrame。结合前面分析的EventHandler类的成员函数handleTouchEvent,我们就可以知道,如果当前正在处理的网页不是加载在一个MainFrame上,并且这个网页的MainFrame是一个LocalFrame,那么发生在这个网页上的TouchEvent将会在它的MainFrame上做HitTest。在其余情况下,发生在当前正在处理的网页上的TouchEvent,将会在该网页所加载在的Frame上做HitTest。
一旦确定在哪个Frame上做HitTest之后,EventHandler类的成员函数hitTestResultAtPoint就会调用这个Frame的成员函数contentRenderer获得一个RenderView对象。从前面文章中一文可以知道,这个RenderView对象描述的就是当前正在处理的网页的RenderObjectTree的根节点。有了这个RenderView对象之后,EventHandler类的成员函数hitTestResultAtPoint就会调用它的成员函数hitTest对参数point描述的TouchPoint进行HitTest。
RenderView类的成员函数hitTest的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderView::hitTest(constHitTestRequest&request,HitTestResult&result)
{
returnhitTest(request,result.hitTestLocation(),result);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderView.cpp中。
RenderView类的成员函数hitTest调用另外一个重载版本的成员函数hitTest对参数result描述的TouchPoint进行HitTest,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderView::hitTest(constHitTestRequest&request,constHitTestLocation&location,HitTestResult&result)
{
......
returnlayer()->hitTest(request,location,result);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderView.cpp中。
RenderView类重载版本的成员函数hitTest首先会调用另外一个成员函数layer获得一个RenderLayer对象。从前面文章中一文可以知道,这个RenderLayer对象描述的就是网页的RenderLayerTree的根节点。有了这个RenderLayer对象之后,RenderView类重载版本的成员函数hitTest就会调用它的成员函数hitTest对象参数参数result描述的TouchPoint进行HitTest,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderLayer::hitTest(constHitTestRequest&request,constHitTestLocation&hitTestLocation,HitTestResult&result)
{
......
LayoutRecthitTestArea=renderer()->view()->documentRect();
......
RenderLayerinsideLayer=hitTestLayer(this,0,request,result,hitTestArea,hitTestLocation,false);
......
returninsideLayer;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
RenderLayer类的成员函数hitTest首先是获得网页的Document对象占据的区域hitTestArea,然后再调用另外一个成员函数hitTestLayer在该区域上对参数hitTestLocation描述的TouchPoint进行HitTest,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
RenderLayerRenderLayer::hitTestLayer(RenderLayerrootLayer,RenderLayercontainerLayer,constHitTestRequest&request,HitTestResult&result,
constLayoutRect&hitTestRect,constHitTestLocation&hitTestLocation,boolappliedTransform,
constHitTestingTransformStatetransformState,doublezOffset)
{
......
//Ensureourlistsand3dstatusareup-to-date.
m_stackingNode->updateLayerListsIfNeeded();
update3DTransformedDescendantStatus();
......
//Thefollowingareusedforkeepingtrackofthez-depthofthehitpointof3d-transformed
//descendants.
doublelocalZOffset=-numeric_limits::infinity();
doublezOffsetForDescendantsPtr=0;
doublezOffsetForContentsPtr=0;
booldepthSortDescendants=false;
if(preserves3D()){
depthSortDescendants=true;
//Ourlayerscandepth-testwithourcontainer,sosharethezdepthpointerwiththecontainer,ifitpassedonedown.
zOffsetForDescendantsPtr=zOffset?zOffset:&localZOffset;
zOffsetForContentsPtr=zOffset?zOffset:&localZOffset;
}elseif(m_has3DTransformedDescendant){
//Flatteninglayerwith3dchildren;usealocalzOffsetpointertodepth-testchildrenandforeground.
depthSortDescendants=true;
zOffsetForDescendantsPtr=zOffset?zOffset:&localZOffset;
zOffsetForContentsPtr=zOffset?zOffset:&localZOffset;
}elseif(zOfwww.shanxiwang.netfset){
zOffsetForDescendantsPtr=0;
//Containerneedsustogivebackazoffsetforthehitlayer.
zOffsetForContentsPtr=zOffset;
}
......
//Thisvariabletrackswhichlayerthemouseendsupbeinginside.
RenderLayercandidateLayer=0;
//Beginbywalkingourlistofpositivelayersfromhighestz-indexdowntothelowestz-index.
RenderLayerhitLayer=hitTestChildren(PositiveZOrderChildren,rootLayer,request,result,hitTestRect,hitTestLocation,
localTransformState.get(),zOffsetForDescendantsPtr,zOffset,unflattenedTransformState.get(),depthSortDescendants);
if(hitLayer){
if(!depthSortDescendants)
returnhitLayer;
candidateLayer=hitLayer;
}
//Nowcheckouroverflowobjects.
hitLayer=hitTestChildren(NormalFlowChildren,rootLayer,request,result,hitTestRect,hitTestLocation,
localTransformState.get(),zOffsetForDescendantsPtr,zOffset,unflattenedTransformState.get(),depthSortDescendants);
if(hitLayer){
if(!depthSortDescendants)
returnhitLayer;
candidateLayer=hitLayer;
}
......
//NextwewanttoseeifthemouseposisinsidethechildRenderObjectsofthelayer.Check
//everyfragmentinreverseorder.
if(isSelfPaintingLayer()){
//HittestwithatemporaryHitTestResult,becauseweonlywanttocommitto''result''ifweknowwe''refrontmost.
HitTestResulttempResult(result.hitTestLocation());
boolinsideFragmentForegroundRect=false;
if(hitTestContentsForFragments(layerFragments,request,tempResult,hitTestLocation,HitTestDescendants,insideFragmentForegroundRect)
&&isHitCandidate(this,false,zOffsetForContentsPtr,unflattenedTransformState.get())){
......
if(!depthSortDescendants)
returnthis;
//Foregroundcandepth-sortwithdescendantlayers,sokeepthisasacandidate.
candidateLayer=this;
}
}
//Nowcheckournegativez-indexchildren.
hitLayer=hitTestChildren(NegativeZOrderChildren,rootLayer,request,result,hitTestRect,hitTestLocation,
localTransformState.get(),zOffsetForDescendantsPtr,zOffset,unflattenedTransformState.get(),depthSortDescendants);
if(hitLayer){
if(!depthSortDescendants)
returnhitLayer;
candidateLayer=hitLayer;
}
......
//Ifwefoundalayer,return.Childlayers,andforegroundalwaysrenderinfrontofbackground.
if(candidateLayer)
returncandidateLayer;
if(isSelfPaintingLayer()){
HitTestResulttempResult(result.hitTestLocation());
boolinsideFragmentBackgroundRect=false;
if(hitTestContentsForFragments(layerFragments,request,tempResult,hitTestLocation,HitTestSelf,insideFragmentBackgroundRect)
&&isHitCandidate(this,false,zOffsetForContentsPtr,unflattenedTransformState.get())){
......
returnthis;
}
......
}
return0;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
RenderLayer类的成员变量m_stackingNode指向的是一个RenderLayerStackingNode对象。这个RenderLayerStackingNode对象描述了当前正在处理的RenderLayer所在的StackingContext。RenderLayer类的成员函数hitTestLayer首先调用这个RenderLayerStackingNode对象的成员函数updateLayerListsIfNeeded更新它所描述的StackingContext所包含的RenderLayer的层次关系,以便接下来可以按照它们的Z轴位置进行HitTest。
RenderLayer类的成员函数hitTestLayer同时还会调用另外一个成员函数update3DTransformedDescendantStatus检查当前正在处理的RenderLayer的子RenderLayer是否设置了3D。如果设置了,那么RenderLayer类的成员变量m_has3DTransformedDescendant就会被设置为true。3D变换使得HitTest不能简单地按照原来Z-Index的大小进行HitTest。
RenderLayer类的成员函数hitTestLayer接下来根据两种不同的情况,采取两种不同的HitTest方法:
1.当前正在处理的RenderLayer将CSS属性tranform-type设置为“preserve-3d”,或者它的子RenderLayer设置了3D变换。在这种情况下,本地变量depthSortDescendants的值会被设置为true,并且另外两个本地变量zOffsetForDescendantsPtr和zOffsetForContentsPtr指向了一个类型为double的地址。这个地址包含的double值描述的是上一个被Hit的RenderLayer在TouchPoint处的Z轴位置。其中,本地变量zOffsetForContentsPtr描述的Z轴位置是给当前正在处理的RenderLayer使用的,而本地变量zOffsetForDescendantsPtr描述的Z轴位置是给当前正在处理的RenderLayer的子RenderLayer使用的。在一个设置了3D变换的环境中,Z-Index值大的RenderLayer不一定位于Z-Index值小的RenderLayer的上面,需要进一步结合它们的3D变换情况进行判断。因此,就需要将上一个被Hit的RenderLayer在TouchPoint处的Z轴位置保存下来,用来与下一个也被Hit的RenderLayer进行比较,以便得出正确的被Hit的RenderLayer。
2.当前正在处理的RenderLayer没有将CSS属性tranform-type设置为“preserve-3d”,以及它的子RenderLayer也没有设置3D变换。在这种情况下,本地变量depthSortDescendants的值会被设置为false。另外两个本地变量zOffsetForDescendantsPtr和zOffsetForContentsPtr,前者被设置为NULL,后者设置为参数zOffset的值。将本地变量zOffsetForDescendantsPtr设置为NULL,是因为当前正在处理的RenderLayer的子RenderLayer在做HitTest时,不需要与其它的子RenderLayer在TouchPoint处进行Z轴位置。将zOffsetForContentsPtr的值指定为参数zOffset的值,是因为调用者可能会指定一个Z轴位置,要求当前正在处理的RenderLayer在TouchPoint处与其进行比较。
对第二种情况的处理比较简单,流程如下所示:
1.按照Z-Index从大到小的顺序对Z-Index值大于等于0的子RenderLayer进行HitTest。如果发生了Hit,那么停止HitTest流程。
2.对当前正在处理的RenderLayer的Foreground层进行HitTest。如果发生了Hit,那么停止HitTest流程。
3.按照Z-Index从大到小的顺序对Z-Index值小于0的子RenderLayer进行HitTest。如果发生了Hit,那么停止HitTest流程。
4.对当前正在处理的RenderLayer关联的RenderObject的Background层进行HitTest。
对第一种情况的处理相对就会复杂一些,如下所示:
1.对当前正在处理的RenderLayer的所有子RenderLayer,以及当前正在处理的RenderLayer的Foreground层,都会一一进行HitTest。在这个HitTest过程中,所有被Hit的RenderLayer,都会根据它们3D变换情况,检查它们在TouchPoint处的Z轴位置。Z轴位置最大的RenderLayer或者RenderObject,将会选择用来接收TouchEvent。
2.如果所有子RenderLayer和当前正在处理的RenderLayer的Foreground都没有发生Hit,那么就会再对当前正在处理的RenderLayer的Background层进行HitTest。
注意,对于每一个子RenderLayer,RenderLayer类的成员函数hitTestLayer就会调用另外一个成员函数hitTestChildren对分别对它们进行HitTest。RenderLayer类的成员函数hitTestChildren又会调用hitTestLayer对每一个子RenderLayer执行具体的HitTest。
这意味着,RenderLayer类的成员函数hitTestLayer会被递归调用来对RenderLayerTree中的每一个RenderLayer进行HitTest。当前正在处理的RenderLayer是否被Hit,RenderLayer类的成员函数hitTestLayer是通过调用两次成员函数hitTestContentsForFragments进行检查。第一次调用是确定当前正在处理的RenderLayer的Foreground层是否发生了HitTest。第二次调用是确定当前正在处理的RenderLayer的Background层是否发生了HitTest。
一旦检查当前正在处理的RenderLayer发生了Hit,那么RenderLayer类的成员函数hitTestLayer还需要调用另外一个成员函数isHitCandidate将它在TouchPoint处的Z轴位置与本地变量zOffsetForContentsPtr描述的Z轴位置(上一个被Hit的RenderLayer在TouchPoint的Z轴位置)进行比较。比较后如果发现当前正在处理的RenderLayer在TouchPoint处的Z轴位置较大,那么才会认为它是被Hit的RenderLayer。
接下来我们就继续分析RenderLayer类的成员函数hitTestContentsForFragments的实现,以便可以了解一个RenderLayer在什么情况会被认为是发生了Hit,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderLayer::hitTestContentsForFragments(constLayerFragments&layerFragments,constHitTestRequest&request,HitTestResult&result,
constHitTestLocation&hitTestLocation,HitTestFilterhitTestFilter,bool&insideClipRect)const
{
......
for(inti=layerFragments.size()-1;i>=0;--i){
constLayerFragment&fragment=layerFragments.at(i);
......
if(hitTestContents(request,result,fragment.layerBounds,hitTestLocation,hitTestFilter))
returntrue;
}
returnfalse;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
前面文章中一文提到,RenderLayer是按Fragment进行划分的。因此,RenderLayer类的成员函数hitTestContentsForFragments分别调用另外一个成员函数hitTestContents对每一个Fragment进行HitTest。只要其中一个Fragment发生了Hit,那么就会认为它所在的RenderLayer发生了Hit。
RenderLayer类的成员函数hitTestContents的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderLayer::hitTestContents(constHitTestRequest&request,HitTestResult&result,constLayoutRect&layerBounds,constHitTestLocation&hitTestLocation,HitTestFilterhitTestFilter)const
{
......
if(!renderer()->hitTest(request,result,hitTestLocation,toLayoutPoint(layerBounds.location()-renderBoxLocation()),hitTestFilter)){
......
returnfalse;
}
......
returntrue;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
RenderLayer类的成员函数hitTestContents首先调用成员函数renderer获得一个RenderObject。这个RenderObject是当前正在处理的RenderLayer的宿主RenderObject。也就是说,我们在为网页创建RenderLayerTree时,为上述RenderObject创建了一个RenderLayer。该RenderObject的子RenderObject如果没有自己的RenderLayer,那么就会与该RenderObject共享同一个RenderLayer。这一点可以参考前面文章中一文。
获得了当前正在处理的RenderLayer的宿主RenderObject之后,RenderLayer类的成员函数hitTestContents就调用它的成员函数hitTest检查它在参数layerBounds描述的区域内是否发生了Hit。如果发生了Hit,那么RenderLayer类的成员函数hitTestContents就直接返回一个true值给调用者。否则的话,就会返回一个false值给调用者。
这一步执行完成之后,就从RenderLayerTree转移到RenderObjectTree进行HitTest,也就是调用RenderObject类的成员函数hitTest进行HitTest,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderObject::hitTest(constHitTestRequest&request,HitTestResult&result,constHitTestLocation&locationInContainer,constLayoutPoint&accumulatedOffset,HitTestFilterhitTestFilter)
{
boolinside=false;
if(hitTestFilter!=HitTestSelf){
//Firsttesttheforegroundlayer(linesandinlines).
inside=nodeAtPoint(request,result,locationInContainer,accumulatedOffset,HitTestForeground);
//Testfloatsnext.
if(!inside)
inside=nodeAtPoint(request,result,locationInContainer,accumulatedOffset,HitTestFloat);
//Finallytesttoseeifthemouseisinthebackground(withinachildblock''sbackground).
if(!inside)
inside=nodeAtPoint(request,result,locationInContainer,accumulatedOffset,HitTestChildBlockBackgrounds);
}
//Seeifthemouseisinsideusbutnotanyofourdescendants
if(hitTestFilter!=HitTestDescendants&&!inside)
inside=nodeAtPoint(request,result,locationInContainer,accumulatedOffset,HitTestBlockBackground);
returninside;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.cpp中。
从前面分析的RenderLayer类的成员函数hitTestLayer可以知道,一个RederLayer关联的RenderObject会进行两次HitTest。第一次是针对该RenderObject的Foreground进行HitTest,这时候参数hitTestFilter的值等于HitTestDescendants。第二次是针对该RenderObject的Background层进行HitTest,这时候参数hitTestFilter的值等于HitTestSelf。
无论是Foreground层,还是Background层,RenderObject类的成员函数hitTest都是通过调用另外一个成员函数nodeAtPoint进行HitTest的。RenderObject类的成员函数nodeAtPoint是从父类RenderBox继承下来的,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderBox::nodeAtPoint(constHitTestRequest&request,HitTestResult&result,constHitTestLocation&locationInContainer,constLayoutPoint&accumulatedOffset,HitTestActionaction)
{
LayoutPointadjustedLocation=accumulatedOffset+location();
//Checkkidsfirst.
for(RenderObjectchild=slowLastChild();child;child=child->previousSibling()){
if((!child->hasLayer()||!toRenderLayerModelObject(child)->layer()->isSelfPaintingLayer())&&child->nodeAtPoint(request,result,locationInContainer,adjustedLocation,action)){
updateHitTestResult(result,locationInContainer.point()-toLayoutSize(adjustedLocation));
returntrue;
}
}
//Checkourboundsnext.Forthispurposealwaysassumethatwecanonlybehitinthe
//foregroundphase(whichistrueforreplacedelementslikeimages).
LayoutRectboundsRect=borderBoxRect();
boundsRect.moveBy(adjustedLocation);
if(visibleToHitTestRequest(request)&&action==HitTestForeground&&locationInContainer.intersects(boundsRect)){
updateHitTestResult(result,locationInContainer.point()-toLayoutSize(adjustedLocation));
if(!result.addNodeToRectBasedTestResult(node(),request,locationInContainer,boundsRect))
returntrue;
}
returnfalse;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBox.cpp中。
RenderBox类的成员函数nodeAtPoint首先对当前正在处理的RenderObject的子RenderObject进行HitTest。这是通过递归调用RenderBox类的成员函数nodeAtPoint实现的。如果子RenderObject没有被Hit,那么RenderBox类的成员函数才会判断当前正在处理的RenderObject是否发生Hit,也就是判断参数locationInContainer描述的TouchPoint是否落在当前正在处理的RenderObject的区域boundsRect内。
这里有一点需要注意,当前正在处理的RenderObject的每一个子RenderObjec并不是都会被递归HitTest。只有那些没有创建RenderLayer的子RenderObject才会进行递归HitTest。这些没有创建自己的RenderLayer的子RenderObject将会与当前正在处理的RenderObject共享同一个RenderLayer。对于那些有自己的RenderLayer的子RenderObject,它们的HitTest将由前面分析的RenderLayer类的成员函数hitTestLayer发起。
当一个RenderObject发生Hit时,RenderBox类的成员函数nodeAtPoint就会调用另外一个成员函数updateHitTestResult将Hit信息记录在参数result描述的一个HitTestResult对象中。RenderBox类的成员函数updateHitTestResult由子类RenderObject实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderObject::updateHitTestResult(HitTestResult&result,constLayoutPoint&point)
{
......
Nodenode=this->node();
//Ifwehittheanonymousrenderersinsidegeneratedcontentweshould
//actuallyhitthegeneratedcontentsowalkuptothePseudoElement.
if(!node&&parent()&&parent()->isBeforeOrAfterContent()){
for(RenderObjectrenderer=parent();renderer&&!node;renderer=renderer->parent())
node=renderer->node();
}
if(node){
result.setInnerNode(node);
......
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.cpp中。
RenderObject类的成员函数updateHitTestResult首先调用成员函数node获得与当前正在处理的RenderObject关联的HTML元素,也就是位于网页的DOMTree中的一个Node,作为当前发生的TouchEvent的TargetNode。
如果当前正在处理的RenderObject没有关联一个HTML元素,那么就说明当前正在处理的RenderObject是一个匿名的RenderObject。这时候需要在网页的RenderObjectTree中找到一个负责生成它的、非匿名的父RenderObject,然后再获得与这个父RenderObject关联的HTML元素,作为当前发生的TouchEvent的TargetNode。
一旦找到了TargetNode,RenderObject类的成员函数updateHitTestResult就会将它保存在参数result描述的一个HitTestResult对象中。这是通过调用HitTestResult类的成员函数setInnerNode实现的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidHitTestResult::setInnerNode(Noden)
{
......
m_innerNode=n;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/HitTestResult.cpp中。
HitTestResult类的成员函数setInnerNode主要是将参数n描述的一个HTML元素保存在成员变量m_innerNode中。这个HTML元素可以通过调用HitTestResult类的成员函数innerNode获得,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classHitTestResult{
public:
......
NodeinnerNode()const{returnm_innerNode.get();}
......
};
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/HitTestResult.h中。
这一步执行完成后,WebKit就通过网页的RenderLayerTree和RenderObjectTree,最终在DOMTree中找到了TargetNode。这个TargetNode负责接收和处理当前发生的TouchEvent。
回到前面分析的EventHandler类的成员函数handleTouchEvent中,接下来它就会将当前发生的TouchEvent分发给前面找到的TargetNode处理,这是通过调用它的成员函数dispatchTouchEvent实现的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolNode::dispatchTouchEvent(PassRefPtrWillBeRawPtrevent)
{
returnEventDispatcher::dispatchEvent(this,TouchEventDispatchMediator::create(event));
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Node.cpp中。
Node类的成员函数dispatchTouchEvent首先调用TouchEventDispatchMediator类的静态成员函数create将参数描述的TouchEvent封装在一个TouchEventDispatchMediator对象中,然的再调用EventDispatcher类的静态成员函数dispatchEvent对该TouchEvent进行处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolEventDispatcher::dispatchEvent(Nodenode,PassRefPtrWillBeRawPtrmediator)
{
......
EventDispatcherdispatcher(node,mediator->event());
returnmediator->dispatchEvent(&dispatcher);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。
EventDispatcher类的静态成员函数dispatchEvent首先从参数mediator描述的TouchEventDispatchMediator对象中取出它所封装的TouchEvent,并且将该TouchEvent封装在一个EventDispatcher对象中,最后调用参数mediator描述的TouchEventDispatchMediator对象的成员函数dispatchEvent对上述TouchEvent进行分发处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolTouchEventDispatchMediator::dispatchEvent(EventDispatcherdispatcher)const
{
event()->eventPath().adjustForTouchEvent(dispatcher->node(),event());
returndispatcher->dispatch();
}
这个函数定义在文件/external/chromium_org/third_party/WebKit/Source/core/events/TouchEvent.cpp中。
TouchEventDispatchMediator类的成员函数dispatchEvent首先调用成员函数event获得一个TouchEvent对象。这个TouchEvent描述的就是当前发生的TouchEvent。调用这个TouchEvent对象的成员函数eventPath可以获得一个EventPath对象。这个EventPath对象描述的是当前发生的TouchEvent的分发路径。关于WebKit中的Event分发路径,下面我们再描述。有了这个EventPath对象之后,TouchEventDispatchMediator类的成员函数dispatchEvent调用它的成员函数adjustForTouchEvent调整TouchEvent分发路径中的ShadowDOM的TouchList。关于ShadowDOM的更详细描述,可以参考文章中这篇文章。
TouchEventDispatchMediator类的成员函数dispatchEvent最后调用参数dispatcher描述的EventDispatcher对象的成员函数dispatch处理它所封装的TouchEvent,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolEventDispatcher::dispatch()
{
.......
voidpreDispatchEventHandlerResult;
if(dispatchEventPreProcess(preDispatchEventHandlerResult)==ContinueDispatching)
if(dispatchEventAtCapturing(windowEventContext)==ContinueDispatching)
if(dispatchEventAtTarget()==ContinueDispatching)
dispatchEventAtBubbling(windowEventContext);
dispatchEventPostProcess(preDispatchEventHandlerResult);
......
return!m_event->defaultPrevented();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。
在网页发生的一个Event,分为五个阶段进行分发处理:
1.PreProcess
2.Capturing
3.Target
4.Bubbling
5.PostProcess
我们通过一个例子说明这五个阶段的处理过程,如下所示:
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
假设在div3上发生了一个TouchEvent。
在PreProcess阶段,WebKit会将TouchEvent分发给div3的PreDispatchEventHandler处理,让div3有机会在DOMEventHandler处理TouchEvent之前做一些事情,用来实现自己的行为。
在Capturing阶段,WebKit会将TouchEvent依次分发给html->body->div1->div2的DOMEventHandler处理。
在Target阶段,WebKit会将TouchEvent依次分发给div3的DOMEventHandler处理。。
在Bubbling阶段,WebKit会将TouchEvent依次分发给div2->div1->body->html的DOMEventHandler处理。
在PostProcess阶段,WebKit会将TouchEvent分发给div3的PostDispatchEventHandler处理,让div3有机会在DOMEventHandler处理TouchEvent之后做一些事情,与它的PreDispatchEventHandler相呼应。
此外,如果在前面4个阶段,TouchEvent的preventDefault函数没有被调用,那么WebKit会将它依次分发给div3->div2->div1->body->html的DefaultEventHandler处理。在这个过程中,如果某一个Node的DefaultEventHandler处理了该TouchEvent,那么该TouchEvent的分发过程就会中止。
其中,PreProcess和PostProcess这两个阶段是一定会执行的。在Capturing、Target和Bubbling这三个阶段,如果某一个Node的DOMEventHandler调用了TouchEvent的stopPropagation函数,那么它就会提前中止,后面的阶段也不会被执行。
PreDispatchEventHandler、PostDispatchEventHandler和DefaultEventHandler是由WebKit实现的,DOMEventHandler可以通过JavaScript进行注册。在注册的时候,可以指定DOMEventHandler在Capturing阶段还是Bubbling阶段接收Event,但是不能同时在这两个阶段都接收。此外,注册TargetNode上的DOMEventHandler没有Capturing阶段还是Bubbling阶段之分,如果Event在Capturing阶段没有被中止,那么它将在Target阶段接收。
明白了WebKit中的Event处理流程之后,接下来我们主要分析TargetNode在Target阶段处理TouchEvent的过程,也就是EventDispatcher类的成员函数dispatchEventAtTarget的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
inlineEventDispatchContinuationEventDispatcher::dispatchEventAtTarget()
{
m_event->setEventPhase(Event::AT_TARGET);
m_event->eventPath()[0].handleLocalEvents(m_event.get());
returnm_event->propagationStopped()?DoneDispatching:ContinueDispatching;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。
EventDispatcher类的成员变量m_event描述的就是当前发生的TouchEvent。这个TouchEvent的EventPath是一个NodeEventContext列表。列表中的第一个NodeEventContext描述的就是当前发生的TouchEvent的TargetNode的上下文信息。有了这个NodeEventContext之后,就可以调用它的成员函数handleLocalEvents将当前发生的TouchEvent分发给TargetNode的DOMEventHandler处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidNodeEventContext::handleLocalEvents(Eventevent)const
{
......
event->setTarget(target());
event->setCurrentTarget(m_currentTarget.get());
m_node->handleLocalEvents(event);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/NodeEventContext.cpp中。
这时候,NodeEventContext类的成员变量m_node描述的就是TouchEvent的TargetNode。NodeEventContext类的成员函数handleLocalEvents调用这个TargetNode的成员函数handleLocalEvents,用来将当前发生的TouchEvent分发给它的DOMEventHandler处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidNode::handleLocalEvents(Eventevent)
{
......
fireEventListeners(event);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Node.cpp中。
Node类的成员函数handleLocalEvents主要是调用另外一个成员函数fireEventListeners将当前发生的TouchEvent分发给TargetNode的DOMEventHandler处理。
Node类的成员函数fireEventListeners是从父类EventTarget继承下来的,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolEventTarget::fireEventListeners(Eventevent)
{
......
EventTargetDatad=eventTargetData();
......
EventListenerVectorlegacyListenersVector=0;
AtomicStringlegacyTypeName=legacyType(event);
if(!legacyTypeName.isEmpty())
legacyListenersVector=d->eventListenerMap.find(legacyTypeName);
EventListenerVectorlistenersVector=d->eventListenerMap.find(event->type());
......
if(listenersVector){
fireEventListeners(event,d,listenersVector);
}elseif(legacyListenersVector){
......
fireEventListeners(event,d,legacyListenersVector);
......
}
......
return!event->defaultPrevented();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventTarget.cpp中。
Node类的成员函数fireEventListeners主要根据TouchEvent的类型在TargetNode注册的DOMEventHandler中找到对应的DOMEventHandler。例如,如果当前发生的是类型为MOVE的TouchEvent,那么Node类的成员函数fireEventListeners就会找到注册在TargetNode上的类型为touchmove的DOMEventHandler。
找到了对应的DOMEventHandler之后,Node类的成员函数fireEventListeners再调用另外一个重载版本的成员函数fireEventListeners将当前发生的TouchEvent分发给它们处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidEventTarget::fireEventListeners(Eventevent,EventTargetDatad,EventListenerVector&entry)
{
......
size_ti=0;
size_tsize=entry.size();
......
for(;i RegisteredEventListener®isteredListener=entry[i];
if(event->eventPhase()==Event::CAPTURING_PHASE&&!registeredListener.useCapture)
continue;
if(event->eventPhase()==Event::BUBBLING_PHASE&®isteredListener.useCapture)
continue;
//IfstopImmediatePropagationhasbeencalled,wejustbreakoutimmediately,without
//handlinganymoreeventsonthistarget.
if(event->immediatePropagationStopped())
break;
ExecutionContextcontext=executionContext();
if(!context)
break;
......
registeredListener.listener->handleEvent(context,event);
......
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventTarget.cpp中。
在Capturing和Bubbling阶段,WebKit也是通过Node类的成员函数fireEventListeners将当前发生的Event分发给EventPath上的Node处理。从这里我们就可以看到,在Capturing阶段,如果一个Node在注册了一个在Capturing阶段接收Event的DOMEventHandler,那么此时该DOMEventHandler就会获得当前发生的Event。同样,在Bubbling阶段,如果一个Node在注册了一个在Bubbling阶段接收Event的DOMEventHandler,那么此时该DOMEventHandler就会获得当前发生的Event。对于TargetNode来说,它注册的DOMEventHandler则会在Target阶段获得当前发生的Event。
这些DOMEventHandler在注册的时候,会被JavaScript引擎V8封装在一个V8AbstractEventListener对象中。Node类的成员函数fireEventListeners通过调用这些V8AbstractEventListener对象的成员函数handleEvent将当前发生的Event分发给它们所封装的DOMEventHandler处理。这就会进入到JavaScript引擎V8里面去执行了。以后我们分析JavaScript引擎V8时,再回过头来看它对Event的处理流程。
至此,我们就分析完成Chromium分发输入事件给WebKit,以及WebKit进行处理的过程了。这个过程是在Render进程的Main线程中执行的。回忆前面文章中一文,滑动和捏合手势这两种特殊的输入事件,是在Render进程的Compositor线程中处理的。
WebKit在处理输入事件的过程中,需要通过RenderLayerTree和RenderObjectTree,在DOMTree中找到输入事件的TargetNode(HitTest)。找到了输入事件的TargetNode之后,分为PreProcess、Capturing、Target、Bubbling和PostProcess五个阶段对输入事件进行处理。其中,Capturing、Target和Bubbling这三个阶段是将输入事件分发给TargetNode的DOMEventHandler处理,也就是我们通过JavaScript注册的EventHandler。DOMEventHandler最终是在JavaScript引擎V8执行的。
至此,Chromium的网页输入事件处理机制我们就全部分析完成了。
|
|