Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WP_HTML_Tag_Processor: Allow non-attribute lexical updates #47068

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,18 @@ class Gutenberg_HTML_Tag_Processor_6_3 {
*/
protected $lexical_updates = array();

/**
* Attribute replacements to apply to input HTML document.
*
* Unlike more generic lexical updates, attribute updates are stored
* in an associative array, where the keys are (lowercase-normalized)
* attribute names, in order to avoid duplication.
*
* @since 6.3.0
* @var WP_HTML_Text_Replacement[]
*/
private $attribute_updates = array();

/**
* Tracks and limits `seek()` calls to prevent accidental infinite loops.
*
Expand Down Expand Up @@ -1237,15 +1249,16 @@ private function skip_whitespace() {
}

/**
* Applies attribute updates and cleans up once a tag is fully parsed.
* Applies lexical updates and cleans up once a tag is fully parsed.
*
* @since 6.2.0
*
* @return void
*/
private function after_tag() {
$this->class_name_updates_to_attributes_updates();
$this->apply_attributes_updates();
$this->class_name_updates_to_attribute_updates();
$this->attribute_updates_to_lexical_updates();
$this->apply_lexical_updates();
$this->tag_name_starts_at = null;
$this->tag_name_length = null;
$this->tag_ends_at = null;
Expand All @@ -1254,17 +1267,17 @@ private function after_tag() {
}

/**
* Converts class name updates into tag attributes updates
* Converts class name updates into tag attribute updates
* (they are accumulated in different data formats for performance).
*
* @see $lexical_updates
* @see $attribute_updates
* @see $classname_updates
*
* @since 6.2.0
*
* @return void
*/
private function class_name_updates_to_attributes_updates() {
private function class_name_updates_to_attribute_updates() {
if ( count( $this->classname_updates ) === 0 ) {
return;
}
Expand Down Expand Up @@ -1398,14 +1411,33 @@ private function class_name_updates_to_attributes_updates() {
}

/**
* Applies attribute updates to HTML document.
* Converts attribute updates into lexical updates.
*
* This method is only meant to run right before the attribute updates are applied.
* The behavior in all other cases is undefined.
*
* @return void
* @since 6.3.0
*
* @see $attribute_updates
* @see $lexical_updates
*/
private function attribute_updates_to_lexical_updates() {
foreach ( $this->attribute_updates as $update ) {
$this->lexical_updates[] = $update;
}
$this->attribute_updates = array();
}

/**
* Applies lexical updates to HTML document.
*
* @since 6.2.0
* @since 6.3.0 Invalidate any bookmarks whose targets are overwritten.
*
* @return void
*/
private function apply_attributes_updates() {
private function apply_lexical_updates() {
if ( ! count( $this->lexical_updates ) ) {
return;
}
Expand Down Expand Up @@ -1527,8 +1559,8 @@ public function seek( $bookmark_name ) {
*
* @since 6.2.0
*
* @param WP_HTML_Text_Replacement $a First attribute update.
* @param WP_HTML_Text_Replacement $b Second attribute update.
* @param WP_HTML_Text_Replacement $a First lexical update.
* @param WP_HTML_Text_Replacement $b Second lexical update.
* @return int Comparison value for string order.
*/
private static function sort_start_ascending( $a, $b ) {
Expand Down Expand Up @@ -1564,11 +1596,11 @@ private static function sort_start_ascending( $a, $b ) {
* @return string|boolean|null Value of enqueued update if present, otherwise false.
*/
private function get_enqueued_attribute_value( $comparable_name ) {
if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) {
if ( ! isset( $this->attribute_updates[ $comparable_name ] ) ) {
return false;
}

$enqueued_text = $this->lexical_updates[ $comparable_name ]->text;
$enqueued_text = $this->attribute_updates[ $comparable_name ]->text;

// Removed attributes erase the entire span.
if ( '' === $enqueued_text ) {
Expand Down Expand Up @@ -1641,7 +1673,7 @@ public function get_attribute( $name ) {

/*
* For every attribute other than `class` it's possible to perform a quick check if
* there's an enqueued lexical update whose value takes priority over what's found in
* there's an enqueued attribute update whose value takes priority over what's found in
* the input document.
*
* The `class` attribute is special though because of the exposed helpers `add_class`
Expand All @@ -1651,7 +1683,7 @@ public function get_attribute( $name ) {
* into an attribute value update.
*/
if ( 'class' === $name ) {
$this->class_name_updates_to_attributes_updates();
$this->class_name_updates_to_attribute_updates();
}

// Return any enqueued attribute value updates if they exist.
Expand Down Expand Up @@ -1879,8 +1911,8 @@ public function set_attribute( $name, $value ) {
*
* Result: <div id="new"/>
*/
$existing_attribute = $this->attributes[ $comparable_name ];
$this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement(
$existing_attribute = $this->attributes[ $comparable_name ];
$this->attribute_updates[ $name ] = new WP_HTML_Text_Replacement(
$existing_attribute->start,
$existing_attribute->end,
$updated_attribute
Expand All @@ -1897,7 +1929,7 @@ public function set_attribute( $name, $value ) {
*
* Result: <div id="new"/>
*/
$this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
$this->attribute_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
$this->tag_name_starts_at + $this->tag_name_length,
$this->tag_name_starts_at + $this->tag_name_length,
' ' . $updated_attribute
Expand Down Expand Up @@ -1955,8 +1987,8 @@ public function remove_attribute( $name ) {
* and when that attribute wasn't originally present.
*/
if ( ! isset( $this->attributes[ $name ] ) ) {
if ( isset( $this->lexical_updates[ $name ] ) ) {
unset( $this->lexical_updates[ $name ] );
if ( isset( $this->attribute_updates[ $name ] ) ) {
unset( $this->attribute_updates[ $name ] );
}
return false;
}
Expand All @@ -1972,7 +2004,7 @@ public function remove_attribute( $name ) {
*
* Result: <div />
*/
$this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement(
$this->attribute_updates[ $name ] = new WP_HTML_Text_Replacement(
$this->attributes[ $name ]->start,
$this->attributes[ $name ]->end,
''
Expand Down Expand Up @@ -2041,7 +2073,10 @@ public function __toString() {
* @return string The processed HTML.
*/
public function get_updated_html() {
$requires_no_updating = 0 === count( $this->classname_updates ) && 0 === count( $this->lexical_updates );
$requires_no_updating =
0 === count( $this->classname_updates ) &&
0 === count( $this->attribute_updates ) &&
0 === count( $this->lexical_updates );

/*
* When there is nothing more to update and nothing has already been
Expand Down Expand Up @@ -2072,8 +2107,9 @@ public function get_updated_html() {
*
* Note: `apply_attributes_updates()` modifies `$this->output_buffer`.
*/
$this->class_name_updates_to_attributes_updates();
$this->apply_attributes_updates();
$this->class_name_updates_to_attribute_updates();
$this->attribute_updates_to_lexical_updates();
$this->apply_lexical_updates();

/*
* 2. Replace the original HTML with the now-updated HTML so that it's possible to
Expand Down