Skip to content

Commit

Permalink
HTML API: Only examine HTML nodes in pop_until() instack of open el…
Browse files Browse the repository at this point in the history
…ements.

The `pop_until( $tag_name )` method in the stack of open elements should only be examining HTML elements, but it has only been checking the tag name. This has led to closing the wrong tags when run from inside foreign content. A very specific situation where this may arise is when a `TEMPLATE` closer is found inside foreign content, inside another template.

{{{
HTML:template   SVG:template                 HTML:/template
<template><svg><template><foreignObject><div></template><div>
╰──< this outer TEMPLATE is closed by this one >───╯
}}}

This patch constains the method to checking for elements matching the tag name which are in the HTML namespace so that the proper detection occurs.

Developed in WordPress#7286
Discussed in https://core.trac.wordpress.org/ticket/61576

Follow-up to [58867].

Props dmsnell, jonsurrell.
See #61576.


git-svn-id: https://develop.svn.wordpress.org/trunk@58992 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
dmsnell committed Sep 4, 2024
1 parent 769f017 commit b73edfb
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 9 deletions.
18 changes: 9 additions & 9 deletions src/wp-includes/html-api/class-wp-html-open-elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/wp-includes/html-api/class-wp-html-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions tests/phpunit/tests/html-api/wpHtmlProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 `</template>` 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 `<template>` token) - _not_ the
* SVG `TEMPLATE` element (the second `<template>` token).
*
* The test is included here because it may show up as unsupported markup and be skipped by
* the html5lib test suite.
*
* @ticket 61576
*/
public function test_template_tag_closes_html_template_element() {
$processor = WP_HTML_Processor::create_fragment( '<template><svg><template><foreignObject><div></template><div>' );

$this->assertTrue( $processor->next_tag( 'DIV' ) );
$this->assertSame( array( 'HTML', 'BODY', 'TEMPLATE', 'SVG', 'TEMPLATE', 'FOREIGNOBJECT', 'DIV' ), $processor->get_breadcrumbs() );
$this->assertTrue( $processor->next_tag( 'DIV' ) );
$this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $processor->get_breadcrumbs() );
}

/**
* Ensures that the tag processor is case sensitive when removing CSS classes in no-quirks mode.
*
Expand Down

0 comments on commit b73edfb

Please sign in to comment.