Merge pull request #5014 from spicyj/native-parents

Thread native-parent pointers through everything
This commit is contained in:
Ben Alpert committed 2015-10-09 15:07:59 -07:00
commit fb9e1ed475
18 files changed
+217 -132

No files matched your search

+7 -25
View File
@@ -15,7 +15,7 @@ var ClientReactRootIndex = require('ClientReactRootIndex');
var DOMProperty = require('DOMProperty');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
var ReactElement = require('ReactElement');
var ReactEmptyComponentRegistry = require('ReactEmptyComponentRegistry');
var ReactInstanceHandles = require('ReactInstanceHandles');
@@ -26,14 +26,12 @@ var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');
var assign = require('Object.assign');
var emptyObject = require('emptyObject');
var containsNode = require('containsNode');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var validateDOMNesting = require('validateDOMNesting');
var warning = require('warning');
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -43,9 +41,6 @@ var ELEMENT_NODE_TYPE = 1;
var DOC_NODE_TYPE = 9;
var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
var ownerDocumentContextKey =
'__ReactMount_ownerDocument$' + Math.random().toString(36).slice(2);
/** Mapping from reactRootID to React component instance. */
var instancesByReactRootID = {};
@@ -279,24 +274,13 @@ function mountComponentIntoNode(
shouldReuseMarkup,
context
) {
if (ReactDOMFeatureFlags.useCreateElement) {
context = assign({}, context);
if (container.nodeType === DOC_NODE_TYPE) {
context[ownerDocumentContextKey] = container;
} else {
context[ownerDocumentContextKey] = container.ownerDocument;
}
}
if (__DEV__) {
if (context === emptyObject) {
context = {};
}
var tag = container.nodeName.toLowerCase();
context[validateDOMNesting.ancestorInfoContextKey] =
validateDOMNesting.updatedAncestorInfo(null, tag, null);
}
var markup = ReactReconciler.mountComponent(
componentInstance, rootID, transaction, context
componentInstance,
rootID,
transaction,
null,
ReactDOMContainerInfo(container),
context
);
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
ReactMount._mountImageIntoNode(
@@ -1081,8 +1065,6 @@ var ReactMount = {
}
},
ownerDocumentContextKey: ownerDocumentContextKey,
/**
* React ID utilities.
*/
@@ -412,9 +412,6 @@ if (__DEV__) {
}
};
validateDOMNesting.ancestorInfoContextKey =
'__validateDOMNesting_ancestorInfo$' + Math.random().toString(36).slice(2);
validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo;
// For testing
@@ -30,7 +30,7 @@ var mouseListenerNames = {
* when `disabled` is set.
*/
var ReactDOMButton = {
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
if (!props.disabled) {
return props;
}
@@ -49,7 +49,7 @@ function forceUpdateIfMounted() {
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
*/
var ReactDOMInput = {
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
var value = LinkedValueUtils.getValue(props);
var checked = LinkedValueUtils.getChecked(props);
@@ -17,13 +17,11 @@ var ReactDOMSelect = require('ReactDOMSelect');
var assign = require('Object.assign');
var warning = require('warning');
var valueContextKey = ReactDOMSelect.valueContextKey;
/**
* Implements an <option> native component that warns when `selected` is set.
*/
var ReactDOMOption = {
mountWrapper: function(inst, props, context) {
mountWrapper: function(inst, props, nativeParent) {
// TODO (yungsters): Remove support for `selected` in <option>.
if (__DEV__) {
warning(
@@ -33,10 +31,13 @@ var ReactDOMOption = {
);
}
// Look up whether this option is 'selected' via context
var selectValue = context[valueContextKey];
// Look up whether this option is 'selected'
var selectValue = null;
if (nativeParent != null && nativeParent._tag === 'select') {
selectValue = ReactDOMSelect.getSelectValueContext(nativeParent);
}
// If context key is null (e.g., no specified value or after initial mount)
// If the value is null (e.g., no specified value or after initial mount)
// or missing (e.g., for <datalist>), we don't change props.selected
var selected = null;
if (selectValue != null) {
@@ -57,7 +58,7 @@ var ReactDOMOption = {
inst._wrapperState = {selected: selected};
},
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
var nativeProps = assign({selected: undefined, children: undefined}, props);
// Read state only from initial mount because <select> updates value
@@ -20,9 +20,6 @@ var warning = require('warning');
var didWarnValueLink = false;
var valueContextKey =
'__ReactDOMSelect_value$' + Math.random().toString(36).slice(2);
function updateOptionsIfPendingUpdateAndMounted() {
if (this._rootNodeID && this._wrapperState.pendingUpdate) {
this._wrapperState.pendingUpdate = false;
@@ -146,9 +143,7 @@ function updateOptions(inst, multiple, propValue) {
* selected.
*/
var ReactDOMSelect = {
valueContextKey: valueContextKey,
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
return assign({}, props, {
onChange: inst._wrapperState.onChange,
value: undefined,
@@ -169,19 +164,17 @@ var ReactDOMSelect = {
};
},
processChildContext: function(inst, props, context) {
// Pass down initial value so initial generated markup has correct
// `selected` attributes
var childContext = assign({}, context);
childContext[valueContextKey] = inst._wrapperState.initialValue;
return childContext;
getSelectValueContext: function(inst) {
// ReactDOMOption looks at this initial value so the initial generated
// markup has correct `selected` attributes
return inst._wrapperState.initialValue;
},
postUpdateWrapper: function(inst) {
var props = inst._currentElement.props;
// After the initial mount, we control selected-ness manually so don't pass
// the context value down
// this value down
inst._wrapperState.initialValue = undefined;
var wasMultiple = inst._wrapperState.wasMultiple;
@@ -44,7 +44,7 @@ function forceUpdateIfMounted() {
* `defaultValue` if specified, or the children content (deprecated).
*/
var ReactDOMTextarea = {
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
invariant(
props.dangerouslySetInnerHTML == null,
'`dangerouslySetInnerHTML` does not make sense on <textarea>.'
@@ -46,8 +46,13 @@ function renderToStringImpl(element, makeStaticMarkup) {
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
var markup =
componentInstance.mountComponent(id, transaction, emptyObject);
var markup = componentInstance.mountComponent(
id,
transaction,
null,
null,
emptyObject
);
if (!makeStaticMarkup) {
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
}
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule DOMNamespaces
*/
'use strict';
var DOMNamespaces = {
html: 'http://www.w3.org/1999/xhtml',
mathml: 'http://www.w3.org/1998/Math/MathML',
svg: 'http://www.w3.org/2000/svg',
};
module.exports = DOMNamespaces;
+68 -46
View File
@@ -16,6 +16,7 @@
var AutoFocusUtils = require('AutoFocusUtils');
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMNamespaces = require('DOMNamespaces');
var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var EventConstants = require('EventConstants');
@@ -474,15 +475,6 @@ function validateDangerousTag(tag) {
}
}
function processChildContextDev(context, inst) {
// Pass down our tag name to child components for validation purposes
context = assign({}, context);
var info = context[validateDOMNesting.ancestorInfoContextKey];
context[validateDOMNesting.ancestorInfoContextKey] =
validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst);
return context;
}
function isCustomComponent(tagName, props) {
return tagName.indexOf('-') >= 0 || props.is != null;
}
@@ -512,17 +504,18 @@ function getNode(inst) {
function ReactDOMComponent(tag) {
validateDangerousTag(tag);
this._tag = tag.toLowerCase();
this._namespaceURI = null;
this._renderedChildren = null;
this._previousStyle = null;
this._previousStyleCopy = null;
this._nativeNode = null;
this._rootNodeID = null;
this._nativeContainerInfo = null;
this._wrapperState = null;
this._topLevelWrapper = null;
this._nodeHasLegacyProperties = false;
if (__DEV__) {
this._unprocessedContextDev = null;
this._processedContextDev = null;
this._ancestorInfo = null;
}
}
@@ -541,11 +534,20 @@ ReactDOMComponent.Mixin = {
* @internal
* @param {string} rootID The root DOM ID for this node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {?ReactDOMComponent} the containing DOM component instance
* @param {?object} info about the native container
* @param {object} context
* @return {string} The computed markup.
*/
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
this._rootNodeID = rootID;
this._nativeContainerInfo = nativeContainerInfo;
var props = this._currentElement.props;
@@ -561,48 +563,80 @@ ReactDOMComponent.Mixin = {
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'button':
props = ReactDOMButton.getNativeProps(this, props, context);
props = ReactDOMButton.getNativeProps(this, props, nativeParent);
break;
case 'input':
ReactDOMInput.mountWrapper(this, props, context);
props = ReactDOMInput.getNativeProps(this, props, context);
ReactDOMInput.mountWrapper(this, props, nativeParent);
props = ReactDOMInput.getNativeProps(this, props);
break;
case 'option':
ReactDOMOption.mountWrapper(this, props, context);
props = ReactDOMOption.getNativeProps(this, props, context);
ReactDOMOption.mountWrapper(this, props, nativeParent);
props = ReactDOMOption.getNativeProps(this, props);
break;
case 'select':
ReactDOMSelect.mountWrapper(this, props, context);
props = ReactDOMSelect.getNativeProps(this, props, context);
context = ReactDOMSelect.processChildContext(this, props, context);
ReactDOMSelect.mountWrapper(this, props, nativeParent);
props = ReactDOMSelect.getNativeProps(this, props);
break;
case 'textarea':
ReactDOMTextarea.mountWrapper(this, props, context);
props = ReactDOMTextarea.getNativeProps(this, props, context);
ReactDOMTextarea.mountWrapper(this, props, nativeParent);
props = ReactDOMTextarea.getNativeProps(this, props);
break;
}
assertValidProps(this, props);
if (__DEV__) {
if (context[validateDOMNesting.ancestorInfoContextKey]) {
validateDOMNesting(
this._tag,
this,
context[validateDOMNesting.ancestorInfoContextKey]
);
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var namespaceURI;
var parentTag;
if (nativeParent != null) {
namespaceURI = nativeParent._namespaceURI;
parentTag = nativeParent._tag;
} else if (nativeContainerInfo != null) {
namespaceURI = nativeContainerInfo._namespaceURI;
parentTag = nativeContainerInfo._tag;
}
if (namespaceURI == null ||
namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
namespaceURI = DOMNamespaces.html;
}
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'svg') {
namespaceURI = DOMNamespaces.svg;
} else if (this._tag === 'math') {
namespaceURI = DOMNamespaces.mathml;
}
}
this._namespaceURI = namespaceURI;
if (__DEV__) {
this._unprocessedContextDev = context;
this._processedContextDev = processChildContextDev(context, this);
context = this._processedContextDev;
var parentInfo;
if (nativeParent != null) {
parentInfo = nativeParent._ancestorInfo;
} else if (nativeContainerInfo != null) {
parentInfo = nativeContainerInfo._ancestorInfo;
}
if (parentInfo) {
// parentInfo should always be present except for the top-level
// component when server rendering
validateDOMNesting(this._tag, this, parentInfo);
}
this._ancestorInfo =
validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
}
var mountImage;
if (transaction.useCreateElement) {
var ownerDocument = context[ReactMount.ownerDocumentContextKey];
var el = ownerDocument.createElement(this._currentElement.type);
var ownerDocument = nativeContainerInfo._ownerDocument;
var el;
if (namespaceURI === DOMNamespaces.html) {
el = ownerDocument.createElement(this._currentElement.type);
} else {
el = ownerDocument.createElementNS(
namespaceURI,
this._currentElement.type
);
}
this._nativeNode = el;
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
// Populate node cache
@@ -835,18 +869,6 @@ ReactDOMComponent.Mixin = {
break;
}
if (__DEV__) {
// If the context is reference-equal to the old one, pass down the same
// processed object so the update bailout in ReactReconciler behaves
// correctly (and identically in dev and prod). See #5005.
if (this._unprocessedContextDev !== context) {
this._unprocessedContextDev = context;
this._processedContextDev = processChildContextDev(context, this);
}
context = this._processedContextDev;
}
assertValidProps(this, nextProps);
this._updateDOMProperties(lastProps, nextProps, transaction);
this._updateDOMChildren(
@@ -0,0 +1,31 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMContainerInfo
*/
'use strict';
var validateDOMNesting = require('validateDOMNesting');
var DOC_NODE_TYPE = 9;
function ReactDOMContainerInfo(node) {
var info = {
_ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
_tag: node.nodeName.toLowerCase(),
_namespaceURI: node.namespaceURI,
};
if (__DEV__) {
info._ancestorInfo =
validateDOMNesting.updatedAncestorInfo(null, info._tag, null);
}
return info;
}
module.exports = ReactDOMContainerInfo;
@@ -68,20 +68,30 @@ assign(ReactDOMTextComponent.prototype, {
* @return {string} Markup for this text node.
* @internal
*/
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
if (__DEV__) {
if (context[validateDOMNesting.ancestorInfoContextKey]) {
validateDOMNesting(
'span',
null,
context[validateDOMNesting.ancestorInfoContextKey]
);
var parentInfo;
if (nativeParent != null) {
parentInfo = nativeParent._ancestorInfo;
} else if (nativeContainerInfo != null) {
parentInfo = nativeContainerInfo._ancestorInfo;
}
if (parentInfo) {
// parentInfo should always be present except for the top-level
// component when server rendering
validateDOMNesting(this._tag, this, parentInfo);
}
}
this._rootNodeID = rootID;
if (transaction.useCreateElement) {
var ownerDocument = context[ReactMount.ownerDocumentContextKey];
var ownerDocument = nativeContainerInfo._ownerDocument;
var el = ownerDocument.createElement('span');
this._nativeNode = el;
DOMPropertyOperations.setAttributeForID(el, rootID);
@@ -11,40 +11,25 @@
'use strict';
var React = require('React');
var instantiateReactComponent = require('instantiateReactComponent');
describe('Danger', function() {
describe('dangerouslyRenderMarkup', function() {
var Danger;
var transaction;
beforeEach(function() {
require('mock-modules').dumpCache();
Danger = require('Danger');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
transaction = new ReactReconcileTransaction(/* forceHTML */ true);
});
it('should render markup', function() {
var markup = instantiateReactComponent(
<div />
).mountComponent('.rX', transaction, {});
var markup = '<div data-reactid=".rX"></div>';
var output = Danger.dangerouslyRenderMarkup([markup])[0];
expect(output.nodeName).toBe('DIV');
});
it('should render markup with props', function() {
var markup = instantiateReactComponent(
<div className="foo" />
).mountComponent(
'.rX',
transaction,
{}
);
var markup = '<div class="foo" data-reactid=".rX"></div>';
var output = Danger.dangerouslyRenderMarkup([markup])[0];
expect(output.nodeName).toBe('DIV');
@@ -52,9 +37,7 @@ describe('Danger', function() {
});
it('should render wrapped markup', function() {
var markup = instantiateReactComponent(
<th />
).mountComponent('.rX', transaction, {});
var markup = '<th data-reactid=".rX"></th>';
var output = Danger.dangerouslyRenderMarkup([markup])[0];
expect(output.nodeName).toBe('TH');
@@ -96,6 +96,8 @@ var ReactCompositeComponentMixin = {
this._currentElement = element;
this._rootNodeID = null;
this._instance = null;
this._nativeParent = null;
this._nativeContainerInfo = null;
// See ReactUpdateQueue
this._pendingElement = null;
@@ -122,10 +124,18 @@ var ReactCompositeComponentMixin = {
* @final
* @internal
*/
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
this._context = context;
this._mountOrder = nextMountID++;
this._rootNodeID = rootID;
this._nativeParent = nativeParent;
this._nativeContainerInfo = nativeContainerInfo;
var publicProps = this._processProps(this._currentElement.props);
var publicContext = this._processContext(context);
@@ -288,6 +298,8 @@ var ReactCompositeComponentMixin = {
this._renderedComponent,
rootID,
transaction,
nativeParent,
nativeContainerInfo,
this._processChildContext(context)
);
if (inst.componentDidMount) {
@@ -739,6 +751,8 @@ var ReactCompositeComponentMixin = {
this._renderedComponent,
this._rootNodeID,
transaction,
this._nativeParent,
this._nativeContainerInfo,
this._processChildContext(context)
);
this._replaceNodeWithMarkup(oldNativeNode, nextMarkup);
@@ -33,13 +33,21 @@ var ReactEmptyComponent = function(instantiate) {
assign(ReactEmptyComponent.prototype, {
construct: function(element) {
},
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
ReactEmptyComponentRegistry.registerNullComponentID(rootID);
this._rootNodeID = rootID;
return ReactReconciler.mountComponent(
this._renderedComponent,
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
);
},
@@ -255,6 +255,8 @@ var ReactMultiChild = {
child,
rootID,
transaction,
this,
this._nativeContainerInfo,
context
);
child._mountIndex = index++;
@@ -503,6 +505,8 @@ var ReactMultiChild = {
child,
rootID,
transaction,
this,
this._nativeContainerInfo,
context
);
child._mountIndex = index;
@@ -29,12 +29,27 @@ var ReactReconciler = {
* @param {ReactComponent} internalInstance
* @param {string} rootID DOM ID of the root node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {?object} the containing native component instance
* @param {?object} info about the native container
* @return {?string} Rendered markup to be inserted into the DOM.
* @final
* @internal
*/
mountComponent: function(internalInstance, rootID, transaction, context) {
var markup = internalInstance.mountComponent(rootID, transaction, context);
mountComponent: function(
internalInstance,
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
var markup = internalInstance.mountComponent(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
);
if (internalInstance._currentElement &&
internalInstance._currentElement.ref != null) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
+1 -1
View File
@@ -446,7 +446,7 @@ ReactShallowRenderer.prototype._render = function(element, transaction, context)
var instance = new ShallowComponentWrapper(element.type);
instance.construct(element);
instance.mountComponent(rootID, transaction, context);
instance.mountComponent(rootID, transaction, null, null, context);
this._instance = instance;
}