Skip to content

Commit e826302

Browse files
committed
Add conditional disabling of unsafe multi host features
1 parent 7aa57d1 commit e826302

File tree

11 files changed

+119
-7
lines changed

11 files changed

+119
-7
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ grd_files_debug_sources = [
995995
"front_end/entrypoints/node_app/NodeMain.js",
996996
"front_end/entrypoints/node_app/nodeConnectionsPanel.css.js",
997997
"front_end/entrypoints/rn_fusebox/FuseboxAppMetadataObserver.js",
998-
"front_end/entrypoints/rn_fusebox/FuseboxExperimentsObserver.js",
998+
"front_end/entrypoints/rn_fusebox/FuseboxFeatureObserver.js",
999999
"front_end/entrypoints/rn_fusebox/FuseboxReconnectDeviceButton.js",
10001000
"front_end/entrypoints/rn_fusebox/FuseboxWindowTitleManager.js",
10011001
"front_end/entrypoints/shell/browser_compatibility_guard.js",

front_end/core/sdk/ReactNativeApplicationModel.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,23 @@ export class ReactNativeApplicationModel extends SDKModel<EventTypes> implements
5252
this.dispatchEventToListeners(Events.METADATA_UPDATED, metadata);
5353
}
5454

55+
systemStateChanged(params: Protocol.ReactNativeApplication.SystemStateChangedEvent): void {
56+
this.dispatchEventToListeners(Events.SYSTEM_STATE_CHANGED, params);
57+
}
58+
5559
traceRequested(): void {
5660
this.dispatchEventToListeners(Events.TRACE_REQUESTED);
5761
}
5862
}
5963

6064
export const enum Events {
6165
METADATA_UPDATED = 'MetadataUpdated',
66+
SYSTEM_STATE_CHANGED = 'SystemStateChanged',
6267
TRACE_REQUESTED = 'TraceRequested',
6368
}
6469

6570
export interface EventTypes {
6671
[Events.METADATA_UPDATED]: Protocol.ReactNativeApplication.MetadataUpdatedEvent;
72+
[Events.SYSTEM_STATE_CHANGED]: Protocol.ReactNativeApplication.SystemStateChangedEvent;
6773
[Events.TRACE_REQUESTED]: void;
6874
}

front_end/entrypoints/rn_fusebox/BUILD.gn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ devtools_module("rn_fusebox") {
1111
sources = [
1212
"FuseboxAppMetadataObserver.ts",
1313
"FuseboxReconnectDeviceButton.ts",
14-
"FuseboxExperimentsObserver.ts",
14+
"FuseboxFeatureObserver.ts",
1515
"FuseboxWindowTitleManager.ts",
1616
]
1717

front_end/entrypoints/rn_fusebox/FuseboxExperimentsObserver.ts renamed to front_end/entrypoints/rn_fusebox/FuseboxFeatureObserver.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,31 @@ const UIStrings = {
1717
* @description Message for the "settings changed" banner shown when a reload is required for the Network panel.
1818
*/
1919
reloadRequiredForNetworkPanelMessage: 'The Network panel is now available for dogfooding. Please reload to access it.',
20+
/**
21+
* @description Title shown when a feature is disabled due to multiple React Native hosts.
22+
*/
23+
multiHostFeatureDisabledTitle: 'Feature disabled',
24+
/**
25+
* @description Detail message shown when a feature is disabled due to multiple React Native hosts.
26+
*/
27+
multiHostFeatureDisabledDetail: 'This feature is disabled as the app or framework has configured multiple React Native hosts, which we do not yet support safely.',
2028
} as const;
2129

22-
const str_ = i18n.i18n.registerUIStrings('entrypoints/rn_fusebox/FuseboxExperimentsObserver.ts', UIStrings);
30+
const str_ = i18n.i18n.registerUIStrings('entrypoints/rn_fusebox/FuseboxFeatureObserver.ts', UIStrings);
2331
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
2432

2533
/**
26-
* [Experimental] Model observer which configures available DevTools features
27-
* based on the target's capabilities, e.g. when a profiling build is identified, or when network inspection is supported.
34+
* The set of features that are not guaranteed to behave safely with multiple
35+
* React Native hosts.
36+
*/
37+
const UNSAFE_MULTI_HOST_FEATURES = new Set([
38+
'network',
39+
'timeline',
40+
]);
41+
42+
/**
43+
* [RN] Model observer which configures available DevTools features and
44+
* experiments based on the target's capabilities.
2845
*/
2946
export class FuseboxFeatureObserver implements
3047
SDK.TargetManager.SDKModelObserver<SDK.ReactNativeApplicationModel.ReactNativeApplicationModel> {
@@ -35,11 +52,14 @@ export class FuseboxFeatureObserver implements
3552
modelAdded(model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel): void {
3653
model.ensureEnabled();
3754
model.addEventListener(SDK.ReactNativeApplicationModel.Events.METADATA_UPDATED, this.#handleMetadataUpdated, this);
55+
model.addEventListener(SDK.ReactNativeApplicationModel.Events.SYSTEM_STATE_CHANGED, this.#handleSystemStateChanged, this);
3856
}
3957

4058
modelRemoved(model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel): void {
4159
model.removeEventListener(
4260
SDK.ReactNativeApplicationModel.Events.METADATA_UPDATED, this.#handleMetadataUpdated, this);
61+
model.removeEventListener(
62+
SDK.ReactNativeApplicationModel.Events.SYSTEM_STATE_CHANGED, this.#handleSystemStateChanged, this);
4363
}
4464

4565
#handleMetadataUpdated(
@@ -57,6 +77,14 @@ export class FuseboxFeatureObserver implements
5777
}
5878
}
5979

80+
#handleSystemStateChanged(
81+
event: Common.EventTarget.EventTargetEvent<Protocol.ReactNativeApplication.SystemStateChangedEvent>): void {
82+
const {isSingleHost} = event.data;
83+
if (!isSingleHost) {
84+
this.#disableSingleHostOnlyFeatures();
85+
}
86+
}
87+
6088
#hideUnsupportedFeaturesForProfilingBuilds(): void {
6189
UI.InspectorView.InspectorView.instance().closeDrawer();
6290

@@ -85,6 +113,43 @@ export class FuseboxFeatureObserver implements
85113
});
86114
}
87115

116+
#disableSingleHostOnlyFeatures(): void {
117+
// Disable relevant CDP domains
118+
const targetManager = SDK.TargetManager.TargetManager.instance();
119+
for (const target of targetManager.targets()) {
120+
void target.networkAgent().invoke_disable();
121+
}
122+
123+
const viewManager = UI.ViewManager.ViewManager.instance();
124+
const panelLocationPromise = viewManager.resolveLocation(UI.ViewManager.ViewLocationValues.PANEL);
125+
126+
// Replace disabled panels with an empty widget
127+
void panelLocationPromise.then(panelLocation => {
128+
UI.ViewManager.getRegisteredViewExtensions().forEach(view => {
129+
if (UNSAFE_MULTI_HOST_FEATURES.has(view.viewId())) {
130+
panelLocation?.removeView(view);
131+
const originalViewId = view.viewId();
132+
const originalTitle = view.title();
133+
134+
UI.ViewManager.registerViewExtension({
135+
location: UI.ViewManager.ViewLocationValues.PANEL,
136+
id: `${originalViewId}-disabled` as Lowercase<string>,
137+
commandPrompt: view.commandPrompt,
138+
title: () => originalTitle,
139+
order: view.order(),
140+
persistence: view.persistence(),
141+
async loadView() {
142+
return new UI.EmptyWidget.EmptyWidget(
143+
i18nString(UIStrings.multiHostFeatureDisabledTitle),
144+
i18nString(UIStrings.multiHostFeatureDisabledDetail),
145+
);
146+
},
147+
});
148+
}
149+
});
150+
});
151+
}
152+
88153
#ensureNetworkPanelEnabled(): void {
89154
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.ENABLE_NETWORK_PANEL)) {
90155
return;

front_end/entrypoints/rn_fusebox/rn_fusebox.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import * as UI from '../../ui/legacy/legacy.js';
2727
import * as Main from '../main/main.js';
2828

2929
import * as FuseboxAppMetadataObserverModule from './FuseboxAppMetadataObserver.js';
30-
import * as FuseboxFeatureObserverModule from './FuseboxExperimentsObserver.js';
30+
import * as FuseboxFeatureObserverModule from './FuseboxFeatureObserver.js';
3131
import * as FuseboxReconnectDeviceButtonModule from './FuseboxReconnectDeviceButton.js';
3232

3333
// To ensure accurate timing measurements, please make sure these perf metrics

front_end/generated/InspectorBackendCommands.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

front_end/generated/protocol-mapping.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export namespace ProtocolMapping {
1717
* device, application, and debugger integration.
1818
*/
1919
'ReactNativeApplication.metadataUpdated': [Protocol.ReactNativeApplication.MetadataUpdatedEvent];
20+
/**
21+
* Emitted when assertions about the debugger backend have changed.
22+
*/
23+
'ReactNativeApplication.systemStateChanged': [Protocol.ReactNativeApplication.SystemStateChangedEvent];
2024
/**
2125
* Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace.
2226
*/

front_end/generated/protocol-proxy-api.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ declare namespace ProtocolProxyApi {
255255
*/
256256
metadataUpdated(params: Protocol.ReactNativeApplication.MetadataUpdatedEvent): void;
257257

258+
/**
259+
* Emitted when assertions about the debugger backend have changed.
260+
*/
261+
systemStateChanged(params: Protocol.ReactNativeApplication.SystemStateChangedEvent): void;
262+
258263
/**
259264
* Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace.
260265
*/

front_end/generated/protocol.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ export namespace ReactNativeApplication {
6262
*/
6363
unstable_networkInspectionEnabled?: boolean;
6464
}
65+
66+
/**
67+
* Emitted when assertions about the debugger backend have changed.
68+
*/
69+
export interface SystemStateChangedEvent {
70+
/**
71+
* Whether the application has a single React Native host. If an app or
72+
* framework intantiates multiple hosts, some debugger features will not
73+
* behave safely and should be dynamically disabled.
74+
*/
75+
isSingleHost: boolean;
76+
}
6577
}
6678

6779
export namespace Accessibility {

third_party/blink/public/devtools_protocol/browser_protocol.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@
7272
}
7373
]
7474
},
75+
{
76+
"name": "systemStateChanged",
77+
"description": "Emitted when assertions about the debugger backend have changed.",
78+
"parameters": [
79+
{
80+
"name": "isSingleHost",
81+
"description": "Whether the application has a single React Native host. If an app or\nframework intantiates multiple hosts, some debugger features will not\n behave safely and should be dynamically disabled.",
82+
"type": "boolean"
83+
}
84+
]
85+
},
7586
{
7687
"name": "traceRequested",
7788
"description": "Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace."
@@ -32027,4 +32038,4 @@
3202732038
]
3202832039
}
3202932040
]
32030-
}
32041+
}

0 commit comments

Comments
 (0)