14
14
/**
15
15
* Tag visitor that optimizes elements with background-image styles.
16
16
*
17
+ * @phpstan-type LcpElementExternalBackgroundImage array{
18
+ * url: non-empty-string,
19
+ * tag: non-empty-string,
20
+ * id: string|null,
21
+ * class: string|null,
22
+ * }
23
+ *
17
24
* @since 0.1.0
18
25
* @access private
19
26
*/
@@ -35,6 +42,14 @@ final class Image_Prioritizer_Background_Image_Styled_Tag_Visitor extends Image_
35
42
*/
36
43
private $ added_lazy_assets = false ;
37
44
45
+ /**
46
+ * Tuples of URL Metric group and the common LCP element external background image.
47
+ *
48
+ * @since n.e.x.t
49
+ * @var array<array{OD_URL_Metric_Group, LcpElementExternalBackgroundImage}>
50
+ */
51
+ private $ group_common_lcp_element_external_background_images ;
52
+
38
53
/**
39
54
* Visits a tag.
40
55
*
@@ -65,33 +80,128 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
65
80
}
66
81
67
82
if ( is_null ( $ background_image_url ) ) {
83
+ $ this ->maybe_preload_external_lcp_background_image ( $ context );
68
84
return false ;
69
85
}
70
86
71
87
$ xpath = $ processor ->get_xpath ();
72
88
73
89
// If this element is the LCP (for a breakpoint group), add a preload link for it.
74
90
foreach ( $ context ->url_metric_group_collection ->get_groups_by_lcp_element ( $ xpath ) as $ group ) {
75
- $ link_attributes = array (
76
- 'rel ' => 'preload ' ,
77
- 'fetchpriority ' => 'high ' ,
78
- 'as ' => 'image ' ,
79
- 'href ' => $ background_image_url ,
80
- 'media ' => 'screen ' ,
81
- );
82
-
83
- $ context ->link_collection ->add_link (
84
- $ link_attributes ,
85
- $ group ->get_minimum_viewport_width (),
86
- $ group ->get_maximum_viewport_width ()
87
- );
91
+ $ this ->add_image_preload_link ( $ context ->link_collection , $ group , $ background_image_url );
88
92
}
89
93
90
94
$ this ->lazy_load_bg_images ( $ context );
91
95
92
96
return true ;
93
97
}
94
98
99
+ /**
100
+ * Gets the common LCP element external background image for a URL Metric group.
101
+ *
102
+ * @since n.e.x.t
103
+ *
104
+ * @param OD_URL_Metric_Group $group Group.
105
+ * @return LcpElementExternalBackgroundImage|null
106
+ */
107
+ private function get_common_lcp_element_external_background_image ( OD_URL_Metric_Group $ group ): ?array {
108
+
109
+ // If the group is not fully populated, we don't have enough URL Metrics to reliably know whether the background image is consistent across page loads.
110
+ // This is intentionally not using $group->is_complete() because we still will use stale URL Metrics in the calculation.
111
+ if ( $ group ->count () !== $ group ->get_sample_size () ) {
112
+ return null ;
113
+ }
114
+
115
+ $ previous_lcp_element_external_background_image = null ;
116
+ foreach ( $ group as $ url_metric ) {
117
+ /**
118
+ * Stored data.
119
+ *
120
+ * @var LcpElementExternalBackgroundImage|null $lcp_element_external_background_image
121
+ */
122
+ $ lcp_element_external_background_image = $ url_metric ->get ( 'lcpElementExternalBackgroundImage ' );
123
+ if ( ! is_array ( $ lcp_element_external_background_image ) ) {
124
+ return null ;
125
+ }
126
+ if ( null !== $ previous_lcp_element_external_background_image && $ previous_lcp_element_external_background_image !== $ lcp_element_external_background_image ) {
127
+ return null ;
128
+ }
129
+ $ previous_lcp_element_external_background_image = $ lcp_element_external_background_image ;
130
+ }
131
+
132
+ return $ previous_lcp_element_external_background_image ;
133
+ }
134
+
135
+ /**
136
+ * Maybe preloads external background image.
137
+ *
138
+ * @since n.e.x.t
139
+ *
140
+ * @param OD_Tag_Visitor_Context $context Context.
141
+ */
142
+ private function maybe_preload_external_lcp_background_image ( OD_Tag_Visitor_Context $ context ): void {
143
+ // Gather the tuples of URL Metric group and the common LCP element external background image.
144
+ // Note the groups of URL Metrics do not change across invocations, we just need to compute this once for all.
145
+ if ( ! is_array ( $ this ->group_common_lcp_element_external_background_images ) ) {
146
+ $ this ->group_common_lcp_element_external_background_images = array ();
147
+ foreach ( $ context ->url_metric_group_collection as $ group ) {
148
+ $ common = $ this ->get_common_lcp_element_external_background_image ( $ group );
149
+ if ( is_array ( $ common ) ) {
150
+ $ this ->group_common_lcp_element_external_background_images [] = array ( $ group , $ common );
151
+ }
152
+ }
153
+ }
154
+
155
+ // There are no common LCP background images, so abort.
156
+ if ( count ( $ this ->group_common_lcp_element_external_background_images ) === 0 ) {
157
+ return ;
158
+ }
159
+
160
+ $ processor = $ context ->processor ;
161
+ $ tag_name = strtoupper ( (string ) $ processor ->get_tag () );
162
+ foreach ( array_keys ( $ this ->group_common_lcp_element_external_background_images ) as $ i ) {
163
+ list ( $ group , $ common ) = $ this ->group_common_lcp_element_external_background_images [ $ i ];
164
+ if (
165
+ // Note that the browser may send a lower-case tag name in the case of XHTML or embedded SVG/MathML, but
166
+ // the HTML Tag Processor is currently normalizing to all upper-case. The HTML Processor on the other
167
+ // hand may return the expected case.
168
+ strtoupper ( $ common ['tag ' ] ) === $ tag_name
169
+ &&
170
+ $ processor ->get_attribute ( 'id ' ) === $ common ['id ' ] // May be checking equality with null.
171
+ &&
172
+ $ processor ->get_attribute ( 'class ' ) === $ common ['class ' ] // May be checking equality with null.
173
+ ) {
174
+ $ this ->add_image_preload_link ( $ context ->link_collection , $ group , $ common ['url ' ] );
175
+
176
+ // Now that the preload link has been added, eliminate the entry to stop looking for it while iterating over the rest of the document.
177
+ unset( $ this ->group_common_lcp_element_external_background_images [ $ i ] );
178
+ }
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Adds an image preload link for the group.
184
+ *
185
+ * @since n.e.x.t
186
+ *
187
+ * @param OD_Link_Collection $link_collection Link collection.
188
+ * @param OD_URL_Metric_Group $group URL Metric group.
189
+ * @param non-empty-string $url Image URL.
190
+ */
191
+ private function add_image_preload_link ( OD_Link_Collection $ link_collection , OD_URL_Metric_Group $ group , string $ url ): void {
192
+ $ link_collection ->add_link (
193
+ array (
194
+ 'rel ' => 'preload ' ,
195
+ 'fetchpriority ' => 'high ' ,
196
+ 'as ' => 'image ' ,
197
+ 'href ' => $ url ,
198
+ 'media ' => 'screen ' ,
199
+ ),
200
+ $ group ->get_minimum_viewport_width (),
201
+ $ group ->get_maximum_viewport_width ()
202
+ );
203
+ }
204
+
95
205
/**
96
206
* Optimizes an element with a background image based on whether it is displayed in any initial viewport.
97
207
*
0 commit comments