Add Fibers to ReactDOMComponentTree
This adds precaching to ReactDOMFiber. I.e. adding a handle from the DOM node to the internal Fiber. This means that we need to expose an internal handle to the reconciler. We use duck typing to figure out if it is a Fiber or Stack instance. The new failing tests are failing because this is now able to actually fire events onto Fibers and then the result of those events are incorrect where as they were ignored before.
Sebastian Markbage committed
Nov 16, 2016 at 15:42 UTC
57cc182f263d2916ed6ac870dd0919da03317a8d
7 files changed
+93
-36
scripts/fiber/tests-failing.txt
+5
-10
@@ -85,10 +85,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
85
* should update arbitrary attributes for tags containing dashes
86
* should update styles when `style` changes from null to object
87
* should empty element when removing innerHTML
88
-* should transition from string content to innerHTML
89
-* should transition from innerHTML to string content
88
* should transition from innerHTML to children in nested el
91
-* should transition from children to innerHTML in nested el
89
* should not incur unnecessary DOM mutations for attributes
90
* should not incur unnecessary DOM mutations for string properties
91
* should not incur unnecessary DOM mutations for boolean properties
@@ -119,9 +116,6 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
116
* gives source code refs for unknown prop warning for exact elements
117
* gives source code refs for unknown prop warning for exact elements in composition
118
122
-src/renderers/dom/shared/__tests__/ReactDOMComponentTree-test.js
123
-* finds instances for nodes
124
-
119
src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
120
* can reconcile text merged by Node.normalize() alongside other elements
121
* can reconcile text merged by Node.normalize()
@@ -134,10 +128,8 @@ src/renderers/dom/shared/__tests__/ReactEventIndependence-test.js
128
* does not when event fired on unmounted tree
129
130
src/renderers/dom/shared/__tests__/ReactEventListener-test.js
137
-* should propagate events one level down
138
-* should propagate events two levels down
139
-* should not get confused by disappearing elements
131
* should batch between handlers from different roots
132
+* should not fire duplicate events for a React DOM tree
133
134
src/renderers/dom/shared/__tests__/inputValueTracking-test.js
135
* should return tracker from node
@@ -207,6 +199,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
199
* should have the correct target value
200
* should control radio buttons
201
* should control radio buttons if the tree updates during render
202
+* should have a this value of undefined if bind is not used
203
* sets type, step, min, max before value always
204
* sets value properly with type coming later in props
205
* does not raise a validation warning when it switches types
@@ -214,7 +207,6 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
207
208
src/renderers/dom/shared/wrappers/__tests__/ReactDOMOption-test.js
209
* should ignore and warn invalid children types
217
-* should be able to use dangerouslySetInnerHTML on option
210
* should set attribute for empty value
211
* should allow ignoring `value` on option
212
@@ -233,6 +225,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
225
* should remember updated value when switching to uncontrolled
226
* should not control defaultValue if readding options
227
* should refresh state on change
228
+* should be able to safely remove select onChange
229
* should select grandchild options nested inside an optgroup
230
231
src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js
@@ -404,6 +397,7 @@ src/renderers/shared/shared/__tests__/ReactTreeTraversal-test.js
397
* should enter from the window to the shallowest
398
* should leave to the window
399
* should leave to the window from the shallowest
400
+* should determine the first common ancestor correctly
401
402
src/renderers/shared/shared/event/__tests__/EventPluginHub-test.js
403
* should prevent non-function listeners, at dispatch
@@ -463,4 +457,5 @@ src/test/__tests__/ReactTestUtils-test.js
457
* should support injected wrapper components as DOM components
458
* should change the value of an input field
459
* should change the value of an input field in a component
460
+* should not warn when simulating events with extra properties
461
* should set the type of the event
scripts/fiber/tests-passing.txt
+8
-5
@@ -525,6 +525,9 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
525
* should skip reserved props on web components
526
* should skip dangerouslySetInnerHTML on web components
527
* should clear all the styles when removing `style`
528
+* should transition from string content to innerHTML
529
+* should transition from innerHTML to string content
530
+* should transition from children to innerHTML in nested el
531
* handles multiple child updates without interference
532
* should generate the correct markup with className
533
* should escape style names and values
@@ -537,6 +540,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
540
541
src/renderers/dom/shared/__tests__/ReactDOMComponentTree-test.js
542
* finds nodes for instances
543
+* finds instances for nodes
544
545
src/renderers/dom/shared/__tests__/ReactDOMIDOperations-test.js
546
* should update innerHTML and preserve whitespace
@@ -554,7 +558,9 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
558
559
src/renderers/dom/shared/__tests__/ReactEventListener-test.js
560
* should dispatch events from outside React tree
557
-* should not fire duplicate events for a React DOM tree
561
+* should propagate events one level down
562
+* should propagate events two levels down
563
+* should not get confused by disappearing elements
564
565
src/renderers/dom/shared/__tests__/escapeTextContentForBrowser-test.js
566
* should escape boolean to string
@@ -679,7 +685,6 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
685
* should not render name attribute if it is not supplied
686
* should not render name attribute if it is not supplied for SSR
687
* should not set a value for submit buttons unnecessarily
682
-* should have a this value of undefined if bind is not used
688
* should update defaultValue to empty string
689
* should not warn if radio value changes but never becomes controlled
690
* should not warn if radio value changes but never becomes uncontrolled
@@ -687,6 +692,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
692
src/renderers/dom/shared/wrappers/__tests__/ReactDOMOption-test.js
693
* should flatten children to a string
694
* should ignore null/undefined/false children without warning
695
+* should be able to use dangerouslySetInnerHTML on option
696
697
src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
698
* should not throw with `defaultValue` and without children
@@ -694,7 +700,6 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
700
* should support server-side rendering
701
* should support server-side rendering with defaultValue
702
* should support server-side rendering with multiple
697
-* should be able to safely remove select onChange
703
704
src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js
705
* should not render value as an attribute
@@ -960,7 +965,6 @@ src/renderers/shared/shared/__tests__/ReactTreeTraversal-test.js
965
* should not traverse when traversing outside DOM
966
* should not traverse when enter/leaving outside DOM
967
* should not traverse if enter/leave the same node
963
-* should determine the first common ancestor correctly
968
969
src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
970
* should be able to inject ordering before plugins
@@ -1297,7 +1301,6 @@ src/test/__tests__/ReactTestUtils-test.js
1301
* can scryRenderedDOMComponentsWithClass with multiple classes
1302
* traverses children in the correct order
1303
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering
1300
-* should not warn when simulating events with extra properties
1304
* can scry with stateless components involved
1305
1306
src/test/__tests__/reactComponentExpect-test.js
src/renderers/dom/fiber/ReactDOMFiber.js
+23
-4
@@ -15,10 +15,13 @@
15
import type { HostChildren } from 'ReactFiberReconciler';
16
17
var ReactFiberReconciler = require('ReactFiberReconciler');
18
+var ReactDOMComponentTree = require('ReactDOMComponentTree');
19
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
20
21
var warning = require('warning');
22
23
+var { precacheFiberNode } = ReactDOMComponentTree;
24
+
25
type DOMContainerElement = Element & { _reactRootContainer: ?Object };
26
27
type Container = Element;
@@ -51,8 +54,14 @@ var DOMRenderer = ReactFiberReconciler({
54
recursivelyAppendChildren(container, children);
55
},
56
54
- createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
55
- const domElement = document.createElement(type);
57
+ createInstance(
58
+ type : string,
59
+ props : Props,
60
+ children : HostChildren<Instance | TextInstance>,
61
+ internalInstanceHandle : Object
62
+ ) : Instance {
63
+ const domElement : Instance = document.createElement(type);
64
+ precacheFiberNode(internalInstanceHandle, domElement);
65
recursivelyAppendChildren(domElement, children);
66
if (typeof props.className !== 'undefined') {
67
domElement.className = props.className;
@@ -61,6 +70,10 @@ var DOMRenderer = ReactFiberReconciler({
70
domElement.textContent = props.children;
71
} else if (typeof props.children === 'number') {
72
domElement.textContent = props.children.toString();
73
+ } else if (typeof props.dangerouslySetInnerHTML === 'object' &&
74
+ props.dangerouslySetInnerHTML !== null &&
75
+ typeof props.dangerouslySetInnerHTML.__html === 'string') {
76
+ domElement.innerHTML = props.dangerouslySetInnerHTML.__html;
77
}
78
return domElement;
79
},
@@ -81,11 +94,17 @@ var DOMRenderer = ReactFiberReconciler({
94
domElement.textContent = newProps.children;
95
} else if (typeof newProps.children === 'number') {
96
domElement.textContent = newProps.children.toString();
97
+ } else if (typeof newProps.dangerouslySetInnerHTML === 'object' &&
98
+ newProps.dangerouslySetInnerHTML !== null &&
99
+ typeof newProps.dangerouslySetInnerHTML.__html === 'string') {
100
+ domElement.innerHTML = newProps.dangerouslySetInnerHTML.__html;
101
}
102
},
103
87
- createTextInstance(text : string) : TextInstance {
88
- return document.createTextNode(text);
104
+ createTextInstance(text : string, internalInstanceHandle : Object) : TextInstance {
105
+ var textNode : TextInstance = document.createTextNode(text);
106
+ precacheFiberNode(internalInstanceHandle, textNode);
107
+ return textNode;
108
},
109
110
commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
src/renderers/dom/shared/ReactDOMComponentTree.js
+28
-2
@@ -13,6 +13,7 @@
13
14
var DOMProperty = require('DOMProperty');
15
var ReactDOMComponentFlags = require('ReactDOMComponentFlags');
16
+var { HostComponent, HostText } = require('ReactTypeOfWork');
17
18
var invariant = require('invariant');
19
@@ -59,6 +60,10 @@ function precacheNode(inst, node) {
60
node[internalInstanceKey] = hostInst;
61
}
62
63
+function precacheFiberNode(hostInst, node) {
64
+ node[internalInstanceKey] = hostInst;
65
+}
66
+
67
function uncacheNode(inst) {
68
var node = inst._hostNode;
69
if (node) {
@@ -133,7 +138,11 @@ function getClosestInstanceFromNode(node) {
138
}
139
140
var closest;
136
- var inst;
141
+ var inst = node[internalInstanceKey];
142
+ if (inst.tag === HostComponent || inst.tag === HostText) {
143
+ // In Fiber, this will always be the deepest root.
144
+ return inst;
145
+ }
146
for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) {
147
closest = inst;
148
if (parents.length) {
@@ -149,7 +158,17 @@ function getClosestInstanceFromNode(node) {
158
* instance, or null if the node was not rendered by this React.
159
*/
160
function getInstanceFromNode(node) {
152
- var inst = getClosestInstanceFromNode(node);
161
+ var inst = node[internalInstanceKey];
162
+ if (inst) {
163
+ if (inst.tag === HostComponent || inst.tag === HostText) {
164
+ return inst;
165
+ } else if (inst._hostNode === node) {
166
+ return inst;
167
+ } else {
168
+ return null;
169
+ }
170
+ }
171
+ inst = getClosestInstanceFromNode(node);
172
if (inst != null && inst._hostNode === node) {
173
return inst;
174
} else {
@@ -162,6 +181,12 @@ function getInstanceFromNode(node) {
181
* DOM node.
182
*/
183
function getNodeFromInstance(inst) {
184
+ if (inst.tag === HostComponent || inst.tag === HostText) {
185
+ // In Fiber this, is just the state node right now. We assume it will be
186
+ // a host component or host text.
187
+ return inst.stateNode;
188
+ }
189
+
190
// Without this first invariant, passing a non-DOM-component triggers the next
191
// invariant for a missing parent, which is super confusing.
192
invariant(
@@ -200,6 +225,7 @@ var ReactDOMComponentTree = {
225
precacheChildNodes: precacheChildNodes,
226
precacheNode: precacheNode,
227
uncacheNode: uncacheNode,
228
+ precacheFiberNode: precacheFiberNode,
229
};
230
231
module.exports = ReactDOMComponentTree;
src/renderers/dom/shared/__tests__/ReactDOMComponentTree-test.js
+23
-9
@@ -24,6 +24,20 @@ describe('ReactDOMComponentTree', () => {
24
return ReactDOM.render(elt, container);
25
}
26
27
+ function getTypeOf(instance) {
28
+ if (typeof instance.tag === 'number') {
29
+ return instance.type;
30
+ }
31
+ return instance._currentElement.type;
32
+ }
33
+
34
+ function getTextOf(instance) {
35
+ if (typeof instance.tag === 'number') {
36
+ return instance.memoizedProps;
37
+ }
38
+ return instance._stringText;
39
+ }
40
+
41
beforeEach(() => {
42
React = require('React');
43
ReactDOM = require('ReactDOM');
@@ -92,20 +106,20 @@ describe('ReactDOMComponentTree', () => {
106
);
107
}
108
95
- expect(renderAndGetInstance(null)._currentElement.type).toBe('section');
96
- expect(renderAndGetInstance('div')._currentElement.type).toBe('div');
97
- expect(renderAndGetInstance('h1')._currentElement.type).toBe('h1');
98
- expect(renderAndGetInstance('p')._currentElement.type).toBe('p');
99
- expect(renderAndGetInstance('input')._currentElement.type).toBe('input');
100
- expect(renderAndGetInstance('main')._currentElement.type).toBe('main');
109
+ expect(getTypeOf(renderAndGetInstance(null))).toBe('section');
110
+ expect(getTypeOf(renderAndGetInstance('div'))).toBe('div');
111
+ expect(getTypeOf(renderAndGetInstance('h1'))).toBe('h1');
112
+ expect(getTypeOf(renderAndGetInstance('p'))).toBe('p');
113
+ expect(getTypeOf(renderAndGetInstance('input'))).toBe('input');
114
+ expect(getTypeOf(renderAndGetInstance('main'))).toBe('main');
115
116
// This one's a text component!
117
var root = renderAndQuery(null);
118
var inst = ReactDOMComponentTree.getInstanceFromNode(root.children[0].childNodes[2]);
105
- expect(inst._stringText).toBe('goodbye.');
119
+ expect(getTextOf(inst)).toBe('goodbye.');
120
107
- expect(renderAndGetClosest('b')._currentElement.type).toBe('main');
108
- expect(renderAndGetClosest('img')._currentElement.type).toBe('main');
121
+ expect(getTypeOf(renderAndGetClosest('b'))).toBe('main');
122
+ expect(getTypeOf(renderAndGetClosest('img'))).toBe('main');
123
});
124
125
});
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+2
-2
@@ -197,7 +197,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
197
}
198
const child = workInProgress.child;
199
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
200
- const instance = createInstance(workInProgress.type, newProps, children);
200
+ const instance = createInstance(workInProgress.type, newProps, children, workInProgress);
201
// TODO: This seems like unnecessary duplication.
202
workInProgress.stateNode = instance;
203
workInProgress.output = instance;
@@ -223,7 +223,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
223
return null;
224
}
225
}
226
- const textInstance = createTextInstance(newText);
226
+ const textInstance = createTextInstance(newText, workInProgress);
227
// TODO: This seems like unnecessary duplication.
228
workInProgress.stateNode = textInstance;
229
workInProgress.output = textInstance;
src/renderers/shared/fiber/ReactFiberReconciler.js
+4
-4
@@ -36,6 +36,8 @@ type HostChildNode<I> = { tag: TypeOfWork, output: HostChildren<I>, sibling: any
36
37
export type HostChildren<I> = null | void | I | HostChildNode<I>;
38
39
+type OpaqueNode = Fiber;
40
+
41
export type HostConfig<T, P, I, TI, C> = {
42
43
// TODO: We don't currently have a quick way to detect that children didn't
@@ -44,11 +46,11 @@ export type HostConfig<T, P, I, TI, C> = {
46
47
updateContainer(containerInfo : C, children : HostChildren<I | TI>) : void,
48
47
- createInstance(type : T, props : P, children : HostChildren<I | TI>) : I,
49
+ createInstance(type : T, props : P, children : HostChildren<I | TI>, internalInstanceHandle : OpaqueNode) : I,
50
prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
51
commitUpdate(instance : I, oldProps : P, newProps : P) : void,
52
51
- createTextInstance(text : string) : TI,
53
+ createTextInstance(text : string, internalInstanceHandle : OpaqueNode) : TI,
54
commitTextUpdate(textInstance : TI, oldText : string, newText : string) : void,
55
56
appendChild(parentInstance : I, child : I | TI) : void,
@@ -61,8 +63,6 @@ export type HostConfig<T, P, I, TI, C> = {
63
useSyncScheduling ?: boolean,
64
};
65
64
-type OpaqueNode = Fiber;
65
-
66
export type Reconciler<C, I, TI> = {
67
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
68
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,