Add support for hide-by-name and hide-by-path component filter regexps

This commit is contained in:
Brian Vaughn
2019-05-01 10:45:18 -07:00
parent a2ba90bf96
commit 0b4bfbc98f
3 changed files with 169 additions and 186 deletions
+130 -170
View File
@@ -37,7 +37,6 @@ import type {
DevToolsHook,
Fiber,
FiberCommitsBackend,
FiberData,
InteractionBackend,
InteractionsBackend,
InteractionWithCommitsBackend,
@@ -48,7 +47,7 @@ import type {
RendererInterface,
} from './types';
import type { InspectedElement } from 'src/devtools/views/Components/types';
import type { FilterPreferences } from 'src/types';
import type { ElementType, FilterPreferences } from 'src/types';
function getInternalReactConstants(version) {
const ReactSymbols = {
@@ -245,18 +244,15 @@ export function attach(
const debug = (name: string, fiber: Fiber, parentFiber: ?Fiber): void => {
if (__DEBUG__) {
const fiberData = getDataForFiber(fiber);
const fiberDisplayName = (fiberData && fiberData.displayName) || 'null';
const parentFiberData =
parentFiber == null ? null : getDataForFiber(parentFiber);
const parentFiberDisplayName =
(parentFiberData && parentFiberData.displayName) || 'null';
const displayName = getDisplayNameForFiber(fiber) || 'null';
const parentDisplayName =
(parentFiber !== null && getDisplayNameForFiber(parentFiber)) || 'null';
// NOTE: calling getFiberID or getPrimaryFiber is unsafe here
// because it will put them in the map. For now, we'll omit them.
// TODO: better debugging story for this.
console.log(
`[renderer] %c${name} %c${fiberDisplayName} %c${
parentFiber ? parentFiberDisplayName : ''
`[renderer] %c${name} %c${displayName} %c${
parentFiber ? parentDisplayName : ''
}`,
'color: red; font-weight: bold;',
'color: blue;',
@@ -271,7 +267,10 @@ export function attach(
hideElementsWithPaths,
} = getSavedFilterPreferences();
// TODO (filter) We could make this more efficient.
// TODO (filter) Should we make this operation more efficient?
// For example, we could add a new recursive unmount tree operation.
// The unmount operations are already significantly smaller than mount opreations though.
// This is something to keep in mind for later.
function updateFilterPreferences(filterPreferences: FilterPreferences) {
if (this._isProfiling) {
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
@@ -302,32 +301,11 @@ export function attach(
});
}
// NOTICE Keep in sync with getDataForFiber()
// NOTICE Keep in sync with get*ForFiber methods
function shouldFilterFiber(fiber: Fiber): boolean {
const { tag } = fiber;
// TODO (filter) This does not yet support display name or path based filtering.
const { _debugSource, tag, type } = fiber;
switch (tag) {
case ClassComponent:
case IncompleteClassComponent:
return hideElementsWithTypes.has(ElementTypeClass);
case FunctionComponent:
return hideElementsWithTypes.has(ElementTypeFunction);
case IndeterminateComponent:
return (
hideElementsWithTypes.has(ElementTypeClass) ||
hideElementsWithTypes.has(ElementTypeFunction)
);
case ForwardRef:
return hideElementsWithTypes.has(ElementTypeForwardRef);
case MemoComponent:
case SimpleMemoComponent:
return hideElementsWithTypes.has(ElementTypeMemo);
case HostComponent:
return hideElementsWithTypes.has(ElementTypeHostComponent);
case HostRoot:
return false; // We never support filtering roots
case DehydratedSuspenseComponent:
// TODO: ideally we would show dehydrated Suspense immediately.
// However, it has some special behavior (like disconnecting
@@ -341,7 +319,7 @@ export function attach(
case Fragment:
return true;
default:
const typeSymbol = getTypeSymbol(fiber.type);
const typeSymbol = getTypeSymbol(type);
switch (typeSymbol) {
case CONCURRENT_MODE_NUMBER:
@@ -350,22 +328,35 @@ export function attach(
case STRICT_MODE_NUMBER:
case STRICT_MODE_SYMBOL_STRING:
return true;
case CONTEXT_PROVIDER_NUMBER:
case CONTEXT_PROVIDER_SYMBOL_STRING:
case CONTEXT_CONSUMER_NUMBER:
case CONTEXT_CONSUMER_SYMBOL_STRING:
return hideElementsWithTypes.has(ElementTypeContext);
case SUSPENSE_NUMBER:
case SUSPENSE_SYMBOL_STRING:
case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
return hideElementsWithTypes.has(ElementTypeSuspense);
case PROFILER_NUMBER:
case PROFILER_SYMBOL_STRING:
return hideElementsWithTypes.has(ElementTypeProfiler);
default:
return false;
break;
}
}
const elementType = getTypeForFiber(fiber);
if (hideElementsWithTypes.has(elementType)) {
return true;
}
if (hideElementsWithDisplayNames.size > 0) {
const displayName = getDisplayNameForFiber(fiber) || '';
for (let displayNameRegExp of hideElementsWithDisplayNames) {
if (displayNameRegExp.test(displayName)) {
return true;
}
}
}
if (_debugSource !== null && hideElementsWithPaths.size > 0) {
const { fileName } = _debugSource;
for (let pathRegExp of hideElementsWithPaths) {
if (pathRegExp.test(fileName)) {
return true;
}
}
}
return false;
}
function getTypeSymbol(type: any): Symbol | number {
@@ -377,9 +368,9 @@ export function attach(
: symbolOrNumber;
}
// NOTICE Keep in sync with shouldFilterFiber()
function getDataForFiber(fiber: Fiber): FiberData {
const { elementType, type, key, tag } = fiber;
// NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
function getDisplayNameForFiber(fiber: Fiber): string | null {
const { elementType, type, tag } = fiber;
// This is to support lazy components with a Promise as the type.
// see https://github.com/facebook/react/pull/13397
@@ -390,96 +381,47 @@ export function attach(
}
}
let fiberData: FiberData = ((null: any): FiberData);
let displayName: string = ((null: any): string);
let resolvedContext: any = null;
switch (tag) {
case ClassComponent:
case IncompleteClassComponent:
fiberData = {
displayName: getDisplayName(resolvedType),
key,
type: ElementTypeClass,
};
break;
return getDisplayName(resolvedType);
case FunctionComponent:
case IndeterminateComponent:
fiberData = {
displayName: getDisplayName(resolvedType),
key,
type: ElementTypeFunction,
};
break;
return getDisplayName(resolvedType);
case EventComponent:
fiberData = {
displayName: null,
key,
type: ElementTypeEventComponent,
};
break;
return null;
case EventTarget:
switch (getTypeSymbol(elementType.type)) {
case EVENT_TARGET_TOUCH_HIT_NUMBER:
case EVENT_TARGET_TOUCH_HIT_STRING:
displayName = 'TouchHitTarget';
break;
return 'TouchHitTarget';
default:
displayName = 'EventTarget';
break;
return 'EventTarget';
}
fiberData = {
displayName,
key,
type: ElementTypeEventTarget,
};
break;
case ForwardRef:
const functionName = getDisplayName(resolvedType.render, '');
displayName =
return (
resolvedType.displayName ||
(functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef');
fiberData = {
displayName,
key,
type: ElementTypeForwardRef,
};
break;
(functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef')
);
case HostRoot:
return {
displayName: null,
key: null,
type: ElementTypeRoot,
};
return null;
case HostComponent:
return {
displayName: type,
key,
type: ElementTypeHostComponent,
};
return type;
case HostPortal:
case HostText:
case Fragment:
return {
displayName: null,
key,
type: ElementTypeOtherOrUnknown,
};
return null;
case MemoComponent:
case SimpleMemoComponent:
if (elementType.displayName) {
displayName = elementType.displayName;
return elementType.displayName;
} else {
displayName = type.displayName || type.name;
displayName = displayName ? `Memo(${displayName})` : 'Memo';
const displayName = type.displayName || type.name;
return displayName ? `Memo(${displayName})` : 'Memo';
}
fiberData = {
displayName,
key,
type: ElementTypeMemo,
};
break;
default:
const typeSymbol = getTypeSymbol(type);
@@ -487,26 +429,14 @@ export function attach(
case CONCURRENT_MODE_NUMBER:
case CONCURRENT_MODE_SYMBOL_STRING:
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
return {
displayName: null,
key: null,
type: ElementTypeOtherOrUnknown,
};
return null;
case CONTEXT_PROVIDER_NUMBER:
case CONTEXT_PROVIDER_SYMBOL_STRING:
// 16.3.0 exposed the context object as "context"
// PR #12501 changed it to "_context" for 16.3.1+
// NOTE Keep in sync with inspectElement()
resolvedContext = fiber.type._context || fiber.type.context;
displayName = `${resolvedContext.displayName ||
'Context'}.Provider`;
fiberData = {
displayName,
key,
type: ElementTypeContext,
};
break;
return `${resolvedContext.displayName || 'Context'}.Provider`;
case CONTEXT_CONSUMER_NUMBER:
case CONTEXT_CONSUMER_SYMBOL_STRING:
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
@@ -516,54 +446,81 @@ export function attach(
// NOTE: TraceUpdatesBackendManager depends on the name ending in '.Consumer'
// If you change the name, figure out a more resilient way to detect it.
displayName = `${resolvedContext.displayName ||
'Context'}.Consumer`;
fiberData = {
displayName,
key,
type: ElementTypeContext,
};
break;
return `${resolvedContext.displayName || 'Context'}.Consumer`;
case STRICT_MODE_NUMBER:
case STRICT_MODE_SYMBOL_STRING:
fiberData = {
displayName: null,
key,
type: ElementTypeOtherOrUnknown,
};
break;
return null;
case SUSPENSE_NUMBER:
case SUSPENSE_SYMBOL_STRING:
case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
fiberData = {
displayName: 'Suspense',
key,
type: ElementTypeSuspense,
};
break;
return 'Suspense';
case PROFILER_NUMBER:
case PROFILER_SYMBOL_STRING:
fiberData = {
displayName: `Profiler(${fiber.memoizedProps.id})`,
key,
type: ElementTypeProfiler,
};
break;
return `Profiler(${fiber.memoizedProps.id})`;
default:
// Unknown element type.
// This may mean a new element type that has not yet been added to DevTools.
fiberData = {
displayName: null,
key,
type: ElementTypeOtherOrUnknown,
};
break;
return null;
}
break;
}
}
return fiberData;
// NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
function getTypeForFiber(fiber: Fiber): ElementType {
const { type, tag } = fiber;
switch (tag) {
case ClassComponent:
case IncompleteClassComponent:
return ElementTypeClass;
case FunctionComponent:
case IndeterminateComponent:
return ElementTypeFunction;
case EventComponent:
return ElementTypeEventComponent;
case EventTarget:
return ElementTypeEventTarget;
case ForwardRef:
return ElementTypeForwardRef;
case HostRoot:
return ElementTypeRoot;
case HostComponent:
return ElementTypeHostComponent;
case HostPortal:
case HostText:
case Fragment:
return ElementTypeOtherOrUnknown;
case MemoComponent:
case SimpleMemoComponent:
return ElementTypeMemo;
default:
const typeSymbol = getTypeSymbol(type);
switch (typeSymbol) {
case CONCURRENT_MODE_NUMBER:
case CONCURRENT_MODE_SYMBOL_STRING:
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
return ElementTypeOtherOrUnknown;
case CONTEXT_PROVIDER_NUMBER:
case CONTEXT_PROVIDER_SYMBOL_STRING:
return ElementTypeContext;
case CONTEXT_CONSUMER_NUMBER:
case CONTEXT_CONSUMER_SYMBOL_STRING:
return ElementTypeContext;
case STRICT_MODE_NUMBER:
case STRICT_MODE_SYMBOL_STRING:
return ElementTypeOtherOrUnknown;
case SUSPENSE_NUMBER:
case SUSPENSE_SYMBOL_STRING:
case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
return ElementTypeSuspense;
case PROFILER_NUMBER:
case PROFILER_SYMBOL_STRING:
return ElementTypeProfiler;
default:
return ElementTypeOtherOrUnknown;
}
}
}
// This is a slightly annoying indirection.
@@ -787,7 +744,9 @@ export function attach(
pushOperation(isProfilingSupported ? 1 : 0);
pushOperation(hasOwnerMetadata ? 1 : 0);
} else {
const { displayName, key, type } = getDataForFiber(fiber);
const { key } = fiber;
const displayName = getDisplayNameForFiber(fiber);
const type = getTypeForFiber(fiber);
const { _debugOwner } = fiber;
const ownerID =
@@ -1647,7 +1606,7 @@ export function attach(
) {
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
// NOTE Keep in sync with getDataForFiber()
// NOTE Keep in sync with get*ForFiber methods
const consumerResolvedContext = type._context || type;
// Global context value.
@@ -1664,7 +1623,7 @@ export function attach(
) {
// 16.3.0 exposed the context object as "context"
// PR #12501 changed it to "_context" for 16.3.1+
// NOTE Keep in sync with getDataForFiber()
// NOTE Keep in sync with get*ForFiber methods
const providerResolvedContext =
currentType._context || currentType.context;
if (providerResolvedContext === consumerResolvedContext) {
@@ -1689,7 +1648,7 @@ export function attach(
let owner = _debugOwner;
while (owner !== null) {
owners.push({
displayName: getDataForFiber(owner).displayName || 'Unknown',
displayName: getDisplayNameForFiber(owner) || 'Unknown',
id: getFiberID(getPrimaryFiber(owner)),
});
owner = owner._debugOwner;
@@ -1719,7 +1678,7 @@ export function attach(
// Can view component source location.
canViewSource,
displayName: getDataForFiber(fiber).displayName,
displayName: getDisplayNameForFiber(fiber),
// Inspectable properties.
// TODO Review sanitization approach for the below inspectable values.
@@ -2183,7 +2142,7 @@ export function attach(
if (child === null) {
break;
}
const displayName = getDataForFiber(child).displayName;
const displayName = getDisplayNameForFiber(child);
if (displayName !== null) {
// Prefer display names that we get from user-defined components.
// We want to avoid using e.g. 'Suspense' unless we find nothing else.
@@ -2226,7 +2185,8 @@ export function attach(
}
function getPathFrame(fiber: Fiber): PathFrame {
let { displayName, key } = getDataForFiber(fiber);
const { key } = fiber;
let displayName = getDisplayNameForFiber(fiber);
const index = fiber.index;
switch (fiber.tag) {
case HostRoot:
+3 -2
View File
@@ -251,10 +251,11 @@ export default class Store extends EventEmitter {
this._filterPreferences = value;
// Update persisted filter preferences stored in localStorage.
saveFilterPreferences(value);
// TODO (filter) Dump all nodes, update renderer preferences, and re-initialize tree.
// TODO (filter) Flushing every time a filter setting is changed is too expensive. We probably need an explitit configm
// Notify the renderer that filter prefernces have changed.
// This is an expensive opreation; it unmounts and remounts the entire tree.
this._bridge.send('updateFilterPreferences', value);
this.emit('filterPreferences');
+36 -14
View File
@@ -89,35 +89,57 @@ export function getDefaultFilterPreferences(): FilterPreferences {
};
}
function getSavedFilterPreferencesFilter(key, value) {
if (typeof value === 'string' && value.indexOf('__REGEXP__') === 0) {
const match = value.substr(9).match(/\/(.*)\/(.*)?/);
return new RegExp(match[1], match[2] || '');
}
return value;
}
export function getSavedFilterPreferences(): FilterPreferences {
const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
if (raw != null) {
const json = JSON.parse(raw);
const json = JSON.parse(raw, getSavedFilterPreferencesFilter);
return {
hideElementsWithTypes: new Set(json.hideElementsWithTypes),
hideElementsWithDisplayNames: new Set(json.hideElementsWithDisplayNames),
hideElementsWithPaths: new Set(json.hideElementsWithPaths),
hideElementsWithDisplayNames: new Set(
json.hideElementsWithDisplayNames.map(source => new RegExp(source))
),
hideElementsWithPaths: new Set(
json.hideElementsWithPaths.map(source => new RegExp(source))
),
};
} else {
return getDefaultFilterPreferences();
}
}
function saveFilterPreferencesFilter(key, value) {
if (value instanceof RegExp) {
return '__REGEXP__' + value.toString();
}
return value;
}
export function saveFilterPreferences(
filterPreferences: FilterPreferences
): void {
localStorage.setItem(
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
JSON.stringify({
hideElementsWithTypes: Array.from(
filterPreferences.hideElementsWithTypes
),
hideElementsWithDisplayNames: Array.from(
filterPreferences.hideElementsWithDisplayNames
),
hideElementsWithPaths: Array.from(
filterPreferences.hideElementsWithPaths
),
})
JSON.stringify(
{
hideElementsWithTypes: Array.from(
filterPreferences.hideElementsWithTypes
),
hideElementsWithDisplayNames: Array.from(
filterPreferences.hideElementsWithDisplayNames
),
hideElementsWithPaths: Array.from(
filterPreferences.hideElementsWithPaths
),
},
saveFilterPreferencesFilter
)
);
}