Skip to content

Commit d01615e

Browse files
committed
Reject expired context-menu reservations
1 parent c8c5bfc commit d01615e

3 files changed

Lines changed: 24 additions & 10 deletions

File tree

src/chrome/src/context-menu-storage.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export function createContextMenuStorage(getStore) {
261261
});
262262
}
263263

264-
async function reserve(tabId, promptId, claimantId, onReserve) {
264+
async function reserve(tabId, promptId, claimantId, onReserve, now = Date.now()) {
265265
const normalizedPromptId = String(promptId || '');
266266
const normalizedClaimantId = String(claimantId || '');
267267
if (!normalizedPromptId || !normalizedClaimantId || typeof onReserve !== 'function') {
@@ -291,12 +291,14 @@ export function createContextMenuStorage(getStore) {
291291
}
292292
const samePrompt = String(activeClaim?.promptId || '') === normalizedPromptId;
293293
const sameClaimant = String(activeClaim?.claimantId || '') === normalizedClaimantId;
294-
if (!samePrompt || !sameClaimant) {
294+
const leaseExpiresAt = Number(activeClaim?.expiresAt || 0);
295+
const activeLease = leaseExpiresAt > Number(now);
296+
if (!samePrompt || !sameClaimant || !activeLease) {
295297
return {
296298
ok: true,
297299
reserved: false,
298-
reason: activeClaim ? 'leased' : 'claim-lost',
299-
leaseExpiresAt: Number(activeClaim?.expiresAt || 0) || undefined,
300+
reason: activeClaim && activeLease ? 'leased' : 'claim-lost',
301+
leaseExpiresAt: leaseExpiresAt || undefined,
300302
};
301303
}
302304

src/firefox/src/context-menu-storage.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export function createContextMenuStorage(getStore) {
261261
});
262262
}
263263

264-
async function reserve(tabId, promptId, claimantId, onReserve) {
264+
async function reserve(tabId, promptId, claimantId, onReserve, now = Date.now()) {
265265
const normalizedPromptId = String(promptId || '');
266266
const normalizedClaimantId = String(claimantId || '');
267267
if (!normalizedPromptId || !normalizedClaimantId || typeof onReserve !== 'function') {
@@ -291,12 +291,14 @@ export function createContextMenuStorage(getStore) {
291291
}
292292
const samePrompt = String(activeClaim?.promptId || '') === normalizedPromptId;
293293
const sameClaimant = String(activeClaim?.claimantId || '') === normalizedClaimantId;
294-
if (!samePrompt || !sameClaimant) {
294+
const leaseExpiresAt = Number(activeClaim?.expiresAt || 0);
295+
const activeLease = leaseExpiresAt > Number(now);
296+
if (!samePrompt || !sameClaimant || !activeLease) {
295297
return {
296298
ok: true,
297299
reserved: false,
298-
reason: activeClaim ? 'leased' : 'claim-lost',
299-
leaseExpiresAt: Number(activeClaim?.expiresAt || 0) || undefined,
300+
reason: activeClaim && activeLease ? 'leased' : 'claim-lost',
301+
leaseExpiresAt: leaseExpiresAt || undefined,
300302
};
301303
}
302304

test/run.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21926,19 +21926,29 @@ test('context-menu prompt storage enforces a durable expiring lease', async () =
2192621926
assert.equal(data.get(storage.claimKey(prompt.tabId))?.claimantId, 'panel-b', `${label}: the durable claim should record the current owner`);
2192721927

2192821928
let reservations = 0;
21929+
const reservationTime = takeover.leaseExpiresAt - 1;
2192921930
const staleOwner = await storage.reserve(prompt.tabId, prompt.id, 'panel-a', () => {
2193021931
reservations += 1;
2193121932
return { accepted: true };
21932-
});
21933+
}, reservationTime);
2193321934
const currentOwner = await storage.reserve(prompt.tabId, prompt.id, 'panel-b', () => {
2193421935
reservations += 1;
2193521936
return { accepted: true, requestId: 'reserved-run' };
21936-
});
21937+
}, reservationTime);
2193721938
assert.equal(staleOwner.reserved, false, `${label}: an expired prior owner must not reserve the run after takeover`);
2193821939
assert.equal(currentOwner.reserved, true, `${label}: the current claimant should reserve the run`);
2193921940
assert.equal(currentOwner.requestId, 'reserved-run', `${label}: reservation should return the detached-run acknowledgement`);
2194021941
assert.equal(reservations, 1, `${label}: ownership validation and the run reservation callback should be exactly once`);
2194121942

21943+
const expiredOwner = await storage.reserve(prompt.tabId, prompt.id, 'panel-b', () => {
21944+
reservations += 1;
21945+
return { accepted: true };
21946+
}, takeover.leaseExpiresAt);
21947+
assert.equal(expiredOwner.reserved, false, `${label}: a claimant must not reserve at or after lease expiry`);
21948+
assert.equal(expiredOwner.reason, 'claim-lost', `${label}: expired ownership should require a fresh claim`);
21949+
assert.equal(expiredOwner.leaseExpiresAt, takeover.leaseExpiresAt, `${label}: expiry rejection should retain retry timing`);
21950+
assert.equal(reservations, 1, `${label}: an expired claimant must not invoke the run reservation callback`);
21951+
2194221952
const released = await storage.release(prompt.tabId, prompt.id, 'panel-b');
2194321953
assert.equal(released.released, true, `${label}: a panel that becomes hidden should relinquish its lease`);
2194421954
assert.equal(released.prompt?.id, prompt.id, `${label}: releasing ownership must retain the durable prompt`);

0 commit comments

Comments
 (0)