本文主要讲解Chromium输入事件的处理过程,在Android平台上,当我们在网页上点击一个HTML元素时,系统会产生一个TouchEvent分发给WebView,WebView将该事件传递到Chromium中,Chromium会对该事件进行一系列的处理,最后分发到WebKit中,WebKit则根据输入事件发生的位置在网页中找到对应的HTML元素进行处理。在整个处理过程中,网页的输入事件是在Browser进程中捕捉的,Browser进程捕获输入事件之后,会将事件发送给Render进程处理。接下来,我们就以TouchEvent为例子,详细讲解事件在Chromium和WebKit中的处理流程。
我们从WebView::onTouchEvent开始分析,调用栈比较简单,如下所示:
WebView::onTouchEvent
WebViewChromium::onTouchEvent
AwContents::onTouchEvent
ContentViewCore::onTouchEvent
ContentViewCore::onTouchEventImpl
这是一个比较常见的调用流程,WebView的api基本上都是这样调用到Chromium里面的,所以我们就不拆开分析了,直接看ContentViewCore::onTouchEventImpl的实现:
private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) {
try {
......
final boolean consumed = nativeOnTouchEvent(mNativeContentViewCore, event,
event.getEventTime(), eventAction,
pointerCount, event.getHistorySize(), event.getActionIndex(),
event.getX(), event.getY(),
pointerCount > 1 ? event.getX(1) : 0,
pointerCount > 1 ? event.getY(1) : 0,
event.getPointerId(0), pointerCount > 1 ? event.getPointerId(1) : -1,
event.getTouchMajor(), pointerCount > 1 ? event.getTouchMajor(1) : 0,
event.getTouchMinor(), pointerCount > 1 ? event.getTouchMinor(1) : 0,
event.getOrientation(), pointerCount > 1 ? event.getOrientation(1) : 0,
event.getRawX(), event.getRawY(),
event.getToolType(0),
pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN,
event.getButtonState(),
event.getMetaState(),
isTouchHandleEvent);
......
} finally {
TraceEvent.end("onTouchEvent");
}
}
在ContentViewCore::onTouchEventImpl中,通过JNI调用到native层。
jboolean ContentViewCoreImpl::OnTouchEvent(JNIEnv* env,
jobject obj,
jobject motion_event,
jlong time_ms,
jint android_action,
jint pointer_count,
jint history_size,
jint action_index,
jfloat pos_x_0,
jfloat pos_y_0,
jfloat pos_x_1,
jfloat pos_y_1,
jint pointer_id_0,
jint pointer_id_1,
jfloat touch_major_0,
jfloat touch_major_1,
jfloat touch_minor_0,
jfloat touch_minor_1,
jfloat orientation_0,
jfloat orientation_1,
jfloat raw_pos_x,
jfloat raw_pos_y,
jint android_tool_type_0,
jint android_tool_type_1,
jint android_button_state,
jint android_meta_state,
jboolean is_touch_handle_event) {
RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
......
return is_touch_handle_event ? rwhv->OnTouchHandleEvent(event)
: rwhv->OnTouchEvent(event);
}
is_touch_handle_event是从java层的ContentViewCore::onTouchEvent传下来的,为false,所以调用的是RenderWidgetHostViewAndroid:: OnTouchEvent。
bool RenderWidgetHostViewAndroid::OnTouchEvent(
const ui::MotionEvent& event) {
......
blink::WebTouchEvent web_event =
ui::CreateWebTouchEventFromMotionEvent(event, result.did_generate_scroll);
host_->ForwardTouchEventWithLatencyInfo(web_event, ui::LatencyInfo());
......
return true;
}
在RenderWidgetHostViewAndroid:: OnTouchEvent中,先调用函数CreateWebTouchEventFromMotionEvent将原始的Touch事件封装在一个blink::WebTouchEvent对象中,而host描述的是一个RenderWidgetHostImpl对象,所以接下来执行的是RenderWidgetHostImpl:: ForwardTouchEventWithLatencyInfo。
void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
const blink::WebTouchEvent& touch_event,
const ui::LatencyInfo& ui_latency) {
......
TouchEventWithLatencyInfo touch_with_latency(touch_event, ui_latency);
......
input_router_->SendTouchEvent(touch_with_latency);
}
在RenderWidgetHostImpl:: ForwardTouchEventWithLatencyInfo中,首先将参数touch_event描述的Touch事件封装在一个TouchEventWithLatencyInfo对象中,然后再InputRouterImpl::SendTouchEvent将这个TouchEventWithLatencyInfo对象发送给Render进程
void InputRouterImpl::SendTouchEvent(
const TouchEventWithLatencyInfo& touch_event) {
input_stream_validator_.Validate(touch_event.event);
touch_event_queue_.QueueEvent(touch_event);
}
在InputRouterImpl::SendTouchEvent中,touch_event_queue_描述的是一个TouchEventQueue对象,通过TouchEventQueue:: QueueEvent调用将参数touch_event描述的Touch事件发送给Render进程。
void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
TRACE_EVENT0("input", "TouchEventQueue::QueueEvent");
// If the queueing of |event| was triggered by an ack dispatch, defer
// processing the event until the dispatch has finished.
if (touch_queue_.empty() && !dispatching_touch_ack_) {
// Optimization of the case without touch handlers. Removing this path
// yields identical results, but this avoids unnecessary allocations.
PreFilterResult filter_result = FilterBeforeForwarding(event.event);
if (filter_result != FORWARD_TO_RENDERER) {
client_->OnTouchEventAck(event,
filter_result == ACK_WITH_NO_CONSUMER_EXISTS
? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
: INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
return;
}
// There is no touch event in the queue. Forward it to the renderer
// immediately.
touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
ForwardNextEventToRenderer();
return;
}
// If the last queued touch-event was a touch-move, and the current event is
// also a touch-move, then the events can be coalesced into a single event.
if (touch_queue_.size() > 1) {
CoalescedWebTouchEvent* last_event = touch_queue_.back();
if (last_event->CoalesceEventIfPossible(event))
return;
}
touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
}
在TouchEventQueue::QueueEvent中,touch_queue_描述的是一个Touch事件队列,这个队列用来暂存即将要发送给Render进程的Touch事件。以下两种情况下,需要将Touch事件暂存在队列中:
1. Touch事件队列touch_queue_不为空,也就是说之前的Touch事件还未发送给Render进程;
2. Render进程发送了一个ACK事件给Browser进程,Browser进程正在分发这个ACK事件,只有当这个ACK事件分发完成之后,Browser进程才可以处理下一个Touch事件,这种情况下,成员变量dispatching_touch_ack_的值就为true,表明当前正在分发ACK事件。
总体上来讲,TouchEventQueue::QueueEvent这个函数的作用就是判断当前这个Touch事件是否可以马上发送。
如果能马上发送,则将它保存在touch_queue_中,然后调用ForwardNextEventToRenderer将它从touch_queue_读取出来并且发送给Render进程。
如果不能马上发送,则同样将它保存在touch_queue_中,但是要等上一个发送给Render进程的Touch事件有ACK之后,才能继续将它发送给Render进程。
不管是什么情况,最终都是通过调用ForwardNextEventToRenderer进行处理的,所以接下来看该函数的实现:
void TouchEventQueue::ForwardNextEventToRenderer() {
TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
......
SendTouchEventImmediately(&touch);
}
在TouchEventQueue::ForwardNextEventToRenderer 中,首先从Touch事件队列中取出第一个Touch事件,然后调用SendTouchEventImmediately发送该事件。
void TouchEventQueue::SendTouchEventImmediately(
TouchEventWithLatencyInfo* touch) {
......
base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
client_->SendTouchEventImmediately(*touch);
......
}
在TouchEventQueue::SendTouchEventImmediately中,client_描述的是前面有提到的InputRouterImpl对象,在调用InputRouterImpl:: SendTouchEventImmediately进行下一步处理之前,还将dispatching_touch则为true,表明当前正在发送一个Touch事件,在发送结束后,会恢复为false。
void InputRouterImpl::SendTouchEventImmediately(
const TouchEventWithLatencyInfo& touch_event) {
......
FilterAndSendWebInputEvent(touch_event.event, touch_event.latency);
}
在InputRouterImpl::SendTouchEventImmediately中,主要调用了另一个成员函数FilterAndSendWebInputEvent进行处理:
void InputRouterImpl::FilterAndSendWebInputEvent(
const WebInputEvent& input_event,
const ui::LatencyInfo& latency_info) {
......
OfferToHandlers(input_event, latency_info);
}
在InputRouterImpl::FilterAndSendWebInputEvent中,调用了另一个成员函数OfferToHandlers将参数input_event描述的Touch事件发送给Render进程。
void InputRouterImpl::OfferToHandlers(const WebInputEvent& input_event,
const ui::LatencyInfo& latency_info) {
......
if (OfferToClient(input_event, latency_info))
return;
OfferToRenderer(input_event, latency_info);
.....
}
在InputRouterImpl::OfferToHandlers中,先调用OfferToClient判断是否需要过滤该Touch事件,如果不需要过滤,则调用OfferToRenderer进行发送。
bool InputRouterImpl::OfferToRenderer(const WebInputEvent& input_event,
const ui::LatencyInfo& latency_info) {
if (Send(new InputMsg_HandleInputEvent(routing_id(), &input_event,
latency_info))) {
......
return true;
}
return false;
}
在InputRouterImpl::OfferToRenderer中,将Touch事件input_event封装在一个IPC消息InputMsg_HandleInputEvent中,然后发送给Render进程处理。
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;
}
InputMsg_HandleInputEvent消息最终由RenderWidget::OnMessageReceived接收处理,从该函数中可以看到,该消息会分发给OnHandleInputEvent处理。
void RenderWidget::OnHandleInputEvent(const blink::WebInputEvent* input_event,
const ui::LatencyInfo& latency_info) {
......
bool prevent_default = false;
if (WebInputEvent::isMouseEventType(input_event->type)) {
......
prevent_default = WillHandleMouseEvent(mouse_event);
}
if (WebInputEvent::isKeyboardEventType(input_event->type)) {
context_menu_source_type_ = ui::MENU_SOURCE_KEYBOARD;
#if defined(OS_ANDROID)
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);
context_menu_source_type_ = ui::MENU_SOURCE_TOUCH;
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);
}
......
InputEventAckState ack_result = processed ?
INPUT_EVENT_ACK_STATE_CONSUMED : INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
......
bool rate_limiting_wanted =
input_event->type == WebInputEvent::MouseMove ||
input_event->type == WebInputEvent::MouseWheel;
......
bool no_ack = ignore_ack_for_mouse_move_from_debugger_ &&
input_event->type == WebInputEvent::MouseMove;
if (WebInputEventTraits::WillReceiveAckFromRenderer(*input_event) &&
!no_ack) {
......
scoped_ptr<IPC::Message> response(
new InputHostMsg_HandleInputEvent_ACK(routing_id_, ack));
if (rate_limiting_wanted && frame_pending && !is_hidden_) {
if (pending_input_event_ack_) {
Send(pending_input_event_ack_.release());
}
pending_input_event_ack_ = response.Pass();
} else {
Send(response.release());
}
}
......
}
RenderWidget:: OnHandleInputEvent的处理过程比较复杂,在将Touch事件分发给WebKit之前,会先做以下判断:
1、 检查input_event是否一个鼠标事件,如果是的话,会调用WillHandleMouseEvent确认是否要对该事件进行处理;
2、 如果是Android平台,检查input_event是否一个DPAD_CENTER键盘事件,如果是的话,并且当前焦点是一个TEXT_INPUT_TYPE_NONE输入控件,那么就调用OnShowImeIfNeeded弹窗输入法;
3、 检查参数input_event是否是一个手势操作,如果是的话,则调用WillHandleGestureEvent确认是否要对该事件进行处理。
如果查看RenderWidget 的成员函数WillHandleMouseEvent和WillHandleGestureEvent就可以知道,这两个函数其实都是直接返回false,则说明RenderWidget不会处理鼠标和手势操作两种输入事件。
我们这里分析的TouchEvent,所以经过上面三个判断之后,prevent_default还是为false,所以接下来会调用webwidget_->handleInputEvent(*input_event)将该事件分发给WebKit处理。
另一方面,在将事件分发给WebKit之后,还要考虑是否需要给Browser进程发送一个ACK,前面我们有提到Browser进程收到Render进程对上一个输入事件的ACK之后,才会给它分发下一个输入事件。因此,Render进程是否给Browser进程发送ACK,可以用来控制Browser进程分发输入事件给Render进程的节奏。
从代码中可以看到,如果是MouseMove或MouseWheel事件,rate_limiting_wanted为true,说明需要控制分发节奏,不会马上进行ACK,因为这类事件可能会大量连续输入,如果每次Render进程都对他们作出响应,那么Render进程负担会很重。
如果是本例中的TouchEvent,则会直接走到Send(response.release()),response为InputHostMsg_HandleInputEvent_ACK,也就是会马上进行ACK。
接下来继续看WebKit里面的处理:WebViewImpl::handleInputEvent
bool WebViewImpl::handleInputEvent(const WebInputEvent& inputEvent)
{
......
// FIXME: This should take in the intended frame, not the local frame root.
if (PageWidgetDelegate::handleInputEvent(*this, inputEvent, mainFrameImpl()->frame()))
return true;
return false;
}
在WebViewImpl::handleInputEvent中,对于Touch事件,会调用PageWidgetDelegate的静态成员函数handleInputEvent进行处理。
bool PageWidgetDelegate::handleInputEvent(PageWidgetEventHandler& handler, const WebInputEvent& event, LocalFrame* root)
{
switch (event.type) {
......
case WebInputEvent::TouchStart:
case WebInputEvent::TouchMove:
case WebInputEvent::TouchEnd:
case WebInputEvent::TouchCancel:
if (!root || !root->view())
return false;
return handler.handleTouchEvent(*root, static_cast<const WebTouchEvent&>(event));
......
default:
return false;
}
}
PageWidgetDelegate::handleInputEvent会对事件类型进行判断,如果是Touch类型的则继续调用handler.handleTouchEvent,handler是一个WebViewImpl对象,由上一次调用传进来的,WebViewImpl继承了PageWidgetEventHandler,handleTouchEvent函数也是直接从父类继承下来的。
bool PageWidgetEventHandler::handleTouchEvent(LocalFrame& mainFrame, const WebTouchEvent& event)
{
return mainFrame.eventHandler().handleTouchEvent(PlatformTouchEventBuilder(mainFrame.view(), event));
}
mainFrame是一个LocalFrame对象,该对象是用来在WebKit层描述在当前进程中加载的网页,这里首先调用了LocalFrame的成员函数eventHandler得到一个EventHandler对象,接着调用了EventHandler:: handleTouchEvent
进行处理。
bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
{
const Vector<PlatformTouchPoint>& points = event.touchPoints();
......
// First do hit tests for any new touch points.
for (unsigned 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()->rootFrameToContents(point.pos()));
HitTestResult result;
if (!m_touchSequenceDocument) {
result = hitTestResultAtPoint(pagePoint, hitType);
} else if (m_touchSequenceDocument->frame()) {
LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->rootFrameToContents(point.pos()));
result = hitTestResultInFrame(m_touchSequenceDocument->frame(), framePoint, hitType);
} else {
continue;
}
Node* node = result.innerNode();
......
if (!m_touchSequenceDocument) {
m_touchSequenceDocument = &(result.innerNode()->document());
}
m_targetForTouchID.set(point.id(), node);
TouchAction effectiveTouchAction = TouchActionUtil::computeEffectiveTouchAction(*node);
if (effectiveTouchAction != TouchActionAuto)
m_frame->page()->chromeClient().setTouchAction(effectiveTouchAction);
}
}
......
bool swallowedTouchEvent = dispatchTouchEvents(event, touchInfos, freshTouchEvents,
allTouchReleased);
......
return swallowedTouchEvent;
}
EventHandler:: handleTouchEvent会对当前发生的Touch事件的每一个Touch Point进行Hit Test,分别找到它们的Target Node,然后再将Touch事件分发给Target Node处理。
这段代码首先获得参数event描述的Touch事件关联的所有Touch Point,保存在本地变量points描述的一个Vector中。每一个Touch Point都是一个PlatformTouchPoint对象,它有五种状态,正常情况下,一个Touch Point的状态变化过程为:TouchPressed->TouchMoved/TouchStationary->TouchReleased/TouchCancelled。
class PlatformTouchPoint {
public:
enum State {
TouchReleased,
TouchPressed,
TouchMoved,
TouchStationary,
TouchCancelled,
TouchStateEnd // Placeholder: must remain the last item.
};
......
}
回到上面的EventHandler:: handleTouchEvent中,遍历所有points中状态为TouchPressed的Touch Point,找到它们的Target Node,并将这些Node保存在m_targetForTouchID中,键值为Touch Point对应的ID,有了这个m_targetForTouchID,当一个Touch Point从TouchPressed状态变为其它状态时,就可以轻松地知道它的Target Node,避免做重复的Hit Test。
当一个Touch Event第一次做Hit Test时,m_touchSequenceDocument为Null,调用hitTestResultAtPoint做Hit Test,之后m_touchSequenceDocument不为Null,调用hitTestResultInFrame做Hit Test,后者会将Hit Test的范围限制在m_touchSequenceDocument指定的Document中,Hit Test的处理过程也非常复杂,没有详细研究所以就不展开分析了。
在遍历完points之后,会调用dispatchTouchEvents将Touch事件分发给前面找到的Target node处理。
bool EventHandler::dispatchTouchEvents(const PlatformTouchEvent& event,
WillBeHeapVector<TouchInfo>& touchInfos, bool freshTouchEvents, bool allTouchReleased)
{
.......
for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
if (!changedTouches[state].m_touches)
continue;
const AtomicString& eventName(touchEventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
const EventTargetSet& targetsForState = changedTouches[state].m_targets;
for (const RefPtrWillBeMember<EventTarget>& eventTarget : targetsForState) {
EventTarget* touchEventTarget = eventTarget.get();
RefPtrWillBeRawPtr<TouchEvent> touchEvent = TouchEvent::create(
touches.get(), touchesByTarget.get(touchEventTarget), changedTouches[state].m_touches.get(),
eventName, touchEventTarget->toNode()->document().domWindow(),
event.modifiers(), event.cancelable(), event.causesScrollingIfUncanceled(), event.timestamp());
touchEventTarget->dispatchEvent(touchEvent.get());
swallowedEvent = swallowedEvent || touchEvent->defaultPrevented() || touchEvent->defaultHandled();
}
}
return swallowedEvent;
}
touchEventTarget是一个EventTarget对象,描述是前面找到的Target node,这里通过调用EventTarget:: dispatchEvent进行分发。
bool EventTarget::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)
{
event->setTrusted(true);
return dispatchEventInternal(event);
}
bool EventTarget::dispatchEventInternal(PassRefPtrWillBeRawPtr<Event> event)
{
event->setTarget(this);
event->setCurrentTarget(this);
event->setEventPhase(Event::AT_TARGET);
bool defaultWasNotPrevented = fireEventListeners(event.get());
event->setEventPhase(0);
return defaultWasNotPrevented;
}
EventTarget:: dispatchEvent的调用过程如上所示,主要是通过fireEventListeners将Touch Event分发给Target Node的DOM Event Handler处理,DOM Event Handler指的是网页通过JS注册的Event处理相关的回调函数。
bool EventTarget::fireEventListeners(Event* event)
{
EventTargetData* d = eventTargetData();
if (!d)
return true;
EventListenerVector* legacyListenersVector = nullptr;
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) {
AtomicString unprefixedTypeName = event->type();
event->setType(legacyTypeName);
fireEventListeners(event, d, *legacyListenersVector);
event->setType(unprefixedTypeName);
}
Editor::countEvent(executionContext(), event);
countLegacyEvents(legacyTypeName, listenersVector, legacyListenersVector);
return !event->defaultPrevented();
}
在该函数中,通过Touch Event的类型在Target Node注册的DOM Event Handler(eventListenerMap)中找到对应的DOM Event Handler,然后再调用fireEventListeners的另一个重载函数进行处理。
void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventListenerVector& entry)
{
......
size_t i = 0;
size_t size = entry.size();
if (!d->firingEventIterators)
d->firingEventIterators = adoptPtr(new FiringEventIteratorVector);
d->firingEventIterators->append(FiringEventIterator(event->type(), i, size));
while (i < size) {
RegisteredEventListener& registeredListener = entry[i];
++i;
if (event->eventPhase() == Event::CAPTURING_PHASE && !registeredListener.useCapture)
continue;
if (event->eventPhase() == Event::BUBBLING_PHASE && registeredListener.useCapture)
continue;
if (event->immediatePropagationStopped())
break;
ExecutionContext* context = executionContext();
if (!context)
break;
......
registeredListener.listener->handleEvent(context, event);
InspectorInstrumentation::didHandleEvent(cookie);
}
d->firingEventIterators->removeLast();
}
这些DOM Event Handler在注册的时候,会被JavaScript引擎封装在一个V8AbstractEventListener对象中。这里的registeredListener.listener 得到的就是一个V8AbstractEventListener对象,通过调用改对象的成员函数handleEvent将当前发生的Event分发给它们所封装的DOM Event Handler处理。这就会进入到JavaScript引擎里面去执行了。
整个Touch Event的分析过程就到此为了,其中有部分细节其实也还没有搞懂,所以也没法讲得很详细,只能大概把流程整理出来,后续有时间再仔细研究了。