mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-06 20:11:38 +08:00
Bug: 1299791 Change-Id: I3648366445238aeb30fa699556fa9d9a4ee937e6 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3629859 Reviewed-by: Eric Leese <leese@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Commit-Queue: Kateryna Prokopenko <kprokopenko@chromium.org>
302 lines
9.0 KiB
TypeScript
302 lines
9.0 KiB
TypeScript
// Copyright 2020 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.
|
|
|
|
import * as Platform from '../platform/platform.js';
|
|
|
|
const queryParamsObject = new URLSearchParams(location.search);
|
|
|
|
let runtimePlatform = '';
|
|
|
|
let runtimeInstance: Runtime|undefined;
|
|
|
|
export function getRemoteBase(location: string = self.location.toString()): {
|
|
base: string,
|
|
version: string,
|
|
}|null {
|
|
const url = new URL(location);
|
|
const remoteBase = url.searchParams.get('remoteBase');
|
|
if (!remoteBase) {
|
|
return null;
|
|
}
|
|
|
|
const version = /\/serve_file\/(@[0-9a-zA-Z]+)\/?$/.exec(remoteBase);
|
|
if (!version) {
|
|
return null;
|
|
}
|
|
|
|
return {base: `${url.origin}/remote/serve_file/${version[1]}/`, version: version[1]};
|
|
}
|
|
|
|
export class Runtime {
|
|
private constructor() {
|
|
}
|
|
|
|
static instance(opts: {
|
|
forceNew: boolean|null,
|
|
}|undefined = {forceNew: null}): Runtime {
|
|
const {forceNew} = opts;
|
|
if (!runtimeInstance || forceNew) {
|
|
runtimeInstance = new Runtime();
|
|
}
|
|
|
|
return runtimeInstance;
|
|
}
|
|
|
|
static removeInstance(): void {
|
|
runtimeInstance = undefined;
|
|
}
|
|
|
|
static queryParam(name: string): string|null {
|
|
return queryParamsObject.get(name);
|
|
}
|
|
|
|
static setQueryParamForTesting(name: string, value: string): void {
|
|
queryParamsObject.set(name, value);
|
|
}
|
|
|
|
static experimentsSetting(): {
|
|
[x: string]: boolean,
|
|
} {
|
|
try {
|
|
return JSON.parse(
|
|
self.localStorage && self.localStorage['experiments'] ? self.localStorage['experiments'] : '{}') as {
|
|
[x: string]: boolean,
|
|
};
|
|
} catch (e) {
|
|
console.error('Failed to parse localStorage[\'experiments\']');
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static setPlatform(platform: string): void {
|
|
runtimePlatform = platform;
|
|
}
|
|
|
|
static platform(): string {
|
|
return runtimePlatform;
|
|
}
|
|
|
|
static isDescriptorEnabled(descriptor: {
|
|
experiment: ((string | undefined)|null),
|
|
condition: ((string | undefined)|null),
|
|
}): boolean {
|
|
const activatorExperiment = descriptor['experiment'];
|
|
if (activatorExperiment === '*') {
|
|
return true;
|
|
}
|
|
if (activatorExperiment && activatorExperiment.startsWith('!') &&
|
|
experiments.isEnabled(activatorExperiment.substring(1))) {
|
|
return false;
|
|
}
|
|
if (activatorExperiment && !activatorExperiment.startsWith('!') && !experiments.isEnabled(activatorExperiment)) {
|
|
return false;
|
|
}
|
|
const condition = descriptor['condition'];
|
|
if (condition && !condition.startsWith('!') && !Runtime.queryParam(condition)) {
|
|
return false;
|
|
}
|
|
if (condition && condition.startsWith('!') && Runtime.queryParam(condition.substring(1))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
loadLegacyModule(modulePath: string): Promise<void> {
|
|
return import(`../../${modulePath}`);
|
|
}
|
|
}
|
|
|
|
export interface Option {
|
|
title: string;
|
|
value: string|boolean;
|
|
raw?: boolean;
|
|
text?: string;
|
|
}
|
|
|
|
export class ExperimentsSupport {
|
|
#experiments: Experiment[];
|
|
#experimentNames: Set<string>;
|
|
#enabledTransiently: Set<string>;
|
|
readonly #enabledByDefault: Set<string>;
|
|
readonly #serverEnabled: Set<string>;
|
|
constructor() {
|
|
this.#experiments = [];
|
|
this.#experimentNames = new Set();
|
|
this.#enabledTransiently = new Set();
|
|
this.#enabledByDefault = new Set();
|
|
this.#serverEnabled = new Set();
|
|
}
|
|
|
|
allConfigurableExperiments(): Experiment[] {
|
|
const result = [];
|
|
for (const experiment of this.#experiments) {
|
|
if (!this.#enabledTransiently.has(experiment.name)) {
|
|
result.push(experiment);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
enabledExperiments(): Experiment[] {
|
|
return this.#experiments.filter(experiment => experiment.isEnabled());
|
|
}
|
|
|
|
private setExperimentsSetting(value: Object): void {
|
|
if (!self.localStorage) {
|
|
return;
|
|
}
|
|
self.localStorage['experiments'] = JSON.stringify(value);
|
|
}
|
|
|
|
register(experimentName: string, experimentTitle: string, unstable?: boolean, docLink?: string): void {
|
|
Platform.DCHECK(
|
|
() => !this.#experimentNames.has(experimentName), 'Duplicate registration of experiment ' + experimentName);
|
|
this.#experimentNames.add(experimentName);
|
|
this.#experiments.push(new Experiment(
|
|
this, experimentName, experimentTitle, Boolean(unstable),
|
|
docLink as Platform.DevToolsPath.UrlString ?? Platform.DevToolsPath.EmptyUrlString));
|
|
}
|
|
|
|
isEnabled(experimentName: string): boolean {
|
|
this.checkExperiment(experimentName);
|
|
// Check for explicitly disabled #experiments first - the code could call setEnable(false) on the experiment enabled
|
|
// by default and we should respect that.
|
|
if (Runtime.experimentsSetting()[experimentName] === false) {
|
|
return false;
|
|
}
|
|
if (this.#enabledTransiently.has(experimentName) || this.#enabledByDefault.has(experimentName)) {
|
|
return true;
|
|
}
|
|
if (this.#serverEnabled.has(experimentName)) {
|
|
return true;
|
|
}
|
|
|
|
return Boolean(Runtime.experimentsSetting()[experimentName]);
|
|
}
|
|
|
|
setEnabled(experimentName: string, enabled: boolean): void {
|
|
this.checkExperiment(experimentName);
|
|
const experimentsSetting = Runtime.experimentsSetting();
|
|
experimentsSetting[experimentName] = enabled;
|
|
this.setExperimentsSetting(experimentsSetting);
|
|
}
|
|
|
|
enableExperimentsTransiently(experimentNames: string[]): void {
|
|
for (const experimentName of experimentNames) {
|
|
this.checkExperiment(experimentName);
|
|
this.#enabledTransiently.add(experimentName);
|
|
}
|
|
}
|
|
|
|
enableExperimentsByDefault(experimentNames: string[]): void {
|
|
for (const experimentName of experimentNames) {
|
|
this.checkExperiment(experimentName);
|
|
this.#enabledByDefault.add(experimentName);
|
|
}
|
|
}
|
|
|
|
setServerEnabledExperiments(experimentNames: string[]): void {
|
|
for (const experiment of experimentNames) {
|
|
this.checkExperiment(experiment);
|
|
this.#serverEnabled.add(experiment);
|
|
}
|
|
}
|
|
|
|
enableForTest(experimentName: string): void {
|
|
this.checkExperiment(experimentName);
|
|
this.#enabledTransiently.add(experimentName);
|
|
}
|
|
|
|
disableForTest(experimentName: string): void {
|
|
this.checkExperiment(experimentName);
|
|
this.#enabledTransiently.delete(experimentName);
|
|
}
|
|
|
|
clearForTest(): void {
|
|
this.#experiments = [];
|
|
this.#experimentNames.clear();
|
|
this.#enabledTransiently.clear();
|
|
this.#enabledByDefault.clear();
|
|
this.#serverEnabled.clear();
|
|
}
|
|
|
|
cleanUpStaleExperiments(): void {
|
|
const experimentsSetting = Runtime.experimentsSetting();
|
|
const cleanedUpExperimentSetting: {
|
|
[x: string]: boolean,
|
|
} = {};
|
|
for (const {name: experimentName} of this.#experiments) {
|
|
if (experimentsSetting.hasOwnProperty(experimentName)) {
|
|
const isEnabled = experimentsSetting[experimentName];
|
|
if (isEnabled || this.#enabledByDefault.has(experimentName)) {
|
|
cleanedUpExperimentSetting[experimentName] = isEnabled;
|
|
}
|
|
}
|
|
}
|
|
this.setExperimentsSetting(cleanedUpExperimentSetting);
|
|
}
|
|
|
|
private checkExperiment(experimentName: string): void {
|
|
Platform.DCHECK(() => this.#experimentNames.has(experimentName), 'Unknown experiment ' + experimentName);
|
|
}
|
|
}
|
|
|
|
export class Experiment {
|
|
name: string;
|
|
title: string;
|
|
unstable: boolean;
|
|
docLink?: Platform.DevToolsPath.UrlString;
|
|
readonly #experiments: ExperimentsSupport;
|
|
constructor(
|
|
experiments: ExperimentsSupport, name: string, title: string, unstable: boolean,
|
|
docLink: Platform.DevToolsPath.UrlString) {
|
|
this.name = name;
|
|
this.title = title;
|
|
this.unstable = unstable;
|
|
this.docLink = docLink;
|
|
this.#experiments = experiments;
|
|
}
|
|
|
|
isEnabled(): boolean {
|
|
return this.#experiments.isEnabled(this.name);
|
|
}
|
|
|
|
setEnabled(enabled: boolean): void {
|
|
this.#experiments.setEnabled(this.name, enabled);
|
|
}
|
|
}
|
|
|
|
// This must be constructed after the query parameters have been parsed.
|
|
export const experiments = new ExperimentsSupport();
|
|
|
|
// TODO(crbug.com/1167717): Make this a const enum again
|
|
// eslint-disable-next-line rulesdir/const_enum
|
|
export enum ExperimentName {
|
|
CAPTURE_NODE_CREATION_STACKS = 'captureNodeCreationStacks',
|
|
CSS_OVERVIEW = 'cssOverview',
|
|
LIVE_HEAP_PROFILE = 'liveHeapProfile',
|
|
DEVELOPER_RESOURCES_VIEW = 'developerResourcesView',
|
|
TIMELINE_REPLAY_EVENT = 'timelineReplayEvent',
|
|
CSP_VIOLATIONS_VIEW = 'cspViolationsView',
|
|
WASM_DWARF_DEBUGGING = 'wasmDWARFDebugging',
|
|
ALL = '*',
|
|
PROTOCOL_MONITOR = 'protocolMonitor',
|
|
WEBAUTHN_PANE = 'webauthnPane',
|
|
SYNC_SETTINGS = 'syncSettings',
|
|
FULL_ACCESSIBILITY_TREE = 'fullAccessibilityTree',
|
|
PRECISE_CHANGES = 'preciseChanges',
|
|
STYLES_PANE_CSS_CHANGES = 'stylesPaneCSSChanges',
|
|
HEADER_OVERRIDES = 'headerOverrides',
|
|
CSS_LAYERS = 'cssLayers',
|
|
EYEDROPPER_COLOR_PICKER = 'eyedropperColorPicker',
|
|
INSTRUMENTATION_BREAKPOINTS = 'instrumentationBreakpoints',
|
|
}
|
|
|
|
// TODO(crbug.com/1167717): Make this a const enum again
|
|
// eslint-disable-next-line rulesdir/const_enum
|
|
export enum ConditionName {
|
|
CAN_DOCK = 'can_dock',
|
|
NOT_SOURCES_HIDE_ADD_FOLDER = '!sources.hide_add_folder',
|
|
}
|