Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Launch immersive experiences directly #1171

Merged
merged 1 commit into from
Oct 21, 2024
Merged

Conversation

felipeerias
Copy link
Collaborator

@felipeerias felipeerias commented Jan 10, 2024

This PR implements support for opening immersive experiences directly as soon as the application is launched.

We do this by adding a built-in extension which will activate a particular element in the page.

We need to set the preference dom.vr.require-gesture to false so this script can launch immersive WebXR experiences without user input. Media autoplay also need to be allowed in this mode.

The information required to identify the element to activate is passed as parameters to the Intent:

  • launch_immersive
  • launch_immersive_parent_xpath
  • launch_immersive_element_xpath
  • a target URL to open

One additional concern is that we should only change the configuration prefs when that is needed for launching Wolvic directly in immersive mode.

The problem is that we only write that prefs file the first time that we create the browser engine, which happens several times at launch time before we have parsed the incoming Intent and we can know in which mode we are launching.

The solution is that WidgetManagerDelegate.isOpenInImmersive() internally will check the current Intent, to cover the case where it is being called too early.

We then pass VRBrowserActivity as the context to the objects that may initialize the engine, so eventually SessionUtils.prepareConfigurationPath() is able to decide whether specific prefs should be added to the configuration file.

For launching Wolvic directly in immersive mode, these prefs are:

  dom.vr.require-gesture: false
  media.autoplay.default: 0

@felipeerias
Copy link
Collaborator Author

felipeerias commented Jan 10, 2024

This PR can be tested from the command line with:

(deprecated)

@felipeerias
Copy link
Collaborator Author

I have updated the PR so that it uses the XPath of the parent element, which is a more flexible solution.

@HollowMan6 HollowMan6 linked an issue Jan 30, 2024 that may be closed by this pull request
Copy link
Member

@svillar svillar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I told @felipeerias about this in private, but I'll add it here for everybody interested to know. The idea of using an extension is nice, but the problem is that the Chromium backend won't have extensions support initially as it's a lot of work. So provided that we don't have a solution at the moment I'd prioritize one that does not involve extensions.

That said I agree that having an extension that inspects the DOM is likely the best we could do, so I indeed have mixed feelings.

@felipeerias felipeerias marked this pull request as draft July 3, 2024 08:59
@felipeerias felipeerias force-pushed the felipeerias/startInImmersive branch 2 times, most recently from 09a9140 to 8aecce9 Compare July 10, 2024 07:59
@felipeerias
Copy link
Collaborator Author

felipeerias commented Jul 10, 2024

This implementation might be the best that we can do, but it still has several issues.

First of all, the command to launch Wolvic in immersive mode needs to know the exact element in the page which will open the WebXR experience. If the page changes, it will be necessary to update that command.

Secondly, the approach of using a Web extension to trigger specific elements in the page will not be possible on Chromium, because we don't support extensions there just yet.

Third, in some cases our extension is not able to reach the immersive button because the WebXR experience is inside of an iframe from a different origin, and security rules prevent us from accessing a cross-origin object from the embedding page. It might be possible to work around this issue by passing messages between the content scripts running on the page and on the iframe.

This last problem is particularly painful because it affects HeyVR, a large repository of WebXR games: the pages are in https://heyvr.io but their iframes point towards https://games.heyvr.io.

Finally, some pages might have specific quirks that we have to consider. For example, Moon Rider requires two clicks to open the immersive experience, and Magical Reflections requires the user to accept cookies before anything else.

With all that in mind, the implementation in this PR does work reasonably well. Check out the following video:

ScreenRecording_2024.07.10-16.57.13.mp4

Command to launch A-Painter:

(deprecated)

Command to launch Moon Rider:

(deprecated)

@felipeerias felipeerias requested review from svillar and removed request for HollowMan6 July 10, 2024 08:04
@felipeerias felipeerias marked this pull request as ready for review July 10, 2024 08:05
@svillar svillar mentioned this pull request Jul 29, 2024
app/src/main/assets/extensions/wolvic_autowebxr/content.js Outdated Show resolved Hide resolved
if (targetElement) {
logDebug('Target element found, calling click()');
targetElement.click();
targetElement.click();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why two?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a case where one click() didn't work. I think that I will remove this and test again different experiences, to check if it is still needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we still have the two clicks... does it mean it's still required?, I still don't get why

@@ -128,6 +128,12 @@ public class VRBrowserActivity extends PlatformActivity implements WidgetManager
public static final String EXTRA_KIOSK = "kiosk";
private static final long BATTERY_UPDATE_INTERVAL = 60 * 1_000_000_000L; // 60 seconds

boolean mOpenInImmersive = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be private

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course.

@svillar svillar linked an issue Aug 13, 2024 that may be closed by this pull request
@felipeerias felipeerias force-pushed the felipeerias/startInImmersive branch 2 times, most recently from 7aad147 to f0f25fb Compare August 14, 2024 09:56
@felipeerias
Copy link
Collaborator Author

@svillar I have updated the PR following your comments. I also had to allow autoplay when we start in this mode to remove an error with Moon Rider, although for some reason it is still not working properly (it was working fine before).

Reference: https://wiki.mozilla.org/Media/block-autoplay

@felipeerias
Copy link
Collaborator Author

felipeerias commented Aug 14, 2024

Access Mars:

(deprecated)

(Note that depending on your setup you might have to add .dev to the package name)

public static final String EXTRA_OPEN_IN_IMMERSIVE = "open_in_immersive";
// Element where a click would be simulated to launch the WebXR experience.
public static final String EXTRA_OPEN_IN_IMMERSIVE_PARENT_XPATH = "open_in_immersive_parent_xpath";
public static final String EXTRA_OPEN_IN_IMMERSIVE_ELEMENT_XPATH = "open_in_immersive_element_xpath";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other Intent parameters in that file are public.

I think it's a good idea for documentation purposes, as a way to make clear that these strings are supposed to be used outside of this class.

if (targetElement) {
logDebug('Target element found, calling click()');
targetElement.click();
targetElement.click();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we still have the two clicks... does it mean it's still required?, I still don't get why


// Limit the number of times that we can try to launch the experience, to avoid an infinite loop.
var retryCounter = 0;
const RETRY_LIMIT = 20;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is kind of arbitrary but a maximum 20" retry seems to much to me. Maybe good for poor connections though...? In that case the user would have some other problems. My point is that users won't get any idea of what's going on for a lot of time. The feeling after 4-5" is that nothing works, nobody will wait for 20" for an app to launch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that's a problem in general with this feature. Some experiences just take a long time to load from the network.

In this initial implementation, we show the browser window before launching the immersive experience. Eventually my plan is to go directly from the loading screen to the immersive experience.

apz.one_touch_pinch.enabled: false
apz.one_touch_pinch.enabled: false,
# allows scripts to open WebXR immersive sessions
dom.vr.require-gesture: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel a bit uneasy having this. Would it be possible not to add this here and manually add it to the runtime settings builder in EngineProvider.kt somehow? We don't need to spend much time on that, but if it's possible to add it just whenever is needed it'd be much better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possibility could be to do it in SessionUtils.prepareConfigurationPath(), which copies the configuration file from the assets to the device's filesystem. Basically, the idea would be to append an extra line to the file if needed:

final String REQUIRE_GESTURE_CONFIG = "dom.vr.require-gesture: false";
WidgetManagerDelegate widgetManager = (WidgetManagerDelegate) aContext;

if (widgetManager.isOpenInImmersive()) {
        outputStream.write(REQUIRE_GESTURE_CONFIG.getBytes());
}

Another approach could be to have two files in the assets, and copy one or the other depending on the current configuration. Maybe that would be better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's enough with the first approach you mentioned. Adding a line to the stream should make it

Copy link
Collaborator Author

@felipeerias felipeerias Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been working on this but it is a bit more complex than I expected because prepareConfigurationPath() is called very early, when we have not parsed the incoming Intent yet and so we don't know that we will be in the automatically immersive mode.

Specifically, as a result of VRBrowserActivity.onCreate() calling

((VRBrowserApplication) getApplication()).onActivityCreate(this)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally found a way to change the configuration prefs only when needed for launching in immersive mode.

The problem is that we only write the prefs file the first time that we create the browser engine, which happens several times at launch time before we have parsed the incoming Intent and we can know in which mode we are launching.

The solution is that WidgetManagerDelegate.isOpenInImmersive() internally will check the current Intent, to cover the case where it is being called too early.

We then pass VRBrowserActivity as the context to the objects that may initialize the engine, so eventually SessionUtils.prepareConfigurationPath() is able to decide whether specific prefs should be added to the configuration file.

For launching Wolvic directly in immersive mode, these prefs are:

  dom.vr.require-gesture: false
  media.autoplay.default: 0

@felipeerias felipeerias changed the title Open immersive experiences directly Launch immersive experiences directly Oct 11, 2024
@felipeerias
Copy link
Collaborator Author

I have thoroughly updated this PR.

Unfortunately, it is hard to find experiences which will work well with it:

  • some will show an ad before the game, like heyvr.io
  • some don't have stable XPath identifiers for the button that launches the experience, like magische-spiegelungen.de
  • some work from the JS console via remote debugging but not from an extension (perhaps a timing issue?), like moonrider.xyz

These are a couple of experiences which work consistently.

A-Painter:

adb shell am start -n "com.igalia.wolvic.dev/com.igalia.wolvic.VRBrowserActivity" \
-a android.intent.action.VIEW                                                     \
-c android.intent.category.LAUNCHER                                               \
-d "https://aframe.io/a-painter/"                                                 \
--ez launch_immersive true                                                        \
--ez hide_webxr_interstitial true                                                 \
-e launch_immersive_element_xpath '\/html\/body\/a-scene\/div[2]\/button'

Access Mars:

adb shell am start -n "com.igalia.wolvic.dev/com.igalia.wolvic.VRBrowserActivity" \
-a android.intent.action.VIEW                                                     \
-c android.intent.category.LAUNCHER                                               \
-d "https://accessmars.withgoogle.com/"                                           \
--ez launch_immersive true                                                        \
--ez hide_webxr_interstitial true                                                 \
-e launch_immersive_element_xpath '\/html\/body\/div\[1\]\/header\/div\/button'

(remove the .dev from the package name if necessary)

Copy link
Member

@svillar svillar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, we're superclose to land this. I have some comments

@felipeerias
Copy link
Collaborator Author

I had to modify AndroidManifest.xml so we could launch Wolvic with parameters without needing to specify the specific Activity.

Now that VersionCheckActivity is the entry point of our application and the only Activity that we export, it needs to use the intent filters that were previously associated to VRBrowserActivity.

These are the new comands to test this feature:

adb shell am start -p com.igalia.wolvic.dev \
-a android.intent.action.VIEW               \
-d "https://aframe.io/a-painter/"           \
--ez launch_immersive true                  \
--ez hide_webxr_interstitial true           \
-e launch_immersive_element_xpath '\/html\/body\/a-scene\/div[2]\/button'
adb shell am start -p com.igalia.wolvic.dev \
-a android.intent.action.VIEW               \
-d "https://accessmars.withgoogle.com/"     \
--ez launch_immersive true                  \
--ez hide_webxr_interstitial true           \
-e launch_immersive_element_xpath '\/html\/body\/div\[1\]\/header\/div\/button'

@svillar svillar linked an issue Oct 15, 2024 that may be closed by this pull request
This PR implements support for opening immersive experiences
directly as soon as the application is launched.

We do this by adding a built-in extension which will activate
a particular element in the page.

We need to set the preference dom.vr.require-gesture to false
so this script can launch immersive WebXR experiences.

Media autoplay is also allowed in this mode.

The information required to identify the element to activate
is passed as parameters to the Intent:

- launch_immersive
- launch_immersive_parent_xpath
- launch_immersive_element_xpath
- a target URL to open

One additional concern is that we should only change the
configuration prefs when that is needed for launching
Wolvic directly in immersive mode.

The problem is that we only write that prefs file the first time
that we create the browser engine, which happens several times at
launch time **before** we have parsed the incoming Intent and we
can know in which mode we are launching.

The solution is that WidgetManagerDelegate.isOpenInImmersive()
internally will check the current Intent, to cover the case
where it is being called too early.

We then pass VRBrowserActivity as the context to the objects
that may initialize the engine, so eventually
SessionUtils.prepareConfigurationPath() is able to decide whether
specific prefs should be added to the configuration file.

For launching Wolvic directly in immersive mode, these prefs are:
  dom.vr.require-gesture: false
  media.autoplay.default: 0

These preferences need to be applied on startup. Therefore, when
a running Wolvic receives a new Intent to launch in immersive we
will finish the current activity and launch a new one.
@felipeerias
Copy link
Collaborator Author

The change which assigned the intent filters to VersionCheckActivity has been already integrated by #1575

I have rebased this PR and it is once again ready for review.

Copy link
Member

@svillar svillar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK let's land this

@svillar svillar merged commit 1cac419 into main Oct 21, 2024
22 checks passed
@svillar svillar deleted the felipeerias/startInImmersive branch October 21, 2024 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants