fix(extension): keep the chosen tab selected when activation fails

Selecting a tab and reopening the popup showed no target again. The selection
only ever existed as currentTabId, which means "the tab we successfully
injected into". Any activation failure — a player frame needing host access, a
page still loading, a document that navigated mid-injection — cleared it, so
the user's choice disappeared along with the failure.

The choice is now stored in its own right, persisted before activation starts
and kept across a failed one. GET_STATUS reports it as targetTabId with a
terminal state next to it: ready, activating, access_required or error, plus
the underlying message. The popup already highlights targetTabId, so the tab
stays visibly selected and can explain itself instead of silently vanishing.

Nothing retries on its own. Reactivation happens only when the user selects
again or grants the missing host access, which is what turned the previous
attempt at this into an endless reinjection loop on every popup open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Timo
2026-08-18 00:28:50 +02:00
parent 75a9ba5d3d
commit fd42de365a
2 changed files with 160 additions and 13 deletions
+49
View File
@@ -607,6 +607,55 @@ test('rejects a hidden cross-origin player after its iframe URL redirects', asyn
expect(await redirectedFrame.locator('video').getAttribute('data-koala-attached')).toBeNull();
});
test('keeps the tab selected when its activation fails', async ({ context, extensionId }) => {
// A page the extension is not allowed to script stands in for any activation
// failure the user can act on. Losing the selection here is what made the
// popup come back empty after it was closed and reopened.
const page = await context.newPage();
await page.goto('chrome://version');
const { tabId, response } = await selectTargetTab(context, extensionId, 'chrome://version/*');
expect(response?.status).not.toBe('ok');
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
expect(status).toMatchObject({
targetTabId: tabId,
targetReady: false,
targetActivationState: 'error'
});
expect(status.targetActivationError).toBeTruthy();
// Reopening the popup must not quietly retry and must not lose the choice.
const second = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
expect(second).toMatchObject({ targetTabId: tabId, targetActivationState: 'error' });
});
test('drops the selection only when the user clears it', async ({ context, extensionId, baseURL }) => {
const url = `${baseURL}/pages/simple-player.html`;
const page = await context.newPage();
await page.goto(url);
await page.waitForFunction(() => window.__fixtureReady === true);
const { tabId } = await selectTargetTab(context, extensionId, url);
await expect
.poll(() => getExtensionState(context, extensionId, { type: 'GET_STATUS' })
.then(state => state.targetTabId))
.toBe(tabId);
const cleared = await getExtensionState(context, extensionId, {
type: 'SET_TARGET_TAB',
tabId: null
});
expect(cleared).toMatchObject({ status: 'ok', tabId: null });
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
expect(status).toMatchObject({
targetTabId: null,
targetReady: false,
targetActivationState: 'none'
});
});
/**
* Reads one global from the top document and from the player frame separately,
* so a test can prove which frame a script was installed in.