/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/'use strict';
async function insertNodesAndExecuteScripts(
source: Document | Element,target: Node,CSPnonce: string | null,) {const ownerDocument = target.ownerDocument || target;
// We need to remove the script content for any scripts that would not run based on CSP
// We restore the script content after moving the nodes into the target
const badNonceScriptNodes: Map<Element, string> = new Map();
if (CSPnonce) {
const scripts = source.querySelectorAll('script');
for (let i = 0; i < scripts.length; i++) {
const script = scripts[i];
if (
!script.hasAttribute('src') &&
script.getAttribute('nonce') !== CSPnonce
) {badNonceScriptNodes.set(script, script.textContent);
script.textContent = '';
}}}let lastChild = null;
while (source.firstChild) {
const node = source.firstChild;
if (lastChild === node) {
throw new Error('Infinite loop.');
}lastChild = node;
if (node.nodeType === 1) {
const element: Element = (node: any);
if (
// $FlowFixMe[prop-missing]
element.dataset != null &&
(element.dataset.rxi != null ||
element.dataset.rri != null ||
element.dataset.rci != null ||
element.dataset.rsi != null)
) {// Fizz external runtime instructions are expected to be in the body.
// When we have renderIntoContainer and renderDocument this will be
// more enforceable. At the moment you can misconfigure your stream and end up
// with instructions that are deep in the document
(ownerDocument.body: any).appendChild(element);
} else {
target.appendChild(element);
if (element.nodeName === 'SCRIPT') {
await executeScript(element);
} else {
const scripts = element.querySelectorAll('script');
for (let i = 0; i < scripts.length; i++) {
const script = scripts[i];
await executeScript(script);
}}}} else {
target.appendChild(node);
}}// restore the textContent now that we have finished attempting to execute scripts
badNonceScriptNodes.forEach((scriptContent, script) => {
script.textContent = scriptContent;
});}async function executeScript(script: Element) {
const ownerDocument = script.ownerDocument;
if (script.parentNode == null) {
throw new Error(
'executeScript expects to be called on script nodes that are currently in a document',
);}const parent = script.parentNode;
const scriptSrc = script.getAttribute('src');
if (scriptSrc) {
if (document !== ownerDocument) {
throw new Error(
'You must set the current document to the global document to use script src in tests',
);}try {
// $FlowFixMe
require(scriptSrc);
} catch (x) {
const event = new window.ErrorEvent('error', {error: x});
window.dispatchEvent(event);
}} else {
const newScript = ownerDocument.createElement('script');
newScript.textContent = script.textContent;
// make sure to add nonce back to script if it exists
for (let i = 0; i < script.attributes.length; i++) {
const attribute = script.attributes[i];
newScript.setAttribute(attribute.name, attribute.value);
}parent.insertBefore(newScript, script);
parent.removeChild(script);
}}function mergeOptions(options: Object, defaultOptions: Object): Object {
return {
...defaultOptions,
...options,
};}function stripExternalRuntimeInNodes(
nodes: HTMLElement[] | HTMLCollection<HTMLElement>,externalRuntimeSrc: string | null,): HTMLElement[] {
if (!Array.isArray(nodes)) {
nodes = Array.from(nodes);
}if (externalRuntimeSrc == null) {
return nodes;
}return nodes.filter(
n =>
(n.tagName !== 'SCRIPT' && n.tagName !== 'script') ||
n.getAttribute('src') !== externalRuntimeSrc,
);}// Since JSDOM doesn't implement a streaming HTML parser, we manually overwrite// readyState here (currently read by ReactDOMServerExternalRuntime). This does// not trigger event callbacks, but we do not rely on any right now.async function withLoadingReadyState<T>(
fn: () => T,
document: Document,
): Promise<T> {
// JSDOM implements readyState in document's direct prototype, but this may
// change in later versions
let prevDescriptor = null;let proto: Object = document;
while (proto != null) {
prevDescriptor = Object.getOwnPropertyDescriptor(proto, 'readyState');
if (prevDescriptor != null) {
break;
}proto = Object.getPrototypeOf(proto);
}Object.defineProperty(document, 'readyState', {
get() {return 'loading';},configurable: true,});const result = await fn();
// $FlowFixMe[incompatible-type]
delete document.readyState;if (prevDescriptor) {
Object.defineProperty(proto, 'readyState', prevDescriptor);
}return result;}function getVisibleChildren(element: Element): React$Node {
const children = [];let node: any = element.firstChild;
while (node) {
if (node.nodeType === 1) {
if (
((node.tagName !== 'SCRIPT' && node.tagName !== 'script') ||
node.hasAttribute('data-meaningful')) &&
node.tagName !== 'TEMPLATE' &&
node.tagName !== 'template' &&
!node.hasAttribute('hidden') &&
!node.hasAttribute('aria-hidden')
) {const props: any = {};
const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) {
if (
attributes[i].name === 'id' &&
attributes[i].value.includes(':')
) {// We assume this is a React added ID that's a non-visual implementation detail.
continue;
}props[attributes[i].name] = attributes[i].value;
}props.children = getVisibleChildren(node);
children.push(
require('react').createElement(node.tagName.toLowerCase(), props),
);}} else if (node.nodeType === 3) {
children.push(node.data);
}node = node.nextSibling;
}return children.length === 0
? undefined
: children.length === 1
? children[0]
: children;
}export {
insertNodesAndExecuteScripts,
mergeOptions,
stripExternalRuntimeInNodes,
withLoadingReadyState,
getVisibleChildren,
};