diff --git a/src/core/ReactComponentBrowserEnvironment.js b/src/core/ReactComponentBrowserEnvironment.js index 26a3e27739..5c60bcb8af 100644 --- a/src/core/ReactComponentBrowserEnvironment.js +++ b/src/core/ReactComponentBrowserEnvironment.js @@ -29,6 +29,9 @@ var getReactRootElementInContainer = require('getReactRootElementInContainer'); var invariant = require('invariant'); +var ELEMENT_NODE_TYPE = 1; +var DOC_NODE_TYPE = 9; + /** * Abstracts away all functionality of `ReactComponent` requires knowledge of @@ -78,8 +81,11 @@ var ReactComponentBrowserEnvironment = { */ mountImageIntoNode: function(markup, container, shouldReuseMarkup) { invariant( - container && container.nodeType === 1, - 'mountComponentIntoNode(...): Target container is not a DOM element.' + container && ( + container.nodeType === ELEMENT_NODE_TYPE || + container.nodeType === DOC_NODE_TYPE && ReactMount.allowFullPageRender + ), + 'mountComponentIntoNode(...): Target container is not valid.' ); if (shouldReuseMarkup) { if (ReactMarkupChecksum.canReuseMarkup( diff --git a/src/core/ReactMount.js b/src/core/ReactMount.js index dc1fd92aa1..9bb4c4efd5 100644 --- a/src/core/ReactMount.js +++ b/src/core/ReactMount.js @@ -197,6 +197,10 @@ function purgeID(id) { * Inside of `container`, the first element rendered is the "reactRoot". */ var ReactMount = { + /** + * Safety guard to prevent accidentally rendering over the entire HTML tree. + */ + allowFullPageRender: false, /** Time spent generating markup. */ totalInstantiationTime: 0, diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index af242c350d..0a78d56915 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -20,6 +20,7 @@ "use strict"; var React; +var ReactMount; var ReactTestUtils; var reactComponentExpect; @@ -27,10 +28,25 @@ var reactComponentExpect; describe('ReactComponent', function() { beforeEach(function() { React = require('React'); + ReactMount = require('ReactMount'); ReactTestUtils = require('ReactTestUtils'); reactComponentExpect = require('reactComponentExpect'); }); + it('should not throw on full document rendering', function() { + var container = {nodeType: 9}; + expect(function() { + React.renderComponent(
, container); + }).toThrow( + 'Invariant Violation: mountComponentIntoNode(...): Target container is ' + + 'not valid.' + ); + ReactMount.allowFullPageRender = true; + expect(function() { + React.renderComponent(
, container); + }).not.toThrow(); + }); + it('should throw on invalid render targets', function() { var container = document.createElement('div'); // jQuery objects are basically arrays; people often pass them in by mistake @@ -38,14 +54,14 @@ describe('ReactComponent', function() { React.renderComponent(
, [container]); }).toThrow( 'Invariant Violation: mountComponentIntoNode(...): Target container is ' + - 'not a DOM element.' + 'not valid.' ); expect(function() { React.renderComponent(
, null); }).toThrow( 'Invariant Violation: mountComponentIntoNode(...): Target container is ' + - 'not a DOM element.' + 'not valid.' ); }); diff --git a/src/environment/ExecutionEnvironment.js b/src/vendor/core/ExecutionEnvironment.js similarity index 100% rename from src/environment/ExecutionEnvironment.js rename to src/vendor/core/ExecutionEnvironment.js diff --git a/src/vendor/core/createNodesFromMarkup.js b/src/vendor/core/createNodesFromMarkup.js index 6ef538fa40..57b93e6cba 100644 --- a/src/vendor/core/createNodesFromMarkup.js +++ b/src/vendor/core/createNodesFromMarkup.js @@ -19,6 +19,8 @@ /*jslint evil: true, sub: true */ +var ExecutionEnvironment = require('ExecutionEnvironment'); + var createArrayFrom = require('createArrayFrom'); var getMarkupWrap = require('getMarkupWrap'); var invariant = require('invariant'); @@ -26,7 +28,8 @@ var invariant = require('invariant'); /** * Dummy container used to render all markup. */ -var dummyNode = document.createElement('div'); +var dummyNode = + ExecutionEnvironment.canUseDOM ? document.createElement('div') : null; /** * Pattern used by `getNodeName`. @@ -56,6 +59,7 @@ function getNodeName(markup) { */ function createNodesFromMarkup(markup, handleScript) { var node = dummyNode; + invariant(!!dummyNode, 'createNodesFromMarkup dummy not initialized'); var nodeName = getNodeName(markup); var wrap = nodeName && getMarkupWrap(nodeName); diff --git a/src/vendor/core/getMarkupWrap.js b/src/vendor/core/getMarkupWrap.js index c35a9d0158..91dac43e19 100644 --- a/src/vendor/core/getMarkupWrap.js +++ b/src/vendor/core/getMarkupWrap.js @@ -16,10 +16,15 @@ * @providesModule getMarkupWrap */ +var ExecutionEnvironment = require('ExecutionEnvironment'); + +var invariant = require('invariant'); + /** * Dummy container used to detect which wraps are necessary. */ -var dummyNode = document.createElement('div'); +var dummyNode = + ExecutionEnvironment.canUseDOM ? document.createElement('div') : null; /** * Some browsers cannot use `innerHTML` to render certain elements standalone, @@ -55,6 +60,7 @@ var markupWrap = { * @return {?array} Markup wrap configuration, if applicable. */ function getMarkupWrap(nodeName) { + invariant(!!dummyNode, 'Markup wrapping node not initialized'); if (!markupWrap.hasOwnProperty(nodeName)) { nodeName = '*'; }