diff --git a/src/browser/ReactDOM.js b/src/browser/ReactDOM.js index 0f6c808f0fee0..c8747bdf2fd18 100644 --- a/src/browser/ReactDOM.js +++ b/src/browser/ReactDOM.js @@ -84,6 +84,7 @@ var ReactDOM = mapObject({ h6: 'h6', head: 'head', header: 'header', + hgroup: 'hgroup', hr: 'hr', html: 'html', i: 'i', diff --git a/src/browser/ui/ReactDOMComponent.js b/src/browser/ui/ReactDOMComponent.js index 4df78e8270108..4645301a5fe53 100644 --- a/src/browser/ui/ReactDOMComponent.js +++ b/src/browser/ui/ReactDOMComponent.js @@ -29,6 +29,7 @@ var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); var keyOf = require('keyOf'); +var validateDOMNesting = require('validateDOMNesting'); var warning = require('warning'); var deleteListener = ReactBrowserEventEmitter.deleteListener; @@ -172,6 +173,15 @@ function validateDangerousTag(tag) { } } +function processChildContext(context, tagName) { + if (__DEV__) { + // Pass down our tag name to child components for validation purposes + context = assign({}, context); + context[validateDOMNesting.parentTagContextKey] = tagName; + } + return context; +} + /** * Creates a new React class that is idempotent and capable of containing other * React components. It accepts event listeners and DOM properties that are @@ -213,7 +223,18 @@ ReactDOMComponent.Mixin = { */ mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; + assertValidProps(this, this._currentElement.props); + if (__DEV__) { + if (context[validateDOMNesting.parentTagContextKey]) { + validateDOMNesting( + context[validateDOMNesting.parentTagContextKey], + this._tag, + this._currentElement + ); + } + } + var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction); var tagContent = this._createContentMarkup(transaction, context); if (!tagContent && omittedCloseTags[this._tag]) { @@ -301,7 +322,7 @@ ReactDOMComponent.Mixin = { var mountImages = this.mountChildren( childrenToUse, transaction, - context + processChildContext(context, this._tag) ); ret = mountImages.join(''); } @@ -342,7 +363,11 @@ ReactDOMComponent.Mixin = { updateComponent: function(transaction, prevElement, nextElement, context) { assertValidProps(this, this._currentElement.props); this._updateDOMProperties(prevElement.props, transaction); - this._updateDOMChildren(prevElement.props, transaction, context); + this._updateDOMChildren( + prevElement.props, + transaction, + processChildContext(context, this._tag) + ); }, /** diff --git a/src/browser/ui/ReactDOMTextComponent.js b/src/browser/ui/ReactDOMTextComponent.js index d561646a12d2f..af6369df97ab8 100644 --- a/src/browser/ui/ReactDOMTextComponent.js +++ b/src/browser/ui/ReactDOMTextComponent.js @@ -19,6 +19,7 @@ var ReactDOMComponent = require('ReactDOMComponent'); var assign = require('Object.assign'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); +var validateDOMNesting = require('validateDOMNesting'); /** * Text nodes violate a couple assumptions that React makes about components: @@ -65,6 +66,16 @@ assign(ReactDOMTextComponent.prototype, { * @internal */ mountComponent: function(rootID, transaction, context) { + if (__DEV__) { + if (context[validateDOMNesting.parentTagContextKey]) { + validateDOMNesting( + context[validateDOMNesting.parentTagContextKey], + 'span', + null + ); + } + } + this._rootNodeID = rootID; var escapedText = escapeTextContentForBrowser(this._stringText); diff --git a/src/browser/ui/ReactDefaultInjection.js b/src/browser/ui/ReactDefaultInjection.js index 771be23117b75..9551c10f25db2 100644 --- a/src/browser/ui/ReactDefaultInjection.js +++ b/src/browser/ui/ReactDefaultInjection.js @@ -38,6 +38,7 @@ var ReactElement = require('ReactElement'); var ReactEventListener = require('ReactEventListener'); var ReactInjection = require('ReactInjection'); var ReactInstanceHandles = require('ReactInstanceHandles'); +var ReactInstanceMap = require('ReactInstanceMap'); var ReactMount = require('ReactMount'); var ReactReconcileTransaction = require('ReactReconcileTransaction'); var SelectEventPlugin = require('SelectEventPlugin'); @@ -51,12 +52,14 @@ function autoGenerateWrapperClass(type) { return ReactClass.createClass({ tagName: type.toUpperCase(), render: function() { + // Copy owner down for debugging info + var internalInstance = ReactInstanceMap.get(this); return new ReactElement( type, - null, - null, - null, - null, + null, // key + null, // ref + internalInstance._currentElement._owner, // owner + null, // context this.props ); } diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index c4fe6280fe1d8..87b025096d516 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -32,6 +32,7 @@ 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 SEPARATOR = ReactInstanceHandles.SEPARATOR; @@ -246,8 +247,14 @@ function mountComponentIntoNode( container, transaction, shouldReuseMarkup) { + var context = emptyObject; + if (__DEV__) { + context = {}; + context[validateDOMNesting.parentTagContextKey] = + container.nodeName.toLowerCase(); + } var markup = ReactReconciler.mountComponent( - componentInstance, rootID, transaction, emptyObject + componentInstance, rootID, transaction, context ); componentInstance._isTopLevel = true; ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup); diff --git a/src/browser/ui/__tests__/ReactDOMComponent-test.js b/src/browser/ui/__tests__/ReactDOMComponent-test.js index 2f99e484531da..990f9dceedc1c 100644 --- a/src/browser/ui/__tests__/ReactDOMComponent-test.js +++ b/src/browser/ui/__tests__/ReactDOMComponent-test.js @@ -373,11 +373,11 @@ describe('ReactDOMComponent', function() { var container = document.createElement('div'); - React.render(, container); + React.render(
, container); expect(container.innerHTML).toContain(''); - React.render(, container); + React.render(, container); expect(console.warn.argsForCall.length).toBe(1); expect(console.warn.argsForCall[0][0]).toContain('void element'); @@ -651,6 +651,54 @@ describe('ReactDOMComponent', function() { 'Invariant Violation: Invalid tag: div>cannot contain a