Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added trace updates feature (DOM only) #16989

Merged
merged 12 commits into from
Oct 3, 2019
1 change: 1 addition & 0 deletions packages/react-devtools-extensions/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ function createPanelIfReactLoaded() {
isProfiling,
supportsReloadAndProfile: isChrome,
supportsProfiling,
supportsTraceUpdates: true,
});
store.profilerStore.profilingData = profilingData;

Expand Down
2 changes: 1 addition & 1 deletion packages/react-devtools-inline/src/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function initialize(
},
});

const store: Store = new Store(bridge);
const store: Store = new Store(bridge, {supportsTraceUpdates: true});

const ForwardRef = forwardRef<Props, mixed>((props, ref) => (
<DevTools ref={ref} bridge={bridge} store={store} {...props} />
Expand Down
27 changes: 27 additions & 0 deletions packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import {
sessionStorageSetItem,
} from 'react-devtools-shared/src/storage';
import setupHighlighter from './views/Highlighter';
import {
initialize as setupTraceUpdates,
toggleEnabled as setTraceUpdatesEnabled,
} from './views/TraceUpdates';
import {patch as patchConsole, unpatch as unpatchConsole} from './console';

import type {BackendBridge} from 'react-devtools-shared/src/bridge';
Expand Down Expand Up @@ -87,13 +91,15 @@ export default class Agent extends EventEmitter<{|
hideNativeHighlight: [],
showNativeHighlight: [NativeType],
shutdown: [],
traceUpdates: [Set<NativeType>],
|}> {
_bridge: BackendBridge;
_isProfiling: boolean = false;
_recordChangeDescriptions: boolean = false;
_rendererInterfaces: {[key: RendererID]: RendererInterface} = {};
_persistedSelection: PersistedSelection | null = null;
_persistedSelectionMatch: PathMatch | null = null;
_traceUpdatesEnabled: boolean = false;

constructor(bridge: BackendBridge) {
super();
Expand Down Expand Up @@ -131,6 +137,7 @@ export default class Agent extends EventEmitter<{|
bridge.addListener('overrideState', this.overrideState);
bridge.addListener('overrideSuspense', this.overrideSuspense);
bridge.addListener('reloadAndProfile', this.reloadAndProfile);
bridge.addListener('setTraceUpdatesEnabled', this.setTraceUpdatesEnabled);
bridge.addListener('startProfiling', this.startProfiling);
bridge.addListener('stopProfiling', this.stopProfiling);
bridge.addListener(
Expand Down Expand Up @@ -159,6 +166,7 @@ export default class Agent extends EventEmitter<{|
bridge.send('isBackendStorageAPISupported', isBackendStorageAPISupported);

setupHighlighter(bridge, this);
setupTraceUpdates(this);
}

get rendererInterfaces(): {[key: RendererID]: RendererInterface} {
Expand Down Expand Up @@ -340,6 +348,8 @@ export default class Agent extends EventEmitter<{|
rendererInterface.startProfiling(this._recordChangeDescriptions);
}

rendererInterface.setTraceUpdatesEnabled(this._traceUpdatesEnabled);

// When the renderer is attached, we need to tell it whether
// we remember the previous selection that we'd like to restore.
// It'll start tracking mounts for matches to the last selection path.
Expand All @@ -349,6 +359,19 @@ export default class Agent extends EventEmitter<{|
}
}

setTraceUpdatesEnabled = (traceUpdatesEnabled: boolean) => {
this._traceUpdatesEnabled = traceUpdatesEnabled;

setTraceUpdatesEnabled(traceUpdatesEnabled);

for (let rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
(rendererID: any)
]: any): RendererInterface);
renderer.setTraceUpdatesEnabled(traceUpdatesEnabled);
}
};

syncSelectionFromNativeElementsPanel = () => {
const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
if (target == null) {
Expand Down Expand Up @@ -416,6 +439,10 @@ export default class Agent extends EventEmitter<{|
}
};

onTraceUpdates = (nodes: Set<NativeType>) => {
this.emit('traceUpdates', nodes);
};

onHookOperations = (operations: Array<number>) => {
if (__DEBUG__) {
debug('onHookOperations', operations);
Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools-shared/src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function initBackend(
}),

hook.sub('operations', agent.onHookOperations),
hook.sub('traceUpdates', agent.onTraceUpdates),

// TODO Add additional subscriptions required for profiling mode
];
Expand Down
5 changes: 5 additions & 0 deletions packages/react-devtools-shared/src/backend/legacy/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,10 @@ export function attach(
// Not implemented.
}

function setTraceUpdatesEnabled(enabled: boolean) {
// Not implemented.
}

function setTrackedPath(path: Array<PathFrame> | null) {
// Not implemented.
}
Expand Down Expand Up @@ -945,6 +949,7 @@ export function attach(
setInHook,
setInProps,
setInState,
setTraceUpdatesEnabled,
setTrackedPath,
startProfiling,
stopProfiling,
Expand Down
109 changes: 100 additions & 9 deletions packages/react-devtools-shared/src/backend/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import type {
InspectedElement,
InspectedElementPayload,
InstanceAndStyle,
NativeType,
Owner,
PathFrame,
PathMatch,
Expand Down Expand Up @@ -532,6 +533,10 @@ export function attach(
const hideElementsWithPaths: Set<RegExp> = new Set();
const hideElementsWithTypes: Set<ElementType> = new Set();

// Highlight updates
let traceUpdatesEnabled: boolean = false;
let traceUpdatesForNodes: Set<NativeType> = new Set();

function applyComponentFilters(componentFilters: Array<ComponentFilter>) {
hideElementsWithTypes.clear();
hideElementsWithDisplayNames.clear();
Expand Down Expand Up @@ -613,7 +618,7 @@ export function attach(
hook.getFiberRoots(rendererID).forEach(root => {
currentRootID = getFiberID(getPrimaryFiber(root.current));
setRootPseudoKey(currentRootID, root.current);
mountFiberRecursively(root.current, null);
mountFiberRecursively(root.current, null, false, false);
flushPendingEvents(root);
currentRootID = -1;
});
Expand Down Expand Up @@ -1227,7 +1232,8 @@ export function attach(
function mountFiberRecursively(
fiber: Fiber,
parentFiber: Fiber | null,
traverseSiblings = false,
traverseSiblings: boolean,
traceNearestHostComponentUpdate: boolean,
) {
if (__DEBUG__) {
debug('mountFiberRecursively()', fiber, parentFiber);
Expand All @@ -1244,6 +1250,20 @@ export function attach(
recordMount(fiber, parentFiber);
}

if (traceUpdatesEnabled) {
const elementType = getElementTypeForFiber(fiber);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this inside the if (traceNearestHostComponentUpdate) condition now that you only use it in one of the branches.

if (traceNearestHostComponentUpdate) {
// If an ancestor updated, we should mark the nearest host nodes for highlighting.
if (elementType === ElementTypeHostComponent) {
traceUpdatesForNodes.add(fiber.stateNode);
traceNearestHostComponentUpdate = false;
}
}

// We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch,
// because we don't want to highlight every host node inside of a newly mounted subtree.
}

const isTimedOutSuspense =
fiber.tag === ReactTypeOfWork.SuspenseComponent &&
fiber.memoizedState !== null;
Expand All @@ -1264,6 +1284,7 @@ export function attach(
fallbackChild,
shouldIncludeInTree ? fiber : parentFiber,
true,
traceNearestHostComponentUpdate,
);
}
} else {
Expand All @@ -1272,6 +1293,7 @@ export function attach(
fiber.child,
shouldIncludeInTree ? fiber : parentFiber,
true,
traceNearestHostComponentUpdate,
);
}
}
Expand All @@ -1281,7 +1303,12 @@ export function attach(
updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath);

if (traverseSiblings && fiber.sibling !== null) {
mountFiberRecursively(fiber.sibling, parentFiber, true);
mountFiberRecursively(
fiber.sibling,
parentFiber,
true,
traceNearestHostComponentUpdate,
);
}
}

Expand Down Expand Up @@ -1430,11 +1457,35 @@ export function attach(
nextFiber: Fiber,
prevFiber: Fiber,
parentFiber: Fiber | null,
traceNearestHostComponentUpdate: boolean,
): boolean {
if (__DEBUG__) {
debug('updateFiberRecursively()', nextFiber, parentFiber);
}

if (traceUpdatesEnabled) {
const elementType = getElementTypeForFiber(nextFiber);
if (traceNearestHostComponentUpdate) {
// If an ancestor updated, we should mark the nearest host nodes for highlighting.
if (elementType === ElementTypeHostComponent) {
traceUpdatesForNodes.add(nextFiber.stateNode);
traceNearestHostComponentUpdate = false;
}
} else {
if (
elementType === ElementTypeFunction ||
elementType === ElementTypeClass ||
elementType === ElementTypeContext
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
) {
// Otherwise if this is a traced ancestor, flag for the nearest host descendant(s).
traceNearestHostComponentUpdate = didFiberRender(
prevFiber,
nextFiber,
);
}
}
}

if (
mostRecentlyInspectedElement !== null &&
mostRecentlyInspectedElement.id ===
Expand Down Expand Up @@ -1481,6 +1532,7 @@ export function attach(
nextFallbackChildSet,
prevFallbackChildSet,
nextFiber,
traceNearestHostComponentUpdate,
)
) {
shouldResetChildren = true;
Expand All @@ -1492,7 +1544,12 @@ export function attach(
// 2. Mount primary set
const nextPrimaryChildSet = nextFiber.child;
if (nextPrimaryChildSet !== null) {
mountFiberRecursively(nextPrimaryChildSet, nextFiber, true);
mountFiberRecursively(
nextPrimaryChildSet,
nextFiber,
true,
traceNearestHostComponentUpdate,
);
}
shouldResetChildren = true;
} else if (!prevDidTimeout && nextDidTimeOut) {
Expand All @@ -1507,7 +1564,12 @@ export function attach(
? nextFiberChild.sibling
: null;
if (nextFallbackChildSet != null) {
mountFiberRecursively(nextFallbackChildSet, nextFiber, true);
mountFiberRecursively(
nextFallbackChildSet,
nextFiber,
true,
traceNearestHostComponentUpdate,
);
shouldResetChildren = true;
}
} else {
Expand All @@ -1530,6 +1592,7 @@ export function attach(
nextChild,
prevChild,
shouldIncludeInTree ? nextFiber : parentFiber,
traceNearestHostComponentUpdate,
)
) {
// If a nested tree child order changed but it can't handle its own
Expand All @@ -1547,6 +1610,8 @@ export function attach(
mountFiberRecursively(
nextChild,
shouldIncludeInTree ? nextFiber : parentFiber,
false,
traceNearestHostComponentUpdate,
);
shouldResetChildren = true;
}
Expand All @@ -1562,6 +1627,19 @@ export function attach(
if (prevChildAtSameIndex !== null) {
shouldResetChildren = true;
}
} else {
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 descendates for highlight.
if (traceNearestHostComponentUpdate) {
const hostFibers = findAllCurrentHostFibers(
getFiberID(getPrimaryFiber(nextFiber)),
);
hostFibers.forEach(hostFiber => {
traceUpdatesForNodes.add(hostFiber.stateNode);
});
}
}
}
}
if (shouldIncludeInTree) {
Expand Down Expand Up @@ -1645,7 +1723,7 @@ export function attach(
};
}

mountFiberRecursively(root.current, null);
mountFiberRecursively(root.current, null, false, false);
flushPendingEvents(root);
currentRootID = -1;
});
Expand All @@ -1671,6 +1749,10 @@ export function attach(
mightBeOnTrackedPath = true;
}

if (traceUpdatesEnabled) {
traceUpdatesForNodes.clear();
}

// Checking root.memoizedInteractions handles multi-renderer edge-case-
// where some v16 renderers support profiling and others don't.
const isProfilingSupported = root.memoizedInteractions != null;
Expand Down Expand Up @@ -1704,10 +1786,10 @@ export function attach(
if (!wasMounted && isMounted) {
// Mount a new root.
setRootPseudoKey(currentRootID, current);
mountFiberRecursively(current, null);
mountFiberRecursively(current, null, false, false);
} else if (wasMounted && isMounted) {
// Update an existing root.
updateFiberRecursively(current, alternate, null);
updateFiberRecursively(current, alternate, null, false);
} else if (wasMounted && !isMounted) {
// Unmount an existing root.
removeRootPseudoKey(currentRootID);
Expand All @@ -1716,7 +1798,7 @@ export function attach(
} else {
// Mount a new root.
setRootPseudoKey(currentRootID, current);
mountFiberRecursively(current, null);
mountFiberRecursively(current, null, false, false);
}

if (isProfiling && isProfilingSupported) {
Expand All @@ -1738,6 +1820,10 @@ export function attach(
// We're done here.
flushPendingEvents(root);

if (traceUpdatesEnabled) {
hook.emit('traceUpdates', traceUpdatesForNodes);
}

currentRootID = -1;
}

Expand Down Expand Up @@ -3015,6 +3101,10 @@ export function attach(
}
};

function setTraceUpdatesEnabled(isEnabled: boolean): void {
traceUpdatesEnabled = isEnabled;
}

return {
cleanup,
findNativeNodesForFiberID,
Expand All @@ -3036,6 +3126,7 @@ export function attach(
setInHook,
setInProps,
setInState,
setTraceUpdatesEnabled,
setTrackedPath,
startProfiling,
stopProfiling,
Expand Down
Loading