Skip to content

Commit 711ad9e

Browse files
committed
Refresh style declarations after mutation
1 parent 61c0783 commit 711ad9e

2 files changed

Lines changed: 131 additions & 6 deletions

File tree

src/wp-includes/html-api/class-wp-html-style-attribute-processor.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ class WP_HTML_Style_Attribute_Processor {
5757
*/
5858
private $current_declaration = -1;
5959

60+
/**
61+
* Whether the current declaration was removed and no new declaration has been selected.
62+
*
63+
* @var bool
64+
*/
65+
private $current_declaration_removed = false;
66+
6067
/**
6168
* Lexical updates to apply to the original style attribute value.
6269
*
@@ -102,12 +109,14 @@ public function next_declaration( ?string $property_name = null ): bool {
102109
null === $property_name ||
103110
$this->matches_property_name( $this->declarations[ $i ]['name'], $property_name )
104111
) {
105-
$this->current_declaration = $i;
112+
$this->current_declaration = $i;
113+
$this->current_declaration_removed = false;
106114
return true;
107115
}
108116
}
109117

110-
$this->current_declaration = count( $this->declarations );
118+
$this->current_declaration = count( $this->declarations );
119+
$this->current_declaration_removed = false;
111120
return false;
112121
}
113122

@@ -174,6 +183,8 @@ public function set_value( string $value, ?bool $important = null ): bool {
174183
$this->current_declaration
175184
);
176185

186+
$this->apply_lexical_updates( $this->current_declaration );
187+
177188
return true;
178189
}
179190

@@ -206,6 +217,8 @@ public function remove_declaration(): bool {
206217
$this->current_declaration
207218
);
208219

220+
$this->apply_lexical_updates( $this->current_declaration - 1, true );
221+
209222
return true;
210223
}
211224

@@ -225,9 +238,10 @@ public function append_declaration( string $property_name, string $value, bool $
225238
return false;
226239
}
227240

228-
$trimmed_style = rtrim( $this->style, self::WHITESPACE );
229-
$insert_at = strlen( $trimmed_style );
230-
$separator = $this->has_queued_append_at( $insert_at ) ? ' ' : '';
241+
$old_declaration_count = count( $this->declarations );
242+
$trimmed_style = rtrim( $this->style, self::WHITESPACE );
243+
$insert_at = strlen( $trimmed_style );
244+
$separator = $this->has_queued_append_at( $insert_at ) ? ' ' : '';
231245

232246
if ( '' === $separator && '' !== trim( $trimmed_style, self::WHITESPACE ) ) {
233247
$separator = ( ';' === substr( $trimmed_style, -1 ) ) ? ' ' : '; ';
@@ -240,6 +254,16 @@ public function append_declaration( string $property_name, string $value, bool $
240254
null
241255
);
242256

257+
$append_after_exhausted_cursor = $this->current_declaration >= $old_declaration_count;
258+
$current_after_append = $append_after_exhausted_cursor
259+
? $old_declaration_count - 1
260+
: $this->current_declaration;
261+
262+
$this->apply_lexical_updates(
263+
$current_after_append,
264+
$this->current_declaration_removed || $append_after_exhausted_cursor
265+
);
266+
243267
return true;
244268
}
245269

@@ -662,13 +686,40 @@ private function is_ignored_token( array $token ): bool {
662686
* @return array{name:string, raw_name:string, leading_start:int, start:int, after:int, trailing_end:int, value_start:int, value_end:int, important:bool}|null
663687
*/
664688
private function get_current_declaration(): ?array {
665-
if ( $this->current_declaration < 0 || ! isset( $this->declarations[ $this->current_declaration ] ) ) {
689+
if (
690+
$this->current_declaration_removed ||
691+
$this->current_declaration < 0 ||
692+
! isset( $this->declarations[ $this->current_declaration ] )
693+
) {
666694
return null;
667695
}
668696

669697
return $this->declarations[ $this->current_declaration ];
670698
}
671699

700+
/**
701+
* Applies queued lexical updates and reparses the updated style attribute value.
702+
*
703+
* @param int $current_declaration Declaration index to keep before the next cursor advance.
704+
* @param bool $current_declaration_removed Optional. Whether the current declaration was removed.
705+
*/
706+
private function apply_lexical_updates( int $current_declaration, bool $current_declaration_removed = false ): void {
707+
$this->style = $this->get_updated_style();
708+
709+
$this->tokens = array();
710+
$this->declarations = array();
711+
$this->lexical_updates = array();
712+
$this->lexical_update_order = 0;
713+
$this->current_declaration = $current_declaration;
714+
$this->current_declaration_removed = $current_declaration_removed;
715+
716+
$this->parse();
717+
718+
if ( $this->current_declaration >= count( $this->declarations ) ) {
719+
$this->current_declaration = count( $this->declarations );
720+
}
721+
}
722+
672723
/**
673724
* Queues a lexical update, replacing any earlier update for the same declaration.
674725
*

tests/phpunit/tests/html-api/wpHtmlStyleAttributeProcessor.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,25 @@ public function test_set_value_updates_current_declaration_without_collapsing_du
117117
$this->assertSame( 'color: green; color: color(display-p3 1 0 0);', $processor->get_updated_style() );
118118
}
119119

120+
/**
121+
* @covers ::set_value
122+
* @covers ::get_value
123+
* @covers ::is_important
124+
*/
125+
public function test_getters_reflect_current_declaration_after_set_value() {
126+
$processor = new WP_HTML_Style_Attribute_Processor( 'color: red; background: white;' );
127+
128+
$this->assertTrue( $processor->next_declaration( 'color' ) );
129+
$this->assertTrue( $processor->set_value( 'green', true ) );
130+
131+
$this->assertSame( 'color', $processor->get_property_name() );
132+
$this->assertSame( 'green', $processor->get_value() );
133+
$this->assertTrue( $processor->is_important() );
134+
135+
$this->assertTrue( $processor->next_declaration() );
136+
$this->assertSame( 'background', $processor->get_property_name() );
137+
}
138+
120139
/**
121140
* @covers ::remove_declaration
122141
* @covers ::get_updated_style
@@ -131,6 +150,25 @@ public function test_remove_declaration_removes_only_current_duplicate() {
131150
$this->assertSame( 'color: red; background: white;', $processor->get_updated_style() );
132151
}
133152

153+
/**
154+
* @covers ::remove_declaration
155+
* @covers ::get_property_name
156+
* @covers ::get_value
157+
*/
158+
public function test_getters_return_null_after_removing_current_declaration_until_cursor_advances() {
159+
$processor = new WP_HTML_Style_Attribute_Processor( 'color: red; background: white;' );
160+
161+
$this->assertTrue( $processor->next_declaration( 'color' ) );
162+
$this->assertTrue( $processor->remove_declaration() );
163+
164+
$this->assertNull( $processor->get_property_name() );
165+
$this->assertNull( $processor->get_value() );
166+
$this->assertFalse( $processor->is_important() );
167+
168+
$this->assertTrue( $processor->next_declaration() );
169+
$this->assertSame( 'background', $processor->get_property_name() );
170+
}
171+
134172
/**
135173
* @covers ::remove_declaration
136174
* @covers ::get_updated_style
@@ -170,6 +208,42 @@ public function test_append_declaration_preserves_multiple_appends_in_order() {
170208
$this->assertSame( 'color: red; background: white;', $processor->get_updated_style() );
171209
}
172210

211+
/**
212+
* @covers ::append_declaration
213+
* @covers ::next_declaration
214+
*/
215+
public function test_appended_declarations_can_be_inspected_by_the_cursor() {
216+
$processor = new WP_HTML_Style_Attribute_Processor( '' );
217+
218+
$this->assertTrue( $processor->append_declaration( 'color', 'red' ) );
219+
220+
$this->assertTrue( $processor->next_declaration() );
221+
$this->assertSame( 'color', $processor->get_property_name() );
222+
$this->assertSame( 'red', $processor->get_value() );
223+
}
224+
225+
/**
226+
* @covers ::append_declaration
227+
* @covers ::next_declaration
228+
* @covers ::get_property_name
229+
*/
230+
public function test_append_declaration_preserves_exhausted_cursor_until_it_advances() {
231+
$processor = new WP_HTML_Style_Attribute_Processor( 'color: red;' );
232+
233+
$this->assertTrue( $processor->next_declaration() );
234+
$this->assertFalse( $processor->next_declaration() );
235+
$this->assertTrue( $processor->append_declaration( 'background', 'white' ) );
236+
237+
$this->assertNull( $processor->get_property_name() );
238+
$this->assertNull( $processor->get_value() );
239+
$this->assertFalse( $processor->set_value( 'green' ) );
240+
241+
$this->assertTrue( $processor->next_declaration() );
242+
$this->assertSame( 'background', $processor->get_property_name() );
243+
$this->assertSame( 'white', $processor->get_value() );
244+
$this->assertSame( 'color: red; background: white;', $processor->get_updated_style() );
245+
}
246+
173247
/**
174248
* @covers ::remove_declaration
175249
* @covers ::append_declaration

0 commit comments

Comments
 (0)