mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(mobile): discard refused picker cache copies (#34772)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -81,10 +81,11 @@ function harness(
|
||||
const files = new Map<string, Uint8Array>()
|
||||
const written: { uri: string; base64: string }[] = []
|
||||
const copied: { from: string; to: string }[] = []
|
||||
const registry = new MediaHandleRegistry({
|
||||
now: () => 1_000,
|
||||
discard: (uri) => discarded.push(uri)
|
||||
})
|
||||
const discard = (uri: string): void => {
|
||||
discarded.push(uri)
|
||||
files.delete(uri)
|
||||
}
|
||||
const registry = new MediaHandleRegistry({ now: () => 1_000, discard })
|
||||
const serve = createNativeMediaVerbServer({
|
||||
registry,
|
||||
launchLibrary: () =>
|
||||
@@ -114,7 +115,7 @@ function harness(
|
||||
return destination
|
||||
},
|
||||
openFile: (uri) => fakeFile(files.get(uri) ?? new Uint8Array()),
|
||||
discard: (uri) => discarded.push(uri),
|
||||
discard,
|
||||
ownsStagedUri: (uri: string) => uri.startsWith('file:'),
|
||||
...overrides
|
||||
})
|
||||
@@ -352,28 +353,104 @@ describe('picking', () => {
|
||||
warn.mockRestore()
|
||||
})
|
||||
|
||||
it('refuses a Files result over the room before it stages any of it', async () => {
|
||||
// DocumentPicker can exceed advisory room; reject before staging rather than copy assets the registry will refuse.
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
it.each([0, 2])(
|
||||
'deletes an over-cap Files result while preserving %i prior handles',
|
||||
async (held) => {
|
||||
const liveUris = Array.from({ length: held }, (_, index) => `${CACHE}/held-${index}.png`)
|
||||
const returnedUris = Array.from(
|
||||
{ length: BRIDGE_MEDIA_MAX_LIVE_HANDLES - held + 1 },
|
||||
(_, index) => `${CACHE}/doc-${index}.pdf`
|
||||
)
|
||||
const probe = harness({
|
||||
launchFiles: () =>
|
||||
Promise.resolve({
|
||||
canceled: false,
|
||||
assets: returnedUris.map((uri) => ({ uri, mimeType: 'application/pdf' }))
|
||||
})
|
||||
})
|
||||
for (const uri of liveUris) {
|
||||
probe.files.set(uri, new Uint8Array(4096))
|
||||
}
|
||||
probe.registry.mint(liveUris.map((uri) => ({ uri, mime: 'image/png', byteLength: 4096 })))
|
||||
for (const uri of returnedUris) {
|
||||
probe.files.set(uri, new Uint8Array(1024 * 1024))
|
||||
}
|
||||
const storedBytes = (): number =>
|
||||
[...probe.files.values()].reduce((bytes, file) => bytes + file.byteLength, 0)
|
||||
expect(storedBytes()).toBe(returnedUris.length * 1024 * 1024 + held * 4096)
|
||||
await expect(
|
||||
probe.serve('native.media.pick', { source: 'files', multiple: true })
|
||||
).rejects.toSatisfy((error) => refusalOf(error) === 'native_media_handle_cap')
|
||||
expect(probe.registry.liveCount()).toBe(held)
|
||||
expect(probe.discarded).toEqual(returnedUris)
|
||||
expect([...probe.files.keys()]).toEqual(liveUris)
|
||||
expect(storedBytes()).toBe(held * 4096)
|
||||
expect(probe.copied).toEqual([])
|
||||
expect(probe.written).toEqual([])
|
||||
probe.registry.releaseAll()
|
||||
expect(probe.files.size).toBe(0)
|
||||
}
|
||||
)
|
||||
|
||||
it('leaves provider originals intact when refusing an over-cap mixed result', async () => {
|
||||
const providerUri = 'content://media/external/images/media/42'
|
||||
const ownedUris = Array.from(
|
||||
{ length: BRIDGE_MEDIA_MAX_LIVE_HANDLES },
|
||||
(_, index) => `${CACHE}/picked-${index}.png`
|
||||
)
|
||||
const probe = harness({
|
||||
launchFiles: () =>
|
||||
Promise.resolve({
|
||||
canceled: false,
|
||||
assets: Array.from({ length: BRIDGE_MEDIA_MAX_LIVE_HANDLES + 1 }, (_, index) => ({
|
||||
uri: `${CACHE}/doc-${index}.pdf`,
|
||||
mimeType: 'application/pdf'
|
||||
}))
|
||||
assets: [providerUri, ...ownedUris].map((uri) => ({ uri, mimeType: 'image/png' }))
|
||||
})
|
||||
})
|
||||
for (let index = 0; index <= BRIDGE_MEDIA_MAX_LIVE_HANDLES; index += 1) {
|
||||
probe.files.set(`${CACHE}/doc-${index}.pdf`, bytesOf(4))
|
||||
for (const uri of [providerUri, ...ownedUris]) {
|
||||
probe.files.set(uri, new Uint8Array(1024 * 1024))
|
||||
}
|
||||
await expect(
|
||||
probe.serve('native.media.pick', { source: 'files', multiple: true })
|
||||
).rejects.toSatisfy((error) => refusalOf(error) === 'native_media_handle_cap')
|
||||
expect(probe.discarded).toEqual(ownedUris)
|
||||
expect([...probe.files.keys()]).toEqual([providerUri])
|
||||
expect(probe.files.get(providerUri)?.byteLength).toBe(1024 * 1024)
|
||||
expect(probe.registry.liveCount()).toBe(0)
|
||||
expect(probe.discarded).toEqual([])
|
||||
warn.mockRestore()
|
||||
expect(probe.copied).toEqual([])
|
||||
})
|
||||
|
||||
it('continues sweeping refused copies when one file cannot be deleted', async () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const returnedUris = Array.from(
|
||||
{ length: BRIDGE_MEDIA_MAX_LIVE_HANDLES + 1 },
|
||||
(_, index) => `${CACHE}/doc-${index}.pdf`
|
||||
)
|
||||
const probe = harness({
|
||||
launchFiles: () =>
|
||||
Promise.resolve({
|
||||
canceled: false,
|
||||
assets: returnedUris.map((uri) => ({ uri, mimeType: 'application/pdf' }))
|
||||
}),
|
||||
discard: (uri) => {
|
||||
probe.discarded.push(uri)
|
||||
if (uri === returnedUris[0]) {
|
||||
throw new Error('cache file is busy')
|
||||
}
|
||||
probe.files.delete(uri)
|
||||
}
|
||||
})
|
||||
try {
|
||||
for (const uri of returnedUris) {
|
||||
probe.files.set(uri, new Uint8Array(1024 * 1024))
|
||||
}
|
||||
await expect(
|
||||
probe.serve('native.media.pick', { source: 'files', multiple: true })
|
||||
).rejects.toSatisfy((error) => refusalOf(error) === 'native_media_handle_cap')
|
||||
expect(probe.discarded).toEqual(returnedUris)
|
||||
expect([...probe.files.keys()]).toEqual([returnedUris[0]])
|
||||
expect(probe.registry.liveCount()).toBe(0)
|
||||
} finally {
|
||||
warn.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('refuses an item bigger than the staging ceiling, and deletes what it picked', async () => {
|
||||
|
||||
@@ -167,6 +167,11 @@ export function createNativeMediaVerbServer(
|
||||
// DocumentPicker ignores room; enforce count before staging any bytes.
|
||||
function withinRoom(assets: readonly PickedAsset[]): readonly PickedAsset[] {
|
||||
if (assets.length > room) {
|
||||
for (const asset of assets) {
|
||||
if (deps.ownsStagedUri(asset.uri)) {
|
||||
discardQuietly(asset.uri)
|
||||
}
|
||||
}
|
||||
throw new BridgeNativeVerbRefusedError(
|
||||
'native_media_handle_cap',
|
||||
`that pick answered ${assets.length} items and this page has room for ${room}`
|
||||
|
||||
Reference in New Issue
Block a user