Chromium分发输入事件给WebKit处理的过程分析

       Chromium的Render进程接收到Browser进程分发过来的输入事件之后,会在Compoistor线程中处理掉滑动和捏合手势这两种特殊的输入事件,其它类型的输入事件则交给Main线程处理。Main线程又会进一步将输入事件分发给WebKit处理。WebKit则根据输入事件发生的位置在网页中找到对应的HTML元素进行处理。本文接下来详细分析Chromium分发输入事件给WebKit处理的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       以Touch事件为例。WebKit接收到Chromium分发给它的Touch事件通知后,所做的第一件事情是做Hit Test,也就是检测Touch事件发生在哪一个HTML元素上,然后再将接收到的Touch事件分发给该它处理,如图1所示:


图1 WebKit处理Touch事件的过程

       HTML元素对应于网页DOM Tree中的Node。要从DOM Tree中找到输入事件的目标Node,必须要知道所有Node的Z轴位置大小,然后按照从上到下的顺序进行Hit Test。从前面Chromium网页Graphics Layer Tree创建过程分析一文可以知道,DOM Tree中的Node在Z轴上的实际位置,除了跟它们本身的Z-Index属性值有关之外,还与它们所处理的Stacking Context的Z-Index值有关。网页的Render Layer Tree记录了所有的Stacking Context,因此,WebKit要从网页的Render Layer Tree入手,才能在DOM Tree中找到正确的目标Node处理当前发生的输入事件。

       接下来,我们就从Compoistor线程将输入事件分发给Main线程开始,分析WebKit接收和处理输入事件的过程。从前面Chromium网页滑动和捏合手势处理过程分析一文可以知道,Compoistor线程是在InputEventFilter类的成员函数ForwardToHandler中将它不处理的输入事件发分给Main线程处理的,如下所示:

void InputEventFilter::ForwardToHandler(const IPC::Message& message) {  
  ......  
  
  int routing_id = message.routing_id();  
  InputMsg_HandleInputEvent::Param params;  
  if (!InputMsg_HandleInputEvent::Read(&message, &params))  
    return;  
  const WebInputEvent* event = params.a;  
  ui::LatencyInfo latency_info = params.b;  
  .......  
  
  InputEventAckState ack_state = handler_.Run(routing_id, event, &latency_info);  
  
  if (ack_state == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) {  
    ......  
    IPC::Message new_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的详细分析可以参考前面Chromium网页滑动和捏合手势处理过程分析一文。对于Compositor线程不处理的输入事件,InputEventFilter类的成员函数ForwardToHandler会将它重新封装在一个InputMsg_HandleInputEvent消息中。这个InputMsg_HandleInputEvent消息又会进一步封装在一个Task中,并且发送给Main线程的消息队列。这个Task绑定了InputEventFilter类的成员函数ForwardToMainListener。

       这意味着接下来InputEventFilter类的成员函数ForwardToMainListener就会在Main线程中被调用,用来处理Compositor线程分发过来的输入事件,如下所示:

void InputEventFilter::ForwardToMainListener(const IPC::Message& message) {
  main_listener_->OnMessageReceived(message);
}
       这个函数定义在文件external/chromium_org/content/renderer/input/input_event_filter.cc中。

       从前面Chromium网页滑动和捏合手势处理过程分析一文可以知道,InputEventFilter类的成员变量main_listener_指向的是一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的就是Render进程中的Render Thread,也就是Main Thread。InputEventFilter类的成员函数ForwardToMainListener调用这个RenderThreadImpl对象的成员函数OnMessageReceived处理参数message描述的输入事件。

       RenderThreadImpl类的成员函数OnMessageReceived是从父类ChildThread继承下来的,它的实现如下所示:

bool ChildThread::OnMessageReceived(const IPC::Message& msg) {
  .....

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ChildThread, msg)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  if (handled)
    return true;

  ......

  return router_.OnMessageReceived(msg);
}
       这个函数定义在文件external/chromium_org/content/child/child_thread.cc 中。

       ChildThread类的成员函数OnMessageReceived首先检查参数msg描述的消息是否要由自己处理。如果不处理,那么就再分发给注册在它里面的Route进行处理。

       从前面Chromium网页Frame Tree创建过程分析一文可以知道,Render进程会为每一个网页创建一个RenderViewImpl对象,用来代表网页所加载在的控件。这个RenderViewImpl对象在初始化的过程中,会通过父类RenderWidget的成员函数DoInit将自己注册为Render Thread中的一个Route,如下所示:

bool RenderWidget::DoInit(int32 opener_id,
                          WebWidget* web_widget,
                          IPC::SyncMessage* create_widget_message) {
  ......

  bool result = RenderThread::Get()->Send(create_widget_message);
  if (result) {
    RenderThread::Get()->AddRoute(routing_id_, this);
    .......
    return true;
  } 

  ......
}
      这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

      这意味着前面分析的RenderThreadImpl类的成员函数OnMessageReceived会将接收到的、自己又不处理的消息分发给RenderWidget类处理。RenderWidget类通过成员函数OnMessageReceived接收RenderThreadImpl类分发过来的消息,并且判断分发过来的消息是否与输入事件有关,也就是判断是否为一个类型为InputMsg_HandleInputEvent的消息。如果是的话,那么就会进行处理。如下所示:

bool RenderWidget::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)
    IPC_MESSAGE_HANDLER(InputMsg_HandleInputEvent, OnHandleInputEvent)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

       RenderWidget类通过成员函数OnMessageReceived会将类型为InputMsg_HandleInputEvent的消息分发给另外一个成员函数OnHandleInputEvent处理,如下所示:

void RenderWidget::OnHandleInputEvent(const blink::WebInputEvent* input_event,
                                      const ui::LatencyInfo& latency_info,
                                      bool is_keyboard_shortcut) {
  ......

  base::TimeTicks start_time;
  if (base::TimeTicks::IsHighResNowFastAndReliable())
    start_time = base::TimeTicks::HighResNow();
  ......

  bool prevent_default = false;
  if (WebInputEvent::isMouseEventType(input_event->type)) {
    const WebMouseEvent& mouse_event =
        *static_cast<const WebMouseEvent*>(input_event);
    ......
    prevent_default = WillHandleMouseEvent(mouse_event);
  }

  if (WebInputEvent::isKeyboardEventType(input_event->type)) {
    ......
#if defined(OS_ANDROID)
    // The DPAD_CENTER key on Android has a dual semantic: (1) in the general
    // case it should behave like a select key (i.e. causing a click if a button
    // is focused). However, if a text field is focused (2), its intended
    // behavior is to just show the IME and don't propagate the key.
    // A typical use case is a web form: the DPAD_CENTER should bring up the IME
    // when clicked on an input text field and cause the form submit if clicked
    // when the submit button is focused, but not vice-versa.
    // The UI layer takes care of translating DPAD_CENTER into a RETURN key,
    // but at this point we have to swallow the event for the scenario (2).
    const WebKeyboardEvent& key_event =
        *static_cast<const WebKeyboardEvent*>(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)) {
    const WebGestureEvent& gesture_event =
        *static_cast<const WebGestureEvent*>(input_event);
    ......
    prevent_default = prevent_default || WillHandleGestureEvent(gesture_event);
  }

  bool processed = 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);
  }

  // If this RawKeyDown event corresponds to a browser keyboard shortcut and
  // it's not processed by webkit, then we need to suppress the upcoming Char
  // events.
  if (!processed && is_keyboard_shortcut)
    suppress_next_char_events_ = true;

  InputEventAckState ack_result = processed ?
      INPUT_EVENT_ACK_STATE_CONSUMED : INPUT_EVENT_ACK_STATE_NOT_CONSUMED;  
  ......

  // dispatch compositor-handled scroll gestures.
  bool event_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);

  bool frame_pending = compositor_ && compositor_->BeginMainFrameRequested();

  // If we don't have a fast and accurate HighResNow, we assume the input
  // handlers are heavy and rate limit them.
  bool rate_limiting_wanted = true;
  if (base::TimeTicks::IsHighResNowFastAndReliable()) {
      base::TimeTicks end_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;
  }

  // Note that we can't use handling_event_type_ here since it will be overriden
  // by reentrant calls for events after the paused one.
  bool no_ack = ignore_ack_for_mouse_move_from_debugger_ &&
      input_event->type == WebInputEvent::MouseMove;
  if (!WebInputEventTraits::IgnoresAckDisposition(*input_event) && !no_ack) {
    InputHostMsg_HandleInputEvent_ACK_Params ack;
    ack.type = input_event->type;
    ack.state = ack_result;
    ack.latency = swap_latency_info;
    scoped_ptr<IPC::Message> response(
        new InputHostMsg_HandleInputEvent_ACK(routing_id_, ack));
    if (rate_limiting_wanted && event_type_can_be_rate_limited &&
        frame_pending && !is_hidden_) {
      // We want to rate limit the input events in this case, so we'll wait for
      // painting to finish before ACKing this message.
      ......
      if (pending_input_event_ack_) {
        // As two different kinds of events could cause us to postpone an ack
        // we send it now, if we have one pending. The Browser should never
        // send us the same kind of event we are delaying the ack for.
        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键盘事件。如果是的话,并且当前获得焦点的是一个Input Text控件,那么就调用另外一个成员函数OnShowImeIfNeeded弹出输入法,以便用户可以输入文本给Input Text控件去。注意,这个检查是针对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。从前面Chromium网页输入事件捕捉和手势检测过程分析一文可以知道,Browser进程收到Render进程对上一个输入事件的ACK之后,才会给它分发下一个输入事件。因此,Render进程是否给Browser进程发送ACK,可以用来控制Browser进程分发输入事件给Render进程的节奏。

       有三种类型的输入事件是需要控制分发节奏的:鼠标移动、鼠标中键滚动和触摸事件。这三类输入事件都有一个特点,它们会连续大量地输入。每一次输入Render进程都需要对它们作出响应,也就是对网页进行处理。如果它们发生得太过频繁,那么就会造成Render进程的负担很重。因此,对于这三类输入事件,RenderWidget类的成员函数OnHandleInputEvent并不会都马上对它们进行ACK。这里有一点需要注意,对于触摸事件,只有WebKit没有对它进行处理,那么RenderWidget类的成员函数OnHandleInputEvent会马上对它进行ACK。这是因为这个触摸事件和接下来发生的触摸事件,可能会触发手势操作,例如滑动手势和捏合手势。这些手势操作必须要迅速进行处理,否则的话,用户就会觉得浏览器没有反应了。

       什么情况下不会马上进行ACK呢?如果网页的当前帧请求执行了一次Commit操作,也就是请求了重新绘制网页的CC Layer Tree,但是网页的CC Layer Tree还没有绘制完成,并且也没有同步到CC Pending Layer Tree,那么就不会马上进行ACK。这个ACK会被缓存在RenderWidget类的成员变量pending_input_event_ack_中。等到网页的CC Layer Tree重新绘制完成并且同步到CC Pending Layer Tree之后,被缓存的ACK才会发送给Browser进程。之所以要这样做,是因为Main线程在重新绘制网页的CC Layer Tree的时候,任务是相当重的,这时候不宜再分发新的输入事件给它处理。

        此外,如果平台实现了高精度时钟,那么RenderWidget类的成员函数OnHandleInputEvent也不一定要等到网页的CC Layer Tree重新绘制完成并且同步到CC Pending Layer Tree之后,才将缓存的ACK才会发送给Browser进程。如果已经过去了一段时间,网页的CC Layer Tree还没有绘制完成,也没有同步到CC Pending Layer Tree,那么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处理的过程。从前面Chromium网页Frame Tree创建过程分析一文可以知道, RenderWidget类的成员变量webwidget_指向的是一个WebViewImpl对象。RenderWidget类的成员函数OnHandleInputEvent就是通过调用这个WebViewImpl对象的成员函数handleInputEvent将参数input_event描述的输入事件分发给WebKit处理的。前面我们已经假设这是一个Touch事件。

       WebViewImpl类的成员函数handleInputEvent的实现如下所示:

bool WebViewImpl::handleInputEvent(const WebInputEvent& inputEvent)
{
    ......

    return PageWidgetDelegate::handleInputEvent(m_page.get(), *this, inputEvent);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。

       对于Touch事件,WebViewImpl类的成员函数handleInputEvent将会调用PageWidgetDelegate类的静态成员函数handleInputEvent对它进行处理,如下所示:

bool PageWidgetDelegate::handleInputEvent(Page* page, PageWidgetEventHandler& handler, const WebInputEvent& event)
{
    LocalFrame* frame = page && page->mainFrame()->isLocalFrame() ? page->deprecatedLocalMainFrame() : 0;
    switch (event.type) {
    ......

    case WebInputEvent::TouchStart:
    case WebInputEvent::TouchMove:
    case WebInputEvent::TouchEnd:
    case WebInputEvent::TouchCancel:
        if (!frame || !frame->view())
            return false;
        return handler.handleTouchEvent(*frame, *static_cast<const WebTouchEvent*>(&event));

    ......

    default:
        return false;
    }
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。

       从前面的调用过程可以知道,参数page是从WebViewImpl类的成员变量m_page传递过来的,它指向的是一个Page对象。PageWidgetDelegate类的静态成员函数handleInputEvent通过这个Page对象可以获得一个LocalFrame对象。这个LocalFrame对象用来在WebKit层描述在当前进程中加载的网页,它的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。

       另外一个参数handler描述的是一个WebViewImpl对象。当第三个参数event描述的是一个Touch相关的事件时,PageWidgetDelegate类的静态成员函数handleInputEvent就会调用参数handler描述的WebViewImpl对象的成员函数handleTouchEvent对它进行处理。

       WebViewImpl类的成员函数handleTouchEvent是从父类PageWidgetEventHandler继承下来的,它的实现如下所示:

bool PageWidgetEventHandler::handleTouchEvent(LocalFrame& mainFrame, const WebTouchEvent& event)
{
    return mainFrame.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事件,如下所示:

bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
{
    ......

    const Vector<PlatformTouchPoint>& points = event.touchPoints();  

    unsigned i;
    ......
    bool allTouchReleased = true;
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
            allTouchReleased = false;
    }

    ......

    // First do hit tests for any new touch points.
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        
        if (point.state() == PlatformTouchPoint::TouchPressed) {
            HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
            LayoutPoint pagePoint = roundedLayoutPoint(m_frame->view()->windowToContents(point.pos()));
            HitTestResult result;
            if (!m_touchSequenceDocument) {
                result = hitTestResultAtPoint(pagePoint, hitType);
            } else if (m_touchSequenceDocument->frame()) {
                LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->windowToContents(point.pos()));
                result = hitTestResultInFrame(m_touchSequenceDocument->frame(), framePoint, hitType);
            } else
                continue;

            Node* node = result.innerNode();
            ......

            if (!m_touchSequenceDocument) {
                // Keep track of which document should receive all touch events
                // in the active sequence. This must be a single document to
                // ensure we don't leak Nodes between documents.
                m_touchSequenceDocument = &(result.innerNode()->document());
                ......
            }

            ......

            m_targetForTouchID.set(point.id(), node);

            ......
        }
    }

    ......

    // Holds the complete set of touches on the screen.
    RefPtrWillBeRawPtr<TouchList> touches = TouchList::create();

    // A different view on the 'touches' list above, filtered and grouped by
    // event target. Used for the 'targetTouches' list in the JS event.
    typedef WillBeHeapHashMap<EventTarget*, RefPtrWillBeMember<TouchList> > TargetTouchesHeapMap;
    TargetTouchesHeapMap touchesByTarget;

    // Array of touches per state, used to assemble the 'changedTouches' list.
    typedef WillBeHeapHashSet<RefPtrWillBeMember<EventTarget> > EventTargetSet;
    struct {
        // The touches corresponding to the particular change state this struct
        // instance represents.
        RefPtrWillBeMember<TouchList> m_touches;
        // Set of targets involved in m_touches.
        EventTargetSet m_targets;
    } changedTouches[PlatformTouchPoint::TouchStateEnd];

    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        PlatformTouchPoint::State pointState = point.state();
        RefPtrWillBeRawPtr<EventTarget> touchTarget = nullptr;

        if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
            // The target should be the original target for this touch, so get
            // it from the hashmap. As it's a release or cancel we also remove
            // it from the map.
            touchTarget = m_targetForTouchID.take(point.id());
        } else {
            // No hittest is performed on move or stationary, since the target
            // is not allowed to change anyway.
            touchTarget = m_targetForTouchID.get(point.id());
        }

        LocalFrame* targetFrame = 0;
        bool knownTarget = false;
        if (touchTarget) {
            Document& doc = touchTarget->toNode()->document();
            // If the target node has moved to a new document while it was being touched,
            // we can't send events to the new document because that could leak nodes
            // from one document to another. See http://crbug.com/394339.
            if (&doc == m_touchSequenceDocument.get()) {
                targetFrame = doc.frame();
                knownTarget = true;
            }
        }

        ......

        RefPtrWillBeRawPtr<Touch> touch = Touch::create(
            targetFrame, touchTarget.get(), point.id(), point.screenPos(), adjustedPagePoint, adjustedRadius, point.rotationAngle(), point.force());

        ......

        // Ensure this target's touch list exists, even if it ends up empty, so
        // it can always be passed to TouchEvent::Create below.
        TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        if (targetTouchesIterator == touchesByTarget.end()) {
            touchesByTarget.set(touchTarget.get(), TouchList::create());
            targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        }

        // touches and targetTouches should only contain information about
        // touches still on the screen, so if this point is released or
        // cancelled it will only appear in the changedTouches list.
        if (pointState != PlatformTouchPoint::TouchReleased && pointState != PlatformTouchPoint::TouchCancelled) {
            touches->append(touch);
            targetTouchesIterator->value->append(touch);
        }

        // Now build up the correct list for changedTouches.
        // Note that  any touches that are in the TouchStationary state (e.g. if
        // the user had several points touched but did not move them all) should
        // never be in the changedTouches list so we do not handle them
        // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
        // for further discussion about the TouchStationary state.
        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();
        ......
    }

    ......

    // Now iterate the changedTouches list and m_targets within it, sending
    // events to the targets as required.
    bool swallowedEvent = false;
    for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
        ......

        const AtomicString& stateName(eventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
        const EventTargetSet& targetsForState = changedTouches[state].m_targets;
        for (EventTargetSet::const_iterator it = targetsForState.begin(); it != targetsForState.end(); ++it) {
            EventTarget* touchEventTarget = it->get();
            RefPtrWillBeRawPtr<TouchEvent> touchEvent = 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();
        }
    }

    return swallowedEvent;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/EventHandler.cpp中。

       EventHandler类的成员函数handleTouchEvent主要是做三件事情:

       1. 对当前发生的Touch事件的每一个Touch Point进行Hit Test,分别找到它们的Target Node。

       2. 对Touch Point进行分类。还与屏幕接触是一类,不再与屏幕接触是另一类。与屏幕接触的一类又划分为两个子类。一个子类是静止不动的,另一个子类是正在移动的。另外,目标Node相同的Touch Point也会被组织在同一个Touch List中。

       3. 将Touch事件分发给Target Node处理。

       接下来,我们就将EventHandler类的成员函数handleTouchEvent划分为三段进行分析,每一段对应于上述的一个事件。

       第一段代码如下所示:

bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
{
    ......

    const Vector<PlatformTouchPoint>& points = event.touchPoints();  

    unsigned i;
    ......
    bool allTouchReleased = true;
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
            allTouchReleased = false;
    }

    ......

    // First do hit tests for any new touch points.
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        
        if (point.state() == PlatformTouchPoint::TouchPressed) {
            HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
            LayoutPoint pagePoint = roundedLayoutPoint(m_frame->view()->windowToContents(point.pos()));
            HitTestResult result;
            if (!m_touchSequenceDocument) {
                result = hitTestResultAtPoint(pagePoint, hitType);
            } else if (m_touchSequenceDocument->frame()) {
                LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->windowToContents(point.pos()));
                result = hitTestResultInFrame(m_touchSequenceDocument->frame(), framePoint, hitType);
            } else
                continue;

            Node* node = result.innerNode();
            ......

            if (!m_touchSequenceDocument) {
                // Keep track of which document should receive all touch events
                // in the active sequence. This must be a single document to
                // ensure we don't leak Nodes between documents.
                m_touchSequenceDocument = &(result.innerNode()->document());
                ......
            }

            ......

            m_targetForTouchID.set(point.id(), node);

            ......
        }
    }

       这段代码首先获得参数event描述的Touch事件关联的所有Touch Point,保存在本地变量points描述的一个Vector中。

       每一个Touch Point都用一个PlatformTouchPoint对象描述,它有五种状态,如下所示:

class PlatformTouchPoint {
public:
    enum State {
        TouchReleased,
        TouchPressed,
        TouchMoved,
        TouchStationary,
        TouchCancelled,
        TouchStateEnd // Placeholder: must remain the last item.
    };

    ......
};
       这些状态定义在文件external/chromium_org/third_party/WebKit/Source/platform/PlatformTouchPoint.h中。

       一个Touch Point的一般状态变化过程为:TouchPressed->TouchMoved/TouchStationary->TouchReleased/TouchCancelled。

       回到上面第一段代码中,它主要做的事情就是对那些状态为TouchPressed的Touch Point进行Hit Test,目的是找到它们的Target Node,并且将这些Target Node保存在EventHandler类的成员变量m_targetForTouchID描述的一个Hash Map中,键值为Touch Point对应的ID。有了这个Hash Map,当一个Touch Point从TouchPressed状态变为其它状态时,就可以轻松地知道它的Target Node,避免做重复的Hit Test。

       一系列连续的Touch Event只能发生在一个Document上。如果两个Touch Event有两个或者两个以上的Touch Point具有相同的ID,那么它们就是连续的Touch Event。它们所发生在的Document由第一个连续的Touch Event的第一个处于TouchPressed状态的Touch Point确定,也就是这个Touch Point的Target Node所在的Document。这个Document一旦确定,就会维护在EventHandler类的成员变量m_touchSequenceDocument中。

       当一个Touch Event的所有Touch Point的状态都处于TouchReleased或者TouchCancelled时,它就结束一个连续的Touch Event系列。这时候EventHandler类的成员变量m_touchSequenceDocument就会设置为NULL,表示接下来发生的Touch Event属于另外一个连续的系列。

       当一个Touch Event所在的Document所未确定时,EventHandler类的成员函数handleTouchEvent调用成员函数hitTestResultAtPoint对第一个Touch Point做Hit Test;当一个Touch Event所在的Document确定时,则调用另外一个成员函数hitTestResultInFrame做Hit Test。后者会将Hit Test的范围限制在指定的Document中。后面我们将以EventHandler类的成员函数hitTestResultAtPoint为例,分析Hit Test的执行过程。

       第二段代码如下所示:

    // Holds the complete set of touches on the screen.
    RefPtrWillBeRawPtr<TouchList> touches = TouchList::create();

    // A different view on the 'touches' list above, filtered and grouped by
    // event target. Used for the 'targetTouches' list in the JS event.
    typedef WillBeHeapHashMap<EventTarget*, RefPtrWillBeMember<TouchList> > TargetTouchesHeapMap;
    TargetTouchesHeapMap touchesByTarget;

    // Array of touches per state, used to assemble the 'changedTouches' list.
    typedef WillBeHeapHashSet<RefPtrWillBeMember<EventTarget> > EventTargetSet;
    struct {
        // The touches corresponding to the particular change state this struct
        // instance represents.
        RefPtrWillBeMember<TouchList> m_touches;
        // Set of targets involved in m_touches.
        EventTargetSet m_targets;
    } changedTouches[PlatformTouchPoint::TouchStateEnd];

    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        PlatformTouchPoint::State pointState = point.state();
        RefPtrWillBeRawPtr<EventTarget> touchTarget = nullptr;

        if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
            // The target should be the original target for this touch, so get
            // it from the hashmap. As it's a release or cancel we also remove
            // it from the map.
            touchTarget = m_targetForTouchID.take(point.id());
        } else {
            // No hittest is performed on move or stationary, since the target
            // is not allowed to change anyway.
            touchTarget = m_targetForTouchID.get(point.id());
        }

        LocalFrame* targetFrame = 0;
        bool knownTarget = false;
        if (touchTarget) {
            Document& doc = touchTarget->toNode()->document();
            // If the target node has moved to a new document while it was being touched,
            // we can't send events to the new document because that could leak nodes
            // from one document to another. See http://crbug.com/394339.
            if (&doc == m_touchSequenceDocument.get()) {
                targetFrame = doc.frame();
                knownTarget = true;
            }
        }

        ......

        RefPtrWillBeRawPtr<Touch> touch = Touch::create(
            targetFrame, touchTarget.get(), point.id(), point.screenPos(), adjustedPagePoint, adjustedRadius, point.rotationAngle(), point.force());

        ......

        // Ensure this target's touch list exists, even if it ends up empty, so
        // it can always be passed to TouchEvent::Create below.
        TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        if (targetTouchesIterator == touchesByTarget.end()) {
            touchesByTarget.set(touchTarget.get(), TouchList::create());
            targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        }

        // touches and targetTouches should only contain information about
        // touches still on the screen, so if this point is released or
        // cancelled it will only appear in the changedTouches list.
        if (pointState != PlatformTouchPoint::TouchReleased && pointState != PlatformTouchPoint::TouchCancelled) {
            touches->append(touch);
            targetTouchesIterator->value->append(touch);
        }

        // Now build up the correct list for changedTouches.
        // Note that  any touches that are in the TouchStationary state (e.g. if
        // the user had several points touched but did not move them all) should
        // never be in the changedTouches list so we do not handle them
        // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
        // for further discussion about the TouchStationary state.
        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();
        ......
    }
       这段代码主要是对Touch Point进行分门别类。

       首先,那些还与屏幕有接触的Touch Point将会保存在本地变量touches描述的一个Touch List中。那些状态不等于TouchReleased和TouchCancelled的Touch Point即为还与屏幕有接触的Touch Point。

       其次,具有相同Target Node的Touch Point又会保存在相同的Touch List中。这些Touch List它们关联的Target Node为键值,保存在本地变量touchesByTarget描述的一个Hash Map中。

       第三,那些位置或者状态发生变化,并且有Target Node的Touch Point会按照状态保存在本地变量changedTouches描述的一个数组中。相同状态的Touch Point保存在同一个Touch List中,它们的Target Node也会保存在同一个Hash Set中。位置或者状态发生变化的Touch Point,即为那些状态不等于TouchStationary的Touch Point。另外,如果一个Touch Point的Target Node所在的Document与当前Touch Event所发生在的Document不一致,那么该Touch Point会被认为是没有Target Node。

       这段代码还会做另外两件事情:

       1. 如果一个Touch Point的状态变为TouchReleased或者TouchCancelled,那么它就会从EventHandler类的成员变量m_targetForTouchID描述的一个Hash Map中移除。结合前面对第一段代码的分析,我们就可以知道,一个连续的Touch Event系列,它关联的Touch Point是会动态增加和移除的。

       2. 如果当前发生的Touch Event的所有Touch Point的状态均为TouchReleased或者TouchCancelled,那么当前连续的Touch Event系列就会结束。这时候EventHandler类的成员变量m_touchSequenceDocument将被设置为NULL。

       第三段代码如下所示:

   // Now iterate the changedTouches list and m_targets within it, sending
    // events to the targets as required.
    bool swallowedEvent = false;
    for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
        ......

        const AtomicString& stateName(eventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
        const EventTargetSet& targetsForState = changedTouches[state].m_targets;
        for (EventTargetSet::const_iterator it = targetsForState.begin(); it != targetsForState.end(); ++it) {
            EventTarget* touchEventTarget = it->get();
            RefPtrWillBeRawPtr<TouchEvent> touchEvent = 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();
        }
    }

    return swallowedEvent;
}
       这段代码将Touch Event分发给Target Node处理。注意,并不是所有的Target Node都会被分发Touch Event。只有那些Touch Point位置或者状态发生了变化的Target Node才会获得Touch Event。

       这段代码按照Touch Point的状态分发Touch Event给Target Node处理,顺序为TouchReleased->TouchPressed->TouchMoved->TouchCancelled。如果具有相同状态的Touch Point关联了不同的Target Node,那么每一个Target Node都会获得一个Touch Event。

       每一个Target Node获得的Touch Event是不同的TouchEvent对象,每一个TouchEvent对象包含了以下三种信息:

       1. 所有与屏幕接触的Touch Point。这些Touch Point有的位于Target Node的范围内,有的可能位于Target Node的范围外。

       2. 位于Target Node的范围内的Touch Point。

       3. 具有相同状态的Touch Point。

       注意,这些Touch Point限定在当前发生的Touch Event关联的Touch Point中,也就是限定在参数event描述的Touch Event关联的Touch Point中。

       EventHandler类的成员函数是通过Target Node的成员函数handleTouchEvent给它们分发Touch Event,也就是通过调用Node类的成员函数handleTouchEvent将Touch Event分发给Target Node处理。

       接下来,我们就继续分析EventHandler类的成员函数hitTestResultAtPoint和Node类的成员函数handleTouchEvent的实现,以便了解WebKit根据Touch Point找到Target Node和将Touch Event分发给Target Node处理的过程。

       EventHandler类的成员函数hitTestResultAtPoint的实现如下所示:

HitTestResult EventHandler::hitTestResultAtPoint(const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType, const LayoutSize& padding)
{
    ......

    // We always send hitTestResultAtPoint to the main frame if we have one,
    // otherwise we might hit areas that are obscured by higher frames.
    if (Page* page = m_frame->page()) {
        LocalFrame* mainFrame = page->mainFrame()->isLocalFrame() ? page->deprecatedLocalMainFrame() : 0;
        if (mainFrame && m_frame != mainFrame) {
            FrameView* frameView = m_frame->view();
            FrameView* mainView = mainFrame->view();
            if (frameView && mainView) {
                IntPoint mainFramePoint = mainView->rootViewToContents(frameView->contentsToRootView(roundedIntPoint(point)));
                return mainFrame->eventHandler().hitTestResultAtPoint(mainFramePoint, hitType, padding);
            }
        }
    }

    HitTestResult result(point, padding.height(), padding.width(), padding.height(), padding.width());
    ......

    // hitTestResultAtPoint is specifically used to hitTest into all frames, thus it always allows child frame content.
    HitTestRequest request(hitType | HitTestRequest::AllowChildFrameContent);
    m_frame->contentRenderer()->hitTest(request, result);
    ......

    return result;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/EventHandler.cpp中。

       EventHandler类的成员函数hitTestResultAtPoint首先会检查当前正在处理的网页的Main Frame是否是一个Local Frame。如果是的话,那么就会在它上面做Hit Test。否则的话,就在当前正在处理的网页所加载在的Frame上做Hit Test,也就是EventHandler类的成员变量m_frame描述的Frame。这个Frame是一个Local Frame。

       前面Chromium网页加载过程简要介绍和学习计划一文提到,在WebKit中,一个Frame有可能是Local的,也可能是Remote的。Local Frame就在当前进程加载一个网页,而Remote Frame在另外一个进程加载一个网页。Frame与Frame之间会形成一个Frame Tree。Frame Tree的根节点就是一个Main Frame。结合前面分析的EventHandler类的成员函数handleTouchEvent,我们就可以知道,如果当前正在处理的网页不是加载在一个Main Frame上,并且这个网页的Main Frame是一个Local Frame,那么发生在这个网页上的Touch Event将会在它的Main Frame上做Hit Test。在其余情况下,发生在当前正在处理的网页上的Touch Event,将会在该网页所加载在的Frame上做Hit Test。

       一旦确定在哪个Frame上做Hit Test之后,EventHandler类的成员函数hitTestResultAtPoint就会调用这个Frame的成员函数contentRenderer获得一个RenderView对象。从前面Chromium网页Render Object Tree创建过程分析一文可以知道,这个RenderView对象描述的就是当前正在处理的网页的Render Object Tree的根节点。有了这个RenderView对象之后,EventHandler类的成员函数hitTestResultAtPoint就会调用它的成员函数hitTest对参数point描述的Touch Point进行Hit Test。

       RenderView类的成员函数hitTest的实现如下所示:

bool RenderView::hitTest(const HitTestRequest& request, HitTestResult& result)
{
    return hitTest(request, result.hitTestLocation(), result);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderView.cpp中。

       RenderView类的成员函数hitTest调用另外一个重载版本的成员函数hitTest对参数result描述的Touch Point进行Hit Test,如下所示:

bool RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result)
{
    ......

    return layer()->hitTest(request, location, result);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderView.cpp中。

       RenderView类重载版本的成员函数hitTest首先会调用另外一个成员函数layer获得一个RenderLayer对象。从前面Chromium网页Render Layer Tree创建过程分析一文可以知道,这个RenderLayer对象描述的就是网页的Render Layer Tree的根节点。有了这个RenderLayer对象之后,RenderView类重载版本的成员函数hitTest就会调用它的成员函数hitTest对象参数result描述的Touch Point进行Hit Test,如下所示:

bool RenderLayer::hitTest(const HitTestRequest& request, const HitTestLocation& hitTestLocation, HitTestResult& result)
{
    ......

    LayoutRect hitTestArea = renderer()->view()->documentRect();
    ......

    RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, hitTestLocation, false);
    ......

    return insideLayer;
}

      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

      RenderLayer类的成员函数hitTest首先是获得网页的Document对象占据的区域hitTestArea,然后再调用另外一个成员函数hitTestLayer在该区域上对参数hitTestLocation描述的Touch Point进行Hit Test,如下所示:

RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result,
                                       const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, bool appliedTransform,
                                       const HitTestingTransformState* transformState, double* zOffset)
{
    ......

    // Ensure our lists and 3d status are up-to-date.
    m_stackingNode->updateLayerListsIfNeeded();
    update3DTransformedDescendantStatus();
    ......

    // The following are used for keeping track of the z-depth of the hit point of 3d-transformed
    // descendants.
    double localZOffset = -numeric_limits<double>::infinity();
    double* zOffsetForDescendantsPtr = 0;
    double* zOffsetForContentsPtr = 0;

    bool depthSortDescendants = false;
    if (preserves3D()) {
        depthSortDescendants = true;
        // Our layers can depth-test with our container, so share the z depth pointer with the container, if it passed one down.
        zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
        zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
    } else if (m_has3DTransformedDescendant) {
        // Flattening layer with 3d children; use a local zOffset pointer to depth-test children and foreground.
        depthSortDescendants = true;
        zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
        zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
    } else if (zOffset) {
        zOffsetForDescendantsPtr = 0;
        // Container needs us to give back a z offset for the hit layer.
        zOffsetForContentsPtr = zOffset;
    }

    ......

    // This variable tracks which layer the mouse ends up being inside.
    RenderLayer* candidateLayer = 0;

    // Begin by walking our list of positive layers from highest z-index down to the lowest z-index.
    RenderLayer* hitLayer = hitTestChildren(PositiveZOrderChildren, rootLayer, request, result, hitTestRect, hitTestLocation,
                                        localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
    if (hitLayer) {
        if (!depthSortDescendants)
            return hitLayer;
        candidateLayer = hitLayer;
    }

    // Now check our overflow objects.
    hitLayer = hitTestChildren(NormalFlowChildren, rootLayer, request, result, hitTestRect, hitTestLocation,
                           localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
    if (hitLayer) {
        if (!depthSortDescendants)
            return hitLayer;
        candidateLayer = hitLayer;
    }

    ......

    // Next we want to see if the mouse pos is inside the child RenderObjects of the layer. Check
    // every fragment in reverse order.
    if (isSelfPaintingLayer()) {
        // Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost.
        HitTestResult tempResult(result.hitTestLocation());
        bool insideFragmentForegroundRect = false;
        if (hitTestContentsForFragments(layerFragments, request, tempResult, hitTestLocation, HitTestDescendants, insideFragmentForegroundRect)
            && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
            ......
            if (!depthSortDescendants)
                return this;
            // Foreground can depth-sort with descendant layers, so keep this as a candidate.
            candidateLayer = this;
        } 
    }

    // Now check our negative z-index children.
    hitLayer = hitTestChildren(NegativeZOrderChildren, rootLayer, request, result, hitTestRect, hitTestLocation,
        localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
    if (hitLayer) {
        if (!depthSortDescendants)
            return hitLayer;
        candidateLayer = hitLayer;
    }

    ......

    // If we found a layer, return. Child layers, and foreground always render in front of background.
    if (candidateLayer)
        return candidateLayer;

    if (isSelfPaintingLayer()) {
        HitTestResult tempResult(result.hitTestLocation());
        bool insideFragmentBackgroundRect = false;
        if (hitTestContentsForFragments(layerFragments, request, tempResult, hitTestLocation, HitTestSelf, insideFragmentBackgroundRect)
            && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
            ......
            return this;
        }
        ......
    }

    return 0;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

       RenderLayer类的成员变量m_stackingNode指向的是一个RenderLayerStackingNode对象。这个RenderLayerStackingNode对象描述了当前正在处理的Render Layer所在的Stacking Context。RenderLayer类的成员函数hitTestLayer首先调用这个RenderLayerStackingNode对象的成员函数updateLayerListsIfNeeded更新它所描述的Stacking Context所包含的Render Layer的层次关系,以便接下来可以按照它们的Z轴位置进行Hit Test。

       RenderLayer类的成员函数hitTestLayer同时还会调用另外一个成员函数update3DTransformedDescendantStatus检查当前正在处理的Render Layer的子Render Layer是否设置了3D。如果设置了,那么RenderLayer类的成员变量m_has3DTransformedDescendant就会被设置为true。3D变换使得Hit Test不能简单地按照原来Z-Index的大小进行Hit Test。

       RenderLayer类的成员函数hitTestLayer接下来根据两种不同的情况,采取两种不同的Hit Test方法:

       1. 当前正在处理的Render Layer将CSS属性tranform-type设置为“preserve-3d”,或者它的子Render Layer设置了3D变换。在这种情况下,本地变量depthSortDescendants的值会被设置为true,并且另外两个本地变量zOffsetForDescendantsPtr和zOffsetForContentsPtr指向了一个类型为double的地址。这个地址包含的double值描述的是上一个被Hit的Render Layer在Touch Point处的Z轴位置。其中,本地变量zOffsetForContentsPtr描述的Z轴位置是给当前正在处理的Render Layer使用的,而本地变量zOffsetForDescendantsPtr描述的Z轴位置是给当前正在处理的Render Layer的子Render Layer使用的。在一个设置了3D变换的环境中,Z-Index值大的Render Layer不一定位于Z-Index值小的Render Layer的上面,需要进一步结合它们的3D变换情况进行判断。因此,就需要将上一个被Hit的Render Layer在Touch Point处的Z轴位置保存下来,用来与下一个也被Hit的Render Layer进行比较,以便得出正确的被Hit的Render Layer。

       2. 当前正在处理的Render Layer没有将CSS属性tranform-type设置为“preserve-3d”,以及它的子Render Layer也没有设置3D变换。在这种情况下,本地变量depthSortDescendants的值会被设置为false。另外两个本地变量zOffsetForDescendantsPtr和zOffsetForContentsPtr,前者被设置为NULL,后者设置为参数zOffset的值。将本地变量zOffsetForDescendantsPtr设置为NULL,是因为当前正在处理的Render Layer的子Render Layer在做Hit Test时,不需要与其它的子Render Layer在Touch Point处进行Z轴位置。将zOffsetForContentsPtr的值指定为参数zOffset的值,是因为调用者可能会指定一个Z轴位置,要求当前正在处理的Render Layer在Touch Point处与其进行比较。

       对第二种情况的处理比较简单,流程如下所示: 

       1. 按照Z-Index从大到小的顺序对Z-Index值大于等于0的子Render Layer进行Hit Test。如果发生了Hit,那么停止Hit Test流程。

       2. 对当前正在处理的Render Layer的Foreground层进行Hit Test。如果发生了Hit,那么停止Hit Test流程。

       3. 按照Z-Index从大到小的顺序对Z-Index值小于0的子Render Layer进行Hit Test。如果发生了Hit,那么停止Hit Test流程。

       4. 对当前正在处理的Render Layer关联的Render Object的Background层进行Hit Test。

       对第一种情况的处理相对就会复杂一些,如下所示:

       1. 对当前正在处理的Render Layer的所有子Render Layer,以及当前正在处理的Render Layer的Foreground层,都会一一进行Hit Test。在这个Hit Test过程中,所有被Hit的Render Layer,都会根据它们3D变换情况,检查它们在Touch Point处的Z轴位置。Z轴位置最大的Render Layer或者Render Object,将会选择用来接收Touch Event。

       2. 如果所有子Render Layer和当前正在处理的Render Layer的Foreground都没有发生Hit,那么就会再对当前正在处理的Render Layer的Background层进行Hit Test。

       注意,对于每一个子Render Layer,RenderLayer类的成员函数hitTestLayer就会调用另外一个成员函数hitTestChildren对分别对它们进行Hit Test。RenderLayer类的成员函数hitTestChildren又会调用hitTestLayer对每一个子Render Layer执行具体的Hit Test。

       这意味着,RenderLayer类的成员函数hitTestLayer会被递归调用来对Render Layer Tree中的每一个Render Layer进行Hit Test。当前正在处理的Render Layer是否被Hit,RenderLayer类的成员函数hitTestLayer是通过调用两次成员函数hitTestContentsForFragments进行检查。第一次调用是确定当前正在处理的Render Layer的Foreground层是否发生了Hit Test。第二次调用是确定当前正在处理的Render Layer的Background层是否发生了Hit Test。

        一旦检查当前正在处理的Render Layer发生了Hit,那么RenderLayer类的成员函数hitTestLayer还需要调用另外一个成员函数isHitCandidate将它在Touch Point处的Z轴位置与本地变量zOffsetForContentsPtr描述的Z轴位置(上一个被Hit的Render Layer在Touch Point的Z轴位置)进行比较。比较后如果发现当前正在处理的Render Layer在Touch Point处的Z轴位置较大,那么才会认为它是被Hit的Render Layer。

       接下来我们就继续分析RenderLayer类的成员函数hitTestContentsForFragments的实现,以便可以了解一个Render Layer在什么情况会被认为是发生了Hit,如下所示:

bool RenderLayer::hitTestContentsForFragments(const LayerFragments& layerFragments, const HitTestRequest& request, HitTestResult& result,
    const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter, bool& insideClipRect) const
{
    ......

    for (int i = layerFragments.size() - 1; i >= 0; --i) {
        const LayerFragment& fragment = layerFragments.at(i);
        ......
        if (hitTestContents(request, result, fragment.layerBounds, hitTestLocation, hitTestFilter))
            return true;
    }

    return false;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

       前面Chromium网页Layer Tree绘制过程分析一文提到,Render Layer是按Fragment进行划分的。因此,RenderLayer类的成员函数hitTestContentsForFragments分别调用另外一个成员函数hitTestContents对每一个Fragment进行Hit Test。只要其中一个Fragment发生了Hit,那么就会认为它所在的Render Layer发生了Hit。

       RenderLayer类的成员函数hitTestContents的实现如下所示:

bool RenderLayer::hitTestContents(const HitTestRequest& request, HitTestResult& result, const LayoutRect& layerBounds, const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter) const
{
    ......

    if (!renderer()->hitTest(request, result, hitTestLocation, toLayoutPoint(layerBounds.location() - renderBoxLocation()), hitTestFilter)) {
        ......
        return false;
    }

    ......

    return true;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

       RenderLayer类的成员函数hitTestContents首先调用成员函数renderer获得一个Render Object。这个Render Object是当前正在处理的Render Layer的宿主Render Object。也就是说,我们在为网页创建Render Layer Tree时,为上述Render Object创建了一个Render Layer。该Render Object的子Render Object如果没有自己的Render Layer,那么就会与该Render Object共享同一个Render Layer。这一点可以参考前面Chromium网页Render Layer Tree创建过程分析一文。 

       获得了当前正在处理的Render Layer的宿主Render Object之后,RenderLayer类的成员函数hitTestContents就调用它的成员函数hitTest检查它在参数layerBounds描述的区域内是否发生了Hit。如果发生了Hit,那么RenderLayer类的成员函数hitTestContents就直接返回一个true值给调用者。否则的话,就会返回一个false值给调用者。

       这一步执行完成之后,就从Render Layer Tree转移到Render Object Tree进行Hit Test,也就是调用RenderObject类的成员函数hitTest进行Hit Test,如下所示:

bool RenderObject::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestFilter hitTestFilter)
{
    bool inside = false;
    if (hitTestFilter != HitTestSelf) {
        // First test the foreground layer (lines and inlines).
        inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestForeground);

        // Test floats next.
        if (!inside)
            inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestFloat);

        // Finally test to see if the mouse is in the background (within a child block's background).
        if (!inside)
            inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestChildBlockBackgrounds);
    }

    // See if the mouse is inside us but not any of our descendants
    if (hitTestFilter != HitTestDescendants && !inside)
        inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestBlockBackground);

    return inside;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.cpp中。

       从前面分析的RenderLayer类的成员函数hitTestLayer可以知道,一个Reder Layer关联的Render Object会进行两次Hit Test。第一次是针对该Render Object的Foreground进行Hit Test,这时候参数hitTestFilter的值等于HitTestDescendants。第二次是针对该Render Object的Background层进行Hit Test,这时候参数hitTestFilter的值等于HitTestSelf。

       无论是Foreground层,还是Background层,RenderObject类的成员函数hitTest都是通过调用另外一个成员函数nodeAtPoint进行Hit Test的。RenderObject类的成员函数nodeAtPoint是从父类RenderBox继承下来的,它的实现如下所示:

bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
{
    LayoutPoint adjustedLocation = accumulatedOffset + location();

    // Check kids first.
    for (RenderObject* child = 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));
            return true;
        }
    }

    // Check our bounds next. For this purpose always assume that we can only be hit in the
    // foreground phase (which is true for replaced elements like images).
    LayoutRect boundsRect = 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))
            return true;
    }

    return false;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBox.cpp中。

       RenderBox类的成员函数nodeAtPoint首先对当前正在处理的Render Object的子Render Object进行Hit Test。这是通过递归调用RenderBox类的成员函数nodeAtPoint实现的。如果子Render Object没有被Hit,那么RenderBox类的成员函数才会判断当前正在处理的Render Object是否发生Hit,也就是判断参数locationInContainer描述的Touch Point是否落在当前正在处理的Render Object的区域boundsRect内。

       这里有一点需要注意,当前正在处理的Render Object的每一个子Render Objec并不是都会被递归Hit Test。只有那些没有创建Render Layer的子Render Object才会进行递归Hit Test。这些没有创建自己的Render Layer的子Render Object将会与当前正在处理的Render Object共享同一个Render Layer。对于那些有自己的Render Layer的子Render Object,它们的Hit Test将由前面分析的RenderLayer类的成员函数hitTestLayer发起。

       当一个Render Object发生Hit时,RenderBox类的成员函数nodeAtPoint就会调用另外一个成员函数updateHitTestResult将Hit信息记录在参数result描述的一个HitTestResult对象中。RenderBox类的成员函数updateHitTestResult由子类RenderObject实现,如下所示:

void RenderObject::updateHitTestResult(HitTestResult& result, const LayoutPoint& point)
{
    ......

    Node* node = this->node();

    // If we hit the anonymous renderers inside generated content we should
    // actually hit the generated content so walk up to the PseudoElement.
    if (!node && parent() && parent()->isBeforeOrAfterContent()) {
        for (RenderObject* renderer = 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获得与当前正在处理的Render Object关联的HTML元素,也就是位于网页的DOM Tree中的一个Node,作为当前发生的Touch Event的Target Node。

       如果当前正在处理的Render Object没有关联一个HTML元素,那么就说明当前正在处理的Render Object是一个匿名的Render Object。这时候需要在网页的Render Object Tree中找到一个负责生成它的、非匿名的父Render Object,然后再获得与这个父Render Object关联的HTML元素,作为当前发生的Touch Event的Target Node。

       一旦找到了Target Node,RenderObject类的成员函数updateHitTestResult就会将它保存在参数result描述的一个HitTestResult对象中。这是通过调用HitTestResult类的成员函数setInnerNode实现的,如下所示:

void HitTestResult::setInnerNode(Node* n)
{
    ......
    m_innerNode = n;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/HitTestResult.cpp中。

       HitTestResult类的成员函数setInnerNode主要是将参数n描述的一个HTML元素保存在成员变量m_innerNode中。这个HTML元素可以通过调用HitTestResult类的成员函数innerNode获得,如下所示:

class HitTestResult {
public:
    ......

    Node* innerNode() const { return m_innerNode.get(); }

    ......
};
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/HitTestResult.h中。

      这一步执行完成后,WebKit就通过网页的Render Layer Tree和Render Object Tree,最终在DOM Tree中找到了Target Node。这个Target Node负责接收和处理当前发生的Touch Event。

      回到前面分析的EventHandler类的成员函数handleTouchEvent中,接下来它就会将当前发生的Touch Event分发给前面找到的Target Node处理,这是通过调用它的成员函数dispatchTouchEvent实现的,如下所示:

bool Node::dispatchTouchEvent(PassRefPtrWillBeRawPtr<TouchEvent> event)
{
    return EventDispatcher::dispatchEvent(this, TouchEventDispatchMediator::create(event));
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Node.cpp中。

       Node类的成员函数dispatchTouchEvent首先调用TouchEventDispatchMediator类的静态成员函数create将参数描述的Touch Event封装在一个TouchEventDispatchMediator对象中,然的再调用EventDispatcher类的静态成员函数dispatchEvent对该Touch Event进行处理,如下所示:

bool EventDispatcher::dispatchEvent(Node* node, PassRefPtrWillBeRawPtr<EventDispatchMediator> mediator)
{
    ......
    EventDispatcher dispatcher(node, mediator->event());
    return mediator->dispatchEvent(&dispatcher);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。

       EventDispatcher类的静态成员函数dispatchEvent首先从参数mediator描述的TouchEventDispatchMediator对象中取出它所封装的Touch Event,并且将该Touch Event封装在一个EventDispatcher对象中,最后调用参数mediator描述的TouchEventDispatchMediator对象的成员函数dispatchEvent对上述Touch Event进行分发处理,如下所示:

bool TouchEventDispatchMediator::dispatchEvent(EventDispatcher* dispatcher) const
{
    event()->eventPath().adjustForTouchEvent(dispatcher->node(), *event());
    return dispatcher->dispatch();
}

       这个函数定义在文件/external/chromium_org/third_party/WebKit/Source/core/events/TouchEvent.cpp中。

       TouchEventDispatchMediator类的成员函数dispatchEvent首先调用成员函数event获得一个TouchEvent对象。这个TouchEvent描述的就是当前发生的Touch Event。调用这个TouchEvent对象的成员函数eventPath可以获得一个EventPath对象。这个EventPath对象描述的是当前发生的Touch Event的分发路径。关于WebKit中的Event分发路径,下面我们再描述。有了这个EventPath对象之后,TouchEventDispatchMediator类的成员函数dispatchEvent调用它的成员函数adjustForTouchEvent调整Touch Event分发路径中的Shadow DOM的Touch List。关于Shadow DOM的更详细描述,可以参考What the Heck is Shadow DOM这篇文章。

        TouchEventDispatchMediator类的成员函数dispatchEvent最后调用参数dispatcher描述的EventDispatcher对象的成员函数dispatch处理它所封装的Touch Event,如下所示:

bool EventDispatcher::dispatch()
{
    .......

    void* preDispatchEventHandlerResult;
    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. Pre Process

       2. Capturing

       3. Target

       4. Bubbling

       5. Post Process

       我们通过一个例子说明这五个阶段的处理过程,如下所示:

<html>
    <body>
        <div id="div1"> 
            <div id="div2">
                <div id="div3">
                </div>
            </div>
        </div>
    </body>
</html>
       假设在div3上发生了一个Touch Event。

       在Pre Process阶段,WebKit会将Touch Event分发给div3的Pre Dispatch Event Handler处理,让div3有机会在DOM Event Handler处理Touch Event之前做一些事情,用来实现自己的行为。

       在Capturing阶段,WebKit会将Touch Event依次分发给html -> body -> div1 -> div2的DOM Event Handler处理。

       在Target阶段,WebKit会将Touch Event依次分发给div3的DOM Event Handler处理。。

       在Bubbling阶段, WebKit会将Touch Event依次分发给div2 -> div1 -> body -> html的DOM Event Handler处理。

       在Post Process阶段,WebKit会将Touch Event分发给div3的Post Dispatch Event Handler处理,让div3有机会在DOM Event Handler处理Touch Event之后做一些事情,与它的Pre Dispatch Event Handler相呼应。

       此外,如果在前面4个阶段,Touch Event的preventDefault函数没有被调用,那么WebKit会将它依次分发给div3 -> div2 -> div1 -> body -> html的Default Event Handler处理。在这个过程中,如果某一个Node的Default Event Handler处理了该Touch Event,那么该Touch Event的分发过程就会中止。

       其中,Pre Process和Post Process这两个阶段是一定会执行的。在Capturing、Target和Bubbling这三个阶段,如果某一个Node的DOM Event Handler调用了Touch Event的stopPropagation函数,那么它就会提前中止,后面的阶段也不会被执行。

       Pre Dispatch Event Handler、Post Dispatch Event Handler和Default Event Handler是由WebKit实现的,DOM Event Handler可以通过JavaScript进行注册。在注册的时候,可以指定DOM Event Handler在Capturing阶段还是Bubbling阶段接收Event,但是不能同时在这两个阶段都接收。此外,注册Target Node上的DOM Event Handler没有Capturing阶段还是Bubbling阶段之分,如果Event在Capturing阶段没有被中止,那么它将在Target阶段接收。

       明白了WebKit中的Event处理流程之后,接下来我们主要分析Target Node在Target阶段处理Touch Event的过程,也就是EventDispatcher类的成员函数dispatchEventAtTarget的实现,如下所示:

inline EventDispatchContinuation EventDispatcher::dispatchEventAtTarget()
{
    m_event->setEventPhase(Event::AT_TARGET);
    m_event->eventPath()[0].handleLocalEvents(m_event.get());
    return m_event->propagationStopped() ? DoneDispatching : ContinueDispatching;
}

      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。

      EventDispatcher类的成员变量m_event描述的就是当前发生的Touch Event。这个Touch Event的Event Path是一个Node Event Context列表。列表中的第一个Node Event Context描述的就是当前发生的Touch Event的Target Node的上下文信息。有了这个Node Event Context之后,就可以调用它的成员函数handleLocalEvents将当前发生的Touch Event分发给Target Node的DOM Event Handler处理,如下所示:

void NodeEventContext::handleLocalEvents(Event* event) 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描述的就是Touch Event的Target Node。NodeEventContext类的成员函数handleLocalEvents调用这个Target Node的成员函数handleLocalEvents,用来将当前发生的Touch Event分发给它的DOM Event Handler处理,如下所示:

void Node::handleLocalEvents(Event* event)
{
    ......

    fireEventListeners(event);
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Node.cpp中。

       Node类的成员函数handleLocalEvents主要是调用另外一个成员函数fireEventListeners将当前发生的Touch Event分发给Target Node的DOM Event Handler处理。

       Node类的成员函数fireEventListeners是从父类EventTarget继承下来的,它的实现如下所示:

bool EventTarget::fireEventListeners(Event* event)
{
    ......

    EventTargetData* d = eventTargetData();
    ......

    EventListenerVector* legacyListenersVector = 0;
    AtomicString legacyTypeName = legacyType(event);
    if (!legacyTypeName.isEmpty())
        legacyListenersVector = d->eventListenerMap.find(legacyTypeName);

    EventListenerVector* listenersVector = d->eventListenerMap.find(event->type());
    ......

    if (listenersVector) {
        fireEventListeners(event, d, *listenersVector);
    } else if (legacyListenersVector) {
        ......
        fireEventListeners(event, d, *legacyListenersVector);
        ......
    }

    ......
    return !event->defaultPrevented();
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventTarget.cpp中。

       Node类的成员函数fireEventListeners主要根据Touch Event的类型在Target Node注册的DOM Event Handler中找到对应的DOM Event Handler。例如,如果当前发生的是类型为MOVE的Touch Event,那么Node类的成员函数fireEventListeners就会找到注册在Target Node上的类型为TouchMove的DOM Event Handler。

       找到了对应的DOM Event Handler之后,Node类的成员函数fireEventListeners再调用另外一个重载版本的成员函数fireEventListeners将当前发生的Touch Event分发给它们处理,如下所示:

void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventListenerVector& entry)
{
    ......

    size_t i = 0;
    size_t size = entry.size();
    ......

    for ( ; i < size; ++i) {
        RegisteredEventListener& registeredListener = entry[i];
        if (event->eventPhase() == Event::CAPTURING_PHASE && !registeredListener.useCapture)
            continue;
        if (event->eventPhase() == Event::BUBBLING_PHASE && registeredListener.useCapture)
            continue;

        // If stopImmediatePropagation has been called, we just break out immediately, without
        // handling any more events on this target.
        if (event->immediatePropagationStopped())
            break;

        ExecutionContext* context = 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分发给Event Path上的Node处理。从这里我们就可以看到,在Capturing阶段,如果一个Node在注册了一个在Capturing阶段接收Event的DOM Event Handler,那么此时该DOM Event Handler就会获得当前发生的Event。同样,在Bubbling阶段,如果一个Node在注册了一个在Bubbling阶段接收Event的DOM Event Handler,那么此时该DOM Event Handler就会获得当前发生的Event。对于Target Node来说,它注册的DOM Event Handler则会在Target阶段获得当前发生的Event。

       这些DOM Event Handler在注册的时候,会被JavaScript引擎V8封装在一个V8AbstractEventListener对象中。Node类的成员函数fireEventListeners通过调用这些V8AbstractEventListener对象的成员函数handleEvent将当前发生的Event分发给它们所封装的DOM Event Handler处理。这就会进入到JavaScript引擎V8里面去执行了。以后我们分析JavaScript引擎V8时,再回过头来看它对Event的处理流程。

       至此,我们就分析完成Chromium分发输入事件给WebKit,以及WebKit进行处理的过程了。这个过程是在Render进程的Main线程中执行的。回忆前面Chromium网页滑动和捏合手势处理过程分析一文,滑动和捏合手势这两种特殊的输入事件,是在Render进程的Compositor线程中处理的。

       WebKit在处理输入事件的过程中,需要通过Render Layer Tree和Render Object Tree,在DOM Tree中找到输入事件的Target Node(Hit Test)。找到了输入事件的Target Node之后,分为Pre Process、Capturing、Target、Bubbling和Post Process五个阶段对输入事件进行处理。其中,Capturing、Target和Bubbling这三个阶段是将输入事件分发给Target Node的DOM Event Handler处理,也就是我们通过JavaScript注册的Event Handler。DOM Event Handler最终是在JavaScript引擎V8执行的。

       至此,Chromium的网页输入事件处理机制我们就全部分析完成了。重新学习可以参考前面Chromium网页输入事件处理机制简要介绍和学习计划这篇文章。更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值