diff --git a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java index 3427784fc8..100d115395 100644 --- a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java +++ b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java @@ -126,6 +126,10 @@ public class VRBrowserActivity extends PlatformActivity implements WidgetManager public static final String EXTRA_HIDE_WEBXR_INTERSTITIAL = "hide_webxr_interstitial"; public static final String EXTRA_HIDE_WHATS_NEW = "hide_whats_new"; public static final String EXTRA_KIOSK = "kiosk"; + 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"; private BroadcastReceiver mCrashReceiver = new BroadcastReceiver() { @Override @@ -242,6 +246,8 @@ public void run() { private ScheduledFuture mNativeWidgetUpdatesTask = null; private Media mPrevActiveMedia = null; private boolean mIsPassthroughEnabled = false; + private String mImmersiveParentElementXPath; + private String mImmersiveTargetElementXPath; private boolean callOnAudioManager(Consumer fn) { if (mAudioManager == null) { @@ -769,6 +775,7 @@ void loadFromIntent(final Intent intent) { boolean openInWindow = false; boolean openInBackground = false; boolean openInKioskMode = false; + boolean openInImmersive = false; Uri dataUri = intent.getData(); Uri targetUri = null; @@ -845,6 +852,14 @@ void loadFromIntent(final Intent intent) { } openInKioskMode = extras.getBoolean(EXTRA_KIOSK, false); + + if (extras.getBoolean(EXTRA_OPEN_IN_IMMERSIVE)) { + mImmersiveParentElementXPath = extras.getString(EXTRA_OPEN_IN_IMMERSIVE_PARENT_XPATH); + mImmersiveTargetElementXPath = extras.getString(EXTRA_OPEN_IN_IMMERSIVE_ELEMENT_XPATH); + + // Open in immersive requires specific information to be present + openInImmersive = targetUri != null && mImmersiveTargetElementXPath != null; + } } // If there is a target URI we open it @@ -856,6 +871,8 @@ void loadFromIntent(final Intent intent) { if (openInKioskMode) { // FIXME this might not work as expected if the app was already running mWindows.openInKioskMode(targetUri.toString()); + } if (openInImmersive) { + mWindows.openInImmersiveMode(targetUri, mImmersiveParentElementXPath, mImmersiveTargetElementXPath); } else { if (openInWindow) { location = Windows.OPEN_IN_NEW_WINDOW; diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java index b58844040f..4f0a4a0561 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; +import android.net.Uri; import android.util.Log; import androidx.annotation.IntDef; @@ -67,6 +68,8 @@ public class Windows implements TrayListener, TopBarWidget.Delegate, TitleBarWid public static final int WHITE = 0xFFFFFFFF; public static final int GRAY = 0x555555FF; + public static final String PARENT_ELEMENT_XPATH_PARAMETER = "wolvic-autowebxr-parentElementXPath"; + public static final String TARGET_ELEMENT_XPATH_PARAMETER = "wolvic-autowebxr-targetElementXPath"; @IntDef(value = { OPEN_IN_FOREGROUND, OPEN_IN_BACKGROUND, OPEN_IN_NEW_WINDOW}) public @interface NewTabLocation {} @@ -1455,6 +1458,32 @@ public void openInKioskMode(@NonNull String aUri) { mFocusedWindow.setKioskMode(true); } + public void openInImmersiveMode(Uri targetUri, String immersiveParentElementXPath, String immersiveTargetElementXPath) { + String extensionId = "wolvic-autowebxr@igalia.com"; + String extensionUrl = "resource://android/assets/extensions/wolvic_autowebxr/"; + + Uri.Builder uriBuilder = targetUri.buildUpon(); + uriBuilder.appendQueryParameter(PARENT_ELEMENT_XPATH_PARAMETER, immersiveParentElementXPath); + uriBuilder.appendQueryParameter(TARGET_ELEMENT_XPATH_PARAMETER, immersiveTargetElementXPath); + Uri extendedUri = uriBuilder.build(); + + Session session = SessionStore.get().createSuspendedSession(extendedUri.toString(), true); + + SessionStore.get().getWebExtensionRuntime().installWebExtension( + extensionId, + extensionUrl, + webExtension -> { + setFirstPaint(mFocusedWindow, session); + mFocusedWindow.setSession(session, WindowWidget.DEACTIVATE_CURRENT_SESSION); + return null; + }, + (s, throwable) -> { + Log.e(LOGTAG, "Error installing the " + extensionId + " Web Extension: " + throwable.getLocalizedMessage()); + return null; + } + ); + } + public void addTab(@NonNull WindowWidget targetWindow, @Nullable String aUri) { Session session = SessionStore.get().createSuspendedSession(aUri, targetWindow.getSession().isPrivateMode()); session.setParentSession(targetWindow.getSession()); diff --git a/app/src/main/assets/extensions/wolvic_autowebxr/content.js b/app/src/main/assets/extensions/wolvic_autowebxr/content.js new file mode 100644 index 0000000000..16059c6ccf --- /dev/null +++ b/app/src/main/assets/extensions/wolvic_autowebxr/content.js @@ -0,0 +1,81 @@ +const LOGTAG = '[wolvic:autowebxr]'; +const ENABLE_LOGS = true; +const logDebug = (...args) => ENABLE_LOGS && console.log(LOGTAG, ...args); + +const PARENT_ELEMENT_XPATH_PARAMETER = 'wolvic-autowebxr-parentElementXPath'; +const TARGET_ELEMENT_XPATH_PARAMETER = 'wolvic-autowebxr-targetElementXPath'; + +var parentElementXPath; +var targetElementXPath; + +function getElementByXPath(document, xpath) { + let result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); + return result.singleNodeValue; +} + +function clickImmersiveElement() { + // check if the current URL has extra query parameters + parentElementXPath = undefined; + targetElementXPath = undefined; + + let url = document.URL; + let params = new URLSearchParams(new URL(url).search); + for (let [key, value] of params) { + if (key === 'wolvic-autowebxr-parentElementXPath') + parentElementXPath = value + else if (key === 'wolvic-autowebxr-targetElementXPath') + targetElementXPath = value; + } + + // we need at least the target element to click + if (!targetElementXPath) + return; + + logDebug('Preparing to open immersive WebXR; parentElementXPath: ' + parentElementXPath + ' ; targetElementXPath: ' + targetElementXPath); + + // the parent element is usually an iframe; if parentElementXPath is null, we use the root + + var parent, parentDocument; + if (parentElementXPath) { + var parent = getElementByXPath(document, parentElementXPath); + if (!parent) { + logDebug('parent not found, retrying'); + setTimeout(clickImmersiveElement, 1000); + return; + } + parentDocument = parent.contentDocument || parent.contentWindow.document; + } else { + parent = window; + parentDocument = document; + } + + if (parentDocument.readyState !== 'complete') { + logDebug('parent is still loading'); + parent.addEventListener('load', function() { + logDebug('parent has finished loading'); + clickImmersiveElement(); + }); + return; + } + + logDebug('parent is loaded'); + + let targetElement = getElementByXPath(parentDocument, targetElementXPath); + + if (targetElement) { + logDebug('target element found, clicking'); + // This fails with "DOMException: A user gesture is required" unless dom.vr.require-gesture is set to false. + targetElement.click(); + } else { + logDebug('target element not found, retrying'); + setTimeout(clickImmersiveElement, 1000); + } +} + +if (document.readyState === 'complete') { + logDebug('document is completely ready'); + clickImmersiveElement(); +} else { + logDebug('document is not ready yet'); + window.addEventListener('load', clickImmersiveElement); +} diff --git a/app/src/main/assets/extensions/wolvic_autowebxr/manifest.json b/app/src/main/assets/extensions/wolvic_autowebxr/manifest.json new file mode 100644 index 0000000000..1dbc14bfc7 --- /dev/null +++ b/app/src/main/assets/extensions/wolvic_autowebxr/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 2, + "name": "Wolvic WebXR Automator", + "version": "1.0", + "description": "Enter WebXR Experiences Automatically", + "browser_specific_settings": { + "gecko": { + "id": "wolvic-autowebxr@igalia.com" + } + }, + "content_scripts": [ + { + "matches": [ + "*://*/*" + ], + "js": [ + "content.js" + ], + "run_at": "document_idle", + "all_frames": false + } + ] +} diff --git a/app/src/main/res/raw/fxr_config.yaml b/app/src/main/res/raw/fxr_config.yaml index a70956746e..381e87e72f 100644 --- a/app/src/main/res/raw/fxr_config.yaml +++ b/app/src/main/res/raw/fxr_config.yaml @@ -26,4 +26,6 @@ prefs: browser.gesture.pinch.in: '' browser.gesture.pinch.out.shift: '' browser.gesture.pinch.in.shift: '' - apz.one_touch_pinch.enabled: false + apz.one_touch_pinch.enabled: false, + # allows scripts to open WebXR immersive sessions + dom.vr.require-gesture: false