1 // Copyright 2017 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.vr; 6 7 import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S; 8 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL_LONG_MS; 9 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL_SHORT_MS; 10 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS; 11 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS; 12 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM; 13 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR; 14 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE; 15 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VR_DON_ENABLED; 16 17 import android.graphics.Bitmap; 18 import android.graphics.BitmapFactory; 19 import android.graphics.Color; 20 import android.os.Build; 21 import android.support.test.InstrumentationRegistry; 22 import android.support.test.uiautomator.UiDevice; 23 24 import androidx.test.filters.MediumTest; 25 26 import org.junit.Assert; 27 import org.junit.Before; 28 import org.junit.Rule; 29 import org.junit.Test; 30 import org.junit.rules.RuleChain; 31 import org.junit.runner.RunWith; 32 33 import org.chromium.base.test.params.ParameterAnnotations.ClassParameter; 34 import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate; 35 import org.chromium.base.test.params.ParameterSet; 36 import org.chromium.base.test.params.ParameterizedRunner; 37 import org.chromium.base.test.util.CommandLineFlags; 38 import org.chromium.base.test.util.CriteriaHelper; 39 import org.chromium.base.test.util.Restriction; 40 import org.chromium.base.test.util.UrlUtils; 41 import org.chromium.chrome.browser.flags.ChromeFeatureList; 42 import org.chromium.chrome.browser.flags.ChromeSwitches; 43 import org.chromium.chrome.browser.vr.rules.XrActivityRestriction; 44 import org.chromium.chrome.browser.vr.util.NativeUiUtils; 45 import org.chromium.chrome.browser.vr.util.PermissionUtils; 46 import org.chromium.chrome.browser.vr.util.VrTestRuleUtils; 47 import org.chromium.chrome.browser.vr.util.VrTransitionUtils; 48 import org.chromium.chrome.test.ChromeActivityTestRule; 49 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; 50 import org.chromium.chrome.test.util.browser.Features.DisableFeatures; 51 52 import java.io.File; 53 import java.util.List; 54 import java.util.concurrent.Callable; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 58 /** 59 * End-to-end tests for transitioning between WebXR's magic window and 60 * presentation modes. 61 */ 62 @RunWith(ParameterizedRunner.class) 63 @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class) 64 @CommandLineFlags. 65 Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-features=LogJsConsoleMessages"}) 66 public class WebXrVrTransitionTest { 67 @ClassParameter 68 private static List<ParameterSet> sClassParams = 69 VrTestRuleUtils.generateDefaultTestRuleParameters(); 70 @Rule 71 public RuleChain mRuleChain; 72 73 private ChromeActivityTestRule mTestRule; 74 private WebXrVrTestFramework mWebXrVrTestFramework; 75 WebXrVrTransitionTest(Callable<ChromeActivityTestRule> callable)76 public WebXrVrTransitionTest(Callable<ChromeActivityTestRule> callable) throws Exception { 77 mTestRule = callable.call(); 78 mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule); 79 } 80 81 @Before setUp()82 public void setUp() { 83 mWebXrVrTestFramework = new WebXrVrTestFramework(mTestRule); 84 } 85 86 /** 87 * Tests that a successful request for an immersive session actually enters VR. 88 */ 89 @Test 90 @MediumTest 91 @CommandLineFlags.Add({"enable-features=WebXR"}) 92 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testRequestSessionEntersVr()93 public void testRequestSessionEntersVr() { 94 testPresentationEntryImpl("generic_webxr_page", mWebXrVrTestFramework); 95 } 96 testPresentationEntryImpl(String url, WebXrVrTestFramework framework)97 private void testPresentationEntryImpl(String url, WebXrVrTestFramework framework) { 98 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 99 framework.enterSessionWithUserGestureOrFail(); 100 Assert.assertTrue("Browser did not enter VR", VrShellDelegate.isInVr()); 101 102 // Verify that we're actually rendering WebXR/VR content and that it's blue (the canvas 103 // is set to blue while presenting). This could be a proper RenderTest, but it's less 104 // overhead to just directly check a pixel. 105 // TODO(https://crbug.com/947252): Run this part unconditionally once the cause of the 106 // flakiness on older devices is fixed. 107 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { 108 CriteriaHelper.pollInstrumentationThread( 109 () 110 -> { 111 // Creating temporary directories doesn't seem to work, so use a fixed 112 // location that we know we can write to. 113 File dumpDirectory = new File(UrlUtils.getIsolatedTestFilePath( 114 "chrome/test/data/vr/framebuffer_dumps")); 115 if (!dumpDirectory.exists() && !dumpDirectory.isDirectory()) { 116 Assert.assertTrue("Failed to make framebuffer dump directory", 117 dumpDirectory.mkdirs()); 118 } 119 File baseImagePath = new File(dumpDirectory, "dump"); 120 NativeUiUtils.dumpNextFramesFrameBuffers(baseImagePath.getPath()); 121 String filepath = baseImagePath.getPath() 122 + NativeUiUtils.FRAME_BUFFER_SUFFIX_WEB_XR_CONTENT + ".png"; 123 BitmapFactory.Options options = new BitmapFactory.Options(); 124 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 125 Bitmap bitmap = BitmapFactory.decodeFile(filepath, options); 126 return bitmap != null && Color.BLUE == bitmap.getPixel(0, 0); 127 }, 128 "Immersive session started, but browser not visibly in VR", 129 POLL_TIMEOUT_LONG_MS, POLL_CHECK_INTERVAL_LONG_MS); 130 } 131 132 framework.assertNoJavaScriptErrors(); 133 } 134 135 /** 136 * Tests that WebXR is not exposed if the flag is not on and the page does 137 * not have an origin trial token. 138 */ 139 @Test 140 @MediumTest 141 @CommandLineFlags 142 .Add({"disable-features=WebXR"}) 143 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testWebXrDisabledWithoutFlagSet()144 public void testWebXrDisabledWithoutFlagSet() { 145 apiDisabledWithoutFlagSetImpl( 146 "test_webxr_disabled_without_flag_set", mWebXrVrTestFramework); 147 } 148 apiDisabledWithoutFlagSetImpl(String url, WebXrVrTestFramework framework)149 private void apiDisabledWithoutFlagSetImpl(String url, WebXrVrTestFramework framework) { 150 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 151 framework.waitOnJavaScriptStep(); 152 framework.endTest(); 153 } 154 155 /** 156 * Tests that the immersive session promise doesn't resolve if the DON flow is 157 * not completed. 158 */ 159 @Test 160 @MediumTest 161 @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VR_DON_ENABLED}) 162 @CommandLineFlags.Add({"enable-features=WebXR"}) 163 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testPresentationPromiseUnresolvedDuringDon_WebXr()164 public void testPresentationPromiseUnresolvedDuringDon_WebXr() { 165 presentationPromiseUnresolvedDuringDonImpl( 166 167 "webxr_test_presentation_promise_unresolved_during_don", mWebXrVrTestFramework); 168 } 169 presentationPromiseUnresolvedDuringDonImpl( String url, WebXrVrTestFramework framework)170 private void presentationPromiseUnresolvedDuringDonImpl( 171 String url, WebXrVrTestFramework framework) { 172 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 173 framework.enterSessionWithUserGestureAndWait(); 174 framework.endTest(); 175 } 176 177 /** 178 * Tests that the immersive session promise is rejected if the DON flow is canceled. 179 */ 180 @Test 181 @MediumTest 182 @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VR_DON_ENABLED}) 183 @CommandLineFlags.Add({"enable-features=WebXR"}) 184 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testPresentationPromiseRejectedIfDonCanceled_WebXr()185 public void testPresentationPromiseRejectedIfDonCanceled_WebXr() { 186 presentationPromiseRejectedIfDonCanceledImpl( 187 188 "webxr_test_presentation_promise_rejected_if_don_canceled", mWebXrVrTestFramework); 189 } 190 presentationPromiseRejectedIfDonCanceledImpl( String url, WebXrVrTestFramework framework)191 private void presentationPromiseRejectedIfDonCanceledImpl( 192 String url, WebXrVrTestFramework framework) { 193 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 194 final UiDevice uiDevice = 195 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 196 framework.enterSessionWithUserGesture(); 197 // Wait until the DON flow appears to be triggered 198 // TODO(bsheedy): Make this less hacky if there's ever an explicit way to check if the 199 // DON flow is currently active https://crbug.com/758296 200 CriteriaHelper.pollUiThread(() -> { 201 String currentPackageName = uiDevice.getCurrentPackageName(); 202 return currentPackageName != null && currentPackageName.equals("com.google.vr.vrcore"); 203 }, "DON flow did not start", POLL_TIMEOUT_LONG_MS, POLL_CHECK_INTERVAL_SHORT_MS); 204 uiDevice.pressBack(); 205 framework.waitOnJavaScriptStep(); 206 framework.endTest(); 207 } 208 209 /** 210 * Tests that the omnibox reappears after exiting an immersive session. 211 */ 212 @Test 213 @MediumTest 214 @CommandLineFlags.Add({"enable-features=WebXR"}) 215 @Restriction(RESTRICTION_TYPE_SVR) testControlsVisibleAfterExitingVr_WebXr()216 public void testControlsVisibleAfterExitingVr_WebXr() throws InterruptedException { 217 controlsVisibleAfterExitingVrImpl("generic_webxr_page", mWebXrVrTestFramework); 218 } 219 controlsVisibleAfterExitingVrImpl(String url, final WebXrVrTestFramework framework)220 private void controlsVisibleAfterExitingVrImpl(String url, final WebXrVrTestFramework framework) 221 throws InterruptedException { 222 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 223 framework.enterSessionWithUserGestureOrFail(); 224 VrTransitionUtils.forceExitVr(); 225 // The hiding of the controls may only propagate after VR has exited, so give it a chance 226 // to propagate. In the worst case this test will erroneously pass, but should never 227 // erroneously fail, and should only be flaky if omnibox showing is broken. 228 Thread.sleep(100); 229 CriteriaHelper.pollUiThread(() 230 -> framework.getRule() 231 .getActivity() 232 .getBrowserControlsManager() 233 .getBrowserControlHiddenRatio() 234 == 0.0, 235 "Browser controls did not unhide after exiting VR", POLL_TIMEOUT_SHORT_MS, 236 POLL_CHECK_INTERVAL_SHORT_MS); 237 framework.assertNoJavaScriptErrors(); 238 } 239 240 /** 241 * Tests that window.requestAnimationFrame stops firing while in a WebXR immersive session, but 242 * resumes afterwards. 243 */ 244 @Test 245 @MediumTest 246 @CommandLineFlags.Add({"enable-features=WebXR"}) 247 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testWindowRafStopsFiringWhilePresenting_WebXr()248 public void testWindowRafStopsFiringWhilePresenting_WebXr() 249 throws InterruptedException { 250 windowRafStopsFiringWhilePresentingImpl( 251 252 "webxr_test_window_raf_stops_firing_during_immersive_session", 253 mWebXrVrTestFramework); 254 } 255 windowRafStopsFiringWhilePresentingImpl(String url, WebXrVrTestFramework framework)256 private void windowRafStopsFiringWhilePresentingImpl(String url, WebXrVrTestFramework framework) 257 throws InterruptedException { 258 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 259 framework.executeStepAndWait("stepVerifyBeforePresent()"); 260 // Pausing of window.rAF is done asynchronously, so wait until that's done. 261 final CountDownLatch vsyncPausedLatch = new CountDownLatch(1); 262 TestVrShellDelegate.getInstance().setVrShellOnVSyncPausedCallback( 263 () -> { vsyncPausedLatch.countDown(); }); 264 framework.enterSessionWithUserGestureOrFail(); 265 vsyncPausedLatch.await(POLL_TIMEOUT_SHORT_MS, TimeUnit.MILLISECONDS); 266 framework.executeStepAndWait("stepVerifyDuringPresent()"); 267 VrTransitionUtils.forceExitVr(); 268 framework.executeStepAndWait("stepVerifyAfterPresent()"); 269 framework.endTest(); 270 } 271 272 /** 273 * Tests renderer crashes while in WebXR presentation stay in VR. 274 */ 275 @Test 276 @MediumTest 277 @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE) 278 @CommandLineFlags.Add({"enable-features=WebXR"}) testRendererKilledInWebXrStaysInVr()279 public void testRendererKilledInWebXrStaysInVr() throws IllegalArgumentException { 280 rendererKilledInVrStaysInVrImpl("generic_webxr_page", mWebXrVrTestFramework); 281 } 282 rendererKilledInVrStaysInVrImpl(String url, WebXrVrTestFramework framework)283 private void rendererKilledInVrStaysInVrImpl(String url, WebXrVrTestFramework framework) { 284 framework.loadFileAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S); 285 framework.enterSessionWithUserGestureOrFail(); 286 framework.simulateRendererKilled(); 287 Assert.assertTrue("Browser did not enter VR", VrShellDelegate.isInVr()); 288 framework.assertNoJavaScriptErrors(); 289 } 290 291 /** 292 * Tests that window.rAF continues to fire when we have a non-immersive session. 293 */ 294 @Test 295 @MediumTest 296 @CommandLineFlags.Add({"enable-features=WebXR"}) 297 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testWindowRafFiresDuringNonImmersiveSession()298 public void testWindowRafFiresDuringNonImmersiveSession() { 299 mWebXrVrTestFramework.loadFileAndAwaitInitialization( 300 "test_window_raf_fires_during_non_immersive_session", PAGE_LOAD_TIMEOUT_S); 301 mWebXrVrTestFramework.waitOnJavaScriptStep(); 302 mWebXrVrTestFramework.endTest(); 303 } 304 305 /** 306 * Tests that non-immersive sessions stop receiving rAFs during an immersive session, but resume 307 * once the immersive session ends. 308 */ 309 @Test 310 @MediumTest 311 @CommandLineFlags.Add({"enable-features=WebXR"}) 312 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) 313 @DisableFeatures(ChromeFeatureList.SEND_TAB_TO_SELF) testNonImmersiveStopsDuringImmersive()314 public void testNonImmersiveStopsDuringImmersive() { 315 mWebXrVrTestFramework.loadFileAndAwaitInitialization( 316 "test_non_immersive_stops_during_immersive", PAGE_LOAD_TIMEOUT_S); 317 mWebXrVrTestFramework.executeStepAndWait("stepBeforeImmersive()"); 318 mWebXrVrTestFramework.enterSessionWithUserGestureOrFail(); 319 mWebXrVrTestFramework.executeStepAndWait("stepDuringImmersive()"); 320 VrTransitionUtils.forceExitVr(); 321 mWebXrVrTestFramework.executeStepAndWait("stepAfterImmersive()"); 322 mWebXrVrTestFramework.endTest(); 323 } 324 325 /** 326 * Tests that the "Press app button to exit" toast appears when entering an immersive WebXR for 327 * VR session with Daydream View paired. 328 */ 329 @Test 330 @MediumTest 331 @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE) 332 @CommandLineFlags.Add({"enable-features=WebXR"}) 333 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA}) testAppButtonExitToast()334 public void testAppButtonExitToast() { 335 mWebXrVrTestFramework.loadFileAndAwaitInitialization( 336 "generic_webxr_page", PAGE_LOAD_TIMEOUT_S); 337 mWebXrVrTestFramework.enterSessionWithUserGestureOrFail(); 338 NativeUiUtils.performActionAndWaitForVisibilityStatus( 339 UserFriendlyElementName.APP_BUTTON_EXIT_TOAST, true /* visible*/, () -> {}); 340 } 341 342 /** 343 * Tests that a permission prompt dismisses by itself when the page navigates away from 344 * the current page. 345 */ 346 @Test 347 @MediumTest 348 @CommandLineFlags.Add({"enable-features=WebXR"}) 349 @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL}) testConsentDialogIsDismissedWhenPageNavigatesAwayInMainFrame()350 public void testConsentDialogIsDismissedWhenPageNavigatesAwayInMainFrame() { 351 mWebXrVrTestFramework.setPermissionPromptAction( 352 WebXrVrTestFramework.PERMISSION_PROMPT_ACTION_DO_NOTHING); 353 mWebXrVrTestFramework.loadFileAndAwaitInitialization( 354 "generic_webxr_page", PAGE_LOAD_TIMEOUT_S); 355 mWebXrVrTestFramework.enterSessionWithUserGesture(); 356 mWebXrVrTestFramework.runJavaScriptOrFail( 357 "window.location.href = 'https://google.com'", POLL_TIMEOUT_SHORT_MS); 358 PermissionUtils.waitForPermissionPromptDismissal(); 359 } 360 } 361