Skip to content

feat: resolve the actual browser for browser-specific code generation - #2687

Open
devtechedge wants to merge 8 commits into
wxt-dev:mainfrom
devtechedge:fix/actual-browser-resolution
Open

devtechedge wants to merge 8 commits into
wxt-dev:mainfrom
devtechedge:fix/actual-browser-resolution

Conversation

@devtechedge

@devtechedge devtechedge commented Oct 24, 2026 •

Copy link
Copy Markdown

Reopening #2622, which was closed by accident when my fork was deleted. Rebased onto current main.

Overview

Fixes #2588.

Right now every build-time browser check compares wxt.config.browser against a literal like "firefox" or "safari". An ios build therefore misses every Safari check and generates the wrong manifest, even though it runs in Safari.

This PR implements the three changes proposed in the issue:

  1. InlineConfig#browser keeps working exactly as before: it still drives the output directory template, import.meta.env.BROWSER, the CHROME/FIREFOX/SAFARI/EDGE/OPERA env constants, the zip artifact name, and browser launching. Nothing changes for existing labels.

  2. InlineConfig#targetBrowserMap maps --browser strings to the actual browser. It defaults to:

    --browser value Actual browser
    chrome, edge, brave, opera, chromium chromium
    firefox firefox
    safari, ios safari

    Labels missing from the map fall back to chromium, which is the code WXT generated for unrecognized labels before this option existed, so their builds are unchanged until the project maps them to another browser.

  3. ResolvedConfig#actualBrowser: "chromium" | "firefox" | "safari" is resolved through that map and is now used for the manifest generation checks: use_dynamic_url (introduced in fix: don't set use_dynamic_url on browsers that don't support it #2581), MV2/MV3 key stripping, background/background scripts, version_name, browser_style/chrome_style, options_ui in Safari, side panels, and the Firefox warnings. The default manifest version (MV2 for Firefox and Safari) is also resolved off the actual browser now, so an ios build gets the same manifest as a safari build.

The per-entrypoint include/exclude lists and per-browser entrypoint options still key off the raw --browser label, since those are user-authored values keyed by label.

Manual Testing

  • Added mapping tests to resolve-config.test.ts: every default mapping resolves correctly, a custom targetBrowserMap entry wins over the defaults and can override one, unmapped labels fall back to chromium, inherited object properties are not resolved, actualBrowser appears on the resolved config, and the output directory still uses the raw label while the default manifest version keys off the actual browser.
  • Added manifest tests: an ios build must generate exactly the same manifest as a safari build for both manifest versions, with the Chrome-only use_dynamic_url and options_ui.open_in_tab fields asserted absent, and a chromium build keeps both so the negative assertions are not vacuous. A custom label mapped to firefox or safari must match that browser's manifest.
  • Added two e2e cases: safari and ios built through the real config pipeline, which also covers the default manifest version and the output directory naming, and a custom targetBrowserMap set in wxt.config.ts.
  • Added a unit test keeping import.meta.env.BROWSER and the browser booleans on the raw label.
  • Added a Map Custom Browsers section to the target different browsers guide for the new option.
  • Verified fail-before/pass-after: swapping manifest.ts back to wxt.config.browser fails the ios and custom-mapping parity tests, while the chromium control case still passes.
  • bun run check:default is green in packages/wxt (ESLint, Oxlint, Publint, TypeScript), and the vitest suite in packages/wxt passes (594 tests).

Related Issue

This PR closes #2588. Related discussion: #2581 (comment) and #2301.

@netlify

netlify Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for creative-fairy-df92c4 ready!

Name Link
🔨 Latest commit cf101ea
🔍 Latest deploy log https://app.netlify.com/projects/creative-fairy-df92c4/deploys/6ae789e03138742f5aea8238
😎 Deploy Preview https://deploy-preview-2687--creative-fairy-df92c4.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 35961373-27c9-48a5-b4ab-a15402031cad

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions github-actions Bot added the pkg/wxt Includes changes to the `packages/wxt` directory label Oct 24, 2026
@PatrykKuniczak

Copy link
Copy Markdown
Collaborator

@devtechedge Let's resolve conflicts, yeah again 😆

I'll visit this PR, this week

@devtechedge

Copy link
Copy Markdown
Author

Thanks, Patryk, for checking in on this.

I merged the latest main into the branch and resolved the conflict in resolve-config.test.ts.

The PR is mergeable again now, and I have run the full test suite plus the lint and type checks locally, all of which pass.

The Validate and pkg.pr.new workflow runs are still waiting on first-time contributor approval, so they have not run yet.

Happy to adjust anything once you have had a chance to look at the branch.

@pkg-pr-new

pkg-pr-new Bot commented Oct 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

@wxt-dev/analytics

npm i https://pkg.pr.new/@wxt-dev/analytics@2687

@wxt-dev/auto-icons

npm i https://pkg.pr.new/@wxt-dev/auto-icons@2687

@wxt-dev/browser

npm i https://pkg.pr.new/@wxt-dev/browser@2687

@wxt-dev/i18n

npm i https://pkg.pr.new/@wxt-dev/i18n@2687

@wxt-dev/is-background

npm i https://pkg.pr.new/@wxt-dev/is-background@2687

@wxt-dev/module-react

npm i https://pkg.pr.new/@wxt-dev/module-react@2687

@wxt-dev/module-solid

npm i https://pkg.pr.new/@wxt-dev/module-solid@2687

@wxt-dev/module-svelte

npm i https://pkg.pr.new/@wxt-dev/module-svelte@2687

@wxt-dev/module-vue

npm i https://pkg.pr.new/@wxt-dev/module-vue@2687

@wxt-dev/runner

npm i https://pkg.pr.new/@wxt-dev/runner@2687

@wxt-dev/storage

npm i https://pkg.pr.new/@wxt-dev/storage@2687

@wxt-dev/unocss

npm i https://pkg.pr.new/@wxt-dev/unocss@2687

@wxt-dev/webextension-polyfill

npm i https://pkg.pr.new/@wxt-dev/webextension-polyfill@2687

wxt

npm i https://pkg.pr.new/wxt@2687

commit: 89f1f4b

@codecov

codecov Bot commented Oct 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.15385% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 80.66%. Comparing base (5c31bc8) to head (89f1f4b).

Files with missing lines Patch % Lines
packages/wxt/src/internal/manifest.ts 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2687      +/-   ##
==========================================
+ Coverage   80.44%   80.66%   +0.21%     
==========================================
  Files         139      140       +1     
  Lines        4162     4173      +11     
  Branches      961      967       +6     
==========================================
+ Hits         3348     3366      +18     
+ Misses        716      710       -6     
+ Partials       98       97       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PatrykKuniczak

Copy link
Copy Markdown
Collaborator

ChatGPT review:

Thanks for the work on this PR. I think the overall approach is correct and the separation between browser (the build target) and actualBrowser (the actual browser implementation) is a good architectural improvement.

Before merging, I would suggest addressing a few additional points to make the implementation more robust and easier to maintain.

1. Add integration tests for Safari/iOS manifest generation

The current regression tests verify the mapping and some manifest-specific behavior, which is a good start. However, I think it would be valuable to test the complete generated manifest for the ios target.

For example, verify that:

  • Safari-specific manifest restrictions are applied.
  • Unsupported Chrome-specific properties are not included.
  • The resulting manifest is valid for the intended Safari environment.
  • The behavior is consistent across the relevant manifest versions.

Ideally, this should include an integration-level test rather than only testing individual properties.

2. Expand test coverage for custom browser mappings

Since targetBrowserMap introduces a configurable mapping mechanism, I think custom mappings deserve explicit regression tests.

For example:

targetBrowserMap: {
  'my-firefox': 'firefox',
  'my-safari': 'safari',
}

The tests should verify that custom targets correctly inherit the behavior of their mapped browsers, including manifest generation and browser-specific compatibility rules.

This is particularly important because custom mappings are one of the main benefits of introducing this abstraction.

3. Revisit the fallback behavior and TypeScript typing

The current fallback:

return targetBrowserMap[browser] ?? (browser as ActualBrowser);

potentially introduces a type-safety issue.

An unknown browser name can be returned as ActualBrowser even though it is not necessarily one of the supported values (chromium, firefox, or safari).

I would suggest considering one of the following approaches:

  • Explicitly validate unknown browser values.
  • Introduce a type that represents both known and unknown browser identifiers.
  • Use a clearly defined fallback strategy instead of relying on a type assertion.

This would make the new abstraction more predictable and prevent the type system from suggesting stronger guarantees than the implementation actually provides.

4. Verify backward compatibility

Since the PR intentionally preserves the existing meaning of browser, I would also recommend adding regression tests to ensure that this behavior remains unchanged.

In particular:

  • import.meta.env.BROWSER should continue exposing the original target.
  • Output directory naming should remain unchanged.
  • ZIP artifact naming should remain unchanged.
  • Browser launching and environment-specific behavior should continue using the original target where appropriate.

This is important because introducing actualBrowser should not unintentionally change existing user-facing behavior.

Overall

I believe the proposed architecture solves the underlying problem correctly. My suggestions are primarily about strengthening regression coverage, ensuring type safety, and validating the behavior beyond the specific iOS/Safari scenario.

I would consider these improvements worthwhile before merging, especially the custom mapping tests and the fallback typing issue.

My suggestion
Let's see: #2648
Maybe you'll be able to use it there also, but don't do it blindly, because that's additional package and we're trying to reduce size and drop unnecessary deps.

Returning the raw label forced a type assertion on unknown --browser values. Chromium is the code WXT already generated for labels it does not recognize, so fall back to it explicitly and keep ActualBrowser honest. Also check own properties, so prototype keys passed to --browser cannot resolve to a function.
Compare complete manifests instead of single keys: an ios build must match the safari build for both manifest versions, custom labels must match the browser they map to, and Chrome-only fields must still appear for a chromium build so the negative assertions are not vacuous. Adds an e2e case that builds both labels, plus a unit test keeping BROWSER and the browser booleans on the raw label.
Explains which labels WXT knows, the Chromium fallback for anything else, and that output paths and import.meta.env.BROWSER keep the label you built with.
@devtechedge

Copy link
Copy Markdown
Author

Fair list, and the typing point was a real bug, so thanks for flagging it 🙂

Unknown labels now resolve to chromium in resolveActualBrowser, which is the output they already had, and the lookup uses own properties so constructor cannot hit the prototype.

On custom mappings, a label mapped to firefox or safari must produce the same complete manifest as that browser, in unit tests and an e2e.

An ios build must match a safari build exactly for both manifest versions, with use_dynamic_url and options_ui.open_in_tab asserted absent.

Safari's web extension converter ships with Xcode and only runs on macOS, so the manifest equality test is what covers validity here.

BROWSER and the browser booleans still use the raw label and are pinned by a test, output naming is covered in the config tests and the e2e, and zip naming and launching were not changed.

On 2648, I do not see an overlap, since it detects the browser at runtime for analytics, where one chromium build runs in Chrome, Edge, Brave or Opera, while this change is build time only and adds no dependency.

I documented the option in the target different browsers guide, and everything is pushed with the suite, lint and type checks passing locally.

Say the word if you want tests for zip naming or the launching path as well.

@PatrykKuniczak

Copy link
Copy Markdown
Collaborator

Fair list, and the typing point was a real bug, so thanks for flagging it 🙂

Unknown labels now resolve to chromium in resolveActualBrowser, which is the output they already had, and the lookup uses own properties so constructor cannot hit the prototype.

On custom mappings, a label mapped to firefox or safari must produce the same complete manifest as that browser, in unit tests and an e2e.

An ios build must match a safari build exactly for both manifest versions, with use_dynamic_url and options_ui.open_in_tab asserted absent.

Safari's web extension converter ships with Xcode and only runs on macOS, so the manifest equality test is what covers validity here.

BROWSER and the browser booleans still use the raw label and are pinned by a test, output naming is covered in the config tests and the e2e, and zip naming and launching were not changed.

On 2648, I do not see an overlap, since it detects the browser at runtime for analytics, where one chromium build runs in Chrome, Edge, Brave or Opera, while this change is build time only and adds no dependency.

I documented the option in the target different browsers guide, and everything is pushed with the suite, lint and type checks passing locally.

Say the word if you want tests for zip naming or the launching path as well.

I'll be back, probably tomorrow and i'll try to give you more feedback :)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg/wxt Includes changes to the `packages/wxt` directory

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resolve the actual browser for any "--browser" strings

3 participants