diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 11c290d1481c..f3441a3557e3 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -989,9 +989,23 @@ function clearContainerSparingly(container: Node) { detachDeletedInstance(element); continue; } + // Script tags are retained to avoid an edge case bug. Normally scripts will execute if they + // are ever inserted into the DOM. However when streaming if a script tag is opened but not + // yet closed some browsers create and insert the script DOM Node but the script cannot execute + // yet until the closing tag is parsed. If something causes React to call clearContainer while + // this DOM node is in the document but not yet executable the DOM node will be removed from the + // document and when the script closing tag comes in the script will not end up running. This seems + // to happen in Chrome/Firefox but not Safari at the moment though this is not necessarily specified + // behavior so it could change in future versions of browsers. While leaving all scripts is broader + // than strictly necessary this is the least amount of additional code to avoid this breaking + // edge case. + // + // Style tags are retained because they may likely come from 3rd party scripts and extensions + case 'SCRIPT': case 'STYLE': { continue; } + // Stylesheet tags are retained because tehy may likely come from 3rd party scripts and extensions case 'LINK': { if (((node: any): HTMLLinkElement).rel.toLowerCase() === 'stylesheet') { continue; @@ -1939,6 +1953,7 @@ export function clearSingleton(instance: Instance): void { isMarkedHoistable(node) || nodeName === 'HEAD' || nodeName === 'BODY' || + nodeName === 'SCRIPT' || nodeName === 'STYLE' || (nodeName === 'LINK' && ((node: any): HTMLLinkElement).rel.toLowerCase() === 'stylesheet') diff --git a/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js b/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js index f8056fd6042e..4d34f2edb7d5 100644 --- a/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js @@ -87,12 +87,14 @@ describe('ReactDOM HostSingleton', () => { let node = element.firstChild; while (node) { if (node.nodeType === 1) { + const el: Element = (node: any); if ( - node.tagName !== 'SCRIPT' && - node.tagName !== 'TEMPLATE' && - node.tagName !== 'template' && - !node.hasAttribute('hidden') && - !node.hasAttribute('aria-hidden') + (el.tagName !== 'SCRIPT' && + el.tagName !== 'TEMPLATE' && + el.tagName !== 'template' && + !el.hasAttribute('hidden') && + !el.hasAttribute('aria-hidden')) || + el.hasAttribute('data-meaningful') ) { const props = {}; const attributes = node.attributes; @@ -742,11 +744,13 @@ describe('ReactDOM HostSingleton', () => {