Skip to content

Commit 9222797

Browse files
authored
feat: use and initialize RDT Frontend in RDT panel (#23)
1 parent c819d50 commit 9222797

File tree

3 files changed

+101
-15
lines changed

3 files changed

+101
-15
lines changed

front_end/panels/react_devtools/ReactDevToolsView.ts

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,100 @@
33
// Use of this source code is governed by a BSD-style license that can be
44
// found in the LICENSE file.
55

6+
import * as i18n from '../../core/i18n/i18n.js';
67
import * as UI from '../../ui/legacy/legacy.js';
8+
import * as SDK from '../../core/sdk/sdk.js';
9+
import * as ReactDevTools from '../../third_party/react-devtools/react-devtools.js';
710

8-
let instance: ReactDevToolsViewImpl;
11+
import type * as ReactDevToolsTypes from '../../third_party/react-devtools/react-devtools.js';
12+
import type * as Common from '../../core/common/common.js';
913

10-
export class ReactDevToolsViewImpl extends UI.Widget.VBox {
11-
static instance(): ReactDevToolsViewImpl {
12-
if (!instance) {
13-
instance = new ReactDevToolsViewImpl();
14-
}
14+
import {Events, ReactDevToolsModel, type EventTypes} from './ReactDevToolsModel.js';
15+
16+
const UIStrings = {
17+
/**
18+
*@description Title of the React DevTools view
19+
*/
20+
title: 'React DevTools',
21+
};
22+
const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsView.ts', UIStrings);
23+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
24+
25+
export class ReactDevToolsViewImpl extends UI.View.SimpleView {
26+
private readonly wall: ReactDevToolsTypes.Wall;
27+
private readonly bridge: ReactDevToolsTypes.Bridge;
28+
private readonly store: ReactDevToolsTypes.Store;
29+
private readonly listeners: Set<ReactDevToolsTypes.WallListener> = new Set();
30+
31+
constructor() {
32+
super(i18nString(UIStrings.title));
33+
34+
this.wall = {
35+
listen: (listener): Function => {
36+
this.listeners.add(listener);
37+
38+
return (): void => {
39+
this.listeners.delete(listener);
40+
};
41+
},
42+
send: (event, payload): void => this.sendMessage(event, payload),
43+
};
44+
45+
// To use the custom Wall we've created, we need to also create our own "Bridge" and "Store" objects.
46+
this.bridge = ReactDevTools.createBridge(this.wall);
47+
this.store = ReactDevTools.createStore(this.bridge);
48+
49+
SDK.TargetManager.TargetManager.instance().addModelListener(
50+
ReactDevToolsModel,
51+
Events.MessageReceived,
52+
this.onMessage,
53+
this,
54+
);
1555

16-
return instance;
56+
SDK.TargetManager.TargetManager.instance().addModelListener(
57+
ReactDevToolsModel,
58+
Events.Initialized,
59+
this.initialize,
60+
this,
61+
);
62+
63+
const loaderContainer = document.createElement('div');
64+
loaderContainer.setAttribute('style', 'display: flex; flex: 1; justify-content: center; align-items: center');
65+
66+
const loader = document.createElement('span');
67+
loader.classList.add('spinner');
68+
69+
loaderContainer.appendChild(loader);
70+
this.contentElement.appendChild(loaderContainer);
1771
}
1872

19-
private constructor() {
20-
super(true, true);
73+
private initialize(): void {
74+
// Remove loader
75+
this.contentElement.removeChildren();
76+
77+
const usingDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
78+
79+
this.registerCSSFiles([ReactDevTools.CSS]);
80+
ReactDevTools.initialize(this.contentElement, {
81+
bridge: this.bridge,
82+
store: this.store,
83+
theme: usingDarkTheme ? 'dark' : 'light',
84+
});
2185
}
2286

23-
override wasShown(): void {
24-
super.wasShown();
87+
private onMessage(event: Common.EventTarget.EventTargetEvent<EventTypes[Events.MessageReceived]>): void {
88+
if (!event.data) {
89+
return;
90+
}
2591

26-
this.render();
92+
for (const listener of this.listeners) {
93+
listener(event.data);
94+
}
2795
}
2896

29-
render(): void {
30-
return;
97+
private sendMessage(event: string, payload?: ReactDevToolsTypes.MessagePayload): void {
98+
for (const model of SDK.TargetManager.TargetManager.instance().models(ReactDevToolsModel, {scoped: true})) {
99+
void model.sendMessage({event, payload});
100+
}
31101
}
32102
}

front_end/panels/react_devtools/react_devtools-meta.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ UI.ViewManager.registerViewExtension({
4646
experiment: Root.Runtime.ExperimentName.ENABLE_REACT_DEVTOOLS_PANEL,
4747
condition: null,
4848
})) {
49-
return Module.ReactDevToolsView.ReactDevToolsViewImpl.instance();
49+
return new Module.ReactDevToolsView.ReactDevToolsViewImpl();
5050
}
5151

5252
return Module.ReactDevToolsPlaceholder.ReactDevToolsPlaceholderImpl.instance();

scripts/build/generate_html_entrypoint.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,21 @@ for (const entrypoint of entrypoints) {
4646
);
4747
}
4848

49+
// React DevTools uses Web Workers API for parsing hook names and uploading large tracing files
50+
// https://github.com/facebook/react/blob/fd35655fae9f88284e01754cadb0707abaca795b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js
51+
// Here we update the CSP header to allow workers from self
52+
if (entrypoint === 'rn_fusebox') {
53+
const cspHeaderRegex = /(?<=<meta http-equiv="Content-Security-Policy" content=")(.*)(?=">)/;
54+
const cspHeaderMatch = rewrittenTemplateContent.match(cspHeaderRegex);
55+
if (cspHeaderMatch === null) {
56+
throw new Error('Couldn\'t find CSP header for rn_fusebox entrypoint: this can break React DevTools panel');
57+
}
58+
59+
rewrittenTemplateContent = rewrittenTemplateContent.replace(
60+
cspHeaderRegex,
61+
'$1; worker-src \'self\' blob:'
62+
);
63+
}
64+
4965
writeIfChanged(path.join(outDirectory, `${entrypoint}.html`), rewrittenTemplateContent);
5066
}

0 commit comments

Comments
 (0)