diff --git a/plugins/embed-optimizer/class-embed-optimizer-tag-visitor.php b/plugins/embed-optimizer/class-embed-optimizer-tag-visitor.php index 072ab03257..3b2636ee81 100644 --- a/plugins/embed-optimizer/class-embed-optimizer-tag-visitor.php +++ b/plugins/embed-optimizer/class-embed-optimizer-tag-visitor.php @@ -50,7 +50,7 @@ private function is_embed_figure( OD_HTML_Tag_Processor $processor ): bool { * @since 0.3.0 * * @param OD_HTML_Tag_Processor $processor Processor. - * @return bool Whether the tag should be measured and stored in URL metrics. + * @return bool Whether the tag should be measured and stored in URL Metrics. */ private function is_embed_wrapper( OD_HTML_Tag_Processor $processor ): bool { return ( @@ -83,7 +83,7 @@ private function is_embed_wrapper( OD_HTML_Tag_Processor $processor ): bool { * @since 0.2.0 * * @param OD_Tag_Visitor_Context $context Tag visitor context. - * @return bool Whether the tag should be tracked in URL metrics. + * @return bool Whether the tag should be tracked in URL Metrics. */ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $processor = $context->processor; @@ -103,7 +103,7 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $this->reduce_layout_shifts( $context ); - // Preconnect links and lazy-loading can only be done once there are URL metrics collected for both mobile and desktop. + // Preconnect links and lazy-loading can only be done once there are URL Metrics collected for both mobile and desktop. if ( $context->url_metric_group_collection->get_first_group()->count() > 0 && diff --git a/plugins/embed-optimizer/detect.js b/plugins/embed-optimizer/detect.js index 596177fd3e..1aab3d0838 100644 --- a/plugins/embed-optimizer/detect.js +++ b/plugins/embed-optimizer/detect.js @@ -1,8 +1,8 @@ /** * Embed Optimizer module for Optimization Detective * - * When a URL metric is being collected by Optimization Detective, this module adds a ResizeObserver to keep track of - * the changed heights for embed blocks. This data is extended/amended onto the element data of the pending URL metric + * When a URL Metric is being collected by Optimization Detective, this module adds a ResizeObserver to keep track of + * the changed heights for embed blocks. This data is extended/amended onto the element data of the pending URL Metric * when it is submitted for storage. */ diff --git a/plugins/embed-optimizer/readme.txt b/plugins/embed-optimizer/readme.txt index 5fb24d2b7c..4f1dc14145 100644 --- a/plugins/embed-optimizer/readme.txt +++ b/plugins/embed-optimizer/readme.txt @@ -11,9 +11,25 @@ Optimizes the performance of embeds by lazy-loading iframes and scripts. == Description == -This plugin's purpose is to optimize the performance of [embeds in WordPress](https://wordpress.org/documentation/article/embeds/), such as YouTube videos, TikToks, and so on. Initially this is achieved by lazy-loading them only when they come into view. This improves performance because embeds are generally very resource-intensive and so lazy-loading them ensures that they don't compete with resources when the page is loading. [Other optimizations](https://github.com/WordPress/performance/issues?q=is%3Aissue+is%3Aopen+label%3A%22%5BPlugin%5D+Embed+Optimizer%22) are planned for the future. +This plugin's purpose is to optimize the performance of [embeds in WordPress](https://wordpress.org/documentation/article/embeds/), such as Tweets, YouTube videos, TikToks, and others. -This plugin also recommends that you install and activate the [Optimization Detective](https://wordpress.org/plugins/optimization-detective/) plugin. When it is active, it will start recording which embeds appear in the initial viewport based on actual visitors to your site. With this information in hand, Embed Optimizer will then avoid lazy-loading embeds which appear in the initial viewport (above the fold). This is important because lazy-loading adds a delay which can hurt the user experience and even degrade the Largest Contentful Paint (LCP) score for the page. In addition to not lazy-loading such above-the-fold embeds, Embed Optimizer will add preconnect links for the hosts of network resources known to be required for the most popular embeds (e.g. YouTube, Twitter, Vimeo, Spotify, VideoPress); this can further speed up the loading of critical embeds. Again, these performance enhancements are only enabled when Optimization Detective is active. +The current optimizations include: + +1. Lazy loading embeds just before they come into view +2. Adding preconnect links for embeds in the initial viewport +3. Reserving space for embeds that resize to reduce layout shifting + +**Lazy loading embeds** improves performance because embeds are generally very resource-intensive, so lazy loading them ensures that they don't compete with resources when the page is loading. Lazy loading of `IFRAME`\-based embeds is handled simply by adding the `loading=lazy` attribute. Lazy loading embeds that include `SCRIPT` tags is handled by using an Intersection Observer to watch for when the embed’s `FIGURE` container is going to enter the viewport and then it dynamically inserts the `SCRIPT` tag. + +**This plugin also recommends that you install and activate the [Optimization Detective](https://wordpress.org/plugins/optimization-detective/) plugin**, which unlocks several optimizations beyond just lazy loading. Without Optimization Detective, lazy loading can actually degrade performance *when an embed is positioned in the initial viewport*. This is because lazy loading such viewport-initial elements can degrade LCP since rendering is delayed by the logic to determine whether the element is visible. This is why WordPress Core tries its best to [avoid](https://make.wordpress.org/core/2021/07/15/refining-wordpress-cores-lazy-loading-implementation/) [lazy loading](https://make.wordpress.org/core/2021/07/15/refining-wordpress-cores-lazy-loading-implementation/) `IMG` tags which appear in the initial viewport, although the server-side heuristics aren’t perfect. This is where Optimization Detective comes in since it detects whether an embed appears in any breakpoint-specific viewports, like mobile, tablet, and desktop. (See also the [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/) plugin which extends Optimization Detective to ensure lazy loading is correctly applied based on whether an IMG is in the initial viewport.) + +When Optimization Detective is active, it will start keeping track of which embeds appear in the initial viewport based on actual visits to your site. With this information in hand, Embed Optimizer will then avoid lazy loading embeds which appear in the initial viewport. Furthermore, for such above-the-fold embeds Embed Optimizer will also **add preconnect links** for resources known to be used by those embeds. For example, if a YouTube embed appears in the initial viewport, Embed Optimizer with Optimization Detective will omit `loading=lazy` while also adding a preconnect link for `https://i.ytimg.com` which is the domain from which YouTube video poster images are served. Such preconnect links cause the initial-viewport embeds to load even faster. + +The other major feature in Embed Optimizer enabled by Optimization Detective is the **reduction of layout shifts** caused by embeds that resize when they load. This is seen commonly in WordPress post embeds or Tweet embeds. Embed Optimizer keeps track of the resized heights of these embeds. With these resized heights stored, Embed Optimizer sets the appropriate height on the container FIGURE element as the viewport-specific `min-height` so that when the embed loads it does not cause a layout shift. + +Since Optimization Detective relies on page visits to learn how the page is laid out, you’ll need to wait until you have visits from a mobile and desktop device to start seeing optimizations applied. Also, note that Optimization Detective does not apply optimizations by default for logged-in admin users. + +Please note that the optimizations are intended to apply to Embed blocks. So if you do not see optimizations applied, make sure that your embeds are not inside of a Classic Block. There are currently **no settings** and no user interface for this plugin since it is designed to work without any configuration. @@ -55,7 +71,7 @@ The [plugin source code](https://github.com/WordPress/performance/tree/trunk/plu **Enhancements** -* Leverage URL metrics to reserve space for embeds to reduce CLS. ([1373](https://github.com/WordPress/performance/pull/1373)) +* Leverage URL Metrics to reserve space for embeds to reduce CLS. ([1373](https://github.com/WordPress/performance/pull/1373)) * Avoid lazy-loading images and embeds unless there are URL Metrics for both mobile and desktop. ([1604](https://github.com/WordPress/performance/pull/1604)) = 0.2.0 = diff --git a/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php index 1b2379df36..798d42f1f6 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php @@ -23,7 +23,7 @@ final class Image_Prioritizer_Background_Image_Styled_Tag_Visitor extends Image_ * Visits a tag. * * @param OD_Tag_Visitor_Context $context Tag visitor context. - * @return bool Whether the tag should be tracked in URL metrics. + * @return bool Whether the tag should be tracked in URL Metrics. */ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $processor = $context->processor; 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 c6c6af5b46..3480c6d72f 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -24,7 +24,7 @@ final class Image_Prioritizer_Img_Tag_Visitor extends Image_Prioritizer_Tag_Visi * * @param OD_Tag_Visitor_Context $context Tag visitor context. * - * @return bool Whether the tag should be tracked in URL metrics. + * @return bool Whether the tag should be tracked in URL Metrics. */ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $processor = $context->processor; @@ -61,7 +61,7 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { * At this point, the element is not the shared LCP across all viewport groups. Nevertheless, server-side * heuristics have added fetchpriority=high to the element, but this is not warranted either due to a lack * of data or because the LCP element is not common across all viewport groups. Since we have collected at - * least some URL metrics (per is_any_group_populated), further below a fetchpriority=high preload link will + * least some URL Metrics (per is_any_group_populated), further below a fetchpriority=high preload link will * be added for the viewport(s) for which this is actually the LCP element. Some viewport groups may never * get populated due to a lack of traffic (e.g. from tablets or phablets), so it is important to remove * fetchpriority=high in such case to prevent server-side heuristics from prioritizing loading the image @@ -71,10 +71,10 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { } /* - * Do not do any lazy-loading if the mobile and desktop viewport groups lack URL metrics. This is important + * Do not do any lazy-loading if the mobile and desktop viewport groups lack URL Metrics. This is important * because if there is an IMG in the initial viewport on desktop but not mobile, if then there are only URL * metrics collected for mobile then the IMG will get lazy-loaded which is good for mobile but for desktop - * it will hurt performance. So this is why it is important to have URL metrics collected for both desktop and + * it will hurt performance. So this is why it is important to have URL Metrics collected for both desktop and * mobile to verify whether maximum intersectionRatio is accounting for both screen sizes. */ $element_max_intersection_ratio = $context->url_metric_group_collection->get_element_max_intersection_ratio( $xpath ); diff --git a/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php index 832db34f21..240b359f65 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-tag-visitor.php @@ -25,7 +25,7 @@ abstract class Image_Prioritizer_Tag_Visitor { * Visits a tag. * * @param OD_Tag_Visitor_Context $context Tag visitor context. - * @return bool Whether the tag should be tracked in URL metrics. + * @return bool Whether the tag should be tracked in URL Metrics. */ abstract public function __invoke( OD_Tag_Visitor_Context $context ): bool; diff --git a/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php index 060f0b8994..120b9a8af8 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php @@ -44,7 +44,7 @@ final class Image_Prioritizer_Video_Tag_Visitor extends Image_Prioritizer_Tag_Vi * @since 0.2.0 * * @param OD_Tag_Visitor_Context $context Tag visitor context. - * @return bool Whether the tag should be tracked in URL metrics. + * @return bool Whether the tag should be tracked in URL Metrics. */ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $processor = $context->processor; @@ -96,8 +96,8 @@ private function reduce_poster_image_size( string $poster, OD_Tag_Visitor_Contex $xpath = $processor->get_xpath(); /* - * 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 + * 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; @@ -173,10 +173,10 @@ private function lazy_load_videos( ?string $poster, OD_Tag_Visitor_Context $cont $processor = $context->processor; /* - * Do not do any lazy-loading if the mobile and desktop viewport groups lack URL metrics. This is important + * Do not do any lazy-loading if the mobile and desktop viewport groups lack URL Metrics. This is important * because if there is a VIDEO in the initial viewport on desktop but not mobile, if then there are only URL * metrics collected for mobile then the VIDEO will get lazy-loaded which is good for mobile but for desktop - * it will hurt performance. So this is why it is important to have URL metrics collected for both desktop and + * it will hurt performance. So this is why it is important to have URL Metrics collected for both desktop and * mobile to verify whether maximum intersectionRatio is accounting for both screen sizes. */ if ( diff --git a/plugins/image-prioritizer/readme.txt b/plugins/image-prioritizer/readme.txt index f7297a80fc..8097fb3d80 100644 --- a/plugins/image-prioritizer/readme.txt +++ b/plugins/image-prioritizer/readme.txt @@ -11,23 +11,23 @@ Optimizes LCP image loading with `fetchpriority=high` and applies image lazy-loa == Description == -This plugin optimizes the loading of images which are the LCP (Largest Contentful Paint) element, including both `img` elements and elements with CSS background images (where there is a `style` attribute with an `background-image` property). Different breakpoints in a theme's responsive design may result in differing elements being the LCP element. Therefore, the LCP element for each breakpoint is captured so that high-fetchpriority preload links with media queries are added which prioritize loading the LCP image specific to the viewport of the visitor. +This plugin optimizes the loading of images (and videos) with prioritization, lazy loading, and more accurate image size selection. -In addition to prioritizing the loading of the LCP image, this plugin also optimizes image loading by ensuring that `loading=lazy` is omitted from any image that appears in the initial viewport for any of the breakpoints, which by default include: +The current optimizations include: -1. 0-320 (small smartphone) -2. 321-480 (normal smartphone) -3. 481-576 (phablets) -4. >576 (desktop) +1. Ensuring `fetchpriority=high` is only added to an `IMG` when it is the Largest Contentful Paint (LCP) element across all responsive breakpoints. +2. Adding breakpoint-specific `fetchpriority=high` preload links for the LCP elements which are `IMG` elements or elements with a CSS `background-image` inline style. +3. Applying lazy-loading to `IMG` tags based on whether they appear in any breakpoint’s initial viewport. (Additionally, [`sizes=auto`](https://make.wordpress.org/core/2024/10/18/auto-sizes-for-lazy-loaded-images-in-wordpress-6-7/) is then also correctly applied.) +4. Adding `fetchpriority=low` to `IMG` tags which appear in the initial viewport but are not visible, such as when they are subsequent carousel slides. +5. Reducing 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). +6. Lazy-loading `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. -If an image does not appear in the initial viewport for any of these viewport groups, then `loading=lazy` is added to the `img` element. +**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. -👉 **Note:** This plugin optimizes pages for actual visitors, and it depends on visitors to optimize pages (since URL metrics need to be collected). As such, you won't see optimizations applied immediately after activating the plugin. And since administrator users are not normal visitors typically, optimizations are not applied for admins by default. +👉 **Note:** This plugin optimizes pages for actual visitors, and it depends on visitors to optimize pages. As such, you won't see optimizations applied immediately after activating the plugin. Please wait for URL Metrics to be gathered for both mobile and desktop visits. And since administrator users are not normal visitors typically, optimizations are not applied for admins by default. There are currently **no settings** and no user interface for this plugin since it is designed to work without any configuration. -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. - == Installation == = Installation from within WordPress = diff --git a/plugins/optimization-detective/class-od-data-validation-exception.php b/plugins/optimization-detective/class-od-data-validation-exception.php index 8ecde91ad3..2efab6fc9c 100644 --- a/plugins/optimization-detective/class-od-data-validation-exception.php +++ b/plugins/optimization-detective/class-od-data-validation-exception.php @@ -12,7 +12,7 @@ } /** - * Exception thrown when failing to validate URL metrics data. + * Exception thrown when failing to validate URL Metrics data. * * @since 0.1.0 * @access private diff --git a/plugins/optimization-detective/class-od-element.php b/plugins/optimization-detective/class-od-element.php index 2f9f776607..c7f243d99b 100644 --- a/plugins/optimization-detective/class-od-element.php +++ b/plugins/optimization-detective/class-od-element.php @@ -12,7 +12,7 @@ } /** - * Data for a single element in a URL metric. + * Data for a single element in a URL Metric. * * @phpstan-import-type ElementData from OD_URL_Metric * @phpstan-import-type DOMRect from OD_URL_Metric @@ -33,7 +33,7 @@ class OD_Element implements ArrayAccess, JsonSerializable { protected $data; /** - * URL metric that this element belongs to. + * URL Metric that this element belongs to. * * @since 0.7.0 * @var OD_URL_Metric @@ -48,7 +48,7 @@ class OD_Element implements ArrayAccess, JsonSerializable { * @phpstan-param ElementData $data * * @param array $data Element data. - * @param OD_URL_Metric $url_metric URL metric. + * @param OD_URL_Metric $url_metric URL Metric. */ public function __construct( array $data, OD_URL_Metric $url_metric ) { $this->data = $data; @@ -56,7 +56,7 @@ public function __construct( array $data, OD_URL_Metric $url_metric ) { } /** - * Gets the URL metric that this element belongs to. + * Gets the URL Metric that this element belongs to. * * @since 0.7.0 * @@ -67,7 +67,7 @@ public function get_url_metric(): OD_URL_Metric { } /** - * Gets the group that this element's URL metric is a part of (which may not be any). + * Gets the group that this element's URL Metric is a part of (which may not be any). * * @since 0.7.0 * diff --git a/plugins/optimization-detective/class-od-strict-url-metric.php b/plugins/optimization-detective/class-od-strict-url-metric.php index f3ecfede21..490315461b 100644 --- a/plugins/optimization-detective/class-od-strict-url-metric.php +++ b/plugins/optimization-detective/class-od-strict-url-metric.php @@ -14,8 +14,8 @@ /** * Representation of the measurements taken from a single client's visit to a specific URL without additionalProperties allowed. * - * This is used exclusively in the REST API endpoint for capturing new URL metrics to prevent invalid additional data from being - * submitted in the request. For URL metrics which have been stored the looser OD_URL_Metric class is used instead. + * This is used exclusively in the REST API endpoint for capturing new URL Metrics to prevent invalid additional data from being + * submitted in the request. For URL Metrics which have been stored the looser OD_URL_Metric class is used instead. * * @since 0.6.0 * @access private diff --git a/plugins/optimization-detective/class-od-tag-visitor-context.php b/plugins/optimization-detective/class-od-tag-visitor-context.php index 6111a64a8f..4dd1279f53 100644 --- a/plugins/optimization-detective/class-od-tag-visitor-context.php +++ b/plugins/optimization-detective/class-od-tag-visitor-context.php @@ -30,7 +30,7 @@ final class OD_Tag_Visitor_Context { public $processor; /** - * URL metric group collection. + * URL Metric group collection. * * @var OD_URL_Metric_Group_Collection * @readonly @@ -49,7 +49,7 @@ final class OD_Tag_Visitor_Context { * Constructor. * * @param OD_HTML_Tag_Processor $processor HTML tag processor. - * @param OD_URL_Metric_Group_Collection $url_metric_group_collection URL metric group collection. + * @param OD_URL_Metric_Group_Collection $url_metric_group_collection URL Metric group collection. * @param OD_Link_Collection $link_collection Link collection. */ public function __construct( OD_HTML_Tag_Processor $processor, OD_URL_Metric_Group_Collection $url_metric_group_collection, OD_Link_Collection $link_collection ) { @@ -65,7 +65,7 @@ public function __construct( OD_HTML_Tag_Processor $processor, OD_URL_Metric_Gro * @todo Remove this when no plugins are possibly referring to the url_metrics_group_collection property anymore. * * @param string $name Property name. - * @return OD_URL_Metric_Group_Collection URL metric group collection. + * @return OD_URL_Metric_Group_Collection URL Metric group collection. * * @throws Error When property is unknown. */ diff --git a/plugins/optimization-detective/class-od-tag-visitor-registry.php b/plugins/optimization-detective/class-od-tag-visitor-registry.php index d57f6f051f..e6b57527cd 100644 --- a/plugins/optimization-detective/class-od-tag-visitor-registry.php +++ b/plugins/optimization-detective/class-od-tag-visitor-registry.php @@ -80,7 +80,7 @@ public function unregister( string $id ): bool { } /** - * Returns an iterator for the URL metrics in the group. + * Returns an iterator for the URL Metrics in the group. * * @return ArrayIterator ArrayIterator for tag visitors. */ @@ -89,9 +89,9 @@ public function getIterator(): ArrayIterator { } /** - * Counts the URL metrics in the group. + * Counts the URL Metrics in the group. * - * @return int<0, max> URL metric count. + * @return int<0, max> URL Metric count. */ public function count(): int { return count( $this->visitors ); diff --git a/plugins/optimization-detective/class-od-url-metric-group-collection.php b/plugins/optimization-detective/class-od-url-metric-group-collection.php index a86cd3efb6..144f89a9c8 100644 --- a/plugins/optimization-detective/class-od-url-metric-group-collection.php +++ b/plugins/optimization-detective/class-od-url-metric-group-collection.php @@ -22,13 +22,13 @@ final class OD_URL_Metric_Group_Collection implements Countable, IteratorAggregate, JsonSerializable { /** - * URL metric groups. + * URL Metric groups. * * The number of groups corresponds to one greater than the number of * breakpoints. This is because breakpoints are the dividing line between - * the groups of URL metrics with specific viewport widths. This extends + * the groups of URL Metrics with specific viewport widths. This extends * even to when there are zero breakpoints: there will still be one group - * in this case, in which every single URL metric is added. + * in this case, in which every single URL Metric is added. * * @var OD_URL_Metric_Group[] * @phpstan-var non-empty-array @@ -54,7 +54,7 @@ final class OD_URL_Metric_Group_Collection implements Countable, IteratorAggrega private $breakpoints; /** - * Sample size for URL metrics for a given breakpoint. + * Sample size for URL Metrics for a given breakpoint. * * @var int * @phpstan-var positive-int @@ -62,9 +62,9 @@ final class OD_URL_Metric_Group_Collection implements Countable, IteratorAggrega private $sample_size; /** - * Freshness age (TTL) for a given URL metric. + * Freshness age (TTL) for a given URL Metric. * - * A freshness age of zero means a URL metric will always be considered stale. + * A freshness age of zero means a URL Metric will always be considered stale. * * @var int * @phpstan-var 0|positive-int @@ -93,10 +93,10 @@ final class OD_URL_Metric_Group_Collection implements Countable, IteratorAggrega * * @throws InvalidArgumentException When an invalid argument is supplied. * - * @param OD_URL_Metric[] $url_metrics URL metrics. + * @param OD_URL_Metric[] $url_metrics URL Metrics. * @param int[] $breakpoints Breakpoints in max widths. * @param int $sample_size Sample size for the maximum number of viewports in a group between breakpoints. - * @param int $freshness_ttl Freshness age (TTL) for a given URL metric. + * @param int $freshness_ttl Freshness age (TTL) for a given URL Metric. */ public function __construct( array $url_metrics, array $breakpoints, int $sample_size, int $freshness_ttl ) { // Set breakpoints. @@ -153,7 +153,7 @@ public function __construct( array $url_metrics, array $breakpoints, int $sample } $this->freshness_ttl = $freshness_ttl; - // Create groups and the URL metrics to them. + // Create groups and the URL Metrics to them. $this->groups = $this->create_groups(); foreach ( $url_metrics as $url_metric ) { $this->add_url_metric( $url_metric ); @@ -220,14 +220,14 @@ private function create_groups(): array { } /** - * Adds a new URL metric to a group. + * Adds a new URL Metric to a group. * - * Once a group reaches the sample size, the oldest URL metric is pushed out. + * Once a group reaches the sample size, the oldest URL Metric is pushed out. * * @since 0.1.0 - * @throws InvalidArgumentException If there is no group available to add a URL metric to. + * @throws InvalidArgumentException If there is no group available to add a URL Metric to. * - * @param OD_URL_Metric $new_url_metric New URL metric. + * @param OD_URL_Metric $new_url_metric New URL Metric. */ public function add_url_metric( OD_URL_Metric $new_url_metric ): void { foreach ( $this->groups as $group ) { @@ -237,7 +237,7 @@ public function add_url_metric( OD_URL_Metric $new_url_metric ): void { } } throw new InvalidArgumentException( - esc_html__( 'No group available to add URL metric to.', 'optimization-detective' ) + esc_html__( 'No group available to add URL Metric to.', 'optimization-detective' ) ); } @@ -248,7 +248,7 @@ public function add_url_metric( OD_URL_Metric $new_url_metric ): void { * @throws InvalidArgumentException When there is no group for the provided viewport width. This would only happen if a negative width is provided. * * @param int $viewport_width Viewport width. - * @return OD_URL_Metric_Group URL metric group for the viewport width. + * @return OD_URL_Metric_Group URL Metric group for the viewport width. */ public function get_group_for_viewport_width( int $viewport_width ): OD_URL_Metric_Group { if ( array_key_exists( __FUNCTION__, $this->result_cache ) && array_key_exists( $viewport_width, $this->result_cache[ __FUNCTION__ ] ) ) { @@ -265,7 +265,7 @@ public function get_group_for_viewport_width( int $viewport_width ): OD_URL_Metr esc_html( sprintf( /* translators: %d is viewport width */ - __( 'No URL metric group found for viewport width: %d', 'optimization-detective' ), + __( 'No URL Metric group found for viewport width: %d', 'optimization-detective' ), $viewport_width ) ) @@ -277,11 +277,11 @@ public function get_group_for_viewport_width( int $viewport_width ): OD_URL_Metr } /** - * Checks whether any group is populated with at least one URL metric. + * Checks whether any group is populated with at least one URL Metric. * * @since 0.5.0 * - * @return bool Whether at least one group has some URL metrics. + * @return bool Whether at least one group has some URL Metrics. */ public function is_any_group_populated(): bool { if ( array_key_exists( __FUNCTION__, $this->result_cache ) ) { @@ -302,17 +302,17 @@ public function is_any_group_populated(): bool { } /** - * Checks whether every group is populated with at least one URL metric each. + * Checks whether every group is populated with at least one URL Metric each. * * They aren't necessarily filled to the sample size, however. - * The URL metrics may also be stale (non-fresh). This method + * The URL Metrics may also be stale (non-fresh). This method * should be contrasted with the `is_every_group_complete()` * method below. * * @since 0.1.0 * @see OD_URL_Metric_Group_Collection::is_every_group_complete() * - * @return bool Whether all groups have some URL metrics. + * @return bool Whether all groups have some URL Metrics. */ public function is_every_group_populated(): bool { if ( array_key_exists( __FUNCTION__, $this->result_cache ) ) { @@ -442,10 +442,10 @@ public function get_common_lcp_element(): ?OD_Element { } /** - * Gets all elements from all URL metrics from all groups keyed by the elements' XPaths. + * Gets all elements from all URL Metrics from all groups keyed by the elements' XPaths. * * This is an O(n^3) function so its results must be cached. This being said, the number of groups should be 4 (one - * more than the default number of breakpoints) and the number of URL metrics for each group should be 3 + * more than the default number of breakpoints) and the number of URL Metrics for each group should be 3 * (the default sample size). Therefore, given the number (n) of visited elements on the page this will only * end up running n*4*3 times. * @@ -475,7 +475,7 @@ public function get_xpath_elements_map(): array { } /** - * Gets the max intersection ratios of all elements across all groups and their captured URL metrics. + * Gets the max intersection ratios of all elements across all groups and their captured URL Metrics. * * @since 0.3.0 * @@ -506,7 +506,7 @@ public function get_all_element_max_intersection_ratios(): array { * Gets all elements' status for whether they are positioned in any initial viewport. * * An element is positioned in the initial viewport if its `boundingClientRect.top` is less than the - * `viewport.height` for any of its recorded URL metrics. Note that even though the element may be positioned in the + * `viewport.height` for any of its recorded URL Metrics. Note that even though the element may be positioned in the * initial viewport, it may not actually be visible. It could be occluded as a latter slide in a carousel in which * case it will have intersectionRatio of 0. Or the element may not be visible due to it or an ancestor having the * `visibility:hidden` style, such as in the case of a dropdown navigation menu. When, for example, an IMG element @@ -542,7 +542,7 @@ public function get_all_elements_positioned_in_any_initial_viewport(): array { } /** - * Gets the max intersection ratio of an element across all groups and their captured URL metrics. + * Gets the max intersection ratio of an element across all groups and their captured URL Metrics. * * @since 0.3.0 * @@ -566,11 +566,11 @@ public function is_element_positioned_in_any_initial_viewport( string $xpath ): } /** - * Gets URL metrics from all groups flattened into one list. + * Gets URL Metrics from all groups flattened into one list. * * @since 0.1.0 * - * @return OD_URL_Metric[] All URL metrics. + * @return OD_URL_Metric[] All URL Metrics. */ public function get_flattened_url_metrics(): array { // The duplication of iterator_to_array is not a mistake. This collection is an @@ -585,7 +585,7 @@ public function get_flattened_url_metrics(): array { } /** - * Returns an iterator for the groups of URL metrics. + * Returns an iterator for the groups of URL Metrics. * * @since 0.1.0 * @@ -596,7 +596,7 @@ public function getIterator(): ArrayIterator { } /** - * Counts the URL metric groups in the collection. + * Counts the URL Metric groups in the collection. * * @since 0.1.0 * diff --git a/plugins/optimization-detective/class-od-url-metric-group.php b/plugins/optimization-detective/class-od-url-metric-group.php index 2346e272d8..0b2bbe444e 100644 --- a/plugins/optimization-detective/class-od-url-metric-group.php +++ b/plugins/optimization-detective/class-od-url-metric-group.php @@ -12,7 +12,7 @@ } /** - * URL metrics grouped by viewport according to breakpoints. + * URL Metrics grouped by viewport according to breakpoints. * * @implements IteratorAggregate * @@ -22,7 +22,7 @@ final class OD_URL_Metric_Group implements IteratorAggregate, Countable, JsonSerializable { /** - * URL metrics. + * URL Metrics. * * @var OD_URL_Metric[] */ @@ -45,7 +45,7 @@ final class OD_URL_Metric_Group implements IteratorAggregate, Countable, JsonSer private $maximum_viewport_width; /** - * Sample size for URL metrics for a given breakpoint. + * Sample size for URL Metrics for a given breakpoint. * * @var int * @phpstan-var positive-int @@ -53,7 +53,7 @@ final class OD_URL_Metric_Group implements IteratorAggregate, Countable, JsonSer private $sample_size; /** - * Freshness age (TTL) for a given URL metric. + * Freshness age (TTL) for a given URL Metric. * * @var int * @phpstan-var 0|positive-int @@ -82,11 +82,11 @@ final class OD_URL_Metric_Group implements IteratorAggregate, Countable, JsonSer * * @throws InvalidArgumentException If arguments are invalid. * - * @param OD_URL_Metric[] $url_metrics URL metrics to add to the group. + * @param OD_URL_Metric[] $url_metrics URL Metrics to add to the group. * @param int $minimum_viewport_width Minimum possible viewport width for the group. Must be zero or greater. * @param int $maximum_viewport_width Maximum possible viewport width for the group. Must be greater than zero and the minimum viewport width. * @param int $sample_size Sample size for the maximum number of viewports in a group between breakpoints. - * @param int $freshness_ttl Freshness age (TTL) for a given URL metric. + * @param int $freshness_ttl Freshness age (TTL) for a given URL Metric. * @param OD_URL_Metric_Group_Collection|null $collection Collection that this instance belongs to. Optional. */ public function __construct( array $url_metrics, int $minimum_viewport_width, int $maximum_viewport_width, int $sample_size, int $freshness_ttl, ?OD_URL_Metric_Group_Collection $collection = null ) { @@ -175,16 +175,16 @@ public function is_viewport_width_in_range( int $viewport_width ): bool { } /** - * Adds a URL metric to the group. + * Adds a URL Metric to the group. * - * @throws InvalidArgumentException If the viewport width of the URL metric is not within the min/max bounds of the group. + * @throws InvalidArgumentException If the viewport width of the URL Metric is not within the min/max bounds of the group. * - * @param OD_URL_Metric $url_metric URL metric. + * @param OD_URL_Metric $url_metric URL Metric. */ public function add_url_metric( OD_URL_Metric $url_metric ): void { if ( ! $this->is_viewport_width_in_range( $url_metric->get_viewport_width() ) ) { throw new InvalidArgumentException( - esc_html__( 'URL metric is not in the viewport range for group.', 'optimization-detective' ) + esc_html__( 'URL Metric is not in the viewport range for group.', 'optimization-detective' ) ); } @@ -196,10 +196,10 @@ public function add_url_metric( OD_URL_Metric $url_metric ): void { $url_metric->set_group( $this ); $this->url_metrics[] = $url_metric; - // If we have too many URL metrics now, remove the oldest ones up to the sample size. + // If we have too many URL Metrics now, remove the oldest ones up to the sample size. if ( count( $this->url_metrics ) > $this->sample_size ) { - // Sort URL metrics in descending order by timestamp. + // Sort URL Metrics in descending order by timestamp. usort( $this->url_metrics, static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int { @@ -207,16 +207,16 @@ static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int { } ); - // Only keep the sample size of the newest URL metrics. + // Only keep the sample size of the newest URL Metrics. $this->url_metrics = array_slice( $this->url_metrics, 0, $this->sample_size ); } } /** - * Determines whether the URL metric group is complete. + * Determines whether the URL Metric group is complete. * - * A group is complete if it has the full sample size of URL metrics - * and all of these URL metrics are fresh. + * A group is complete if it has the full sample size of URL Metrics + * and all of these URL Metrics are fresh. * * @return bool Whether complete. */ @@ -246,7 +246,7 @@ public function is_complete(): bool { /** * Gets the LCP element in the viewport group. * - * @return OD_Element|null LCP element data or null if not available, either because there are no URL metrics or + * @return OD_Element|null LCP element data or null if not available, either because there are no URL Metrics or * the LCP element type is not supported. */ public function get_lcp_element(): ?OD_Element { @@ -299,7 +299,7 @@ public function get_lcp_element(): ?OD_Element { $breadcrumb_counts[ $i ] += 1; $breadcrumb_element[ $i ] = $element; - break; // We found the LCP element for the URL metric, go to the next URL metric. + break; // We found the LCP element for the URL Metric, go to the next URL Metric. } } @@ -321,7 +321,7 @@ public function get_lcp_element(): ?OD_Element { } /** - * Returns an iterator for the URL metrics in the group. + * Returns an iterator for the URL Metrics in the group. * * @return ArrayIterator ArrayIterator for OD_URL_Metric instances. */ @@ -330,9 +330,9 @@ public function getIterator(): ArrayIterator { } /** - * Counts the URL metrics in the group. + * Counts the URL Metrics in the group. * - * @return int<0, max> URL metric count. + * @return int<0, max> URL Metric count. */ public function count(): int { return count( $this->url_metrics ); diff --git a/plugins/optimization-detective/class-od-url-metric.php b/plugins/optimization-detective/class-od-url-metric.php index a08eff8d81..d76890aa3f 100644 --- a/plugins/optimization-detective/class-od-url-metric.php +++ b/plugins/optimization-detective/class-od-url-metric.php @@ -78,7 +78,7 @@ class OD_URL_Metric implements JsonSerializable { * * @throws OD_Data_Validation_Exception When the input is invalid. * - * @param array $data URL metric data. + * @param array $data URL Metric data. */ public function __construct( array $data ) { if ( ! isset( $data['uuid'] ) ) { @@ -125,7 +125,7 @@ private function prepare_data( array $data ): array { } /** - * Gets the group that this URL metric is a part of (which may not be any). + * Gets the group that this URL Metric is a part of (which may not be any). * * @since 0.7.0 * @@ -136,7 +136,7 @@ public function get_group(): ?OD_URL_Metric_Group { } /** - * Sets the group that this URL metric is a part of. + * Sets the group that this URL Metric is a part of. * * @since 0.7.0 * @@ -200,7 +200,7 @@ public static function get_json_schema(): array { 'required' => true, 'properties' => array( 'uuid' => array( - 'description' => __( 'The UUID for the URL metric.', 'optimization-detective' ), + 'description' => __( 'The UUID for the URL Metric.', 'optimization-detective' ), 'type' => 'string', 'format' => 'uuid', 'required' => true, @@ -232,7 +232,7 @@ public static function get_json_schema(): array { 'additionalProperties' => false, ), 'timestamp' => array( - 'description' => __( 'Timestamp at which the URL metric was captured.', 'optimization-detective' ), + 'description' => __( 'Timestamp at which the URL Metric was captured.', 'optimization-detective' ), 'type' => 'number', 'required' => true, 'readonly' => true, // Omit from REST API. @@ -284,7 +284,7 @@ public static function get_json_schema(): array { ); /** - * Filters additional schema properties which should be allowed at the root of a URL metric. + * Filters additional schema properties which should be allowed at the root of a URL Metric. * * @since 0.6.0 * @@ -296,7 +296,7 @@ public static function get_json_schema(): array { } /** - * Filters additional schema properties which should be allowed for an elements item in a URL metric. + * Filters additional schema properties which should be allowed for an element's item in a URL Metric. * * @since 0.6.0 * diff --git a/plugins/optimization-detective/detect.js b/plugins/optimization-detective/detect.js index 9445ffbe13..00069c72de 100644 --- a/plugins/optimization-detective/detect.js +++ b/plugins/optimization-detective/detect.js @@ -85,11 +85,11 @@ function error( ...message ) { } /** - * Checks whether the URL metric(s) for the provided viewport width is needed. + * Checks whether the URL Metric(s) for the provided viewport width is needed. * * @param {number} viewportWidth - Current viewport width. * @param {URLMetricGroupStatus[]} urlMetricGroupStatuses - Viewport group statuses. - * @return {boolean} Whether URL metrics are needed. + * @return {boolean} Whether URL Metrics are needed. */ function isViewportNeeded( viewportWidth, urlMetricGroupStatuses ) { let lastWasLacking = false; @@ -128,7 +128,7 @@ function recursiveFreeze( obj ) { } /** - * URL metric being assembled for submission. + * URL Metric being assembled for submission. * * @type {URLMetric} */ @@ -155,7 +155,7 @@ function getRootData() { } /** - * Extends root URL metric data. + * Extends root URL Metric data. * * @param {ExtendedRootData} properties */ @@ -241,12 +241,12 @@ function extendElementData( xpath, properties ) { * @param {string} args.restApiEndpoint URL for where to send the detection data. * @param {string} args.restApiNonce Nonce for writing to the REST API. * @param {string} args.currentUrl Current URL. - * @param {string} args.urlMetricSlug Slug for URL metric. - * @param {string} args.urlMetricNonce Nonce for URL metric storage. - * @param {URLMetricGroupStatus[]} args.urlMetricGroupStatuses URL metric group statuses. - * @param {number} args.storageLockTTL The TTL (in seconds) for the URL metric storage lock. + * @param {string} args.urlMetricSlug Slug for URL Metric. + * @param {string} args.urlMetricNonce Nonce for URL Metric storage. + * @param {URLMetricGroupStatus[]} args.urlMetricGroupStatuses URL Metric group statuses. + * @param {number} args.storageLockTTL The TTL (in seconds) for the URL Metric storage lock. * @param {string} args.webVitalsLibrarySrc The URL for the web-vitals library. - * @param {Object} [args.urlMetricGroupCollection] URL metric group collection, when in debug mode. + * @param {Object} [args.urlMetricGroupCollection] URL Metric group collection, when in debug mode. */ export default async function detect( { serveTime, @@ -268,7 +268,7 @@ export default async function detect( { const currentTime = getCurrentTime(); if ( isDebug ) { - log( 'Stored URL metric group collection:', urlMetricGroupCollection ); + log( 'Stored URL Metric group collection:', urlMetricGroupCollection ); } // Abort running detection logic if it was served in a cached page. @@ -281,10 +281,10 @@ export default async function detect( { return; } - // Abort if the current viewport is not among those which need URL metrics. + // Abort if the current viewport is not among those which need URL Metrics. if ( ! isViewportNeeded( win.innerWidth, urlMetricGroupStatuses ) ) { if ( isDebug ) { - log( 'No need for URL metrics from the current viewport.' ); + log( 'No need for URL Metrics from the current viewport.' ); } return; } @@ -497,7 +497,7 @@ export default async function detect( { } if ( isDebug ) { - log( 'Current URL metric:', urlMetric ); + log( 'Current URL Metric:', urlMetric ); } // Wait for the page to be hidden. @@ -545,7 +545,7 @@ export default async function detect( { setStorageLock( getCurrentTime() ); if ( isDebug ) { - log( 'Sending URL metric:', urlMetric ); + log( 'Sending URL Metric:', urlMetric ); } const url = new URL( restApiEndpoint ); diff --git a/plugins/optimization-detective/detection.php b/plugins/optimization-detective/detection.php index 1a6133b58f..7d18ae8374 100644 --- a/plugins/optimization-detective/detection.php +++ b/plugins/optimization-detective/detection.php @@ -16,8 +16,8 @@ * @since 0.1.0 * @access private * - * @param string $slug URL metrics slug. - * @param OD_URL_Metric_Group_Collection $group_collection URL metric group collection. + * @param string $slug URL Metrics slug. + * @param OD_URL_Metric_Group_Collection $group_collection URL Metric group collection. */ function od_get_detection_script( string $slug, OD_URL_Metric_Group_Collection $group_collection ): string { /** diff --git a/plugins/optimization-detective/readme.txt b/plugins/optimization-detective/readme.txt index 3c257e47a4..3d51bc0000 100644 --- a/plugins/optimization-detective/readme.txt +++ b/plugins/optimization-detective/readme.txt @@ -11,7 +11,9 @@ Provides an API for leveraging real user metrics to detect optimizations to appl == Description == -This plugin captures real user metrics about what elements are displayed on the page across a variety of device form factors (e.g. desktop, tablet, and phone) in order to apply loading optimizations which are not possible with WordPress’s current server-side heuristics. This plugin is a dependency which does not provide end-user functionality on its own. For that, please install the dependent plugin [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/) (among [others](https://github.com/WordPress/performance/labels/%5BPlugin%5D%20Optimization%20Detective) to come from the WordPress Core Performance team). +This plugin captures real user metrics about what elements are displayed on the page across a variety of device form factors (e.g. desktop, tablet, and phone) in order to apply loading optimizations which are not possible with WordPress’s current server-side heuristics. + +This plugin is a dependency which does not provide end-user functionality on its own. For that, please install the dependent plugin [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/) or [Embed Optimizer](https://wordpress.org/plugins/embed-optimizer/) (among [others](https://github.com/WordPress/performance/labels/%5BPlugin%5D%20Optimization%20Detective) to come from the WordPress Core Performance team). = Background = @@ -21,11 +23,18 @@ In order to increase the accuracy of identifying the LCP element, including acro = Technical Foundation = -At the core of Optimization Detective is the “URL Metric”, information about a page according to how it was loaded by a client with a specific viewport width. This includes which elements were visible in the initial viewport and which one was the LCP element. Each URL on a site can have an associated set of these URL Metrics (stored in a custom post type) which are gathered from real users. It gathers a sample of URL Metrics according to common responsive breakpoints (e.g. mobile, tablet, and desktop). When no more URL Metrics are needed for a URL due to the sample size being obtained for the breakpoints, it discontinues serving the JavaScript to gather the metrics (leveraging the [web-vitals.js](https://github.com/GoogleChrome/web-vitals) library). With the URL Metrics in hand, the output-buffered page is sent through the HTML Tag Processor and--when the [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/) dependent plugin is installed--the images which were the LCP element for various breakpoints will get prioritized with high-priority preload links (along with `fetchpriority=high` on the actual `img` tag when it is the common LCP element across all breakpoints). LCP elements with background images added via inline `background-image` styles are also prioritized with preload links. +At the core of Optimization Detective is the “URL Metric”, information about a page according to how it was loaded by a client with a specific viewport width. This includes which elements were visible in the initial viewport and which one was the LCP element. The URL Metric data is also extensible. Each URL on a site can have an associated set of these URL Metrics (stored in a custom post type) which are gathered from the visits of real users. It gathers samples of URL Metrics which are grouped according to WordPress's default responsive breakpoints: + +1. Mobile: 0-480px +2. Phablet: 481-600px +3. Tablet: 601-782px +4. Desktop: \>782px + +When no more URL Metrics are needed for a URL due to the sample size being obtained for the viewport group, it discontinues serving the JavaScript to gather the metrics (leveraging the [web-vitals.js](https://github.com/GoogleChrome/web-vitals) library). With the URL Metrics in hand, the output-buffered page is sent through the HTML Tag Processor and--when the [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/) dependent plugin is installed--the images which were the LCP element for various breakpoints will get prioritized with high-priority preload links (along with `fetchpriority=high` on the actual `img` tag when it is the common LCP element across all breakpoints). LCP elements with background images added via inline `background-image` styles are also prioritized with preload links. URL Metrics have a “freshness TTL” after which they will be stale and the JavaScript will be served again to start gathering metrics again to ensure that the right elements continue to get their loading prioritized. When a URL Metrics custom post type hasn't been touched in a while, it is automatically garbage-collected. -👉 **Note:** This plugin optimizes pages for actual visitors, and it depends on visitors to optimize pages (since URL metrics need to be collected). As such, you won't see optimizations applied immediately after activating the plugin (and dependent plugin(s)). And since administrator users are not normal visitors typically, optimizations are not applied for admins by default (but this can be overridden with the `od_can_optimize_response` filter below). URL metrics are not collected for administrators because it is likely that additional elements will be present on the page which are not also shown to non-administrators, meaning the URL metrics could not reliably be reused between them. +👉 **Note:** This plugin optimizes pages for actual visitors, and it depends on visitors to optimize pages (since URL Metrics need to be collected). As such, you won't see optimizations applied immediately after activating the plugin (and dependent plugin(s)). And since administrator users are not normal visitors typically, optimizations are not applied for admins by default (but this can be overridden with the `od_can_optimize_response` filter below). URL Metrics are not collected for administrators because it is likely that additional elements will be present on the page which are not also shown to non-administrators, meaning the URL Metrics could not reliably be reused between them. There are currently **no settings** and no user interface for this plugin since it is designed to work without any configuration. @@ -37,16 +46,43 @@ When the `WP_DEBUG` constant is enabled, additional logging for Optimization Det Fires when the Optimization Detective is initializing. This action is useful for loading extension code that depends on Optimization Detective to be running. The version of the plugin is passed as the sole argument so that if the required version is not present, the callback can short circuit. -**Filter:** `od_breakpoint_max_widths` (default: [480, 600, 782]) +**Action:** `od_register_tag_visitors` (argument: `OD_Tag_Visitor_Registry`) + +Fires to register tag visitors before walking over the document to perform optimizations. -Filters the breakpoint max widths to group URL metrics for various viewports. Each number represents the maximum width (inclusive) for a given breakpoint. So if there is one number, 480, then this means there will be two viewport groupings, one for 0<=480, and another >480. If instead there were three provided breakpoints (320, 480, 576) then this means there will be four groups: +For example, to register a new tag visitor that targets `H1` elements: + +` +register( + 'my-plugin/h1', + static function ( OD_Tag_Visitor_Context $context ): bool { + if ( $context->processor->get_tag() !== 'H1' ) { + return false; + } + // Now optimize based on stored URL Metrics in $context->url_metric_group_collection. + // ... + + // Returning true causes the tag to be tracked in URL Metrics. If there is no need + // for this, as in there is no reference to $context->url_metric_group_collection + // in a tag visitor, then this can instead return false. + return true; + } + ); + } +); +` - 1. 0-320 (small smartphone) - 2. 321-480 (normal smartphone) - 3. 481-576 (phablets) - 4. >576 (desktop) +Refer to [Image Prioritizer](https://github.com/WordPress/performance/tree/trunk/plugins/image-prioritizer) and [Embed Optimizer](https://github.com/WordPress/performance/tree/trunk/plugins/embed-optimizer) for real world examples of how tag visitors are used. Registered tag visitors need only be callables, so in addition to providing a closure you may provide a `callable-string` or even a class which has an `__invoke()` method. -The default breakpoints are reused from Gutenberg which appear to be used the most in media queries that affect frontend styles. +**Filter:** `od_breakpoint_max_widths` (default: `array(480, 600, 782)`) + +Filters the breakpoint max widths to group URL Metrics for various viewports. Each number represents the maximum width (inclusive) for a given breakpoint. So if there is one number, 480, then this means there will be two viewport groupings, one for 0\<=480, and another \>480. If instead there are the two breakpoints defined, 480 and 782, then this means there will be three viewport groups of URL Metrics, one for 0\<=480 (i.e. mobile), another 481\<=782 (i.e. phablet/tablet), and another \>782 (i.e. desktop). + +These default breakpoints are reused from Gutenberg which appear to be used the most in media queries that affect frontend styles. **Filter:** `od_can_optimize_response` (default: boolean condition, see below) @@ -67,7 +103,7 @@ add_filter( 'od_can_optimize_response', '__return_true' ); **Filter:** `od_url_metrics_breakpoint_sample_size` (default: 3) -Filters the sample size for a breakpoint's URL metrics on a given URL. The sample size must be greater than zero. During development, it may be helpful to reduce the sample size to 1: +Filters the sample size for a breakpoint's URL Metrics on a given URL. The sample size must be greater than zero. During development, it may be helpful to reduce the sample size to 1: ` 'object', + 'properties' => array_fill_keys( + array( + 'width', + 'height', + 'x', + 'y', + 'top', + 'right', + 'bottom', + 'left', + ), + array( + 'type' => 'number', + 'required' => true, + ) + ), + ); + return $additional_properties; + } +); +` + +See also [example usage](https://github.com/WordPress/performance/blob/6bb8405c5c446e3b66c2bfa3ae03ba61b188bca2/plugins/embed-optimizer/hooks.php#L81-L110) in Embed Optimizer. + +**Filter:** `od_url_metric_schema_root_additional_properties` (default: empty array) + +Filters additional schema properties which should be allowed at the root of a URL Metric. + +The usage here is the same as the previous filter, except it allows new properties to be added to the root of the URL Metric and not just to one of the object items in the `elements` property. + +**Filter:** `od_extension_module_urls` (default: empty array of strings) + +Filters the list of extension script module URLs to import when performing detection. + +For example: + +` +> $request * * @param WP_REST_Request $request REST API request. - * @param int $post_id ID for the URL metric post. - * @param OD_URL_Metric_Group_Collection $url_metric_group_collection URL metric group collection. - * @param OD_URL_Metric_Group $url_metric_group URL metric group. - * @param OD_URL_Metric $url_metric URL metric. + * @param int $post_id ID for the URL Metric post. + * @param OD_URL_Metric_Group_Collection $url_metric_group_collection URL Metric group collection. + * @param OD_URL_Metric_Group $url_metric_group URL Metric group. + * @param OD_URL_Metric $url_metric URL Metric. */ public function __construct( WP_REST_Request $request, int $post_id, OD_URL_Metric_Group_Collection $url_metric_group_collection, OD_URL_Metric_Group $url_metric_group, OD_URL_Metric $url_metric ) { $this->request = $request; diff --git a/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php b/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php index 0115524b29..e55028a328 100644 --- a/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php +++ b/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php @@ -51,7 +51,7 @@ public static function add_hooks(): void { } /** - * Registers post type for URL metrics storage. + * Registers post type for URL Metrics storage. * * This the configuration for this post type is similar to the oembed_cache in core. * @@ -78,11 +78,11 @@ public static function register_post_type(): void { } /** - * Gets URL metrics post. + * Gets URL Metrics post. * * @since 0.1.0 * - * @param string $slug URL metrics slug. + * @param string $slug URL Metrics slug. * @return WP_Post|null Post object if exists. */ public static function get_post( string $slug ): ?WP_Post { @@ -109,12 +109,12 @@ public static function get_post( string $slug ): ?WP_Post { } /** - * Parses post content in URL metrics post. + * Parses post content in URL Metrics post. * * @since 0.1.0 * - * @param WP_Post $post URL metrics post. - * @return OD_URL_Metric[] URL metrics. + * @param WP_Post $post URL Metrics post. + * @return OD_URL_Metric[] URL Metrics. */ public static function get_url_metrics_from_post( WP_Post $post ): array { $this_function = __METHOD__; @@ -175,7 +175,7 @@ static function ( $url_metric_data ) use ( $trigger_error ) { $e->getMessage() . $suffix ), // This is not a warning because schema changes will happen, and so it is expected - // that this will result in existing URL metrics being invalidated. + // that this will result in existing URL Metrics being invalidated. E_USER_NOTICE ); @@ -189,13 +189,13 @@ static function ( $url_metric_data ) use ( $trigger_error ) { } /** - * Stores URL metric by merging it with the other URL metrics which share the same normalized query vars. + * Stores URL Metric by merging it with the other URL Metrics which share the same normalized query vars. * * @since 0.1.0 * @todo There is duplicate logic here with od_handle_rest_request(). * * @param string $slug Slug (hash of normalized query vars). - * @param OD_URL_Metric $new_url_metric New URL metric. + * @param OD_URL_Metric $new_url_metric New URL Metric. * @return int|WP_Error Post ID or WP_Error otherwise. */ public static function store_url_metric( string $slug, OD_URL_Metric $new_url_metric ) { diff --git a/plugins/optimization-detective/storage/data.php b/plugins/optimization-detective/storage/data.php index 4ac7698d16..e05f623520 100644 --- a/plugins/optimization-detective/storage/data.php +++ b/plugins/optimization-detective/storage/data.php @@ -11,9 +11,9 @@ } /** - * Gets the freshness age (TTL) for a given URL metric. + * Gets the freshness age (TTL) for a given URL Metric. * - * When a URL metric expires it is eligible to be replaced by a newer one if its viewport lies within the same breakpoint. + * When a URL Metric expires it is eligible to be replaced by a newer one if its viewport lies within the same breakpoint. * * @since 0.1.0 * @access private @@ -22,9 +22,9 @@ */ function od_get_url_metric_freshness_ttl(): int { /** - * Filters the freshness age (TTL) for a given URL metric. + * Filters the freshness age (TTL) for a given URL Metric. * - * The freshness TTL must be at least zero, in which it considers URL metrics to always be stale. + * The freshness TTL must be at least zero, in which it considers URL Metrics to always be stale. * In practice, the value should be at least an hour. * * @since 0.1.0 @@ -54,7 +54,7 @@ function od_get_url_metric_freshness_ttl(): int { /** * Gets the normalized query vars for the current request. * - * This is used as a cache key for stored URL metrics. + * This is used as a cache key for stored URL Metrics. * * TODO: For non-singular requests, consider adding the post IDs from The Loop to ensure publishing a new post will invalidate the cache. * @@ -77,7 +77,7 @@ function od_get_normalized_query_vars(): array { ); } - // Vary URL metrics by whether the user is logged in since additional elements may be present. + // Vary URL Metrics by whether the user is logged in since additional elements may be present. if ( is_user_logged_in() ) { $normalized_query_vars['user_logged_in'] = true; } @@ -124,7 +124,7 @@ function od_get_current_url(): string { } /** - * Gets slug for URL metrics. + * Gets slug for URL Metrics. * * A slug is the hash of the normalized query vars. * @@ -141,9 +141,9 @@ function od_get_url_metrics_slug( array $query_vars ): string { } /** - * Computes nonce for storing URL metrics for a specific slug. + * Computes nonce for storing URL Metrics for a specific slug. * - * This is used in the REST API to authenticate the storage of new URL metrics from a given URL. + * This is used in the REST API to authenticate the storage of new URL Metrics from a given URL. * * @since 0.1.0 * @access private @@ -161,7 +161,7 @@ function od_get_url_metrics_storage_nonce( string $slug, string $url ): string { } /** - * Verifies nonce for storing URL metrics for a specific slug. + * Verifies nonce for storing URL Metrics for a specific slug. * * @since 0.1.0 * @access private @@ -180,16 +180,16 @@ function od_verify_url_metrics_storage_nonce( string $nonce, string $slug, strin } /** - * Gets the minimum allowed viewport aspect ratio for URL metrics. + * Gets the minimum allowed viewport aspect ratio for URL Metrics. * * @since 0.6.0 * @access private * - * @return float Minimum viewport aspect ratio for URL metrics. + * @return float Minimum viewport aspect ratio for URL Metrics. */ function od_get_minimum_viewport_aspect_ratio(): float { /** - * Filters the minimum allowed viewport aspect ratio for URL metrics. + * Filters the minimum allowed viewport aspect ratio for URL Metrics. * * The 0.4 default value is intended to accommodate the phone with the greatest known aspect * ratio at 21:9 when rotated 90 degrees to 9:21 (0.429). @@ -202,16 +202,16 @@ function od_get_minimum_viewport_aspect_ratio(): float { } /** - * Gets the maximum allowed viewport aspect ratio for URL metrics. + * Gets the maximum allowed viewport aspect ratio for URL Metrics. * * @since 0.6.0 * @access private * - * @return float Maximum viewport aspect ratio for URL metrics. + * @return float Maximum viewport aspect ratio for URL Metrics. */ function od_get_maximum_viewport_aspect_ratio(): float { /** - * Filters the maximum allowed viewport aspect ratio for URL metrics. + * Filters the maximum allowed viewport aspect ratio for URL Metrics. * * The 2.5 default value is intended to accommodate the phone with the greatest known aspect * ratio at 21:9 (2.333). @@ -224,7 +224,7 @@ function od_get_maximum_viewport_aspect_ratio(): float { } /** - * Gets the breakpoint max widths to group URL metrics for various viewports. + * Gets the breakpoint max widths to group URL Metrics for various viewports. * * Each number represents the maximum width (inclusive) for a given breakpoint. So if there is one number, 480, then * this means there will be two viewport groupings, one for 0<=480, and another >480. If instead there were three @@ -288,7 +288,7 @@ static function ( $original_breakpoint ) use ( $function_name ): int { return $breakpoint; }, /** - * Filters the breakpoint max widths to group URL metrics for various viewports. + * Filters the breakpoint max widths to group URL Metrics for various viewports. * * A breakpoint must be greater than zero and less than PHP_INT_MAX. This array may be empty in which case there * are no responsive breakpoints and all URL Metrics are collected in a single group. @@ -306,11 +306,11 @@ static function ( $original_breakpoint ) use ( $function_name ): int { } /** - * Gets the sample size for a breakpoint's URL metrics on a given URL. + * Gets the sample size for a breakpoint's URL Metrics on a given URL. * - * A breakpoint divides URL metrics for viewports which are smaller and those which are larger. Given the default + * A breakpoint divides URL Metrics for viewports which are smaller and those which are larger. Given the default * sample size of 3 and there being just a single breakpoint (480) by default, for a given URL, there would be a maximum - * total of 6 URL metrics stored for a given URL: 3 for mobile and 3 for desktop. + * total of 6 URL Metrics stored for a given URL: 3 for mobile and 3 for desktop. * * @since 0.1.0 * @access private @@ -319,7 +319,7 @@ static function ( $original_breakpoint ) use ( $function_name ): int { */ function od_get_url_metrics_breakpoint_sample_size(): int { /** - * Filters the sample size for a breakpoint's URL metrics on a given URL. + * Filters the sample size for a breakpoint's URL Metrics on a given URL. * * The sample size must be greater than zero. * diff --git a/plugins/optimization-detective/storage/rest-api.php b/plugins/optimization-detective/storage/rest-api.php index 849e5d86ca..2a5f5019b5 100644 --- a/plugins/optimization-detective/storage/rest-api.php +++ b/plugins/optimization-detective/storage/rest-api.php @@ -18,7 +18,7 @@ const OD_REST_API_NAMESPACE = 'optimization-detective/v1'; /** - * Route for storing a URL metric. + * Route for storing a URL Metric. * * Note the `:store` art of the endpoint follows Google's guidance in AIP-136 for the use of the POST method in a way * that does not strictly follow the standard usage. Namely, submitting a POST request to this endpoint will either @@ -30,7 +30,7 @@ const OD_URL_METRICS_ROUTE = '/url-metrics:store'; /** - * Registers endpoint for storage of URL metric. + * Registers endpoint for storage of URL Metric. * * @since 0.1.0 * @access private @@ -53,7 +53,7 @@ function od_register_endpoint(): void { 'pattern' => '^[0-9a-f]+$', 'validate_callback' => static function ( string $nonce, WP_REST_Request $request ) { if ( ! od_verify_url_metrics_storage_nonce( $nonce, $request->get_param( 'slug' ), $request->get_param( 'url' ) ) ) { - return new WP_Error( 'invalid_nonce', __( 'URL metrics nonce verification failure.', 'optimization-detective' ) ); + return new WP_Error( 'invalid_nonce', __( 'URL Metrics nonce verification failure.', 'optimization-detective' ) ); } return true; }, @@ -77,7 +77,7 @@ function od_register_endpoint(): void { if ( OD_Storage_Lock::is_locked() ) { return new WP_Error( 'url_metric_storage_locked', - __( 'URL metric storage is presently locked for the current IP.', 'optimization-detective' ), + __( 'URL Metric storage is presently locked for the current IP.', 'optimization-detective' ), array( 'status' => 403 ) ); } @@ -109,7 +109,7 @@ function od_handle_rest_request( WP_REST_Request $request ) { od_get_url_metric_freshness_ttl() ); - // Block the request if URL metrics aren't needed for the provided viewport width. + // Block the request if URL Metrics aren't needed for the provided viewport width. try { $url_metric_group = $url_metric_group_collection->get_group_for_viewport_width( $request->get_param( 'viewport' )['width'] @@ -120,7 +120,7 @@ function od_handle_rest_request( WP_REST_Request $request ) { if ( $url_metric_group->is_complete() ) { return new WP_Error( 'url_metric_group_complete', - __( 'The URL metric group for the provided viewport is already complete.', 'optimization-detective' ), + __( 'The URL Metric group for the provided viewport is already complete.', 'optimization-detective' ), array( 'status' => 403 ) ); } @@ -153,7 +153,7 @@ function od_handle_rest_request( WP_REST_Request $request ) { 'rest_invalid_param', sprintf( /* translators: %s is exception name */ - __( 'Failed to validate URL metric: %s', 'optimization-detective' ), + __( 'Failed to validate URL Metric: %s', 'optimization-detective' ), $e->getMessage() ), array( 'status' => 400 ) diff --git a/plugins/optimization-detective/tests/storage/test-rest-api.php b/plugins/optimization-detective/tests/storage/test-rest-api.php index 157bc23d76..426617f500 100644 --- a/plugins/optimization-detective/tests/storage/test-rest-api.php +++ b/plugins/optimization-detective/tests/storage/test-rest-api.php @@ -88,7 +88,7 @@ function ( OD_URL_Metric_Store_Request_Context $context ): void { $this->assertInstanceOf( WP_Post::class, $post ); $url_metrics = OD_URL_Metrics_Post_Type::get_url_metrics_from_post( $post ); - $this->assertCount( 1, $url_metrics, 'Expected number of URL metrics stored.' ); + $this->assertCount( 1, $url_metrics, 'Expected number of URL Metrics stored.' ); $this->assertSame( $valid_params['elements'], $this->get_array_json_data( $url_metrics[0]->get( 'elements' ) ) ); $this->assertSame( $valid_params['viewport']['width'], $url_metrics[0]->get_viewport_width() ); @@ -417,7 +417,7 @@ public function test_rest_request_breakpoint_not_needed_for_specific_breakpoint( public function test_rest_request_over_populate_wider_viewport_group(): void { add_filter( 'od_url_metric_storage_lock_ttl', '__return_zero' ); - // First establish a single breakpoint, so there are two groups of URL metrics + // First establish a single breakpoint, so there are two groups of URL Metrics // with viewport widths 0-480 and >481. $breakpoint_width = 480; add_filter( @@ -456,7 +456,7 @@ static function ( OD_URL_Metric_Group $group ) { $this->assertCount( 0, $url_metric_groups[0], 'Expected first group to be empty.' ); $this->assertCount( $sample_size, end( $url_metric_groups ), 'Expected last group to be fully populated.' ); - // Now attempt to store one more URL metric for the wider viewport group. + // Now attempt to store one more URL Metric for the wider viewport group. // This should fail because the group is already fully populated to the sample size. $request = $this->create_request( $wider_viewport_params ); $response = rest_get_server()->dispatch( $request ); @@ -472,7 +472,7 @@ static function ( OD_URL_Metric_Group $group ) { public function test_rest_request_over_populate_narrower_viewport_group(): void { add_filter( 'od_url_metric_storage_lock_ttl', '__return_zero' ); - // First establish a single breakpoint, so there are two groups of URL metrics + // First establish a single breakpoint, so there are two groups of URL Metrics // with viewport widths 0-480 and >481. $breakpoint_width = 480; add_filter( @@ -490,7 +490,7 @@ static function () use ( $breakpoint_width ): array { $narrower_viewport_params ); - // Now attempt to store one more URL metric for the narrower viewport group. + // Now attempt to store one more URL Metric for the narrower viewport group. // This should fail because the group is already fully populated to the sample size. $request = $this->create_request( $narrower_viewport_params ); $response = rest_get_server()->dispatch( $request ); @@ -498,10 +498,10 @@ static function () use ( $breakpoint_width ): array { } /** - * Populate URL metrics. + * Populate URL Metrics. * - * @param int $count Count of URL metrics to populate. - * @param array $params Params for URL metric. + * @param int $count Count of URL Metrics to populate. + * @param array $params Params for URL Metric. */ private function populate_url_metrics( int $count, array $params ): void { for ( $i = 0; $i < $count; $i++ ) { @@ -566,7 +566,7 @@ private function recursive_merge( array $base_array, array $sparse_array ): arra } /** - * Creates a request to store a URL metric. + * Creates a request to store a URL Metric. * * @param array $params Params. * @return WP_REST_Request> Request. diff --git a/plugins/optimization-detective/tests/test-class-od-url-metrics-group-collection.php b/plugins/optimization-detective/tests/test-class-od-url-metrics-group-collection.php index 0900651783..8553841aba 100644 --- a/plugins/optimization-detective/tests/test-class-od-url-metrics-group-collection.php +++ b/plugins/optimization-detective/tests/test-class-od-url-metrics-group-collection.php @@ -176,7 +176,7 @@ public function data_provider_sample_size_and_breakpoints(): array { * * @param int $sample_size Sample size. * @param int[] $breakpoints Breakpoints. - * @param array $viewport_widths Viewport widths mapped to the number of URL metrics to instantiate. + * @param array $viewport_widths Viewport widths mapped to the number of URL Metrics to instantiate. * @param array $expected_counts Minimum viewport widths mapped to the expected counts in each group. * * @dataProvider data_provider_sample_size_and_breakpoints @@ -214,7 +214,7 @@ public function test_adding_pushes_out_old_metrics(): void { $breakpoints = array( 400, 600 ); $group_collection = new OD_URL_Metric_Group_Collection( array(), $breakpoints, $sample_size, HOUR_IN_SECONDS ); - // Populate the groups with stale URL metrics. + // Populate the groups with stale URL Metrics. $viewport_widths = array( 300, 500, 700 ); $old_timestamp = microtime( true ) - ( HOUR_IN_SECONDS + 1 ); @@ -233,7 +233,7 @@ public function test_adding_pushes_out_old_metrics(): void { } } - // Try adding one URL metric for each breakpoint group. + // Try adding one URL Metric for each breakpoint group. foreach ( $viewport_widths as $viewport_width ) { $group_collection->add_url_metric( $this->get_sample_url_metric( array( 'viewport_width' => $viewport_width ) ) ); } @@ -242,7 +242,7 @@ public function test_adding_pushes_out_old_metrics(): void { $this->assertCount( $max_possible_url_metrics_count, $group_collection->get_flattened_url_metrics(), - 'Expected the total count of URL metrics to not exceed the multiple of the sample size.' + 'Expected the total count of URL Metrics to not exceed the multiple of the sample size.' ); $new_count = 0; foreach ( $group_collection->get_flattened_url_metrics() as $url_metric ) { @@ -250,8 +250,8 @@ public function test_adding_pushes_out_old_metrics(): void { ++$new_count; } } - $this->assertGreaterThan( 0, $new_count, 'Expected there to be at least one new URL metric.' ); - $this->assertSame( count( $viewport_widths ), $new_count, 'Expected the new URL metrics to all have been added.' ); + $this->assertGreaterThan( 0, $new_count, 'Expected there to be at least one new URL Metric.' ); + $this->assertSame( count( $viewport_widths ), $new_count, 'Expected the new URL Metrics to all have been added.' ); } /** @@ -731,7 +731,7 @@ public function data_provider_element_max_intersection_ratios(): array { * * @dataProvider data_provider_element_max_intersection_ratios * - * @param array $url_metrics URL metrics. + * @param array $url_metrics URL Metrics. * @param array $expected Expected. */ public function test_get_all_element_max_intersection_ratios( array $url_metrics, array $expected ): void { @@ -908,7 +908,7 @@ public function data_provider_get_all_elements_positioned_in_any_initial_viewpor * * @dataProvider data_provider_get_all_elements_positioned_in_any_initial_viewport * - * @param array $url_metrics URL metrics. + * @param array $url_metrics URL Metrics. * @param array $expected Expected. */ public function test_get_all_elements_positioned_in_any_initial_viewport( array $url_metrics, array $expected ): void { diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 7669154b64..6c8062664c 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -468,3 +468,27 @@ function webp_uploads_get_mime_type_image( int $attachment_id, string $src, stri return null; } + +/** + * Retrieves the MIME type of a file, checking the file directly if possible, + * and falling back to the attachment's MIME type if needed. + * + * The function attempts to determine the MIME type directly from the file. + * If that information is unavailable, it uses the MIME type from the attachment metadata. + * If neither is available, it defaults to an empty string. + * + * @since n.e.x.t + * + * @param string $file The path to the file. + * @param int $attachment_id The attachment ID. + * @return string The MIME type of the file, or an empty string if not found. + */ +function webp_uploads_get_file_mime_type( string $file, int $attachment_id ): string { + /* + * We need to get the MIME type ideally from the file, as WordPress Core may have already altered it. + * The post MIME type is typically not updated during that process. + */ + $filetype = wp_check_filetype( $file ); + $mime_type = $filetype['type'] ?? get_post_mime_type( $attachment_id ); + return is_string( $mime_type ) ? $mime_type : ''; +} diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 8354eea92c..a7a9b308da 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -52,18 +52,21 @@ * } An array with the updated structure for the metadata before is stored in the database. */ function webp_uploads_create_sources_property( array $metadata, int $attachment_id ): array { - // This should take place only on the JPEG image. - $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms(); + $file = get_attached_file( $attachment_id, true ); + // File does not exist. + if ( false === $file || ! file_exists( $file ) ) { + return $metadata; + } - // Not a supported mime type to create the sources property. - $mime_type = get_post_mime_type( $attachment_id ); - if ( ! is_string( $mime_type ) || ! isset( $valid_mime_transforms[ $mime_type ] ) ) { + $mime_type = webp_uploads_get_file_mime_type( $file, $attachment_id ); + if ( '' === $mime_type ) { return $metadata; } - $file = get_attached_file( $attachment_id, true ); - // File does not exist. - if ( false === $file || ! file_exists( $file ) ) { + $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms(); + + // Not a supported mime type to create the sources property. + if ( ! isset( $valid_mime_transforms[ $mime_type ] ) ) { return $metadata; } diff --git a/plugins/webp-uploads/picture-element.php b/plugins/webp-uploads/picture-element.php index e5ea68cbfb..8beb11ef49 100644 --- a/plugins/webp-uploads/picture-element.php +++ b/plugins/webp-uploads/picture-element.php @@ -22,9 +22,21 @@ function webp_uploads_wrap_image_in_picture( string $image, string $context, int if ( 'the_content' !== $context ) { return $image; } - $image_meta = wp_get_attachment_metadata( $attachment_id ); - $original_file_mime_type = get_post_mime_type( $attachment_id ); - if ( false === $original_file_mime_type || ! isset( $image_meta['sizes'] ) ) { + + $file = get_attached_file( $attachment_id, true ); + // File does not exist. + if ( false === $file || ! file_exists( $file ) ) { + return $image; + } + + $original_file_mime_type = webp_uploads_get_file_mime_type( $file, $attachment_id ); + if ( '' === $original_file_mime_type ) { + return $image; + } + + $image_meta = wp_get_attachment_metadata( $attachment_id ); + + if ( ! isset( $image_meta['sizes'] ) ) { return $image; } diff --git a/plugins/webp-uploads/tests/test-load.php b/plugins/webp-uploads/tests/test-load.php index f70fd19e9f..c9c8f0d32f 100644 --- a/plugins/webp-uploads/tests/test-load.php +++ b/plugins/webp-uploads/tests/test-load.php @@ -43,9 +43,9 @@ static function ( string $filename ) { /** * Don't create the original mime type for JPEG images. * - * @dataProvider data_provider_supported_image_types + * @dataProvider data_provider_supported_image_types_with_threshold */ - public function test_it_should_not_create_the_original_mime_type_for_jpeg_images( string $image_type ): void { + public function test_it_should_not_create_the_original_mime_type_for_jpeg_images( string $image_type, bool $above_big_image_size = false ): void { $mime_type = 'image/' . $image_type; $this->set_image_output_type( $image_type ); if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { @@ -53,6 +53,16 @@ public function test_it_should_not_create_the_original_mime_type_for_jpeg_images } $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); + if ( $above_big_image_size ) { + // Add threshold to create a `-scaled` output image for testing. + add_filter( + 'big_image_size_threshold', + static function () { + return 850; + } + ); + } + // There should be an image_type source, but no JPEG source for the full image. $this->assertImageHasSource( $attachment_id, $mime_type ); $this->assertImageNotHasSource( $attachment_id, 'image/jpeg' ); @@ -106,42 +116,6 @@ public function test_it_should_create_the_original_mime_type_as_well_with_all_th } } - /** - * Create JPEG and output type for JPEG images, if opted in. - * - * @dataProvider data_provider_supported_image_types - */ - public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_opted_in( string $image_type ): void { - $mime_type = 'image/' . $image_type; - if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { - $this->markTestSkipped( "Mime type $mime_type is not supported." ); - } - $this->set_image_output_type( $image_type ); - - update_option( 'perflab_generate_webp_and_jpeg', true ); - $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); - - // There should be JPEG and mime_type sources for the full image. - $this->assertImageHasSource( $attachment_id, 'image/jpeg' ); - $this->assertImageHasSource( $attachment_id, $mime_type ); - - $metadata = wp_get_attachment_metadata( $attachment_id ); - - // The full image should be a JPEG. - $this->assertArrayHasKey( 'file', $metadata ); - $this->assertStringEndsWith( $metadata['sources']['image/jpeg']['file'], $metadata['file'] ); - $this->assertStringEndsWith( $metadata['sources']['image/jpeg']['file'], get_attached_file( $attachment_id ) ); - - // The post MIME type should be JPEG. - $this->assertSame( 'image/jpeg', get_post_mime_type( $attachment_id ) ); - - // There should be JPEG and WebP sources for all sizes. - foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { - $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/jpeg' ); - $this->assertImageHasSizeSource( $attachment_id, $size_name, $mime_type ); - } - } - /** * Create JPEG and output format for JPEG images, if perflab_generate_webp_and_jpeg option set. * @@ -749,6 +723,20 @@ public function data_provider_supported_image_types(): array { ); } + /** + * Data provider for tests returns the supported image types to run the tests with and without threshold check. + * + * @return array> An array of valid image types. + */ + public function data_provider_supported_image_types_with_threshold(): array { + return array( + 'webp' => array( 'webp' ), + 'webp with 850 threshold' => array( 'webp', true ), + 'avif' => array( 'avif' ), + 'avif with 850 threshold' => array( 'avif', true ), + ); + } + /** * Prevent replacing an image if image was uploaded via external source or plugin. *