feat(popup): Add Firefox default_area and theme_icons support (#2097)

Co-authored-by: Aaron <aaronklinker1@gmail.com>
This commit is contained in:
wotan-allfather
2026-02-09 07:28:26 +00:00
committed by GitHub
parent 2c57840c44
commit 19456ec855
6 changed files with 135 additions and 1 deletions
+13
View File
@@ -470,6 +470,19 @@ When you define a Newtab entrypoint, WXT will automatically update the manifest
/>
<meta name="manifest.type" content="page_action|browser_action" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Firefox only: where to place the action button -->
<meta
name="manifest.default_area"
content="navbar|menupanel|tabstrip|personaltoolbar"
/>
<!-- Firefox only: icons for light/dark themes -->
<meta
name="manifest.theme_icons"
content="[
{ light: '/icon-light-16.png', dark: '/icon-dark-16.png', size: 16 },
{ light: '/icon-light-32.png', dark: '/icon-dark-32.png', size: 32 }
]"
/>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
@@ -106,6 +106,88 @@ describe('Manifest Utils', () => {
expect(actual[expectedType]).toEqual(expected);
},
);
it('should include default_area for Firefox in mv3', async () => {
const popup = fakePopupEntrypoint({
options: {
// @ts-expect-error: Force this to be undefined
mv2Key: null,
defaultArea: 'navbar',
},
outputDir: outDir,
skipped: false,
});
const buildOutput = fakeBuildOutput();
setFakeWxt({
config: {
manifestVersion: 3,
outDir,
},
});
const { manifest: actual } = await generateManifest(
[popup],
buildOutput,
);
expect((actual.action as any).default_area).toBe('navbar');
});
it('should include theme_icons for Firefox in mv3', async () => {
const themeIcons = [
{ light: '/icon-light-16.png', dark: '/icon-dark-16.png', size: 16 },
{ light: '/icon-light-32.png', dark: '/icon-dark-32.png', size: 32 },
];
const popup = fakePopupEntrypoint({
options: {
// @ts-expect-error: Force this to be undefined
mv2Key: null,
themeIcons,
},
outputDir: outDir,
skipped: false,
});
const buildOutput = fakeBuildOutput();
setFakeWxt({
config: {
manifestVersion: 3,
outDir,
},
});
const { manifest: actual } = await generateManifest(
[popup],
buildOutput,
);
expect((actual.action as any).theme_icons).toEqual(themeIcons);
});
it('should include default_area for Firefox in mv2', async () => {
const popup = fakePopupEntrypoint({
options: {
// @ts-expect-error: Force this to be undefined
mv2Key: null,
defaultArea: 'menupanel',
},
outputDir: outDir,
skipped: false,
});
const buildOutput = fakeBuildOutput();
setFakeWxt({
config: {
manifestVersion: 2,
outDir,
},
});
const { manifest: actual } = await generateManifest(
[popup],
buildOutput,
);
expect((actual.browser_action as any).default_area).toBe('menupanel');
});
});
describe('action without popup', () => {
@@ -276,16 +276,20 @@ async function getPopupEntrypoint(
defaultIcon: options.defaultIcon,
defaultTitle: options.title,
mv2Key: options.type,
defaultArea: options.defaultArea,
},
wxt.config.browser,
);
if (stictOptions.mv2Key && stictOptions.mv2Key !== 'page_action')
stictOptions.mv2Key = 'browser_action';
// themeIcons is an array of objects, not a per-browser option
const themeIcons = options.themeIcons;
return {
type: 'popup',
name: 'popup',
options: stictOptions,
options: { ...stictOptions, themeIcons },
inputPath: info.inputPath,
outputDir: wxt.config.outDir,
};
+6
View File
@@ -281,6 +281,12 @@ function addEntrypoints(
if (popup.options.browserStyle)
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
options.browser_style = popup.options.browserStyle;
if (popup.options.defaultArea)
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
options.default_area = popup.options.defaultArea;
if (popup.options.themeIcons)
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
options.theme_icons = popup.options.themeIcons;
if (manifest.manifest_version === 3) {
manifest.action = {
...manifest.action,
@@ -146,6 +146,10 @@ export const fakePopupEntrypoint = fakeObjectCreator<PopupEntrypoint>(() => ({
'page_action',
undefined,
]),
// Firefox-specific options - kept undefined by default to avoid breaking existing tests
browserStyle: undefined,
defaultArea: undefined,
themeIcons: undefined,
},
skipped: faker.datatype.boolean(),
}));
+25
View File
@@ -648,6 +648,19 @@ export interface IsolatedWorldContentScriptEntrypointOptions extends BaseContent
world?: 'ISOLATED';
}
/**
* Firefox theme icon definition for light/dark mode support.
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action#theme_icons
*/
export interface ThemeIcon {
/** Path to the icon shown when the browser uses a light theme. */
light: string;
/** Path to the icon shown when the browser uses a dark theme. */
dark: string;
/** Icon size in pixels. */
size: number;
}
export interface PopupEntrypointOptions extends BaseEntrypointOptions {
/**
* Defaults to "browser_action" to be equivalent to MV3's "action" key
@@ -656,6 +669,18 @@ export interface PopupEntrypointOptions extends BaseEntrypointOptions {
defaultIcon?: Record<string, string>;
defaultTitle?: PerBrowserOption<string>;
browserStyle?: PerBrowserOption<boolean>;
/**
* Firefox only. Defines the part of the browser in which the button is initially placed.
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#default_area
*/
defaultArea?: PerBrowserOption<
'navbar' | 'menupanel' | 'tabstrip' | 'personaltoolbar'
>;
/**
* Firefox only. Icons for light and dark themes.
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#theme_icons
*/
themeIcons?: ThemeIcon[];
}
export interface OptionsEntrypointOptions extends BaseEntrypointOptions {