1 // Copyright 2019 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.gesturenav; 6 7 import android.app.Activity; 8 import android.graphics.Point; 9 import android.support.test.InstrumentationRegistry; 10 import android.util.DisplayMetrics; 11 12 import androidx.test.filters.SmallTest; 13 14 import org.hamcrest.Matchers; 15 import org.junit.After; 16 import org.junit.Assert; 17 import org.junit.Before; 18 import org.junit.Rule; 19 import org.junit.Test; 20 import org.junit.runner.RunWith; 21 22 import org.chromium.base.ActivityState; 23 import org.chromium.base.ApplicationStatus; 24 import org.chromium.base.test.util.CommandLineFlags; 25 import org.chromium.base.test.util.Criteria; 26 import org.chromium.base.test.util.CriteriaHelper; 27 import org.chromium.base.test.util.DisabledTest; 28 import org.chromium.base.test.util.Restriction; 29 import org.chromium.chrome.browser.compositor.layouts.OverviewModeController; 30 import org.chromium.chrome.browser.flags.ChromeSwitches; 31 import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler; 32 import org.chromium.chrome.browser.tab.Tab; 33 import org.chromium.chrome.browser.tab.TabLaunchType; 34 import org.chromium.chrome.browser.tabbed_mode.TabbedRootUiCoordinator; 35 import org.chromium.chrome.browser.tabmodel.TabCreator; 36 import org.chromium.chrome.test.ChromeJUnit4ClassRunner; 37 import org.chromium.chrome.test.ChromeTabbedActivityTestRule; 38 import org.chromium.chrome.test.util.ChromeTabUtils; 39 import org.chromium.components.embedder_support.util.UrlConstants; 40 import org.chromium.content_public.browser.LoadUrlParams; 41 import org.chromium.content_public.browser.test.util.TestThreadUtils; 42 import org.chromium.content_public.common.ContentUrlConstants; 43 import org.chromium.net.test.EmbeddedTestServer; 44 import org.chromium.ui.base.PageTransition; 45 import org.chromium.ui.test.util.UiRestriction; 46 47 /** 48 * Tests {@link NavigationHandler} navigating back/forward using overscroll history navigation. 49 */ 50 @RunWith(ChromeJUnit4ClassRunner.class) 51 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, 52 "enable-features=OverscrollHistoryNavigation<GestureNavigation", 53 "force-fieldtrials=GestureNavigation/Enabled", 54 "force-fieldtrial-params=GestureNavigation.Enabled:" 55 + "gesture_navigation_triggering_area_width/36"}) 56 public class NavigationHandlerTest { 57 private static final String RENDERED_PAGE = "/chrome/test/data/android/navigate/simple.html"; 58 private static final boolean LEFT_EDGE = true; 59 private static final boolean RIGHT_EDGE = false; 60 private static final int PAGELOAD_TIMEOUT_MS = 4000; 61 62 private EmbeddedTestServer mTestServer; 63 private HistoryNavigationLayout mNavigationLayout; 64 private NavigationHandler mNavigationHandler; 65 private float mEdgeWidthPx; 66 67 @Rule 68 public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); 69 70 @Before setUp()71 public void setUp() throws InterruptedException { 72 mActivityTestRule.startMainActivityOnBlankPage(); 73 CompositorAnimationHandler.setTestingMode(true); 74 DisplayMetrics displayMetrics = new DisplayMetrics(); 75 mActivityTestRule.getActivity().getWindowManager().getDefaultDisplay().getMetrics( 76 displayMetrics); 77 mEdgeWidthPx = displayMetrics.density * NavigationHandler.DEFAULT_EDGE_WIDTH_DP; 78 HistoryNavigationCoordinator coordinator = getNavigationCoordinator(); 79 mNavigationLayout = coordinator.getLayoutForTesting(); 80 mNavigationHandler = coordinator.getNavigationHandlerForTesting(); 81 } 82 83 @After tearDown()84 public void tearDown() { 85 if (mTestServer != null) mTestServer.stopAndDestroyServer(); 86 } 87 getNavigationCoordinator()88 private HistoryNavigationCoordinator getNavigationCoordinator() { 89 TabbedRootUiCoordinator uiCoordinator = 90 (TabbedRootUiCoordinator) mActivityTestRule.getActivity() 91 .getRootUiCoordinatorForTesting(); 92 return uiCoordinator.getHistoryNavigationCoordinatorForTesting(); 93 } 94 currentTab()95 private Tab currentTab() { 96 return mActivityTestRule.getActivity().getActivityTabProvider().get(); 97 } 98 loadNewTabPage()99 private void loadNewTabPage() { 100 ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(), 101 mActivityTestRule.getActivity(), false, true); 102 } 103 assertNavigateOnSwipeFrom(boolean edge, String toUrl)104 private void assertNavigateOnSwipeFrom(boolean edge, String toUrl) { 105 ChromeTabUtils.waitForTabPageLoaded(currentTab(), toUrl, () -> swipeFromEdge(edge), 10); 106 CriteriaHelper.pollUiThread( 107 () 108 -> Criteria.checkThat(ChromeTabUtils.getUrlStringOnUiThread(currentTab()), 109 Matchers.is(toUrl))); 110 Assert.assertEquals( 111 "Didn't navigate back", toUrl, ChromeTabUtils.getUrlStringOnUiThread(currentTab())); 112 } 113 swipeFromEdge(boolean leftEdge)114 private void swipeFromEdge(boolean leftEdge) { 115 Point size = new Point(); 116 mActivityTestRule.getActivity().getWindowManager().getDefaultDisplay().getSize(size); 117 final float startx = leftEdge ? mEdgeWidthPx / 2 : size.x - mEdgeWidthPx / 2; 118 final float endx = size.x / 2; 119 final float yMiddle = size.y / 2; 120 swipe(leftEdge, startx, endx, yMiddle); 121 } 122 123 // Make an edge swipe too short to trigger the navigation. shortSwipeFromEdge(boolean leftEdge)124 private void shortSwipeFromEdge(boolean leftEdge) { 125 Point size = new Point(); 126 mActivityTestRule.getActivity().getWindowManager().getDefaultDisplay().getSize(size); 127 final float startx = leftEdge ? 0 : size.x; 128 final float endx = leftEdge ? mEdgeWidthPx : size.x - mEdgeWidthPx; 129 final float yMiddle = size.y / 2; 130 swipe(leftEdge, startx, endx, yMiddle); 131 } 132 swipe(boolean leftEdge, float startx, float endx, float y)133 private void swipe(boolean leftEdge, float startx, float endx, float y) { 134 // # of pixels (of reasonally small value) which a finger moves across 135 // per one motion event. 136 final float distancePx = 6.0f; 137 final float step = Math.signum(endx - startx) * distancePx; 138 final int eventCounts = (int) ((endx - startx) / step); 139 140 TestThreadUtils.runOnUiThreadBlocking(() -> { 141 mNavigationHandler.onDown(); 142 float nextx = startx + step; 143 for (int i = 0; i < eventCounts; i++, nextx += step) { 144 mNavigationHandler.onScroll(startx, -step, 0, nextx, y); 145 } 146 mNavigationHandler.release(true); 147 }); 148 } 149 150 @Test 151 @SmallTest testShortSwipeDoesNotTriggerNavigation()152 public void testShortSwipeDoesNotTriggerNavigation() { 153 mActivityTestRule.loadUrl(UrlConstants.NTP_URL); 154 shortSwipeFromEdge(LEFT_EDGE); 155 CriteriaHelper.pollUiThread(mNavigationLayout::isLayoutDetached, 156 "Navigation Layout should be detached after use"); 157 Assert.assertEquals("Current page should not change", UrlConstants.NTP_URL, 158 ChromeTabUtils.getUrlStringOnUiThread(currentTab())); 159 } 160 161 @Test 162 @SmallTest testCloseChromeAtHistoryStackHead()163 public void testCloseChromeAtHistoryStackHead() { 164 loadNewTabPage(); 165 final Activity activity = mActivityTestRule.getActivity(); 166 swipeFromEdge(LEFT_EDGE); 167 CriteriaHelper.pollUiThread(() -> { 168 int state = ApplicationStatus.getStateForActivity(activity); 169 return state == ActivityState.STOPPED || state == ActivityState.DESTROYED; 170 }, "Chrome should be in background"); 171 } 172 173 @Test 174 @SmallTest testLayoutGetsDetachedAfterUse()175 public void testLayoutGetsDetachedAfterUse() { 176 mActivityTestRule.loadUrl(UrlConstants.NTP_URL); 177 mActivityTestRule.loadUrl(UrlConstants.RECENT_TABS_URL); 178 swipeFromEdge(LEFT_EDGE); 179 CriteriaHelper.pollUiThread(mNavigationLayout::isLayoutDetached, 180 "Navigation Layout should be detached after use"); 181 Assert.assertNull(mNavigationLayout.getDetachLayoutRunnable()); 182 } 183 184 @Test 185 @SmallTest testReleaseGlowWithoutPrecedingPullIgnored()186 public void testReleaseGlowWithoutPrecedingPullIgnored() { 187 mTestServer = EmbeddedTestServer.createAndStartServer( 188 InstrumentationRegistry.getInstrumentation().getContext()); 189 mActivityTestRule.loadUrl(mTestServer.getURL(RENDERED_PAGE)); 190 TestThreadUtils.runOnUiThreadBlocking(() -> { 191 // Right swipe on a rendered page to initiate overscroll glow. 192 mNavigationHandler.onDown(); 193 mNavigationHandler.triggerUi(true, 0, 0); 194 195 // Test that a release without preceding pull requests works 196 // without crashes. 197 mNavigationHandler.release(true); 198 }); 199 200 // Just check we're still on the same URL. 201 Assert.assertEquals(mTestServer.getURL(RENDERED_PAGE), 202 ChromeTabUtils.getUrlStringOnUiThread(currentTab())); 203 } 204 205 @Test 206 @SmallTest testSwipeNavigateOnNativePage()207 public void testSwipeNavigateOnNativePage() { 208 mActivityTestRule.loadUrl(UrlConstants.NTP_URL); 209 mActivityTestRule.loadUrl(UrlConstants.RECENT_TABS_URL); 210 assertNavigateOnSwipeFrom(LEFT_EDGE, UrlConstants.NTP_URL); 211 assertNavigateOnSwipeFrom(RIGHT_EDGE, UrlConstants.RECENT_TABS_URL); 212 } 213 214 @Test 215 @SmallTest testSwipeNavigateOnRenderedPage()216 public void testSwipeNavigateOnRenderedPage() { 217 mTestServer = EmbeddedTestServer.createAndStartServer( 218 InstrumentationRegistry.getInstrumentation().getContext()); 219 mActivityTestRule.loadUrl(mTestServer.getURL(RENDERED_PAGE)); 220 mActivityTestRule.loadUrl(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL); 221 222 assertNavigateOnSwipeFrom(LEFT_EDGE, mTestServer.getURL(RENDERED_PAGE)); 223 assertNavigateOnSwipeFrom(RIGHT_EDGE, ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL); 224 } 225 226 @Test 227 @SmallTest testLeftEdgeSwipeClosesTabLaunchedFromLink()228 public void testLeftEdgeSwipeClosesTabLaunchedFromLink() { 229 Tab oldTab = currentTab(); 230 TabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false); 231 Tab newTab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> { 232 return tabCreator.createNewTab( 233 new LoadUrlParams(UrlConstants.RECENT_TABS_URL, PageTransition.LINK), 234 TabLaunchType.FROM_LINK, oldTab); 235 }); 236 Assert.assertEquals(newTab, currentTab()); 237 swipeFromEdge(LEFT_EDGE); 238 239 // Assert that the new tab was closed and the old tab is the current tab again. 240 CriteriaHelper.pollUiThread(() -> !newTab.isInitialized()); 241 Assert.assertEquals(oldTab, currentTab()); 242 Assert.assertEquals("Chrome should remain in foreground", ActivityState.RESUMED, 243 ApplicationStatus.getStateForActivity(mActivityTestRule.getActivity())); 244 } 245 246 @Test 247 @SmallTest 248 @DisabledTest(message = "https://crbug.com/1147553") testSwipeAfterDestroy()249 public void testSwipeAfterDestroy() { 250 mTestServer = EmbeddedTestServer.createAndStartServer( 251 InstrumentationRegistry.getInstrumentation().getContext()); 252 mActivityTestRule.loadUrl(mTestServer.getURL(RENDERED_PAGE)); 253 getNavigationCoordinator().destroy(); 254 255 // |triggerUi| can be invoked by SwipeRefreshHandler on the rendered 256 // page. Make sure this won't crash after the coordinator (and also 257 // handler action delegate) is destroyed. 258 Assert.assertFalse(mNavigationHandler.triggerUi(LEFT_EDGE, 0, 0)); 259 260 // Just check we're still on the same URL. 261 Assert.assertEquals(mTestServer.getURL(RENDERED_PAGE), 262 ChromeTabUtils.getUrlStringOnUiThread(currentTab())); 263 } 264 265 @Test 266 @SmallTest 267 @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) testEdgeSwipeIsNoopInTabSwitcher()268 public void testEdgeSwipeIsNoopInTabSwitcher() { 269 mActivityTestRule.loadUrl(UrlConstants.NTP_URL); 270 mActivityTestRule.loadUrl(UrlConstants.RECENT_TABS_URL); 271 setTabSwitcherModeAndWait(true); 272 swipeFromEdge(LEFT_EDGE); 273 Assert.assertTrue("Chrome should stay in tab switcher", 274 mActivityTestRule.getActivity().isInOverviewMode()); 275 setTabSwitcherModeAndWait(false); 276 Assert.assertEquals("Current page should not change", UrlConstants.RECENT_TABS_URL, 277 ChromeTabUtils.getUrlStringOnUiThread(currentTab())); 278 } 279 280 /** 281 * Enter or exit the tab switcher with animations and wait for the scene to change. 282 * @param inSwitcher Whether to enter or exit the tab switcher. 283 */ setTabSwitcherModeAndWait(boolean inSwitcher)284 private void setTabSwitcherModeAndWait(boolean inSwitcher) { 285 OverviewModeController controller = mActivityTestRule.getActivity().getLayoutManager(); 286 if (inSwitcher) { 287 TestThreadUtils.runOnUiThreadBlocking(() -> controller.showOverview(false)); 288 } else { 289 TestThreadUtils.runOnUiThreadBlocking(() -> controller.hideOverview(false)); 290 } 291 } 292 } 293