From 87dd16d6d140c58b73f91dd67a8ed0784be76f1d Mon Sep 17 00:00:00 2001 From: phit <2097483+phit@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:30:10 +0200 Subject: [PATCH] fix: hide the whole VR-entering click from the site Only the click event was stopped, but players and lightboxes often act on pointerdown/mousedown/pointerup/mouseup and paused the video or closed. Swallow every event of that gesture over the video at the window capture phase, and start VR on pointerdown, the first event Chrome accepts as user activation (touchend for touch). Co-Authored-By: Claude Opus 5.5 --- src/generic.js | 62 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/generic.js b/src/generic.js index fe64216..e5490c7 100644 --- a/src/generic.js +++ b/src/generic.js @@ -123,22 +123,58 @@ function createController() { hint = null; } + // All events of the click that enters VR. The site must see none of them, or it may pause the + // video, close its lightbox or start a drag. + const GESTURE_EVENTS = ['pointerdown', 'mousedown', 'touchstart', 'pointerup', 'mouseup', 'touchend', 'click']; + let gestureStarted = false; + let vrRequested = false; + let gestureTimeout = null; + function isOnVideo(event) { + const { clientX, clientY } = event.changedTouches?.[0] ?? event; const rect = video.getBoundingClientRect(); - return ( - event.clientX >= rect.left && - event.clientX <= rect.right && - event.clientY >= rect.top && - event.clientY <= rect.bottom - ); + return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom; } - // capture phase, so the site doesn't also see the click (and e.g. pause the video) - function onClick(event) { - if (!isOnVideo(event)) return; + // Chrome only starts VR from events that count as user activation + function activatesUser(event) { + if (event.type === 'pointerdown') return event.pointerType !== 'touch'; + if (event.type === 'pointerup') return event.pointerType === 'touch'; + return event.type === 'mousedown' || event.type === 'touchend' || event.type === 'click'; + } + + function listenToGesture(listen) { + for (const type of GESTURE_EVENTS) { + if (listen) window.addEventListener(type, onGestureEvent, { capture: true, passive: false }); + else window.removeEventListener(type, onGestureEvent, true); + } + } + + // capture phase on window, so this runs before the site's own handlers + function onGestureEvent(event) { + if (!gestureStarted) { + if (!isOnVideo(event)) return; + gestureStarted = true; + // swallow the rest of this click wherever it ends, then stop listening + gestureTimeout = setTimeout(endGesture, 1000); + } event.preventDefault(); - event.stopPropagation(); - window.removeEventListener('click', onClick, true); + event.stopImmediatePropagation(); + if (!vrRequested && activatesUser(event)) { + vrRequested = true; + startVR(); + } + if (event.type === 'click') endGesture(); + } + + function endGesture() { + clearTimeout(gestureTimeout); + listenToGesture(false); + gestureStarted = false; + vrRequested = false; + } + + function startVR() { hideHint(); // the site doesn't see this click, so start playback ourselves if (video.paused) video.play().catch(() => {}); @@ -160,7 +196,7 @@ function createController() { } function stop() { - window.removeEventListener('click', onClick, true); + endGesture(); hideHint(); clearInterval(removedCheck); video?.removeEventListener('emptied', stop); @@ -186,7 +222,7 @@ function createController() { removedCheck = setInterval(() => { if (!video.isConnected) stop(); }, 1000); - window.addEventListener('click', onClick, true); + listenToGesture(true); showHint('Click the video to enter VR'); state = 'armed'; return { state };