@samitouri / QOS-React-2 / commits / 1dca72ebfa

Thread native-parent pointers through everything

Now we don't repurpose context for our own secret needs (hi Dan). In this diff I avoid storing the native parent on native (DOM) components and store it only on composites, but we'll probably want to store it on native components too soon for event bubbling.

Ben Alpert committed Sep 30, 2015 at 14:42 UTC 1dca72ebfa97030292aed360a30cf59ef8c4c065
16 files changed +159 -113
src/renderers/dom/client/ReactMount.js
+7 -25
@@ -15,7 +15,7 @@ var ClientReactRootIndex = require('ClientReactRootIndex');
15 var DOMProperty = require('DOMProperty');
16 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
17 var ReactCurrentOwner = require('ReactCurrentOwner');
18 -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
18 +var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
19 var ReactElement = require('ReactElement');
20 var ReactEmptyComponentRegistry = require('ReactEmptyComponentRegistry');
21 var ReactInstanceHandles = require('ReactInstanceHandles');
@@ -26,14 +26,12 @@ var ReactReconciler = require('ReactReconciler');
26 var ReactUpdateQueue = require('ReactUpdateQueue');
27 var ReactUpdates = require('ReactUpdates');
28
29 -var assign = require('Object.assign');
29 var emptyObject = require('emptyObject');
30 var containsNode = require('containsNode');
31 var instantiateReactComponent = require('instantiateReactComponent');
32 var invariant = require('invariant');
33 var setInnerHTML = require('setInnerHTML');
34 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
36 -var validateDOMNesting = require('validateDOMNesting');
35 var warning = require('warning');
36
37 var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -43,9 +41,6 @@ var ELEMENT_NODE_TYPE = 1;
41 var DOC_NODE_TYPE = 9;
42 var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
43
46 -var ownerDocumentContextKey =
47 - '__ReactMount_ownerDocument$' + Math.random().toString(36).slice(2);
48 -
44
45 /** Mapping from reactRootID to React component instance. */
46 var instancesByReactRootID = {};
@@ -279,24 +274,13 @@ function mountComponentIntoNode(
274 shouldReuseMarkup,
275 context
276 ) {
282 - if (ReactDOMFeatureFlags.useCreateElement) {
283 - context = assign({}, context);
284 - if (container.nodeType === DOC_NODE_TYPE) {
285 - context[ownerDocumentContextKey] = container;
286 - } else {
287 - context[ownerDocumentContextKey] = container.ownerDocument;
288 - }
289 - }
290 - if (__DEV__) {
291 - if (context === emptyObject) {
292 - context = {};
293 - }
294 - var tag = container.nodeName.toLowerCase();
295 - context[validateDOMNesting.ancestorInfoContextKey] =
296 - validateDOMNesting.updatedAncestorInfo(null, tag, null);
297 - }
277 var markup = ReactReconciler.mountComponent(
299 - componentInstance, rootID, transaction, context
278 + componentInstance,
279 + rootID,
280 + transaction,
281 + null,
282 + ReactDOMContainerInfo(container),
283 + context
284 );
285 componentInstance._renderedComponent._topLevelWrapper = componentInstance;
286 ReactMount._mountImageIntoNode(
@@ -1081,8 +1065,6 @@ var ReactMount = {
1065 }
1066 },
1067
1084 - ownerDocumentContextKey: ownerDocumentContextKey,
1085 -
1068 /**
1069 * React ID utilities.
1070 */
src/renderers/dom/client/validateDOMNesting.js
-3
@@ -412,9 +412,6 @@ if (__DEV__) {
412 }
413 };
414
415 - validateDOMNesting.ancestorInfoContextKey =
416 - '__validateDOMNesting_ancestorInfo$' + Math.random().toString(36).slice(2);
417 -
415 validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo;
416
417 // For testing
src/renderers/dom/client/wrappers/ReactDOMButton.js
+1 -1
@@ -30,7 +30,7 @@ var mouseListenerNames = {
30 * when `disabled` is set.
31 */
32 var ReactDOMButton = {
33 - getNativeProps: function(inst, props, context) {
33 + getNativeProps: function(inst, props) {
34 if (!props.disabled) {
35 return props;
36 }
src/renderers/dom/client/wrappers/ReactDOMInput.js
+1 -1
@@ -49,7 +49,7 @@ function forceUpdateIfMounted() {
49 * @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
50 */
51 var ReactDOMInput = {
52 - getNativeProps: function(inst, props, context) {
52 + getNativeProps: function(inst, props) {
53 var value = LinkedValueUtils.getValue(props);
54 var checked = LinkedValueUtils.getChecked(props);
55
src/renderers/dom/client/wrappers/ReactDOMOption.js
+8 -7
@@ -17,13 +17,11 @@ var ReactDOMSelect = require('ReactDOMSelect');
17 var assign = require('Object.assign');
18 var warning = require('warning');
19
20 -var valueContextKey = ReactDOMSelect.valueContextKey;
21 -
20 /**
21 * Implements an <option> native component that warns when `selected` is set.
22 */
23 var ReactDOMOption = {
26 - mountWrapper: function(inst, props, context) {
24 + mountWrapper: function(inst, props, nativeParent) {
25 // TODO (yungsters): Remove support for `selected` in <option>.
26 if (__DEV__) {
27 warning(
@@ -33,10 +31,13 @@ var ReactDOMOption = {
31 );
32 }
33
36 - // Look up whether this option is 'selected' via context
37 - var selectValue = context[valueContextKey];
34 + // Look up whether this option is 'selected'
35 + var selectValue = null;
36 + if (nativeParent != null && nativeParent._tag === 'select') {
37 + selectValue = ReactDOMSelect.getSelectValueContext(nativeParent);
38 + }
39
39 - // If context key is null (e.g., no specified value or after initial mount)
40 + // If the value is null (e.g., no specified value or after initial mount)
41 // or missing (e.g., for <datalist>), we don't change props.selected
42 var selected = null;
43 if (selectValue != null) {
@@ -57,7 +58,7 @@ var ReactDOMOption = {
58 inst._wrapperState = {selected: selected};
59 },
60
60 - getNativeProps: function(inst, props, context) {
61 + getNativeProps: function(inst, props) {
62 var nativeProps = assign({selected: undefined, children: undefined}, props);
63
64 // Read state only from initial mount because <select> updates value
src/renderers/dom/client/wrappers/ReactDOMSelect.js
+6 -13
@@ -20,9 +20,6 @@ var warning = require('warning');
20
21 var didWarnValueLink = false;
22
23 -var valueContextKey =
24 - '__ReactDOMSelect_value$' + Math.random().toString(36).slice(2);
25 -
23 function updateOptionsIfPendingUpdateAndMounted() {
24 if (this._rootNodeID && this._wrapperState.pendingUpdate) {
25 this._wrapperState.pendingUpdate = false;
@@ -146,9 +143,7 @@ function updateOptions(inst, multiple, propValue) {
143 * selected.
144 */
145 var ReactDOMSelect = {
149 - valueContextKey: valueContextKey,
150 -
151 - getNativeProps: function(inst, props, context) {
146 + getNativeProps: function(inst, props) {
147 return assign({}, props, {
148 onChange: inst._wrapperState.onChange,
149 value: undefined,
@@ -169,19 +164,17 @@ var ReactDOMSelect = {
164 };
165 },
166
172 - processChildContext: function(inst, props, context) {
173 - // Pass down initial value so initial generated markup has correct
174 - // `selected` attributes
175 - var childContext = assign({}, context);
176 - childContext[valueContextKey] = inst._wrapperState.initialValue;
177 - return childContext;
167 + getSelectValueContext: function(inst) {
168 + // ReactDOMOption looks at this initial value so the initial generated
169 + // markup has correct `selected` attributes
170 + return inst._wrapperState.initialValue;
171 },
172
173 postUpdateWrapper: function(inst) {
174 var props = inst._currentElement.props;
175
176 // After the initial mount, we control selected-ness manually so don't pass
184 - // the context value down
177 + // this value down
178 inst._wrapperState.initialValue = undefined;
179
180 var wasMultiple = inst._wrapperState.wasMultiple;
src/renderers/dom/client/wrappers/ReactDOMTextarea.js
+1 -1
@@ -44,7 +44,7 @@ function forceUpdateIfMounted() {
44 * `defaultValue` if specified, or the children content (deprecated).
45 */
46 var ReactDOMTextarea = {
47 - getNativeProps: function(inst, props, context) {
47 + getNativeProps: function(inst, props) {
48 invariant(
49 props.dangerouslySetInnerHTML == null,
50 '`dangerouslySetInnerHTML` does not make sense on <textarea>.'
src/renderers/dom/server/ReactServerRendering.js
+7 -2
@@ -46,8 +46,13 @@ function renderToStringImpl(element, makeStaticMarkup) {
46
47 return transaction.perform(function() {
48 var componentInstance = instantiateReactComponent(element, null);
49 - var markup =
50 - componentInstance.mountComponent(id, transaction, emptyObject);
49 + var markup = componentInstance.mountComponent(
50 + id,
51 + transaction,
52 + null,
53 + null,
54 + emptyObject
55 + );
56 if (!makeStaticMarkup) {
57 markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
58 }
src/renderers/dom/shared/ReactDOMComponent.js
+34 -47
@@ -474,15 +474,6 @@ function validateDangerousTag(tag) {
474 }
475 }
476
477 -function processChildContextDev(context, inst) {
478 - // Pass down our tag name to child components for validation purposes
479 - context = assign({}, context);
480 - var info = context[validateDOMNesting.ancestorInfoContextKey];
481 - context[validateDOMNesting.ancestorInfoContextKey] =
482 - validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst);
483 - return context;
484 -}
485 -
477 function isCustomComponent(tagName, props) {
478 return tagName.indexOf('-') >= 0 || props.is != null;
479 }
@@ -517,12 +508,12 @@ function ReactDOMComponent(tag) {
508 this._previousStyleCopy = null;
509 this._nativeNode = null;
510 this._rootNodeID = null;
511 + this._nativeContainerInfo = null;
512 this._wrapperState = null;
513 this._topLevelWrapper = null;
514 this._nodeHasLegacyProperties = false;
515 if (__DEV__) {
524 - this._unprocessedContextDev = null;
525 - this._processedContextDev = null;
516 + this._ancestorInfo = null;
517 }
518 }
519
@@ -541,11 +532,20 @@ ReactDOMComponent.Mixin = {
532 * @internal
533 * @param {string} rootID The root DOM ID for this node.
534 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
535 + * @param {?ReactDOMComponent} the containing DOM component instance
536 + * @param {?object} info about the native container
537 * @param {object} context
538 * @return {string} The computed markup.
539 */
547 - mountComponent: function(rootID, transaction, context) {
540 + mountComponent: function(
541 + rootID,
542 + transaction,
543 + nativeParent,
544 + nativeContainerInfo,
545 + context
546 + ) {
547 this._rootNodeID = rootID;
548 + this._nativeContainerInfo = nativeContainerInfo;
549
550 var props = this._currentElement.props;
551
@@ -561,47 +561,46 @@ ReactDOMComponent.Mixin = {
561 transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
562 break;
563 case 'button':
564 - props = ReactDOMButton.getNativeProps(this, props, context);
564 + props = ReactDOMButton.getNativeProps(this, props, nativeParent);
565 break;
566 case 'input':
567 - ReactDOMInput.mountWrapper(this, props, context);
568 - props = ReactDOMInput.getNativeProps(this, props, context);
567 + ReactDOMInput.mountWrapper(this, props, nativeParent);
568 + props = ReactDOMInput.getNativeProps(this, props);
569 break;
570 case 'option':
571 - ReactDOMOption.mountWrapper(this, props, context);
572 - props = ReactDOMOption.getNativeProps(this, props, context);
571 + ReactDOMOption.mountWrapper(this, props, nativeParent);
572 + props = ReactDOMOption.getNativeProps(this, props);
573 break;
574 case 'select':
575 - ReactDOMSelect.mountWrapper(this, props, context);
576 - props = ReactDOMSelect.getNativeProps(this, props, context);
577 - context = ReactDOMSelect.processChildContext(this, props, context);
575 + ReactDOMSelect.mountWrapper(this, props, nativeParent);
576 + props = ReactDOMSelect.getNativeProps(this, props);
577 break;
578 case 'textarea':
580 - ReactDOMTextarea.mountWrapper(this, props, context);
581 - props = ReactDOMTextarea.getNativeProps(this, props, context);
579 + ReactDOMTextarea.mountWrapper(this, props, nativeParent);
580 + props = ReactDOMTextarea.getNativeProps(this, props);
581 break;
582 }
583
584 assertValidProps(this, props);
585 if (__DEV__) {
587 - if (context[validateDOMNesting.ancestorInfoContextKey]) {
588 - validateDOMNesting(
589 - this._tag,
590 - this,
591 - context[validateDOMNesting.ancestorInfoContextKey]
592 - );
586 + var parentInfo;
587 + if (nativeParent != null) {
588 + parentInfo = nativeParent._ancestorInfo;
589 + } else if (nativeContainerInfo != null) {
590 + parentInfo = nativeContainerInfo._ancestorInfo;
591 }
594 - }
595 -
596 - if (__DEV__) {
597 - this._unprocessedContextDev = context;
598 - this._processedContextDev = processChildContextDev(context, this);
599 - context = this._processedContextDev;
592 + if (parentInfo) {
593 + // parentInfo should always be present except for the top-level
594 + // component when server rendering
595 + validateDOMNesting(this._tag, this, parentInfo);
596 + }
597 + this._ancestorInfo =
598 + validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
599 }
600
601 var mountImage;
602 if (transaction.useCreateElement) {
604 - var ownerDocument = context[ReactMount.ownerDocumentContextKey];
603 + var ownerDocument = nativeContainerInfo._ownerDocument;
604 var el = ownerDocument.createElement(this._currentElement.type);
605 this._nativeNode = el;
606 DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
@@ -835,18 +834,6 @@ ReactDOMComponent.Mixin = {
834 break;
835 }
836
838 - if (__DEV__) {
839 - // If the context is reference-equal to the old one, pass down the same
840 - // processed object so the update bailout in ReactReconciler behaves
841 - // correctly (and identically in dev and prod). See #5005.
842 - if (this._unprocessedContextDev !== context) {
843 - this._unprocessedContextDev = context;
844 - this._processedContextDev = processChildContextDev(context, this);
845 - }
846 - context = this._processedContextDev;
847 - }
848 -
849 -
837 assertValidProps(this, nextProps);
838 this._updateDOMProperties(lastProps, nextProps, transaction);
839 this._updateDOMChildren(
src/renderers/dom/shared/ReactDOMContainerInfo.js new
+30
@@ -0,0 +1,30 @@
1 +/**
2 + * Copyright 2013-2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactDOMContainerInfo
10 + */
11 +
12 +'use strict';
13 +
14 +var validateDOMNesting = require('validateDOMNesting');
15 +
16 +var DOC_NODE_TYPE = 9;
17 +
18 +function ReactDOMContainerInfo(node) {
19 + var info = {
20 + _ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
21 + };
22 + if (__DEV__) {
23 + var tag = node.nodeName.toLowerCase();
24 + info._ancestorInfo =
25 + validateDOMNesting.updatedAncestorInfo(null, tag, null);
26 + }
27 + return info;
28 +}
29 +
30 +module.exports = ReactDOMContainerInfo;
src/renderers/dom/shared/ReactDOMTextComponent.js
+18 -8
@@ -68,20 +68,30 @@ assign(ReactDOMTextComponent.prototype, {
68 * @return {string} Markup for this text node.
69 * @internal
70 */
71 - mountComponent: function(rootID, transaction, context) {
71 + mountComponent: function(
72 + rootID,
73 + transaction,
74 + nativeParent,
75 + nativeContainerInfo,
76 + context
77 + ) {
78 if (__DEV__) {
73 - if (context[validateDOMNesting.ancestorInfoContextKey]) {
74 - validateDOMNesting(
75 - 'span',
76 - null,
77 - context[validateDOMNesting.ancestorInfoContextKey]
78 - );
79 + var parentInfo;
80 + if (nativeParent != null) {
81 + parentInfo = nativeParent._ancestorInfo;
82 + } else if (nativeContainerInfo != null) {
83 + parentInfo = nativeContainerInfo._ancestorInfo;
84 + }
85 + if (parentInfo) {
86 + // parentInfo should always be present except for the top-level
87 + // component when server rendering
88 + validateDOMNesting(this._tag, this, parentInfo);
89 }
90 }
91
92 this._rootNodeID = rootID;
93 if (transaction.useCreateElement) {
84 - var ownerDocument = context[ReactMount.ownerDocumentContextKey];
94 + var ownerDocument = nativeContainerInfo._ownerDocument;
95 var el = ownerDocument.createElement('span');
96 this._nativeNode = el;
97 DOMPropertyOperations.setAttributeForID(el, rootID);
src/renderers/shared/reconciler/ReactCompositeComponent.js
+15 -1
@@ -96,6 +96,8 @@ var ReactCompositeComponentMixin = {
96 this._currentElement = element;
97 this._rootNodeID = null;
98 this._instance = null;
99 + this._nativeParent = null;
100 + this._nativeContainerInfo = null;
101
102 // See ReactUpdateQueue
103 this._pendingElement = null;
@@ -122,10 +124,18 @@ var ReactCompositeComponentMixin = {
124 * @final
125 * @internal
126 */
125 - mountComponent: function(rootID, transaction, context) {
127 + mountComponent: function(
128 + rootID,
129 + transaction,
130 + nativeParent,
131 + nativeContainerInfo,
132 + context
133 + ) {
134 this._context = context;
135 this._mountOrder = nextMountID++;
136 this._rootNodeID = rootID;
137 + this._nativeParent = nativeParent;
138 + this._nativeContainerInfo = nativeContainerInfo;
139
140 var publicProps = this._processProps(this._currentElement.props);
141 var publicContext = this._processContext(context);
@@ -288,6 +298,8 @@ var ReactCompositeComponentMixin = {
298 this._renderedComponent,
299 rootID,
300 transaction,
301 + nativeParent,
302 + nativeContainerInfo,
303 this._processChildContext(context)
304 );
305 if (inst.componentDidMount) {
@@ -739,6 +751,8 @@ var ReactCompositeComponentMixin = {
751 this._renderedComponent,
752 this._rootNodeID,
753 transaction,
754 + this._nativeParent,
755 + this._nativeContainerInfo,
756 this._processChildContext(context)
757 );
758 this._replaceNodeWithMarkup(oldNativeNode, nextMarkup);
src/renderers/shared/reconciler/ReactEmptyComponent.js
+9 -1
@@ -33,13 +33,21 @@ var ReactEmptyComponent = function(instantiate) {
33 assign(ReactEmptyComponent.prototype, {
34 construct: function(element) {
35 },
36 - mountComponent: function(rootID, transaction, context) {
36 + mountComponent: function(
37 + rootID,
38 + transaction,
39 + nativeParent,
40 + nativeContainerInfo,
41 + context
42 + ) {
43 ReactEmptyComponentRegistry.registerNullComponentID(rootID);
44 this._rootNodeID = rootID;
45 return ReactReconciler.mountComponent(
46 this._renderedComponent,
47 rootID,
48 transaction,
49 + nativeParent,
50 + nativeContainerInfo,
51 context
52 );
53 },
src/renderers/shared/reconciler/ReactMultiChild.js
+4
@@ -255,6 +255,8 @@ var ReactMultiChild = {
255 child,
256 rootID,
257 transaction,
258 + this,
259 + this._nativeContainerInfo,
260 context
261 );
262 child._mountIndex = index++;
@@ -503,6 +505,8 @@ var ReactMultiChild = {
505 child,
506 rootID,
507 transaction,
508 + this,
509 + this._nativeContainerInfo,
510 context
511 );
512 child._mountIndex = index;
src/renderers/shared/reconciler/ReactReconciler.js
+17 -2
@@ -29,12 +29,27 @@ var ReactReconciler = {
29 * @param {ReactComponent} internalInstance
30 * @param {string} rootID DOM ID of the root node.
31 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
32 + * @param {?object} the containing native component instance
33 + * @param {?object} info about the native container
34 * @return {?string} Rendered markup to be inserted into the DOM.
35 * @final
36 * @internal
37 */
36 - mountComponent: function(internalInstance, rootID, transaction, context) {
37 - var markup = internalInstance.mountComponent(rootID, transaction, context);
38 + mountComponent: function(
39 + internalInstance,
40 + rootID,
41 + transaction,
42 + nativeParent,
43 + nativeContainerInfo,
44 + context
45 + ) {
46 + var markup = internalInstance.mountComponent(
47 + rootID,
48 + transaction,
49 + nativeParent,
50 + nativeContainerInfo,
51 + context
52 + );
53 if (internalInstance._currentElement &&
54 internalInstance._currentElement.ref != null) {
55 transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
src/test/ReactTestUtils.js
+1 -1
@@ -446,7 +446,7 @@ ReactShallowRenderer.prototype._render = function(element, transaction, context)
446 var instance = new ShallowComponentWrapper(element.type);
447 instance.construct(element);
448
449 - instance.mountComponent(rootID, transaction, context);
449 + instance.mountComponent(rootID, transaction, null, null, context);
450
451 this._instance = instance;
452 }