Skip to content

Commit

Permalink
Merge pull request #1595 from WordPress/fix/1575-poster-size
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy authored Oct 17, 2024
2 parents b7e9c5d + 69c717a commit ecceec8
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ private function reduce_layout_shifts( OD_Tag_Visitor_Context $context ): void {
$embed_wrapper_xpath = self::get_embed_wrapper_xpath( $processor->get_xpath() );

/**
* Collection of the minimum heights for the element with each group keyed by the minimum viewport with.
* Collection of the minimum heights for the element with each group keyed by the minimum viewport width.
*
* @var array<int, array{group: OD_URL_Metric_Group, height: int}> $minimums
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,99 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
return false;
}

// Skip empty poster attributes and data: URLs.
$poster = trim( (string) $processor->get_attribute( 'poster' ) );
// TODO: If $context->url_metric_group_collection->get_element_max_intersection_ratio( $xpath ) is 0.0, then the video is not in any initial viewport and the VIDEO tag could get the preload=none attribute added.

$poster = $this->get_poster( $context );

if ( null !== $poster ) {
$this->reduce_poster_image_size( $poster, $context );
$this->preload_poster_image( $poster, $context );

return true;
}

return false;
}

/**
* Gets the poster from the current VIDEO element.
*
* Skips empty poster attributes and data: URLs.
*
* @since n.e.x.t
*
* @param OD_Tag_Visitor_Context $context Tag visitor context.
* @return non-empty-string|null Poster or null if not defined or is a data: URL.
*/
private function get_poster( OD_Tag_Visitor_Context $context ): ?string {
$poster = trim( (string) $context->processor->get_attribute( 'poster' ) );
if ( '' === $poster || $this->is_data_url( $poster ) ) {
return false;
return null;
}
return $poster;
}

/**
* Reduces poster image size by choosing one that fits the maximum video size more closely.
*
* @since n.e.x.t
*
* @param non-empty-string $poster Poster image URL.
* @param OD_Tag_Visitor_Context $context Tag visitor context, with the cursor currently at a VIDEO tag.
*/
private function reduce_poster_image_size( string $poster, OD_Tag_Visitor_Context $context ): void {
$processor = $context->processor;

$xpath = $processor->get_xpath();

// TODO: If $context->url_metric_group_collection->get_element_max_intersection_ratio( $xpath ) is 0.0, then the video is not in any initial viewport and the VIDEO tag could get the preload=none attribute added.
/*
* Obtain maximum width of the element exclusively from the URL metrics group with the widest viewport width,
* which would be desktop. This prevents the situation where if URL metrics have only so far been gathered for
* mobile viewports that an excessively-small poster would end up getting served to the first desktop visitor.
*/
$max_element_width = 0;
$widest_group = array_reduce(
iterator_to_array( $context->url_metric_group_collection ),
static function ( $carry, OD_URL_Metric_Group $group ) {
return ( null === $carry || $group->get_minimum_viewport_width() > $carry->get_minimum_viewport_width() ) ? $group : $carry;
}
);
foreach ( $widest_group as $url_metric ) {
foreach ( $url_metric->get_elements() as $element ) {
if ( $element['xpath'] === $xpath ) {
$max_element_width = max( $max_element_width, $element['boundingClientRect']['width'] );
break; // Move on to the next URL Metric.
}
}
}

// If the element wasn't present in any URL Metrics gathered for desktop, then abort downsizing the poster.
if ( 0 === $max_element_width ) {
return;
}

$poster_id = attachment_url_to_postid( $poster );

if ( $poster_id > 0 ) {
$smaller_image_url = wp_get_attachment_image_url( $poster_id, array( (int) $max_element_width, 0 ) );
if ( is_string( $smaller_image_url ) ) {
$processor->set_attribute( 'poster', $smaller_image_url );
}
}
}

/**
* Preloads poster image for the LCP <video> element.
*
* @since n.e.x.t
*
* @param non-empty-string $poster Poster image URL.
* @param OD_Tag_Visitor_Context $context Tag visitor context, with the cursor currently at a VIDEO tag.
*/
private function preload_poster_image( string $poster, OD_Tag_Visitor_Context $context ): void {
$processor = $context->processor;

$xpath = $processor->get_xpath();

// If this element is the LCP (for a breakpoint group), add a preload link for it.
foreach ( $context->url_metric_group_collection->get_groups_by_lcp_element( $xpath ) as $group ) {
Expand All @@ -66,7 +150,5 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
$group->get_maximum_viewport_width()
);
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

$full_url = '';
$expected_url = '';

return array(
'set_up' => static function ( Test_Image_Prioritizer_Helper $test_case, WP_UnitTest_Factory $factory ) use ( &$full_url, &$expected_url ): void {
$breakpoint_max_widths = array( 480, 600, 782 );
add_filter(
'od_breakpoint_max_widths',
static function () use ( $breakpoint_max_widths ) {
return $breakpoint_max_widths;
}
);

$element = array(
'isLCP' => false,
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]',
'boundingClientRect' => $test_case->get_sample_dom_rect(),
);

foreach ( array_merge( $breakpoint_max_widths, array( 1000 ) ) as $viewport_width ) {
OD_URL_Metrics_Post_Type::store_url_metric(
od_get_url_metrics_slug( od_get_normalized_query_vars() ),
$test_case->get_sample_url_metric(
array(
'viewport_width' => $viewport_width,
'elements' => array( $element ),
)
)
);
}

$attachment_id = $factory->attachment->create_object(
DIR_TESTDATA . '/images/33772.jpg',
0,
array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
)
);

wp_generate_attachment_metadata( $attachment_id, DIR_TESTDATA . '/images/33772.jpg' );

$full_url = wp_get_attachment_url( $attachment_id );
$expected_url = wp_get_attachment_image_url( $attachment_id, array( (int) $element['boundingClientRect']['width'], 0 ) );
},
'buffer' => static function () use ( &$full_url ) {
return "
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>...</title>
</head>
<body>
<video class=\"desktop\" poster=\"$full_url\" width=\"1200\" height=\"500\" crossorigin>
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
</video>
</body>
</html>
";
},
'expected' => static function () use ( &$full_url, &$expected_url ) {
return "
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>...</title>
</head>
<body>
<video data-od-replaced-poster=\"$full_url\" data-od-xpath=\"/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]\" class=\"desktop\" poster=\"$expected_url\" width=\"1200\" height=\"500\" crossorigin>
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
</video>
<script type=\"module\">/* import detect ... */</script>
</body>
</html>
";
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

$full_url = '';

return array(
'set_up' => static function ( Test_Image_Prioritizer_Helper $test_case, WP_UnitTest_Factory $factory ) use ( &$full_url ): void {
$breakpoint_max_widths = array( 480, 600, 782 );
add_filter(
'od_breakpoint_max_widths',
static function () use ( $breakpoint_max_widths ) {
return $breakpoint_max_widths;
}
);

$element = array(
'isLCP' => false,
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]',
'boundingClientRect' => $test_case->get_sample_dom_rect(),
);

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() ),
$test_case->get_sample_url_metric(
array(
'viewport_width' => $non_desktop_viewport_width,
'elements' => array( $element ),
)
)
);
}

$attachment_id = $factory->attachment->create_object(
DIR_TESTDATA . '/images/33772.jpg',
0,
array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
)
);

wp_generate_attachment_metadata( $attachment_id, DIR_TESTDATA . '/images/33772.jpg' );

$full_url = wp_get_attachment_url( $attachment_id );
},
'buffer' => static function () use ( &$full_url ) {
return "
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>...</title>
</head>
<body>
<video class=\"desktop\" poster=\"$full_url\" width=\"1200\" height=\"500\" crossorigin>
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
</video>
</body>
</html>
";
},
'expected' => static function () use ( &$full_url ) {
return "
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>...</title>
</head>
<body>
<video data-od-xpath=\"/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]\" class=\"desktop\" poster=\"$full_url\" width=\"1200\" height=\"500\" crossorigin>
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
</video>
<script type=\"module\">/* import detect ... */</script>
</body>
</html>
";
},
);
11 changes: 9 additions & 2 deletions plugins/image-prioritizer/tests/test-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,23 @@ public function data_provider_test_filter_tag_visitors(): array {
* @covers Image_Prioritizer_Background_Image_Styled_Tag_Visitor
*
* @dataProvider data_provider_test_filter_tag_visitors
*
* @param callable $set_up Setup function.
* @param callable|string $buffer Content before.
* @param callable|string $expected Expected content after.
*/
public function test_image_prioritizer_register_tag_visitors( Closure $set_up, string $buffer, string $expected ): void {
$set_up( $this );
public function test_image_prioritizer_register_tag_visitors( callable $set_up, $buffer, $expected ): void {
$set_up( $this, $this::factory() );

$buffer = is_string( $buffer ) ? $buffer : $buffer();
$buffer = preg_replace(
':<script type="module">.+?</script>:s',
'<script type="module">/* import detect ... */</script>',
od_optimize_template_output_buffer( $buffer )
);

$expected = is_string( $expected ) ? $expected : $expected();

$this->assertEquals(
$this->remove_initial_tabs( $expected ),
$this->remove_initial_tabs( $buffer ),
Expand Down

0 comments on commit ecceec8

Please sign in to comment.