Merge pull request #6753 from facebook/fix-6750

Fix a memory leak in ReactComponentTreeDevtool
This commit is contained in:
Dan Abramov
2016-05-12 03:41:55 +01:00
9 changed files with 182 additions and 112 deletions

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();
}
}

View File

@@ -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');

View File

@@ -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);

View File

@@ -23,6 +23,11 @@ var ReactNativeOperationHistoryDevtool = {
},
clearHistory() {
if (ReactNativeOperationHistoryDevtool._preventClearing) {
// Should only be used for tests.
return;
}
history = [];
},

View File

@@ -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([]);
});
});

View File

@@ -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([]);
});
});

View File

@@ -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',

View File

@@ -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);

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;
},