react/packages/react-devtools-shared/src/backend/legacy/renderer.js
Luna Ruan 5b57bc6e31
[Draft] don't patch console during first render (#22308)
Previously, DevTools always overrode the native console to dim or supress StrictMode double logging. It also overrode console.log (in addition to console.error and console.warn). However, this changes the location shown by the browser console, which causes a bad developer experience. There is currently a TC39 proposal that would allow us to extend console without breaking developer experience, but in the meantime this PR changes the StrictMode console override behavior so that we only patch the console during the StrictMode double render so that, during the first render, the location points to developer code rather than our DevTools console code.
2021-09-21 15:00:11 -07:00

1121 lines
32 KiB
JavaScript

/**
* 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
*/
import {
ElementTypeClass,
ElementTypeFunction,
ElementTypeRoot,
ElementTypeHostComponent,
ElementTypeOtherOrUnknown,
} from 'react-devtools-shared/src/types';
import {getUID, utfEncodeString, printOperationsArray} from '../../utils';
import {
cleanForBridge,
copyToClipboard,
copyWithDelete,
copyWithRename,
copyWithSet,
} from '../utils';
import {
deletePathInObject,
getDisplayName,
getInObject,
renamePathInObject,
setInObject,
} from 'react-devtools-shared/src/utils';
import {
__DEBUG__,
TREE_OPERATION_ADD,
TREE_OPERATION_REMOVE,
TREE_OPERATION_REORDER_CHILDREN,
} from '../../constants';
import {decorateMany, forceUpdate, restoreMany} from './utils';
import type {
DevToolsHook,
GetFiberIDForNative,
InspectedElementPayload,
InstanceAndStyle,
NativeType,
PathFrame,
PathMatch,
RendererInterface,
} from '../types';
import type {
ComponentFilter,
ElementType,
} from 'react-devtools-shared/src/types';
import type {InspectedElement, SerializedElement} from '../types';
export type InternalInstance = Object;
type LegacyRenderer = Object;
function getData(internalInstance: InternalInstance) {
let displayName = null;
let key = null;
// != used deliberately here to catch undefined and null
if (internalInstance._currentElement != null) {
if (internalInstance._currentElement.key) {
key = '' + internalInstance._currentElement.key;
}
const elementType = internalInstance._currentElement.type;
if (typeof elementType === 'string') {
displayName = elementType;
} else if (typeof elementType === 'function') {
displayName = getDisplayName(elementType);
}
}
return {
displayName,
key,
};
}
function getElementType(internalInstance: InternalInstance): ElementType {
// != used deliberately here to catch undefined and null
if (internalInstance._currentElement != null) {
const elementType = internalInstance._currentElement.type;
if (typeof elementType === 'function') {
const publicInstance = internalInstance.getPublicInstance();
if (publicInstance !== null) {
return ElementTypeClass;
} else {
return ElementTypeFunction;
}
} else if (typeof elementType === 'string') {
return ElementTypeHostComponent;
}
}
return ElementTypeOtherOrUnknown;
}
function getChildren(internalInstance: Object): Array<any> {
const children = [];
// If the parent is a native node without rendered children, but with
// multiple string children, then the `element` that gets passed in here is
// a plain value -- a string or number.
if (typeof internalInstance !== 'object') {
// No children
} else if (
internalInstance._currentElement === null ||
internalInstance._currentElement === false
) {
// No children
} else if (internalInstance._renderedComponent) {
const child = internalInstance._renderedComponent;
if (getElementType(child) !== ElementTypeOtherOrUnknown) {
children.push(child);
}
} else if (internalInstance._renderedChildren) {
const renderedChildren = internalInstance._renderedChildren;
for (const name in renderedChildren) {
const child = renderedChildren[name];
if (getElementType(child) !== ElementTypeOtherOrUnknown) {
children.push(child);
}
}
}
// Note: we skip the case where children are just strings or numbers
// because the new DevTools skips over host text nodes anyway.
return children;
}
export function attach(
hook: DevToolsHook,
rendererID: number,
renderer: LegacyRenderer,
global: Object,
): RendererInterface {
const idToInternalInstanceMap: Map<number, InternalInstance> = new Map();
const internalInstanceToIDMap: WeakMap<
InternalInstance,
number,
> = new WeakMap();
const internalInstanceToRootIDMap: WeakMap<
InternalInstance,
number,
> = new WeakMap();
let getInternalIDForNative: GetFiberIDForNative = ((null: any): GetFiberIDForNative);
let findNativeNodeForInternalID: (id: number) => ?NativeType;
if (renderer.ComponentTree) {
getInternalIDForNative = (node, findNearestUnfilteredAncestor) => {
const internalInstance = renderer.ComponentTree.getClosestInstanceFromNode(
node,
);
return internalInstanceToIDMap.get(internalInstance) || null;
};
findNativeNodeForInternalID = (id: number) => {
const internalInstance = idToInternalInstanceMap.get(id);
return renderer.ComponentTree.getNodeFromInstance(internalInstance);
};
} else if (renderer.Mount.getID && renderer.Mount.getNode) {
getInternalIDForNative = (node, findNearestUnfilteredAncestor) => {
// Not implemented.
return null;
};
findNativeNodeForInternalID = (id: number) => {
// Not implemented.
return null;
};
}
function getDisplayNameForFiberID(id: number): string | null {
const internalInstance = idToInternalInstanceMap.get(id);
return internalInstance ? getData(internalInstance).displayName : null;
}
function getID(internalInstance: InternalInstance): number {
if (typeof internalInstance !== 'object' || internalInstance === null) {
throw new Error('Invalid internal instance: ' + internalInstance);
}
if (!internalInstanceToIDMap.has(internalInstance)) {
const id = getUID();
internalInstanceToIDMap.set(internalInstance, id);
idToInternalInstanceMap.set(id, internalInstance);
}
return ((internalInstanceToIDMap.get(internalInstance): any): number);
}
function areEqualArrays(a, b) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
// This is shared mutable state that lets us keep track of where we are.
let parentIDStack = [];
let oldReconcilerMethods = null;
if (renderer.Reconciler) {
// React 15
oldReconcilerMethods = decorateMany(renderer.Reconciler, {
mountComponent(fn, args) {
const internalInstance = args[0];
const hostContainerInfo = args[3];
if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
return fn.apply(this, args);
}
if (hostContainerInfo._topLevelWrapper === undefined) {
// SSR
return fn.apply(this, args);
}
const id = getID(internalInstance);
// Push the operation.
const parentID =
parentIDStack.length > 0
? parentIDStack[parentIDStack.length - 1]
: 0;
recordMount(internalInstance, id, parentID);
parentIDStack.push(id);
// Remember the root.
internalInstanceToRootIDMap.set(
internalInstance,
getID(hostContainerInfo._topLevelWrapper),
);
try {
const result = fn.apply(this, args);
parentIDStack.pop();
return result;
} catch (err) {
parentIDStack = [];
throw err;
} finally {
if (parentIDStack.length === 0) {
const rootID = internalInstanceToRootIDMap.get(internalInstance);
if (rootID === undefined) {
throw new Error('Expected to find root ID.');
}
flushPendingEvents(rootID);
}
}
},
performUpdateIfNecessary(fn, args) {
const internalInstance = args[0];
if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
return fn.apply(this, args);
}
const id = getID(internalInstance);
parentIDStack.push(id);
const prevChildren = getChildren(internalInstance);
try {
const result = fn.apply(this, args);
const nextChildren = getChildren(internalInstance);
if (!areEqualArrays(prevChildren, nextChildren)) {
// Push the operation
recordReorder(internalInstance, id, nextChildren);
}
parentIDStack.pop();
return result;
} catch (err) {
parentIDStack = [];
throw err;
} finally {
if (parentIDStack.length === 0) {
const rootID = internalInstanceToRootIDMap.get(internalInstance);
if (rootID === undefined) {
throw new Error('Expected to find root ID.');
}
flushPendingEvents(rootID);
}
}
},
receiveComponent(fn, args) {
const internalInstance = args[0];
if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
return fn.apply(this, args);
}
const id = getID(internalInstance);
parentIDStack.push(id);
const prevChildren = getChildren(internalInstance);
try {
const result = fn.apply(this, args);
const nextChildren = getChildren(internalInstance);
if (!areEqualArrays(prevChildren, nextChildren)) {
// Push the operation
recordReorder(internalInstance, id, nextChildren);
}
parentIDStack.pop();
return result;
} catch (err) {
parentIDStack = [];
throw err;
} finally {
if (parentIDStack.length === 0) {
const rootID = internalInstanceToRootIDMap.get(internalInstance);
if (rootID === undefined) {
throw new Error('Expected to find root ID.');
}
flushPendingEvents(rootID);
}
}
},
unmountComponent(fn, args) {
const internalInstance = args[0];
if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
return fn.apply(this, args);
}
const id = getID(internalInstance);
parentIDStack.push(id);
try {
const result = fn.apply(this, args);
parentIDStack.pop();
// Push the operation.
recordUnmount(internalInstance, id);
return result;
} catch (err) {
parentIDStack = [];
throw err;
} finally {
if (parentIDStack.length === 0) {
const rootID = internalInstanceToRootIDMap.get(internalInstance);
if (rootID === undefined) {
throw new Error('Expected to find root ID.');
}
flushPendingEvents(rootID);
}
}
},
});
}
function cleanup() {
if (oldReconcilerMethods !== null) {
if (renderer.Component) {
restoreMany(renderer.Component.Mixin, oldReconcilerMethods);
} else {
restoreMany(renderer.Reconciler, oldReconcilerMethods);
}
}
oldReconcilerMethods = null;
}
function recordMount(
internalInstance: InternalInstance,
id: number,
parentID: number,
) {
const isRoot = parentID === 0;
if (__DEBUG__) {
console.log(
'%crecordMount()',
'color: green; font-weight: bold;',
id,
getData(internalInstance).displayName,
);
}
if (isRoot) {
// TODO Is this right? For all versions?
const hasOwnerMetadata =
internalInstance._currentElement != null &&
internalInstance._currentElement._owner != null;
pushOperation(TREE_OPERATION_ADD);
pushOperation(id);
pushOperation(ElementTypeRoot);
pushOperation(0); // isProfilingSupported?
pushOperation(hasOwnerMetadata ? 1 : 0);
} else {
const type = getElementType(internalInstance);
const {displayName, key} = getData(internalInstance);
const ownerID =
internalInstance._currentElement != null &&
internalInstance._currentElement._owner != null
? getID(internalInstance._currentElement._owner)
: 0;
const displayNameStringID = getStringID(displayName);
const keyStringID = getStringID(key);
pushOperation(TREE_OPERATION_ADD);
pushOperation(id);
pushOperation(type);
pushOperation(parentID);
pushOperation(ownerID);
pushOperation(displayNameStringID);
pushOperation(keyStringID);
}
}
function recordReorder(
internalInstance: InternalInstance,
id: number,
nextChildren: Array<InternalInstance>,
) {
pushOperation(TREE_OPERATION_REORDER_CHILDREN);
pushOperation(id);
const nextChildIDs = nextChildren.map(getID);
pushOperation(nextChildIDs.length);
for (let i = 0; i < nextChildIDs.length; i++) {
pushOperation(nextChildIDs[i]);
}
}
function recordUnmount(internalInstance: InternalInstance, id: number) {
pendingUnmountedIDs.push(id);
idToInternalInstanceMap.delete(id);
}
function crawlAndRecordInitialMounts(
id: number,
parentID: number,
rootID: number,
) {
if (__DEBUG__) {
console.group('crawlAndRecordInitialMounts() id:', id);
}
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance != null) {
internalInstanceToRootIDMap.set(internalInstance, rootID);
recordMount(internalInstance, id, parentID);
getChildren(internalInstance).forEach(child =>
crawlAndRecordInitialMounts(getID(child), id, rootID),
);
}
if (__DEBUG__) {
console.groupEnd();
}
}
function flushInitialOperations() {
// Crawl roots though and register any nodes that mounted before we were injected.
const roots =
renderer.Mount._instancesByReactRootID ||
renderer.Mount._instancesByContainerID;
for (const key in roots) {
const internalInstance = roots[key];
const id = getID(internalInstance);
crawlAndRecordInitialMounts(id, 0, id);
flushPendingEvents(id);
}
}
const pendingOperations: Array<number> = [];
const pendingStringTable: Map<string, number> = new Map();
let pendingUnmountedIDs: Array<number> = [];
let pendingStringTableLength: number = 0;
let pendingUnmountedRootID: number | null = null;
function flushPendingEvents(rootID: number) {
if (
pendingOperations.length === 0 &&
pendingUnmountedIDs.length === 0 &&
pendingUnmountedRootID === null
) {
return;
}
const numUnmountIDs =
pendingUnmountedIDs.length + (pendingUnmountedRootID === null ? 0 : 1);
const operations = new Array(
// Identify which renderer this update is coming from.
2 + // [rendererID, rootFiberID]
// How big is the string table?
1 + // [stringTableLength]
// Then goes the actual string table.
pendingStringTableLength +
// All unmounts are batched in a single message.
// [TREE_OPERATION_REMOVE, removedIDLength, ...ids]
(numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +
// Mount operations
pendingOperations.length,
);
// Identify which renderer this update is coming from.
// This enables roots to be mapped to renderers,
// Which in turn enables fiber properations, states, and hooks to be inspected.
let i = 0;
operations[i++] = rendererID;
operations[i++] = rootID;
// Now fill in the string table.
// [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]
operations[i++] = pendingStringTableLength;
pendingStringTable.forEach((value, key) => {
operations[i++] = key.length;
const encodedKey = utfEncodeString(key);
for (let j = 0; j < encodedKey.length; j++) {
operations[i + j] = encodedKey[j];
}
i += key.length;
});
if (numUnmountIDs > 0) {
// All unmounts except roots are batched in a single message.
operations[i++] = TREE_OPERATION_REMOVE;
// The first number is how many unmounted IDs we're gonna send.
operations[i++] = numUnmountIDs;
// Fill in the unmounts
for (let j = 0; j < pendingUnmountedIDs.length; j++) {
operations[i++] = pendingUnmountedIDs[j];
}
// The root ID should always be unmounted last.
if (pendingUnmountedRootID !== null) {
operations[i] = pendingUnmountedRootID;
i++;
}
}
// Fill in the rest of the operations.
for (let j = 0; j < pendingOperations.length; j++) {
operations[i + j] = pendingOperations[j];
}
i += pendingOperations.length;
if (__DEBUG__) {
printOperationsArray(operations);
}
// If we've already connected to the frontend, just pass the operations through.
hook.emit('operations', operations);
pendingOperations.length = 0;
pendingUnmountedIDs = [];
pendingUnmountedRootID = null;
pendingStringTable.clear();
pendingStringTableLength = 0;
}
function pushOperation(op: number): void {
if (__DEV__) {
if (!Number.isInteger(op)) {
console.error(
'pushOperation() was called but the value is not an integer.',
op,
);
}
}
pendingOperations.push(op);
}
function getStringID(str: string | null): number {
if (str === null) {
return 0;
}
const existingID = pendingStringTable.get(str);
if (existingID !== undefined) {
return existingID;
}
const stringID = pendingStringTable.size + 1;
pendingStringTable.set(str, stringID);
// The string table total length needs to account
// both for the string length, and for the array item
// that contains the length itself. Hence + 1.
pendingStringTableLength += str.length + 1;
return stringID;
}
let currentlyInspectedElementID: number | null = null;
let currentlyInspectedPaths: Object = {};
// Track the intersection of currently inspected paths,
// so that we can send their data along if the element is re-rendered.
function mergeInspectedPaths(path: Array<string | number>) {
let current = currentlyInspectedPaths;
path.forEach(key => {
if (!current[key]) {
current[key] = {};
}
current = current[key];
});
}
function createIsPathAllowed(key: string) {
// This function helps prevent previously-inspected paths from being dehydrated in updates.
// This is important to avoid a bad user experience where expanded toggles collapse on update.
return function isPathAllowed(path: Array<string | number>): boolean {
let current = currentlyInspectedPaths[key];
if (!current) {
return false;
}
for (let i = 0; i < path.length; i++) {
current = current[path[i]];
if (!current) {
return false;
}
}
return true;
};
}
// Fast path props lookup for React Native style editor.
function getInstanceAndStyle(id: number): InstanceAndStyle {
let instance = null;
let style = null;
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance != null) {
instance = internalInstance._instance || null;
const element = internalInstance._currentElement;
if (element != null && element.props != null) {
style = element.props.style || null;
}
}
return {
instance,
style,
};
}
function updateSelectedElement(id: number): void {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance == null) {
console.warn(`Could not find instance with id "${id}"`);
return;
}
switch (getElementType(internalInstance)) {
case ElementTypeClass:
global.$r = internalInstance._instance;
break;
case ElementTypeFunction:
const element = internalInstance._currentElement;
if (element == null) {
console.warn(`Could not find element with id "${id}"`);
return;
}
global.$r = {
props: element.props,
type: element.type,
};
break;
default:
global.$r = null;
break;
}
}
function storeAsGlobal(
id: number,
path: Array<string | number>,
count: number,
): void {
const inspectedElement = inspectElementRaw(id);
if (inspectedElement !== null) {
const value = getInObject(inspectedElement, path);
const key = `$reactTemp${count}`;
window[key] = value;
console.log(key);
console.log(value);
}
}
function copyElementPath(id: number, path: Array<string | number>): void {
const inspectedElement = inspectElementRaw(id);
if (inspectedElement !== null) {
copyToClipboard(getInObject(inspectedElement, path));
}
}
function inspectElement(
requestID: number,
id: number,
path: Array<string | number> | null,
): InspectedElementPayload {
if (currentlyInspectedElementID !== id) {
currentlyInspectedElementID = id;
currentlyInspectedPaths = {};
}
const inspectedElement = inspectElementRaw(id);
if (inspectedElement === null) {
return {
id,
responseID: requestID,
type: 'not-found',
};
}
if (path !== null) {
mergeInspectedPaths(path);
}
// Any time an inspected element has an update,
// we should update the selected $r value as wel.
// Do this before dehyration (cleanForBridge).
updateSelectedElement(id);
inspectedElement.context = cleanForBridge(
inspectedElement.context,
createIsPathAllowed('context'),
);
inspectedElement.props = cleanForBridge(
inspectedElement.props,
createIsPathAllowed('props'),
);
inspectedElement.state = cleanForBridge(
inspectedElement.state,
createIsPathAllowed('state'),
);
return {
id,
responseID: requestID,
type: 'full-data',
value: inspectedElement,
};
}
function inspectElementRaw(id: number): InspectedElement | null {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance == null) {
return null;
}
const {displayName, key} = getData(internalInstance);
const type = getElementType(internalInstance);
let context = null;
let owners = null;
let props = null;
let state = null;
let source = null;
const element = internalInstance._currentElement;
if (element !== null) {
props = element.props;
source = element._source != null ? element._source : null;
let owner = element._owner;
if (owner) {
owners = [];
while (owner != null) {
owners.push({
displayName: getData(owner).displayName || 'Unknown',
id: getID(owner),
key: element.key,
type: getElementType(owner),
});
if (owner._currentElement) {
owner = owner._currentElement._owner;
}
}
}
}
const publicInstance = internalInstance._instance;
if (publicInstance != null) {
context = publicInstance.context || null;
state = publicInstance.state || null;
}
// Not implemented
const errors = [];
const warnings = [];
return {
id,
// Does the current renderer support editable hooks and function props?
canEditHooks: false,
canEditFunctionProps: false,
// Does the current renderer support advanced editing interface?
canEditHooksAndDeletePaths: false,
canEditHooksAndRenamePaths: false,
canEditFunctionPropsDeletePaths: false,
canEditFunctionPropsRenamePaths: false,
// Toggle error boundary did not exist in legacy versions
canToggleError: false,
isErrored: false,
targetErrorBoundaryID: null,
// Suspense did not exist in legacy versions
canToggleSuspense: false,
// Can view component source location.
canViewSource: type === ElementTypeClass || type === ElementTypeFunction,
// Only legacy context exists in legacy versions.
hasLegacyContext: true,
displayName: displayName,
type: type,
key: key != null ? key : null,
// Inspectable properties.
context,
hooks: null,
props,
state,
errors,
warnings,
// List of owners
owners,
// Location of component in source code.
source,
rootType: null,
rendererPackageName: null,
rendererVersion: null,
};
}
function logElementToConsole(id: number): void {
const result = inspectElementRaw(id);
if (result === null) {
console.warn(`Could not find element with id "${id}"`);
return;
}
const supportsGroup = typeof console.groupCollapsed === 'function';
if (supportsGroup) {
console.groupCollapsed(
`[Click to expand] %c<${result.displayName || 'Component'} />`,
// --dom-tag-name-color is the CSS variable Chrome styles HTML elements with in the console.
'color: var(--dom-tag-name-color); font-weight: normal;',
);
}
if (result.props !== null) {
console.log('Props:', result.props);
}
if (result.state !== null) {
console.log('State:', result.state);
}
if (result.context !== null) {
console.log('Context:', result.context);
}
const nativeNode = findNativeNodeForInternalID(id);
if (nativeNode !== null) {
console.log('Node:', nativeNode);
}
if (window.chrome || /firefox/i.test(navigator.userAgent)) {
console.log(
'Right-click any value to save it as a global variable for further inspection.',
);
}
if (supportsGroup) {
console.groupEnd();
}
}
function prepareViewAttributeSource(
id: number,
path: Array<string | number>,
): void {
const inspectedElement = inspectElementRaw(id);
if (inspectedElement !== null) {
window.$attribute = getInObject(inspectedElement, path);
}
}
function prepareViewElementSource(id: number): void {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance == null) {
console.warn(`Could not find instance with id "${id}"`);
return;
}
const element = internalInstance._currentElement;
if (element == null) {
console.warn(`Could not find element with id "${id}"`);
return;
}
global.$type = element.type;
}
function deletePath(
type: 'context' | 'hooks' | 'props' | 'state',
id: number,
hookID: ?number,
path: Array<string | number>,
): void {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance != null) {
const publicInstance = internalInstance._instance;
if (publicInstance != null) {
switch (type) {
case 'context':
deletePathInObject(publicInstance.context, path);
forceUpdate(publicInstance);
break;
case 'hooks':
throw new Error('Hooks not supported by this renderer');
case 'props':
const element = internalInstance._currentElement;
internalInstance._currentElement = {
...element,
props: copyWithDelete(element.props, path),
};
forceUpdate(publicInstance);
break;
case 'state':
deletePathInObject(publicInstance.state, path);
forceUpdate(publicInstance);
break;
}
}
}
}
function renamePath(
type: 'context' | 'hooks' | 'props' | 'state',
id: number,
hookID: ?number,
oldPath: Array<string | number>,
newPath: Array<string | number>,
): void {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance != null) {
const publicInstance = internalInstance._instance;
if (publicInstance != null) {
switch (type) {
case 'context':
renamePathInObject(publicInstance.context, oldPath, newPath);
forceUpdate(publicInstance);
break;
case 'hooks':
throw new Error('Hooks not supported by this renderer');
case 'props':
const element = internalInstance._currentElement;
internalInstance._currentElement = {
...element,
props: copyWithRename(element.props, oldPath, newPath),
};
forceUpdate(publicInstance);
break;
case 'state':
renamePathInObject(publicInstance.state, oldPath, newPath);
forceUpdate(publicInstance);
break;
}
}
}
}
function overrideValueAtPath(
type: 'context' | 'hooks' | 'props' | 'state',
id: number,
hookID: ?number,
path: Array<string | number>,
value: any,
): void {
const internalInstance = idToInternalInstanceMap.get(id);
if (internalInstance != null) {
const publicInstance = internalInstance._instance;
if (publicInstance != null) {
switch (type) {
case 'context':
setInObject(publicInstance.context, path, value);
forceUpdate(publicInstance);
break;
case 'hooks':
throw new Error('Hooks not supported by this renderer');
case 'props':
const element = internalInstance._currentElement;
internalInstance._currentElement = {
...element,
props: copyWithSet(element.props, path, value),
};
forceUpdate(publicInstance);
break;
case 'state':
setInObject(publicInstance.state, path, value);
forceUpdate(publicInstance);
break;
}
}
}
}
// v16+ only features
const getProfilingData = () => {
throw new Error('getProfilingData not supported by this renderer');
};
const handleCommitFiberRoot = () => {
throw new Error('handleCommitFiberRoot not supported by this renderer');
};
const handleCommitFiberUnmount = () => {
throw new Error('handleCommitFiberUnmount not supported by this renderer');
};
const handlePostCommitFiberRoot = () => {
throw new Error('handlePostCommitFiberRoot not supported by this renderer');
};
const overrideError = () => {
throw new Error('overrideError not supported by this renderer');
};
const overrideSuspense = () => {
throw new Error('overrideSuspense not supported by this renderer');
};
const startProfiling = () => {
// Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
};
const stopProfiling = () => {
// Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
};
function getBestMatchForTrackedPath(): PathMatch | null {
// Not implemented.
return null;
}
function getPathForElement(id: number): Array<PathFrame> | null {
// Not implemented.
return null;
}
function updateComponentFilters(componentFilters: Array<ComponentFilter>) {
// Not implemented.
}
function setTraceUpdatesEnabled(enabled: boolean) {
// Not implemented.
}
function setTrackedPath(path: Array<PathFrame> | null) {
// Not implemented.
}
function getOwnersList(id: number): Array<SerializedElement> | null {
// Not implemented.
return null;
}
function clearErrorsAndWarnings() {
// Not implemented
}
function clearErrorsForFiberID(id: number) {
// Not implemented
}
function clearWarningsForFiberID(id: number) {
// Not implemented
}
function patchConsoleForStrictMode() {}
function unpatchConsoleForStrictMode() {}
return {
clearErrorsAndWarnings,
clearErrorsForFiberID,
clearWarningsForFiberID,
cleanup,
copyElementPath,
deletePath,
flushInitialOperations,
getBestMatchForTrackedPath,
getDisplayNameForFiberID,
getFiberIDForNative: getInternalIDForNative,
getInstanceAndStyle,
findNativeNodesForFiberID: (id: number) => {
const nativeNode = findNativeNodeForInternalID(id);
return nativeNode == null ? null : [nativeNode];
},
getOwnersList,
getPathForElement,
getProfilingData,
handleCommitFiberRoot,
handleCommitFiberUnmount,
handlePostCommitFiberRoot,
inspectElement,
logElementToConsole,
overrideError,
overrideSuspense,
overrideValueAtPath,
renamePath,
patchConsoleForStrictMode,
prepareViewAttributeSource,
prepareViewElementSource,
renderer,
setTraceUpdatesEnabled,
setTrackedPath,
startProfiling,
stopProfiling,
storeAsGlobal,
unpatchConsoleForStrictMode,
updateComponentFilters,
};
}