Add support for hide-by-name and hide-by-path component filter regexps
Brian Vaughn committed
Apr 30, 2019 at 12:52 UTC
0b4bfbc98fdf3cf8c11a25ef7d8f8841cbc549d6
3 files changed
+169
-186
src/backend/renderer.js
+130
-170
@@ -37,7 +37,6 @@ import type {
37
DevToolsHook,
38
Fiber,
39
FiberCommitsBackend,
40
- FiberData,
40
InteractionBackend,
41
InteractionsBackend,
42
InteractionWithCommitsBackend,
@@ -48,7 +47,7 @@ import type {
47
RendererInterface,
48
} from './types';
49
import type { InspectedElement } from 'src/devtools/views/Components/types';
51
-import type { FilterPreferences } from 'src/types';
50
+import type { ElementType, FilterPreferences } from 'src/types';
51
52
function getInternalReactConstants(version) {
53
const ReactSymbols = {
@@ -245,18 +244,15 @@ export function attach(
244
245
const debug = (name: string, fiber: Fiber, parentFiber: ?Fiber): void => {
246
if (__DEBUG__) {
248
- const fiberData = getDataForFiber(fiber);
249
- const fiberDisplayName = (fiberData && fiberData.displayName) || 'null';
250
- const parentFiberData =
251
- parentFiber == null ? null : getDataForFiber(parentFiber);
252
- const parentFiberDisplayName =
253
- (parentFiberData && parentFiberData.displayName) || 'null';
247
+ const displayName = getDisplayNameForFiber(fiber) || 'null';
248
+ const parentDisplayName =
249
+ (parentFiber !== null && getDisplayNameForFiber(parentFiber)) || 'null';
250
// NOTE: calling getFiberID or getPrimaryFiber is unsafe here
251
// because it will put them in the map. For now, we'll omit them.
252
// TODO: better debugging story for this.
253
console.log(
258
- `[renderer] %c${name} %c${fiberDisplayName} %c${
259
- parentFiber ? parentFiberDisplayName : ''
254
+ `[renderer] %c${name} %c${displayName} %c${
255
+ parentFiber ? parentDisplayName : ''
256
}`,
257
'color: red; font-weight: bold;',
258
'color: blue;',
@@ -271,7 +267,10 @@ export function attach(
267
hideElementsWithPaths,
268
} = getSavedFilterPreferences();
269
274
- // TODO (filter) We could make this more efficient.
270
+ // TODO (filter) Should we make this operation more efficient?
271
+ // For example, we could add a new recursive unmount tree operation.
272
+ // The unmount operations are already significantly smaller than mount opreations though.
273
+ // This is something to keep in mind for later.
274
function updateFilterPreferences(filterPreferences: FilterPreferences) {
275
if (this._isProfiling) {
276
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
@@ -302,32 +301,11 @@ export function attach(
301
});
302
}
303
305
- // NOTICE Keep in sync with getDataForFiber()
304
+ // NOTICE Keep in sync with get*ForFiber methods
305
function shouldFilterFiber(fiber: Fiber): boolean {
307
- const { tag } = fiber;
308
-
309
- // TODO (filter) This does not yet support display name or path based filtering.
306
+ const { _debugSource, tag, type } = fiber;
307
308
switch (tag) {
312
- case ClassComponent:
313
- case IncompleteClassComponent:
314
- return hideElementsWithTypes.has(ElementTypeClass);
315
- case FunctionComponent:
316
- return hideElementsWithTypes.has(ElementTypeFunction);
317
- case IndeterminateComponent:
318
- return (
319
- hideElementsWithTypes.has(ElementTypeClass) ||
320
- hideElementsWithTypes.has(ElementTypeFunction)
321
- );
322
- case ForwardRef:
323
- return hideElementsWithTypes.has(ElementTypeForwardRef);
324
- case MemoComponent:
325
- case SimpleMemoComponent:
326
- return hideElementsWithTypes.has(ElementTypeMemo);
327
- case HostComponent:
328
- return hideElementsWithTypes.has(ElementTypeHostComponent);
329
- case HostRoot:
330
- return false; // We never support filtering roots
309
case DehydratedSuspenseComponent:
310
// TODO: ideally we would show dehydrated Suspense immediately.
311
// However, it has some special behavior (like disconnecting
@@ -341,7 +319,7 @@ export function attach(
319
case Fragment:
320
return true;
321
default:
344
- const typeSymbol = getTypeSymbol(fiber.type);
322
+ const typeSymbol = getTypeSymbol(type);
323
324
switch (typeSymbol) {
325
case CONCURRENT_MODE_NUMBER:
@@ -350,22 +328,35 @@ export function attach(
328
case STRICT_MODE_NUMBER:
329
case STRICT_MODE_SYMBOL_STRING:
330
return true;
353
- case CONTEXT_PROVIDER_NUMBER:
354
- case CONTEXT_PROVIDER_SYMBOL_STRING:
355
- case CONTEXT_CONSUMER_NUMBER:
356
- case CONTEXT_CONSUMER_SYMBOL_STRING:
357
- return hideElementsWithTypes.has(ElementTypeContext);
358
- case SUSPENSE_NUMBER:
359
- case SUSPENSE_SYMBOL_STRING:
360
- case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
361
- return hideElementsWithTypes.has(ElementTypeSuspense);
362
- case PROFILER_NUMBER:
363
- case PROFILER_SYMBOL_STRING:
364
- return hideElementsWithTypes.has(ElementTypeProfiler);
331
default:
366
- return false;
332
+ break;
333
}
334
}
335
+
336
+ const elementType = getTypeForFiber(fiber);
337
+ if (hideElementsWithTypes.has(elementType)) {
338
+ return true;
339
+ }
340
+
341
+ if (hideElementsWithDisplayNames.size > 0) {
342
+ const displayName = getDisplayNameForFiber(fiber) || '';
343
+ for (let displayNameRegExp of hideElementsWithDisplayNames) {
344
+ if (displayNameRegExp.test(displayName)) {
345
+ return true;
346
+ }
347
+ }
348
+ }
349
+
350
+ if (_debugSource !== null && hideElementsWithPaths.size > 0) {
351
+ const { fileName } = _debugSource;
352
+ for (let pathRegExp of hideElementsWithPaths) {
353
+ if (pathRegExp.test(fileName)) {
354
+ return true;
355
+ }
356
+ }
357
+ }
358
+
359
+ return false;
360
}
361
362
function getTypeSymbol(type: any): Symbol | number {
@@ -377,9 +368,9 @@ export function attach(
368
: symbolOrNumber;
369
}
370
380
- // NOTICE Keep in sync with shouldFilterFiber()
381
- function getDataForFiber(fiber: Fiber): FiberData {
382
- const { elementType, type, key, tag } = fiber;
371
+ // NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
372
+ function getDisplayNameForFiber(fiber: Fiber): string | null {
373
+ const { elementType, type, tag } = fiber;
374
375
// This is to support lazy components with a Promise as the type.
376
// see https://github.com/facebook/react/pull/13397
@@ -390,96 +381,47 @@ export function attach(
381
}
382
}
383
393
- let fiberData: FiberData = ((null: any): FiberData);
394
- let displayName: string = ((null: any): string);
384
let resolvedContext: any = null;
385
386
switch (tag) {
387
case ClassComponent:
388
case IncompleteClassComponent:
400
- fiberData = {
401
- displayName: getDisplayName(resolvedType),
402
- key,
403
- type: ElementTypeClass,
404
- };
405
- break;
389
+ return getDisplayName(resolvedType);
390
case FunctionComponent:
391
case IndeterminateComponent:
408
- fiberData = {
409
- displayName: getDisplayName(resolvedType),
410
- key,
411
- type: ElementTypeFunction,
412
- };
413
- break;
392
+ return getDisplayName(resolvedType);
393
case EventComponent:
415
- fiberData = {
416
- displayName: null,
417
- key,
418
- type: ElementTypeEventComponent,
419
- };
420
- break;
394
+ return null;
395
case EventTarget:
396
switch (getTypeSymbol(elementType.type)) {
397
case EVENT_TARGET_TOUCH_HIT_NUMBER:
398
case EVENT_TARGET_TOUCH_HIT_STRING:
425
- displayName = 'TouchHitTarget';
426
- break;
399
+ return 'TouchHitTarget';
400
default:
428
- displayName = 'EventTarget';
429
- break;
401
+ return 'EventTarget';
402
}
431
- fiberData = {
432
- displayName,
433
- key,
434
- type: ElementTypeEventTarget,
435
- };
436
- break;
403
case ForwardRef:
404
const functionName = getDisplayName(resolvedType.render, '');
439
- displayName =
405
+ return (
406
resolvedType.displayName ||
441
- (functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef');
442
-
443
- fiberData = {
444
- displayName,
445
- key,
446
- type: ElementTypeForwardRef,
447
- };
448
- break;
407
+ (functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef')
408
+ );
409
case HostRoot:
450
- return {
451
- displayName: null,
452
- key: null,
453
- type: ElementTypeRoot,
454
- };
410
+ return null;
411
case HostComponent:
456
- return {
457
- displayName: type,
458
- key,
459
- type: ElementTypeHostComponent,
460
- };
412
+ return type;
413
case HostPortal:
414
case HostText:
415
case Fragment:
464
- return {
465
- displayName: null,
466
- key,
467
- type: ElementTypeOtherOrUnknown,
468
- };
416
+ return null;
417
case MemoComponent:
418
case SimpleMemoComponent:
419
if (elementType.displayName) {
472
- displayName = elementType.displayName;
420
+ return elementType.displayName;
421
} else {
474
- displayName = type.displayName || type.name;
475
- displayName = displayName ? `Memo(${displayName})` : 'Memo';
422
+ const displayName = type.displayName || type.name;
423
+ return displayName ? `Memo(${displayName})` : 'Memo';
424
}
477
- fiberData = {
478
- displayName,
479
- key,
480
- type: ElementTypeMemo,
481
- };
482
- break;
425
default:
426
const typeSymbol = getTypeSymbol(type);
427
@@ -487,26 +429,14 @@ export function attach(
429
case CONCURRENT_MODE_NUMBER:
430
case CONCURRENT_MODE_SYMBOL_STRING:
431
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
490
- return {
491
- displayName: null,
492
- key: null,
493
- type: ElementTypeOtherOrUnknown,
494
- };
432
+ return null;
433
case CONTEXT_PROVIDER_NUMBER:
434
case CONTEXT_PROVIDER_SYMBOL_STRING:
435
// 16.3.0 exposed the context object as "context"
436
// PR #12501 changed it to "_context" for 16.3.1+
437
// NOTE Keep in sync with inspectElement()
438
resolvedContext = fiber.type._context || fiber.type.context;
501
- displayName = `${resolvedContext.displayName ||
502
- 'Context'}.Provider`;
503
-
504
- fiberData = {
505
- displayName,
506
- key,
507
- type: ElementTypeContext,
508
- };
509
- break;
439
+ return `${resolvedContext.displayName || 'Context'}.Provider`;
440
case CONTEXT_CONSUMER_NUMBER:
441
case CONTEXT_CONSUMER_SYMBOL_STRING:
442
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
@@ -516,54 +446,81 @@ export function attach(
446
447
// NOTE: TraceUpdatesBackendManager depends on the name ending in '.Consumer'
448
// If you change the name, figure out a more resilient way to detect it.
519
- displayName = `${resolvedContext.displayName ||
520
- 'Context'}.Consumer`;
521
-
522
- fiberData = {
523
- displayName,
524
- key,
525
- type: ElementTypeContext,
526
- };
527
- break;
449
+ return `${resolvedContext.displayName || 'Context'}.Consumer`;
450
case STRICT_MODE_NUMBER:
451
case STRICT_MODE_SYMBOL_STRING:
530
- fiberData = {
531
- displayName: null,
532
- key,
533
- type: ElementTypeOtherOrUnknown,
534
- };
535
- break;
452
+ return null;
453
case SUSPENSE_NUMBER:
454
case SUSPENSE_SYMBOL_STRING:
455
case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
539
- fiberData = {
540
- displayName: 'Suspense',
541
- key,
542
- type: ElementTypeSuspense,
543
- };
544
- break;
456
+ return 'Suspense';
457
case PROFILER_NUMBER:
458
case PROFILER_SYMBOL_STRING:
547
- fiberData = {
548
- displayName: `Profiler(${fiber.memoizedProps.id})`,
549
- key,
550
- type: ElementTypeProfiler,
551
- };
552
- break;
459
+ return `Profiler(${fiber.memoizedProps.id})`;
460
default:
461
// Unknown element type.
462
// This may mean a new element type that has not yet been added to DevTools.
556
- fiberData = {
557
- displayName: null,
558
- key,
559
- type: ElementTypeOtherOrUnknown,
560
- };
561
- break;
463
+ return null;
464
}
563
- break;
465
}
466
+ }
467
566
- return fiberData;
468
+ // NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
469
+ function getTypeForFiber(fiber: Fiber): ElementType {
470
+ const { type, tag } = fiber;
471
+
472
+ switch (tag) {
473
+ case ClassComponent:
474
+ case IncompleteClassComponent:
475
+ return ElementTypeClass;
476
+ case FunctionComponent:
477
+ case IndeterminateComponent:
478
+ return ElementTypeFunction;
479
+ case EventComponent:
480
+ return ElementTypeEventComponent;
481
+ case EventTarget:
482
+ return ElementTypeEventTarget;
483
+ case ForwardRef:
484
+ return ElementTypeForwardRef;
485
+ case HostRoot:
486
+ return ElementTypeRoot;
487
+ case HostComponent:
488
+ return ElementTypeHostComponent;
489
+ case HostPortal:
490
+ case HostText:
491
+ case Fragment:
492
+ return ElementTypeOtherOrUnknown;
493
+ case MemoComponent:
494
+ case SimpleMemoComponent:
495
+ return ElementTypeMemo;
496
+ default:
497
+ const typeSymbol = getTypeSymbol(type);
498
+
499
+ switch (typeSymbol) {
500
+ case CONCURRENT_MODE_NUMBER:
501
+ case CONCURRENT_MODE_SYMBOL_STRING:
502
+ case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
503
+ return ElementTypeOtherOrUnknown;
504
+ case CONTEXT_PROVIDER_NUMBER:
505
+ case CONTEXT_PROVIDER_SYMBOL_STRING:
506
+ return ElementTypeContext;
507
+ case CONTEXT_CONSUMER_NUMBER:
508
+ case CONTEXT_CONSUMER_SYMBOL_STRING:
509
+ return ElementTypeContext;
510
+ case STRICT_MODE_NUMBER:
511
+ case STRICT_MODE_SYMBOL_STRING:
512
+ return ElementTypeOtherOrUnknown;
513
+ case SUSPENSE_NUMBER:
514
+ case SUSPENSE_SYMBOL_STRING:
515
+ case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
516
+ return ElementTypeSuspense;
517
+ case PROFILER_NUMBER:
518
+ case PROFILER_SYMBOL_STRING:
519
+ return ElementTypeProfiler;
520
+ default:
521
+ return ElementTypeOtherOrUnknown;
522
+ }
523
+ }
524
}
525
526
// This is a slightly annoying indirection.
@@ -787,7 +744,9 @@ export function attach(
744
pushOperation(isProfilingSupported ? 1 : 0);
745
pushOperation(hasOwnerMetadata ? 1 : 0);
746
} else {
790
- const { displayName, key, type } = getDataForFiber(fiber);
747
+ const { key } = fiber;
748
+ const displayName = getDisplayNameForFiber(fiber);
749
+ const type = getTypeForFiber(fiber);
750
const { _debugOwner } = fiber;
751
752
const ownerID =
@@ -1647,7 +1606,7 @@ export function attach(
1606
) {
1607
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
1608
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
1650
- // NOTE Keep in sync with getDataForFiber()
1609
+ // NOTE Keep in sync with get*ForFiber methods
1610
const consumerResolvedContext = type._context || type;
1611
1612
// Global context value.
@@ -1664,7 +1623,7 @@ export function attach(
1623
) {
1624
// 16.3.0 exposed the context object as "context"
1625
// PR #12501 changed it to "_context" for 16.3.1+
1667
- // NOTE Keep in sync with getDataForFiber()
1626
+ // NOTE Keep in sync with get*ForFiber methods
1627
const providerResolvedContext =
1628
currentType._context || currentType.context;
1629
if (providerResolvedContext === consumerResolvedContext) {
@@ -1689,7 +1648,7 @@ export function attach(
1648
let owner = _debugOwner;
1649
while (owner !== null) {
1650
owners.push({
1692
- displayName: getDataForFiber(owner).displayName || 'Unknown',
1651
+ displayName: getDisplayNameForFiber(owner) || 'Unknown',
1652
id: getFiberID(getPrimaryFiber(owner)),
1653
});
1654
owner = owner._debugOwner;
@@ -1719,7 +1678,7 @@ export function attach(
1678
// Can view component source location.
1679
canViewSource,
1680
1722
- displayName: getDataForFiber(fiber).displayName,
1681
+ displayName: getDisplayNameForFiber(fiber),
1682
1683
// Inspectable properties.
1684
// TODO Review sanitization approach for the below inspectable values.
@@ -2183,7 +2142,7 @@ export function attach(
2142
if (child === null) {
2143
break;
2144
}
2186
- const displayName = getDataForFiber(child).displayName;
2145
+ const displayName = getDisplayNameForFiber(child);
2146
if (displayName !== null) {
2147
// Prefer display names that we get from user-defined components.
2148
// We want to avoid using e.g. 'Suspense' unless we find nothing else.
@@ -2226,7 +2185,8 @@ export function attach(
2185
}
2186
2187
function getPathFrame(fiber: Fiber): PathFrame {
2229
- let { displayName, key } = getDataForFiber(fiber);
2188
+ const { key } = fiber;
2189
+ let displayName = getDisplayNameForFiber(fiber);
2190
const index = fiber.index;
2191
switch (fiber.tag) {
2192
case HostRoot:
src/devtools/store.js
+3
-2
@@ -251,10 +251,11 @@ export default class Store extends EventEmitter {
251
252
this._filterPreferences = value;
253
254
+ // Update persisted filter preferences stored in localStorage.
255
saveFilterPreferences(value);
256
256
- // TODO (filter) Dump all nodes, update renderer preferences, and re-initialize tree.
257
- // TODO (filter) Flushing every time a filter setting is changed is too expensive. We probably need an explitit configm
257
+ // Notify the renderer that filter prefernces have changed.
258
+ // This is an expensive opreation; it unmounts and remounts the entire tree.
259
this._bridge.send('updateFilterPreferences', value);
260
261
this.emit('filterPreferences');
src/utils.js
+36
-14
@@ -89,35 +89,57 @@ export function getDefaultFilterPreferences(): FilterPreferences {
89
};
90
}
91
92
+function getSavedFilterPreferencesFilter(key, value) {
93
+ if (typeof value === 'string' && value.indexOf('__REGEXP__') === 0) {
94
+ const match = value.substr(9).match(/\/(.*)\/(.*)?/);
95
+ return new RegExp(match[1], match[2] || '');
96
+ }
97
+ return value;
98
+}
99
+
100
export function getSavedFilterPreferences(): FilterPreferences {
101
const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
102
if (raw != null) {
95
- const json = JSON.parse(raw);
103
+ const json = JSON.parse(raw, getSavedFilterPreferencesFilter);
104
return {
105
hideElementsWithTypes: new Set(json.hideElementsWithTypes),
98
- hideElementsWithDisplayNames: new Set(json.hideElementsWithDisplayNames),
99
- hideElementsWithPaths: new Set(json.hideElementsWithPaths),
106
+ hideElementsWithDisplayNames: new Set(
107
+ json.hideElementsWithDisplayNames.map(source => new RegExp(source))
108
+ ),
109
+ hideElementsWithPaths: new Set(
110
+ json.hideElementsWithPaths.map(source => new RegExp(source))
111
+ ),
112
};
113
} else {
114
return getDefaultFilterPreferences();
115
}
116
}
117
118
+function saveFilterPreferencesFilter(key, value) {
119
+ if (value instanceof RegExp) {
120
+ return '__REGEXP__' + value.toString();
121
+ }
122
+ return value;
123
+}
124
+
125
export function saveFilterPreferences(
126
filterPreferences: FilterPreferences
127
): void {
128
localStorage.setItem(
129
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
111
- JSON.stringify({
112
- hideElementsWithTypes: Array.from(
113
- filterPreferences.hideElementsWithTypes
114
- ),
115
- hideElementsWithDisplayNames: Array.from(
116
- filterPreferences.hideElementsWithDisplayNames
117
- ),
118
- hideElementsWithPaths: Array.from(
119
- filterPreferences.hideElementsWithPaths
120
- ),
121
- })
130
+ JSON.stringify(
131
+ {
132
+ hideElementsWithTypes: Array.from(
133
+ filterPreferences.hideElementsWithTypes
134
+ ),
135
+ hideElementsWithDisplayNames: Array.from(
136
+ filterPreferences.hideElementsWithDisplayNames
137
+ ),
138
+ hideElementsWithPaths: Array.from(
139
+ filterPreferences.hideElementsWithPaths
140
+ ),
141
+ },
142
+ saveFilterPreferencesFilter
143
+ )
144
);
145
}