Compare commits
4 Commits
eslint-plu
...
gh/mofeiZ/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2c0670eb2 | ||
|
|
02732c11c6 | ||
|
|
9af74e024c | ||
|
|
b658948159 |
@@ -284,6 +284,19 @@ export function createHOCFilter(isEnabled: boolean = true) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createEnvironmentNameFilter(
|
||||
env: string,
|
||||
isEnabled: boolean = true,
|
||||
) {
|
||||
const Types = require('react-devtools-shared/src/frontend/types');
|
||||
return {
|
||||
type: Types.ComponentFilterEnvironmentName,
|
||||
isEnabled,
|
||||
isValid: true,
|
||||
value: env,
|
||||
};
|
||||
}
|
||||
|
||||
export function createElementTypeFilter(
|
||||
elementType: ElementType,
|
||||
isEnabled: boolean = true,
|
||||
|
||||
188
packages/react-devtools-shared/src/backend/agent.js
vendored
188
packages/react-devtools-shared/src/backend/agent.js
vendored
@@ -220,6 +220,7 @@ export default class Agent extends EventEmitter<{
|
||||
this.updateConsolePatchSettings,
|
||||
);
|
||||
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
|
||||
bridge.addListener('getEnvironmentNames', this.getEnvironmentNames);
|
||||
|
||||
// Temporarily support older standalone front-ends sending commands to newer embedded backends.
|
||||
// We do this because React Native embeds the React DevTools backend,
|
||||
@@ -341,84 +342,123 @@ export default class Agent extends EventEmitter<{
|
||||
}
|
||||
|
||||
getIDForHostInstance(target: HostInstance): number | null {
|
||||
let bestMatch: null | HostInstance = null;
|
||||
let bestRenderer: null | RendererInterface = null;
|
||||
// Find the nearest ancestor which is mounted by a React.
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
const nearestNode: null = renderer.getNearestMountedHostInstance(target);
|
||||
if (nearestNode !== null) {
|
||||
if (nearestNode === target) {
|
||||
// Exact match we can exit early.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
bestMatch === null ||
|
||||
(!isReactNativeEnvironment() && bestMatch.contains(nearestNode))
|
||||
) {
|
||||
// If this is the first match or the previous match contains the new match,
|
||||
// so the new match is a deeper and therefore better match.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
if (isReactNativeEnvironment() || typeof target.nodeType !== 'number') {
|
||||
// In React Native or non-DOM we simply pick any renderer that has a match.
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
try {
|
||||
const match = renderer.getElementIDForHostInstance(target);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
} catch (error) {
|
||||
// Some old React versions might throw if they can't find a match.
|
||||
// If so we should ignore it...
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestRenderer != null && bestMatch != null) {
|
||||
try {
|
||||
return bestRenderer.getElementIDForHostInstance(bestMatch, true);
|
||||
} catch (error) {
|
||||
// Some old React versions might throw if they can't find a match.
|
||||
// If so we should ignore it...
|
||||
return null;
|
||||
} else {
|
||||
// In the DOM we use a smarter mechanism to find the deepest a DOM node
|
||||
// that is registered if there isn't an exact match.
|
||||
let bestMatch: null | Element = null;
|
||||
let bestRenderer: null | RendererInterface = null;
|
||||
// Find the nearest ancestor which is mounted by a React.
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
const nearestNode: null | Element = renderer.getNearestMountedDOMNode(
|
||||
(target: any),
|
||||
);
|
||||
if (nearestNode !== null) {
|
||||
if (nearestNode === target) {
|
||||
// Exact match we can exit early.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
break;
|
||||
}
|
||||
if (bestMatch === null || bestMatch.contains(nearestNode)) {
|
||||
// If this is the first match or the previous match contains the new match,
|
||||
// so the new match is a deeper and therefore better match.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestRenderer != null && bestMatch != null) {
|
||||
try {
|
||||
return bestRenderer.getElementIDForHostInstance(bestMatch);
|
||||
} catch (error) {
|
||||
// Some old React versions might throw if they can't find a match.
|
||||
// If so we should ignore it...
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getComponentNameForHostInstance(target: HostInstance): string | null {
|
||||
// We duplicate this code from getIDForHostInstance to avoid an object allocation.
|
||||
let bestMatch: null | HostInstance = null;
|
||||
let bestRenderer: null | RendererInterface = null;
|
||||
// Find the nearest ancestor which is mounted by a React.
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
const nearestNode = renderer.getNearestMountedHostInstance(target);
|
||||
if (nearestNode !== null) {
|
||||
if (nearestNode === target) {
|
||||
// Exact match we can exit early.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
bestMatch === null ||
|
||||
(!isReactNativeEnvironment() && bestMatch.contains(nearestNode))
|
||||
) {
|
||||
// If this is the first match or the previous match contains the new match,
|
||||
// so the new match is a deeper and therefore better match.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
if (isReactNativeEnvironment() || typeof target.nodeType !== 'number') {
|
||||
// In React Native or non-DOM we simply pick any renderer that has a match.
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
try {
|
||||
const id = renderer.getElementIDForHostInstance(target);
|
||||
if (id) {
|
||||
return renderer.getDisplayNameForElementID(id);
|
||||
}
|
||||
} catch (error) {
|
||||
// Some old React versions might throw if they can't find a match.
|
||||
// If so we should ignore it...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestRenderer != null && bestMatch != null) {
|
||||
try {
|
||||
const id = bestRenderer.getElementIDForHostInstance(bestMatch, true);
|
||||
if (id) {
|
||||
return bestRenderer.getDisplayNameForElementID(id);
|
||||
return null;
|
||||
} else {
|
||||
// In the DOM we use a smarter mechanism to find the deepest a DOM node
|
||||
// that is registered if there isn't an exact match.
|
||||
let bestMatch: null | Element = null;
|
||||
let bestRenderer: null | RendererInterface = null;
|
||||
// Find the nearest ancestor which is mounted by a React.
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
const nearestNode: null | Element = renderer.getNearestMountedDOMNode(
|
||||
(target: any),
|
||||
);
|
||||
if (nearestNode !== null) {
|
||||
if (nearestNode === target) {
|
||||
// Exact match we can exit early.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
break;
|
||||
}
|
||||
if (bestMatch === null || bestMatch.contains(nearestNode)) {
|
||||
// If this is the first match or the previous match contains the new match,
|
||||
// so the new match is a deeper and therefore better match.
|
||||
bestMatch = nearestNode;
|
||||
bestRenderer = renderer;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Some old React versions might throw if they can't find a match.
|
||||
// If so we should ignore it...
|
||||
}
|
||||
if (bestRenderer != null && bestMatch != null) {
|
||||
try {
|
||||
const id = bestRenderer.getElementIDForHostInstance(bestMatch);
|
||||
if (id) {
|
||||
return bestRenderer.getDisplayNameForElementID(id);
|
||||
}
|
||||
} catch (error) {
|
||||
// Some old React versions might throw if they can't find a match.
|
||||
// If so we should ignore it...
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getBackendVersion: () => void = () => {
|
||||
@@ -814,6 +854,24 @@ export default class Agent extends EventEmitter<{
|
||||
}
|
||||
};
|
||||
|
||||
getEnvironmentNames: () => void = () => {
|
||||
let accumulatedNames = null;
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = this._rendererInterfaces[+rendererID];
|
||||
const names = renderer.getEnvironmentNames();
|
||||
if (accumulatedNames === null) {
|
||||
accumulatedNames = names;
|
||||
} else {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
if (accumulatedNames.indexOf(names[i]) === -1) {
|
||||
accumulatedNames.push(names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._bridge.send('environmentNames', accumulatedNames || []);
|
||||
};
|
||||
|
||||
onTraceUpdates: (nodes: Set<HostInstance>) => void = nodes => {
|
||||
this.emit('traceUpdates', nodes);
|
||||
};
|
||||
|
||||
@@ -135,17 +135,7 @@ export function registerRenderer(
|
||||
renderer: ReactRenderer,
|
||||
onErrorOrWarning?: OnErrorOrWarning,
|
||||
): void {
|
||||
const {
|
||||
currentDispatcherRef,
|
||||
getCurrentFiber,
|
||||
findFiberByHostInstance,
|
||||
version,
|
||||
} = renderer;
|
||||
|
||||
// Ignore React v15 and older because they don't expose a component stack anyway.
|
||||
if (typeof findFiberByHostInstance !== 'function') {
|
||||
return;
|
||||
}
|
||||
const {currentDispatcherRef, getCurrentFiber, version} = renderer;
|
||||
|
||||
// currentDispatcherRef gets injected for v16.8+ to support hooks inspection.
|
||||
// getCurrentFiber gets injected for v16.9+.
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ComponentFilterElementType,
|
||||
ComponentFilterHOC,
|
||||
ComponentFilterLocation,
|
||||
ComponentFilterEnvironmentName,
|
||||
ElementTypeClass,
|
||||
ElementTypeContext,
|
||||
ElementTypeFunction,
|
||||
@@ -721,6 +722,11 @@ export function getInternalReactConstants(version: string): {
|
||||
};
|
||||
}
|
||||
|
||||
// All environment names we've seen so far. This lets us create a list of filters to apply.
|
||||
// This should ideally include env of filtered Components too so that you can add those as
|
||||
// filters at the same time as removing some other filter.
|
||||
const knownEnvironmentNames: Set<string> = new Set();
|
||||
|
||||
// Map of one or more Fibers in a pair to their unique id number.
|
||||
// We track both Fibers to support Fast Refresh,
|
||||
// which may forcefully replace one of the pair as part of hot reloading.
|
||||
@@ -732,35 +738,93 @@ const fiberToFiberInstanceMap: Map<Fiber, FiberInstance> = new Map();
|
||||
// operations that should be the same whether the current and work-in-progress Fiber is used.
|
||||
const idToDevToolsInstanceMap: Map<number, DevToolsInstance> = new Map();
|
||||
|
||||
// Map of resource DOM nodes to all the Fibers that depend on it.
|
||||
const hostResourceToFiberMap: Map<HostInstance, Set<Fiber>> = new Map();
|
||||
// Map of canonical HostInstances to the nearest parent DevToolsInstance.
|
||||
const publicInstanceToDevToolsInstanceMap: Map<HostInstance, DevToolsInstance> =
|
||||
new Map();
|
||||
// Map of resource DOM nodes to all the nearest DevToolsInstances that depend on it.
|
||||
const hostResourceToDevToolsInstanceMap: Map<
|
||||
HostInstance,
|
||||
Set<DevToolsInstance>,
|
||||
> = new Map();
|
||||
|
||||
function getPublicInstance(instance: HostInstance): HostInstance {
|
||||
// Typically the PublicInstance and HostInstance is the same thing but not in Fabric.
|
||||
// So we need to detect this and use that as the public instance.
|
||||
return typeof instance === 'object' &&
|
||||
instance !== null &&
|
||||
typeof instance.canonical === 'object'
|
||||
? (instance.canonical: any)
|
||||
: typeof instance._nativeTag === 'number'
|
||||
? instance._nativeTag
|
||||
: instance;
|
||||
}
|
||||
|
||||
function aquireHostInstance(
|
||||
nearestInstance: DevToolsInstance,
|
||||
hostInstance: HostInstance,
|
||||
): void {
|
||||
const publicInstance = getPublicInstance(hostInstance);
|
||||
publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);
|
||||
}
|
||||
|
||||
function releaseHostInstance(
|
||||
nearestInstance: DevToolsInstance,
|
||||
hostInstance: HostInstance,
|
||||
): void {
|
||||
const publicInstance = getPublicInstance(hostInstance);
|
||||
if (
|
||||
publicInstanceToDevToolsInstanceMap.get(publicInstance) === nearestInstance
|
||||
) {
|
||||
publicInstanceToDevToolsInstanceMap.delete(publicInstance);
|
||||
}
|
||||
}
|
||||
|
||||
function aquireHostResource(
|
||||
fiber: Fiber,
|
||||
nearestInstance: DevToolsInstance,
|
||||
resource: ?{instance?: HostInstance},
|
||||
): void {
|
||||
const hostInstance = resource && resource.instance;
|
||||
if (hostInstance) {
|
||||
let resourceFibers = hostResourceToFiberMap.get(hostInstance);
|
||||
if (resourceFibers === undefined) {
|
||||
resourceFibers = new Set();
|
||||
hostResourceToFiberMap.set(hostInstance, resourceFibers);
|
||||
const publicInstance = getPublicInstance(hostInstance);
|
||||
let resourceInstances =
|
||||
hostResourceToDevToolsInstanceMap.get(publicInstance);
|
||||
if (resourceInstances === undefined) {
|
||||
resourceInstances = new Set();
|
||||
hostResourceToDevToolsInstanceMap.set(publicInstance, resourceInstances);
|
||||
// Store the first match in the main map for quick access when selecting DOM node.
|
||||
publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);
|
||||
}
|
||||
resourceFibers.add(fiber);
|
||||
resourceInstances.add(nearestInstance);
|
||||
}
|
||||
}
|
||||
|
||||
function releaseHostResource(
|
||||
fiber: Fiber,
|
||||
nearestInstance: DevToolsInstance,
|
||||
resource: ?{instance?: HostInstance},
|
||||
): void {
|
||||
const hostInstance = resource && resource.instance;
|
||||
if (hostInstance) {
|
||||
const resourceFibers = hostResourceToFiberMap.get(hostInstance);
|
||||
if (resourceFibers !== undefined) {
|
||||
resourceFibers.delete(fiber);
|
||||
if (resourceFibers.size === 0) {
|
||||
hostResourceToFiberMap.delete(hostInstance);
|
||||
const publicInstance = getPublicInstance(hostInstance);
|
||||
const resourceInstances =
|
||||
hostResourceToDevToolsInstanceMap.get(publicInstance);
|
||||
if (resourceInstances !== undefined) {
|
||||
resourceInstances.delete(nearestInstance);
|
||||
if (resourceInstances.size === 0) {
|
||||
hostResourceToDevToolsInstanceMap.delete(publicInstance);
|
||||
publicInstanceToDevToolsInstanceMap.delete(publicInstance);
|
||||
} else if (
|
||||
publicInstanceToDevToolsInstanceMap.get(publicInstance) ===
|
||||
nearestInstance
|
||||
) {
|
||||
// This was the first one. Store the next first one in the main map for easy access.
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const firstInstance of resourceInstances) {
|
||||
publicInstanceToDevToolsInstanceMap.set(
|
||||
firstInstance,
|
||||
nearestInstance,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1099,6 +1163,7 @@ export function attach(
|
||||
const hideElementsWithDisplayNames: Set<RegExp> = new Set();
|
||||
const hideElementsWithPaths: Set<RegExp> = new Set();
|
||||
const hideElementsWithTypes: Set<ElementType> = new Set();
|
||||
const hideElementsWithEnvs: Set<string> = new Set();
|
||||
|
||||
// Highlight updates
|
||||
let traceUpdatesEnabled: boolean = false;
|
||||
@@ -1108,6 +1173,7 @@ export function attach(
|
||||
hideElementsWithTypes.clear();
|
||||
hideElementsWithDisplayNames.clear();
|
||||
hideElementsWithPaths.clear();
|
||||
hideElementsWithEnvs.clear();
|
||||
|
||||
componentFilters.forEach(componentFilter => {
|
||||
if (!componentFilter.isEnabled) {
|
||||
@@ -1133,6 +1199,9 @@ export function attach(
|
||||
case ComponentFilterHOC:
|
||||
hideElementsWithDisplayNames.add(new RegExp('\\('));
|
||||
break;
|
||||
case ComponentFilterEnvironmentName:
|
||||
hideElementsWithEnvs.add(componentFilter.value);
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
`Invalid component filter type "${componentFilter.type}"`,
|
||||
@@ -1215,7 +1284,14 @@ export function attach(
|
||||
flushPendingEvents();
|
||||
}
|
||||
|
||||
function shouldFilterVirtual(data: ReactComponentInfo): boolean {
|
||||
function getEnvironmentNames(): Array<string> {
|
||||
return Array.from(knownEnvironmentNames);
|
||||
}
|
||||
|
||||
function shouldFilterVirtual(
|
||||
data: ReactComponentInfo,
|
||||
secondaryEnv: null | string,
|
||||
): boolean {
|
||||
// For purposes of filtering Server Components are always Function Components.
|
||||
// Environment will be used to filter Server vs Client.
|
||||
// Technically they can be forwardRef and memo too but those filters will go away
|
||||
@@ -1236,6 +1312,14 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(data.env == null || hideElementsWithEnvs.has(data.env)) &&
|
||||
(secondaryEnv === null || hideElementsWithEnvs.has(secondaryEnv))
|
||||
) {
|
||||
// If a Component has two environments, you have to filter both for it not to appear.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1294,6 +1378,26 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
if (hideElementsWithEnvs.has('Client')) {
|
||||
// If we're filtering out the Client environment we should filter out all
|
||||
// "Client Components". Technically that also includes the built-ins but
|
||||
// since that doesn't actually include any additional code loading it's
|
||||
// useful to not filter out the built-ins. Those can be filtered separately.
|
||||
// There's no other way to filter out just Function components on the Client.
|
||||
// Therefore, this only filters Class and Function components.
|
||||
switch (tag) {
|
||||
case ClassComponent:
|
||||
case IncompleteClassComponent:
|
||||
case IncompleteFunctionComponent:
|
||||
case FunctionComponent:
|
||||
case IndeterminateComponent:
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* DISABLED: https://github.com/facebook/react/pull/28417
|
||||
if (hideElementsWithPaths.size > 0) {
|
||||
const source = getSourceForFiber(fiber);
|
||||
@@ -1421,50 +1525,29 @@ export function attach(
|
||||
|
||||
// Removes a Fiber (and its alternate) from the Maps used to track their id.
|
||||
// This method should always be called when a Fiber is unmounting.
|
||||
function untrackFiber(fiberInstance: FiberInstance) {
|
||||
function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) {
|
||||
if (__DEBUG__) {
|
||||
debug('untrackFiber()', fiberInstance.data, null);
|
||||
debug('untrackFiber()', fiber, null);
|
||||
}
|
||||
// TODO: Consider using a WeakMap instead. The only thing where that doesn't work
|
||||
// is React Native Paper which tracks tags but that support is eventually going away
|
||||
// and can use the old findFiberByHostInstance strategy.
|
||||
|
||||
if (fiber.tag === HostHoistable) {
|
||||
releaseHostResource(nearestInstance, fiber.memoizedState);
|
||||
} else if (
|
||||
fiber.tag === HostComponent ||
|
||||
fiber.tag === HostText ||
|
||||
fiber.tag === HostSingleton
|
||||
) {
|
||||
releaseHostInstance(nearestInstance, fiber.stateNode);
|
||||
}
|
||||
|
||||
idToDevToolsInstanceMap.delete(fiberInstance.id);
|
||||
|
||||
const fiber = fiberInstance.data;
|
||||
|
||||
// Restore any errors/warnings associated with this fiber to the pending
|
||||
// map. I.e. treat it as before we tracked the instances. This lets us
|
||||
// restore them if we remount the same Fibers later. Otherwise we rely
|
||||
// on the GC of the Fibers to clean them up.
|
||||
if (fiberInstance.errors !== null) {
|
||||
pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
|
||||
fiberInstance.errors = null;
|
||||
}
|
||||
if (fiberInstance.warnings !== null) {
|
||||
pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
|
||||
fiberInstance.warnings = null;
|
||||
}
|
||||
|
||||
if (fiberInstance.flags & FORCE_ERROR) {
|
||||
fiberInstance.flags &= ~FORCE_ERROR;
|
||||
forceErrorCount--;
|
||||
if (forceErrorCount === 0 && setErrorHandler != null) {
|
||||
setErrorHandler(shouldErrorFiberAlwaysNull);
|
||||
}
|
||||
}
|
||||
if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
|
||||
fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
|
||||
forceFallbackCount--;
|
||||
if (forceFallbackCount === 0 && setSuspenseHandler != null) {
|
||||
setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
|
||||
}
|
||||
}
|
||||
|
||||
if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
|
||||
fiberToFiberInstanceMap.delete(fiber);
|
||||
}
|
||||
const {alternate} = fiber;
|
||||
if (alternate !== null) {
|
||||
if (fiberToFiberInstanceMap.get(alternate) === fiberInstance) {
|
||||
fiberToFiberInstanceMap.delete(alternate);
|
||||
// Recursively clean up any filtered Fibers below this one as well since
|
||||
// we won't recordUnmount on those.
|
||||
for (let child = fiber.child; child !== null; child = child.sibling) {
|
||||
if (shouldFilterFiber(child)) {
|
||||
untrackFiber(nearestInstance, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2309,7 +2392,47 @@ export function attach(
|
||||
pendingRealUnmountedIDs.push(id);
|
||||
}
|
||||
|
||||
untrackFiber(fiberInstance);
|
||||
idToDevToolsInstanceMap.delete(fiberInstance.id);
|
||||
|
||||
// Restore any errors/warnings associated with this fiber to the pending
|
||||
// map. I.e. treat it as before we tracked the instances. This lets us
|
||||
// restore them if we remount the same Fibers later. Otherwise we rely
|
||||
// on the GC of the Fibers to clean them up.
|
||||
if (fiberInstance.errors !== null) {
|
||||
pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
|
||||
fiberInstance.errors = null;
|
||||
}
|
||||
if (fiberInstance.warnings !== null) {
|
||||
pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
|
||||
fiberInstance.warnings = null;
|
||||
}
|
||||
|
||||
if (fiberInstance.flags & FORCE_ERROR) {
|
||||
fiberInstance.flags &= ~FORCE_ERROR;
|
||||
forceErrorCount--;
|
||||
if (forceErrorCount === 0 && setErrorHandler != null) {
|
||||
setErrorHandler(shouldErrorFiberAlwaysNull);
|
||||
}
|
||||
}
|
||||
if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
|
||||
fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
|
||||
forceFallbackCount--;
|
||||
if (forceFallbackCount === 0 && setSuspenseHandler != null) {
|
||||
setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
|
||||
}
|
||||
}
|
||||
|
||||
if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
|
||||
fiberToFiberInstanceMap.delete(fiber);
|
||||
}
|
||||
const {alternate} = fiber;
|
||||
if (alternate !== null) {
|
||||
if (fiberToFiberInstanceMap.get(alternate) === fiberInstance) {
|
||||
fiberToFiberInstanceMap.delete(alternate);
|
||||
}
|
||||
}
|
||||
|
||||
untrackFiber(fiberInstance, fiber);
|
||||
}
|
||||
|
||||
// Running state of the remaining children from the previous version of this parent that
|
||||
@@ -2489,7 +2612,14 @@ export function attach(
|
||||
}
|
||||
// Scan up until the next Component to see if this component changed environment.
|
||||
const componentInfo: ReactComponentInfo = (debugEntry: any);
|
||||
if (shouldFilterVirtual(componentInfo)) {
|
||||
const secondaryEnv = getSecondaryEnvironmentName(fiber._debugInfo, i);
|
||||
if (componentInfo.env != null) {
|
||||
knownEnvironmentNames.add(componentInfo.env);
|
||||
}
|
||||
if (secondaryEnv !== null) {
|
||||
knownEnvironmentNames.add(secondaryEnv);
|
||||
}
|
||||
if (shouldFilterVirtual(componentInfo, secondaryEnv)) {
|
||||
// Skip.
|
||||
continue;
|
||||
}
|
||||
@@ -2511,10 +2641,6 @@ export function attach(
|
||||
);
|
||||
}
|
||||
previousVirtualInstance = createVirtualInstance(componentInfo);
|
||||
const secondaryEnv = getSecondaryEnvironmentName(
|
||||
fiber._debugInfo,
|
||||
i,
|
||||
);
|
||||
recordVirtualMount(
|
||||
previousVirtualInstance,
|
||||
reconcilingParent,
|
||||
@@ -2621,7 +2747,21 @@ export function attach(
|
||||
}
|
||||
|
||||
if (fiber.tag === HostHoistable) {
|
||||
aquireHostResource(fiber, fiber.memoizedState);
|
||||
const nearestInstance = reconcilingParent;
|
||||
if (nearestInstance === null) {
|
||||
throw new Error('Did not expect a host hoistable to be the root');
|
||||
}
|
||||
aquireHostResource(nearestInstance, fiber.memoizedState);
|
||||
} else if (
|
||||
fiber.tag === HostComponent ||
|
||||
fiber.tag === HostText ||
|
||||
fiber.tag === HostSingleton
|
||||
) {
|
||||
const nearestInstance = reconcilingParent;
|
||||
if (nearestInstance === null) {
|
||||
throw new Error('Did not expect a host hoistable to be the root');
|
||||
}
|
||||
aquireHostInstance(nearestInstance, fiber.stateNode);
|
||||
}
|
||||
|
||||
if (fiber.tag === SuspenseComponent) {
|
||||
@@ -2919,7 +3059,17 @@ export function attach(
|
||||
continue;
|
||||
}
|
||||
const componentInfo: ReactComponentInfo = (debugEntry: any);
|
||||
if (shouldFilterVirtual(componentInfo)) {
|
||||
const secondaryEnv = getSecondaryEnvironmentName(
|
||||
nextChild._debugInfo,
|
||||
i,
|
||||
);
|
||||
if (componentInfo.env != null) {
|
||||
knownEnvironmentNames.add(componentInfo.env);
|
||||
}
|
||||
if (secondaryEnv !== null) {
|
||||
knownEnvironmentNames.add(secondaryEnv);
|
||||
}
|
||||
if (shouldFilterVirtual(componentInfo, secondaryEnv)) {
|
||||
continue;
|
||||
}
|
||||
if (level === virtualLevel) {
|
||||
@@ -2983,10 +3133,6 @@ export function attach(
|
||||
} else {
|
||||
// Otherwise we create a new instance.
|
||||
const newVirtualInstance = createVirtualInstance(componentInfo);
|
||||
const secondaryEnv = getSecondaryEnvironmentName(
|
||||
nextChild._debugInfo,
|
||||
i,
|
||||
);
|
||||
recordVirtualMount(
|
||||
newVirtualInstance,
|
||||
reconcilingParent,
|
||||
@@ -3236,8 +3382,12 @@ export function attach(
|
||||
}
|
||||
try {
|
||||
if (nextFiber.tag === HostHoistable) {
|
||||
releaseHostResource(prevFiber, prevFiber.memoizedState);
|
||||
aquireHostResource(nextFiber, nextFiber.memoizedState);
|
||||
const nearestInstance = reconcilingParent;
|
||||
if (nearestInstance === null) {
|
||||
throw new Error('Did not expect a host hoistable to be the root');
|
||||
}
|
||||
releaseHostResource(nearestInstance, prevFiber.memoizedState);
|
||||
aquireHostResource(nearestInstance, nextFiber.memoizedState);
|
||||
}
|
||||
|
||||
const isSuspense = nextFiber.tag === SuspenseComponent;
|
||||
@@ -3338,6 +3488,18 @@ export function attach(
|
||||
// I.e. we just restore them by undoing what we did above.
|
||||
fiberInstance.firstChild = remainingReconcilingChildren;
|
||||
remainingReconcilingChildren = null;
|
||||
|
||||
if (traceUpdatesEnabled) {
|
||||
// If we're tracing updates and we've bailed out before reaching a host node,
|
||||
// we should fall back to recursively marking the nearest host descendants for highlight.
|
||||
if (traceNearestHostComponentUpdate) {
|
||||
const hostInstances =
|
||||
findAllCurrentHostInstances(fiberInstance);
|
||||
hostInstances.forEach(hostInstance => {
|
||||
traceUpdatesForNodes.add(hostInstance);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If this fiber is filtered there might be changes to this set elsewhere so we have
|
||||
// to visit each child to place it back in the set. We let the child bail out instead.
|
||||
@@ -3349,19 +3511,6 @@ export function attach(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (traceUpdatesEnabled) {
|
||||
// If we're tracing updates and we've bailed out before reaching a host node,
|
||||
// we should fall back to recursively marking the nearest host descendants for highlight.
|
||||
if (traceNearestHostComponentUpdate) {
|
||||
const hostInstances = findAllCurrentHostInstances(
|
||||
getFiberInstanceThrows(nextFiber),
|
||||
);
|
||||
hostInstances.forEach(hostInstance => {
|
||||
traceUpdatesForNodes.add(hostInstance);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3635,15 +3784,31 @@ export function attach(
|
||||
return null;
|
||||
}
|
||||
|
||||
function findAllCurrentHostInstances(
|
||||
fiberInstance: FiberInstance,
|
||||
): $ReadOnlyArray<HostInstance> {
|
||||
const hostInstances = [];
|
||||
const fiber = fiberInstance.data;
|
||||
if (!fiber) {
|
||||
return hostInstances;
|
||||
function appendHostInstancesByDevToolsInstance(
|
||||
devtoolsInstance: DevToolsInstance,
|
||||
hostInstances: Array<HostInstance>,
|
||||
) {
|
||||
if (devtoolsInstance.kind === FIBER_INSTANCE) {
|
||||
const fiber = devtoolsInstance.data;
|
||||
appendHostInstancesByFiber(fiber, hostInstances);
|
||||
return;
|
||||
}
|
||||
// Search the tree for the nearest child Fiber and add all its host instances.
|
||||
// TODO: If the true nearest Fiber is filtered, we might skip it and instead include all
|
||||
// the children below it. In the extreme case, searching the whole tree.
|
||||
for (
|
||||
let child = devtoolsInstance.firstChild;
|
||||
child !== null;
|
||||
child = child.nextSibling
|
||||
) {
|
||||
appendHostInstancesByDevToolsInstance(child, hostInstances);
|
||||
}
|
||||
}
|
||||
|
||||
function appendHostInstancesByFiber(
|
||||
fiber: Fiber,
|
||||
hostInstances: Array<HostInstance>,
|
||||
): void {
|
||||
// Next we'll drill down this component to find all HostComponent/Text.
|
||||
let node: Fiber = fiber;
|
||||
while (true) {
|
||||
@@ -3663,19 +3828,24 @@ export function attach(
|
||||
continue;
|
||||
}
|
||||
if (node === fiber) {
|
||||
return hostInstances;
|
||||
return;
|
||||
}
|
||||
while (!node.sibling) {
|
||||
if (!node.return || node.return === fiber) {
|
||||
return hostInstances;
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
// Flow needs the return here, but ESLint complains about it.
|
||||
// eslint-disable-next-line no-unreachable
|
||||
}
|
||||
|
||||
function findAllCurrentHostInstances(
|
||||
devtoolsInstance: DevToolsInstance,
|
||||
): $ReadOnlyArray<HostInstance> {
|
||||
const hostInstances: Array<HostInstance> = [];
|
||||
appendHostInstancesByDevToolsInstance(devtoolsInstance, hostInstances);
|
||||
return hostInstances;
|
||||
}
|
||||
|
||||
@@ -3686,17 +3856,7 @@ export function attach(
|
||||
console.warn(`Could not find DevToolsInstance with id "${id}"`);
|
||||
return null;
|
||||
}
|
||||
if (devtoolsInstance.kind !== FIBER_INSTANCE) {
|
||||
// TODO: Handle VirtualInstance.
|
||||
return null;
|
||||
}
|
||||
const fiber = devtoolsInstance.data;
|
||||
if (fiber === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hostInstances = findAllCurrentHostInstances(devtoolsInstance);
|
||||
return hostInstances;
|
||||
return findAllCurrentHostInstances(devtoolsInstance);
|
||||
} catch (err) {
|
||||
// The fiber might have unmounted by now.
|
||||
return null;
|
||||
@@ -3715,82 +3875,21 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
function getNearestMountedHostInstance(
|
||||
hostInstance: HostInstance,
|
||||
): null | HostInstance {
|
||||
const mountedFiber = renderer.findFiberByHostInstance(hostInstance);
|
||||
if (mountedFiber != null) {
|
||||
if (mountedFiber.stateNode !== hostInstance) {
|
||||
// If it's not a perfect match the specific one might be a resource.
|
||||
// We don't need to look at any parents because host resources don't have
|
||||
// children so it won't be in any parent if it's not this one.
|
||||
if (hostResourceToFiberMap.has(hostInstance)) {
|
||||
return hostInstance;
|
||||
}
|
||||
}
|
||||
return mountedFiber.stateNode;
|
||||
function getNearestMountedDOMNode(publicInstance: Element): null | Element {
|
||||
let domNode: null | Element = publicInstance;
|
||||
while (domNode && !publicInstanceToDevToolsInstanceMap.has(domNode)) {
|
||||
// $FlowFixMe: In practice this is either null or Element.
|
||||
domNode = domNode.parentNode;
|
||||
}
|
||||
if (hostResourceToFiberMap.has(hostInstance)) {
|
||||
return hostInstance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findNearestUnfilteredElementID(searchFiber: Fiber) {
|
||||
let fiber: null | Fiber = searchFiber;
|
||||
while (fiber !== null) {
|
||||
const fiberInstance = getFiberInstanceUnsafe(fiber);
|
||||
if (fiberInstance !== null) {
|
||||
// TODO: Ideally we would not have any filtered FiberInstances which
|
||||
// would make this logic much simpler. Unfortunately, we sometimes
|
||||
// eagerly add to the map and some times don't eagerly clean it up.
|
||||
// TODO: If the fiber is filtered, the FiberInstance wouldn't really
|
||||
// exist which would mean that we also don't have a way to get to the
|
||||
// VirtualInstances.
|
||||
if (!shouldFilterFiber(fiberInstance.data)) {
|
||||
return fiberInstance.id;
|
||||
}
|
||||
// We couldn't use this Fiber but we might have a VirtualInstance
|
||||
// that is the nearest unfiltered instance.
|
||||
const parentInstance = fiberInstance.parent;
|
||||
if (
|
||||
parentInstance !== null &&
|
||||
parentInstance.kind === VIRTUAL_INSTANCE
|
||||
) {
|
||||
// Virtual Instances only exist if they're unfiltered.
|
||||
return parentInstance.id;
|
||||
}
|
||||
// If we find a parent Fiber, it might not be the nearest parent
|
||||
// so we break out and continue walking the Fiber tree instead.
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
return null;
|
||||
return domNode;
|
||||
}
|
||||
|
||||
function getElementIDForHostInstance(
|
||||
hostInstance: HostInstance,
|
||||
findNearestUnfilteredAncestor: boolean = false,
|
||||
publicInstance: HostInstance,
|
||||
): number | null {
|
||||
const resourceFibers = hostResourceToFiberMap.get(hostInstance);
|
||||
if (resourceFibers !== undefined) {
|
||||
// This is a resource. Find the first unfiltered instance.
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const resourceFiber of resourceFibers) {
|
||||
const elementID = findNearestUnfilteredElementID(resourceFiber);
|
||||
if (elementID !== null) {
|
||||
return elementID;
|
||||
}
|
||||
}
|
||||
// If we don't find one, fallthrough to select the parent instead.
|
||||
}
|
||||
const fiber = renderer.findFiberByHostInstance(hostInstance);
|
||||
if (fiber != null) {
|
||||
if (!findNearestUnfilteredAncestor) {
|
||||
// TODO: Remove this option. It's not used.
|
||||
return getFiberIDThrows(fiber);
|
||||
}
|
||||
return findNearestUnfilteredElementID(fiber);
|
||||
const instance = publicInstanceToDevToolsInstanceMap.get(publicInstance);
|
||||
if (instance !== undefined) {
|
||||
return instance.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -3925,7 +4024,7 @@ export function attach(
|
||||
owner = ownerFiber._debugOwner;
|
||||
} else {
|
||||
const ownerInfo: ReactComponentInfo = (owner: any); // Refined
|
||||
if (!shouldFilterVirtual(ownerInfo)) {
|
||||
if (!shouldFilterVirtual(ownerInfo, null)) {
|
||||
return ownerInfo;
|
||||
}
|
||||
owner = ownerInfo.owner;
|
||||
@@ -5723,7 +5822,7 @@ export function attach(
|
||||
flushInitialOperations,
|
||||
getBestMatchForTrackedPath,
|
||||
getDisplayNameForElementID,
|
||||
getNearestMountedHostInstance,
|
||||
getNearestMountedDOMNode,
|
||||
getElementIDForHostInstance,
|
||||
getInstanceAndStyle,
|
||||
getOwnersList,
|
||||
@@ -5750,5 +5849,6 @@ export function attach(
|
||||
storeAsGlobal,
|
||||
unpatchConsoleForStrictMode,
|
||||
updateComponentFilters,
|
||||
getEnvironmentNames,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,7 +73,12 @@ export function initBackend(
|
||||
|
||||
// Inject any not-yet-injected renderers (if we didn't reload-and-profile)
|
||||
if (rendererInterface == null) {
|
||||
if (typeof renderer.findFiberByHostInstance === 'function') {
|
||||
if (
|
||||
// v16-19
|
||||
typeof renderer.findFiberByHostInstance === 'function' ||
|
||||
// v16.8+
|
||||
renderer.currentDispatcherRef != null
|
||||
) {
|
||||
// react-reconciler v16+
|
||||
rendererInterface = attach(hook, id, renderer, global);
|
||||
} else if (renderer.ComponentTree) {
|
||||
|
||||
@@ -145,15 +145,13 @@ export function attach(
|
||||
let getElementIDForHostInstance: GetElementIDForHostInstance =
|
||||
((null: any): GetElementIDForHostInstance);
|
||||
let findHostInstanceForInternalID: (id: number) => ?HostInstance;
|
||||
let getNearestMountedHostInstance = (
|
||||
node: HostInstance,
|
||||
): null | HostInstance => {
|
||||
let getNearestMountedDOMNode = (node: Element): null | Element => {
|
||||
// Not implemented.
|
||||
return null;
|
||||
};
|
||||
|
||||
if (renderer.ComponentTree) {
|
||||
getElementIDForHostInstance = (node, findNearestUnfilteredAncestor) => {
|
||||
getElementIDForHostInstance = node => {
|
||||
const internalInstance =
|
||||
renderer.ComponentTree.getClosestInstanceFromNode(node);
|
||||
return internalInstanceToIDMap.get(internalInstance) || null;
|
||||
@@ -162,9 +160,7 @@ export function attach(
|
||||
const internalInstance = idToInternalInstanceMap.get(id);
|
||||
return renderer.ComponentTree.getNodeFromInstance(internalInstance);
|
||||
};
|
||||
getNearestMountedHostInstance = (
|
||||
node: HostInstance,
|
||||
): null | HostInstance => {
|
||||
getNearestMountedDOMNode = (node: Element): null | Element => {
|
||||
const internalInstance =
|
||||
renderer.ComponentTree.getClosestInstanceFromNode(node);
|
||||
if (internalInstance != null) {
|
||||
@@ -173,7 +169,7 @@ export function attach(
|
||||
return null;
|
||||
};
|
||||
} else if (renderer.Mount.getID && renderer.Mount.getNode) {
|
||||
getElementIDForHostInstance = (node, findNearestUnfilteredAncestor) => {
|
||||
getElementIDForHostInstance = node => {
|
||||
// Not implemented.
|
||||
return null;
|
||||
};
|
||||
@@ -1078,6 +1074,11 @@ export function attach(
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
function getEnvironmentNames(): Array<string> {
|
||||
// No RSC support.
|
||||
return [];
|
||||
}
|
||||
|
||||
function setTraceUpdatesEnabled(enabled: boolean) {
|
||||
// Not implemented.
|
||||
}
|
||||
@@ -1121,7 +1122,7 @@ export function attach(
|
||||
flushInitialOperations,
|
||||
getBestMatchForTrackedPath,
|
||||
getDisplayNameForElementID,
|
||||
getNearestMountedHostInstance,
|
||||
getNearestMountedDOMNode,
|
||||
getElementIDForHostInstance,
|
||||
getInstanceAndStyle,
|
||||
findHostInstancesForElementID: (id: number) => {
|
||||
@@ -1152,5 +1153,6 @@ export function attach(
|
||||
storeAsGlobal,
|
||||
unpatchConsoleForStrictMode,
|
||||
updateComponentFilters,
|
||||
getEnvironmentNames,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ export type GetDisplayNameForElementID = (id: number) => string | null;
|
||||
|
||||
export type GetElementIDForHostInstance = (
|
||||
component: HostInstance,
|
||||
findNearestUnfilteredAncestor?: boolean,
|
||||
) => number | null;
|
||||
export type FindHostInstancesForElementID = (
|
||||
id: number,
|
||||
@@ -106,10 +105,11 @@ export type Lane = number;
|
||||
export type Lanes = number;
|
||||
|
||||
export type ReactRenderer = {
|
||||
findFiberByHostInstance: (hostInstance: HostInstance) => Fiber | null,
|
||||
version: string,
|
||||
rendererPackageName: string,
|
||||
bundleType: BundleType,
|
||||
// 16.0+ - To be removed in future versions.
|
||||
findFiberByHostInstance?: (hostInstance: HostInstance) => Fiber | null,
|
||||
// 16.9+
|
||||
overrideHookState?: ?(
|
||||
fiber: Object,
|
||||
@@ -358,9 +358,7 @@ export type RendererInterface = {
|
||||
findHostInstancesForElementID: FindHostInstancesForElementID,
|
||||
flushInitialOperations: () => void,
|
||||
getBestMatchForTrackedPath: () => PathMatch | null,
|
||||
getNearestMountedHostInstance: (
|
||||
component: HostInstance,
|
||||
) => HostInstance | null,
|
||||
getNearestMountedDOMNode: (component: Element) => Element | null,
|
||||
getElementIDForHostInstance: GetElementIDForHostInstance,
|
||||
getDisplayNameForElementID: GetDisplayNameForElementID,
|
||||
getInstanceAndStyle(id: number): InstanceAndStyle,
|
||||
@@ -416,6 +414,7 @@ export type RendererInterface = {
|
||||
) => void,
|
||||
unpatchConsoleForStrictMode: () => void,
|
||||
updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
|
||||
getEnvironmentNames: () => Array<string>,
|
||||
|
||||
// Timeline profiler interface
|
||||
|
||||
|
||||
2
packages/react-devtools-shared/src/bridge.js
vendored
2
packages/react-devtools-shared/src/bridge.js
vendored
@@ -189,6 +189,7 @@ export type BackendEvents = {
|
||||
operations: [Array<number>],
|
||||
ownersList: [OwnersList],
|
||||
overrideComponentFilters: [Array<ComponentFilter>],
|
||||
environmentNames: [Array<string>],
|
||||
profilingData: [ProfilingDataBackend],
|
||||
profilingStatus: [boolean],
|
||||
reloadAppForProfiling: [],
|
||||
@@ -237,6 +238,7 @@ type FrontendEvents = {
|
||||
stopProfiling: [],
|
||||
storeAsGlobal: [StoreAsGlobalParams],
|
||||
updateComponentFilters: [Array<ComponentFilter>],
|
||||
getEnvironmentNames: [],
|
||||
updateConsolePatchSettings: [ConsolePatchSettings],
|
||||
viewAttributeSource: [ViewAttributeSourceParams],
|
||||
viewElementSource: [ElementAndRendererID],
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
use,
|
||||
} from 'react';
|
||||
import {
|
||||
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
ComponentFilterElementType,
|
||||
ComponentFilterHOC,
|
||||
ComponentFilterLocation,
|
||||
ComponentFilterEnvironmentName,
|
||||
ElementTypeClass,
|
||||
ElementTypeContext,
|
||||
ElementTypeFunction,
|
||||
@@ -52,11 +54,16 @@ import type {
|
||||
ElementType,
|
||||
ElementTypeComponentFilter,
|
||||
RegExpComponentFilter,
|
||||
EnvironmentNameComponentFilter,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
|
||||
const vscodeFilepath = 'vscode://file/{path}:{line}';
|
||||
|
||||
export default function ComponentsSettings(_: {}): React.Node {
|
||||
export default function ComponentsSettings({
|
||||
environmentNames,
|
||||
}: {
|
||||
environmentNames: Promise<Array<string>>,
|
||||
}): React.Node {
|
||||
const store = useContext(StoreContext);
|
||||
const {parseHookNames, setParseHookNames} = useContext(SettingsContext);
|
||||
|
||||
@@ -101,6 +108,30 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
Array<ComponentFilter>,
|
||||
>(() => [...store.componentFilters]);
|
||||
|
||||
const usedEnvironmentNames = use(environmentNames);
|
||||
|
||||
const resolvedEnvironmentNames = useMemo(() => {
|
||||
const set = new Set(usedEnvironmentNames);
|
||||
// If there are other filters already specified but are not currently
|
||||
// on the page, we still allow them as options.
|
||||
for (let i = 0; i < componentFilters.length; i++) {
|
||||
const filter = componentFilters[i];
|
||||
if (filter.type === ComponentFilterEnvironmentName) {
|
||||
set.add(filter.value);
|
||||
}
|
||||
}
|
||||
// Client is special and is always available as a default.
|
||||
if (set.size > 0) {
|
||||
// Only show any options at all if there's any other option already
|
||||
// used by a filter or if any environments are used by the page.
|
||||
// Note that "Client" can have been added above which would mean
|
||||
// that we should show it as an option regardless if it's the only
|
||||
// option.
|
||||
set.add('Client');
|
||||
}
|
||||
return Array.from(set).sort();
|
||||
}, [usedEnvironmentNames, componentFilters]);
|
||||
|
||||
const addFilter = useCallback(() => {
|
||||
setComponentFilters(prevComponentFilters => {
|
||||
return [
|
||||
@@ -146,6 +177,13 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
isEnabled: componentFilter.isEnabled,
|
||||
isValid: true,
|
||||
};
|
||||
} else if (type === ComponentFilterEnvironmentName) {
|
||||
cloned[index] = {
|
||||
type: ComponentFilterEnvironmentName,
|
||||
isEnabled: componentFilter.isEnabled,
|
||||
isValid: true,
|
||||
value: 'Client',
|
||||
};
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
@@ -210,6 +248,29 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
[],
|
||||
);
|
||||
|
||||
const updateFilterValueEnvironmentName = useCallback(
|
||||
(componentFilter: ComponentFilter, value: string) => {
|
||||
if (componentFilter.type !== ComponentFilterEnvironmentName) {
|
||||
throw Error('Invalid value for environment name filter');
|
||||
}
|
||||
|
||||
setComponentFilters(prevComponentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...prevComponentFilters];
|
||||
if (componentFilter.type === ComponentFilterEnvironmentName) {
|
||||
const index = prevComponentFilters.indexOf(componentFilter);
|
||||
if (index >= 0) {
|
||||
cloned[index] = {
|
||||
...componentFilter,
|
||||
value,
|
||||
};
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const removeFilter = useCallback((index: number) => {
|
||||
setComponentFilters(prevComponentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...prevComponentFilters];
|
||||
@@ -246,6 +307,11 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
...((cloned[index]: any): BooleanComponentFilter),
|
||||
isEnabled,
|
||||
};
|
||||
} else if (componentFilter.type === ComponentFilterEnvironmentName) {
|
||||
cloned[index] = {
|
||||
...((cloned[index]: any): EnvironmentNameComponentFilter),
|
||||
isEnabled,
|
||||
};
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
@@ -380,10 +446,16 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
<option value={ComponentFilterDisplayName}>name</option>
|
||||
<option value={ComponentFilterElementType}>type</option>
|
||||
<option value={ComponentFilterHOC}>hoc</option>
|
||||
{resolvedEnvironmentNames.length > 0 && (
|
||||
<option value={ComponentFilterEnvironmentName}>
|
||||
environment
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
{componentFilter.type === ComponentFilterElementType &&
|
||||
{(componentFilter.type === ComponentFilterElementType ||
|
||||
componentFilter.type === ComponentFilterEnvironmentName) &&
|
||||
'equals'}
|
||||
{(componentFilter.type === ComponentFilterLocation ||
|
||||
componentFilter.type === ComponentFilterDisplayName) &&
|
||||
@@ -428,6 +500,23 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
value={componentFilter.value}
|
||||
/>
|
||||
)}
|
||||
{componentFilter.type === ComponentFilterEnvironmentName && (
|
||||
<select
|
||||
className={styles.Select}
|
||||
value={componentFilter.value}
|
||||
onChange={({currentTarget}) =>
|
||||
updateFilterValueEnvironmentName(
|
||||
componentFilter,
|
||||
currentTarget.value,
|
||||
)
|
||||
}>
|
||||
{resolvedEnvironmentNames.map(name => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
<Button
|
||||
|
||||
@@ -58,7 +58,8 @@ export default function SettingsModal(_: {}): React.Node {
|
||||
}
|
||||
|
||||
function SettingsModalImpl(_: {}) {
|
||||
const {setIsModalShowing} = useContext(SettingsModalContext);
|
||||
const {setIsModalShowing, environmentNames} =
|
||||
useContext(SettingsModalContext);
|
||||
const dismissModal = useCallback(
|
||||
() => setIsModalShowing(false),
|
||||
[setIsModalShowing],
|
||||
@@ -81,7 +82,7 @@ function SettingsModalImpl(_: {}) {
|
||||
let view = null;
|
||||
switch (selectedTabID) {
|
||||
case 'components':
|
||||
view = <ComponentsSettings />;
|
||||
view = <ComponentsSettings environmentNames={environmentNames} />;
|
||||
break;
|
||||
// $FlowFixMe[incompatible-type] is this missing in TabID?
|
||||
case 'debugging':
|
||||
|
||||
@@ -10,7 +10,16 @@
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import * as React from 'react';
|
||||
import {createContext, useMemo, useState} from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useState,
|
||||
startTransition,
|
||||
} from 'react';
|
||||
|
||||
import {BridgeContext} from '../context';
|
||||
import type {FrontendBridge} from '../../../bridge';
|
||||
|
||||
export type DisplayDensity = 'comfortable' | 'compact';
|
||||
export type Theme = 'auto' | 'light' | 'dark';
|
||||
@@ -18,7 +27,7 @@ export type Theme = 'auto' | 'light' | 'dark';
|
||||
type Context = {
|
||||
isModalShowing: boolean,
|
||||
setIsModalShowing: (value: boolean) => void,
|
||||
...
|
||||
environmentNames: null | Promise<Array<string>>,
|
||||
};
|
||||
|
||||
const SettingsModalContext: ReactContext<Context> = createContext<Context>(
|
||||
@@ -26,20 +35,42 @@ const SettingsModalContext: ReactContext<Context> = createContext<Context>(
|
||||
);
|
||||
SettingsModalContext.displayName = 'SettingsModalContext';
|
||||
|
||||
function fetchEnvironmentNames(bridge: FrontendBridge): Promise<Array<string>> {
|
||||
return new Promise(resolve => {
|
||||
function onEnvironmentNames(names: Array<string>) {
|
||||
bridge.removeListener('environmentNames', onEnvironmentNames);
|
||||
resolve(names);
|
||||
}
|
||||
bridge.addListener('environmentNames', onEnvironmentNames);
|
||||
bridge.send('getEnvironmentNames');
|
||||
});
|
||||
}
|
||||
|
||||
function SettingsModalContextController({
|
||||
children,
|
||||
}: {
|
||||
children: React$Node,
|
||||
}): React.Node {
|
||||
const [isModalShowing, setIsModalShowing] = useState<boolean>(false);
|
||||
const bridge = useContext(BridgeContext);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({isModalShowing, setIsModalShowing}),
|
||||
[isModalShowing, setIsModalShowing],
|
||||
);
|
||||
const setIsModalShowing: boolean => void = useCallback((value: boolean) => {
|
||||
startTransition(() => {
|
||||
setContext({
|
||||
isModalShowing: value,
|
||||
setIsModalShowing,
|
||||
environmentNames: value ? fetchEnvironmentNames(bridge) : null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const [currentContext, setContext] = useState<Context>({
|
||||
isModalShowing: false,
|
||||
setIsModalShowing,
|
||||
environmentNames: null,
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsModalContext.Provider value={value}>
|
||||
<SettingsModalContext.Provider value={currentContext}>
|
||||
{children}
|
||||
</SettingsModalContext.Provider>
|
||||
);
|
||||
|
||||
@@ -76,8 +76,9 @@ export const ComponentFilterElementType = 1;
|
||||
export const ComponentFilterDisplayName = 2;
|
||||
export const ComponentFilterLocation = 3;
|
||||
export const ComponentFilterHOC = 4;
|
||||
export const ComponentFilterEnvironmentName = 5;
|
||||
|
||||
export type ComponentFilterType = 1 | 2 | 3 | 4;
|
||||
export type ComponentFilterType = 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
// Hide all elements of types in this Set.
|
||||
// We hide host components only by default.
|
||||
@@ -102,10 +103,18 @@ export type BooleanComponentFilter = {
|
||||
type: 4,
|
||||
};
|
||||
|
||||
export type EnvironmentNameComponentFilter = {
|
||||
isEnabled: boolean,
|
||||
isValid: boolean,
|
||||
type: 5,
|
||||
value: string,
|
||||
};
|
||||
|
||||
export type ComponentFilter =
|
||||
| BooleanComponentFilter
|
||||
| ElementTypeComponentFilter
|
||||
| RegExpComponentFilter;
|
||||
| RegExpComponentFilter
|
||||
| EnvironmentNameComponentFilter;
|
||||
|
||||
export type HookName = string | null;
|
||||
// Map of hook source ("<filename>:<line-number>:<column-number>") to name.
|
||||
|
||||
28
packages/react-reconciler/src/ReactFiber.js
vendored
28
packages/react-reconciler/src/ReactFiber.js
vendored
@@ -404,10 +404,16 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
|
||||
workInProgress.dependencies =
|
||||
currentDependencies === null
|
||||
? null
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
: __DEV__
|
||||
? {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
_debugThenableState: currentDependencies._debugThenableState,
|
||||
}
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
|
||||
// These will be overridden during the parent's reconciliation
|
||||
workInProgress.sibling = current.sibling;
|
||||
@@ -503,10 +509,16 @@ export function resetWorkInProgress(
|
||||
workInProgress.dependencies =
|
||||
currentDependencies === null
|
||||
? null
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
: __DEV__
|
||||
? {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
_debugThenableState: currentDependencies._debugThenableState,
|
||||
}
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Note: We don't reset the actualTime counts. It's useful to accumulate
|
||||
|
||||
12
packages/react-reconciler/src/ReactFiberHooks.js
vendored
12
packages/react-reconciler/src/ReactFiberHooks.js
vendored
@@ -637,6 +637,18 @@ function finishRenderingHooks<Props, SecondArg>(
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
workInProgress._debugHookTypes = hookTypesDev;
|
||||
// Stash the thenable state for use by DevTools.
|
||||
if (workInProgress.dependencies === null) {
|
||||
if (thenableState !== null) {
|
||||
workInProgress.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: null,
|
||||
_debugThenableState: thenableState,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
workInProgress.dependencies._debugThenableState = thenableState;
|
||||
}
|
||||
}
|
||||
|
||||
// We can assume the previous dispatcher is always this one, since we set it
|
||||
|
||||
@@ -825,10 +825,16 @@ function readContextForConsumer_withSelect<C>(
|
||||
|
||||
// This is the first dependency for this component. Create a new list.
|
||||
lastContextDependency = contextItem;
|
||||
consumer.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
consumer.dependencies = __DEV__
|
||||
? {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
_debugThenableState: null,
|
||||
}
|
||||
: {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
if (enableLazyContextPropagation) {
|
||||
consumer.flags |= NeedsPropagation;
|
||||
}
|
||||
@@ -869,10 +875,16 @@ function readContextForConsumer<C>(
|
||||
|
||||
// This is the first dependency for this component. Create a new list.
|
||||
lastContextDependency = contextItem;
|
||||
consumer.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
consumer.dependencies = __DEV__
|
||||
? {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
_debugThenableState: null,
|
||||
}
|
||||
: {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
if (enableLazyContextPropagation) {
|
||||
consumer.flags |= NeedsPropagation;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import type {
|
||||
} from './ReactFiberTracingMarkerComponent';
|
||||
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates';
|
||||
import type {ComponentStackNode} from 'react-server/src/ReactFizzComponentStack';
|
||||
import type {ThenableState} from './ReactFiberThenable';
|
||||
|
||||
// Unwind Circular: moved from ReactFiberHooks.old
|
||||
export type HookType =
|
||||
@@ -81,7 +82,7 @@ export type Dependencies = {
|
||||
| ContextDependency<mixed>
|
||||
| ContextDependencyWithSelect<mixed>
|
||||
| null,
|
||||
...
|
||||
_debugThenableState?: null | ThenableState, // DEV-only
|
||||
};
|
||||
|
||||
export type MemoCache = {
|
||||
|
||||
Reference in New Issue
Block a user