@samitouri / QOS-React / commits / 45b90d4866

Move renderer host configs into separate modules (#12791)

* Separate test renderer host config * Separate ART renderer host config * Separate ReactDOM host config * Extract RN Fabric host config * Extract RN host config

Dan Abramov committed May 15, 2018 at 01:12 UTC 45b90d48668dae48aad611819b891d029aa2fb27
13 files changed +1698 -1586
packages/react-art/src/ReactART.js
+5 -405
@@ -7,267 +7,18 @@
7
8 import React from 'react';
9 import ReactFiberReconciler from 'react-reconciler';
10 -import * as ReactScheduler from 'react-scheduler';
10 +import Transform from 'art/core/transform';
11 import Mode from 'art/modes/current';
12 import FastNoSideEffects from 'art/modes/fast-noSideEffects';
13 -import Transform from 'art/core/transform';
14 -import invariant from 'fbjs/lib/invariant';
15 -import emptyObject from 'fbjs/lib/emptyObject';
13 +
14 +import ReactARTHostConfig from './ReactARTHostConfig';
15 +import {TYPES, childrenAsString} from './ReactARTInternals';
16
17 Mode.setCurrent(
18 // Change to 'art/modes/dom' for easier debugging via SVG
19 FastNoSideEffects,
20 );
21
22 -const pooledTransform = new Transform();
23 -
24 -const EVENT_TYPES = {
25 - onClick: 'click',
26 - onMouseMove: 'mousemove',
27 - onMouseOver: 'mouseover',
28 - onMouseOut: 'mouseout',
29 - onMouseUp: 'mouseup',
30 - onMouseDown: 'mousedown',
31 -};
32 -
33 -const TYPES = {
34 - CLIPPING_RECTANGLE: 'ClippingRectangle',
35 - GROUP: 'Group',
36 - SHAPE: 'Shape',
37 - TEXT: 'Text',
38 -};
39 -
40 -const UPDATE_SIGNAL = {};
41 -
42 -/** Helper Methods */
43 -
44 -function addEventListeners(instance, type, listener) {
45 - // We need to explicitly unregister before unmount.
46 - // For this reason we need to track subscriptions.
47 - if (!instance._listeners) {
48 - instance._listeners = {};
49 - instance._subscriptions = {};
50 - }
51 -
52 - instance._listeners[type] = listener;
53 -
54 - if (listener) {
55 - if (!instance._subscriptions[type]) {
56 - instance._subscriptions[type] = instance.subscribe(
57 - type,
58 - createEventHandler(instance),
59 - instance,
60 - );
61 - }
62 - } else {
63 - if (instance._subscriptions[type]) {
64 - instance._subscriptions[type]();
65 - delete instance._subscriptions[type];
66 - }
67 - }
68 -}
69 -
70 -function childrenAsString(children) {
71 - if (!children) {
72 - return '';
73 - } else if (typeof children === 'string') {
74 - return children;
75 - } else if (children.length) {
76 - return children.join('');
77 - } else {
78 - return '';
79 - }
80 -}
81 -
82 -function createEventHandler(instance) {
83 - return function handleEvent(event) {
84 - const listener = instance._listeners[event.type];
85 -
86 - if (!listener) {
87 - // Noop
88 - } else if (typeof listener === 'function') {
89 - listener.call(instance, event);
90 - } else if (listener.handleEvent) {
91 - listener.handleEvent(event);
92 - }
93 - };
94 -}
95 -
96 -function destroyEventListeners(instance) {
97 - if (instance._subscriptions) {
98 - for (let type in instance._subscriptions) {
99 - instance._subscriptions[type]();
100 - }
101 - }
102 -
103 - instance._subscriptions = null;
104 - instance._listeners = null;
105 -}
106 -
107 -function getScaleX(props) {
108 - if (props.scaleX != null) {
109 - return props.scaleX;
110 - } else if (props.scale != null) {
111 - return props.scale;
112 - } else {
113 - return 1;
114 - }
115 -}
116 -
117 -function getScaleY(props) {
118 - if (props.scaleY != null) {
119 - return props.scaleY;
120 - } else if (props.scale != null) {
121 - return props.scale;
122 - } else {
123 - return 1;
124 - }
125 -}
126 -
127 -function isSameFont(oldFont, newFont) {
128 - if (oldFont === newFont) {
129 - return true;
130 - } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
131 - return false;
132 - } else {
133 - return (
134 - newFont.fontSize === oldFont.fontSize &&
135 - newFont.fontStyle === oldFont.fontStyle &&
136 - newFont.fontVariant === oldFont.fontVariant &&
137 - newFont.fontWeight === oldFont.fontWeight &&
138 - newFont.fontFamily === oldFont.fontFamily
139 - );
140 - }
141 -}
142 -
143 -/** Render Methods */
144 -
145 -function applyClippingRectangleProps(instance, props, prevProps = {}) {
146 - applyNodeProps(instance, props, prevProps);
147 -
148 - instance.width = props.width;
149 - instance.height = props.height;
150 -}
151 -
152 -function applyGroupProps(instance, props, prevProps = {}) {
153 - applyNodeProps(instance, props, prevProps);
154 -
155 - instance.width = props.width;
156 - instance.height = props.height;
157 -}
158 -
159 -function applyNodeProps(instance, props, prevProps = {}) {
160 - const scaleX = getScaleX(props);
161 - const scaleY = getScaleY(props);
162 -
163 - pooledTransform
164 - .transformTo(1, 0, 0, 1, 0, 0)
165 - .move(props.x || 0, props.y || 0)
166 - .rotate(props.rotation || 0, props.originX, props.originY)
167 - .scale(scaleX, scaleY, props.originX, props.originY);
168 -
169 - if (props.transform != null) {
170 - pooledTransform.transform(props.transform);
171 - }
172 -
173 - if (
174 - instance.xx !== pooledTransform.xx ||
175 - instance.yx !== pooledTransform.yx ||
176 - instance.xy !== pooledTransform.xy ||
177 - instance.yy !== pooledTransform.yy ||
178 - instance.x !== pooledTransform.x ||
179 - instance.y !== pooledTransform.y
180 - ) {
181 - instance.transformTo(pooledTransform);
182 - }
183 -
184 - if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
185 - instance.indicate(props.cursor, props.title);
186 - }
187 -
188 - if (instance.blend && props.opacity !== prevProps.opacity) {
189 - instance.blend(props.opacity == null ? 1 : props.opacity);
190 - }
191 -
192 - if (props.visible !== prevProps.visible) {
193 - if (props.visible == null || props.visible) {
194 - instance.show();
195 - } else {
196 - instance.hide();
197 - }
198 - }
199 -
200 - for (let type in EVENT_TYPES) {
201 - addEventListeners(instance, EVENT_TYPES[type], props[type]);
202 - }
203 -}
204 -
205 -function applyRenderableNodeProps(instance, props, prevProps = {}) {
206 - applyNodeProps(instance, props, prevProps);
207 -
208 - if (prevProps.fill !== props.fill) {
209 - if (props.fill && props.fill.applyFill) {
210 - props.fill.applyFill(instance);
211 - } else {
212 - instance.fill(props.fill);
213 - }
214 - }
215 - if (
216 - prevProps.stroke !== props.stroke ||
217 - prevProps.strokeWidth !== props.strokeWidth ||
218 - prevProps.strokeCap !== props.strokeCap ||
219 - prevProps.strokeJoin !== props.strokeJoin ||
220 - // TODO: Consider deep check of stokeDash; may benefit VML in IE.
221 - prevProps.strokeDash !== props.strokeDash
222 - ) {
223 - instance.stroke(
224 - props.stroke,
225 - props.strokeWidth,
226 - props.strokeCap,
227 - props.strokeJoin,
228 - props.strokeDash,
229 - );
230 - }
231 -}
232 -
233 -function applyShapeProps(instance, props, prevProps = {}) {
234 - applyRenderableNodeProps(instance, props, prevProps);
235 -
236 - const path = props.d || childrenAsString(props.children);
237 -
238 - const prevDelta = instance._prevDelta;
239 - const prevPath = instance._prevPath;
240 -
241 - if (
242 - path !== prevPath ||
243 - path.delta !== prevDelta ||
244 - prevProps.height !== props.height ||
245 - prevProps.width !== props.width
246 - ) {
247 - instance.draw(path, props.width, props.height);
248 -
249 - instance._prevDelta = path.delta;
250 - instance._prevPath = path;
251 - }
252 -}
253 -
254 -function applyTextProps(instance, props, prevProps = {}) {
255 - applyRenderableNodeProps(instance, props, prevProps);
256 -
257 - const string = props.children;
258 -
259 - if (
260 - instance._currentString !== string ||
261 - !isSameFont(props.font, prevProps.font) ||
262 - props.alignment !== prevProps.alignment ||
263 - props.path !== prevProps.path
264 - ) {
265 - instance.draw(string, props.font, props.alignment, props.path);
266 -
267 - instance._currentString = string;
268 - }
269 -}
270 -
22 /** Declarative fill-type objects; API design not finalized */
23
24 const slice = Array.prototype.slice;
@@ -383,158 +134,7 @@ class Text extends React.Component {
134
135 /** ART Renderer */
136
386 -const ARTRenderer = ReactFiberReconciler({
387 - appendInitialChild(parentInstance, child) {
388 - if (typeof child === 'string') {
389 - // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
390 - invariant(false, 'Text children should already be flattened.');
391 - return;
392 - }
393 -
394 - child.inject(parentInstance);
395 - },
396 -
397 - createInstance(type, props, internalInstanceHandle) {
398 - let instance;
399 -
400 - switch (type) {
401 - case TYPES.CLIPPING_RECTANGLE:
402 - instance = Mode.ClippingRectangle();
403 - instance._applyProps = applyClippingRectangleProps;
404 - break;
405 - case TYPES.GROUP:
406 - instance = Mode.Group();
407 - instance._applyProps = applyGroupProps;
408 - break;
409 - case TYPES.SHAPE:
410 - instance = Mode.Shape();
411 - instance._applyProps = applyShapeProps;
412 - break;
413 - case TYPES.TEXT:
414 - instance = Mode.Text(
415 - props.children,
416 - props.font,
417 - props.alignment,
418 - props.path,
419 - );
420 - instance._applyProps = applyTextProps;
421 - break;
422 - }
423 -
424 - invariant(instance, 'ReactART does not support the type "%s"', type);
425 -
426 - instance._applyProps(instance, props);
427 -
428 - return instance;
429 - },
430 -
431 - createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
432 - return text;
433 - },
434 -
435 - finalizeInitialChildren(domElement, type, props) {
436 - return false;
437 - },
438 -
439 - getPublicInstance(instance) {
440 - return instance;
441 - },
442 -
443 - prepareForCommit() {
444 - // Noop
445 - },
446 -
447 - prepareUpdate(domElement, type, oldProps, newProps) {
448 - return UPDATE_SIGNAL;
449 - },
450 -
451 - resetAfterCommit() {
452 - // Noop
453 - },
454 -
455 - resetTextContent(domElement) {
456 - // Noop
457 - },
458 -
459 - shouldDeprioritizeSubtree(type, props) {
460 - return false;
461 - },
462 -
463 - getRootHostContext() {
464 - return emptyObject;
465 - },
466 -
467 - getChildHostContext() {
468 - return emptyObject;
469 - },
470 -
471 - scheduleDeferredCallback: ReactScheduler.rIC,
472 -
473 - shouldSetTextContent(type, props) {
474 - return (
475 - typeof props.children === 'string' || typeof props.children === 'number'
476 - );
477 - },
478 -
479 - now: ReactScheduler.now,
480 -
481 - // The ART renderer is secondary to the React DOM renderer.
482 - isPrimaryRenderer: false,
483 -
484 - mutation: {
485 - appendChild(parentInstance, child) {
486 - if (child.parentNode === parentInstance) {
487 - child.eject();
488 - }
489 - child.inject(parentInstance);
490 - },
491 -
492 - appendChildToContainer(parentInstance, child) {
493 - if (child.parentNode === parentInstance) {
494 - child.eject();
495 - }
496 - child.inject(parentInstance);
497 - },
498 -
499 - insertBefore(parentInstance, child, beforeChild) {
500 - invariant(
501 - child !== beforeChild,
502 - 'ReactART: Can not insert node before itself',
503 - );
504 - child.injectBefore(beforeChild);
505 - },
506 -
507 - insertInContainerBefore(parentInstance, child, beforeChild) {
508 - invariant(
509 - child !== beforeChild,
510 - 'ReactART: Can not insert node before itself',
511 - );
512 - child.injectBefore(beforeChild);
513 - },
514 -
515 - removeChild(parentInstance, child) {
516 - destroyEventListeners(child);
517 - child.eject();
518 - },
519 -
520 - removeChildFromContainer(parentInstance, child) {
521 - destroyEventListeners(child);
522 - child.eject();
523 - },
524 -
525 - commitTextUpdate(textInstance, oldText, newText) {
526 - // Noop
527 - },
528 -
529 - commitMount(instance, type, newProps) {
530 - // Noop
531 - },
532 -
533 - commitUpdate(instance, updatePayload, type, oldProps, newProps) {
534 - instance._applyProps(instance, newProps, oldProps);
535 - },
536 - },
537 -});
137 +const ARTRenderer = ReactFiberReconciler(ReactARTHostConfig);
138
139 /** API */
140
packages/react-art/src/ReactARTHostConfig.js new
+390
@@ -0,0 +1,390 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import * as ReactScheduler from 'react-scheduler';
9 +import Transform from 'art/core/transform';
10 +import Mode from 'art/modes/current';
11 +import invariant from 'fbjs/lib/invariant';
12 +import emptyObject from 'fbjs/lib/emptyObject';
13 +
14 +import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
15 +
16 +const pooledTransform = new Transform();
17 +
18 +const UPDATE_SIGNAL = {};
19 +
20 +/** Helper Methods */
21 +
22 +function addEventListeners(instance, type, listener) {
23 + // We need to explicitly unregister before unmount.
24 + // For this reason we need to track subscriptions.
25 + if (!instance._listeners) {
26 + instance._listeners = {};
27 + instance._subscriptions = {};
28 + }
29 +
30 + instance._listeners[type] = listener;
31 +
32 + if (listener) {
33 + if (!instance._subscriptions[type]) {
34 + instance._subscriptions[type] = instance.subscribe(
35 + type,
36 + createEventHandler(instance),
37 + instance,
38 + );
39 + }
40 + } else {
41 + if (instance._subscriptions[type]) {
42 + instance._subscriptions[type]();
43 + delete instance._subscriptions[type];
44 + }
45 + }
46 +}
47 +
48 +function createEventHandler(instance) {
49 + return function handleEvent(event) {
50 + const listener = instance._listeners[event.type];
51 +
52 + if (!listener) {
53 + // Noop
54 + } else if (typeof listener === 'function') {
55 + listener.call(instance, event);
56 + } else if (listener.handleEvent) {
57 + listener.handleEvent(event);
58 + }
59 + };
60 +}
61 +
62 +function destroyEventListeners(instance) {
63 + if (instance._subscriptions) {
64 + for (let type in instance._subscriptions) {
65 + instance._subscriptions[type]();
66 + }
67 + }
68 +
69 + instance._subscriptions = null;
70 + instance._listeners = null;
71 +}
72 +
73 +function getScaleX(props) {
74 + if (props.scaleX != null) {
75 + return props.scaleX;
76 + } else if (props.scale != null) {
77 + return props.scale;
78 + } else {
79 + return 1;
80 + }
81 +}
82 +
83 +function getScaleY(props) {
84 + if (props.scaleY != null) {
85 + return props.scaleY;
86 + } else if (props.scale != null) {
87 + return props.scale;
88 + } else {
89 + return 1;
90 + }
91 +}
92 +
93 +function isSameFont(oldFont, newFont) {
94 + if (oldFont === newFont) {
95 + return true;
96 + } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
97 + return false;
98 + } else {
99 + return (
100 + newFont.fontSize === oldFont.fontSize &&
101 + newFont.fontStyle === oldFont.fontStyle &&
102 + newFont.fontVariant === oldFont.fontVariant &&
103 + newFont.fontWeight === oldFont.fontWeight &&
104 + newFont.fontFamily === oldFont.fontFamily
105 + );
106 + }
107 +}
108 +
109 +/** Render Methods */
110 +
111 +function applyClippingRectangleProps(instance, props, prevProps = {}) {
112 + applyNodeProps(instance, props, prevProps);
113 +
114 + instance.width = props.width;
115 + instance.height = props.height;
116 +}
117 +
118 +function applyGroupProps(instance, props, prevProps = {}) {
119 + applyNodeProps(instance, props, prevProps);
120 +
121 + instance.width = props.width;
122 + instance.height = props.height;
123 +}
124 +
125 +function applyNodeProps(instance, props, prevProps = {}) {
126 + const scaleX = getScaleX(props);
127 + const scaleY = getScaleY(props);
128 +
129 + pooledTransform
130 + .transformTo(1, 0, 0, 1, 0, 0)
131 + .move(props.x || 0, props.y || 0)
132 + .rotate(props.rotation || 0, props.originX, props.originY)
133 + .scale(scaleX, scaleY, props.originX, props.originY);
134 +
135 + if (props.transform != null) {
136 + pooledTransform.transform(props.transform);
137 + }
138 +
139 + if (
140 + instance.xx !== pooledTransform.xx ||
141 + instance.yx !== pooledTransform.yx ||
142 + instance.xy !== pooledTransform.xy ||
143 + instance.yy !== pooledTransform.yy ||
144 + instance.x !== pooledTransform.x ||
145 + instance.y !== pooledTransform.y
146 + ) {
147 + instance.transformTo(pooledTransform);
148 + }
149 +
150 + if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
151 + instance.indicate(props.cursor, props.title);
152 + }
153 +
154 + if (instance.blend && props.opacity !== prevProps.opacity) {
155 + instance.blend(props.opacity == null ? 1 : props.opacity);
156 + }
157 +
158 + if (props.visible !== prevProps.visible) {
159 + if (props.visible == null || props.visible) {
160 + instance.show();
161 + } else {
162 + instance.hide();
163 + }
164 + }
165 +
166 + for (let type in EVENT_TYPES) {
167 + addEventListeners(instance, EVENT_TYPES[type], props[type]);
168 + }
169 +}
170 +
171 +function applyRenderableNodeProps(instance, props, prevProps = {}) {
172 + applyNodeProps(instance, props, prevProps);
173 +
174 + if (prevProps.fill !== props.fill) {
175 + if (props.fill && props.fill.applyFill) {
176 + props.fill.applyFill(instance);
177 + } else {
178 + instance.fill(props.fill);
179 + }
180 + }
181 + if (
182 + prevProps.stroke !== props.stroke ||
183 + prevProps.strokeWidth !== props.strokeWidth ||
184 + prevProps.strokeCap !== props.strokeCap ||
185 + prevProps.strokeJoin !== props.strokeJoin ||
186 + // TODO: Consider deep check of stokeDash; may benefit VML in IE.
187 + prevProps.strokeDash !== props.strokeDash
188 + ) {
189 + instance.stroke(
190 + props.stroke,
191 + props.strokeWidth,
192 + props.strokeCap,
193 + props.strokeJoin,
194 + props.strokeDash,
195 + );
196 + }
197 +}
198 +
199 +function applyShapeProps(instance, props, prevProps = {}) {
200 + applyRenderableNodeProps(instance, props, prevProps);
201 +
202 + const path = props.d || childrenAsString(props.children);
203 +
204 + const prevDelta = instance._prevDelta;
205 + const prevPath = instance._prevPath;
206 +
207 + if (
208 + path !== prevPath ||
209 + path.delta !== prevDelta ||
210 + prevProps.height !== props.height ||
211 + prevProps.width !== props.width
212 + ) {
213 + instance.draw(path, props.width, props.height);
214 +
215 + instance._prevDelta = path.delta;
216 + instance._prevPath = path;
217 + }
218 +}
219 +
220 +function applyTextProps(instance, props, prevProps = {}) {
221 + applyRenderableNodeProps(instance, props, prevProps);
222 +
223 + const string = props.children;
224 +
225 + if (
226 + instance._currentString !== string ||
227 + !isSameFont(props.font, prevProps.font) ||
228 + props.alignment !== prevProps.alignment ||
229 + props.path !== prevProps.path
230 + ) {
231 + instance.draw(string, props.font, props.alignment, props.path);
232 +
233 + instance._currentString = string;
234 + }
235 +}
236 +
237 +const ReactARTHostConfig = {
238 + appendInitialChild(parentInstance, child) {
239 + if (typeof child === 'string') {
240 + // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
241 + invariant(false, 'Text children should already be flattened.');
242 + return;
243 + }
244 +
245 + child.inject(parentInstance);
246 + },
247 +
248 + createInstance(type, props, internalInstanceHandle) {
249 + let instance;
250 +
251 + switch (type) {
252 + case TYPES.CLIPPING_RECTANGLE:
253 + instance = Mode.ClippingRectangle();
254 + instance._applyProps = applyClippingRectangleProps;
255 + break;
256 + case TYPES.GROUP:
257 + instance = Mode.Group();
258 + instance._applyProps = applyGroupProps;
259 + break;
260 + case TYPES.SHAPE:
261 + instance = Mode.Shape();
262 + instance._applyProps = applyShapeProps;
263 + break;
264 + case TYPES.TEXT:
265 + instance = Mode.Text(
266 + props.children,
267 + props.font,
268 + props.alignment,
269 + props.path,
270 + );
271 + instance._applyProps = applyTextProps;
272 + break;
273 + }
274 +
275 + invariant(instance, 'ReactART does not support the type "%s"', type);
276 +
277 + instance._applyProps(instance, props);
278 +
279 + return instance;
280 + },
281 +
282 + createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
283 + return text;
284 + },
285 +
286 + finalizeInitialChildren(domElement, type, props) {
287 + return false;
288 + },
289 +
290 + getPublicInstance(instance) {
291 + return instance;
292 + },
293 +
294 + prepareForCommit() {
295 + // Noop
296 + },
297 +
298 + prepareUpdate(domElement, type, oldProps, newProps) {
299 + return UPDATE_SIGNAL;
300 + },
301 +
302 + resetAfterCommit() {
303 + // Noop
304 + },
305 +
306 + resetTextContent(domElement) {
307 + // Noop
308 + },
309 +
310 + shouldDeprioritizeSubtree(type, props) {
311 + return false;
312 + },
313 +
314 + getRootHostContext() {
315 + return emptyObject;
316 + },
317 +
318 + getChildHostContext() {
319 + return emptyObject;
320 + },
321 +
322 + scheduleDeferredCallback: ReactScheduler.rIC,
323 +
324 + shouldSetTextContent(type, props) {
325 + return (
326 + typeof props.children === 'string' || typeof props.children === 'number'
327 + );
328 + },
329 +
330 + now: ReactScheduler.now,
331 +
332 + // The ART renderer is secondary to the React DOM renderer.
333 + isPrimaryRenderer: false,
334 +
335 + mutation: {
336 + appendChild(parentInstance, child) {
337 + if (child.parentNode === parentInstance) {
338 + child.eject();
339 + }
340 + child.inject(parentInstance);
341 + },
342 +
343 + appendChildToContainer(parentInstance, child) {
344 + if (child.parentNode === parentInstance) {
345 + child.eject();
346 + }
347 + child.inject(parentInstance);
348 + },
349 +
350 + insertBefore(parentInstance, child, beforeChild) {
351 + invariant(
352 + child !== beforeChild,
353 + 'ReactART: Can not insert node before itself',
354 + );
355 + child.injectBefore(beforeChild);
356 + },
357 +
358 + insertInContainerBefore(parentInstance, child, beforeChild) {
359 + invariant(
360 + child !== beforeChild,
361 + 'ReactART: Can not insert node before itself',
362 + );
363 + child.injectBefore(beforeChild);
364 + },
365 +
366 + removeChild(parentInstance, child) {
367 + destroyEventListeners(child);
368 + child.eject();
369 + },
370 +
371 + removeChildFromContainer(parentInstance, child) {
372 + destroyEventListeners(child);
373 + child.eject();
374 + },
375 +
376 + commitTextUpdate(textInstance, oldText, newText) {
377 + // Noop
378 + },
379 +
380 + commitMount(instance, type, newProps) {
381 + // Noop
382 + },
383 +
384 + commitUpdate(instance, updatePayload, type, oldProps, newProps) {
385 + instance._applyProps(instance, newProps, oldProps);
386 + },
387 + },
388 +};
389 +
390 +export default ReactARTHostConfig;
packages/react-art/src/ReactARTInternals.js new
+34
@@ -0,0 +1,34 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +export const TYPES = {
9 + CLIPPING_RECTANGLE: 'ClippingRectangle',
10 + GROUP: 'Group',
11 + SHAPE: 'Shape',
12 + TEXT: 'Text',
13 +};
14 +
15 +export const EVENT_TYPES = {
16 + onClick: 'click',
17 + onMouseMove: 'mousemove',
18 + onMouseOver: 'mouseover',
19 + onMouseOut: 'mouseout',
20 + onMouseUp: 'mouseup',
21 + onMouseDown: 'mousedown',
22 +};
23 +
24 +export function childrenAsString(children) {
25 + if (!children) {
26 + return '';
27 + } else if (typeof children === 'string') {
28 + return children;
29 + } else if (children.length) {
30 + return children.join('');
31 + } else {
32 + return '';
33 + }
34 +}
packages/react-dom/src/client/ReactDOM.js
+4 -545
@@ -14,12 +14,12 @@ import type {
14 FiberRoot,
15 Batch as FiberRootBatch,
16 } from 'react-reconciler/src/ReactFiberRoot';
17 +import type {Container} from './ReactDOMHostConfig';
18
19 import '../shared/checkReact';
20 import './ReactDOMClientInjection';
21
22 import ReactFiberReconciler from 'react-reconciler';
22 -// TODO: direct imports like some-package/src/* are bad. Fix me.
23 import * as ReactPortal from 'shared/ReactPortal';
24 import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
25 import * as ReactGenericBatching from 'events/ReactGenericBatching';
@@ -29,53 +29,29 @@ import * as EventPluginRegistry from 'events/EventPluginRegistry';
29 import * as EventPropagators from 'events/EventPropagators';
30 import * as ReactInstanceMap from 'shared/ReactInstanceMap';
31 import ReactVersion from 'shared/ReactVersion';
32 -import * as ReactScheduler from 'react-scheduler';
32 import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
33 import getComponentName from 'shared/getComponentName';
34 import invariant from 'fbjs/lib/invariant';
35 import lowPriorityWarning from 'shared/lowPriorityWarning';
36 import warning from 'fbjs/lib/warning';
37
38 +import ReactDOMHostConfig from './ReactDOMHostConfig';
39 import * as ReactDOMComponentTree from './ReactDOMComponentTree';
40 import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
41 -import * as ReactInputSelection from './ReactInputSelection';
42 -import setTextContent from './setTextContent';
43 -import validateDOMNesting from './validateDOMNesting';
44 -import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
41 import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
46 -import {getChildNamespace} from '../shared/DOMNamespaces';
42 import {
43 ELEMENT_NODE,
49 - TEXT_NODE,
44 COMMENT_NODE,
45 DOCUMENT_NODE,
46 DOCUMENT_FRAGMENT_NODE,
47 } from '../shared/HTMLNodeType';
48 import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
55 -const {
56 - createElement,
57 - createTextNode,
58 - setInitialProperties,
59 - diffProperties,
60 - updateProperties,
61 - diffHydratedProperties,
62 - diffHydratedText,
63 - warnForUnmatchedText,
64 - warnForDeletedHydratableElement,
65 - warnForDeletedHydratableText,
66 - warnForInsertedHydratedElement,
67 - warnForInsertedHydratedText,
68 -} = ReactDOMFiberComponent;
69 -const {updatedAncestorInfo} = validateDOMNesting;
70 -const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
71 -
72 -let SUPPRESS_HYDRATION_WARNING;
49 +
50 let topLevelUpdateWarnings;
51 let warnOnInvalidCallback;
52 let didWarnAboutUnstableCreatePortal = false;
53
54 if (__DEV__) {
78 - SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
55 if (
56 typeof Map !== 'function' ||
57 Map.prototype == null ||
@@ -157,26 +133,6 @@ type DOMContainer =
133 _reactRootContainer: ?Root,
134 });
135
160 -type Container = Element | Document;
161 -type Props = {
162 - autoFocus?: boolean,
163 - children?: mixed,
164 - hidden?: boolean,
165 - suppressHydrationWarning?: boolean,
166 -};
167 -type Instance = Element;
168 -type TextInstance = Text;
169 -
170 -type HostContextDev = {
171 - namespace: string,
172 - ancestorInfo: mixed,
173 -};
174 -type HostContextProd = string;
175 -type HostContext = HostContextDev | HostContextProd;
176 -
177 -let eventsEnabled: ?boolean = null;
178 -let selectionInformation: ?mixed = null;
179 -
136 type Batch = FiberRootBatch & {
137 render(children: ReactNodeList): Work,
138 then(onComplete: () => mixed): void,
@@ -491,504 +447,7 @@ function shouldHydrateDueToLegacyHeuristic(container) {
447 );
448 }
449
494 -function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
495 - switch (type) {
496 - case 'button':
497 - case 'input':
498 - case 'select':
499 - case 'textarea':
500 - return !!props.autoFocus;
501 - }
502 - return false;
503 -}
504 -
505 -const DOMRenderer = ReactFiberReconciler({
506 - getRootHostContext(rootContainerInstance: Container): HostContext {
507 - let type;
508 - let namespace;
509 - const nodeType = rootContainerInstance.nodeType;
510 - switch (nodeType) {
511 - case DOCUMENT_NODE:
512 - case DOCUMENT_FRAGMENT_NODE: {
513 - type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
514 - let root = (rootContainerInstance: any).documentElement;
515 - namespace = root ? root.namespaceURI : getChildNamespace(null, '');
516 - break;
517 - }
518 - default: {
519 - const container: any =
520 - nodeType === COMMENT_NODE
521 - ? rootContainerInstance.parentNode
522 - : rootContainerInstance;
523 - const ownNamespace = container.namespaceURI || null;
524 - type = container.tagName;
525 - namespace = getChildNamespace(ownNamespace, type);
526 - break;
527 - }
528 - }
529 - if (__DEV__) {
530 - const validatedTag = type.toLowerCase();
531 - const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
532 - return {namespace, ancestorInfo};
533 - }
534 - return namespace;
535 - },
536 -
537 - getChildHostContext(
538 - parentHostContext: HostContext,
539 - type: string,
540 - ): HostContext {
541 - if (__DEV__) {
542 - const parentHostContextDev = ((parentHostContext: any): HostContextDev);
543 - const namespace = getChildNamespace(parentHostContextDev.namespace, type);
544 - const ancestorInfo = updatedAncestorInfo(
545 - parentHostContextDev.ancestorInfo,
546 - type,
547 - null,
548 - );
549 - return {namespace, ancestorInfo};
550 - }
551 - const parentNamespace = ((parentHostContext: any): HostContextProd);
552 - return getChildNamespace(parentNamespace, type);
553 - },
554 -
555 - getPublicInstance(instance) {
556 - return instance;
557 - },
558 -
559 - prepareForCommit(): void {
560 - eventsEnabled = ReactBrowserEventEmitter.isEnabled();
561 - selectionInformation = ReactInputSelection.getSelectionInformation();
562 - ReactBrowserEventEmitter.setEnabled(false);
563 - },
564 -
565 - resetAfterCommit(): void {
566 - ReactInputSelection.restoreSelection(selectionInformation);
567 - selectionInformation = null;
568 - ReactBrowserEventEmitter.setEnabled(eventsEnabled);
569 - eventsEnabled = null;
570 - },
571 -
572 - createInstance(
573 - type: string,
574 - props: Props,
575 - rootContainerInstance: Container,
576 - hostContext: HostContext,
577 - internalInstanceHandle: Object,
578 - ): Instance {
579 - let parentNamespace: string;
580 - if (__DEV__) {
581 - // TODO: take namespace into account when validating.
582 - const hostContextDev = ((hostContext: any): HostContextDev);
583 - validateDOMNesting(type, null, hostContextDev.ancestorInfo);
584 - if (
585 - typeof props.children === 'string' ||
586 - typeof props.children === 'number'
587 - ) {
588 - const string = '' + props.children;
589 - const ownAncestorInfo = updatedAncestorInfo(
590 - hostContextDev.ancestorInfo,
591 - type,
592 - null,
593 - );
594 - validateDOMNesting(null, string, ownAncestorInfo);
595 - }
596 - parentNamespace = hostContextDev.namespace;
597 - } else {
598 - parentNamespace = ((hostContext: any): HostContextProd);
599 - }
600 - const domElement: Instance = createElement(
601 - type,
602 - props,
603 - rootContainerInstance,
604 - parentNamespace,
605 - );
606 - precacheFiberNode(internalInstanceHandle, domElement);
607 - updateFiberProps(domElement, props);
608 - return domElement;
609 - },
610 -
611 - appendInitialChild(
612 - parentInstance: Instance,
613 - child: Instance | TextInstance,
614 - ): void {
615 - parentInstance.appendChild(child);
616 - },
617 -
618 - finalizeInitialChildren(
619 - domElement: Instance,
620 - type: string,
621 - props: Props,
622 - rootContainerInstance: Container,
623 - ): boolean {
624 - setInitialProperties(domElement, type, props, rootContainerInstance);
625 - return shouldAutoFocusHostComponent(type, props);
626 - },
627 -
628 - prepareUpdate(
629 - domElement: Instance,
630 - type: string,
631 - oldProps: Props,
632 - newProps: Props,
633 - rootContainerInstance: Container,
634 - hostContext: HostContext,
635 - ): null | Array<mixed> {
636 - if (__DEV__) {
637 - const hostContextDev = ((hostContext: any): HostContextDev);
638 - if (
639 - typeof newProps.children !== typeof oldProps.children &&
640 - (typeof newProps.children === 'string' ||
641 - typeof newProps.children === 'number')
642 - ) {
643 - const string = '' + newProps.children;
644 - const ownAncestorInfo = updatedAncestorInfo(
645 - hostContextDev.ancestorInfo,
646 - type,
647 - null,
648 - );
649 - validateDOMNesting(null, string, ownAncestorInfo);
650 - }
651 - }
652 - return diffProperties(
653 - domElement,
654 - type,
655 - oldProps,
656 - newProps,
657 - rootContainerInstance,
658 - );
659 - },
660 -
661 - shouldSetTextContent(type: string, props: Props): boolean {
662 - return (
663 - type === 'textarea' ||
664 - typeof props.children === 'string' ||
665 - typeof props.children === 'number' ||
666 - (typeof props.dangerouslySetInnerHTML === 'object' &&
667 - props.dangerouslySetInnerHTML !== null &&
668 - typeof props.dangerouslySetInnerHTML.__html === 'string')
669 - );
670 - },
671 -
672 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
673 - return !!props.hidden;
674 - },
675 -
676 - createTextInstance(
677 - text: string,
678 - rootContainerInstance: Container,
679 - hostContext: HostContext,
680 - internalInstanceHandle: Object,
681 - ): TextInstance {
682 - if (__DEV__) {
683 - const hostContextDev = ((hostContext: any): HostContextDev);
684 - validateDOMNesting(null, text, hostContextDev.ancestorInfo);
685 - }
686 - const textNode: TextInstance = createTextNode(text, rootContainerInstance);
687 - precacheFiberNode(internalInstanceHandle, textNode);
688 - return textNode;
689 - },
690 -
691 - now: ReactScheduler.now,
692 -
693 - isPrimaryRenderer: true,
694 -
695 - mutation: {
696 - commitMount(
697 - domElement: Instance,
698 - type: string,
699 - newProps: Props,
700 - internalInstanceHandle: Object,
701 - ): void {
702 - // Despite the naming that might imply otherwise, this method only
703 - // fires if there is an `Update` effect scheduled during mounting.
704 - // This happens if `finalizeInitialChildren` returns `true` (which it
705 - // does to implement the `autoFocus` attribute on the client). But
706 - // there are also other cases when this might happen (such as patching
707 - // up text content during hydration mismatch). So we'll check this again.
708 - if (shouldAutoFocusHostComponent(type, newProps)) {
709 - ((domElement: any):
710 - | HTMLButtonElement
711 - | HTMLInputElement
712 - | HTMLSelectElement
713 - | HTMLTextAreaElement).focus();
714 - }
715 - },
716 -
717 - commitUpdate(
718 - domElement: Instance,
719 - updatePayload: Array<mixed>,
720 - type: string,
721 - oldProps: Props,
722 - newProps: Props,
723 - internalInstanceHandle: Object,
724 - ): void {
725 - // Update the props handle so that we know which props are the ones with
726 - // with current event handlers.
727 - updateFiberProps(domElement, newProps);
728 - // Apply the diff to the DOM node.
729 - updateProperties(domElement, updatePayload, type, oldProps, newProps);
730 - },
731 -
732 - resetTextContent(domElement: Instance): void {
733 - setTextContent(domElement, '');
734 - },
735 -
736 - commitTextUpdate(
737 - textInstance: TextInstance,
738 - oldText: string,
739 - newText: string,
740 - ): void {
741 - textInstance.nodeValue = newText;
742 - },
743 -
744 - appendChild(
745 - parentInstance: Instance,
746 - child: Instance | TextInstance,
747 - ): void {
748 - parentInstance.appendChild(child);
749 - },
750 -
751 - appendChildToContainer(
752 - container: Container,
753 - child: Instance | TextInstance,
754 - ): void {
755 - if (container.nodeType === COMMENT_NODE) {
756 - (container.parentNode: any).insertBefore(child, container);
757 - } else {
758 - container.appendChild(child);
759 - }
760 - },
761 -
762 - insertBefore(
763 - parentInstance: Instance,
764 - child: Instance | TextInstance,
765 - beforeChild: Instance | TextInstance,
766 - ): void {
767 - parentInstance.insertBefore(child, beforeChild);
768 - },
769 -
770 - insertInContainerBefore(
771 - container: Container,
772 - child: Instance | TextInstance,
773 - beforeChild: Instance | TextInstance,
774 - ): void {
775 - if (container.nodeType === COMMENT_NODE) {
776 - (container.parentNode: any).insertBefore(child, beforeChild);
777 - } else {
778 - container.insertBefore(child, beforeChild);
779 - }
780 - },
781 -
782 - removeChild(
783 - parentInstance: Instance,
784 - child: Instance | TextInstance,
785 - ): void {
786 - parentInstance.removeChild(child);
787 - },
788 -
789 - removeChildFromContainer(
790 - container: Container,
791 - child: Instance | TextInstance,
792 - ): void {
793 - if (container.nodeType === COMMENT_NODE) {
794 - (container.parentNode: any).removeChild(child);
795 - } else {
796 - container.removeChild(child);
797 - }
798 - },
799 - },
800 -
801 - hydration: {
802 - canHydrateInstance(
803 - instance: Instance | TextInstance,
804 - type: string,
805 - props: Props,
806 - ): null | Instance {
807 - if (
808 - instance.nodeType !== ELEMENT_NODE ||
809 - type.toLowerCase() !== instance.nodeName.toLowerCase()
810 - ) {
811 - return null;
812 - }
813 - // This has now been refined to an element node.
814 - return ((instance: any): Instance);
815 - },
816 -
817 - canHydrateTextInstance(
818 - instance: Instance | TextInstance,
819 - text: string,
820 - ): null | TextInstance {
821 - if (text === '' || instance.nodeType !== TEXT_NODE) {
822 - // Empty strings are not parsed by HTML so there won't be a correct match here.
823 - return null;
824 - }
825 - // This has now been refined to a text node.
826 - return ((instance: any): TextInstance);
827 - },
828 -
829 - getNextHydratableSibling(
830 - instance: Instance | TextInstance,
831 - ): null | Instance | TextInstance {
832 - let node = instance.nextSibling;
833 - // Skip non-hydratable nodes.
834 - while (
835 - node &&
836 - node.nodeType !== ELEMENT_NODE &&
837 - node.nodeType !== TEXT_NODE
838 - ) {
839 - node = node.nextSibling;
840 - }
841 - return (node: any);
842 - },
843 -
844 - getFirstHydratableChild(
845 - parentInstance: Container | Instance,
846 - ): null | Instance | TextInstance {
847 - let next = parentInstance.firstChild;
848 - // Skip non-hydratable nodes.
849 - while (
850 - next &&
851 - next.nodeType !== ELEMENT_NODE &&
852 - next.nodeType !== TEXT_NODE
853 - ) {
854 - next = next.nextSibling;
855 - }
856 - return (next: any);
857 - },
858 -
859 - hydrateInstance(
860 - instance: Instance,
861 - type: string,
862 - props: Props,
863 - rootContainerInstance: Container,
864 - hostContext: HostContext,
865 - internalInstanceHandle: Object,
866 - ): null | Array<mixed> {
867 - precacheFiberNode(internalInstanceHandle, instance);
868 - // TODO: Possibly defer this until the commit phase where all the events
869 - // get attached.
870 - updateFiberProps(instance, props);
871 - let parentNamespace: string;
872 - if (__DEV__) {
873 - const hostContextDev = ((hostContext: any): HostContextDev);
874 - parentNamespace = hostContextDev.namespace;
875 - } else {
876 - parentNamespace = ((hostContext: any): HostContextProd);
877 - }
878 - return diffHydratedProperties(
879 - instance,
880 - type,
881 - props,
882 - parentNamespace,
883 - rootContainerInstance,
884 - );
885 - },
886 -
887 - hydrateTextInstance(
888 - textInstance: TextInstance,
889 - text: string,
890 - internalInstanceHandle: Object,
891 - ): boolean {
892 - precacheFiberNode(internalInstanceHandle, textInstance);
893 - return diffHydratedText(textInstance, text);
894 - },
895 -
896 - didNotMatchHydratedContainerTextInstance(
897 - parentContainer: Container,
898 - textInstance: TextInstance,
899 - text: string,
900 - ) {
901 - if (__DEV__) {
902 - warnForUnmatchedText(textInstance, text);
903 - }
904 - },
905 -
906 - didNotMatchHydratedTextInstance(
907 - parentType: string,
908 - parentProps: Props,
909 - parentInstance: Instance,
910 - textInstance: TextInstance,
911 - text: string,
912 - ) {
913 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
914 - warnForUnmatchedText(textInstance, text);
915 - }
916 - },
917 -
918 - didNotHydrateContainerInstance(
919 - parentContainer: Container,
920 - instance: Instance | TextInstance,
921 - ) {
922 - if (__DEV__) {
923 - if (instance.nodeType === 1) {
924 - warnForDeletedHydratableElement(parentContainer, (instance: any));
925 - } else {
926 - warnForDeletedHydratableText(parentContainer, (instance: any));
927 - }
928 - }
929 - },
930 -
931 - didNotHydrateInstance(
932 - parentType: string,
933 - parentProps: Props,
934 - parentInstance: Instance,
935 - instance: Instance | TextInstance,
936 - ) {
937 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
938 - if (instance.nodeType === 1) {
939 - warnForDeletedHydratableElement(parentInstance, (instance: any));
940 - } else {
941 - warnForDeletedHydratableText(parentInstance, (instance: any));
942 - }
943 - }
944 - },
945 -
946 - didNotFindHydratableContainerInstance(
947 - parentContainer: Container,
948 - type: string,
949 - props: Props,
950 - ) {
951 - if (__DEV__) {
952 - warnForInsertedHydratedElement(parentContainer, type, props);
953 - }
954 - },
955 -
956 - didNotFindHydratableContainerTextInstance(
957 - parentContainer: Container,
958 - text: string,
959 - ) {
960 - if (__DEV__) {
961 - warnForInsertedHydratedText(parentContainer, text);
962 - }
963 - },
964 -
965 - didNotFindHydratableInstance(
966 - parentType: string,
967 - parentProps: Props,
968 - parentInstance: Instance,
969 - type: string,
970 - props: Props,
971 - ) {
972 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
973 - warnForInsertedHydratedElement(parentInstance, type, props);
974 - }
975 - },
976 -
977 - didNotFindHydratableTextInstance(
978 - parentType: string,
979 - parentProps: Props,
980 - parentInstance: Instance,
981 - text: string,
982 - ) {
983 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
984 - warnForInsertedHydratedText(parentInstance, text);
985 - }
986 - },
987 - },
988 -
989 - scheduleDeferredCallback: ReactScheduler.rIC,
990 - cancelDeferredCallback: ReactScheduler.cIC,
991 -});
450 +const DOMRenderer = ReactFiberReconciler(ReactDOMHostConfig);
451
452 ReactGenericBatching.injection.injectRenderer(DOMRenderer);
453
packages/react-dom/src/client/ReactDOMHostConfig.js new
+568
@@ -0,0 +1,568 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as ReactScheduler from 'react-scheduler';
11 +
12 +import * as ReactDOMComponentTree from './ReactDOMComponentTree';
13 +import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
14 +import * as ReactInputSelection from './ReactInputSelection';
15 +import setTextContent from './setTextContent';
16 +import validateDOMNesting from './validateDOMNesting';
17 +import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
18 +import {getChildNamespace} from '../shared/DOMNamespaces';
19 +import {
20 + ELEMENT_NODE,
21 + TEXT_NODE,
22 + COMMENT_NODE,
23 + DOCUMENT_NODE,
24 + DOCUMENT_FRAGMENT_NODE,
25 +} from '../shared/HTMLNodeType';
26 +
27 +export type Container = Element | Document;
28 +type Props = {
29 + autoFocus?: boolean,
30 + children?: mixed,
31 + hidden?: boolean,
32 + suppressHydrationWarning?: boolean,
33 +};
34 +type Instance = Element;
35 +type TextInstance = Text;
36 +
37 +type HostContextDev = {
38 + namespace: string,
39 + ancestorInfo: mixed,
40 +};
41 +type HostContextProd = string;
42 +type HostContext = HostContextDev | HostContextProd;
43 +
44 +const {
45 + createElement,
46 + createTextNode,
47 + setInitialProperties,
48 + diffProperties,
49 + updateProperties,
50 + diffHydratedProperties,
51 + diffHydratedText,
52 + warnForUnmatchedText,
53 + warnForDeletedHydratableElement,
54 + warnForDeletedHydratableText,
55 + warnForInsertedHydratedElement,
56 + warnForInsertedHydratedText,
57 +} = ReactDOMFiberComponent;
58 +const {updatedAncestorInfo} = validateDOMNesting;
59 +const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
60 +
61 +let SUPPRESS_HYDRATION_WARNING;
62 +if (__DEV__) {
63 + SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
64 +}
65 +
66 +let eventsEnabled: ?boolean = null;
67 +let selectionInformation: ?mixed = null;
68 +
69 +function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
70 + switch (type) {
71 + case 'button':
72 + case 'input':
73 + case 'select':
74 + case 'textarea':
75 + return !!props.autoFocus;
76 + }
77 + return false;
78 +}
79 +
80 +const ReactDOMHostConfig = {
81 + getRootHostContext(rootContainerInstance: Container): HostContext {
82 + let type;
83 + let namespace;
84 + const nodeType = rootContainerInstance.nodeType;
85 + switch (nodeType) {
86 + case DOCUMENT_NODE:
87 + case DOCUMENT_FRAGMENT_NODE: {
88 + type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
89 + let root = (rootContainerInstance: any).documentElement;
90 + namespace = root ? root.namespaceURI : getChildNamespace(null, '');
91 + break;
92 + }
93 + default: {
94 + const container: any =
95 + nodeType === COMMENT_NODE
96 + ? rootContainerInstance.parentNode
97 + : rootContainerInstance;
98 + const ownNamespace = container.namespaceURI || null;
99 + type = container.tagName;
100 + namespace = getChildNamespace(ownNamespace, type);
101 + break;
102 + }
103 + }
104 + if (__DEV__) {
105 + const validatedTag = type.toLowerCase();
106 + const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
107 + return {namespace, ancestorInfo};
108 + }
109 + return namespace;
110 + },
111 +
112 + getChildHostContext(
113 + parentHostContext: HostContext,
114 + type: string,
115 + ): HostContext {
116 + if (__DEV__) {
117 + const parentHostContextDev = ((parentHostContext: any): HostContextDev);
118 + const namespace = getChildNamespace(parentHostContextDev.namespace, type);
119 + const ancestorInfo = updatedAncestorInfo(
120 + parentHostContextDev.ancestorInfo,
121 + type,
122 + null,
123 + );
124 + return {namespace, ancestorInfo};
125 + }
126 + const parentNamespace = ((parentHostContext: any): HostContextProd);
127 + return getChildNamespace(parentNamespace, type);
128 + },
129 +
130 + getPublicInstance(instance: Instance | TextInstance): * {
131 + return instance;
132 + },
133 +
134 + prepareForCommit(): void {
135 + eventsEnabled = ReactBrowserEventEmitter.isEnabled();
136 + selectionInformation = ReactInputSelection.getSelectionInformation();
137 + ReactBrowserEventEmitter.setEnabled(false);
138 + },
139 +
140 + resetAfterCommit(): void {
141 + ReactInputSelection.restoreSelection(selectionInformation);
142 + selectionInformation = null;
143 + ReactBrowserEventEmitter.setEnabled(eventsEnabled);
144 + eventsEnabled = null;
145 + },
146 +
147 + createInstance(
148 + type: string,
149 + props: Props,
150 + rootContainerInstance: Container,
151 + hostContext: HostContext,
152 + internalInstanceHandle: Object,
153 + ): Instance {
154 + let parentNamespace: string;
155 + if (__DEV__) {
156 + // TODO: take namespace into account when validating.
157 + const hostContextDev = ((hostContext: any): HostContextDev);
158 + validateDOMNesting(type, null, hostContextDev.ancestorInfo);
159 + if (
160 + typeof props.children === 'string' ||
161 + typeof props.children === 'number'
162 + ) {
163 + const string = '' + props.children;
164 + const ownAncestorInfo = updatedAncestorInfo(
165 + hostContextDev.ancestorInfo,
166 + type,
167 + null,
168 + );
169 + validateDOMNesting(null, string, ownAncestorInfo);
170 + }
171 + parentNamespace = hostContextDev.namespace;
172 + } else {
173 + parentNamespace = ((hostContext: any): HostContextProd);
174 + }
175 + const domElement: Instance = createElement(
176 + type,
177 + props,
178 + rootContainerInstance,
179 + parentNamespace,
180 + );
181 + precacheFiberNode(internalInstanceHandle, domElement);
182 + updateFiberProps(domElement, props);
183 + return domElement;
184 + },
185 +
186 + appendInitialChild(
187 + parentInstance: Instance,
188 + child: Instance | TextInstance,
189 + ): void {
190 + parentInstance.appendChild(child);
191 + },
192 +
193 + finalizeInitialChildren(
194 + domElement: Instance,
195 + type: string,
196 + props: Props,
197 + rootContainerInstance: Container,
198 + ): boolean {
199 + setInitialProperties(domElement, type, props, rootContainerInstance);
200 + return shouldAutoFocusHostComponent(type, props);
201 + },
202 +
203 + prepareUpdate(
204 + domElement: Instance,
205 + type: string,
206 + oldProps: Props,
207 + newProps: Props,
208 + rootContainerInstance: Container,
209 + hostContext: HostContext,
210 + ): null | Array<mixed> {
211 + if (__DEV__) {
212 + const hostContextDev = ((hostContext: any): HostContextDev);
213 + if (
214 + typeof newProps.children !== typeof oldProps.children &&
215 + (typeof newProps.children === 'string' ||
216 + typeof newProps.children === 'number')
217 + ) {
218 + const string = '' + newProps.children;
219 + const ownAncestorInfo = updatedAncestorInfo(
220 + hostContextDev.ancestorInfo,
221 + type,
222 + null,
223 + );
224 + validateDOMNesting(null, string, ownAncestorInfo);
225 + }
226 + }
227 + return diffProperties(
228 + domElement,
229 + type,
230 + oldProps,
231 + newProps,
232 + rootContainerInstance,
233 + );
234 + },
235 +
236 + shouldSetTextContent(type: string, props: Props): boolean {
237 + return (
238 + type === 'textarea' ||
239 + typeof props.children === 'string' ||
240 + typeof props.children === 'number' ||
241 + (typeof props.dangerouslySetInnerHTML === 'object' &&
242 + props.dangerouslySetInnerHTML !== null &&
243 + typeof props.dangerouslySetInnerHTML.__html === 'string')
244 + );
245 + },
246 +
247 + shouldDeprioritizeSubtree(type: string, props: Props): boolean {
248 + return !!props.hidden;
249 + },
250 +
251 + createTextInstance(
252 + text: string,
253 + rootContainerInstance: Container,
254 + hostContext: HostContext,
255 + internalInstanceHandle: Object,
256 + ): TextInstance {
257 + if (__DEV__) {
258 + const hostContextDev = ((hostContext: any): HostContextDev);
259 + validateDOMNesting(null, text, hostContextDev.ancestorInfo);
260 + }
261 + const textNode: TextInstance = createTextNode(text, rootContainerInstance);
262 + precacheFiberNode(internalInstanceHandle, textNode);
263 + return textNode;
264 + },
265 +
266 + now: ReactScheduler.now,
267 +
268 + isPrimaryRenderer: true,
269 +
270 + mutation: {
271 + commitMount(
272 + domElement: Instance,
273 + type: string,
274 + newProps: Props,
275 + internalInstanceHandle: Object,
276 + ): void {
277 + // Despite the naming that might imply otherwise, this method only
278 + // fires if there is an `Update` effect scheduled during mounting.
279 + // This happens if `finalizeInitialChildren` returns `true` (which it
280 + // does to implement the `autoFocus` attribute on the client). But
281 + // there are also other cases when this might happen (such as patching
282 + // up text content during hydration mismatch). So we'll check this again.
283 + if (shouldAutoFocusHostComponent(type, newProps)) {
284 + ((domElement: any):
285 + | HTMLButtonElement
286 + | HTMLInputElement
287 + | HTMLSelectElement
288 + | HTMLTextAreaElement).focus();
289 + }
290 + },
291 +
292 + commitUpdate(
293 + domElement: Instance,
294 + updatePayload: Array<mixed>,
295 + type: string,
296 + oldProps: Props,
297 + newProps: Props,
298 + internalInstanceHandle: Object,
299 + ): void {
300 + // Update the props handle so that we know which props are the ones with
301 + // with current event handlers.
302 + updateFiberProps(domElement, newProps);
303 + // Apply the diff to the DOM node.
304 + updateProperties(domElement, updatePayload, type, oldProps, newProps);
305 + },
306 +
307 + resetTextContent(domElement: Instance): void {
308 + setTextContent(domElement, '');
309 + },
310 +
311 + commitTextUpdate(
312 + textInstance: TextInstance,
313 + oldText: string,
314 + newText: string,
315 + ): void {
316 + textInstance.nodeValue = newText;
317 + },
318 +
319 + appendChild(
320 + parentInstance: Instance,
321 + child: Instance | TextInstance,
322 + ): void {
323 + parentInstance.appendChild(child);
324 + },
325 +
326 + appendChildToContainer(
327 + container: Container,
328 + child: Instance | TextInstance,
329 + ): void {
330 + if (container.nodeType === COMMENT_NODE) {
331 + (container.parentNode: any).insertBefore(child, container);
332 + } else {
333 + container.appendChild(child);
334 + }
335 + },
336 +
337 + insertBefore(
338 + parentInstance: Instance,
339 + child: Instance | TextInstance,
340 + beforeChild: Instance | TextInstance,
341 + ): void {
342 + parentInstance.insertBefore(child, beforeChild);
343 + },
344 +
345 + insertInContainerBefore(
346 + container: Container,
347 + child: Instance | TextInstance,
348 + beforeChild: Instance | TextInstance,
349 + ): void {
350 + if (container.nodeType === COMMENT_NODE) {
351 + (container.parentNode: any).insertBefore(child, beforeChild);
352 + } else {
353 + container.insertBefore(child, beforeChild);
354 + }
355 + },
356 +
357 + removeChild(
358 + parentInstance: Instance,
359 + child: Instance | TextInstance,
360 + ): void {
361 + parentInstance.removeChild(child);
362 + },
363 +
364 + removeChildFromContainer(
365 + container: Container,
366 + child: Instance | TextInstance,
367 + ): void {
368 + if (container.nodeType === COMMENT_NODE) {
369 + (container.parentNode: any).removeChild(child);
370 + } else {
371 + container.removeChild(child);
372 + }
373 + },
374 + },
375 +
376 + hydration: {
377 + canHydrateInstance(
378 + instance: Instance | TextInstance,
379 + type: string,
380 + props: Props,
381 + ): null | Instance {
382 + if (
383 + instance.nodeType !== ELEMENT_NODE ||
384 + type.toLowerCase() !== instance.nodeName.toLowerCase()
385 + ) {
386 + return null;
387 + }
388 + // This has now been refined to an element node.
389 + return ((instance: any): Instance);
390 + },
391 +
392 + canHydrateTextInstance(
393 + instance: Instance | TextInstance,
394 + text: string,
395 + ): null | TextInstance {
396 + if (text === '' || instance.nodeType !== TEXT_NODE) {
397 + // Empty strings are not parsed by HTML so there won't be a correct match here.
398 + return null;
399 + }
400 + // This has now been refined to a text node.
401 + return ((instance: any): TextInstance);
402 + },
403 +
404 + getNextHydratableSibling(
405 + instance: Instance | TextInstance,
406 + ): null | Instance | TextInstance {
407 + let node = instance.nextSibling;
408 + // Skip non-hydratable nodes.
409 + while (
410 + node &&
411 + node.nodeType !== ELEMENT_NODE &&
412 + node.nodeType !== TEXT_NODE
413 + ) {
414 + node = node.nextSibling;
415 + }
416 + return (node: any);
417 + },
418 +
419 + getFirstHydratableChild(
420 + parentInstance: Container | Instance,
421 + ): null | Instance | TextInstance {
422 + let next = parentInstance.firstChild;
423 + // Skip non-hydratable nodes.
424 + while (
425 + next &&
426 + next.nodeType !== ELEMENT_NODE &&
427 + next.nodeType !== TEXT_NODE
428 + ) {
429 + next = next.nextSibling;
430 + }
431 + return (next: any);
432 + },
433 +
434 + hydrateInstance(
435 + instance: Instance,
436 + type: string,
437 + props: Props,
438 + rootContainerInstance: Container,
439 + hostContext: HostContext,
440 + internalInstanceHandle: Object,
441 + ): null | Array<mixed> {
442 + precacheFiberNode(internalInstanceHandle, instance);
443 + // TODO: Possibly defer this until the commit phase where all the events
444 + // get attached.
445 + updateFiberProps(instance, props);
446 + let parentNamespace: string;
447 + if (__DEV__) {
448 + const hostContextDev = ((hostContext: any): HostContextDev);
449 + parentNamespace = hostContextDev.namespace;
450 + } else {
451 + parentNamespace = ((hostContext: any): HostContextProd);
452 + }
453 + return diffHydratedProperties(
454 + instance,
455 + type,
456 + props,
457 + parentNamespace,
458 + rootContainerInstance,
459 + );
460 + },
461 +
462 + hydrateTextInstance(
463 + textInstance: TextInstance,
464 + text: string,
465 + internalInstanceHandle: Object,
466 + ): boolean {
467 + precacheFiberNode(internalInstanceHandle, textInstance);
468 + return diffHydratedText(textInstance, text);
469 + },
470 +
471 + didNotMatchHydratedContainerTextInstance(
472 + parentContainer: Container,
473 + textInstance: TextInstance,
474 + text: string,
475 + ) {
476 + if (__DEV__) {
477 + warnForUnmatchedText(textInstance, text);
478 + }
479 + },
480 +
481 + didNotMatchHydratedTextInstance(
482 + parentType: string,
483 + parentProps: Props,
484 + parentInstance: Instance,
485 + textInstance: TextInstance,
486 + text: string,
487 + ) {
488 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
489 + warnForUnmatchedText(textInstance, text);
490 + }
491 + },
492 +
493 + didNotHydrateContainerInstance(
494 + parentContainer: Container,
495 + instance: Instance | TextInstance,
496 + ) {
497 + if (__DEV__) {
498 + if (instance.nodeType === 1) {
499 + warnForDeletedHydratableElement(parentContainer, (instance: any));
500 + } else {
501 + warnForDeletedHydratableText(parentContainer, (instance: any));
502 + }
503 + }
504 + },
505 +
506 + didNotHydrateInstance(
507 + parentType: string,
508 + parentProps: Props,
509 + parentInstance: Instance,
510 + instance: Instance | TextInstance,
511 + ) {
512 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
513 + if (instance.nodeType === 1) {
514 + warnForDeletedHydratableElement(parentInstance, (instance: any));
515 + } else {
516 + warnForDeletedHydratableText(parentInstance, (instance: any));
517 + }
518 + }
519 + },
520 +
521 + didNotFindHydratableContainerInstance(
522 + parentContainer: Container,
523 + type: string,
524 + props: Props,
525 + ) {
526 + if (__DEV__) {
527 + warnForInsertedHydratedElement(parentContainer, type, props);
528 + }
529 + },
530 +
531 + didNotFindHydratableContainerTextInstance(
532 + parentContainer: Container,
533 + text: string,
534 + ) {
535 + if (__DEV__) {
536 + warnForInsertedHydratedText(parentContainer, text);
537 + }
538 + },
539 +
540 + didNotFindHydratableInstance(
541 + parentType: string,
542 + parentProps: Props,
543 + parentInstance: Instance,
544 + type: string,
545 + props: Props,
546 + ) {
547 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
548 + warnForInsertedHydratedElement(parentInstance, type, props);
549 + }
550 + },
551 +
552 + didNotFindHydratableTextInstance(
553 + parentType: string,
554 + parentProps: Props,
555 + parentInstance: Instance,
556 + text: string,
557 + ) {
558 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
559 + warnForInsertedHydratedText(parentInstance, text);
560 + }
561 + },
562 + },
563 +
564 + scheduleDeferredCallback: ReactScheduler.rIC,
565 + cancelDeferredCallback: ReactScheduler.cIC,
566 +};
567 +
568 +export default ReactDOMHostConfig;
packages/react-native-renderer/src/ReactFabricHostConfig.js new
+355
@@ -0,0 +1,355 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {
11 + MeasureInWindowOnSuccessCallback,
12 + MeasureLayoutOnSuccessCallback,
13 + MeasureOnSuccessCallback,
14 + NativeMethodsMixinType,
15 + ReactNativeBaseComponentViewConfig,
16 +} from './ReactNativeTypes';
17 +
18 +import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
19 +import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
20 +import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
21 +import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
22 +
23 +import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
24 +import invariant from 'fbjs/lib/invariant';
25 +
26 +// Modules provided by RN:
27 +import TextInputState from 'TextInputState';
28 +import FabricUIManager from 'FabricUIManager';
29 +import UIManager from 'UIManager';
30 +
31 +// Counter for uniquely identifying views.
32 +// % 10 === 1 means it is a rootTag.
33 +// % 2 === 0 means it is a Fabric tag.
34 +// This means that they never overlap.
35 +let nextReactTag = 2;
36 +
37 +type HostContext = $ReadOnly<{|
38 + isInAParentText: boolean,
39 +|}>;
40 +
41 +/**
42 + * This is used for refs on host components.
43 + */
44 +class ReactFabricHostComponent {
45 + _nativeTag: number;
46 + viewConfig: ReactNativeBaseComponentViewConfig;
47 + currentProps: Props;
48 +
49 + constructor(
50 + tag: number,
51 + viewConfig: ReactNativeBaseComponentViewConfig,
52 + props: Props,
53 + ) {
54 + this._nativeTag = tag;
55 + this.viewConfig = viewConfig;
56 + this.currentProps = props;
57 + }
58 +
59 + blur() {
60 + TextInputState.blurTextInput(this._nativeTag);
61 + }
62 +
63 + focus() {
64 + TextInputState.focusTextInput(this._nativeTag);
65 + }
66 +
67 + measure(callback: MeasureOnSuccessCallback) {
68 + UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
69 + }
70 +
71 + measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
72 + UIManager.measureInWindow(
73 + this._nativeTag,
74 + mountSafeCallback(this, callback),
75 + );
76 + }
77 +
78 + measureLayout(
79 + relativeToNativeNode: number,
80 + onSuccess: MeasureLayoutOnSuccessCallback,
81 + onFail: () => void /* currently unused */,
82 + ) {
83 + UIManager.measureLayout(
84 + this._nativeTag,
85 + relativeToNativeNode,
86 + mountSafeCallback(this, onFail),
87 + mountSafeCallback(this, onSuccess),
88 + );
89 + }
90 +
91 + setNativeProps(nativeProps: Object) {
92 + if (__DEV__) {
93 + warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
94 + }
95 +
96 + const updatePayload = ReactNativeAttributePayload.create(
97 + nativeProps,
98 + this.viewConfig.validAttributes,
99 + );
100 +
101 + // Avoid the overhead of bridge calls if there's no update.
102 + // This is an expensive no-op for Android, and causes an unnecessary
103 + // view invalidation for certain components (eg RCTTextInput) on iOS.
104 + if (updatePayload != null) {
105 + UIManager.updateView(
106 + this._nativeTag,
107 + this.viewConfig.uiViewClassName,
108 + updatePayload,
109 + );
110 + }
111 + }
112 +}
113 +
114 +// eslint-disable-next-line no-unused-expressions
115 +(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
116 +
117 +type Node = Object;
118 +type ChildSet = Object;
119 +type Container = number;
120 +type Instance = {
121 + node: Node,
122 + canonical: ReactFabricHostComponent,
123 +};
124 +type Props = Object;
125 +type TextInstance = {
126 + node: Node,
127 +};
128 +
129 +const ReacFabricHostConfig = {
130 + appendInitialChild(
131 + parentInstance: Instance,
132 + child: Instance | TextInstance,
133 + ): void {
134 + FabricUIManager.appendChild(parentInstance.node, child.node);
135 + },
136 +
137 + createInstance(
138 + type: string,
139 + props: Props,
140 + rootContainerInstance: Container,
141 + hostContext: HostContext,
142 + internalInstanceHandle: Object,
143 + ): Instance {
144 + const tag = nextReactTag;
145 + nextReactTag += 2;
146 +
147 + const viewConfig = ReactNativeViewConfigRegistry.get(type);
148 +
149 + if (__DEV__) {
150 + for (const key in viewConfig.validAttributes) {
151 + if (props.hasOwnProperty(key)) {
152 + deepFreezeAndThrowOnMutationInDev(props[key]);
153 + }
154 + }
155 + }
156 +
157 + invariant(
158 + type !== 'RCTView' || !hostContext.isInAParentText,
159 + 'Nesting of <View> within <Text> is not currently supported.',
160 + );
161 +
162 + const updatePayload = ReactNativeAttributePayload.create(
163 + props,
164 + viewConfig.validAttributes,
165 + );
166 +
167 + const node = FabricUIManager.createNode(
168 + tag, // reactTag
169 + viewConfig.uiViewClassName, // viewName
170 + rootContainerInstance, // rootTag
171 + updatePayload, // props
172 + internalInstanceHandle, // internalInstanceHandle
173 + );
174 +
175 + const component = new ReactFabricHostComponent(tag, viewConfig, props);
176 +
177 + return {
178 + node: node,
179 + canonical: component,
180 + };
181 + },
182 +
183 + createTextInstance(
184 + text: string,
185 + rootContainerInstance: Container,
186 + hostContext: HostContext,
187 + internalInstanceHandle: Object,
188 + ): TextInstance {
189 + invariant(
190 + hostContext.isInAParentText,
191 + 'Text strings must be rendered within a <Text> component.',
192 + );
193 +
194 + const tag = nextReactTag;
195 + nextReactTag += 2;
196 +
197 + const node = FabricUIManager.createNode(
198 + tag, // reactTag
199 + 'RCTRawText', // viewName
200 + rootContainerInstance, // rootTag
201 + {text: text}, // props
202 + internalInstanceHandle, // instance handle
203 + );
204 +
205 + return {
206 + node: node,
207 + };
208 + },
209 +
210 + finalizeInitialChildren(
211 + parentInstance: Instance,
212 + type: string,
213 + props: Props,
214 + rootContainerInstance: Container,
215 + ): boolean {
216 + return false;
217 + },
218 +
219 + getRootHostContext(rootContainerInstance: Container): HostContext {
220 + return {isInAParentText: false};
221 + },
222 +
223 + getChildHostContext(
224 + parentHostContext: HostContext,
225 + type: string,
226 + ): HostContext {
227 + const prevIsInAParentText = parentHostContext.isInAParentText;
228 + const isInAParentText =
229 + type === 'AndroidTextInput' || // Android
230 + type === 'RCTMultilineTextInputView' || // iOS
231 + type === 'RCTSinglelineTextInputView' || // iOS
232 + type === 'RCTText' ||
233 + type === 'RCTVirtualText';
234 +
235 + if (prevIsInAParentText !== isInAParentText) {
236 + return {isInAParentText};
237 + } else {
238 + return parentHostContext;
239 + }
240 + },
241 +
242 + getPublicInstance(instance: Instance): * {
243 + return instance.canonical;
244 + },
245 +
246 + now: ReactNativeFrameScheduling.now,
247 +
248 + // The Fabric renderer is secondary to the existing React Native renderer.
249 + isPrimaryRenderer: false,
250 +
251 + prepareForCommit(): void {
252 + // Noop
253 + },
254 +
255 + prepareUpdate(
256 + instance: Instance,
257 + type: string,
258 + oldProps: Props,
259 + newProps: Props,
260 + rootContainerInstance: Container,
261 + hostContext: HostContext,
262 + ): null | Object {
263 + const viewConfig = instance.canonical.viewConfig;
264 + const updatePayload = ReactNativeAttributePayload.diff(
265 + oldProps,
266 + newProps,
267 + viewConfig.validAttributes,
268 + );
269 + // TODO: If the event handlers have changed, we need to update the current props
270 + // in the commit phase but there is no host config hook to do it yet.
271 + return updatePayload;
272 + },
273 +
274 + resetAfterCommit(): void {
275 + // Noop
276 + },
277 +
278 + scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
279 + cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
280 +
281 + shouldDeprioritizeSubtree(type: string, props: Props): boolean {
282 + return false;
283 + },
284 +
285 + shouldSetTextContent(type: string, props: Props): boolean {
286 + // TODO (bvaughn) Revisit this decision.
287 + // Always returning false simplifies the createInstance() implementation,
288 + // But creates an additional child Fiber for raw text children.
289 + // No additional native views are created though.
290 + // It's not clear to me which is better so I'm deferring for now.
291 + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
292 + return false;
293 + },
294 +
295 + persistence: {
296 + cloneInstance(
297 + instance: Instance,
298 + updatePayload: null | Object,
299 + type: string,
300 + oldProps: Props,
301 + newProps: Props,
302 + internalInstanceHandle: Object,
303 + keepChildren: boolean,
304 + recyclableInstance: null | Instance,
305 + ): Instance {
306 + const node = instance.node;
307 + let clone;
308 + if (keepChildren) {
309 + if (updatePayload !== null) {
310 + clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
311 + } else {
312 + clone = FabricUIManager.cloneNode(node);
313 + }
314 + } else {
315 + if (updatePayload !== null) {
316 + clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
317 + node,
318 + updatePayload,
319 + );
320 + } else {
321 + clone = FabricUIManager.cloneNodeWithNewChildren(node);
322 + }
323 + }
324 + return {
325 + node: clone,
326 + canonical: instance.canonical,
327 + };
328 + },
329 +
330 + createContainerChildSet(container: Container): ChildSet {
331 + return FabricUIManager.createChildSet(container);
332 + },
333 +
334 + appendChildToContainerChildSet(
335 + childSet: ChildSet,
336 + child: Instance | TextInstance,
337 + ): void {
338 + FabricUIManager.appendChildToSet(childSet, child.node);
339 + },
340 +
341 + finalizeContainerChildren(
342 + container: Container,
343 + newChildren: ChildSet,
344 + ): void {
345 + FabricUIManager.completeRoot(container, newChildren);
346 + },
347 +
348 + replaceContainerChildren(
349 + container: Container,
350 + newChildren: ChildSet,
351 + ): void {},
352 + },
353 +};
354 +
355 +export default ReacFabricHostConfig;
packages/react-native-renderer/src/ReactFabricRenderer.js
+2 -343
@@ -7,350 +7,9 @@
7 * @flow
8 */
9
10 -import type {
11 - MeasureInWindowOnSuccessCallback,
12 - MeasureLayoutOnSuccessCallback,
13 - MeasureOnSuccessCallback,
14 - NativeMethodsMixinType,
15 - ReactNativeBaseComponentViewConfig,
16 -} from './ReactNativeTypes';
17 -
18 -import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
19 -import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
20 -import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
21 -import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
10 import ReactFiberReconciler from 'react-reconciler';
11 +import ReactFabricHostConfig from './ReactFabricHostConfig';
12
24 -import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
25 -import invariant from 'fbjs/lib/invariant';
26 -
27 -// Modules provided by RN:
28 -import TextInputState from 'TextInputState';
29 -import FabricUIManager from 'FabricUIManager';
30 -import UIManager from 'UIManager';
31 -
32 -// Counter for uniquely identifying views.
33 -// % 10 === 1 means it is a rootTag.
34 -// % 2 === 0 means it is a Fabric tag.
35 -// This means that they never overlap.
36 -let nextReactTag = 2;
37 -
38 -type HostContext = $ReadOnly<{|
39 - isInAParentText: boolean,
40 -|}>;
41 -
42 -/**
43 - * This is used for refs on host components.
44 - */
45 -class ReactFabricHostComponent {
46 - _nativeTag: number;
47 - viewConfig: ReactNativeBaseComponentViewConfig;
48 - currentProps: Props;
49 -
50 - constructor(
51 - tag: number,
52 - viewConfig: ReactNativeBaseComponentViewConfig,
53 - props: Props,
54 - ) {
55 - this._nativeTag = tag;
56 - this.viewConfig = viewConfig;
57 - this.currentProps = props;
58 - }
59 -
60 - blur() {
61 - TextInputState.blurTextInput(this._nativeTag);
62 - }
63 -
64 - focus() {
65 - TextInputState.focusTextInput(this._nativeTag);
66 - }
67 -
68 - measure(callback: MeasureOnSuccessCallback) {
69 - UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
70 - }
71 -
72 - measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
73 - UIManager.measureInWindow(
74 - this._nativeTag,
75 - mountSafeCallback(this, callback),
76 - );
77 - }
78 -
79 - measureLayout(
80 - relativeToNativeNode: number,
81 - onSuccess: MeasureLayoutOnSuccessCallback,
82 - onFail: () => void /* currently unused */,
83 - ) {
84 - UIManager.measureLayout(
85 - this._nativeTag,
86 - relativeToNativeNode,
87 - mountSafeCallback(this, onFail),
88 - mountSafeCallback(this, onSuccess),
89 - );
90 - }
91 -
92 - setNativeProps(nativeProps: Object) {
93 - if (__DEV__) {
94 - warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
95 - }
96 -
97 - const updatePayload = ReactNativeAttributePayload.create(
98 - nativeProps,
99 - this.viewConfig.validAttributes,
100 - );
101 -
102 - // Avoid the overhead of bridge calls if there's no update.
103 - // This is an expensive no-op for Android, and causes an unnecessary
104 - // view invalidation for certain components (eg RCTTextInput) on iOS.
105 - if (updatePayload != null) {
106 - UIManager.updateView(
107 - this._nativeTag,
108 - this.viewConfig.uiViewClassName,
109 - updatePayload,
110 - );
111 - }
112 - }
113 -}
114 -
115 -// eslint-disable-next-line no-unused-expressions
116 -(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
117 -
118 -type Node = Object;
119 -type ChildSet = Object;
120 -type Container = number;
121 -type Instance = {
122 - node: Node,
123 - canonical: ReactFabricHostComponent,
124 -};
125 -type Props = Object;
126 -type TextInstance = {
127 - node: Node,
128 -};
129 -
130 -const ReactFabricRenderer = ReactFiberReconciler({
131 - appendInitialChild(
132 - parentInstance: Instance,
133 - child: Instance | TextInstance,
134 - ): void {
135 - FabricUIManager.appendChild(parentInstance.node, child.node);
136 - },
137 -
138 - createInstance(
139 - type: string,
140 - props: Props,
141 - rootContainerInstance: Container,
142 - hostContext: HostContext,
143 - internalInstanceHandle: Object,
144 - ): Instance {
145 - const tag = nextReactTag;
146 - nextReactTag += 2;
147 -
148 - const viewConfig = ReactNativeViewConfigRegistry.get(type);
149 -
150 - if (__DEV__) {
151 - for (const key in viewConfig.validAttributes) {
152 - if (props.hasOwnProperty(key)) {
153 - deepFreezeAndThrowOnMutationInDev(props[key]);
154 - }
155 - }
156 - }
157 -
158 - invariant(
159 - type !== 'RCTView' || !hostContext.isInAParentText,
160 - 'Nesting of <View> within <Text> is not currently supported.',
161 - );
162 -
163 - const updatePayload = ReactNativeAttributePayload.create(
164 - props,
165 - viewConfig.validAttributes,
166 - );
167 -
168 - const node = FabricUIManager.createNode(
169 - tag, // reactTag
170 - viewConfig.uiViewClassName, // viewName
171 - rootContainerInstance, // rootTag
172 - updatePayload, // props
173 - internalInstanceHandle, // internalInstanceHandle
174 - );
175 -
176 - const component = new ReactFabricHostComponent(tag, viewConfig, props);
177 -
178 - return {
179 - node: node,
180 - canonical: component,
181 - };
182 - },
183 -
184 - createTextInstance(
185 - text: string,
186 - rootContainerInstance: Container,
187 - hostContext: HostContext,
188 - internalInstanceHandle: Object,
189 - ): TextInstance {
190 - invariant(
191 - hostContext.isInAParentText,
192 - 'Text strings must be rendered within a <Text> component.',
193 - );
194 -
195 - const tag = nextReactTag;
196 - nextReactTag += 2;
197 -
198 - const node = FabricUIManager.createNode(
199 - tag, // reactTag
200 - 'RCTRawText', // viewName
201 - rootContainerInstance, // rootTag
202 - {text: text}, // props
203 - internalInstanceHandle, // instance handle
204 - );
205 -
206 - return {
207 - node: node,
208 - };
209 - },
210 -
211 - finalizeInitialChildren(
212 - parentInstance: Instance,
213 - type: string,
214 - props: Props,
215 - rootContainerInstance: Container,
216 - ): boolean {
217 - return false;
218 - },
219 -
220 - getRootHostContext(rootContainerInstance: Container): HostContext {
221 - return {isInAParentText: false};
222 - },
223 -
224 - getChildHostContext(
225 - parentHostContext: HostContext,
226 - type: string,
227 - ): HostContext {
228 - const prevIsInAParentText = parentHostContext.isInAParentText;
229 - const isInAParentText =
230 - type === 'AndroidTextInput' || // Android
231 - type === 'RCTMultilineTextInputView' || // iOS
232 - type === 'RCTSinglelineTextInputView' || // iOS
233 - type === 'RCTText' ||
234 - type === 'RCTVirtualText';
235 -
236 - if (prevIsInAParentText !== isInAParentText) {
237 - return {isInAParentText};
238 - } else {
239 - return parentHostContext;
240 - }
241 - },
242 -
243 - getPublicInstance(instance) {
244 - return instance.canonical;
245 - },
246 -
247 - now: ReactNativeFrameScheduling.now,
248 -
249 - // The Fabric renderer is secondary to the existing React Native renderer.
250 - isPrimaryRenderer: false,
251 -
252 - prepareForCommit(): void {
253 - // Noop
254 - },
255 -
256 - prepareUpdate(
257 - instance: Instance,
258 - type: string,
259 - oldProps: Props,
260 - newProps: Props,
261 - rootContainerInstance: Container,
262 - hostContext: HostContext,
263 - ): null | Object {
264 - const viewConfig = instance.canonical.viewConfig;
265 - const updatePayload = ReactNativeAttributePayload.diff(
266 - oldProps,
267 - newProps,
268 - viewConfig.validAttributes,
269 - );
270 - // TODO: If the event handlers have changed, we need to update the current props
271 - // in the commit phase but there is no host config hook to do it yet.
272 - return updatePayload;
273 - },
274 -
275 - resetAfterCommit(): void {
276 - // Noop
277 - },
278 -
279 - scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
280 - cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
281 -
282 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
283 - return false;
284 - },
285 -
286 - shouldSetTextContent(type: string, props: Props): boolean {
287 - // TODO (bvaughn) Revisit this decision.
288 - // Always returning false simplifies the createInstance() implementation,
289 - // But creates an additional child Fiber for raw text children.
290 - // No additional native views are created though.
291 - // It's not clear to me which is better so I'm deferring for now.
292 - // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
293 - return false;
294 - },
295 -
296 - persistence: {
297 - cloneInstance(
298 - instance: Instance,
299 - updatePayload: null | Object,
300 - type: string,
301 - oldProps: Props,
302 - newProps: Props,
303 - internalInstanceHandle: Object,
304 - keepChildren: boolean,
305 - recyclableInstance: null | Instance,
306 - ): Instance {
307 - const node = instance.node;
308 - let clone;
309 - if (keepChildren) {
310 - if (updatePayload !== null) {
311 - clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
312 - } else {
313 - clone = FabricUIManager.cloneNode(node);
314 - }
315 - } else {
316 - if (updatePayload !== null) {
317 - clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
318 - node,
319 - updatePayload,
320 - );
321 - } else {
322 - clone = FabricUIManager.cloneNodeWithNewChildren(node);
323 - }
324 - }
325 - return {
326 - node: clone,
327 - canonical: instance.canonical,
328 - };
329 - },
330 -
331 - createContainerChildSet(container: Container): ChildSet {
332 - return FabricUIManager.createChildSet(container);
333 - },
334 -
335 - appendChildToContainerChildSet(
336 - childSet: ChildSet,
337 - child: Instance | TextInstance,
338 - ): void {
339 - FabricUIManager.appendChildToSet(childSet, child.node);
340 - },
341 -
342 - finalizeContainerChildren(
343 - container: Container,
344 - newChildren: ChildSet,
345 - ): void {
346 - FabricUIManager.completeRoot(container, newChildren);
347 - },
348 -
349 - replaceContainerChildren(
350 - container: Container,
351 - newChildren: ChildSet,
352 - ): void {},
353 - },
354 -});
13 +const ReactFabricRenderer = ReactFiberReconciler(ReactFabricHostConfig);
14
15 export default ReactFabricRenderer;
packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+1 -1
@@ -14,7 +14,7 @@ import type {
14 NativeMethodsMixinType,
15 ReactNativeBaseComponentViewConfig,
16 } from './ReactNativeTypes';
17 -import type {Instance} from './ReactNativeFiberRenderer';
17 +import type {Instance} from './ReactNativeHostConfig';
18
19 // Modules provided by RN:
20 import TextInputState from 'TextInputState';
packages/react-native-renderer/src/ReactNativeHostConfig.js renamed
+4 -5
@@ -9,7 +9,6 @@
9
10 import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
11
12 -import ReactFiberReconciler from 'react-reconciler';
12 import emptyObject from 'fbjs/lib/emptyObject';
13 import invariant from 'fbjs/lib/invariant';
14
@@ -64,7 +63,7 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
63 }
64 }
65
67 -const NativeRenderer = ReactFiberReconciler({
66 +const ReactNativeHostConfig = {
67 appendInitialChild(
68 parentInstance: Instance,
69 child: Instance | TextInstance,
@@ -193,7 +192,7 @@ const NativeRenderer = ReactFiberReconciler({
192 }
193 },
194
196 - getPublicInstance(instance) {
195 + getPublicInstance(instance: Instance): * {
196 return instance;
197 },
198
@@ -427,6 +426,6 @@ const NativeRenderer = ReactFiberReconciler({
426 // Noop
427 },
428 },
430 -});
429 +};
430
432 -export default NativeRenderer;
431 +export default ReactNativeHostConfig;
packages/react-native-renderer/src/ReactNativeRenderer.js
+4 -1
@@ -12,6 +12,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
12
13 import './ReactNativeInjection';
14
15 +import ReactFiberReconciler from 'react-reconciler';
16 import * as ReactPortal from 'shared/ReactPortal';
17 import * as ReactGenericBatching from 'events/ReactGenericBatching';
18 import ReactVersion from 'shared/ReactVersion';
@@ -20,16 +21,18 @@ import UIManager from 'UIManager';
21
22 import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
23
24 +import ReactNativeHostConfig from './ReactNativeHostConfig';
25 import NativeMethodsMixin from './NativeMethodsMixin';
26 import ReactNativeComponent from './ReactNativeComponent';
27 import * as ReactNativeComponentTree from './ReactNativeComponentTree';
26 -import ReactNativeFiberRenderer from './ReactNativeFiberRenderer';
28 import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
29
30 import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
31 import getComponentName from 'shared/getComponentName';
32 import warning from 'fbjs/lib/warning';
33
34 +const ReactNativeFiberRenderer = ReactFiberReconciler(ReactNativeHostConfig);
35 +
36 const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
37
38 function findNodeHandle(componentOrHandle: any): ?number {
packages/react-test-renderer/src/ReactTestHostConfig.js new
+218
@@ -0,0 +1,218 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import emptyObject from 'fbjs/lib/emptyObject';
11 +
12 +import * as TestRendererScheduling from './ReactTestRendererScheduling';
13 +
14 +export type Instance = {|
15 + type: string,
16 + props: Object,
17 + children: Array<Instance | TextInstance>,
18 + rootContainerInstance: Container,
19 + tag: 'INSTANCE',
20 +|};
21 +
22 +export type TextInstance = {|
23 + text: string,
24 + tag: 'TEXT',
25 +|};
26 +
27 +type Container = {|
28 + children: Array<Instance | TextInstance>,
29 + createNodeMock: Function,
30 + tag: 'CONTAINER',
31 +|};
32 +
33 +type Props = Object;
34 +
35 +const UPDATE_SIGNAL = {};
36 +
37 +function getPublicInstance(inst: Instance | TextInstance): * {
38 + switch (inst.tag) {
39 + case 'INSTANCE':
40 + const createNodeMock = inst.rootContainerInstance.createNodeMock;
41 + return createNodeMock({
42 + type: inst.type,
43 + props: inst.props,
44 + });
45 + default:
46 + return inst;
47 + }
48 +}
49 +
50 +function appendChild(
51 + parentInstance: Instance | Container,
52 + child: Instance | TextInstance,
53 +): void {
54 + const index = parentInstance.children.indexOf(child);
55 + if (index !== -1) {
56 + parentInstance.children.splice(index, 1);
57 + }
58 + parentInstance.children.push(child);
59 +}
60 +
61 +function insertBefore(
62 + parentInstance: Instance | Container,
63 + child: Instance | TextInstance,
64 + beforeChild: Instance | TextInstance,
65 +): void {
66 + const index = parentInstance.children.indexOf(child);
67 + if (index !== -1) {
68 + parentInstance.children.splice(index, 1);
69 + }
70 + const beforeIndex = parentInstance.children.indexOf(beforeChild);
71 + parentInstance.children.splice(beforeIndex, 0, child);
72 +}
73 +
74 +function removeChild(
75 + parentInstance: Instance | Container,
76 + child: Instance | TextInstance,
77 +): void {
78 + const index = parentInstance.children.indexOf(child);
79 + parentInstance.children.splice(index, 1);
80 +}
81 +
82 +const ReactTestHostConfig = {
83 + getRootHostContext() {
84 + return emptyObject;
85 + },
86 +
87 + getChildHostContext() {
88 + return emptyObject;
89 + },
90 +
91 + prepareForCommit(): void {
92 + // noop
93 + },
94 +
95 + resetAfterCommit(): void {
96 + // noop
97 + },
98 +
99 + createInstance(
100 + type: string,
101 + props: Props,
102 + rootContainerInstance: Container,
103 + hostContext: Object,
104 + internalInstanceHandle: Object,
105 + ): Instance {
106 + return {
107 + type,
108 + props,
109 + children: [],
110 + rootContainerInstance,
111 + tag: 'INSTANCE',
112 + };
113 + },
114 +
115 + appendInitialChild(
116 + parentInstance: Instance,
117 + child: Instance | TextInstance,
118 + ): void {
119 + const index = parentInstance.children.indexOf(child);
120 + if (index !== -1) {
121 + parentInstance.children.splice(index, 1);
122 + }
123 + parentInstance.children.push(child);
124 + },
125 +
126 + finalizeInitialChildren(
127 + testElement: Instance,
128 + type: string,
129 + props: Props,
130 + rootContainerInstance: Container,
131 + ): boolean {
132 + return false;
133 + },
134 +
135 + prepareUpdate(
136 + testElement: Instance,
137 + type: string,
138 + oldProps: Props,
139 + newProps: Props,
140 + rootContainerInstance: Container,
141 + hostContext: Object,
142 + ): null | {} {
143 + return UPDATE_SIGNAL;
144 + },
145 +
146 + shouldSetTextContent(type: string, props: Props): boolean {
147 + return false;
148 + },
149 +
150 + shouldDeprioritizeSubtree(type: string, props: Props): boolean {
151 + return false;
152 + },
153 +
154 + createTextInstance(
155 + text: string,
156 + rootContainerInstance: Container,
157 + hostContext: Object,
158 + internalInstanceHandle: Object,
159 + ): TextInstance {
160 + return {
161 + text,
162 + tag: 'TEXT',
163 + };
164 + },
165 +
166 + getPublicInstance,
167 +
168 + scheduleDeferredCallback: TestRendererScheduling.scheduleDeferredCallback,
169 + cancelDeferredCallback: TestRendererScheduling.cancelDeferredCallback,
170 + // This approach enables `now` to be mocked by tests,
171 + // Even after the reconciler has initialized and read host config values.
172 + now: () => TestRendererScheduling.nowImplementation(),
173 +
174 + isPrimaryRenderer: true,
175 +
176 + mutation: {
177 + commitUpdate(
178 + instance: Instance,
179 + updatePayload: {},
180 + type: string,
181 + oldProps: Props,
182 + newProps: Props,
183 + internalInstanceHandle: Object,
184 + ): void {
185 + instance.type = type;
186 + instance.props = newProps;
187 + },
188 +
189 + commitMount(
190 + instance: Instance,
191 + type: string,
192 + newProps: Props,
193 + internalInstanceHandle: Object,
194 + ): void {
195 + // noop
196 + },
197 +
198 + commitTextUpdate(
199 + textInstance: TextInstance,
200 + oldText: string,
201 + newText: string,
202 + ): void {
203 + textInstance.text = newText;
204 + },
205 + resetTextContent(testElement: Instance): void {
206 + // noop
207 + },
208 +
209 + appendChild: appendChild,
210 + appendChildToContainer: appendChild,
211 + insertBefore: insertBefore,
212 + insertInContainerBefore: insertBefore,
213 + removeChild: removeChild,
214 + removeChildFromContainer: removeChild,
215 + },
216 +};
217 +
218 +export default ReactTestHostConfig;
packages/react-test-renderer/src/ReactTestRenderer.js
+13 -286
@@ -9,12 +9,11 @@
9
10 import type {Fiber} from 'react-reconciler/src/ReactFiber';
11 import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
12 -import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
12 +import type {Instance, TextInstance} from './ReactTestHostConfig';
13
14 import ReactFiberReconciler from 'react-reconciler';
15 import {batchedUpdates} from 'events/ReactGenericBatching';
16 import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
17 -import emptyObject from 'fbjs/lib/emptyObject';
17 import {
18 Fragment,
19 FunctionalComponent,
@@ -31,6 +30,9 @@ import {
30 } from 'shared/ReactTypeOfWork';
31 import invariant from 'fbjs/lib/invariant';
32
33 +import ReactTestHostConfig from './ReactTestHostConfig';
34 +import * as TestRendererScheduling from './ReactTestRendererScheduling';
35 +
36 type TestRendererOptions = {
37 createNodeMock: (element: React$Element<any>) => any,
38 unstable_isAsync: boolean,
@@ -44,26 +46,6 @@ type ReactTestRendererJSON = {|
46 |};
47 type ReactTestRendererNode = ReactTestRendererJSON | string;
48
47 -type Container = {|
48 - children: Array<Instance | TextInstance>,
49 - createNodeMock: Function,
50 - tag: 'CONTAINER',
51 -|};
52 -
53 -type Props = Object;
54 -type Instance = {|
55 - type: string,
56 - props: Object,
57 - children: Array<Instance | TextInstance>,
58 - rootContainerInstance: Container,
59 - tag: 'INSTANCE',
60 -|};
61 -
62 -type TextInstance = {|
63 - text: string,
64 - tag: 'TEXT',
65 -|};
66 -
49 type FindOptions = $Shape<{
50 // performs a "greedy" search: if a matching node is found, will continue
51 // to search within the matching node's children. (default: true)
@@ -72,203 +54,7 @@ type FindOptions = $Shape<{
54
55 export type Predicate = (node: ReactTestInstance) => ?boolean;
56
75 -const UPDATE_SIGNAL = {};
76 -
77 -function getPublicInstance(inst: Instance | TextInstance): * {
78 - switch (inst.tag) {
79 - case 'INSTANCE':
80 - const createNodeMock = inst.rootContainerInstance.createNodeMock;
81 - return createNodeMock({
82 - type: inst.type,
83 - props: inst.props,
84 - });
85 - default:
86 - return inst;
87 - }
88 -}
89 -
90 -function appendChild(
91 - parentInstance: Instance | Container,
92 - child: Instance | TextInstance,
93 -): void {
94 - const index = parentInstance.children.indexOf(child);
95 - if (index !== -1) {
96 - parentInstance.children.splice(index, 1);
97 - }
98 - parentInstance.children.push(child);
99 -}
100 -
101 -function insertBefore(
102 - parentInstance: Instance | Container,
103 - child: Instance | TextInstance,
104 - beforeChild: Instance | TextInstance,
105 -): void {
106 - const index = parentInstance.children.indexOf(child);
107 - if (index !== -1) {
108 - parentInstance.children.splice(index, 1);
109 - }
110 - const beforeIndex = parentInstance.children.indexOf(beforeChild);
111 - parentInstance.children.splice(beforeIndex, 0, child);
112 -}
113 -
114 -function removeChild(
115 - parentInstance: Instance | Container,
116 - child: Instance | TextInstance,
117 -): void {
118 - const index = parentInstance.children.indexOf(child);
119 - parentInstance.children.splice(index, 1);
120 -}
121 -
122 -// Current virtual time
123 -let nowImplementation = () => 0;
124 -let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
125 -let yieldedValues: Array<mixed> | null = null;
126 -
127 -const TestRenderer = ReactFiberReconciler({
128 - getRootHostContext() {
129 - return emptyObject;
130 - },
131 -
132 - getChildHostContext() {
133 - return emptyObject;
134 - },
135 -
136 - prepareForCommit(): void {
137 - // noop
138 - },
139 -
140 - resetAfterCommit(): void {
141 - // noop
142 - },
143 -
144 - createInstance(
145 - type: string,
146 - props: Props,
147 - rootContainerInstance: Container,
148 - hostContext: Object,
149 - internalInstanceHandle: Object,
150 - ): Instance {
151 - return {
152 - type,
153 - props,
154 - children: [],
155 - rootContainerInstance,
156 - tag: 'INSTANCE',
157 - };
158 - },
159 -
160 - appendInitialChild(
161 - parentInstance: Instance,
162 - child: Instance | TextInstance,
163 - ): void {
164 - const index = parentInstance.children.indexOf(child);
165 - if (index !== -1) {
166 - parentInstance.children.splice(index, 1);
167 - }
168 - parentInstance.children.push(child);
169 - },
170 -
171 - finalizeInitialChildren(
172 - testElement: Instance,
173 - type: string,
174 - props: Props,
175 - rootContainerInstance: Container,
176 - ): boolean {
177 - return false;
178 - },
179 -
180 - prepareUpdate(
181 - testElement: Instance,
182 - type: string,
183 - oldProps: Props,
184 - newProps: Props,
185 - rootContainerInstance: Container,
186 - hostContext: Object,
187 - ): null | {} {
188 - return UPDATE_SIGNAL;
189 - },
190 -
191 - shouldSetTextContent(type: string, props: Props): boolean {
192 - return false;
193 - },
194 -
195 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
196 - return false;
197 - },
198 -
199 - createTextInstance(
200 - text: string,
201 - rootContainerInstance: Container,
202 - hostContext: Object,
203 - internalInstanceHandle: Object,
204 - ): TextInstance {
205 - return {
206 - text,
207 - tag: 'TEXT',
208 - };
209 - },
210 -
211 - scheduleDeferredCallback(
212 - callback: (deadline: Deadline) => mixed,
213 - options?: {timeout: number},
214 - ): number {
215 - scheduledCallback = callback;
216 - return 0;
217 - },
218 -
219 - cancelDeferredCallback(timeoutID: number): void {
220 - scheduledCallback = null;
221 - },
222 -
223 - getPublicInstance,
224 -
225 - // This approach enables `now` to be mocked by tests,
226 - // Even after the reconciler has initialized and read host config values.
227 - now: () => nowImplementation(),
228 -
229 - isPrimaryRenderer: true,
230 -
231 - mutation: {
232 - commitUpdate(
233 - instance: Instance,
234 - updatePayload: {},
235 - type: string,
236 - oldProps: Props,
237 - newProps: Props,
238 - internalInstanceHandle: Object,
239 - ): void {
240 - instance.type = type;
241 - instance.props = newProps;
242 - },
243 -
244 - commitMount(
245 - instance: Instance,
246 - type: string,
247 - newProps: Props,
248 - internalInstanceHandle: Object,
249 - ): void {
250 - // noop
251 - },
252 -
253 - commitTextUpdate(
254 - textInstance: TextInstance,
255 - oldText: string,
256 - newText: string,
257 - ): void {
258 - textInstance.text = newText;
259 - },
260 - resetTextContent(testElement: Instance): void {
261 - // noop
262 - },
263 -
264 - appendChild: appendChild,
265 - appendChildToContainer: appendChild,
266 - insertBefore: insertBefore,
267 - insertInContainerBefore: insertBefore,
268 - removeChild: removeChild,
269 - removeChildFromContainer: removeChild,
270 - },
271 -});
57 +const TestRenderer = ReactFiberReconciler(ReactTestHostConfig);
58
59 const defaultTestOptions = {
60 createNodeMock: function() {
@@ -444,7 +230,7 @@ class ReactTestInstance {
230
231 get instance() {
232 if (this._fiber.tag === HostComponent) {
447 - return getPublicInstance(this._fiber.stateNode);
233 + return ReactTestHostConfig.getPublicInstance(this._fiber.stateNode);
234 } else {
235 return this._fiber.stateNode;
236 }
@@ -676,77 +462,20 @@ const ReactTestRendererFiber = {
462 container = null;
463 root = null;
464 },
679 - unstable_flushAll(): Array<mixed> {
680 - yieldedValues = null;
681 - while (scheduledCallback !== null) {
682 - const cb = scheduledCallback;
683 - scheduledCallback = null;
684 - cb({
685 - timeRemaining() {
686 - // Keep rendering until there's no more work
687 - return 999;
688 - },
689 - // React's scheduler has its own way of keeping track of expired
690 - // work and doesn't read this, so don't bother setting it to the
691 - // correct value.
692 - didTimeout: false,
693 - });
694 - }
695 - if (yieldedValues === null) {
696 - // Always return an array.
697 - return [];
698 - }
699 - return yieldedValues;
700 - },
701 - unstable_flushThrough(expectedValues: Array<mixed>): Array<mixed> {
702 - let didStop = false;
703 - yieldedValues = null;
704 - while (scheduledCallback !== null && !didStop) {
705 - const cb = scheduledCallback;
706 - scheduledCallback = null;
707 - cb({
708 - timeRemaining() {
709 - if (
710 - yieldedValues !== null &&
711 - yieldedValues.length >= expectedValues.length
712 - ) {
713 - // We at least as many values as expected. Stop rendering.
714 - didStop = true;
715 - return 0;
716 - }
717 - // Keep rendering.
718 - return 999;
719 - },
720 - // React's scheduler has its own way of keeping track of expired
721 - // work and doesn't read this, so don't bother setting it to the
722 - // correct value.
723 - didTimeout: false,
724 - });
725 - }
726 - if (yieldedValues === null) {
727 - // Always return an array.
728 - return [];
729 - }
730 - return yieldedValues;
731 - },
732 - unstable_yield(value: mixed): void {
733 - if (yieldedValues === null) {
734 - yieldedValues = [value];
735 - } else {
736 - yieldedValues.push(value);
737 - }
738 - },
465 getInstance() {
466 if (root == null || root.current == null) {
467 return null;
468 }
469 return TestRenderer.getPublicRootInstance(root);
470 },
471 + unstable_flushAll: TestRendererScheduling.flushAll,
472 unstable_flushSync(fn: Function) {
746 - yieldedValues = [];
747 - TestRenderer.flushSync(fn);
748 - return yieldedValues;
473 + return TestRendererScheduling.withCleanYields(() => {
474 + TestRenderer.flushSync(fn);
475 + });
476 },
477 + unstable_flushThrough: TestRendererScheduling.flushThrough,
478 + unstable_yield: TestRendererScheduling.yieldValue,
479 };
480
481 Object.defineProperty(
@@ -771,9 +500,7 @@ const ReactTestRendererFiber = {
500 unstable_batchedUpdates: batchedUpdates,
501 /* eslint-enable camelcase */
502
774 - unstable_setNowImplementation(implementation: () => number): void {
775 - nowImplementation = implementation;
776 - },
503 + unstable_setNowImplementation: TestRendererScheduling.setNowImplementation,
504 };
505
506 export default ReactTestRendererFiber;
packages/react-test-renderer/src/ReactTestRendererScheduling.js new
+100
@@ -0,0 +1,100 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
11 +
12 +// Current virtual time
13 +export let nowImplementation = () => 0;
14 +export let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
15 +export let yieldedValues: Array<mixed> | null = null;
16 +
17 +export function scheduleDeferredCallback(
18 + callback: (deadline: Deadline) => mixed,
19 + options?: {timeout: number},
20 +): number {
21 + scheduledCallback = callback;
22 + return 0;
23 +}
24 +
25 +export function cancelDeferredCallback(timeoutID: number): void {
26 + scheduledCallback = null;
27 +}
28 +
29 +export function setNowImplementation(implementation: () => number): void {
30 + nowImplementation = implementation;
31 +}
32 +
33 +export function flushAll(): Array<mixed> {
34 + yieldedValues = null;
35 + while (scheduledCallback !== null) {
36 + const cb = scheduledCallback;
37 + scheduledCallback = null;
38 + cb({
39 + timeRemaining() {
40 + // Keep rendering until there's no more work
41 + return 999;
42 + },
43 + // React's scheduler has its own way of keeping track of expired
44 + // work and doesn't read this, so don't bother setting it to the
45 + // correct value.
46 + didTimeout: false,
47 + });
48 + }
49 + if (yieldedValues === null) {
50 + // Always return an array.
51 + return [];
52 + }
53 + return yieldedValues;
54 +}
55 +
56 +export function flushThrough(expectedValues: Array<mixed>): Array<mixed> {
57 + let didStop = false;
58 + yieldedValues = null;
59 + while (scheduledCallback !== null && !didStop) {
60 + const cb = scheduledCallback;
61 + scheduledCallback = null;
62 + cb({
63 + timeRemaining() {
64 + if (
65 + yieldedValues !== null &&
66 + yieldedValues.length >= expectedValues.length
67 + ) {
68 + // We at least as many values as expected. Stop rendering.
69 + didStop = true;
70 + return 0;
71 + }
72 + // Keep rendering.
73 + return 999;
74 + },
75 + // React's scheduler has its own way of keeping track of expired
76 + // work and doesn't read this, so don't bother setting it to the
77 + // correct value.
78 + didTimeout: false,
79 + });
80 + }
81 + if (yieldedValues === null) {
82 + // Always return an array.
83 + return [];
84 + }
85 + return yieldedValues;
86 +}
87 +
88 +export function yieldValue(value: mixed): void {
89 + if (yieldedValues === null) {
90 + yieldedValues = [value];
91 + } else {
92 + yieldedValues.push(value);
93 + }
94 +}
95 +
96 +export function withCleanYields(fn: Function) {
97 + yieldedValues = [];
98 + fn();
99 + return yieldedValues;
100 +}