diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index 6cbf35f2f47..805b7a5513a 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -1,132 +1,8 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js -* renders a blank div with client render on top of good server markup -* renders a div with inline styles with client render on top of good server markup -* renders a self-closing tag with client render on top of good server markup -* renders a self-closing tag as a child with client render on top of good server markup -* renders simple numbers with client render on top of good server markup -* renders simple strings with client render on top of good server markup -* renders string prop with true value with client render on top of good server markup -* renders string prop with false value with client render on top of good server markup -* renders boolean prop with true value with client render on top of good server markup -* renders boolean prop with false value with client render on top of good server markup -* renders boolean prop with self value with client render on top of good server markup -* renders boolean prop with "" value with client render on top of good server markup -* renders boolean prop with string value with client render on top of good server markup -* renders boolean prop with array value with client render on top of good server markup -* renders boolean prop with object value with client render on top of good server markup -* renders boolean prop with non-zero number value with client render on top of good server markup -* renders boolean prop with zero value with client render on top of good server markup -* renders download prop with true value with client render on top of good server markup -* renders download prop with false value with client render on top of good server markup -* renders download prop with string value with client render on top of good server markup -* renders download prop with string "true" value with client render on top of good server markup -* renders className prop with string value with client render on top of good server markup -* renders className prop with empty string value with client render on top of good server markup -* renders className prop with true value with client render on top of good server markup -* renders className prop with false value with client render on top of good server markup -* renders htmlFor with string value with client render on top of good server markup -* renders htmlFor with an empty string with client render on top of good server markup -* renders className prop with true value with client render on top of good server markup -* renders className prop with false value with client render on top of good server markup -* renders no ref attribute with client render on top of good server markup -* renders no children attribute with client render on top of good server markup -* renders no key attribute with client render on top of good server markup -* renders no dangerouslySetInnerHTML attribute with client render on top of good server markup -* renders no unknown attributes with client render on top of good server markup -* renders unknown data- attributes with client render on top of good server markup -* renders no unknown attributes for non-standard elements with client render on top of good server markup -* renders unknown attributes for custom elements with client render on top of good server markup -* renders unknown attributes for custom elements using is with client render on top of good server markup -* renders no HTML events with client render on top of good server markup -* renders a div with text with client render on top of good server markup -* renders a div with text with flanking whitespace with client render on top of good server markup -* renders a div with an empty text child with client render on top of good server markup -* renders a div with multiple empty text children with client render on top of good server markup -* renders a div with multiple whitespace children with client render on top of good server markup -* renders a div with text sibling to a node with client render on top of good server markup -* renders a non-standard element with text with client render on top of good server markup -* renders a custom element with text with client render on top of good server markup -* renders a leading blank child with a text sibling with client render on top of good server markup -* renders a trailing blank child with a text sibling with client render on top of good server markup -* renders an element with two text children with client render on top of good server markup -* renders a number as single child with client render on top of good server markup -* renders zero as single child with client render on top of good server markup -* renders an element with number and text children with client render on top of good server markup -* renders null single child as blank with client render on top of good server markup -* renders false single child as blank with client render on top of good server markup -* renders undefined single child as blank with client render on top of good server markup -* renders a null component children as empty with client render on top of good server markup -* renders null children as blank with client render on top of good server markup -* renders false children as blank with client render on top of good server markup -* renders null and false children together as blank with client render on top of good server markup -* renders only null and false children as blank with client render on top of good server markup -* renders an svg element with client render on top of good server markup -* renders svg element with an xlink with client render on top of good server markup -* renders a math element with client render on top of good server markup -* renders an img with client render on top of good server markup -* renders a button with client render on top of good server markup -* renders a div with dangerouslySetInnerHTML with client render on top of good server markup -* renders a newline-eating tag with content not starting with \n with client render on top of good server markup -* renders a newline-eating tag with content starting with \n with client render on top of good server markup -* renders a normal tag with content starting with \n with client render on top of good server markup -* renders stateless components with client render on top of good server markup -* renders ES6 class components with client render on top of good server markup -* renders factory components with client render on top of good server markup -* renders single child hierarchies of components with client render on top of good server markup -* renders multi-child hierarchies of components with client render on top of good server markup -* renders a div with a child with client render on top of good server markup -* renders a div with multiple children with client render on top of good server markup -* renders a div with multiple children separated by whitespace with client render on top of good server markup -* renders a div with a single child surrounded by whitespace with client render on top of good server markup -* renders >,<, and & as single child with client render on top of good server markup -* renders >,<, and & as multiple children with client render on top of good server markup -* renders an input with a value and an onChange with client render on top of good server markup -* renders an input with a value and readOnly with client render on top of good server markup -* renders an input with a value and no onChange/readOnly with client render on top of good server markup -* renders an input with a defaultValue with client render on top of good server markup -* renders an input value overriding defaultValue with client render on top of good server markup -* renders an input value overriding defaultValue no matter the prop order with client render on top of good server markup -* renders a checkbox that is checked with an onChange with client render on top of good server markup -* renders a checkbox that is checked with readOnly with client render on top of good server markup -* renders a checkbox that is checked and no onChange/readOnly with client render on top of good server markup -* renders a checkbox with defaultChecked with client render on top of good server markup -* renders a checkbox checked overriding defaultChecked with client render on top of good server markup -* renders a checkbox checked overriding defaultChecked no matter the prop order with client render on top of good server markup -* renders a textarea with a value and an onChange with client render on top of good server markup -* renders a textarea with a value and readOnly with client render on top of good server markup -* renders a textarea with a value and no onChange/readOnly with client render on top of good server markup -* renders a textarea with a defaultValue with client render on top of good server markup -* renders a textarea value overriding defaultValue with client render on top of good server markup -* renders a textarea value overriding defaultValue no matter the prop order with client render on top of good server markup -* renders a select with a value and an onChange with client render on top of good server markup -* renders a select with a value and readOnly with client render on top of good server markup -* renders a select with a multiple values and an onChange with client render on top of good server markup -* renders a select with a multiple values and readOnly with client render on top of good server markup -* renders a select with a value and no onChange/readOnly with client render on top of good server markup -* renders a select with a defaultValue with client render on top of good server markup -* renders a select value overriding defaultValue with client render on top of good server markup -* renders a select value overriding defaultValue no matter the prop order with client render on top of good server markup -* renders a controlled text input with client render on top of good server markup -* renders a controlled textarea with client render on top of good server markup -* renders a controlled checkbox with client render on top of good server markup -* renders a controlled select with client render on top of good server markup -* should not blow away user-entered text on successful reconnect to an uncontrolled input -* should not blow away user-entered text on successful reconnect to a controlled input * should not blow away user-entered text on successful reconnect to an uncontrolled checkbox * should not blow away user-entered text on successful reconnect to a controlled checkbox * should not blow away user-selected value on successful reconnect to an uncontrolled select * should not blow away user-selected value on successful reconnect to an controlled select -* renders class child with context with client render on top of good server markup -* renders stateless child with context with client render on top of good server markup -* renders class child without context with client render on top of good server markup -* renders stateless child without context with client render on top of good server markup -* renders class child with wrong context with client render on top of good server markup -* renders stateless child with wrong context with client render on top of good server markup -* renders with context passed through to a grandchild with client render on top of good server markup -* renders a child context overriding a parent context with client render on top of good server markup -* renders a child context merged with a parent context with client render on top of good server markup -* renders with a call to componentWillMount before getChildContext with client render on top of good server markup -* should send the correct element to ref functions on client src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js * can reconcile text merged by Node.normalize() alongside other elements @@ -136,12 +12,3 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js src/renderers/dom/shared/__tests__/ReactMount-test.js * marks top-level mounts - -src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js -* should be able to adopt server markup -* should not be able to unmount component from document node -* should not be able to switch root constructors -* should be able to mount into document -* should give helpful errors on state desync -* should throw on full document render w/ no markup -* supports findDOMNode on full-page components diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index dcc1103e8b1..dd9cabc14b4 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -972,166 +972,247 @@ src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * renders a blank div with server string render * renders a blank div with clean client render +* renders a blank div with client render on top of good server markup * renders a div with inline styles with server string render * renders a div with inline styles with clean client render +* renders a div with inline styles with client render on top of good server markup * renders a self-closing tag with server string render * renders a self-closing tag with clean client render +* renders a self-closing tag with client render on top of good server markup * renders a self-closing tag as a child with server string render * renders a self-closing tag as a child with clean client render +* renders a self-closing tag as a child with client render on top of good server markup * renders simple numbers with server string render * renders simple numbers with clean client render +* renders simple numbers with client render on top of good server markup * renders simple strings with server string render * renders simple strings with clean client render +* renders simple strings with client render on top of good server markup * renders string prop with true value with server string render * renders string prop with true value with clean client render +* renders string prop with true value with client render on top of good server markup * renders string prop with false value with server string render * renders string prop with false value with clean client render +* renders string prop with false value with client render on top of good server markup * renders boolean prop with true value with server string render * renders boolean prop with true value with clean client render +* renders boolean prop with true value with client render on top of good server markup * renders boolean prop with false value with server string render * renders boolean prop with false value with clean client render +* renders boolean prop with false value with client render on top of good server markup * renders boolean prop with self value with server string render * renders boolean prop with self value with clean client render +* renders boolean prop with self value with client render on top of good server markup * renders boolean prop with "" value with server string render * renders boolean prop with "" value with clean client render +* renders boolean prop with "" value with client render on top of good server markup * renders boolean prop with string value with server string render * renders boolean prop with string value with clean client render +* renders boolean prop with string value with client render on top of good server markup * renders boolean prop with array value with server string render * renders boolean prop with array value with clean client render +* renders boolean prop with array value with client render on top of good server markup * renders boolean prop with object value with server string render * renders boolean prop with object value with clean client render +* renders boolean prop with object value with client render on top of good server markup * renders boolean prop with non-zero number value with server string render * renders boolean prop with non-zero number value with clean client render +* renders boolean prop with non-zero number value with client render on top of good server markup * renders boolean prop with zero value with server string render * renders boolean prop with zero value with clean client render +* renders boolean prop with zero value with client render on top of good server markup * renders download prop with true value with server string render * renders download prop with true value with clean client render +* renders download prop with true value with client render on top of good server markup * renders download prop with false value with server string render * renders download prop with false value with clean client render +* renders download prop with false value with client render on top of good server markup * renders download prop with string value with server string render * renders download prop with string value with clean client render +* renders download prop with string value with client render on top of good server markup * renders download prop with string "true" value with server string render * renders download prop with string "true" value with clean client render +* renders download prop with string "true" value with client render on top of good server markup * renders className prop with string value with server string render * renders className prop with string value with clean client render +* renders className prop with string value with client render on top of good server markup * renders className prop with empty string value with server string render * renders className prop with empty string value with clean client render +* renders className prop with empty string value with client render on top of good server markup * renders className prop with true value with server string render * renders className prop with true value with clean client render +* renders className prop with true value with client render on top of good server markup * renders className prop with false value with server string render * renders className prop with false value with clean client render +* renders className prop with false value with client render on top of good server markup * renders htmlFor with string value with server string render * renders htmlFor with string value with clean client render +* renders htmlFor with string value with client render on top of good server markup * renders htmlFor with an empty string with server string render * renders htmlFor with an empty string with clean client render +* renders htmlFor with an empty string with client render on top of good server markup * renders className prop with true value with server string render * renders className prop with true value with clean client render +* renders className prop with true value with client render on top of good server markup * renders className prop with false value with server string render * renders className prop with false value with clean client render +* renders className prop with false value with client render on top of good server markup * renders no ref attribute with server string render * renders no ref attribute with clean client render +* renders no ref attribute with client render on top of good server markup * renders no children attribute with server string render * renders no children attribute with clean client render +* renders no children attribute with client render on top of good server markup * renders no key attribute with server string render * renders no key attribute with clean client render +* renders no key attribute with client render on top of good server markup * renders no dangerouslySetInnerHTML attribute with server string render * renders no dangerouslySetInnerHTML attribute with clean client render +* renders no dangerouslySetInnerHTML attribute with client render on top of good server markup * renders no unknown attributes with server string render * renders no unknown attributes with clean client render +* renders no unknown attributes with client render on top of good server markup * renders unknown data- attributes with server string render * renders unknown data- attributes with clean client render +* renders unknown data- attributes with client render on top of good server markup * renders no unknown attributes for non-standard elements with server string render * renders no unknown attributes for non-standard elements with clean client render +* renders no unknown attributes for non-standard elements with client render on top of good server markup * renders unknown attributes for custom elements with server string render * renders unknown attributes for custom elements with clean client render +* renders unknown attributes for custom elements with client render on top of good server markup * renders unknown attributes for custom elements using is with server string render * renders unknown attributes for custom elements using is with clean client render +* renders unknown attributes for custom elements using is with client render on top of good server markup * renders no HTML events with server string render * renders no HTML events with clean client render +* renders no HTML events with client render on top of good server markup * renders a div with text with server string render * renders a div with text with clean client render +* renders a div with text with client render on top of good server markup * renders a div with text with flanking whitespace with server string render * renders a div with text with flanking whitespace with clean client render +* renders a div with text with flanking whitespace with client render on top of good server markup * renders a div with an empty text child with server string render * renders a div with an empty text child with clean client render +* renders a div with an empty text child with client render on top of good server markup * renders a div with multiple empty text children with server string render * renders a div with multiple empty text children with clean client render +* renders a div with multiple empty text children with client render on top of good server markup * renders a div with multiple whitespace children with server string render * renders a div with multiple whitespace children with clean client render +* renders a div with multiple whitespace children with client render on top of good server markup * renders a div with text sibling to a node with server string render * renders a div with text sibling to a node with clean client render +* renders a div with text sibling to a node with client render on top of good server markup * renders a non-standard element with text with server string render * renders a non-standard element with text with clean client render +* renders a non-standard element with text with client render on top of good server markup * renders a custom element with text with server string render * renders a custom element with text with clean client render +* renders a custom element with text with client render on top of good server markup * renders a leading blank child with a text sibling with server string render * renders a leading blank child with a text sibling with clean client render +* renders a leading blank child with a text sibling with client render on top of good server markup * renders a trailing blank child with a text sibling with server string render * renders a trailing blank child with a text sibling with clean client render +* renders a trailing blank child with a text sibling with client render on top of good server markup * renders an element with two text children with server string render * renders an element with two text children with clean client render +* renders an element with two text children with client render on top of good server markup * renders a number as single child with server string render * renders a number as single child with clean client render +* renders a number as single child with client render on top of good server markup * renders zero as single child with server string render * renders zero as single child with clean client render +* renders zero as single child with client render on top of good server markup * renders an element with number and text children with server string render * renders an element with number and text children with clean client render +* renders an element with number and text children with client render on top of good server markup * renders null single child as blank with server string render * renders null single child as blank with clean client render +* renders null single child as blank with client render on top of good server markup * renders false single child as blank with server string render * renders false single child as blank with clean client render +* renders false single child as blank with client render on top of good server markup * renders undefined single child as blank with server string render * renders undefined single child as blank with clean client render +* renders undefined single child as blank with client render on top of good server markup * renders a null component children as empty with server string render * renders a null component children as empty with clean client render +* renders a null component children as empty with client render on top of good server markup * renders null children as blank with server string render * renders null children as blank with clean client render +* renders null children as blank with client render on top of good server markup * renders false children as blank with server string render * renders false children as blank with clean client render +* renders false children as blank with client render on top of good server markup * renders null and false children together as blank with server string render * renders null and false children together as blank with clean client render +* renders null and false children together as blank with client render on top of good server markup * renders only null and false children as blank with server string render * renders only null and false children as blank with clean client render +* renders only null and false children as blank with client render on top of good server markup * renders an svg element with server string render * renders an svg element with clean client render +* renders an svg element with client render on top of good server markup * renders svg element with an xlink with server string render * renders svg element with an xlink with clean client render +* renders svg element with an xlink with client render on top of good server markup * renders a math element with server string render * renders a math element with clean client render +* renders a math element with client render on top of good server markup * renders an img with server string render * renders an img with clean client render +* renders an img with client render on top of good server markup * renders a button with server string render * renders a button with clean client render +* renders a button with client render on top of good server markup * renders a div with dangerouslySetInnerHTML with server string render * renders a div with dangerouslySetInnerHTML with clean client render +* renders a div with dangerouslySetInnerHTML with client render on top of good server markup * renders a newline-eating tag with content not starting with \n with server string render * renders a newline-eating tag with content not starting with \n with clean client render +* renders a newline-eating tag with content not starting with \n with client render on top of good server markup * renders a newline-eating tag with content starting with \n with server string render * renders a newline-eating tag with content starting with \n with clean client render +* renders a newline-eating tag with content starting with \n with client render on top of good server markup * renders a normal tag with content starting with \n with server string render * renders a normal tag with content starting with \n with clean client render +* renders a normal tag with content starting with \n with client render on top of good server markup * renders stateless components with server string render * renders stateless components with clean client render +* renders stateless components with client render on top of good server markup * renders ES6 class components with server string render * renders ES6 class components with clean client render +* renders ES6 class components with client render on top of good server markup * renders factory components with server string render * renders factory components with clean client render +* renders factory components with client render on top of good server markup * renders single child hierarchies of components with server string render * renders single child hierarchies of components with clean client render +* renders single child hierarchies of components with client render on top of good server markup * renders multi-child hierarchies of components with server string render * renders multi-child hierarchies of components with clean client render +* renders multi-child hierarchies of components with client render on top of good server markup * renders a div with a child with server string render * renders a div with a child with clean client render +* renders a div with a child with client render on top of good server markup * renders a div with multiple children with server string render * renders a div with multiple children with clean client render +* renders a div with multiple children with client render on top of good server markup * renders a div with multiple children separated by whitespace with server string render * renders a div with multiple children separated by whitespace with clean client render +* renders a div with multiple children separated by whitespace with client render on top of good server markup * renders a div with a single child surrounded by whitespace with server string render * renders a div with a single child surrounded by whitespace with clean client render +* renders a div with a single child surrounded by whitespace with client render on top of good server markup * renders >,<, and & as single child with server string render * renders >,<, and & as single child with clean client render +* renders >,<, and & as single child with client render on top of good server markup * renders >,<, and & as multiple children with server string render * renders >,<, and & as multiple children with clean client render +* renders >,<, and & as multiple children with client render on top of good server markup * throws when rendering a string component with server string render * throws when rendering an undefined component with server string render * throws when rendering a number component with server string render @@ -1152,80 +1233,122 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * throws when rendering string with client render on top of bad server markup * renders an input with a value and an onChange with server string render * renders an input with a value and an onChange with clean client render +* renders an input with a value and an onChange with client render on top of good server markup * renders an input with a value and readOnly with server string render * renders an input with a value and readOnly with clean client render +* renders an input with a value and readOnly with client render on top of good server markup * renders an input with a value and no onChange/readOnly with server string render * renders an input with a value and no onChange/readOnly with clean client render +* renders an input with a value and no onChange/readOnly with client render on top of good server markup * renders an input with a defaultValue with server string render * renders an input with a defaultValue with clean client render +* renders an input with a defaultValue with client render on top of good server markup * renders an input value overriding defaultValue with server string render * renders an input value overriding defaultValue with clean client render +* renders an input value overriding defaultValue with client render on top of good server markup * renders an input value overriding defaultValue no matter the prop order with server string render * renders an input value overriding defaultValue no matter the prop order with clean client render +* renders an input value overriding defaultValue no matter the prop order with client render on top of good server markup * renders a checkbox that is checked with an onChange with server string render * renders a checkbox that is checked with an onChange with clean client render +* renders a checkbox that is checked with an onChange with client render on top of good server markup * renders a checkbox that is checked with readOnly with server string render * renders a checkbox that is checked with readOnly with clean client render +* renders a checkbox that is checked with readOnly with client render on top of good server markup * renders a checkbox that is checked and no onChange/readOnly with server string render * renders a checkbox that is checked and no onChange/readOnly with clean client render +* renders a checkbox that is checked and no onChange/readOnly with client render on top of good server markup * renders a checkbox with defaultChecked with server string render * renders a checkbox with defaultChecked with clean client render +* renders a checkbox with defaultChecked with client render on top of good server markup * renders a checkbox checked overriding defaultChecked with server string render * renders a checkbox checked overriding defaultChecked with clean client render +* renders a checkbox checked overriding defaultChecked with client render on top of good server markup * renders a checkbox checked overriding defaultChecked no matter the prop order with server string render * renders a checkbox checked overriding defaultChecked no matter the prop order with clean client render +* renders a checkbox checked overriding defaultChecked no matter the prop order with client render on top of good server markup * renders a textarea with a value and an onChange with server string render * renders a textarea with a value and an onChange with clean client render +* renders a textarea with a value and an onChange with client render on top of good server markup * renders a textarea with a value and readOnly with server string render * renders a textarea with a value and readOnly with clean client render +* renders a textarea with a value and readOnly with client render on top of good server markup * renders a textarea with a value and no onChange/readOnly with server string render * renders a textarea with a value and no onChange/readOnly with clean client render +* renders a textarea with a value and no onChange/readOnly with client render on top of good server markup * renders a textarea with a defaultValue with server string render * renders a textarea with a defaultValue with clean client render +* renders a textarea with a defaultValue with client render on top of good server markup * renders a textarea value overriding defaultValue with server string render * renders a textarea value overriding defaultValue with clean client render +* renders a textarea value overriding defaultValue with client render on top of good server markup * renders a textarea value overriding defaultValue no matter the prop order with server string render * renders a textarea value overriding defaultValue no matter the prop order with clean client render +* renders a textarea value overriding defaultValue no matter the prop order with client render on top of good server markup * renders a select with a value and an onChange with server string render * renders a select with a value and an onChange with clean client render +* renders a select with a value and an onChange with client render on top of good server markup * renders a select with a value and readOnly with server string render * renders a select with a value and readOnly with clean client render +* renders a select with a value and readOnly with client render on top of good server markup * renders a select with a multiple values and an onChange with server string render * renders a select with a multiple values and an onChange with clean client render +* renders a select with a multiple values and an onChange with client render on top of good server markup * renders a select with a multiple values and readOnly with server string render * renders a select with a multiple values and readOnly with clean client render +* renders a select with a multiple values and readOnly with client render on top of good server markup * renders a select with a value and no onChange/readOnly with server string render * renders a select with a value and no onChange/readOnly with clean client render +* renders a select with a value and no onChange/readOnly with client render on top of good server markup * renders a select with a defaultValue with server string render * renders a select with a defaultValue with clean client render +* renders a select with a defaultValue with client render on top of good server markup * renders a select value overriding defaultValue with server string render * renders a select value overriding defaultValue with clean client render +* renders a select value overriding defaultValue with client render on top of good server markup * renders a select value overriding defaultValue no matter the prop order with server string render * renders a select value overriding defaultValue no matter the prop order with clean client render +* renders a select value overriding defaultValue no matter the prop order with client render on top of good server markup * renders a controlled text input with clean client render +* renders a controlled text input with client render on top of good server markup * renders a controlled textarea with clean client render +* renders a controlled textarea with client render on top of good server markup * renders a controlled checkbox with clean client render +* renders a controlled checkbox with client render on top of good server markup * renders a controlled select with clean client render +* renders a controlled select with client render on top of good server markup +* should not blow away user-entered text on successful reconnect to an uncontrolled input +* should not blow away user-entered text on successful reconnect to a controlled input * renders class child with context with server string render * renders class child with context with clean client render +* renders class child with context with client render on top of good server markup * renders stateless child with context with server string render * renders stateless child with context with clean client render +* renders stateless child with context with client render on top of good server markup * renders class child without context with server string render * renders class child without context with clean client render +* renders class child without context with client render on top of good server markup * renders stateless child without context with server string render * renders stateless child without context with clean client render +* renders stateless child without context with client render on top of good server markup * renders class child with wrong context with server string render * renders class child with wrong context with clean client render +* renders class child with wrong context with client render on top of good server markup * renders stateless child with wrong context with server string render * renders stateless child with wrong context with clean client render +* renders stateless child with wrong context with client render on top of good server markup * renders with context passed through to a grandchild with server string render * renders with context passed through to a grandchild with clean client render +* renders with context passed through to a grandchild with client render on top of good server markup * renders a child context overriding a parent context with server string render * renders a child context overriding a parent context with clean client render +* renders a child context overriding a parent context with client render on top of good server markup * renders a child context merged with a parent context with server string render * renders a child context merged with a parent context with clean client render +* renders a child context merged with a parent context with client render on top of good server markup * renders with a call to componentWillMount before getChildContext with server string render * renders with a call to componentWillMount before getChildContext with clean client render +* renders with a call to componentWillMount before getChildContext with client render on top of good server markup * throws when rendering if getChildContext exists without childContextTypes with server string render * throws when rendering if getChildContext exists without childContextTypes with clean client render * throws when rendering if getChildContext exists without childContextTypes with client render on top of bad server markup @@ -1234,6 +1357,7 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * throws when rendering if getChildContext returns a value not in childContextTypes with client render on top of bad server markup * should not run ref code on server * should run ref code on client +* should send the correct element to ref functions on client * should have string refs on client when rendered over server markup * should reconnect ES6 Class to ES6 Class * should reconnect Pure Component to ES6 Class @@ -1274,6 +1398,15 @@ src/renderers/dom/shared/__tests__/ReactMount-test.js src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js * should destroy a react root upon request +src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js +* should be able to adopt server markup +* should not be able to unmount component from document node +* should not be able to switch root constructors +* should be able to mount into document +* should give helpful errors on state desync +* should throw on full document render w/ no markup +* supports findDOMNode on full-page components + src/renderers/dom/shared/__tests__/ReactServerRendering-test.js * should generate simple markup * should generate simple markup for self-closing tags diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index f7856a4b13a..635f4c91dab 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -36,6 +36,7 @@ var { DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, } = require('HTMLNodeType'); +var {ID_ATTRIBUTE_NAME} = require('DOMProperty'); var findDOMNode = require('findDOMNode'); var invariant = require('fbjs/lib/invariant'); @@ -63,9 +64,11 @@ findDOMNode._injectFiber(function(fiber: Fiber) { return DOMRenderer.findHostInstance(fiber); }); -type DOMContainerElement = Element & {_reactRootContainer: ?Object}; +type DOMContainer = + | (Element & {_reactRootContainer: ?Object}) + | (Document & {_reactRootContainer: ?Object}); -type Container = Element; +type Container = Element | Document; type Props = { autoFocus?: boolean, children?: mixed, @@ -98,12 +101,6 @@ function isValidContainer(node) { node.nodeType === DOCUMENT_FRAGMENT_NODE)); } -function validateContainer(container) { - if (!isValidContainer(container)) { - throw new Error('Target container is not a DOM element.'); - } -} - function getReactRootElementInContainer(container: any) { if (!container) { return null; @@ -116,6 +113,11 @@ function getReactRootElementInContainer(container: any) { } } +function shouldReuseContent(container) { + const rootElement = getReactRootElementInContainer(container); + return !!(rootElement && rootElement.getAttribute(ID_ATTRIBUTE_NAME)); +} + function shouldAutoFocusHostComponent(type: string, props: Props): boolean { switch (type) { case 'button': @@ -129,16 +131,19 @@ function shouldAutoFocusHostComponent(type: string, props: Props): boolean { var DOMRenderer = ReactFiberReconciler({ getRootHostContext(rootContainerInstance: Container): HostContext { - const ownNamespace = rootContainerInstance.namespaceURI || null; - const type = rootContainerInstance.tagName; - const namespace = getChildNamespace(ownNamespace, type); + let type; + let namespace; + if (rootContainerInstance.nodeType === DOCUMENT_NODE) { + type = '#document'; + let root = (rootContainerInstance: any).documentElement; + namespace = root ? root.namespaceURI : getChildNamespace(null, ''); + } else { + const ownNamespace = (rootContainerInstance: any).namespaceURI || null; + type = (rootContainerInstance: any).tagName; + namespace = getChildNamespace(ownNamespace, type); + } if (__DEV__) { - const isMountingIntoDocument = - rootContainerInstance.ownerDocument.documentElement === - rootContainerInstance; - const validatedTag = isMountingIntoDocument - ? '#document' - : type.toLowerCase(); + const validatedTag = type.toLowerCase(); const ancestorInfo = updatedAncestorInfo(null, validatedTag, null); return {namespace, ancestorInfo}; } @@ -360,6 +365,65 @@ var DOMRenderer = ReactFiberReconciler({ parentInstance.removeChild(child); }, + canHydrateInstance( + instance: Instance | TextInstance, + type: string, + props: Props, + ): boolean { + return instance.nodeType === 1 && type === instance.nodeName.toLowerCase(); + }, + + canHydrateTextInstance(instance: Instance | TextInstance): boolean { + return instance.nodeType === 3; + }, + + getNextHydratableSibling( + instance: Instance | TextInstance, + ): null | Instance | TextInstance { + let node = instance.nextSibling; + // Skip non-hydratable nodes. + /* + while (node && node.nodeType !== 1 && node.nodeType !== 3) { + node = node.nextSibling; + } + */ + return (node: any); + }, + + getFirstHydratableChild( + parentInstance: Container | Instance, + ): null | Instance | TextInstance { + let next = parentInstance.firstChild; + // Skip non-hydratable nodes. + /* + while (next && next.nodeType !== 1 && next.nodeType !== 3) { + next = next.nextSibling; + } + */ + return (next: any); + }, + + hydrateInstance( + instance: Instance, + type: string, + props: Props, + rootContainerInstance: Container, + internalInstanceHandle: Object, + ): void { + precacheFiberNode(internalInstanceHandle, instance); + // TODO: Possibly defer this until the commit phase where all the events + // get attached. + updateFiberProps(instance, props); + setInitialProperties(instance, type, props, rootContainerInstance); + }, + + hydrateTextInstance( + textInstance: TextInstance, + internalInstanceHandle: Object, + ): void { + precacheFiberNode(internalInstanceHandle, textInstance); + }, + scheduleAnimationCallback: ReactDOMFrameScheduling.rAF, scheduleDeferredCallback: ReactDOMFrameScheduling.rIC, @@ -386,19 +450,48 @@ function warnAboutUnstableUse() { function renderSubtreeIntoContainer( parentComponent: ?ReactComponent, children: ReactNodeList, - containerNode: DOMContainerElement | Document, + container: DOMContainer, callback: ?Function, ) { - validateContainer(containerNode); + invariant( + isValidContainer(container), + 'Target container is not a DOM element.', + ); + + if (__DEV__) { + const isRootRenderedBySomeReact = !!container._reactRootContainer; + const rootEl = getReactRootElementInContainer(container); + const hasNonRootReactChild = !!(rootEl && + ReactDOMComponentTree.getInstanceFromNode(rootEl)); + + warning( + !hasNonRootReactChild || isRootRenderedBySomeReact, + 'render(...): Replacing React-rendered children with a new root ' + + 'component. If you intended to update the children of this node, ' + + 'you should instead have the existing children update their state ' + + 'and render the new components instead of calling ReactDOM.render.', + ); + + warning( + container.nodeType !== 1 || + !container.tagName || + container.tagName.toUpperCase() !== 'BODY', + 'render(): Rendering components directly into document.body is ' + + 'discouraged, since its children are often manipulated by third-party ' + + 'scripts and browser extensions. This may lead to subtle ' + + 'reconciliation issues. Try rendering into a container element created ' + + 'for your app.', + ); + } - let container: DOMContainerElement = containerNode.nodeType === DOCUMENT_NODE - ? (containerNode: any).documentElement - : (containerNode: any); let root = container._reactRootContainer; if (!root) { // First clear any existing content. - while (container.lastChild) { - container.removeChild(container.lastChild); + // TODO: Figure out the best heuristic here. + if (!shouldReuseContent(container)) { + while (container.lastChild) { + container.removeChild(container.lastChild); + } } const newRoot = DOMRenderer.createContainer(container); root = container._reactRootContainer = newRoot; @@ -415,11 +508,9 @@ function renderSubtreeIntoContainer( var ReactDOM = { render( element: ReactElement, - container: DOMContainerElement, + container: DOMContainer, callback: ?Function, ) { - validateContainer(container); - if (ReactFeatureFlags.disableNewFiberFeatures) { // Top-level check occurs here instead of inside child reconciler because // because requirements vary between renderers. E.g. React Art @@ -452,38 +543,13 @@ var ReactDOM = { } } } - - if (__DEV__) { - const isRootRenderedBySomeReact = !!container._reactRootContainer; - const rootEl = getReactRootElementInContainer(container); - const hasNonRootReactChild = !!(rootEl && - ReactDOMComponentTree.getInstanceFromNode(rootEl)); - - warning( - !hasNonRootReactChild || isRootRenderedBySomeReact, - 'render(...): Replacing React-rendered children with a new root ' + - 'component. If you intended to update the children of this node, ' + - 'you should instead have the existing children update their state ' + - 'and render the new components instead of calling ReactDOM.render.', - ); - - warning( - !container.tagName || container.tagName.toUpperCase() !== 'BODY', - 'render(): Rendering components directly into document.body is ' + - 'discouraged, since its children are often manipulated by third-party ' + - 'scripts and browser extensions. This may lead to subtle ' + - 'reconciliation issues. Try rendering into a container element created ' + - 'for your app.', - ); - } - return renderSubtreeIntoContainer(null, element, container, callback); }, unstable_renderSubtreeIntoContainer( parentComponent: ReactComponent, element: ReactElement, - containerNode: DOMContainerElement | Document, + containerNode: DOMContainer, callback: ?Function, ) { invariant( @@ -498,7 +564,7 @@ var ReactDOM = { ); }, - unmountComponentAtNode(container: DOMContainerElement) { + unmountComponentAtNode(container: DOMContainer) { invariant( isValidContainer(container), 'unmountComponentAtNode(...): Target container is not a DOM element.', @@ -535,7 +601,7 @@ var ReactDOM = { unstable_createPortal( children: ReactNodeList, - container: DOMContainerElement, + container: DOMContainer, key: ?string = null, ) { // TODO: pass ReactDOM portal implementation as third argument diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js index 6b10507c3f3..948711f5b2a 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js +++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js @@ -23,7 +23,7 @@ var ReactDOMFiberOption = require('ReactDOMFiberOption'); var ReactDOMFiberSelect = require('ReactDOMFiberSelect'); var ReactDOMFiberTextarea = require('ReactDOMFiberTextarea'); var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber'); -var {DOCUMENT_FRAGMENT_NODE} = require('HTMLNodeType'); +var {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} = require('HTMLNodeType'); var assertValidProps = require('assertValidProps'); var emptyFunction = require('fbjs/lib/emptyFunction'); @@ -72,9 +72,10 @@ if (__DEV__) { } function ensureListeningTo(rootContainerElement, registrationName) { - var isDocumentFragment = + var isDocumentOrFragment = + rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE; - var doc = isDocumentFragment + var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument; listenTo(registrationName, doc); @@ -171,7 +172,7 @@ function trapBubbledEventsLocal(node: Element, tag: string) { function setInitialDOMProperties( domElement: Element, - rootContainerElement: Element, + rootContainerElement: Element | Document, nextProps: Object, isCustomComponentTag: boolean, ): void { @@ -304,12 +305,15 @@ var ReactDOMFiberComponent = { createElement( type: string, props: Object, - rootContainerElement: Element, + rootContainerElement: Element | Document, parentNamespace: string, ): Element { // We create tags in the namespace of their parent container, except HTML // tags get no namespace. - var ownerDocument = rootContainerElement.ownerDocument; + var ownerDocument: Document = rootContainerElement.nodeType === + DOCUMENT_NODE + ? (rootContainerElement: any) + : rootContainerElement.ownerDocument; var domElement: Element; var namespaceURI = parentNamespace; if (namespaceURI === HTML_NAMESPACE) { @@ -369,7 +373,7 @@ var ReactDOMFiberComponent = { domElement: Element, tag: string, rawProps: Object, - rootContainerElement: Element, + rootContainerElement: Element | Document, ): void { var isCustomComponentTag = isCustomComponent(tag, rawProps); if (__DEV__) { @@ -472,7 +476,7 @@ var ReactDOMFiberComponent = { tag: string, lastRawProps: Object, nextRawProps: Object, - rootContainerElement: Element, + rootContainerElement: Element | Document, ): null | Array { if (__DEV__) { validatePropertiesInDevelopment(tag, nextRawProps); diff --git a/src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js b/src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js index 642db7bd780..3d0a90dc77a 100644 --- a/src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js +++ b/src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js @@ -14,11 +14,10 @@ var React; var ReactDOM; var ReactDOMServer; +var ReactDOMFeatureFlags; var getTestDocument; -var testDocument; - var UNMOUNT_INVARIANT_MESSAGE = ' tried to unmount. ' + 'Because of cross-browser quirks it is impossible to unmount some ' + @@ -33,14 +32,11 @@ describe('rendering React components at document', () => { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); getTestDocument = require('getTestDocument'); - - testDocument = getTestDocument(); }); it('should be able to adopt server markup', () => { - expect(testDocument).not.toBeUndefined(); - class Root extends React.Component { render() { return ( @@ -57,7 +53,7 @@ describe('rendering React components at document', () => { } var markup = ReactDOMServer.renderToString(); - testDocument = getTestDocument(markup); + var testDocument = getTestDocument(markup); var body = testDocument.body; ReactDOM.render(, testDocument); @@ -66,12 +62,10 @@ describe('rendering React components at document', () => { ReactDOM.render(, testDocument); expect(testDocument.body.innerHTML).toBe('Hello moon'); - expect(body).toBe(testDocument.body); + expect(body === testDocument.body).toBe(true); }); it('should not be able to unmount component from document node', () => { - expect(testDocument).not.toBeUndefined(); - class Root extends React.Component { render() { return ( @@ -88,20 +82,24 @@ describe('rendering React components at document', () => { } var markup = ReactDOMServer.renderToString(); - testDocument = getTestDocument(markup); + var testDocument = getTestDocument(markup); ReactDOM.render(, testDocument); expect(testDocument.body.innerHTML).toBe('Hello world'); - expect(function() { + if (ReactDOMFeatureFlags.useFiber) { + // In Fiber this actually works. It might not be a good idea though. ReactDOM.unmountComponentAtNode(testDocument); - }).toThrowError(UNMOUNT_INVARIANT_MESSAGE); + expect(testDocument.firstChild).toBe(null); + } else { + expect(function() { + ReactDOM.unmountComponentAtNode(testDocument); + }).toThrowError(UNMOUNT_INVARIANT_MESSAGE); - expect(testDocument.body.innerHTML).toBe('Hello world'); + expect(testDocument.body.innerHTML).toBe('Hello world'); + } }); it('should not be able to switch root constructors', () => { - expect(testDocument).not.toBeUndefined(); - class Component extends React.Component { render() { return ( @@ -133,23 +131,28 @@ describe('rendering React components at document', () => { } var markup = ReactDOMServer.renderToString(); - testDocument = getTestDocument(markup); + var testDocument = getTestDocument(markup); ReactDOM.render(, testDocument); expect(testDocument.body.innerHTML).toBe('Hello world'); // Reactive update - expect(function() { + if (ReactDOMFeatureFlags.useFiber) { + // This works but is probably a bad idea. ReactDOM.render(, testDocument); - }).toThrowError(UNMOUNT_INVARIANT_MESSAGE); - expect(testDocument.body.innerHTML).toBe('Hello world'); + expect(testDocument.body.innerHTML).toBe('Goodbye world'); + } else { + expect(function() { + ReactDOM.render(, testDocument); + }).toThrowError(UNMOUNT_INVARIANT_MESSAGE); + + expect(testDocument.body.innerHTML).toBe('Hello world'); + } }); it('should be able to mount into document', () => { - expect(testDocument).not.toBeUndefined(); - class Component extends React.Component { render() { return ( @@ -168,7 +171,7 @@ describe('rendering React components at document', () => { var markup = ReactDOMServer.renderToString( , ); - testDocument = getTestDocument(markup); + var testDocument = getTestDocument(markup); ReactDOM.render(, testDocument); @@ -176,8 +179,6 @@ describe('rendering React components at document', () => { }); it('should give helpful errors on state desync', () => { - expect(testDocument).not.toBeUndefined(); - class Component extends React.Component { render() { return ( @@ -196,29 +197,32 @@ describe('rendering React components at document', () => { var markup = ReactDOMServer.renderToString( , ); - testDocument = getTestDocument(markup); + var testDocument = getTestDocument(markup); - expect(function() { - // Notice the text is different! + if (ReactDOMFeatureFlags.useFiber) { ReactDOM.render(, testDocument); - }).toThrowError( - "You're trying to render a component to the document using " + - 'server rendering but the checksum was invalid. This usually ' + - 'means you rendered a different component type or props on ' + - 'the client from the one on the server, or your render() methods ' + - 'are impure. React cannot handle this case due to cross-browser ' + - 'quirks by rendering at the document root. You should look for ' + - 'environment dependent code in your components and ensure ' + - 'the props are the same client and server side:\n' + - ' (client) dy data-reactid="4">Hello worldGoodbye world', - ); + expect(testDocument.body.innerHTML).toBe('Hello world'); + } else { + expect(function() { + // Notice the text is different! + ReactDOM.render(, testDocument); + }).toThrowError( + "You're trying to render a component to the document using " + + 'server rendering but the checksum was invalid. This usually ' + + 'means you rendered a different component type or props on ' + + 'the client from the one on the server, or your render() methods ' + + 'are impure. React cannot handle this case due to cross-browser ' + + 'quirks by rendering at the document root. You should look for ' + + 'environment dependent code in your components and ensure ' + + 'the props are the same client and server side:\n' + + ' (client) dy data-reactid="4">Hello worldGoodbye world', + ); + } }); it('should throw on full document render w/ no markup', () => { - expect(testDocument).not.toBeUndefined(); - - var container = testDocument; + var testDocument = getTestDocument(); class Component extends React.Component { render() { @@ -235,14 +239,19 @@ describe('rendering React components at document', () => { } } - expect(function() { - ReactDOM.render(, container); - }).toThrowError( - "You're trying to render a component to the document but you didn't " + - "use server rendering. We can't do this without using server " + - 'rendering due to cross-browser quirks. See ' + - 'ReactDOMServer.renderToString() for server rendering.', - ); + if (ReactDOMFeatureFlags.useFiber) { + ReactDOM.render(, testDocument); + expect(testDocument.body.innerHTML).toBe('Hello world'); + } else { + expect(function() { + ReactDOM.render(, testDocument); + }).toThrowError( + "You're trying to render a component to the document but you didn't " + + "use server rendering. We can't do this without using server " + + 'rendering due to cross-browser quirks. See ' + + 'ReactDOMServer.renderToString() for server rendering.', + ); + } }); it('supports findDOMNode on full-page components', () => { @@ -258,7 +267,7 @@ describe('rendering React components at document', () => { ); var markup = ReactDOMServer.renderToString(tree); - testDocument = getTestDocument(markup); + var testDocument = getTestDocument(markup); var component = ReactDOM.render(tree, testDocument); expect(testDocument.body.innerHTML).toBe('Hello world'); expect(ReactDOM.findDOMNode(component).tagName).toBe('HTML'); diff --git a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js index 889356a1f13..93975911b6c 100644 --- a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js +++ b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js @@ -268,11 +268,8 @@ describe('ReactDOMServer', () => { var expectedMarkup = lastMarkup; if (ReactDOMFeatureFlags.useFiber) { - var reactMetaData = /\s+data-react[a-z-]+="[^"]*"/g; var reactComments = //g; - expectedMarkup = expectedMarkup - .replace(reactMetaData, '') - .replace(reactComments, ''); + expectedMarkup = expectedMarkup.replace(reactComments, ''); } expect(element.innerHTML).toBe(expectedMarkup); diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index 6cf9645a92f..1cebd90a03c 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -436,6 +436,12 @@ function createFiberFromElementType( exports.createFiberFromElementType = createFiberFromElementType; +exports.createFiberFromHostInstanceForDeletion = function(): Fiber { + const fiber = createFiber(HostComponent, null, NoContext); + fiber.type = 'DELETED'; + return fiber; +}; + exports.createFiberFromCoroutine = function( coroutine: ReactCoroutine, internalContextTag: TypeOfInternalContext, diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index dce9c2b9318..8d9a8b4abb9 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -15,6 +15,7 @@ import type {ReactCoroutine} from 'ReactTypes'; import type {Fiber} from 'ReactFiber'; import type {HostContext} from 'ReactFiberHostContext'; +import type {HydrationContext} from 'ReactFiberHydrationContext'; import type {FiberRoot} from 'ReactFiberRoot'; import type {HostConfig} from 'ReactFiberReconciler'; import type {PriorityLevel} from 'ReactPriorityLevel'; @@ -65,6 +66,7 @@ if (__DEV__) { module.exports = function( config: HostConfig, hostContext: HostContext, + hydrationContext: HydrationContext, scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void, getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel, ) { @@ -76,6 +78,12 @@ module.exports = function( const {pushHostContext, pushHostContainer} = hostContext; + const { + enterHydrationState, + resetHydrationState, + tryToClaimNextHydratableInstance, + } = hydrationContext; + const { adoptClassInstance, constructClassInstance, @@ -349,13 +357,43 @@ module.exports = function( if (prevState === state) { // If the state is the same as before, that's a bailout because we had // no work matching this priority. + resetHydrationState(); return bailoutOnAlreadyFinishedWork(current, workInProgress); } const element = state.element; + if (current === null || current.child === null) { + // If we don't have any current children this might be the first pass. + // We always try to hydrate. If this isn't a hydration pass there won't + // be any children to hydrate which is effectively the same thing as + // not hydrating. + if (enterHydrationState(workInProgress)) { + // This is a bit of a hack. We track the host root as a placement to + // know that we're currently in a mounting state. That way isMounted + // works as expected. We must reset this before committing. + // TODO: Delete this when we delete isMounted and findDOMNode. + workInProgress.effectTag |= Placement; + + // Ensure that children mount into this root without tracking + // side-effects. This ensures that we don't store Placement effects on + // nodes that will be hydrated. + workInProgress.child = mountChildFibersInPlace( + workInProgress, + workInProgress.child, + element, + priorityLevel, + ); + markChildAsProgressed(current, workInProgress, priorityLevel); + return workInProgress.child; + } + } + // Otherwise reset hydration state in case we aborted and resumed another + // root. + resetHydrationState(); reconcileChildren(current, workInProgress, element); memoizeState(workInProgress, state); return workInProgress.child; } + resetHydrationState(); // If there is no update queue, that's a bailout because the root has no props. return bailoutOnAlreadyFinishedWork(current, workInProgress); } @@ -363,6 +401,10 @@ module.exports = function( function updateHostComponent(current, workInProgress) { pushHostContext(workInProgress); + if (current === null) { + tryToClaimNextHydratableInstance(workInProgress); + } + let nextProps = workInProgress.pendingProps; const prevProps = current !== null ? current.memoizedProps : null; const memoizedProps = workInProgress.memoizedProps; @@ -471,6 +513,9 @@ module.exports = function( } function updateHostText(current, workInProgress) { + if (current === null) { + tryToClaimNextHydratableInstance(workInProgress); + } let nextProps = workInProgress.pendingProps; if (nextProps === null) { nextProps = workInProgress.memoizedProps; diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index c6e0082ed9c..092c825e880 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -15,6 +15,7 @@ import type {ReactCoroutine} from 'ReactTypes'; import type {Fiber} from 'ReactFiber'; import type {HostContext} from 'ReactFiberHostContext'; +import type {HydrationContext} from 'ReactFiberHydrationContext'; import type {FiberRoot} from 'ReactFiberRoot'; import type {HostConfig} from 'ReactFiberReconciler'; @@ -35,7 +36,7 @@ var { YieldComponent, Fragment, } = ReactTypeOfWork; -var {Ref, Update} = ReactTypeOfSideEffect; +var {Placement, Ref, Update} = ReactTypeOfSideEffect; if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); @@ -46,6 +47,7 @@ var invariant = require('fbjs/lib/invariant'); module.exports = function( config: HostConfig, hostContext: HostContext, + hydrationContext: HydrationContext, ) { const { createInstance, @@ -62,6 +64,12 @@ module.exports = function( popHostContainer, } = hostContext; + const { + hydrateHostInstance, + hydrateHostTextInstance, + popHydrationState, + } = hydrationContext; + function markChildAsProgressed(current, workInProgress, priorityLevel) { // We now have clones. Let's store them as the currently progressed work. workInProgress.progressedChild = workInProgress.child; @@ -206,6 +214,15 @@ module.exports = function( fiberRoot.context = fiberRoot.pendingContext; fiberRoot.pendingContext = null; } + + if (current === null || current.child === null) { + // If we hydrated, pop so that we can delete any remaining children + // that weren't hydrated. + popHydrationState(workInProgress); + // This resets the hacky state to fix isMounted before committing. + // TODO: Delete this when we delete isMounted and findDOMNode. + workInProgress.effectTag &= ~Placement; + } return null; } case HostComponent: { @@ -258,28 +275,37 @@ module.exports = function( // "stack" as the parent. Then append children as we go in beginWork // or completeWork depending on we want to add then top->down or // bottom->up. Top->down is faster in IE11. - const instance = createInstance( - type, - newProps, - rootContainerInstance, - currentHostContext, - workInProgress, - ); - - appendAllChildren(instance, workInProgress); - - // Certain renderers require commit-time effects for initial mount. - // (eg DOM renderer supports auto-focus for certain elements). - // Make sure such renderers get scheduled for later work. - if ( - finalizeInitialChildren( - instance, + let instance; + let wasHydrated = popHydrationState(workInProgress); + if (wasHydrated) { + instance = hydrateHostInstance( + workInProgress, + rootContainerInstance, + ); + } else { + instance = createInstance( type, newProps, rootContainerInstance, - ) - ) { - markUpdate(workInProgress); + currentHostContext, + workInProgress, + ); + + appendAllChildren(instance, workInProgress); + + // Certain renderers require commit-time effects for initial mount. + // (eg DOM renderer supports auto-focus for certain elements). + // Make sure such renderers get scheduled for later work. + if ( + finalizeInitialChildren( + instance, + type, + newProps, + rootContainerInstance, + ) + ) { + markUpdate(workInProgress); + } } workInProgress.stateNode = instance; @@ -311,12 +337,21 @@ module.exports = function( } const rootContainerInstance = getRootHostContainer(); const currentHostContext = getHostContext(); - const textInstance = createTextInstance( - newText, - rootContainerInstance, - currentHostContext, - workInProgress, - ); + let textInstance; + let wasHydrated = popHydrationState(workInProgress); + if (wasHydrated) { + textInstance = hydrateHostTextInstance( + workInProgress, + rootContainerInstance, + ); + } else { + textInstance = createTextInstance( + newText, + rootContainerInstance, + currentHostContext, + workInProgress, + ); + } workInProgress.stateNode = textInstance; } return null; diff --git a/src/renderers/shared/fiber/ReactFiberHydrationContext.js b/src/renderers/shared/fiber/ReactFiberHydrationContext.js new file mode 100644 index 00000000000..2237e368a06 --- /dev/null +++ b/src/renderers/shared/fiber/ReactFiberHydrationContext.js @@ -0,0 +1,234 @@ +/** + * 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. + * + * @providesModule ReactFiberHydrationContext + * @flow + */ + +'use strict'; + +import type {Fiber} from 'ReactFiber'; +import type {HostConfig} from 'ReactFiberReconciler'; + +var invariant = require('fbjs/lib/invariant'); + +const {HostComponent, HostRoot} = require('ReactTypeOfWork'); +const {Deletion, Placement} = require('ReactTypeOfSideEffect'); + +const {createFiberFromHostInstanceForDeletion} = require('ReactFiber'); + +export type HydrationContext = { + enterHydrationState(fiber: Fiber): boolean, + resetHydrationState(): void, + tryToClaimNextHydratableInstance(fiber: Fiber): void, + hydrateHostInstance(fiber: Fiber): I, + hydrateHostTextInstance(fiber: Fiber): TI, + popHydrationState(fiber: Fiber): boolean, +}; + +module.exports = function( + config: HostConfig, +): HydrationContext { + const { + shouldSetTextContent, + canHydrateInstance, + canHydrateTextInstance, + getNextHydratableSibling, + getFirstHydratableChild, + hydrateInstance, + hydrateTextInstance, + } = config; + + // If this doesn't have hydration mode. + if ( + !(canHydrateInstance && + canHydrateTextInstance && + getNextHydratableSibling && + getFirstHydratableChild && + hydrateInstance && + hydrateTextInstance) + ) { + return { + enterHydrationState() { + return false; + }, + resetHydrationState() {}, + tryToClaimNextHydratableInstance() {}, + hydrateHostInstance() { + invariant(false, 'React bug.'); + }, + hydrateHostTextInstance() { + invariant(false, 'React bug.'); + }, + popHydrationState(fiber: Fiber) { + return false; + }, + }; + } + + // The deepest Fiber on the stack involved in a hydration context. + // This may have been an insertion or a hydration. + let hydrationParentFiber: null | Fiber = null; + let nextHydratableInstance: null | I | TI = null; + let isHydrating: boolean = false; + + function enterHydrationState(fiber: Fiber) { + const parentInstance = fiber.stateNode.containerInfo; + nextHydratableInstance = getFirstHydratableChild(parentInstance); + hydrationParentFiber = fiber; + isHydrating = true; + return true; + } + + function deleteHydratableInstance(returnFiber: Fiber, instance: I | TI) { + const childToDelete = createFiberFromHostInstanceForDeletion(); + childToDelete.stateNode = instance; + childToDelete.return = returnFiber; + // Deletions are added in reversed order so we add it to the front. + const last = returnFiber.progressedLastDeletion; + if (last !== null) { + last.nextEffect = childToDelete; + returnFiber.progressedLastDeletion = childToDelete; + } else { + returnFiber.progressedFirstDeletion = returnFiber.progressedLastDeletion = childToDelete; + } + childToDelete.effectTag = Deletion; + + if (returnFiber.lastEffect !== null) { + returnFiber.lastEffect.nextEffect = childToDelete; + returnFiber.lastEffect = childToDelete; + } else { + returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; + } + } + + function tryToClaimNextHydratableInstance(fiber: Fiber) { + if (!isHydrating) { + return; + } + let nextInstance = nextHydratableInstance; + if (!nextInstance) { + // Nothing to hydrate. Make it an insertion. + fiber.effectTag |= Placement; + isHydrating = false; + hydrationParentFiber = fiber; + return; + } + const type = fiber.type; + const props = fiber.memoizedProps; + if (!canHydrateInstance(nextInstance, type, props)) { + // If we can't hydrate this instance let's try the next one. + // We use this as a heuristic. It's based on intuition and not data so it + // might be flawed or unnecessary. + nextInstance = getNextHydratableSibling(nextInstance); + if (!nextInstance || !canHydrateInstance(nextInstance, type, props)) { + // Nothing to hydrate. Make it an insertion. + fiber.effectTag |= Placement; + isHydrating = false; + hydrationParentFiber = fiber; + return; + } + // We matched the next one, we'll now assume that the first one was + // superfluous and we'll delete it. Since we can't eagerly delete it + // we'll have to schedule a deletion. To do that, this node needs a dummy + // fiber associated with it. + deleteHydratableInstance( + (hydrationParentFiber: any), + nextHydratableInstance, + ); + } + fiber.stateNode = nextInstance; + hydrationParentFiber = fiber; + nextHydratableInstance = getFirstHydratableChild(nextInstance); + } + + function hydrateHostInstance(fiber: Fiber, rootContainerInstance: any): I { + const instance: I = fiber.stateNode; + hydrateInstance( + instance, + fiber.type, + fiber.memoizedProps, + rootContainerInstance, + fiber, + ); + return instance; + } + + function hydrateHostTextInstance(fiber: Fiber): TI { + const textInstance: TI = fiber.stateNode; + hydrateTextInstance(textInstance, fiber); + return textInstance; + } + + function popToNextHostParent(fiber: Fiber): void { + let parent = fiber.return; + while ( + parent !== null && + parent.tag !== HostComponent && + parent.tag !== HostRoot + ) { + parent = parent.return; + } + hydrationParentFiber = parent; + } + + function popHydrationState(fiber: Fiber): boolean { + if (fiber !== hydrationParentFiber) { + // We're deeper than the current hydration context, inside an inserted + // tree. + return false; + } + if (!isHydrating) { + // If we're not currently hydrating but we're in a hydration context, then + // we were an insertion and now need to pop up reenter hydration of our + // siblings. + popToNextHostParent(fiber); + isHydrating = true; + return false; + } + + // If we have any remaining hydratable nodes, we need to delete them now. + // We only do this deeper than head and body since they tend to have random + // other nodes in them. We also ignore components with pure text content in + // side of them. + // TODO: Better heuristic. + if ( + fiber.tag !== HostComponent || + (fiber.type !== 'head' && + fiber.type !== 'body' && + !shouldSetTextContent(fiber.memoizedProps)) + ) { + let nextInstance = nextHydratableInstance; + while (nextInstance) { + deleteHydratableInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); + } + } + + popToNextHostParent(fiber); + nextHydratableInstance = hydrationParentFiber + ? getNextHydratableSibling(fiber.stateNode) + : null; + return true; + } + + function resetHydrationState() { + hydrationParentFiber = null; + nextHydratableInstance = null; + isHydrating = false; + } + + return { + enterHydrationState, + resetHydrationState, + tryToClaimNextHydratableInstance, + hydrateHostInstance, + hydrateHostTextInstance, + popHydrationState, + }; +}; diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index e1d2026202c..14426385a01 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -114,6 +114,23 @@ export type HostConfig = { prepareForCommit(): void, resetAfterCommit(): void, + // Optional hydration + canHydrateInstance?: (instance: I | TI, type: T, props: P) => boolean, + canHydrateTextInstance?: (instance: I | TI) => boolean, + getNextHydratableSibling?: (instance: I | TI) => null | I | TI, + getFirstHydratableChild?: (parentInstance: C | I) => null | I | TI, + hydrateInstance?: ( + instance: I, + type: T, + props: P, + rootContainerInstance: C, + internalInstanceHandle: OpaqueHandle, + ) => void, + hydrateTextInstance?: ( + textInstance: TI, + internalInstanceHandle: OpaqueHandle, + ) => void, + useSyncScheduling?: boolean, }; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 844e1957814..71e9aca36ac 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -16,6 +16,7 @@ import type {Fiber} from 'ReactFiber'; import type {FiberRoot} from 'ReactFiberRoot'; import type {HostConfig, Deadline} from 'ReactFiberReconciler'; import type {PriorityLevel} from 'ReactPriorityLevel'; +import type {HydrationContext} from 'ReactFiberHydrationContext'; export type CapturedError = { componentName: ?string, @@ -43,6 +44,7 @@ var ReactFiberBeginWork = require('ReactFiberBeginWork'); var ReactFiberCompleteWork = require('ReactFiberCompleteWork'); var ReactFiberCommitWork = require('ReactFiberCommitWork'); var ReactFiberHostContext = require('ReactFiberHostContext'); +var ReactFiberHydrationContext = require('ReactFiberHydrationContext'); var ReactFeatureFlags = require('ReactFeatureFlags'); var {ReactCurrentOwner} = require('ReactGlobalSharedState'); var getComponentName = require('getComponentName'); @@ -145,14 +147,22 @@ module.exports = function( config: HostConfig, ) { const hostContext = ReactFiberHostContext(config); + const hydrationContext: HydrationContext = ReactFiberHydrationContext( + config, + ); const {popHostContainer, popHostContext, resetHostContainer} = hostContext; const {beginWork, beginFailedWork} = ReactFiberBeginWork( config, hostContext, + hydrationContext, scheduleUpdate, getPriorityContext, ); - const {completeWork} = ReactFiberCompleteWork(config, hostContext); + const {completeWork} = ReactFiberCompleteWork( + config, + hostContext, + hydrationContext, + ); const { commitPlacement, commitDeletion, diff --git a/src/test/getTestDocument.js b/src/test/getTestDocument.js index bd0c0e9cba7..ca946c7019c 100644 --- a/src/test/getTestDocument.js +++ b/src/test/getTestDocument.js @@ -12,13 +12,14 @@ 'use strict'; function getTestDocument(markup) { - document.open(); - document.write( + var doc = document.implementation.createHTMLDocument(''); + doc.open(); + doc.write( markup || 'test doc', ); - document.close(); - return document; + doc.close(); + return doc; } module.exports = getTestDocument;