diff --git a/src/wp-includes/html-api/class-wp-html-open-elements.php b/src/wp-includes/html-api/class-wp-html-open-elements.php index 5ce1f8feb552c..cb913853f0ee9 100644 --- a/src/wp-includes/html-api/class-wp-html-open-elements.php +++ b/src/wp-includes/html-api/class-wp-html-open-elements.php @@ -530,31 +530,31 @@ public function pop(): bool { } /** - * Pops nodes off of the stack of open elements until one with the given tag name has been popped. + * Pops nodes off of the stack of open elements until an HTML tag with the given name has been popped. * * @since 6.4.0 * * @see WP_HTML_Open_Elements::pop * - * @param string $tag_name Name of tag that needs to be popped off of the stack of open elements. + * @param string $html_tag_name Name of tag that needs to be popped off of the stack of open elements. * @return bool Whether a tag of the given name was found and popped off of the stack of open elements. */ - public function pop_until( string $tag_name ): bool { + public function pop_until( string $html_tag_name ): bool { foreach ( $this->walk_up() as $item ) { - if ( 'context-node' === $item->bookmark_name ) { - return true; - } - $this->pop(); + if ( 'html' !== $item->namespace ) { + continue; + } + if ( - '(internal: H1 through H6 - do not use)' === $tag_name && + '(internal: H1 through H6 - do not use)' === $html_tag_name && in_array( $item->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) { return true; } - if ( $tag_name === $item->node_name ) { + if ( $html_tag_name === $item->node_name ) { return true; } } 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 55b906136820f..cb581fac3988f 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5428,6 +5428,11 @@ private function reset_insertion_mode_appropriately(): void { } } + // All of the following rules are for matching HTML elements. + if ( 'html' !== $node->namespace ) { + continue; + } + switch ( $node->node_name ) { /* * > 4. If node is a `select` element, run these substeps: @@ -5443,6 +5448,10 @@ private function reset_insertion_mode_appropriately(): void { case 'SELECT': if ( ! $last ) { foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $ancestor ) { + if ( 'html' !== $ancestor->namespace ) { + continue; + } + switch ( $ancestor->node_name ) { /* * > 5. If _ancestor_ is a `template` node, jump to the step below diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index e9b9063f77a7b..17dd2ff7fbd68 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -520,6 +520,30 @@ public function test_foreign_content_script_self_closing() { $this->assertTrue( $processor->next_tag( 'script' ) ); } + /** + * Ensures that the HTML Processor correctly handles TEMPLATE tag closing and namespaces. + * + * This is a tricky test case that corresponds to the html5lib tests "template/line1466". + * + * When the `` token is reached it is in the HTML namespace (thanks to the + * SVG `foreignObject` element). It is not handled as foreign content; therefore, it + * closes the open HTML `TEMPLATE` element (the first `