Skip to content
Open
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
21 changes: 18 additions & 3 deletions packages/react-devtools-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,30 @@ if (process.env.NODE_ENV !== 'production') {
> **NOTE** that this API (`connectToDevTools`) must be (1) run in the same context as React and (2) must be called before React packages are imported (e.g. `react`, `react-dom`, `react-native`).

### `initialize` arguments
| Argument | Description |
|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `settings` | Optional. If not specified, or received as null, then default settings are used. Can be plain object or a Promise that resolves with the [plain settings object](#Settings). If Promise rejects, the console will not be patched and some console features from React DevTools will not work. |
| Argument | Description |
|---------------------------|-------------|
| `settings` | Optional. If not specified, or received as null, then default settings are used. Can be plain object or a Promise that resolves with the [plain settings object](#Settings). If Promise rejects, the console will not be patched and some console features from React DevTools will not work. |
| `shouldStartProfilingNow` | Optional. Whether to start profiling immediately after installing the hook. Defaults to `false`. |
| `profilingSettings` | Optional. Profiling settings used when `shouldStartProfilingNow` is `true`. Defaults to `{ recordChangeDescriptions: false, recordTimeline: false }`. |
| `componentFilters` | Optional. Array or Promise that resolves to an array of component filters to apply before DevTools connects. Defaults to the built-in host component filter. See [Component filters](#component-filters) for the full spec. |

#### `Settings`
| Spec | Default value |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <pre>{<br> appendComponentStack: boolean,<br> breakOnConsoleErrors: boolean,<br> showInlineWarningsAndErrors: boolean,<br> hideConsoleLogsInStrictMode: boolean,<br> disableSecondConsoleLogDimmingInStrictMode: boolean<br>}</pre> | <pre>{<br> appendComponentStack: true,<br> breakOnConsoleErrors: false,<br> showInlineWarningsAndErrors: true,<br> hideConsoleLogsInStrictMode: false,<br> disableSecondConsoleLogDimmingInStrictMode: false<br>}</pre> |

#### Component filters
Each filter object must include `type` and `isEnabled`. Some filters also require `value` or `isValid`.

| Type | Required fields | Description |
|------|-----------------|-------------|
| `ComponentFilterElementType` (`1`) | `type`, `isEnabled`, `value: ElementType` | Hides elements of the given element type. DevTools defaults to hiding host components. |
| `ComponentFilterDisplayName` (`2`) | `type`, `isEnabled`, `isValid`, `value: string` | Hides components whose display name matches the provided RegExp string. |
| `ComponentFilterLocation` (`3`) | `type`, `isEnabled`, `isValid`, `value: string` | Hides components whose source location matches the provided RegExp string. |
| `ComponentFilterHOC` (`4`) | `type`, `isEnabled`, `isValid` | Hides higher-order components. |
| `ComponentFilterEnvironmentName` (`5`) | `type`, `isEnabled`, `isValid`, `value: string` | Hides components whose environment name matches the provided string. |
| `ComponentFilterActivitySlice` (`6`) | `type`, `isEnabled`, `isValid`, `activityID`, `rendererID` | Filters activity slices; usually managed by DevTools rather than user code. |

### `connectToDevTools` options
| Prop | Default | Description |
|------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------|
Expand Down
25 changes: 8 additions & 17 deletions packages/react-devtools-core/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,17 @@ export function initialize(
| Promise<DevToolsHookSettings>,
shouldStartProfilingNow: boolean = false,
profilingSettings?: ProfilingSettings,
maybeComponentFiltersOrComponentFiltersPromise?:
| Array<ComponentFilter>
| Promise<Array<ComponentFilter>>,
) {
const componentFiltersOrComponentFiltersPromise =
maybeComponentFiltersOrComponentFiltersPromise
? maybeComponentFiltersOrComponentFiltersPromise
: savedComponentFilters;
installHook(
window,
componentFiltersOrComponentFiltersPromise,
maybeSettingsOrSettingsPromise,
shouldStartProfilingNow,
profilingSettings,
Expand Down Expand Up @@ -174,19 +182,6 @@ export function connectToDevTools(options: ?ConnectOptions) {
},
);

// The renderer interface doesn't read saved component filters directly,
// because they are generally stored in localStorage within the context of the extension.
// Because of this it relies on the extension to pass filters.
// In the case of the standalone DevTools being used with a website,
// saved filters are injected along with the backend script tag so we shouldn't override them here.
// This injection strategy doesn't work for React Native though.
// Ideally the backend would save the filters itself, but RN doesn't provide a sync storage solution.
// So for now we just fall back to using the default filters...
if (window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == null) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
bridge.send('overrideComponentFilters', savedComponentFilters);
}

// TODO (npm-packages) Warn if "isBackendStorageAPISupported"
// $FlowFixMe[incompatible-call] found when upgrading Flow
const agent = new Agent(bridge, isProfiling, onReloadAndProfile);
Expand Down Expand Up @@ -381,10 +376,6 @@ export function connectWithCustomMessagingProtocol({
},
);

if (window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == null) {
bridge.send('overrideComponentFilters', savedComponentFilters);
}

const agent = new Agent(bridge, isProfiling, onReloadAndProfile);
if (typeof onReloadAndProfileFlagsReset === 'function') {
onReloadAndProfileFlagsReset();
Expand Down
11 changes: 3 additions & 8 deletions packages/react-devtools-core/src/standalone.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,17 +356,12 @@ function startServer(
// because they are generally stored in localStorage within the context of the extension.
// Because of this it relies on the extension to pass filters, so include them wth the response here.
// This will ensure that saved filters are shared across different web pages.
const savedPreferencesString = `
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
getSavedComponentFilters(),
)};`;
const componentFiltersString = JSON.stringify(getSavedComponentFilters());

response.end(
savedPreferencesString +
backendFile.toString() +
'\n;' +
backendFile.toString() +
'\n;' +
'ReactDevToolsBackend.initialize();' +
`ReactDevToolsBackend.initialize(undefined, undefined, undefined, ${componentFiltersString});` +
'\n' +
`ReactDevToolsBackend.connectToDevTools({port: ${port}, host: '${host}', useHttps: ${
useHttps ? 'true' : 'false'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@
// This is the only purpose of this script - to send persisted settings to installHook.js content script

import type {UnknownMessageEvent} from './messages';
import type {DevToolsHookSettings} from 'react-devtools-shared/src/backend/types';
import type {
DevToolsHookSettings,
DevToolsSettings,
} from 'react-devtools-shared/src/backend/types';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
import {postMessage} from './messages';

import {getDefaultComponentFilters} from 'react-devtools-shared/src/utils';

async function messageListener(event: UnknownMessageEvent) {
if (event.source !== window) {
return;
}

if (event.data.source === 'react-devtools-hook-installer') {
if (event.data.payload.handshake) {
const settings: Partial<DevToolsHookSettings> =
const settings: Partial<DevToolsSettings> =
await chrome.storage.local.get();
// If storage was empty (first installation), define default settings
const hookSettings: DevToolsHookSettings = {
Expand All @@ -41,10 +47,15 @@ async function messageListener(event: UnknownMessageEvent) {
? settings.disableSecondConsoleLogDimmingInStrictMode
: false,
};
const componentFilters: Array<ComponentFilter> = Array.isArray(
settings.componentFilters,
)
? settings.componentFilters
: getDefaultComponentFilters();

postMessage({
source: 'react-devtools-hook-settings-injector',
payload: {settings: hookSettings},
source: 'react-devtools-settings-injector',
payload: {hookSettings, componentFilters},
});

window.removeEventListener('message', messageListener);
Expand All @@ -54,6 +65,6 @@ async function messageListener(event: UnknownMessageEvent) {

window.addEventListener('message', messageListener);
postMessage({
source: 'react-devtools-hook-settings-injector',
source: 'react-devtools-settings-injector',
payload: {handshake: true},
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type {UnknownMessageEvent} from './messages';
import type {DevToolsHookSettings} from 'react-devtools-shared/src/backend/types';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';

import {installHook} from 'react-devtools-shared/src/hook';
import {
Expand All @@ -11,13 +12,14 @@ import {
import {postMessage} from './messages';

let resolveHookSettingsInjection: (settings: DevToolsHookSettings) => void;
let resolveComponentFiltersInjection: (filters: Array<ComponentFilter>) => void;

function messageListener(event: UnknownMessageEvent) {
if (event.source !== window) {
return;
}

if (event.data.source === 'react-devtools-hook-settings-injector') {
if (event.data.source === 'react-devtools-settings-injector') {
const payload = event.data.payload;
// In case handshake message was sent prior to hookSettingsInjector execution
// We can't guarantee order
Expand All @@ -26,9 +28,10 @@ function messageListener(event: UnknownMessageEvent) {
source: 'react-devtools-hook-installer',
payload: {handshake: true},
});
} else if (payload.settings) {
} else if (payload.hookSettings) {
window.removeEventListener('message', messageListener);
resolveHookSettingsInjection(payload.settings);
resolveHookSettingsInjection(payload.hookSettings);
resolveComponentFiltersInjection(payload.componentFilters);
}
}
}
Expand All @@ -38,6 +41,11 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
const hookSettingsPromise = new Promise<DevToolsHookSettings>(resolve => {
resolveHookSettingsInjection = resolve;
});
const componentFiltersPromise = new Promise<Array<ComponentFilter>>(
resolve => {
resolveComponentFiltersInjection = resolve;
},
);

window.addEventListener('message', messageListener);
postMessage({
Expand All @@ -50,6 +58,7 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
// Can't delay hook installation, inject settings lazily
installHook(
window,
componentFiltersPromise,
hookSettingsPromise,
shouldStartProfiling,
profilingSettings,
Expand Down
22 changes: 12 additions & 10 deletions packages/react-devtools-extensions/src/contentScripts/messages.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @flow */

import type {DevToolsHookSettings} from 'react-devtools-shared/src/backend/types';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';

export function postMessage(event: UnknownMessageEventData): void {
window.postMessage(event);
Expand All @@ -10,7 +11,7 @@ export interface UnknownMessageEvent
extends MessageEvent<UnknownMessageEventData> {}

export type UnknownMessageEventData =
| HookSettingsInjectorEventData
| SettingsInjectorEventData
| HookInstallerEventData;

export type HookInstallerEventData = {
Expand All @@ -24,19 +25,20 @@ export type HookInstallerEventPayloadHandshake = {
handshake: true,
};

export type HookSettingsInjectorEventData = {
source: 'react-devtools-hook-settings-injector',
payload: HookSettingsInjectorEventPayload,
export type SettingsInjectorEventData = {
source: 'react-devtools-settings-injector',
payload: SettingsInjectorEventPayload,
};

export type HookSettingsInjectorEventPayload =
| HookSettingsInjectorEventPayloadHandshake
| HookSettingsInjectorEventPayloadSettings;
export type SettingsInjectorEventPayload =
| SettingsInjectorEventPayloadHandshake
| SettingsInjectorEventPayloadSettings;

export type HookSettingsInjectorEventPayloadHandshake = {
export type SettingsInjectorEventPayloadHandshake = {
handshake: true,
};

export type HookSettingsInjectorEventPayloadSettings = {
settings: DevToolsHookSettings,
export type SettingsInjectorEventPayloadSettings = {
hookSettings: DevToolsHookSettings,
componentFilters: Array<ComponentFilter>,
};
4 changes: 2 additions & 2 deletions packages/react-devtools-extensions/src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ function createBridgeAndStore() {
createSuspensePanel();
});

store.addListener('settingsUpdated', settings => {
chrome.storage.local.set(settings);
store.addListener('settingsUpdated', (hookSettings, componentFilters) => {
chrome.storage.local.set({...hookSettings, componentFilters});
});

if (!isProfiling) {
Expand Down
30 changes: 15 additions & 15 deletions packages/react-devtools-inline/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ import type {
BackendBridge,
SavedPreferencesParams,
} from 'react-devtools-shared/src/bridge';
import type {Wall} from 'react-devtools-shared/src/frontend/types';
import type {
ComponentFilter,
Wall,
} from 'react-devtools-shared/src/frontend/types';
import {
getIfReloadedAndProfiling,
getIsReloadAndProfileSupported,
onReloadAndProfile,
onReloadAndProfileFlagsReset,
} from 'react-devtools-shared/src/utils';

let resolveComponentFiltersInjection: (filters: Array<ComponentFilter>) => void;
const componentFiltersPromise = new Promise<Array<ComponentFilter>>(resolve => {
resolveComponentFiltersInjection = resolve;
});

function startActivation(contentWindow: any, bridge: BackendBridge) {
const onSavedPreferences = (data: SavedPreferencesParams) => {
// This is the only message we're listening for,
Expand All @@ -26,21 +34,13 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {

const {componentFilters} = data;

contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;

// TRICKY
// The backend entry point may be required in the context of an iframe or the parent window.
// If it's required within the parent window, store the saved values on it as well,
// since the injected renderer interface will read from window.
// Technically we don't need to store them on the contentWindow in this case,
// but it doesn't really hurt anything to store them there too.
if (contentWindow !== window) {
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
}

finishActivation(contentWindow, bridge);
resolveComponentFiltersInjection(componentFilters);
};

componentFiltersPromise.then(
finishActivation.bind(null, contentWindow, bridge),
);

bridge.addListener('savedPreferences', onSavedPreferences);

// The backend may be unable to read saved preferences directly,
Expand Down Expand Up @@ -113,5 +113,5 @@ export function createBridge(contentWindow: any, wall?: Wall): BackendBridge {
}

export function initialize(contentWindow: any): void {
installHook(contentWindow);
installHook(contentWindow, componentFiltersPromise);
}
3 changes: 1 addition & 2 deletions packages/react-devtools-shared/src/__tests__/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,8 @@ beforeEach(() => {

// Initialize filters to a known good state.
setSavedComponentFilters(getDefaultComponentFilters());
global.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = getDefaultComponentFilters();

installHook(global, {
installHook(global, getDefaultComponentFilters(), {
appendComponentStack: true,
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/react-devtools-shared/src/attachRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
RendererID,
ProfilingSettings,
} from 'react-devtools-shared/src/backend/types';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';

import {attach as attachFlight} from 'react-devtools-shared/src/backend/flight/renderer';
import {attach as attachFiber} from 'react-devtools-shared/src/backend/fiber/renderer';
Expand All @@ -32,6 +33,9 @@ export default function attachRenderer(
global: Object,
shouldStartProfilingNow: boolean,
profilingSettings: ProfilingSettings,
componentFiltersOrComponentFiltersPromise:
| Array<ComponentFilter>
| Promise<Array<ComponentFilter>>,
): RendererInterface | void {
// only attach if the renderer is compatible with the current version of the backend
if (!isMatchingRender(renderer.reconcilerVersion || renderer.version)) {
Expand All @@ -58,6 +62,7 @@ export default function attachRenderer(
global,
shouldStartProfilingNow,
profilingSettings,
componentFiltersOrComponentFiltersPromise,
);
} else if (renderer.ComponentTree) {
// react-dom v15
Expand Down
Loading
Loading