Skip to content

Commit ecceec8

Browse files
authored
Merge pull request #1595 from WordPress/fix/1575-poster-size
2 parents b7e9c5d + 69c717a commit ecceec8

5 files changed

Lines changed: 258 additions & 9 deletions

‎plugins/embed-optimizer/class-embed-optimizer-tag-visitor.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ private function reduce_layout_shifts( OD_Tag_Visitor_Context $context ): void {
208208
$embed_wrapper_xpath = self::get_embed_wrapper_xpath( $processor->get_xpath() );
209209

210210
/**
211-
* Collection of the minimum heights for the element with each group keyed by the minimum viewport with.
211+
* Collection of the minimum heights for the element with each group keyed by the minimum viewport width.
212212
*
213213
* @var array<int, array{group: OD_URL_Metric_Group, height: int}> $minimums
214214
*/

‎plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php‎

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,99 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
3535
return false;
3636
}
3737

38-
// Skip empty poster attributes and data: URLs.
39-
$poster = trim( (string) $processor->get_attribute( 'poster' ) );
38+
// 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.
39+
40+
$poster = $this->get_poster( $context );
41+
42+
if ( null !== $poster ) {
43+
$this->reduce_poster_image_size( $poster, $context );
44+
$this->preload_poster_image( $poster, $context );
45+
46+
return true;
47+
}
48+
49+
return false;
50+
}
51+
52+
/**
53+
* Gets the poster from the current VIDEO element.
54+
*
55+
* Skips empty poster attributes and data: URLs.
56+
*
57+
* @since n.e.x.t
58+
*
59+
* @param OD_Tag_Visitor_Context $context Tag visitor context.
60+
* @return non-empty-string|null Poster or null if not defined or is a data: URL.
61+
*/
62+
private function get_poster( OD_Tag_Visitor_Context $context ): ?string {
63+
$poster = trim( (string) $context->processor->get_attribute( 'poster' ) );
4064
if ( '' === $poster || $this->is_data_url( $poster ) ) {
41-
return false;
65+
return null;
4266
}
67+
return $poster;
68+
}
69+
70+
/**
71+
* Reduces poster image size by choosing one that fits the maximum video size more closely.
72+
*
73+
* @since n.e.x.t
74+
*
75+
* @param non-empty-string $poster Poster image URL.
76+
* @param OD_Tag_Visitor_Context $context Tag visitor context, with the cursor currently at a VIDEO tag.
77+
*/
78+
private function reduce_poster_image_size( string $poster, OD_Tag_Visitor_Context $context ): void {
79+
$processor = $context->processor;
4380

4481
$xpath = $processor->get_xpath();
4582

46-
// 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.
83+
/*
84+
* Obtain maximum width of the element exclusively from the URL metrics group with the widest viewport width,
85+
* which would be desktop. This prevents the situation where if URL metrics have only so far been gathered for
86+
* mobile viewports that an excessively-small poster would end up getting served to the first desktop visitor.
87+
*/
88+
$max_element_width = 0;
89+
$widest_group = array_reduce(
90+
iterator_to_array( $context->url_metric_group_collection ),
91+
static function ( $carry, OD_URL_Metric_Group $group ) {
92+
return ( null === $carry || $group->get_minimum_viewport_width() > $carry->get_minimum_viewport_width() ) ? $group : $carry;
93+
}
94+
);
95+
foreach ( $widest_group as $url_metric ) {
96+
foreach ( $url_metric->get_elements() as $element ) {
97+
if ( $element['xpath'] === $xpath ) {
98+
$max_element_width = max( $max_element_width, $element['boundingClientRect']['width'] );
99+
break; // Move on to the next URL Metric.
100+
}
101+
}
102+
}
103+
104+
// If the element wasn't present in any URL Metrics gathered for desktop, then abort downsizing the poster.
105+
if ( 0 === $max_element_width ) {
106+
return;
107+
}
108+
109+
$poster_id = attachment_url_to_postid( $poster );
110+
111+
if ( $poster_id > 0 ) {
112+
$smaller_image_url = wp_get_attachment_image_url( $poster_id, array( (int) $max_element_width, 0 ) );
113+
if ( is_string( $smaller_image_url ) ) {
114+
$processor->set_attribute( 'poster', $smaller_image_url );
115+
}
116+
}
117+
}
118+
119+
/**
120+
* Preloads poster image for the LCP <video> element.
121+
*
122+
* @since n.e.x.t
123+
*
124+
* @param non-empty-string $poster Poster image URL.
125+
* @param OD_Tag_Visitor_Context $context Tag visitor context, with the cursor currently at a VIDEO tag.
126+
*/
127+
private function preload_poster_image( string $poster, OD_Tag_Visitor_Context $context ): void {
128+
$processor = $context->processor;
129+
130+
$xpath = $processor->get_xpath();
47131

48132
// If this element is the LCP (for a breakpoint group), add a preload link for it.
49133
foreach ( $context->url_metric_group_collection->get_groups_by_lcp_element( $xpath ) as $group ) {
@@ -66,7 +150,5 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
66150
$group->get_maximum_viewport_width()
67151
);
68152
}
69-
70-
return true;
71153
}
72154
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
$full_url = '';
4+
$expected_url = '';
5+
6+
return array(
7+
'set_up' => static function ( Test_Image_Prioritizer_Helper $test_case, WP_UnitTest_Factory $factory ) use ( &$full_url, &$expected_url ): void {
8+
$breakpoint_max_widths = array( 480, 600, 782 );
9+
add_filter(
10+
'od_breakpoint_max_widths',
11+
static function () use ( $breakpoint_max_widths ) {
12+
return $breakpoint_max_widths;
13+
}
14+
);
15+
16+
$element = array(
17+
'isLCP' => false,
18+
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]',
19+
'boundingClientRect' => $test_case->get_sample_dom_rect(),
20+
);
21+
22+
foreach ( array_merge( $breakpoint_max_widths, array( 1000 ) ) as $viewport_width ) {
23+
OD_URL_Metrics_Post_Type::store_url_metric(
24+
od_get_url_metrics_slug( od_get_normalized_query_vars() ),
25+
$test_case->get_sample_url_metric(
26+
array(
27+
'viewport_width' => $viewport_width,
28+
'elements' => array( $element ),
29+
)
30+
)
31+
);
32+
}
33+
34+
$attachment_id = $factory->attachment->create_object(
35+
DIR_TESTDATA . '/images/33772.jpg',
36+
0,
37+
array(
38+
'post_mime_type' => 'image/jpeg',
39+
'post_excerpt' => 'A sample caption',
40+
)
41+
);
42+
43+
wp_generate_attachment_metadata( $attachment_id, DIR_TESTDATA . '/images/33772.jpg' );
44+
45+
$full_url = wp_get_attachment_url( $attachment_id );
46+
$expected_url = wp_get_attachment_image_url( $attachment_id, array( (int) $element['boundingClientRect']['width'], 0 ) );
47+
},
48+
'buffer' => static function () use ( &$full_url ) {
49+
return "
50+
<html lang=\"en\">
51+
<head>
52+
<meta charset=\"utf-8\">
53+
<title>...</title>
54+
</head>
55+
<body>
56+
<video class=\"desktop\" poster=\"$full_url\" width=\"1200\" height=\"500\" crossorigin>
57+
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
58+
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
59+
</video>
60+
</body>
61+
</html>
62+
";
63+
},
64+
'expected' => static function () use ( &$full_url, &$expected_url ) {
65+
return "
66+
<html lang=\"en\">
67+
<head>
68+
<meta charset=\"utf-8\">
69+
<title>...</title>
70+
</head>
71+
<body>
72+
<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>
73+
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
74+
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
75+
</video>
76+
<script type=\"module\">/* import detect ... */</script>
77+
</body>
78+
</html>
79+
";
80+
},
81+
);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
$full_url = '';
4+
5+
return array(
6+
'set_up' => static function ( Test_Image_Prioritizer_Helper $test_case, WP_UnitTest_Factory $factory ) use ( &$full_url ): void {
7+
$breakpoint_max_widths = array( 480, 600, 782 );
8+
add_filter(
9+
'od_breakpoint_max_widths',
10+
static function () use ( $breakpoint_max_widths ) {
11+
return $breakpoint_max_widths;
12+
}
13+
);
14+
15+
$element = array(
16+
'isLCP' => false,
17+
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]',
18+
'boundingClientRect' => $test_case->get_sample_dom_rect(),
19+
);
20+
21+
foreach ( $breakpoint_max_widths as $non_desktop_viewport_width ) {
22+
OD_URL_Metrics_Post_Type::store_url_metric(
23+
od_get_url_metrics_slug( od_get_normalized_query_vars() ),
24+
$test_case->get_sample_url_metric(
25+
array(
26+
'viewport_width' => $non_desktop_viewport_width,
27+
'elements' => array( $element ),
28+
)
29+
)
30+
);
31+
}
32+
33+
$attachment_id = $factory->attachment->create_object(
34+
DIR_TESTDATA . '/images/33772.jpg',
35+
0,
36+
array(
37+
'post_mime_type' => 'image/jpeg',
38+
'post_excerpt' => 'A sample caption',
39+
)
40+
);
41+
42+
wp_generate_attachment_metadata( $attachment_id, DIR_TESTDATA . '/images/33772.jpg' );
43+
44+
$full_url = wp_get_attachment_url( $attachment_id );
45+
},
46+
'buffer' => static function () use ( &$full_url ) {
47+
return "
48+
<html lang=\"en\">
49+
<head>
50+
<meta charset=\"utf-8\">
51+
<title>...</title>
52+
</head>
53+
<body>
54+
<video class=\"desktop\" poster=\"$full_url\" width=\"1200\" height=\"500\" crossorigin>
55+
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
56+
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
57+
</video>
58+
</body>
59+
</html>
60+
";
61+
},
62+
'expected' => static function () use ( &$full_url ) {
63+
return "
64+
<html lang=\"en\">
65+
<head>
66+
<meta charset=\"utf-8\">
67+
<title>...</title>
68+
</head>
69+
<body>
70+
<video data-od-xpath=\"/*[1][self::HTML]/*[2][self::BODY]/*[1][self::VIDEO]\" class=\"desktop\" poster=\"$full_url\" width=\"1200\" height=\"500\" crossorigin>
71+
<source src=\"https://example.com/header.webm\" type=\"video/webm\">
72+
<source src=\"https://example.com/header.mp4\" type=\"video/mp4\">
73+
</video>
74+
<script type=\"module\">/* import detect ... */</script>
75+
</body>
76+
</html>
77+
";
78+
},
79+
);

‎plugins/image-prioritizer/tests/test-helper.php‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,23 @@ public function data_provider_test_filter_tag_visitors(): array {
7979
* @covers Image_Prioritizer_Background_Image_Styled_Tag_Visitor
8080
*
8181
* @dataProvider data_provider_test_filter_tag_visitors
82+
*
83+
* @param callable $set_up Setup function.
84+
* @param callable|string $buffer Content before.
85+
* @param callable|string $expected Expected content after.
8286
*/
83-
public function test_image_prioritizer_register_tag_visitors( Closure $set_up, string $buffer, string $expected ): void {
84-
$set_up( $this );
87+
public function test_image_prioritizer_register_tag_visitors( callable $set_up, $buffer, $expected ): void {
88+
$set_up( $this, $this::factory() );
8589

90+
$buffer = is_string( $buffer ) ? $buffer : $buffer();
8691
$buffer = preg_replace(
8792
':<script type="module">.+?</script>:s',
8893
'<script type="module">/* import detect ... */</script>',
8994
od_optimize_template_output_buffer( $buffer )
9095
);
9196

97+
$expected = is_string( $expected ) ? $expected : $expected();
98+
9299
$this->assertEquals(
93100
$this->remove_initial_tabs( $expected ),
94101
$this->remove_initial_tabs( $buffer ),

0 commit comments

Comments
 (0)