From 12884f0361c0bd965e87ad0b99578f81328a0ab0 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Thu, 10 Aug 2023 08:37:20 +0000 Subject: [PATCH] HTML API: Add support for BUTTON element. This patch adds support to process the BUTTON element. This requires adding some additional semantic rules to handle situations where a BUTTON element is already in scope. Also included is a fixup to enforce that `WP_HTML_Processor::next_tag()` never returns for a tag closer. This is useful with the Tag Processor, but not for the HTML Processor. There were tests relying on this behavior to assert that internal processes were working as they should, but those tests have been updated to use the semi-private `step()` function, which does stop on tag closers. This patch is one in a series of changes to expand support within the HTML API, moving gradually to allow for more focused changes that are easier to review and test. The HTML Processor is a work in progress with a certain set of features slated to be ready and tested by 6.4.0, but it will only contain partial support of the HTML5 specification even after that. Whenever it cannot positively recognize and process its input it bails, and certain function stubs and logical stubs exist to structure future expansions of support. Props dmsnell. Fixes #58961. Built from https://develop.svn.wordpress.org/trunk@56380 git-svn-id: http://core.svn.wordpress.org/trunk@55892 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- .../html-api/class-wp-html-open-elements.php | 31 ++++++++------ .../class-wp-html-processor-state.php | 13 ++++++ .../html-api/class-wp-html-processor.php | 41 +++++++++++++++++-- wp-includes/version.php | 2 +- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/wp-includes/html-api/class-wp-html-open-elements.php b/wp-includes/html-api/class-wp-html-open-elements.php index 0518ccb8bf..035717b444 100644 --- a/wp-includes/html-api/class-wp-html-open-elements.php +++ b/wp-includes/html-api/class-wp-html-open-elements.php @@ -115,6 +115,15 @@ class WP_HTML_Open_Elements { if ( $node->node_name === $tag_name ) { return true; } + + switch ( $node->node_name ) { + case 'HTML': + return false; + } + + if ( in_array( $node->node_name, $termination_list, true ) ) { + return true; + } } return false; @@ -175,19 +184,7 @@ class WP_HTML_Open_Elements { * @return bool Whether given element is in scope. */ public function has_element_in_button_scope( $tag_name ) { - return $this->has_element_in_specific_scope( - $tag_name, - array( - - /* - * Because it's not currently possible to encounter - * one of the termination elements, they don't need - * to be listed here. If they were, they would be - * unreachable and only waste CPU cycles while - * scanning through HTML. - */ - ) - ); + return $this->has_element_in_specific_scope( $tag_name, array( 'BUTTON' ) ); } /** @@ -394,6 +391,10 @@ class WP_HTML_Open_Elements { * cases where the precalculated value needs to change. */ switch ( $item->node_name ) { + case 'BUTTON': + $this->has_p_in_button_scope = false; + break; + case 'P': $this->has_p_in_button_scope = true; break; @@ -419,6 +420,10 @@ class WP_HTML_Open_Elements { * cases where the precalculated value needs to change. */ switch ( $item->node_name ) { + case 'BUTTON': + $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); + break; + case 'P': $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); break; diff --git a/wp-includes/html-api/class-wp-html-processor-state.php b/wp-includes/html-api/class-wp-html-processor-state.php index b9fa53d0bd..3fe5192431 100644 --- a/wp-includes/html-api/class-wp-html-processor-state.php +++ b/wp-includes/html-api/class-wp-html-processor-state.php @@ -107,6 +107,19 @@ class WP_HTML_Processor_State { */ public $context_node = null; + /** + * The frameset-ok flag indicates if a `FRAMESET` element is allowed in the current state. + * + * > The frameset-ok flag is set to "ok" when the parser is created. It is set to "not ok" after certain tokens are seen. + * + * @since 6.4.0 + * + * @see https://html.spec.whatwg.org/#frameset-ok-flag + * + * @var bool + */ + public $frameset_ok = true; + /** * Constructor - creates a new and empty state value. * diff --git a/wp-includes/html-api/class-wp-html-processor.php b/wp-includes/html-api/class-wp-html-processor.php index 7a8eb34596..6e1723494c 100644 --- a/wp-includes/html-api/class-wp-html-processor.php +++ b/wp-includes/html-api/class-wp-html-processor.php @@ -349,7 +349,13 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { */ public function next_tag( $query = null ) { if ( null === $query ) { - return $this->step(); + while ( $this->step() ) { + if ( ! $this->is_tag_closer() ) { + return true; + } + } + + return false; } if ( is_string( $query ) ) { @@ -366,7 +372,13 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { } if ( ! ( array_key_exists( 'breadcrumbs', $query ) && is_array( $query['breadcrumbs'] ) ) ) { - return $this->step(); + while ( $this->step() ) { + if ( ! $this->is_tag_closer() ) { + return true; + } + } + + return false; } if ( isset( $query['tag_closers'] ) && 'visit' === $query['tag_closers'] ) { @@ -383,7 +395,7 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { $crumb = end( $breadcrumbs ); $target = strtoupper( $crumb ); - while ( $this->step() ) { + while ( $match_offset > 0 && $this->step() ) { if ( $target !== $this->get_tag() ) { continue; } @@ -395,7 +407,7 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { } $crumb = prev( $breadcrumbs ); - if ( false === $crumb && 0 === --$match_offset ) { + if ( false === $crumb && 0 === --$match_offset && ! $this->is_tag_closer() ) { return true; } } @@ -510,6 +522,22 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { $op = "{$op_sigil}{$tag_name}"; switch ( $op ) { + /* + * > A start tag whose tag name is "button" + */ + case '+BUTTON': + if ( $this->state->stack_of_open_elements->has_element_in_scope( 'BUTTON' ) ) { + // @TODO: Indicate a parse error once it's possible. This error does not impact the logic here. + $this->generate_implied_end_tags(); + $this->state->stack_of_open_elements->pop_until( 'BUTTON' ); + } + + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->current_token ); + $this->state->frameset_ok = false; + + return true; + /* * > A start tag whose tag name is one of: "address", "article", "aside", * > "blockquote", "center", "details", "dialog", "dir", "div", "dl", @@ -535,15 +563,20 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { * > "menu", "nav", "ol", "pre", "search", "section", "summary", "ul" */ case '-BLOCKQUOTE': + case '-BUTTON': case '-DIV': case '-FIGCAPTION': case '-FIGURE': if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name ) ) { + // @TODO: Report parse error. // Ignore the token. return $this->step(); } $this->generate_implied_end_tags(); + if ( $this->state->stack_of_open_elements->current_node()->node_name !== $tag_name ) { + // @TODO: Record parse error: this error doesn't impact parsing. + } $this->state->stack_of_open_elements->pop_until( $tag_name ); return true; diff --git a/wp-includes/version.php b/wp-includes/version.php index 456790bc59..7618d87979 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.4-alpha-56379'; +$wp_version = '6.4-alpha-56380'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.