Portals: Keep track of last down motion events

It is possible to activate on touchstart events, and we want to be able
to maintain touch state across activation in that scenario as well.

Bug: 914376
Change-Id: I2970f6184b01f6f9d3f5a4fd81fd3c52b17089bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1661800
Reviewed-by: David Trainor <dtrainor@chromium.org>
Commit-Queue: Adithya Srinivasan <adithyas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#671124}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 20c3416..e8c20047 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -138,10 +138,10 @@
     private ContentCaptureConsumer mContentCaptureConsumer;
 
     /**
-     * Last MOVE MotionEvent dispatched to this object for a currently active gesture. If there is
-     * no active gesture, this is null.
+     * Last MotionEvent dispatched to this object for a currently active gesture. If there is no
+     * active gesture, this is null.
      */
-    private @Nullable MotionEvent mLastMoveEvent;
+    private @Nullable MotionEvent mLastActiveTouchEvent;
 
     /**
      * This view is created on demand to display debugging information.
@@ -261,12 +261,13 @@
                     public void onLayoutChange(View v, int left, int top, int right, int bottom,
                             int oldLeft, int oldTop, int oldRight, int oldBottom) {
                         v.removeOnLayoutChangeListener(this);
-                        if (mLastMoveEvent == null) return;
-                        MotionEvent downEvent = MotionEvent.obtain(mLastMoveEvent);
-                        downEvent.setAction(MotionEvent.ACTION_DOWN);
-                        CompositorViewHolder.this.dispatchTouchEvent(downEvent);
-                        for (int i = 1; i < mLastMoveEvent.getPointerCount(); i++) {
-                            MotionEvent pointerDownEvent = MotionEvent.obtain(mLastMoveEvent);
+                        if (mLastActiveTouchEvent == null) return;
+                        MotionEvent touchEvent = MotionEvent.obtain(mLastActiveTouchEvent);
+                        touchEvent.setAction(MotionEvent.ACTION_DOWN);
+                        CompositorViewHolder.this.dispatchTouchEvent(touchEvent);
+                        for (int i = 1; i < mLastActiveTouchEvent.getPointerCount(); i++) {
+                            MotionEvent pointerDownEvent =
+                                    MotionEvent.obtain(mLastActiveTouchEvent);
                             pointerDownEvent.setAction(MotionEvent.ACTION_POINTER_DOWN
                                     | (i << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                             CompositorViewHolder.this.dispatchTouchEvent(pointerDownEvent);
@@ -597,17 +598,20 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent e) {
-        updateLastMoveEvent(e);
+        updateLastActiveTouchEvent(e);
         return super.dispatchTouchEvent(e);
     }
 
-    private void updateLastMoveEvent(MotionEvent e) {
-        if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
-            mLastMoveEvent = e;
+    private void updateLastActiveTouchEvent(MotionEvent e) {
+        if (e.getActionMasked() == MotionEvent.ACTION_MOVE
+                || e.getActionMasked() == MotionEvent.ACTION_DOWN
+                || e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
+                || e.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
+            mLastActiveTouchEvent = e;
         }
         if (e.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || e.getActionMasked() == MotionEvent.ACTION_UP) {
-            mLastMoveEvent = null;
+            mLastActiveTouchEvent = null;
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java
index 166fb957..5c543e4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java
@@ -206,8 +206,8 @@
     @MediumTest
     @Feature({"Portals"})
     public void testTouchTransfer() throws Exception {
-        mActivityTestRule.startMainActivityWithURL(
-                mTestServer.getURL("/chrome/test/data/android/portals/touch-transfer.html"));
+        mActivityTestRule.startMainActivityWithURL(mTestServer.getURL(
+                "/chrome/test/data/android/portals/touch-transfer.html?event=overscroll"));
 
         ChromeActivity activity = mActivityTestRule.getActivity();
         Tab tab = activity.getActivityTab();
@@ -239,4 +239,43 @@
         WebContents contents = mActivityTestRule.getWebContents();
         Assert.assertTrue(Coordinates.createFor(contents).getScrollYPixInt() > 0);
     }
+
+    /**
+     * Tests that touch is transferred after triggering portal activation on touchstart.
+     */
+    @Test
+    @MediumTest
+    @Feature({"Portals"})
+    public void testTouchTransferAfterTouchStartActivate() throws Exception {
+        mActivityTestRule.startMainActivityWithURL(mTestServer.getURL(
+                "/chrome/test/data/android/portals/touch-transfer.html?event=touchstart"));
+
+        ChromeActivity activity = mActivityTestRule.getActivity();
+        Tab tab = activity.getActivityTab();
+        View contentView = tab.getContentView();
+        LayoutAfterTabContentsSwappedObserver layoutObserver =
+                new LayoutAfterTabContentsSwappedObserver(tab);
+        CallbackHelper layoutWaiter = layoutObserver.getCallbackHelper();
+        int currLayoutCount = layoutWaiter.getCallCount();
+
+        int dragStartX = 30;
+        int dragStartY = contentView.getHeight() / 2;
+        int dragEndX = dragStartX;
+        int dragEndY = 30;
+        long downTime = System.currentTimeMillis();
+
+        // Initial touch to trigger activation.
+        TouchCommon.dragStart(activity, dragStartX, dragStartY, downTime);
+
+        // Wait for the first layout after tab contents are swapped. This is needed as touch events
+        // sent before the first layout are dropped.
+        layoutWaiter.waitForCallback(currLayoutCount, 1);
+
+        // Continue and finish drag.
+        TouchCommon.dragTo(activity, dragStartX, dragEndX, dragStartY, dragEndY, 100, downTime);
+        TouchCommon.dragEnd(activity, dragEndX, dragEndY, downTime);
+
+        WebContents contents = mActivityTestRule.getWebContents();
+        Assert.assertTrue(Coordinates.createFor(contents).getScrollYPixInt() > 0);
+    }
 }
diff --git a/chrome/test/data/android/portals/touch-transfer.html b/chrome/test/data/android/portals/touch-transfer.html
index 34c7ac9..0394c20 100644
--- a/chrome/test/data/android/portals/touch-transfer.html
+++ b/chrome/test/data/android/portals/touch-transfer.html
@@ -4,7 +4,8 @@
   <div>This is the embedder</div>
   <portal src="touch-transfer-portal.html"></portal>
   <script>
-    document.addEventListener("overscroll", e => {
+    var eventType = (new URL(window.location)).searchParams.get("event");
+    document.addEventListener(eventType, e => {
       var portal = document.querySelector("portal");
       portal.activate();
     });