Skip to content

Commit 23a6bbf

Browse files
committed
HTML API: Prevent HTML newline normalization on foreign elements.
HTML and foreign element normalization differ in some cases. Ensure the HTML-specific newline injection is not applied to foreign elements like `svg:textarea`. Developed in WordPress#12322. Follow-up to [61747]. See #65372. git-svn-id: https://develop.svn.wordpress.org/trunk@62574 602fd350-edb4-49c9-b593-d223f7449a82
1 parent db48dcf commit 23a6bbf

2 files changed

Lines changed: 40 additions & 15 deletions

File tree

src/wp-includes/html-api/class-wp-html-processor.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,8 +1468,8 @@ public function serialize_token(): string {
14681468

14691469
/*
14701470
* The HTML parser strips a leading newline immediately after the start
1471-
* tag of TEXTAREA, PRE, and LISTING elements. When serializing, prepend
1472-
* a leading newline to ensure the semantic HTML content is preserved.
1471+
* tag of TEXTAREA, PRE, and LISTING elements in HTML content. When serializing,
1472+
* prepend a leading newline to ensure the semantic HTML content is preserved.
14731473
*
14741474
* For example, `<pre>\n\nX</pre>` must not become `<pre>\nX</pre>` because its content
14751475
* has changed. However, `<pre>X</pre>` and `<pre>\nX</pre>` are _equivalent_.
@@ -1488,7 +1488,7 @@ public function serialize_token(): string {
14881488
*
14891489
* @see https://html.spec.whatwg.org/multipage/parsing.html
14901490
*/
1491-
if ( 'TEXTAREA' === $tag_name || 'PRE' === $tag_name || 'LISTING' === $tag_name ) {
1491+
if ( $in_html && ( 'TEXTAREA' === $tag_name || 'PRE' === $tag_name || 'LISTING' === $tag_name ) ) {
14921492
$html .= "\n";
14931493
}
14941494

tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -463,21 +463,30 @@ public static function data_provider_serialize_doctype() {
463463
}
464464

465465
/**
466-
* Ensures that leading newlines in PRE, LISTING, and TEXTAREA elements are preserved upon normalization,
467-
* and that normalization is idempotent in these cases.
466+
* Ensures that leading newlines in PRE, LISTING, and TEXTAREA elements are normalized
467+
* according to their parsing namespace, and that normalization is idempotent in these cases.
468468
*
469469
* @ticket 64607
470470
*
471471
* @dataProvider data_provider_normalize_special_leading_newline_cases
472472
*
473473
* @param string $input HTML input containing leading newlines in PRE, LISTING, or TEXTAREA elements.
474-
* @param string $expected Expected output after normalization, which should preserve leading newlines.
474+
* @param string $expected Expected exact output after normalization.
475475
*/
476476
public function test_normalize_special_leading_newline_handling( string $input, string $expected ) {
477477
$normalized = WP_HTML_Processor::normalize( $input );
478-
$this->assertEqualHTML( $expected, $normalized );
478+
479+
/*
480+
* Byte equality pins normalize()'s serialized form; HTML equality verifies
481+
* semantic equivalence. This distinction matters because HTML parsing ignores
482+
* one leading LF after PRE, LISTING, and TEXTAREA start tags.
483+
*/
484+
$this->assertSame( $expected, $normalized );
485+
$this->assertEqualHTML( $input, $normalized );
486+
479487
$normalized_twice = WP_HTML_Processor::normalize( $normalized );
480-
$this->assertEqualHTML( $expected, $normalized_twice );
488+
$this->assertSame( $expected, $normalized_twice );
489+
$this->assertEqualHTML( $normalized, $normalized_twice );
481490
}
482491

483492
/**
@@ -653,50 +662,66 @@ public static function data_provider_normalized_fuzzer_cases_that_should_be_idem
653662
/**
654663
* Data provider.
655664
*
656-
* @return array[]
665+
* @return array<string, array{string, string}>
657666
*/
658-
public static function data_provider_normalize_special_leading_newline_cases() {
667+
public static function data_provider_normalize_special_leading_newline_cases(): array {
659668
return array(
660669
'Leading newline in PRE' => array(
661670
"<pre>\nline 1\nline 2</pre>",
662-
"<pre>line 1\nline 2</pre>",
671+
"<pre>\nline 1\nline 2</pre>",
663672
),
664673
'Double leading newline in PRE' => array(
665674
"<pre>\n\nline 2\nline 3</pre>",
666675
"<pre>\n\nline 2\nline 3</pre>",
667676
),
668677
'Multiple text nodes inside PRE' => array(
669678
"<pre>\nline 1<!--comment--> still line 1</pre>",
670-
'<pre>line 1<!--comment--> still line 1</pre>',
679+
"<pre>\nline 1<!--comment--> still line 1</pre>",
671680
),
672681
'Multiple text nodes inside PRE with leading newlines' => array(
673682
"<pre>\n\nline 2<!--comment--> still line 2</pre>",
674683
"<pre>\n\nline 2<!--comment--> still line 2</pre>",
675684
),
676685
'Leading newline in LISTING' => array(
677686
"<listing>\nline 1\nline 2</listing>",
678-
"<listing>line 1\nline 2</listing>",
687+
"<listing>\nline 1\nline 2</listing>",
679688
),
680689
'Double leading newline in LISTING' => array(
681690
"<listing>\n\nline 2\nline 3</listing>",
682691
"<listing>\n\nline 2\nline 3</listing>",
683692
),
684693
'Multiple text nodes inside LISTING' => array(
685694
"<listing>\nline 1<!--comment--> still line 1</listing>",
686-
'<listing>line 1<!--comment--> still line 1</listing>',
695+
"<listing>\nline 1<!--comment--> still line 1</listing>",
687696
),
688697
'Multiple text nodes inside LISTING with leading newlines' => array(
689698
"<listing>\n\nline 2<!--comment--> still line 2</listing>",
690699
"<listing>\n\nline 2<!--comment--> still line 2</listing>",
691700
),
692701
'Leading newline in TEXTAREA' => array(
693702
"<textarea>\nline 1\nline 2</textarea>",
694-
"<textarea>line 1\nline 2</textarea>",
703+
"<textarea>\nline 1\nline 2</textarea>",
695704
),
696705
'Double leading newline in TEXTAREA' => array(
697706
"<textarea>\n\nline 2\nline 3</textarea>",
698707
"<textarea>\n\nline 2\nline 3</textarea>",
699708
),
709+
'Foreign MathML TEXTAREA does not ignore leading newlines' => array(
710+
'<math><textarea>X</textarea></math>',
711+
'<math><textarea>X</textarea></math>',
712+
),
713+
'Foreign MathML TEXTAREA preserves leading newline' => array(
714+
"<math><textarea>\nX</textarea></math>",
715+
"<math><textarea>\nX</textarea></math>",
716+
),
717+
'Foreign SVG TEXTAREA does not ignore leading newlines' => array(
718+
'<svg><textarea>X</textarea></svg>',
719+
'<svg><textarea>X</textarea></svg>',
720+
),
721+
'Foreign SVG TEXTAREA preserves leading newline' => array(
722+
"<svg><textarea>\nX</textarea></svg>",
723+
"<svg><textarea>\nX</textarea></svg>",
724+
),
700725
);
701726
}
702727
}

0 commit comments

Comments
 (0)