Chromium网页输入事件捕捉和手势检测过程分析

发表于 5年以前  | 总阅读数:2434 次

连续的输入事件可能会产生一定的手势操作,例如滑动手势和捏合手势。在Chromium中,网页的输入事件是在Browser进程中捕捉的。Browser进程捕获输入事件之后,会进行手势操作检测。检测出来的手势操作将会发送给Render进程处理,因为它们需要应用在网页之上。与此同时,Browser进程也会将原始的输入事件发送给Render进程处理。本文接下来就分析Browser进程处理网页输入事件的过程。

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

接下来我们将以Chromium自带的Content Shell APK为例,说明Chromium的Browser进程捕获网页输入事件以及检测手势操作的过程,如图1所示:

图1 Browser进程处理网页输入事件的过程

从前面Chromium网页输入事件处理机制简要介绍和学习计划一文可以知道,Content Shell APK将网页渲染在一个SurfaceView控件上。这个SurfaceView又是嵌入在一个ContentView控件里面的。当用户在网页上触发了一个输入事件时,例如触发一个Touch事件时,这个Touch事件就会被系统分发给上述ContentView控件处理,表现为该ContentView控件的成员函数onTouchEvent被调用。

ContentView控件得到Touch事件之后,会将它传递到Chromium的C++层去处理。Java层的每一个ContentView控件在C++层都对应一个ContentViewCore对象。C++层的ContentViewCore对象得到Touch事件之后,就会通过一个Gesture Dector和一个Scale Gesture Detector进行滑动(Scroll)和捏合(Pinch)手势检测。检测出来的滑动和捏合手势将会统一保存在一个Gestrue Packet中。这个Gestrue Packet接下来会被一个Input Router封装在一个类型为InputMsg_HandleInputEvent的IPC消息中,发送给Render进程处理。

注意,Touch事件经过手势检测之后,它本身也会被上述Input Router通过另外一个InputMsg_HandleInputEvent消息发送给Render进程处理。这意味着在这种情况下,Render进程将收到两个InputMsg_HandleInputEvent消息。

接下来,我们就从ContentView类的成员函数onTouchEvent开始,分析Browser进程处理网页输入事件的过程,如下所示:

public class ContentView extends FrameLayout
            implements ContentViewCore.InternalAccessDelegate, SmartClipProvider {
        ......

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return mContentViewCore.onTouchEvent(event);
        }

        ......
    }

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentView.java中。

参数event指向的MotionEvent对象描述的就是当前发生的Touch事件。ContentView类的成员变量mContentViewCore指向的是一个ContentViewCore对象,ContentView类的成员函数onTouchEvent调用这个ContentViewCore对象的成员函数onTouchEvent处理参数event所描述的Touch事件。

ContentViewCore类的成员函数onTouchEvent的实现如下所示:

public class ContentViewCore
            implements NavigationClient, AccessibilityStateChangeListener, ScreenOrientationObserver {
        ......

        public boolean onTouchEvent(MotionEvent event) {
            TraceEvent.begin("onTouchEvent");
            try {
                ......

                final int pointerCount = event.getPointerCount();
                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.getRawX(), event.getRawY(),
                        event.getToolType(0),
                        pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN,
                        event.getButtonState());

                ......
                return consumed;
            } finally {
                TraceEvent.end("onTouchEvent");
            }
        }

        ......
    }

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java中。

ContentViewCore类的成员函数onTouchEvent主要是调用另外一个成员函数nativeOnTouchEvent处理参数event描述的Touch事件。

ContentViewCore类的成员函数nativeOnTouchEvent是一个JNI函数,它由C++层的函数Java_com_android_org_chromium_content_browser_ContentViewCore_nativeOnTouchEvent实现,如下所示:

__attribute__((visibility("default")))
    jboolean
        Java_com_android_org_chromium_content_browser_ContentViewCore_nativeOnTouchEvent(JNIEnv*
        env,
        jobject jcaller,
        jlong nativeContentViewCoreImpl,
        jobject event,
        jlong timeMs,
        jint action,
        jint pointerCount,
        jint historySize,
        jint actionIndex,
        jfloat x0,
        jfloat y0,
        jfloat x1,
        jfloat y1,
        jint pointerId0,
        jint pointerId1,
        jfloat touchMajor0,
        jfloat touchMajor1,
        jfloat rawX,
        jfloat rawY,
        jint androidToolType0,
        jint androidToolType1,
        jint androidButtonState) {
      ContentViewCoreImpl* native =
          reinterpret_cast<ContentViewCoreImpl*>(nativeContentViewCoreImpl);
      CHECK_NATIVE_PTR(env, jcaller, native, "OnTouchEvent", false);
      return native->OnTouchEvent(env, jcaller, event, timeMs, action, pointerCount,
          historySize, actionIndex, x0, y0, x1, y1, pointerId0, pointerId1,
          touchMajor0, touchMajor1, rawX, rawY, androidToolType0, androidToolType1,
          androidButtonState);
    }

这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentViewCore_jni.h中。

参数nativeContentViewCoreImpl描述的是C++层的一个ContentViewCoreImpl对象,函数Java_com_android_org_chromium_content_browser_ContentViewCore_nativeOnTouchEvent调用这个ContentViewCoreImpl对象的成员函数OnTouchEvent处理其它参数所描述的Touch事件。

ContentViewCoreImpl类的成员函数OnTouchEvent的实现如下所示:

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 raw_pos_x,
                                               jfloat raw_pos_y,
                                               jint android_tool_type_0,
                                               jint android_tool_type_1,
                                               jint android_button_state) {
      RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
      ......

      MotionEventAndroid event(1.f / dpi_scale(),
                               env,
                               motion_event,
                               time_ms,
                               android_action,
                               pointer_count,
                               history_size,
                               action_index,
                               pos_x_0,
                               pos_y_0,
                               pos_x_1,
                               pos_y_1,
                               pointer_id_0,
                               pointer_id_1,
                               touch_major_0,
                               touch_major_1,
                               raw_pos_x,
                               raw_pos_y,
                               android_tool_type_0,
                               android_tool_type_1,
                               android_button_state);

      return rwhv->OnTouchEvent(event);
    }

这个函数定义在文件external/chromium_org/content/browser/android/content_view_core_impl.cc中。

ContentViewCoreImpl类的成员函数OnTouchEvent首先调用成员函数GetRenderWidgetHostViewAndroid获得一个RenderWidgetHostViewAndroid对象。这个RenderWidgetHostViewAndroid对象用来在C++层描述加载网页的控件,它的创建过程可以参考前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文。

ContentViewCoreImpl类的成员函数OnTouchEvent接下来又将参数描述的Touch事件封装在一个MotionEventAndroid对象中,然后将该MotionEventAndroid对象传递给前面获得的RenderWidgetHostViewAndroid对象的成员函数OnTouchEvent处理。

RenderWidgetHostViewAndroid对象的成员函数OnTouchEvent的实现如下所示:

bool RenderWidgetHostViewAndroid::OnTouchEvent(
        const ui::MotionEvent& event) {
      ......

      if (!gesture_provider_.OnTouchEvent(event))
        return false;

      ......

      // Short-circuit touch forwarding if no touch handlers exist.
      if (!host_->ShouldForwardTouchEvent()) {
        const bool event_consumed = false;
        gesture_provider_.OnTouchEventAck(event_consumed);
        return true;
      }

      SendTouchEvent(CreateWebTouchEventFromMotionEvent(event));
      return true;
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。

RenderWidgetHostViewAndroid类的成员变量gesture_provider_描述的是一个FilteredGestureProvider对象。RenderWidgetHostViewAndroid类的成员函数OnTouchEvent首先调用这个FilteredGestureProvider对象的成员函数OnTouchEvent检测参数event描述的Touch事件是否产生了手势操作。如果有发生,那么就会将它们发送给Render进程处理。

RenderWidgetHostViewAndroid类的成员变量host_指向的是一个RenderWidgetHostImpl对象。这个RenderWidgetHostImpl对象也是用来在C++层描述加载网页的控件的,它的创建过程可以参考前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文。RenderWidgetHostViewAndroid类的成员函数OnTouchEvent接下来调用这个RenderWidgetHostImpl对象的成员函数ShouldForwardTouchEvent检查Render进程是否注册了处理Touch事件的Handler。如果没有注册的话,那么就不需要将参数event描述的Touch事件发送给它处理了。

我们假设Render进程注册了处理Touch事件的Handler。在这种情况下,RenderWidgetHostViewAndroid类的成员函数OnTouchEvent就会调用函数CreateWebTouchEventFromMotionEvent将参数event描述的Touch事件封装成一个blink::WebTouchEvent对象,并且调用另外一个成员函数SendTouchEvent将该blink::WebTouchEvent对象发送给Render进程处理。注意,这个blink::WebTouchEvent对象描述的是原始的Touch事件,它不是一个手势操作。

接下来,我们先分析FilteredGestureProvider类的成员函数OnTouchEvent检测手势操作的过程,接着再分析函数CreateWebTouchEventFromMotionEvent创建blink::WebTouchEvent对象的过程,以及RenderWidgetHostViewAndroid类的成员函数SendTouchEvent向Render进程发送Touch事件的过程。

FilteredGestureProvider类的成员函数OnTouchEvent的实现如下所示:

bool FilteredGestureProvider::OnTouchEvent(const MotionEvent& event) {
      DCHECK(!handling_event_);
      base::AutoReset<bool> handling_event(&handling_event_, true);

      pending_gesture_packet_ = GestureEventDataPacket::FromTouch(event);

      if (!gesture_provider_.OnTouchEvent(event))
        return false;

      TouchDispositionGestureFilter::PacketResult result =
          gesture_filter_.OnGesturePacket(pending_gesture_packet_);
      if (result != TouchDispositionGestureFilter::SUCCESS) {
        NOTREACHED() << "Invalid touch gesture sequence detected.";
        return false;
      }

      return true;
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/filtered_gesture_provider.cc中。

FilteredGestureProvider类的成员函数OnTouchEvent首先将成员变量handling_event_的值设置为true,表示当前正处于收集手势操作的过程中,不要将正在收集的手势操作发送给Render进程处理,而是等到全部收集完毕再一起发送给Render进程处理。注意,当FilteredGestureProvider类的成员函数OnTouchEvent的调用结束后,FilteredGestureProvider类的成员变量handling_event的值将自动恢复为false。

FilteredGestureProvider类的成员函数OnTouchEvent接下来调用GestureEventDataPacket类的静态成员函数FromTouch创建一个用来保存手势操作的Gesture Event Data Packet,如下所示:

GestureEventDataPacket GestureEventDataPacket::FromTouch(
        const ui::MotionEvent& touch) {
      return GestureEventDataPacket(touch.GetEventTime(),
                                    ToGestureSource(touch),
                                    gfx::PointF(touch.GetX(), touch.GetY()),
                                    gfx::PointF(touch.GetRawX(), touch.GetRawY()));
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_event_data_packet.cc中。

GestureEventDataPacket类的静态成员函数FromTouch首先调用函数ToGestureSource获得接下来要创建的Gesture Event Data Packet的类型,接着创建一个该类型的Gesture Event Data Packet返回给调用者。

函数ToGestureSource的实现如下所示:

GestureEventDataPacket::GestureSource ToGestureSource(
        const ui::MotionEvent& event) {
      switch (event.GetAction()) {
        case ui::MotionEvent::ACTION_DOWN:
          return GestureEventDataPacket::TOUCH_SEQUENCE_START;
        case ui::MotionEvent::ACTION_UP:
          return GestureEventDataPacket::TOUCH_SEQUENCE_END;
        case ui::MotionEvent::ACTION_MOVE:
          return GestureEventDataPacket::TOUCH_MOVE;
        case ui::MotionEvent::ACTION_CANCEL:
          return GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL;
        case ui::MotionEvent::ACTION_POINTER_DOWN:
          return GestureEventDataPacket::TOUCH_START;
        case ui::MotionEvent::ACTION_POINTER_UP:
          return GestureEventDataPacket::TOUCH_END;
      };
      NOTREACHED() << "Invalid ui::MotionEvent action: " << event.GetAction();
      return GestureEventDataPacket::INVALID;
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_event_data_packet.cc中。

函数ToGestureSource返回的Gesture Event Data Packet的类型与参数evnet描述的Touch事件的类型有关。例如,假设event描述的是一个ACTION_MOVE类型的Touch事件,那么函数ToGestureSource返回的Gesture Event Data Packet的类型就为GestureEventDataPacket::TOUCH_MOVE。

在接下来的分析中,我们就假设当前要处理的是一个ACTION_MOVE类型的Touch事件,这意味着FilteredGestureProvider类的成员函数OnTouchEvent调用GestureEventDataPacket类的静态成员函数FromTouch获得的是一个类型为GestureEventDataPacket::TOUCH_MOVE的Gesture Event Data Packet。这个Gesture Event Data Packet保存在FilteredGestureProvider类的成员变量pending_gesture_packet_中。

回到FilteredGestureProvider类的成员函数OnTouchEvent中,它接下来调用成员变量gesture_provider_描述的一个GestureProvider对象的成员函数OnTouchEvent检查参数event描述的Touch事件是否产生了手势操作。如果产生了,那么就会将它们保存在成员变量pending_gesture_packet_描述的Gesture Event Data Packet中。

FilteredGestureProvider类的成员变量gesture_filter_描述的是一个TouchDispositionGestureFilter对象,FilteredGestureProvider类的成员函数OnTouchEvent最后调用这个TouchDispositionGestureFilter对象的成员函数OnGesturePacket将成员变量pending_gesture_packet_描述的Gesture Event Data Packet发送给Render进程处理,也就是将前面检测到的手势操作发送给Render进程处理。

接下来,我们先分析GestureProvider对象的成员函数OnTouchEvent检测手势操作的过程,接下来再分析TouchDispositionGestureFilter类的成员函数OnGesturePacket发送手势操作给Render进程的过程。

GestureProvider类的成员函数OnTouchEvent的实现如下所示:

bool GestureProvider::OnTouchEvent(const MotionEvent& event) {
      ......

      gesture_listener_->OnTouchEvent(event, in_scale_gesture);
      scale_gesture_listener_->OnTouchEvent(event);

      ......

      return true;
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

GestureProvider类的成员变量gesture_listener_指向的是一个GestureListenerImpl对象。这个GestureListenerImpl对象负责检测参数event描述的Touch事件是否产生滑动手势操作。这是通过调用它的成员函数OnTouchEvent实现的。

GestureProvider类的成员变量scale_gesture_listener_指向的是一个ScaleGestureListenerImpl对象。这个ScaleGestureListenerImpl对象负责检测参数event描述的Touch事件是否产生捏合手势操作。这是通过调用它的成员函数OnTouchEvent实现的。

接下来,我们就分别分析GestureListenerImpl类和ScaleGestureListenerImpl类的成员函数OnTouchEvent的实现,以便了解滑动和捏合手势操作的检测过程。

GestureListenerImpl类的成员函数OnTouchEvent的实现如下所示:

class GestureProvider::GestureListenerImpl
        : public GestureDetector::GestureListener,
          public GestureDetector::DoubleTapListener {
     public:
      ......

      bool OnTouchEvent(const MotionEvent& e,
                        bool is_scale_gesture_detection_in_progress) {
        ......

        return gesture_detector_.OnTouchEvent(e);
      }

     private:
      ......

      GestureDetector gesture_detector_;

      ......
    };

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

GestureListenerImpl类的成员函数OnTouchEvent通过调用成员变量gesture_detector_描述的一个GestureDetector对象的成员函数OnTouchEvent检测测参数e描述的Touch事件是否产生了滑动手势操作。

GestureDetector类的成员函数OnTouchEvent的实现如下所示:

bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
      const MotionEvent::Action action = ev.GetAction();

      ......

      const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
      const int skip_index = pointer_up ? ev.GetActionIndex() : -1;

      // Determine focal point.
      float sum_x = 0, sum_y = 0;
      const int count = static_cast<int>(ev.GetPointerCount());
      for (int i = 0; i < count; i++) {
        if (skip_index == i)
          continue;
        sum_x += ev.GetX(i);
        sum_y += ev.GetY(i);
      }
      const int div = pointer_up ? count - 1 : count;
      const float focus_x = sum_x / div;
      const float focus_y = sum_y / div;

      bool handled = false;

      switch (action) {
        ......

        case MotionEvent::ACTION_MOVE:
          {
            const float scroll_x = last_focus_x_ - focus_x;
            const float scroll_y = last_focus_y_ - focus_y;
            if (is_double_tapping_) {
              // Give the move events of the double-tap.
              DCHECK(double_tap_listener_);
              handled |= double_tap_listener_->OnDoubleTapEvent(ev);
            } else if (always_in_tap_region_) {
              const float delta_x = focus_x - down_focus_x_;
              const float delta_y = focus_y - down_focus_y_;
              const float distance_square = delta_x * delta_x + delta_y * delta_y;
              if (distance_square > touch_slop_square_) {
                handled = listener_->OnScroll(
                    *current_down_event_, ev, scroll_x, scroll_y);
                last_focus_x_ = focus_x;
                last_focus_y_ = focus_y;
                always_in_tap_region_ = false;
                ......
              }
              ......
            } else if (std::abs(scroll_x) > kScrollEpsilon ||
                       std::abs(scroll_y) > kScrollEpsilon) {
              handled =
                  listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
              last_focus_x_ = focus_x;
              last_focus_y_ = focus_y;
            }

          ......

          break;

        ......
      }

      return handled;
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_detector.cc中。

前面我们假设参数ev描述的是一个ACTION_MOVE类型的Touch事件。GestureDetector类的成员函数OnTouchEvent首先会计算这个Touch事件的位置(focus_x, focus_y)。注意,这个Touch事件可能包含了多个触摸点,因此在计算它的位置时,通过将所有的触摸点进行算术平均得到。

GestureDetector类的成员变量last_focus_x_和last_focus_y_记录的是上一个类型为ACTION_MOVE的Touch事件的位置(last_focusx, last_focusy)。GestureDetector类的成员函数OnTouchEvent通过比较(last_focusx, last_focusy)和(focus_x, focus_y)的值,得到连续两个类型为ACTION_MOVE的Touch事件在网页的X轴和Y轴上所产生的滑动量scroll_x和scroll_y。

GestureDetector类的成员变量is_double_tapping_是一个布尔变量。当它的值等于true的时候,表示用户在规定的时间和空间内连续点击了两次网页。这种情况称为Double Tap,这时候GestureDetector类的成员函数OnTouchEvent会将参数ev描述的类型为ACTION_MOVE的Touch事件交给成员变量double_tap_listener_指向的一个DoubleTapListener对象的成员函数OnDoubleTapEvent处理。也就是说,Double Tap之后的类型为ACTION_MOVE的Touch事件将不会产生滑动手势操作。

GestureDetector类的成员变量always_in_tap_region_也是一个布尔变量。当它的值等于true的时候,表示用户之前触发了一个类型为ACTION_DOWN的Touch事件。在这种情况下,GestureDetector类的成员函数OnTouchEvent需要计算当前发生的类型为ACTION_MOVE的Touch事件与之前触发的类型为ACTION_DOWN的Touch事件的位置距离。当这个距离大于预设的值之时,GestureDetector类的成员变量always_in_tap_region_会被重置为false,表示后面触发类型为ACTION_UP的Touch事件时,不要产生一个Single Tap事件。与此同时,需要产生一个滑动手势操作。这个滑动手势操作通过调用GestureDetector类的成员变量listener_描述的一个GestureListener对象的成员函数OnScroll进行处理。

从前面的分析我们可以看出,Single Tap事件与滑动手势操作是互斥的。一个Single Tap事件指的是指在规定时间和空间内先后发生了一个类型为ACTION_DOWN的Touch事件和一个类型为ACTION_UP的Touch事件。在这两个Touch事件之间发生的类型为ACTION_MOVE的Touch事件将不会产生手势操作。

当GestureDetector类的成员变量is_double_tapping_和always_in_tapregion 的值都等于false的时候,GestureDetector类的成员函数OnTouchEvent检查连续两个类型为ACTION_MOVE的Touch事件在网页的X轴和Y轴上所产生的滑动量scroll_x和scroll_y是否超过了预设的阀值。如果超过了,那么就认为产生了一个滑动手势操作。这个滑动手势操作也是通过调用GestureDetector类的成员变量listener_描述的一个GestureListener对象的成员函数OnScroll进行处理。

接下来我们主要关注滑动手势操作的处理过程。GestureDetector类的成员变量listener_指向的实际上是一个GestureListenerImpl对象。这个GestureListenerImpl对象就是前面提到的GestureProvider类的成员变量gesture_listener_所指向的GestureListenerImpl对象。这意味着GestureDetector类的成员函数OnTouchEvent检测到的滑动手势操作将由这个GestureListenerImpl对象的成员函数OnScroll进行处理。

GestureListenerImpl类的成员函数OnScroll的实现如下所示:

class GestureProvider::GestureListenerImpl
        : public GestureDetector::GestureListener,
          public GestureDetector::DoubleTapListener {
     public:
      ......

      virtual bool OnScroll(const MotionEvent& e1,
                            const MotionEvent& e2,
                            float raw_distance_x,
                            float raw_distance_y) OVERRIDE {
        float distance_x = raw_distance_x;
        float distance_y = raw_distance_y;
        ......

        if (!provider_->IsScrollInProgress()) {
          // Note that scroll start hints are in distance traveled, where
          // scroll deltas are in the opposite direction.
          GestureEventDetails scroll_details(
              ET_GESTURE_SCROLL_BEGIN, -raw_distance_x, -raw_distance_y);

          // Use the co-ordinates from the touch down, as these co-ordinates are
          // used to determine which layer the scroll should affect.
          provider_->Send(CreateGesture(scroll_details,
                                        e2.GetId(),
                                        e2.GetEventTime(),
                                        e1.GetX(),
                                        e1.GetY(),
                                        e1.GetRawX(),
                                        e1.GetRawY(),
                                        e2.GetPointerCount(),
                                        GetBoundingBox(e2)));
        }


        if (distance_x || distance_y) {
          const gfx::RectF bounding_box = GetBoundingBox(e2);
          const gfx::PointF center = bounding_box.CenterPoint();
          const gfx::PointF raw_center =
              center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY());
          GestureEventDetails scroll_details(
              ET_GESTURE_SCROLL_UPDATE, -distance_x, -distance_y);
          provider_->Send(CreateGesture(scroll_details,
                                        e2.GetId(),
                                        e2.GetEventTime(),
                                        center.x(),
                                        center.y(),
                                        raw_center.x(),
                                        raw_center.y(),
                                        e2.GetPointerCount(),
                                        bounding_box));
        }

        return true;
      }

      ......
    };

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

GestureListenerImpl类的成员函数OnScroll首先调用成员变量provider_指向的一个GestureProvider对象的成员函数IsScrollInProgress判断网页当前是否正在滑动过程中。如果不是的话,那么就说明现在要开始对网页进行滑动。这时候Browser进程会先发送一个类型为ET_GESTURE_SCROLL_BEGIN的手势操作给Render进程。

在网页有滑动的情况下,也就是网页至少在X轴和Y轴之一有偏移时,GestureListenerImpl类的成员函数OnScroll接下来还会向Render进程发送一个类型为ET_GESTURE_SCROLL_UPDATE的手势操作。

不管是类型为ET_GESTURE_SCROLL_BEGIN的手势操作,还是类型为ET_GESTURE_SCROLL_UPDATE的手势操作,它们都会通过函数CreateGesture封装为一个GestureEventData对象。这两个GestureEventData对象都是通过调用GestureListenerImpl类的成员变量provider_指向的GestureProvider对象的成员函数Send发送给Render进程的,如下所示:

void GestureProvider::Send(GestureEventData gesture) {
      ......

      client_->OnGestureEvent(gesture);
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

GestureProvider类的成员变量client_指向的是一个FilteredGestureProvider对象。这个FilteredGestureProvider对象就是前面分析的RenderWidgetHostViewAndroid类的成员变量gesture_provider_所指向的FilteredGestureProvider对象。

GestureProvider类的成员函数Send主要是调用上述FilteredGestureProvider对象的成员函数OnGestureEvent将参数gesture描述的手势操作发送给Render进程处理,如下所示:

void FilteredGestureProvider::OnGestureEvent(const GestureEventData& event) {
      if (handling_event_) {
        pending_gesture_packet_.Push(event);
        return;
      }

      gesture_filter_.OnGesturePacket(
          GestureEventDataPacket::FromTouchTimeout(event));
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/filtered_gesture_provider.cc中。

从前面的分析可以知道,当前正在处理的FilteredGestureProvider对象的成员变量handling_event_已经被设置为true,表示此时仅仅收集参数event描述的手势操作,而不要将它发送给Render进程。参数event描述的手势操作被收集在FilteredGestureProvider类的成员变量pending_gesture_packet_描述的一个Gesture Event Data Packet中。从前面的分析可以知道,这个Gesture Event Data Packet的类型为GestureEventDataPacket::TOUCH_MOVE。

如果当前正在处理的FilteredGestureProvider对象的成员变量handling_event_的值不等于true,那么FilteredGestureProvider类的成员函数OnGestureEvent将会直接将参数event描述的手势操作发送给Render进程,这是通过调用另外一个成员变量gesture_filter_描述的一个TouchDispositionGestureFilter对象的成员函数OnGesturePacket实现的。后面我们再分析这个发送过程。

这一步执行完成后,Browser进程就对当前发生的Touch事件进行了滑动手势检测,并且检测到的滑动手势操作已经保存在一个Gesture Event Data Packet中。回到前面分析的GestureProvider类的成员函数OnTouchEvent中,接下来它会继续调用另外一个成员变量scale_gesture_listener_指向的是ScaleGestureListenerImpl对象的成员函数OnTouchEvent检测当前发生的Touch事件是否产生了捏合手势操作。如果产生了,那么同样将它收集在上述的Gesture Event Data Packet中。

接下来我们就继续分析捏合手势操作的检测过程,也就是ScaleGestureListenerImpl类的成员函数OnTouchEvent的实现,如下所示:

class GestureProvider::ScaleGestureListenerImpl
        : public ScaleGestureDetector::ScaleGestureListener {
     public:
      ......

      bool OnTouchEvent(const MotionEvent& event) {
        ......
        bool handled = scale_gesture_detector_.OnTouchEvent(event);
        ......
        return handled;
      }

      ......

     private:
      ......

      ScaleGestureDetector scale_gesture_detector_;

      ......
    };

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

ScaleGestureListenerImpl类的成员函数OnTouchEvent主要是调用成员变量scale_gesture_detector_描述的一个ScaleGestureDetector对象的成员函数OnTouchEvent检测参数event描述的Touch事件是否产生捏合手势操作。

ScaleGestureDetector类的成员函数OnTouchEvent的实现如下所示:

bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
      ......

      const int action = event.GetAction();

      ......

      // Span is the average distance between touch points through the focal point;
      // i.e. the diameter of the circle with a radius of the average deviation from
      // the focal point.
      const float span_x = dev_x * 2;
      const float span_y = dev_y * 2;
      float span;
      if (InDoubleTapMode()) {
        span = span_y;
      } else {
        span = std::sqrt(span_x * span_x + span_y * span_y);
      }
      ......

      const float min_span = InDoubleTapMode() ? span_slop_ : min_span_;
      if (!in_progress_ && span >= min_span && (InDoubleTapMode() || count > 1) &&
          (was_in_progress || std::abs(span - initial_span_) > span_slop_)) {
        ......
        in_progress_ = listener_->OnScaleBegin(*this, event);
      }

      // Handle motion; focal point and span/scale factor are changing.
      if (action == MotionEvent::ACTION_MOVE) {
        ......

        if (in_progress_) {
          update_prev = listener_->OnScale(*this, event);
        }

        ......
      }

      return true;
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/scale_gesture_detector.cc中。

ScaleGestureDetector类的成员函数OnTouchEvent首先计算网页被捏合的大小span。这是根据网页在X轴和Y轴上的捏合大小span_x和span_y计算得到的。

ScaleGestureDetector类的成员函数OnTouchEvent接下来判断网页被捏合的大小span是否大于等于预设的阀值。如果大于等于,并且网页是刚开始被捏合,那么就会调用成员变量listener_指向的一个ScaleGestureListenerImpl对象的成员函数OnScaleBegin,用来询问是否允许产生一个捏合手势操作。如果允许的话,ScaleGestureDetector类的成员变量in_progress_就会被设置为true。

上述ScaleGestureListenerImpl对象就是前面分析的GestureProvider类的成员变量scale_gesture_listener_所指向的ScaleGestureListenerImpl对象,它的成员函数OnScaleBegin的实现如下所示:

class GestureProvider::ScaleGestureListenerImpl
        : public ScaleGestureDetector::ScaleGestureListener {
     public:
      ......

      virtual bool OnScaleBegin(const ScaleGestureDetector& detector,
                                const MotionEvent& e) OVERRIDE {
        if (ignore_multitouch_events_ && !detector.InDoubleTapMode())
          return false;
        ......
        return true;
      }

      ......
    };

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

ScaleGestureListenerImpl类的成员函数OnScaleBegin在两种情况下的返回值为true。第一种情况是当它的成员变量ignore_multitouch_events_的值等于false时。这表示当网页被多点触摸时,有可能需要对网页进行缩放,也就是要产生一个捏合手势操作。第二种情况是网页被Double Tap时。

回到ScaleGestureDetector类的成员函数OnTouchEvent中。综合起来,我们就可以知道,当网页被多点触摸移动或者Double Tap后移动,并且移动的距离或者两次Tap的距离大于等于预设值时,那么就会产生捏合手势操作。这个捏合手势操作将会交给ScaleGestureDetector类的成员变量listener_指向的ScaleGestureListenerImpl对象的成员函数OnScale处理,如下所示:

class GestureProvider::ScaleGestureListenerImpl
        : public ScaleGestureDetector::ScaleGestureListener {
     public:
      ......

      virtual bool OnScale(const ScaleGestureDetector& detector,
                           const MotionEvent& e) OVERRIDE {
        ......

        if (!pinch_event_sent_) {
          pinch_event_sent_ = true;
          provider_->Send(CreateGesture(ET_GESTURE_PINCH_BEGIN,
                                        e.GetId(),
                                        detector.GetEventTime(),
                                        detector.GetFocusX(),
                                        detector.GetFocusY(),
                                        detector.GetFocusX() + e.GetRawOffsetX(),
                                        detector.GetFocusY() + e.GetRawOffsetY(),
                                        e.GetPointerCount(),
                                        GetBoundingBox(e)));
        }

        ......

        float scale = detector.GetScaleFactor();
        ......

        GestureEventDetails pinch_details(ET_GESTURE_PINCH_UPDATE, scale, 0);
        provider_->Send(CreateGesture(pinch_details,
                                      e.GetId(),
                                      detector.GetEventTime(),
                                      detector.GetFocusX(),
                                      detector.GetFocusY(),
                                      detector.GetFocusX() + e.GetRawOffsetX(),
                                      detector.GetFocusY() + e.GetRawOffsetY(),
                                      e.GetPointerCount(),
                                      GetBoundingBox(e)));
        return true;
      }

      ......
    };

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/gesture_provider.cc中。

ScaleGestureListenerImpl类的成员函数OnScale首先检查网页是否刚刚开始被捏合。如果是的话,ScaleGestureListenerImpl类的成员变量pinch_event_sent_的值就会等于false。在这种情况下,Browser进程会先发送一个类型为ET_GESTURE_PINCH_BEGIN的手势操作给Render进程。

ScaleGestureListenerImpl类的成员函数OnScale接下来又通过调用参数detector描述的ScaleGestureDetector对象的成员函数GetScaleFactor得到捏合手势操作所产生的缩放因子,然后将这个缩放因子封装在一个类型为ET_GESTURE_PINCH_UPDATE的手势操作中发送给Render进程。

与前面提到的类型为ET_GESTURE_SCROLL_BEGIN和ET_GESTURE_SCROLL_UPDATE的手势操作一样,类型为ET_GESTURE_PINCH_BEGIN和ET_GESTURE_PINCH_UPDATE的手势操作也是通过GestureProvider类的成员函数Send发送给Render进程的。但是在我们这个情景中,GestureProvider类的成员函数Send并没有将这些手势操作发送给Render进程,而仅仅是将它们收集在一个Gesture Event Data Packet中。

这一步执行完成之后,Browser进程就对当前发生的Touch事件进行了滑动手势和捏合手势检测,并且检测出来的手势操作(ET_GESTURE_SCROLL_BEGIN、ET_GESTURE_SCROLL_UPDATE、ET_GESTURE_PINCH_BEGIN和ET_GESTURE_PINCH_UPDATE)都保存了FilteredGestureProvider类的成员变量pending_gesture_packet_描述的一个类型为GestureEventDataPacket::TOUCH_MOVE的Gesture Event Data Packet。

回到FilteredGestureProvider类的成员函数OnTouchEvent中,它接下来要做的工作就将保存在成员变量pending_gesture_packet_描述的Gesture Event Data Packet中的手势操作发送给Render进程处理,这是通过调用另外一个成员变量gesture_filter_描述的一个TouchDispositionGestureFilter对象的成员函数OnGesturePacket实现的,如下所示:

TouchDispositionGestureFilter::PacketResult
    TouchDispositionGestureFilter::OnGesturePacket(
        const GestureEventDataPacket& packet) {
      ......

      if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT &&
          Tail().empty()) {
        // Handle the timeout packet immediately if the packet preceding the timeout
        // has already been dispatched.
        FilterAndSendPacket(packet);
        return SUCCESS;
      }

      Tail().push(packet);
      return SUCCESS;
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/touch_disposition_gesture_filter.cc中。

TouchDispositionGestureFilter类的成员函数OnGesturePacket首先判断参数packet描述的Gesture Event Data Packet的类型是否等于GestureEventDataPacket::TOUCH_TIMEOUT。如果等于,并且当前的Gesture Event Data Packet队列为空,那么参数packet描述的Gesture Event Data Packet就会马上被发送给Render进程。这个发送过程是通过调用TouchDispositionGestureFilter类的成员函数FilterAndSendPacket进行的。

从前面的分析可以知道,参数packet描述的Gesture Event Data Packet的类型为GestureEventDataPacket::TOUCH_MOVE,因此它将不会马上被发送给Render进程,而是被保存在一个Gesture Event Data Packet队列中。那么,这个队列中的Gesture Event Data Packet什么会被发送给Render进程呢?当Render进程处理完成Browser进程上一次发送给它的Gesture Event Data Packet之后,它就会给Browser进程发送一个ACK。Browser进程接收到这个ACK之后,就会从队列中取出下一个Gesture Event Data Packet发送给Render进程处理。这个发送过程同样也是通过调用TouchDispositionGestureFilter类的成员函数FilterAndSendPacket进行的。因此,接下来我们就继续分析TouchDispositionGestureFilter类的成员函数FilterAndSendPacket的实现,如下所示:

void TouchDispositionGestureFilter::FilterAndSendPacket(
        const GestureEventDataPacket& packet) {
      ......

      for (size_t i = 0; i < packet.gesture_count(); ++i) {
        const GestureEventData& gesture = packet.gesture(i);
        ......

        SendGesture(gesture, packet);
      }

      ......
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/touch_disposition_gesture_filter.cc中。

TouchDispositionGestureFilter类的成员函数FilterAndSendPacket遍历保存在参数packet描述的Gesture Event Data Packet中的每一个手势操作,并且调用另外一个成员函数SendGesture分别将这些手势操作发送给Render进程,如下所示:

void TouchDispositionGestureFilter::SendGesture(
        const GestureEventData& event,
        const GestureEventDataPacket& packet_being_sent) {
      ......

      client_->ForwardGestureEvent(event);
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/touch_disposition_gesture_filter.cc中。

TouchDispositionGestureFilter类的成员变量client_指向的是一个FilteredGestureProvider对象。这个FilteredGestureProvider对象就是前面分析的RenderWidgetHostViewAndroid类的成员变量gesture_provider_所描述的FilteredGestureProvider对象。TouchDispositionGestureFilter类的成员函数SendGesture通过调用这个FilteredGestureProvider对象的成员函数ForwardGestureEvent将参数event描述的手势操作发送给Render进程。

FilteredGestureProvider类的成员函数ForwardGestureEvent的实现如下所示:

void FilteredGestureProvider::ForwardGestureEvent(
        const GestureEventData& event) {
      client_->OnGestureEvent(event);
    }

这个函数定义在文件external/chromium_org/ui/events/gesture_detection/filtered_gesture_provider.cc中。

FilteredGestureProvider类的成员变量client_指向的是一个RenderWidgetHostViewAndroid对象。这个RenderWidgetHostViewAndroid对象就前面描述的在Browser进程中用来加载网页的控件。FilteredGestureProvider类的成员函数ForwardGestureEvent通过调用这个RenderWidgetHostViewAndroid对象的成员函数OnGestureEvent将参数event描述的手势操作发送给Render进程。

RenderWidgetHostViewAndroid类的成员函数OnGestureEvent的实现如下所示:

void RenderWidgetHostViewAndroid::OnGestureEvent(
        const ui::GestureEventData& gesture) {
      ......

      SendGestureEvent(CreateWebGestureEventFromGestureEventData(gesture));
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。

RenderWidgetHostViewAndroid类的成员函数OnGestureEvent首先调用函数CreateWebGestureEventFromGestureEventData将参数gesture描述的手势操作封装在一个WebGestureEvent对象中,如下所示:

WebGestureEvent CreateWebGestureEventFromGestureEventData(
        const ui::GestureEventData& data) {
      WebGestureEvent gesture;
      gesture.x = data.x;
      gesture.y = data.y;
      gesture.globalX = data.raw_x;
      gesture.globalY = data.raw_y;
      gesture.timeStampSeconds = (data.time - base::TimeTicks()).InSecondsF();
      gesture.sourceDevice = blink::WebGestureDeviceTouchscreen;

      switch (data.type()) {
        ......

        case ui::ET_GESTURE_SCROLL_UPDATE:
          gesture.type = WebInputEvent::GestureScrollUpdate;
          gesture.data.scrollUpdate.deltaX = data.details.scroll_x();
          gesture.data.scrollUpdate.deltaY = data.details.scroll_y();
          break;

        ......

        case ui::ET_GESTURE_PINCH_UPDATE:
          gesture.type = WebInputEvent::GesturePinchUpdate;
          gesture.data.pinchUpdate.scale = data.details.scale();
          break;

        ......
      }

      return gesture;
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/web_input_event_util.cc中。

函数CreateWebGestureEventFromGestureEventData会将不同类型的手势操作封装在不同类型的WebGestureEvent对象中。例如,ui::ET_GESTURE_SCROLL_UPDATE类的手势操作,即滑动手势操作,会封装在一个类型为WebInputEvent::GestureScrollUpdate的WebGestureEvent对象中;又如,ui::ET_GESTURE_PINCH_UPDATE类型的手势操作,即捏合手势操作,会封装在一个类型为WebInputEvent::GesturePinchUpdate的WebGestureEvent对象中。

回到RenderWidgetHostViewAndroid类的成员函数OnGestureEvent中,它将手势操作封装在一个WebGestureEvent对象之后,再调用另外一个成员函数SendGestureEvent将这个WebGestureEvent对象发送给Render进程,如下所示:

void RenderWidgetHostViewAndroid::SendGestureEvent(
        const blink::WebGestureEvent& event) {
      ......

      if (host_)
        host_->ForwardGestureEventWithLatencyInfo(event, CreateLatencyInfo(event));
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。

RenderWidgetHostViewAndroid类的成员变量host_指向的是一个RenderWidgetHostImpl对象。这个RenderWidgetHostImpl对象描述的是加载当前正在发生输入事件的网页的Render进程。RenderWidgetHostViewAndroid类的成员函数SendGestureEvent调用这个RenderWidgetHostImpl对象的成员函数ForwardGestureEventWithLatencyInfo将参数event描述的手势操作发送给它所描述的Render进程。

RenderWidgetHostImpl类的成员函数ForwardGestureEventWithLatencyInfo的实现如下所示:

void RenderWidgetHostImpl::ForwardGestureEventWithLatencyInfo(
        const blink::WebGestureEvent& gesture_event,
        const ui::LatencyInfo& ui_latency) {
      ......

      GestureEventWithLatencyInfo gesture_with_latency(gesture_event, latency_info);
      input_router_->SendGestureEvent(gesture_with_latency);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。

RenderWidgetHostImpl类的成员函数ForwardGestureEventWithLatencyInfo首先将参数gesture_event描述的手势操作封装在一个GestureEventWithLatencyInfo对象中。

RenderWidgetHostImpl类的成员变量input_router_指向的是一个InputRouterImpl对象。这个InputRouterImpl负责将输入事件发送给Render进程。因此,RenderWidgetHostImpl类的成员函数SendGestureEvent就通过调用这个InputRouterImpl对象的成员函数SendGestureEvent将上述封装了手势操作的GestureEventWithLatencyInfo对象发送给Render进程。

InputRouterImpl类的成员函数SendGestureEvent的实现如下所示:

void InputRouterImpl::SendGestureEvent(
        const GestureEventWithLatencyInfo& original_gesture_event) {
      ......

      GestureEventWithLatencyInfo gesture_event(original_gesture_event);

      ......

      SendGestureEventImmediately(gesture_event);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

InputRouterImpl类的成员函数SendGestureEvent主要是调用另外一个成员函数SendGestureEventImmediately将参数original_gesture_event描述的手势操作发送给Render进程,如下所示:

void InputRouterImpl::SendGestureEventImmediately(
        const GestureEventWithLatencyInfo& gesture_event) {
      ......

      FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

InputRouterImpl类的成员函数SendGestureEventImmediately又主要是调用另外一个成员函数FilterAndSendWebInputEvent将参数gesture_event描述的手势操作发送给Render进程,如下所示:

void InputRouterImpl::FilterAndSendWebInputEvent(
        const WebInputEvent& input_event,
        const ui::LatencyInfo& latency_info,
        bool is_keyboard_shortcut) {
      ......

      OfferToHandlers(input_event, latency_info, is_keyboard_shortcut);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

InputRouterImpl类的成员函数FilterAndSendWebInputEvent又主要是调用另外一个成员函数OfferToHandlers将参数input_event描述的手势操作发送给Render进程,如下所示:

void InputRouterImpl::OfferToHandlers(const WebInputEvent& input_event,
                                          const ui::LatencyInfo& latency_info,
                                          bool is_keyboard_shortcut) {
      ......

      if (OfferToClient(input_event, latency_info))
        return;

      OfferToRenderer(input_event, latency_info, is_keyboard_shortcut);

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

InputRouterImpl类的成员函数OfferToHandlers首先调用成员函数OfferToClient询问Browser进程是否要过滤参数input_event描述的手势操作。如果过滤的话,那么InputRouterImpl类的成员函数OfferToHandlers就不会将它发送给Render进程。否则的话,就会调用另外一个成员函数OfferToRenderer进行发送。

我们假设Browser进程不过滤参数input_event描述的手势操作,因此接下来这个手势就会通过InputRouterImpl类的成员函数OfferToRenderer发送给Render进程,如下所示:

bool InputRouterImpl::OfferToRenderer(const WebInputEvent& input_event,
                                          const ui::LatencyInfo& latency_info,
                                          bool is_keyboard_shortcut) {
      if (Send(new InputMsg_HandleInputEvent(
              routing_id(), &input_event, latency_info, is_keyboard_shortcut))) {
        ......
        return true;
      }
      return false;
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

从这里就可以看到,InputRouterImpl类的成员函数OfferToRenderer会将参数input_event描述的手势操作封装在一个类型为InputMsg_HandleInputEvent的IPC消息中,然后再将这个消息发送给Render进程处理。这个处理过程我们在接下来的一篇文章中再详细分析。

这一步执行完成后,Browser进程就将检测到的手势操作发送给Render进程了。回到前面分析的RenderWidgetHostViewAndroid类的成员函数OnTouchEvent中,它接下来再调用函数CreateWebTouchEventFromMotionEvent将原始的Touch事件封装在一个blink::WebTouchEvent对象中,如下所示:

blink::WebTouchEvent CreateWebTouchEventFromMotionEvent(
        const ui::MotionEvent& event) {
      blink::WebTouchEvent result;

      WebTouchEventTraits::ResetType(
          ToWebInputEventType(event.GetAction()),
          (event.GetEventTime() - base::TimeTicks()).InSecondsF(),
          &result);

      result.touchesLength =
          std::min(event.GetPointerCount(),
                   static_cast<size_t>(WebTouchEvent::touchesLengthCap));
      DCHECK_GT(result.touchesLength, 0U);

      for (size_t i = 0; i < result.touchesLength; ++i)
        result.touches[i] = CreateWebTouchPoint(event, i);

      return result;
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/web_input_event_util.cc中。

函数CreateWebTouchEventFromMotionEvent首先调用函数ToWebInputEventType获得接下来要创建的blink::WebTouchEvent对象的类型,如下所示:

WebInputEvent::Type ToWebInputEventType(MotionEvent::Action action) {
      switch (action) {
        case MotionEvent::ACTION_DOWN:
          return WebInputEvent::TouchStart;
        case MotionEvent::ACTION_MOVE:
          return WebInputEvent::TouchMove;
        case MotionEvent::ACTION_UP:
          return WebInputEvent::TouchEnd;
        case MotionEvent::ACTION_CANCEL:
          return WebInputEvent::TouchCancel;
        case MotionEvent::ACTION_POINTER_DOWN:
          return WebInputEvent::TouchStart;
        case MotionEvent::ACTION_POINTER_UP:
          return WebInputEvent::TouchEnd;
      }
      NOTREACHED() << "Invalid MotionEvent::Action.";
      return WebInputEvent::Undefined;
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/web_input_event_util.cc中。

参数action表示要封装的Touch事件的类型。函数ToWebInputEventType会根据不同的Touch事件类型返回不同的blink::WebTouchEvent对象类型。例如,对于类型为MotionEvent::ACTION_MOVE的Touch事件,函数ToWebInputEventType返回的blink::WebTouchEvent对象类型为WebInputEvent::TouchMove。

回到函数CreateWebTouchEventFromMotionEvent中,它获得了接下来要创建的blink::WebTouchEvent对象的类型之后,就会创建这个blink::WebTouchEvent对象,并且会将event描述的Touch事件的所有信息,例如触摸点位置,保存在创建出来的blink::WebTouchEvent对象中。

这一步执行完成之后,再回到前面分析的RenderWidgetHostViewAndroid类的成员函数OnTouchEvent中,它接下来就会将前面创建的blink::WebTouchEvent对象发送给Render进程处理,这是通过调用另外一个成员函数SendTouchEvent实现的,如下所示:

void RenderWidgetHostViewAndroid::SendTouchEvent(
        const blink::WebTouchEvent& event) {
      if (host_)
        host_->ForwardTouchEventWithLatencyInfo(event, CreateLatencyInfo(event));

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。

前面提到,RenderWidgetHostViewAndroid类的成员变量host_指向的是一个RenderWidgetHostImpl对象,RenderWidgetHostViewAndroid类的成员函数SendTouchEvent调用这个RenderWidgetHostImpl对象的成员函数ForwardTouchEventWithLatencyInfo将参数event描述的Touch事件发送给Render进程。

RenderWidgetHostImpl类的成员函数ForwardTouchEventWithLatencyInfo的实现如下所示:

void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
          const blink::WebTouchEvent& touch_event,
          const ui::LatencyInfo& ui_latency) {
      ......

      ui::LatencyInfo latency_info =
          CreateRWHLatencyInfoIfNotExist(&ui_latency, touch_event.type);
      TouchEventWithLatencyInfo touch_with_latency(touch_event, latency_info);
      input_router_->SendTouchEvent(touch_with_latency);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。

RenderWidgetHostImpl类的成员函数ForwardTouchEventWithLatencyInfo首先将参数touch_event描述的Touch事件封装在一个TouchEventWithLatencyInfo对象中,然后再调用成员变量input_router_指向的一个InputRouterImpl对象的成员函数SendTouchEvent将这个TouchEventWithLatencyInfo对象发送给Render进程。

InputRouterImpl类的成员函数SendTouchEvent的实现如下所示:

void InputRouterImpl::SendTouchEvent(
        const TouchEventWithLatencyInfo& touch_event) {
      ......
      touch_event_queue_.QueueEvent(touch_event);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

InputRouterImpl类的成员变量touch_event_queue_描述的是一个TouchEventQueue对象,InputRouterImpl类的成员函数SendTouchEvent调用这个TouchEventQueue对象的成员函数QueueEvent将参数touch_event描述的Touch事件发送给Render进程。

TouchEventQueue类的成员函数QueueEvent的实现如下所示:

void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
      ......

      // 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_) {
        ......

        // 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));
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/touch_event_queue.cc中。

TouchEventQueue类的成员变量touch_queue_描述的是一个Touch事件队列。这个队列用来暂存即将要发送给Render进程的Touch事件。一个即将要发送的Touch事件在两种情况下需要暂存在队列中:

1. 在它之前的Touch事件还未发送给Render进程,即Touch事件队列不为空。

2. Render进程正在发送一个ACK事件给Browser进程,而Browser进程正在分发这个ACK事件。这个ACK事件分发完成之后,Browser进程才可以将下一个Touch事件发送给Render进程处理。这时候TouchEventQueue类的成员变量dispatching_touch_ack_的值就不等于NULL,它指向正在分发的ACK事件。

TouchEventQueue类的成员函数QueueEvent所做的事情就是判断参数event描述的Touch事件是否能够马上发送。如果能马上发送,那么就会将它保存在Touch事件队列中,然后再调用另外一个成员函数ForwardNextEventToRenderer将它从Touch事件队列读取出来,并且发送给Render进程。如果不能马上发送,那么同样会将它保存在Touch事件队列中,不过要等到上一个发送给Render进程的Touch事件被ACK之后,才能继续将它发送给Render进程。这同样是通过调用TouchEventQueue类的成员函数ForwardNextEventToRenderer进行发送的。

我们注意到,在将参数event描述的Touch事件保存在Touch事件队列之前,如果队列不为空,那么TouchEventQueue类的成员函数QueueEvent会判断参数event描述的Touch事件与队列中最后一个Touch事件是否是相同的,也就是它们所包含的触摸点都是一样的。如果相同,那么就可以合并为一个Touch事件发送给Render进程。合并后的Touch事件使用一个CoalescedWebTouchEvent对象描述。这样可以避免重复向Render进程发送相同的Touch事件。

我们假设参数event描述的Touch事件可以马上发送给Render进程,因此接下来我们就继续分析TouchEventQueue类的成员函数ForwardNextEventToRenderer的实现,如下所示:

void TouchEventQueue::ForwardNextEventToRenderer() {
      ......

      TouchEventWithLatencyInfo touch = touch_queue_.front()->coalesced_event();
      ......

      // A synchronous ack will reset |dispatching_touch_|, in which case
      // the touch timeout should not be started.
      base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
      SendTouchEventImmediately(touch);

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/touch_event_queue.cc中。

TouchEventQueue类的成员函数ForwardNextEventToRenderer首先从Touch事件队列中取出第一个Touch事件,然后调用另外一个成员函数SendTouchEventImmediately将该Touch事件发送给Render进程。在发送的过程中,TouchEventQueue类的成员变量dispatching_touch_会被设置为true,并且会在发送结束后(也就是TouchEventQueue类的成员函数ForwardNextEventToRenderer调用结束),恢复为false。

TouchEventQueue类的成员函数SendTouchEventImmediately的实现如下所示:

void TouchEventQueue::SendTouchEventImmediately(
        const TouchEventWithLatencyInfo& touch) {
      ......

      client_->SendTouchEventImmediately(touch);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/touch_event_queue.cc中。

TouchEventQueue类的成员变量client_指向的是一个InputRouterImpl对象。这个InputRouterImpl对象就前面分析的RenderWidgetHostImpl类的成员变量input_router_所指向的InputRouterImpl对象。TouchEventQueue类的成员函数SendTouchEventImmediately调用这个InputRouterImpl对象的成员函数SendTouchEventImmediately将参数touch描述的Touch事件发送给Render进程。

InputRouterImpl类的成员函数SendTouchEventImmediately的实现如下所示:

void InputRouterImpl::SendTouchEventImmediately(
        const TouchEventWithLatencyInfo& touch_event) {
      ......

      FilterAndSendWebInputEvent(touch_event.event, touch_event.latency, false);
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/input/input_router_impl.cc中。

从这里可以看到,InputRouterImpl类的成员函数SendTouchEventImmediately是调用我们前面已经分析过的另外一个成员函数FilterAndSendWebInputEvent将参数touch_event描述的Touch事件发送给Render进程的。从前面的分析可以知道,这个Touch事件封装在一个类型为WebInputEvent::TouchMove的WebInputEvent对象中,它的发送过程与前面分析的滑动手势操作和捏合手势操作的发送过程是一样的,只不过后两者分别封装在类型为WebInputEvent::GestureScrollUpdate和WebInputEvent::GesturePinchUpdate的WebInputEvent对象中。

至此,我们就以Touch事件为例,分析完成了Browser进程捕捉网页输入事件,以及从中检测手势操作的过程。这些网页输入事件和手势操作都是通过类型为InputMsg_HandleInputEvent的IPC消息发送给Render进程处理的。在接下来的两篇文章中,我们就详细分析Render进程处理网页输入事件和手势操作的过程,也就是Render进程处理类型为InputMsg_HandleInputEvent的IPC消息的过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录