From d7b570e9554320361f8e2f575a5f85e48241fcb2 Mon Sep 17 00:00:00 2001 From: sebmarkbage Date: Tue, 11 Apr 2023 01:48:08 +0000 Subject: [PATCH] Move validation of text nesting into ReactDOMComponent (#26594) Extract validateTextNesting from validateDOMNesting. We only need the parent tag when validating text nodes. Then validate it in setProp. DiffTrain build for [ac43bf6870a15566507477a4504f22160835c8d3](https://github.com/facebook/react/commit/ac43bf6870a15566507477a4504f22160835c8d3) --- compiled/facebook-www/REVISION | 2 +- compiled/facebook-www/ReactART-prod.modern.js | 4 +- compiled/facebook-www/ReactDOM-dev.classic.js | 1027 ++++++++--------- compiled/facebook-www/ReactDOM-dev.modern.js | 1027 ++++++++--------- .../ReactDOMTesting-dev.classic.js | 1027 ++++++++--------- .../ReactDOMTesting-dev.modern.js | 1027 ++++++++--------- compiled/facebook-www/WARNINGS | 6 +- 7 files changed, 2010 insertions(+), 2110 deletions(-) diff --git a/compiled/facebook-www/REVISION b/compiled/facebook-www/REVISION index 0d19580f82bb8..33a28b915b424 100644 --- a/compiled/facebook-www/REVISION +++ b/compiled/facebook-www/REVISION @@ -1 +1 @@ -ca41adb8c1b256705f73d1fb657421a03dfad82c +ac43bf6870a15566507477a4504f22160835c8d3 diff --git a/compiled/facebook-www/ReactART-prod.modern.js b/compiled/facebook-www/ReactART-prod.modern.js index e0098cb8bb68f..09621cdea5062 100644 --- a/compiled/facebook-www/ReactART-prod.modern.js +++ b/compiled/facebook-www/ReactART-prod.modern.js @@ -9743,7 +9743,7 @@ var slice = Array.prototype.slice, return null; }, bundleType: 0, - version: "18.3.0-www-modern-f0a7ed1e", + version: "18.3.0-www-modern-910e3a9f", rendererPackageName: "react-art" }; var internals$jscomp$inline_1324 = { @@ -9774,7 +9774,7 @@ var internals$jscomp$inline_1324 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "18.3.0-www-modern-f0a7ed1e" + reconcilerVersion: "18.3.0-www-modern-910e3a9f" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_1325 = __REACT_DEVTOOLS_GLOBAL_HOOK__; diff --git a/compiled/facebook-www/ReactDOM-dev.classic.js b/compiled/facebook-www/ReactDOM-dev.classic.js index 7d9245d5518dd..3651919fa1f06 100644 --- a/compiled/facebook-www/ReactDOM-dev.classic.js +++ b/compiled/facebook-www/ReactDOM-dev.classic.js @@ -4444,6 +4444,489 @@ function restoreControlledTextareaState(element, props) { updateTextarea(element, props); } +// This validation code was written based on the HTML5 parsing spec: +// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope +// +// Note: this does not catch all invalid nesting, nor does it try to (as it's +// not clear what practical benefit doing so provides); instead, we warn only +// for cases where the parser will give a parse tree differing from what React +// intended. For example,
is invalid but we don't warn +// because it still parses correctly; we do warn for other cases like nested +//

tags where the beginning of the second element implicitly closes the +// first, causing a confusing mess. +// https://html.spec.whatwg.org/multipage/syntax.html#special +var specialTags = [ + "address", + "applet", + "area", + "article", + "aside", + "base", + "basefont", + "bgsound", + "blockquote", + "body", + "br", + "button", + "caption", + "center", + "col", + "colgroup", + "dd", + "details", + "dir", + "div", + "dl", + "dt", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "iframe", + "img", + "input", + "isindex", + "li", + "link", + "listing", + "main", + "marquee", + "menu", + "menuitem", + "meta", + "nav", + "noembed", + "noframes", + "noscript", + "object", + "ol", + "p", + "param", + "plaintext", + "pre", + "script", + "section", + "select", + "source", + "style", + "summary", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "title", + "tr", + "track", + "ul", + "wbr", + "xmp" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope + +var inScopeTags = [ + "applet", + "caption", + "html", + "table", + "td", + "th", + "marquee", + "object", + "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point + // TODO: Distinguish by namespace here -- for , including it here + // errs on the side of fewer warnings + "foreignObject", + "desc", + "title" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope + +var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags + +var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; +var emptyAncestorInfoDev = { + current: null, + formTag: null, + aTagInScope: null, + buttonTagInScope: null, + nobrTagInScope: null, + pTagInButtonScope: null, + listItemTagAutoclosing: null, + dlItemTagAutoclosing: null, + containerTagInScope: null +}; + +function updatedAncestorInfoDev(oldInfo, tag) { + { + var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); + + var info = { + tag: tag + }; + + if (inScopeTags.indexOf(tag) !== -1) { + ancestorInfo.aTagInScope = null; + ancestorInfo.buttonTagInScope = null; + ancestorInfo.nobrTagInScope = null; + } + + if (buttonScopeTags.indexOf(tag) !== -1) { + ancestorInfo.pTagInButtonScope = null; + } // See rules for 'li', 'dd', 'dt' start tags in + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + if ( + specialTags.indexOf(tag) !== -1 && + tag !== "address" && + tag !== "div" && + tag !== "p" + ) { + ancestorInfo.listItemTagAutoclosing = null; + ancestorInfo.dlItemTagAutoclosing = null; + } + + ancestorInfo.current = info; + + if (tag === "form") { + ancestorInfo.formTag = info; + } + + if (tag === "a") { + ancestorInfo.aTagInScope = info; + } + + if (tag === "button") { + ancestorInfo.buttonTagInScope = info; + } + + if (tag === "nobr") { + ancestorInfo.nobrTagInScope = info; + } + + if (tag === "p") { + ancestorInfo.pTagInButtonScope = info; + } + + if (tag === "li") { + ancestorInfo.listItemTagAutoclosing = info; + } + + if (tag === "dd" || tag === "dt") { + ancestorInfo.dlItemTagAutoclosing = info; + } + + if (tag === "#document" || tag === "html") { + ancestorInfo.containerTagInScope = null; + } else if (!ancestorInfo.containerTagInScope) { + ancestorInfo.containerTagInScope = info; + } + + return ancestorInfo; + } +} +/** + * Returns whether + */ + +function isTagValidWithParent(tag, parentTag) { + // First, let's check if we're in an unusual parsing mode... + switch (parentTag) { + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect + case "select": + return tag === "option" || tag === "optgroup" || tag === "#text"; + + case "optgroup": + return tag === "option" || tag === "#text"; + // Strictly speaking, seeing an <option> doesn't mean we're in a <select> + // but + + case "option": + return tag === "#text"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption + // No special behavior since these rules fall back to "in body" mode for + // all except special table nodes which cause bad parsing behavior anyway. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr + + case "tr": + return ( + tag === "th" || + tag === "td" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody + + case "tbody": + case "thead": + case "tfoot": + return ( + tag === "tr" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup + + case "colgroup": + return tag === "col" || tag === "template"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable + + case "table": + return ( + tag === "caption" || + tag === "colgroup" || + tag === "tbody" || + tag === "tfoot" || + tag === "thead" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead + + case "head": + return ( + tag === "base" || + tag === "basefont" || + tag === "bgsound" || + tag === "link" || + tag === "meta" || + tag === "title" || + tag === "noscript" || + tag === "noframes" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element + + case "html": + return tag === "head" || tag === "body" || tag === "frameset"; + + case "frameset": + return tag === "frame"; + + case "#document": + return tag === "html"; + } // Probably in the "in body" parsing mode, so we outlaw only tag combos + // where the parsing rules cause implicit opens or closes to be added. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + switch (tag) { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ( + parentTag !== "h1" && + parentTag !== "h2" && + parentTag !== "h3" && + parentTag !== "h4" && + parentTag !== "h5" && + parentTag !== "h6" + ); + + case "rp": + case "rt": + return impliedEndTags.indexOf(parentTag) === -1; + + case "body": + case "caption": + case "col": + case "colgroup": + case "frameset": + case "frame": + case "head": + case "html": + case "tbody": + case "td": + case "tfoot": + case "th": + case "thead": + case "tr": + // These tags are only valid with a few parents that have special child + // parsing rules -- if we're down here, then none of those matched and + // so we allow it only if we don't know what the parent is, as all other + // cases are invalid. + return parentTag == null; + } + + return true; +} +/** + * Returns whether + */ + +function findInvalidAncestorForTag(tag, ancestorInfo) { + switch (tag) { + case "address": + case "article": + case "aside": + case "blockquote": + case "center": + case "details": + case "dialog": + case "dir": + case "div": + case "dl": + case "fieldset": + case "figcaption": + case "figure": + case "footer": + case "header": + case "hgroup": + case "main": + case "menu": + case "nav": + case "ol": + case "p": + case "section": + case "summary": + case "ul": + case "pre": + case "listing": + case "table": + case "hr": + case "xmp": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ancestorInfo.pTagInButtonScope; + + case "form": + return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; + + case "li": + return ancestorInfo.listItemTagAutoclosing; + + case "dd": + case "dt": + return ancestorInfo.dlItemTagAutoclosing; + + case "button": + return ancestorInfo.buttonTagInScope; + + case "a": + // Spec says something about storing a list of markers, but it sounds + // equivalent to this check. + return ancestorInfo.aTagInScope; + + case "nobr": + return ancestorInfo.nobrTagInScope; + } + + return null; +} + +var didWarn = {}; + +function validateDOMNesting(childTag, ancestorInfo) { + { + ancestorInfo = ancestorInfo || emptyAncestorInfoDev; + var parentInfo = ancestorInfo.current; + var parentTag = parentInfo && parentInfo.tag; + var invalidParent = isTagValidWithParent(childTag, parentTag) + ? null + : parentInfo; + var invalidAncestor = invalidParent + ? null + : findInvalidAncestorForTag(childTag, ancestorInfo); + var invalidParentOrAncestor = invalidParent || invalidAncestor; + + if (!invalidParentOrAncestor) { + return; + } + + var ancestorTag = invalidParentOrAncestor.tag; + var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + var tagDisplayName = "<" + childTag + ">"; + + if (invalidParent) { + var info = ""; + + if (ancestorTag === "table" && childTag === "tr") { + info += + " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + + "the browser."; + } + + error( + "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s", + tagDisplayName, + ancestorTag, + info + ); + } else { + error( + "validateDOMNesting(...): %s cannot appear as a descendant of " + + "<%s>.", + tagDisplayName, + ancestorTag + ); + } + } +} + +function validateTextNesting(childText, parentTag) { + { + if (isTagValidWithParent("#text", parentTag)) { + return; + } // eslint-disable-next-line react-internal/safe-string-coercion + + var warnKey = "#text|" + parentTag; + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + + if (/\S/.test(childText)) { + error( + "validateDOMNesting(...): Text nodes cannot appear as a child of <%s>.", + parentTag + ); + } else { + error( + "validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <%s>. " + + "Make sure you don't have any extra whitespace between tags on " + + "each line of your source code.", + parentTag + ); + } + } +} + var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; var MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML"; var SVG_NAMESPACE = "http://www.w3.org/2000/svg"; // Assumes there is no parent namespace. @@ -22011,14 +22494,8 @@ function updateHostComponent( // component is hitting the resume path. Figure out why. Possibly // related to `hidden`. - var currentHostContext = getHostContext(); - var updatePayload = prepareUpdate( - instance, - type, - oldProps, - newProps, - currentHostContext - ); // TODO: Type this specific to this type of component. + getHostContext(); + var updatePayload = prepareUpdate(instance, type, oldProps, newProps); // TODO: Type this specific to this type of component. workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there // is a new ref we mark this as an update. All the work is done in commitWork. @@ -33422,7 +33899,7 @@ function createFiberRoot( return root; } -var ReactVersion = "18.3.0-www-classic-6d02b9b0"; +var ReactVersion = "18.3.0-www-classic-f8663d04"; function createPortal$1( children, @@ -37901,10 +38378,13 @@ function setProp(domElement, tag, key, value, props, prevValue) { switch (key) { case "children": { if (typeof value === "string") { - // Avoid setting initial textContent when the text is empty. In IE11 setting + { + validateTextNesting(value, tag); + } // Avoid setting initial textContent when the text is empty. In IE11 setting // textContent on a <textarea> will cause the placeholder to not // show within the <textarea> until it has been focused and blurred again. // https://github.com/facebook/react/issues/6731#issuecomment-254874553 + var canSetTextContent = tag !== "body" && (tag !== "textarea" || value !== ""); @@ -37912,6 +38392,10 @@ function setProp(domElement, tag, key, value, props, prevValue) { setTextContent(domElement, value); } } else if (typeof value === "number") { + { + validateTextNesting("" + value, tag); + } + var _canSetTextContent = tag !== "body"; if (_canSetTextContent) { @@ -40946,489 +41430,6 @@ function restoreControlledState(domElement, tag, props) { } } -// This validation code was written based on the HTML5 parsing spec: -// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope -// -// Note: this does not catch all invalid nesting, nor does it try to (as it's -// not clear what practical benefit doing so provides); instead, we warn only -// for cases where the parser will give a parse tree differing from what React -// intended. For example, <b><div></div></b> is invalid but we don't warn -// because it still parses correctly; we do warn for other cases like nested -// <p> tags where the beginning of the second element implicitly closes the -// first, causing a confusing mess. -// https://html.spec.whatwg.org/multipage/syntax.html#special -var specialTags = [ - "address", - "applet", - "area", - "article", - "aside", - "base", - "basefont", - "bgsound", - "blockquote", - "body", - "br", - "button", - "caption", - "center", - "col", - "colgroup", - "dd", - "details", - "dir", - "div", - "dl", - "dt", - "embed", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "frame", - "frameset", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "head", - "header", - "hgroup", - "hr", - "html", - "iframe", - "img", - "input", - "isindex", - "li", - "link", - "listing", - "main", - "marquee", - "menu", - "menuitem", - "meta", - "nav", - "noembed", - "noframes", - "noscript", - "object", - "ol", - "p", - "param", - "plaintext", - "pre", - "script", - "section", - "select", - "source", - "style", - "summary", - "table", - "tbody", - "td", - "template", - "textarea", - "tfoot", - "th", - "thead", - "title", - "tr", - "track", - "ul", - "wbr", - "xmp" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope - -var inScopeTags = [ - "applet", - "caption", - "html", - "table", - "td", - "th", - "marquee", - "object", - "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point - // TODO: Distinguish by namespace here -- for <title>, including it here - // errs on the side of fewer warnings - "foreignObject", - "desc", - "title" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope - -var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags - -var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; -var emptyAncestorInfoDev = { - current: null, - formTag: null, - aTagInScope: null, - buttonTagInScope: null, - nobrTagInScope: null, - pTagInButtonScope: null, - listItemTagAutoclosing: null, - dlItemTagAutoclosing: null, - containerTagInScope: null -}; - -function updatedAncestorInfoDev(oldInfo, tag) { - { - var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); - - var info = { - tag: tag - }; - - if (inScopeTags.indexOf(tag) !== -1) { - ancestorInfo.aTagInScope = null; - ancestorInfo.buttonTagInScope = null; - ancestorInfo.nobrTagInScope = null; - } - - if (buttonScopeTags.indexOf(tag) !== -1) { - ancestorInfo.pTagInButtonScope = null; - } // See rules for 'li', 'dd', 'dt' start tags in - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - if ( - specialTags.indexOf(tag) !== -1 && - tag !== "address" && - tag !== "div" && - tag !== "p" - ) { - ancestorInfo.listItemTagAutoclosing = null; - ancestorInfo.dlItemTagAutoclosing = null; - } - - ancestorInfo.current = info; - - if (tag === "form") { - ancestorInfo.formTag = info; - } - - if (tag === "a") { - ancestorInfo.aTagInScope = info; - } - - if (tag === "button") { - ancestorInfo.buttonTagInScope = info; - } - - if (tag === "nobr") { - ancestorInfo.nobrTagInScope = info; - } - - if (tag === "p") { - ancestorInfo.pTagInButtonScope = info; - } - - if (tag === "li") { - ancestorInfo.listItemTagAutoclosing = info; - } - - if (tag === "dd" || tag === "dt") { - ancestorInfo.dlItemTagAutoclosing = info; - } - - if (tag === "#document" || tag === "html") { - ancestorInfo.containerTagInScope = null; - } else if (!ancestorInfo.containerTagInScope) { - ancestorInfo.containerTagInScope = info; - } - - return ancestorInfo; - } -} -/** - * Returns whether - */ - -function isTagValidWithParent(tag, parentTag) { - // First, let's check if we're in an unusual parsing mode... - switch (parentTag) { - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect - case "select": - return tag === "option" || tag === "optgroup" || tag === "#text"; - - case "optgroup": - return tag === "option" || tag === "#text"; - // Strictly speaking, seeing an <option> doesn't mean we're in a <select> - // but - - case "option": - return tag === "#text"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption - // No special behavior since these rules fall back to "in body" mode for - // all except special table nodes which cause bad parsing behavior anyway. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr - - case "tr": - return ( - tag === "th" || - tag === "td" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody - - case "tbody": - case "thead": - case "tfoot": - return ( - tag === "tr" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup - - case "colgroup": - return tag === "col" || tag === "template"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable - - case "table": - return ( - tag === "caption" || - tag === "colgroup" || - tag === "tbody" || - tag === "tfoot" || - tag === "thead" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead - - case "head": - return ( - tag === "base" || - tag === "basefont" || - tag === "bgsound" || - tag === "link" || - tag === "meta" || - tag === "title" || - tag === "noscript" || - tag === "noframes" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element - - case "html": - return tag === "head" || tag === "body" || tag === "frameset"; - - case "frameset": - return tag === "frame"; - - case "#document": - return tag === "html"; - } // Probably in the "in body" parsing mode, so we outlaw only tag combos - // where the parsing rules cause implicit opens or closes to be added. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - switch (tag) { - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ( - parentTag !== "h1" && - parentTag !== "h2" && - parentTag !== "h3" && - parentTag !== "h4" && - parentTag !== "h5" && - parentTag !== "h6" - ); - - case "rp": - case "rt": - return impliedEndTags.indexOf(parentTag) === -1; - - case "body": - case "caption": - case "col": - case "colgroup": - case "frameset": - case "frame": - case "head": - case "html": - case "tbody": - case "td": - case "tfoot": - case "th": - case "thead": - case "tr": - // These tags are only valid with a few parents that have special child - // parsing rules -- if we're down here, then none of those matched and - // so we allow it only if we don't know what the parent is, as all other - // cases are invalid. - return parentTag == null; - } - - return true; -} -/** - * Returns whether - */ - -function findInvalidAncestorForTag(tag, ancestorInfo) { - switch (tag) { - case "address": - case "article": - case "aside": - case "blockquote": - case "center": - case "details": - case "dialog": - case "dir": - case "div": - case "dl": - case "fieldset": - case "figcaption": - case "figure": - case "footer": - case "header": - case "hgroup": - case "main": - case "menu": - case "nav": - case "ol": - case "p": - case "section": - case "summary": - case "ul": - case "pre": - case "listing": - case "table": - case "hr": - case "xmp": - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ancestorInfo.pTagInButtonScope; - - case "form": - return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; - - case "li": - return ancestorInfo.listItemTagAutoclosing; - - case "dd": - case "dt": - return ancestorInfo.dlItemTagAutoclosing; - - case "button": - return ancestorInfo.buttonTagInScope; - - case "a": - // Spec says something about storing a list of markers, but it sounds - // equivalent to this check. - return ancestorInfo.aTagInScope; - - case "nobr": - return ancestorInfo.nobrTagInScope; - } - - return null; -} - -var didWarn = {}; - -function validateDOMNesting(childTag, childText, ancestorInfo) { - { - ancestorInfo = ancestorInfo || emptyAncestorInfoDev; - var parentInfo = ancestorInfo.current; - var parentTag = parentInfo && parentInfo.tag; - - if (childText != null) { - if (childTag != null) { - error( - "validateDOMNesting: when childText is passed, childTag should be null" - ); - } - - childTag = "#text"; - } else if (childTag == null) { - error("validateDOMNesting: when childText or childTag must be provided"); - - return; - } - - var invalidParent = isTagValidWithParent(childTag, parentTag) - ? null - : parentInfo; - var invalidAncestor = invalidParent - ? null - : findInvalidAncestorForTag(childTag, ancestorInfo); - var invalidParentOrAncestor = invalidParent || invalidAncestor; - - if (!invalidParentOrAncestor) { - return; - } - - var ancestorTag = invalidParentOrAncestor.tag; - var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion - - if (didWarn[warnKey]) { - return; - } - - didWarn[warnKey] = true; - var tagDisplayName = childTag; - var whitespaceInfo = ""; - - if (childTag === "#text") { - if (childText != null && /\S/.test(childText)) { - tagDisplayName = "Text nodes"; - } else { - tagDisplayName = "Whitespace text nodes"; - whitespaceInfo = - " Make sure you don't have any extra whitespace between tags on " + - "each line of your source code."; - } - } else { - tagDisplayName = "<" + childTag + ">"; - } - - if (invalidParent) { - var info = ""; - - if (ancestorTag === "table" && childTag === "tr") { - info += - " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + - "the browser."; - } - - error( - "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s", - tagDisplayName, - ancestorTag, - whitespaceInfo, - info - ); - } else { - error( - "validateDOMNesting(...): %s cannot appear as a descendant of " + - "<%s>.", - tagDisplayName, - ancestorTag - ); - } - } -} - function validateLinkPropsForStyleResource(props) { { // This should only be called when we know we are opting into Resource semantics (i.e. precedence is not null) @@ -41756,20 +41757,7 @@ function createInstance( { // TODO: take namespace into account when validating. var hostContextDev = hostContext; - validateDOMNesting(type, null, hostContextDev.ancestorInfo); - - if ( - typeof props.children === "string" || - typeof props.children === "number" - ) { - var string = "" + props.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - + validateDOMNesting(type, hostContextDev.ancestorInfo); namespace = hostContextDev.namespace; } @@ -41921,23 +41909,6 @@ function prepareUpdate(domElement, type, oldProps, newProps, hostContext) { return null; } - { - var hostContextDev = hostContext; - - if ( - typeof newProps.children !== typeof oldProps.children && - (typeof newProps.children === "string" || - typeof newProps.children === "number") - ) { - var string = "" + newProps.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - } - return diffProperties(domElement, type, oldProps, newProps); } function shouldSetTextContent(type, props) { @@ -41959,7 +41930,11 @@ function createTextInstance( ) { { var hostContextDev = hostContext; - validateDOMNesting(null, text, hostContextDev.ancestorInfo); + var ancestor = hostContextDev.ancestorInfo.current; + + if (ancestor != null) { + validateTextNesting(text, ancestor.tag); + } } var textNode = getOwnerDocumentFromRootContainer( @@ -42826,7 +42801,7 @@ function resolveSingletonInstance( var hostContextDev = hostContext; if (validateDOMNestingDev) { - validateDOMNesting(type, null, hostContextDev.ancestorInfo); + validateDOMNesting(type, hostContextDev.ancestorInfo); } } diff --git a/compiled/facebook-www/ReactDOM-dev.modern.js b/compiled/facebook-www/ReactDOM-dev.modern.js index 46179e7089835..f6816cd73dfec 100644 --- a/compiled/facebook-www/ReactDOM-dev.modern.js +++ b/compiled/facebook-www/ReactDOM-dev.modern.js @@ -4257,6 +4257,489 @@ function restoreControlledTextareaState(element, props) { updateTextarea(element, props); } +// This validation code was written based on the HTML5 parsing spec: +// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope +// +// Note: this does not catch all invalid nesting, nor does it try to (as it's +// not clear what practical benefit doing so provides); instead, we warn only +// for cases where the parser will give a parse tree differing from what React +// intended. For example, <b><div></div></b> is invalid but we don't warn +// because it still parses correctly; we do warn for other cases like nested +// <p> tags where the beginning of the second element implicitly closes the +// first, causing a confusing mess. +// https://html.spec.whatwg.org/multipage/syntax.html#special +var specialTags = [ + "address", + "applet", + "area", + "article", + "aside", + "base", + "basefont", + "bgsound", + "blockquote", + "body", + "br", + "button", + "caption", + "center", + "col", + "colgroup", + "dd", + "details", + "dir", + "div", + "dl", + "dt", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "iframe", + "img", + "input", + "isindex", + "li", + "link", + "listing", + "main", + "marquee", + "menu", + "menuitem", + "meta", + "nav", + "noembed", + "noframes", + "noscript", + "object", + "ol", + "p", + "param", + "plaintext", + "pre", + "script", + "section", + "select", + "source", + "style", + "summary", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "title", + "tr", + "track", + "ul", + "wbr", + "xmp" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope + +var inScopeTags = [ + "applet", + "caption", + "html", + "table", + "td", + "th", + "marquee", + "object", + "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point + // TODO: Distinguish by namespace here -- for <title>, including it here + // errs on the side of fewer warnings + "foreignObject", + "desc", + "title" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope + +var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags + +var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; +var emptyAncestorInfoDev = { + current: null, + formTag: null, + aTagInScope: null, + buttonTagInScope: null, + nobrTagInScope: null, + pTagInButtonScope: null, + listItemTagAutoclosing: null, + dlItemTagAutoclosing: null, + containerTagInScope: null +}; + +function updatedAncestorInfoDev(oldInfo, tag) { + { + var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); + + var info = { + tag: tag + }; + + if (inScopeTags.indexOf(tag) !== -1) { + ancestorInfo.aTagInScope = null; + ancestorInfo.buttonTagInScope = null; + ancestorInfo.nobrTagInScope = null; + } + + if (buttonScopeTags.indexOf(tag) !== -1) { + ancestorInfo.pTagInButtonScope = null; + } // See rules for 'li', 'dd', 'dt' start tags in + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + if ( + specialTags.indexOf(tag) !== -1 && + tag !== "address" && + tag !== "div" && + tag !== "p" + ) { + ancestorInfo.listItemTagAutoclosing = null; + ancestorInfo.dlItemTagAutoclosing = null; + } + + ancestorInfo.current = info; + + if (tag === "form") { + ancestorInfo.formTag = info; + } + + if (tag === "a") { + ancestorInfo.aTagInScope = info; + } + + if (tag === "button") { + ancestorInfo.buttonTagInScope = info; + } + + if (tag === "nobr") { + ancestorInfo.nobrTagInScope = info; + } + + if (tag === "p") { + ancestorInfo.pTagInButtonScope = info; + } + + if (tag === "li") { + ancestorInfo.listItemTagAutoclosing = info; + } + + if (tag === "dd" || tag === "dt") { + ancestorInfo.dlItemTagAutoclosing = info; + } + + if (tag === "#document" || tag === "html") { + ancestorInfo.containerTagInScope = null; + } else if (!ancestorInfo.containerTagInScope) { + ancestorInfo.containerTagInScope = info; + } + + return ancestorInfo; + } +} +/** + * Returns whether + */ + +function isTagValidWithParent(tag, parentTag) { + // First, let's check if we're in an unusual parsing mode... + switch (parentTag) { + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect + case "select": + return tag === "option" || tag === "optgroup" || tag === "#text"; + + case "optgroup": + return tag === "option" || tag === "#text"; + // Strictly speaking, seeing an <option> doesn't mean we're in a <select> + // but + + case "option": + return tag === "#text"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption + // No special behavior since these rules fall back to "in body" mode for + // all except special table nodes which cause bad parsing behavior anyway. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr + + case "tr": + return ( + tag === "th" || + tag === "td" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody + + case "tbody": + case "thead": + case "tfoot": + return ( + tag === "tr" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup + + case "colgroup": + return tag === "col" || tag === "template"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable + + case "table": + return ( + tag === "caption" || + tag === "colgroup" || + tag === "tbody" || + tag === "tfoot" || + tag === "thead" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead + + case "head": + return ( + tag === "base" || + tag === "basefont" || + tag === "bgsound" || + tag === "link" || + tag === "meta" || + tag === "title" || + tag === "noscript" || + tag === "noframes" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element + + case "html": + return tag === "head" || tag === "body" || tag === "frameset"; + + case "frameset": + return tag === "frame"; + + case "#document": + return tag === "html"; + } // Probably in the "in body" parsing mode, so we outlaw only tag combos + // where the parsing rules cause implicit opens or closes to be added. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + switch (tag) { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ( + parentTag !== "h1" && + parentTag !== "h2" && + parentTag !== "h3" && + parentTag !== "h4" && + parentTag !== "h5" && + parentTag !== "h6" + ); + + case "rp": + case "rt": + return impliedEndTags.indexOf(parentTag) === -1; + + case "body": + case "caption": + case "col": + case "colgroup": + case "frameset": + case "frame": + case "head": + case "html": + case "tbody": + case "td": + case "tfoot": + case "th": + case "thead": + case "tr": + // These tags are only valid with a few parents that have special child + // parsing rules -- if we're down here, then none of those matched and + // so we allow it only if we don't know what the parent is, as all other + // cases are invalid. + return parentTag == null; + } + + return true; +} +/** + * Returns whether + */ + +function findInvalidAncestorForTag(tag, ancestorInfo) { + switch (tag) { + case "address": + case "article": + case "aside": + case "blockquote": + case "center": + case "details": + case "dialog": + case "dir": + case "div": + case "dl": + case "fieldset": + case "figcaption": + case "figure": + case "footer": + case "header": + case "hgroup": + case "main": + case "menu": + case "nav": + case "ol": + case "p": + case "section": + case "summary": + case "ul": + case "pre": + case "listing": + case "table": + case "hr": + case "xmp": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ancestorInfo.pTagInButtonScope; + + case "form": + return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; + + case "li": + return ancestorInfo.listItemTagAutoclosing; + + case "dd": + case "dt": + return ancestorInfo.dlItemTagAutoclosing; + + case "button": + return ancestorInfo.buttonTagInScope; + + case "a": + // Spec says something about storing a list of markers, but it sounds + // equivalent to this check. + return ancestorInfo.aTagInScope; + + case "nobr": + return ancestorInfo.nobrTagInScope; + } + + return null; +} + +var didWarn = {}; + +function validateDOMNesting(childTag, ancestorInfo) { + { + ancestorInfo = ancestorInfo || emptyAncestorInfoDev; + var parentInfo = ancestorInfo.current; + var parentTag = parentInfo && parentInfo.tag; + var invalidParent = isTagValidWithParent(childTag, parentTag) + ? null + : parentInfo; + var invalidAncestor = invalidParent + ? null + : findInvalidAncestorForTag(childTag, ancestorInfo); + var invalidParentOrAncestor = invalidParent || invalidAncestor; + + if (!invalidParentOrAncestor) { + return; + } + + var ancestorTag = invalidParentOrAncestor.tag; + var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + var tagDisplayName = "<" + childTag + ">"; + + if (invalidParent) { + var info = ""; + + if (ancestorTag === "table" && childTag === "tr") { + info += + " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + + "the browser."; + } + + error( + "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s", + tagDisplayName, + ancestorTag, + info + ); + } else { + error( + "validateDOMNesting(...): %s cannot appear as a descendant of " + + "<%s>.", + tagDisplayName, + ancestorTag + ); + } + } +} + +function validateTextNesting(childText, parentTag) { + { + if (isTagValidWithParent("#text", parentTag)) { + return; + } // eslint-disable-next-line react-internal/safe-string-coercion + + var warnKey = "#text|" + parentTag; + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + + if (/\S/.test(childText)) { + error( + "validateDOMNesting(...): Text nodes cannot appear as a child of <%s>.", + parentTag + ); + } else { + error( + "validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <%s>. " + + "Make sure you don't have any extra whitespace between tags on " + + "each line of your source code.", + parentTag + ); + } + } +} + var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; var MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML"; var SVG_NAMESPACE = "http://www.w3.org/2000/svg"; // Assumes there is no parent namespace. @@ -21881,14 +22364,8 @@ function updateHostComponent( // component is hitting the resume path. Figure out why. Possibly // related to `hidden`. - var currentHostContext = getHostContext(); - var updatePayload = prepareUpdate( - instance, - type, - oldProps, - newProps, - currentHostContext - ); // TODO: Type this specific to this type of component. + getHostContext(); + var updatePayload = prepareUpdate(instance, type, oldProps, newProps); // TODO: Type this specific to this type of component. workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there // is a new ref we mark this as an update. All the work is done in commitWork. @@ -33258,7 +33735,7 @@ function createFiberRoot( return root; } -var ReactVersion = "18.3.0-www-modern-f0a7ed1e"; +var ReactVersion = "18.3.0-www-modern-910e3a9f"; function createPortal$1( children, @@ -38403,10 +38880,13 @@ function setProp(domElement, tag, key, value, props, prevValue) { switch (key) { case "children": { if (typeof value === "string") { - // Avoid setting initial textContent when the text is empty. In IE11 setting + { + validateTextNesting(value, tag); + } // Avoid setting initial textContent when the text is empty. In IE11 setting // textContent on a <textarea> will cause the placeholder to not // show within the <textarea> until it has been focused and blurred again. // https://github.com/facebook/react/issues/6731#issuecomment-254874553 + var canSetTextContent = tag !== "body" && (tag !== "textarea" || value !== ""); @@ -38414,6 +38894,10 @@ function setProp(domElement, tag, key, value, props, prevValue) { setTextContent(domElement, value); } } else if (typeof value === "number") { + { + validateTextNesting("" + value, tag); + } + var _canSetTextContent = tag !== "body"; if (_canSetTextContent) { @@ -41448,489 +41932,6 @@ function restoreControlledState(domElement, tag, props) { } } -// This validation code was written based on the HTML5 parsing spec: -// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope -// -// Note: this does not catch all invalid nesting, nor does it try to (as it's -// not clear what practical benefit doing so provides); instead, we warn only -// for cases where the parser will give a parse tree differing from what React -// intended. For example, <b><div></div></b> is invalid but we don't warn -// because it still parses correctly; we do warn for other cases like nested -// <p> tags where the beginning of the second element implicitly closes the -// first, causing a confusing mess. -// https://html.spec.whatwg.org/multipage/syntax.html#special -var specialTags = [ - "address", - "applet", - "area", - "article", - "aside", - "base", - "basefont", - "bgsound", - "blockquote", - "body", - "br", - "button", - "caption", - "center", - "col", - "colgroup", - "dd", - "details", - "dir", - "div", - "dl", - "dt", - "embed", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "frame", - "frameset", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "head", - "header", - "hgroup", - "hr", - "html", - "iframe", - "img", - "input", - "isindex", - "li", - "link", - "listing", - "main", - "marquee", - "menu", - "menuitem", - "meta", - "nav", - "noembed", - "noframes", - "noscript", - "object", - "ol", - "p", - "param", - "plaintext", - "pre", - "script", - "section", - "select", - "source", - "style", - "summary", - "table", - "tbody", - "td", - "template", - "textarea", - "tfoot", - "th", - "thead", - "title", - "tr", - "track", - "ul", - "wbr", - "xmp" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope - -var inScopeTags = [ - "applet", - "caption", - "html", - "table", - "td", - "th", - "marquee", - "object", - "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point - // TODO: Distinguish by namespace here -- for <title>, including it here - // errs on the side of fewer warnings - "foreignObject", - "desc", - "title" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope - -var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags - -var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; -var emptyAncestorInfoDev = { - current: null, - formTag: null, - aTagInScope: null, - buttonTagInScope: null, - nobrTagInScope: null, - pTagInButtonScope: null, - listItemTagAutoclosing: null, - dlItemTagAutoclosing: null, - containerTagInScope: null -}; - -function updatedAncestorInfoDev(oldInfo, tag) { - { - var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); - - var info = { - tag: tag - }; - - if (inScopeTags.indexOf(tag) !== -1) { - ancestorInfo.aTagInScope = null; - ancestorInfo.buttonTagInScope = null; - ancestorInfo.nobrTagInScope = null; - } - - if (buttonScopeTags.indexOf(tag) !== -1) { - ancestorInfo.pTagInButtonScope = null; - } // See rules for 'li', 'dd', 'dt' start tags in - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - if ( - specialTags.indexOf(tag) !== -1 && - tag !== "address" && - tag !== "div" && - tag !== "p" - ) { - ancestorInfo.listItemTagAutoclosing = null; - ancestorInfo.dlItemTagAutoclosing = null; - } - - ancestorInfo.current = info; - - if (tag === "form") { - ancestorInfo.formTag = info; - } - - if (tag === "a") { - ancestorInfo.aTagInScope = info; - } - - if (tag === "button") { - ancestorInfo.buttonTagInScope = info; - } - - if (tag === "nobr") { - ancestorInfo.nobrTagInScope = info; - } - - if (tag === "p") { - ancestorInfo.pTagInButtonScope = info; - } - - if (tag === "li") { - ancestorInfo.listItemTagAutoclosing = info; - } - - if (tag === "dd" || tag === "dt") { - ancestorInfo.dlItemTagAutoclosing = info; - } - - if (tag === "#document" || tag === "html") { - ancestorInfo.containerTagInScope = null; - } else if (!ancestorInfo.containerTagInScope) { - ancestorInfo.containerTagInScope = info; - } - - return ancestorInfo; - } -} -/** - * Returns whether - */ - -function isTagValidWithParent(tag, parentTag) { - // First, let's check if we're in an unusual parsing mode... - switch (parentTag) { - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect - case "select": - return tag === "option" || tag === "optgroup" || tag === "#text"; - - case "optgroup": - return tag === "option" || tag === "#text"; - // Strictly speaking, seeing an <option> doesn't mean we're in a <select> - // but - - case "option": - return tag === "#text"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption - // No special behavior since these rules fall back to "in body" mode for - // all except special table nodes which cause bad parsing behavior anyway. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr - - case "tr": - return ( - tag === "th" || - tag === "td" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody - - case "tbody": - case "thead": - case "tfoot": - return ( - tag === "tr" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup - - case "colgroup": - return tag === "col" || tag === "template"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable - - case "table": - return ( - tag === "caption" || - tag === "colgroup" || - tag === "tbody" || - tag === "tfoot" || - tag === "thead" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead - - case "head": - return ( - tag === "base" || - tag === "basefont" || - tag === "bgsound" || - tag === "link" || - tag === "meta" || - tag === "title" || - tag === "noscript" || - tag === "noframes" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element - - case "html": - return tag === "head" || tag === "body" || tag === "frameset"; - - case "frameset": - return tag === "frame"; - - case "#document": - return tag === "html"; - } // Probably in the "in body" parsing mode, so we outlaw only tag combos - // where the parsing rules cause implicit opens or closes to be added. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - switch (tag) { - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ( - parentTag !== "h1" && - parentTag !== "h2" && - parentTag !== "h3" && - parentTag !== "h4" && - parentTag !== "h5" && - parentTag !== "h6" - ); - - case "rp": - case "rt": - return impliedEndTags.indexOf(parentTag) === -1; - - case "body": - case "caption": - case "col": - case "colgroup": - case "frameset": - case "frame": - case "head": - case "html": - case "tbody": - case "td": - case "tfoot": - case "th": - case "thead": - case "tr": - // These tags are only valid with a few parents that have special child - // parsing rules -- if we're down here, then none of those matched and - // so we allow it only if we don't know what the parent is, as all other - // cases are invalid. - return parentTag == null; - } - - return true; -} -/** - * Returns whether - */ - -function findInvalidAncestorForTag(tag, ancestorInfo) { - switch (tag) { - case "address": - case "article": - case "aside": - case "blockquote": - case "center": - case "details": - case "dialog": - case "dir": - case "div": - case "dl": - case "fieldset": - case "figcaption": - case "figure": - case "footer": - case "header": - case "hgroup": - case "main": - case "menu": - case "nav": - case "ol": - case "p": - case "section": - case "summary": - case "ul": - case "pre": - case "listing": - case "table": - case "hr": - case "xmp": - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ancestorInfo.pTagInButtonScope; - - case "form": - return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; - - case "li": - return ancestorInfo.listItemTagAutoclosing; - - case "dd": - case "dt": - return ancestorInfo.dlItemTagAutoclosing; - - case "button": - return ancestorInfo.buttonTagInScope; - - case "a": - // Spec says something about storing a list of markers, but it sounds - // equivalent to this check. - return ancestorInfo.aTagInScope; - - case "nobr": - return ancestorInfo.nobrTagInScope; - } - - return null; -} - -var didWarn = {}; - -function validateDOMNesting(childTag, childText, ancestorInfo) { - { - ancestorInfo = ancestorInfo || emptyAncestorInfoDev; - var parentInfo = ancestorInfo.current; - var parentTag = parentInfo && parentInfo.tag; - - if (childText != null) { - if (childTag != null) { - error( - "validateDOMNesting: when childText is passed, childTag should be null" - ); - } - - childTag = "#text"; - } else if (childTag == null) { - error("validateDOMNesting: when childText or childTag must be provided"); - - return; - } - - var invalidParent = isTagValidWithParent(childTag, parentTag) - ? null - : parentInfo; - var invalidAncestor = invalidParent - ? null - : findInvalidAncestorForTag(childTag, ancestorInfo); - var invalidParentOrAncestor = invalidParent || invalidAncestor; - - if (!invalidParentOrAncestor) { - return; - } - - var ancestorTag = invalidParentOrAncestor.tag; - var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion - - if (didWarn[warnKey]) { - return; - } - - didWarn[warnKey] = true; - var tagDisplayName = childTag; - var whitespaceInfo = ""; - - if (childTag === "#text") { - if (childText != null && /\S/.test(childText)) { - tagDisplayName = "Text nodes"; - } else { - tagDisplayName = "Whitespace text nodes"; - whitespaceInfo = - " Make sure you don't have any extra whitespace between tags on " + - "each line of your source code."; - } - } else { - tagDisplayName = "<" + childTag + ">"; - } - - if (invalidParent) { - var info = ""; - - if (ancestorTag === "table" && childTag === "tr") { - info += - " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + - "the browser."; - } - - error( - "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s", - tagDisplayName, - ancestorTag, - whitespaceInfo, - info - ); - } else { - error( - "validateDOMNesting(...): %s cannot appear as a descendant of " + - "<%s>.", - tagDisplayName, - ancestorTag - ); - } - } -} - function validateLinkPropsForStyleResource(props) { { // This should only be called when we know we are opting into Resource semantics (i.e. precedence is not null) @@ -42258,20 +42259,7 @@ function createInstance( { // TODO: take namespace into account when validating. var hostContextDev = hostContext; - validateDOMNesting(type, null, hostContextDev.ancestorInfo); - - if ( - typeof props.children === "string" || - typeof props.children === "number" - ) { - var string = "" + props.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - + validateDOMNesting(type, hostContextDev.ancestorInfo); namespace = hostContextDev.namespace; } @@ -42423,23 +42411,6 @@ function prepareUpdate(domElement, type, oldProps, newProps, hostContext) { return null; } - { - var hostContextDev = hostContext; - - if ( - typeof newProps.children !== typeof oldProps.children && - (typeof newProps.children === "string" || - typeof newProps.children === "number") - ) { - var string = "" + newProps.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - } - return diffProperties(domElement, type, oldProps, newProps); } function shouldSetTextContent(type, props) { @@ -42461,7 +42432,11 @@ function createTextInstance( ) { { var hostContextDev = hostContext; - validateDOMNesting(null, text, hostContextDev.ancestorInfo); + var ancestor = hostContextDev.ancestorInfo.current; + + if (ancestor != null) { + validateTextNesting(text, ancestor.tag); + } } var textNode = getOwnerDocumentFromRootContainer( @@ -43328,7 +43303,7 @@ function resolveSingletonInstance( var hostContextDev = hostContext; if (validateDOMNestingDev) { - validateDOMNesting(type, null, hostContextDev.ancestorInfo); + validateDOMNesting(type, hostContextDev.ancestorInfo); } } diff --git a/compiled/facebook-www/ReactDOMTesting-dev.classic.js b/compiled/facebook-www/ReactDOMTesting-dev.classic.js index 14d8e1ce6e84e..449a532b27877 100644 --- a/compiled/facebook-www/ReactDOMTesting-dev.classic.js +++ b/compiled/facebook-www/ReactDOMTesting-dev.classic.js @@ -4478,6 +4478,489 @@ function restoreControlledTextareaState(element, props) { updateTextarea(element, props); } +// This validation code was written based on the HTML5 parsing spec: +// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope +// +// Note: this does not catch all invalid nesting, nor does it try to (as it's +// not clear what practical benefit doing so provides); instead, we warn only +// for cases where the parser will give a parse tree differing from what React +// intended. For example, <b><div></div></b> is invalid but we don't warn +// because it still parses correctly; we do warn for other cases like nested +// <p> tags where the beginning of the second element implicitly closes the +// first, causing a confusing mess. +// https://html.spec.whatwg.org/multipage/syntax.html#special +var specialTags = [ + "address", + "applet", + "area", + "article", + "aside", + "base", + "basefont", + "bgsound", + "blockquote", + "body", + "br", + "button", + "caption", + "center", + "col", + "colgroup", + "dd", + "details", + "dir", + "div", + "dl", + "dt", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "iframe", + "img", + "input", + "isindex", + "li", + "link", + "listing", + "main", + "marquee", + "menu", + "menuitem", + "meta", + "nav", + "noembed", + "noframes", + "noscript", + "object", + "ol", + "p", + "param", + "plaintext", + "pre", + "script", + "section", + "select", + "source", + "style", + "summary", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "title", + "tr", + "track", + "ul", + "wbr", + "xmp" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope + +var inScopeTags = [ + "applet", + "caption", + "html", + "table", + "td", + "th", + "marquee", + "object", + "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point + // TODO: Distinguish by namespace here -- for <title>, including it here + // errs on the side of fewer warnings + "foreignObject", + "desc", + "title" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope + +var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags + +var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; +var emptyAncestorInfoDev = { + current: null, + formTag: null, + aTagInScope: null, + buttonTagInScope: null, + nobrTagInScope: null, + pTagInButtonScope: null, + listItemTagAutoclosing: null, + dlItemTagAutoclosing: null, + containerTagInScope: null +}; + +function updatedAncestorInfoDev(oldInfo, tag) { + { + var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); + + var info = { + tag: tag + }; + + if (inScopeTags.indexOf(tag) !== -1) { + ancestorInfo.aTagInScope = null; + ancestorInfo.buttonTagInScope = null; + ancestorInfo.nobrTagInScope = null; + } + + if (buttonScopeTags.indexOf(tag) !== -1) { + ancestorInfo.pTagInButtonScope = null; + } // See rules for 'li', 'dd', 'dt' start tags in + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + if ( + specialTags.indexOf(tag) !== -1 && + tag !== "address" && + tag !== "div" && + tag !== "p" + ) { + ancestorInfo.listItemTagAutoclosing = null; + ancestorInfo.dlItemTagAutoclosing = null; + } + + ancestorInfo.current = info; + + if (tag === "form") { + ancestorInfo.formTag = info; + } + + if (tag === "a") { + ancestorInfo.aTagInScope = info; + } + + if (tag === "button") { + ancestorInfo.buttonTagInScope = info; + } + + if (tag === "nobr") { + ancestorInfo.nobrTagInScope = info; + } + + if (tag === "p") { + ancestorInfo.pTagInButtonScope = info; + } + + if (tag === "li") { + ancestorInfo.listItemTagAutoclosing = info; + } + + if (tag === "dd" || tag === "dt") { + ancestorInfo.dlItemTagAutoclosing = info; + } + + if (tag === "#document" || tag === "html") { + ancestorInfo.containerTagInScope = null; + } else if (!ancestorInfo.containerTagInScope) { + ancestorInfo.containerTagInScope = info; + } + + return ancestorInfo; + } +} +/** + * Returns whether + */ + +function isTagValidWithParent(tag, parentTag) { + // First, let's check if we're in an unusual parsing mode... + switch (parentTag) { + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect + case "select": + return tag === "option" || tag === "optgroup" || tag === "#text"; + + case "optgroup": + return tag === "option" || tag === "#text"; + // Strictly speaking, seeing an <option> doesn't mean we're in a <select> + // but + + case "option": + return tag === "#text"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption + // No special behavior since these rules fall back to "in body" mode for + // all except special table nodes which cause bad parsing behavior anyway. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr + + case "tr": + return ( + tag === "th" || + tag === "td" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody + + case "tbody": + case "thead": + case "tfoot": + return ( + tag === "tr" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup + + case "colgroup": + return tag === "col" || tag === "template"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable + + case "table": + return ( + tag === "caption" || + tag === "colgroup" || + tag === "tbody" || + tag === "tfoot" || + tag === "thead" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead + + case "head": + return ( + tag === "base" || + tag === "basefont" || + tag === "bgsound" || + tag === "link" || + tag === "meta" || + tag === "title" || + tag === "noscript" || + tag === "noframes" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element + + case "html": + return tag === "head" || tag === "body" || tag === "frameset"; + + case "frameset": + return tag === "frame"; + + case "#document": + return tag === "html"; + } // Probably in the "in body" parsing mode, so we outlaw only tag combos + // where the parsing rules cause implicit opens or closes to be added. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + switch (tag) { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ( + parentTag !== "h1" && + parentTag !== "h2" && + parentTag !== "h3" && + parentTag !== "h4" && + parentTag !== "h5" && + parentTag !== "h6" + ); + + case "rp": + case "rt": + return impliedEndTags.indexOf(parentTag) === -1; + + case "body": + case "caption": + case "col": + case "colgroup": + case "frameset": + case "frame": + case "head": + case "html": + case "tbody": + case "td": + case "tfoot": + case "th": + case "thead": + case "tr": + // These tags are only valid with a few parents that have special child + // parsing rules -- if we're down here, then none of those matched and + // so we allow it only if we don't know what the parent is, as all other + // cases are invalid. + return parentTag == null; + } + + return true; +} +/** + * Returns whether + */ + +function findInvalidAncestorForTag(tag, ancestorInfo) { + switch (tag) { + case "address": + case "article": + case "aside": + case "blockquote": + case "center": + case "details": + case "dialog": + case "dir": + case "div": + case "dl": + case "fieldset": + case "figcaption": + case "figure": + case "footer": + case "header": + case "hgroup": + case "main": + case "menu": + case "nav": + case "ol": + case "p": + case "section": + case "summary": + case "ul": + case "pre": + case "listing": + case "table": + case "hr": + case "xmp": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ancestorInfo.pTagInButtonScope; + + case "form": + return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; + + case "li": + return ancestorInfo.listItemTagAutoclosing; + + case "dd": + case "dt": + return ancestorInfo.dlItemTagAutoclosing; + + case "button": + return ancestorInfo.buttonTagInScope; + + case "a": + // Spec says something about storing a list of markers, but it sounds + // equivalent to this check. + return ancestorInfo.aTagInScope; + + case "nobr": + return ancestorInfo.nobrTagInScope; + } + + return null; +} + +var didWarn = {}; + +function validateDOMNesting(childTag, ancestorInfo) { + { + ancestorInfo = ancestorInfo || emptyAncestorInfoDev; + var parentInfo = ancestorInfo.current; + var parentTag = parentInfo && parentInfo.tag; + var invalidParent = isTagValidWithParent(childTag, parentTag) + ? null + : parentInfo; + var invalidAncestor = invalidParent + ? null + : findInvalidAncestorForTag(childTag, ancestorInfo); + var invalidParentOrAncestor = invalidParent || invalidAncestor; + + if (!invalidParentOrAncestor) { + return; + } + + var ancestorTag = invalidParentOrAncestor.tag; + var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + var tagDisplayName = "<" + childTag + ">"; + + if (invalidParent) { + var info = ""; + + if (ancestorTag === "table" && childTag === "tr") { + info += + " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + + "the browser."; + } + + error( + "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s", + tagDisplayName, + ancestorTag, + info + ); + } else { + error( + "validateDOMNesting(...): %s cannot appear as a descendant of " + + "<%s>.", + tagDisplayName, + ancestorTag + ); + } + } +} + +function validateTextNesting(childText, parentTag) { + { + if (isTagValidWithParent("#text", parentTag)) { + return; + } // eslint-disable-next-line react-internal/safe-string-coercion + + var warnKey = "#text|" + parentTag; + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + + if (/\S/.test(childText)) { + error( + "validateDOMNesting(...): Text nodes cannot appear as a child of <%s>.", + parentTag + ); + } else { + error( + "validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <%s>. " + + "Make sure you don't have any extra whitespace between tags on " + + "each line of your source code.", + parentTag + ); + } + } +} + var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; var MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML"; var SVG_NAMESPACE = "http://www.w3.org/2000/svg"; // Assumes there is no parent namespace. @@ -6679,10 +7162,13 @@ function setProp(domElement, tag, key, value, props, prevValue) { switch (key) { case "children": { if (typeof value === "string") { - // Avoid setting initial textContent when the text is empty. In IE11 setting + { + validateTextNesting(value, tag); + } // Avoid setting initial textContent when the text is empty. In IE11 setting // textContent on a <textarea> will cause the placeholder to not // show within the <textarea> until it has been focused and blurred again. // https://github.com/facebook/react/issues/6731#issuecomment-254874553 + var canSetTextContent = tag !== "body" && (tag !== "textarea" || value !== ""); @@ -6690,6 +7176,10 @@ function setProp(domElement, tag, key, value, props, prevValue) { setTextContent(domElement, value); } } else if (typeof value === "number") { + { + validateTextNesting("" + value, tag); + } + var _canSetTextContent = tag !== "body"; if (_canSetTextContent) { @@ -25244,14 +25734,8 @@ function updateHostComponent( // component is hitting the resume path. Figure out why. Possibly // related to `hidden`. - var currentHostContext = getHostContext(); - var updatePayload = prepareUpdate( - instance, - type, - oldProps, - newProps, - currentHostContext - ); // TODO: Type this specific to this type of component. + getHostContext(); + var updatePayload = prepareUpdate(instance, type, oldProps, newProps); // TODO: Type this specific to this type of component. workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there // is a new ref we mark this as an update. All the work is done in commitWork. @@ -37138,7 +37622,7 @@ function createFiberRoot( return root; } -var ReactVersion = "18.3.0-www-classic-7ad36f09"; +var ReactVersion = "18.3.0-www-classic-01e3a587"; function createPortal$1( children, @@ -42525,489 +43009,6 @@ function hasRole(element, role) { return role === getImplicitRole(element); } -// This validation code was written based on the HTML5 parsing spec: -// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope -// -// Note: this does not catch all invalid nesting, nor does it try to (as it's -// not clear what practical benefit doing so provides); instead, we warn only -// for cases where the parser will give a parse tree differing from what React -// intended. For example, <b><div></div></b> is invalid but we don't warn -// because it still parses correctly; we do warn for other cases like nested -// <p> tags where the beginning of the second element implicitly closes the -// first, causing a confusing mess. -// https://html.spec.whatwg.org/multipage/syntax.html#special -var specialTags = [ - "address", - "applet", - "area", - "article", - "aside", - "base", - "basefont", - "bgsound", - "blockquote", - "body", - "br", - "button", - "caption", - "center", - "col", - "colgroup", - "dd", - "details", - "dir", - "div", - "dl", - "dt", - "embed", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "frame", - "frameset", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "head", - "header", - "hgroup", - "hr", - "html", - "iframe", - "img", - "input", - "isindex", - "li", - "link", - "listing", - "main", - "marquee", - "menu", - "menuitem", - "meta", - "nav", - "noembed", - "noframes", - "noscript", - "object", - "ol", - "p", - "param", - "plaintext", - "pre", - "script", - "section", - "select", - "source", - "style", - "summary", - "table", - "tbody", - "td", - "template", - "textarea", - "tfoot", - "th", - "thead", - "title", - "tr", - "track", - "ul", - "wbr", - "xmp" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope - -var inScopeTags = [ - "applet", - "caption", - "html", - "table", - "td", - "th", - "marquee", - "object", - "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point - // TODO: Distinguish by namespace here -- for <title>, including it here - // errs on the side of fewer warnings - "foreignObject", - "desc", - "title" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope - -var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags - -var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; -var emptyAncestorInfoDev = { - current: null, - formTag: null, - aTagInScope: null, - buttonTagInScope: null, - nobrTagInScope: null, - pTagInButtonScope: null, - listItemTagAutoclosing: null, - dlItemTagAutoclosing: null, - containerTagInScope: null -}; - -function updatedAncestorInfoDev(oldInfo, tag) { - { - var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); - - var info = { - tag: tag - }; - - if (inScopeTags.indexOf(tag) !== -1) { - ancestorInfo.aTagInScope = null; - ancestorInfo.buttonTagInScope = null; - ancestorInfo.nobrTagInScope = null; - } - - if (buttonScopeTags.indexOf(tag) !== -1) { - ancestorInfo.pTagInButtonScope = null; - } // See rules for 'li', 'dd', 'dt' start tags in - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - if ( - specialTags.indexOf(tag) !== -1 && - tag !== "address" && - tag !== "div" && - tag !== "p" - ) { - ancestorInfo.listItemTagAutoclosing = null; - ancestorInfo.dlItemTagAutoclosing = null; - } - - ancestorInfo.current = info; - - if (tag === "form") { - ancestorInfo.formTag = info; - } - - if (tag === "a") { - ancestorInfo.aTagInScope = info; - } - - if (tag === "button") { - ancestorInfo.buttonTagInScope = info; - } - - if (tag === "nobr") { - ancestorInfo.nobrTagInScope = info; - } - - if (tag === "p") { - ancestorInfo.pTagInButtonScope = info; - } - - if (tag === "li") { - ancestorInfo.listItemTagAutoclosing = info; - } - - if (tag === "dd" || tag === "dt") { - ancestorInfo.dlItemTagAutoclosing = info; - } - - if (tag === "#document" || tag === "html") { - ancestorInfo.containerTagInScope = null; - } else if (!ancestorInfo.containerTagInScope) { - ancestorInfo.containerTagInScope = info; - } - - return ancestorInfo; - } -} -/** - * Returns whether - */ - -function isTagValidWithParent(tag, parentTag) { - // First, let's check if we're in an unusual parsing mode... - switch (parentTag) { - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect - case "select": - return tag === "option" || tag === "optgroup" || tag === "#text"; - - case "optgroup": - return tag === "option" || tag === "#text"; - // Strictly speaking, seeing an <option> doesn't mean we're in a <select> - // but - - case "option": - return tag === "#text"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption - // No special behavior since these rules fall back to "in body" mode for - // all except special table nodes which cause bad parsing behavior anyway. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr - - case "tr": - return ( - tag === "th" || - tag === "td" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody - - case "tbody": - case "thead": - case "tfoot": - return ( - tag === "tr" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup - - case "colgroup": - return tag === "col" || tag === "template"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable - - case "table": - return ( - tag === "caption" || - tag === "colgroup" || - tag === "tbody" || - tag === "tfoot" || - tag === "thead" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead - - case "head": - return ( - tag === "base" || - tag === "basefont" || - tag === "bgsound" || - tag === "link" || - tag === "meta" || - tag === "title" || - tag === "noscript" || - tag === "noframes" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element - - case "html": - return tag === "head" || tag === "body" || tag === "frameset"; - - case "frameset": - return tag === "frame"; - - case "#document": - return tag === "html"; - } // Probably in the "in body" parsing mode, so we outlaw only tag combos - // where the parsing rules cause implicit opens or closes to be added. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - switch (tag) { - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ( - parentTag !== "h1" && - parentTag !== "h2" && - parentTag !== "h3" && - parentTag !== "h4" && - parentTag !== "h5" && - parentTag !== "h6" - ); - - case "rp": - case "rt": - return impliedEndTags.indexOf(parentTag) === -1; - - case "body": - case "caption": - case "col": - case "colgroup": - case "frameset": - case "frame": - case "head": - case "html": - case "tbody": - case "td": - case "tfoot": - case "th": - case "thead": - case "tr": - // These tags are only valid with a few parents that have special child - // parsing rules -- if we're down here, then none of those matched and - // so we allow it only if we don't know what the parent is, as all other - // cases are invalid. - return parentTag == null; - } - - return true; -} -/** - * Returns whether - */ - -function findInvalidAncestorForTag(tag, ancestorInfo) { - switch (tag) { - case "address": - case "article": - case "aside": - case "blockquote": - case "center": - case "details": - case "dialog": - case "dir": - case "div": - case "dl": - case "fieldset": - case "figcaption": - case "figure": - case "footer": - case "header": - case "hgroup": - case "main": - case "menu": - case "nav": - case "ol": - case "p": - case "section": - case "summary": - case "ul": - case "pre": - case "listing": - case "table": - case "hr": - case "xmp": - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ancestorInfo.pTagInButtonScope; - - case "form": - return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; - - case "li": - return ancestorInfo.listItemTagAutoclosing; - - case "dd": - case "dt": - return ancestorInfo.dlItemTagAutoclosing; - - case "button": - return ancestorInfo.buttonTagInScope; - - case "a": - // Spec says something about storing a list of markers, but it sounds - // equivalent to this check. - return ancestorInfo.aTagInScope; - - case "nobr": - return ancestorInfo.nobrTagInScope; - } - - return null; -} - -var didWarn = {}; - -function validateDOMNesting(childTag, childText, ancestorInfo) { - { - ancestorInfo = ancestorInfo || emptyAncestorInfoDev; - var parentInfo = ancestorInfo.current; - var parentTag = parentInfo && parentInfo.tag; - - if (childText != null) { - if (childTag != null) { - error( - "validateDOMNesting: when childText is passed, childTag should be null" - ); - } - - childTag = "#text"; - } else if (childTag == null) { - error("validateDOMNesting: when childText or childTag must be provided"); - - return; - } - - var invalidParent = isTagValidWithParent(childTag, parentTag) - ? null - : parentInfo; - var invalidAncestor = invalidParent - ? null - : findInvalidAncestorForTag(childTag, ancestorInfo); - var invalidParentOrAncestor = invalidParent || invalidAncestor; - - if (!invalidParentOrAncestor) { - return; - } - - var ancestorTag = invalidParentOrAncestor.tag; - var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion - - if (didWarn[warnKey]) { - return; - } - - didWarn[warnKey] = true; - var tagDisplayName = childTag; - var whitespaceInfo = ""; - - if (childTag === "#text") { - if (childText != null && /\S/.test(childText)) { - tagDisplayName = "Text nodes"; - } else { - tagDisplayName = "Whitespace text nodes"; - whitespaceInfo = - " Make sure you don't have any extra whitespace between tags on " + - "each line of your source code."; - } - } else { - tagDisplayName = "<" + childTag + ">"; - } - - if (invalidParent) { - var info = ""; - - if (ancestorTag === "table" && childTag === "tr") { - info += - " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + - "the browser."; - } - - error( - "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s", - tagDisplayName, - ancestorTag, - whitespaceInfo, - info - ); - } else { - error( - "validateDOMNesting(...): %s cannot appear as a descendant of " + - "<%s>.", - tagDisplayName, - ancestorTag - ); - } - } -} - function validateLinkPropsForStyleResource(props) { { // This should only be called when we know we are opting into Resource semantics (i.e. precedence is not null) @@ -43335,20 +43336,7 @@ function createInstance( { // TODO: take namespace into account when validating. var hostContextDev = hostContext; - validateDOMNesting(type, null, hostContextDev.ancestorInfo); - - if ( - typeof props.children === "string" || - typeof props.children === "number" - ) { - var string = "" + props.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - + validateDOMNesting(type, hostContextDev.ancestorInfo); namespace = hostContextDev.namespace; } @@ -43500,23 +43488,6 @@ function prepareUpdate(domElement, type, oldProps, newProps, hostContext) { return null; } - { - var hostContextDev = hostContext; - - if ( - typeof newProps.children !== typeof oldProps.children && - (typeof newProps.children === "string" || - typeof newProps.children === "number") - ) { - var string = "" + newProps.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - } - return diffProperties(domElement, type, oldProps, newProps); } function shouldSetTextContent(type, props) { @@ -43538,7 +43509,11 @@ function createTextInstance( ) { { var hostContextDev = hostContext; - validateDOMNesting(null, text, hostContextDev.ancestorInfo); + var ancestor = hostContextDev.ancestorInfo.current; + + if (ancestor != null) { + validateTextNesting(text, ancestor.tag); + } } var textNode = getOwnerDocumentFromRootContainer( @@ -44537,7 +44512,7 @@ function resolveSingletonInstance( var hostContextDev = hostContext; if (validateDOMNestingDev) { - validateDOMNesting(type, null, hostContextDev.ancestorInfo); + validateDOMNesting(type, hostContextDev.ancestorInfo); } } diff --git a/compiled/facebook-www/ReactDOMTesting-dev.modern.js b/compiled/facebook-www/ReactDOMTesting-dev.modern.js index 82a2b7b2ff2e4..3fc9fb58781c4 100644 --- a/compiled/facebook-www/ReactDOMTesting-dev.modern.js +++ b/compiled/facebook-www/ReactDOMTesting-dev.modern.js @@ -4393,6 +4393,489 @@ function restoreControlledTextareaState(element, props) { updateTextarea(element, props); } +// This validation code was written based on the HTML5 parsing spec: +// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope +// +// Note: this does not catch all invalid nesting, nor does it try to (as it's +// not clear what practical benefit doing so provides); instead, we warn only +// for cases where the parser will give a parse tree differing from what React +// intended. For example, <b><div></div></b> is invalid but we don't warn +// because it still parses correctly; we do warn for other cases like nested +// <p> tags where the beginning of the second element implicitly closes the +// first, causing a confusing mess. +// https://html.spec.whatwg.org/multipage/syntax.html#special +var specialTags = [ + "address", + "applet", + "area", + "article", + "aside", + "base", + "basefont", + "bgsound", + "blockquote", + "body", + "br", + "button", + "caption", + "center", + "col", + "colgroup", + "dd", + "details", + "dir", + "div", + "dl", + "dt", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "iframe", + "img", + "input", + "isindex", + "li", + "link", + "listing", + "main", + "marquee", + "menu", + "menuitem", + "meta", + "nav", + "noembed", + "noframes", + "noscript", + "object", + "ol", + "p", + "param", + "plaintext", + "pre", + "script", + "section", + "select", + "source", + "style", + "summary", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "title", + "tr", + "track", + "ul", + "wbr", + "xmp" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope + +var inScopeTags = [ + "applet", + "caption", + "html", + "table", + "td", + "th", + "marquee", + "object", + "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point + // TODO: Distinguish by namespace here -- for <title>, including it here + // errs on the side of fewer warnings + "foreignObject", + "desc", + "title" +]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope + +var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags + +var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; +var emptyAncestorInfoDev = { + current: null, + formTag: null, + aTagInScope: null, + buttonTagInScope: null, + nobrTagInScope: null, + pTagInButtonScope: null, + listItemTagAutoclosing: null, + dlItemTagAutoclosing: null, + containerTagInScope: null +}; + +function updatedAncestorInfoDev(oldInfo, tag) { + { + var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); + + var info = { + tag: tag + }; + + if (inScopeTags.indexOf(tag) !== -1) { + ancestorInfo.aTagInScope = null; + ancestorInfo.buttonTagInScope = null; + ancestorInfo.nobrTagInScope = null; + } + + if (buttonScopeTags.indexOf(tag) !== -1) { + ancestorInfo.pTagInButtonScope = null; + } // See rules for 'li', 'dd', 'dt' start tags in + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + if ( + specialTags.indexOf(tag) !== -1 && + tag !== "address" && + tag !== "div" && + tag !== "p" + ) { + ancestorInfo.listItemTagAutoclosing = null; + ancestorInfo.dlItemTagAutoclosing = null; + } + + ancestorInfo.current = info; + + if (tag === "form") { + ancestorInfo.formTag = info; + } + + if (tag === "a") { + ancestorInfo.aTagInScope = info; + } + + if (tag === "button") { + ancestorInfo.buttonTagInScope = info; + } + + if (tag === "nobr") { + ancestorInfo.nobrTagInScope = info; + } + + if (tag === "p") { + ancestorInfo.pTagInButtonScope = info; + } + + if (tag === "li") { + ancestorInfo.listItemTagAutoclosing = info; + } + + if (tag === "dd" || tag === "dt") { + ancestorInfo.dlItemTagAutoclosing = info; + } + + if (tag === "#document" || tag === "html") { + ancestorInfo.containerTagInScope = null; + } else if (!ancestorInfo.containerTagInScope) { + ancestorInfo.containerTagInScope = info; + } + + return ancestorInfo; + } +} +/** + * Returns whether + */ + +function isTagValidWithParent(tag, parentTag) { + // First, let's check if we're in an unusual parsing mode... + switch (parentTag) { + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect + case "select": + return tag === "option" || tag === "optgroup" || tag === "#text"; + + case "optgroup": + return tag === "option" || tag === "#text"; + // Strictly speaking, seeing an <option> doesn't mean we're in a <select> + // but + + case "option": + return tag === "#text"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption + // No special behavior since these rules fall back to "in body" mode for + // all except special table nodes which cause bad parsing behavior anyway. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr + + case "tr": + return ( + tag === "th" || + tag === "td" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody + + case "tbody": + case "thead": + case "tfoot": + return ( + tag === "tr" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup + + case "colgroup": + return tag === "col" || tag === "template"; + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable + + case "table": + return ( + tag === "caption" || + tag === "colgroup" || + tag === "tbody" || + tag === "tfoot" || + tag === "thead" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead + + case "head": + return ( + tag === "base" || + tag === "basefont" || + tag === "bgsound" || + tag === "link" || + tag === "meta" || + tag === "title" || + tag === "noscript" || + tag === "noframes" || + tag === "style" || + tag === "script" || + tag === "template" + ); + // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element + + case "html": + return tag === "head" || tag === "body" || tag === "frameset"; + + case "frameset": + return tag === "frame"; + + case "#document": + return tag === "html"; + } // Probably in the "in body" parsing mode, so we outlaw only tag combos + // where the parsing rules cause implicit opens or closes to be added. + // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody + + switch (tag) { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ( + parentTag !== "h1" && + parentTag !== "h2" && + parentTag !== "h3" && + parentTag !== "h4" && + parentTag !== "h5" && + parentTag !== "h6" + ); + + case "rp": + case "rt": + return impliedEndTags.indexOf(parentTag) === -1; + + case "body": + case "caption": + case "col": + case "colgroup": + case "frameset": + case "frame": + case "head": + case "html": + case "tbody": + case "td": + case "tfoot": + case "th": + case "thead": + case "tr": + // These tags are only valid with a few parents that have special child + // parsing rules -- if we're down here, then none of those matched and + // so we allow it only if we don't know what the parent is, as all other + // cases are invalid. + return parentTag == null; + } + + return true; +} +/** + * Returns whether + */ + +function findInvalidAncestorForTag(tag, ancestorInfo) { + switch (tag) { + case "address": + case "article": + case "aside": + case "blockquote": + case "center": + case "details": + case "dialog": + case "dir": + case "div": + case "dl": + case "fieldset": + case "figcaption": + case "figure": + case "footer": + case "header": + case "hgroup": + case "main": + case "menu": + case "nav": + case "ol": + case "p": + case "section": + case "summary": + case "ul": + case "pre": + case "listing": + case "table": + case "hr": + case "xmp": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return ancestorInfo.pTagInButtonScope; + + case "form": + return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; + + case "li": + return ancestorInfo.listItemTagAutoclosing; + + case "dd": + case "dt": + return ancestorInfo.dlItemTagAutoclosing; + + case "button": + return ancestorInfo.buttonTagInScope; + + case "a": + // Spec says something about storing a list of markers, but it sounds + // equivalent to this check. + return ancestorInfo.aTagInScope; + + case "nobr": + return ancestorInfo.nobrTagInScope; + } + + return null; +} + +var didWarn = {}; + +function validateDOMNesting(childTag, ancestorInfo) { + { + ancestorInfo = ancestorInfo || emptyAncestorInfoDev; + var parentInfo = ancestorInfo.current; + var parentTag = parentInfo && parentInfo.tag; + var invalidParent = isTagValidWithParent(childTag, parentTag) + ? null + : parentInfo; + var invalidAncestor = invalidParent + ? null + : findInvalidAncestorForTag(childTag, ancestorInfo); + var invalidParentOrAncestor = invalidParent || invalidAncestor; + + if (!invalidParentOrAncestor) { + return; + } + + var ancestorTag = invalidParentOrAncestor.tag; + var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + var tagDisplayName = "<" + childTag + ">"; + + if (invalidParent) { + var info = ""; + + if (ancestorTag === "table" && childTag === "tr") { + info += + " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + + "the browser."; + } + + error( + "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s", + tagDisplayName, + ancestorTag, + info + ); + } else { + error( + "validateDOMNesting(...): %s cannot appear as a descendant of " + + "<%s>.", + tagDisplayName, + ancestorTag + ); + } + } +} + +function validateTextNesting(childText, parentTag) { + { + if (isTagValidWithParent("#text", parentTag)) { + return; + } // eslint-disable-next-line react-internal/safe-string-coercion + + var warnKey = "#text|" + parentTag; + + if (didWarn[warnKey]) { + return; + } + + didWarn[warnKey] = true; + + if (/\S/.test(childText)) { + error( + "validateDOMNesting(...): Text nodes cannot appear as a child of <%s>.", + parentTag + ); + } else { + error( + "validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <%s>. " + + "Make sure you don't have any extra whitespace between tags on " + + "each line of your source code.", + parentTag + ); + } + } +} + var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; var MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML"; var SVG_NAMESPACE = "http://www.w3.org/2000/svg"; // Assumes there is no parent namespace. @@ -22017,14 +22500,8 @@ function updateHostComponent( // component is hitting the resume path. Figure out why. Possibly // related to `hidden`. - var currentHostContext = getHostContext(); - var updatePayload = prepareUpdate( - instance, - type, - oldProps, - newProps, - currentHostContext - ); // TODO: Type this specific to this type of component. + getHostContext(); + var updatePayload = prepareUpdate(instance, type, oldProps, newProps); // TODO: Type this specific to this type of component. workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there // is a new ref we mark this as an update. All the work is done in commitWork. @@ -33877,7 +34354,7 @@ function createFiberRoot( return root; } -var ReactVersion = "18.3.0-www-modern-16c00150"; +var ReactVersion = "18.3.0-www-modern-ffbb36ea"; function createPortal$1( children, @@ -39022,10 +39499,13 @@ function setProp(domElement, tag, key, value, props, prevValue) { switch (key) { case "children": { if (typeof value === "string") { - // Avoid setting initial textContent when the text is empty. In IE11 setting + { + validateTextNesting(value, tag); + } // Avoid setting initial textContent when the text is empty. In IE11 setting // textContent on a <textarea> will cause the placeholder to not // show within the <textarea> until it has been focused and blurred again. // https://github.com/facebook/react/issues/6731#issuecomment-254874553 + var canSetTextContent = tag !== "body" && (tag !== "textarea" || value !== ""); @@ -39033,6 +39513,10 @@ function setProp(domElement, tag, key, value, props, prevValue) { setTextContent(domElement, value); } } else if (typeof value === "number") { + { + validateTextNesting("" + value, tag); + } + var _canSetTextContent = tag !== "body"; if (_canSetTextContent) { @@ -42067,489 +42551,6 @@ function restoreControlledState(domElement, tag, props) { } } -// This validation code was written based on the HTML5 parsing spec: -// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope -// -// Note: this does not catch all invalid nesting, nor does it try to (as it's -// not clear what practical benefit doing so provides); instead, we warn only -// for cases where the parser will give a parse tree differing from what React -// intended. For example, <b><div></div></b> is invalid but we don't warn -// because it still parses correctly; we do warn for other cases like nested -// <p> tags where the beginning of the second element implicitly closes the -// first, causing a confusing mess. -// https://html.spec.whatwg.org/multipage/syntax.html#special -var specialTags = [ - "address", - "applet", - "area", - "article", - "aside", - "base", - "basefont", - "bgsound", - "blockquote", - "body", - "br", - "button", - "caption", - "center", - "col", - "colgroup", - "dd", - "details", - "dir", - "div", - "dl", - "dt", - "embed", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "frame", - "frameset", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "head", - "header", - "hgroup", - "hr", - "html", - "iframe", - "img", - "input", - "isindex", - "li", - "link", - "listing", - "main", - "marquee", - "menu", - "menuitem", - "meta", - "nav", - "noembed", - "noframes", - "noscript", - "object", - "ol", - "p", - "param", - "plaintext", - "pre", - "script", - "section", - "select", - "source", - "style", - "summary", - "table", - "tbody", - "td", - "template", - "textarea", - "tfoot", - "th", - "thead", - "title", - "tr", - "track", - "ul", - "wbr", - "xmp" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope - -var inScopeTags = [ - "applet", - "caption", - "html", - "table", - "td", - "th", - "marquee", - "object", - "template", // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point - // TODO: Distinguish by namespace here -- for <title>, including it here - // errs on the side of fewer warnings - "foreignObject", - "desc", - "title" -]; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope - -var buttonScopeTags = inScopeTags.concat(["button"]); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags - -var impliedEndTags = ["dd", "dt", "li", "option", "optgroup", "p", "rp", "rt"]; -var emptyAncestorInfoDev = { - current: null, - formTag: null, - aTagInScope: null, - buttonTagInScope: null, - nobrTagInScope: null, - pTagInButtonScope: null, - listItemTagAutoclosing: null, - dlItemTagAutoclosing: null, - containerTagInScope: null -}; - -function updatedAncestorInfoDev(oldInfo, tag) { - { - var ancestorInfo = assign({}, oldInfo || emptyAncestorInfoDev); - - var info = { - tag: tag - }; - - if (inScopeTags.indexOf(tag) !== -1) { - ancestorInfo.aTagInScope = null; - ancestorInfo.buttonTagInScope = null; - ancestorInfo.nobrTagInScope = null; - } - - if (buttonScopeTags.indexOf(tag) !== -1) { - ancestorInfo.pTagInButtonScope = null; - } // See rules for 'li', 'dd', 'dt' start tags in - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - if ( - specialTags.indexOf(tag) !== -1 && - tag !== "address" && - tag !== "div" && - tag !== "p" - ) { - ancestorInfo.listItemTagAutoclosing = null; - ancestorInfo.dlItemTagAutoclosing = null; - } - - ancestorInfo.current = info; - - if (tag === "form") { - ancestorInfo.formTag = info; - } - - if (tag === "a") { - ancestorInfo.aTagInScope = info; - } - - if (tag === "button") { - ancestorInfo.buttonTagInScope = info; - } - - if (tag === "nobr") { - ancestorInfo.nobrTagInScope = info; - } - - if (tag === "p") { - ancestorInfo.pTagInButtonScope = info; - } - - if (tag === "li") { - ancestorInfo.listItemTagAutoclosing = info; - } - - if (tag === "dd" || tag === "dt") { - ancestorInfo.dlItemTagAutoclosing = info; - } - - if (tag === "#document" || tag === "html") { - ancestorInfo.containerTagInScope = null; - } else if (!ancestorInfo.containerTagInScope) { - ancestorInfo.containerTagInScope = info; - } - - return ancestorInfo; - } -} -/** - * Returns whether - */ - -function isTagValidWithParent(tag, parentTag) { - // First, let's check if we're in an unusual parsing mode... - switch (parentTag) { - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect - case "select": - return tag === "option" || tag === "optgroup" || tag === "#text"; - - case "optgroup": - return tag === "option" || tag === "#text"; - // Strictly speaking, seeing an <option> doesn't mean we're in a <select> - // but - - case "option": - return tag === "#text"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption - // No special behavior since these rules fall back to "in body" mode for - // all except special table nodes which cause bad parsing behavior anyway. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr - - case "tr": - return ( - tag === "th" || - tag === "td" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody - - case "tbody": - case "thead": - case "tfoot": - return ( - tag === "tr" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup - - case "colgroup": - return tag === "col" || tag === "template"; - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable - - case "table": - return ( - tag === "caption" || - tag === "colgroup" || - tag === "tbody" || - tag === "tfoot" || - tag === "thead" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead - - case "head": - return ( - tag === "base" || - tag === "basefont" || - tag === "bgsound" || - tag === "link" || - tag === "meta" || - tag === "title" || - tag === "noscript" || - tag === "noframes" || - tag === "style" || - tag === "script" || - tag === "template" - ); - // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element - - case "html": - return tag === "head" || tag === "body" || tag === "frameset"; - - case "frameset": - return tag === "frame"; - - case "#document": - return tag === "html"; - } // Probably in the "in body" parsing mode, so we outlaw only tag combos - // where the parsing rules cause implicit opens or closes to be added. - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody - - switch (tag) { - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ( - parentTag !== "h1" && - parentTag !== "h2" && - parentTag !== "h3" && - parentTag !== "h4" && - parentTag !== "h5" && - parentTag !== "h6" - ); - - case "rp": - case "rt": - return impliedEndTags.indexOf(parentTag) === -1; - - case "body": - case "caption": - case "col": - case "colgroup": - case "frameset": - case "frame": - case "head": - case "html": - case "tbody": - case "td": - case "tfoot": - case "th": - case "thead": - case "tr": - // These tags are only valid with a few parents that have special child - // parsing rules -- if we're down here, then none of those matched and - // so we allow it only if we don't know what the parent is, as all other - // cases are invalid. - return parentTag == null; - } - - return true; -} -/** - * Returns whether - */ - -function findInvalidAncestorForTag(tag, ancestorInfo) { - switch (tag) { - case "address": - case "article": - case "aside": - case "blockquote": - case "center": - case "details": - case "dialog": - case "dir": - case "div": - case "dl": - case "fieldset": - case "figcaption": - case "figure": - case "footer": - case "header": - case "hgroup": - case "main": - case "menu": - case "nav": - case "ol": - case "p": - case "section": - case "summary": - case "ul": - case "pre": - case "listing": - case "table": - case "hr": - case "xmp": - case "h1": - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - return ancestorInfo.pTagInButtonScope; - - case "form": - return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; - - case "li": - return ancestorInfo.listItemTagAutoclosing; - - case "dd": - case "dt": - return ancestorInfo.dlItemTagAutoclosing; - - case "button": - return ancestorInfo.buttonTagInScope; - - case "a": - // Spec says something about storing a list of markers, but it sounds - // equivalent to this check. - return ancestorInfo.aTagInScope; - - case "nobr": - return ancestorInfo.nobrTagInScope; - } - - return null; -} - -var didWarn = {}; - -function validateDOMNesting(childTag, childText, ancestorInfo) { - { - ancestorInfo = ancestorInfo || emptyAncestorInfoDev; - var parentInfo = ancestorInfo.current; - var parentTag = parentInfo && parentInfo.tag; - - if (childText != null) { - if (childTag != null) { - error( - "validateDOMNesting: when childText is passed, childTag should be null" - ); - } - - childTag = "#text"; - } else if (childTag == null) { - error("validateDOMNesting: when childText or childTag must be provided"); - - return; - } - - var invalidParent = isTagValidWithParent(childTag, parentTag) - ? null - : parentInfo; - var invalidAncestor = invalidParent - ? null - : findInvalidAncestorForTag(childTag, ancestorInfo); - var invalidParentOrAncestor = invalidParent || invalidAncestor; - - if (!invalidParentOrAncestor) { - return; - } - - var ancestorTag = invalidParentOrAncestor.tag; - var warnKey = String(!!invalidParent) + "|" + childTag + "|" + ancestorTag; // eslint-disable-next-line react-internal/safe-string-coercion - - if (didWarn[warnKey]) { - return; - } - - didWarn[warnKey] = true; - var tagDisplayName = childTag; - var whitespaceInfo = ""; - - if (childTag === "#text") { - if (childText != null && /\S/.test(childText)) { - tagDisplayName = "Text nodes"; - } else { - tagDisplayName = "Whitespace text nodes"; - whitespaceInfo = - " Make sure you don't have any extra whitespace between tags on " + - "each line of your source code."; - } - } else { - tagDisplayName = "<" + childTag + ">"; - } - - if (invalidParent) { - var info = ""; - - if (ancestorTag === "table" && childTag === "tr") { - info += - " Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by " + - "the browser."; - } - - error( - "validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s", - tagDisplayName, - ancestorTag, - whitespaceInfo, - info - ); - } else { - error( - "validateDOMNesting(...): %s cannot appear as a descendant of " + - "<%s>.", - tagDisplayName, - ancestorTag - ); - } - } -} - function validateLinkPropsForStyleResource(props) { { // This should only be called when we know we are opting into Resource semantics (i.e. precedence is not null) @@ -42877,20 +42878,7 @@ function createInstance( { // TODO: take namespace into account when validating. var hostContextDev = hostContext; - validateDOMNesting(type, null, hostContextDev.ancestorInfo); - - if ( - typeof props.children === "string" || - typeof props.children === "number" - ) { - var string = "" + props.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - + validateDOMNesting(type, hostContextDev.ancestorInfo); namespace = hostContextDev.namespace; } @@ -43042,23 +43030,6 @@ function prepareUpdate(domElement, type, oldProps, newProps, hostContext) { return null; } - { - var hostContextDev = hostContext; - - if ( - typeof newProps.children !== typeof oldProps.children && - (typeof newProps.children === "string" || - typeof newProps.children === "number") - ) { - var string = "" + newProps.children; - var ownAncestorInfo = updatedAncestorInfoDev( - hostContextDev.ancestorInfo, - type - ); - validateDOMNesting(null, string, ownAncestorInfo); - } - } - return diffProperties(domElement, type, oldProps, newProps); } function shouldSetTextContent(type, props) { @@ -43080,7 +43051,11 @@ function createTextInstance( ) { { var hostContextDev = hostContext; - validateDOMNesting(null, text, hostContextDev.ancestorInfo); + var ancestor = hostContextDev.ancestorInfo.current; + + if (ancestor != null) { + validateTextNesting(text, ancestor.tag); + } } var textNode = getOwnerDocumentFromRootContainer( @@ -44079,7 +44054,7 @@ function resolveSingletonInstance( var hostContextDev = hostContext; if (validateDOMNestingDev) { - validateDOMNesting(type, null, hostContextDev.ancestorInfo); + validateDOMNesting(type, hostContextDev.ancestorInfo); } } diff --git a/compiled/facebook-www/WARNINGS b/compiled/facebook-www/WARNINGS index 8761170ba0e74..c54246f3a5da7 100644 --- a/compiled/facebook-www/WARNINGS +++ b/compiled/facebook-www/WARNINGS @@ -397,7 +397,7 @@ "unmountComponentAtNode(): The node you're attempting to unmount was rendered by React and is not a top-level container. %s" "unmountComponentAtNode(): The node you're attempting to unmount was rendered by another copy of React." "useInsertionEffect must not schedule updates." -"validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s" +"validateDOMNesting(...): %s cannot appear as a child of <%s>.%s" "validateDOMNesting(...): %s cannot appear as a descendant of <%s>." -"validateDOMNesting: when childText is passed, childTag should be null" -"validateDOMNesting: when childText or childTag must be provided" +"validateDOMNesting(...): Text nodes cannot appear as a child of <%s>." +"validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <%s>. Make sure you don't have any extra whitespace between tags on each line of your source code."