-
Notifications
You must be signed in to change notification settings - Fork 138
Integrate Auto Sizes with Image Prioritizer to ensure correct sizes=auto #1322
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
Changes from all commits
3c0426e
f7b4798
4090361
cf22f8c
99a5a03
83cabaf
b485b35
f5e4231
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| <?php | ||
| /** | ||
| * Optimization Detective extensions by Auto Sizes. | ||
| * | ||
| * @since n.e.x.t | ||
| * @package auto-sizes | ||
| */ | ||
|
|
||
| if ( ! defined( 'ABSPATH' ) ) { | ||
| exit; // Exit if accessed directly. | ||
| } | ||
|
|
||
| /** | ||
| * Visits responsive lazy-loaded IMG tags to ensure they include sizes=auto. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param OD_Tag_Visitor_Context $context Tag visitor context. | ||
| * @return false Whether the tag should be recorded in URL metrics. | ||
| */ | ||
| function auto_sizes_visit_tag( OD_Tag_Visitor_Context $context ): bool { | ||
| if ( 'IMG' !== $context->processor->get_tag() ) { | ||
| return false; | ||
| } | ||
|
|
||
| $sizes = $context->processor->get_attribute( 'sizes' ); | ||
| if ( ! is_string( $sizes ) ) { | ||
| return false; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, what if
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in f5e4231 |
||
| } | ||
|
|
||
| $sizes = preg_split( '/\s*,\s*/', $sizes ); | ||
| if ( ! is_array( $sizes ) ) { | ||
| return false; | ||
| } | ||
|
|
||
| $is_lazy_loaded = ( 'lazy' === $context->processor->get_attribute( 'loading' ) ); | ||
| $has_auto_sizes = in_array( 'auto', $sizes, true ); | ||
|
|
||
| $changed = false; | ||
| if ( $is_lazy_loaded && ! $has_auto_sizes ) { | ||
| array_unshift( $sizes, 'auto' ); | ||
| $changed = true; | ||
| } elseif ( ! $is_lazy_loaded && $has_auto_sizes ) { | ||
| $sizes = array_diff( $sizes, array( 'auto' ) ); | ||
| $changed = true; | ||
| } | ||
| if ( $changed ) { | ||
| $context->processor->set_attribute( 'sizes', join( ', ', $sizes ) ); | ||
| } | ||
|
|
||
| return false; // Since this tag visitor does not require this tag to be included in the URL Metrics. | ||
| } | ||
|
|
||
| /** | ||
| * Registers the tag visitor for image tags. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param OD_Tag_Visitor_Registry $registry Tag visitor registry. | ||
| */ | ||
| function auto_sizes_register_tag_visitors( OD_Tag_Visitor_Registry $registry ): void { | ||
| $registry->register( 'auto-sizes', 'auto_sizes_visit_tag' ); | ||
| } | ||
|
|
||
| // Important: The Image Prioritizer's IMG tag visitor is registered at priority 10, so priority 100 ensures that the loading attribute has been correctly set by the time the Auto Sizes visitor runs. | ||
| add_action( 'od_register_tag_visitors', 'auto_sizes_register_tag_visitors', 100 ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?php | ||
| /** | ||
| * Test Bootstrap for Auto Sizes. | ||
| * | ||
| * @package auto-sizes | ||
| */ | ||
|
|
||
| // Require the suggested plugins. | ||
| require_once __DIR__ . '/../../optimization-detective/load.php'; | ||
| require_once __DIR__ . '/../../image-prioritizer/load.php'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| <?php | ||
| /** | ||
| * Tests for auto-sizes plugin's optimization-detective.php. | ||
| * | ||
| * @package auto-sizes | ||
| */ | ||
|
|
||
| class Test_Auto_Sizes_Optimization_Detective extends WP_UnitTestCase { | ||
| /** | ||
| * Runs the routine before each test is executed. | ||
| */ | ||
| public function set_up(): void { | ||
| parent::set_up(); | ||
| if ( ! defined( 'OPTIMIZATION_DETECTIVE_VERSION' ) ) { | ||
| $this->markTestSkipped( 'Optimization Detective is not active.' ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Tests auto_sizes_register_tag_visitors(). | ||
| * | ||
| * @covers ::auto_sizes_register_tag_visitors | ||
| */ | ||
| public function test_auto_sizes_register_tag_visitors(): void { | ||
| if ( ! class_exists( OD_Tag_Visitor_Registry::class ) ) { | ||
| $this->markTestSkipped( 'Optimization Detective is not active.' ); | ||
| } | ||
| $registry = new OD_Tag_Visitor_Registry(); | ||
| auto_sizes_register_tag_visitors( $registry ); | ||
| $this->assertTrue( $registry->is_registered( 'auto-sizes' ) ); | ||
| $this->assertEquals( 'auto_sizes_visit_tag', $registry->get_registered( 'auto-sizes' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Data provider. | ||
| * | ||
| * @return array<string, mixed> Data. | ||
| */ | ||
| public function data_provider_test_od_optimize_template_output_buffer(): array { | ||
| 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( | ||
| 'element_metrics' => array( | ||
| 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
| 'isLCP' => false, | ||
| 'intersectionRatio' => 1, | ||
| ), | ||
| 'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
| 'expected' => '<img data-od-removed-loading="lazy" src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
| ), | ||
|
|
||
| 'non_responsive_image' => array( | ||
| 'element_metrics' => array( | ||
| 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
| 'isLCP' => false, | ||
| 'intersectionRatio' => 0, | ||
| ), | ||
| 'buffer' => '<img src="https://example.com/foo.jpg" alt="Quux" width="1200" height="800" loading="lazy">', | ||
| 'expected' => '<img src="https://example.com/foo.jpg" alt="Quux" width="1200" height="800" loading="lazy">', | ||
| ), | ||
|
|
||
| 'auto_sizes_added' => array( | ||
| 'element_metrics' => array( | ||
| 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
| 'isLCP' => false, | ||
| 'intersectionRatio' => 0, | ||
| ), | ||
| 'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
| 'expected' => '<img data-od-replaced-sizes="(max-width: 600px) 480px, 800px" src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
| ), | ||
|
|
||
| 'auto_sizes_already_added' => array( | ||
| 'element_metrics' => array( | ||
| 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
| 'isLCP' => false, | ||
| 'intersectionRatio' => 0, | ||
| ), | ||
| 'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
| 'expected' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
| ), | ||
|
|
||
| // If Auto Sizes added the sizes=auto attribute but Image Prioritizer ended up removing it due to the image not being lazy-loaded, remove sizes=auto again. | ||
| 'wrongly_auto_sized_responsive_img' => array( | ||
| 'element_metrics' => array( | ||
| 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
| 'isLCP' => false, | ||
| 'intersectionRatio' => 1, | ||
| ), | ||
| 'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
| 'expected' => '<img data-od-replaced-sizes="auto, (max-width: 600px) 480px, 800px" data-od-removed-loading="lazy" src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Test auto_sizes_visit_tag(). | ||
| * | ||
| * @covers ::auto_sizes_visit_tag | ||
| * | ||
| * @dataProvider data_provider_test_od_optimize_template_output_buffer | ||
| * @throws Exception But it won't. | ||
| * @phpstan-param array<string, mixed> $element_metrics | ||
| */ | ||
| public function test_od_optimize_template_output_buffer( array $element_metrics, string $buffer, string $expected ): void { | ||
| $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( | ||
| $slug, | ||
| $this->get_validated_url_metric( | ||
| $viewport_width, | ||
| array( | ||
| $element_metrics, | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| $remove_initial_tabs = static function ( string $input ): string { | ||
| return (string) preg_replace( '/^\t+/m', '', $input ); | ||
| }; | ||
|
|
||
| $html_start_doc = '<html lang="en"><head><meta charset="utf-8"><title>...</title></head><body>'; | ||
| $html_end_doc = '</body></html>'; | ||
|
|
||
| $expected = $remove_initial_tabs( $expected ); | ||
| $buffer = $remove_initial_tabs( $buffer ); | ||
|
|
||
| $buffer = od_optimize_template_output_buffer( $html_start_doc . $buffer . $html_end_doc ); | ||
| $buffer = preg_replace( '#.+?<body[^>]*>#s', '', $buffer ); | ||
| $buffer = preg_replace( '#</body>.*$#s', '', $buffer ); | ||
|
|
||
| $this->assertEquals( $expected, $buffer ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets a validated URL metric. | ||
| * | ||
| * @param int $viewport_width Viewport width for the URL metric. | ||
| * @param array<array{xpath: string, isLCP: bool}> $elements Elements. | ||
| * @return OD_URL_Metric URL metric. | ||
| * @throws Exception From OD_URL_Metric if there is a parse error, but there won't be. | ||
| */ | ||
| private function get_validated_url_metric( int $viewport_width, array $elements = array() ): OD_URL_Metric { | ||
| $data = array( | ||
| 'url' => home_url( '/' ), | ||
| 'viewport' => array( | ||
| 'width' => $viewport_width, | ||
| 'height' => 800, | ||
| ), | ||
| 'timestamp' => microtime( true ), | ||
| 'elements' => array_map( | ||
| static function ( array $element ): array { | ||
| return array_merge( | ||
| array( | ||
| 'isLCPCandidate' => true, | ||
| 'intersectionRatio' => 1, | ||
| 'intersectionRect' => array( | ||
| 'width' => 100, | ||
| 'height' => 100, | ||
| ), | ||
| 'boundingClientRect' => array( | ||
| 'width' => 100, | ||
| 'height' => 100, | ||
| ), | ||
| ), | ||
| $element | ||
| ); | ||
| }, | ||
| $elements | ||
| ), | ||
| ); | ||
| return new OD_URL_Metric( $data ); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.