diff --git a/mobile/src/platform/native-wakelock.test.ts b/mobile/src/platform/native-wakelock.test.ts index 27dfbfeb77f..6ff03fc8139 100644 --- a/mobile/src/platform/native-wakelock.test.ts +++ b/mobile/src/platform/native-wakelock.test.ts @@ -115,4 +115,26 @@ describe('a release issued while its own activate is still in flight', () => { gated.settle('activate:orca-mobile-dictation:1:c') await expect(first).resolves.toEqual({ active: true }) }) + + it('does not ask the device again for a tag a queued release already gave back', async () => { + // `dispose` queues behind the tag's own operations, so by the time it runs the release ahead + // of it may have returned the tag. Deactivating an unheld tag is a native call this module + // does not make: its failure would read to the page as a lock it could not drop. + const gated = createGatedDevice() + const server = createNativeWakelockServer(gated.device) + const activated = server.serve({ active: true, tag: 'orca-mobile-dictation:1:e' }) + await settleMicrotasks() + gated.settle('activate:orca-mobile-dictation:1:e') + await expect(activated).resolves.toEqual({ active: true }) + const released = server.serve({ active: false, tag: 'orca-mobile-dictation:1:e' }) + await settleMicrotasks() + server.dispose() + gated.settle('deactivate:orca-mobile-dictation:1:e') + await expect(released).resolves.toEqual({ active: false }) + await settleMicrotasks() + expect(gated.calls).toEqual([ + 'activate:orca-mobile-dictation:1:e', + 'deactivate:orca-mobile-dictation:1:e' + ]) + }) }) diff --git a/mobile/src/platform/native-wakelock.ts b/mobile/src/platform/native-wakelock.ts index 49e1da7cf49..0c785df0e67 100644 --- a/mobile/src/platform/native-wakelock.ts +++ b/mobile/src/platform/native-wakelock.ts @@ -104,8 +104,13 @@ export function createNativeWakelockServer(device: WakelockDevice): NativeWakelo // Quiet, for the reason every other dispose here is: this runs while a screen is going // away, and a device that would not drop a tag is not something the page can be told about. // Forgotten only on success, so one this device refused stays recorded and a later release - // still reaches it. Queued behind that tag's own operations rather than racing them. + // still reaches it. Queued behind that tag's own operations rather than racing them, and + // re-reading the set once it runs: a release already in flight may have given it back, and + // deactivating an unheld tag is a native call this module does not make. void enqueue(tag, async () => { + if (!held.has(tag)) { + return + } await device.deactivate(tag) held.delete(tag) }).catch(() => undefined)