From 4d3988086736c7a5dc19af620cbc8e0315a21c96 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 2 Feb 2025 21:43:46 -0800 Subject: [PATCH 01/11] Compute responsive sizes from URL Metrics --- ...lass-image-prioritizer-img-tag-visitor.php | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index c55129d761..81336ae764 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -44,6 +44,43 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { return false; } + /** + * Computes responsive sizes for the current element based on its boundingClientRect width captured in URL Metrics. + * + * @since n.e.x.t + * + * @param OD_Tag_Visitor_Context $context Context. + * @return non-empty-string[] Computed sizes. + */ + private function compute_sizes( OD_Tag_Visitor_Context $context ): array { + $sizes = array(); + + $xpath = $context->processor->get_xpath(); + foreach ( $context->url_metric_group_collection as $group ) { + $element_max_width = 0; + foreach ( $group->get_xpath_elements_map()[ $xpath ] ?? array() as $element ) { + $element_max_width = max( $element_max_width, $element->get_bounding_client_rect()['width'] ); + } + + if ( $element_max_width > 0 ) { + // TODO: The min-width will need to be adjusted after . + // TODO: Consider reusing od_generate_media_query() here but note the extra parentheses should be removed from its output. + $media_queries = array(); + if ( $group->get_minimum_viewport_width() !== 0 ) { + $media_queries[] = sprintf( 'min-width: %dpx', $group->get_minimum_viewport_width() ); + } + if ( $group->get_maximum_viewport_width() !== PHP_INT_MAX ) { + $media_queries[] = sprintf( 'max-width: %dpx', $group->get_maximum_viewport_width() ); + } + if ( count( $media_queries ) > 0 ) { + $sizes[] = sprintf( '(%s) %dpx', join( ' and ', $media_queries ), round( $element_max_width ) ); + } + } + } + + return $sizes; + } + /** * Process an IMG element. * @@ -146,21 +183,36 @@ private function process_img( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_C $processor->remove_attribute( 'fetchpriority' ); } - // Ensure that sizes=auto is set properly. - $sizes = $processor->get_attribute( 'sizes' ); - if ( is_string( $sizes ) ) { + // Ensure that sizes is set properly when it is a responsive image (it has a srcset attribute). + if ( is_string( $processor->get_attribute( 'srcset' ) ) ) { + $sizes = $processor->get_attribute( 'sizes' ); + if ( ! is_string( $sizes ) ) { + $sizes = ''; + } + $is_lazy = 'lazy' === $this->get_attribute_value( $processor, 'loading' ); $has_auto = $this->sizes_attribute_includes_valid_auto( $sizes ); if ( $is_lazy && ! $has_auto ) { - $processor->set_attribute( 'sizes', "auto, $sizes" ); + $sizes = "auto, $sizes"; } elseif ( ! $is_lazy && $has_auto ) { // Remove auto from the beginning of the list. - $processor->set_attribute( - 'sizes', - (string) preg_replace( '/^[ \t\f\r\n]*auto[ \t\f\r\n]*(,[ \t\f\r\n]*)?/i', '', $sizes ) - ); + $sizes = (string) preg_replace( '/^[ \t\f\r\n]*auto[ \t\f\r\n]*(,[ \t\f\r\n]*)?/i', '', $sizes ); } + + // Compute more accurate sizes when it isn't lazy-loaded and sizes=auto isn't taking care of it. + if ( ! $is_lazy ) { + $computed_sizes = $this->compute_sizes( $context ); + if ( count( $computed_sizes ) > 0 ) { + $new_sizes = join( ', ', $computed_sizes ); + if ( '' !== $sizes ) { + $new_sizes .= ", $sizes"; + } + $sizes = $new_sizes; + } + } + + $processor->set_attribute( 'sizes', $sizes ); } $parent_tag = $this->get_parent_tag_name( $context ); From 7b620c2f261250580423095e941c91d9fcfeee9e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 2 Feb 2025 21:44:45 -0800 Subject: [PATCH 02/11] Improve defaults in URL Metric sample data --- ...ss-optimization-detective-test-helpers.php | 62 ++++++++++++++----- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/plugins/optimization-detective/tests/class-optimization-detective-test-helpers.php b/plugins/optimization-detective/tests/class-optimization-detective-test-helpers.php index dc06730ab9..d1a45a9393 100644 --- a/plugins/optimization-detective/tests/class-optimization-detective-test-helpers.php +++ b/plugins/optimization-detective/tests/class-optimization-detective-test-helpers.php @@ -12,7 +12,9 @@ * @phpstan-type ElementDataSubset array{ * xpath: string, * isLCP?: bool, - * intersectionRatio?: float + * intersectionRatio?: float, + * intersectionRect?: array, + * boundingClientRect?: array, * } */ trait Optimization_Detective_Test_Helpers { @@ -48,18 +50,22 @@ public function populate_url_metrics( array $elements, bool $complete = true ): /** * Gets a sample DOM rect for testing. * + * @param float $width Width. + * @param float $height Height. * @return array */ - public function get_sample_dom_rect(): array { + public function get_sample_dom_rect( float $width = 500.1, float $height = 500.2 ): array { + $x = 100.3; + $y = 200.0; return array( - 'width' => 500.1, - 'height' => 500.2, - 'x' => 100.3, - 'y' => 100.4, - 'top' => 0.1, + 'width' => $width, + 'height' => $height, + 'x' => $x, + 'y' => $y, + 'top' => $y, 'right' => 0.2, 'bottom' => 0.3, - 'left' => 0.4, + 'left' => $x, ); } @@ -90,6 +96,9 @@ public function get_sample_url_metric( array $params ): OD_URL_Metric { ), $params ); + if ( ! isset( $params['viewport_height'] ) ) { + $params['viewport_height'] = ceil( $params['viewport_width'] / 2 ); + } if ( array_key_exists( 'element', $params ) ) { $params['elements'][] = $params['element']; @@ -101,19 +110,38 @@ public function get_sample_url_metric( array $params ): OD_URL_Metric { 'url' => $params['url'], 'viewport' => array( 'width' => $params['viewport_width'], - 'height' => $params['viewport_height'] ?? ceil( $params['viewport_width'] / 2 ), + 'height' => $params['viewport_height'], ), 'timestamp' => $params['timestamp'], 'elements' => array_map( - function ( array $element ): array { + function ( array $element ) use ( $params ): array { + $default_props = array( + 'isLCP' => false, + 'isLCPCandidate' => $element['isLCP'] ?? false, + 'intersectionRatio' => 1, + 'boundingClientRect' => $this->get_sample_dom_rect( round( $params['viewport_width'] ) * 0.9, round( $params['viewport_height'] * 0.9 ) ), + ); + + if ( isset( $element['intersectionRatio'] ) && $element['intersectionRatio'] < PHP_FLOAT_EPSILON ) { + $default_props['intersectionRect'] = array_fill_keys( array_keys( $this->get_sample_dom_rect() ), 0.0 ); + } else { + $default_props['intersectionRect'] = $default_props['boundingClientRect']; + } + + $element['intersectionRect'] = array_merge( + $this->get_sample_dom_rect(), + $default_props['intersectionRect'], + $element['intersectionRect'] ?? array() + ); + + $element['boundingClientRect'] = array_merge( + $this->get_sample_dom_rect(), + $default_props['boundingClientRect'], + $element['boundingClientRect'] ?? array() + ); + return array_merge( - array( - 'isLCP' => false, - 'isLCPCandidate' => $element['isLCP'] ?? false, - 'intersectionRatio' => 1, - 'intersectionRect' => $this->get_sample_dom_rect(), - 'boundingClientRect' => $this->get_sample_dom_rect(), - ), + $default_props, $element ); }, From f964684e3adffaabf53803a60e9c4e3ad649aea2 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 2 Feb 2025 21:47:18 -0800 Subject: [PATCH 03/11] Update tests --- .../set-up.php | 10 +------ .../set-up.php | 10 +------ .../set-up.php | 13 ++------- .../buffer.html | 4 +-- .../expected.html | 12 ++++---- .../set-up.php | 28 ++++++++----------- .../expected.html | 4 +-- .../set-up.php | 10 +------ .../expected.html | 2 +- .../expected.html | 2 +- .../expected.html | 2 +- .../expected.html | 2 +- .../image-prioritizer/tests/test-helper.php | 26 +++++++---------- 13 files changed, 40 insertions(+), 85 deletions(-) diff --git a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php index 5839f6eb3c..b0acbfcb07 100644 --- a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php @@ -10,13 +10,6 @@ static function () use ( $breakpoint_max_widths ) { } ); - $outside_viewport_rect = array_merge( - $test_case->get_sample_dom_rect(), - array( - 'top' => 100000, - ) - ); - foreach ( $breakpoint_max_widths as $non_desktop_viewport_width ) { for ( $i = 0; $i < $sample_size; $i++ ) { OD_URL_Metrics_Post_Type::store_url_metric( @@ -29,8 +22,7 @@ static function () use ( $breakpoint_max_widths ) { 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php index dc4dc10f49..602fc0221d 100644 --- a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php @@ -9,13 +9,6 @@ static function () use ( $breakpoint_max_widths ) { } ); - $outside_viewport_rect = array_merge( - $test_case->get_sample_dom_rect(), - array( - 'top' => 100000, - ) - ); - foreach ( $breakpoint_max_widths as $non_desktop_viewport_width ) { OD_URL_Metrics_Post_Type::store_url_metric( od_get_url_metrics_slug( od_get_normalized_query_vars() ), @@ -27,8 +20,7 @@ static function () use ( $breakpoint_max_widths ) { 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php index fe81ed23db..bdf18f9142 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php @@ -1,12 +1,5 @@ get_sample_dom_rect(), - array( - 'top' => 100000, - ) - ); - $test_case->populate_url_metrics( array( array( @@ -17,15 +10,13 @@ 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), array( 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[4][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), ) ); diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/buffer.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/buffer.html index 06087ee6b6..806fd84b35 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/buffer.html +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/buffer.html @@ -11,9 +11,9 @@ Foo

Pretend this is a super long paragraph that pushes the next image mostly out of the initial viewport.

- Bar + Bar

Now the following image is definitely outside the initial viewport.

- Baz + Baz Qux Quux diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html index 0d86051f12..016f96ecaa 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html @@ -2,19 +2,19 @@ ... - +

Pretend this is a super long paragraph that pushes the next image mostly out of the initial viewport.

- Bar + Bar

Now the following image is definitely outside the initial viewport.

- Baz + Baz Qux Quux
diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php index 2bf089a7c4..e72cbe3167 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php @@ -1,13 +1,7 @@ get_sample_dom_rect(), - array( - 'top' => 100000, - ) - ); + $slug = od_get_url_metrics_slug( od_get_normalized_query_vars() ); + $sample_size = od_get_url_metrics_breakpoint_sample_size(); foreach ( array_merge( od_get_breakpoint_max_widths(), array( 1000 ) ) as $viewport_width ) { for ( $i = 0; $i < $sample_size; $i++ ) { OD_URL_Metrics_Post_Type::store_url_metric( @@ -31,31 +25,31 @@ 'intersectionRatio' => 0.0, // Subsequent carousel slide. ), array( - 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::IMG]', - 'isLCP' => false, - 'intersectionRatio' => 0 === $i ? 0.5 : 0.0, // Make sure that the _max_ intersection ratio is considered. + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 0 === $i ? 0.5 : 0.0, // Make sure that the _max_ intersection ratio is considered. + 'boundingClientRect' => array( + 'width' => $viewport_width - 10, + ), ), // All are outside all initial viewports. array( 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[5][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), array( 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[6][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), array( 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[7][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html index 1323295b46..a15ffa4bd6 100644 --- a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html @@ -5,7 +5,7 @@ - +
@@ -23,7 +23,7 @@

Last Post

First Post

This post does have a featured image, and the server-side heuristics in WordPress cause it to get fetchpriority=high, but it should not have this since it is out of the viewport on mobile.

diff --git a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php index 72ff40f7e4..0f422707b4 100644 --- a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php @@ -10,13 +10,6 @@ static function () { $slug = od_get_url_metrics_slug( od_get_normalized_query_vars() ); $sample_size = od_get_url_metrics_breakpoint_sample_size(); - $outside_viewport_rect = array_merge( - $test_case->get_sample_dom_rect(), - array( - 'top' => 100000, - ) - ); - // Populate the mobile and desktop viewport groups only. foreach ( array( 400, 800 ) as $viewport_width ) { for ( $i = 0; $i < $sample_size; $i++ ) { @@ -35,8 +28,7 @@ static function () { 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::MAIN]/*[4][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html index d3119732ba..fa23141064 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html @@ -9,7 +9,7 @@ - Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html index f42e7fd2b4..dc83a6718a 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html @@ -8,7 +8,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html index 1ce78a0f70..fe6d654fba 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html index 50244f0fbb..d6241ea9b5 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-helper.php b/plugins/image-prioritizer/tests/test-helper.php index 0c1092853f..d64191973e 100644 --- a/plugins/image-prioritizer/tests/test-helper.php +++ b/plugins/image-prioritizer/tests/test-helper.php @@ -102,13 +102,6 @@ public function test_end_to_end( string $directory ): void { * @return array Data. */ public function data_provider_test_auto_sizes(): array { - $outside_viewport_rect = array_merge( - $this->get_sample_dom_rect(), - array( - 'top' => 1000, - ) - ); - return array( // Note: The Image Prioritizer plugin removes the loading attribute, and so then Auto Sizes does not then add sizes=auto. 'wrongly_lazy_responsive_img' => array( @@ -118,7 +111,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), 'non_responsive_image' => array( @@ -126,8 +119,7 @@ public function data_provider_test_auto_sizes(): array { 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), 'buffer' => 'Quux', 'expected' => 'Quux', @@ -138,8 +130,7 @@ public function data_provider_test_auto_sizes(): array { 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), 'buffer' => 'Foo', 'expected' => 'Foo', @@ -150,8 +141,7 @@ public function data_provider_test_auto_sizes(): array { 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0, - 'intersectionRect' => $outside_viewport_rect, - 'boundingClientRect' => $outside_viewport_rect, + 'boundingClientRect' => array( 'top' => 100000 ), ), 'buffer' => 'Foo', 'expected' => 'Foo', @@ -165,7 +155,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), 'wrongly_auto_sized_responsive_img_with_only_auto' => array( @@ -175,7 +165,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), ); } @@ -184,6 +174,10 @@ public function data_provider_test_auto_sizes(): array { * Test auto sizes. * * @covers Image_Prioritizer_Img_Tag_Visitor::__invoke + * @covers Image_Prioritizer_Img_Tag_Visitor::process_img + * @covers Image_Prioritizer_Tag_Visitor::get_attribute_value + * @covers Image_Prioritizer_Img_Tag_Visitor::compute_sizes + * @covers Image_Prioritizer_Img_Tag_Visitor::sizes_attribute_includes_valid_auto * * @dataProvider data_provider_test_auto_sizes * @phpstan-param array{ xpath: string, isLCP: bool, intersectionRatio: int } $element_metrics From f606dac2120751660fcca813e393a2552e1f28c7 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 2 Feb 2025 22:49:20 -0800 Subject: [PATCH 04/11] Improve adding sizes=auto when attribute originally absent --- .../class-image-prioritizer-img-tag-visitor.php | 6 +++++- plugins/image-prioritizer/tests/test-helper.php | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 81336ae764..be947b43be 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -194,7 +194,11 @@ private function process_img( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_C $has_auto = $this->sizes_attribute_includes_valid_auto( $sizes ); if ( $is_lazy && ! $has_auto ) { - $sizes = "auto, $sizes"; + $new_sizes = 'auto'; + if ( '' !== trim( $sizes, " \t\f\r\n" ) ) { + $new_sizes .= ', '; + } + $sizes = $new_sizes . $sizes; } elseif ( ! $is_lazy && $has_auto ) { // Remove auto from the beginning of the list. $sizes = (string) preg_replace( '/^[ \t\f\r\n]*auto[ \t\f\r\n]*(,[ \t\f\r\n]*)?/i', '', $sizes ); diff --git a/plugins/image-prioritizer/tests/test-helper.php b/plugins/image-prioritizer/tests/test-helper.php index d64191973e..340ec3a578 100644 --- a/plugins/image-prioritizer/tests/test-helper.php +++ b/plugins/image-prioritizer/tests/test-helper.php @@ -136,6 +136,17 @@ public function data_provider_test_auto_sizes(): array { 'expected' => 'Foo', ), + 'sizes_attribute_added' => array( + 'element_metrics' => array( + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 0, + 'boundingClientRect' => array( 'top' => 100000 ), + ), + 'buffer' => 'Foo', + 'expected' => 'Foo', + ), + 'auto_sizes_already_added' => array( 'element_metrics' => array( 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', From 74c2ce60f6adc0ce8e7a120dfd04d1bfb95c2ddc Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 4 Feb 2025 13:48:10 -0800 Subject: [PATCH 05/11] Use media query range syntax in sizes attribute --- .../class-image-prioritizer-img-tag-visitor.php | 17 ++++++----------- .../expected.html | 10 +++++----- .../expected.html | 4 ++-- .../expected.html | 2 +- .../expected.html | 2 +- .../expected.html | 2 +- .../expected.html | 2 +- plugins/image-prioritizer/tests/test-helper.php | 6 +++--- 8 files changed, 20 insertions(+), 25 deletions(-) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 00fb31066d..3c3902a467 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -63,18 +63,13 @@ private function compute_sizes( OD_Tag_Visitor_Context $context ): array { } if ( $element_max_width > 0 ) { - // TODO: The min-width will need to be adjusted after . - // TODO: Consider reusing od_generate_media_query() here but note the extra parentheses should be removed from its output. - $media_queries = array(); - if ( $group->get_minimum_viewport_width() !== 0 ) { - $media_queries[] = sprintf( 'min-width: %dpx', $group->get_minimum_viewport_width() ); - } - if ( $group->get_maximum_viewport_width() !== PHP_INT_MAX && $group->get_maximum_viewport_width() !== null ) { - $media_queries[] = sprintf( 'max-width: %dpx', $group->get_maximum_viewport_width() ); - } - if ( count( $media_queries ) > 0 ) { - $sizes[] = sprintf( '(%s) %dpx', join( ' and ', $media_queries ), round( $element_max_width ) ); + $size = sprintf( '%dpx', $element_max_width ); + + $media_feature = od_generate_media_query( $group->get_minimum_viewport_width(), $group->get_maximum_viewport_width() ); + if ( null !== $media_feature ) { + $size = "$media_feature $size"; } + $sizes[] = $size; } } diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html index 8dab235060..cc1c5d216d 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html @@ -2,17 +2,17 @@ ... - +

Pretend this is a super long paragraph that pushes the next image mostly out of the initial viewport.

- Bar + Bar

Now the following image is definitely outside the initial viewport.

Baz Qux diff --git a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html index ed77affe4f..1d7b82b7a3 100644 --- a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html @@ -5,7 +5,7 @@ - +
@@ -23,7 +23,7 @@

Last Post

First Post

This post does have a featured image, and the server-side heuristics in WordPress cause it to get fetchpriority=high, but it should not have this since it is out of the viewport on mobile.

diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html index b3cf6d6ec0..af3517b09d 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html @@ -9,7 +9,7 @@ - Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html index baa4ad5231..edbbde0518 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html @@ -8,7 +8,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html index 134dbc9440..d88103ed5b 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html index 5699b0e229..638ff5108c 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-helper.php b/plugins/image-prioritizer/tests/test-helper.php index 7dd286a2a7..79f6086391 100644 --- a/plugins/image-prioritizer/tests/test-helper.php +++ b/plugins/image-prioritizer/tests/test-helper.php @@ -111,7 +111,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), 'non_responsive_image' => array( @@ -166,7 +166,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), 'wrongly_auto_sized_responsive_img_with_only_auto' => array( @@ -176,7 +176,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), ); } From cdda7c3e024b4149e9f97670d3c76f78132d4d0d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Feb 2025 12:12:28 -0800 Subject: [PATCH 06/11] Move compute_sizes method down further in the class Co-authored-by: felixarntz --- ...lass-image-prioritizer-img-tag-visitor.php | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 3c3902a467..5709c03872 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -44,38 +44,6 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { return false; } - /** - * Computes responsive sizes for the current element based on its boundingClientRect width captured in URL Metrics. - * - * @since n.e.x.t - * - * @param OD_Tag_Visitor_Context $context Context. - * @return non-empty-string[] Computed sizes. - */ - private function compute_sizes( OD_Tag_Visitor_Context $context ): array { - $sizes = array(); - - $xpath = $context->processor->get_xpath(); - foreach ( $context->url_metric_group_collection as $group ) { - $element_max_width = 0; - foreach ( $group->get_xpath_elements_map()[ $xpath ] ?? array() as $element ) { - $element_max_width = max( $element_max_width, $element->get_bounding_client_rect()['width'] ); - } - - if ( $element_max_width > 0 ) { - $size = sprintf( '%dpx', $element_max_width ); - - $media_feature = od_generate_media_query( $group->get_minimum_viewport_width(), $group->get_maximum_viewport_width() ); - if ( null !== $media_feature ) { - $size = "$media_feature $size"; - } - $sizes[] = $size; - } - } - - return $sizes; - } - /** * Process an IMG element. * @@ -436,4 +404,36 @@ private function sizes_attribute_includes_valid_auto( string $sizes_attr ): bool return 'auto' === $sizes_attr || str_starts_with( $sizes_attr, 'auto,' ); } } + + /** + * Computes responsive sizes for the current element based on its boundingClientRect width captured in URL Metrics. + * + * @since n.e.x.t + * + * @param OD_Tag_Visitor_Context $context Context. + * @return non-empty-string[] Computed sizes. + */ + private function compute_sizes( OD_Tag_Visitor_Context $context ): array { + $sizes = array(); + + $xpath = $context->processor->get_xpath(); + foreach ( $context->url_metric_group_collection as $group ) { + $element_max_width = 0; + foreach ( $group->get_xpath_elements_map()[ $xpath ] ?? array() as $element ) { + $element_max_width = max( $element_max_width, $element->get_bounding_client_rect()['width'] ); + } + + if ( $element_max_width > 0 ) { + $size = sprintf( '%dpx', $element_max_width ); + + $media_feature = od_generate_media_query( $group->get_minimum_viewport_width(), $group->get_maximum_viewport_width() ); + if ( null !== $media_feature ) { + $size = "$media_feature $size"; + } + $sizes[] = $size; + } + } + + return $sizes; + } } From 6045b18ebb9d558870353e882c2a7938bf134bfa Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Feb 2025 12:17:09 -0800 Subject: [PATCH 07/11] Add clarifying comments --- .../class-image-prioritizer-img-tag-visitor.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 5709c03872..7ba40c9e85 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -418,16 +418,18 @@ private function compute_sizes( OD_Tag_Visitor_Context $context ): array { $xpath = $context->processor->get_xpath(); foreach ( $context->url_metric_group_collection as $group ) { + // Obtain the maximum width that the image appears among all URL Metrics collected for this viewport group. $element_max_width = 0; foreach ( $group->get_xpath_elements_map()[ $xpath ] ?? array() as $element ) { $element_max_width = max( $element_max_width, $element->get_bounding_client_rect()['width'] ); } + // Use the maximum width as the size for image in this breakpoint. if ( $element_max_width > 0 ) { - $size = sprintf( '%dpx', $element_max_width ); - + $size = sprintf( '%dpx', $element_max_width ); $media_feature = od_generate_media_query( $group->get_minimum_viewport_width(), $group->get_maximum_viewport_width() ); if ( null !== $media_feature ) { + // Note: The null case only happens when a site has filtered od_breakpoint_max_widths to be an empty array, meaning there is only one viewport group. $size = "$media_feature $size"; } $sizes[] = $size; From 93961fa15faae40f1aeda3676ee2f501f5f2dd11 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Feb 2025 13:59:29 -0800 Subject: [PATCH 08/11] Only retain original sizes if some groups are not populated --- .../class-image-prioritizer-img-tag-visitor.php | 2 +- .../expected.html | 10 +++++----- .../expected.html | 2 +- .../expected.html | 2 +- .../expected.html | 2 +- plugins/image-prioritizer/tests/test-helper.php | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 7ba40c9e85..5a6e4ae083 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -172,7 +172,7 @@ private function process_img( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_C $computed_sizes = $this->compute_sizes( $context ); if ( count( $computed_sizes ) > 0 ) { $new_sizes = join( ', ', $computed_sizes ); - if ( '' !== $sizes ) { + if ( '' !== $sizes && ! $context->url_metric_group_collection->is_every_group_populated() ) { $new_sizes .= ", $sizes"; } $sizes = $new_sizes; diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html index cc1c5d216d..9c2f2341f3 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/expected.html @@ -2,17 +2,17 @@ ... - +

Pretend this is a super long paragraph that pushes the next image mostly out of the initial viewport.

- Bar + Bar

Now the following image is definitely outside the initial viewport.

Baz Qux diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html index edbbde0518..c7e42571c4 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/expected.html @@ -8,7 +8,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html index d88103ed5b..1ce00fed27 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html index 638ff5108c..2ed73953a8 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-helper.php b/plugins/image-prioritizer/tests/test-helper.php index 79f6086391..79a4092c41 100644 --- a/plugins/image-prioritizer/tests/test-helper.php +++ b/plugins/image-prioritizer/tests/test-helper.php @@ -111,7 +111,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), 'non_responsive_image' => array( @@ -166,7 +166,7 @@ public function data_provider_test_auto_sizes(): array { 'intersectionRatio' => 1, ), 'buffer' => 'Foo', - 'expected' => 'Foo', + 'expected' => 'Foo', ), 'wrongly_auto_sized_responsive_img_with_only_auto' => array( From eca53e657a153857a989d226b98368528bd7aeab Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Feb 2025 14:02:02 -0800 Subject: [PATCH 09/11] Update readme with new capability --- plugins/image-prioritizer/readme.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/image-prioritizer/readme.txt b/plugins/image-prioritizer/readme.txt index c1e47602c3..92ea6e3887 100644 --- a/plugins/image-prioritizer/readme.txt +++ b/plugins/image-prioritizer/readme.txt @@ -7,7 +7,7 @@ License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, optimization, image, lcp, lazy-load -Prioritizes the loading of images and videos based on how visible they are to actual visitors; adds fetchpriority and applies lazy loading. +Prioritizes the loading of images and videos based on how they appear to actual visitors: adds fetchpriority, preloads, lazy-loads, and sets sizes. == Description == @@ -27,7 +27,9 @@ The current optimizations include: 1. Apply lazy loading to `IMG` tags based on whether they appear in any breakpoint’s initial viewport. 2. Implement lazy loading of CSS background images added via inline `style` attributes. 3. Lazy-load `VIDEO` tags by setting the appropriate attributes based on whether they appear in the initial viewport. If a `VIDEO` is the LCP element, it gets `preload=auto`; if it is in an initial viewport, the `preload=metadata` default is left; if it is not in an initial viewport, it gets `preload=none`. Lazy-loaded videos also get initial `preload`, `autoplay`, and `poster` attributes restored when the `VIDEO` is going to enter the viewport. -5. Ensure that [`sizes=auto`](https://make.wordpress.org/core/2024/10/18/auto-sizes-for-lazy-loaded-images-in-wordpress-6-7/) is added to all lazy-loaded `IMG` elements. +5. Responsive image sizes: + 1. Ensure [`sizes=auto`](https://make.wordpress.org/core/2024/10/18/auto-sizes-for-lazy-loaded-images-in-wordpress-6-7/) is added to all lazy-loaded `IMG` elements. + 2. Compute the `sizes` attribute using the widths of an image collected from URL Metrics for each breakpoint (when not lazy-loaded since then handled by `sizes=auto`). 6. Reduce the size of the `poster` image of a `VIDEO` from full size to the size appropriate for the maximum width of the video (on desktop). **This plugin requires the [Optimization Detective](https://wordpress.org/plugins/optimization-detective/) plugin as a dependency.** Please refer to that plugin for additional background on how this plugin works as well as additional developer options. From d2b9a5c2c9503ee0e8965e0a506ac685460525d4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Feb 2025 14:07:18 -0800 Subject: [PATCH 10/11] Add comment explaining why original sizes are retained --- .../class-image-prioritizer-img-tag-visitor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 5a6e4ae083..bd6bb88800 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -172,6 +172,10 @@ private function process_img( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_C $computed_sizes = $this->compute_sizes( $context ); if ( count( $computed_sizes ) > 0 ) { $new_sizes = join( ', ', $computed_sizes ); + + // Preserve the original sizes as a fallback when URL Metrics are missing from one or more viewport group. + // Note that when all groups are populated, the media features will span all possible viewport widths from + // zero to infinity, so there is no need to include the original sizes since they will never match. if ( '' !== $sizes && ! $context->url_metric_group_collection->is_every_group_populated() ) { $new_sizes .= ", $sizes"; } From 4d0251a4396c3e3621a30eacb37854dcba45dd58 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Feb 2025 17:37:43 -0800 Subject: [PATCH 11/11] Improve description of sizes computation Co-authored-by: felixarntz --- plugins/image-prioritizer/readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/image-prioritizer/readme.txt b/plugins/image-prioritizer/readme.txt index 92ea6e3887..e7ca146c2f 100644 --- a/plugins/image-prioritizer/readme.txt +++ b/plugins/image-prioritizer/readme.txt @@ -28,8 +28,8 @@ The current optimizations include: 2. Implement lazy loading of CSS background images added via inline `style` attributes. 3. Lazy-load `VIDEO` tags by setting the appropriate attributes based on whether they appear in the initial viewport. If a `VIDEO` is the LCP element, it gets `preload=auto`; if it is in an initial viewport, the `preload=metadata` default is left; if it is not in an initial viewport, it gets `preload=none`. Lazy-loaded videos also get initial `preload`, `autoplay`, and `poster` attributes restored when the `VIDEO` is going to enter the viewport. 5. Responsive image sizes: - 1. Ensure [`sizes=auto`](https://make.wordpress.org/core/2024/10/18/auto-sizes-for-lazy-loaded-images-in-wordpress-6-7/) is added to all lazy-loaded `IMG` elements. - 2. Compute the `sizes` attribute using the widths of an image collected from URL Metrics for each breakpoint (when not lazy-loaded since then handled by `sizes=auto`). + 1. Compute the `sizes` attribute using the widths of an image collected from URL Metrics for each breakpoint (when not lazy-loaded since then handled by `sizes=auto`). + 2. Ensure [`sizes=auto`](https://make.wordpress.org/core/2024/10/18/auto-sizes-for-lazy-loaded-images-in-wordpress-6-7/) is set on `IMG` tags after setting correct lazy-loading (above). 6. Reduce the size of the `poster` image of a `VIDEO` from full size to the size appropriate for the maximum width of the video (on desktop). **This plugin requires the [Optimization Detective](https://wordpress.org/plugins/optimization-detective/) plugin as a dependency.** Please refer to that plugin for additional background on how this plugin works as well as additional developer options.