Skip to content

feat: use and initialize RDT Frontend in RDT panel #23

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 84 additions & 14 deletions front_end/panels/react_devtools/ReactDevToolsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,100 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as i18n from '../../core/i18n/i18n.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as ReactDevTools from '../../third_party/react-devtools/react-devtools.js';

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

export class ReactDevToolsViewImpl extends UI.Widget.VBox {
static instance(): ReactDevToolsViewImpl {
if (!instance) {
instance = new ReactDevToolsViewImpl();
}
import {Events, ReactDevToolsModel, type EventTypes} from './ReactDevToolsModel.js';

const UIStrings = {
/**
*@description Title of the React DevTools view
*/
title: 'React DevTools',
};
const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export class ReactDevToolsViewImpl extends UI.View.SimpleView {
private readonly wall: ReactDevToolsTypes.Wall;
private readonly bridge: ReactDevToolsTypes.Bridge;
private readonly store: ReactDevToolsTypes.Store;
private readonly listeners: Set<ReactDevToolsTypes.WallListener> = new Set();

constructor() {
super(i18nString(UIStrings.title));

this.wall = {
listen: (listener): Function => {
this.listeners.add(listener);

return (): void => {
this.listeners.delete(listener);
};
},
send: (event, payload): void => this.sendMessage(event, payload),
};

// To use the custom Wall we've created, we need to also create our own "Bridge" and "Store" objects.
this.bridge = ReactDevTools.createBridge(this.wall);
this.store = ReactDevTools.createStore(this.bridge);

SDK.TargetManager.TargetManager.instance().addModelListener(
ReactDevToolsModel,
Events.MessageReceived,
this.onMessage,
this,
);

return instance;
SDK.TargetManager.TargetManager.instance().addModelListener(
ReactDevToolsModel,
Events.Initialized,
this.initialize,
this,
);

const loaderContainer = document.createElement('div');
loaderContainer.setAttribute('style', 'display: flex; flex: 1; justify-content: center; align-items: center');

const loader = document.createElement('span');
loader.classList.add('spinner');

loaderContainer.appendChild(loader);
this.contentElement.appendChild(loaderContainer);
}

private constructor() {
super(true, true);
private initialize(): void {
// Remove loader
this.contentElement.removeChildren();

const usingDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;

this.registerCSSFiles([ReactDevTools.CSS]);
ReactDevTools.initialize(this.contentElement, {
bridge: this.bridge,
store: this.store,
theme: usingDarkTheme ? 'dark' : 'light',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits - can be filed as followup tasks IMO:

  1. Is this reactive to theme changes?
  2. Does CDT allow the user to override the theme? Will this honour that setting?

Copy link
Author

@hoxyq hoxyq Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this reactive to theme changes?

Nope, it doesn't, unfortunately. I am keeping this in T185412086, because we can reuse it for browser extension.

Does CDT allow the user to override the theme? Will this honour that setting?

Yes, I've specified using dark theme in my Chrome and my Fusebox window is opened in dark mode across different reloads.

});
}

override wasShown(): void {
super.wasShown();
private onMessage(event: Common.EventTarget.EventTargetEvent<EventTypes[Events.MessageReceived]>): void {
if (!event.data) {
return;
}

this.render();
for (const listener of this.listeners) {
listener(event.data);
}
}

render(): void {
return;
private sendMessage(event: string, payload?: ReactDevToolsTypes.MessagePayload): void {
for (const model of SDK.TargetManager.TargetManager.instance().models(ReactDevToolsModel, {scoped: true})) {
void model.sendMessage({event, payload});
}
}
}
2 changes: 1 addition & 1 deletion front_end/panels/react_devtools/react_devtools-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ UI.ViewManager.registerViewExtension({
experiment: Root.Runtime.ExperimentName.ENABLE_REACT_DEVTOOLS_PANEL,
condition: null,
})) {
return Module.ReactDevToolsView.ReactDevToolsViewImpl.instance();
return new Module.ReactDevToolsView.ReactDevToolsViewImpl();
}

return Module.ReactDevToolsPlaceholder.ReactDevToolsPlaceholderImpl.instance();
Expand Down
16 changes: 16 additions & 0 deletions scripts/build/generate_html_entrypoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,21 @@ for (const entrypoint of entrypoints) {
);
}

// React DevTools uses Web Workers API for parsing hook names and uploading large tracing files
// https://github.com/facebook/react/blob/fd35655fae9f88284e01754cadb0707abaca795b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js
// Here we update the CSP header to allow workers from self
if (entrypoint === 'rn_fusebox') {
const cspHeaderRegex = /(?<=<meta http-equiv="Content-Security-Policy" content=")(.*)(?=">)/;
const cspHeaderMatch = rewrittenTemplateContent.match(cspHeaderRegex);
if (cspHeaderMatch === null) {
throw new Error('Couldn\'t find CSP header for rn_fusebox entrypoint: this can break React DevTools panel');
}

rewrittenTemplateContent = rewrittenTemplateContent.replace(
cspHeaderRegex,
'$1; worker-src \'self\' blob:'
);
}

writeIfChanged(path.join(outDirectory, `${entrypoint}.html`), rewrittenTemplateContent);
}