Commit 545102b5 authored by Adithya Srinivasan's avatar Adithya Srinivasan Committed by Commit Bot

Portals: Transfer touch state across activation (Android)

Portals can be activated in response to scroll events like overscroll
and we want users to be able to continue scrolling after activation
without tapping the screen again. This CL dispatches touch events
targeted at the new ContentView after activation, which mirror where
the pointers were before activation.

Design doc: https://docs.google.com/document/d/12EsIEQIJAzh4-3aNQNhxoQ8Z7ayrSodmWbo06R9PbAQ/edit?usp=sharing

Bug: 914376
Change-Id: I1ade7ac3da7451de14b38dd66056178cdcab7631
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1622906
Commit-Queue: Adithya Srinivasan <adithyas@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665179}
parent 5625aa64
......@@ -137,6 +137,12 @@ public class CompositorViewHolder extends FrameLayout
private boolean mShouldCreateContentCaptureConsumer = true;
private ContentCaptureConsumer mContentCaptureConsumer;
/**
* Last MOVE MotionEvent dispatched to this object for a currently active gesture. If there is
* no active gesture, this is null.
*/
private @Nullable MotionEvent mLastMoveEvent;
/**
* This view is created on demand to display debugging information.
*/
......@@ -237,6 +243,37 @@ public class CompositorViewHolder extends FrameLayout
public void onContentChanged(Tab tab) {
CompositorViewHolder.this.onContentChanged();
}
@Override
public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) {
/**
* After swapping web contents, any gesture active in the old ContentView is
* cancelled. We still want to continue a previously running gesture in the new
* ContentView, so we synthetically dispatch a new ACTION_DOWN MotionEvent with the
* coordinates of where we estimate the pointer currently is (the coordinates of
* the last ACTION_MOVE MotionEvent received before the swap).
*
* We wait for layout to happen as the newly created ContentView currently has a
* width and height of zero, which would result in the event not being dispatched.
*/
mView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
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);
pointerDownEvent.setAction(MotionEvent.ACTION_POINTER_DOWN
| (i << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
CompositorViewHolder.this.dispatchTouchEvent(pointerDownEvent);
}
}
});
}
};
addOnLayoutChangeListener(new OnLayoutChangeListener() {
......@@ -558,6 +595,22 @@ public class CompositorViewHolder extends FrameLayout
return ret;
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
updateLastMoveEvent(e);
return super.dispatchTouchEvent(e);
}
private void updateLastMoveEvent(MotionEvent e) {
if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
mLastMoveEvent = e;
}
if (e.getActionMasked() == MotionEvent.ACTION_CANCEL
|| e.getActionMasked() == MotionEvent.ACTION_UP) {
mLastMoveEvent = null;
}
}
/**
* @return The {@link LayoutManager} associated with this view.
*/
......
......@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.portals;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.view.View;
import org.junit.After;
import org.junit.Assert;
......@@ -24,14 +25,17 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.Coordinates;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.net.test.EmbeddedTestServer;
/**
* Tests for the chrome/ layer support of the HTML portal element.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-features=Portals"})
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-features=Portals",
"enable-blink-features=OverscrollCustomization"})
public class PortalsTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
......@@ -164,4 +168,31 @@ public class PortalsTest {
executeScriptAndAwaitSwap(tab, "reactivatePredecessor();");
JavaScriptUtils.executeJavaScriptAndWaitForResult(tab.getWebContents(), "removePortal();");
}
/**
* Tests that a drag that started in the predecessor page causes a scroll in the activated page
* after a scroll triggered activation.
*/
@Test
@MediumTest
@Feature({"Portals"})
public void testTouchTransfer() throws Exception {
mActivityTestRule.startMainActivityWithURL(
mTestServer.getURL("/chrome/test/data/android/portals/touch-transfer.html"));
ChromeActivity activity = mActivityTestRule.getActivity();
View contentView = activity.getActivityTab().getContentView();
int dragStartX = 30;
int dragStartY = contentView.getHeight() / 2;
int dragEndX = dragStartX;
int dragEndY = 30;
long downTime = System.currentTimeMillis();
TouchCommon.dragStart(activity, dragStartX, dragStartY, downTime);
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);
}
}
<!DOCTYPE html>
<html>
<style>
#scroller {
width: 300px;
height: 2000px;
background-color: green;
}
</style>
<body>
<div id="scroller"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<div>This is the embedder</div>
<portal src="touch-transfer-portal.html"></portal>
<script>
document.addEventListener("overscroll", e => {
var portal = document.querySelector("portal");
portal.activate();
});
</script>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment