[php-src] Issue #21379: `Dom\HTMLDocument` misparses `</noscript>` in `<head>` and nests following nodes into `<noscript>`

[email protected] (NoNoNo)
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/21379
Author: NoNoNo

### Description

## Summary

When parsing HTML with `Dom\HTMLDocument::createFromString()`, a `</noscript>` end tag inside `<head>` is not handled correctly in the HTML5 parser path.

As a result, subsequent head elements (for example `<link>`) are incorrectly inserted as children of `<noscript>`.

This is a behavior bug in the Lexbor HTML tree-construction path used by `Dom\HTMLDocument`, not in legacy `DOMDocument::loadHTML()`.

## Affected component

- PHP `ext/dom` modern HTML5 parser (`Dom\HTMLDocument`)
- Vendored Lexbor tree insertion mode implementation:
  - `ext/lexbor/lexbor/html/tree/insertion_mode/in_head_noscript.c`

## Environment

- PHP: `8.5.1` (also reproduced while inspecting 8.5.3 source tree)
- libxml runtime: `2.9.13`
- API used: `Dom\HTMLDocument::createFromString()`

## Reproducer

```php
<?php

$html = '<!DOCTYPE html><html><head>
<noscript>
    <style>body { margin: 0; }</style>
</noscript>
<link href="/style.css" rel="stylesheet">
</head><body></body></html>';

$doc = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
echo $doc->saveHTML(), PHP_EOL;

$link = $doc->getElementsByTagName('link')->item(0);
echo "Link parent: ", $link->parentNode->nodeName, PHP_EOL;
```

https://3v4l.org/TmBjH#v8.5.3

## Actual result

- Serialized tree effectively moves `</noscript>` to after `<link>`.
- `link->parentNode->nodeName` is `NOSCRIPT`.

Example output:

```html
<!DOCTYPE html><html><head>
<noscript>
    <style>body { margin: 0; }</style>

<link href="/style.css" rel="stylesheet">
</noscript></head><body></body></html>
Link parent: NOSCRIPT
```

## Expected result

- `</noscript>` should close the `<noscript>` element.
- `<link>` should be a direct child of `<head>`.
- `link->parentNode->nodeName` should be `HEAD`.

## Control comparison

Using legacy parser path:

```php
$d = new DOMDocument();
@$d->loadHTML($html, LIBXML_NOERROR);
echo $d->getElementsByTagName('link')->item(0)->parentNode->nodeName;
```

Result is `head` (as expected), confirming issue is specific to modern HTML5 parser path.

## Root cause analysis

The closing-tag handler for **in-head-noscript insertion mode** does not implement handling for `</noscript>`.

Current code:

- `ext/lexbor/lexbor/html/tree/insertion_mode/in_head_noscript.c:95`
- `lxb_html_tree_insertion_mode_in_head_noscript_closed(...)`

Behavior:

1. If closing tag is `</br>`, it routes to `anything_else`.
2. Otherwise it emits parse error (`LXB_HTML_RULES_ERROR_UNTO`) and returns `true`.
3. It never handles `LXB_TAG_NOSCRIPT`, never pops `<noscript>`, and never restores `tree->mode = in_head`.

Because the open-elements stack still has `<noscript>` as current node, the next `<link>` token (delegated to `in_head`) is inserted under `<noscript>`.

## Suggested fix direction

In `lxb_html_tree_insertion_mode_in_head_noscript_closed(...)`, add explicit handling for `LXB_TAG_NOSCRIPT`:

1. Verify current node is `noscript` (or report parse error if not in expected state).
2. Pop current node from open-elements stack.
3. Set `tree->mode = lxb_html_tree_insertion_mode_in_head`.
4. Return `true`.

This should match intended HTML5 tree-construction behavior for closing `noscript` in this insertion mode.

## Suggested regression test

Add a DOM test that parses:

```html
<!doctype html><html><head><noscript></noscript><link rel="stylesheet" href="/x.css"></head><body></body></html>
```

And asserts:

- `getElementsByTagName("link")[0]->parentNode->nodeName === "HEAD"`
- serialization does not place `<link>` inside `<noscript>`.

## Notes

- This issue is independent from libxml2 legacy HTML parser behavior.
- It appears in the Lexbor-based parser path used by `Dom\HTMLDocument`.


### PHP Version

```plain
PHP 8.5.1 (cli) (built: Dec 16 2025 15:59:07) (NTS)
Copyright (c) The PHP Group
Built by Homebrew
Zend Engine v4.5.1, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.1, Copyright (c), by Zend Technologies

Also in 8.5.3, compare https://3v4l.org/TmBjH#v8.5.3
```

### Operating System

_No response_
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.