Fix a memory leak in ReactComponentTreeDevtool

`ReactDebugTool` used to only call `purgeUnmountedComponents()` while profiling, so information about unmounted instances kept accumulating when not profiling.

Additionally, unmounting in React Native and rendering to string did not correctly clean up the devtool.

Finally, the tests tested the wrong behavior and relied on explicit `purgeUnmountedComponent()` calls.

To fix this, we:

* Test specifically that unmounting is enough to clean up the tree devtool.
* Add missing `onBeginFlush` and `onEndFlush` calls to server and native rendering so `ReactDebugTool` knows when to copy the tree.

Fixes #6750
This commit is contained in:
Dan Abramov
2016-05-12 03:19:55 +01:00
parent 027d9a919b
commit 3779a5d18c
9 changed files with 182 additions and 112 deletions
+31 -22
View File
@@ -48,45 +48,54 @@ var currentTimerDebugID = null;
var currentTimerStartTime = null;
var currentTimerType = null;
function clearHistory() {
ReactComponentTreeDevtool.purgeUnmountedComponents();
ReactNativeOperationHistoryDevtool.clearHistory();
}
function getTreeSnapshot(registeredIDs) {
return registeredIDs.reduce((tree, id) => {
var ownerID = ReactComponentTreeDevtool.getOwnerID(id);
var parentID = ReactComponentTreeDevtool.getParentID(id);
tree[id] = {
displayName: ReactComponentTreeDevtool.getDisplayName(id),
text: ReactComponentTreeDevtool.getText(id),
updateCount: ReactComponentTreeDevtool.getUpdateCount(id),
childIDs: ReactComponentTreeDevtool.getChildIDs(id),
// Text nodes don't have owners but this is close enough.
ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID),
parentID,
};
return tree;
}, {});
}
function resetMeasurements() {
if (__DEV__) {
if (!isProfiling || currentFlushNesting === 0) {
currentFlushStartTime = null;
currentFlushMeasurements = null;
return;
}
var previousStartTime = currentFlushStartTime;
var previousMeasurements = currentFlushMeasurements || [];
var previousOperations = ReactNativeOperationHistoryDevtool.getHistory();
if (!isProfiling || currentFlushNesting === 0) {
currentFlushStartTime = null;
currentFlushMeasurements = null;
clearHistory();
return;
}
if (previousMeasurements.length || previousOperations.length) {
var registeredIDs = ReactComponentTreeDevtool.getRegisteredIDs();
flushHistory.push({
duration: performanceNow() - previousStartTime,
measurements: previousMeasurements || [],
operations: previousOperations || [],
treeSnapshot: registeredIDs.reduce((tree, id) => {
var ownerID = ReactComponentTreeDevtool.getOwnerID(id);
var parentID = ReactComponentTreeDevtool.getParentID(id);
tree[id] = {
displayName: ReactComponentTreeDevtool.getDisplayName(id),
text: ReactComponentTreeDevtool.getText(id),
updateCount: ReactComponentTreeDevtool.getUpdateCount(id),
childIDs: ReactComponentTreeDevtool.getChildIDs(id),
// Text nodes don't have owners but this is close enough.
ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID),
parentID,
};
return tree;
}, {}),
treeSnapshot: getTreeSnapshot(registeredIDs),
});
}
clearHistory();
currentFlushStartTime = performanceNow();
currentFlushMeasurements = [];
ReactComponentTreeDevtool.purgeUnmountedComponents();
ReactNativeOperationHistoryDevtool.clearHistory();
}
}
@@ -241,6 +241,21 @@ describe('ReactPerf', function() {
});
});
it('should include stats for components unmounted during measurement', function() {
var container = document.createElement('div');
var measurements = measure(() => {
ReactDOM.render(<Div><Div key="a" /></Div>, container);
ReactDOM.render(<Div><Div key="b" /></Div>, container);
});
expect(ReactPerf.getExclusive(measurements)).toEqual([{
key: 'Div',
instanceCount: 3,
counts: { ctor: 3, render: 4 },
durations: { ctor: 3, render: 4 },
totalDuration: 7,
}]);
});
it('warns once when using getMeasurementsSummaryMap', function() {
var measurements = measure(() => {});
spyOn(console, 'error');
@@ -106,6 +106,11 @@ var ReactComponentTreeDevtool = {
},
purgeUnmountedComponents() {
if (ReactComponentTreeDevtool._preventPurging) {
// Should only be used for testing.
return;
}
Object.keys(tree)
.filter(id => !tree[id].isMounted)
.forEach(purgeDeep);
@@ -23,6 +23,11 @@ var ReactNativeOperationHistoryDevtool = {
},
clearHistory() {
if (ReactNativeOperationHistoryDevtool._preventClearing) {
// Should only be used for tests.
return;
}
history = [];
},
@@ -97,13 +97,20 @@ describe('ReactComponentTreeDevtool', () => {
// Ensure the tree is correct on every step.
pairs.forEach(([element, expectedTree]) => {
currentElement = element;
// Mount a new tree or update the existing tree.
ReactDOM.render(<Wrapper />, node);
expect(getActualTree()).toEqual(expectedTree);
// Purging should have no effect
// on the tree we expect to see.
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getActualTree()).toEqual(expectedTree);
});
// Unmounting the root node should purge
// the whole subtree automatically.
ReactDOM.unmountComponentAtNode(node);
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getActualTree()).toBe(undefined);
expect(getRootDisplayNames()).toEqual([]);
expect(getRegisteredDisplayNames()).toEqual([]);
@@ -112,8 +119,23 @@ describe('ReactComponentTreeDevtool', () => {
// Ensure the tree is correct on every step.
pairs.forEach(([element, expectedTree]) => {
currentElement = element;
// Rendering to string should not produce any entries
// because ReactDebugTool purges it when the flush ends.
ReactDOMServer.renderToString(<Wrapper />);
expect(getActualTree()).toBe(undefined);
expect(getRootDisplayNames()).toEqual([]);
expect(getRegisteredDisplayNames()).toEqual([]);
// To test it, we tell the devtool to ignore next purge
// so the cleanup request by ReactDebugTool is ignored.
// This lets us make assertions on the actual tree.
ReactComponentTreeDevtool._preventPurging = true;
ReactDOMServer.renderToString(<Wrapper />);
ReactComponentTreeDevtool._preventPurging = false;
expect(getActualTree()).toEqual(expectedTree);
// Purge manually since we skipped the automatic purge.
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getActualTree()).toBe(undefined);
expect(getRootDisplayNames()).toEqual([]);
@@ -1631,7 +1653,7 @@ describe('ReactComponentTreeDevtool', () => {
assertTreeMatches([element, tree], {includeOwnerDisplayName: true});
});
it('preserves unmounted components until purge', () => {
it('purges unmounted components automatically', () => {
var node = document.createElement('div');
var renderBar = true;
var fooInstance;
@@ -1666,31 +1688,15 @@ describe('ReactComponentTreeDevtool', () => {
renderBar = false;
ReactDOM.render(<Foo />, node);
expect(
getTree(barInstance._debugID, {
includeParentDisplayName: true,
expectedParentID: fooInstance._debugID,
})
getTree(barInstance._debugID, {expectedParentID: null})
).toEqual({
displayName: 'Bar',
parentDisplayName: 'Foo',
displayName: 'Unknown',
children: [],
});
ReactDOM.unmountComponentAtNode(node);
expect(
getTree(barInstance._debugID, {
includeParentDisplayName: true,
expectedParentID: fooInstance._debugID,
})
).toEqual({
displayName: 'Bar',
parentDisplayName: 'Foo',
children: [],
});
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(
getTree(barInstance._debugID, {includeParentDisplayName: true})
getTree(barInstance._debugID, {expectedParentID: null})
).toEqual({
displayName: 'Unknown',
children: [],
@@ -1719,7 +1725,7 @@ describe('ReactComponentTreeDevtool', () => {
ReactDOM.unmountComponentAtNode(node);
expect(ReactComponentTreeDevtool.getUpdateCount(divID)).toEqual(0);
expect(ReactComponentTreeDevtool.getUpdateCount(spanID)).toEqual(2);
expect(ReactComponentTreeDevtool.getUpdateCount(spanID)).toEqual(0);
});
it('does not report top-level wrapper as a root', () => {
@@ -1733,12 +1739,6 @@ describe('ReactComponentTreeDevtool', () => {
ReactDOM.unmountComponentAtNode(node);
expect(getRootDisplayNames()).toEqual([]);
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getRootDisplayNames()).toEqual([]);
// This currently contains TopLevelWrapper until purge
// so we only check it at the very end.
expect(getRegisteredDisplayNames()).toEqual([]);
});
});
@@ -119,13 +119,20 @@ describe('ReactComponentTreeDevtool', () => {
// Ensure the tree is correct on every step.
pairs.forEach(([element, expectedTree]) => {
currentElement = element;
// Mount a new tree or update the existing tree.
ReactNative.render(<Wrapper />, 1);
expect(getActualTree()).toEqual(expectedTree);
// Purging should have no effect
// on the tree we expect to see.
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getActualTree()).toEqual(expectedTree);
});
// Unmounting the root node should purge
// the whole subtree automatically.
ReactNative.unmountComponentAtNode(1);
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getActualTree()).toBe(undefined);
expect(getRootDisplayNames()).toEqual([]);
expect(getRegisteredDisplayNames()).toEqual([]);
@@ -134,10 +141,13 @@ describe('ReactComponentTreeDevtool', () => {
// Ensure the tree is correct on every step.
pairs.forEach(([element, expectedTree]) => {
currentElement = element;
// Mount a new tree.
ReactNative.render(<Wrapper />, 1);
ReactNative.unmountComponentAtNode(1);
expect(getActualTree()).toEqual(expectedTree);
ReactComponentTreeDevtool.purgeUnmountedComponents();
// Unmounting should clean it up.
ReactNative.unmountComponentAtNode(1);
expect(getActualTree()).toBe(undefined);
expect(getRootDisplayNames()).toEqual([]);
expect(getRegisteredDisplayNames()).toEqual([]);
@@ -1620,7 +1630,7 @@ describe('ReactComponentTreeDevtool', () => {
assertTreeMatches([element, tree], {includeOwnerDisplayName: true});
});
it('preserves unmounted components until purge', () => {
it('purges unmounted components automatically', () => {
var renderBar = true;
var fooInstance;
var barInstance;
@@ -1654,31 +1664,15 @@ describe('ReactComponentTreeDevtool', () => {
renderBar = false;
ReactNative.render(<Foo />, 1);
expect(
getTree(barInstance._debugID, {
includeParentDisplayName: true,
expectedParentID: fooInstance._debugID,
})
getTree(barInstance._debugID, {expectedParentID: null})
).toEqual({
displayName: 'Bar',
parentDisplayName: 'Foo',
displayName: 'Unknown',
children: [],
});
ReactNative.unmountComponentAtNode(1);
expect(
getTree(barInstance._debugID, {
includeParentDisplayName: true,
expectedParentID: fooInstance._debugID,
})
).toEqual({
displayName: 'Bar',
parentDisplayName: 'Foo',
children: [],
});
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(
getTree(barInstance._debugID, {includeParentDisplayName: true})
getTree(barInstance._debugID, {expectedParentID: null})
).toEqual({
displayName: 'Unknown',
children: [],
@@ -1705,7 +1699,7 @@ describe('ReactComponentTreeDevtool', () => {
ReactNative.unmountComponentAtNode(1);
expect(ReactComponentTreeDevtool.getUpdateCount(viewID)).toEqual(0);
expect(ReactComponentTreeDevtool.getUpdateCount(imageID)).toEqual(2);
expect(ReactComponentTreeDevtool.getUpdateCount(imageID)).toEqual(0);
});
it('does not report top-level wrapper as a root', () => {
@@ -1717,12 +1711,6 @@ describe('ReactComponentTreeDevtool', () => {
ReactNative.unmountComponentAtNode(1);
expect(getRootDisplayNames()).toEqual([]);
ReactComponentTreeDevtool.purgeUnmountedComponents();
expect(getRootDisplayNames()).toEqual([]);
// This currently contains TopLevelWrapper until purge
// so we only check it at the very end.
expect(getRegisteredDisplayNames()).toEqual([]);
});
});
@@ -36,9 +36,10 @@ describe('ReactNativeOperationHistoryDevtool', () => {
describe('mount', () => {
it('gets recorded for native roots', () => {
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><p>Hi.</p></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'mount',
@@ -53,7 +54,10 @@ describe('ReactNativeOperationHistoryDevtool', () => {
return <div><p>Hi.</p></div>;
}
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
assertHistoryMatches([{
instanceID: inst._debugID,
@@ -70,6 +74,8 @@ describe('ReactNativeOperationHistoryDevtool', () => {
return null;
}
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
// Empty DOM components should be invisible to devtools.
@@ -82,11 +88,12 @@ describe('ReactNativeOperationHistoryDevtool', () => {
return element;
}
ReactNativeOperationHistoryDevtool._preventClearing = true;
var node = document.createElement('div');
element = null;
ReactDOM.render(<Foo />, node);
ReactNativeOperationHistoryDevtool.clearHistory();
element = <span />;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
@@ -104,12 +111,14 @@ describe('ReactNativeOperationHistoryDevtool', () => {
describe('update styles', () => {
it('gets recorded during mount', () => {
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div style={{
color: 'red',
backgroundColor: 'yellow',
}} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
if (ReactDOMFeatureFlags.useCreateElement) {
assertHistoryMatches([{
instanceID: inst._debugID,
@@ -138,7 +147,7 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div style={{ color: 'red' }} />, node);
ReactDOM.render(<div style={{
color: 'blue',
@@ -171,7 +180,7 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div style={{
color: 'red',
backgroundColor: 'yellow',
@@ -196,9 +205,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
describe('simple attribute', () => {
it('gets recorded during mount', () => {
var node = document.createElement('div');
ReactDOM.render(<div className="rad" tabIndex={42} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div className="rad" tabIndex={42} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
if (ReactDOMFeatureFlags.useCreateElement) {
assertHistoryMatches([{
instanceID: inst._debugID,
@@ -228,10 +239,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div className="rad" />, node);
ReactDOM.render(<div className="mad" tabIndex={42} />, node);
ReactDOM.render(<div tabIndex={43} />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'update attribute',
@@ -262,9 +274,10 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div disabled={true} />, node);
ReactDOM.render(<div disabled={false} />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'update attribute',
@@ -280,9 +293,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
describe('custom attribute', () => {
it('gets recorded during mount', () => {
var node = document.createElement('div');
ReactDOM.render(<div data-x="rad" data-y={42} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div data-x="rad" data-y={42} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
if (ReactDOMFeatureFlags.useCreateElement) {
assertHistoryMatches([{
instanceID: inst._debugID,
@@ -312,10 +327,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div data-x="rad" />, node);
ReactDOM.render(<div data-x="mad" data-y={42} />, node);
ReactDOM.render(<div data-y={43} />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'update attribute',
@@ -343,9 +359,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
describe('attribute on a web component', () => {
it('gets recorded during mount', () => {
var node = document.createElement('div');
ReactDOM.render(<my-component className="rad" tabIndex={42} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<my-component className="rad" tabIndex={42} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
if (ReactDOMFeatureFlags.useCreateElement) {
assertHistoryMatches([{
instanceID: inst._debugID,
@@ -375,10 +393,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<my-component />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<my-component className="rad" />, node);
ReactDOM.render(<my-component className="mad" tabIndex={42} />, node);
ReactDOM.render(<my-component tabIndex={43} />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'update attribute',
@@ -411,8 +430,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div>Hi.</div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Bye.</div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace text',
@@ -425,8 +445,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Bye.</div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace text',
@@ -439,8 +460,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div><span /><p /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Bye.</div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'remove child',
@@ -460,8 +482,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
var node = document.createElement('div');
ReactDOM.render(<div>Hi.</div>, node);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Hi.</div>, node);
assertHistoryMatches([]);
});
});
@@ -473,8 +496,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
var inst1 = ReactDOMComponentTree.getInstanceFromNode(node.firstChild.childNodes[0]);
var inst2 = ReactDOMComponentTree.getInstanceFromNode(node.firstChild.childNodes[3]);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>{'Bye.'}{43}</div>, node);
assertHistoryMatches([{
instanceID: inst1._debugID,
type: 'replace text',
@@ -490,8 +514,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
var node = document.createElement('div');
ReactDOM.render(<div>{'Hi.'}{42}</div>, node);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>{'Hi.'}{42}</div>, node);
assertHistoryMatches([]);
});
});
@@ -509,9 +534,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
element = <span />;
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace with',
@@ -528,11 +555,13 @@ describe('ReactNativeOperationHistoryDevtool', () => {
var node = document.createElement('div');
element = <span />;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
element = null;
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace with',
@@ -550,9 +579,11 @@ describe('ReactNativeOperationHistoryDevtool', () => {
element = <div />;
ReactDOM.render(<Foo />, node);
ReactNativeOperationHistoryDevtool.clearHistory();
element = <div />;
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
assertHistoryMatches([]);
});
});
@@ -563,11 +594,12 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div>Hi.</div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Bye.'}} />,
node
);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace children',
@@ -583,11 +615,12 @@ describe('ReactNativeOperationHistoryDevtool', () => {
);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Bye.'}} />,
node
);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace children',
@@ -600,11 +633,12 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div><span /><p /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />,
node
);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'remove child',
@@ -627,11 +661,12 @@ describe('ReactNativeOperationHistoryDevtool', () => {
node
);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />,
node
);
assertHistoryMatches([]);
});
});
@@ -642,8 +677,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div><span /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><span /><p /></div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'insert child',
@@ -658,8 +694,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div><span key="a" /><p key="b" /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><p key="b" /><span key="a" /></div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'move child',
@@ -674,8 +711,9 @@ describe('ReactNativeOperationHistoryDevtool', () => {
ReactDOM.render(<div><span key="a" /><p key="b" /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool.clearHistory();
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><span key="a" /></div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'remove child',
@@ -37,6 +37,9 @@ function renderToStringImpl(element, makeStaticMarkup) {
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);
return transaction.perform(function() {
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
var componentInstance = instantiateReactComponent(element);
var markup = ReactReconciler.mountComponent(
componentInstance,
@@ -49,6 +52,7 @@ function renderToStringImpl(element, makeStaticMarkup) {
ReactInstrumentation.debugTool.onUnmountComponent(
componentInstance._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}
if (!makeStaticMarkup) {
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
+6
View File
@@ -216,8 +216,14 @@ var ReactNativeMount = {
if (!instance) {
return false;
}
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
ReactNativeMount.unmountComponentFromNode(instance, containerTag);
delete ReactNativeMount._instancesByContainerID[containerTag];
if (__DEV__) {
ReactInstrumentation.debugTool.onEndFlush();
}
return true;
},