配色: 字号:
Chromium分发输入事件给WebKit处理的过程分析
2016-10-22 | 阅:  转:  |  分享 
  
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的网页输入事件处理机制我们就全部分析完成了。

献花(0)
+1
(本文系网络学习天...首藏)