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

Android社区 收藏文章

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

以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流程。

  1. 对当前正在处理的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

相关标签

扫一扫

在手机上阅读