-
Notifications
You must be signed in to change notification settings - Fork 338
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
feat: Add --chromium-pref
flag
#2912
base: master
Are you sure you want to change the base?
Changes from 7 commits
4557df4
584b9e6
73ae575
9df53ce
0f6a879
caa193a
c8dea96
b1779d9
61e8a86
ed9a645
caad914
6123e12
02476f5
eefc4ad
7281ae1
035bf98
3bac4fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -49,6 +49,7 @@ export default async function run( | |||||
firefoxApkComponent, | ||||||
// Chromium CLI options. | ||||||
chromiumBinary, | ||||||
chromiumPref, | ||||||
chromiumProfile, | ||||||
}, | ||||||
{ | ||||||
|
@@ -86,6 +87,10 @@ export default async function run( | |||||
customPrefs['extensions.manifestV3.enabled'] = true; | ||||||
} | ||||||
|
||||||
// Create an alias for --chromium-pref since it has been transformed into an | ||||||
// object containing one or more preferences. | ||||||
const customChromiumPrefs = { ...chromiumPref }; | ||||||
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. I see that you basically copied the previous few lines. The logic there did not completely make sense, I've sent a PR to fix that up: #3218 Let's simplify to the following:
Suggested change
|
||||||
|
||||||
const manifestData = await getValidatedManifest(sourceDir); | ||||||
|
||||||
const profileDir = firefoxProfile || chromiumProfile; | ||||||
|
@@ -193,6 +198,7 @@ export default async function run( | |||||
...commonRunnerParams, | ||||||
chromiumBinary, | ||||||
chromiumProfile, | ||||||
customChromiumPrefs, | ||||||
}; | ||||||
|
||||||
const chromiumRunner = await createExtensionRunner({ | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -12,6 +12,7 @@ import { | |||||
launch as defaultChromiumLaunch, | ||||||
} from 'chrome-launcher'; | ||||||
import WebSocket, { WebSocketServer } from 'ws'; | ||||||
import set from 'set-value'; | ||||||
|
||||||
import { createLogger } from '../util/logger.js'; | ||||||
import { TempDir } from '../util/temp-dir.js'; | ||||||
|
@@ -26,6 +27,10 @@ export const DEFAULT_CHROME_FLAGS = ChromeLauncher.defaultFlags().filter( | |||||
(flag) => !EXCLUDED_CHROME_FLAGS.includes(flag), | ||||||
); | ||||||
|
||||||
const DEFAULT_PREFS = { | ||||||
'extensions.ui.developer_mode': true, | ||||||
}; | ||||||
|
||||||
/** | ||||||
* Implements an IExtensionRunner which manages a Chromium instance. | ||||||
*/ | ||||||
|
@@ -210,6 +215,7 @@ export class ChromiumExtensionRunner { | |||||
userDataDir, | ||||||
// Ignore default flags to keep the extension enabled. | ||||||
ignoreDefaultFlags: true, | ||||||
prefs: this.getPrefs(), | ||||||
}); | ||||||
|
||||||
this.chromiumInstance.process.once('close', () => { | ||||||
|
@@ -414,4 +420,18 @@ export class ChromiumExtensionRunner { | |||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Returns a deep preferences object based on a set of flat preferences, like | ||||||
* "extensions.ui.developer_mode". | ||||||
*/ | ||||||
getPrefs() { | ||||||
return Object.entries({ | ||||||
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. To avoid the dependency of Here is an example (not tested): function mapToObject(map) {
return Object.fromEntries(
Array.from(
map,
([k, v]) => [k, v instanceof Map ? mapToObject(v) : v]
)
);
}
// Logic for getPrefs(), given prefs = DEFAUL_PREFS etc.
const prefsMap = new Map();
for (let [key, value] of prefs) {
let submap = prefsMap;
const props = key.split(".");
const lastProp = props.pop();
for (let prop of props) {
if (!submap.has(prop)) {
submap.set(prop, new Map());
}
submap = submap.get(prop);
if (!(submap instanceof Map)) {
throw new Error(`Cannot set ${key} because a value already exists at ${prop}`);
}
}
// TODO: Consider log.warn() if submap.has(lastProp) before overwriting.
submap.set(lastProp, value);
}
return mapToObject(prefsMap); 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. I'm going to base the function off this implementation. It's very compact and doesn't require using maps: https://youmightnotneed.com/lodash/#set Will add some basic tests too. Why do you want to avoid overwriting existing values? None of the default prefs I set in this PR are necessary for running the extension, they're just nice defaults that someone could turn off if they wanted. I also can't think of a use-case in the future where that would be useful. 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 snippet you've selected and lodash ( var setValue = require('set-value');
setValue({}, 'hasOwnProperty.call', 1);
// Expected: true. Actual: TypeError: Object.prototype.hasOwnProperty.call is not a function
console.log(Object.prototype.hasOwnProperty.call(Math, "min"));
Are you referring to "TODO: Consider log.warn() if submap.has(lastProp) before overwriting."? The purpose of that is to let the user know that a built-in value has been overwritten. Not that big of a deal. I'm also fine with omitting it. Overwriting the value when there is a
It is not meaningful to set "download" to 1, and a warning could be nice. Not required though. 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. I see now. I misunderstood lots of things about the original comment lol. But now that I've implemented and tested your original suggestion, we're on the same page. Only comment is about throwing the error. See these test cases: We throw an error on the first, but not the second. I think we should make both behave the same way, either throw an error or allow the later value to override the earlier. Thoughts? |
||||||
...DEFAULT_PREFS, | ||||||
...(this.params.customChromiumPrefs || {}), | ||||||
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. Spread syntax with
Suggested change
|
||||||
}).reduce((prefs, [key, value]) => { | ||||||
set(prefs, key, value); | ||||||
return prefs; | ||||||
}, {}); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -630,6 +630,18 @@ Example: $0 --help run. | |
demandOption: false, | ||
type: 'string', | ||
}, | ||
'chromium-pref': { | ||
describe: | ||
'Launch chromium with a custom preference ' + | ||
'(example: --chromium-pref=browser.theme.follows_system_colors=false). ' + | ||
'You can repeat this option to set more than one ' + | ||
'preference.', | ||
demandOption: false, | ||
requiresArg: true, | ||
type: 'array', | ||
coerce: (arg) => | ||
arg != null ? coerceCLICustomPreference(arg) : undefined, | ||
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.
|
||
}, | ||
'chromium-profile': { | ||
describe: 'Path to a custom Chromium profile', | ||
demandOption: false, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,7 +49,7 @@ describe('util/extension-runners/chromium', async () => { | |
it('uses the expected chrome flags', () => { | ||
// Flags from chrome-launcher v0.14.0 | ||
const expectedFlags = [ | ||
'--disable-features=Translate', | ||
'--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication', | ||
'--disable-component-extensions-with-background-pages', | ||
'--disable-background-networking', | ||
'--disable-component-update', | ||
|
@@ -66,6 +66,9 @@ describe('util/extension-runners/chromium', async () => { | |
'--password-store=basic', | ||
'--use-mock-keychain', | ||
'--force-fieldtrials=*BackgroundTracing/default/', | ||
'--disable-hang-monitor', | ||
'--disable-prompt-on-repost', | ||
'--disable-domain-reliability', | ||
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. These changes were required after upgrading chrome-launcher, similar to #2825 (comment) |
||
]; | ||
|
||
assert.deepEqual(DEFAULT_CHROME_FLAGS, expectedFlags); | ||
|
@@ -615,6 +618,56 @@ describe('util/extension-runners/chromium', async () => { | |
}), | ||
); | ||
|
||
it('does pass default prefs to chrome', async () => { | ||
const { params } = prepareExtensionRunnerParams(); | ||
|
||
const runnerInstance = new ChromiumExtensionRunner(params); | ||
await runnerInstance.run(); | ||
|
||
sinon.assert.calledOnce(params.chromiumLaunch); | ||
sinon.assert.calledWithMatch(params.chromiumLaunch, { | ||
prefs: { | ||
extensions: { | ||
ui: { | ||
developer_mode: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
await runnerInstance.exit(); | ||
}); | ||
|
||
it('does pass custom prefs to chrome', async () => { | ||
const { params } = prepareExtensionRunnerParams({ | ||
params: { | ||
customChromiumPrefs: { | ||
'download.default_directory': '/some/directory', | ||
'extensions.ui.developer_mode': false, | ||
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. Let's split the test in two: that a custom pref can be passed, and that a default pref can be overridden. We want to make sure that custom prefs are merged with the default prefs, and not changed. The current implementation is correct, but the test as constructed would still pass if the implementation failed to merge, which is a sign of an incomplete test. |
||
}, | ||
}, | ||
}); | ||
|
||
const runnerInstance = new ChromiumExtensionRunner(params); | ||
await runnerInstance.run(); | ||
|
||
sinon.assert.calledOnce(params.chromiumLaunch); | ||
sinon.assert.calledWithMatch(params.chromiumLaunch, { | ||
prefs: { | ||
download: { | ||
default_directory: '/some/directory', | ||
}, | ||
extensions: { | ||
ui: { | ||
developer_mode: false, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
await runnerInstance.exit(); | ||
}); | ||
|
||
describe('reloadAllExtensions', () => { | ||
let runnerInstance; | ||
let wsClient; | ||
|
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.
Let's not add the
set-value
module. It appears to be unmaintained (no responses from the project maintainer on several comments from the past year), and upon inspection I also see the lack of safeguards against pollution of built-in prototype members.