swc-project/swc · warning · swc_html_parser::error::Error

HTML start tag "{tag_name}" in a foreign namespace context

Error message

HTML start tag "{tag_name}" in a foreign namespace context

What it means

A start tag from the spec's breakout list (b, big, blockquote, body, br, center, code, dd, div, dl, dt, em, embed, h1-h6, head, hr, i, img, li, listing, menu, meta, nobr, ol, p, pre, ruby, s, small, span, strong, strike, sub, sup, table, tt, u, ul, var) was seen while the adjusted current node was in a foreign namespace (crates/swc_html_parser/src/parser/mod.rs:781). The parser records HtmlStartTagInForeignContext, calls pop_until_in_foreign() — force-closing the entire SVG/MathML subtree — and reprocesses the tag with normal HTML rules.

Source

Thrown at crates/swc_html_parser/src/parser/mod.rs:781

                        | "ol"
                        | "p"
                        | "pre"
                        | "ruby"
                        | "s"
                        | "small"
                        | "span"
                        | "strong"
                        | "strike"
                        | "sub"
                        | "sup"
                        | "table"
                        | "tt"
                        | "u"
                        | "ul"
                        | "var"
                ) =>
            {
                self.errors.push(Error::new(
                    token_and_info.span,
                    ErrorKind::HtmlStartTagInForeignContext(tag_name.clone()),
                ));
                self.open_elements_stack.pop_until_in_foreign();
                self.process_token(token_and_info, None)?;
            }
            Token::StartTag {
                tag_name,
                attributes,
                ..
            } if tag_name == "font"
                && attributes
                    .iter()
                    .any(|attribute| matches!(&*attribute.name, "color" | "face" | "size")) =>
            {
                self.errors.push(Error::new(
                    token_and_info.span,
                    ErrorKind::HtmlStartTagInForeignContext(tag_name.clone()),

View on GitHub (pinned to 5176682b65)

Solutions

  1. Close the <svg>/<math> element before any HTML layout tag; balance the foreign element's open/close pair in the template
  2. Use <svg><foreignObject> when you genuinely need HTML flow content inside SVG (integration points keep HTML rules active there)
  3. Fix the missing </svg> or </math> that swallows the following HTML
  4. Filter ErrorKind::HtmlStartTagInForeignContext(_) if you accept the force-close recovery

Example fix

<!-- before: <p> breaks out of the svg subtree -->
<svg><p>Styled text</p></svg>

<!-- after: keep them separate -->
<svg></svg>
<p>Styled text</p>

<!-- or embed HTML inside SVG properly: -->
<svg><foreignObject width="200" height="50"><p>Styled text</p></foreignObject></svg>
Defensive patterns

Strategy: validation

Validate before calling

const BREAKOUT: &[&str] = &["b","big","blockquote","body","br","center","code","dd","div","dl","dt","em","embed","h1","h2","h3","h4","h5","h6","head","hr","i","img","li","listing","menu","meta","nobr","ol","p","pre","ruby","s","small","span","strong","strike","sub","sup","table","tt","u","ul","var"];

fn html_breakout_in_foreign(html: &str) -> Option<usize> {
    let lower = html.to_ascii_lowercase();
    for root in ["<svg", "<math"] {
        let close = format!("</{}", &root[1..]);
        let mut from = 0;
        while let Some(rel) = lower[from..].find(root) {
            let start = from + rel;
            let end = lower[start..].find(&close).map(|i| start + i).unwrap_or(lower.len());
            for (i, _) in lower[start..end].match_indices('<') {
                let rest = &lower[start + i + 1..];
                let rest = rest.strip_prefix('/').unwrap_or(rest);
                let name: String = rest.chars().take_while(char::is_ascii_alphanumeric).collect();
                if BREAKOUT.contains(&name.as_str()) {
                    return Some(start + i);
                }
            }
            from = end + close.len();
        }
    }
    None
}

Try / catch

use swc_html_parser::error::ErrorKind;

let mut errors = Vec::new();
let doc = swc_html_parser::parse_file_as_document(&fm, config, &mut errors)?;

for err in &errors {
    if let ErrorKind::HtmlStartTagInForeignContext(tag) = err.kind() {
        // The svg/math subtree was force-closed at this tag; check if that
        // truncated content you cared about.
        log::warn!("html tag <{tag}> closed foreign content");
    }
}

Prevention

When it happens

Trigger: Input like `<svg><p>hi</p></svg>`, `<math><div>x</div></math>`, or any HTML layout/typography tag opened between <svg>/<math> and its matching close tag. Very frequent when a closing </svg> or </math> is missing, so all subsequent page HTML is dispatched as foreign content until a breakout tag appears.

Common situations: Authors wrapping page layout in SVG by mistake, WYSIWYG editors injecting <br>/<p> inside SVG islands, template partials that conditionally drop the </svg>, appended HTML after an unclosed <math> formula.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/176bd65ee0c80410. Report an issue: GitHub.