mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(browser): load newly opened background tabs (#19633)
* fix(browser): admit newly opened background pages
* fix(browser): scope background page mount admission
---------
Co-authored-by: Merge Sim <sim@local>
(cherry picked from commit 94a64cf40f)
This commit is contained in:
committed by
Merge Sim
parent
a883e17116
commit
7578cd3387
+3
-1
@@ -15,6 +15,7 @@ import {
|
||||
useClientHostedBrowserRows
|
||||
} from '@/lib/pane-manager/client-hosted-browser-row-state'
|
||||
import { ClientHostedBrowserHostRowPane } from '../client-hosted-browser-host-row-pane'
|
||||
import { useAnyBrowserPageMountAdmission } from '../host-guest/browser-page-mount-admission'
|
||||
|
||||
// Why: Electron <webview> destroys its guest on DOM reparent, so BrowserPanes render at worktree level and moving a tab between groups only swaps the overlay's CSS position-anchor.
|
||||
|
||||
@@ -60,7 +61,8 @@ const BrowserOverlaySlot = memo(function BrowserOverlaySlot({
|
||||
? browserTab.pageIds
|
||||
: [browserTab.activePageId ?? browserTab.id]
|
||||
const needsGuestPaint = useBrowserGuestPaintRetention(browserPageIds)
|
||||
const isPaintable = isActive || needsGuestPaint
|
||||
const isMountAdmitted = useAnyBrowserPageMountAdmission(browserPageIds)
|
||||
const isPaintable = isActive || needsGuestPaint || isMountAdmitted
|
||||
// Why: CSS anchor positioning pins the overlay to its owning group's body — a tab move only swaps positionAnchor, no measurement/state.
|
||||
// Orphan branch (no anchorName) stays display:none until the tab is reassigned or destroyed.
|
||||
const style: React.CSSProperties = useMemo(
|
||||
|
||||
@@ -22,6 +22,10 @@ import { WorkspaceDocPagePane } from '../workspace-doc/workspace-doc-page-pane'
|
||||
import { DeferredBrowserContent } from './DeferredBrowserContent'
|
||||
import { isBrowserPagePanePaintable } from '../host-guest/browser-page-paintability'
|
||||
import { SshRoutedBrowserPageGate } from './ssh-routed-browser-page-gate'
|
||||
import {
|
||||
isBrowserPageMountAdmitted,
|
||||
useAnyBrowserPageMountAdmission
|
||||
} from '../host-guest/browser-page-mount-admission'
|
||||
|
||||
export default function BrowserPane({
|
||||
browserTab,
|
||||
@@ -71,6 +75,7 @@ export default function BrowserPane({
|
||||
() => localBrowserPages.map((page) => page.id),
|
||||
[localBrowserPages]
|
||||
)
|
||||
const hasAdmittedPage = useAnyBrowserPageMountAdmission(localBrowserPageIds)
|
||||
const pageDriver = useBrowserDriverForPage(activeBrowserPageId)
|
||||
// Why: a runtime-backed page is streamed, never locally driven, so its driver must read idle.
|
||||
const activeBrowserDriver = runtimeEnvironmentActive ? IDLE_BROWSER_DRIVER : pageDriver
|
||||
@@ -166,7 +171,9 @@ export default function BrowserPane({
|
||||
key={page.id}
|
||||
retainMounted={isWorktreeActive}
|
||||
mountEligible={isBrowserPagePanePaintable({
|
||||
isActive: isActive && page.id === activeBrowserPageId,
|
||||
isActive:
|
||||
(isActive && page.id === activeBrowserPageId) ||
|
||||
(hasAdmittedPage && isBrowserPageMountAdmitted(page.id)),
|
||||
isAutomationVisible: automationVisiblePageIds.has(page.id),
|
||||
isMobileDriven: mobileDrivenPageIds.has(page.id),
|
||||
hasRemoteViewer: remotelyViewedPageIds.has(page.id)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useSyncExternalStore } from 'react'
|
||||
|
||||
// Newly requested pages must start a guest even when opened in the background. Restored pages are
|
||||
// deliberately absent so worktree restoration can remain lazy.
|
||||
const admittedPageIds = new Set<string>()
|
||||
const listeners = new Set<() => void>()
|
||||
let version = 0
|
||||
|
||||
export function isBrowserPageMountAdmitted(pageId: string): boolean {
|
||||
return admittedPageIds.has(pageId)
|
||||
}
|
||||
|
||||
function emit(): void {
|
||||
version += 1
|
||||
for (const listener of listeners) {
|
||||
listener()
|
||||
}
|
||||
}
|
||||
|
||||
export function admitBrowserPageMount(pageId: string): void {
|
||||
if (admittedPageIds.has(pageId)) {
|
||||
return
|
||||
}
|
||||
admittedPageIds.add(pageId)
|
||||
emit()
|
||||
}
|
||||
|
||||
export function releaseBrowserPageMount(pageId: string): void {
|
||||
if (!admittedPageIds.delete(pageId)) {
|
||||
return
|
||||
}
|
||||
emit()
|
||||
}
|
||||
|
||||
export function useBrowserPageMountAdmission(pageId: string): boolean {
|
||||
useSyncExternalStore(
|
||||
(listener) => {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
},
|
||||
() => {
|
||||
void version
|
||||
return isBrowserPageMountAdmitted(pageId)
|
||||
},
|
||||
() => false
|
||||
)
|
||||
return isBrowserPageMountAdmitted(pageId)
|
||||
}
|
||||
|
||||
export function useAnyBrowserPageMountAdmission(pageIds: readonly string[]): boolean {
|
||||
useSyncExternalStore(
|
||||
(listener) => {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
},
|
||||
() => {
|
||||
void version
|
||||
return pageIds.some(isBrowserPageMountAdmitted)
|
||||
},
|
||||
() => false
|
||||
)
|
||||
return pageIds.some(isBrowserPageMountAdmitted)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { closeRemoteBrowserPageInOwningEnvironment } from './browser-remote-clos
|
||||
import { releaseDocPreviewGrant } from '@/lib/doc-preview-grants'
|
||||
import { destroyWorkspaceWebviews } from '../browser-webview-cleanup'
|
||||
import { omitRecordKeys } from '../worktrees/teardown/record-key-omission'
|
||||
import { releaseBrowserPageMount } from '@/components/browser-pane/host-guest/browser-page-mount-admission'
|
||||
|
||||
export function createBrowserCloseActions(
|
||||
set: BrowserSliceSet,
|
||||
@@ -28,6 +29,7 @@ export function createBrowserCloseActions(
|
||||
// grant is the only authority the preview scheme honors — a closed document must stop being
|
||||
// readable, and it must stop being readable even if the reducer bails out below.
|
||||
let docPageIdsToRelease: string[] = []
|
||||
let closedPagesForMountRelease: string[] = []
|
||||
let activeBrowserWorktreeIdToNotify: string | null = null
|
||||
set((s) => {
|
||||
let owningWorktreeId: string | null = null
|
||||
@@ -49,6 +51,7 @@ export function createBrowserCloseActions(
|
||||
}
|
||||
|
||||
const closedPages = s.browserPagesByWorkspace[tabId] ?? []
|
||||
closedPagesForMountRelease = closedPages.map((page) => page.id)
|
||||
const nextBrowserPagesByWorkspace = { ...s.browserPagesByWorkspace }
|
||||
delete nextBrowserPagesByWorkspace[tabId]
|
||||
const nextBrowserAnnotationsByPageId = { ...s.browserAnnotationsByPageId }
|
||||
@@ -179,6 +182,9 @@ export function createBrowserCloseActions(
|
||||
for (const docPageId of docPageIdsToRelease) {
|
||||
releaseDocPreviewGrant(docPageId)
|
||||
}
|
||||
for (const pageId of closedPagesForMountRelease) {
|
||||
releaseBrowserPageMount(pageId)
|
||||
}
|
||||
|
||||
for (const tabs of Object.values(get().unifiedTabsByWorktree)) {
|
||||
const workspaceItem = tabs.find(
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
import { ORCA_BROWSER_BLANK_URL } from '../../../../../shared/constants'
|
||||
import { closeRemoteBrowserPageInOwningEnvironment } from './browser-remote-close'
|
||||
import { releaseDocPreviewGrant } from '@/lib/doc-preview-grants'
|
||||
import {
|
||||
admitBrowserPageMount,
|
||||
releaseBrowserPageMount
|
||||
} from '@/components/browser-pane/host-guest/browser-page-mount-admission'
|
||||
|
||||
export function createBrowserPageCreateActions(
|
||||
set: BrowserSliceSet,
|
||||
@@ -33,6 +37,9 @@ export function createBrowserPageCreateActions(
|
||||
undefined,
|
||||
options?.docLocation
|
||||
)
|
||||
if (!options?.browserRuntimeEnvironmentId && !options?.docLocation) {
|
||||
admitBrowserPageMount(page.id)
|
||||
}
|
||||
|
||||
set((s) => {
|
||||
const pages = s.browserPagesByWorkspace[workspaceId] ?? []
|
||||
@@ -94,6 +101,7 @@ export function createBrowserPageCreateActions(
|
||||
},
|
||||
|
||||
closeBrowserPage: (pageId) => {
|
||||
releaseBrowserPageMount(pageId)
|
||||
let closedWorkspaceIdForLabel: string | null = null
|
||||
let docPageIdToRelease: string | null = null
|
||||
const remotePagesToClose: { worktreeId: string; handle: RemoteBrowserPageHandle }[] = []
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '../browser-page-records'
|
||||
import { getBrowserSessionProfileHostId } from './browser-host-state'
|
||||
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import { admitBrowserPageMount } from '@/components/browser-pane/host-guest/browser-page-mount-admission'
|
||||
|
||||
export function createBrowserTabActions(
|
||||
set: BrowserSliceSet,
|
||||
@@ -48,6 +49,9 @@ export function createBrowserTabActions(
|
||||
browserPageId,
|
||||
options?.docLocation
|
||||
)
|
||||
if (!options?.browserRuntimeEnvironmentId && !options?.docLocation) {
|
||||
admitBrowserPageMount(page.id)
|
||||
}
|
||||
// Why: with no explicit profile, inherit the user's default so a Settings preference applies to new tabs.
|
||||
const sessionProfileId =
|
||||
options?.sessionProfileId !== undefined
|
||||
|
||||
Reference in New Issue
Block a user