mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-28 13:13:37 +08:00
[npm] Allow to select (unstable) features with npm start.
This adds an option `--enable-unstable-features`, which now also turns on multimodal support for Freestyler. It also adds `--enable-features` and `--disable-features`, to allow more fine-grained control over the exact feature set when necessary. Fixed: 406941932 Change-Id: I7fcecbba833a2e0a66166890aa5af218ae2a8f7e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6404318 Reviewed-by: Nikolay Vitkov <nvitkov@chromium.org>
This commit is contained in:
@@ -169,6 +169,29 @@ npm start -- http://www.example.com
|
||||
|
||||
to automatically open `http://www.example.com` in the newly spawned Chrome tab.
|
||||
|
||||
|
||||
##### Controlling the feature set
|
||||
|
||||
By default `npm start` will enable a bunch of experimental features (related to DevTools) that are considered ready for teamfood.
|
||||
To also enable experimental features that aren't yet considered sufficiently stable to enable them by default for the team, run:
|
||||
|
||||
```bash
|
||||
# Long version
|
||||
npm start -- --unstable-features
|
||||
|
||||
# Short version
|
||||
npm start -- -u
|
||||
```
|
||||
|
||||
Just like with Chrome itself, you can also control the set of enabled and disabled features using
|
||||
|
||||
```bash
|
||||
npm start -- --enable-features=DevToolsAutomaticFileSystems
|
||||
npm start -- --disable-features=DevToolsWellKnown --enable-features=DevToolsFreestyler:multimodal/true
|
||||
```
|
||||
|
||||
which you can use to override the default feature set.
|
||||
|
||||
#### Running from file system
|
||||
|
||||
This works with Chromium 79 or later.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Representation of the feature set that is configured for Chrome. This
|
||||
* keeps track of enabled and disabled features and generates the correct
|
||||
* combination of `--enable-features` / `--disable-features` command line
|
||||
* flags.
|
||||
*
|
||||
* There are unit tests for this in `./devtools_build.test.mjs`.
|
||||
*/
|
||||
export class FeatureSet {
|
||||
#disabled = new Set();
|
||||
#enabled = new Map();
|
||||
|
||||
/**
|
||||
* Disables the given `feature`.
|
||||
*
|
||||
* @param {string} feature the name of the feature to disable.
|
||||
*/
|
||||
disable(feature) {
|
||||
this.#disabled.add(feature);
|
||||
this.#enabled.delete(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the given `feature`, and optionally adds the `parameters` to it.
|
||||
* For example:
|
||||
* ```js
|
||||
* featureSet.enable('DevToolsFreestyler', {patching: true});
|
||||
* ```
|
||||
* The parameters are additive.
|
||||
*
|
||||
* @param {string} feature the name of the feature to enable.
|
||||
* @param {object} parameters the additional parameters to pass to it, in
|
||||
* the form of key/value pairs.
|
||||
*/
|
||||
enable(feature, parameters = {}) {
|
||||
this.#disabled.delete(feature);
|
||||
if (!this.#enabled.has(feature)) {
|
||||
this.#enabled.set(feature, Object.create(null));
|
||||
}
|
||||
for (const [key, value] of Object.entries(parameters)) {
|
||||
this.#enabled.get(feature)[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the other `featureSet` into this.
|
||||
*
|
||||
* @param featureSet the other `FeatureSet` to apply.
|
||||
*/
|
||||
merge(featureSet) {
|
||||
for (const feature of featureSet.#disabled) {
|
||||
this.disable(feature);
|
||||
}
|
||||
for (const [feature, parameters] of featureSet.#enabled) {
|
||||
this.enable(feature, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields the command line parameters to pass to the invocation of
|
||||
* a Chrome binary for achieving the state of the feature set.
|
||||
*/
|
||||
* [Symbol.iterator]() {
|
||||
const disabledFeatures = [...this.#disabled];
|
||||
if (disabledFeatures.length) {
|
||||
yield `--disable-features=${disabledFeatures.sort().join(',')}`;
|
||||
}
|
||||
const enabledFeatures = [...this.#enabled].map(([feature, parameters]) => {
|
||||
parameters = Object.entries(parameters);
|
||||
if (parameters.length) {
|
||||
parameters = parameters.map(([key, value]) => `${key}/${value}`);
|
||||
feature = `${feature}:${parameters.sort().join('/')}`;
|
||||
}
|
||||
return feature;
|
||||
});
|
||||
if (enabledFeatures.length) {
|
||||
yield `--enable-features=${enabledFeatures.sort().join(',')}`;
|
||||
}
|
||||
}
|
||||
|
||||
static parse(text) {
|
||||
const features = [];
|
||||
for (const str of text.split(',')) {
|
||||
const parts = str.split(':');
|
||||
if (parts.length < 1 || parts.length > 2) {
|
||||
throw new Error(`Invalid feature declaration '${str}'`);
|
||||
}
|
||||
const feature = parts[0];
|
||||
const parameters = Object.create(null);
|
||||
if (parts.length > 1) {
|
||||
const args = parts[1].split('/');
|
||||
if (args.length % 2 !== 0) {
|
||||
throw new Error(`Invalid parameters '${parts[1]}' for feature ${feature}`);
|
||||
}
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
const key = args[i + 0];
|
||||
const value = args[i + 1];
|
||||
parameters[key] = value;
|
||||
}
|
||||
}
|
||||
features.push({feature, parameters});
|
||||
}
|
||||
return features;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2025 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Run these tests with:
|
||||
//
|
||||
// npx mocha scripts/devtools_build.test.mjs
|
||||
|
||||
import {assert} from 'chai';
|
||||
|
||||
import {FeatureSet} from './devtools_build.mjs';
|
||||
|
||||
describe('FeatureSet', () => {
|
||||
it('yields an empty set of arguments by default', () => {
|
||||
const featureSet = new FeatureSet();
|
||||
|
||||
assert.isEmpty([...featureSet]);
|
||||
});
|
||||
|
||||
it('can enable features', () => {
|
||||
const featureSet = new FeatureSet();
|
||||
|
||||
featureSet.enable('DevToolsFreestyler');
|
||||
featureSet.enable('DevToolsWellKnown');
|
||||
|
||||
assert.deepEqual([...featureSet], [
|
||||
'--enable-features=DevToolsFreestyler,DevToolsWellKnown',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can enable features with parameters', () => {
|
||||
const featureSet = new FeatureSet();
|
||||
|
||||
featureSet.enable('DevToolsFreestyler', {patching: true});
|
||||
featureSet.enable('DevToolsFreestyler', {user_tier: 'TESTERS', multimodal: true});
|
||||
featureSet.enable('DevToolsAiAssistancePerformanceAgent', {insights_enabled: true});
|
||||
|
||||
assert.deepEqual([...featureSet], [
|
||||
'--enable-features=DevToolsAiAssistancePerformanceAgent:insights_enabled/true,DevToolsFreestyler:multimodal/true/patching/true/user_tier/TESTERS',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can disable features', () => {
|
||||
const featureSet = new FeatureSet();
|
||||
|
||||
featureSet.disable('MediaRouter');
|
||||
featureSet.disable('DevToolsAiGeneratedTimelineLabels');
|
||||
|
||||
assert.deepEqual([...featureSet], [
|
||||
'--disable-features=DevToolsAiGeneratedTimelineLabels,MediaRouter',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can disable and enable unrelated features', () => {
|
||||
const featureSet = new FeatureSet();
|
||||
|
||||
featureSet.disable('MediaRouter');
|
||||
featureSet.enable('DevToolsAutomaticFileSystems');
|
||||
|
||||
assert.deepEqual([...featureSet], [
|
||||
'--disable-features=MediaRouter',
|
||||
'--enable-features=DevToolsAutomaticFileSystems',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can disable previously enabled features', () => {
|
||||
const featureSet = new FeatureSet();
|
||||
|
||||
featureSet.enable('DevToolsFreestyler', {patching: true});
|
||||
featureSet.enable('DevToolsWellKnown');
|
||||
featureSet.disable('DevToolsFreestyler');
|
||||
|
||||
assert.deepEqual([...featureSet], [
|
||||
'--disable-features=DevToolsFreestyler',
|
||||
'--enable-features=DevToolsWellKnown',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can merge feature sets', () => {
|
||||
const fs1 = new FeatureSet();
|
||||
fs1.enable('DevToolsFreestyler', {patching: true});
|
||||
fs1.enable('DevToolsWellKnown');
|
||||
fs1.disable('MediaRouter');
|
||||
const fs2 = new FeatureSet();
|
||||
fs2.disable('DevToolsWellKnown');
|
||||
fs2.enable('DevToolsFreestyler', {multimodal: true});
|
||||
|
||||
fs1.merge(fs2);
|
||||
|
||||
assert.deepEqual([...fs1], [
|
||||
'--disable-features=DevToolsWellKnown,MediaRouter',
|
||||
'--enable-features=DevToolsFreestyler:multimodal/true/patching/true',
|
||||
]);
|
||||
assert.deepEqual([...fs2], [
|
||||
'--disable-features=DevToolsWellKnown',
|
||||
'--enable-features=DevToolsFreestyler:multimodal/true',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can parse --enable-features/--disable-features declarations', () => {
|
||||
assert.deepEqual(FeatureSet.parse('MediaRouter'), [
|
||||
{feature: 'MediaRouter', parameters: {}},
|
||||
]);
|
||||
assert.deepEqual(FeatureSet.parse('DevToolsFreestyler:multimodal/true/patching/true'), [
|
||||
{feature: 'DevToolsFreestyler', parameters: {multimodal: 'true', patching: 'true'}},
|
||||
]);
|
||||
assert.deepEqual(FeatureSet.parse('DevToolsFreestyler:multimodal/true,DevToolsWellKnown'), [
|
||||
{feature: 'DevToolsFreestyler', parameters: {multimodal: 'true'}},
|
||||
{feature: 'DevToolsWellKnown', parameters: {}},
|
||||
]);
|
||||
});
|
||||
});
|
||||
+45
-20
@@ -8,28 +8,26 @@ import path from 'node:path';
|
||||
import yargs from 'yargs';
|
||||
import {hideBin} from 'yargs/helpers';
|
||||
|
||||
import {FeatureSet} from './devtools_build.mjs';
|
||||
import {
|
||||
downloadedChromeBinaryPath,
|
||||
isInChromiumDirectory,
|
||||
rootPath,
|
||||
} from './devtools_paths.js';
|
||||
|
||||
// The list of features that are enabled by default.
|
||||
const ENABLE_FEATURES = [
|
||||
'DevToolsAiGeneratedTimelineLabels',
|
||||
'DevToolsAutomaticFileSystems',
|
||||
'DevToolsCssValueTracing',
|
||||
'DevToolsFreestyler:patching/true,user_tier/TESTERS',
|
||||
'DevToolsWellKnown',
|
||||
'DevToolsAiGeneratedTimelineLabels',
|
||||
'DevToolsAiAssistancePerformanceAgent:insights_enabled/true',
|
||||
];
|
||||
// The default feature set.
|
||||
const DEFAULT_FEATURE_SET = new FeatureSet();
|
||||
process.platform === 'darwin' && DEFAULT_FEATURE_SET.disable('MediaRouter');
|
||||
DEFAULT_FEATURE_SET.enable('DevToolsAiAssistancePerformanceAgent', {insights_enabled: true});
|
||||
DEFAULT_FEATURE_SET.enable('DevToolsAiGeneratedTimelineLabels');
|
||||
DEFAULT_FEATURE_SET.enable('DevToolsAutomaticFileSystems');
|
||||
DEFAULT_FEATURE_SET.enable('DevToolsCssValueTracing');
|
||||
DEFAULT_FEATURE_SET.enable('DevToolsFreestyler', {patching: true, user_tier: 'TESTERS'});
|
||||
DEFAULT_FEATURE_SET.enable('DevToolsWellKnown');
|
||||
|
||||
// The list of features that are disabled by default.
|
||||
const DISABLE_FEATURES = [];
|
||||
if (process.platform === 'darwin') {
|
||||
DISABLE_FEATURES.push('MediaRouter');
|
||||
}
|
||||
// The unstable feature set (can be enabled via `--enable-unstable-features`).
|
||||
const UNSTABLE_FEATURE_SET = new FeatureSet();
|
||||
UNSTABLE_FEATURE_SET.enable('DevToolsFreestyler', {multimodal: true});
|
||||
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option('browser', {
|
||||
@@ -46,6 +44,22 @@ const argv = yargs(hideBin(process.argv))
|
||||
throw new Error(`Unsupported channel "${arg}"`);
|
||||
},
|
||||
})
|
||||
.option('unstable-features', {
|
||||
alias: 'u',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Enable potentially unstable features',
|
||||
})
|
||||
.option('enable-features', {
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Enable specific features (just like with Chrome)',
|
||||
})
|
||||
.option('disable-features', {
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Disable specific features (just like with Chrome)',
|
||||
})
|
||||
.option('open', {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
@@ -62,12 +76,13 @@ const argv = yargs(hideBin(process.argv))
|
||||
default: false,
|
||||
description: 'Enable verbose logging',
|
||||
})
|
||||
.group(['unstable-features', 'enable-features', 'disable-features'], 'Feature set:')
|
||||
.usage('npm start -- [options] [urls...]')
|
||||
.help('help')
|
||||
.version(false)
|
||||
.parseSync();
|
||||
|
||||
const {browser, target, open, verbose} = argv;
|
||||
const {browser, disableFeatures, enableFeatures, unstableFeatures, open, target, verbose} = argv;
|
||||
const cwd = process.cwd();
|
||||
const {env} = process;
|
||||
const runBuildPath = path.join(import.meta.dirname, 'run_build.mjs');
|
||||
@@ -94,7 +109,7 @@ function findBrowserBinary() {
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.debug(`Launching custom binary at ${binary}.`);
|
||||
console.debug(`Launching custom binary at ${browser}.`);
|
||||
}
|
||||
return browser;
|
||||
}
|
||||
@@ -121,9 +136,19 @@ function start() {
|
||||
args.push('--use-mock-keychain');
|
||||
}
|
||||
|
||||
// Disable/Enable experimental features.
|
||||
args.push(`--disable-features=${DISABLE_FEATURES.join(',')}`);
|
||||
args.push(`--enable-features=${ENABLE_FEATURES.join(',')}`);
|
||||
// Disable/Enable features.
|
||||
const featureSet = new FeatureSet();
|
||||
featureSet.merge(DEFAULT_FEATURE_SET);
|
||||
if (unstableFeatures) {
|
||||
featureSet.merge(UNSTABLE_FEATURE_SET);
|
||||
}
|
||||
for (const {feature} of FeatureSet.parse(disableFeatures)) {
|
||||
featureSet.disable(feature);
|
||||
}
|
||||
for (const {feature, parameters} of FeatureSet.parse(enableFeatures)) {
|
||||
featureSet.enable(feature, parameters);
|
||||
}
|
||||
args.push(...featureSet);
|
||||
|
||||
// Open with our freshly built DevTools front-end.
|
||||
const genDir = path.join(rootPath(), 'out', target, 'gen');
|
||||
|
||||
Reference in New Issue
Block a user