Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ words:
- unwatch
- USSS
- vitepress
- waterfox
- webext
- webextension
- webextrc
Expand Down
57 changes: 57 additions & 0 deletions packages/wxt/src/core/__tests__/resolve-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,61 @@ describe('resolveConfig', () => {
);
});
});

describe('actualBrowser', () => {
it.each<[string, string]>([
['chrome', 'chromium'],
['edge', 'chromium'],
['brave', 'chromium'],
['opera', 'chromium'],
['chromium', 'chromium'],
['firefox', 'firefox'],
['safari', 'safari'],
['ios', 'safari'],
])(
'should resolve browser "%s" to actualBrowser "%s"',
async (browser, expected) => {
const config = await resolveConfig({ browser }, 'build');

expect(config.actualBrowser).toBe(expected);
},
);

it('should resolve unknown browsers to themselves', async () => {
const config = await resolveConfig({ browser: 'waterfox' }, 'build');

expect(config.actualBrowser).toBe('waterfox');
});

it('should let a custom targetBrowserMap entry win over the defaults', async () => {
const config = await resolveConfig(
{
browser: 'waterfox',
targetBrowserMap: { waterfox: 'firefox' },
},
'build',
);

expect(config.actualBrowser).toBe('firefox');
});

it('should not change the raw browser label', async () => {
const config = await resolveConfig({ browser: 'ios' }, 'build');

expect(config.browser).toBe('ios');
});

it('should keep the raw browser label in the output directory', async () => {
const config = await resolveConfig({ browser: 'ios' }, 'build');

expect(config.outDir).toContain(`ios-mv${config.manifestVersion}`);
});

it('should default the manifest version based on the actual browser', async () => {
const config = await resolveConfig({ browser: 'ios' }, 'build');

expect(config.actualBrowser).toBe('safari');
expect(config.manifestVersion).toBe(2);
});
});
});
12 changes: 11 additions & 1 deletion packages/wxt/src/core/resolve-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { NullishRequired } from './utils/types';
import { pathExists } from './utils/fs';
import { normalizePath } from './utils';
import { glob } from 'tinyglobby';
import {
defaultTargetBrowserMap,
resolveActualBrowser,
} from './utils/target-browser';
import { builtinModules } from '../builtin-modules';
import { getEslintVersion } from './utils/eslint';
import { safeStringToNumber } from './utils/number';
Expand Down Expand Up @@ -79,9 +83,14 @@ export async function resolveConfig(
`Current target browser \`${browser}\` is not in your \`targetBrowsers\` list!`,
);
}
const targetBrowserMap = {
...defaultTargetBrowserMap,
...mergedConfig.targetBrowserMap,
};
const actualBrowser = resolveActualBrowser(browser, targetBrowserMap);
const manifestVersion =
mergedConfig.manifestVersion ??
(browser === 'firefox' || browser === 'safari' ? 2 : 3);
(actualBrowser === 'firefox' || actualBrowser === 'safari' ? 2 : 3);
const mode = mergedConfig.mode ?? COMMAND_MODES[command];
const env: ConfigEnv = { browser, command, manifestVersion, mode };

Expand Down Expand Up @@ -197,6 +206,7 @@ export async function resolveConfig(
return {
browser,
targetBrowsers,
actualBrowser,
command,
debug,
entrypointsDir,
Expand Down
46 changes: 46 additions & 0 deletions packages/wxt/src/core/utils/__tests__/manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,52 @@ describe('Manifest Utils', () => {
]);
}
});

it('should key browser-specific checks off actualBrowser, not the raw browser label', async () => {
const cs: ContentScriptEntrypoint = {
type: 'content-script',
name: 'one',
inputPath: 'entrypoints/one.content.ts',
outputDir: contentScriptOutDir,
options: {
matches: ['*://google.com/*'],
cssInjectionMode: 'ui',
},
skipped: false,
};
const styles: OutputAsset = {
type: 'asset',
fileName: 'content-scripts/one.css',
};

const entrypoints = [cs];
const buildOutput: Omit<BuildOutput, 'manifest'> = {
publicAssets: [],
steps: [{ entrypoints: cs, chunks: [styles] }],
};

setFakeWxt({
config: {
outDir,
command: 'build',
manifestVersion: 3,
browser: 'ios',
actualBrowser: 'safari',
},
});

const { manifest: actual } = await generateManifest(
entrypoints,
buildOutput,
);

expect(actual.web_accessible_resources).toEqual([
{
matches: ['*://google.com/*'],
resources: ['content-scripts/one.css'],
},
]);
});
});

describe('registration', () => {
Expand Down
36 changes: 20 additions & 16 deletions packages/wxt/src/core/utils/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ export async function generateManifest(
manifest.version = version;
manifest.version_name =
// Firefox doesn't support version_name
wxt.config.browser === 'firefox' || versionName === version
wxt.config.actualBrowser === 'firefox' || versionName === version
? undefined
: versionName;

// Warn if building for Firefox without data_collection_permissions
if (
wxt.config.browser === 'firefox' &&
wxt.config.actualBrowser === 'firefox' &&
!userManifest.browser_specific_settings?.gecko
?.data_collection_permissions &&
!wxt.config.suppressWarnings?.firefoxDataCollection
Expand All @@ -130,7 +130,7 @@ export async function generateManifest(
}

if (
wxt.config.browser === 'firefox' &&
wxt.config.actualBrowser === 'firefox' &&
!manifest.browser_specific_settings?.gecko?.id &&
!wxt.config.suppressWarnings?.firefoxId
) {
Expand All @@ -142,7 +142,7 @@ export async function generateManifest(

addEntrypoints(manifest, entrypoints, buildOutput);

if (wxt.config.browser === 'firefox') {
if (wxt.config.actualBrowser === 'firefox') {
addDiscoveredThemeIcons(manifest, buildOutput);
}

Expand Down Expand Up @@ -237,7 +237,10 @@ function addEntrypoints(
wxt.config.outDir,
'.js',
);
if (wxt.config.browser === 'firefox' && wxt.config.manifestVersion === 3) {
if (
wxt.config.actualBrowser === 'firefox' &&
wxt.config.manifestVersion === 3
) {
manifest.background = {
type: background.options.type,
scripts: [script],
Expand All @@ -256,7 +259,7 @@ function addEntrypoints(
}

if (bookmarks) {
if (wxt.config.browser === 'firefox') {
if (wxt.config.actualBrowser === 'firefox') {
wxt.logger.warn(
'Bookmarks are not supported by Firefox. chrome_url_overrides.bookmarks was not added to the manifest',
);
Expand All @@ -271,7 +274,7 @@ function addEntrypoints(
}

if (history) {
if (wxt.config.browser === 'firefox') {
if (wxt.config.actualBrowser === 'firefox') {
wxt.logger.warn(
'Bookmarks are not supported by Firefox. chrome_url_overrides.history was not added to the manifest',
);
Expand Down Expand Up @@ -320,7 +323,7 @@ function addEntrypoints(
const actionKey =
wxt.config.manifestVersion === 2
? (popup.options.actionType ?? 'browser_action')
: wxt.config.browser === 'firefox' &&
: wxt.config.actualBrowser === 'firefox' &&
popup.options.actionType === 'page_action'
? 'page_action'
: 'action';
Expand All @@ -343,24 +346,24 @@ function addEntrypoints(
if (options) {
const page = getEntrypointBundlePath(options, wxt.config.outDir, '.html');
manifest.options_ui = {
...(wxt.config.browser !== 'safari' && {
...(wxt.config.actualBrowser !== 'safari' && {
open_in_tab: options.options.openInTab ?? false,
}),
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
browser_style:
wxt.config.browser === 'firefox'
wxt.config.actualBrowser === 'firefox'
? options.options.browserStyle
: undefined,
chrome_style:
wxt.config.browser !== 'firefox'
wxt.config.actualBrowser !== 'firefox'
? options.options.chromeStyle
: undefined,
page,
};
}

if (sandboxes?.length) {
if (wxt.config.browser === 'firefox') {
if (wxt.config.actualBrowser === 'firefox') {
wxt.logger.warn(
'Sandboxed pages not supported by Firefox. sandbox.pages was not added to the manifest',
);
Expand All @@ -382,7 +385,7 @@ function addEntrypoints(
'.html',
);

if (wxt.config.browser === 'firefox') {
if (wxt.config.actualBrowser === 'firefox') {
manifest.sidebar_action = {
default_panel: page,
browser_style: defaultSidepanel.options.browserStyle,
Expand Down Expand Up @@ -593,7 +596,8 @@ export function getContentScriptCssWebAccessibleResources(
// Chrome-only MV3 field (obscures the resource URL behind a per-session
// token); Firefox ignores it, and Safari's web extension converter
// rejects it as an unsupported key.
...(wxt.config.browser !== 'firefox' && wxt.config.browser !== 'safari'
...(wxt.config.actualBrowser !== 'firefox' &&
wxt.config.actualBrowser !== 'safari'
? { use_dynamic_url: true }
: {}),
matches:
Expand Down Expand Up @@ -734,11 +738,11 @@ function stripKeys(manifest: Browser.runtime.Manifest): void {
let keysToRemove: string[] = [];
if (wxt.config.manifestVersion === 2) {
keysToRemove.push(...mv3OnlyKeys);
if (wxt.config.browser === 'firefox')
if (wxt.config.actualBrowser === 'firefox')
keysToRemove.push(...firefoxMv3OnlyKeys);
} else {
keysToRemove.push(...mv2OnlyKeys);
if (wxt.config.browser !== 'firefox')
if (wxt.config.actualBrowser !== 'firefox')
keysToRemove.push(...chromeMv2OnlyKeys);
}

Expand Down
33 changes: 33 additions & 0 deletions packages/wxt/src/core/utils/target-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {
ActualBrowser,
TargetBrowser,
TargetBrowserMap,
} from '../../types';

/**
* Default mapping from `--browser` values to the actual browser WXT generates
* code for. Custom `--browser` values are only mapped when the project adds an
* entry for them in its `targetBrowserMap` config.
*/
export const defaultTargetBrowserMap: TargetBrowserMap = {
chrome: 'chromium',
edge: 'chromium',
brave: 'chromium',
opera: 'chromium',
chromium: 'chromium',
firefox: 'firefox',
safari: 'safari',
ios: 'safari',
};

/**
* Resolve the actual browser to generate code for from a `--browser` value.
* Unknown values resolve to themselves so their behavior is unchanged until the
* project maps them to an actual browser.
*/
export function resolveActualBrowser(
browser: TargetBrowser,
targetBrowserMap: TargetBrowserMap = defaultTargetBrowserMap,
): ActualBrowser {
return targetBrowserMap[browser] ?? (browser as ActualBrowser);
}
7 changes: 7 additions & 0 deletions packages/wxt/src/core/utils/testing/fake-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { mock } from 'vitest-mock-extended';
import { vi } from 'vitest';
import { setWxtForTesting } from '../../wxt';
import { resolveActualBrowser } from '../target-browser';
import type { Browser } from '@wxt-dev/browser';

faker.seed(__TEST_SEED__);
Expand Down Expand Up @@ -232,6 +233,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {

return {
browser,
actualBrowser: resolveActualBrowser(browser),
targetBrowsers: [],
command,
entrypointsDir: fakeDir(),
Expand Down Expand Up @@ -341,6 +343,11 @@ export const fakeWxtDevServer = fakeObjectCreator<WxtDevServer>(() => ({

export function setFakeWxt(overrides?: DeepPartial<Wxt>) {
const wxt = fakeWxt(overrides);
// Mirror resolveConfig: `actualBrowser` is resolved from `browser` unless
// the test overrides it directly.
if (overrides?.config?.actualBrowser == null) {
wxt.config.actualBrowser = resolveActualBrowser(wxt.config.browser);
}
setWxtForTesting(wxt);
return wxt;
}
Expand Down
24 changes: 24 additions & 0 deletions packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ export interface InlineConfig {
* @default [ ]
*/
targetBrowsers?: TargetBrowser[];
/**
* Map `--browser` values to the actual browser WXT generates code for.
*
* WXT makes build-time decisions, like which manifest options to generate,
* based on the actual browser, not the `--browser` label. Use this to map
* custom `--browser` values to the browser they actually run in. Values not
* in the map are not mapped to any browser, so add an entry here for any
* custom label.
*
* @default <span v-pre>`{ chrome: "chromium", edge: "chromium", brave: "chromium", opera: "chromium", chromium: "chromium", firefox: "firefox", safari: "safari", ios: "safari" }`</span>
*/
targetBrowserMap?: TargetBrowserMap;
/**
* Explicitly set a manifest version to target. This will override the default
* manifest version for each command, and can be overridden by the command
Expand Down Expand Up @@ -629,6 +641,13 @@ export interface ReloadContentScriptPayload {

export type TargetBrowser = string;
export type TargetManifestVersion = 2 | 3;
/**
* The actual browser WXT generates code for, resolved from the `--browser`
* value via `targetBrowserMap`.
*/
export type ActualBrowser = 'chromium' | 'firefox' | 'safari';
/** Maps a `--browser` value to the {@link ActualBrowser} WXT generates code for. */
export type TargetBrowserMap = Record<TargetBrowser, ActualBrowser>;

export type UserConfig = Omit<InlineConfig, 'configFile'>;

Expand Down Expand Up @@ -1568,6 +1587,11 @@ export interface ResolvedConfig {
command: WxtCommand;
browser: TargetBrowser;
targetBrowsers: TargetBrowser[];
/**
* The actual browser WXT generates code for, resolved from `browser` via
* `targetBrowserMap`.
*/
actualBrowser: ActualBrowser;
manifestVersion: TargetManifestVersion;
env: ConfigEnv;
logger: WxtLogger;
Expand Down