2017-05-03 15:35:00 +02:00
/ * *
* Copyright 2013 - present , 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 .
*
* /
/* global hasOwnProperty:true */
'use strict' ;
var _prodInvariant = require ( './reactProdInvariant' ) ,
_assign = require ( 'object-assign' ) ;
var AutoFocusUtils = require ( './AutoFocusUtils' ) ;
var CSSPropertyOperations = require ( './CSSPropertyOperations' ) ;
var DOMLazyTree = require ( './DOMLazyTree' ) ;
var DOMNamespaces = require ( './DOMNamespaces' ) ;
var DOMProperty = require ( './DOMProperty' ) ;
var DOMPropertyOperations = require ( './DOMPropertyOperations' ) ;
var EventPluginHub = require ( './EventPluginHub' ) ;
var EventPluginRegistry = require ( './EventPluginRegistry' ) ;
var ReactBrowserEventEmitter = require ( './ReactBrowserEventEmitter' ) ;
var ReactDOMComponentFlags = require ( './ReactDOMComponentFlags' ) ;
var ReactDOMComponentTree = require ( './ReactDOMComponentTree' ) ;
var ReactDOMInput = require ( './ReactDOMInput' ) ;
var ReactDOMOption = require ( './ReactDOMOption' ) ;
var ReactDOMSelect = require ( './ReactDOMSelect' ) ;
var ReactDOMTextarea = require ( './ReactDOMTextarea' ) ;
var ReactInstrumentation = require ( './ReactInstrumentation' ) ;
var ReactMultiChild = require ( './ReactMultiChild' ) ;
var ReactServerRenderingTransaction = require ( './ReactServerRenderingTransaction' ) ;
var emptyFunction = require ( 'fbjs/lib/emptyFunction' ) ;
var escapeTextContentForBrowser = require ( './escapeTextContentForBrowser' ) ;
var invariant = require ( 'fbjs/lib/invariant' ) ;
var isEventSupported = require ( './isEventSupported' ) ;
var shallowEqual = require ( 'fbjs/lib/shallowEqual' ) ;
2017-08-14 05:01:11 +02:00
var inputValueTracking = require ( './inputValueTracking' ) ;
2017-05-03 15:35:00 +02:00
var validateDOMNesting = require ( './validateDOMNesting' ) ;
var warning = require ( 'fbjs/lib/warning' ) ;
var Flags = ReactDOMComponentFlags ;
var deleteListener = EventPluginHub . deleteListener ;
var getNode = ReactDOMComponentTree . getNodeFromInstance ;
var listenTo = ReactBrowserEventEmitter . listenTo ;
var registrationNameModules = EventPluginRegistry . registrationNameModules ;
// For quickly matching children type, to test if can be treated as content.
2017-08-14 05:01:11 +02:00
var CONTENT _TYPES = { string : true , number : true } ;
2017-05-03 15:35:00 +02:00
var STYLE = 'style' ;
var HTML = '__html' ;
var RESERVED _PROPS = {
children : null ,
dangerouslySetInnerHTML : null ,
suppressContentEditableWarning : null
} ;
// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
var DOC _FRAGMENT _TYPE = 11 ;
function getDeclarationErrorAddendum ( internalInstance ) {
if ( internalInstance ) {
var owner = internalInstance . _currentElement . _owner || null ;
if ( owner ) {
var name = owner . getName ( ) ;
if ( name ) {
return ' This DOM node was rendered by `' + name + '`.' ;
}
}
}
return '' ;
}
function friendlyStringify ( obj ) {
if ( typeof obj === 'object' ) {
if ( Array . isArray ( obj ) ) {
return '[' + obj . map ( friendlyStringify ) . join ( ', ' ) + ']' ;
} else {
var pairs = [ ] ;
for ( var key in obj ) {
if ( Object . prototype . hasOwnProperty . call ( obj , key ) ) {
var keyEscaped = /^[a-z$_][\w$_]*$/i . test ( key ) ? key : JSON . stringify ( key ) ;
pairs . push ( keyEscaped + ': ' + friendlyStringify ( obj [ key ] ) ) ;
}
}
return '{' + pairs . join ( ', ' ) + '}' ;
}
} else if ( typeof obj === 'string' ) {
return JSON . stringify ( obj ) ;
} else if ( typeof obj === 'function' ) {
return '[function object]' ;
}
// Differs from JSON.stringify in that undefined because undefined and that
// inf and nan don't become null
return String ( obj ) ;
}
var styleMutationWarning = { } ;
function checkAndWarnForMutatedStyle ( style1 , style2 , component ) {
if ( style1 == null || style2 == null ) {
return ;
}
if ( shallowEqual ( style1 , style2 ) ) {
return ;
}
var componentName = component . _tag ;
var owner = component . _currentElement . _owner ;
var ownerName ;
if ( owner ) {
ownerName = owner . getName ( ) ;
}
var hash = ownerName + '|' + componentName ;
if ( styleMutationWarning . hasOwnProperty ( hash ) ) {
return ;
}
styleMutationWarning [ hash ] = true ;
process . env . NODE _ENV !== 'production' ? warning ( false , '`%s` was passed a style object that has previously been mutated. ' + 'Mutating `style` is deprecated. Consider cloning it beforehand. Check ' + 'the `render` %s. Previous style: %s. Mutated style: %s.' , componentName , owner ? 'of `' + ownerName + '`' : 'using <' + componentName + '>' , friendlyStringify ( style1 ) , friendlyStringify ( style2 ) ) : void 0 ;
}
/ * *
* @ param { object } component
* @ param { ? object } props
* /
function assertValidProps ( component , props ) {
if ( ! props ) {
return ;
}
// Note the use of `==` which checks for null or undefined.
if ( voidElementTags [ component . _tag ] ) {
! ( props . children == null && props . dangerouslySetInnerHTML == null ) ? process . env . NODE _ENV !== 'production' ? invariant ( false , '%s is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.%s' , component . _tag , component . _currentElement . _owner ? ' Check the render method of ' + component . _currentElement . _owner . getName ( ) + '.' : '' ) : _prodInvariant ( '137' , component . _tag , component . _currentElement . _owner ? ' Check the render method of ' + component . _currentElement . _owner . getName ( ) + '.' : '' ) : void 0 ;
}
if ( props . dangerouslySetInnerHTML != null ) {
! ( props . children == null ) ? process . env . NODE _ENV !== 'production' ? invariant ( false , 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' ) : _prodInvariant ( '60' ) : void 0 ;
! ( typeof props . dangerouslySetInnerHTML === 'object' && HTML in props . dangerouslySetInnerHTML ) ? process . env . NODE _ENV !== 'production' ? invariant ( false , '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://fb.me/react-invariant-dangerously-set-inner-html for more information.' ) : _prodInvariant ( '61' ) : void 0 ;
}
if ( process . env . NODE _ENV !== 'production' ) {
process . env . NODE _ENV !== 'production' ? warning ( props . innerHTML == null , 'Directly setting property `innerHTML` is not permitted. ' + 'For more information, lookup documentation on `dangerouslySetInnerHTML`.' ) : void 0 ;
process . env . NODE _ENV !== 'production' ? warning ( props . suppressContentEditableWarning || ! props . contentEditable || props . children == null , 'A component is `contentEditable` and contains `children` managed by ' + 'React. It is now your responsibility to guarantee that none of ' + 'those nodes are unexpectedly modified or duplicated. This is ' + 'probably not intentional.' ) : void 0 ;
process . env . NODE _ENV !== 'production' ? warning ( props . onFocusIn == null && props . onFocusOut == null , 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' + 'All React events are normalized to bubble, so onFocusIn and onFocusOut ' + 'are not needed/supported by React.' ) : void 0 ;
}
! ( props . style == null || typeof props . style === 'object' ) ? process . env . NODE _ENV !== 'production' ? invariant ( false , 'The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + \'em\'}} when using JSX.%s' , getDeclarationErrorAddendum ( component ) ) : _prodInvariant ( '62' , getDeclarationErrorAddendum ( component ) ) : void 0 ;
}
function enqueuePutListener ( inst , registrationName , listener , transaction ) {
if ( transaction instanceof ReactServerRenderingTransaction ) {
return ;
}
if ( process . env . NODE _ENV !== 'production' ) {
// IE8 has no API for event capturing and the `onScroll` event doesn't
// bubble.
2017-08-14 05:01:11 +02:00
process . env . NODE _ENV !== 'production' ? warning ( registrationName !== 'onScroll' || isEventSupported ( 'scroll' , true ) , "This browser doesn't support the `onScroll` event" ) : void 0 ;
2017-05-03 15:35:00 +02:00
}
var containerInfo = inst . _hostContainerInfo ;
var isDocumentFragment = containerInfo . _node && containerInfo . _node . nodeType === DOC _FRAGMENT _TYPE ;
var doc = isDocumentFragment ? containerInfo . _node : containerInfo . _ownerDocument ;
listenTo ( registrationName , doc ) ;
transaction . getReactMountReady ( ) . enqueue ( putListener , {
inst : inst ,
registrationName : registrationName ,
listener : listener
} ) ;
}
function putListener ( ) {
var listenerToPut = this ;
EventPluginHub . putListener ( listenerToPut . inst , listenerToPut . registrationName , listenerToPut . listener ) ;
}
function inputPostMount ( ) {
var inst = this ;
ReactDOMInput . postMountWrapper ( inst ) ;
}
function textareaPostMount ( ) {
var inst = this ;
ReactDOMTextarea . postMountWrapper ( inst ) ;
}
function optionPostMount ( ) {
var inst = this ;
ReactDOMOption . postMountWrapper ( inst ) ;
}
var setAndValidateContentChildDev = emptyFunction ;
if ( process . env . NODE _ENV !== 'production' ) {
setAndValidateContentChildDev = function ( content ) {
var hasExistingContent = this . _contentDebugID != null ;
var debugID = this . _debugID ;
// This ID represents the inlined child that has no backing instance:
var contentDebugID = - debugID ;
if ( content == null ) {
if ( hasExistingContent ) {
ReactInstrumentation . debugTool . onUnmountComponent ( this . _contentDebugID ) ;
}
this . _contentDebugID = null ;
return ;
}
validateDOMNesting ( null , String ( content ) , this , this . _ancestorInfo ) ;
this . _contentDebugID = contentDebugID ;
if ( hasExistingContent ) {
ReactInstrumentation . debugTool . onBeforeUpdateComponent ( contentDebugID , content ) ;
ReactInstrumentation . debugTool . onUpdateComponent ( contentDebugID ) ;
} else {
ReactInstrumentation . debugTool . onBeforeMountComponent ( contentDebugID , content , debugID ) ;
ReactInstrumentation . debugTool . onMountComponent ( contentDebugID ) ;
ReactInstrumentation . debugTool . onSetChildren ( debugID , [ contentDebugID ] ) ;
}
} ;
}
// There are so many media events, it makes sense to just
// maintain a list rather than create a `trapBubbledEvent` for each
var mediaEvents = {
topAbort : 'abort' ,
topCanPlay : 'canplay' ,
topCanPlayThrough : 'canplaythrough' ,
topDurationChange : 'durationchange' ,
topEmptied : 'emptied' ,
topEncrypted : 'encrypted' ,
topEnded : 'ended' ,
topError : 'error' ,
topLoadedData : 'loadeddata' ,
topLoadedMetadata : 'loadedmetadata' ,
topLoadStart : 'loadstart' ,
topPause : 'pause' ,
topPlay : 'play' ,
topPlaying : 'playing' ,
topProgress : 'progress' ,
topRateChange : 'ratechange' ,
topSeeked : 'seeked' ,
topSeeking : 'seeking' ,
topStalled : 'stalled' ,
topSuspend : 'suspend' ,
topTimeUpdate : 'timeupdate' ,
topVolumeChange : 'volumechange' ,
topWaiting : 'waiting'
} ;
2017-08-14 05:01:11 +02:00
function trackInputValue ( ) {
inputValueTracking . track ( this ) ;
}
2017-05-03 15:35:00 +02:00
function trapBubbledEventsLocal ( ) {
var inst = this ;
// If a component renders to null or if another component fatals and causes
// the state of the tree to be corrupted, `node` here can be null.
! inst . _rootNodeID ? process . env . NODE _ENV !== 'production' ? invariant ( false , 'Must be mounted to trap events' ) : _prodInvariant ( '63' ) : void 0 ;
var node = getNode ( inst ) ;
! node ? process . env . NODE _ENV !== 'production' ? invariant ( false , 'trapBubbledEvent(...): Requires node to be rendered.' ) : _prodInvariant ( '64' ) : void 0 ;
switch ( inst . _tag ) {
case 'iframe' :
case 'object' :
inst . _wrapperState . listeners = [ ReactBrowserEventEmitter . trapBubbledEvent ( 'topLoad' , 'load' , node ) ] ;
break ;
case 'video' :
case 'audio' :
inst . _wrapperState . listeners = [ ] ;
// Create listener for each media event
for ( var event in mediaEvents ) {
if ( mediaEvents . hasOwnProperty ( event ) ) {
inst . _wrapperState . listeners . push ( ReactBrowserEventEmitter . trapBubbledEvent ( event , mediaEvents [ event ] , node ) ) ;
}
}
break ;
case 'source' :
inst . _wrapperState . listeners = [ ReactBrowserEventEmitter . trapBubbledEvent ( 'topError' , 'error' , node ) ] ;
break ;
case 'img' :
inst . _wrapperState . listeners = [ ReactBrowserEventEmitter . trapBubbledEvent ( 'topError' , 'error' , node ) , ReactBrowserEventEmitter . trapBubbledEvent ( 'topLoad' , 'load' , node ) ] ;
break ;
case 'form' :
inst . _wrapperState . listeners = [ ReactBrowserEventEmitter . trapBubbledEvent ( 'topReset' , 'reset' , node ) , ReactBrowserEventEmitter . trapBubbledEvent ( 'topSubmit' , 'submit' , node ) ] ;
break ;
case 'input' :
case 'select' :
case 'textarea' :
inst . _wrapperState . listeners = [ ReactBrowserEventEmitter . trapBubbledEvent ( 'topInvalid' , 'invalid' , node ) ] ;
break ;
}
}
function postUpdateSelectWrapper ( ) {
ReactDOMSelect . postUpdateWrapper ( this ) ;
}
// For HTML, certain tags should omit their close tag. We keep a whitelist for
// those special-case tags.
var omittedCloseTags = {
2017-08-14 05:01:11 +02:00
area : true ,
base : true ,
br : true ,
col : true ,
embed : true ,
hr : true ,
img : true ,
input : true ,
keygen : true ,
link : true ,
meta : true ,
param : true ,
source : true ,
track : true ,
wbr : true
// NOTE: menuitem's close tag should be omitted, but that causes problems.
2017-05-03 15:35:00 +02:00
} ;
var newlineEatingTags = {
2017-08-14 05:01:11 +02:00
listing : true ,
pre : true ,
textarea : true
2017-05-03 15:35:00 +02:00
} ;
// For HTML, certain tags cannot have children. This has the same purpose as
// `omittedCloseTags` except that `menuitem` should still have its closing tag.
var voidElementTags = _assign ( {
2017-08-14 05:01:11 +02:00
menuitem : true
2017-05-03 15:35:00 +02:00
} , omittedCloseTags ) ;
// We accept any tag to be rendered but since this gets injected into arbitrary
// HTML, we want to make sure that it's a safe tag.
// http://www.w3.org/TR/REC-xml/#NT-Name
var VALID _TAG _REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/ ; // Simplified subset
var validatedTagCache = { } ;
var hasOwnProperty = { } . hasOwnProperty ;
function validateDangerousTag ( tag ) {
if ( ! hasOwnProperty . call ( validatedTagCache , tag ) ) {
! VALID _TAG _REGEX . test ( tag ) ? process . env . NODE _ENV !== 'production' ? invariant ( false , 'Invalid tag: %s' , tag ) : _prodInvariant ( '65' , tag ) : void 0 ;
validatedTagCache [ tag ] = true ;
}
}
function isCustomComponent ( tagName , props ) {
return tagName . indexOf ( '-' ) >= 0 || props . is != null ;
}
var globalIdCounter = 1 ;
/ * *
* Creates a new React class that is idempotent and capable of containing other
* React components . It accepts event listeners and DOM properties that are
* valid according to ` DOMProperty ` .
*
* - Event listeners : ` onClick ` , ` onMouseDown ` , etc .
* - DOM properties : ` className ` , ` name ` , ` title ` , etc .
*
* The ` style ` property functions differently from the DOM API . It accepts an
* object mapping of style properties to values .
*
* @ constructor ReactDOMComponent
* @ extends ReactMultiChild
* /
function ReactDOMComponent ( element ) {
var tag = element . type ;
validateDangerousTag ( tag ) ;
this . _currentElement = element ;
this . _tag = tag . toLowerCase ( ) ;
this . _namespaceURI = null ;
this . _renderedChildren = null ;
this . _previousStyle = null ;
this . _previousStyleCopy = null ;
this . _hostNode = null ;
this . _hostParent = null ;
this . _rootNodeID = 0 ;
this . _domID = 0 ;
this . _hostContainerInfo = null ;
this . _wrapperState = null ;
this . _topLevelWrapper = null ;
this . _flags = 0 ;
if ( process . env . NODE _ENV !== 'production' ) {
this . _ancestorInfo = null ;
setAndValidateContentChildDev . call ( this , null ) ;
}
}
ReactDOMComponent . displayName = 'ReactDOMComponent' ;
ReactDOMComponent . Mixin = {
/ * *
* Generates root tag markup then recurses . This method has side effects and
* is not idempotent .
*
* @ internal
* @ param { ReactReconcileTransaction | ReactServerRenderingTransaction } transaction
* @ param { ? ReactDOMComponent } the parent component instance
* @ param { ? object } info about the host container
* @ param { object } context
* @ return { string } The computed markup .
* /
mountComponent : function ( transaction , hostParent , hostContainerInfo , context ) {
this . _rootNodeID = globalIdCounter ++ ;
this . _domID = hostContainerInfo . _idCounter ++ ;
this . _hostParent = hostParent ;
this . _hostContainerInfo = hostContainerInfo ;
var props = this . _currentElement . props ;
switch ( this . _tag ) {
case 'audio' :
case 'form' :
case 'iframe' :
case 'img' :
case 'link' :
case 'object' :
case 'source' :
case 'video' :
this . _wrapperState = {
listeners : null
} ;
transaction . getReactMountReady ( ) . enqueue ( trapBubbledEventsLocal , this ) ;
break ;
case 'input' :
ReactDOMInput . mountWrapper ( this , props , hostParent ) ;
props = ReactDOMInput . getHostProps ( this , props ) ;
2017-08-14 05:01:11 +02:00
transaction . getReactMountReady ( ) . enqueue ( trackInputValue , this ) ;
2017-05-03 15:35:00 +02:00
transaction . getReactMountReady ( ) . enqueue ( trapBubbledEventsLocal , this ) ;
break ;
case 'option' :
ReactDOMOption . mountWrapper ( this , props , hostParent ) ;
props = ReactDOMOption . getHostProps ( this , props ) ;
break ;
case 'select' :
ReactDOMSelect . mountWrapper ( this , props , hostParent ) ;
props = ReactDOMSelect . getHostProps ( this , props ) ;
transaction . getReactMountReady ( ) . enqueue ( trapBubbledEventsLocal , this ) ;
break ;
case 'textarea' :
ReactDOMTextarea . mountWrapper ( this , props , hostParent ) ;
props = ReactDOMTextarea . getHostProps ( this , props ) ;
2017-08-14 05:01:11 +02:00
transaction . getReactMountReady ( ) . enqueue ( trackInputValue , this ) ;
2017-05-03 15:35:00 +02:00
transaction . getReactMountReady ( ) . enqueue ( trapBubbledEventsLocal , this ) ;
break ;
}
assertValidProps ( this , props ) ;
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var namespaceURI ;
var parentTag ;
if ( hostParent != null ) {
namespaceURI = hostParent . _namespaceURI ;
parentTag = hostParent . _tag ;
} else if ( hostContainerInfo . _tag ) {
namespaceURI = hostContainerInfo . _namespaceURI ;
parentTag = hostContainerInfo . _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 ( process . env . NODE _ENV !== 'production' ) {
var parentInfo ;
if ( hostParent != null ) {
parentInfo = hostParent . _ancestorInfo ;
} else if ( hostContainerInfo . _tag ) {
parentInfo = hostContainerInfo . _ancestorInfo ;
}
if ( parentInfo ) {
// parentInfo should always be present except for the top-level
// component when server rendering
validateDOMNesting ( this . _tag , null , this , parentInfo ) ;
}
this . _ancestorInfo = validateDOMNesting . updatedAncestorInfo ( parentInfo , this . _tag , this ) ;
}
var mountImage ;
if ( transaction . useCreateElement ) {
var ownerDocument = hostContainerInfo . _ownerDocument ;
var el ;
if ( namespaceURI === DOMNamespaces . html ) {
if ( this . _tag === 'script' ) {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument . createElement ( 'div' ) ;
var type = this . _currentElement . type ;
div . innerHTML = '<' + type + '></' + type + '>' ;
el = div . removeChild ( div . firstChild ) ;
} else if ( props . is ) {
el = ownerDocument . createElement ( this . _currentElement . type , props . is ) ;
} else {
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
el = ownerDocument . createElement ( this . _currentElement . type ) ;
}
} else {
el = ownerDocument . createElementNS ( namespaceURI , this . _currentElement . type ) ;
}
ReactDOMComponentTree . precacheNode ( this , el ) ;
this . _flags |= Flags . hasCachedChildNodes ;
if ( ! this . _hostParent ) {
DOMPropertyOperations . setAttributeForRoot ( el ) ;
}
this . _updateDOMProperties ( null , props , transaction ) ;
var lazyTree = DOMLazyTree ( el ) ;
this . _createInitialChildren ( transaction , props , context , lazyTree ) ;
mountImage = lazyTree ;
} else {
var tagOpen = this . _createOpenTagMarkupAndPutListeners ( transaction , props ) ;
var tagContent = this . _createContentMarkup ( transaction , props , context ) ;
if ( ! tagContent && omittedCloseTags [ this . _tag ] ) {
mountImage = tagOpen + '/>' ;
} else {
mountImage = tagOpen + '>' + tagContent + '</' + this . _currentElement . type + '>' ;
}
}
switch ( this . _tag ) {
case 'input' :
transaction . getReactMountReady ( ) . enqueue ( inputPostMount , this ) ;
if ( props . autoFocus ) {
transaction . getReactMountReady ( ) . enqueue ( AutoFocusUtils . focusDOMComponent , this ) ;
}
break ;
case 'textarea' :
transaction . getReactMountReady ( ) . enqueue ( textareaPostMount , this ) ;
if ( props . autoFocus ) {
transaction . getReactMountReady ( ) . enqueue ( AutoFocusUtils . focusDOMComponent , this ) ;
}
break ;
case 'select' :
if ( props . autoFocus ) {
transaction . getReactMountReady ( ) . enqueue ( AutoFocusUtils . focusDOMComponent , this ) ;
}
break ;
case 'button' :
if ( props . autoFocus ) {
transaction . getReactMountReady ( ) . enqueue ( AutoFocusUtils . focusDOMComponent , this ) ;
}
break ;
case 'option' :
transaction . getReactMountReady ( ) . enqueue ( optionPostMount , this ) ;
break ;
}
return mountImage ;
} ,
/ * *
* Creates markup for the open tag and all attributes .
*
* This method has side effects because events get registered .
*
* Iterating over object properties is faster than iterating over arrays .
* @ see http : //jsperf.com/obj-vs-arr-iteration
*
* @ private
* @ param { ReactReconcileTransaction | ReactServerRenderingTransaction } transaction
* @ param { object } props
* @ return { string } Markup of opening tag .
* /
_createOpenTagMarkupAndPutListeners : function ( transaction , props ) {
var ret = '<' + this . _currentElement . type ;
for ( var propKey in props ) {
if ( ! props . hasOwnProperty ( propKey ) ) {
continue ;
}
var propValue = props [ propKey ] ;
if ( propValue == null ) {
continue ;
}
if ( registrationNameModules . hasOwnProperty ( propKey ) ) {
if ( propValue ) {
enqueuePutListener ( this , propKey , propValue , transaction ) ;
}
} else {
if ( propKey === STYLE ) {
if ( propValue ) {
if ( process . env . NODE _ENV !== 'production' ) {
// See `_updateDOMProperties`. style block
this . _previousStyle = propValue ;
}
propValue = this . _previousStyleCopy = _assign ( { } , props . style ) ;
}
propValue = CSSPropertyOperations . createMarkupForStyles ( propValue , this ) ;
}
var markup = null ;
if ( this . _tag != null && isCustomComponent ( this . _tag , props ) ) {
if ( ! RESERVED _PROPS . hasOwnProperty ( propKey ) ) {
markup = DOMPropertyOperations . createMarkupForCustomAttribute ( propKey , propValue ) ;
}
} else {
markup = DOMPropertyOperations . createMarkupForProperty ( propKey , propValue ) ;
}
if ( markup ) {
ret += ' ' + markup ;
}
}
}
// For static pages, no need to put React ID and checksum. Saves lots of
// bytes.
if ( transaction . renderToStaticMarkup ) {
return ret ;
}
if ( ! this . _hostParent ) {
ret += ' ' + DOMPropertyOperations . createMarkupForRoot ( ) ;
}
ret += ' ' + DOMPropertyOperations . createMarkupForID ( this . _domID ) ;
return ret ;
} ,
/ * *
* Creates markup for the content between the tags .
*
* @ private
* @ param { ReactReconcileTransaction | ReactServerRenderingTransaction } transaction
* @ param { object } props
* @ param { object } context
* @ return { string } Content markup .
* /
_createContentMarkup : function ( transaction , props , context ) {
var ret = '' ;
// Intentional use of != to avoid catching zero/false.
var innerHTML = props . dangerouslySetInnerHTML ;
if ( innerHTML != null ) {
if ( innerHTML . _ _html != null ) {
ret = innerHTML . _ _html ;
}
} else {
var contentToUse = CONTENT _TYPES [ typeof props . children ] ? props . children : null ;
var childrenToUse = contentToUse != null ? null : props . children ;
if ( contentToUse != null ) {
// TODO: Validate that text is allowed as a child of this node
ret = escapeTextContentForBrowser ( contentToUse ) ;
if ( process . env . NODE _ENV !== 'production' ) {
setAndValidateContentChildDev . call ( this , contentToUse ) ;
}
} else if ( childrenToUse != null ) {
var mountImages = this . mountChildren ( childrenToUse , transaction , context ) ;
ret = mountImages . join ( '' ) ;
}
}
if ( newlineEatingTags [ this . _tag ] && ret . charAt ( 0 ) === '\n' ) {
// text/html ignores the first character in these tags if it's a newline
// Prefer to break application/xml over text/html (for now) by adding
// a newline specifically to get eaten by the parser. (Alternately for
// textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first
// \r is normalized out by HTMLTextAreaElement#value.)
// See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre>
// See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions>
// See: <http://www.w3.org/TR/html5/syntax.html#newlines>
// See: Parsing of "textarea" "listing" and "pre" elements
// from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody>
return '\n' + ret ;
} else {
return ret ;
}
} ,
_createInitialChildren : function ( transaction , props , context , lazyTree ) {
// Intentional use of != to avoid catching zero/false.
var innerHTML = props . dangerouslySetInnerHTML ;
if ( innerHTML != null ) {
if ( innerHTML . _ _html != null ) {
DOMLazyTree . queueHTML ( lazyTree , innerHTML . _ _html ) ;
}
} else {
var contentToUse = CONTENT _TYPES [ typeof props . children ] ? props . children : null ;
var childrenToUse = contentToUse != null ? null : props . children ;
// TODO: Validate that text is allowed as a child of this node
if ( contentToUse != null ) {
// Avoid setting textContent when the text is empty. In IE11 setting
// textContent on a text area will cause the placeholder to not
// show within the textarea until it has been focused and blurred again.
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
if ( contentToUse !== '' ) {
if ( process . env . NODE _ENV !== 'production' ) {
setAndValidateContentChildDev . call ( this , contentToUse ) ;
}
DOMLazyTree . queueText ( lazyTree , contentToUse ) ;
}
} else if ( childrenToUse != null ) {
var mountImages = this . mountChildren ( childrenToUse , transaction , context ) ;
for ( var i = 0 ; i < mountImages . length ; i ++ ) {
DOMLazyTree . queueChild ( lazyTree , mountImages [ i ] ) ;
}
}
}
} ,
/ * *
* Receives a next element and updates the component .
*
* @ internal
* @ param { ReactElement } nextElement
* @ param { ReactReconcileTransaction | ReactServerRenderingTransaction } transaction
* @ param { object } context
* /
receiveComponent : function ( nextElement , transaction , context ) {
var prevElement = this . _currentElement ;
this . _currentElement = nextElement ;
this . updateComponent ( transaction , prevElement , nextElement , context ) ;
} ,
/ * *
* Updates a DOM component after it has already been allocated and
* attached to the DOM . Reconciles the root DOM node , then recurses .
*
* @ param { ReactReconcileTransaction } transaction
* @ param { ReactElement } prevElement
* @ param { ReactElement } nextElement
* @ internal
* @ overridable
* /
updateComponent : function ( transaction , prevElement , nextElement , context ) {
var lastProps = prevElement . props ;
var nextProps = this . _currentElement . props ;
switch ( this . _tag ) {
case 'input' :
lastProps = ReactDOMInput . getHostProps ( this , lastProps ) ;
nextProps = ReactDOMInput . getHostProps ( this , nextProps ) ;
break ;
case 'option' :
lastProps = ReactDOMOption . getHostProps ( this , lastProps ) ;
nextProps = ReactDOMOption . getHostProps ( this , nextProps ) ;
break ;
case 'select' :
lastProps = ReactDOMSelect . getHostProps ( this , lastProps ) ;
nextProps = ReactDOMSelect . getHostProps ( this , nextProps ) ;
break ;
case 'textarea' :
lastProps = ReactDOMTextarea . getHostProps ( this , lastProps ) ;
nextProps = ReactDOMTextarea . getHostProps ( this , nextProps ) ;
break ;
}
assertValidProps ( this , nextProps ) ;
this . _updateDOMProperties ( lastProps , nextProps , transaction ) ;
this . _updateDOMChildren ( lastProps , nextProps , transaction , context ) ;
switch ( this . _tag ) {
case 'input' :
// Update the wrapper around inputs *after* updating props. This has to
// happen after `_updateDOMProperties`. Otherwise HTML5 input validations
// raise warnings and prevent the new value from being assigned.
ReactDOMInput . updateWrapper ( this ) ;
break ;
case 'textarea' :
ReactDOMTextarea . updateWrapper ( this ) ;
break ;
case 'select' :
// <select> value update needs to occur after <option> children
// reconciliation
transaction . getReactMountReady ( ) . enqueue ( postUpdateSelectWrapper , this ) ;
break ;
}
} ,
/ * *
* Reconciles the properties by detecting differences in property values and
* updating the DOM as necessary . This function is probably the single most
* critical path for performance optimization .
*
* TODO : Benchmark whether checking for changed values in memory actually
* improves performance ( especially statically positioned elements ) .
* TODO : Benchmark the effects of putting this at the top since 99 % of props
* do not change for a given reconciliation .
* TODO : Benchmark areas that can be improved with caching .
*
* @ private
* @ param { object } lastProps
* @ param { object } nextProps
* @ param { ? DOMElement } node
* /
_updateDOMProperties : function ( lastProps , nextProps , transaction ) {
var propKey ;
var styleName ;
var styleUpdates ;
for ( propKey in lastProps ) {
if ( nextProps . hasOwnProperty ( propKey ) || ! lastProps . hasOwnProperty ( propKey ) || lastProps [ propKey ] == null ) {
continue ;
}
if ( propKey === STYLE ) {
var lastStyle = this . _previousStyleCopy ;
for ( styleName in lastStyle ) {
if ( lastStyle . hasOwnProperty ( styleName ) ) {
styleUpdates = styleUpdates || { } ;
styleUpdates [ styleName ] = '' ;
}
}
this . _previousStyleCopy = null ;
} else if ( registrationNameModules . hasOwnProperty ( propKey ) ) {
if ( lastProps [ propKey ] ) {
// Only call deleteListener if there was a listener previously or
// else willDeleteListener gets called when there wasn't actually a
// listener (e.g., onClick={null})
deleteListener ( this , propKey ) ;
}
} else if ( isCustomComponent ( this . _tag , lastProps ) ) {
if ( ! RESERVED _PROPS . hasOwnProperty ( propKey ) ) {
DOMPropertyOperations . deleteValueForAttribute ( getNode ( this ) , propKey ) ;
}
} else if ( DOMProperty . properties [ propKey ] || DOMProperty . isCustomAttribute ( propKey ) ) {
DOMPropertyOperations . deleteValueForProperty ( getNode ( this ) , propKey ) ;
}
}
for ( propKey in nextProps ) {
var nextProp = nextProps [ propKey ] ;
var lastProp = propKey === STYLE ? this . _previousStyleCopy : lastProps != null ? lastProps [ propKey ] : undefined ;
if ( ! nextProps . hasOwnProperty ( propKey ) || nextProp === lastProp || nextProp == null && lastProp == null ) {
continue ;
}
if ( propKey === STYLE ) {
if ( nextProp ) {
if ( process . env . NODE _ENV !== 'production' ) {
checkAndWarnForMutatedStyle ( this . _previousStyleCopy , this . _previousStyle , this ) ;
this . _previousStyle = nextProp ;
}
nextProp = this . _previousStyleCopy = _assign ( { } , nextProp ) ;
} else {
this . _previousStyleCopy = null ;
}
if ( lastProp ) {
// Unset styles on `lastProp` but not on `nextProp`.
for ( styleName in lastProp ) {
if ( lastProp . hasOwnProperty ( styleName ) && ( ! nextProp || ! nextProp . hasOwnProperty ( styleName ) ) ) {
styleUpdates = styleUpdates || { } ;
styleUpdates [ styleName ] = '' ;
}
}
// Update styles that changed since `lastProp`.
for ( styleName in nextProp ) {
if ( nextProp . hasOwnProperty ( styleName ) && lastProp [ styleName ] !== nextProp [ styleName ] ) {
styleUpdates = styleUpdates || { } ;
styleUpdates [ styleName ] = nextProp [ styleName ] ;
}
}
} else {
// Relies on `updateStylesByID` not mutating `styleUpdates`.
styleUpdates = nextProp ;
}
} else if ( registrationNameModules . hasOwnProperty ( propKey ) ) {
if ( nextProp ) {
enqueuePutListener ( this , propKey , nextProp , transaction ) ;
} else if ( lastProp ) {
deleteListener ( this , propKey ) ;
}
} else if ( isCustomComponent ( this . _tag , nextProps ) ) {
if ( ! RESERVED _PROPS . hasOwnProperty ( propKey ) ) {
DOMPropertyOperations . setValueForAttribute ( getNode ( this ) , propKey , nextProp ) ;
}
} else if ( DOMProperty . properties [ propKey ] || DOMProperty . isCustomAttribute ( propKey ) ) {
var node = getNode ( this ) ;
// If we're updating to null or undefined, we should remove the property
// from the DOM node instead of inadvertently setting to a string. This
// brings us in line with the same behavior we have on initial render.
if ( nextProp != null ) {
DOMPropertyOperations . setValueForProperty ( node , propKey , nextProp ) ;
} else {
DOMPropertyOperations . deleteValueForProperty ( node , propKey ) ;
}
}
}
if ( styleUpdates ) {
CSSPropertyOperations . setValueForStyles ( getNode ( this ) , styleUpdates , this ) ;
}
} ,
/ * *
* Reconciles the children with the various properties that affect the
* children content .
*
* @ param { object } lastProps
* @ param { object } nextProps
* @ param { ReactReconcileTransaction } transaction
* @ param { object } context
* /
_updateDOMChildren : function ( lastProps , nextProps , transaction , context ) {
var lastContent = CONTENT _TYPES [ typeof lastProps . children ] ? lastProps . children : null ;
var nextContent = CONTENT _TYPES [ typeof nextProps . children ] ? nextProps . children : null ;
var lastHtml = lastProps . dangerouslySetInnerHTML && lastProps . dangerouslySetInnerHTML . _ _html ;
var nextHtml = nextProps . dangerouslySetInnerHTML && nextProps . dangerouslySetInnerHTML . _ _html ;
// Note the use of `!=` which checks for null or undefined.
var lastChildren = lastContent != null ? null : lastProps . children ;
var nextChildren = nextContent != null ? null : nextProps . children ;
// If we're switching from children to content/html or vice versa, remove
// the old content
var lastHasContentOrHtml = lastContent != null || lastHtml != null ;
var nextHasContentOrHtml = nextContent != null || nextHtml != null ;
if ( lastChildren != null && nextChildren == null ) {
this . updateChildren ( null , transaction , context ) ;
} else if ( lastHasContentOrHtml && ! nextHasContentOrHtml ) {
this . updateTextContent ( '' ) ;
if ( process . env . NODE _ENV !== 'production' ) {
ReactInstrumentation . debugTool . onSetChildren ( this . _debugID , [ ] ) ;
}
}
if ( nextContent != null ) {
if ( lastContent !== nextContent ) {
this . updateTextContent ( '' + nextContent ) ;
if ( process . env . NODE _ENV !== 'production' ) {
setAndValidateContentChildDev . call ( this , nextContent ) ;
}
}
} else if ( nextHtml != null ) {
if ( lastHtml !== nextHtml ) {
this . updateMarkup ( '' + nextHtml ) ;
}
if ( process . env . NODE _ENV !== 'production' ) {
ReactInstrumentation . debugTool . onSetChildren ( this . _debugID , [ ] ) ;
}
} else if ( nextChildren != null ) {
if ( process . env . NODE _ENV !== 'production' ) {
setAndValidateContentChildDev . call ( this , null ) ;
}
this . updateChildren ( nextChildren , transaction , context ) ;
}
} ,
getHostNode : function ( ) {
return getNode ( this ) ;
} ,
/ * *
* Destroys all event registrations for this instance . Does not remove from
* the DOM . That must be done by the parent .
*
* @ internal
* /
unmountComponent : function ( safely ) {
switch ( this . _tag ) {
case 'audio' :
case 'form' :
case 'iframe' :
case 'img' :
case 'link' :
case 'object' :
case 'source' :
case 'video' :
var listeners = this . _wrapperState . listeners ;
if ( listeners ) {
for ( var i = 0 ; i < listeners . length ; i ++ ) {
listeners [ i ] . remove ( ) ;
}
}
break ;
2017-08-14 05:01:11 +02:00
case 'input' :
case 'textarea' :
inputValueTracking . stopTracking ( this ) ;
break ;
2017-05-03 15:35:00 +02:00
case 'html' :
case 'head' :
case 'body' :
/ * *
* Components like < html > < head > and < body > can ' t be removed or added
* easily in a cross - browser way , however it ' s valuable to be able to
* take advantage of React ' s reconciliation for styling and < title >
* management . So we just document it and throw in dangerous cases .
* /
! false ? process . env . NODE _ENV !== 'production' ? invariant ( false , '<%s> tried to unmount. Because of cross-browser quirks it is impossible to unmount some top-level components (eg <html>, <head>, and <body>) reliably and efficiently. To fix this, have a single top-level component that never unmounts render these elements.' , this . _tag ) : _prodInvariant ( '66' , this . _tag ) : void 0 ;
break ;
}
this . unmountChildren ( safely ) ;
ReactDOMComponentTree . uncacheNode ( this ) ;
EventPluginHub . deleteAllListeners ( this ) ;
this . _rootNodeID = 0 ;
this . _domID = 0 ;
this . _wrapperState = null ;
if ( process . env . NODE _ENV !== 'production' ) {
setAndValidateContentChildDev . call ( this , null ) ;
}
} ,
getPublicInstance : function ( ) {
return getNode ( this ) ;
}
} ;
_assign ( ReactDOMComponent . prototype , ReactDOMComponent . Mixin , ReactMultiChild . Mixin ) ;
module . exports = ReactDOMComponent ;