From b72563f110eaf50551159743a5b1706316620aa9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 19:52:47 +0000 Subject: [PATCH 1/3] feat: let an image keep its own sizes attribute The page profiler adds its measured breakpoints to an image's sizes attribute unless the value contains calc() or a similar formula. Sites with hand-written sizes had no supported way to keep them. An image with data-optml-keep-sizes now keeps its existing sizes attribute (srcset candidates are still added). The optml_keep_existing_sizes filter does the same site-wide without changing the markup. Refs #1115 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01MEXWtYdE6e3ALYtqtP8j2p --- README.md | 8 +++++++ inc/tag_replacer.php | 34 ++++++++++++++++++++++++++- readme.txt | 8 +++++++ tests/test-srcset.php | 54 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 76601f34..dcd90f45 100644 --- a/README.md +++ b/README.md @@ -345,3 +345,11 @@ add_filter('optml_dont_replace_url', function( $old, $url ) { }, 10, 2); + += Can I keep the sizes attribute I wrote for an image ? = + +Yes. Add the `data-optml-keep-sizes` attribute to the image tag and Optimole leaves its `sizes` attribute as it is. To keep the existing `sizes` attribute on every image, add this snippet: + + +add_filter( 'optml_keep_existing_sizes', '__return_true' ); + diff --git a/inc/tag_replacer.php b/inc/tag_replacer.php index 3330edb2..f54ea6f7 100644 --- a/inc/tag_replacer.php +++ b/inc/tag_replacer.php @@ -13,6 +13,11 @@ final class Optml_Tag_Replacer extends Optml_App_Replacer { use Optml_Validator; use Optml_Dam_Offload_Utils; + /** + * Attribute an image uses to keep its own sizes attribute. + */ + const KEEP_SIZES_ATTRIBUTE = 'data-optml-keep-sizes'; + /** * Cached object instance. * @@ -554,7 +559,12 @@ public function add_missing_srcset_attributes( $tag, $missing_srcsets, $new_url, if ( ! empty( $new_sizes_entries ) ) { $should_skip_sizes = false; - if ( $has_existing_sizes ) { + if ( $has_existing_sizes && $this->keeps_existing_sizes( $tag ) ) { + $should_skip_sizes = true; + if ( OPTML_DEBUG ) { + do_action( 'optml_log', 'Keeping the existing sizes attribute: ' . $new_url ); + } + } elseif ( $has_existing_sizes ) { // Check if existing sizes contains calc() or complex formulas $sizes = $this->find_quoted_attribute( $tag, 'sizes' ); if ( null !== $sizes ) { @@ -834,6 +844,28 @@ private function has_attribute( string $tag, string $name ): bool { return in_array( $name, array_column( $this->get_attributes( $tag ) ?? [], 'name' ), true ); } + /** + * Check whether an image's own sizes attribute must be left as it is. + * + * An image opts out with the data-optml-keep-sizes attribute. The optml_keep_existing_sizes filter + * does it for every image, or for the ones it picks, with no change to the markup. + * + * @param string $tag The image tag. + * + * @return bool True if the existing sizes attribute is kept. + */ + private function keeps_existing_sizes( string $tag ): bool { + $keep = $this->has_attribute( $tag, self::KEEP_SIZES_ATTRIBUTE ); + + /** + * Filters whether Optimole leaves an image's existing sizes attribute unchanged. + * + * @param bool $keep Whether the existing sizes attribute is kept. True when the image has data-optml-keep-sizes. + * @param string $tag The image tag. + */ + return (bool) apply_filters( 'optml_keep_existing_sizes', $keep, $tag ); + } + /** * Check whether the tag's src or srcset is a data: URI placeholder. * diff --git a/readme.txt b/readme.txt index 89ca3fab..a785ac9d 100755 --- a/readme.txt +++ b/readme.txt @@ -345,3 +345,11 @@ add_filter('optml_dont_replace_url', function( $old, $url ) { }, 10, 2); + += Can I keep the sizes attribute I wrote for an image ? = + +Yes. Add the `data-optml-keep-sizes` attribute to the image tag and Optimole leaves its `sizes` attribute as it is. To keep the existing `sizes` attribute on every image, add this snippet: + + +add_filter( 'optml_keep_existing_sizes', '__return_true' ); + diff --git a/tests/test-srcset.php b/tests/test-srcset.php index 5b90d7d6..aa5ba03b 100644 --- a/tests/test-srcset.php +++ b/tests/test-srcset.php @@ -168,6 +168,60 @@ public function test_add_missing_srcset_attributes_with_existing_sizes() { $this->assertStringContainsString( '(max-width: 768px) 400px', $result ); } + /** + * An image with data-optml-keep-sizes keeps its hand-written sizes, and still gets the new srcset entries. + */ + public function test_keep_sizes_attribute_preserves_existing_sizes() { + $tag_replacer = new Optml_Tag_Replacer(); + + $tag = 'Test'; + $missing_srcsets = [ + [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ], + [ 'w' => 400, 'h' => 300, 'd' => 1, 's' => '400w', 'b' => 768 ], + ]; + + $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false ); + + $this->assertStringContainsString( 'sizes="(max-width: 480px) 300px"', $result ); + $this->assertStringNotContainsString( '(max-width: 320px) 200px', $result ); + $this->assertStringContainsString( '200w', $result ); + } + + /** + * The optml_keep_existing_sizes filter keeps existing sizes site-wide, with no change to the markup. + */ + public function test_keep_existing_sizes_filter() { + $tag_replacer = new Optml_Tag_Replacer(); + + $tag = 'Test'; + $missing_srcsets = [ + [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ], + ]; + + add_filter( 'optml_keep_existing_sizes', '__return_true' ); + $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false ); + remove_filter( 'optml_keep_existing_sizes', '__return_true' ); + + $this->assertStringContainsString( 'sizes="(max-width: 480px) 300px"', $result ); + $this->assertStringNotContainsString( '(max-width: 320px) 200px', $result ); + } + + /** + * The attribute only protects an existing sizes: an image without one still gets the generated sizes. + */ + public function test_keep_sizes_attribute_without_existing_sizes_adds_sizes() { + $tag_replacer = new Optml_Tag_Replacer(); + + $tag = 'Test'; + $missing_srcsets = [ + [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ], + ]; + + $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false ); + + $this->assertStringContainsString( '(max-width: 320px) 200px', $result ); + } + /** * Test retina images setting respect */ From f5c1078da647b79746d38fb2521e71ac0908aa5c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 20:40:32 +0000 Subject: [PATCH 2/3] Clarify keep-sizes log, FAQ and tests Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01MEXWtYdE6e3ALYtqtP8j2p --- README.md | 2 +- inc/tag_replacer.php | 6 +++--- readme.txt | 2 +- tests/test-srcset.php | 8 +++++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dcd90f45..8da99532 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ add_filter('optml_dont_replace_url', function( $old, $url ) { = Can I keep the sizes attribute I wrote for an image ? = -Yes. Add the `data-optml-keep-sizes` attribute to the image tag and Optimole leaves its `sizes` attribute as it is. To keep the existing `sizes` attribute on every image, add this snippet: +Yes. Add the `data-optml-keep-sizes` attribute to the image tag and the page profiler won't add its own entries to the image's `sizes` attribute. Images lazy loaded by Optimole don't keep `srcset` or `sizes`, since the lazy loader doesn't need them. To keep the existing `sizes` attribute on every image, add this snippet: add_filter( 'optml_keep_existing_sizes', '__return_true' ); diff --git a/inc/tag_replacer.php b/inc/tag_replacer.php index f54ea6f7..0df7d581 100644 --- a/inc/tag_replacer.php +++ b/inc/tag_replacer.php @@ -555,7 +555,7 @@ public function add_missing_srcset_attributes( $tag, $missing_srcsets, $new_url, $tag = $this->insert_after_attribute( $tag, 'src', $srcset_attr ); } - // Handle sizes attribute - skip if existing sizes contains calc() or complex formulas + // Handle sizes attribute - skip if the image asks to keep its existing sizes, or they contain calc() or complex formulas if ( ! empty( $new_sizes_entries ) ) { $should_skip_sizes = false; @@ -606,9 +606,9 @@ function ( $a, $b ) { } } } else { - // Log that sizes were skipped due to complex formulas + // Log that sizes were skipped (kept on request, or complex formula) if ( OPTML_DEBUG ) { - do_action( 'optml_log', 'Skipped adding sizes due to complex formula detection' ); + do_action( 'optml_log', 'Skipped adding sizes' ); } } } diff --git a/readme.txt b/readme.txt index a785ac9d..da7c72b0 100755 --- a/readme.txt +++ b/readme.txt @@ -348,7 +348,7 @@ add_filter('optml_dont_replace_url', function( $old, $url ) { = Can I keep the sizes attribute I wrote for an image ? = -Yes. Add the `data-optml-keep-sizes` attribute to the image tag and Optimole leaves its `sizes` attribute as it is. To keep the existing `sizes` attribute on every image, add this snippet: +Yes. Add the `data-optml-keep-sizes` attribute to the image tag and the page profiler won't add its own entries to the image's `sizes` attribute. Images lazy loaded by Optimole don't keep `srcset` or `sizes`, since the lazy loader doesn't need them. To keep the existing `sizes` attribute on every image, add this snippet: add_filter( 'optml_keep_existing_sizes', '__return_true' ); diff --git a/tests/test-srcset.php b/tests/test-srcset.php index aa5ba03b..8ece8e87 100644 --- a/tests/test-srcset.php +++ b/tests/test-srcset.php @@ -207,17 +207,19 @@ public function test_keep_existing_sizes_filter() { } /** - * The attribute only protects an existing sizes: an image without one still gets the generated sizes. + * A filter returning false overrides the attribute, so the existing sizes are enhanced again. */ - public function test_keep_sizes_attribute_without_existing_sizes_adds_sizes() { + public function test_keep_existing_sizes_filter_can_override_attribute() { $tag_replacer = new Optml_Tag_Replacer(); - $tag = 'Test'; + $tag = 'Test'; $missing_srcsets = [ [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ], ]; + add_filter( 'optml_keep_existing_sizes', '__return_false' ); $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false ); + remove_filter( 'optml_keep_existing_sizes', '__return_false' ); $this->assertStringContainsString( '(max-width: 320px) 200px', $result ); } From 2809bb468953b7fb15b4b69d40383e8793127731 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 21:21:24 +0000 Subject: [PATCH 3/3] Check the attribute keeps sizes before the filter overrides it Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01MEXWtYdE6e3ALYtqtP8j2p --- tests/test-srcset.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test-srcset.php b/tests/test-srcset.php index 8ece8e87..3f8c6b3d 100644 --- a/tests/test-srcset.php +++ b/tests/test-srcset.php @@ -217,6 +217,9 @@ public function test_keep_existing_sizes_filter_can_override_attribute() { [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ], ]; + $kept = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false ); + $this->assertStringNotContainsString( '(max-width: 320px) 200px', $kept ); + add_filter( 'optml_keep_existing_sizes', '__return_false' ); $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false ); remove_filter( 'optml_keep_existing_sizes', '__return_false' );