From 0deab0a60878082c4cf87da10669bdc929e0c891 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 30 Jan 2024 15:53:48 +0100 Subject: [PATCH 1/5] Use $p variable for processor like other tests --- tests/phpunit/tests/html-api/wpHtmlProcessor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index 109f384e25e3c..67d38e29d5e09 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -227,9 +227,9 @@ public function data_void_tags() { * @param string $tag_name Name of the tag to test. */ public function test_step_in_body_fails_on_unsupported_tags( $tag_name ) { - $fragment = WP_HTML_Processor::create_fragment( '<' . $tag_name . '>' ); - $this->assertFalse( $fragment->next_tag(), 'Should fail to find tag: ' . $tag_name . '.' ); - $this->assertEquals( $fragment->get_last_error(), WP_HTML_Processor::ERROR_UNSUPPORTED, 'Should have unsupported last error.' ); + $p = WP_HTML_Processor::create_fragment( '<' . $tag_name . '>' ); + $this->assertFalse( $p->next_tag(), 'Should fail to find tag: ' . $tag_name . '.' ); + $this->assertEquals( $p->get_last_error(), WP_HTML_Processor::ERROR_UNSUPPORTED, 'Should have unsupported last error.' ); } /** From e0668b6ec5fec3b439220b05f389e534581e7a60 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 30 Jan 2024 16:51:02 +0100 Subject: [PATCH 2/5] Add failing test --- .../tests/html-api/wpHtmlProcessor.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index 67d38e29d5e09..895d00f5cf46f 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -188,6 +188,59 @@ public function test_cannot_nest_void_tags( $tag_name ) { ); } + /** + * Ensure non-nesting tags do not nest when processing tokens. + * + * @ticket 60283 + * + * @dataProvider data_void_tags + * + * @param string $tag_name Name of void tag under test. + */ + public function test_cannot_nest_void_tags_next_token( $tag_name ) { + $processor = WP_HTML_Processor::create_fragment( "<{$tag_name}>
" ); + + /* + * This HTML represents the same as the following HTML, + * assuming that it were provided `` as the tag: + * + * + * + * + *
+ * + * + */ + + $found_tag = $processor->next_token(); + + if ( WP_HTML_Processor::ERROR_UNSUPPORTED === $processor->get_last_error() ) { + $this->markTestSkipped( "Tag {$tag_name} is not supported." ); + } + + $this->assertTrue( + $found_tag, + "Could not find first {$tag_name}." + ); + + $this->assertSame( + array( 'HTML', 'BODY', $tag_name ), + $processor->get_breadcrumbs(), + 'Found incorrect nesting of first element.' + ); + + $this->assertTrue( + $processor->next_token(), + 'Should have found the DIV as the second tag.' + ); + + $this->assertSame( + array( 'HTML', 'BODY', 'DIV' ), + $processor->get_breadcrumbs(), + "DIV should have been a sibling of the {$tag_name}." + ); + } + /** * Data provider. * From 7890b53d218cf2db687b3bed7571924e603864f5 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 30 Jan 2024 16:55:03 +0100 Subject: [PATCH 3/5] Update ticket --- tests/phpunit/tests/html-api/wpHtmlProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index 895d00f5cf46f..696fb1c26e609 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -191,7 +191,7 @@ public function test_cannot_nest_void_tags( $tag_name ) { /** * Ensure non-nesting tags do not nest when processing tokens. * - * @ticket 60283 + * @ticket 60382 * * @dataProvider data_void_tags * From 31d71de1f8cb3e08c353adebe167f3ecf8d15334 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 30 Jan 2024 15:34:23 -0700 Subject: [PATCH 4/5] Ensure void and self-closing elements pop from stack when advancing. Previously, the logic to pop void and self-closing elements from the stack of open elements only ran when stepping into the next node in a document. With the introduction of `next_token()` there appeared a new way to reprocesses the current token, so this logic would be skipped when calling `next_token()` _into_ a void or self-closing element, leaving it on the stack. In this patch the logic runs whenever the processor is not reprocessing the current token. A new class constant communicates that `step()` should treat the current token as if it arrived there itself, that is, to process it with the normal rules but without advancing the parser. --- .../html-api/class-wp-html-processor.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 4bde9e1c099c8..6b0879cde892f 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -431,7 +431,7 @@ public function next_token() { $found_a_token = parent::next_token(); if ( '#tag' === $this->get_token_type() ) { - $this->step( self::REPROCESS_CURRENT_NODE ); + $this->step( self::PROCESS_CURRENT_NODE ); } return $found_a_token; @@ -513,7 +513,7 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { return false; } - if ( self::PROCESS_NEXT_NODE === $node_to_process ) { + if ( self::REPROCESS_CURRENT_NODE !== $node_to_process ) { /* * Void elements still hop onto the stack of open elements even though * there's no corresponding closing tag. This is important for managing @@ -532,7 +532,9 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { if ( $top_node && self::is_void( $top_node->node_name ) ) { $this->state->stack_of_open_elements->pop(); } + } + if ( self::PROCESS_NEXT_NODE === $node_to_process ) { while ( parent::next_token() && '#tag' !== $this->get_token_type() ) { continue; } @@ -1781,6 +1783,15 @@ public static function is_void( $tag_name ) { */ const REPROCESS_CURRENT_NODE = 'reprocess-current-node'; + /** + * Indicates that the current HTML token should be processed without advancing the parser. + * + * @since 6.5.0 + * + * @var string + */ + const PROCESS_CURRENT_NODE = 'process-current-node'; + /** * Indicates that the parser encountered unsupported markup and has bailed. * From 19bea7f73658b217afc04f88ba6c195cd990d8d8 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 31 Jan 2024 10:26:06 +0100 Subject: [PATCH 5/5] Revert "Use $p variable for processor like other tests" This reverts commit 0deab0a60878082c4cf87da10669bdc929e0c891. --- tests/phpunit/tests/html-api/wpHtmlProcessor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index 696fb1c26e609..60d368efae1cc 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -280,9 +280,9 @@ public function data_void_tags() { * @param string $tag_name Name of the tag to test. */ public function test_step_in_body_fails_on_unsupported_tags( $tag_name ) { - $p = WP_HTML_Processor::create_fragment( '<' . $tag_name . '>' ); - $this->assertFalse( $p->next_tag(), 'Should fail to find tag: ' . $tag_name . '.' ); - $this->assertEquals( $p->get_last_error(), WP_HTML_Processor::ERROR_UNSUPPORTED, 'Should have unsupported last error.' ); + $fragment = WP_HTML_Processor::create_fragment( '<' . $tag_name . '>' ); + $this->assertFalse( $fragment->next_tag(), 'Should fail to find tag: ' . $tag_name . '.' ); + $this->assertEquals( $fragment->get_last_error(), WP_HTML_Processor::ERROR_UNSUPPORTED, 'Should have unsupported last error.' ); } /**