fix(extension): remove webNavigation permission

This commit is contained in:
Timo
2026-08-17 18:41:40 +02:00
parent 76adafd1e4
commit 4d7897028b
7 changed files with 30 additions and 86 deletions
+2 -18
View File
@@ -2190,7 +2190,7 @@ function setPageApiSeekEnabled(enabled) {
}
async function deactivateMediaFrameMonitors(tabId) {
const targets = await listMediaFrameScriptTargets(chrome, tabId);
const targets = listMediaFrameScriptTargets(tabId);
await Promise.all(targets.map(async target => {
const documentId = target.documentIds?.[0];
const frameId = target.frameIds?.[0];
@@ -2293,7 +2293,7 @@ function createTargetActivationSupersededError() {
}
async function injectMediaFrameMonitors(tabId, contentTarget) {
const targets = await listMediaFrameScriptTargets(chrome, tabId);
const targets = listMediaFrameScriptTargets(tabId);
let injectedCount = 0;
await Promise.all(targets.map(async target => {
try {
@@ -2923,22 +2923,6 @@ if (chrome.permissions?.onAdded?.addListener) {
});
}
if (chrome.webNavigation?.onCompleted?.addListener) {
chrome.webNavigation.onCompleted.addListener((details) => {
ensureState().then(async () => {
const tabId = normalizeTabId(details?.tabId);
const frameId = normalizeFrameId(details?.frameId);
if (tabId === null
|| tabId !== normalizeTabId(currentTabId)
|| frameId === 0
|| (frameId !== normalizeFrameId(currentTargetFrameId) && currentTargetHasVideo)) {
return;
}
await refreshCurrentMediaTarget(tabId, { queueIfRunning: true });
}).catch(error => addLog(`Media frame navigation refresh failed: ${error.message}`, 'warn'));
});
}
if (chrome.tabs?.onRemoved?.addListener) {
chrome.tabs.onRemoved.addListener((removedTabId) => {
ensureState().then(async () => {
+1 -2
View File
@@ -11,8 +11,7 @@
"scripting",
"alarms",
"activeTab",
"notifications",
"webNavigation"
"notifications"
],
"host_permissions": [
"<all_urls>"
+2 -16
View File
@@ -427,21 +427,7 @@ function contentTarget(tabId, selected) {
};
}
export async function listMediaFrameScriptTargets(chromeApi, tabId) {
if (chromeApi.webNavigation?.getAllFrames) {
try {
const frames = await chromeApi.webNavigation.getAllFrames({ tabId });
if (Array.isArray(frames) && frames.length > 0) {
return frames
.filter(frame => Number.isInteger(frame?.frameId))
.map(frame => (typeof frame.documentId === 'string' && frame.documentId
? { tabId, documentIds: [frame.documentId] }
: { tabId, frameIds: [frame.frameId] }));
}
} catch {
// Older browsers fall back to the all-frames probe below.
}
}
export function listMediaFrameScriptTargets(tabId) {
return [{ tabId, allFrames: true }];
}
@@ -466,7 +452,7 @@ export async function resolveMediaContentTarget(chromeApi, tabId, {
let ambiguous = false;
for (let attempt = 0; attempt < attempts; attempt++) {
const scriptTargets = await listMediaFrameScriptTargets(chromeApi, tabId);
const scriptTargets = listMediaFrameScriptTargets(tabId);
let results = await executeInAccessibleFrames(
chromeApi,
scriptTargets,
+6 -42
View File
@@ -155,55 +155,19 @@ describe('cross-origin media-frame targeting', () => {
});
});
it('keeps an accessible player when an unrelated child frame is denied', async () => {
const top = frame(0, {
bestVideo: null,
videoCount: 0,
embeddedFrames: [
{
href: 'https://player-8.example/embed',
origin: 'https://player-8.example',
area: 830 * 498,
width: 830,
height: 498,
visible: true,
mediaHint: true
},
{
href: 'https://widget-denied.example/frame',
origin: 'https://widget-denied.example',
area: 300 * 250,
width: 300,
height: 250,
visible: true,
mediaHint: false
}
]
});
const player = frame(8);
const getAllFrames = vi.fn().mockResolvedValue([
{ frameId: 0, documentId: 'document-0' },
{ frameId: 8, documentId: 'document-8' },
{ frameId: 9, documentId: 'document-9' }
]);
const executeScript = vi.fn().mockImplementation(async ({ target, func }) => {
const documentId = target.documentIds?.[0];
if (documentId === 'document-9') throw new Error('Cannot access contents of the page');
if (func?.name !== 'inspectMediaFrame') return [];
return documentId === 'document-8' ? [player] : [top];
});
it('uses all-frame probing without a navigation permission', async () => {
const executeScript = vi.fn().mockResolvedValue([frame(8)]);
await expect(resolveMediaContentTarget(
{ scripting: { executeScript }, webNavigation: { getAllFrames } },
{ scripting: { executeScript } },
42,
{ attempts: 1, probeDelayMs: 0 }
)).resolves.toMatchObject({
frameId: 8,
documentId: 'document-8',
hasVideo: true
hasVideo: true,
scriptTarget: { tabId: 42, documentIds: ['document-8'] }
});
expect(executeScript.mock.calls.some(([options]) => options.target.allFrames === true)).toBe(false);
expect(executeScript.mock.calls.some(([options]) => options.target.documentIds?.[0] === 'document-9')).toBe(true);
expect(executeScript.mock.calls[0][0].target).toEqual({ tabId: 42, allFrames: true });
});
it('does not trust parent visibility from an older probe token', () => {
+12 -5
View File
@@ -39,14 +39,14 @@ describe('target tab lifecycle', () => {
it('removes monitors injected by a superseded cross-tab activation', () => {
expect(backgroundSource).toContain('function isTargetActivationSuperseded(tabId, activationGeneration)');
expect(backgroundSource).toContain('activationGeneration\n });');
expect(backgroundSource).toMatch(/navigationRetries: navigationRetries - 1,\s*activationGeneration\s*\}\)/);
expect(backgroundSource).toMatch(/await injectMediaFrameMonitors\(tabId, contentTarget\);[\s\S]*if \(isTargetActivationSuperseded\(tabId, activationGeneration\)\)[\s\S]*await deactivateMediaFrameMonitors\(tabId\);/);
expect(backgroundSource).toContain("error.code = 'target_activation_superseded'");
});
it('binds cross-origin targets to an exact document and monitors every accessible frame', () => {
it('uses all-frame probing for cross-origin targets without navigation permissions', () => {
expect(backgroundSource).toContain("files: ['media-frame-monitor.js']");
expect(backgroundSource).toContain('const targets = await listMediaFrameScriptTargets(chrome, tabId)');
expect(backgroundSource).toContain('const targets = listMediaFrameScriptTargets(tabId)');
expect(backgroundSource).toContain('One denied widget frame must not block the selected player');
expect(backgroundSource).toContain("navigationError.code = 'media_target_navigated'");
expect(backgroundSource).toContain("{ type: 'MEDIA_MONITOR_DEACTIVATE' }");
@@ -57,8 +57,15 @@ describe('target tab lifecycle', () => {
expect(monitorSource).toContain('if (!force && nextSignature === lastCandidateSignature) return');
expect(monitorSource).toContain("const MEDIA_STATE_EVENTS = ['play', 'pause', 'loadedmetadata'");
expect(monitorSource).toContain("node.querySelector?.('video, iframe, frame')");
expect(manifest.permissions).toContain('webNavigation');
expect(backgroundSource).toContain('chrome.webNavigation.onCompleted.addListener');
expect(manifest.permissions).toEqual([
'storage',
'tabs',
'scripting',
'alarms',
'activeTab',
'notifications'
]);
expect(backgroundSource).not.toMatch(/chrome\.(?:web)?Navigation/);
});
it('serializes content commands and coalesces target refreshes', () => {