diff --git a/inc/tag_replacer.php b/inc/tag_replacer.php
index 3ec4741e..74d2d6e3 100644
--- a/inc/tag_replacer.php
+++ b/inc/tag_replacer.php
@@ -500,8 +500,13 @@ public function add_missing_srcset_attributes( $tag, $missing_srcsets, $new_url,
}
$breakpoint = isset( $srcset_data['b'] ) ? (int) $srcset_data['b'] : 0;
+ // A dpr entry advertises its delivered width ("824w"): the CSS width times the dpr. The CDN
+ // multiplies w by dpr, so it gets the CSS width, which is also the slot in the sizes attribute.
+ $css_width = $dpr > 1 ? (int) round( $width / $dpr ) : $width;
+ $css_height = $dpr > 1 ? (int) round( $height / $dpr ) : $height;
+
// Generate optimized URL for this size
- $optimized_url = $this->change_url_for_size( $new_url, $width, $height, $dpr );
+ $optimized_url = $this->change_url_for_size( $new_url, $css_width, $css_height, $dpr );
if ( $optimized_url ) {
$escaped_url = esc_url( $optimized_url );
@@ -512,7 +517,7 @@ public function add_missing_srcset_attributes( $tag, $missing_srcsets, $new_url,
// Add sizes attribute entry for responsive breakpoints
if ( $breakpoint > 0 ) {
- $new_sizes_entries[] = '(max-width: ' . $breakpoint . 'px) ' . $width . 'px';
+ $new_sizes_entries[] = '(max-width: ' . $breakpoint . 'px) ' . $css_width . 'px';
}
}
}
diff --git a/tests/test-srcset.php b/tests/test-srcset.php
index befee5d9..255116eb 100644
--- a/tests/test-srcset.php
+++ b/tests/test-srcset.php
@@ -175,9 +175,7 @@ public function test_add_missing_srcset_attributes_retina_disabled() {
$tag_replacer = new Optml_Tag_Replacer();
// Mock settings to disable retina images
- $settings = $this->getMockBuilder( 'Optml_Settings' )->getMock();
- $settings->method( 'get' )->with( 'retina_images' )->willReturn( 'disabled' );
- $tag_replacer->settings = $settings;
+ $tag_replacer->settings = $this->retina_settings( 'disabled' );
$tag = '
';
$missing_srcsets = [
@@ -191,6 +189,69 @@ public function test_add_missing_srcset_attributes_retina_disabled() {
$this->assertStringNotContainsString( '400w', $result );
}
+ /**
+ * With Retina enabled, the profiler sends a 1x and a 2x entry per breakpoint. The 2x entry
+ * advertises its delivered width (824w); the CDN multiplies w by dpr, so its URL asks for the
+ * CSS width (w:412/dpr:2), not w:824/dpr:2, which would deliver 1648px.
+ */
+ public function test_add_missing_srcset_attributes_retina_entry_delivers_its_advertised_width(): void {
+ $tag_replacer = new Optml_Tag_Replacer();
+ $tag_replacer->settings = $this->retina_settings( 'enabled' );
+
+ $result = $tag_replacer->add_missing_srcset_attributes(
+ '
',
+ [
+ [ 'w' => 412, 'h' => 300, 'd' => 1, 's' => '412w', 'b' => 768 ],
+ [ 'w' => 824, 'h' => 600, 'd' => 2, 's' => '824w', 'b' => 768 ],
+ ],
+ 'https://example.i.optimole.com/w:auto/h:auto/q:mauto/https://example.com/image.jpg',
+ false
+ );
+
+ $this->assertStringContainsString( 'w:412/h:300/q:mauto/https://example.com/image.jpg 412w', $result );
+ $this->assertStringContainsString( 'w:412/dpr:2/h:300/q:mauto/https://example.com/image.jpg 824w', $result );
+ $this->assertStringNotContainsString( 'w:824', $result );
+ }
+
+ /**
+ * The 1x and 2x entries of a breakpoint share one slot: the sizes attribute gets one
+ * condition for it, with the CSS width, not a second one with the doubled width.
+ */
+ public function test_add_missing_srcset_attributes_retina_entries_share_one_sizes_condition(): void {
+ $tag_replacer = new Optml_Tag_Replacer();
+ $tag_replacer->settings = $this->retina_settings( 'enabled' );
+
+ $result = $tag_replacer->add_missing_srcset_attributes(
+ '
',
+ [
+ [ 'w' => 300, 'h' => 200, 'd' => 1, 's' => '300w', 'b' => 480 ],
+ [ 'w' => 600, 'h' => 400, 'd' => 2, 's' => '600w', 'b' => 480 ],
+ [ 'w' => 412, 'h' => 300, 'd' => 1, 's' => '412w', 'b' => 768 ],
+ [ 'w' => 824, 'h' => 600, 'd' => 2, 's' => '824w', 'b' => 768 ],
+ ],
+ 'https://example.i.optimole.com/w:auto/h:auto/q:mauto/https://example.com/image.jpg',
+ false
+ );
+
+ $this->assertSame( 1, substr_count( $result, '(max-width: 480px)' ) );
+ $this->assertSame( 1, substr_count( $result, '(max-width: 768px)' ) );
+ $this->assertStringContainsString( 'sizes="(max-width: 480px) 300px, (max-width: 768px) 412px"', $result );
+ }
+
+ /**
+ * Settings mock for the Retina images option.
+ *
+ * @param string $retina_images The option value, 'enabled' or 'disabled'.
+ *
+ * @return Optml_Settings
+ */
+ private function retina_settings( string $retina_images ): Optml_Settings {
+ $settings = $this->getMockBuilder( 'Optml_Settings' )->getMock();
+ $settings->method( 'get' )->with( 'retina_images' )->willReturn( $retina_images );
+
+ return $settings;
+ }
+
/**
* Test enhance_existing_srcset method
*/