blob: 31ad417c789fa8f30de124e7a08b047b4d8bebe9 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL_SHORT_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_NON_DAYDREAM;
import android.graphics.PointF;
import android.os.Build;
import android.os.SystemClock;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.view.MotionEvent;
import android.view.View;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.vr.mock.MockVrDaydreamApi;
import org.chromium.chrome.browser.vr.rules.XrActivityRestriction;
import org.chromium.chrome.browser.vr.util.NativeUiUtils;
import org.chromium.chrome.browser.vr.util.PermissionUtils;
import org.chromium.chrome.browser.vr.util.VrShellDelegateUtils;
import org.chromium.chrome.browser.vr.util.VrTestRuleUtils;
import org.chromium.chrome.browser.vr.util.VrTransitionUtils;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.content_public.browser.ViewEventSink;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* End-to-end tests for sending input while using WebVR and WebXR.
*/
@RunWith(ParameterizedRunner.class)
@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
"enable-features=LogJsConsoleMessages", "enable-webvr"})
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) // WebVR and WebXR are only supported on L+
public class WebXrVrInputTest {
@ClassParameter
private static List<ParameterSet> sClassParams =
VrTestRuleUtils.generateDefaultTestRuleParameters();
@Rule
public RuleChain mRuleChain;
private ChromeActivityTestRule mTestRule;
private WebXrVrTestFramework mWebXrVrTestFramework;
private WebVrTestFramework mWebVrTestFramework;
public WebXrVrInputTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule);
}
@Before
public void setUp() throws Exception {
mWebXrVrTestFramework = new WebXrVrTestFramework(mTestRule);
mWebVrTestFramework = new WebVrTestFramework(mTestRule);
}
private void assertAppButtonEffect(boolean shouldHaveExited, WebXrVrTestFramework framework) {
String boolExpression = (framework instanceof WebVrTestFramework)
? "!vrDisplay.isPresenting"
: "sessionInfos[sessionTypes.IMMERSIVE].currentSession == null";
Assert.assertEquals("App button effect matched expectation", shouldHaveExited,
mWebXrVrTestFramework.pollJavaScriptBoolean(boolExpression, POLL_TIMEOUT_SHORT_MS));
}
/**
* Tests that screen touches are not registered when in VR. Disabled on standalones because
* they don't have touchscreens.
*/
@Test
@MediumTest
@DisableIf.
Build(message = "Flaky on K/L crbug.com/762126", sdk_is_less_than = Build.VERSION_CODES.M)
@Restriction(RESTRICTION_TYPE_SVR)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testScreenTapsNotRegistered() throws InterruptedException {
screenTapsNotRegisteredImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("test_screen_taps_not_registered"),
mWebVrTestFramework);
}
/**
* Tests that screen touches are not registered when in an immersive session. Disabled on
* standalones because they don't have touchscreens.
*/
@Test
@MediumTest
@DisableIf
.Build(message = "Flaky on K/L crbug.com/762126",
sdk_is_less_than = Build.VERSION_CODES.M)
@Restriction(RESTRICTION_TYPE_SVR)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void
testScreenTapsNotRegistered_WebXr() throws InterruptedException {
screenTapsNotRegisteredImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile(
"webxr_test_screen_taps_not_registered"),
mWebXrVrTestFramework);
}
private void screenTapsNotRegisteredImpl(String url, final WebXrVrTestFramework framework)
throws InterruptedException {
framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
framework.executeStepAndWait("stepVerifyNoInitialTaps()");
framework.enterSessionWithUserGestureOrFail();
VrTransitionUtils.waitForOverlayGone();
// Wait on VrShell to say that its parent consumed the touch event.
// Set to 2 because there's an ACTION_DOWN followed by ACTION_UP
final CountDownLatch touchRegisteredLatch = new CountDownLatch(2);
TestVrShellDelegate.getVrShellForTesting().setOnDispatchTouchEventForTesting(
new OnDispatchTouchEventCallback() {
@Override
public void onDispatchTouchEvent(boolean parentConsumed) {
if (!parentConsumed) Assert.fail("Parent did not consume event");
touchRegisteredLatch.countDown();
}
});
TouchCommon.singleClickView(mTestRule.getActivity().getWindow().getDecorView());
Assert.assertTrue("VrShell did not dispatch touches",
touchRegisteredLatch.await(POLL_TIMEOUT_LONG_MS * 10, TimeUnit.MILLISECONDS));
framework.executeStepAndWait("stepVerifyNoAdditionalTaps()");
framework.endTest();
}
/**
* Tests that Daydream controller clicks are registered as gamepad button pressed.
*/
@Test
@LargeTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testControllerClicksRegisteredOnDaydream() throws InterruptedException {
EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
mWebVrTestFramework.loadUrlAndAwaitInitialization(
WebVrTestFramework.getFileUrlForHtmlTestFile("test_gamepad_button"),
PAGE_LOAD_TIMEOUT_S);
// Wait to enter VR
mWebVrTestFramework.enterSessionWithUserGestureOrFail();
// The Gamepad API can flakily fail to detect the gamepad from a single button press, so
// spam it with button presses
boolean controllerConnected = false;
for (int i = 0; i < 10; i++) {
controller.performControllerClick();
if (mWebVrTestFramework.runJavaScriptOrFail("index != -1", POLL_TIMEOUT_SHORT_MS)
.equals("true")) {
controllerConnected = true;
break;
}
}
Assert.assertTrue("Gamepad API did not detect controller", controllerConnected);
// It's possible for input to get backed up if the emulated controller is being slow, so
// ensure that any outstanding output has been received before starting by waiting for
// 60 frames (1 second) of not receiving input.
mWebVrTestFramework.pollJavaScriptBooleanOrFail("isInputDrained()", POLL_TIMEOUT_LONG_MS);
// Have a separate start condition so that the above presses/releases don't get
// accidentally detected during the actual test
mWebVrTestFramework.runJavaScriptOrFail("canStartTest = true;", POLL_TIMEOUT_SHORT_MS);
// Send a controller click and wait for JavaScript to receive it.
controller.sendClickButtonToggleEvent();
mWebVrTestFramework.waitOnJavaScriptStep();
controller.sendClickButtonToggleEvent();
mWebVrTestFramework.waitOnJavaScriptStep();
mWebVrTestFramework.endTest();
}
/**
* Tests that Daydream controller clicks are registered as XR input in an immersive session.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testControllerClicksRegisteredOnDaydream_WebXr()
throws InterruptedException {
EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_input"),
PAGE_LOAD_TIMEOUT_S);
mWebXrVrTestFramework.enterSessionWithUserGestureOrFail();
int numIterations = 10;
mWebXrVrTestFramework.runJavaScriptOrFail(
"stepSetupListeners(" + String.valueOf(numIterations) + ")", POLL_TIMEOUT_SHORT_MS);
// Click the touchpad a bunch of times and make sure they're all registered.
for (int i = 0; i < numIterations; i++) {
controller.sendClickButtonToggleEvent();
controller.sendClickButtonToggleEvent();
// The controller emulation can sometimes deliver controller input at weird times such
// that we only register 8 or 9 of the 10 press/release pairs. So, send a press/release
// and wait for it to register before doing another.
mWebXrVrTestFramework.waitOnJavaScriptStep();
}
mWebXrVrTestFramework.endTest();
}
private long sendScreenTouchDown(final View view, final int x, final int y) {
long downTime = SystemClock.uptimeMillis();
TestThreadUtils.runOnUiThreadBlocking(() -> {
view.dispatchTouchEvent(
MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0));
});
return downTime;
}
private void sendScreenTouchUp(final View view, final int x, final int y, final long downTime) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
long now = SystemClock.uptimeMillis();
view.dispatchTouchEvent(
MotionEvent.obtain(downTime, now, MotionEvent.ACTION_UP, x, y, 0));
});
}
private void spamScreenTaps(final View view, final int x, final int y, final int iterations) {
// Tap the screen a bunch of times.
// Android doesn't seem to like sending touch events too quickly, so have a short delay
// between events.
for (int i = 0; i < iterations; i++) {
long downTime = sendScreenTouchDown(view, x, y);
SystemClock.sleep(100);
sendScreenTouchUp(view, x, y, downTime);
SystemClock.sleep(100);
}
}
/**
* Tests that screen touches are still registered when the viewer is Cardboard.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testScreenTapsRegisteredOnCardboard() throws InterruptedException {
mWebVrTestFramework.loadUrlAndAwaitInitialization(
WebVrTestFramework.getFileUrlForHtmlTestFile("test_gamepad_button"),
PAGE_LOAD_TIMEOUT_S);
// This boolean is used by testControllerClicksRegisteredOnDaydream to prevent some
// flakiness, but is unnecessary here, so set immediately
mWebVrTestFramework.runJavaScriptOrFail("canStartTest = true;", POLL_TIMEOUT_SHORT_MS);
// Wait to enter VR
mWebVrTestFramework.enterSessionWithUserGestureOrFail();
int x = mWebVrTestFramework.getCurrentContentView().getWidth() / 2;
int y = mWebVrTestFramework.getCurrentContentView().getHeight() / 2;
// TODO(mthiesse, https://crbug.com/758374): Injecting touch events into the root GvrLayout
// (VrShell) is flaky. Sometimes the events just don't get routed to the presentation
// view for no apparent reason. We should figure out why this is and see if it's fixable.
final View presentationView =
TestVrShellDelegate.getVrShellForTesting().getPresentationViewForTesting();
long downTime = sendScreenTouchDown(presentationView, x, y);
mWebVrTestFramework.waitOnJavaScriptStep();
sendScreenTouchUp(presentationView, x, y, downTime);
mWebVrTestFramework.waitOnJavaScriptStep();
mWebVrTestFramework.endTest();
}
/**
* Tests that screen touches are registered as XR input when the viewer is Cardboard.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testScreenTapsRegisteredOnCardboard_WebXr() throws InterruptedException {
mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_input"),
PAGE_LOAD_TIMEOUT_S);
mWebXrVrTestFramework.enterSessionWithUserGestureOrFail();
// Make it so that the webpage doesn't try to finish the JavaScript step after each input
// since we don't need to ack each one like with the Daydream controller.
mWebXrVrTestFramework.runJavaScriptOrFail(
"finishAfterEachInput = false", POLL_TIMEOUT_SHORT_MS);
int numIterations = 10;
mWebXrVrTestFramework.runJavaScriptOrFail(
"stepSetupListeners(" + String.valueOf(numIterations) + ")", POLL_TIMEOUT_SHORT_MS);
int x = mWebXrVrTestFramework.getCurrentContentView().getWidth() / 2;
int y = mWebXrVrTestFramework.getCurrentContentView().getHeight() / 2;
// TODO(mthiesse, https://crbug.com/758374): Injecting touch events into the root GvrLayout
// (VrShell) is flaky. Sometimes the events just don't get routed to the presentation
// view for no apparent reason. We should figure out why this is and see if it's fixable.
final View presentationView =
TestVrShellDelegate.getVrShellForTesting().getPresentationViewForTesting();
// Tap the screen a bunch of times and make sure that they're all registered.
spamScreenTaps(presentationView, x, y, numIterations);
mWebXrVrTestFramework.waitOnJavaScriptStep();
mWebXrVrTestFramework.endTest();
}
/**
* Tests that focus is locked to the presenting display for purposes of VR input.
*/
@Test
@MediumTest
@DisableIf.
Build(message = "K/M https://crbug.com/897259", sdk_is_less_than = Build.VERSION_CODES.N)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testPresentationLocksFocus() throws InterruptedException {
presentationLocksFocusImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("test_presentation_locks_focus"),
mWebVrTestFramework);
}
/**
* Tests that focus is locked to the device with an immersive session for the purposes of
* VR input.
*/
@Test
@MediumTest
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testPresentationLocksFocus_WebXr() throws InterruptedException {
presentationLocksFocusImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile(
"webxr_test_presentation_locks_focus"),
mWebXrVrTestFramework);
}
private void presentationLocksFocusImpl(String url, WebXrVrTestFramework framework)
throws InterruptedException {
framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
framework.enterSessionWithUserGestureOrFail();
framework.executeStepAndWait("stepSetupFocusLoss()");
framework.endTest();
}
/**
* Verifies that pressing the Daydream controller's 'app' button causes the user to exit
* WebVR presentation.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public void testAppButtonExitsPresentation() throws InterruptedException {
appButtonExitsPresentationImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("generic_webvr_page"),
mWebVrTestFramework);
}
/**
* Tests that pressing the Daydream controller's 'app' button causes the user to exit a
* WebXR immersive session.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
public void testAppButtonExitsPresentation_WebXr() throws InterruptedException {
appButtonExitsPresentationImpl(
WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
mWebXrVrTestFramework);
}
private void appButtonExitsPresentationImpl(String url, WebXrVrTestFramework framework)
throws InterruptedException {
framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
framework.enterSessionWithUserGestureOrFail();
NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
assertAppButtonEffect(true /* shouldHaveExited */, framework);
framework.assertNoJavaScriptErrors();
}
/**
* Verifies that pressing the Daydream controller's 'app' button does not cause the user to exit
* WebVR presentation when VR browsing is disabled.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testAppButtonNoopsWhenBrowsingDisabled()
throws InterruptedException, ExecutionException {
appButtonNoopsTestImpl(WebVrTestFramework.getFileUrlForHtmlTestFile("generic_webvr_page"),
mWebVrTestFramework);
}
/**
* Verifies that pressing the Daydream controller's 'app' button does not cause the user to exit
* WebVR presentation when VR browsing isn't supported by the Activity.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.WAA,
XrActivityRestriction.SupportedActivity.CCT})
public void
testAppButtonNoopsWhenBrowsingNotSupported() throws InterruptedException, ExecutionException {
appButtonNoopsTestImpl(WebVrTestFramework.getFileUrlForHtmlTestFile("generic_webvr_page"),
mWebVrTestFramework);
}
/**
* Verifies that pressing the Daydream controller's 'app' button does not cause the user to exit
* a WebXR immersive session when VR browsing is disabled.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
public void testAppButtonNoopsWhenBrowsingDisabled_WebXr()
throws InterruptedException, ExecutionException {
appButtonNoopsTestImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
mWebXrVrTestFramework);
}
/**
* Verifies that pressing the Daydream controller's 'app' button does not cause the user to exit
* a WebXR immersive session when VR browsing isn't supported by the Activity.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.WAA,
XrActivityRestriction.SupportedActivity.CCT})
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
public void
testAppButtonNoopsWhenBrowsingNotSupported_WebXr()
throws InterruptedException, ExecutionException {
appButtonNoopsTestImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
mWebXrVrTestFramework);
}
private void appButtonNoopsTestImpl(String url, WebXrVrTestFramework framework)
throws InterruptedException, ExecutionException {
VrShellDelegateUtils.getDelegateInstance().setVrBrowsingDisabled(true);
framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
framework.enterSessionWithUserGestureOrFail();
MockVrDaydreamApi mockApi = new MockVrDaydreamApi();
VrShellDelegateUtils.getDelegateInstance().overrideDaydreamApiForTesting(mockApi);
NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
Assert.assertFalse("App button left Chrome",
TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return mockApi.getExitFromVrCalled()
|| mockApi.getLaunchVrHomescreenCalled();
}
}));
assertAppButtonEffect(false /* shouldHaveExited */, framework);
VrShellDelegateUtils.getDelegateInstance().overrideDaydreamApiForTesting(null);
framework.assertNoJavaScriptErrors();
}
/**
* Tests that focus loss updates synchronously.
*/
@DisabledTest(message = "crbug.com/859666")
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testFocusUpdatesSynchronously() throws InterruptedException {
mWebVrTestFramework.loadUrlAndAwaitInitialization(
WebVrTestFramework.getFileUrlForHtmlTestFile(
"generic_webvr_page_with_activate_listener"),
PAGE_LOAD_TIMEOUT_S);
CriteriaHelper.pollUiThread(
()
-> {
return VrShellDelegateUtils.getDelegateInstance().isListeningForWebVrActivate();
},
"DisplayActivate was never registered", POLL_TIMEOUT_LONG_MS,
POLL_CHECK_INTERVAL_SHORT_MS);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ViewEventSink.from(mTestRule.getWebContents()).onPauseForTesting();
Assert.assertFalse(
"VR Shell is listening for headset insertion after WebContents paused",
VrShellDelegateUtils.getDelegateInstance().isListeningForWebVrActivate());
});
mWebVrTestFramework.assertNoJavaScriptErrors();
}
/**
* Verifies that pressing the Daydream controller's 'app' button causes the user to exit
* WebVR presentation even when the page is not submitting frames.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public void testAppButtonAfterPageStopsSubmitting() throws InterruptedException {
appButtonAfterPageStopsSubmittingImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("webvr_page_submits_once"),
mWebVrTestFramework);
}
/**
* Verifies that pressing the Daydream controller's 'app' button causes the user to exit
* a WebXR presentation even when the page is not submitting frames.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
public void testAppButtonAfterPageStopsSubmitting_WebXr() throws InterruptedException {
appButtonAfterPageStopsSubmittingImpl(
WebXrVrTestFramework.getFileUrlForHtmlTestFile("webxr_page_submits_once"),
mWebXrVrTestFramework);
}
private void appButtonAfterPageStopsSubmittingImpl(String url, WebXrVrTestFramework framework)
throws InterruptedException {
framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
framework.enterSessionWithUserGestureOrFail();
// Wait for page to stop submitting frames.
framework.waitOnJavaScriptStep();
NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
assertAppButtonEffect(true /* shouldHaveExited */, framework);
framework.assertNoJavaScriptErrors();
}
/**
* Verifies that a Gamepad API gamepad is returned on the XRSession's input
* source instead of the navigator array when using WebXR and a Daydream
* headset.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testWebXrInputSourceHasGamepad() throws InterruptedException {
webxrGamepadSupportImpl(true /* daydream */);
}
/**
* Verifies that the XRSession has an input source when using WebXR and
* Cardboard. There should be no gamepads on the input source or navigator
* array.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testWebXrInputSourceWithoutGamepad_Cardboard() throws InterruptedException {
webxrGamepadSupportImpl(false /* daydream */);
}
private void webxrGamepadSupportImpl(boolean daydream) throws InterruptedException {
mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_gamepad_support"),
PAGE_LOAD_TIMEOUT_S);
mWebXrVrTestFramework.enterSessionWithUserGestureOrFail();
// Spam input to make sure the Gamepad API registers the gamepad if it should.
int numIterations = 10;
if (daydream) {
EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
for (int i = 0; i < numIterations; i++) {
controller.performControllerClick();
}
// Verify that there is a gamepad on the XRInputSource and that it
// has the expected mapping of '' (the Daydream controller does not
// meet the 'xr-standard' mapping requirements).
mWebXrVrTestFramework.executeStepAndWait("validateMapping('')");
} else {
int x = mWebXrVrTestFramework.getCurrentContentView().getWidth() / 2;
int y = mWebXrVrTestFramework.getCurrentContentView().getHeight() / 2;
View presentationView =
TestVrShellDelegate.getVrShellForTesting().getPresentationViewForTesting();
spamScreenTaps(presentationView, x, y, numIterations);
mWebXrVrTestFramework.executeStepAndWait("validateInputSourceHasNoGamepad()");
}
mWebVrTestFramework.runJavaScriptOrFail("done()", POLL_TIMEOUT_SHORT_MS);
mWebXrVrTestFramework.endTest();
}
/**
* Tests that long pressing the app button shows a toast indicating which permissions are in
* use, and that it disappears at the correct time.
*/
@Test
@LargeTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testAppButtonLongPressDisplaysPermissions() throws InterruptedException {
testAppButtonLongPressDisplaysPermissionsImpl();
}
/**
* Tests that long pressing the app button shows a toast indicating which permissions are in
* use, and that it disappears at the correct time while in incognito mode.
*/
@Test
@LargeTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA})
public void testAppButtonLongPressDisplaysPermissionsIncognito()
throws InterruptedException {
mWebXrVrTestFramework.openIncognitoTab("about:blank");
testAppButtonLongPressDisplaysPermissionsImpl();
}
private void testAppButtonLongPressDisplaysPermissionsImpl() throws InterruptedException {
// Note that we need to pass in the WebContents to use throughout this because automatically
// using the first tab's WebContents doesn't work in Incognito.
mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
mWebXrVrTestFramework.getEmbeddedServerUrlForHtmlTestFile(
"generic_webxr_permission_page"),
PAGE_LOAD_TIMEOUT_S);
WebXrVrTestFramework.runJavaScriptOrFail("requestPermission({audio:true})",
POLL_TIMEOUT_SHORT_MS, mTestRule.getWebContents());
// Accept the permission prompt. Standalone devices need to be special cased since they
// will be in the VR Browser.
if (TestVrShellDelegate.isOnStandalone()) {
NativeUiUtils.enableMockedInput();
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.BROWSING_DIALOG, true /* visible */, () -> {});
NativeUiUtils.waitForUiQuiescence();
NativeUiUtils.clickFallbackUiPositiveButton();
} else {
PermissionUtils.waitForPermissionPrompt();
PermissionUtils.acceptPermissionPrompt();
}
WebXrVrTestFramework.waitOnJavaScriptStep(mTestRule.getWebContents());
mWebXrVrTestFramework.enterSessionWithUserGestureOrFail(mTestRule.getWebContents());
// The permission toasts automatically show for ~5 seconds when entering an immersive
// session, so wait for that to disappear
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {});
SystemClock.sleep(4500);
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, false /* visible */, () -> {});
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {
NativeUiUtils.pressAppButton(UserFriendlyElementName.NONE, new PointF());
});
// The toast should automatically disappear after ~5 second after the button is pressed,
// regardless of whether it's released or not.
SystemClock.sleep(1000);
NativeUiUtils.releaseAppButton(UserFriendlyElementName.NONE, new PointF());
SystemClock.sleep(3500);
// Make sure it's still present shortly before we expect it to disappear.
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {});
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, false /* visible */, () -> {});
// Do the same, but make sure the toast disappears even with the button still held.
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {
NativeUiUtils.pressAppButton(UserFriendlyElementName.NONE, new PointF());
});
SystemClock.sleep(4500);
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {});
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, false /* visible */, () -> {});
}
/**
* Tests that permission requests while in a WebXR for VR exclusive session work as expected.
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
// TODO(https://crbug.com/901494): Make this run everywhere when permissions are
// unbroken.
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA})
public void testInSessionPermissionRequests() throws InterruptedException {
testInSessionPermissionRequestsImpl();
}
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA})
public void testInSessionPermissionRequestsIncognito() throws InterruptedException {
mWebXrVrTestFramework.openIncognitoTab("about:blank");
testInSessionPermissionRequestsImpl();
}
private void testInSessionPermissionRequestsImpl() throws InterruptedException {
// Note that we need to pass in the WebContents to use throughout this because automatically
// using the first tab's WebContents doesn't work in Incognito.
mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
mWebXrVrTestFramework.getEmbeddedServerUrlForHtmlTestFile(
"generic_webxr_permission_page"),
PAGE_LOAD_TIMEOUT_S);
mWebXrVrTestFramework.enterSessionWithUserGestureOrFail(mTestRule.getWebContents());
NativeUiUtils.enableMockedInput();
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_HOSTED_CONTENT, true /* visible */, () -> {
WebXrVrTestFramework.runJavaScriptOrFail("requestPermission({audio:true})",
POLL_TIMEOUT_SHORT_MS, mTestRule.getWebContents());
});
NativeUiUtils.waitForUiQuiescence();
// Click outside the prompt and ensure that it gets dismissed.
NativeUiUtils.clickElement(
UserFriendlyElementName.WEB_XR_HOSTED_CONTENT, new PointF(0.55f, 0.0f));
WebXrVrTestFramework.waitOnJavaScriptStep(mTestRule.getWebContents());
// Accept the permission this time and ensure it propogates to the page.
NativeUiUtils.performActionAndWaitForVisibilityStatus(
UserFriendlyElementName.WEB_XR_HOSTED_CONTENT, true /* visible */, () -> {
WebXrVrTestFramework.runJavaScriptOrFail("requestPermission({audio:true})",
POLL_TIMEOUT_SHORT_MS, mTestRule.getWebContents());
});
NativeUiUtils.waitForUiQuiescence();
NativeUiUtils.clickElement(
UserFriendlyElementName.WEB_XR_HOSTED_CONTENT, new PointF(0.4f, -0.4f));
WebXrVrTestFramework.waitOnJavaScriptStep(mTestRule.getWebContents());
Assert.assertTrue("Could not grant permission while in WebXR immersive session",
WebXrVrTestFramework
.runJavaScriptOrFail("lastPermissionRequestSucceeded",
POLL_TIMEOUT_SHORT_MS, mTestRule.getWebContents())
.equals("true"));
}
}