diff --git a/src/main/browser/extensions/permissions/browser-extension-user-gesture.ts b/src/main/browser/extensions/permissions/browser-extension-user-gesture.ts index 8a6274d8511..ddf81edeee9 100644 --- a/src/main/browser/extensions/permissions/browser-extension-user-gesture.ts +++ b/src/main/browser/extensions/permissions/browser-extension-user-gesture.ts @@ -22,19 +22,21 @@ export function noteBrowserExtensionUserGesture( recentGestures.set(signal, Date.now()) } -export function hasBrowserExtensionUserGesture(caller: BrowserExtensionCaller): boolean { - if (caller.userGesture === true) { - return true - } +/** When Orca last handed the caller's extension a gesture, if still within the gesture window. */ +export function getBrowserExtensionUserGestureAt(caller: BrowserExtensionCaller): number | null { const state = getHostSessionState(caller.session) if (!state) { - return false + return null } const signal = getBrowserExtensionContextSignal(state, caller.extension.id) const at = recentGestures.get(signal) - return ( - at !== undefined && + return at !== undefined && !signal.aborted && Date.now() - at <= BROWSER_EXTENSION_USER_GESTURE_WINDOW_MS - ) + ? at + : null +} + +export function hasBrowserExtensionUserGesture(caller: BrowserExtensionCaller): boolean { + return caller.userGesture === true || getBrowserExtensionUserGestureAt(caller) !== null } diff --git a/src/main/browser/extensions/tabs-and-windows/browser-extension-agent-caller.ts b/src/main/browser/extensions/tabs-and-windows/browser-extension-agent-caller.ts index c4e2a8c3148..799be836ea7 100644 --- a/src/main/browser/extensions/tabs-and-windows/browser-extension-agent-caller.ts +++ b/src/main/browser/extensions/tabs-and-windows/browser-extension-agent-caller.ts @@ -3,7 +3,7 @@ import { getLastBrowserGuestInputInSession } from '../../guest-lifecycle/input/b import type { BrowserExtensionCaller } from '../api-runtime/browser-extension-namespace-registry' import { BROWSER_EXTENSION_USER_GESTURE_WINDOW_MS, - hasBrowserExtensionUserGesture + getBrowserExtensionUserGestureAt } from '../permissions/browser-extension-user-gesture' import type { openBrowserExtensionTab } from './browser-extension-tab-model' @@ -19,24 +19,28 @@ function pageOfGuest(guestWebContentsId: number): AgentPage { /** * The agent's page this call acts for, or null when the user set it off. An extension reacts to * its profile's pages (a wallet popup, an OAuth tab) without saying which input started it, so - * the latest click or key in the profile decides, then whether an agent is driving a page there. + * the newest recent click, key or Orca-delivered gesture decides, then whether an agent leads. */ export function findBrowserExtensionCallerAgentPage( caller: BrowserExtensionCaller ): AgentPage | null { - // Why first: an agent's click on an extension page also counts as that page's user gesture. - if (caller.webContents && browserManager.isAgentLeadingGuest(caller.webContents.id)) { - return pageOfGuest(caller.webContents.id) - } - if (hasBrowserExtensionUserGesture(caller)) { + const now = Date.now() + const lastInput = getLastBrowserGuestInputInSession(caller.session) + const input = + lastInput && now - lastInput.at <= BROWSER_EXTENSION_USER_GESTURE_WINDOW_MS ? lastInput : null + const gestureAt = getBrowserExtensionUserGestureAt(caller) + if (gestureAt !== null && (!input || gestureAt >= input.at)) { return null } - const input = getLastBrowserGuestInputInSession(caller.session) - if (input && Date.now() - input.at <= BROWSER_EXTENSION_USER_GESTURE_WINDOW_MS) { + if (input) { // Agents and remote viewers send injected input; only the desktop user's own may surface. return input.byAgent ? pageOfGuest(input.guestWebContentsId) : null } - return browserManager.findAgentPageInSession(caller.session) + // Why before userGesture: an agent's click on an extension page also activates that page. + if (caller.webContents && browserManager.isAgentLeadingGuest(caller.webContents.id)) { + return pageOfGuest(caller.webContents.id) + } + return caller.userGesture ? null : browserManager.findAgentPageInSession(caller.session) } /** An agent's extension tab opens in the background beside its page, never in the user's view. */