@samitouri / QOS-React-2 / commits / 83205c0aea

[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 +});