import EventEmitter from '../events';
import {
SESSION_STORAGE_LAST_SELECTION_KEY,
UNKNOWN_SUSPENDERS_NONE,
__DEBUG__,
} from '../constants';
import setupHighlighter from './views/Highlighter';
import {
initialize as setupTraceUpdates,
toggleEnabled as setTraceUpdatesEnabled,
} from './views/TraceUpdates';
import {currentBridgeProtocol} from 'react-devtools-shared/src/bridge';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {
InstanceAndStyle,
HostInstance,
OwnersList,
PathFrame,
PathMatch,
RendererID,
RendererInterface,
DevToolsHookSettings,
InspectedElement,
} from './types';
import type {
ComponentFilter,
DehydratedData,
ElementType,
} from 'react-devtools-shared/src/frontend/types';
import type {GroupItem} from './views/TraceUpdates/canvas';
import {gte, isReactNativeEnvironment} from './utils';
import {
sessionStorageGetItem,
sessionStorageRemoveItem,
sessionStorageSetItem,
} from '../storage';
const debug = (methodName: string, ...args: Array<string>) => {
if (__DEBUG__) {
console.log(
`%cAgent %c${methodName}`,
'color: purple; font-weight: bold;',
'font-weight: bold;',
...args,
);
}
};
type ElementAndRendererID = {
id: number,
rendererID: number,
};
type StoreAsGlobalParams = {
count: number,
id: number,
path: Array<string | number>,
rendererID: number,
};
type CopyElementParams = {
id: number,
path: Array<string | number>,
rendererID: number,
};
type InspectElementParams = {
forceFullData: boolean,
id: number,
path: Array<string | number> | null,
rendererID: number,
requestID: number,
};
type InspectScreenParams = {
forceFullData: boolean,
id: number,
path: Array<string | number> | null,
requestID: number,
};
type OverrideHookParams = {
id: number,
hookID: number,
path: Array<string | number>,
rendererID: number,
wasForwarded?: boolean,
value: any,
};
type SetInParams = {
id: number,
path: Array<string | number>,
rendererID: number,
wasForwarded?: boolean,
value: any,
};
type PathType = 'props' | 'hooks' | 'state' | 'context';
type DeletePathParams = {
type: PathType,
hookID?: ?number,
id: number,
path: Array<string | number>,
rendererID: number,
};
type RenamePathParams = {
type: PathType,
hookID?: ?number,
id: number,
oldPath: Array<string | number>,
newPath: Array<string | number>,
rendererID: number,
};
type OverrideValueAtPathParams = {
type: PathType,
hookID?: ?number,
id: number,
path: Array<string | number>,
rendererID: number,
value: any,
};
type OverrideErrorParams = {
id: number,
rendererID: number,
forceError: boolean,
};
type OverrideSuspenseParams = {
id: number,
rendererID: number,
forceFallback: boolean,
};
type OverrideSuspenseMilestoneParams = {
suspendedSet: Array<number>,
};
type PersistedSelection = {
rendererID: number,
path: Array<PathFrame>,
};
function createEmptyInspectedScreen(
arbitraryRootID: number,
type: ElementType,
): InspectedElement {
const suspendedBy: DehydratedData = {
cleaned: [],
data: [],
unserializable: [],
};
return {
id: arbitraryRootID,
type: type,
isErrored: false,
errors: [],
warnings: [],
suspendedBy,
suspendedByRange: null,
unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
rootType: null,
plugins: {stylex: null},
nativeTag: null,
env: null,
source: null,
stack: null,
rendererPackageName: null,
rendererVersion: null,
key: null,
canEditFunctionProps: false,
canEditHooks: false,
canEditFunctionPropsDeletePaths: false,
canEditFunctionPropsRenamePaths: false,
canEditHooksAndDeletePaths: false,
canEditHooksAndRenamePaths: false,
canToggleError: false,
canToggleSuspense: false,
isSuspended: false,
hasLegacyContext: false,
context: null,
hooks: null,
props: null,
state: null,
owners: null,
};
}
function mergeRoots(
left: InspectedElement,
right: InspectedElement,
suspendedByOffset: number,
): void {
const leftSuspendedByRange = left.suspendedByRange;
const rightSuspendedByRange = right.suspendedByRange;
if (right.isErrored) {
left.isErrored = true;
}
for (let i = 0; i < right.errors.length; i++) {
left.errors.push(right.errors[i]);
}
for (let i = 0; i < right.warnings.length; i++) {
left.warnings.push(right.warnings[i]);
}
const leftSuspendedBy: DehydratedData = left.suspendedBy;
const {data, cleaned, unserializable} = (right.suspendedBy: DehydratedData);
const leftSuspendedByData = ((leftSuspendedBy.data: any): Array<mixed>);
const rightSuspendedByData = ((data: any): Array<mixed>);
for (let i = 0; i < rightSuspendedByData.length; i++) {
leftSuspendedByData.push(rightSuspendedByData[i]);
}
for (let i = 0; i < cleaned.length; i++) {
leftSuspendedBy.cleaned.push(
[suspendedByOffset + cleaned[i][0]].concat(cleaned[i].slice(1)),
);
}
for (let i = 0; i < unserializable.length; i++) {
leftSuspendedBy.unserializable.push(
[suspendedByOffset + unserializable[i][0]].concat(
unserializable[i].slice(1),
),
);
}
if (rightSuspendedByRange !== null) {
if (leftSuspendedByRange === null) {
left.suspendedByRange = [
rightSuspendedByRange[0],
rightSuspendedByRange[1],
];
} else {
if (rightSuspendedByRange[0] < leftSuspendedByRange[0]) {
leftSuspendedByRange[0] = rightSuspendedByRange[0];
}
if (rightSuspendedByRange[1] > leftSuspendedByRange[1]) {
leftSuspendedByRange[1] = rightSuspendedByRange[1];
}
}
}
}
export default class Agent extends EventEmitter<{
hideNativeHighlight: [],
showNativeHighlight: [HostInstance],
startInspectingNative: [],
stopInspectingNative: [],
shutdown: [],
traceUpdates: [Set<HostInstance>],
drawTraceUpdates: [Array<HostInstance>],
drawGroupedTraceUpdatesWithNames: [Array<Array<GroupItem>>],
disableTraceUpdates: [],
getIfHasUnsupportedRendererVersion: [],
updateHookSettings: [$ReadOnly<DevToolsHookSettings>],
getHookSettings: [],
}> {
_bridge: BackendBridge;
_isProfiling: boolean = false;
_rendererInterfaces: {[key: RendererID]: RendererInterface, ...} = {};
_persistedSelection: PersistedSelection | null = null;
_persistedSelectionMatch: PathMatch | null = null;
_traceUpdatesEnabled: boolean = false;
_onReloadAndProfile:
| ((recordChangeDescriptions: boolean, recordTimeline: boolean) => void)
| void;
constructor(
bridge: BackendBridge,
isProfiling: boolean = false,
onReloadAndProfile?: (
recordChangeDescriptions: boolean,
recordTimeline: boolean,
) => void,
) {
super();
this._isProfiling = isProfiling;
this._onReloadAndProfile = onReloadAndProfile;
const persistedSelectionString = sessionStorageGetItem(
SESSION_STORAGE_LAST_SELECTION_KEY,
);
if (persistedSelectionString != null) {
this._persistedSelection = JSON.parse(persistedSelectionString);
}
this._bridge = bridge;
bridge.addListener('clearErrorsAndWarnings', this.clearErrorsAndWarnings);
bridge.addListener('clearErrorsForElementID', this.clearErrorsForElementID);
bridge.addListener(
'clearWarningsForElementID',
this.clearWarningsForElementID,
);
bridge.addListener('copyElementPath', this.copyElementPath);
bridge.addListener('deletePath', this.deletePath);
bridge.addListener('getBackendVersion', this.getBackendVersion);
bridge.addListener('getBridgeProtocol', this.getBridgeProtocol);
bridge.addListener('getProfilingData', this.getProfilingData);
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
bridge.addListener('getOwnersList', this.getOwnersList);
bridge.addListener('inspectElement', this.inspectElement);
bridge.addListener('inspectScreen', this.inspectScreen);
bridge.addListener('logElementToConsole', this.logElementToConsole);
bridge.addListener('overrideError', this.overrideError);
bridge.addListener('overrideSuspense', this.overrideSuspense);
bridge.addListener(
'overrideSuspenseMilestone',
this.overrideSuspenseMilestone,
);
bridge.addListener('overrideValueAtPath', this.overrideValueAtPath);
bridge.addListener('reloadAndProfile', this.reloadAndProfile);
bridge.addListener('renamePath', this.renamePath);
bridge.addListener('setTraceUpdatesEnabled', this.setTraceUpdatesEnabled);
bridge.addListener('startProfiling', this.startProfiling);
bridge.addListener('stopProfiling', this.stopProfiling);
bridge.addListener('storeAsGlobal', this.storeAsGlobal);
bridge.addListener(
'syncSelectionFromBuiltinElementsPanel',
this.syncSelectionFromBuiltinElementsPanel,
);
bridge.addListener('shutdown', this.shutdown);
bridge.addListener('updateHookSettings', this.updateHookSettings);
bridge.addListener('getHookSettings', this.getHookSettings);
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
bridge.addListener('getEnvironmentNames', this.getEnvironmentNames);
bridge.addListener(
'getIfHasUnsupportedRendererVersion',
this.getIfHasUnsupportedRendererVersion,
);
bridge.addListener('overrideContext', this.overrideContext);
bridge.addListener('overrideHookState', this.overrideHookState);
bridge.addListener('overrideProps', this.overrideProps);
bridge.addListener('overrideState', this.overrideState);
setupHighlighter(bridge, this);
setupTraceUpdates(this);
bridge.send('backendInitialized');
if (this._isProfiling) {
bridge.send('profilingStatus', true);
}
}
get rendererInterfaces(): {[key: RendererID]: RendererInterface, ...} {
return this._rendererInterfaces;
}
clearErrorsAndWarnings: ({rendererID: RendererID}) => void = ({
rendererID,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}"`);
} else {
renderer.clearErrorsAndWarnings();
}
};
clearErrorsForElementID: ElementAndRendererID => void = ({
id,
rendererID,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}"`);
} else {
renderer.clearErrorsForElementID(id);
}
};
clearWarningsForElementID: ElementAndRendererID => void = ({
id,
rendererID,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}"`);
} else {
renderer.clearWarningsForElementID(id);
}
};
copyElementPath: CopyElementParams => void = ({
id,
path,
rendererID,
}: CopyElementParams) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
const value = renderer.getSerializedElementValueByPath(id, path);
if (value != null) {
this._bridge.send('saveToClipboard', value);
} else {
console.warn(`Unable to obtain serialized value for element "${id}"`);
}
}
};
deletePath: DeletePathParams => void = ({
hookID,
id,
path,
rendererID,
type,
}: DeletePathParams) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.deletePath(type, id, hookID, path);
}
};
getInstanceAndStyle({
id,
rendererID,
}: ElementAndRendererID): InstanceAndStyle | null {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}"`);
return null;
}
return renderer.getInstanceAndStyle(id);
}
getIDForHostInstance(
target: HostInstance,
onlySuspenseNodes?: boolean,
): null | {id: number, rendererID: number} {
if (isReactNativeEnvironment() || typeof target.nodeType !== 'number') {
for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
try {
const id = onlySuspenseNodes
? renderer.getSuspenseNodeIDForHostInstance(target)
: renderer.getElementIDForHostInstance(target);
if (id !== null) {
return {
id: id,
rendererID: +rendererID,
};
}
} catch (error) {
}
}
return null;
} else {
let bestMatch: null | Element = null;
let bestRenderer: null | RendererInterface = null;
let bestRendererID: number = 0;
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) {
bestMatch = nearestNode;
bestRenderer = renderer;
bestRendererID = +rendererID;
break;
}
if (bestMatch === null || bestMatch.contains(nearestNode)) {
bestMatch = nearestNode;
bestRenderer = renderer;
bestRendererID = +rendererID;
}
}
}
if (bestRenderer != null && bestMatch != null) {
try {
const id = onlySuspenseNodes
? bestRenderer.getSuspenseNodeIDForHostInstance(bestMatch)
: bestRenderer.getElementIDForHostInstance(bestMatch);
if (id !== null) {
return {
id,
rendererID: bestRendererID,
};
}
} catch (error) {
}
}
return null;
}
}
getComponentNameForHostInstance(target: HostInstance): string | null {
const match = this.getIDForHostInstance(target);
if (match !== null) {
const renderer = ((this._rendererInterfaces[
(match.rendererID: any)
]: any): RendererInterface);
return renderer.getDisplayNameForElementID(match.id);
}
return null;
}
getBackendVersion: () => void = () => {
const version = process.env.DEVTOOLS_VERSION;
if (version) {
this._bridge.send('backendVersion', version);
}
};
getBridgeProtocol: () => void = () => {
this._bridge.send('bridgeProtocol', currentBridgeProtocol);
};
getProfilingData: ({rendererID: RendererID}) => void = ({rendererID}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}"`);
}
this._bridge.send('profilingData', renderer.getProfilingData());
};
getProfilingStatus: () => void = () => {
this._bridge.send('profilingStatus', this._isProfiling);
};
getOwnersList: ElementAndRendererID => void = ({id, rendererID}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
const owners = renderer.getOwnersList(id);
this._bridge.send('ownersList', ({id, owners}: OwnersList));
}
};
inspectElement: InspectElementParams => void = ({
forceFullData,
id,
path,
rendererID,
requestID,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
this._bridge.send(
'inspectedElement',
renderer.inspectElement(requestID, id, path, forceFullData),
);
if (
this._persistedSelectionMatch === null ||
this._persistedSelectionMatch.id !== id
) {
this._persistedSelection = null;
this._persistedSelectionMatch = null;
renderer.setTrackedPath(null);
this._lastSelectedElementID = id;
this._lastSelectedRendererID = rendererID;
if (!this._persistSelectionTimerScheduled) {
this._persistSelectionTimerScheduled = true;
setTimeout(this._persistSelection, 1000);
}
}
}
};
inspectScreen: InspectScreenParams => void = ({
requestID,
id,
forceFullData,
path: screenPath,
}) => {
let inspectedScreen: InspectedElement | null = null;
let found = false;
let suspendedByOffset = 0;
let suspendedByPathIndex: number | null = null;
let rendererPath: InspectElementParams['path'] = null;
if (screenPath !== null && screenPath.length > 1) {
const secondaryCategory = screenPath[0];
if (secondaryCategory !== 'suspendedBy') {
throw new Error(
'Only hydrating suspendedBy paths is supported. This is a bug.',
);
}
if (typeof screenPath[1] !== 'number') {
throw new Error(
`Expected suspendedBy index to be a number. Received '${screenPath[1]}' instead. This is a bug.`,
);
}
suspendedByPathIndex = screenPath[1];
rendererPath = screenPath.slice(2);
}
for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
let path: InspectElementParams['path'] = null;
if (suspendedByPathIndex !== null && rendererPath !== null) {
const suspendedByPathRendererIndex =
suspendedByPathIndex - suspendedByOffset;
const rendererHasRequestedSuspendedByPath =
renderer.getElementAttributeByPath(id, [
'suspendedBy',
suspendedByPathRendererIndex,
]) !== undefined;
if (rendererHasRequestedSuspendedByPath) {
path = ['suspendedBy', suspendedByPathRendererIndex].concat(
rendererPath,
);
}
}
const inspectedRootsPayload = renderer.inspectElement(
requestID,
id,
path,
forceFullData,
);
switch (inspectedRootsPayload.type) {
case 'hydrated-path':
inspectedRootsPayload.path[1] += suspendedByOffset;
if (inspectedRootsPayload.value !== null) {
for (
let i = 0;
i < inspectedRootsPayload.value.cleaned.length;
i++
) {
inspectedRootsPayload.value.cleaned[i][1] += suspendedByOffset;
}
}
this._bridge.send('inspectedScreen', inspectedRootsPayload);
return;
case 'full-data':
const inspectedRoots = inspectedRootsPayload.value;
if (inspectedScreen === null) {
inspectedScreen = createEmptyInspectedScreen(
inspectedRoots.id,
inspectedRoots.type,
);
}
mergeRoots(inspectedScreen, inspectedRoots, suspendedByOffset);
const dehydratedSuspendedBy: DehydratedData =
inspectedRoots.suspendedBy;
const suspendedBy = ((dehydratedSuspendedBy.data: any): Array<mixed>);
suspendedByOffset += suspendedBy.length;
found = true;
break;
case 'no-change':
found = true;
const rootsSuspendedBy: Array<mixed> =
(renderer.getElementAttributeByPath(id, ['suspendedBy']): any);
suspendedByOffset += rootsSuspendedBy.length;
break;
case 'not-found':
break;
case 'error':
this._bridge.send('inspectedScreen', inspectedRootsPayload);
return;
}
}
if (inspectedScreen === null) {
if (found) {
this._bridge.send('inspectedScreen', {
type: 'no-change',
responseID: requestID,
id,
});
} else {
this._bridge.send('inspectedScreen', {
type: 'not-found',
responseID: requestID,
id,
});
}
} else {
this._bridge.send('inspectedScreen', {
type: 'full-data',
responseID: requestID,
id,
value: inspectedScreen,
});
}
};
logElementToConsole: ElementAndRendererID => void = ({id, rendererID}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.logElementToConsole(id);
}
};
overrideError: OverrideErrorParams => void = ({
id,
rendererID,
forceError,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.overrideError(id, forceError);
}
};
overrideSuspense: OverrideSuspenseParams => void = ({
id,
rendererID,
forceFallback,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.overrideSuspense(id, forceFallback);
}
};
overrideSuspenseMilestone: OverrideSuspenseMilestoneParams => void = ({
suspendedSet,
}) => {
for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
if (renderer.supportsTogglingSuspense) {
renderer.overrideSuspenseMilestone(suspendedSet);
}
}
};
overrideValueAtPath: OverrideValueAtPathParams => void = ({
hookID,
id,
path,
rendererID,
type,
value,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.overrideValueAtPath(type, id, hookID, path, value);
}
};
overrideContext: SetInParams => void = ({
id,
path,
rendererID,
wasForwarded,
value,
}) => {
if (!wasForwarded) {
this.overrideValueAtPath({
id,
path,
rendererID,
type: 'context',
value,
});
}
};
overrideHookState: OverrideHookParams => void = ({
id,
hookID,
path,
rendererID,
wasForwarded,
value,
}) => {
if (!wasForwarded) {
this.overrideValueAtPath({
id,
path,
rendererID,
type: 'hooks',
value,
});
}
};
overrideProps: SetInParams => void = ({
id,
path,
rendererID,
wasForwarded,
value,
}) => {
if (!wasForwarded) {
this.overrideValueAtPath({
id,
path,
rendererID,
type: 'props',
value,
});
}
};
overrideState: SetInParams => void = ({
id,
path,
rendererID,
wasForwarded,
value,
}) => {
if (!wasForwarded) {
this.overrideValueAtPath({
id,
path,
rendererID,
type: 'state',
value,
});
}
};
onReloadAndProfileSupportedByHost: () => void = () => {
this._bridge.send('isReloadAndProfileSupportedByBackend', true);
};
reloadAndProfile: ({
recordChangeDescriptions: boolean,
recordTimeline: boolean,
}) => void = ({recordChangeDescriptions, recordTimeline}) => {
if (typeof this._onReloadAndProfile === 'function') {
this._onReloadAndProfile(recordChangeDescriptions, recordTimeline);
}
this._bridge.send('reloadAppForProfiling');
};
renamePath: RenamePathParams => void = ({
hookID,
id,
newPath,
oldPath,
rendererID,
type,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.renamePath(type, id, hookID, oldPath, newPath);
}
};
selectNode(target: HostInstance | null): void {
const match = target !== null ? this.getIDForHostInstance(target) : null;
this._bridge.send(
'selectElement',
match !== null
? match.id
:
null,
);
}
registerRendererInterface(
rendererID: RendererID,
rendererInterface: RendererInterface,
) {
this._rendererInterfaces[rendererID] = rendererInterface;
rendererInterface.setTraceUpdatesEnabled(this._traceUpdatesEnabled);
const renderer = rendererInterface.renderer;
if (renderer !== null) {
const devRenderer = renderer.bundleType === 1;
const enableSuspenseTab =
devRenderer && gte(renderer.version, '19.3.0-canary');
if (enableSuspenseTab) {
this._bridge.send('enableSuspenseTab');
}
}
const selection = this._persistedSelection;
if (selection !== null && selection.rendererID === rendererID) {
rendererInterface.setTrackedPath(selection.path);
}
}
setTraceUpdatesEnabled: (traceUpdatesEnabled: boolean) => void =
traceUpdatesEnabled => {
this._traceUpdatesEnabled = traceUpdatesEnabled;
setTraceUpdatesEnabled(traceUpdatesEnabled);
for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
renderer.setTraceUpdatesEnabled(traceUpdatesEnabled);
}
};
syncSelectionFromBuiltinElementsPanel: () => void = () => {
const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
this.selectNode(target == null ? null : target);
};
shutdown: () => void = () => {
this.emit('shutdown');
this._bridge.removeAllListeners();
this.removeAllListeners();
};
startProfiling: ({
recordChangeDescriptions: boolean,
recordTimeline: boolean,
}) => void = ({recordChangeDescriptions, recordTimeline}) => {
this._isProfiling = true;
for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
renderer.startProfiling(recordChangeDescriptions, recordTimeline);
}
this._bridge.send('profilingStatus', this._isProfiling);
};
stopProfiling: () => void = () => {
this._isProfiling = false;
for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
renderer.stopProfiling();
}
this._bridge.send('profilingStatus', this._isProfiling);
};
stopInspectingNative: (selected: boolean) => void = selected => {
this._bridge.send('stopInspectingHost', selected);
};
storeAsGlobal: StoreAsGlobalParams => void = ({
count,
id,
path,
rendererID,
}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
renderer.storeAsGlobal(id, path, count);
}
};
updateHookSettings: (settings: $ReadOnly<DevToolsHookSettings>) => void =
settings => {
this.emit('updateHookSettings', settings);
};
getHookSettings: () => void = () => {
this.emit('getHookSettings');
};
onHookSettings: (settings: $ReadOnly<DevToolsHookSettings>) => void =
settings => {
this._bridge.send('hookSettings', settings);
};
updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void =
componentFilters => {
for (const rendererIDString in this._rendererInterfaces) {
const rendererID = +rendererIDString;
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
if (this._lastSelectedRendererID === rendererID) {
const path = renderer.getPathForElement(this._lastSelectedElementID);
if (path !== null) {
renderer.setTrackedPath(path);
this._persistedSelection = {
rendererID,
path,
};
}
}
renderer.updateComponentFilters(componentFilters);
}
const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
if (target != null) {
this.selectNode(target);
}
};
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);
};
onFastRefreshScheduled: () => void = () => {
if (__DEBUG__) {
debug('onFastRefreshScheduled');
}
this._bridge.send('fastRefreshScheduled');
};
onHookOperations: (operations: Array<number>) => void = operations => {
if (__DEBUG__) {
debug(
'onHookOperations',
`(${operations.length}) [${operations.join(', ')}]`,
);
}
this._bridge.send('operations', operations);
if (this._persistedSelection !== null) {
const rendererID = operations[0];
if (this._persistedSelection.rendererID === rendererID) {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}"`);
} else {
const prevMatch = this._persistedSelectionMatch;
const nextMatch = renderer.getBestMatchForTrackedPath();
this._persistedSelectionMatch = nextMatch;
const prevMatchID = prevMatch !== null ? prevMatch.id : null;
const nextMatchID = nextMatch !== null ? nextMatch.id : null;
if (prevMatchID !== nextMatchID) {
if (nextMatchID !== null) {
this._bridge.send('selectElement', nextMatchID);
}
}
if (nextMatch !== null && nextMatch.isFullMatch) {
this._persistedSelection = null;
this._persistedSelectionMatch = null;
renderer.setTrackedPath(null);
}
}
}
}
};
getIfHasUnsupportedRendererVersion: () => void = () => {
this.emit('getIfHasUnsupportedRendererVersion');
};
onUnsupportedRenderer() {
this._bridge.send('unsupportedRendererVersion');
}
_persistSelectionTimerScheduled: boolean = false;
_lastSelectedRendererID: number = -1;
_lastSelectedElementID: number = -1;
_persistSelection: any = () => {
this._persistSelectionTimerScheduled = false;
const rendererID = this._lastSelectedRendererID;
const id = this._lastSelectedElementID;
const renderer = this._rendererInterfaces[rendererID];
const path = renderer != null ? renderer.getPathForElement(id) : null;
if (path !== null) {
sessionStorageSetItem(
SESSION_STORAGE_LAST_SELECTION_KEY,
JSON.stringify(({rendererID, path}: PersistedSelection)),
);
} else {
sessionStorageRemoveItem(SESSION_STORAGE_LAST_SELECTION_KEY);
}
};
}