@samitouri / QOS-React / commits / 57cc182f26

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,