From 22f82a67ee793d27a6642f32d7922f2bf72388cc Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 5 Feb 2024 22:49:18 -0600 Subject: [PATCH] feat: Automatically convert MV3 `web_accessible_resources` to MV2 (#423) --- src/core/utils/__tests__/manifest.test.ts | 49 ++++++++++++++++ src/core/utils/manifest.ts | 70 ++++++++++++++++++----- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/core/utils/__tests__/manifest.test.ts b/src/core/utils/__tests__/manifest.test.ts index e0ca4643..9f49f103 100644 --- a/src/core/utils/__tests__/manifest.test.ts +++ b/src/core/utils/__tests__/manifest.test.ts @@ -851,6 +851,55 @@ describe('Manifest Utils', () => { 'content-scripts/one.css', ]); }); + + it('should convert mv3 items to mv2 strings automatically', async () => { + setFakeWxt({ + config: { + outDir, + manifestVersion: 2, + manifest: { + web_accessible_resources: [ + { + matches: ['*://*/*'], + resources: ['/icon-128.png'], + }, + { + matches: ['https://google.com'], + resources: ['/icon-128.png', '/icon-32.png'], + }, + ], + }, + }, + }); + + const { manifest: actual } = await generateManifest( + [], + fakeBuildOutput(), + ); + + expect(actual.web_accessible_resources).toEqual([ + '/icon-128.png', + '/icon-32.png', + ]); + }); + + it('should convert mv2 strings to mv3 items with a warning automatically', async () => { + setFakeWxt({ + config: { + outDir, + manifestVersion: 3, + manifest: { + web_accessible_resources: ['/icon.svg'], + }, + }, + }); + + await expect(() => + generateManifest([], fakeBuildOutput()), + ).rejects.toThrow( + 'Non-MV3 web_accessible_resources detected: ["/icon.svg"]. When manually defining web_accessible_resources, define them as MV3 objects ({ matches: [...], resources: [...] }), and WXT will automatically convert them to MV2 when necessary.', + ); + }); }); describe('transformManifest option', () => { diff --git a/src/core/utils/manifest.ts b/src/core/utils/manifest.ts index 2a13e757..27a6a2cc 100644 --- a/src/core/utils/manifest.ts +++ b/src/core/utils/manifest.ts @@ -77,7 +77,7 @@ export async function generateManifest( }; const userManifest = wxt.config.manifest; - const manifest = defu( + let manifest = defu( userManifest, baseManifest, ) as Manifest.WebExtensionManifest; @@ -116,6 +116,13 @@ export async function generateManifest( wxt.config.transformManifest(manifest); await wxt.hooks.callHook('build:manifestGenerated', wxt, manifest); + if (wxt.config.manifestVersion === 2) + convertWebAccessibleResourcesToMv2(manifest); + + if (wxt.config.manifestVersion === 3) { + validateMv3WebAccessbileResources(manifest); + } + if (manifest.name == null) throw Error( "Manifest 'name' is missing. Either:\n1. Set the name in your /package.json\n2. Set a name via the manifest option in your wxt.config.ts", @@ -501,7 +508,8 @@ export function getContentScriptCssWebAccessibleResources( contentScripts: ContentScriptEntrypoint[], contentScriptCssMap: Record, ): any[] { - const resources: any[] = []; + const resources: Manifest.WebExtensionManifestWebAccessibleResourcesC2ItemType[] = + []; contentScripts.forEach((script) => { if (script.options.cssInjectionMode !== 'ui') return; @@ -509,17 +517,13 @@ export function getContentScriptCssWebAccessibleResources( const cssFile = contentScriptCssMap[script.name]; if (cssFile == null) return; - if (wxt.config.manifestVersion === 2) { - resources.push(cssFile); - } else { - resources.push({ - resources: [cssFile], - matches: resolvePerBrowserOption( - script.options.matches, - wxt.config.browser, - ).map((matchPattern) => stripPathFromMatchPattern(matchPattern)), - }); - } + resources.push({ + resources: [cssFile], + matches: resolvePerBrowserOption( + script.options.matches, + wxt.config.browser, + ).map((matchPattern) => stripPathFromMatchPattern(matchPattern)), + }); }); return resources; @@ -573,3 +577,43 @@ export function stripPathFromMatchPattern(pattern: string) { const startOfPath = pattern.indexOf('/', protocolSepIndex + 3); return pattern.substring(0, startOfPath) + '/*'; } + +/** + * Converts all MV3 web accessible resources to their MV2 forms. MV3 web accessible resources are + * generated in this file, and may be defined by the user in their manifest. In both cases, when + * targetting MV2, automatically convert their definitions down to the basic MV2 array. + */ +export function convertWebAccessibleResourcesToMv2( + manifest: Manifest.WebExtensionManifest, +): void { + if (manifest.web_accessible_resources == null) return; + + manifest.web_accessible_resources = Array.from( + new Set( + manifest.web_accessible_resources.flatMap((item) => { + if (typeof item === 'string') return item; + return item.resources; + }), + ), + ); +} + +/** + * Make sure all resources are in MV3 format. If not, add a wanring + */ +export function validateMv3WebAccessbileResources( + manifest: Manifest.WebExtensionManifest, +): void { + if (manifest.web_accessible_resources == null) return; + + const stringResources = manifest.web_accessible_resources.filter( + (item) => typeof item === 'string', + ); + if (stringResources.length > 0) { + throw Error( + `Non-MV3 web_accessible_resources detected: ${JSON.stringify( + stringResources, + )}. When manually defining web_accessible_resources, define them as MV3 objects ({ matches: [...], resources: [...] }), and WXT will automatically convert them to MV2 when necessary.`, + ); + } +}