diff --git a/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js b/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js new file mode 100644 index 0000000000000..f4aab13ece0c5 --- /dev/null +++ b/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js @@ -0,0 +1,298 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// This is a DevTools fork of ReactComponentStackFrame. +// This fork enables DevTools to use the same "native" component stack format, +// while still maintaining support for multiple renderer versions +// (which use different values for ReactTypeOfWork). + +import type {Source} from 'shared/ReactElementType'; +import type {LazyComponent} from 'react/src/ReactLazy'; +import type {CurrentDispatcherRef} from './types'; + +import { + BLOCK_NUMBER, + BLOCK_SYMBOL_STRING, + FORWARD_REF_NUMBER, + FORWARD_REF_SYMBOL_STRING, + LAZY_NUMBER, + LAZY_SYMBOL_STRING, + MEMO_NUMBER, + MEMO_SYMBOL_STRING, + SUSPENSE_NUMBER, + SUSPENSE_SYMBOL_STRING, + SUSPENSE_LIST_NUMBER, + SUSPENSE_LIST_SYMBOL_STRING, +} from './ReactSymbols'; + +// These methods are safe to import from shared; +// there is no React-specific logic here. +import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev'; + +let prefix; +export function describeBuiltInComponentFrame( + name: string, + source: void | null | Source, + ownerFn: void | null | Function, +): string { + if (prefix === undefined) { + // Extract the VM specific prefix used by each line. + try { + throw Error(); + } catch (x) { + const match = x.stack.trim().match(/\n( *(at )?)/); + prefix = (match && match[1]) || ''; + } + } + // We use the prefix to ensure our stacks line up with native stack frames. + return '\n' + prefix + name; +} + +let reentry = false; +let componentFrameCache; +if (__DEV__) { + const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; + componentFrameCache = new PossiblyWeakMap(); +} + +export function describeNativeComponentFrame( + fn: Function, + construct: boolean, + currentDispatcherRef: CurrentDispatcherRef, +): string { + // If something asked for a stack inside a fake render, it should get ignored. + if (!fn || reentry) { + return ''; + } + + if (__DEV__) { + const frame = componentFrameCache.get(fn); + if (frame !== undefined) { + return frame; + } + } + + let control; + + reentry = true; + let previousDispatcher; + if (__DEV__) { + previousDispatcher = currentDispatcherRef.current; + // Set the dispatcher in DEV because this might be call in the render function + // for warnings. + currentDispatcherRef.current = null; + disableLogs(); + } + try { + // This should throw. + if (construct) { + // Something should be setting the props in the constructor. + const Fake = function() { + throw Error(); + }; + // $FlowFixMe + Object.defineProperty(Fake.prototype, 'props', { + set: function() { + // We use a throwing setter instead of frozen or non-writable props + // because that won't throw in a non-strict mode function. + throw Error(); + }, + }); + if (typeof Reflect === 'object' && Reflect.construct) { + // We construct a different control for this case to include any extra + // frames added by the construct call. + try { + Reflect.construct(Fake, []); + } catch (x) { + control = x; + } + Reflect.construct(fn, [], Fake); + } else { + try { + Fake.call(); + } catch (x) { + control = x; + } + fn.call(Fake.prototype); + } + } else { + try { + throw Error(); + } catch (x) { + control = x; + } + fn(); + } + } catch (sample) { + // This is inlined manually because closure doesn't do it for us. + if (sample && control && typeof sample.stack === 'string') { + // This extracts the first frame from the sample that isn't also in the control. + // Skipping one frame that we assume is the frame that calls the two. + const sampleLines = sample.stack.split('\n'); + const controlLines = control.stack.split('\n'); + let s = sampleLines.length - 1; + let c = controlLines.length - 1; + while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) { + // We expect at least one stack frame to be shared. + // Typically this will be the root most one. However, stack frames may be + // cut off due to maximum stack limits. In this case, one maybe cut off + // earlier than the other. We assume that the sample is longer or the same + // and there for cut off earlier. So we should find the root most frame in + // the sample somewhere in the control. + c--; + } + for (; s >= 1 && c >= 0; s--, c--) { + // Next we find the first one that isn't the same which should be the + // frame that called our sample function and the control. + if (sampleLines[s] !== controlLines[c]) { + // In V8, the first line is describing the message but other VMs don't. + // If we're about to return the first line, and the control is also on the same + // line, that's a pretty good indicator that our sample threw at same line as + // the control. I.e. before we entered the sample frame. So we ignore this result. + // This can happen if you passed a class to function component, or non-function. + if (s !== 1 || c !== 1) { + do { + s--; + c--; + // We may still have similar intermediate frames from the construct call. + // The next one that isn't the same should be our match though. + if (c < 0 || sampleLines[s] !== controlLines[c]) { + // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier. + const frame = '\n' + sampleLines[s].replace(' at new ', ' at '); + if (__DEV__) { + if (typeof fn === 'function') { + componentFrameCache.set(fn, frame); + } + } + // Return the line we found. + return frame; + } + } while (s >= 1 && c >= 0); + } + break; + } + } + } + } finally { + reentry = false; + if (__DEV__) { + currentDispatcherRef.current = previousDispatcher; + reenableLogs(); + } + } + // Fallback to just using the name if we couldn't make it throw. + const name = fn ? fn.displayName || fn.name : ''; + const syntheticFrame = name ? describeBuiltInComponentFrame(name) : ''; + if (__DEV__) { + if (typeof fn === 'function') { + componentFrameCache.set(fn, syntheticFrame); + } + } + return syntheticFrame; +} + +export function describeClassComponentFrame( + ctor: Function, + source: void | null | Source, + ownerFn: void | null | Function, + currentDispatcherRef: CurrentDispatcherRef, +): string { + return describeNativeComponentFrame(ctor, true, currentDispatcherRef); +} + +export function describeFunctionComponentFrame( + fn: Function, + source: void | null | Source, + ownerFn: void | null | Function, + currentDispatcherRef: CurrentDispatcherRef, +): string { + return describeNativeComponentFrame(fn, false, currentDispatcherRef); +} + +function shouldConstruct(Component: Function) { + const prototype = Component.prototype; + return !!(prototype && prototype.isReactComponent); +} + +export function describeUnknownElementTypeFrameInDEV( + type: any, + source: void | null | Source, + ownerFn: void | null | Function, + currentDispatcherRef: CurrentDispatcherRef, +): string { + if (!__DEV__) { + return ''; + } + if (type == null) { + return ''; + } + if (typeof type === 'function') { + return describeNativeComponentFrame( + type, + shouldConstruct(type), + currentDispatcherRef, + ); + } + if (typeof type === 'string') { + return describeBuiltInComponentFrame(type, source, ownerFn); + } + switch (type) { + case SUSPENSE_NUMBER: + case SUSPENSE_SYMBOL_STRING: + return describeBuiltInComponentFrame('Suspense', source, ownerFn); + case SUSPENSE_LIST_NUMBER: + case SUSPENSE_LIST_SYMBOL_STRING: + return describeBuiltInComponentFrame('SuspenseList', source, ownerFn); + } + if (typeof type === 'object') { + switch (type.$$typeof) { + case FORWARD_REF_NUMBER: + case FORWARD_REF_SYMBOL_STRING: + return describeFunctionComponentFrame( + type.render, + source, + ownerFn, + currentDispatcherRef, + ); + case MEMO_NUMBER: + case MEMO_SYMBOL_STRING: + // Memo may contain any component type so we recursively resolve it. + return describeUnknownElementTypeFrameInDEV( + type.type, + source, + ownerFn, + currentDispatcherRef, + ); + case BLOCK_NUMBER: + case BLOCK_SYMBOL_STRING: + return describeFunctionComponentFrame( + type._render, + source, + ownerFn, + currentDispatcherRef, + ); + case LAZY_NUMBER: + case LAZY_SYMBOL_STRING: { + const lazyComponent: LazyComponent = (type: any); + const payload = lazyComponent._payload; + const init = lazyComponent._init; + try { + // Lazy may contain any component type so we recursively resolve it. + return describeUnknownElementTypeFrameInDEV( + init(payload), + source, + ownerFn, + currentDispatcherRef, + ); + } catch (x) {} + } + } + } + return ''; +} diff --git a/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js b/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js new file mode 100644 index 0000000000000..ecb2ac6d3be58 --- /dev/null +++ b/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js @@ -0,0 +1,108 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// This is a DevTools fork of ReactFiberComponentStack. +// This fork enables DevTools to use the same "native" component stack format, +// while still maintaining support for multiple renderer versions +// (which use different values for ReactTypeOfWork). + +import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type {CurrentDispatcherRef, WorkTagMap} from './types'; + +import { + describeBuiltInComponentFrame, + describeFunctionComponentFrame, + describeClassComponentFrame, +} from './DevToolsComponentStackFrame'; + +function describeFiber( + workTagMap: WorkTagMap, + workInProgress: Fiber, + currentDispatcherRef: CurrentDispatcherRef, +): string { + const { + HostComponent, + LazyComponent, + SuspenseComponent, + SuspenseListComponent, + FunctionComponent, + IndeterminateComponent, + SimpleMemoComponent, + ForwardRef, + Block, + ClassComponent, + } = workTagMap; + + const owner: null | Function = __DEV__ + ? workInProgress._debugOwner + ? workInProgress._debugOwner.type + : null + : null; + const source = __DEV__ ? workInProgress._debugSource : null; + switch (workInProgress.tag) { + case HostComponent: + return describeBuiltInComponentFrame(workInProgress.type, source, owner); + case LazyComponent: + return describeBuiltInComponentFrame('Lazy', source, owner); + case SuspenseComponent: + return describeBuiltInComponentFrame('Suspense', source, owner); + case SuspenseListComponent: + return describeBuiltInComponentFrame('SuspenseList', source, owner); + case FunctionComponent: + case IndeterminateComponent: + case SimpleMemoComponent: + return describeFunctionComponentFrame( + workInProgress.type, + source, + owner, + currentDispatcherRef, + ); + case ForwardRef: + return describeFunctionComponentFrame( + workInProgress.type.render, + source, + owner, + currentDispatcherRef, + ); + case Block: + return describeFunctionComponentFrame( + workInProgress.type._render, + source, + owner, + currentDispatcherRef, + ); + case ClassComponent: + return describeClassComponentFrame( + workInProgress.type, + source, + owner, + currentDispatcherRef, + ); + default: + return ''; + } +} + +export function getStackByFiberInDevAndProd( + workTagMap: WorkTagMap, + workInProgress: Fiber, + currentDispatcherRef: CurrentDispatcherRef, +): string { + try { + let info = ''; + let node = workInProgress; + do { + info += describeFiber(workTagMap, node, currentDispatcherRef); + node = node.return; + } while (node); + return info; + } catch (x) { + return '\nError generating stack: ' + x.message + '\n' + x.stack; + } +} diff --git a/packages/react-devtools-shared/src/backend/ReactSymbols.js b/packages/react-devtools-shared/src/backend/ReactSymbols.js new file mode 100644 index 0000000000000..99dd55e8037d2 --- /dev/null +++ b/packages/react-devtools-shared/src/backend/ReactSymbols.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// This list should be kept updated to reflect additions to 'shared/ReactSymbols'. +// DevTools can't import symbols from 'shared/ReactSymbols' directly for two reasons: +// 1. DevTools requires symbols which may have been deleted in more recent versions (e.g. concurrent mode) +// 2. DevTools must support both Symbol and numeric forms of each symbol; +// Since e.g. standalone DevTools runs in a separate process, it can't rely on its own ES capabilities. + +export const BLOCK_NUMBER = 0xead9; +export const BLOCK_SYMBOL_STRING = 'Symbol(react.block'; + +export const CONCURRENT_MODE_NUMBER = 0xeacf; +export const CONCURRENT_MODE_SYMBOL_STRING = 'Symbol(react.concurrent_mode)'; + +export const CONTEXT_NUMBER = 0xeace; +export const CONTEXT_SYMBOL_STRING = 'Symbol(react.context'; + +export const CONTEXT_CONSUMER_NUMBER = 0xeace; +export const CONTEXT_CONSUMER_SYMBOL_STRING = 'Symbol(react.context)'; + +export const CONTEXT_PROVIDER_NUMBER = 0xeacd; +export const CONTEXT_PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)'; + +export const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = 'Symbol(react.async_mode)'; + +export const ELEMENT_NUMBER = 0xeac7; +export const ELEMENT_SYMBOL_STRING = 'Symbol(react.element'; + +export const DEBUG_TRACING_MODE_NUMBER = 0xeae1; +export const DEBUG_TRACING_MODE_SYMBOL_STRING = 'Symbol(react.debug_trace_mode'; + +export const FORWARD_REF_NUMBER = 0xead0; +export const FORWARD_REF_SYMBOL_STRING = 'Symbol(react.forward_ref'; + +export const FRAGMENT_NUMBER = 0xeacb; +export const FRAGMENT_SYMBOL_STRING = 'Symbol(react.fragment'; + +export const FUNDAMENTAL_NUMBER = 0xead5; +export const FUNDAMENTAL_SYMBOL_STRING = 'Symbol(react.fundamental'; + +export const LAZY_NUMBER = 0xead4; +export const LAZY_SYMBOL_STRING = 'Symbol(react.lazy'; + +export const MEMO_NUMBER = 0xead3; +export const MEMO_SYMBOL_STRING = 'Symbol(react.memo'; + +export const OPAQUE_ID_NUMBER = 0xeae0; +export const OPAQUE_ID_SYMBOL_STRING = 'Symbol(react.opaque.id'; + +export const PORTAL_NUMBER = 0xeaca; +export const PORTAL_SYMBOL_STRING = 'Symbol(react.portal'; + +export const PROFILER_NUMBER = 0xead2; +export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler'; + +export const PROVIDER_NUMBER = 0xeacd; +export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider'; + +export const RESPONDER_NUMBER = 0xead6; +export const RESPONDER_SYMBOL_STRING = 'Symbol(react.responder'; + +export const SCOPE_NUMBER = 0xead7; +export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope'; + +export const SERVER_BLOCK_NUMBER = 0xeada; +export const SERVER_BLOCK_SYMBOL_STRING = 'Symbol(react.server.block'; + +export const STRICT_MODE_NUMBER = 0xeacc; +export const STRICT_MODE_SYMBOL_STRING = 'Symbol(react.strict_mode'; + +export const SUSPENSE_NUMBER = 0xead1; +export const SUSPENSE_SYMBOL_STRING = 'Symbol(react.suspense'; + +export const SUSPENSE_LIST_NUMBER = 0xead8; +export const SUSPENSE_LIST_SYMBOL_STRING = 'Symbol(react.suspense_list'; diff --git a/packages/react-devtools-shared/src/backend/console.js b/packages/react-devtools-shared/src/backend/console.js index 806ff21982f5e..ae8b37714c536 100644 --- a/packages/react-devtools-shared/src/backend/console.js +++ b/packages/react-devtools-shared/src/backend/console.js @@ -8,9 +8,10 @@ */ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; -import type {ReactRenderer} from './types'; +import type {CurrentDispatcherRef, ReactRenderer, WorkTagMap} from './types'; -import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack'; +import {getInternalReactConstants} from './renderer'; +import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack'; const APPEND_STACK_TO_METHODS = ['error', 'trace', 'warn']; @@ -24,7 +25,10 @@ const ROW_COLUMN_NUMBER_REGEX = /:\d+:\d+$/; const injectedRenderers: Map< ReactRenderer, {| + currentDispatcherRef: CurrentDispatcherRef, getCurrentFiber: () => Fiber | null, + getDisplayNameForFiber: (fiber: Fiber) => string | null, + workTagMap: WorkTagMap, |}, > = new Map(); @@ -52,16 +56,30 @@ export function dangerous_setTargetConsoleForTesting( // These internals will be used if the console is patched. // Injecting them separately allows the console to easily be patched or un-patched later (at runtime). export function registerRenderer(renderer: ReactRenderer): void { - const {getCurrentFiber, findFiberByHostInstance} = renderer; + 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; } - if (typeof getCurrentFiber === 'function') { + // currentDispatcherRef gets injected for v16.8+ to support hooks inspection. + // getCurrentFiber gets injected for v16.9+. + if (currentDispatcherRef != null && typeof getCurrentFiber === 'function') { + const {getDisplayNameForFiber, ReactTypeOfWork} = getInternalReactConstants( + version, + ); + injectedRenderers.set(renderer, { + currentDispatcherRef, getCurrentFiber, + getDisplayNameForFiber, + workTagMap: ReactTypeOfWork, }); } } @@ -104,10 +122,19 @@ export function patch(): void { // If there's a component stack for at least one of the injected renderers, append it. // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?) // eslint-disable-next-line no-for-of-loops/no-for-of-loops - for (const {getCurrentFiber} of injectedRenderers.values()) { + for (const { + currentDispatcherRef, + getCurrentFiber, + getDisplayNameForFiber, + workTagMap, + } of injectedRenderers.values()) { const current: ?Fiber = getCurrentFiber(); if (current != null) { - const componentStack = getStackByFiberInDevAndProd(current); + const componentStack = getStackByFiberInDevAndProd( + workTagMap, + current, + currentDispatcherRef, + ); if (componentStack !== '') { args.push(componentStack); } diff --git a/packages/react-devtools-shared/src/backend/describeComponentFrame.js b/packages/react-devtools-shared/src/backend/describeComponentFrame.js deleted file mode 100644 index 1be9d86494211..0000000000000 --- a/packages/react-devtools-shared/src/backend/describeComponentFrame.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -// This file was forked from the React GitHub repo: -// https://raw.githubusercontent.com/facebook/react/master/packages/shared/describeComponentFrame.js -// -// It has been modified slightly to add a zero width space as commented below. - -const BEFORE_SLASH_RE = /^(.*)[\\/]/; - -export default function describeComponentFrame( - name: null | string, - source: any, - ownerName: null | string, -) { - let sourceInfo = ''; - if (source) { - const path = source.fileName; - let fileName = path.replace(BEFORE_SLASH_RE, ''); - if (__DEV__) { - // In DEV, include code for a common special case: - // prefer "folder/index.js" instead of just "index.js". - if (/^index\./.test(fileName)) { - const match = path.match(BEFORE_SLASH_RE); - if (match) { - const pathBeforeSlash = match[1]; - if (pathBeforeSlash) { - const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, ''); - // Note the below string contains a zero width space after the "/" character. - // This is to prevent browsers like Chrome from formatting the file name as a link. - // (Since this is a source link, it would not work to open the source file anyway.) - fileName = folderName + '/​' + fileName; - } - } - } - } - sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')'; - } else if (ownerName) { - sourceInfo = ' (created by ' + ownerName + ')'; - } - return '\n in ' + (name || 'Unknown') + sourceInfo; -} diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index dbe7270527386..209d306f5ef84 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -49,6 +49,25 @@ import { patch as patchConsole, registerRenderer as registerRendererWithConsole, } from './console'; +import { + CONCURRENT_MODE_NUMBER, + CONCURRENT_MODE_SYMBOL_STRING, + DEPRECATED_ASYNC_MODE_SYMBOL_STRING, + CONTEXT_PROVIDER_NUMBER, + CONTEXT_PROVIDER_SYMBOL_STRING, + CONTEXT_CONSUMER_NUMBER, + CONTEXT_CONSUMER_SYMBOL_STRING, + STRICT_MODE_NUMBER, + STRICT_MODE_SYMBOL_STRING, + PROFILER_NUMBER, + PROFILER_SYMBOL_STRING, + SCOPE_NUMBER, + SCOPE_SYMBOL_STRING, + FORWARD_REF_NUMBER, + FORWARD_REF_SYMBOL_STRING, + MEMO_NUMBER, + MEMO_SYMBOL_STRING, +} from './ReactSymbols'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { @@ -66,6 +85,7 @@ import type { ProfilingDataForRootBackend, ReactRenderer, RendererInterface, + WorkTagMap, } from './types'; import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types'; import type { @@ -76,26 +96,6 @@ import type { type getDisplayNameForFiberType = (fiber: Fiber) => string | null; type getTypeSymbolType = (type: any) => Symbol | number; -type ReactSymbolsType = {| - CONCURRENT_MODE_NUMBER: number, - CONCURRENT_MODE_SYMBOL_STRING: string, - DEPRECATED_ASYNC_MODE_SYMBOL_STRING: string, - CONTEXT_CONSUMER_NUMBER: number, - CONTEXT_CONSUMER_SYMBOL_STRING: string, - CONTEXT_PROVIDER_NUMBER: number, - CONTEXT_PROVIDER_SYMBOL_STRING: string, - FORWARD_REF_NUMBER: number, - FORWARD_REF_SYMBOL_STRING: string, - MEMO_NUMBER: number, - MEMO_SYMBOL_STRING: string, - PROFILER_NUMBER: number, - PROFILER_SYMBOL_STRING: string, - STRICT_MODE_NUMBER: number, - STRICT_MODE_SYMBOL_STRING: string, - SCOPE_NUMBER: number, - SCOPE_SYMBOL_STRING: string, -|}; - type ReactPriorityLevelsType = {| ImmediatePriority: number, UserBlockingPriority: number, @@ -105,32 +105,6 @@ type ReactPriorityLevelsType = {| NoPriority: number, |}; -type ReactTypeOfWorkType = {| - ClassComponent: number, - ContextConsumer: number, - ContextProvider: number, - CoroutineComponent: number, - CoroutineHandlerPhase: number, - DehydratedSuspenseComponent: number, - ForwardRef: number, - Fragment: number, - FunctionComponent: number, - HostComponent: number, - HostPortal: number, - HostRoot: number, - HostText: number, - IncompleteClassComponent: number, - IndeterminateComponent: number, - LazyComponent: number, - MemoComponent: number, - Mode: number, - Profiler: number, - SimpleMemoComponent: number, - SuspenseComponent: number, - SuspenseListComponent: number, - YieldComponent: number, -|}; - type ReactTypeOfSideEffectType = {| NoEffect: number, PerformedWork: number, @@ -149,30 +123,9 @@ export function getInternalReactConstants( getDisplayNameForFiber: getDisplayNameForFiberType, getTypeSymbol: getTypeSymbolType, ReactPriorityLevels: ReactPriorityLevelsType, - ReactSymbols: ReactSymbolsType, ReactTypeOfSideEffect: ReactTypeOfSideEffectType, - ReactTypeOfWork: ReactTypeOfWorkType, + ReactTypeOfWork: WorkTagMap, |} { - const ReactSymbols: ReactSymbolsType = { - CONCURRENT_MODE_NUMBER: 0xeacf, - CONCURRENT_MODE_SYMBOL_STRING: 'Symbol(react.concurrent_mode)', - DEPRECATED_ASYNC_MODE_SYMBOL_STRING: 'Symbol(react.async_mode)', - CONTEXT_CONSUMER_NUMBER: 0xeace, - CONTEXT_CONSUMER_SYMBOL_STRING: 'Symbol(react.context)', - CONTEXT_PROVIDER_NUMBER: 0xeacd, - CONTEXT_PROVIDER_SYMBOL_STRING: 'Symbol(react.provider)', - FORWARD_REF_NUMBER: 0xead0, - FORWARD_REF_SYMBOL_STRING: 'Symbol(react.forward_ref)', - MEMO_NUMBER: 0xead3, - MEMO_SYMBOL_STRING: 'Symbol(react.memo)', - PROFILER_NUMBER: 0xead2, - PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)', - STRICT_MODE_NUMBER: 0xeacc, - STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)', - SCOPE_NUMBER: 0xead7, - SCOPE_SYMBOL_STRING: 'Symbol(react.scope)', - }; - const ReactTypeOfSideEffect: ReactTypeOfSideEffectType = { NoEffect: 0b00, PerformedWork: 0b01, @@ -195,13 +148,14 @@ export function getInternalReactConstants( NoPriority: 90, }; - let ReactTypeOfWork: ReactTypeOfWorkType = ((null: any): ReactTypeOfWorkType); + let ReactTypeOfWork: WorkTagMap = ((null: any): WorkTagMap); // ********************************************************** // The section below is copied from files in React repo. // Keep it in sync, and add version guards if it changes. if (gte(version, '16.6.0-beta.0')) { ReactTypeOfWork = { + Block: 22, ClassComponent: 1, ContextConsumer: 9, ContextProvider: 10, @@ -228,6 +182,7 @@ export function getInternalReactConstants( }; } else if (gte(version, '16.4.3-alpha')) { ReactTypeOfWork = { + Block: -1, // Doesn't exist yet ClassComponent: 2, ContextConsumer: 11, ContextProvider: 12, @@ -254,6 +209,7 @@ export function getInternalReactConstants( }; } else { ReactTypeOfWork = { + Block: -1, // Doesn't exist yet ClassComponent: 2, ContextConsumer: 12, ContextProvider: 13, @@ -310,26 +266,6 @@ export function getInternalReactConstants( SuspenseListComponent, } = ReactTypeOfWork; - const { - CONCURRENT_MODE_NUMBER, - CONCURRENT_MODE_SYMBOL_STRING, - DEPRECATED_ASYNC_MODE_SYMBOL_STRING, - CONTEXT_PROVIDER_NUMBER, - CONTEXT_PROVIDER_SYMBOL_STRING, - CONTEXT_CONSUMER_NUMBER, - CONTEXT_CONSUMER_SYMBOL_STRING, - STRICT_MODE_NUMBER, - STRICT_MODE_SYMBOL_STRING, - PROFILER_NUMBER, - PROFILER_SYMBOL_STRING, - SCOPE_NUMBER, - SCOPE_SYMBOL_STRING, - FORWARD_REF_NUMBER, - FORWARD_REF_SYMBOL_STRING, - MEMO_NUMBER, - MEMO_SYMBOL_STRING, - } = ReactSymbols; - function resolveFiberType(type: any) { const typeSymbol = getTypeSymbol(type); switch (typeSymbol) { @@ -431,7 +367,6 @@ export function getInternalReactConstants( getTypeSymbol, ReactPriorityLevels, ReactTypeOfWork, - ReactSymbols, ReactTypeOfSideEffect, }; } @@ -447,7 +382,6 @@ export function attach( getTypeSymbol, ReactPriorityLevels, ReactTypeOfWork, - ReactSymbols, ReactTypeOfSideEffect, } = getInternalReactConstants(renderer.version); const {NoEffect, PerformedWork, Placement} = ReactTypeOfSideEffect; @@ -477,19 +411,6 @@ export function attach( IdlePriority, NoPriority, } = ReactPriorityLevels; - const { - CONCURRENT_MODE_NUMBER, - CONCURRENT_MODE_SYMBOL_STRING, - DEPRECATED_ASYNC_MODE_SYMBOL_STRING, - CONTEXT_CONSUMER_NUMBER, - CONTEXT_CONSUMER_SYMBOL_STRING, - CONTEXT_PROVIDER_NUMBER, - CONTEXT_PROVIDER_SYMBOL_STRING, - PROFILER_NUMBER, - PROFILER_SYMBOL_STRING, - STRICT_MODE_NUMBER, - STRICT_MODE_SYMBOL_STRING, - } = ReactSymbols; const { overrideHookState, diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 9b3c0ea335ae3..81521bd577c4a 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -25,6 +25,33 @@ export type WorkTag = number; export type SideEffectTag = number; export type ExpirationTime = number; +export type WorkTagMap = {| + Block: WorkTag, + ClassComponent: WorkTag, + ContextConsumer: WorkTag, + ContextProvider: WorkTag, + CoroutineComponent: WorkTag, + CoroutineHandlerPhase: WorkTag, + DehydratedSuspenseComponent: WorkTag, + ForwardRef: WorkTag, + Fragment: WorkTag, + FunctionComponent: WorkTag, + HostComponent: WorkTag, + HostPortal: WorkTag, + HostRoot: WorkTag, + HostText: WorkTag, + IncompleteClassComponent: WorkTag, + IndeterminateComponent: WorkTag, + LazyComponent: WorkTag, + MemoComponent: WorkTag, + Mode: WorkTag, + Profiler: WorkTag, + SimpleMemoComponent: WorkTag, + SuspenseComponent: WorkTag, + SuspenseListComponent: WorkTag, + YieldComponent: WorkTag, +|}; + // TODO: If it's useful for the frontend to know which types of data an Element has // (e.g. props, state, context, hooks) then we could add a bitmask field for this // to keep the number of attributes small. @@ -38,6 +65,7 @@ export type NativeType = Object; export type RendererID = number; type Dispatcher = any; +export type CurrentDispatcherRef = {|current: null | Dispatcher|}; export type GetDisplayNameForFiberID = ( id: number, @@ -77,7 +105,7 @@ export type ReactRenderer = { scheduleUpdate?: ?(fiber: Object) => void, setSuspenseHandler?: ?(shouldSuspend: (fiber: Object) => boolean) => void, // Only injected by React v16.8+ in order to support hooks inspection. - currentDispatcherRef?: {|current: null | Dispatcher|}, + currentDispatcherRef?: CurrentDispatcherRef, // Only injected by React v16.9+ in DEV mode. // Enables DevTools to append owners-only component stack to error messages. getCurrentFiber?: () => Fiber | null, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js index 5424d35039cb3..ae0ef9493a6c9 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js @@ -463,7 +463,7 @@ function InspectedElementView({ ); } -// This function is based on packages/shared/describeComponentFrame.js +// This function is based on describeComponentFrame() in packages/shared/ReactComponentStackFrame function formatSourceForDisplay(fileName: string, lineNumber: string) { const BEFORE_SLASH_RE = /^(.*)[\\\/]/; diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index a4ecac2fa6608..672ed8d8aa27d 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -7,6 +7,10 @@ * @flow */ +// ATTENTION +// When adding new symbols to this file, +// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols' + // The Symbol used to tag the ReactElement-like types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. export let REACT_ELEMENT_TYPE = 0xeac7;