diff --git a/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js b/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js index 968fda10dab5..ab353bae54d5 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js @@ -334,4 +334,85 @@ describe('commit tree', () => { expect(commitTrees[1].nodes.size).toBe(2); // + }); }); + + it('should tolerate malformed operations without throwing', () => { + const {getCommitTree, invalidateCommitTrees} = require('react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder'); + const { + ElementTypeFunction, + ElementTypeRoot, + } = require('react-devtools-shared/src/frontend/types'); + + const rootID = 1; + const snapshots = new Map(); + snapshots.set(rootID, { + id: rootID, + children: [2], + displayName: 'Root', + hocDisplayNames: null, + key: null, + type: ElementTypeRoot, + compiledWithForget: false, + }); + snapshots.set(2, { + id: 2, + children: [], + displayName: 'Child', + hocDisplayNames: null, + key: null, + type: ElementTypeFunction, + compiledWithForget: false, + }); + const initialTreeBaseDurations = new Map(); + initialTreeBaseDurations.set(rootID, 0); + initialTreeBaseDurations.set(2, 0); + + const ops0 = [0, rootID, 0]; + const ops1 = [ + 0, rootID, 0, + 1, 2, ElementTypeFunction, rootID, 0, 0, 0, + 2, 1, 999, + 6, + 99, + ]; + + const profilingData = { + dataForRoots: new Map([ + [ + rootID, + { + commitData: [], + displayName: 'root', + initialTreeBaseDurations, + operations: [ops0, ops1], + rootID, + snapshots, + }, + ], + ]), + imported: false, + timelineData: [], + }; + + const fakeProfilerStore = {profilingData}; + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + invalidateCommitTrees(); + expect(() => { + const commitTree = getCommitTree({ + commitIndex: 1, + profilerStore: fakeProfilerStore, + rootID, + }); + expect(commitTree.nodes.size).toBe(2); + }).not.toThrow(); + + expect(errorSpy).toHaveBeenCalled(); + expect(warnSpy).toHaveBeenCalled(); + + errorSpy.mockRestore(); + warnSpy.mockRestore(); + }); + }); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js index 41c5c7a2c098..506f269cd06d 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js @@ -200,9 +200,11 @@ function updateTree( i += 3; if (nodes.has(id)) { - throw new Error( - `Commit tree already contains fiber "${id}". This is a bug in React DevTools.`, + // Duplicate add detected; log and skip instead of throwing to avoid crashing the UI. + console.error( + `Commit tree already contains fiber "${id}". Skipping duplicate add.`, ); + break; } if (type === ElementTypeRoot) { @@ -284,9 +286,11 @@ function updateTree( i++; if (!nodes.has(id)) { - throw new Error( - `Commit tree does not contain fiber "${id}". This is a bug in React DevTools.`, + // Missing node; log and skip this remove operation. + console.error( + `Commit tree does not contain fiber "${id}". Skipping remove.`, ); + continue; } const node = getClonedNode(id); @@ -311,7 +315,9 @@ function updateTree( break; } case TREE_OPERATION_REMOVE_ROOT: { - throw Error('Operation REMOVE_ROOT is not supported while profiling.'); + // Removing roots is not supported while profiling; log and ignore. + console.warn('Operation REMOVE_ROOT is not supported while profiling. Ignoring.'); + break; } case TREE_OPERATION_REORDER_CHILDREN: { id = ((operations[i + 1]: any): number); @@ -491,7 +497,10 @@ function updateTree( } default: - throw Error(`Unsupported Bridge operation "${operation}"`); + // Unsupported operation; log and stop processing further operations to avoid unpredictable state. + console.error(`Unsupported Bridge operation "${operation}". Ignoring remaining operations.`); + i = operations.length; + break; } }