-
Notifications
You must be signed in to change notification settings - Fork 1.7k
build(extension): add TARGET-driven dual builds with Firefox manifest #2000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "manifest_version": 3, | ||
| "name": "Cap - Screen Recorder & Screen Capture", | ||
| "short_name": "Cap", | ||
| "description": "Free, open source screen recorder. Capture your screen, camera & mic in Firefox and share a video link the moment you stop.", | ||
| "version": "1.0.2", | ||
| "homepage_url": "https://cap.so", | ||
| "browser_specific_settings": { | ||
| "gecko": { | ||
| "id": "extension@cap.so", | ||
| "strict_min_version": "128.0", | ||
| "data_collection_permissions": { | ||
| "required": ["authenticationInfo", "websiteContent"] | ||
| } | ||
| } | ||
| }, | ||
| "icons": { | ||
| "16": "icons/icon-16.png", | ||
| "32": "icons/icon-32.png", | ||
| "48": "icons/icon-48.png", | ||
| "128": "icons/icon-128.png" | ||
| }, | ||
| "action": { | ||
| "default_title": "Record your screen with Cap", | ||
| "default_icon": { | ||
| "16": "icons/icon-16.png", | ||
| "32": "icons/icon-32.png", | ||
| "48": "icons/icon-48.png", | ||
| "128": "icons/icon-128.png" | ||
| } | ||
| }, | ||
| "background": { | ||
| "scripts": ["assets/service-worker.js"], | ||
| "type": "module" | ||
| }, | ||
| "options_ui": { | ||
| "page": "options.html", | ||
| "open_in_tab": true | ||
| }, | ||
| "permissions": ["activeTab", "identity", "scripting", "storage"], | ||
| "host_permissions": ["http://*/*", "https://*/*", "file:///*"], | ||
| "content_scripts": [ | ||
| { | ||
| "matches": ["http://*/*", "https://*/*", "file:///*"], | ||
| "js": ["assets/content-bootstrap.js"], | ||
| "run_at": "document_idle" | ||
| } | ||
| ], | ||
| "web_accessible_resources": [ | ||
| { | ||
| "resources": ["icons/*", "content/overlay.js", "welcome.html"], | ||
| "matches": ["http://*/*", "https://*/*", "file:///*"] | ||
| }, | ||
| { | ||
| "resources": ["camera-preview.html", "popup.html"], | ||
| "matches": ["http://*/*", "https://*/*", "file:///*"] | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // The recorder document (recorder.html) hosts capture, upload, device | ||
| // enumeration, the mic probe and the camera-preview relay. On Chrome it runs | ||
| // as an offscreen document; this module owns its lifecycle so the rest of the | ||
| // service worker never touches chrome.offscreen directly. | ||
| export const RECORDER_URL = "recorder.html"; | ||
|
|
||
| let recorderDocumentCreation: Promise<void> | null = null; | ||
|
|
||
| const getRecorderContexts = async () => { | ||
| const recorderUrl = chrome.runtime.getURL(RECORDER_URL); | ||
| return new Promise<Array<{ documentUrl?: string }>>((resolve) => { | ||
| chrome.runtime.getContexts( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file assumes |
||
| { | ||
| contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT], | ||
| documentUrls: [recorderUrl], | ||
| }, | ||
|
Comment on lines
+9
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The shared background script imports this helper for both targets, but Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/chrome-extension/src/background/recorder-host.ts
Line: 9-16
Comment:
**Firefox Reaches Offscreen APIs**
The shared background script imports this helper for both targets, but `hasRecorderHost()` and `ensureRecorderHost()` call Chrome-only offscreen APIs without checking the target. In the Firefox build, normal paths like starting a recording, probing the mic, enumerating devices, or connecting camera preview can reach `chrome.runtime.getContexts` or `chrome.offscreen.createDocument`, producing a runtime error instead of opening a Firefox recorder page.
How can I resolve this? If you propose a fix, please make it concise. |
||
| (contexts) => resolve(contexts), | ||
| ); | ||
| }); | ||
| }; | ||
|
|
||
| export const hasRecorderHost = async () => | ||
| (await getRecorderContexts()).length > 0; | ||
|
|
||
| const createOffscreenDocument = () => | ||
| new Promise<void>((resolve, reject) => { | ||
| chrome.offscreen.createDocument( | ||
| { | ||
| url: RECORDER_URL, | ||
| reasons: ["USER_MEDIA", "DISPLAY_MEDIA", "BLOBS", "AUDIO_PLAYBACK"], | ||
| justification: "Record and upload Cap videos from an extension page.", | ||
| }, | ||
| () => { | ||
| const error = chrome.runtime.lastError; | ||
| if (!error) { | ||
| resolve(); | ||
| return; | ||
| } | ||
|
|
||
| const message = error.message ?? "Failed to create offscreen document"; | ||
| if (message.toLowerCase().includes("single offscreen document")) { | ||
| resolve(); | ||
| return; | ||
| } | ||
|
|
||
| reject(new Error(message)); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| export const ensureRecorderHost = async () => { | ||
| const contexts = await getRecorderContexts(); | ||
| if (contexts.length > 0) return; | ||
|
|
||
| recorderDocumentCreation ??= createOffscreenDocument().finally(() => { | ||
| recorderDocumentCreation = null; | ||
| }); | ||
| await recorderDocumentCreation; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { TARGET } from "./target"; | ||
|
|
||
| // Per-target feature availability. Firefox has no chrome.offscreen or | ||
| // chrome.tabCapture, its getDisplayMedia exposes no system audio or per-tab | ||
| // surface, MV3 host permissions are user-grantable rather than granted at | ||
| // install, and getDisplayMedia requires transient user activation so capture | ||
| // cannot start without a click inside the recorder document. | ||
| export const capabilities = { | ||
| supportsTabCapture: TARGET === "chrome", | ||
| supportsOffscreen: TARGET === "chrome", | ||
| supportsSystemAudioCapture: TARGET === "chrome", | ||
| hostPermissionsGrantedAtInstall: TARGET === "chrome", | ||
| recorderNeedsUserGesture: TARGET === "firefox", | ||
| } as const; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // "chrome-extension:" on Chromium, "moz-extension:" on Firefox. Sender checks | ||
| // must use this instead of a hardcoded literal or Firefox extension pages get | ||
| // misclassified as web pages. | ||
| export const EXTENSION_PROTOCOL = new URL(chrome.runtime.getURL("")).protocol; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // Injected by vite `define` per build target; undefined under vitest, which | ||
| // runs without a define and must behave like the Chrome build. | ||
| declare const __TARGET__: "chrome" | "firefox" | undefined; | ||
|
|
||
| export type ExtensionTarget = "chrome" | "firefox"; | ||
|
|
||
| export const TARGET: ExtensionTarget = | ||
| typeof __TARGET__ === "undefined" ? "chrome" : __TARGET__; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the Chrome publish workflow runs the default
build, this script now creates bothdist/chromeanddist/firefox. The existing package step zipsdistas the extension root, so the upload contains target subdirectories and Firefox artifacts instead of a valid Chrome extension layout.Prompt To Fix With AI