Commit a3015a89 authored by bsheedy's avatar bsheedy Committed by Commit Bot

Add AR instrumentation test support

Adds the necessary changes to build and run
AR (augmented reality) tests in Monochrome.

Does not actually enable the test APK to be
built or run anywhere - that will be added
at a later time once additional work to
properly run the tests on bots is done, e.g.
installing the ArCore APK.

Bug: 851020
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_layout_tests_slimming_paint_v2;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:linux_vr;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I4148fcc6626ef79852f78f6019cc7c6f297f3e37
Reviewed-on: https://chromium-review.googlesource.com/1101958
Commit-Queue: Brian Sheedy <bsheedy@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarBrandon Jones <bajones@chromium.org>
Reviewed-by: default avataragrieve <agrieve@chromium.org>
Reviewed-by: default avatarMichael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/master@{#573599}
parent b342453f
...@@ -2807,6 +2807,8 @@ if (enable_java_templates) { ...@@ -2807,6 +2807,8 @@ if (enable_java_templates) {
# Generate incremental apk related operations at runtime. # Generate incremental apk related operations at runtime.
public_deps += _incremental_apk_operations public_deps += _incremental_apk_operations
} }
} else {
not_needed([ "_incremental_apk_operations" ])
} }
} }
...@@ -2852,13 +2854,18 @@ if (enable_java_templates) { ...@@ -2852,13 +2854,18 @@ if (enable_java_templates) {
template("instrumentation_test_apk") { template("instrumentation_test_apk") {
assert(defined(invoker.apk_name)) assert(defined(invoker.apk_name))
testonly = true testonly = true
_incremental_allowed =
!defined(invoker.never_incremental) || !invoker.never_incremental
_apk_target_name = "${target_name}__apk" _apk_target_name = "${target_name}__apk"
_test_runner_target_name = "${target_name}__test_runner_script" _test_runner_target_name = "${target_name}__test_runner_script"
_dist_ijar_path = _dist_ijar_path =
"$root_build_dir/test.lib.java/" + invoker.apk_name + ".jar" "$root_build_dir/test.lib.java/" + invoker.apk_name + ".jar"
_incremental_test_runner_target_name = if (_incremental_allowed) {
"${_test_runner_target_name}_incremental" _incremental_test_runner_target_name =
_incremental_test_name = "${invoker.target_name}_incremental" "${_test_runner_target_name}_incremental"
_incremental_test_name = "${invoker.target_name}_incremental"
}
if (incremental_apk_by_default) { if (incremental_apk_by_default) {
_incremental_test_runner_target_name = _test_runner_target_name _incremental_test_runner_target_name = _test_runner_target_name
_incremental_test_name = invoker.target_name _incremental_test_name = invoker.target_name
...@@ -2883,22 +2890,24 @@ if (enable_java_templates) { ...@@ -2883,22 +2890,24 @@ if (enable_java_templates) {
test_jar = _dist_ijar_path test_jar = _dist_ijar_path
} }
} }
test_runner_script(_incremental_test_runner_target_name) { if (_incremental_allowed) {
forward_variables_from(invoker, test_runner_script(_incremental_test_runner_target_name) {
[ forward_variables_from(invoker,
"additional_apks", [
"apk_under_test", "additional_apks",
"data", "apk_under_test",
"data_deps", "data",
"deps", "data_deps",
"ignore_all_data_deps", "deps",
"public_deps", "ignore_all_data_deps",
]) "public_deps",
test_name = _incremental_test_name ])
test_type = "instrumentation" test_name = _incremental_test_name
apk_target = ":$_apk_target_name" test_type = "instrumentation"
test_jar = _dist_ijar_path apk_target = ":$_apk_target_name"
incremental_install = true test_jar = _dist_ijar_path
incremental_install = true
}
} }
android_apk(_apk_target_name) { android_apk(_apk_target_name) {
...@@ -2981,14 +2990,16 @@ if (enable_java_templates) { ...@@ -2981,14 +2990,16 @@ if (enable_java_templates) {
} }
} }
group("${target_name}_incremental") { if (_incremental_allowed) {
public_deps = [ group("${target_name}_incremental") {
":$_incremental_test_runner_target_name", public_deps = [
":${_apk_target_name}_dist_ijar", ":$_incremental_test_runner_target_name",
":${_apk_target_name}_incremental", ":${_apk_target_name}_dist_ijar",
] ":${_apk_target_name}_incremental",
if (defined(invoker.apk_under_test)) { ]
public_deps += [ "${invoker.apk_under_test}_incremental" ] if (defined(invoker.apk_under_test)) {
public_deps += [ "${invoker.apk_under_test}_incremental" ]
}
} }
} }
} }
......
This diff is collapsed.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.chrome.tests"> package="org.chromium.chrome.tests">
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" /> <uses-sdk android:minSdkVersion="{{min_sdk_version}}" android:targetSdkVersion="{{target_sdk_version}}" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.READ_LOGS"/> <uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:label="ChromePublicTest"> android:label="{% block application_name %}ChromePublicTest{% endblock %}">
<uses-library android:name="android.test.runner" /> <uses-library android:name="android.test.runner" />
......
{% extends "chrome/android/javatests/AndroidManifest.xml" %}
## Copyright 2018 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.
## Note: This is a jinja2 template, processed at build time into the final manifest.
##
{% block application_name %}MonochromePublicTest{% endblock %}
\ No newline at end of file
...@@ -178,6 +178,7 @@ public class CastTestRule extends ChromeActivityTestRule<ChromeActivity> { ...@@ -178,6 +178,7 @@ public class CastTestRule extends ChromeActivityTestRule<ChromeActivity> {
return waitForStates(states, waitTimeMs); return waitForStates(states, waitTimeMs);
} }
@Override
public EmbeddedTestServer getTestServer() { public EmbeddedTestServer getTestServer() {
return mTestServer; return mTestServer;
} }
......
...@@ -61,6 +61,7 @@ public class NotificationTestRule extends ChromeActivityTestRule<ChromeTabbedAct ...@@ -61,6 +61,7 @@ public class NotificationTestRule extends ChromeActivityTestRule<ChromeTabbedAct
} }
/** Returns the test server. */ /** Returns the test server. */
@Override
public EmbeddedTestServer getTestServer() { public EmbeddedTestServer getTestServer() {
return mTestServer; return mTestServer;
} }
......
...@@ -209,6 +209,18 @@ public class TestFramework { ...@@ -209,6 +209,18 @@ public class TestFramework {
} }
} }
/**
* Helper function to make sure that the JavaScript test harness did not detect any failures.
* Similar to endTest, but does not fail if the test is still detected as running. This is
* useful because not all tests make use of the test harness' test/assert features (particularly
* simple enter/exit tests), but may still want to ensure that no unexpected JavaScript errors
* were encountered.
* @param webContents The Webcontents for the tab to check for failures in.
*/
public static void assertNoJavaScriptErrors(WebContents webContents) {
Assert.assertNotEquals(checkTestStatus(webContents), STATUS_FAILED);
}
/** /**
* Polls the provided JavaScript boolean until the timeout is reached or * Polls the provided JavaScript boolean until the timeout is reached or
* the boolean is true. * the boolean is true.
......
// Copyright 2018 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_shell;
import static org.chromium.chrome.browser.vr_shell.TestFramework.PAGE_LOAD_TIMEOUT_S;
import static org.chromium.chrome.browser.vr_shell.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import org.junit.After;
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.MinAndroidSdkLevel;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.vr_shell.rules.VrActivityRestriction;
import org.chromium.chrome.browser.vr_shell.util.VrTestRuleUtils;
import org.chromium.chrome.browser.vr_shell.util.XrTransitionUtils;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.net.test.EmbeddedTestServer;
import java.util.List;
import java.util.concurrent.Callable;
/**
* End-to-end tests for testing WebXR for AR's requestSession behavior.
*/
@RunWith(ParameterizedRunner.class)
@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@CommandLineFlags.
Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-features=WebXR,WebXRHitTest"})
@MinAndroidSdkLevel(Build.VERSION_CODES.N) // WebXR for AR is only supported on N+
public class WebXrArSessionTest {
@ClassParameter
private static List<ParameterSet> sClassParams =
VrTestRuleUtils.generateDefaultVrTestRuleParameters();
@Rule
public RuleChain mRuleChain;
private ChromeActivityTestRule mTestRule;
private XrTestFramework mXrTestFramework;
private EmbeddedTestServer mServer;
private boolean mShouldCreateServer;
public WebXrArSessionTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInVrActivityRestrictionRule(mTestRule);
}
@Before
public void setUp() throws Exception {
mXrTestFramework = new XrTestFramework(mTestRule);
// WebappActivityTestRule automatically creates a test server, and creating multiple causes
// it to crash hitting a DCHECK. So, only handle the server ourselves if whatever test rule
// we're using doesn't create one itself.
mServer = mTestRule.getTestServer();
if (mServer == null) {
mShouldCreateServer = true;
mServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
}
}
@After
public void tearDown() throws Exception {
if (mServer != null && mShouldCreateServer) {
mServer.stopAndDestroyServer();
}
}
/**
* Tests that a session request for AR succeeds.
*/
@Test
@MediumTest
@VrActivityRestriction({VrActivityRestriction.SupportedActivity.ALL})
public void testArRequestSessionSucceeds() throws InterruptedException {
mXrTestFramework.loadUrlAndAwaitInitialization(
mServer.getURL(XrTestFramework.getEmbeddedServerPathForHtmlTestFile(
"test_ar_request_session_succeeds")),
PAGE_LOAD_TIMEOUT_S);
XrTransitionUtils.enterArSessionOrFail(mXrTestFramework.getFirstTabWebContents());
XrTestFramework.assertNoJavaScriptErrors(mXrTestFramework.getFirstTabWebContents());
}
/**
* Tests that repeatedly starting and stopping AR sessions does not cause any unexpected
* behavior. Regression test for https://crbug.com/837894.
*/
@Test
@MediumTest
@VrActivityRestriction({VrActivityRestriction.SupportedActivity.ALL})
public void testRepeatedArSessionsSucceed() throws InterruptedException {
mXrTestFramework.loadUrlAndAwaitInitialization(
mServer.getURL(XrTestFramework.getEmbeddedServerPathForHtmlTestFile(
"test_ar_request_session_succeeds")),
PAGE_LOAD_TIMEOUT_S);
for (int i = 0; i < 2; i++) {
XrTransitionUtils.enterArSessionOrFail(mXrTestFramework.getFirstTabWebContents());
XrTransitionUtils.endArSession(mXrTestFramework.getFirstTabWebContents());
}
XrTestFramework.assertNoJavaScriptErrors(mXrTestFramework.getFirstTabWebContents());
}
/**
* Tests that repeated calls to requestSession on the same page only prompts the user for
* camera permissions once.
*/
@Test
@MediumTest
@VrActivityRestriction({VrActivityRestriction.SupportedActivity.ALL})
public void testRepeatedArSessionsOnlyPromptPermissionsOnce() throws InterruptedException {
mXrTestFramework.loadUrlAndAwaitInitialization(
mServer.getURL(XrTestFramework.getEmbeddedServerPathForHtmlTestFile(
"test_ar_request_session_succeeds")),
PAGE_LOAD_TIMEOUT_S);
Assert.assertTrue(XrTransitionUtils.arSessionRequestWouldTriggerPermissionPrompt(
mXrTestFramework.getFirstTabWebContents()));
XrTransitionUtils.enterArSessionOrFail(mXrTestFramework.getFirstTabWebContents());
XrTransitionUtils.endArSession(mXrTestFramework.getFirstTabWebContents());
// Manually run through the same steps as enterArSessionOrFail so that we don't trigger
// its automatic permission acceptance.
Assert.assertFalse(XrTransitionUtils.arSessionRequestWouldTriggerPermissionPrompt(
mXrTestFramework.getFirstTabWebContents()));
XrTransitionUtils.enterPresentation(mXrTestFramework.getFirstTabWebContents());
Assert.assertTrue(XrTestFramework.pollJavaScriptBoolean(
"sessionInfos[sessionTypes.AR].currentSession != null", POLL_TIMEOUT_LONG_MS,
mXrTestFramework.getFirstTabWebContents()));
}
}
\ No newline at end of file
...@@ -121,7 +121,6 @@ public class TransitionUtils { ...@@ -121,7 +121,6 @@ public class TransitionUtils {
* JavaScript step to finish. * JavaScript step to finish.
* *
* Only meant to be used alongside the test framework from VrTestFramework. * Only meant to be used alongside the test framework from VrTestFramework.
* @param cvc The ContentViewCore for the tab the canvas is in.
* @param webContents The WebContents for the tab the JavaScript step is in. * @param webContents The WebContents for the tab the JavaScript step is in.
*/ */
public static void enterPresentationAndWait(WebContents webContents) { public static void enterPresentationAndWait(WebContents webContents) {
......
...@@ -5,20 +5,23 @@ ...@@ -5,20 +5,23 @@
package org.chromium.chrome.browser.vr_shell.util; package org.chromium.chrome.browser.vr_shell.util;
import static org.chromium.chrome.browser.vr_shell.XrTestFramework.POLL_TIMEOUT_LONG_MS; import static org.chromium.chrome.browser.vr_shell.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr_shell.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import android.content.DialogInterface;
import org.junit.Assert; import org.junit.Assert;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.permissions.PermissionDialogController;
import org.chromium.chrome.browser.vr_shell.TestVrShellDelegate; import org.chromium.chrome.browser.vr_shell.TestVrShellDelegate;
import org.chromium.chrome.browser.vr_shell.XrTestFramework; import org.chromium.chrome.browser.vr_shell.XrTestFramework;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
/** /**
* Class containing utility functions for transitioning between different * Class containing utility functions for transitioning between different
* states in VR, such as fullscreen, WebVR presentation, and the VR browser. * states in WebXR, e.g. entering exclusive or AR sessions.
*
* All the transitions in this class are performed directly through Chrome,
* as opposed to NFC tag simulation which involves receiving an intent from
* an outside application (VR Services).
*/ */
public class XrTransitionUtils extends TransitionUtils { public class XrTransitionUtils extends TransitionUtils {
/** /**
...@@ -26,9 +29,72 @@ public class XrTransitionUtils extends TransitionUtils { ...@@ -26,9 +29,72 @@ public class XrTransitionUtils extends TransitionUtils {
* the two APIs. * the two APIs.
*/ */
public static void enterPresentationOrFail(WebContents webContents) { public static void enterPresentationOrFail(WebContents webContents) {
XrTestFramework.runJavaScriptOrFail(
"sessionTypeToRequest = sessionTypes.EXCLUSIVE", POLL_TIMEOUT_LONG_MS, webContents);
enterPresentation(webContents); enterPresentation(webContents);
Assert.assertTrue(XrTestFramework.pollJavaScriptBoolean( Assert.assertTrue(XrTestFramework.pollJavaScriptBoolean(
"exclusiveSession != null", POLL_TIMEOUT_LONG_MS, webContents)); "sessionInfos[sessionTypes.EXCLUSIVE].currentSession != null", POLL_TIMEOUT_LONG_MS,
webContents));
Assert.assertTrue(TestVrShellDelegate.getVrShellForTesting().getWebVrModeEnabled()); Assert.assertTrue(TestVrShellDelegate.getVrShellForTesting().getWebVrModeEnabled());
} }
/**
* Requests that WebXR start an AR session, failing the test if it does not succeed.
* @param webContents The WebContents to run start the AR session in.
*/
public static void enterArSessionOrFail(WebContents webContents) {
XrTestFramework.runJavaScriptOrFail(
"sessionTypeToRequest = sessionTypes.AR", POLL_TIMEOUT_LONG_MS, webContents);
// Requesting an AR session for the first time on a page will always prompt for camera
// permissions, but not on subsequent requests, so check to see if we'll need to accept it
// after requesting the session.
boolean expectPermissionPrompt = arSessionRequestWouldTriggerPermissionPrompt(webContents);
// TODO(bsheedy): Rename enterPresentation since it's used for both presentation and AR?
enterPresentation(webContents);
if (expectPermissionPrompt) {
// Wait for the permission prompt to appear.
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return PermissionDialogController.getInstance().getCurrentDialogForTesting()
!= null;
}
});
// Accept the permission prompt.
ThreadUtils.runOnUiThreadBlocking(() -> {
PermissionDialogController.getInstance()
.getCurrentDialogForTesting()
.getButton(DialogInterface.BUTTON_POSITIVE)
.performClick();
});
}
Assert.assertTrue(XrTestFramework.pollJavaScriptBoolean(
"sessionInfos[sessionTypes.AR].currentSession != null", POLL_TIMEOUT_LONG_MS,
webContents));
}
/**
* Ends the current AR session.
* @param webContents The WebContents to end the AR session in.
*/
public static void endArSession(WebContents webContents) {
XrTestFramework.runJavaScriptOrFail("sessionInfos[sessionTypes.AR].currentSession.end()",
POLL_TIMEOUT_SHORT_MS, webContents);
}
/**
* Checks whether an AR session request would trigger a Camera permission prompt.
* @param webContents The WebContents to check permissions in.
* @return True if an AR session request will cause a permission prompt, false otherwise.
*/
public static boolean arSessionRequestWouldTriggerPermissionPrompt(WebContents webContents) {
XrTestFramework.runJavaScriptOrFail("checkIfArSessionWouldTriggerPermissionPrompt()",
POLL_TIMEOUT_SHORT_MS, webContents);
Assert.assertTrue(XrTestFramework.pollJavaScriptBoolean(
"arSessionRequestWouldTriggerPermissionPrompt !== null", POLL_TIMEOUT_SHORT_MS,
webContents));
return Boolean.valueOf(
XrTestFramework.runJavaScriptOrFail("arSessionRequestWouldTriggerPermissionPrompt",
POLL_TIMEOUT_SHORT_MS, webContents));
}
} }
...@@ -80,6 +80,7 @@ public class WebappActivityTestRule extends ChromeActivityTestRule<WebappActivit ...@@ -80,6 +80,7 @@ public class WebappActivityTestRule extends ChromeActivityTestRule<WebappActivit
super(WebappActivity0.class); super(WebappActivity0.class);
} }
@Override
public EmbeddedTestServer getTestServer() { public EmbeddedTestServer getTestServer() {
return mTestServerRule.getServer(); return mTestServerRule.getServer();
} }
......
...@@ -24,19 +24,23 @@ void XrBrowserTestBase::EnterPresentation(content::WebContents* web_contents) { ...@@ -24,19 +24,23 @@ void XrBrowserTestBase::EnterPresentation(content::WebContents* web_contents) {
void XrBrowserTestBase::EnterPresentationOrFail( void XrBrowserTestBase::EnterPresentationOrFail(
content::WebContents* web_contents) { content::WebContents* web_contents) {
EnterPresentation(web_contents); EnterPresentation(web_contents);
EXPECT_TRUE(PollJavaScriptBoolean("exclusiveSession!= null", kPollTimeoutLong, EXPECT_TRUE(PollJavaScriptBoolean(
web_contents)); "sessionInfos[sessionTypes.EXCLUSIVE].currentSession != null",
kPollTimeoutLong, web_contents));
} }
void XrBrowserTestBase::ExitPresentation(content::WebContents* web_contents) { void XrBrowserTestBase::ExitPresentation(content::WebContents* web_contents) {
EXPECT_TRUE(content::ExecuteScript(web_contents, "exclusiveSession.end()")); EXPECT_TRUE(content::ExecuteScript(
web_contents,
"sessionInfos[sessionTypes.EXCLUSIVE].currentSession.end()"));
} }
void XrBrowserTestBase::ExitPresentationOrFail( void XrBrowserTestBase::ExitPresentationOrFail(
content::WebContents* web_contents) { content::WebContents* web_contents) {
ExitPresentation(web_contents); ExitPresentation(web_contents);
EXPECT_TRUE(PollJavaScriptBoolean("exclusiveSession == null", EXPECT_TRUE(PollJavaScriptBoolean(
kPollTimeoutLong, web_contents)); "sessionInfos[sessionTypes.EXCLUSIVE].currentSession == null",
kPollTimeoutLong, web_contents));
} }
} // namespace vr } // namespace vr
...@@ -56,6 +56,7 @@ import org.chromium.content.browser.test.util.JavaScriptUtils; ...@@ -56,6 +56,7 @@ import org.chromium.content.browser.test.util.JavaScriptUtils;
import org.chromium.content.browser.test.util.RenderProcessLimit; import org.chromium.content.browser.test.util.RenderProcessLimit;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.PageTransition;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
...@@ -621,6 +622,13 @@ public class ChromeActivityTestRule<T extends ChromeActivity> extends ActivityTe ...@@ -621,6 +622,13 @@ public class ChromeActivityTestRule<T extends ChromeActivity> extends ActivityTe
return mCurrentTestName; return mCurrentTestName;
} }
/**
* Gets the ChromeActivityTestRule's EmbeddedTestServer instance if it has one.
*/
public EmbeddedTestServer getTestServer() {
return null;
}
/** /**
* @return {@link WebContents} of the active tab of the activity. * @return {@link WebContents} of the active tab of the activity.
*/ */
......
<!doctype html>
<!--
Tests that AR requestSessions succeed.
-->
<html>
<head>
<link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
</head>
<body>
<canvas id="webgl-canvas"></canvas>
<script src="../../../../../../third_party/WebKit/LayoutTests/resources/testharness.js"></script>
<script src="../resources/webxr_e2e.js"></script>
<script>var shouldAutoCreateNonExclusiveSession = false;</script>
<script src="../resources/webxr_boilerplate.js"></script>
</body>
</html>
...@@ -21,7 +21,7 @@ is active, but resumes afterwards. ...@@ -21,7 +21,7 @@ is active, but resumes afterwards.
// Verify that we call a rAF once, then make sure any subsequent calls // Verify that we call a rAF once, then make sure any subsequent calls
// are not done while there is an exclusive session. // are not done while there is an exclusive session.
onMagicWindowXRFrameCallback = function() { onMagicWindowXRFrameCallback = function() {
if (exclusiveSession !== null) { if (sessionInfos[sessionTypes.EXCLUSIVE].currentSession !== null) {
t.step( () => { t.step( () => {
assert_unreached( assert_unreached(
"Non-exclusive rAF called during exclusive session"); "Non-exclusive rAF called during exclusive session");
......
...@@ -62,9 +62,10 @@ that Daydream controller input is registered when using Daydream View. ...@@ -62,9 +62,10 @@ that Daydream controller input is registered when using Daydream View.
function stepSetupListeners(numIterations) { function stepSetupListeners(numIterations) {
iterations = numIterations; iterations = numIterations;
exclusiveSession.addEventListener('selectstart', onSelectStart, false); let currentSession = sessionInfos[sessionTypes.EXCLUSIVE].currentSession;
exclusiveSession.addEventListener('selectend', onSelectEnd, false); currentSession.addEventListener('selectstart', onSelectStart, false);
exclusiveSession.addEventListener('select', onSelect, false); currentSession.addEventListener('selectend', onSelectEnd, false);
currentSession.addEventListener('select', onSelect, false);
} }
</script> </script>
</body> </body>
......
...@@ -18,6 +18,9 @@ test can query for whether each submitted frame used the correct pose. ...@@ -18,6 +18,9 @@ test can query for whether each submitted frame used the correct pose.
var t = async_test("Pose data is correct"); var t = async_test("Pose data is correct");
var frame_id = 0; var frame_id = 0;
var frame_data_array = {}; var frame_data_array = {};
// We exit presentation before checking stuff that needs the frame of
// reference, so we need to cache its value.
var cached_frame_of_ref = null;
function FloatCompare(a, b) { function FloatCompare(a, b) {
return Math.abs(a-b) < 0.001; return Math.abs(a-b) < 0.001;
...@@ -40,13 +43,13 @@ test can query for whether each submitted frame used the correct pose. ...@@ -40,13 +43,13 @@ test can query for whether each submitted frame used the correct pose.
function checkFrameView(frame_id, eye, expected) { function checkFrameView(frame_id, eye, expected) {
let frame_data = frame_data_array[frame_id]; let frame_data = frame_data_array[frame_id];
let pose = frame_data.getDevicePose(exclusiveFrameOfRef); let pose = frame_data.getDevicePose(cached_frame_of_ref);
return MatrixCompare(pose.getViewMatrix(frame_data_array[frame_id].views[eye]), expected); return MatrixCompare(pose.getViewMatrix(frame_data_array[frame_id].views[eye]), expected);
} }
function checkFramePose(frame_id, expected) { function checkFramePose(frame_id, expected) {
let frame_data = frame_data_array[frame_id]; let frame_data = frame_data_array[frame_id];
let pose = frame_data.getDevicePose(exclusiveFrameOfRef); let pose = frame_data.getDevicePose(cached_frame_of_ref);
if (!pose) { if (!pose) {
// We can intermittently get null poses. For now treat them as passing, // We can intermittently get null poses. For now treat them as passing,
// even though this should be fixed. // even though this should be fixed.
...@@ -63,6 +66,7 @@ test can query for whether each submitted frame used the correct pose. ...@@ -63,6 +66,7 @@ test can query for whether each submitted frame used the correct pose.
// Encode an index into the clear color. // Encode an index into the clear color.
frame_id++; frame_id++;
frame_data_array[frame_id] = frame; frame_data_array[frame_id] = frame;
cached_frame_of_ref = sessionInfos[sessionTypes.EXCLUSIVE].currentFrameOfRef;
var encoded_frame_id = {}; var encoded_frame_id = {};
encoded_frame_id.r = frame_id % 256; encoded_frame_id.r = frame_id % 256;
......
...@@ -27,7 +27,7 @@ Tests that WebXR doesn't update frame data when the tab is not focused ...@@ -27,7 +27,7 @@ Tests that WebXR doesn't update frame data when the tab is not focused
return; return;
} }
onMagicWindowXRFrameCallback = null; onMagicWindowXRFrameCallback = null;
pose = frame.getDevicePose(magicWindowFrameOfRef); pose = frame.getDevicePose(sessionInfos[sessionTypes.MAGIC_WINDOW].currentFrameOfRef);
t.step( () => { t.step( () => {
assert_true(pose != null, assert_true(pose != null,
"getDevicePose returned a non-null object"); "getDevicePose returned a non-null object");
......
...@@ -22,17 +22,81 @@ var onMagicWindowXRFrameCallback = null; ...@@ -22,17 +22,81 @@ var onMagicWindowXRFrameCallback = null;
var onExclusiveXRFrameCallback = null; var onExclusiveXRFrameCallback = null;
var shouldSubmitFrame = true; var shouldSubmitFrame = true;
var hasPresentedFrame = false; var hasPresentedFrame = false;
var arSessionRequestWouldTriggerPermissionPrompt = null;
var magicWindowSession = null; var sessionTypes = Object.freeze({
var magicWindowFrameOfRef = null; EXCLUSIVE: 1,
var exclusiveSession = null; AR: 2,
var exclusiveFrameOfRef = null; MAGIC_WINDOW: 3
});
var sessionTypeToRequest = sessionTypes.EXCLUSIVE;
class SessionInfo {
constructor() {
this.session = null;
this.frameOfRef = null;
}
get currentSession() {
return this.session;
}
get currentFrameOfRef() {
return this.frameOfRef;
}
set currentSession(session) {
this.session = session;
}
set currentFrameOfRef(frameOfRef) {
this.frameOfRef = frameOfRef;
}
clearSession() {
this.session = null;
this.frameOfRef = null;
}
}
var sessionInfos = {}
sessionInfos[sessionTypes.EXCLUSIVE] = new SessionInfo();
sessionInfos[sessionTypes.AR] = new SessionInfo();
sessionInfos[sessionTypes.MAGIC_WINDOW] = new SessionInfo();
function getSessionType(session) {
if (session.exclusive) {
return sessionTypes.EXCLUSIVE;
} else if (sessionInfos[sessionTypes.AR].currentSession == session) {
// TODO(bsheedy): Replace this check if there's ever something like
// session.ar for checking if the session is AR-capable.
return sessionTypes.AR;
} else {
return sessionTypes.MAGIC_WINDOW;
}
}
function onRequestSession() { function onRequestSession() {
xrDevice.requestSession({exclusive: true}).then( (session) => { switch (sessionTypeToRequest) {
exclusiveSession = session; case sessionTypes.EXCLUSIVE:
onSessionStarted(session); xrDevice.requestSession({exclusive: true}).then( (session) => {
}); sessionInfos[sessionTypes.EXCLUSIVE].currentSession = session;
onSessionStarted(session);
});
break;
case sessionTypes.AR:
let sessionOptions = {
requestAR: true,
outputContext: webglCanvas.getContext('xrpresent'),
};
xrDevice.requestSession(sessionOptions).then((session) => {
sessionInfos[sessionTypes.AR].currentSession = session;
onSessionStarted(session);
});
break;
default:
throw 'Given unsupported WebXR session type enum ' + sessionTypeToRequest;
}
} }
function onSessionStarted(session) { function onSessionStarted(session) {
...@@ -45,7 +109,7 @@ function onSessionStarted(session) { ...@@ -45,7 +109,7 @@ function onSessionStarted(session) {
let offscreenCanvas = document.createElement('canvas'); let offscreenCanvas = document.createElement('canvas');
gl = offscreenCanvas.getContext('webgl', glAttribs); gl = offscreenCanvas.getContext('webgl', glAttribs);
if (!gl) { if (!gl) {
console.error('Failed to get WebGL context'); throw 'Failed to get WebGL context';
} }
gl.clearColor(0.0, 1.0, 0.0, 1.0); gl.clearColor(0.0, 1.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST); gl.enable(gl.DEPTH_TEST);
...@@ -55,28 +119,21 @@ function onSessionStarted(session) { ...@@ -55,28 +119,21 @@ function onSessionStarted(session) {
session.baseLayer = new XRWebGLLayer(session, gl); session.baseLayer = new XRWebGLLayer(session, gl);
session.requestFrameOfReference('eye-level').then( (frameOfRef) => { session.requestFrameOfReference('eye-level').then( (frameOfRef) => {
if (session.exclusive) { sessionInfos[getSessionType(session)].currentFrameOfRef = frameOfRef;
exclusiveFrameOfRef = frameOfRef;
} else {
magicWindowFrameOfRef = frameOfRef;
}
session.requestAnimationFrame(onXRFrame); session.requestAnimationFrame(onXRFrame);
}); });
} }
function onSessionEnded(event) { function onSessionEnded(event) {
if (event.session.exclusive) sessionInfos[getSessionType(event.session)].clearSession();
exclusiveSession = null
else
magicWindowSession = null;
} }
function onXRFrame(t, frame) { function onXRFrame(t, frame) {
let session = frame.session; let session = frame.session;
session.requestAnimationFrame(onXRFrame); session.requestAnimationFrame(onXRFrame);
let frameOfRef = session.exclusive ?
exclusiveFrameOfRef : let frameOfRef = null;
magicWindowFrameOfRef; frameOfRef = sessionInfos[getSessionType(session)].currentFrameOfRef;
let pose = frame.getDevicePose(frameOfRef); let pose = frame.getDevicePose(frameOfRef);
// Exiting the rAF callback without dirtying the GL context is interpreted // Exiting the rAF callback without dirtying the GL context is interpreted
...@@ -87,22 +144,40 @@ function onXRFrame(t, frame) { ...@@ -87,22 +144,40 @@ function onXRFrame(t, frame) {
gl.bindFramebuffer(gl.FRAMEBUFFER, session.baseLayer.framebuffer); gl.bindFramebuffer(gl.FRAMEBUFFER, session.baseLayer.framebuffer);
// If in an exclusive session, set canvas to blue. Otherwise, red. // If in an exclusive session, set canvas to blue.
if (session.exclusive) { // If in an AR session, just draw the camera.
gl.clearColor(0.0, 0.0, 1.0, 1.0); // Otherwise, red.
if (onExclusiveXRFrameCallback) { switch (getSessionType(session)) {
onExclusiveXRFrameCallback(session, frame, gl); case sessionTypes.EXCLUSIVE:
} gl.clearColor(0.0, 0.0, 1.0, 1.0);
} else { if (onExclusiveXRFrameCallback) {
if (onMagicWindowXRFrameCallback) { onExclusiveXRFrameCallback(session, frame, gl);
onMagicWindowXRFrameCallback(session, frame); }
} gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.clearColor(1.0, 0.0, 0.0, 1.0); break;
case sessionTypes.AR:
// Do nothing for now
break;
default:
if (onMagicWindowXRFrameCallback) {
onMagicWindowXRFrameCallback(session, frame);
}
gl.clearColor(1.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
} }
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
hasPresentedFrame = true; hasPresentedFrame = true;
} }
function checkIfArSessionWouldTriggerPermissionPrompt() {
arSessionRequestWouldTriggerPermissionPrompt = null;
navigator.permissions.query({name: 'camera'}).then( (permission) => {
arSessionRequestWouldTriggerPermissionPrompt = permission.state == 'prompt';
}, () => {
throw 'Permission query rejected';
});
}
// Try to get an XRDevice and set up a non-exclusive session with it // Try to get an XRDevice and set up a non-exclusive session with it
if (navigator.xr) { if (navigator.xr) {
navigator.xr.requestDevice().then( (device) => { navigator.xr.requestDevice().then( (device) => {
...@@ -113,11 +188,22 @@ if (navigator.xr) { ...@@ -113,11 +188,22 @@ if (navigator.xr) {
// Set up the device to have a non-exclusive session (magic window) drawing // Set up the device to have a non-exclusive session (magic window) drawing
// into the full screen canvas on the page // into the full screen canvas on the page
let ctx = webglCanvas.getContext('xrpresent'); let ctx = webglCanvas.getContext('xrpresent');
device.requestSession({outputContext: ctx}).then( (session) => { // WebXR for VR tests want a non-exclusive session to be automatically
onSessionStarted(session); // created on page load to reduce the amount of boilerplate code necessary.
}).then( () => { // However, doing so during AR tests currently fails due to AR sessions
// always requiring a user gesture. So, allow a page to set a variable
// before loading this JavaScript file if they wish to skip the automatic
// non-exclusive session creation.
if (typeof shouldAutoCreateNonExclusiveSession === 'undefined'
|| shouldAutoCreateNonExclusiveSession === true) {
device.requestSession({outputContext: ctx}).then( (session) => {
onSessionStarted(session);
}).then( () => {
initializationSteps['magicWindowStarted'] = true;
});
} else {
initializationSteps['magicWindowStarted'] = true; initializationSteps['magicWindowStarted'] = true;
}); }
}).then( () => { }).then( () => {
initializationSteps['getXRDevice'] = true; initializationSteps['getXRDevice'] = true;
}); });
......
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