@samitouri / QOS-React / commits / 203850013a

Don't wrap text in <span> elements

Instead, use opening and closing comment nodes to delimit text data.

Michael Wiencek committed Dec 30, 2015 at 02:01 UTC 203850013a42f1cde6fba1dea3c26c4b40e6cc90
11 files changed +297 -55
src/renderers/dom/client/ReactDOMComponentTree.js
+6 -4
@@ -87,10 +87,12 @@ function precacheChildNodes(inst, node) {
87 }
88 // We assume the child nodes are in the same order as the child instances.
89 for (; childNode !== null; childNode = childNode.nextSibling) {
90 - if (childNode.nodeType === 1 &&
91 - childNode.getAttribute(ATTR_NAME) === String(childID) ||
92 - childNode.nodeType === 8 &&
93 - childNode.nodeValue === ' react-empty: ' + childID + ' ') {
90 + if ((childNode.nodeType === 1 &&
91 + childNode.getAttribute(ATTR_NAME) === String(childID)) ||
92 + (childNode.nodeType === 8 &&
93 + childNode.nodeValue === ' react-text: ' + childID + ' ') ||
94 + (childNode.nodeType === 8 &&
95 + childNode.nodeValue === ' react-empty: ' + childID + ' ')) {
96 precacheNode(childInst, childNode);
97 continue outer;
98 }
src/renderers/dom/client/__tests__/ReactDOMComponentTree-test.js
+3 -1
@@ -100,7 +100,9 @@ describe('ReactDOMComponentTree', function() {
100 expect(renderAndGetInstance('main')._currentElement.type).toBe('main');
101
102 // This one's a text component!
103 - expect(renderAndGetInstance('span')._stringText).toBe('goodbye.');
103 + var root = renderAndQuery(null);
104 + var inst = ReactDOMComponentTree.getInstanceFromNode(root.children[0].childNodes[2]);
105 + expect(inst._stringText).toBe('goodbye.');
106
107 expect(renderAndGetClosest('b')._currentElement.type).toBe('main');
108 expect(renderAndGetClosest('img')._currentElement.type).toBe('main');
src/renderers/dom/client/utils/DOMChildrenOperations.js
+82 -2
@@ -22,6 +22,11 @@ var setInnerHTML = require('setInnerHTML');
22 var setTextContent = require('setTextContent');
23
24 function getNodeAfter(parentNode, node) {
25 + // Special case for text components, which return [open, close] comments
26 + // from getNativeNode.
27 + if (Array.isArray(node)) {
28 + node = node[1];
29 + }
30 return node ? node.nextSibling : parentNode.firstChild;
31 }
32
@@ -51,6 +56,78 @@ function insertLazyTreeChildAt(parentNode, childTree, referenceNode) {
56 DOMLazyTree.insertTreeBefore(parentNode, childTree, referenceNode);
57 }
58
59 +function moveChild(parentNode, childNode, referenceNode) {
60 + if (Array.isArray(childNode)) {
61 + moveDelimitedText(parentNode, childNode[0], childNode[1], referenceNode);
62 + } else {
63 + insertChildAt(parentNode, childNode, referenceNode);
64 + }
65 +}
66 +
67 +function removeChild(parentNode, childNode) {
68 + if (Array.isArray(childNode)) {
69 + var closingComment = childNode[1];
70 + childNode = childNode[0];
71 + removeDelimitedText(parentNode, childNode, closingComment);
72 + parentNode.removeChild(closingComment);
73 + }
74 + parentNode.removeChild(childNode);
75 +}
76 +
77 +function moveDelimitedText(
78 + parentNode,
79 + openingComment,
80 + closingComment,
81 + referenceNode
82 +) {
83 + var node = openingComment;
84 + while (true) {
85 + var nextNode = node.nextSibling;
86 + insertChildAt(parentNode, node, referenceNode);
87 + if (node === closingComment) {
88 + break;
89 + }
90 + node = nextNode;
91 + }
92 +}
93 +
94 +function removeDelimitedText(parentNode, startNode, closingComment) {
95 + while (true) {
96 + var node = startNode.nextSibling;
97 + if (node === closingComment) {
98 + // The closing comment is removed by ReactMultiChild.
99 + break;
100 + } else {
101 + parentNode.removeChild(node);
102 + }
103 + }
104 +}
105 +
106 +function replaceDelimitedText(openingComment, closingComment, stringText) {
107 + var parentNode = openingComment.parentNode;
108 + var nodeAfterComment = openingComment.nextSibling;
109 + if (nodeAfterComment === closingComment) {
110 + // There are no text nodes between the opening and closing comments; insert
111 + // a new one if stringText isn't empty.
112 + if (stringText) {
113 + insertChildAt(
114 + parentNode,
115 + document.createTextNode(stringText),
116 + nodeAfterComment
117 + );
118 + }
119 + } else {
120 + if (stringText) {
121 + // Set the text content of the first node after the opening comment, and
122 + // remove all following nodes up until the closing comment.
123 + setTextContent(nodeAfterComment, stringText);
124 + removeDelimitedText(parentNode, nodeAfterComment, closingComment);
125 + } else {
126 + removeDelimitedText(parentNode, openingComment, closingComment);
127 + }
128 + }
129 +}
130 +
131 /**
132 * Operations for updating with DOM children.
133 */
@@ -60,6 +137,8 @@ var DOMChildrenOperations = {
137
138 updateTextContent: setTextContent,
139
140 + replaceDelimitedText: replaceDelimitedText,
141 +
142 /**
143 * Updates a component's children by processing a series of updates. The
144 * update configurations are each expected to have a `parentNode` property.
@@ -79,7 +158,7 @@ var DOMChildrenOperations = {
158 );
159 break;
160 case ReactMultiChildUpdateTypes.MOVE_EXISTING:
82 - insertChildAt(
161 + moveChild(
162 parentNode,
163 update.fromNode,
164 getNodeAfter(parentNode, update.afterNode)
@@ -98,7 +177,7 @@ var DOMChildrenOperations = {
177 );
178 break;
179 case ReactMultiChildUpdateTypes.REMOVE_NODE:
101 - parentNode.removeChild(update.fromNode);
180 + removeChild(parentNode, update.fromNode);
181 break;
182 }
183 }
@@ -108,6 +187,7 @@ var DOMChildrenOperations = {
187
188 ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', {
189 updateTextContent: 'updateTextContent',
190 + replaceDelimitedText: 'replaceDelimitedText',
191 });
192
193 module.exports = DOMChildrenOperations;
src/renderers/dom/client/validateDOMNesting.js
+9 -4
@@ -384,6 +384,11 @@ if (__DEV__) {
384 }
385 didWarn[warnKey] = true;
386
387 + var tagDisplayName = childTag;
388 + if (childTag !== '#text') {
389 + tagDisplayName = '<' + childTag + '>';
390 + }
391 +
392 if (invalidParent) {
393 var info = '';
394 if (ancestorTag === 'table' && childTag === 'tr') {
@@ -393,9 +398,9 @@ if (__DEV__) {
398 }
399 warning(
400 false,
396 - 'validateDOMNesting(...): <%s> cannot appear as a child of <%s>. ' +
401 + 'validateDOMNesting(...): %s cannot appear as a child of <%s>. ' +
402 'See %s.%s',
398 - childTag,
403 + tagDisplayName,
404 ancestorTag,
405 ownerInfo,
406 info
@@ -403,9 +408,9 @@ if (__DEV__) {
408 } else {
409 warning(
410 false,
406 - 'validateDOMNesting(...): <%s> cannot appear as a descendant of ' +
411 + 'validateDOMNesting(...): %s cannot appear as a descendant of ' +
412 '<%s>. See %s.',
408 - childTag,
413 + tagDisplayName,
414 ancestorTag,
415 ownerInfo
416 );
src/renderers/dom/server/__tests__/ReactServerRendering-test.js
+4 -4
@@ -103,8 +103,8 @@ describe('ReactServerRendering', function() {
103 ID_ATTRIBUTE_NAME + '="[^"]+" ' +
104 ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
105 '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
106 - '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
107 - '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">child</span>' +
106 + '<!-- react-text: [0-9]+ -->My name is <!-- /react-text -->' +
107 + '<!-- react-text: [0-9]+ -->child<!-- /react-text -->' +
108 '</span>' +
109 '</div>'
110 );
@@ -153,8 +153,8 @@ describe('ReactServerRendering', function() {
153 '<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
154 ID_ATTRIBUTE_NAME + '="[^"]+" ' +
155 ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
156 - '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
157 - '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
156 + '<!-- react-text: [0-9]+ -->Component name: <!-- /react-text -->' +
157 + '<!-- react-text: [0-9]+ -->TestComponent<!-- /react-text -->' +
158 '</span>'
159 );
160 expect(lifecycle).toEqual(
src/renderers/dom/shared/ReactDOMTextComponent.js
+40 -18
@@ -13,7 +13,6 @@
13
14 var DOMChildrenOperations = require('DOMChildrenOperations');
15 var DOMLazyTree = require('DOMLazyTree');
16 -var DOMPropertyOperations = require('DOMPropertyOperations');
16 var ReactDOMComponentTree = require('ReactDOMComponentTree');
17 var ReactPerf = require('ReactPerf');
18
@@ -21,16 +20,14 @@ var assign = require('Object.assign');
20 var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
21 var validateDOMNesting = require('validateDOMNesting');
22
24 -var getNode = ReactDOMComponentTree.getNodeFromInstance;
25 -
23 /**
24 * Text nodes violate a couple assumptions that React makes about components:
25 *
26 * - When mounting text into the DOM, adjacent text nodes are merged.
27 * - Text nodes cannot be assigned a React root ID.
28 *
32 - * This component is used to wrap strings in elements so that they can undergo
33 - * the same reconciliation that is applied to elements.
29 + * This component is used to wrap strings between comment nodes so that they
30 + * can undergo the same reconciliation that is applied to elements.
31 *
32 * TODO: Investigate representing React components in the DOM with text nodes.
33 *
@@ -49,6 +46,8 @@ var ReactDOMTextComponent = function(text) {
46 // Properties
47 this._domID = null;
48 this._mountIndex = 0;
49 + this._openingComment = null;
50 + this._commentNodes = null;
51 };
52
53 assign(ReactDOMTextComponent.prototype, {
@@ -77,34 +76,44 @@ assign(ReactDOMTextComponent.prototype, {
76 if (parentInfo) {
77 // parentInfo should always be present except for the top-level
78 // component when server rendering
80 - validateDOMNesting('span', this, parentInfo);
79 + validateDOMNesting('#text', this, parentInfo);
80 }
81 }
82
83 var domID = nativeContainerInfo._idCounter++;
84 + var openingValue = ' react-text: ' + domID + ' ';
85 + var closingValue = ' /react-text ';
86 this._domID = domID;
87 this._nativeParent = nativeParent;
88 if (transaction.useCreateElement) {
89 var ownerDocument = nativeContainerInfo._ownerDocument;
89 - var el = ownerDocument.createElement('span');
90 - ReactDOMComponentTree.precacheNode(this, el);
91 - var lazyTree = DOMLazyTree(el);
92 - DOMLazyTree.queueText(lazyTree, this._stringText);
90 + var openingComment = ownerDocument.createComment(openingValue);
91 + var closingComment = ownerDocument.createComment(closingValue);
92 + var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
93 + DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
94 + if (this._stringText) {
95 + DOMLazyTree.queueChild(
96 + lazyTree,
97 + DOMLazyTree(ownerDocument.createTextNode(this._stringText))
98 + );
99 + }
100 + DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
101 + this._openingComment = openingComment;
102 + ReactDOMComponentTree.precacheNode(this, closingComment);
103 return lazyTree;
104 } else {
105 var escapedText = escapeTextContentForBrowser(this._stringText);
106
107 if (transaction.renderToStaticMarkup) {
98 - // Normally we'd wrap this in a `span` for the reasons stated above, but
99 - // since this is a situation where React won't take over (static pages),
100 - // we can simply return the text as it is.
108 + // Normally we'd wrap this between comment nodes for the reasons stated
109 + // above, but since this is a situation where React won't take over
110 + // (static pages), we can simply return the text as it is.
111 return escapedText;
112 }
113
114 return (
105 - '<span ' + DOMPropertyOperations.createMarkupForID(domID) + '>' +
106 - escapedText +
107 - '</span>'
115 + '<!--' + openingValue + '-->' + escapedText +
116 + '<!--' + closingValue + '-->'
117 );
118 }
119 },
@@ -125,16 +134,29 @@ assign(ReactDOMTextComponent.prototype, {
134 // and/or updateComponent to do the actual update for consistency with
135 // other component types?
136 this._stringText = nextStringText;
128 - DOMChildrenOperations.updateTextContent(getNode(this), nextStringText);
137 + var commentNodes = this.getNativeNode();
138 + DOMChildrenOperations.replaceDelimitedText(
139 + commentNodes[0],
140 + commentNodes[1],
141 + nextStringText
142 + );
143 }
144 }
145 },
146
147 getNativeNode: function() {
134 - return getNode(this);
148 + var nativeNode = this._commentNodes;
149 + if (nativeNode) {
150 + return nativeNode;
151 + }
152 + nativeNode = [this._openingComment, this._nativeNode];
153 + this._commentNodes = nativeNode;
154 + return nativeNode;
155 },
156
157 unmountComponent: function() {
158 + this._openingComment = null;
159 + this._commentNodes = null;
160 ReactDOMComponentTree.uncacheNode(this);
161 },
162
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+2 -2
@@ -1260,8 +1260,8 @@ describe('ReactDOMComponent', function() {
1260 'match the DOM tree generated by the browser.'
1261 );
1262 expect(console.error.argsForCall[1][0]).toBe(
1263 - 'Warning: validateDOMNesting(...): <span> cannot appear as a child ' +
1264 - 'of <table>. See Foo > table > span.'
1263 + 'Warning: validateDOMNesting(...): #text cannot appear as a child ' +
1264 + 'of <table>. See Foo > table > #text.'
1265 );
1266 });
1267
src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
+82 -12
@@ -13,26 +13,96 @@
13
14 var React;
15 var ReactDOM;
16 +var ReactDOMServer;
17
18 describe('ReactDOMTextComponent', function() {
19 beforeEach(function() {
20 React = require('React');
21 ReactDOM = require('ReactDOM');
22 + ReactDOMServer = require('ReactDOMServer');
23 });
24
25 it('updates a mounted text component in place', function() {
26 var el = document.createElement('div');
25 - var inst = ReactDOM.render(<div>{'foo'}{'bar'}</div>, el);
26 -
27 - var foo = ReactDOM.findDOMNode(inst).children[0];
28 - var bar = ReactDOM.findDOMNode(inst).children[1];
29 - expect(foo.tagName).toBe('SPAN');
30 - expect(bar.tagName).toBe('SPAN');
31 -
32 - inst = ReactDOM.render(<div>{'baz'}{'qux'}</div>, el);
33 - // After the update, the spans should have stayed in place (as opposed to
34 - // getting unmounted and remounted)
35 - expect(ReactDOM.findDOMNode(inst).children[0]).toBe(foo);
36 - expect(ReactDOM.findDOMNode(inst).children[1]).toBe(bar);
27 + var inst = ReactDOM.render(<div><span />{'foo'}{'bar'}</div>, el);
28 +
29 + var foo = ReactDOM.findDOMNode(inst).childNodes[2];
30 + var bar = ReactDOM.findDOMNode(inst).childNodes[5];
31 + expect(foo.data).toBe('foo');
32 + expect(bar.data).toBe('bar');
33 +
34 + inst = ReactDOM.render(<div><span />{'baz'}{'qux'}</div>, el);
35 + // After the update, the text nodes should have stayed in place (as opposed
36 + // to getting unmounted and remounted)
37 + expect(ReactDOM.findDOMNode(inst).childNodes[2]).toBe(foo);
38 + expect(ReactDOM.findDOMNode(inst).childNodes[5]).toBe(bar);
39 + expect(foo.data).toBe('baz');
40 + expect(bar.data).toBe('qux');
41 + });
42 +
43 + it('can be toggled in and out of the markup', function() {
44 + var el = document.createElement('div');
45 + var inst = ReactDOM.render(<div>{'foo'}<div />{'bar'}</div>, el);
46 +
47 + var container = ReactDOM.findDOMNode(inst);
48 + var childDiv = container.childNodes[3];
49 + var childNodes;
50 +
51 + inst = ReactDOM.render(<div>{null}<div />{null}</div>, el);
52 + container = ReactDOM.findDOMNode(inst);
53 + childNodes = container.childNodes;
54 + expect(childNodes.length).toBe(1);
55 + expect(childNodes[0]).toBe(childDiv);
56 +
57 + inst = ReactDOM.render(<div>{'foo'}<div />{'bar'}</div>, el);
58 + container = ReactDOM.findDOMNode(inst);
59 + childNodes = container.childNodes;
60 + expect(childNodes.length).toBe(7);
61 + expect(childNodes[1].data).toBe('foo');
62 + expect(childNodes[3]).toBe(childDiv);
63 + expect(childNodes[5].data).toBe('bar');
64 + });
65 +
66 + it('can reconcile text merged by Node.normalize()', function() {
67 + var el = document.createElement('div');
68 + var inst = ReactDOM.render(<div>{'foo'}{'bar'}{'baz'}</div>, el);
69 +
70 + var container = ReactDOM.findDOMNode(inst);
71 + container.normalize();
72 +
73 + inst = ReactDOM.render(<div>{'bar'}{'baz'}{'qux'}</div>, el);
74 + container = ReactDOM.findDOMNode(inst);
75 + expect(container.textContent).toBe('barbazqux');
76 + });
77 +
78 + it('can reconcile text from pre-rendered markup', function() {
79 + var el = document.createElement('div');
80 + var reactEl = <div>{'foo'}{'bar'}{'baz'}</div>;
81 + el.innerHTML = ReactDOMServer.renderToString(reactEl);
82 +
83 + ReactDOM.render(reactEl, el);
84 + expect(el.textContent).toBe('foobarbaz');
85 +
86 + reactEl = <div>{''}{''}{''}</div>;
87 + el.innerHTML = ReactDOMServer.renderToString(reactEl);
88 +
89 + ReactDOM.render(reactEl, el);
90 + expect(el.textContent).toBe('');
91 + });
92 +
93 + it('can reconcile text arbitrarily split into multiple nodes', function() {
94 + var el = document.createElement('div');
95 + var inst = ReactDOM.render(<div><span />{'foobarbaz'}</div>, el);
96 +
97 + var container = ReactDOM.findDOMNode(inst);
98 + var childNodes = container.childNodes;
99 + var textNode = childNodes[2];
100 + textNode.textContent = 'foo';
101 + container.insertBefore(document.createTextNode('bar'), childNodes[3]);
102 + container.insertBefore(document.createTextNode('baz'), childNodes[3]);
103 +
104 + inst = ReactDOM.render(<div><span />{'barbazqux'}</div>, el);
105 + container = ReactDOM.findDOMNode(inst);
106 + expect(container.textContent).toBe('barbazqux');
107 });
108 });
src/renderers/shared/reconciler/__tests__/ReactMultiChildText-test.js
+55 -5
@@ -49,24 +49,40 @@ var expectChildren = function(d, children) {
49 expect(textNode.data).toBe('' + children);
50 }
51 } else {
52 - expect(outerNode.childNodes.length).toBe(children.length);
52 + var openingCommentNode;
53 + var closingCommentNode;
54 + var mountIndex = 0;
55
56 for (var i = 0; i < children.length; i++) {
57 var child = children[i];
58
59 if (typeof child === 'string') {
58 - textNode = outerNode.childNodes[i].firstChild;
60 + openingCommentNode = outerNode.childNodes[mountIndex];
61 +
62 + expect(openingCommentNode.nodeType).toBe(8);
63 + expect(openingCommentNode.nodeValue).toMatch(' react-text: [0-9]+ ');
64
65 if (child === '') {
61 - expect(textNode).toBe(null);
66 + textNode = null;
67 + closingCommentNode = openingCommentNode.nextSibling;
68 + mountIndex += 2;
69 } else {
63 - expect(textNode).not.toBe(null);
70 + textNode = openingCommentNode.nextSibling;
71 + closingCommentNode = textNode.nextSibling;
72 + mountIndex += 3;
73 + }
74 +
75 + if (textNode) {
76 expect(textNode.nodeType).toBe(3);
77 expect(textNode.data).toBe('' + child);
78 }
79 +
80 + expect(closingCommentNode.nodeType).toBe(8);
81 + expect(closingCommentNode.nodeValue).toBe(' /react-text ');
82 } else {
68 - var elementDOMNode = outerNode.childNodes[i];
83 + var elementDOMNode = outerNode.childNodes[mountIndex];
84 expect(elementDOMNode.tagName).toBe('DIV');
85 + mountIndex++;
86 }
87 }
88 }
@@ -194,4 +210,38 @@ describe('ReactMultiChildText', function() {
210 ReactTestUtils.renderIntoDocument(<div><h1>{['A', 'B']}</h1></div>);
211 }).not.toThrow();
212 });
213 +
214 + it('should reorder keyed text nodes', function() {
215 + spyOn(console, 'error');
216 +
217 + var container = document.createElement('div');
218 + ReactDOM.render(
219 + <div>{new Map([['a', 'alpha'], ['b', 'beta']])}</div>,
220 + container
221 + );
222 +
223 + var childNodes = container.firstChild.childNodes;
224 + var alpha1 = childNodes[0];
225 + var alpha2 = childNodes[1];
226 + var alpha3 = childNodes[2];
227 + var beta1 = childNodes[3];
228 + var beta2 = childNodes[4];
229 + var beta3 = childNodes[5];
230 +
231 + ReactDOM.render(
232 + <div>{new Map([['b', 'beta'], ['a', 'alpha']])}</div>,
233 + container
234 + );
235 +
236 + childNodes = container.firstChild.childNodes;
237 + expect(childNodes[0]).toBe(beta1);
238 + expect(childNodes[1]).toBe(beta2);
239 + expect(childNodes[2]).toBe(beta3);
240 + expect(childNodes[3]).toBe(alpha1);
241 + expect(childNodes[4]).toBe(alpha2);
242 + expect(childNodes[5]).toBe(alpha3);
243 +
244 + // Using Maps as children gives a single warning
245 + expect(console.error.calls.length).toBe(1);
246 + });
247 });
src/test/ReactDefaultPerf.js
+13 -3
@@ -27,7 +27,7 @@ function addValue(obj, key, val) {
27 obj[key] = (obj[key] || 0) + val;
28 }
29
30 -// Composites don't have any built-in ID: we have to make our own
30 +// Composite/text components don't have any built-in ID: we have to make our own
31 var compositeIDMap;
32 var compositeIDCounter = 17000;
33 function getIDOfComposite(inst) {
@@ -43,6 +43,14 @@ function getIDOfComposite(inst) {
43 }
44 }
45
46 +function getID(inst) {
47 + if (inst.hasOwnProperty('_rootNodeID')) {
48 + return inst._rootNodeID;
49 + } else {
50 + return getIDOfComposite(inst);
51 + }
52 +}
53 +
54 var ReactDefaultPerf = {
55 _allMeasurements: [], // last item in the list is the current one
56 _mountStack: [0],
@@ -224,8 +232,10 @@ var ReactDefaultPerf = {
232 } else if (fnName === 'replaceNodeWithMarkup') {
233 // Old node is already unmounted; can't get its instance
234 id = ReactDOMComponentTree.getInstanceFromNode(args[1].node)._rootNodeID;
235 + } else if (fnName === 'replaceDelimitedText') {
236 + id = getID(ReactDOMComponentTree.getInstanceFromNode(args[1]));
237 } else if (typeof id === 'object') {
228 - id = ReactDOMComponentTree.getInstanceFromNode(args[0])._rootNodeID;
238 + id = getID(ReactDOMComponentTree.getInstanceFromNode(args[0]));
239 }
240 ReactDefaultPerf._recordWrite(
241 id,
@@ -291,7 +301,7 @@ var ReactDefaultPerf = {
301 fnName === 'receiveComponent')) {
302
303 rv = func.apply(this, args);
294 - entry.hierarchy[this._rootNodeID] =
304 + entry.hierarchy[getID(this)] =
305 ReactDefaultPerf._compositeStack.slice();
306 return rv;
307 } else {
src/test/ReactDefaultPerfAnalysis.js
+1
@@ -27,6 +27,7 @@ var DOM_OPERATION_TYPES = {
27 'deleteValueForProperty': 'remove attribute',
28 'setValueForStyles': 'update styles',
29 'replaceNodeWithMarkup': 'replace',
30 + 'replaceDelimitedText': 'replace',
31 'updateTextContent': 'set textContent',
32 };
33