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 f0b0fcb595f56..d1585cdea5bf5 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 @@ -189,11 +189,6 @@ public function current_node_is( string $identity ): bool { /** * Returns whether an element is in a specific scope. * - * ## HTML Support - * - * This function skips checking for the termination list because there - * are no supported elements which appear in the termination list. - * * @since 6.4.0 * * @see https://html.spec.whatwg.org/#has-an-element-in-the-specific-scope @@ -311,18 +306,22 @@ public function has_element_in_table_scope( $tag_name ) { /** * Returns whether a particular element is in select scope. * + * This test differs from the others like it, in that its rules are inverted. + * Instead of arriving at a match when one of any tag in a termination group + * is reached, this one terminates if any other tag is reached. + * + * > The stack of open elements is said to have a particular element in select scope when it has + * > that element in the specific scope consisting of all element types except the following: + * > - optgroup in the HTML namespace + * > - option in the HTML namespace + * * @since 6.4.0 Stub implementation (throws). * @since 6.7.0 Full implementation. * * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope * - * > The stack of open elements is said to have a particular element in select scope when it has - * > that element in the specific scope consisting of all element types except the following: - * > - optgroup in the HTML namespace - * > - option in the HTML namespace - * * @param string $tag_name Name of tag to check. - * @return bool Whether given element is in scope. + * @return bool Whether the given element is in SELECT scope. */ public function has_element_in_select_scope( $tag_name ) { foreach ( $this->walk_up() as $node ) { 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 2c2baadd2ce26..40538491152ad 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1351,18 +1351,11 @@ private function step_in_body() { $this->insert_html_element( $this->state->current_token ); $this->state->frameset_ok = false; - /* - * If the insertion mode is one of - * - "in table" - * - "in caption" - * - "in table body" - * - "in row" - * - "in cell" - * then switch the insertion mode to "in select in table" - * - * Otherwise, switch the insertion mode to "in select". - */ switch ( $this->state->insertion_mode ) { + /* + * > If the insertion mode is one of "in table", "in caption", "in table body", "in row", + * > or "in cell", then switch the insertion mode to "in select in table". + */ case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE: case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION: case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY: @@ -1370,6 +1363,10 @@ private function step_in_body() { case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL: $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE; break; + + /* + * > Otherwise, switch the insertion mode to "in select". + */ default: $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT; break; @@ -1496,13 +1493,13 @@ private function step_in_body() { } } - /* + /** * Parses next element in the 'in head' insertion mode. * * This internal function performs the 'in head' insertion mode * logic for the generalized WP_HTML_Processor::step() function. * - * @since 6.7.0 + * @since 6.7.0 Stub implementation. * * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. * @@ -1542,6 +1539,23 @@ private function step_in_select() { * > Any other character token */ case '#text': + $current_token = $this->bookmarks[ $this->state->current_token->bookmark_name ]; + + /* + * > A character token that is U+0000 NULL + * + * If a text node only comprises null bytes then it should be + * entirely ignored and should not return to calling code. + */ + if ( + 1 <= $current_token->length && + "\x00" === $this->html[ $current_token->start ] && + strspn( $this->html, "\x00", $current_token->start, $current_token->length ) === $current_token->length + ) { + // Parse error: ignore the token. + return $this->step(); + } + $this->insert_html_element( $this->state->current_token ); return true; @@ -1558,7 +1572,7 @@ private function step_in_select() { * > A DOCTYPE token */ case 'html': - // Parse error. Ignore the token. + // Parse error: ignore the token. return $this->step(); /* @@ -1615,6 +1629,7 @@ private function step_in_select() { $this->state->stack_of_open_elements->pop(); return true; } + // Parse error: ignore the token. return $this->step(); @@ -1626,31 +1641,36 @@ private function step_in_select() { $this->state->stack_of_open_elements->pop(); return true; } + // Parse error: ignore the token. return $this->step(); /* * > An end tag whose tag name is "select" * > A start tag whose tag name is "select" + * + * > It just gets treated like an end tag. */ case '-SELECT': case '+SELECT': if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) { + // Parse error: ignore the token. return $this->step(); } $this->state->stack_of_open_elements->pop_until( 'SELECT' ); - $this->state->stack_of_open_elements->pop(); $this->reset_insertion_mode(); return true; /* * > A start tag whose tag name is one of: "input", "keygen", "textarea" + * + * All three of these tags are considered a parse error when found in this insertion mode. */ case '+INPUT': case '+KEYGEN': case '+TEXTAREA': - // Parse error. if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) { + // Ignore the token. return $this->step(); } $this->state->stack_of_open_elements->pop_until( 'SELECT' ); @@ -2262,7 +2282,7 @@ private function close_a_p_element() { * Closes elements that have implied end tags. * * @since 6.4.0 - * @since 6.7.0 Support "option" and "optgroup". + * @since 6.7.0 Full spec support. * * @see https://html.spec.whatwg.org/#generate-implied-end-tags * @@ -2276,11 +2296,16 @@ private function generate_implied_end_tags( $except_for_this_element = null ) { 'OPTGROUP', 'OPTION', 'P', + 'RB', + 'RP', + 'RT', + 'RTC', ); - $current_node = $this->state->stack_of_open_elements->current_node(); + $no_exclusions = ! isset( $except_for_this_element ); + while ( - $current_node && $current_node->node_name !== $except_for_this_element && + ( $no_exclusions || ! $this->state->stack_of_open_elements->current_node_is( $except_for_this_element ) ) && in_array( $this->state->stack_of_open_elements->current_node(), $elements_with_implied_end_tags, true ) ) { $this->state->stack_of_open_elements->pop(); @@ -2294,18 +2319,31 @@ private function generate_implied_end_tags( $except_for_this_element = null ) { * different from generating end tags in the normal sense. * * @since 6.4.0 + * @since 6.7.0 Full spec support. * * @see WP_HTML_Processor::generate_implied_end_tags * @see https://html.spec.whatwg.org/#generate-implied-end-tags */ private function generate_implied_end_tags_thoroughly() { $elements_with_implied_end_tags = array( + 'CAPTION', + 'COLGROUP', 'DD', 'DT', 'LI', 'OPTGROUP', 'OPTION', 'P', + 'RB', + 'RP', + 'RT', + 'RTC', + 'TBODY', + 'TD', + 'TFOOT', + 'TH', + 'THEAD', + 'TR', ); while ( in_array( $this->state->stack_of_open_elements->current_node(), $elements_with_implied_end_tags, true ) ) {