Skip to content
Merged
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
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,7 @@ Full feature parity is supported for desktop Chrome, Edge, Brave, and Chromium
when the browser is based on Chromium 105 or newer. Latest stable is recommended
for release testing.

Firefox desktop is also a supported release target, but its support contract is
different:
Firefox desktop can load the extension, but support remains experimental:

- compile/run, Monaco, and extension-runtime flows are supported
- Firefox 153+ uses WebAssembly JSPI for live, line-buffered `std::cin`,
Expand Down Expand Up @@ -348,7 +347,6 @@ This writes:

- `release/browser-cpp-chrome-v<version>.zip` for the Chrome Web Store listing
- `release/browser-cpp-edge-v<version>.zip` for Microsoft Edge Add-ons
- `release/browser-cpp-firefox-v<version>.zip` for Firefox unsigned/manual submission packaging
- `release/browser-cpp-brave-v<version>.zip` for Brave validation/distribution
- `release/browser-cpp-chromium-v<version>.zip` for Chromium/GitHub distribution
- `release/firefox-unlisted/*.xpi` after the protected release workflow signs the Firefox unlisted build
Expand All @@ -359,11 +357,11 @@ The release manifest tracks the browser package matrix:

- Chrome is the canonical Chromium-family payload
- Edge, Brave, and Chromium currently reuse that payload under browser-labeled filenames
- Firefox has its own manifest, background entry, unsigned ZIP artifact, and signing metadata
- Firefox has its own manifest, background entry, smoke-tested temporary package, and signing metadata

Chrome, Edge, Brave, and Chromium still share the same MV3 payload. Firefox is
packaged from `dist-firefox/` as a separate payload because its manifest and
background model differ from Chromium.
built from `dist-firefox/` as a separate payload because its manifest and
background model differ from Chromium; release distribution uses the signed XPI.

Store submission notes should state:

Expand All @@ -385,7 +383,7 @@ Use `.github/workflows/release.yml` to publish one GitHub Release per
5. Fetches the Clang toolchain
6. Runs lint, build, release validation, and E2E checks
7. Runs Firefox packaging smoke validation
8. Produces the browser-labeled ZIPs plus checksums and release metadata
8. Produces the Chromium-family ZIPs plus checksums and release metadata
9. Signs the Firefox unlisted XPI with protected AMO credentials
10. Creates or updates GitHub Release `v<version>` and uploads all files under `release/`

Expand Down Expand Up @@ -458,7 +456,7 @@ manual/GitHub-distributed channel.
1. Run `npm run test:browser:firefox`.
2. Review `amo/metadata/listed.json` and update it if the release changes
Firefox-facing product behavior or listing copy.
3. For public AMO publication, upload the Firefox package and metadata manually
3. For public AMO publication, build the Firefox package from `dist-firefox/`, then upload it with the metadata manually
through the owner-managed listing workflow.
4. For self-distribution, verify that the protected release workflow produced a
signed artifact under `release/firefox-unlisted/`.
Expand Down
2 changes: 1 addition & 1 deletion amo/metadata/listed.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"ownerAction": "manual-submission",
"summary": "Public AMO listing remains a manual owner-managed release step.",
"artifacts": [
"release/browser-cpp-firefox-v<version>.zip"
"dist-firefox/ (package manually with web-ext for listed AMO submission)"
],
"notes": [
"Review Firefox limitations around file and workspace persistence before submission.",
Expand Down
6 changes: 3 additions & 3 deletions docs/release-playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
## Manual Release Flow

- Review the uploaded artifacts and confirm the version bump is intentional.
- For Firefox, confirm the unsigned ZIP and the AMO/manual-submission metadata are present before owner handoff.
- For Firefox, confirm the Firefox build and AMO/manual-submission metadata are ready before owner handoff.
- Use the existing tag/manual release workflow for final publication.
- Publish browser store listings and verify installed updates as required by the target browser.
- The protected release workflow signs the Firefox unlisted XPI with AMO credentials and uploads it with the other release assets.
Expand Down Expand Up @@ -74,7 +74,7 @@ Passing these gates proves that:

- Firefox-specific manifest generation succeeds
- the Firefox extension package passes `web-ext` lint/build smoke
- release packaging emits the Firefox artifact and release manifest
- release packaging emits the Chromium-family artifacts and release manifest
- manifest/package metadata stay version-synchronized

### Manual Firefox runtime QA
Expand All @@ -97,7 +97,7 @@ real Firefox desktop build:
persistence behavior matches reality.
8. Restart Firefox and verify session/workspace restore behavior matches the
documented limitations.
9. Install the packaged Firefox ZIP/XPI and repeat the compile/run sanity check.
9. Install the signed Firefox XPI and repeat the compile/run sanity check.

### Release decision

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "browser.cpp",
"short_name": "browser.cpp",
"description": "In-browser C++20 IDE powered by Monaco Editor and WASM Clang",
"version": "0.4.0",
"version": "0.4.1",
"minimum_chrome_version": "105",
"icons": {
"16": "icons/icon16.png",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "browser.cpp",
"version": "0.4.0",
"version": "0.4.1",
"description": "In-browser C++20 IDE with WASM Clang toolchain",
"private": true,
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions scripts/e2e-firefox-compatibility.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ test('e2e: Firefox compatibility report identifies live stdin as unsupported wit
assert.deepEqual(report.missing, []);
assert.ok(report.limitations.some((item) => item.key === 'limitedInteractiveStdin'));
assert.ok(report.limitations.some((item) => item.key === 'firefoxWorkspacePersistence'));
assert.ok(message.includes('requires worker-side JSPI'));
assert.ok(message.includes('Persistent folder write-back'));
assert.equal(
message,
'Firefox support is still experimental. Brave, Chrome, Chromium, or Edge browsers are recommended.'
);
});

test('e2e: target manifest generation creates a Firefox manifest without Chromium-only background keys', () => {
Expand Down
43 changes: 31 additions & 12 deletions scripts/e2e-release-packaging.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,24 @@ test('e2e: clean release workspace removes stale dist and release outputs', () =
assert.equal(fs.existsSync(path.join(repoRoot, 'release')), false);
});

test('e2e: release packaging creates all browser artifacts and metadata', () => {
test('e2e: release packaging removes stale Firefox ZIP artifacts', () => {
const repoRoot = makeRepoFixture();
fs.mkdirSync(path.join(repoRoot, 'release'), { recursive: true });
fs.writeFileSync(
path.join(repoRoot, 'release', 'browser-cpp-firefox-v0.3.0.zip'),
'stale Firefox artifact\n',
'utf8'
);

createReleaseArtifacts({ repoRoot });

assert.equal(
fs.existsSync(path.join(repoRoot, 'release', 'browser-cpp-firefox-v0.3.0.zip')),
false
);
});

test('e2e: release packaging creates Chromium-family browser artifacts and metadata', () => {
const repoRoot = makeRepoFixture();
const result = createReleaseArtifacts({ repoRoot });

Expand All @@ -232,26 +249,28 @@ test('e2e: release packaging creates all browser artifacts and metadata', () =>
assert.equal(checksumLines.length, publishableTargets.length);

const firefoxTarget = manifest.targets.find((target) => target.target === 'firefox');
assert.equal(firefoxTarget.publishable, true);
assert.equal(firefoxTarget.publishable, false);
assert.equal(firefoxTarget.packageStrategy, 'distinct');
assert.equal(firefoxTarget.fileName, 'browser-cpp-firefox-v1.2.3.zip');
assert.equal(firefoxTarget.fileName, null);
assert.equal(
firefoxTarget.blockReason,
'Firefox unsigned ZIP generation is disabled; use the signed unlisted XPI.'
);
assert.equal(firefoxTarget.signing.listed, 'manual-owner-submission');
assert.equal(firefoxTarget.signing.unlisted, 'required-release-artifact');

const edgeArtifact = manifest.artifacts.find((artifact) => artifact.target === 'edge');
assert.equal(edgeArtifact.packageStrategy, 'shared-with:chrome');
assert.equal(edgeArtifact.payloadGroup, 'chromium-mv3');

const firefoxArtifact = manifest.artifacts.find((artifact) => artifact.target === 'firefox');
assert.equal(firefoxArtifact.packageStrategy, 'distinct');
assert.equal(firefoxArtifact.payloadGroup, 'firefox-webext');
assert.equal(firefoxArtifact.sharedPayload, false);
assert.equal(firefoxArtifact.format, 'zip');
assert.equal(firefoxArtifact.sourceDir, 'dist-firefox');
assert.equal(
manifest.artifacts.some((artifact) => artifact.target === 'firefox'),
false
);

for (const artifact of result.artifacts) {
assert.equal(fs.existsSync(artifact.filePath), true);
assert.match(artifact.fileName, /^browser-cpp-(chrome|edge|firefox|brave|chromium)-v1\.2\.3\.zip$/);
assert.match(artifact.fileName, /^browser-cpp-(chrome|edge|brave|chromium)-v1\.2\.3\.zip$/);
}
});

Expand All @@ -277,13 +296,13 @@ test('e2e: release-target metadata includes Firefox and shared Chromium payloads

const firefox = getReleaseTarget('firefox');
assert.equal(firefox.packageStrategy, 'distinct');
assert.equal(firefox.publishable, true);
assert.equal(firefox.publishable, false);
assert.equal(firefox.payloadGroup, 'firefox-webext');

const publishableTargets = getPublishableReleaseTargets();
assert.deepEqual(
publishableTargets.map((target) => target.key),
['chrome', 'edge', 'firefox', 'brave', 'chromium']
['chrome', 'edge', 'brave', 'chromium']
);
});

Expand Down
11 changes: 11 additions & 0 deletions scripts/package-extension-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,16 @@ function getCommitSha(repoRoot) {
return null;
}

function removeStaleFirefoxZipArtifacts(releaseDir) {
if (!fs.existsSync(releaseDir)) return;

for (const name of fs.readdirSync(releaseDir)) {
if (!/^browser-cpp-firefox-v.+\.zip$/.test(name)) continue;
const filePath = path.join(releaseDir, name);
if (fs.statSync(filePath).isFile()) fs.unlinkSync(filePath);
}
}

function createReleaseArtifacts(options = {}) {
const repoRoot = options.repoRoot || path.resolve(__dirname, '..');
const distDir = options.distDir || path.join(repoRoot, 'dist');
Expand All @@ -180,6 +190,7 @@ function createReleaseArtifacts(options = {}) {
]);

fs.mkdirSync(releaseDir, { recursive: true });
removeStaleFirefoxZipArtifacts(releaseDir);

const artifacts = publishableTargets.map((target) => {
const payloadGroup = target.payloadGroup;
Expand Down
3 changes: 2 additions & 1 deletion scripts/release-targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const TARGETS = Object.freeze([
channel: 'Firefox Add-ons / signed XPI',
packageStrategy: 'distinct',
payloadGroup: 'firefox-webext',
publishable: true,
publishable: false,
blockReason: 'Firefox unsigned ZIP generation is disabled; use the signed unlisted XPI.',
signing: {
listed: 'manual-owner-submission',
unlisted: 'required-release-artifact',
Expand Down
6 changes: 6 additions & 0 deletions src/ui/browser-capabilities.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getExtensionAPI } from '../extension-api.mjs';
const MINIMUM_CHROMIUM_MAJOR = 105;
const MINIMUM_FIREFOX_MAJOR = 140;
export const FIREFOX_JSPI_STDIN_MINIMUM_MAJOR = 153;
const FIREFOX_EXPERIMENTAL_WARNING =
'Firefox support is still experimental. Brave, Chrome, Chromium, or Edge browsers are recommended.';

const BASE_REQUIRED_CAPABILITIES = [
{ key: 'extensionRuntime', label: 'Extension runtime API' },
Expand Down Expand Up @@ -223,6 +225,10 @@ export function createBrowserCompatibilityReport(root = globalThis, workerCapabi
}

export function formatBrowserCompatibilityMessage(report) {
if (report.capabilities?.browserFamily === 'firefox') {
return FIREFOX_EXPERIMENTAL_WARNING;
}

const lines = [];
if (report.missing.length) {
lines.push('Browser compatibility warning: required capabilities are missing:');
Expand Down
Loading