mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
fix(browser): keep Orca shortcuts working while an HTML document preview has focus (#41066)
* fix(browser): forward Orca shortcuts from focused HTML document previews Workspace-document preview guests never installed the guest shortcut forwarder that browser pages and plugin frames use, so once a preview took focus Ctrl+Tab and the other window shortcuts stopped reaching Orca. Install it in attachWorkspaceDocGuestPolicies, bound to the preview's own host, with browserPage: false so browser-page chords stay with the document, and dispose it with the rest of the doc policy. * test(browser): pin Ctrl+Tab forwarding from a focused HTML document preview * fix(browser): activate a document preview's split when it is clicked A press inside the preview never reaches the host DOM, so forwarded shortcuts such as Close acted on whichever split was active before. * refactor(browser): read a document preview's page id from its guest policy --------- Co-authored-by: agustin2606 <agustinvarela2606@gmail.com>
This commit is contained in:
co-authored by
agustin2606
parent
e33a851d8e
commit
83ec1c09dd
@@ -66,6 +66,12 @@ export function readDocPreviewGuestBoundGrantId(guest: Electron.WebContents): st
|
||||
return previewGuests.get(guest)?.readBoundGrantId() ?? null
|
||||
}
|
||||
|
||||
/** Browser page of the guest's live grant; null before the grant binds or once it is revoked. */
|
||||
export function readDocPreviewGuestBoundPageId(guest: Electron.WebContents): string | null {
|
||||
const grantId = readDocPreviewGuestBoundGrantId(guest)
|
||||
return (grantId && getDocPreviewGrant(grantId)?.browserPageId) || null
|
||||
}
|
||||
|
||||
/** URL of the guest's committed document; unlike getURL(), pushState cannot rewrite it. */
|
||||
export function readDocPreviewGuestDocumentUrl(guest: Electron.WebContents): string {
|
||||
return previewGuests.get(guest)?.readDocumentUrl() ?? ''
|
||||
|
||||
+29
-4
@@ -5,10 +5,11 @@ import { BrowserManagerGuestCleanup } from './browser-manager-guest-cleanup'
|
||||
import { allowPageUnload } from '../../navigation/browser-guest-unload-policy'
|
||||
import {
|
||||
installDocPreviewGuestPolicy,
|
||||
readDocPreviewGuestBoundGrantId
|
||||
readDocPreviewGuestBoundPageId
|
||||
} from '../../../document-preview/doc-preview-guest-policy'
|
||||
import { getDocPreviewGrant } from '../../../document-preview/doc-preview-grant-registry'
|
||||
import { setupGuestEscapeForwarding } from '../../input/shortcuts/browser-guest-escape-forwarding'
|
||||
import { setupGuestShortcutForwarding } from '../../input/shortcuts/browser-guest-shortcut-forwarding'
|
||||
import { forwardGuestMousePress } from '../../input/browser-guest-mouse-press'
|
||||
import { installGuestDevToolsCloseShortcut } from '../../devtools/browser-guest-devtools-close-shortcut'
|
||||
|
||||
export abstract class BrowserManagerGuestPolicy extends BrowserManagerGuestCleanup {
|
||||
@@ -92,15 +93,37 @@ export abstract class BrowserManagerGuestPolicy extends BrowserManagerGuestClean
|
||||
host: Electron.WebContents
|
||||
): void {
|
||||
const disposeDocPolicy = installDocPreviewGuestPolicy(guest, host)
|
||||
const resolveHost = (): Electron.WebContents | null => (host.isDestroyed() ? null : host)
|
||||
const disposeEscapeForwarding = setupGuestEscapeForwarding({
|
||||
guest,
|
||||
resolveTarget: () => {
|
||||
const grantId = readDocPreviewGuestBoundGrantId(guest)
|
||||
const browserPageId = grantId ? getDocPreviewGrant(grantId)?.browserPageId : undefined
|
||||
const browserPageId = readDocPreviewGuestBoundPageId(guest)
|
||||
return browserPageId ? { browserPageId, renderer: host } : null
|
||||
},
|
||||
hasActiveGrabOp: (browserPageId) => this.hasActiveGrabOp(browserPageId)
|
||||
})
|
||||
// Why: a focused preview swallows keys, so Orca's window shortcuts (Ctrl+Tab, close tab) are
|
||||
// forwarded to its own host as for plugin frames; browser-page chords stay with the document.
|
||||
const disposeShortcutForwarding = setupGuestShortcutForwarding({
|
||||
browserTabId: `workspace-doc:${guest.id}`,
|
||||
guest,
|
||||
resolveRenderer: resolveHost,
|
||||
getKeybindings: () => this.settingsResolver?.().keybindings,
|
||||
browserPage: false
|
||||
})
|
||||
// Why: a press in the preview never reaches the host DOM, so its split stays inactive and
|
||||
// forwarded shortcuts like Close would act on another split (#22144). Presses only: previews
|
||||
// have no history listener for the side buttons a release would send.
|
||||
const reportPress = (_event: Electron.Event, mouse: Electron.MouseInputEvent): void => {
|
||||
if (mouse.type !== 'mouseDown') {
|
||||
return
|
||||
}
|
||||
const browserPageId = readDocPreviewGuestBoundPageId(guest)
|
||||
if (browserPageId) {
|
||||
forwardGuestMousePress(guest, browserPageId, resolveHost, mouse)
|
||||
}
|
||||
}
|
||||
guest.on('before-mouse-event', reportPress)
|
||||
const handleDestroyed = (): void => {
|
||||
this.cleanupGuestPolicyAttachment(guest.id)
|
||||
}
|
||||
@@ -109,9 +132,11 @@ export abstract class BrowserManagerGuestPolicy extends BrowserManagerGuestClean
|
||||
this.policyCleanupByGuestId.set(guest.id, () => {
|
||||
disposeDocPolicy()
|
||||
disposeEscapeForwarding()
|
||||
disposeShortcutForwarding()
|
||||
try {
|
||||
guest.off('destroyed', handleDestroyed)
|
||||
guest.off('will-prevent-unload', allowPageUnload)
|
||||
guest.off('before-mouse-event', reportPress)
|
||||
} catch {
|
||||
// guest may already be destroyed
|
||||
}
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { afterEach, describe, expect, it, vi, type Mock } from 'vitest'
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: { getPath: () => '/downloads' },
|
||||
BrowserWindow: { fromWebContents: () => null },
|
||||
clipboard: { writeText: vi.fn() },
|
||||
shell: { openExternal: vi.fn() },
|
||||
Menu: { buildFromTemplate: vi.fn() },
|
||||
screen: { getCursorScreenPoint: () => ({ x: 0, y: 0 }) },
|
||||
webContents: { fromId: () => null }
|
||||
}))
|
||||
vi.mock('../../popups/presentation/popup-origin-bar-window', () => ({
|
||||
openPopupWithOriginBar: vi.fn()
|
||||
}))
|
||||
|
||||
import { buildDocPreviewUrl } from '../../../../../shared/document-preview/doc-preview-scheme'
|
||||
import {
|
||||
mintDocPreviewGrant,
|
||||
revokeAllDocPreviewGrants
|
||||
} from '../../../document-preview/doc-preview-grant-registry'
|
||||
import {
|
||||
DestroyedGuestTestContents,
|
||||
DestroyedGuestTestManager
|
||||
} from './browser-manager-destroyed-guest-test-fixture'
|
||||
|
||||
describe('workspace document guest input', () => {
|
||||
const guests: DestroyedGuestTestContents[] = []
|
||||
|
||||
function attachDocumentGuest(): {
|
||||
guest: DestroyedGuestTestContents
|
||||
send: Mock
|
||||
} {
|
||||
const grant = mintDocPreviewGrant({
|
||||
owner: {
|
||||
kind: 'runtime',
|
||||
environmentId: 'env-1',
|
||||
worktreeSelector: 'id:worktree-1',
|
||||
worktreeRoot: '/repo'
|
||||
},
|
||||
root: '/repo',
|
||||
entryRelativePath: 'page.html',
|
||||
browserPageId: 'doc-page-1'
|
||||
})
|
||||
const url = buildDocPreviewUrl(grant.id, grant.entryRelativePath)
|
||||
const guest = Object.assign(new DestroyedGuestTestContents(7), { getURL: () => url })
|
||||
const send = vi.fn()
|
||||
const host = Object.assign(new DestroyedGuestTestContents(5001), { send })
|
||||
new DestroyedGuestTestManager().attachGuestPolicies(guest.asWebContents(), null, {
|
||||
profile: 'workspace-doc',
|
||||
host: host.asWebContents()
|
||||
})
|
||||
guests.push(guest)
|
||||
return { guest, send }
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const guest of guests.splice(0)) {
|
||||
guest.destroy()
|
||||
}
|
||||
revokeAllDocPreviewGrants()
|
||||
})
|
||||
|
||||
it('forwards Ctrl+Tab from a focused document preview to its host window', () => {
|
||||
const { guest, send } = attachDocumentGuest()
|
||||
guest.emit(
|
||||
'before-input-event',
|
||||
{ preventDefault: vi.fn() },
|
||||
{
|
||||
type: 'keyDown',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
control: true,
|
||||
shift: false,
|
||||
alt: false,
|
||||
meta: false
|
||||
}
|
||||
)
|
||||
|
||||
expect(send.mock.calls).toEqual([['ui:ctrlTabKeyDown', { shiftKey: false }]])
|
||||
})
|
||||
|
||||
it('reports a press in the preview so its split becomes active', () => {
|
||||
const { guest, send } = attachDocumentGuest()
|
||||
guest.emit('before-mouse-event', { preventDefault: vi.fn() }, { type: 'mouseDown', x: 1, y: 1 })
|
||||
|
||||
expect(send.mock.calls).toEqual([
|
||||
['ui:browserPagePointerDown', { browserPageId: 'doc-page-1' }]
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user