@samitouri / QOS-React-2 / commits / 0b4bfbc98f

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 }