diff --git a/e2e/tests/manifest-content.test.ts b/e2e/tests/manifest-content.test.ts index 8720966b..e344d07d 100644 --- a/e2e/tests/manifest-content.test.ts +++ b/e2e/tests/manifest-content.test.ts @@ -194,15 +194,15 @@ describe('Manifest Content', () => { }); describe('content_scripts', () => { - it('should group content scripts and styles together based on their matches and run_at', async () => { + it('should group content scripts and styles together based on their manifest properties', async () => { const project = new TestProject(); project.addFile( 'entrypoints/one.content/index.ts', `import "./style.css"; - export default defineContentScript({ - matches: ["*://google.com/*"], - main: () => {}, - })`, + export default defineContentScript({ + matches: ["*://google.com/*"], + main: () => {}, + })`, ); project.addFile( 'entrypoints/one.content/style.css', @@ -211,11 +211,11 @@ describe('Manifest Content', () => { project.addFile( 'entrypoints/two.content/index.ts', `import "./style.css"; - export default defineContentScript({ - matches: ["*://google.com/*"], - runAt: "document_end", - main: () => {}, - })`, + export default defineContentScript({ + matches: ["*://google.com/*"], + runAt: "document_end", + main: () => {}, + })`, ); project.addFile( 'entrypoints/two.content/style.css', @@ -224,11 +224,11 @@ describe('Manifest Content', () => { project.addFile( 'entrypoints/three.content/index.ts', `import "./style.css"; - export default defineContentScript({ - matches: ["*://google.com/*"], - runAt: "document_end", - main: () => {}, - })`, + export default defineContentScript({ + matches: ["*://google.com/*"], + runAt: "document_end", + main: () => {}, + })`, ); project.addFile( 'entrypoints/three.content/style.css', @@ -237,11 +237,11 @@ describe('Manifest Content', () => { project.addFile( 'entrypoints/four.content/index.ts', `import "./style.css"; - export default defineContentScript({ - matches: ["*://duckduckgo.com/*"], - runAt: "document_end", - main: () => {}, - })`, + export default defineContentScript({ + matches: ["*://duckduckgo.com/*"], + runAt: "document_end", + main: () => {}, + })`, ); project.addFile( 'entrypoints/four.content/style.css', @@ -276,15 +276,15 @@ describe('Manifest Content', () => { project.addFile( 'entrypoints/one.content/index.ts', `export default defineContentScript({ - matches: ["*://google.com/*"], - main: () => {}, - })`, + matches: ["*://google.com/*"], + main: () => {}, + })`, ); project.addFile( 'entrypoints/two.content/style.css', `body { - background-color: red; - }`, + background-color: red; + }`, ); project.setConfigFileConfig({ manifest: { diff --git a/src/core/types/external.ts b/src/core/types/external.ts index 9caf0e4a..6c761b31 100644 --- a/src/core/types/external.ts +++ b/src/core/types/external.ts @@ -416,6 +416,8 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint { * - `"manual"` - Exclude the CSS from the manifest. You are responsible for manually loading it * onto the page. Use `browser.runtime.getURL("content-scripts/.css")` to get the file's * URL + * - `"ui"` - Exclude the CSS from the manifest. CSS will be automatically added to your UI when + * calling `createContentScriptUi` * * @default "manifest" */ diff --git a/src/core/utils/__tests__/content-scripts.test.ts b/src/core/utils/__tests__/content-scripts.test.ts index 0732a7a6..125d0ff1 100644 --- a/src/core/utils/__tests__/content-scripts.test.ts +++ b/src/core/utils/__tests__/content-scripts.test.ts @@ -1,26 +1,36 @@ import { describe, expect, it } from 'vitest'; import { hashContentScriptOptions } from '../content-scripts'; +import { fakeInternalConfig } from '../../../testing/fake-objects'; describe('Content Script Utils', () => { describe('hashContentScriptOptions', () => { it('should return a string containing all the options with defaults applied', () => { - const hash = hashContentScriptOptions({ matches: [] }); + const hash = hashContentScriptOptions( + { matches: [] }, + fakeInternalConfig(), + ); expect(hash).toMatchInlineSnapshot( - '"[[\\"allFrames\\",false],[\\"excludeGlobs\\",[]],[\\"excludeMatches\\",[]],[\\"includeGlobs\\",[]],[\\"matchAboutBlank\\",false],[\\"matches\\",[]],[\\"matchOriginAsFallback\\",false],[\\"runAt\\",\\"document_idle\\"],[\\"world\\",\\"ISOLATED\\"]]"', + '"[[\\"all_frames\\",false],[\\"exclude_globs\\",[]],[\\"exclude_matches\\",[]],[\\"include_globs\\",[]],[\\"match_about_blank\\",false],[\\"match_origin_as_fallback\\",false],[\\"matches\\",[]],[\\"run_at\\",\\"document_idle\\"],[\\"world\\",\\"ISOLATED\\"]]"', ); }); it('should be consistent regardless of the object ordering and default values', () => { - const hash1 = hashContentScriptOptions({ - allFrames: true, - matches: ['*://google.com/*', '*://duckduckgo.com/*'], - matchAboutBlank: false, - }); - const hash2 = hashContentScriptOptions({ - matches: ['*://duckduckgo.com/*', '*://google.com/*'], - allFrames: true, - }); + const hash1 = hashContentScriptOptions( + { + allFrames: true, + matches: ['*://google.com/*', '*://duckduckgo.com/*'], + matchAboutBlank: false, + }, + fakeInternalConfig(), + ); + const hash2 = hashContentScriptOptions( + { + matches: ['*://duckduckgo.com/*', '*://google.com/*'], + allFrames: true, + }, + fakeInternalConfig(), + ); expect(hash1).toBe(hash2); }); diff --git a/src/core/utils/content-scripts.ts b/src/core/utils/content-scripts.ts index 2f31b124..7629761d 100644 --- a/src/core/utils/content-scripts.ts +++ b/src/core/utils/content-scripts.ts @@ -9,18 +9,27 @@ import { resolvePerBrowserOption } from './entrypoints'; */ export function hashContentScriptOptions( options: ContentScriptEntrypoint['options'], + config: InternalConfig, ): string { - const withDefaults: ContentScriptEntrypoint['options'] = { - excludeGlobs: [], - excludeMatches: [], - includeGlobs: [], - matchAboutBlank: false, - matchOriginAsFallback: false, - runAt: 'document_idle', - allFrames: false, + const simplifiedOptions = mapWxtOptionsToContentScript(options, config); + + // Remove undefined fields and use defaults to generate hash + Object.keys(simplifiedOptions).forEach((key) => { + // @ts-expect-error: key not typed as keyof ... + if (simplifiedOptions[key] == null) delete simplifiedOptions[key]; + }); + + const withDefaults: Manifest.ContentScript = { + exclude_globs: [], + exclude_matches: [], + include_globs: [], + match_about_blank: false, + run_at: 'document_idle', + all_frames: false, + // @ts-expect-error - not in type + match_origin_as_fallback: false, world: 'ISOLATED', - // TODO: strip undefined fields from options object to improve content script grouping. - ...options, + ...simplifiedOptions, }; return JSON.stringify( Object.entries(withDefaults) diff --git a/src/core/utils/manifest.ts b/src/core/utils/manifest.ts index 0688d603..8463de04 100644 --- a/src/core/utils/manifest.ts +++ b/src/core/utils/manifest.ts @@ -314,7 +314,7 @@ function addEntrypoints( ); } else { const hashToEntrypointsMap = contentScripts.reduce((map, script) => { - const hash = hashContentScriptOptions(script.options); + const hash = hashContentScriptOptions(script.options, config); if (map.has(hash)) map.get(hash)?.push(script); else map.set(hash, [script]); return map;