Skip to content
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

Add support to run any Chromium-based or Gecko-based browsers #186

Merged
merged 1 commit into from
Sep 16, 2024
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
1 change: 1 addition & 0 deletions examples/content-env/.env.chromium-based
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTENSION_PUBLIC_DESCRIPTION_TEXT="Chromium-based example"
1 change: 1 addition & 0 deletions examples/content-env/.env.gecko-based
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTENSION_PUBLIC_DESCRIPTION_TEXT="Gecko-based Add-on example"
1 change: 1 addition & 0 deletions examples/new-env-esm/.env.chromium-based
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTENSION_PUBLIC_DESCRIPTION_TEXT="Chromium-based example"
1 change: 1 addition & 0 deletions examples/new-env-esm/.env.gecko-based
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTENSION_PUBLIC_DESCRIPTION_TEXT="Gecko-based Add-on example"
1 change: 1 addition & 0 deletions examples/new-env/.env.chromium-based
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTENSION_PUBLIC_DESCRIPTION_TEXT="Chromium-based example"
1 change: 1 addition & 0 deletions examples/new-env/.env.gecko-based
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTENSION_PUBLIC_DESCRIPTION_TEXT="Gecko-based Add-on example"
34 changes: 33 additions & 1 deletion programs/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,31 @@ extensionJs
.description('Starts the development server (development mode)')
.option(
'-u, --user-data-dir <path-to-file | boolean>',
'[DEPRECATED - Use "--profile" instead] what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile'
)
.option(
'--profile <path-to-file | boolean>',
'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile'
)
.option(
'-b, --browser <chrome | edge | firefox>',
'specify a browser to run your extension in development mode'
)
.option(
'--chromium-binary <path-to-binary>',
'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default'
)
.option(
'--gecko-binary <path-to-binary>',
'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default'
)
.option(
'--polyfill [boolean]',
'whether or not to apply the cross-browser polyfill. Defaults to `true`'
)
.option(
'-p, --port <number>',
'what port should Extension.js run. Defaults to `3000`'
'what port should Extension.js WebSocket server run. Defaults to `8000`'
)
.action(async function (
pathOrRemoteUrl: string,
Expand Down Expand Up @@ -128,12 +140,24 @@ extensionJs
.description('Starts the development server (production mode)')
.option(
'-u, --user-data-dir <path-to-file | boolean>',
'[DEPRECATED - Use "--profile" instead] what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile'
)
.option(
'--profile <path-to-file | boolean>',
'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile'
)
.option(
'-b, --browser <chrome | edge | firefox>',
'specify a browser to run your extension in development mode'
)
.option(
'--chromium-binary <path-to-binary>',
'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default'
)
.option(
'--gecko-binary <path-to-binary>',
'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default'
)
.option(
'--polyfill [boolean]',
'whether or not to apply the cross-browser polyfill. Defaults to `true`'
Expand Down Expand Up @@ -166,6 +190,14 @@ extensionJs
// .arguments('[project-name]')
// .usage('preview [path-to-remote-extension] [options]')
// .description('Builds the extension for production')
// .option(
// '--chromium-binary <path-to-binary>',
// 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default'
// )
// .option(
// '--gecko-binary <path-to-binary>',
// 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default'
// )
// .option(
// '-b, --browser <chrome | edge | firefox>',
// 'specify a browser to preview your extension in production mode'
Expand Down
8 changes: 7 additions & 1 deletion programs/cli/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export type BrowsersSupported = 'chrome' | 'edge' | 'firefox' | 'all'
export type BrowsersSupported =
| 'chrome'
| 'edge'
| 'firefox'
| 'chromium-based'
| 'gecko-based'
| 'all'

export interface CreateOptions {
template?: string
Expand Down
6 changes: 4 additions & 2 deletions programs/develop/commands/commands-lib/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export function manifestNotFoundError(manifestPath: string) {
}

export function building(browser: DevOptions['browser']): string {
const extensionOutput = browser === 'firefox' ? 'Add-on' : 'Extension'
const extensionOutput =
browser === 'firefox' || browser === 'gecko-based' ? 'Add-on' : 'Extension'

return (
`${getLoggingPrefix('info')} Building ${capitalizedBrowserName(browser)} ` +
Expand Down Expand Up @@ -83,7 +84,8 @@ export function ready(
browser: DevOptions['browser']
) {
const modeColor = mode === 'production' ? brightBlue : brightBlue
const extensionOutput = browser === 'firefox' ? 'Add-on' : 'Extension'
const extensionOutput =
browser === 'firefox' || browser === 'gecko-based' ? 'Add-on' : 'Extension'

return (
`${getLoggingPrefix('success')} ` +
Expand Down
4 changes: 3 additions & 1 deletion programs/develop/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as messages from './commands-lib/messages'
import {installDependencies} from './commands-lib/install-dependencies'

export interface DevOptions {
browser: 'chrome' | 'edge' | 'firefox'
browser: 'chrome' | 'edge' | 'firefox' | 'chromium-based' | 'gecko-based'
mode: 'development' | 'production' | 'none' | undefined
port?: number
noOpen?: boolean
Expand All @@ -25,6 +25,8 @@ export interface DevOptions {
browserFlags?: string[]
startingUrl?: string
polyfill?: boolean
chromiumBinary?: string
geckoBinary?: string
}

export async function extensionDev(
Expand Down
2 changes: 2 additions & 0 deletions programs/develop/commands/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {DevOptions} from './dev'
export interface PreviewOptions {
browser: DevOptions['browser']
userDataDir?: string
chromiumBinary?: string
geckoBinary?: string
}

export async function extensionPreview(
Expand Down
2 changes: 2 additions & 0 deletions programs/develop/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface StartOptions {
noOpen?: boolean
userDataDir?: string
polyfill?: boolean
chromiumBinary?: string
geckoBinary?: string
}

export async function extensionStart(
Expand Down
3 changes: 2 additions & 1 deletion programs/develop/plugin-browsers/browsers-lib/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export function stdoutData(
browser: DevOptions['browser'],
mode: DevOptions['mode']
) {
const extensionOutput = browser === 'firefox' ? 'Add-on' : 'Extension'
const extensionOutput =
browser === 'firefox' || browser === 'gecko-based' ? 'Add-on' : 'Extension'
return (
`${getLoggingPrefix(browser, 'success')} ` +
`${capitalizedBrowserName(browser)} ${extensionOutput} ` +
Expand Down
2 changes: 2 additions & 0 deletions programs/develop/plugin-browsers/browsers-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export interface PluginOptions {
startingUrl?: string
browserConsole?: boolean
devtools?: boolean
chromiumBinary?: string
geckoBinary?: string
}
66 changes: 30 additions & 36 deletions programs/develop/plugin-browsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import {RunFirefoxPlugin} from './run-firefox'
import {DevOptions} from '../commands/dev'

/**
* BrowsersPlugin works by creating a WebSockets server
* that listens to changes triggered by the user extension
* via webpack. When a change is detected, the server sends
* a message to an extension called reload-extension, which
* is injected into the browser. This extension is responsible
* for sending messages to the user extension. We do that by
* injecting a script into the background page that listens
* to messages from the reload-extension.
* BrowsersPlugin works by finding the binary for the browser specified in the
* options and running it with the extension loaded.
*
* Features supported:
* - Service worker - Full extension reload (chrome.runtime.reload)
* - manifest.json - Full extension reload (chrome.runtime.reload)
* Supports:
* - User profile - a custom profile can be specified for the target browser
* - Preferences - a set of preferences can be specified for the target browser
* - Starting URL - a custom URL can be specified for the target browser
* - Browser flags - a set of flags can be specified for the target browser
* - Chromium binary - a custom path to the Chromium binary can be specified
* - Gecko binary - a custom path to the Gecko binary can be specified
*
* Browsers supported:
* - Chrome
* - Edge
* - Firefox
*/
export class BrowsersPlugin {
public static readonly name: string = 'plugin-browsers'
Expand All @@ -30,6 +33,8 @@ export class BrowsersPlugin {
public readonly profile: string
public readonly preferences: Record<string, any>
public readonly startingUrl: string
public readonly chromiumBinary?: string
public readonly geckoBinary?: string

constructor(options: PluginInterface) {
this.extension = [
Expand All @@ -38,7 +43,7 @@ export class BrowsersPlugin {
(flag) => !flag.startsWith('--load-extension=')
) || [])
]
this.browser = options.browser
this.browser = options.browser || 'chrome'
this.browserFlags =
options.browserFlags?.filter(
(flag) => !flag.startsWith('--load-extension=')
Expand All @@ -47,6 +52,8 @@ export class BrowsersPlugin {
this.profile = options.profile || ''
this.preferences = options.preferences || {}
this.startingUrl = options.startingUrl || ''
this.chromiumBinary = options.chromiumBinary
this.geckoBinary = options.geckoBinary
}

private getProfilePath(
Expand Down Expand Up @@ -76,42 +83,29 @@ export class BrowsersPlugin {
compiler,
this.browser,
this.userDataDir || this.profile
)
),
chromiumBinary: this.chromiumBinary,
geckoBinary: this.geckoBinary
}

// 1 - Bundle the reloader and manager extensions. The reloader extension
// is injected into the browser and is responsible for sending reload
// requests to the user extension. The manager extension is responsible
// for everything else, for now opening the chrome://extension page on startup.
// It starts a new browser instance with the user extension loaded.
console.log('browser', this.browser)
switch (this.browser) {
case 'chrome': {
new RunChromiumPlugin({
...config,
browser: 'chrome'
}).apply(compiler)
break
}

case 'chrome':
case 'edge':
new RunChromiumPlugin({
...config,
browser: 'edge'
}).apply(compiler)
case 'chromium-based': {
new RunChromiumPlugin(config).apply(compiler)
break
}

case 'firefox':
new RunFirefoxPlugin({
...config,
browser: 'firefox'
}).apply(compiler)
case 'gecko-based':
new RunFirefoxPlugin(config).apply(compiler)
break

default: {
// TODO: cezaraugusto add attempt to run with any user-declared path
new RunChromiumPlugin({
...config,
browser: this.browser
browser: 'chrome'
}).apply(compiler)
break
}
Expand Down
10 changes: 9 additions & 1 deletion programs/develop/plugin-browsers/run-chromium/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'fs'
import path from 'path'
import {type Compiler} from 'webpack'
import {spawn} from 'child_process'
import {browserConfig} from './browser-config'
Expand All @@ -24,6 +25,7 @@ export class RunChromiumPlugin {
public readonly startingUrl?: string
public readonly autoReload?: boolean
public readonly stats?: boolean
public readonly chromiumBinary?: string

constructor(options: PluginInterface) {
this.extension = options.extension
Expand All @@ -33,6 +35,7 @@ export class RunChromiumPlugin {
this.profile = options.profile || options.userDataDir
this.preferences = options.preferences
this.startingUrl = options.startingUrl
this.chromiumBinary = options.chromiumBinary
}

private launchChromium(browser: DevOptions['browser']) {
Expand All @@ -47,10 +50,15 @@ export class RunChromiumPlugin {
browserBinaryLocation = require(`${browser}-location`)()
break

case 'chromium-based':
browserBinaryLocation = path.normalize(this.chromiumBinary!)
break

default:
browserBinaryLocation = require(`${browser}`)
break
}

if (!fs.existsSync(browserBinaryLocation) || '') {
console.error(
messages.browserNotInstalledError(browser, browserBinaryLocation)
Expand All @@ -65,7 +73,7 @@ export class RunChromiumPlugin {

const stdio =
process.env.EXTENSION_ENV === 'development' ? 'inherit' : 'ignore'
const child = spawn(browserBinaryLocation, launchArgs, {stdio})
const child = spawn(`${browserBinaryLocation}`, launchArgs, {stdio})

if (process.env.EXTENSION_ENV === 'development') {
child.stdout?.pipe(process.stdout)
Expand Down
Loading