diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js index 6de468e870..213ed73931 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js +++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js @@ -14,8 +14,6 @@ 'use strict'; -import type { Fiber } from 'ReactFiber'; - var CSSPropertyOperations = require('CSSPropertyOperations'); var DOMNamespaces = require('DOMNamespaces'); var DOMProperty = require('DOMProperty'); @@ -39,7 +37,6 @@ var inputValueTracking = require('inputValueTracking'); var warning = require('warning'); var didWarnShadyDOM = false; -var getNode = ReactDOMComponentTree.getNodeFromInstance; var listenTo = ReactBrowserEventEmitter.listenTo; var registrationNameModules = EventPluginRegistry.registrationNameModules; @@ -162,7 +159,7 @@ var mediaEvents = { topWaiting: 'waiting', }; -function trapClickOnNonInteractiveElement(inst) { +function trapClickOnNonInteractiveElement(node : any) { // Mobile Safari does not fire properly bubble click events on // non-interactive elements, which means delegated click listeners do not // fire. The workaround for this bug involves attaching an empty click @@ -172,20 +169,16 @@ function trapClickOnNonInteractiveElement(inst) { // bookkeeping for it. Not sure if we need to clear it when the listener is // removed. // TODO: Only do this for the relevant Safaris maybe? - var node = getNode(inst); node.onclick = emptyFunction; } -function trapBubbledEventsLocal(inst, tag) { +function trapBubbledEventsLocal(node : Element, tag : string) { // 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. - var node = getNode(inst); - invariant( - node, - 'trapBubbledEvent(...): Requires node to be rendered.' - ); // TODO: Make sure that we check isMounted before firing any of these events. + // TODO: Inline these below since we're calling this from an equivalent + // switch statement. switch (tag) { case 'iframe': case 'object': @@ -307,12 +300,12 @@ function isCustomComponent(tagName, props) { * * TODO: Benchmark whether checking for changed values in memory actually * improves performance (especially statically positioned elements). - * TODO: Benchmark the effects of putting workInProgress at the top since 99% of props + * 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. */ function updateDOMProperties( - workInProgress : Fiber, + domElement : Element, rootContainerElement : Element, lastProps : null | Object, nextProps : Object, @@ -347,13 +340,13 @@ function updateDOMProperties( // Do nothing for deleted listeners. } else if (wasCustomComponentTag) { DOMPropertyOperations.deleteValueForAttribute( - getNode(workInProgress), + domElement, propKey ); } else if ( DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) { - DOMPropertyOperations.deleteValueForProperty(getNode(workInProgress), propKey); + DOMPropertyOperations.deleteValueForProperty(domElement, propKey); } } for (propKey in nextProps) { @@ -400,10 +393,10 @@ function updateDOMProperties( if (nextHtml) { if (lastHtml) { if (lastHtml !== nextHtml) { - setInnerHTML(getNode(workInProgress), '' + nextHtml); + setInnerHTML(domElement, '' + nextHtml); } } else { - setInnerHTML(getNode(workInProgress), nextHtml); + setInnerHTML(domElement, nextHtml); } } else { // TODO: It might be too late to clear this if we have children @@ -411,9 +404,9 @@ function updateDOMProperties( } } else if (propKey === CHILDREN) { if (typeof nextProp === 'string') { - setTextContent(getNode(workInProgress), nextProp); + setTextContent(domElement, nextProp); } else if (typeof nextProp === 'number') { - setTextContent(getNode(workInProgress), '' + nextProp); + setTextContent(domElement, '' + nextProp); } } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) { // Noop @@ -423,29 +416,28 @@ function updateDOMProperties( } } else if (isCustomComponentTag) { DOMPropertyOperations.setValueForAttribute( - getNode(workInProgress), + domElement, propKey, nextProp ); } else if ( DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) { - var node = getNode(workInProgress); // 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); + DOMPropertyOperations.setValueForProperty(domElement, propKey, nextProp); } else { - DOMPropertyOperations.deleteValueForProperty(node, propKey); + DOMPropertyOperations.deleteValueForProperty(domElement, propKey); } } } if (styleUpdates) { CSSPropertyOperations.setValueForStyles( - getNode(workInProgress), + domElement, styleUpdates, - workInProgress + null // TODO: Change CSSPropertyOperations to use getCurrentOwnerName. ); } } @@ -459,7 +451,7 @@ var ReactDOMFiberComponent = { }, mountComponent: function( - workInProgress : Fiber, + internalInstanceHandle : Object, tag : string, rootContainerElement : Element, props : Object, @@ -469,53 +461,6 @@ var ReactDOMFiberComponent = { // validateDangerousTag(tag); // tag.toLowerCase(); Do we need to apply lower case only on non-custom elements? - switch (tag) { - case 'audio': - case 'form': - case 'iframe': - case 'img': - case 'link': - case 'object': - case 'source': - case 'video': - trapBubbledEventsLocal(workInProgress); - break; - case 'input': - ReactDOMFiberInput.mountWrapper(workInProgress, props); - props = ReactDOMFiberInput.getHostProps(workInProgress, props); - // TODO: Make sure we check if this is still unmounted or do any clean - // up necessary since we never stop tracking anymore. - inputValueTracking.track(workInProgress); - trapBubbledEventsLocal(workInProgress); - // For controlled components we always need to ensure we're listening - // to onChange. Even if there is no listener. - ensureListeningTo(rootContainerElement, 'onChange'); - break; - case 'option': - ReactDOMFiberOption.mountWrapper(workInProgress, props); - props = ReactDOMFiberOption.getHostProps(workInProgress, props); - break; - case 'select': - ReactDOMFiberSelect.mountWrapper(workInProgress, props); - props = ReactDOMFiberSelect.getHostProps(workInProgress, props); - trapBubbledEventsLocal(workInProgress); - // For controlled components we always need to ensure we're listening - // to onChange. Even if there is no listener. - ensureListeningTo(rootContainerElement, 'onChange'); - break; - case 'textarea': - ReactDOMFiberTextarea.mountWrapper(workInProgress, props); - props = ReactDOMFiberTextarea.getHostProps(workInProgress, props); - inputValueTracking.track(workInProgress); - trapBubbledEventsLocal(workInProgress); - // For controlled components we always need to ensure we're listening - // to onChange. Even if there is no listener. - ensureListeningTo(rootContainerElement, 'onChange'); - break; - } - - assertValidProps(tag, props); - // We create tags in the namespace of their parent container, except HTML // tags get no namespace. var namespaceURI = rootContainerElement.namespaceURI; @@ -534,7 +479,7 @@ var ReactDOMFiberComponent = { } var ownerDocument = rootContainerElement.ownerDocument; - var el; + var domElement : Element; if (namespaceURI === DOMNamespaces.html) { if (tag === 'script') { // Create the script via .innerHTML so its "parser-inserted" flag is @@ -543,24 +488,24 @@ var ReactDOMFiberComponent = { div.innerHTML = ''; // This is guaranteed to yield a script element. var firstChild = ((div.firstChild : any) : Element); - el = div.removeChild(firstChild); + domElement = div.removeChild(firstChild); } else if (props.is) { - el = ownerDocument.createElement(tag, props.is); + domElement = ownerDocument.createElement(tag, 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(tag); + domElement = ownerDocument.createElement(tag); } } else { - el = ownerDocument.createElementNS( + domElement = ownerDocument.createElementNS( namespaceURI, tag ); } var isCustomComponentTag = isCustomComponent(tag, props); if (__DEV__) { - if (isCustomComponentTag && !didWarnShadyDOM && el.shadyRoot) { + if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) { warning( false, '%s is using shady DOM. Using shady DOM with React can ' + @@ -570,9 +515,57 @@ var ReactDOMFiberComponent = { didWarnShadyDOM = true; } } - ReactDOMComponentTree.precacheFiberNode(workInProgress, el); + + switch (tag) { + case 'audio': + case 'form': + case 'iframe': + case 'img': + case 'link': + case 'object': + case 'source': + case 'video': + trapBubbledEventsLocal(domElement, tag); + break; + case 'input': + ReactDOMFiberInput.mountWrapper(domElement, props); + props = ReactDOMFiberInput.getHostProps(domElement, props); + // TODO: Make sure we check if this is still unmounted or do any clean + // up necessary since we never stop tracking anymore. + inputValueTracking.track(domElement); + trapBubbledEventsLocal(domElement, tag); + // For controlled components we always need to ensure we're listening + // to onChange. Even if there is no listener. + ensureListeningTo(rootContainerElement, 'onChange'); + break; + case 'option': + ReactDOMFiberOption.mountWrapper(domElement, props); + props = ReactDOMFiberOption.getHostProps(domElement, props); + break; + case 'select': + ReactDOMFiberSelect.mountWrapper(domElement, props); + props = ReactDOMFiberSelect.getHostProps(domElement, props); + trapBubbledEventsLocal(domElement, tag); + // For controlled components we always need to ensure we're listening + // to onChange. Even if there is no listener. + ensureListeningTo(rootContainerElement, 'onChange'); + break; + case 'textarea': + ReactDOMFiberTextarea.mountWrapper(domElement, props); + props = ReactDOMFiberTextarea.getHostProps(domElement, props); + inputValueTracking.track(domElement); // TODO + trapBubbledEventsLocal(domElement, tag); + // For controlled components we always need to ensure we're listening + // to onChange. Even if there is no listener. + ensureListeningTo(rootContainerElement, 'onChange'); + break; + } + + assertValidProps(tag, props); + + ReactDOMComponentTree.precacheFiberNode(internalInstanceHandle, domElement); updateDOMProperties( - workInProgress, + domElement, rootContainerElement, null, props, @@ -580,72 +573,73 @@ var ReactDOMFiberComponent = { isCustomComponentTag ); + // TODO: All these autoFocus won't work because the component is not in the + // DOM yet. We need a special effect to handle this. switch (tag) { case 'input': - ReactDOMFiberInput.postMountWrapper(workInProgress, props); + ReactDOMFiberInput.postMountWrapper(domElement, props); if (props.autoFocus) { - focusNode(getNode(workInProgress)); + focusNode(domElement); } break; case 'textarea': - ReactDOMFiberTextarea.postMountWrapper(workInProgress, props); + ReactDOMFiberTextarea.postMountWrapper(domElement, props); if (props.autoFocus) { - focusNode(getNode(workInProgress)); + focusNode(domElement); } break; case 'select': if (props.autoFocus) { - focusNode(getNode(workInProgress)); + focusNode(domElement); } break; case 'button': if (props.autoFocus) { - focusNode(getNode(workInProgress)); + focusNode(domElement); } break; case 'option': - ReactDOMFiberOption.postMountWrapper(workInProgress, props); + ReactDOMFiberOption.postMountWrapper(domElement, props); break; default: if (typeof props.onClick === 'function') { - trapClickOnNonInteractiveElement(workInProgress); + trapClickOnNonInteractiveElement(domElement); } break; } - return el; + return domElement; }, receiveComponent: function( - workInProgress : Fiber, + domElement : Element, rootContainerElement : Element, tag : string, + lastProps : Object, nextProps : Object, context : Object ) { - var lastProps = workInProgress.memoizedProps; - switch (tag) { case 'input': - lastProps = ReactDOMFiberInput.getHostProps(workInProgress, lastProps); - nextProps = ReactDOMFiberInput.getHostProps(workInProgress, nextProps); + lastProps = ReactDOMFiberInput.getHostProps(domElement, lastProps); + nextProps = ReactDOMFiberInput.getHostProps(domElement, nextProps); break; case 'option': - lastProps = ReactDOMFiberOption.getHostProps(workInProgress, lastProps); - nextProps = ReactDOMFiberOption.getHostProps(workInProgress, nextProps); + lastProps = ReactDOMFiberOption.getHostProps(domElement, lastProps); + nextProps = ReactDOMFiberOption.getHostProps(domElement, nextProps); break; case 'select': - lastProps = ReactDOMFiberSelect.getHostProps(workInProgress, lastProps); - nextProps = ReactDOMFiberSelect.getHostProps(workInProgress, nextProps); + lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastProps); + nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextProps); break; case 'textarea': - lastProps = ReactDOMFiberTextarea.getHostProps(workInProgress, lastProps); - nextProps = ReactDOMFiberTextarea.getHostProps(workInProgress, nextProps); + lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastProps); + nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextProps); break; default: if (typeof lastProps.onClick !== 'function' && typeof nextProps.onClick === 'function') { - trapClickOnNonInteractiveElement(workInProgress); + trapClickOnNonInteractiveElement(domElement); } break; } @@ -654,7 +648,7 @@ var ReactDOMFiberComponent = { var wasCustomComponentTag = isCustomComponent(tag, lastProps); var isCustomComponentTag = isCustomComponent(tag, nextProps); updateDOMProperties( - workInProgress, + domElement, rootContainerElement, lastProps, nextProps, @@ -667,38 +661,33 @@ var ReactDOMFiberComponent = { // 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. - ReactDOMFiberInput.updateWrapper(workInProgress, nextProps); + ReactDOMFiberInput.updateWrapper(domElement, nextProps); break; case 'textarea': - ReactDOMFiberTextarea.updateWrapper(workInProgress, nextProps); + ReactDOMFiberTextarea.updateWrapper(domElement, nextProps); break; case 'select': //