[DevTools] Add options for disabling some features (#22136)
Holger Benl committed
Aug 20, 2021 at 18:55 UTC
83205c0aea3226eac063910bab128f3258d7c7dc
8 files changed
+184
-90
packages/react-devtools-inline/src/frontend.js
+6
-1
@@ -20,11 +20,16 @@ import type {Wall} from 'react-devtools-shared/src/types';
20
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
21
import type {Props} from 'react-devtools-shared/src/devtools/views/DevTools';
22
23
-export function createStore(bridge: FrontendBridge): Store {
23
+type Config = {|
24
+ supportsNativeInspection?: boolean,
25
+|};
26
+
27
+export function createStore(bridge: FrontendBridge, config?: Config): Store {
28
return new Store(bridge, {
29
checkBridgeProtocolCompatibility: true,
30
supportsTraceUpdates: true,
31
supportsSchedulingProfiler: true,
32
+ supportsNativeInspection: config?.supportsNativeInspection !== false,
33
});
34
}
35
packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
+30
-16
@@ -10,7 +10,7 @@
10
import * as React from 'react';
11
import {useCallback, useContext} from 'react';
12
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
13
-import {BridgeContext, StoreContext} from '../context';
13
+import {BridgeContext, StoreContext, OptionsContext} from '../context';
14
import Button from '../Button';
15
import ButtonIcon from '../ButtonIcon';
16
import {ModalDialogContext} from '../ModalDialog';
@@ -37,6 +37,12 @@ export default function InspectedElementWrapper(_: Props) {
37
);
38
const bridge = useContext(BridgeContext);
39
const store = useContext(StoreContext);
40
+ const {
41
+ hideToggleErrorAction,
42
+ hideToggleSuspenseAction,
43
+ hideLogAction,
44
+ hideViewSourceAction,
45
+ } = useContext(OptionsContext);
46
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
47
48
const {
@@ -108,10 +114,14 @@ export default function InspectedElementWrapper(_: Props) {
114
inspectedElement.state != null;
115
116
const canToggleError =
111
- inspectedElement != null && inspectedElement.canToggleError;
117
+ !hideToggleErrorAction &&
118
+ inspectedElement != null &&
119
+ inspectedElement.canToggleError;
120
121
const canToggleSuspense =
114
- inspectedElement != null && inspectedElement.canToggleSuspense;
122
+ !hideToggleSuspenseAction &&
123
+ inspectedElement != null &&
124
+ inspectedElement.canToggleSuspense;
125
126
const toggleErrored = useCallback(() => {
127
if (inspectedElement == null || targetErrorBoundaryID == null) {
@@ -248,19 +258,23 @@ export default function InspectedElementWrapper(_: Props) {
258
<ButtonIcon type="view-dom" />
259
</Button>
260
)}
251
- <Button
252
- className={styles.IconButton}
253
- onClick={logElement}
254
- title="Log this component data to the console">
255
- <ButtonIcon type="log-data" />
256
- </Button>
257
- <Button
258
- className={styles.IconButton}
259
- disabled={!canViewSource}
260
- onClick={viewSource}
261
- title="View source for this element">
262
- <ButtonIcon type="view-source" />
263
- </Button>
261
+ {!hideLogAction && (
262
+ <Button
263
+ className={styles.IconButton}
264
+ onClick={logElement}
265
+ title="Log this component data to the console">
266
+ <ButtonIcon type="log-data" />
267
+ </Button>
268
+ )}
269
+ {!hideViewSourceAction && (
270
+ <Button
271
+ className={styles.IconButton}
272
+ disabled={!canViewSource}
273
+ onClick={viewSource}
274
+ title="View source for this element">
275
+ <ButtonIcon type="view-source" />
276
+ </Button>
277
+ )}
278
</div>
279
280
{inspectedElement === null && (
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementPropsTree.js
+5
-1
@@ -9,6 +9,7 @@
9
10
import {copy} from 'clipboard-js';
11
import * as React from 'react';
12
+import {OptionsContext} from '../context';
13
import Button from '../Button';
14
import ButtonIcon from '../ButtonIcon';
15
import KeyValue from './KeyValue';
@@ -35,6 +36,8 @@ export default function InspectedElementPropsTree({
36
inspectedElement,
37
store,
38
}: Props) {
39
+ const {readOnly} = React.useContext(OptionsContext);
40
+
41
const {
42
canEditFunctionProps,
43
canEditFunctionPropsDeletePaths,
@@ -45,7 +48,8 @@ export default function InspectedElementPropsTree({
48
49
const canDeletePaths =
50
type === ElementTypeClass || canEditFunctionPropsDeletePaths;
48
- const canEditValues = type === ElementTypeClass || canEditFunctionProps;
51
+ const canEditValues =
52
+ !readOnly && (type === ElementTypeClass || canEditFunctionProps);
53
const canRenamePaths =
54
type === ElementTypeClass || canEditFunctionPropsRenamePaths;
55
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js
+5
-1
@@ -8,6 +8,7 @@
8
*/
9
10
import * as React from 'react';
11
+import {OptionsContext} from '../context';
12
import EditableValue from './EditableValue';
13
import Store from '../../store';
14
import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
@@ -27,7 +28,10 @@ export default function InspectedElementSuspenseToggle({
28
inspectedElement,
29
store,
30
}: Props) {
30
- const {canToggleSuspense, id, state, type} = inspectedElement;
31
+ const {readOnly} = React.useContext(OptionsContext);
32
+
33
+ const {id, state, type} = inspectedElement;
34
+ const canToggleSuspense = !readOnly && inspectedElement.canToggleSuspense;
35
36
if (type !== ElementTypeSuspense) {
37
return null;
packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
+16
-10
@@ -9,6 +9,7 @@
9
10
import * as React from 'react';
11
import {useTransition, useContext, useRef, useState} from 'react';
12
+import {OptionsContext} from '../context';
13
import EditableName from './EditableName';
14
import EditableValue from './EditableValue';
15
import NewArrayValue from './NewArrayValue';
@@ -74,6 +75,11 @@ export default function KeyValue({
75
store,
76
value,
77
}: KeyValueProps) {
78
+ const {readOnly: readOnlyGlobalFlag} = useContext(OptionsContext);
79
+ canDeletePaths = !readOnlyGlobalFlag && canDeletePaths;
80
+ canEditValues = !readOnlyGlobalFlag && canEditValues;
81
+ canRenamePaths = !readOnlyGlobalFlag && canRenamePaths;
82
+
83
const {id} = inspectedElement;
84
85
const [isOpen, setIsOpen] = useState<boolean>(false);
@@ -82,10 +88,10 @@ export default function KeyValue({
88
const {inspectPaths} = useContext(InspectedElementContext);
89
90
let isInspectable = false;
85
- let isReadOnly = false;
91
+ let isReadOnlyBasedOnMetadata = false;
92
if (value !== null && typeof value === 'object') {
93
isInspectable = value[meta.inspectable] && value[meta.size] !== 0;
88
- isReadOnly = value[meta.readonly];
94
+ isReadOnlyBasedOnMetadata = value[meta.readonly];
95
}
96
97
const [isInspectPathsPending, startInspectPathsTransition] = useTransition();
@@ -330,9 +336,9 @@ export default function KeyValue({
336
key={index}
337
alphaSort={alphaSort}
338
bridge={bridge}
333
- canDeletePaths={canDeletePaths && !isReadOnly}
334
- canEditValues={canEditValues && !isReadOnly}
335
- canRenamePaths={canRenamePaths && !isReadOnly}
339
+ canDeletePaths={canDeletePaths && !isReadOnlyBasedOnMetadata}
340
+ canEditValues={canEditValues && !isReadOnlyBasedOnMetadata}
341
+ canRenamePaths={canRenamePaths && !isReadOnlyBasedOnMetadata}
342
canRenamePathsAtDepth={canRenamePathsAtDepth}
343
depth={depth + 1}
344
element={element}
@@ -348,7 +354,7 @@ export default function KeyValue({
354
/>
355
));
356
351
- if (canEditValues && !isReadOnly) {
357
+ if (canEditValues && !isReadOnlyBasedOnMetadata) {
358
children.push(
359
<NewArrayValue
360
key="NewKeyValue"
@@ -404,9 +410,9 @@ export default function KeyValue({
410
key={key}
411
alphaSort={alphaSort}
412
bridge={bridge}
407
- canDeletePaths={canDeletePaths && !isReadOnly}
408
- canEditValues={canEditValues && !isReadOnly}
409
- canRenamePaths={canRenamePaths && !isReadOnly}
413
+ canDeletePaths={canDeletePaths && !isReadOnlyBasedOnMetadata}
414
+ canEditValues={canEditValues && !isReadOnlyBasedOnMetadata}
415
+ canRenamePaths={canRenamePaths && !isReadOnlyBasedOnMetadata}
416
canRenamePathsAtDepth={canRenamePathsAtDepth}
417
depth={depth + 1}
418
element={element}
@@ -421,7 +427,7 @@ export default function KeyValue({
427
/>
428
));
429
424
- if (canEditValues && !isReadOnly) {
430
+ if (canEditValues && !isReadOnlyBasedOnMetadata) {
431
children.push(
432
<NewKeyValue
433
key="NewKeyValue"
packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+9
-4
@@ -23,7 +23,7 @@ import {FixedSizeList} from 'react-window';
23
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
24
import Icon from '../Icon';
25
import {SettingsContext} from '../Settings/SettingsContext';
26
-import {BridgeContext, StoreContext} from '../context';
26
+import {BridgeContext, StoreContext, OptionsContext} from '../context';
27
import Element from './Element';
28
import InspectHostNodesToggle from './InspectHostNodesToggle';
29
import OwnersStack from './OwnersStack';
@@ -62,6 +62,7 @@ export default function Tree(props: Props) {
62
} = useContext(TreeStateContext);
63
const bridge = useContext(BridgeContext);
64
const store = useContext(StoreContext);
65
+ const {hideSettings} = useContext(OptionsContext);
66
const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] = useState(
67
false,
68
);
@@ -344,11 +345,11 @@ export default function Tree(props: Props) {
345
<Suspense fallback={<Loading />}>
346
{ownerID !== null ? <OwnersStack /> : <SearchInput />}
347
</Suspense>
347
- <div className={styles.VRule} />
348
{showInlineWarningsAndErrors &&
349
ownerID === null &&
350
(errors > 0 || warnings > 0) && (
351
<React.Fragment>
352
+ <div className={styles.VRule} />
353
{errors > 0 && (
354
<div className={styles.IconAndCount}>
355
<Icon className={styles.ErrorIcon} type="error" />
@@ -376,10 +377,14 @@ export default function Tree(props: Props) {
377
title="Clear all errors and warnings">
378
<ButtonIcon type="clear" />
379
</Button>
379
- <div className={styles.VRule} />
380
</React.Fragment>
381
)}
382
- <SettingsModalContextToggle />
382
+ {!hideSettings && (
383
+ <Fragment>
384
+ <div className={styles.VRule} />
385
+ <SettingsModalContextToggle />
386
+ </Fragment>
387
+ )}
388
</div>
389
<div
390
className={styles.AutoSizerWrapper}
packages/react-devtools-shared/src/devtools/views/DevTools.js
+95
-57
@@ -15,7 +15,12 @@ import '@reach/tooltip/styles.css';
15
import * as React from 'react';
16
import {useEffect, useLayoutEffect, useMemo, useRef} from 'react';
17
import Store from '../store';
18
-import {BridgeContext, ContextMenuContext, StoreContext} from './context';
18
+import {
19
+ BridgeContext,
20
+ ContextMenuContext,
21
+ StoreContext,
22
+ OptionsContext,
23
+} from './context';
24
import Components from './Components/Components';
25
import Profiler from './Profiler/Profiler';
26
import TabBar from './TabBar';
@@ -74,6 +79,12 @@ export type Props = {|
79
warnIfUnsupportedVersionDetected?: boolean,
80
viewAttributeSourceFunction?: ?ViewAttributeSource,
81
viewElementSourceFunction?: ?ViewElementSource,
82
+ readOnly?: boolean,
83
+ hideSettings?: boolean,
84
+ hideToggleErrorAction?: boolean,
85
+ hideToggleSuspenseAction?: boolean,
86
+ hideLogAction?: boolean,
87
+ hideViewSourceAction?: boolean,
88
89
// This property is used only by the web extension target.
90
// The built-in tab UI is hidden in that case, in favor of the browser's own panel tabs.
@@ -126,6 +137,12 @@ export default function DevTools({
137
warnIfUnsupportedVersionDetected = false,
138
viewAttributeSourceFunction,
139
viewElementSourceFunction,
140
+ readOnly,
141
+ hideSettings,
142
+ hideToggleErrorAction,
143
+ hideToggleSuspenseAction,
144
+ hideLogAction,
145
+ hideViewSourceAction,
146
}: Props) {
147
const [currentTab, setTab] = useLocalStorage<TabID>(
148
'React::DevTools::defaultTab',
@@ -138,6 +155,25 @@ export default function DevTools({
155
tab = overrideTab;
156
}
157
158
+ const options = useMemo(
159
+ () => ({
160
+ readOnly: readOnly || false,
161
+ hideSettings: hideSettings || false,
162
+ hideToggleErrorAction: hideToggleErrorAction || false,
163
+ hideToggleSuspenseAction: hideToggleSuspenseAction || false,
164
+ hideLogAction: hideLogAction || false,
165
+ hideViewSourceAction: hideViewSourceAction || false,
166
+ }),
167
+ [
168
+ readOnly,
169
+ hideSettings,
170
+ hideToggleErrorAction,
171
+ hideToggleSuspenseAction,
172
+ hideLogAction,
173
+ hideViewSourceAction,
174
+ ],
175
+ );
176
+
177
const viewElementSource = useMemo(
178
() => ({
179
canViewElementSourceFunction: canViewElementSourceFunction || null,
@@ -210,65 +246,67 @@ export default function DevTools({
246
return (
247
<BridgeContext.Provider value={bridge}>
248
<StoreContext.Provider value={store}>
213
- <ContextMenuContext.Provider value={contextMenu}>
214
- <ModalDialogContextController>
215
- <SettingsContextController
216
- browserTheme={browserTheme}
217
- componentsPortalContainer={componentsPortalContainer}
218
- profilerPortalContainer={profilerPortalContainer}>
219
- <ViewElementSourceContext.Provider value={viewElementSource}>
220
- <HookNamesContext.Provider value={hookNamesContext}>
221
- <TreeContextController>
222
- <ProfilerContextController>
223
- <SchedulingProfilerContextController>
224
- <ThemeProvider>
225
- <div
226
- className={styles.DevTools}
227
- ref={devToolsRef}
228
- data-react-devtools-portal-root={true}>
229
- {showTabBar && (
230
- <div className={styles.TabBar}>
231
- <ReactLogo />
232
- <span className={styles.DevToolsVersion}>
233
- {process.env.DEVTOOLS_VERSION}
234
- </span>
235
- <div className={styles.Spacer} />
236
- <TabBar
237
- currentTab={tab}
238
- id="DevTools"
239
- selectTab={setTab}
240
- tabs={tabs}
241
- type="navigation"
249
+ <OptionsContext.Provider value={options}>
250
+ <ContextMenuContext.Provider value={contextMenu}>
251
+ <ModalDialogContextController>
252
+ <SettingsContextController
253
+ browserTheme={browserTheme}
254
+ componentsPortalContainer={componentsPortalContainer}
255
+ profilerPortalContainer={profilerPortalContainer}>
256
+ <ViewElementSourceContext.Provider value={viewElementSource}>
257
+ <HookNamesContext.Provider value={hookNamesContext}>
258
+ <TreeContextController>
259
+ <ProfilerContextController>
260
+ <SchedulingProfilerContextController>
261
+ <ThemeProvider>
262
+ <div
263
+ className={styles.DevTools}
264
+ ref={devToolsRef}
265
+ data-react-devtools-portal-root={true}>
266
+ {showTabBar && (
267
+ <div className={styles.TabBar}>
268
+ <ReactLogo />
269
+ <span className={styles.DevToolsVersion}>
270
+ {process.env.DEVTOOLS_VERSION}
271
+ </span>
272
+ <div className={styles.Spacer} />
273
+ <TabBar
274
+ currentTab={tab}
275
+ id="DevTools"
276
+ selectTab={setTab}
277
+ tabs={tabs}
278
+ type="navigation"
279
+ />
280
+ </div>
281
+ )}
282
+ <div
283
+ className={styles.TabContent}
284
+ hidden={tab !== 'components'}>
285
+ <Components
286
+ portalContainer={componentsPortalContainer}
287
+ />
288
+ </div>
289
+ <div
290
+ className={styles.TabContent}
291
+ hidden={tab !== 'profiler'}>
292
+ <Profiler
293
+ portalContainer={profilerPortalContainer}
294
/>
295
</div>
244
- )}
245
- <div
246
- className={styles.TabContent}
247
- hidden={tab !== 'components'}>
248
- <Components
249
- portalContainer={componentsPortalContainer}
250
- />
251
- </div>
252
- <div
253
- className={styles.TabContent}
254
- hidden={tab !== 'profiler'}>
255
- <Profiler
256
- portalContainer={profilerPortalContainer}
257
- />
296
</div>
259
- </div>
260
- </ThemeProvider>
261
- </SchedulingProfilerContextController>
262
- </ProfilerContextController>
263
- </TreeContextController>
264
- </HookNamesContext.Provider>
265
- </ViewElementSourceContext.Provider>
266
- </SettingsContextController>
267
- <UnsupportedBridgeProtocolDialog />
268
- {warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
269
- {warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
270
- </ModalDialogContextController>
271
- </ContextMenuContext.Provider>
297
+ </ThemeProvider>
298
+ </SchedulingProfilerContextController>
299
+ </ProfilerContextController>
300
+ </TreeContextController>
301
+ </HookNamesContext.Provider>
302
+ </ViewElementSourceContext.Provider>
303
+ </SettingsContextController>
304
+ <UnsupportedBridgeProtocolDialog />
305
+ {warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
306
+ {warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
307
+ </ModalDialogContextController>
308
+ </ContextMenuContext.Provider>
309
+ </OptionsContext.Provider>
310
</StoreContext.Provider>
311
</BridgeContext.Provider>
312
);
packages/react-devtools-shared/src/devtools/views/context.js
+18
@@ -31,3 +31,21 @@ export const ContextMenuContext = createContext<ContextMenuContextType>({
31
viewAttributeSourceFunction: null,
32
});
33
ContextMenuContext.displayName = 'ContextMenuContext';
34
+
35
+export type OptionsContextType = {|
36
+ readOnly: boolean,
37
+ hideSettings: boolean,
38
+ hideToggleErrorAction: boolean,
39
+ hideToggleSuspenseAction: boolean,
40
+ hideLogAction: boolean,
41
+ hideViewSourceAction: boolean,
42
+|};
43
+
44
+export const OptionsContext = createContext<OptionsContextType>({
45
+ readOnly: false,
46
+ hideSettings: false,
47
+ hideToggleErrorAction: false,
48
+ hideToggleSuspenseAction: false,
49
+ hideLogAction: false,
50
+ hideViewSourceAction: false,
51
+});