Skip to content

Commit 89ea38c

Browse files
committed
HTML API: Preserve ::set_modifiable_text() TEXTAREA leading newlines.
HTML specifies that a single newline is ignored at the start of a `TEXTAREA`. If `::set_modifiable_text()` is called with a leading newline, ensure it is preserved in the resulting HTML. Developed in #11062. Props jonsurrell, dmsnell. See #64609. git-svn-id: https://develop.svn.wordpress.org/trunk@61754 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 33ebdd7 commit 89ea38c

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3877,6 +3877,17 @@ static function ( $tag_match ) {
38773877
$plaintext_content
38783878
);
38793879

3880+
/*
3881+
* HTML ignores a single leading newline in this context. If a leading newline
3882+
* is intended, preserve it by adding an extra newline.
3883+
*/
3884+
if (
3885+
'TEXTAREA' === $this->get_tag() &&
3886+
1 === strspn( $plaintext_content, "\n\r", 0, 1 )
3887+
) {
3888+
$plaintext_content = "\n{$plaintext_content}";
3889+
}
3890+
38803891
/*
38813892
* These don't _need_ to be escaped, but since they are decoded it's
38823893
* safe to leave them escaped and this can prevent other code from
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* Unit tests covering WP_HTML_Processor modifiable text functionality.
4+
*
5+
* @package WordPress
6+
* @subpackage HTML-API
7+
* @group html-api
8+
*
9+
* @coversDefaultClass WP_HTML_Processor
10+
*/
11+
class Tests_HtmlApi_WpHtmlProcessorModifiableText extends WP_UnitTestCase {
12+
/**
13+
* TEXTAREA elements ignore the first newline in their content.
14+
* Setting the modifiable text with a leading newline (or carriage return variants)
15+
* should ensure that the leading newline is present in the resulting TEXTAREA.
16+
*
17+
* TEXTAREA are treated as atomic tags by the tag processor, so `set_modifiable_text()`
18+
* is called directly on the TEXTAREA token.
19+
*
20+
* @ticket 64609
21+
*
22+
* @dataProvider data_modifiable_text_special_textarea
23+
*
24+
* @param string $set_text Text to set.
25+
* @param string $expected_html Expected HTML output.
26+
*/
27+
public function test_modifiable_text_special_textarea( string $set_text, string $expected_html ) {
28+
$processor = WP_HTML_Processor::create_fragment( '<textarea></textarea>' );
29+
$processor->next_token();
30+
$processor->set_modifiable_text( $set_text );
31+
$this->assertSame(
32+
strtr(
33+
$set_text,
34+
array(
35+
"\r\n" => "\n",
36+
"\r" => "\n",
37+
)
38+
),
39+
$processor->get_modifiable_text(),
40+
'Should have preserved or normalized the leading newline in the TEXTAREA content.'
41+
);
42+
$this->assertEqualHTML(
43+
$expected_html,
44+
$processor->get_updated_html(),
45+
'<body>',
46+
'Should have correctly output the TEXTAREA HTML.'
47+
);
48+
}
49+
50+
/**
51+
* Data provider.
52+
*
53+
* @return array[]
54+
*/
55+
public static function data_modifiable_text_special_textarea() {
56+
return array(
57+
'Leading newline' => array(
58+
"\nAFTER NEWLINE",
59+
"<textarea>\n\nAFTER NEWLINE</textarea>",
60+
),
61+
'Leading carriage return' => array(
62+
"\rCR",
63+
"<textarea>\n\nCR</textarea>",
64+
),
65+
'Leading carriage return + newline' => array(
66+
"\r\nCR-N",
67+
"<textarea>\n\nCR-N</textarea>",
68+
),
69+
);
70+
}
71+
}

tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,4 +636,32 @@ public function test_json_auto_escaping() {
636636
$decoded_json_from_html
637637
);
638638
}
639+
640+
/**
641+
* TEXTAREA elements ignore the first newline in their content.
642+
* Setting the modifiable text with a leading newline should ensure that the leading newline
643+
* is present in the resulting element.
644+
*
645+
* @ticket 64609
646+
*/
647+
public function test_modifiable_text_special_textarea() {
648+
$processor = new WP_HTML_Tag_Processor( '<textarea></textarea>' );
649+
$processor->next_token();
650+
$processor->set_modifiable_text( "\nAFTER NEWLINE" );
651+
$this->assertSame(
652+
"\nAFTER NEWLINE",
653+
$processor->get_modifiable_text(),
654+
'Should have preserved the leading newline in the content.'
655+
);
656+
$this->assertEqualHTML(
657+
<<<'HTML'
658+
<textarea>
659+
660+
AFTER NEWLINE</textarea>
661+
HTML,
662+
$processor->get_updated_html(),
663+
'<body>',
664+
'Should have preserved the leading newline in the content.'
665+
);
666+
}
639667
}

0 commit comments

Comments
 (0)