// SPDX-License-Identifier: AGPL-3.0-or-later // The shield: what covers the screen while the panel is dark. // // This manages the wake gesture: a double tap wake. // Above the dusk veil (9000), which is itself above --z-modal: the fade may // still be running when the panel is cut. const SHIELD_Z_INDEX = 9500; // Two taps inside this window, and this far apart at most, count as a double // tap. const DOUBLE_TAP_MS = 500; const DOUBLE_TAP_SLOP_PX = 80; let shield = null; let lastTap = null; let wakeAction = null; /** * Cover the screen. Idempotent; the first caller's `onWake` stands. * * @param {object} [options] * @param {Function} [options.onWake] Run when the user double taps. */ export function raiseShield({ onWake } = {}) { if (shield) { return; } lastTap = null; wakeAction = onWake ?? null; shield = document.createElement("div"); shield.className = "screen-shield"; shield.style.cssText = [ "position: fixed", "inset: 0", `z-index: ${SHIELD_Z_INDEX}`, // Transparent: the dusk veil and the screen-off effect do the visuals. // This element exists only to catch input. "background: transparent", // Servo ignores touch-action, so the handlers below are what actually stop // the gesture. Declared anyway for correctness elsewhere. "touch-action: none", ].join(";"); // Capture phase, and every pointer/touch stage: a swallowed `down` whose // `move` and `up` still landed would leave the page mid-gesture. for (const type of [ "pointerdown", "pointermove", "pointerup", "pointercancel", "touchstart", "touchmove", "touchend", "touchcancel", "click", "dblclick", "contextmenu", "wheel", ]) { shield.addEventListener(type, swallow, true); } shield.addEventListener("pointerup", onTap, true); document.body.appendChild(shield); } // Uncover the screen. Idempotent. export function lowerShield() { shield?.remove(); shield = null; lastTap = null; wakeAction = null; } // True while the shield is up, i.e. while the screen is meant to be dark. export function shieldIsUp() { return shield !== null; } function swallow(event) { event.preventDefault(); event.stopPropagation(); } // A double tap wakes. Tracked on pointerup rather than pointerdown so a press // that turns into a drag does not count as a tap. function onTap(event) { const now = event.timeStamp; const previous = lastTap; lastTap = { x: event.clientX, y: event.clientY, at: now }; if ( previous && now - previous.at <= DOUBLE_TAP_MS && Math.abs(event.clientX - previous.x) <= DOUBLE_TAP_SLOP_PX && Math.abs(event.clientY - previous.y) <= DOUBLE_TAP_SLOP_PX ) { const wake = wakeAction; lowerShield(); wake?.(); } }