From 68a01be56d4b3e25f3c2a5e9f692543752d9a3f1 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 1 Nov 2023 17:06:00 -0700
Subject: [PATCH 01/62] Add initial REST API endpoint for storing page metrics
---
.../image-loading-optimization/detect.js | 17 ++-
.../image-loading-optimization/hooks.php | 8 +-
.../image-loading-optimization/load.php | 1 +
.../image-loading-optimization/rest-api.php | 115 ++++++++++++++++++
4 files changed, 139 insertions(+), 2 deletions(-)
create mode 100644 modules/images/image-loading-optimization/rest-api.php
diff --git a/modules/images/image-loading-optimization/detect.js b/modules/images/image-loading-optimization/detect.js
index c279a7ad93..a0e7a3ce8c 100644
--- a/modules/images/image-loading-optimization/detect.js
+++ b/modules/images/image-loading-optimization/detect.js
@@ -78,11 +78,15 @@ function getBreadcrumbs( leafElement ) {
* @param {number} serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
* @param {number} detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
* @param {boolean} isDebug Whether to show debug messages.
+ * @param {string} restApiEndpoint URL for where to send the detection data.
+ * @param {string} restApiNonce Nonce for writing to the REST API.
*/
export default async function detect(
serveTime,
detectionTimeWindow,
- isDebug
+ isDebug,
+ restApiEndpoint,
+ restApiNonce
) {
const runTime = new Date().valueOf();
@@ -271,6 +275,17 @@ export default async function detect(
pageMetrics.elements.push( elementMetrics );
}
+ // TODO: Wait until idle.
+ const response = await fetch( restApiEndpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': restApiNonce,
+ },
+ body: JSON.stringify( pageMetrics ),
+ } );
+ log( 'response:', await response.json() );
+
// TODO: Send data to server.
log( pageMetrics );
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 09692aa828..620c780a03 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -68,7 +68,13 @@ function image_loading_optimization_print_detection_script() {
*/
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
- $detect_args = array( $serve_time, $detection_time_window, WP_DEBUG );
+ $detect_args = array(
+ $serve_time,
+ $detection_time_window,
+ WP_DEBUG,
+ rest_url( '/perflab/v1/image-loading-optimization/metrics-storage' ),
+ wp_create_nonce( 'wp_rest' ),
+ );
wp_print_inline_script_tag(
sprintf(
'import detect from %s; detect( ...%s )',
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index fb6e31c2bf..6f271a96d2 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -15,3 +15,4 @@
require_once __DIR__ . '/helper.php';
require_once __DIR__ . '/hooks.php';
+require_once __DIR__ . '/rest-api.php';
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
new file mode 100644
index 0000000000..bef2555c2c
--- /dev/null
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -0,0 +1,115 @@
+ 'object',
+ 'properties' => array(
+ 'width' => array(
+ 'type' => 'number',
+ 'minimum' => 0,
+ ),
+ 'height' => array(
+ 'type' => 'number',
+ 'minimum' => 0,
+ ),
+ // TODO: There are other properties to define if we need them: x, y, top, right, bottom, left.
+ ),
+ );
+
+ register_rest_route(
+ 'perflab/v1',
+ '/image-loading-optimization/metrics-storage',
+ array(
+ 'methods' => 'POST',
+ 'callback' => 'image_loading_optimization_handle_rest_request',
+ 'permission_callback' => '__return_true', // Needs to be available to unauthenticated visitors.
+ 'args' => array(
+ 'viewport' => array(
+ 'description' => __( 'Viewport dimensions', 'performance-lab' ),
+ 'type' => 'object',
+ 'required' => true,
+ 'properties' => array(
+ 'width' => array(
+ 'type' => 'int',
+ 'minimum' => 0,
+ ),
+ 'height' => array(
+ 'type' => 'int',
+ 'minimum' => 0,
+ ),
+ ),
+ ),
+ 'elements' => array(
+ 'description' => __( 'Element metrics', 'performance-lab' ),
+ 'type' => 'array',
+ 'items' => array(
+ // See the ElementMetrics in detect.js.
+ 'type' => 'object',
+ 'properties' => array(
+ 'isLCP' => array(
+ 'type' => 'bool',
+ ),
+ 'isLCPCandidate' => array(
+ 'type' => 'bool',
+ ),
+ 'breadcrumbs' => array(
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'tagName' => array(
+ 'type' => 'string',
+ // TODO: Pattern?
+ ),
+ 'index' => array(
+ 'type' => 'int',
+ 'minimum' => 0,
+ ),
+ ),
+ ),
+ ),
+ 'intersectionRatio' => array(
+ 'type' => 'number',
+ 'minimum' => 0.0,
+ 'maximum' => 1.0,
+ ),
+ 'intersectionRect' => $dom_rect_schema,
+ 'boundingClientRect' => $dom_rect_schema,
+ ),
+ ),
+ ),
+ ),
+ )
+ );
+}
+add_action( 'rest_api_init', 'image_loading_optimization_register_endpoint' );
+
+/**
+ * Handle REST API request to store metrics.
+ *
+ * @param WP_REST_Request $request Request.
+ * @return WP_REST_Response Response.
+ */
+function image_loading_optimization_handle_rest_request( WP_REST_Request $request ) {
+
+ return new WP_REST_Response(
+ array(
+ 'success' => true,
+ 'body' => $request->get_json_params(),
+ )
+ );
+}
From b4e29d609647c8e23fc4db6532e50ba8692e82c5 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 2 Nov 2023 16:06:26 -0700
Subject: [PATCH 02/62] Include url in PageMetrics and further define schema
---
.../image-loading-optimization/detect.js | 2 ++
.../image-loading-optimization/hooks.php | 3 ++
.../image-loading-optimization/rest-api.php | 34 ++++++++++++-------
3 files changed, 27 insertions(+), 12 deletions(-)
diff --git a/modules/images/image-loading-optimization/detect.js b/modules/images/image-loading-optimization/detect.js
index a0e7a3ce8c..129079b4a7 100644
--- a/modules/images/image-loading-optimization/detect.js
+++ b/modules/images/image-loading-optimization/detect.js
@@ -31,6 +31,7 @@ function warn( ...message ) {
/**
* @typedef {Object} PageMetrics
+ * @property {string} url - URL of the page.
* @property {Object} viewport - Viewport.
* @property {number} viewport.width - Viewport width.
* @property {number} viewport.height - Viewport height.
@@ -235,6 +236,7 @@ export default async function detect(
/** @type {PageMetrics} */
const pageMetrics = {
+ url: win.location.href, // TODO: Consider sending canonical URL instead.
viewport: {
width: win.innerWidth,
height: win.innerHeight,
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 620c780a03..666c795214 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -49,6 +49,9 @@ static function ( $output ) {
/**
* Prints the script for detecting loaded images and the LCP element.
+ *
+ * @todo This should eventually only print the script if metrics are needed.
+ * @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
*/
function image_loading_optimization_print_detection_script() {
$serve_time = ceil( microtime( true ) * 1000 );
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index bef2555c2c..69519ec562 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -38,18 +38,25 @@ function image_loading_optimization_register_endpoint() {
'callback' => 'image_loading_optimization_handle_rest_request',
'permission_callback' => '__return_true', // Needs to be available to unauthenticated visitors.
'args' => array(
+ 'url' => array(
+ 'type' => 'string',
+ 'required' => true,
+ 'format' => 'uri',
+ ),
'viewport' => array(
'description' => __( 'Viewport dimensions', 'performance-lab' ),
'type' => 'object',
'required' => true,
'properties' => array(
'width' => array(
- 'type' => 'int',
- 'minimum' => 0,
+ 'type' => 'int',
+ 'required' => true,
+ 'minimum' => 0,
),
'height' => array(
- 'type' => 'int',
- 'minimum' => 0,
+ 'type' => 'int',
+ 'required' => true,
+ 'minimum' => 0,
),
),
),
@@ -61,19 +68,21 @@ function image_loading_optimization_register_endpoint() {
'type' => 'object',
'properties' => array(
'isLCP' => array(
- 'type' => 'bool',
+ 'type' => 'bool',
+ 'required' => true,
),
'isLCPCandidate' => array(
'type' => 'bool',
),
'breadcrumbs' => array(
- 'type' => 'array',
- 'items' => array(
+ 'type' => 'array',
+ 'required' => true,
+ 'items' => array(
'type' => 'object',
'properties' => array(
'tagName' => array(
- 'type' => 'string',
- // TODO: Pattern?
+ 'type' => 'string',
+ 'pattern' => '^[a-zA-Z0-9-]+$',
),
'index' => array(
'type' => 'int',
@@ -83,9 +92,10 @@ function image_loading_optimization_register_endpoint() {
),
),
'intersectionRatio' => array(
- 'type' => 'number',
- 'minimum' => 0.0,
- 'maximum' => 1.0,
+ 'type' => 'number',
+ 'required' => true,
+ 'minimum' => 0.0,
+ 'maximum' => 1.0,
),
'intersectionRect' => $dom_rect_schema,
'boundingClientRect' => $dom_rect_schema,
From ffcae75d01ea982722fa217c336c76be0d2d1ccc Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 2 Nov 2023 16:12:06 -0700
Subject: [PATCH 03/62] Validate that the provided URL is for this site
---
.../images/image-loading-optimization/rest-api.php | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index 69519ec562..bcb764643a 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -39,9 +39,15 @@ function image_loading_optimization_register_endpoint() {
'permission_callback' => '__return_true', // Needs to be available to unauthenticated visitors.
'args' => array(
'url' => array(
- 'type' => 'string',
- 'required' => true,
- 'format' => 'uri',
+ 'type' => 'string',
+ 'required' => true,
+ 'format' => 'uri',
+ 'validate_callback' => static function ( $url ) {
+ if ( ! wp_validate_redirect( $url ) ) {
+ return new WP_Error( 'non_origin_url', __( 'URL for another site provided.', 'performance-lab' ) );
+ }
+ return true;
+ },
),
'viewport' => array(
'description' => __( 'Viewport dimensions', 'performance-lab' ),
From 1634fb9a5be0f8e22e4950c086b94e36e52f76cb Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 2 Nov 2023 17:18:32 -0700
Subject: [PATCH 04/62] Ensure both tagName and index are required
---
modules/images/image-loading-optimization/rest-api.php | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index bcb764643a..335a70aa05 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -87,12 +87,14 @@ function image_loading_optimization_register_endpoint() {
'type' => 'object',
'properties' => array(
'tagName' => array(
- 'type' => 'string',
- 'pattern' => '^[a-zA-Z0-9-]+$',
+ 'type' => 'string',
+ 'required' => true,
+ 'pattern' => '^[a-zA-Z0-9-]+$',
),
'index' => array(
- 'type' => 'int',
- 'minimum' => 0,
+ 'type' => 'int',
+ 'required' => true,
+ 'minimum' => 0,
),
),
),
From 534eba2d1378970b5a535739190bcc459c6f8fd6 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 2 Nov 2023 17:57:58 -0700
Subject: [PATCH 05/62] Add initial storage locking to protect against flooding
---
.../image-loading-optimization/helper.php | 56 +++++++++++++++++++
.../image-loading-optimization/hooks.php | 5 ++
.../image-loading-optimization/rest-api.php | 16 +++++-
3 files changed, 76 insertions(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/helper.php b/modules/images/image-loading-optimization/helper.php
index 77e6b12b10..59e47f937a 100644
--- a/modules/images/image-loading-optimization/helper.php
+++ b/modules/images/image-loading-optimization/helper.php
@@ -9,3 +9,59 @@
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
+
+/**
+ * Gets the TTL for the metrics storage lock.
+ *
+ * @return int TTL.
+ */
+function image_loading_optimization_get_metrics_storage_lock_ttl() {
+
+ /**
+ * Filters how long a given IP is locked from submitting another metrics-storage REST API request.
+ *
+ * @param int $ttl TTL.
+ */
+ return (int) apply_filters( 'perflab_image_loading_detection_lock_ttl', 10 * MINUTE_IN_SECONDS );
+}
+
+/**
+ * Gets transient key for locking metrics storage (for the current IP).
+ *
+ * @return string Transient key.
+ */
+function image_loading_optimization_get_metrics_storage_lock_transient_key() {
+ $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
+ return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
+}
+
+/**
+ * Sets metrics storage lock (for the current IP).
+ */
+function image_loading_optimization_set_metrics_storage_lock() {
+ $ttl = image_loading_optimization_get_metrics_storage_lock_ttl();
+ $key = image_loading_optimization_get_metrics_storage_lock_transient_key();
+ if ( 0 === $ttl ) {
+ delete_transient( $key );
+ } else {
+ set_transient( $key, time(), $ttl );
+ }
+}
+
+/**
+ * Checks whether metrics storage is locked (for the current IP).
+ *
+ * @todo This isn't working properly?
+ * @return bool Whether locked.
+ */
+function image_loading_optimization_is_metrics_storage_locked() {
+ $ttl = image_loading_optimization_get_metrics_storage_lock_ttl();
+ if ( 0 === $ttl ) {
+ return false;
+ }
+ $transient = (int) get_transient( image_loading_optimization_get_metrics_storage_lock_transient_key() );
+ if ( 0 === $transient ) {
+ return false;
+ }
+ return time() - $transient < $ttl;
+}
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 666c795214..88dd70635b 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -54,6 +54,11 @@ static function ( $output ) {
* @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
*/
function image_loading_optimization_print_detection_script() {
+
+ if ( image_loading_optimization_is_metrics_storage_locked() ) {
+ return;
+ }
+
$serve_time = ceil( microtime( true ) * 1000 );
/**
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index 335a70aa05..0746ff1315 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -36,7 +36,17 @@ function image_loading_optimization_register_endpoint() {
array(
'methods' => 'POST',
'callback' => 'image_loading_optimization_handle_rest_request',
- 'permission_callback' => '__return_true', // Needs to be available to unauthenticated visitors.
+ 'permission_callback' => static function () {
+ // Needs to be available to unauthenticated visitors.
+ if ( image_loading_optimization_is_metrics_storage_locked() ) {
+ return new WP_Error(
+ 'metrics_storage_locked',
+ __( 'Metrics storage is presently locked for the current IP.', 'performance-lab' ),
+ array( 'status' => 403 )
+ );
+ }
+ return true;
+ },
'args' => array(
'url' => array(
'type' => 'string',
@@ -124,6 +134,10 @@ function image_loading_optimization_register_endpoint() {
*/
function image_loading_optimization_handle_rest_request( WP_REST_Request $request ) {
+ // TODO: We need storage.
+
+ image_loading_optimization_set_metrics_storage_lock();
+
return new WP_REST_Response(
array(
'success' => true,
From ba30471f82bc97fae8eb9788375d81d2d2bdabaa Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 3 Nov 2023 11:44:18 -0700
Subject: [PATCH 06/62] Update metrics storage locking
---
modules/images/image-loading-optimization/helper.php | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/modules/images/image-loading-optimization/helper.php b/modules/images/image-loading-optimization/helper.php
index 59e47f937a..e55f0bb992 100644
--- a/modules/images/image-loading-optimization/helper.php
+++ b/modules/images/image-loading-optimization/helper.php
@@ -20,14 +20,17 @@ function image_loading_optimization_get_metrics_storage_lock_ttl() {
/**
* Filters how long a given IP is locked from submitting another metrics-storage REST API request.
*
+ * Filtering the TTL to zero will disable any metrics storage locking. This is useful during development.
+ *
* @param int $ttl TTL.
*/
- return (int) apply_filters( 'perflab_image_loading_detection_lock_ttl', 10 * MINUTE_IN_SECONDS );
+ return (int) apply_filters( 'perflab_image_loading_optimization_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
}
/**
* Gets transient key for locking metrics storage (for the current IP).
*
+ * @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
* @return string Transient key.
*/
function image_loading_optimization_get_metrics_storage_lock_transient_key() {
@@ -51,7 +54,6 @@ function image_loading_optimization_set_metrics_storage_lock() {
/**
* Checks whether metrics storage is locked (for the current IP).
*
- * @todo This isn't working properly?
* @return bool Whether locked.
*/
function image_loading_optimization_is_metrics_storage_locked() {
@@ -59,9 +61,9 @@ function image_loading_optimization_is_metrics_storage_locked() {
if ( 0 === $ttl ) {
return false;
}
- $transient = (int) get_transient( image_loading_optimization_get_metrics_storage_lock_transient_key() );
- if ( 0 === $transient ) {
+ $locked_time = (int) get_transient( image_loading_optimization_get_metrics_storage_lock_transient_key() );
+ if ( 0 === $locked_time ) {
return false;
}
- return time() - $transient < $ttl;
+ return time() - $locked_time < $ttl;
}
From 4e0d7de6cc43c0d7bd9e89c4352b754239fcda79 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 3 Nov 2023 11:54:32 -0700
Subject: [PATCH 07/62] Move storage helper functions into storage.php
---
.../image-loading-optimization/helper.php | 58 ----------------
.../image-loading-optimization/load.php | 1 +
.../image-loading-optimization/storage.php | 69 +++++++++++++++++++
3 files changed, 70 insertions(+), 58 deletions(-)
create mode 100644 modules/images/image-loading-optimization/storage.php
diff --git a/modules/images/image-loading-optimization/helper.php b/modules/images/image-loading-optimization/helper.php
index e55f0bb992..77e6b12b10 100644
--- a/modules/images/image-loading-optimization/helper.php
+++ b/modules/images/image-loading-optimization/helper.php
@@ -9,61 +9,3 @@
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
-
-/**
- * Gets the TTL for the metrics storage lock.
- *
- * @return int TTL.
- */
-function image_loading_optimization_get_metrics_storage_lock_ttl() {
-
- /**
- * Filters how long a given IP is locked from submitting another metrics-storage REST API request.
- *
- * Filtering the TTL to zero will disable any metrics storage locking. This is useful during development.
- *
- * @param int $ttl TTL.
- */
- return (int) apply_filters( 'perflab_image_loading_optimization_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
-}
-
-/**
- * Gets transient key for locking metrics storage (for the current IP).
- *
- * @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
- * @return string Transient key.
- */
-function image_loading_optimization_get_metrics_storage_lock_transient_key() {
- $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
- return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
-}
-
-/**
- * Sets metrics storage lock (for the current IP).
- */
-function image_loading_optimization_set_metrics_storage_lock() {
- $ttl = image_loading_optimization_get_metrics_storage_lock_ttl();
- $key = image_loading_optimization_get_metrics_storage_lock_transient_key();
- if ( 0 === $ttl ) {
- delete_transient( $key );
- } else {
- set_transient( $key, time(), $ttl );
- }
-}
-
-/**
- * Checks whether metrics storage is locked (for the current IP).
- *
- * @return bool Whether locked.
- */
-function image_loading_optimization_is_metrics_storage_locked() {
- $ttl = image_loading_optimization_get_metrics_storage_lock_ttl();
- if ( 0 === $ttl ) {
- return false;
- }
- $locked_time = (int) get_transient( image_loading_optimization_get_metrics_storage_lock_transient_key() );
- if ( 0 === $locked_time ) {
- return false;
- }
- return time() - $locked_time < $ttl;
-}
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 6f271a96d2..e760b0a6e9 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -15,4 +15,5 @@
require_once __DIR__ . '/helper.php';
require_once __DIR__ . '/hooks.php';
+require_once __DIR__ . '/storage.php';
require_once __DIR__ . '/rest-api.php';
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
new file mode 100644
index 0000000000..e3032c28bb
--- /dev/null
+++ b/modules/images/image-loading-optimization/storage.php
@@ -0,0 +1,69 @@
+
Date: Fri, 3 Nov 2023 12:15:23 -0700
Subject: [PATCH 08/62] Add post type for page metrics storage
---
.../image-loading-optimization/storage.php | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index e3032c28bb..49a33ccd75 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -10,6 +10,8 @@
exit; // Exit if accessed directly.
}
+define( 'IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
+
/**
* Gets the TTL for the metrics storage lock.
*
@@ -67,3 +69,28 @@ function image_loading_optimization_is_metrics_storage_locked() {
}
return time() - $locked_time < $ttl;
}
+
+/**
+ * Register post type for metrics storage.
+ *
+ * This the configuration for this post type is similar to the oembed_cache in core.
+ */
+function image_loading_optimization_register_page_metrics_post_type() {
+ register_post_type(
+ IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE,
+ array(
+ 'labels' => array(
+ 'name' => __( 'Page Metrics', 'performance-lab' ),
+ 'singular_name' => __( 'Page Metrics', 'performance-lab' ),
+ ),
+ 'public' => false,
+ 'hierarchical' => false,
+ 'rewrite' => false,
+ 'query_var' => false,
+ 'delete_with_user' => false,
+ 'can_export' => false,
+ 'supports' => array(),
+ )
+ );
+}
+add_action( 'init', 'image_loading_optimization_register_page_metrics_post_type' );
From b7396c8106c5d224bc1853aa14a4a0144ac0da4e Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 6 Nov 2023 17:15:32 -0800
Subject: [PATCH 09/62] WIP
---
composer.json | 3 +-
.../image-loading-optimization/rest-api.php | 2 +-
.../image-loading-optimization/storage.php | 189 +++++++++++++++++-
3 files changed, 191 insertions(+), 3 deletions(-)
diff --git a/composer.json b/composer.json
index 8ddca6af09..27cd7d9afc 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,8 @@
},
"require": {
"composer/installers": "~1.0",
- "php": ">=7|^8"
+ "php": ">=7|^8",
+ "ext-json": "*"
},
"scripts": {
"phpstan": "phpstan analyze --ansi --memory-limit=2048M",
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index 0746ff1315..a347f75d29 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -32,7 +32,7 @@ function image_loading_optimization_register_endpoint() {
register_rest_route(
'perflab/v1',
- '/image-loading-optimization/metrics-storage',
+ '/image-loading-optimization/metrics-storage', // @todo or rather metric-storage?
array(
'methods' => 'POST',
'callback' => 'image_loading_optimization_handle_rest_request',
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index 49a33ccd75..c9413609b3 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -29,6 +29,22 @@ function image_loading_optimization_get_metrics_storage_lock_ttl() {
return (int) apply_filters( 'perflab_image_loading_optimization_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
}
+/**
+ * Gets the maximum width for a viewport to be considered as a mobile device.
+ *
+ * @todo This could instead return an array of thresholds, like [ 320, 480, 576 ] which would add additional buckets for small smartphones and phablets in addition to normal smartphones and desktops.
+ * @return int Viewport width.
+ */
+function image_loading_optimization_get_max_mobile_viewport_width() {
+
+ /**
+ * Filters the maximum width for a viewport to be considered as a mobile device.
+ *
+ * @param int $mobile_max_width Mobile max width.
+ */
+ return (int) apply_filters( 'perflab_image_loading_optimization_max_mobile_viewport_with', 480 );
+}
+
/**
* Gets transient key for locking metrics storage (for the current IP).
*
@@ -89,8 +105,179 @@ function image_loading_optimization_register_page_metrics_post_type() {
'query_var' => false,
'delete_with_user' => false,
'can_export' => false,
- 'supports' => array(),
+ 'supports' => array( 'title' ), // The original URL is stored in the post_title, and the MD5 hash in the post_name.
)
);
}
add_action( 'init', 'image_loading_optimization_register_page_metrics_post_type' );
+
+/**
+ * Gets desired sample size for a viewport's page metrics.
+ *
+ * @return int
+ */
+function image_loading_optimization_get_page_metrics_viewport_sample_size() {
+ /**
+ * Filters desired sample size for a viewport's page metrics.
+ *
+ * @param int $sample_size Sample size.
+ */
+ return (int) apply_filters( 'perflab_image_loading_optimization_page_metrics_viewport_sample_size', 10 );
+}
+
+/**
+ * Get slug for page metrics post.
+ *
+ * @param string $url URL.
+ * @return string Slug for URL.
+ */
+function image_loading_optimization_get_page_metrics_slug( $url ) {
+ return md5( $url );
+}
+
+/**
+ * Get page metrics post.
+ *
+ * @param string $url URL.
+ * @return WP_Post|null Post object if exists.
+ */
+function image_loading_optimization_get_page_metrics_post( $url ) {
+ $post_query = new WP_Query(
+ array(
+ 'post_type' => IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE,
+ 'post_status' => 'publish',
+ 'name' => image_loading_optimization_get_page_metrics_slug( $url ),
+ 'posts_per_page' => 1,
+ 'no_found_rows' => true,
+ 'cache_results' => true,
+ 'update_post_meta_cache' => false,
+ 'update_post_term_cache' => false,
+ 'lazy_load_term_meta' => false,
+ )
+ );
+
+ $post = array_shift( $post_query->posts );
+ if ( $post instanceof WP_Post ) {
+ return $post;
+ } else {
+ return null;
+ }
+}
+
+/**
+ * Store page metrics.
+ *
+ * @param WP_Post $post Page metrics post.
+ * @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
+ */
+function image_loading_optimization_parse_stored_page_metrics( WP_Post $post ) {
+ $page_metrics = json_decode( $post->post_content, true );
+ if ( json_last_error() ) {
+ return new WP_Error(
+ 'page_metrics_json_parse_error',
+ sprintf(
+ /* translators: 1: Post type slug, 2: JSON error message */
+ __( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
+ IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE,
+ json_last_error_msg()
+ )
+ );
+ }
+ if ( ! is_array( $page_metrics ) ) {
+ return new WP_Error(
+ 'page_metrics_invalid_data_format',
+ sprintf(
+ /* translators: %s is post type slug */
+ __( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
+ IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE
+ )
+ );
+ }
+ return $page_metrics;
+}
+
+/**
+ *
+ * @todo This needs to take a set of page metrics and segment the individual metrics into breakpoints.
+ *
+ * @return void
+ */
+function image_loading_optimization_segment_stored_page_metrics() {
+
+}
+
+/**
+ * Store page metrics.
+ *
+ * The $validated_page_metrics parameter has the following array shape:
+ *
+ * {
+ * 'url': string,
+ * 'viewport': array{
+ * 'width': int,
+ * 'height': int
+ * },
+ * 'elements': array
+ * }
+ *
+ * @param array $validated_page_metrics Page metrics, already validated by REST API.
+ * @return true|WP_Error True on success or WP_Error otherwise.
+ */
+function image_loading_optimization_store_page_metrics( array $validated_page_metrics ) {
+ $url = $validated_page_metrics['url'];
+ unset( $validated_page_metrics['url'] ); // Not stored in post_content but rather in post_title/post_name.
+
+ // TODO: What about storing a version identifier?
+ $post_data = array(
+ 'post_title' => $url,
+ );
+
+ $post = image_loading_optimization_get_page_metrics_post( $url );
+
+ if ( $post instanceof WP_Post ) {
+ $post_data['ID'] = $post->ID;
+ $post_data['post_name'] = $post->post_name;
+
+ $page_metrics = image_loading_optimization_parse_stored_page_metrics( $post );
+ if ( $page_metrics instanceof WP_Error ) {
+ if ( function_exists( 'wp_trigger_error' ) ) {
+ wp_trigger_error( __FUNCTION__, esc_html( $page_metrics->get_error_message() ) );
+ }
+ $page_metrics = array();
+ }
+ } else {
+ $post_data['post_name'] = image_loading_optimization_get_page_metrics_slug( $url );
+ $page_metrics = array();
+ }
+
+ // TODO: Unshift the first metrics entry if we are currently at the max allowed.
+ $segmented_page_metrics =
+
+ $mobile_max_width = image_loading_optimization_get_max_mobile_viewport_width();
+ $viewport_sample_size = image_loading_optimization_get_page_metrics_viewport_sample_size();
+
+ $viewport_page_metrics = array();
+
+ $existing_storage[] = $validated_page_metrics;
+
+
+ $post_data['post_content'] = wp_json_encode( $validated_page_metrics );
+
+ $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
+ if ( $has_kses ) {
+ // Prevent KSES from corrupting JSON in post_content.
+ kses_remove_filters();
+ }
+
+ if ( isset( $post_data['ID'] ) ) {
+ $result = wp_update_post( wp_slash( $post_data ), true );
+ } else {
+ $result = wp_insert_post( wp_slash( $post_data ), true );
+ }
+
+ if ( $has_kses ) {
+ kses_init_filters();
+ }
+
+ return $result instanceof WP_Error ? $result : true;
+}
From 0e5b79f660970bfa1d4dffdf2bcdebfb0099bb3f Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 6 Nov 2023 19:53:06 -0800
Subject: [PATCH 10/62] Fix storage and improve naming
---
.../image-loading-optimization/hooks.php | 4 +-
.../image-loading-optimization/rest-api.php | 31 +++++---
.../image-loading-optimization/storage.php | 74 ++++++++++---------
3 files changed, 61 insertions(+), 48 deletions(-)
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 88dd70635b..43fa5a1686 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -55,7 +55,7 @@ static function ( $output ) {
*/
function image_loading_optimization_print_detection_script() {
- if ( image_loading_optimization_is_metrics_storage_locked() ) {
+ if ( image_loading_optimization_is_page_metric_storage_locked() ) {
return;
}
@@ -80,7 +80,7 @@ function image_loading_optimization_print_detection_script() {
$serve_time,
$detection_time_window,
WP_DEBUG,
- rest_url( '/perflab/v1/image-loading-optimization/metrics-storage' ),
+ rest_url( IMAGE_LOADING_OPTIMIZATION_REST_API_NAMESPACE . IMAGE_LOADING_OPTIMIZATION_PAGE_METRIC_STORAGE_ROUTE ),
wp_create_nonce( 'wp_rest' ),
);
wp_print_inline_script_tag(
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index a347f75d29..6f33a8c17b 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -10,8 +10,11 @@
exit; // Exit if accessed directly.
}
+define( 'IMAGE_LOADING_OPTIMIZATION_REST_API_NAMESPACE', 'image-loading-optimization/v1' );
+define( 'IMAGE_LOADING_OPTIMIZATION_PAGE_METRIC_STORAGE_ROUTE', '/image-loading-optimization/page-metric-storage' );
+
/**
- * Register endpoint for storage of metrics.
+ * Register endpoint for storage of page metric.
*/
function image_loading_optimization_register_endpoint() {
@@ -31,17 +34,17 @@ function image_loading_optimization_register_endpoint() {
);
register_rest_route(
- 'perflab/v1',
- '/image-loading-optimization/metrics-storage', // @todo or rather metric-storage?
+ IMAGE_LOADING_OPTIMIZATION_REST_API_NAMESPACE,
+ IMAGE_LOADING_OPTIMIZATION_PAGE_METRIC_STORAGE_ROUTE,
array(
'methods' => 'POST',
'callback' => 'image_loading_optimization_handle_rest_request',
'permission_callback' => static function () {
// Needs to be available to unauthenticated visitors.
- if ( image_loading_optimization_is_metrics_storage_locked() ) {
+ if ( image_loading_optimization_is_page_metric_storage_locked() ) {
return new WP_Error(
- 'metrics_storage_locked',
- __( 'Metrics storage is presently locked for the current IP.', 'performance-lab' ),
+ 'page_metric_storage_locked',
+ __( 'Page metric storage is presently locked for the current IP.', 'performance-lab' ),
array( 'status' => 403 )
);
}
@@ -130,18 +133,26 @@ function image_loading_optimization_register_endpoint() {
* Handle REST API request to store metrics.
*
* @param WP_REST_Request $request Request.
- * @return WP_REST_Response Response.
+ * @return WP_REST_Response|WP_Error Response.
*/
function image_loading_optimization_handle_rest_request( WP_REST_Request $request ) {
// TODO: We need storage.
- image_loading_optimization_set_metrics_storage_lock();
+ image_loading_optimization_set_page_metric_storage_lock();
+
+ $result = image_loading_optimization_store_page_metric( $request->get_json_params() );
+
+ if ( $result instanceof WP_Error ) {
+ return $result;
+ }
- return new WP_REST_Response(
+ $response = new WP_REST_Response(
array(
'success' => true,
- 'body' => $request->get_json_params(),
+ 'post_id' => $result,
)
);
+ $response->set_status( 201 );
+ return $response;
}
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index c9413609b3..9f430b9dd4 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -13,16 +13,16 @@
define( 'IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
/**
- * Gets the TTL for the metrics storage lock.
+ * Gets the TTL for the page metric storage lock.
*
* @return int TTL.
*/
-function image_loading_optimization_get_metrics_storage_lock_ttl() {
+function image_loading_optimization_get_page_metric_storage_lock_ttl() {
/**
- * Filters how long a given IP is locked from submitting another metrics-storage REST API request.
+ * Filters how long a given IP is locked from submitting another metric-storage REST API request.
*
- * Filtering the TTL to zero will disable any metrics storage locking. This is useful during development.
+ * Filtering the TTL to zero will disable any metric storage locking. This is useful during development.
*
* @param int $ttl TTL.
*/
@@ -46,22 +46,22 @@ function image_loading_optimization_get_max_mobile_viewport_width() {
}
/**
- * Gets transient key for locking metrics storage (for the current IP).
+ * Gets transient key for locking page metric storage (for the current IP).
*
* @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
* @return string Transient key.
*/
-function image_loading_optimization_get_metrics_storage_lock_transient_key() {
+function image_loading_optimization_get_page_metric_storage_lock_transient_key() {
$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
}
/**
- * Sets metrics storage lock (for the current IP).
+ * Sets page metric storage lock (for the current IP).
*/
-function image_loading_optimization_set_metrics_storage_lock() {
- $ttl = image_loading_optimization_get_metrics_storage_lock_ttl();
- $key = image_loading_optimization_get_metrics_storage_lock_transient_key();
+function image_loading_optimization_set_page_metric_storage_lock() {
+ $ttl = image_loading_optimization_get_page_metric_storage_lock_ttl();
+ $key = image_loading_optimization_get_page_metric_storage_lock_transient_key();
if ( 0 === $ttl ) {
delete_transient( $key );
} else {
@@ -70,16 +70,16 @@ function image_loading_optimization_set_metrics_storage_lock() {
}
/**
- * Checks whether metrics storage is locked (for the current IP).
+ * Checks whether page metric storage is locked (for the current IP).
*
* @return bool Whether locked.
*/
-function image_loading_optimization_is_metrics_storage_locked() {
- $ttl = image_loading_optimization_get_metrics_storage_lock_ttl();
+function image_loading_optimization_is_page_metric_storage_locked() {
+ $ttl = image_loading_optimization_get_page_metric_storage_lock_ttl();
if ( 0 === $ttl ) {
return false;
}
- $locked_time = (int) get_transient( image_loading_optimization_get_metrics_storage_lock_transient_key() );
+ $locked_time = (int) get_transient( image_loading_optimization_get_page_metric_storage_lock_transient_key() );
if ( 0 === $locked_time ) {
return false;
}
@@ -87,7 +87,7 @@ function image_loading_optimization_is_metrics_storage_locked() {
}
/**
- * Register post type for metrics storage.
+ * Register post type for page metrics storage.
*
* This the configuration for this post type is similar to the oembed_cache in core.
*/
@@ -126,7 +126,7 @@ function image_loading_optimization_get_page_metrics_viewport_sample_size() {
}
/**
- * Get slug for page metrics post.
+ * Gets slug for page metrics post.
*
* @param string $url URL.
* @return string Slug for URL.
@@ -165,7 +165,7 @@ function image_loading_optimization_get_page_metrics_post( $url ) {
}
/**
- * Store page metrics.
+ * Parses post content in page metrics post.
*
* @param WP_Post $post Page metrics post.
* @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
@@ -202,14 +202,14 @@ function image_loading_optimization_parse_stored_page_metrics( WP_Post $post ) {
*
* @return void
*/
-function image_loading_optimization_segment_stored_page_metrics() {
+function image_loading_optimization_segment_stored_page_metrics( array $page_metrics, array $breakpoints ) {
}
/**
- * Store page metrics.
+ * Stores page metric by merging it with the other page metrics for a given URL.
*
- * The $validated_page_metrics parameter has the following array shape:
+ * The $validated_page_metric parameter has the following array shape:
*
* {
* 'url': string,
@@ -220,12 +220,14 @@ function image_loading_optimization_segment_stored_page_metrics() {
* 'elements': array
* }
*
- * @param array $validated_page_metrics Page metrics, already validated by REST API.
- * @return true|WP_Error True on success or WP_Error otherwise.
+ * @param array $validated_page_metric Page metric, already validated by REST API.
+ *
+ * @return int|WP_Error Post ID or WP_Error otherwise.
*/
-function image_loading_optimization_store_page_metrics( array $validated_page_metrics ) {
- $url = $validated_page_metrics['url'];
- unset( $validated_page_metrics['url'] ); // Not stored in post_content but rather in post_title/post_name.
+function image_loading_optimization_store_page_metric( array $validated_page_metric ) {
+ $url = $validated_page_metric['url'];
+ unset( $validated_page_metric['url'] ); // Not stored in post_content but rather in post_title/post_name.
+ $validated_page_metric['timestamp'] = time();
// TODO: What about storing a version identifier?
$post_data = array(
@@ -250,18 +252,16 @@ function image_loading_optimization_store_page_metrics( array $validated_page_me
$page_metrics = array();
}
- // TODO: Unshift the first metrics entry if we are currently at the max allowed.
- $segmented_page_metrics =
-
- $mobile_max_width = image_loading_optimization_get_max_mobile_viewport_width();
+ // Add the provided page metric to the page metrics.
+ // TODO: Need to implement viewport breakpoint segmenting.
+ // $segmented_page_metrics =
+ // $mobile_max_width = image_loading_optimization_get_max_mobile_viewport_width();
$viewport_sample_size = image_loading_optimization_get_page_metrics_viewport_sample_size();
+ // $viewport_page_metrics = array();
+ $page_metrics = array_slice( $page_metrics, 0, $viewport_sample_size - 1 ); // Make room for the additional page metric.
+ array_unshift( $page_metrics, $validated_page_metric );
- $viewport_page_metrics = array();
-
- $existing_storage[] = $validated_page_metrics;
-
-
- $post_data['post_content'] = wp_json_encode( $validated_page_metrics );
+ $post_data['post_content'] = wp_json_encode( $page_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
if ( $has_kses ) {
@@ -269,6 +269,8 @@ function image_loading_optimization_store_page_metrics( array $validated_page_me
kses_remove_filters();
}
+ $post_data['post_type'] = IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE;
+ $post_data['post_status'] = 'publish';
if ( isset( $post_data['ID'] ) ) {
$result = wp_update_post( wp_slash( $post_data ), true );
} else {
@@ -279,5 +281,5 @@ function image_loading_optimization_store_page_metrics( array $validated_page_me
kses_init_filters();
}
- return $result instanceof WP_Error ? $result : true;
+ return $result;
}
From 739a53e55f461cada695d4a24e9dccfc95f6567b Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 6 Nov 2023 20:01:40 -0800
Subject: [PATCH 11/62] Use ILO consistently as the prefix
---
.../image-loading-optimization/hooks.php | 12 ++--
.../image-loading-optimization/load.php | 6 +-
.../image-loading-optimization/rest-api.php | 22 +++----
.../image-loading-optimization/storage.php | 64 +++++++++----------
4 files changed, 52 insertions(+), 52 deletions(-)
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 43fa5a1686..718cfe8853 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -31,7 +31,7 @@
* @param mixed $passthrough Optional. Filter value. Default null.
* @return mixed Unmodified value of $passthrough.
*/
-function image_loading_optimization_buffer_output( $passthrough = null ) {
+function ilo_buffer_output( $passthrough = null ) {
ob_start(
static function ( $output ) {
/**
@@ -45,7 +45,7 @@ static function ( $output ) {
);
return $passthrough;
}
-add_filter( 'template_include', 'image_loading_optimization_buffer_output', PHP_INT_MAX );
+add_filter( 'template_include', 'ilo_buffer_output', PHP_INT_MAX );
/**
* Prints the script for detecting loaded images and the LCP element.
@@ -53,9 +53,9 @@ static function ( $output ) {
* @todo This should eventually only print the script if metrics are needed.
* @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
*/
-function image_loading_optimization_print_detection_script() {
+function ilo_print_detection_script() {
- if ( image_loading_optimization_is_page_metric_storage_locked() ) {
+ if ( ilo_is_page_metric_storage_locked() ) {
return;
}
@@ -80,7 +80,7 @@ function image_loading_optimization_print_detection_script() {
$serve_time,
$detection_time_window,
WP_DEBUG,
- rest_url( IMAGE_LOADING_OPTIMIZATION_REST_API_NAMESPACE . IMAGE_LOADING_OPTIMIZATION_PAGE_METRIC_STORAGE_ROUTE ),
+ rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRIC_STORAGE_ROUTE ),
wp_create_nonce( 'wp_rest' ),
);
wp_print_inline_script_tag(
@@ -92,4 +92,4 @@ function image_loading_optimization_print_detection_script() {
array( 'type' => 'module' )
);
}
-add_action( 'wp_print_footer_scripts', 'image_loading_optimization_print_detection_script' );
+add_action( 'wp_print_footer_scripts', 'ilo_print_detection_script' );
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 0828ab47bb..2aaace8afa 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -9,14 +9,14 @@
*/
// Define the constant.
-if ( defined( 'IMAGE_LOADING_OPTIMIZATION_VERSION' ) ) {
+if ( defined( 'ILO_VERSION' ) ) {
return;
}
-define( 'IMAGE_LOADING_OPTIMIZATION_VERSION', 'Performance Lab ' . PERFLAB_VERSION );
+define( 'ILO_VERSION', 'Performance Lab ' . PERFLAB_VERSION );
// Do not load the code if it is already loaded through another means.
-if ( function_exists( 'image_loading_optimization_buffer_output' ) ) {
+if ( function_exists( 'ilo_buffer_output' ) ) {
return;
}
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index 6f33a8c17b..672f5cf9e8 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -10,13 +10,13 @@
exit; // Exit if accessed directly.
}
-define( 'IMAGE_LOADING_OPTIMIZATION_REST_API_NAMESPACE', 'image-loading-optimization/v1' );
-define( 'IMAGE_LOADING_OPTIMIZATION_PAGE_METRIC_STORAGE_ROUTE', '/image-loading-optimization/page-metric-storage' );
+define( 'ILO_REST_API_NAMESPACE', 'image-loading-optimization/v1' );
+define( 'ILO_PAGE_METRIC_STORAGE_ROUTE', '/image-loading-optimization/page-metric-storage' );
/**
* Register endpoint for storage of page metric.
*/
-function image_loading_optimization_register_endpoint() {
+function ilo_register_endpoint() {
$dom_rect_schema = array(
'type' => 'object',
@@ -34,14 +34,14 @@ function image_loading_optimization_register_endpoint() {
);
register_rest_route(
- IMAGE_LOADING_OPTIMIZATION_REST_API_NAMESPACE,
- IMAGE_LOADING_OPTIMIZATION_PAGE_METRIC_STORAGE_ROUTE,
+ ILO_REST_API_NAMESPACE,
+ ILO_PAGE_METRIC_STORAGE_ROUTE,
array(
'methods' => 'POST',
- 'callback' => 'image_loading_optimization_handle_rest_request',
+ 'callback' => 'ilo_handle_rest_request',
'permission_callback' => static function () {
// Needs to be available to unauthenticated visitors.
- if ( image_loading_optimization_is_page_metric_storage_locked() ) {
+ if ( ilo_is_page_metric_storage_locked() ) {
return new WP_Error(
'page_metric_storage_locked',
__( 'Page metric storage is presently locked for the current IP.', 'performance-lab' ),
@@ -127,7 +127,7 @@ function image_loading_optimization_register_endpoint() {
)
);
}
-add_action( 'rest_api_init', 'image_loading_optimization_register_endpoint' );
+add_action( 'rest_api_init', 'ilo_register_endpoint' );
/**
* Handle REST API request to store metrics.
@@ -135,13 +135,13 @@ function image_loading_optimization_register_endpoint() {
* @param WP_REST_Request $request Request.
* @return WP_REST_Response|WP_Error Response.
*/
-function image_loading_optimization_handle_rest_request( WP_REST_Request $request ) {
+function ilo_handle_rest_request( WP_REST_Request $request ) {
// TODO: We need storage.
- image_loading_optimization_set_page_metric_storage_lock();
+ ilo_set_page_metric_storage_lock();
- $result = image_loading_optimization_store_page_metric( $request->get_json_params() );
+ $result = ilo_store_page_metric( $request->get_json_params() );
if ( $result instanceof WP_Error ) {
return $result;
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index 9f430b9dd4..8e92fc2e01 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -10,14 +10,14 @@
exit; // Exit if accessed directly.
}
-define( 'IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
+define( 'ILO_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
/**
* Gets the TTL for the page metric storage lock.
*
* @return int TTL.
*/
-function image_loading_optimization_get_page_metric_storage_lock_ttl() {
+function ilo_get_page_metric_storage_lock_ttl() {
/**
* Filters how long a given IP is locked from submitting another metric-storage REST API request.
@@ -26,7 +26,7 @@ function image_loading_optimization_get_page_metric_storage_lock_ttl() {
*
* @param int $ttl TTL.
*/
- return (int) apply_filters( 'perflab_image_loading_optimization_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
+ return (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
}
/**
@@ -35,14 +35,14 @@ function image_loading_optimization_get_page_metric_storage_lock_ttl() {
* @todo This could instead return an array of thresholds, like [ 320, 480, 576 ] which would add additional buckets for small smartphones and phablets in addition to normal smartphones and desktops.
* @return int Viewport width.
*/
-function image_loading_optimization_get_max_mobile_viewport_width() {
+function ilo_get_max_mobile_viewport_width() {
/**
* Filters the maximum width for a viewport to be considered as a mobile device.
*
* @param int $mobile_max_width Mobile max width.
*/
- return (int) apply_filters( 'perflab_image_loading_optimization_max_mobile_viewport_with', 480 );
+ return (int) apply_filters( 'ilo_max_mobile_viewport_with', 480 );
}
/**
@@ -51,7 +51,7 @@ function image_loading_optimization_get_max_mobile_viewport_width() {
* @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
* @return string Transient key.
*/
-function image_loading_optimization_get_page_metric_storage_lock_transient_key() {
+function ilo_get_page_metric_storage_lock_transient_key() {
$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
}
@@ -59,9 +59,9 @@ function image_loading_optimization_get_page_metric_storage_lock_transient_key()
/**
* Sets page metric storage lock (for the current IP).
*/
-function image_loading_optimization_set_page_metric_storage_lock() {
- $ttl = image_loading_optimization_get_page_metric_storage_lock_ttl();
- $key = image_loading_optimization_get_page_metric_storage_lock_transient_key();
+function ilo_set_page_metric_storage_lock() {
+ $ttl = ilo_get_page_metric_storage_lock_ttl();
+ $key = ilo_get_page_metric_storage_lock_transient_key();
if ( 0 === $ttl ) {
delete_transient( $key );
} else {
@@ -74,12 +74,12 @@ function image_loading_optimization_set_page_metric_storage_lock() {
*
* @return bool Whether locked.
*/
-function image_loading_optimization_is_page_metric_storage_locked() {
- $ttl = image_loading_optimization_get_page_metric_storage_lock_ttl();
+function ilo_is_page_metric_storage_locked() {
+ $ttl = ilo_get_page_metric_storage_lock_ttl();
if ( 0 === $ttl ) {
return false;
}
- $locked_time = (int) get_transient( image_loading_optimization_get_page_metric_storage_lock_transient_key() );
+ $locked_time = (int) get_transient( ilo_get_page_metric_storage_lock_transient_key() );
if ( 0 === $locked_time ) {
return false;
}
@@ -91,9 +91,9 @@ function image_loading_optimization_is_page_metric_storage_locked() {
*
* This the configuration for this post type is similar to the oembed_cache in core.
*/
-function image_loading_optimization_register_page_metrics_post_type() {
+function ilo_register_page_metrics_post_type() {
register_post_type(
- IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE,
+ ILO_PAGE_METRICS_POST_TYPE,
array(
'labels' => array(
'name' => __( 'Page Metrics', 'performance-lab' ),
@@ -109,20 +109,20 @@ function image_loading_optimization_register_page_metrics_post_type() {
)
);
}
-add_action( 'init', 'image_loading_optimization_register_page_metrics_post_type' );
+add_action( 'init', 'ilo_register_page_metrics_post_type' );
/**
* Gets desired sample size for a viewport's page metrics.
*
* @return int
*/
-function image_loading_optimization_get_page_metrics_viewport_sample_size() {
+function ilo_get_page_metrics_viewport_sample_size() {
/**
* Filters desired sample size for a viewport's page metrics.
*
* @param int $sample_size Sample size.
*/
- return (int) apply_filters( 'perflab_image_loading_optimization_page_metrics_viewport_sample_size', 10 );
+ return (int) apply_filters( 'ilo_page_metrics_viewport_sample_size', 10 );
}
/**
@@ -131,7 +131,7 @@ function image_loading_optimization_get_page_metrics_viewport_sample_size() {
* @param string $url URL.
* @return string Slug for URL.
*/
-function image_loading_optimization_get_page_metrics_slug( $url ) {
+function ilo_get_page_metrics_slug( $url ) {
return md5( $url );
}
@@ -141,12 +141,12 @@ function image_loading_optimization_get_page_metrics_slug( $url ) {
* @param string $url URL.
* @return WP_Post|null Post object if exists.
*/
-function image_loading_optimization_get_page_metrics_post( $url ) {
+function ilo_get_page_metrics_post( $url ) {
$post_query = new WP_Query(
array(
- 'post_type' => IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE,
+ 'post_type' => ILO_PAGE_METRICS_POST_TYPE,
'post_status' => 'publish',
- 'name' => image_loading_optimization_get_page_metrics_slug( $url ),
+ 'name' => ilo_get_page_metrics_slug( $url ),
'posts_per_page' => 1,
'no_found_rows' => true,
'cache_results' => true,
@@ -170,7 +170,7 @@ function image_loading_optimization_get_page_metrics_post( $url ) {
* @param WP_Post $post Page metrics post.
* @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
*/
-function image_loading_optimization_parse_stored_page_metrics( WP_Post $post ) {
+function ilo_parse_stored_page_metrics( WP_Post $post ) {
$page_metrics = json_decode( $post->post_content, true );
if ( json_last_error() ) {
return new WP_Error(
@@ -178,7 +178,7 @@ function image_loading_optimization_parse_stored_page_metrics( WP_Post $post ) {
sprintf(
/* translators: 1: Post type slug, 2: JSON error message */
__( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
- IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE,
+ ILO_PAGE_METRICS_POST_TYPE,
json_last_error_msg()
)
);
@@ -189,7 +189,7 @@ function image_loading_optimization_parse_stored_page_metrics( WP_Post $post ) {
sprintf(
/* translators: %s is post type slug */
__( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
- IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE
+ ILO_PAGE_METRICS_POST_TYPE
)
);
}
@@ -202,7 +202,7 @@ function image_loading_optimization_parse_stored_page_metrics( WP_Post $post ) {
*
* @return void
*/
-function image_loading_optimization_segment_stored_page_metrics( array $page_metrics, array $breakpoints ) {
+function ilo_segment_stored_page_metrics( array $page_metrics, array $breakpoints ) {
}
@@ -224,7 +224,7 @@ function image_loading_optimization_segment_stored_page_metrics( array $page_met
*
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
-function image_loading_optimization_store_page_metric( array $validated_page_metric ) {
+function ilo_store_page_metric( array $validated_page_metric ) {
$url = $validated_page_metric['url'];
unset( $validated_page_metric['url'] ); // Not stored in post_content but rather in post_title/post_name.
$validated_page_metric['timestamp'] = time();
@@ -234,13 +234,13 @@ function image_loading_optimization_store_page_metric( array $validated_page_met
'post_title' => $url,
);
- $post = image_loading_optimization_get_page_metrics_post( $url );
+ $post = ilo_get_page_metrics_post( $url );
if ( $post instanceof WP_Post ) {
$post_data['ID'] = $post->ID;
$post_data['post_name'] = $post->post_name;
- $page_metrics = image_loading_optimization_parse_stored_page_metrics( $post );
+ $page_metrics = ilo_parse_stored_page_metrics( $post );
if ( $page_metrics instanceof WP_Error ) {
if ( function_exists( 'wp_trigger_error' ) ) {
wp_trigger_error( __FUNCTION__, esc_html( $page_metrics->get_error_message() ) );
@@ -248,15 +248,15 @@ function image_loading_optimization_store_page_metric( array $validated_page_met
$page_metrics = array();
}
} else {
- $post_data['post_name'] = image_loading_optimization_get_page_metrics_slug( $url );
+ $post_data['post_name'] = ilo_get_page_metrics_slug( $url );
$page_metrics = array();
}
// Add the provided page metric to the page metrics.
// TODO: Need to implement viewport breakpoint segmenting.
// $segmented_page_metrics =
- // $mobile_max_width = image_loading_optimization_get_max_mobile_viewport_width();
- $viewport_sample_size = image_loading_optimization_get_page_metrics_viewport_sample_size();
+ // $mobile_max_width = ilo_get_max_mobile_viewport_width();
+ $viewport_sample_size = ilo_get_page_metrics_viewport_sample_size();
// $viewport_page_metrics = array();
$page_metrics = array_slice( $page_metrics, 0, $viewport_sample_size - 1 ); // Make room for the additional page metric.
array_unshift( $page_metrics, $validated_page_metric );
@@ -269,7 +269,7 @@ function image_loading_optimization_store_page_metric( array $validated_page_met
kses_remove_filters();
}
- $post_data['post_type'] = IMAGE_LOADING_OPTIMIZATION_PAGE_METRICS_POST_TYPE;
+ $post_data['post_type'] = ILO_PAGE_METRICS_POST_TYPE;
$post_data['post_status'] = 'publish';
if ( isset( $post_data['ID'] ) ) {
$result = wp_update_post( wp_slash( $post_data ), true );
From e4d1578188f2b2b11f0ea1dc8d2d9868eaf78eae Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 6 Nov 2023 21:04:57 -0800
Subject: [PATCH 12/62] Segment page metrics by breakpoint and constrain sample
size
---
.../image-loading-optimization/storage.php | 78 ++++++++++++++-----
1 file changed, 59 insertions(+), 19 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index 8e92fc2e01..fd49729f28 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -30,19 +30,35 @@ function ilo_get_page_metric_storage_lock_ttl() {
}
/**
- * Gets the maximum width for a viewport to be considered as a mobile device.
+ * Gets the breakpoint max widths to group page metrics for various viewports.
*
- * @todo This could instead return an array of thresholds, like [ 320, 480, 576 ] which would add additional buckets for small smartphones and phablets in addition to normal smartphones and desktops.
- * @return int Viewport width.
+ * Each max with 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 viewport groupings:
+ *
+ * 1. 0-320 (small smartphone)
+ * 2. 321-480 (normal smartphone)
+ * 3. 481-576 (phablets)
+ * 4. >576 (desktop)
+ *
+ * @return int[] Breakpoint max widths, sorted in ascending order.
*/
-function ilo_get_max_mobile_viewport_width() {
+function ilo_get_breakpoint_max_widths() {
/**
- * Filters the maximum width for a viewport to be considered as a mobile device.
+ * Filters the breakpoint max widths to group page metrics for various viewports.
*
- * @param int $mobile_max_width Mobile max width.
+ * @param int[] $breakpoint_max_widths Max widths for viewport breakpoints.
*/
- return (int) apply_filters( 'ilo_max_mobile_viewport_with', 480 );
+ $breakpoint_max_widths = array_map(
+ static function ( $breakpoint_max_width ) {
+ return (int) $breakpoint_max_width;
+ },
+ (array) apply_filters( 'ilo_viewport_breakpoint_max_widths', array( 480 ) )
+ );
+
+ sort( $breakpoint_max_widths );
+ return $breakpoint_max_widths;
}
/**
@@ -116,7 +132,7 @@ function ilo_register_page_metrics_post_type() {
*
* @return int
*/
-function ilo_get_page_metrics_viewport_sample_size() {
+function ilo_get_page_metrics_breakpoint_sample_size() {
/**
* Filters desired sample size for a viewport's page metrics.
*
@@ -197,13 +213,31 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
}
/**
+ * Groups page metrics by breakpoint.
*
- * @todo This needs to take a set of page metrics and segment the individual metrics into breakpoints.
- *
- * @return void
+ * @param array $page_metrics Page metrics.
+ * @param int[] $breakpoints Viewport breakpoint max widths, sorted in ascending order.
+ * @return array Grouped page metrics.
*/
-function ilo_segment_stored_page_metrics( array $page_metrics, array $breakpoints ) {
-
+function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ) {
+ $max_index = count( $breakpoints );
+ $groups = array_fill( 0, $max_index + 1, array() );
+ $largest_breakpoint = $breakpoints[ $max_index - 1 ];
+ foreach ( $page_metrics as $page_metric ) {
+ if ( ! isset( $page_metric['viewport']['width'] ) ) {
+ continue;
+ }
+ $viewport_width = $page_metric['viewport']['width'];
+ if ( $viewport_width > $largest_breakpoint ) {
+ $groups[ $max_index ][] = $page_metric;
+ }
+ foreach ( $breakpoints as $group => $breakpoint ) {
+ if ( $viewport_width <= $breakpoint ) {
+ $groups[ $group ][] = $page_metric;
+ }
+ }
+ }
+ return $groups;
}
/**
@@ -253,14 +287,20 @@ function ilo_store_page_metric( array $validated_page_metric ) {
}
// Add the provided page metric to the page metrics.
- // TODO: Need to implement viewport breakpoint segmenting.
- // $segmented_page_metrics =
- // $mobile_max_width = ilo_get_max_mobile_viewport_width();
- $viewport_sample_size = ilo_get_page_metrics_viewport_sample_size();
- // $viewport_page_metrics = array();
- $page_metrics = array_slice( $page_metrics, 0, $viewport_sample_size - 1 ); // Make room for the additional page metric.
array_unshift( $page_metrics, $validated_page_metric );
+ $breakpoints = ilo_get_breakpoint_max_widths();
+ $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
+ $grouped_page_metrics = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoints );
+
+ foreach ( $grouped_page_metrics as &$breakpoint_page_metrics ) {
+ if ( count( $breakpoint_page_metrics ) > $sample_size ) {
+ $breakpoint_page_metrics = array_slice( $breakpoint_page_metrics, 0, $sample_size );
+ }
+ }
+
+ $page_metrics = array_merge( ...$grouped_page_metrics );
+ // TODO: Also need to capture the current theme and template which can be used to invalidate the cached page metrics.
$post_data['post_content'] = wp_json_encode( $page_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
From 2b1a15b519d5e62883c5161a7d8c72b1fb9828e6 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 7 Nov 2023 11:26:37 -0800
Subject: [PATCH 13/62] Improve function ordering
---
.../image-loading-optimization/detect.js | 5 +-
.../image-loading-optimization/rest-api.php | 3 -
.../image-loading-optimization/storage.php | 62 +++++++++----------
3 files changed, 33 insertions(+), 37 deletions(-)
diff --git a/modules/images/image-loading-optimization/detect.js b/modules/images/image-loading-optimization/detect.js
index 1bc18b8db2..5971637f13 100644
--- a/modules/images/image-loading-optimization/detect.js
+++ b/modules/images/image-loading-optimization/detect.js
@@ -289,6 +289,8 @@ export default async function detect(
pageMetrics.elements.push( elementMetrics );
}
+ log( pageMetrics );
+
// TODO: Wait until idle.
const response = await fetch( restApiEndpoint, {
method: 'POST',
@@ -300,9 +302,6 @@ export default async function detect(
} );
log( 'response:', await response.json() );
- // TODO: Send data to server.
- log( pageMetrics );
-
// Clean up.
breadcrumbedElementsMap.clear();
}
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/rest-api.php
index 672f5cf9e8..74967a0331 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/rest-api.php
@@ -136,9 +136,6 @@ function ilo_register_endpoint() {
* @return WP_REST_Response|WP_Error Response.
*/
function ilo_handle_rest_request( WP_REST_Request $request ) {
-
- // TODO: We need storage.
-
ilo_set_page_metric_storage_lock();
$result = ilo_store_page_metric( $request->get_json_params() );
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index fd49729f28..fd7ea9fd88 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -12,23 +12,6 @@
define( 'ILO_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
-/**
- * Gets the TTL for the page metric storage lock.
- *
- * @return int TTL.
- */
-function ilo_get_page_metric_storage_lock_ttl() {
-
- /**
- * Filters how long a given IP is locked from submitting another metric-storage REST API request.
- *
- * Filtering the TTL to zero will disable any metric storage locking. This is useful during development.
- *
- * @param int $ttl TTL.
- */
- return (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
-}
-
/**
* Gets the breakpoint max widths to group page metrics for various viewports.
*
@@ -61,6 +44,37 @@ static function ( $breakpoint_max_width ) {
return $breakpoint_max_widths;
}
+/**
+ * Gets desired sample size for a viewport's page metrics.
+ *
+ * @return int Sample size.
+ */
+function ilo_get_page_metrics_breakpoint_sample_size() {
+ /**
+ * Filters desired sample size for a viewport's page metrics.
+ *
+ * @param int $sample_size Sample size.
+ */
+ return (int) apply_filters( 'ilo_page_metrics_viewport_sample_size', 10 );
+}
+
+/**
+ * Gets the TTL for the page metric storage lock.
+ *
+ * @return int TTL.
+ */
+function ilo_get_page_metric_storage_lock_ttl() {
+
+ /**
+ * Filters how long a given IP is locked from submitting another metric-storage REST API request.
+ *
+ * Filtering the TTL to zero will disable any metric storage locking. This is useful during development.
+ *
+ * @param int $ttl TTL.
+ */
+ return (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
+}
+
/**
* Gets transient key for locking page metric storage (for the current IP).
*
@@ -127,20 +141,6 @@ function ilo_register_page_metrics_post_type() {
}
add_action( 'init', 'ilo_register_page_metrics_post_type' );
-/**
- * Gets desired sample size for a viewport's page metrics.
- *
- * @return int
- */
-function ilo_get_page_metrics_breakpoint_sample_size() {
- /**
- * Filters desired sample size for a viewport's page metrics.
- *
- * @param int $sample_size Sample size.
- */
- return (int) apply_filters( 'ilo_page_metrics_viewport_sample_size', 10 );
-}
-
/**
* Gets slug for page metrics post.
*
From f516af85f463f36cf009a76ab8075475599a268a Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 7 Nov 2023 20:03:46 -0800
Subject: [PATCH 14/62] Add ilo_get_page_metric_ttl
---
.../image-loading-optimization/hooks.php | 1 +
.../image-loading-optimization/storage.php | 25 ++++++++++++++++---
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 718cfe8853..59b98e61a7 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -55,6 +55,7 @@ static function ( $output ) {
*/
function ilo_print_detection_script() {
+ // TODO: Also abort if we don't need any new page metrics due to the sample size being full.
if ( ilo_is_page_metric_storage_locked() ) {
return;
}
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index fd7ea9fd88..606200553a 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -37,7 +37,7 @@ function ilo_get_breakpoint_max_widths() {
static function ( $breakpoint_max_width ) {
return (int) $breakpoint_max_width;
},
- (array) apply_filters( 'ilo_viewport_breakpoint_max_widths', array( 480 ) )
+ (array) apply_filters( 'ilo_breakpoint_max_widths', array( 480 ) )
);
sort( $breakpoint_max_widths );
@@ -45,7 +45,7 @@ static function ( $breakpoint_max_width ) {
}
/**
- * Gets desired sample size for a viewport's page metrics.
+ * Gets desired sample size for a breakpoint's page metrics.
*
* @return int Sample size.
*/
@@ -55,7 +55,25 @@ function ilo_get_page_metrics_breakpoint_sample_size() {
*
* @param int $sample_size Sample size.
*/
- return (int) apply_filters( 'ilo_page_metrics_viewport_sample_size', 10 );
+ return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 10 );
+}
+
+/**
+ * Gets the expiration age for a given page metric.
+ *
+ * When a page metric expires it is eligible to be replaced by a newer one.
+ *
+ * TODO: However, we keep viewport-specific page metrics regardless of TTL.
+ *
+ * @return int Expiration age in seconds.
+ */
+function ilo_get_page_metric_ttl() {
+ /**
+ * Filters the expiration age for a given page metric.
+ *
+ * @param int $ttl TTL.
+ */
+ return (int) apply_filters( 'ilo_page_metric_ttl', MONTH_IN_SECONDS );
}
/**
@@ -300,7 +318,6 @@ function ilo_store_page_metric( array $validated_page_metric ) {
$page_metrics = array_merge( ...$grouped_page_metrics );
- // TODO: Also need to capture the current theme and template which can be used to invalidate the cached page metrics.
$post_data['post_content'] = wp_json_encode( $page_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
From 2f804613a2dbdfd6a23746bf4d770b5d675de24e Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 7 Nov 2023 20:08:12 -0800
Subject: [PATCH 15/62] Move lock functions into separate file
---
.../image-loading-optimization/storage.php | 60 +---------------
.../storage/lock.php | 69 +++++++++++++++++++
2 files changed, 71 insertions(+), 58 deletions(-)
create mode 100644 modules/images/image-loading-optimization/storage/lock.php
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index 606200553a..b5d1d37be8 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -10,6 +10,8 @@
exit; // Exit if accessed directly.
}
+require_once __DIR__ . '/storage/lock.php';
+
define( 'ILO_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
/**
@@ -76,64 +78,6 @@ function ilo_get_page_metric_ttl() {
return (int) apply_filters( 'ilo_page_metric_ttl', MONTH_IN_SECONDS );
}
-/**
- * Gets the TTL for the page metric storage lock.
- *
- * @return int TTL.
- */
-function ilo_get_page_metric_storage_lock_ttl() {
-
- /**
- * Filters how long a given IP is locked from submitting another metric-storage REST API request.
- *
- * Filtering the TTL to zero will disable any metric storage locking. This is useful during development.
- *
- * @param int $ttl TTL.
- */
- return (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
-}
-
-/**
- * Gets transient key for locking page metric storage (for the current IP).
- *
- * @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
- * @return string Transient key.
- */
-function ilo_get_page_metric_storage_lock_transient_key() {
- $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
- return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
-}
-
-/**
- * Sets page metric storage lock (for the current IP).
- */
-function ilo_set_page_metric_storage_lock() {
- $ttl = ilo_get_page_metric_storage_lock_ttl();
- $key = ilo_get_page_metric_storage_lock_transient_key();
- if ( 0 === $ttl ) {
- delete_transient( $key );
- } else {
- set_transient( $key, time(), $ttl );
- }
-}
-
-/**
- * Checks whether page metric storage is locked (for the current IP).
- *
- * @return bool Whether locked.
- */
-function ilo_is_page_metric_storage_locked() {
- $ttl = ilo_get_page_metric_storage_lock_ttl();
- if ( 0 === $ttl ) {
- return false;
- }
- $locked_time = (int) get_transient( ilo_get_page_metric_storage_lock_transient_key() );
- if ( 0 === $locked_time ) {
- return false;
- }
- return time() - $locked_time < $ttl;
-}
-
/**
* Register post type for page metrics storage.
*
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
new file mode 100644
index 0000000000..efbb89424c
--- /dev/null
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -0,0 +1,69 @@
+
Date: Tue, 7 Nov 2023 20:22:11 -0800
Subject: [PATCH 16/62] Reorganize functions into files
---
.../image-loading-optimization/load.php | 1 -
.../image-loading-optimization/storage.php | 276 +-----------------
.../storage/data.php | 125 ++++++++
.../storage/lock.php | 2 +-
.../storage/post-type.php | 180 ++++++++++++
.../{ => storage}/rest-api.php | 3 +-
6 files changed, 311 insertions(+), 276 deletions(-)
create mode 100644 modules/images/image-loading-optimization/storage/data.php
create mode 100644 modules/images/image-loading-optimization/storage/post-type.php
rename modules/images/image-loading-optimization/{ => storage}/rest-api.php (97%)
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 2aaace8afa..68f6287195 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -23,4 +23,3 @@
require_once __DIR__ . '/helper.php';
require_once __DIR__ . '/hooks.php';
require_once __DIR__ . '/storage.php';
-require_once __DIR__ . '/rest-api.php';
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index b5d1d37be8..5ac9fb9220 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -11,276 +11,6 @@
}
require_once __DIR__ . '/storage/lock.php';
-
-define( 'ILO_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
-
-/**
- * Gets the breakpoint max widths to group page metrics for various viewports.
- *
- * Each max with 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 viewport groupings:
- *
- * 1. 0-320 (small smartphone)
- * 2. 321-480 (normal smartphone)
- * 3. 481-576 (phablets)
- * 4. >576 (desktop)
- *
- * @return int[] Breakpoint max widths, sorted in ascending order.
- */
-function ilo_get_breakpoint_max_widths() {
-
- /**
- * Filters the breakpoint max widths to group page metrics for various viewports.
- *
- * @param int[] $breakpoint_max_widths Max widths for viewport breakpoints.
- */
- $breakpoint_max_widths = array_map(
- static function ( $breakpoint_max_width ) {
- return (int) $breakpoint_max_width;
- },
- (array) apply_filters( 'ilo_breakpoint_max_widths', array( 480 ) )
- );
-
- sort( $breakpoint_max_widths );
- return $breakpoint_max_widths;
-}
-
-/**
- * Gets desired sample size for a breakpoint's page metrics.
- *
- * @return int Sample size.
- */
-function ilo_get_page_metrics_breakpoint_sample_size() {
- /**
- * Filters desired sample size for a viewport's page metrics.
- *
- * @param int $sample_size Sample size.
- */
- return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 10 );
-}
-
-/**
- * Gets the expiration age for a given page metric.
- *
- * When a page metric expires it is eligible to be replaced by a newer one.
- *
- * TODO: However, we keep viewport-specific page metrics regardless of TTL.
- *
- * @return int Expiration age in seconds.
- */
-function ilo_get_page_metric_ttl() {
- /**
- * Filters the expiration age for a given page metric.
- *
- * @param int $ttl TTL.
- */
- return (int) apply_filters( 'ilo_page_metric_ttl', MONTH_IN_SECONDS );
-}
-
-/**
- * Register post type for page metrics storage.
- *
- * This the configuration for this post type is similar to the oembed_cache in core.
- */
-function ilo_register_page_metrics_post_type() {
- register_post_type(
- ILO_PAGE_METRICS_POST_TYPE,
- array(
- 'labels' => array(
- 'name' => __( 'Page Metrics', 'performance-lab' ),
- 'singular_name' => __( 'Page Metrics', 'performance-lab' ),
- ),
- 'public' => false,
- 'hierarchical' => false,
- 'rewrite' => false,
- 'query_var' => false,
- 'delete_with_user' => false,
- 'can_export' => false,
- 'supports' => array( 'title' ), // The original URL is stored in the post_title, and the MD5 hash in the post_name.
- )
- );
-}
-add_action( 'init', 'ilo_register_page_metrics_post_type' );
-
-/**
- * Gets slug for page metrics post.
- *
- * @param string $url URL.
- * @return string Slug for URL.
- */
-function ilo_get_page_metrics_slug( $url ) {
- return md5( $url );
-}
-
-/**
- * Get page metrics post.
- *
- * @param string $url URL.
- * @return WP_Post|null Post object if exists.
- */
-function ilo_get_page_metrics_post( $url ) {
- $post_query = new WP_Query(
- array(
- 'post_type' => ILO_PAGE_METRICS_POST_TYPE,
- 'post_status' => 'publish',
- 'name' => ilo_get_page_metrics_slug( $url ),
- 'posts_per_page' => 1,
- 'no_found_rows' => true,
- 'cache_results' => true,
- 'update_post_meta_cache' => false,
- 'update_post_term_cache' => false,
- 'lazy_load_term_meta' => false,
- )
- );
-
- $post = array_shift( $post_query->posts );
- if ( $post instanceof WP_Post ) {
- return $post;
- } else {
- return null;
- }
-}
-
-/**
- * Parses post content in page metrics post.
- *
- * @param WP_Post $post Page metrics post.
- * @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
- */
-function ilo_parse_stored_page_metrics( WP_Post $post ) {
- $page_metrics = json_decode( $post->post_content, true );
- if ( json_last_error() ) {
- return new WP_Error(
- 'page_metrics_json_parse_error',
- sprintf(
- /* translators: 1: Post type slug, 2: JSON error message */
- __( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
- ILO_PAGE_METRICS_POST_TYPE,
- json_last_error_msg()
- )
- );
- }
- if ( ! is_array( $page_metrics ) ) {
- return new WP_Error(
- 'page_metrics_invalid_data_format',
- sprintf(
- /* translators: %s is post type slug */
- __( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
- ILO_PAGE_METRICS_POST_TYPE
- )
- );
- }
- return $page_metrics;
-}
-
-/**
- * Groups page metrics by breakpoint.
- *
- * @param array $page_metrics Page metrics.
- * @param int[] $breakpoints Viewport breakpoint max widths, sorted in ascending order.
- * @return array Grouped page metrics.
- */
-function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ) {
- $max_index = count( $breakpoints );
- $groups = array_fill( 0, $max_index + 1, array() );
- $largest_breakpoint = $breakpoints[ $max_index - 1 ];
- foreach ( $page_metrics as $page_metric ) {
- if ( ! isset( $page_metric['viewport']['width'] ) ) {
- continue;
- }
- $viewport_width = $page_metric['viewport']['width'];
- if ( $viewport_width > $largest_breakpoint ) {
- $groups[ $max_index ][] = $page_metric;
- }
- foreach ( $breakpoints as $group => $breakpoint ) {
- if ( $viewport_width <= $breakpoint ) {
- $groups[ $group ][] = $page_metric;
- }
- }
- }
- return $groups;
-}
-
-/**
- * Stores page metric by merging it with the other page metrics for a given URL.
- *
- * The $validated_page_metric parameter has the following array shape:
- *
- * {
- * 'url': string,
- * 'viewport': array{
- * 'width': int,
- * 'height': int
- * },
- * 'elements': array
- * }
- *
- * @param array $validated_page_metric Page metric, already validated by REST API.
- *
- * @return int|WP_Error Post ID or WP_Error otherwise.
- */
-function ilo_store_page_metric( array $validated_page_metric ) {
- $url = $validated_page_metric['url'];
- unset( $validated_page_metric['url'] ); // Not stored in post_content but rather in post_title/post_name.
- $validated_page_metric['timestamp'] = time();
-
- // TODO: What about storing a version identifier?
- $post_data = array(
- 'post_title' => $url,
- );
-
- $post = ilo_get_page_metrics_post( $url );
-
- if ( $post instanceof WP_Post ) {
- $post_data['ID'] = $post->ID;
- $post_data['post_name'] = $post->post_name;
-
- $page_metrics = ilo_parse_stored_page_metrics( $post );
- if ( $page_metrics instanceof WP_Error ) {
- if ( function_exists( 'wp_trigger_error' ) ) {
- wp_trigger_error( __FUNCTION__, esc_html( $page_metrics->get_error_message() ) );
- }
- $page_metrics = array();
- }
- } else {
- $post_data['post_name'] = ilo_get_page_metrics_slug( $url );
- $page_metrics = array();
- }
-
- // Add the provided page metric to the page metrics.
- array_unshift( $page_metrics, $validated_page_metric );
- $breakpoints = ilo_get_breakpoint_max_widths();
- $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
- $grouped_page_metrics = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoints );
-
- foreach ( $grouped_page_metrics as &$breakpoint_page_metrics ) {
- if ( count( $breakpoint_page_metrics ) > $sample_size ) {
- $breakpoint_page_metrics = array_slice( $breakpoint_page_metrics, 0, $sample_size );
- }
- }
-
- $page_metrics = array_merge( ...$grouped_page_metrics );
-
- $post_data['post_content'] = wp_json_encode( $page_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
-
- $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
- if ( $has_kses ) {
- // Prevent KSES from corrupting JSON in post_content.
- kses_remove_filters();
- }
-
- $post_data['post_type'] = ILO_PAGE_METRICS_POST_TYPE;
- $post_data['post_status'] = 'publish';
- if ( isset( $post_data['ID'] ) ) {
- $result = wp_update_post( wp_slash( $post_data ), true );
- } else {
- $result = wp_insert_post( wp_slash( $post_data ), true );
- }
-
- if ( $has_kses ) {
- kses_init_filters();
- }
-
- return $result;
-}
+require_once __DIR__ . '/storage/post-type.php';
+require_once __DIR__ . '/storage/data.php';
+require_once __DIR__ . '/storage/rest-api.php';
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
new file mode 100644
index 0000000000..f042422c4b
--- /dev/null
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -0,0 +1,125 @@
+ $sample_size ) {
+ $breakpoint_page_metrics = array_slice( $breakpoint_page_metrics, 0, $sample_size );
+ }
+ }
+
+ return array_merge( ...$grouped_page_metrics );
+}
+
+/**
+ * Gets the breakpoint max widths to group page metrics for various viewports.
+ *
+ * Each max with 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 viewport groupings:
+ *
+ * 1. 0-320 (small smartphone)
+ * 2. 321-480 (normal smartphone)
+ * 3. 481-576 (phablets)
+ * 4. >576 (desktop)
+ *
+ * @return int[] Breakpoint max widths, sorted in ascending order.
+ */
+function ilo_get_breakpoint_max_widths() {
+
+ /**
+ * Filters the breakpoint max widths to group page metrics for various viewports.
+ *
+ * @param int[] $breakpoint_max_widths Max widths for viewport breakpoints.
+ */
+ $breakpoint_max_widths = array_map(
+ static function ( $breakpoint_max_width ) {
+ return (int) $breakpoint_max_width;
+ },
+ (array) apply_filters( 'ilo_breakpoint_max_widths', array( 480 ) )
+ );
+
+ sort( $breakpoint_max_widths );
+ return $breakpoint_max_widths;
+}
+
+/**
+ * Gets desired sample size for a breakpoint's page metrics.
+ *
+ * @return int Sample size.
+ */
+function ilo_get_page_metrics_breakpoint_sample_size() {
+ /**
+ * Filters desired sample size for a viewport's page metrics.
+ *
+ * @param int $sample_size Sample size.
+ */
+ return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 10 );
+}
+
+/**
+ * Groups page metrics by breakpoint.
+ *
+ * @param array $page_metrics Page metrics.
+ * @param int[] $breakpoints Viewport breakpoint max widths, sorted in ascending order.
+ * @return array Grouped page metrics.
+ */
+function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ) {
+ $max_index = count( $breakpoints );
+ $groups = array_fill( 0, $max_index + 1, array() );
+ $largest_breakpoint = $breakpoints[ $max_index - 1 ];
+ foreach ( $page_metrics as $page_metric ) {
+ if ( ! isset( $page_metric['viewport']['width'] ) ) {
+ continue;
+ }
+ $viewport_width = $page_metric['viewport']['width'];
+ if ( $viewport_width > $largest_breakpoint ) {
+ $groups[ $max_index ][] = $page_metric;
+ }
+ foreach ( $breakpoints as $group => $breakpoint ) {
+ if ( $viewport_width <= $breakpoint ) {
+ $groups[ $group ][] = $page_metric;
+ }
+ }
+ }
+ return $groups;
+}
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index efbb89424c..1ae536397b 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -1,6 +1,6 @@
array(
+ 'name' => __( 'Page Metrics', 'performance-lab' ),
+ 'singular_name' => __( 'Page Metrics', 'performance-lab' ),
+ ),
+ 'public' => false,
+ 'hierarchical' => false,
+ 'rewrite' => false,
+ 'query_var' => false,
+ 'delete_with_user' => false,
+ 'can_export' => false,
+ 'supports' => array( 'title' ), // The original URL is stored in the post_title, and the MD5 hash in the post_name.
+ )
+ );
+}
+add_action( 'init', 'ilo_register_page_metrics_post_type' );
+
+/**
+ * Gets slug for page metrics post.
+ *
+ * @param string $url URL.
+ * @return string Slug for URL.
+ */
+function ilo_get_page_metrics_slug( $url ) {
+ return md5( $url );
+}
+
+/**
+ * Get page metrics post.
+ *
+ * @param string $url URL.
+ * @return WP_Post|null Post object if exists.
+ */
+function ilo_get_page_metrics_post( $url ) {
+ $post_query = new WP_Query(
+ array(
+ 'post_type' => ILO_PAGE_METRICS_POST_TYPE,
+ 'post_status' => 'publish',
+ 'name' => ilo_get_page_metrics_slug( $url ),
+ 'posts_per_page' => 1,
+ 'no_found_rows' => true,
+ 'cache_results' => true,
+ 'update_post_meta_cache' => false,
+ 'update_post_term_cache' => false,
+ 'lazy_load_term_meta' => false,
+ )
+ );
+
+ $post = array_shift( $post_query->posts );
+ if ( $post instanceof WP_Post ) {
+ return $post;
+ } else {
+ return null;
+ }
+}
+
+/**
+ * Parses post content in page metrics post.
+ *
+ * @param WP_Post $post Page metrics post.
+ * @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
+ */
+function ilo_parse_stored_page_metrics( WP_Post $post ) {
+ $page_metrics = json_decode( $post->post_content, true );
+ if ( json_last_error() ) {
+ return new WP_Error(
+ 'page_metrics_json_parse_error',
+ sprintf(
+ /* translators: 1: Post type slug, 2: JSON error message */
+ __( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
+ ILO_PAGE_METRICS_POST_TYPE,
+ json_last_error_msg()
+ )
+ );
+ }
+ if ( ! is_array( $page_metrics ) ) {
+ return new WP_Error(
+ 'page_metrics_invalid_data_format',
+ sprintf(
+ /* translators: %s is post type slug */
+ __( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
+ ILO_PAGE_METRICS_POST_TYPE
+ )
+ );
+ }
+ return $page_metrics;
+}
+
+/**
+ * Stores page metric by merging it with the other page metrics for a given URL.
+ *
+ * The $validated_page_metric parameter has the following array shape:
+ *
+ * {
+ * 'url': string,
+ * 'viewport': array{
+ * 'width': int,
+ * 'height': int
+ * },
+ * 'elements': array
+ * }
+ *
+ * @param array $validated_page_metric Page metric, already validated by REST API.
+ *
+ * @return int|WP_Error Post ID or WP_Error otherwise.
+ */
+function ilo_store_page_metric( array $validated_page_metric ) {
+ $url = $validated_page_metric['url'];
+ unset( $validated_page_metric['url'] ); // Not stored in post_content but rather in post_title/post_name.
+ $validated_page_metric['timestamp'] = time();
+
+ // TODO: What about storing a version identifier?
+ $post_data = array(
+ 'post_title' => $url,
+ );
+
+ $post = ilo_get_page_metrics_post( $url );
+
+ if ( $post instanceof WP_Post ) {
+ $post_data['ID'] = $post->ID;
+ $post_data['post_name'] = $post->post_name;
+
+ $page_metrics = ilo_parse_stored_page_metrics( $post );
+ if ( $page_metrics instanceof WP_Error ) {
+ if ( function_exists( 'wp_trigger_error' ) ) {
+ wp_trigger_error( __FUNCTION__, esc_html( $page_metrics->get_error_message() ) );
+ }
+ $page_metrics = array();
+ }
+ } else {
+ $post_data['post_name'] = ilo_get_page_metrics_slug( $url );
+ $page_metrics = array();
+ }
+
+ $page_metrics = ilo_unshift_page_metrics( $page_metrics, $validated_page_metric );
+
+ $post_data['post_content'] = wp_json_encode( $page_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
+
+ $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
+ if ( $has_kses ) {
+ // Prevent KSES from corrupting JSON in post_content.
+ kses_remove_filters();
+ }
+
+ $post_data['post_type'] = ILO_PAGE_METRICS_POST_TYPE;
+ $post_data['post_status'] = 'publish';
+ if ( isset( $post_data['ID'] ) ) {
+ $result = wp_update_post( wp_slash( $post_data ), true );
+ } else {
+ $result = wp_insert_post( wp_slash( $post_data ), true );
+ }
+
+ if ( $has_kses ) {
+ kses_init_filters();
+ }
+
+ return $result;
+}
diff --git a/modules/images/image-loading-optimization/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
similarity index 97%
rename from modules/images/image-loading-optimization/rest-api.php
rename to modules/images/image-loading-optimization/storage/rest-api.php
index 74967a0331..42c7d6af74 100644
--- a/modules/images/image-loading-optimization/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -138,7 +138,8 @@ function ilo_register_endpoint() {
function ilo_handle_rest_request( WP_REST_Request $request ) {
ilo_set_page_metric_storage_lock();
- $result = ilo_store_page_metric( $request->get_json_params() );
+ $page_metric = $request->get_json_params();
+ $result = ilo_store_page_metric( $page_metric );
if ( $result instanceof WP_Error ) {
return $result;
From d2455e9c4dfe843e4ed8d4a8f3c80bd52f1a49f0 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 7 Nov 2023 20:26:05 -0800
Subject: [PATCH 17/62] Use PHP 7 const
---
.../images/image-loading-optimization/storage/post-type.php | 6 +++---
.../images/image-loading-optimization/storage/rest-api.php | 5 +++--
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index c0f6788d87..de9f10d5be 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -10,7 +10,7 @@
exit; // Exit if accessed directly.
}
-define( 'ILO_PAGE_METRICS_POST_TYPE', 'ilo_page_metrics' );
+const ILO_PAGE_METRICS_POST_TYPE = 'ilo_page_metrics';
/**
* Register post type for page metrics storage.
@@ -88,7 +88,7 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
return new WP_Error(
'page_metrics_json_parse_error',
sprintf(
- /* translators: 1: Post type slug, 2: JSON error message */
+ /* translators: 1: Post type slug, 2: JSON error message */
__( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
ILO_PAGE_METRICS_POST_TYPE,
json_last_error_msg()
@@ -99,7 +99,7 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
return new WP_Error(
'page_metrics_invalid_data_format',
sprintf(
- /* translators: %s is post type slug */
+ /* translators: %s is post type slug */
__( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
ILO_PAGE_METRICS_POST_TYPE
)
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 42c7d6af74..87c6e77da4 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -10,8 +10,9 @@
exit; // Exit if accessed directly.
}
-define( 'ILO_REST_API_NAMESPACE', 'image-loading-optimization/v1' );
-define( 'ILO_PAGE_METRIC_STORAGE_ROUTE', '/image-loading-optimization/page-metric-storage' );
+const ILO_REST_API_NAMESPACE = 'image-loading-optimization/v1';
+
+const ILO_PAGE_METRIC_STORAGE_ROUTE = '/image-loading-optimization/page-metric-storage';
/**
* Register endpoint for storage of page metric.
From 79bafac29a0882a1f1f577aab133ee4846a5f0c1 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 7 Nov 2023 20:32:56 -0800
Subject: [PATCH 18/62] Improve static analysis
---
.../images/image-loading-optimization/storage/rest-api.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 87c6e77da4..facd678dd8 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -39,7 +39,9 @@ function ilo_register_endpoint() {
ILO_PAGE_METRIC_STORAGE_ROUTE,
array(
'methods' => 'POST',
- 'callback' => 'ilo_handle_rest_request',
+ 'callback' => static function ( WP_REST_Request $request ) {
+ return ilo_handle_rest_request( $request );
+ },
'permission_callback' => static function () {
// Needs to be available to unauthenticated visitors.
if ( ilo_is_page_metric_storage_locked() ) {
From c01649442959849de3796239d4092a864bcfdac8 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 8 Nov 2023 11:32:55 -0800
Subject: [PATCH 19/62] Split out detection logic into separate include
---
.../image-loading-optimization/detection.php | 59 +++++++++++++++++++
.../{ => detection}/detect.js | 0
.../image-loading-optimization/hooks.php | 48 ---------------
.../image-loading-optimization/load.php | 1 +
.../image-loading-optimization/storage.php | 2 +-
5 files changed, 61 insertions(+), 49 deletions(-)
create mode 100644 modules/images/image-loading-optimization/detection.php
rename modules/images/image-loading-optimization/{ => detection}/detect.js (100%)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
new file mode 100644
index 0000000000..1cc0567559
--- /dev/null
+++ b/modules/images/image-loading-optimization/detection.php
@@ -0,0 +1,59 @@
+ 'module' )
+ );
+}
+add_action( 'wp_print_footer_scripts', 'ilo_print_detection_script' );
diff --git a/modules/images/image-loading-optimization/detect.js b/modules/images/image-loading-optimization/detection/detect.js
similarity index 100%
rename from modules/images/image-loading-optimization/detect.js
rename to modules/images/image-loading-optimization/detection/detect.js
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 59b98e61a7..1f002dab9d 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -46,51 +46,3 @@ static function ( $output ) {
return $passthrough;
}
add_filter( 'template_include', 'ilo_buffer_output', PHP_INT_MAX );
-
-/**
- * Prints the script for detecting loaded images and the LCP element.
- *
- * @todo This should eventually only print the script if metrics are needed.
- * @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
- */
-function ilo_print_detection_script() {
-
- // TODO: Also abort if we don't need any new page metrics due to the sample size being full.
- if ( ilo_is_page_metric_storage_locked() ) {
- return;
- }
-
- $serve_time = ceil( microtime( true ) * 1000 );
-
- /**
- * Filters the time window between serve time and run time in which loading detection is allowed to run.
- *
- * Allow this amount of milliseconds between when the page was first generated (and perhaps cached) and when the
- * detect function on the page is allowed to perform its detection logic and submit the request to store the results.
- * This avoids situations in which there is missing detection metrics in which case a site with page caching which
- * also has a lot of traffic could result in a cache stampede.
- *
- * @since n.e.x.t
- * @todo The value should probably be something like the 99th percentile of Time To Last Byte (TTLB) for WordPress sites in CrUX.
- *
- * @param int $detection_time_window Detection time window in milliseconds.
- */
- $detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
-
- $detect_args = array(
- $serve_time,
- $detection_time_window,
- WP_DEBUG,
- rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRIC_STORAGE_ROUTE ),
- wp_create_nonce( 'wp_rest' ),
- );
- wp_print_inline_script_tag(
- sprintf(
- 'import detect from %s; detect( ...%s )',
- wp_json_encode( add_query_arg( 'ver', PERFLAB_VERSION, plugin_dir_url( __FILE__ ) . 'detect.js' ) ),
- wp_json_encode( $detect_args )
- ),
- array( 'type' => 'module' )
- );
-}
-add_action( 'wp_print_footer_scripts', 'ilo_print_detection_script' );
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 68f6287195..118b22d91b 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -23,3 +23,4 @@
require_once __DIR__ . '/helper.php';
require_once __DIR__ . '/hooks.php';
require_once __DIR__ . '/storage.php';
+require_once __DIR__ . '/detection.php';
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
index 5ac9fb9220..52bbf3b344 100644
--- a/modules/images/image-loading-optimization/storage.php
+++ b/modules/images/image-loading-optimization/storage.php
@@ -1,6 +1,6 @@
Date: Wed, 8 Nov 2023 11:33:41 -0800
Subject: [PATCH 20/62] Remove unused helper.php
---
modules/images/image-loading-optimization/helper.php | 11 -----------
modules/images/image-loading-optimization/load.php | 1 -
2 files changed, 12 deletions(-)
delete mode 100644 modules/images/image-loading-optimization/helper.php
diff --git a/modules/images/image-loading-optimization/helper.php b/modules/images/image-loading-optimization/helper.php
deleted file mode 100644
index 77e6b12b10..0000000000
--- a/modules/images/image-loading-optimization/helper.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
Date: Wed, 8 Nov 2023 11:44:05 -0800
Subject: [PATCH 21/62] Improve logging
---
.../detection/detect.js | 49 ++++++++++++++-----
.../storage/rest-api.php | 5 +-
2 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 5971637f13..0fa5d7f534 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -25,6 +25,16 @@ function warn( ...message ) {
console.warn( consoleLogPrefix, ...message );
}
+/**
+ * Log an error.
+ *
+ * @param {...*} message
+ */
+function error( ...message ) {
+ // eslint-disable-next-line no-console
+ console.error( consoleLogPrefix, ...message );
+}
+
/**
* @typedef {Object} Breadcrumb
* @property {number} index - Index of element among sibling elements.
@@ -264,7 +274,7 @@ export default async function detect(
);
if ( ! breadcrumbs ) {
if ( isDebug ) {
- warn( 'Unable to look up breadcrumbs for element' );
+ error( 'Unable to look up breadcrumbs for element' );
}
continue;
}
@@ -289,18 +299,33 @@ export default async function detect(
pageMetrics.elements.push( elementMetrics );
}
- log( pageMetrics );
+ if ( isDebug ) {
+ log( 'Page metrics:', pageMetrics );
+ }
- // TODO: Wait until idle.
- const response = await fetch( restApiEndpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-WP-Nonce': restApiNonce,
- },
- body: JSON.stringify( pageMetrics ),
- } );
- log( 'response:', await response.json() );
+ // TODO: Wait until idle? Yield to main?
+ try {
+ const response = await fetch( restApiEndpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': restApiNonce,
+ },
+ body: JSON.stringify( pageMetrics ),
+ } );
+ if ( isDebug ) {
+ const body = await response.json();
+ if ( response.status === 200 ) {
+ log( 'Response:', body );
+ } else {
+ error( 'Failure:', body );
+ }
+ }
+ } catch ( err ) {
+ if ( isDebug ) {
+ error( err );
+ }
+ }
// Clean up.
breadcrumbedElementsMap.clear();
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index facd678dd8..000c00e688 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -148,12 +148,11 @@ function ilo_handle_rest_request( WP_REST_Request $request ) {
return $result;
}
- $response = new WP_REST_Response(
+ return new WP_REST_Response(
array(
'success' => true,
'post_id' => $result,
+ 'data' => ilo_parse_stored_page_metrics( ilo_get_page_metrics_post( $page_metric['url'] ) ), // TODO: Remove this debug data.
)
);
- $response->set_status( 201 );
- return $response;
}
From a65d0be988ad313f3c851c5bb66e662a1a63d126 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 8 Nov 2023 21:07:53 -0800
Subject: [PATCH 22/62] Restore version constant
---
modules/images/image-loading-optimization/load.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 283d45b2ad..90aac38d17 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -9,11 +9,11 @@
*/
// Define the constant.
-if ( defined( 'ILO_VERSION' ) ) {
+if ( defined( 'IMAGE_LOADING_OPTIMIZATION_VERSION' ) ) {
return;
}
-define( 'ILO_VERSION', 'Performance Lab ' . PERFLAB_VERSION );
+define( 'IMAGE_LOADING_OPTIMIZATION_VERSION', 'Performance Lab ' . PERFLAB_VERSION );
// Do not load the code if it is already loaded through another means.
if ( function_exists( 'ilo_buffer_output' ) ) {
From a17c9a267fe4a31d071f0e59b957ff2f51d1199d Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 8 Nov 2023 21:08:23 -0800
Subject: [PATCH 23/62] Remove redundant plugin short-circuit
---
modules/images/image-loading-optimization/load.php | 5 -----
1 file changed, 5 deletions(-)
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 90aac38d17..6004979d30 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -15,11 +15,6 @@
define( 'IMAGE_LOADING_OPTIMIZATION_VERSION', 'Performance Lab ' . PERFLAB_VERSION );
-// Do not load the code if it is already loaded through another means.
-if ( function_exists( 'ilo_buffer_output' ) ) {
- return;
-}
-
require_once __DIR__ . '/hooks.php';
require_once __DIR__ . '/storage.php';
require_once __DIR__ . '/detection.php';
From 7f6cc2be72512be94acb2781152fde530e656ef7 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 8 Nov 2023 21:13:43 -0800
Subject: [PATCH 24/62] Remove superflous include file
---
.../images/image-loading-optimization/load.php | 8 +++++++-
.../image-loading-optimization/storage.php | 16 ----------------
2 files changed, 7 insertions(+), 17 deletions(-)
delete mode 100644 modules/images/image-loading-optimization/storage.php
diff --git a/modules/images/image-loading-optimization/load.php b/modules/images/image-loading-optimization/load.php
index 6004979d30..1086e0ed9d 100644
--- a/modules/images/image-loading-optimization/load.php
+++ b/modules/images/image-loading-optimization/load.php
@@ -16,5 +16,11 @@
define( 'IMAGE_LOADING_OPTIMIZATION_VERSION', 'Performance Lab ' . PERFLAB_VERSION );
require_once __DIR__ . '/hooks.php';
-require_once __DIR__ . '/storage.php';
+
+// Storage logic.
+require_once __DIR__ . '/storage/lock.php';
+require_once __DIR__ . '/storage/post-type.php';
+require_once __DIR__ . '/storage/data.php';
+require_once __DIR__ . '/storage/rest-api.php';
+
require_once __DIR__ . '/detection.php';
diff --git a/modules/images/image-loading-optimization/storage.php b/modules/images/image-loading-optimization/storage.php
deleted file mode 100644
index 52bbf3b344..0000000000
--- a/modules/images/image-loading-optimization/storage.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
Date: Wed, 8 Nov 2023 21:16:14 -0800
Subject: [PATCH 25/62] Improve page metrics route
---
modules/images/image-loading-optimization/detection.php | 2 +-
.../images/image-loading-optimization/storage/rest-api.php | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 1cc0567559..76dd045198 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -44,7 +44,7 @@ function ilo_print_detection_script() {
$serve_time,
$detection_time_window,
WP_DEBUG,
- rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRIC_STORAGE_ROUTE ),
+ rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
wp_create_nonce( 'wp_rest' ),
);
wp_print_inline_script_tag(
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 000c00e688..4844392d3a 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -12,7 +12,7 @@
const ILO_REST_API_NAMESPACE = 'image-loading-optimization/v1';
-const ILO_PAGE_METRIC_STORAGE_ROUTE = '/image-loading-optimization/page-metric-storage';
+const ILO_PAGE_METRICS_ROUTE = '/page-metrics';
/**
* Register endpoint for storage of page metric.
@@ -36,7 +36,7 @@ function ilo_register_endpoint() {
register_rest_route(
ILO_REST_API_NAMESPACE,
- ILO_PAGE_METRIC_STORAGE_ROUTE,
+ ILO_PAGE_METRICS_ROUTE,
array(
'methods' => 'POST',
'callback' => static function ( WP_REST_Request $request ) {
From fb8837b11999f96d9a07c07661767452ad885982 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 8 Nov 2023 21:29:27 -0800
Subject: [PATCH 26/62] Update composer lockfile
---
composer.lock | 73 +++++++++++++++++++++++++++------------------------
1 file changed, 39 insertions(+), 34 deletions(-)
diff --git a/composer.lock b/composer.lock
index 59e7347f06..6a9ae7127c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8afb8511538e46c6875a017b72ad8711",
+ "content-hash": "2dcd132f2c017c64da30a4a4b6f78f29",
"packages": [
{
"name": "composer/installers",
@@ -236,30 +236,30 @@
},
{
"name": "doctrine/instantiator",
- "version": "2.0.0",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
- "php": "^8.1"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^11",
+ "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "^1.9.4",
- "phpstan/phpstan-phpunit": "^1.3",
- "phpunit/phpunit": "^9.5.27",
- "vimeo/psalm": "^5.4"
+ "phpbench/phpbench": "^0.16 || ^1",
+ "phpstan/phpstan": "^1.4",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.30 || ^5.4"
},
"type": "library",
"autoload": {
@@ -286,7 +286,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/2.0.0"
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@@ -302,7 +302,7 @@
"type": "tidelift"
}
],
- "time": "2022-12-30T00:23:10+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -532,16 +532,16 @@
},
{
"name": "php-stubs/wordpress-stubs",
- "version": "v6.3.0",
+ "version": "v6.4.0",
"source": {
"type": "git",
"url": "https://github.com/php-stubs/wordpress-stubs.git",
- "reference": "adda7609e71d5f4dc7b87c74f8ec9e3437d2e92c"
+ "reference": "286d42eeb44c6808633cc59b8dbb9aa75fe41264"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/adda7609e71d5f4dc7b87c74f8ec9e3437d2e92c",
- "reference": "adda7609e71d5f4dc7b87c74f8ec9e3437d2e92c",
+ "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/286d42eeb44c6808633cc59b8dbb9aa75fe41264",
+ "reference": "286d42eeb44c6808633cc59b8dbb9aa75fe41264",
"shasum": ""
},
"require-dev": {
@@ -554,6 +554,7 @@
},
"suggest": {
"paragonie/sodium_compat": "Pure PHP implementation of libsodium",
+ "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
},
"type": "library",
@@ -570,9 +571,9 @@
],
"support": {
"issues": "https://github.com/php-stubs/wordpress-stubs/issues",
- "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.3.0"
+ "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.4.0"
},
- "time": "2023-08-10T16:34:11+00:00"
+ "time": "2023-11-08T07:02:08+00:00"
},
{
"name": "phpcompatibility/php-compatibility",
@@ -865,16 +866,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.10.38",
+ "version": "1.10.41",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691"
+ "reference": "c6174523c2a69231df55bdc65b61655e72876d76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691",
- "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76",
+ "reference": "c6174523c2a69231df55bdc65b61655e72876d76",
"shasum": ""
},
"require": {
@@ -923,7 +924,7 @@
"type": "tidelift"
}
],
- "time": "2023-10-06T14:19:14+00:00"
+ "time": "2023-11-05T12:57:57+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -2614,22 +2615,22 @@
},
{
"name": "szepeviktor/phpstan-wordpress",
- "version": "v1.3.0",
+ "version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/szepeviktor/phpstan-wordpress.git",
- "reference": "5b5cc77ed51fdaf64efe3f00b5aae4b709d2cfa9"
+ "reference": "b8516ed6bab7ec50aae981698ce3f67f1be2e45a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/5b5cc77ed51fdaf64efe3f00b5aae4b709d2cfa9",
- "reference": "5b5cc77ed51fdaf64efe3f00b5aae4b709d2cfa9",
+ "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/b8516ed6bab7ec50aae981698ce3f67f1be2e45a",
+ "reference": "b8516ed6bab7ec50aae981698ce3f67f1be2e45a",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0",
- "phpstan/phpstan": "^1.10.0",
+ "phpstan/phpstan": "^1.10.30",
"symfony/polyfill-php73": "^1.12.0"
},
"require-dev": {
@@ -2640,6 +2641,9 @@
"phpunit/phpunit": "^8.0 || ^9.0",
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.8"
},
+ "suggest": {
+ "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods"
+ },
"type": "phpstan-extension",
"extra": {
"phpstan": {
@@ -2667,9 +2671,9 @@
],
"support": {
"issues": "https://github.com/szepeviktor/phpstan-wordpress/issues",
- "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.0"
+ "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.2"
},
- "time": "2023-04-23T06:15:06+00:00"
+ "time": "2023-10-16T17:23:56+00:00"
},
{
"name": "theseer/tokenizer",
@@ -2902,8 +2906,9 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=7|^8"
+ "php": ">=7|^8",
+ "ext-json": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.2.0"
}
From 80c6827277ebcad35c8ebdfe173c773fc8d23d71 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 9 Nov 2023 10:36:29 -0800
Subject: [PATCH 27/62] Pass object to detect() instead of positional args
---
.../image-loading-optimization/detection.php | 12 ++++++------
.../detection/detect.js | 17 +++++++++--------
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 76dd045198..3dcd945e91 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -41,15 +41,15 @@ function ilo_print_detection_script() {
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
$detect_args = array(
- $serve_time,
- $detection_time_window,
- WP_DEBUG,
- rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
- wp_create_nonce( 'wp_rest' ),
+ 'serveTime' => $serve_time,
+ 'detectionTimeWindow' => $detection_time_window,
+ 'isDebug' => WP_DEBUG,
+ 'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
+ 'restApiNonce' => wp_create_nonce( 'wp_rest' ),
);
wp_print_inline_script_tag(
sprintf(
- 'import detect from %s; detect( ...%s )',
+ 'import detect from %s; detect( %s )',
wp_json_encode( add_query_arg( 'ver', PERFLAB_VERSION, plugin_dir_url( __FILE__ ) . 'detection/detect.js' ) ),
wp_json_encode( $detect_args )
),
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 0fa5d7f534..7498e3d81d 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -98,19 +98,20 @@ function getBreadcrumbs( leafElement ) {
/**
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
*
- * @param {number} serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
- * @param {number} detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
- * @param {boolean} isDebug Whether to show debug messages.
- * @param {string} restApiEndpoint URL for where to send the detection data.
- * @param {string} restApiNonce Nonce for writing to the REST API.
+ * @param {Object} args Args.
+ * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
+ * @param {number} args.detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
+ * @param {boolean} args.isDebug Whether to show debug messages.
+ * @param {string} args.restApiEndpoint URL for where to send the detection data.
+ * @param {string} args.restApiNonce Nonce for writing to the REST API.
*/
-export default async function detect(
+export default async function detect( {
serveTime,
detectionTimeWindow,
isDebug,
restApiEndpoint,
- restApiNonce
-) {
+ restApiNonce,
+} ) {
const runTime = new Date().valueOf();
// Abort running detection logic if it was served in a cached page.
From f6208c20ba799d74b2eced8a161145d68cb5c551 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 9 Nov 2023 10:50:11 -0800
Subject: [PATCH 28/62] Add description to
ilo_get_page_metrics_breakpoint_sample_size and reduce default size to 3
---
.../images/image-loading-optimization/storage/data.php | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index f042422c4b..8bb8b3fb68 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -83,17 +83,21 @@ static function ( $breakpoint_max_width ) {
}
/**
- * Gets desired sample size for a breakpoint's page metrics.
+ * Gets the sample size for a breakpoint's page metrics on a given URL.
+ *
+ * A breakpoint divides page 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 page metrics stored for a given URL: 3 for mobile and 3 for desktop.
*
* @return int Sample size.
*/
function ilo_get_page_metrics_breakpoint_sample_size() {
/**
- * Filters desired sample size for a viewport's page metrics.
+ * Filters the sample size for a breakpoint's page metrics on a given URL.
*
* @param int $sample_size Sample size.
*/
- return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 10 );
+ return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 3 );
}
/**
From fcf9acd3cdbfd33a940c75ef9088d5f265a0181a Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Thu, 9 Nov 2023 15:23:39 -0800
Subject: [PATCH 29/62] Add initial function to get normalized current URL
---
.../storage/data.php | 72 +++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 8bb8b3fb68..5de95f4049 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -28,6 +28,78 @@ function ilo_get_page_metric_ttl() {
return (int) apply_filters( 'ilo_page_metric_ttl', MONTH_IN_SECONDS );
}
+/**
+ * Gets the normalized current URL.
+ *
+ * TODO: This will need to be made more robust for non-singular URLs. What about multi-faceted archives with multiple taxonomies and date parameters?
+ *
+ * @return string Normalized current URL.
+ */
+function ilo_get_normalized_current_url() {
+ if ( is_singular() ) {
+ $url = wp_get_canonical_url();
+ if ( $url ) {
+ return $url;
+ }
+ }
+
+ $home_path = wp_parse_url( home_url( '/' ), PHP_URL_PATH );
+
+ $scheme = is_ssl() ? 'https' : 'http';
+ $host = strtok( $_SERVER['HTTP_HOST'], ':' ); // Use of strtok() since wp-env erroneously includes port in host.
+ $port = (int) $_SERVER['SERVER_PORT'];
+ $path = '';
+ $query = '';
+ if ( preg_match( '%(^.+?)(?:\?([^#]+))?%', wp_unslash( $_SERVER['REQUEST_URI'] ), $matches ) ) {
+ if ( ! empty( $matches[1] ) ) {
+ $path = $matches[1];
+ }
+ if ( ! empty( $matches[2] ) ) {
+ $query = $matches[2];
+ }
+ }
+ if ( $query ) {
+ $removable_query_args = wp_removable_query_args();
+ $removable_query_args[] = 'fbclid';
+
+ $old_query_args = array();
+ $new_query_args = array();
+ wp_parse_str( $query, $old_query_args );
+ foreach ( $old_query_args as $key => $value ) {
+ if (
+ str_starts_with( 'utm_', $key ) ||
+ in_array( $key, $removable_query_args, true )
+ ) {
+ continue;
+ }
+ $new_query_args[ $key ] = $value;
+ }
+ asort( $new_query_args );
+ $query = build_query( $new_query_args );
+ }
+
+ // Normalize open-ended URLs.
+ if ( is_404() ) {
+ $path = $home_path;
+ $query = 'error=404';
+ } elseif ( is_search() ) {
+ $path = $home_path;
+ $query = 's={}';
+ }
+
+ // Rebuild the URL.
+ $url = $scheme . '://' . $host;
+ if ( 80 !== $port && 443 !== $port ) {
+ $url .= ":{$port}";
+ }
+ $url .= $path;
+ if ( $query ) {
+ $url .= "?{$query}";
+ }
+
+ return $url;
+}
+
/**
* Unshift a new page metric onto an array of page metrics.
*
From 224ea516dd0ff470f51f9905d43865c2ee67b9ea Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 10 Nov 2023 11:22:40 -0800
Subject: [PATCH 30/62] Add function for normalizing query vars and getting
current URL
---
.../storage/data.php | 106 +++++++++---------
1 file changed, 51 insertions(+), 55 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 5de95f4049..c2ac6b38e4 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -29,75 +29,71 @@ function ilo_get_page_metric_ttl() {
}
/**
- * Gets the normalized current URL.
+ * Get the URL for the current request.
*
- * TODO: This will need to be made more robust for non-singular URLs. What about multi-faceted archives with multiple taxonomies and date parameters?
+ * This is essentially the REQUEST_URI prefixed by the scheme and host for the home URL.
+ * This is needed in particular due to subdirectory installs.
*
- * @return string Normalized current URL.
+ * @return string Current URL.
*/
-function ilo_get_normalized_current_url() {
- if ( is_singular() ) {
- $url = wp_get_canonical_url();
- if ( $url ) {
- return $url;
- }
+function ilo_get_current_url() {
+ $parsed_url = wp_parse_url( home_url() );
+
+ if ( ! is_array( $parsed_url ) ) {
+ $parsed_url = array();
}
- $home_path = wp_parse_url( home_url( '/' ), PHP_URL_PATH );
+ if ( empty( $parsed_url['scheme'] ) ) {
+ $parsed_url['scheme'] = is_ssl() ? 'https' : 'http';
+ }
+ if ( ! isset( $parsed_url['host'] ) ) {
+ $parsed_url['host'] = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : 'localhost';
+ }
- $scheme = is_ssl() ? 'https' : 'http';
- $host = strtok( $_SERVER['HTTP_HOST'], ':' ); // Use of strtok() since wp-env erroneously includes port in host.
- $port = (int) $_SERVER['SERVER_PORT'];
- $path = '';
- $query = '';
- if ( preg_match( '%(^.+?)(?:\?([^#]+))?%', wp_unslash( $_SERVER['REQUEST_URI'] ), $matches ) ) {
- if ( ! empty( $matches[1] ) ) {
- $path = $matches[1];
- }
- if ( ! empty( $matches[2] ) ) {
- $query = $matches[2];
+ $current_url = $parsed_url['scheme'] . '://';
+ if ( isset( $parsed_url['user'] ) ) {
+ $current_url .= $parsed_url['user'];
+ if ( isset( $parsed_url['pass'] ) ) {
+ $current_url .= ':' . $parsed_url['pass'];
}
+ $current_url .= '@';
}
- if ( $query ) {
- $removable_query_args = wp_removable_query_args();
- $removable_query_args[] = 'fbclid';
-
- $old_query_args = array();
- $new_query_args = array();
- wp_parse_str( $query, $old_query_args );
- foreach ( $old_query_args as $key => $value ) {
- if (
- str_starts_with( 'utm_', $key ) ||
- in_array( $key, $removable_query_args, true )
- ) {
- continue;
- }
- $new_query_args[ $key ] = $value;
- }
- asort( $new_query_args );
- $query = build_query( $new_query_args );
+ $current_url .= $parsed_url['host'];
+ if ( isset( $parsed_url['port'] ) ) {
+ $current_url .= ':' . $parsed_url['port'];
}
+ $current_url .= '/';
- // Normalize open-ended URLs.
- if ( is_404() ) {
- $path = $home_path;
- $query = 'error=404';
- } elseif ( is_search() ) {
- $path = $home_path;
- $query = 's={}';
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $current_url .= ltrim( wp_unslash( $_SERVER['REQUEST_URI'] ), '/' );
}
+ return esc_url_raw( $current_url );
+}
- // Rebuild the URL.
- $url = $scheme . '://' . $host;
- if ( 80 !== $port && 443 !== $port ) {
- $url .= ":{$port}";
- }
- $url .= $path;
- if ( $query ) {
- $url .= "?{$query}";
+/**
+ * Gets the normalized query vars for the current request.
+ *
+ * This is used as a cache key for stored page metrics.
+ *
+ * @return array Normalized query vars.
+ */
+function ilo_get_normalized_query_vars() {
+ global $wp;
+
+ // Note that the order of this array is naturally normalized since it is
+ // assembled by iterating over public_query_vars.
+ $normalized_query_vars = $wp->query_vars;
+
+ // Normalize unbounded query vars.
+ if ( is_404() ) {
+ $normalized_query_vars = array(
+ 'error' => 404,
+ );
+ } elseif ( array_key_exists( 's', $normalized_query_vars ) ) {
+ $normalized_query_vars['s'] = '...';
}
- return $url;
+ return $normalized_query_vars;
}
/**
From dd9b3754dc3d3aed99dada94656e9e1988ae1b07 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 10 Nov 2023 11:23:35 -0800
Subject: [PATCH 31/62] Use current() instead of array_shift()
---
modules/images/image-loading-optimization/storage/post-type.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index de9f10d5be..578dcc2db2 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -68,7 +68,7 @@ function ilo_get_page_metrics_post( $url ) {
)
);
- $post = array_shift( $post_query->posts );
+ $post = current( $post_query->posts );
if ( $post instanceof WP_Post ) {
return $post;
} else {
From f9a14939d935e37a360dae450a4a19fff9f92c43 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 10 Nov 2023 12:39:11 -0800
Subject: [PATCH 32/62] Use query vars instead of URL for computing slug; add
HMAC
---
.../image-loading-optimization/detection.php | 5 +++
.../detection/detect.js | 8 ++++-
.../storage/data.php | 24 ++++++++++++++
.../storage/post-type.php | 32 ++++++-------------
.../storage/rest-api.php | 20 ++++++++++--
5 files changed, 64 insertions(+), 25 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 3dcd945e91..00f5e10c72 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -40,12 +40,17 @@ function ilo_print_detection_script() {
*/
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
+ $query_vars = ilo_get_normalized_query_vars();
+ $slug = ilo_get_page_metrics_slug( $query_vars );
+
$detect_args = array(
'serveTime' => $serve_time,
'detectionTimeWindow' => $detection_time_window,
'isDebug' => WP_DEBUG,
'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
'restApiNonce' => wp_create_nonce( 'wp_rest' ),
+ 'pageMetricsSlug' => $slug,
+ 'pageMetricsHmac' => ilo_get_slug_hmac( $slug ), // TODO: Or would a nonce make more sense with the $slug being the action?
);
wp_print_inline_script_tag(
sprintf(
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 7498e3d81d..dec3879ba3 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -104,6 +104,8 @@ function getBreadcrumbs( leafElement ) {
* @param {boolean} args.isDebug Whether to show debug messages.
* @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.pageMetricsSlug Slug for page metrics.
+ * @param {string} args.pageMetricsHmac HMAC for the page metric slug.
*/
export default async function detect( {
serveTime,
@@ -111,6 +113,8 @@ export default async function detect( {
isDebug,
restApiEndpoint,
restApiNonce,
+ pageMetricsSlug,
+ pageMetricsHmac,
} ) {
const runTime = new Date().valueOf();
@@ -259,7 +263,9 @@ export default async function detect( {
/** @type {PageMetrics} */
const pageMetrics = {
- url: win.location.href, // TODO: Consider sending canonical URL instead.
+ url: win.location.href,
+ slug: pageMetricsSlug,
+ hmac: pageMetricsHmac,
viewport: {
width: win.innerWidth,
height: win.innerHeight,
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index c2ac6b38e4..63e5ccc3b7 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -96,6 +96,30 @@ function ilo_get_normalized_query_vars() {
return $normalized_query_vars;
}
+/**
+ * Gets slug for page metrics.
+ *
+ * @see ilo_get_normalized_query_vars()
+ *
+ * @param array $query_vars Normalized query vars.
+ * @return string Slug.
+ */
+function ilo_get_page_metrics_slug( $query_vars ) {
+ return md5( wp_json_encode( $query_vars ) );
+}
+
+/**
+ * Compute HMAC for page metrics slug.
+ *
+ * This is used in the REST API to authenticate the storage of new page metrics from a given URL.
+ *
+ * @param string $slug Page metrics slug.
+ * @return false HMAC.
+ */
+function ilo_get_slug_hmac( $slug ) {
+ return hash_hmac( 'sha1', $slug, wp_salt() );
+}
+
/**
* Unshift a new page metric onto an array of page metrics.
*
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index 578dcc2db2..d3bfefdc32 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -37,28 +37,18 @@ function ilo_register_page_metrics_post_type() {
}
add_action( 'init', 'ilo_register_page_metrics_post_type' );
-/**
- * Gets slug for page metrics post.
- *
- * @param string $url URL.
- * @return string Slug for URL.
- */
-function ilo_get_page_metrics_slug( $url ) {
- return md5( $url );
-}
-
/**
* Get page metrics post.
*
- * @param string $url URL.
+ * @param string $slug Page metrics slug.
* @return WP_Post|null Post object if exists.
*/
-function ilo_get_page_metrics_post( $url ) {
+function ilo_get_page_metrics_post( $slug ) {
$post_query = new WP_Query(
array(
'post_type' => ILO_PAGE_METRICS_POST_TYPE,
'post_status' => 'publish',
- 'name' => ilo_get_page_metrics_slug( $url ),
+ 'name' => $slug,
'posts_per_page' => 1,
'no_found_rows' => true,
'cache_results' => true,
@@ -114,7 +104,6 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
* The $validated_page_metric parameter has the following array shape:
*
* {
- * 'url': string,
* 'viewport': array{
* 'width': int,
* 'height': int
@@ -122,21 +111,20 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
* 'elements': array
* }
*
- * @param array $validated_page_metric Page metric, already validated by REST API.
- *
+ * @param string $url URL for the page metrics. This is used purely as metadata.
+ * @param string $slug Page metrics slug (computed from query vars).
+ * @param array $validated_page_metric Page metric, already validated by REST API.
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
-function ilo_store_page_metric( array $validated_page_metric ) {
- $url = $validated_page_metric['url'];
- unset( $validated_page_metric['url'] ); // Not stored in post_content but rather in post_title/post_name.
+function ilo_store_page_metric( $url, $slug, array $validated_page_metric ) {
$validated_page_metric['timestamp'] = time();
// TODO: What about storing a version identifier?
$post_data = array(
- 'post_title' => $url,
+ 'post_title' => $url, // TODO: Should we keep this? It can help with debugging.
);
- $post = ilo_get_page_metrics_post( $url );
+ $post = ilo_get_page_metrics_post( $slug );
if ( $post instanceof WP_Post ) {
$post_data['ID'] = $post->ID;
@@ -150,7 +138,7 @@ function ilo_store_page_metric( array $validated_page_metric ) {
$page_metrics = array();
}
} else {
- $post_data['post_name'] = ilo_get_page_metrics_slug( $url );
+ $post_data['post_name'] = $slug;
$page_metrics = array();
}
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 4844392d3a..fb2c2c9868 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -65,6 +65,22 @@ function ilo_register_endpoint() {
return true;
},
),
+ 'slug' => array(
+ 'type' => 'string',
+ 'required' => true,
+ 'pattern' => '^[0-9a-f]{32}$',
+ ),
+ 'hmac' => array(
+ 'type' => 'string',
+ 'required' => true,
+ 'pattern' => '^[0-9a-f]+$',
+ 'validate_callback' => static function ( $hmac, WP_REST_Request $request ) {
+ if ( ! hash_equals( $hmac, ilo_get_slug_hmac( $request->get_param( 'slug' ) ) ) ) {
+ return new WP_Error( 'invalid_hmac', __( 'HMAC comparison failure.', 'performance-lab' ) );
+ }
+ return true;
+ },
+ ),
'viewport' => array(
'description' => __( 'Viewport dimensions', 'performance-lab' ),
'type' => 'object',
@@ -142,7 +158,7 @@ function ilo_handle_rest_request( WP_REST_Request $request ) {
ilo_set_page_metric_storage_lock();
$page_metric = $request->get_json_params();
- $result = ilo_store_page_metric( $page_metric );
+ $result = ilo_store_page_metric( $page_metric['url'], $page_metric['slug'], $request->get_json_params() );
if ( $result instanceof WP_Error ) {
return $result;
@@ -152,7 +168,7 @@ function ilo_handle_rest_request( WP_REST_Request $request ) {
array(
'success' => true,
'post_id' => $result,
- 'data' => ilo_parse_stored_page_metrics( ilo_get_page_metrics_post( $page_metric['url'] ) ), // TODO: Remove this debug data.
+ 'data' => ilo_parse_stored_page_metrics( ilo_get_page_metrics_post( $page_metric['slug'] ) ), // TODO: Remove this debug data.
)
);
}
From 5dc6828bd39683c931f75e0a60250316e145fdaa Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 10 Nov 2023 13:03:35 -0800
Subject: [PATCH 33/62] Use nonce instead of hmac
---
.../image-loading-optimization/detection.php | 2 +-
.../detection/detect.js | 6 ++---
.../storage/data.php | 27 ++++++++++++++++---
.../storage/rest-api.php | 8 +++---
4 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 00f5e10c72..113577ffb0 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -50,7 +50,7 @@ function ilo_print_detection_script() {
'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
'restApiNonce' => wp_create_nonce( 'wp_rest' ),
'pageMetricsSlug' => $slug,
- 'pageMetricsHmac' => ilo_get_slug_hmac( $slug ), // TODO: Or would a nonce make more sense with the $slug being the action?
+ 'pageMetricsNonce' => ilo_get_page_metrics_storage_nonce( $slug ),
);
wp_print_inline_script_tag(
sprintf(
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index dec3879ba3..6379cf7856 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -105,7 +105,7 @@ function getBreadcrumbs( leafElement ) {
* @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.pageMetricsSlug Slug for page metrics.
- * @param {string} args.pageMetricsHmac HMAC for the page metric slug.
+ * @param {string} args.pageMetricsNonce Nonce for page metrics storage.
*/
export default async function detect( {
serveTime,
@@ -114,7 +114,7 @@ export default async function detect( {
restApiEndpoint,
restApiNonce,
pageMetricsSlug,
- pageMetricsHmac,
+ pageMetricsNonce,
} ) {
const runTime = new Date().valueOf();
@@ -265,7 +265,7 @@ export default async function detect( {
const pageMetrics = {
url: win.location.href,
slug: pageMetricsSlug,
- hmac: pageMetricsHmac,
+ nonce: pageMetricsNonce,
viewport: {
width: win.innerWidth,
height: win.innerHeight,
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 63e5ccc3b7..f3813143b6 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -109,15 +109,34 @@ function ilo_get_page_metrics_slug( $query_vars ) {
}
/**
- * Compute HMAC for page metrics slug.
+ * Compute nonce for storing page metrics for a specific slug.
*
* This is used in the REST API to authenticate the storage of new page metrics from a given URL.
*
+ * @see wp_create_nonce()
+ * @see ilo_verify_page_metrics_storage_nonce()
+ *
* @param string $slug Page metrics slug.
- * @return false HMAC.
+ * @return string Nonce.
+ */
+function ilo_get_page_metrics_storage_nonce( $slug ) {
+ return wp_create_nonce( "store_page_metrics:{$slug}" );
+}
+
+/**
+ * Verify nonce for storing page metrics for a specific slug.
+ *
+ * @see wp_verify_nonce()
+ * @see ilo_get_page_metrics_storage_nonce()
+ *
+ * @param string $nonce Page metrics storage nonce.
+ * @param string $slug Page metrics slug.
+ * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago,
+ * 2 if the nonce is valid and generated between 12-24 hours ago.
+ * False if the nonce is invalid.
*/
-function ilo_get_slug_hmac( $slug ) {
- return hash_hmac( 'sha1', $slug, wp_salt() );
+function ilo_verify_page_metrics_storage_nonce( $nonce, $slug ) {
+ return wp_verify_nonce( $nonce, "store_page_metrics:{$slug}" );
}
/**
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index fb2c2c9868..2c2816c9a3 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -70,13 +70,13 @@ function ilo_register_endpoint() {
'required' => true,
'pattern' => '^[0-9a-f]{32}$',
),
- 'hmac' => array(
+ 'nonce' => array(
'type' => 'string',
'required' => true,
'pattern' => '^[0-9a-f]+$',
- 'validate_callback' => static function ( $hmac, WP_REST_Request $request ) {
- if ( ! hash_equals( $hmac, ilo_get_slug_hmac( $request->get_param( 'slug' ) ) ) ) {
- return new WP_Error( 'invalid_hmac', __( 'HMAC comparison failure.', 'performance-lab' ) );
+ 'validate_callback' => static function ( $nonce, WP_REST_Request $request ) {
+ if ( ! ilo_verify_page_metrics_storage_nonce( $nonce, $request->get_param( 'slug' ) ) ) {
+ return new WP_Error( 'invalid_nonce', __( 'Page metrics nonce verification failure.', 'performance-lab' ) );
}
return true;
},
From e0f2b6826ee48502a6ba3594f93f82b4e3c0c877 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Fri, 10 Nov 2023 15:19:04 -0800
Subject: [PATCH 34/62] Prevent collecting page metrics when sample size is
full for all breakpoints
---
.../image-loading-optimization/detection.php | 40 ++++++++++++++++---
.../storage/data.php | 40 ++++++++++++-------
.../storage/post-type.php | 20 +++++++++-
.../storage/rest-api.php | 10 +++--
4 files changed, 87 insertions(+), 23 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 113577ffb0..a8a7c3c234 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -17,8 +17,41 @@
* @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
*/
function ilo_print_detection_script() {
+ $query_vars = ilo_get_normalized_query_vars();
+ $slug = ilo_get_page_metrics_slug( $query_vars );
+ $data = ilo_get_page_metrics_data( $slug );
+ if ( ! is_array( $data ) ) {
+ $data = $data;
+ }
+
+ $metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $data, ilo_get_breakpoint_max_widths() );
+ $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
+ $freshness_ttl = ilo_get_page_metric_freshness_ttl();
+
+ // TODO: This same logic needs to be in the endpoint so that we can reject requests when not needed.
+ $current_time = time();
+ $needed_minimum_viewport_widths = array();
+ foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $page_metrics ) {
+ $needs_page_metrics = false;
+ if ( count( $page_metrics ) < $sample_size ) {
+ $needs_page_metrics = true;
+ } else {
+ foreach ( $page_metrics as $page_metric ) {
+ if ( isset( $page_metric['timestamp'] ) && $page_metric['timestamp'] + $freshness_ttl < $current_time ) {
+ $needs_page_metrics = true;
+ break;
+ }
+ }
+ }
+ $needed_minimum_viewport_widths[ $minimum_viewport_width ] = $needs_page_metrics;
+ }
- // TODO: Also abort if we don't need any new page metrics due to the sample size being full.
+ // Abort if we already have all the sample size we need for all breakpoints.
+ if ( count( array_filter( $needed_minimum_viewport_widths ) ) === 0 ) {
+ return;
+ }
+
+ // Abort if storage is locked.
if ( ilo_is_page_metric_storage_locked() ) {
return;
}
@@ -40,9 +73,6 @@ function ilo_print_detection_script() {
*/
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
- $query_vars = ilo_get_normalized_query_vars();
- $slug = ilo_get_page_metrics_slug( $query_vars );
-
$detect_args = array(
'serveTime' => $serve_time,
'detectionTimeWindow' => $detection_time_window,
@@ -54,7 +84,7 @@ function ilo_print_detection_script() {
);
wp_print_inline_script_tag(
sprintf(
- 'import detect from %s; detect( %s )',
+ 'import detect from %s; detect( %s );',
wp_json_encode( add_query_arg( 'ver', PERFLAB_VERSION, plugin_dir_url( __FILE__ ) . 'detection/detect.js' ) ),
wp_json_encode( $detect_args )
),
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index f3813143b6..1eda44737b 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -15,11 +15,9 @@
*
* When a page metric expires it is eligible to be replaced by a newer one.
*
- * TODO: However, we keep viewport-specific page metrics regardless of TTL.
- *
* @return int Expiration age in seconds.
*/
-function ilo_get_page_metric_ttl() {
+function ilo_get_page_metric_freshness_ttl() {
/**
* Filters the expiration age for a given page metric.
*
@@ -216,25 +214,39 @@ function ilo_get_page_metrics_breakpoint_sample_size() {
*
* @param array $page_metrics Page metrics.
* @param int[] $breakpoints Viewport breakpoint max widths, sorted in ascending order.
- * @return array Grouped page metrics.
+ * @return array Page metrics grouped by breakpoint. The array keys are the minimum widths for a viewport to lie within
+ * the breakpoint. The returned array is always one larger than the provided array of breakpoints, since
+ * the breakpoints reflect the max inclusive boundaries whereas the return value is the groups of page
+ * metrics with viewports on either side of the breakpoint boundaries.
*/
function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ) {
- $max_index = count( $breakpoints );
- $groups = array_fill( 0, $max_index + 1, array() );
- $largest_breakpoint = $breakpoints[ $max_index - 1 ];
+
+ // Convert breakpoint max widths into viewport minimum widths.
+ $viewport_minimum_widths = array_map(
+ static function ( $breakpoint ) {
+ return $breakpoint + 1;
+ },
+ $breakpoints
+ );
+
+ $grouped = array_fill_keys( array_merge( array( 0 ), $viewport_minimum_widths ), array() );
+
foreach ( $page_metrics as $page_metric ) {
if ( ! isset( $page_metric['viewport']['width'] ) ) {
continue;
}
$viewport_width = $page_metric['viewport']['width'];
- if ( $viewport_width > $largest_breakpoint ) {
- $groups[ $max_index ][] = $page_metric;
- }
- foreach ( $breakpoints as $group => $breakpoint ) {
- if ( $viewport_width <= $breakpoint ) {
- $groups[ $group ][] = $page_metric;
+
+ $current_minimum_viewport = 0;
+ foreach ( $viewport_minimum_widths as $viewport_minimum_width ) {
+ if ( $viewport_width > $viewport_minimum_width ) {
+ $current_minimum_viewport = $viewport_minimum_width;
+ } else {
+ break;
}
}
+
+ $grouped[ $current_minimum_viewport ][] = $page_metric;
}
- return $groups;
+ return $grouped;
}
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index d3bfefdc32..c9a0c173ba 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -31,7 +31,7 @@ function ilo_register_page_metrics_post_type() {
'query_var' => false,
'delete_with_user' => false,
'can_export' => false,
- 'supports' => array( 'title' ), // The original URL is stored in the post_title, and the MD5 hash in the post_name.
+ 'supports' => array( 'title' ), // The original URL is stored in the post_title, and the post_name is a hash of the query vars.
)
);
}
@@ -98,6 +98,24 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
return $page_metrics;
}
+/**
+ * Parses post content in page metrics post.
+ *
+ * @param string $slug Page metrics slug.
+ * @return array Page metrics data, or null if invalid.
+ */
+function ilo_get_page_metrics_data( $slug ) {
+ $post = ilo_get_page_metrics_post( $slug );
+ if ( ! ( $post instanceof WP_Post ) ) {
+ return null;
+ }
+ $data = ilo_parse_stored_page_metrics( $post );
+ if ( ! is_array( $data ) ) {
+ return null;
+ }
+ return $data;
+}
+
/**
* Stores page metric by merging it with the other page metrics for a given URL.
*
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 2c2816c9a3..7d48ced227 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -157,8 +157,12 @@ function ilo_register_endpoint() {
function ilo_handle_rest_request( WP_REST_Request $request ) {
ilo_set_page_metric_storage_lock();
- $page_metric = $request->get_json_params();
- $result = ilo_store_page_metric( $page_metric['url'], $page_metric['slug'], $request->get_json_params() );
+ $page_metric = wp_array_slice_assoc( $request->get_json_params(), array( 'viewport', 'elements' ) );
+ $result = ilo_store_page_metric(
+ $request->get_param( 'url' ),
+ $request->get_param( 'slug' ),
+ $page_metric
+ );
if ( $result instanceof WP_Error ) {
return $result;
@@ -168,7 +172,7 @@ function ilo_handle_rest_request( WP_REST_Request $request ) {
array(
'success' => true,
'post_id' => $result,
- 'data' => ilo_parse_stored_page_metrics( ilo_get_page_metrics_post( $page_metric['slug'] ) ), // TODO: Remove this debug data.
+ 'data' => ilo_parse_stored_page_metrics( ilo_get_page_metrics_post( $request->get_param( 'slug' ) ) ), // TODO: Remove this debug data.
)
);
}
From a6b7760bcbe4c44acadb31f656e06993fecb50bc Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 13 Nov 2023 10:58:27 -0800
Subject: [PATCH 35/62] Fix function prefix in tests and self-assignment
---
admin/server-timing.php | 4 ++--
modules/images/image-loading-optimization/detection.php | 2 +-
server-timing/class-perflab-server-timing.php | 2 +-
tests/admin/server-timing-tests.php | 2 +-
.../images/image-loading-optimization/load-tests.php | 6 +++---
5 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/admin/server-timing.php b/admin/server-timing.php
index 46f28df890..85daaf1464 100644
--- a/admin/server-timing.php
+++ b/admin/server-timing.php
@@ -43,7 +43,7 @@ function perflab_add_server_timing_page() {
* @since 2.6.0
*/
function perflab_load_server_timing_page() {
- if ( ! has_filter( 'template_include', 'image_loading_optimization_buffer_output' ) ) {
+ if ( ! has_filter( 'template_include', 'ilo_buffer_output' ) ) {
/*
* This settings section technically includes a field, however it is directly rendered as part of the section
* callback due to requiring custom markup.
@@ -95,7 +95,7 @@ static function () {
);
?>
-
+
assertArrayHasKey( PERFLAB_SERVER_TIMING_SCREEN, $wp_settings_sections );
$expected_sections = array( 'benchmarking' );
- if ( ! has_filter( 'template_include', 'image_loading_optimization_buffer_output' ) ) {
+ if ( ! has_filter( 'template_include', 'ilo_buffer_output' ) ) {
$expected_sections[] = 'output-buffering';
}
$this->assertEqualSets(
diff --git a/tests/modules/images/image-loading-optimization/load-tests.php b/tests/modules/images/image-loading-optimization/load-tests.php
index a1a9333c05..3fb443947d 100644
--- a/tests/modules/images/image-loading-optimization/load-tests.php
+++ b/tests/modules/images/image-loading-optimization/load-tests.php
@@ -16,14 +16,14 @@ class Image_Loading_Optimization_Load_Tests extends ImagesTestCase {
* @test
*/
public function it_is_hooking_output_buffering_at_template_include() {
- $this->assertEquals( PHP_INT_MAX, has_filter( 'template_include', 'image_loading_optimization_buffer_output' ) );
+ $this->assertEquals( PHP_INT_MAX, has_filter( 'template_include', 'ilo_buffer_output' ) );
}
/**
* Make output is buffered and that it is also filtered.
*
* @test
- * @covers ::image_loading_optimization_buffer_output
+ * @covers ::ilo_buffer_output
*/
public function it_buffers_and_filters_output() {
$original = 'Hello World!';
@@ -42,7 +42,7 @@ function ( $buffer ) use ( $original, $expected ) {
);
$original_ob_level = ob_get_level();
- image_loading_optimization_buffer_output();
+ ilo_buffer_output();
$this->assertSame( $original_ob_level + 1, ob_get_level(), 'Expected call to ob_start().' );
echo $original;
From f7aa73b4ee4ec342d933e5a405376e4725371aee Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 13 Nov 2023 11:03:10 -0800
Subject: [PATCH 36/62] Fix filter for ilo_get_page_metric_freshness_ttl;
reduce TTL from month to day
---
.../image-loading-optimization/storage/data.php | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 1eda44737b..91d20d46a7 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -11,19 +11,19 @@
}
/**
- * Gets the expiration age for a given page metric.
+ * Gets the freshness age (TTL) for a given page metric.
*
- * When a page metric expires it is eligible to be replaced by a newer one.
+ * When a page metric expires it is eligible to be replaced by a newer one if its viewport lies within the same breakpoint.
*
- * @return int Expiration age in seconds.
+ * @return int Expiration TTL in seconds.
*/
function ilo_get_page_metric_freshness_ttl() {
/**
- * Filters the expiration age for a given page metric.
+ * Filters the freshness age (TTL) for a given page metric.
*
- * @param int $ttl TTL.
+ * @param int $ttl Expiration TTL in seconds.
*/
- return (int) apply_filters( 'ilo_page_metric_ttl', MONTH_IN_SECONDS );
+ return (int) apply_filters( 'ilo_page_metric_freshness_ttl', DAY_IN_SECONDS );
}
/**
From 8eb0dbef72f7a7957f9fc5660601901b19760d3e Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 13 Nov 2023 17:16:39 -0800
Subject: [PATCH 37/62] Add client-side and server-side checks for whether page
metrics needed for breakpoints
---
.../image-loading-optimization/detection.php | 45 ++++-----------
.../detection/detect.js | 47 +++++++++++++---
.../storage/data.php | 55 +++++++++++++++++++
.../storage/rest-api.php | 15 ++++-
4 files changed, 116 insertions(+), 46 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 91f336793d..2f96406e0f 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -13,41 +13,15 @@
/**
* Prints the script for detecting loaded images and the LCP element.
*
- * @todo This should eventually only print the script if metrics are needed.
* @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
*/
function ilo_print_detection_script() {
$query_vars = ilo_get_normalized_query_vars();
$slug = ilo_get_page_metrics_slug( $query_vars );
- $data = ilo_get_page_metrics_data( $slug );
- if ( ! is_array( $data ) ) {
- $data = array();
- }
-
- $metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $data, ilo_get_breakpoint_max_widths() );
- $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
- $freshness_ttl = ilo_get_page_metric_freshness_ttl();
-
- // TODO: This same logic needs to be in the endpoint so that we can reject requests when not needed.
- $current_time = time();
- $needed_minimum_viewport_widths = array();
- foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $page_metrics ) {
- $needs_page_metrics = false;
- if ( count( $page_metrics ) < $sample_size ) {
- $needs_page_metrics = true;
- } else {
- foreach ( $page_metrics as $page_metric ) {
- if ( isset( $page_metric['timestamp'] ) && $page_metric['timestamp'] + $freshness_ttl < $current_time ) {
- $needs_page_metrics = true;
- break;
- }
- }
- }
- $needed_minimum_viewport_widths[ $minimum_viewport_width ] = $needs_page_metrics;
- }
// Abort if we already have all the sample size we need for all breakpoints.
- if ( count( array_filter( $needed_minimum_viewport_widths ) ) === 0 ) {
+ $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths( $slug );
+ if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return;
}
@@ -74,13 +48,14 @@ function ilo_print_detection_script() {
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
$detect_args = array(
- 'serveTime' => $serve_time,
- 'detectionTimeWindow' => $detection_time_window,
- 'isDebug' => WP_DEBUG,
- 'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
- 'restApiNonce' => wp_create_nonce( 'wp_rest' ),
- 'pageMetricsSlug' => $slug,
- 'pageMetricsNonce' => ilo_get_page_metrics_storage_nonce( $slug ),
+ 'serveTime' => $serve_time,
+ 'detectionTimeWindow' => $detection_time_window,
+ 'isDebug' => WP_DEBUG,
+ 'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
+ 'restApiNonce' => wp_create_nonce( 'wp_rest' ),
+ 'pageMetricsSlug' => $slug,
+ 'pageMetricsNonce' => ilo_get_page_metrics_storage_nonce( $slug ),
+ 'neededMinimumViewportWidths' => $needed_minimum_viewport_widths,
);
wp_print_inline_script_tag(
sprintf(
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 6379cf7856..5101f127df 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -95,17 +95,40 @@ function getBreadcrumbs( leafElement ) {
return breadcrumbs;
}
+/**
+ * Checks whether the page metric(s) for the provided viewport width is needed.
+ *
+ * @param {number} viewportWidth - Current viewport width.
+ * @param {Array[]} neededMinimumViewportWidths - Needed minimum viewport widths, in ascending order.
+ * @return {boolean} Whether page metrics are needed.
+ */
+function isViewportNeeded( viewportWidth, neededMinimumViewportWidths ) {
+ let lastWasNeeded = false;
+ for ( const [
+ minimumViewportWidth,
+ isNeeded,
+ ] of neededMinimumViewportWidths ) {
+ if ( viewportWidth >= minimumViewportWidth ) {
+ lastWasNeeded = isNeeded;
+ } else {
+ break;
+ }
+ }
+ return lastWasNeeded;
+}
+
/**
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
*
- * @param {Object} args Args.
- * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
- * @param {number} args.detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
- * @param {boolean} args.isDebug Whether to show debug messages.
- * @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.pageMetricsSlug Slug for page metrics.
- * @param {string} args.pageMetricsNonce Nonce for page metrics storage.
+ * @param {Object} args Args.
+ * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
+ * @param {number} args.detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
+ * @param {boolean} args.isDebug Whether to show debug messages.
+ * @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.pageMetricsSlug Slug for page metrics.
+ * @param {string} args.pageMetricsNonce Nonce for page metrics storage.
+ * @param {Array} args.neededMinimumViewportWidths Needed minimum viewport widths for page metrics.
*/
export default async function detect( {
serveTime,
@@ -115,6 +138,7 @@ export default async function detect( {
restApiNonce,
pageMetricsSlug,
pageMetricsNonce,
+ neededMinimumViewportWidths, // TODO: The name is not great here.
} ) {
const runTime = new Date().valueOf();
@@ -139,6 +163,13 @@ export default async function detect( {
return;
}
+ if ( ! isViewportNeeded( win.innerWidth, neededMinimumViewportWidths ) ) {
+ if ( isDebug ) {
+ log( 'No need for page metrics from the current viewport.' );
+ }
+ return;
+ }
+
if ( isDebug ) {
log( 'Proceeding with detection' );
}
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 91d20d46a7..1088de68b1 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -250,3 +250,58 @@ static function ( $breakpoint ) {
}
return $grouped;
}
+
+/**
+ * Get needed minimum viewport widths.
+ *
+ * @param string $slug Page metric slug.
+ * @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
+ */
+function ilo_get_needed_minimum_viewport_widths( $slug ) {
+ $data = ilo_get_page_metrics_data( $slug );
+ if ( ! is_array( $data ) ) {
+ $data = array();
+ }
+
+ $metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $data, ilo_get_breakpoint_max_widths() );
+ $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
+ $freshness_ttl = ilo_get_page_metric_freshness_ttl();
+
+ $current_time = time();
+ $needed_minimum_viewport_widths = array();
+ foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $viewport_page_metrics ) {
+ $needs_page_metrics = false;
+ if ( count( $viewport_page_metrics ) < $sample_size ) {
+ $needs_page_metrics = true;
+ } else {
+ foreach ( $viewport_page_metrics as $page_metric ) {
+ if ( isset( $page_metric['timestamp'] ) && $page_metric['timestamp'] + $freshness_ttl < $current_time ) {
+ $needs_page_metrics = true;
+ break;
+ }
+ }
+ }
+ $needed_minimum_viewport_widths[] = array(
+ $minimum_viewport_width,
+ $needs_page_metrics,
+ );
+ }
+
+ return $needed_minimum_viewport_widths;
+}
+
+
+/**
+ * Checks whether there is a page metric needed for one of the breakpoints.
+ *
+ * @param array $needed_minimum_viewport_widths Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
+ * @return bool Whether a page metric is needed.
+ */
+function ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) {
+ foreach ( $needed_minimum_viewport_widths as list( $minimum_viewport_width, $is_needed ) ) {
+ if ( $is_needed ) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 7d48ced227..bcd90347a0 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -155,13 +155,22 @@ function ilo_register_endpoint() {
* @return WP_REST_Response|WP_Error Response.
*/
function ilo_handle_rest_request( WP_REST_Request $request ) {
+ $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths( $request->get_param( 'slug' ) );
+ if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
+ return new WP_Error(
+ 'no_page_metric_needed',
+ __( 'No page metric needed for any of the breakpoints.', 'performance-lab' ),
+ array( 'status' => 403 )
+ );
+ }
+
ilo_set_page_metric_storage_lock();
+ $new_page_metric = wp_array_slice_assoc( $request->get_json_params(), array( 'viewport', 'elements' ) );
- $page_metric = wp_array_slice_assoc( $request->get_json_params(), array( 'viewport', 'elements' ) );
- $result = ilo_store_page_metric(
+ $result = ilo_store_page_metric(
$request->get_param( 'url' ),
$request->get_param( 'slug' ),
- $page_metric
+ $new_page_metric
);
if ( $result instanceof WP_Error ) {
From 8c6b55984b7604ac01886bf8c87e8720748f7354 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 13 Nov 2023 17:21:49 -0800
Subject: [PATCH 38/62] Remove unused ilo_get_current_url()
---
.../storage/data.php | 43 -------------------
1 file changed, 43 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 1088de68b1..a544363d34 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -26,48 +26,6 @@ function ilo_get_page_metric_freshness_ttl() {
return (int) apply_filters( 'ilo_page_metric_freshness_ttl', DAY_IN_SECONDS );
}
-/**
- * Get the URL for the current request.
- *
- * This is essentially the REQUEST_URI prefixed by the scheme and host for the home URL.
- * This is needed in particular due to subdirectory installs.
- *
- * @return string Current URL.
- */
-function ilo_get_current_url() {
- $parsed_url = wp_parse_url( home_url() );
-
- if ( ! is_array( $parsed_url ) ) {
- $parsed_url = array();
- }
-
- if ( empty( $parsed_url['scheme'] ) ) {
- $parsed_url['scheme'] = is_ssl() ? 'https' : 'http';
- }
- if ( ! isset( $parsed_url['host'] ) ) {
- $parsed_url['host'] = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : 'localhost';
- }
-
- $current_url = $parsed_url['scheme'] . '://';
- if ( isset( $parsed_url['user'] ) ) {
- $current_url .= $parsed_url['user'];
- if ( isset( $parsed_url['pass'] ) ) {
- $current_url .= ':' . $parsed_url['pass'];
- }
- $current_url .= '@';
- }
- $current_url .= $parsed_url['host'];
- if ( isset( $parsed_url['port'] ) ) {
- $current_url .= ':' . $parsed_url['port'];
- }
- $current_url .= '/';
-
- if ( isset( $_SERVER['REQUEST_URI'] ) ) {
- $current_url .= ltrim( wp_unslash( $_SERVER['REQUEST_URI'] ), '/' );
- }
- return esc_url_raw( $current_url );
-}
-
/**
* Gets the normalized query vars for the current request.
*
@@ -290,7 +248,6 @@ function ilo_get_needed_minimum_viewport_widths( $slug ) {
return $needed_minimum_viewport_widths;
}
-
/**
* Checks whether there is a page metric needed for one of the breakpoints.
*
From 7e4064ab11f2befa4e8987e1149a7052019160bb Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 13 Nov 2023 17:40:14 -0800
Subject: [PATCH 39/62] Improve testability of
ilo_get_needed_minimum_viewport_widths()
---
.../image-loading-optimization/detection.php | 8 +++++++-
.../storage/data.php | 19 +++++++------------
.../storage/post-type.php | 13 +++++++++----
.../storage/rest-api.php | 8 +++++++-
4 files changed, 30 insertions(+), 18 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 2f96406e0f..8bc5bd373f 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -20,7 +20,13 @@ function ilo_print_detection_script() {
$slug = ilo_get_page_metrics_slug( $query_vars );
// Abort if we already have all the sample size we need for all breakpoints.
- $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths( $slug );
+ $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths(
+ ilo_get_page_metrics_data( $slug ),
+ time(),
+ ilo_get_breakpoint_max_widths(),
+ ilo_get_page_metrics_breakpoint_sample_size(),
+ ilo_get_page_metric_freshness_ttl()
+ );
if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return;
}
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index a544363d34..8a8573b7c6 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -212,20 +212,15 @@ static function ( $breakpoint ) {
/**
* Get needed minimum viewport widths.
*
- * @param string $slug Page metric slug.
+ * @param array $page_metrics Page metrics.
+ * @param int $current_time Current time.
+ * @param int[] $breakpoint_max_widths Breakpoint max widths.
+ * @param int $sample_size Sample size for viewports in a breakpoint.
+ * @param int $freshness_ttl Freshness TTL for a page metric.
* @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
*/
-function ilo_get_needed_minimum_viewport_widths( $slug ) {
- $data = ilo_get_page_metrics_data( $slug );
- if ( ! is_array( $data ) ) {
- $data = array();
- }
-
- $metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $data, ilo_get_breakpoint_max_widths() );
- $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
- $freshness_ttl = ilo_get_page_metric_freshness_ttl();
-
- $current_time = time();
+function ilo_get_needed_minimum_viewport_widths( $page_metrics, $current_time, $breakpoint_max_widths, $sample_size, $freshness_ttl ) {
+ $metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoint_max_widths );
$needed_minimum_viewport_widths = array();
foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $viewport_page_metrics ) {
$needs_page_metrics = false;
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index c9a0c173ba..b5b5a6db29 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -99,19 +99,24 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
}
/**
- * Parses post content in page metrics post.
+ * Gets page metrics for a slug.
+ *
+ * This is a convenience abstractions for lower-level functions.
+ *
+ * @see ilo_get_page_metrics_post()
+ * @see ilo_parse_stored_page_metrics()
*
* @param string $slug Page metrics slug.
- * @return array Page metrics data, or null if invalid.
+ * @return array Page metrics data, or empty array if invalid.
*/
function ilo_get_page_metrics_data( $slug ) {
$post = ilo_get_page_metrics_post( $slug );
if ( ! ( $post instanceof WP_Post ) ) {
- return null;
+ return array();
}
$data = ilo_parse_stored_page_metrics( $post );
if ( ! is_array( $data ) ) {
- return null;
+ return array();
}
return $data;
}
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index bcd90347a0..2e8a0518dd 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -155,7 +155,13 @@ function ilo_register_endpoint() {
* @return WP_REST_Response|WP_Error Response.
*/
function ilo_handle_rest_request( WP_REST_Request $request ) {
- $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths( $request->get_param( 'slug' ) );
+ $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths(
+ ilo_get_page_metrics_data( $request->get_param( 'slug' ) ),
+ time(),
+ ilo_get_breakpoint_max_widths(),
+ ilo_get_page_metrics_breakpoint_sample_size(),
+ ilo_get_page_metric_freshness_ttl()
+ );
if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return new WP_Error(
'no_page_metric_needed',
From a1f1aaa9e9261740380558af5c9901661465e6b5 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Mon, 13 Nov 2023 18:16:54 -0800
Subject: [PATCH 40/62] Opt for sessionStorage for storage lock for aborting
---
.../image-loading-optimization/detection.php | 6 +-
.../detection/detect.js | 87 ++++++++++++++++---
2 files changed, 76 insertions(+), 17 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 8bc5bd373f..287ef79425 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -31,11 +31,6 @@ function ilo_print_detection_script() {
return;
}
- // Abort if storage is locked.
- if ( ilo_is_page_metric_storage_locked() ) {
- return;
- }
-
$serve_time = ceil( microtime( true ) * 1000 );
/**
@@ -62,6 +57,7 @@ function ilo_print_detection_script() {
'pageMetricsSlug' => $slug,
'pageMetricsNonce' => ilo_get_page_metrics_storage_nonce( $slug ),
'neededMinimumViewportWidths' => $needed_minimum_viewport_widths,
+ 'storageLockTTL' => ilo_get_page_metric_storage_lock_ttl(),
);
wp_print_inline_script_tag(
sprintf(
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 5101f127df..11bc7d0c3b 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -5,6 +5,43 @@ const doc = win.document;
const consoleLogPrefix = '[Image Loading Optimization]';
+const storageLockTimeSessionKey = 'iloStorageLockTime';
+
+/**
+ * Checks whether storage is locked.
+ *
+ * @param {number} currentTime - Current time in milliseconds.
+ * @param {number} storageLockTTL - Storage lock TTL in seconds.
+ * @return {boolean} Whether storage is locked.
+ */
+function isStorageLocked( currentTime, storageLockTTL ) {
+ try {
+ const storageLockTime = parseInt(
+ sessionStorage.getItem( storageLockTimeSessionKey )
+ );
+ return (
+ ! isNaN( storageLockTime ) &&
+ currentTime < storageLockTime + storageLockTTL * 1000
+ );
+ } catch ( e ) {
+ return false;
+ }
+}
+
+/**
+ * Set the storage lock.
+ *
+ * @param {number} currentTime - Current time in milliseconds.
+ */
+function setStorageLock( currentTime ) {
+ try {
+ sessionStorage.setItem(
+ storageLockTimeSessionKey,
+ String( currentTime )
+ );
+ } catch ( e ) {}
+}
+
/**
* Log a message.
*
@@ -117,18 +154,28 @@ function isViewportNeeded( viewportWidth, neededMinimumViewportWidths ) {
return lastWasNeeded;
}
+/**
+ * Gets the current time in milliseconds.
+ *
+ * @return {number} Current time in milliseconds.
+ */
+function getCurrentTime() {
+ return new Date().valueOf();
+}
+
/**
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
*
- * @param {Object} args Args.
- * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
- * @param {number} args.detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
- * @param {boolean} args.isDebug Whether to show debug messages.
- * @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.pageMetricsSlug Slug for page metrics.
- * @param {string} args.pageMetricsNonce Nonce for page metrics storage.
- * @param {Array} args.neededMinimumViewportWidths Needed minimum viewport widths for page metrics.
+ * @param {Object} args Args.
+ * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
+ * @param {number} args.detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
+ * @param {boolean} args.isDebug Whether to show debug messages.
+ * @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.pageMetricsSlug Slug for page metrics.
+ * @param {string} args.pageMetricsNonce Nonce for page metrics storage.
+ * @param {Array[]} args.neededMinimumViewportWidths Needed minimum viewport widths for page metrics.
+ * @param {number} args.storageLockTTL The TTL (in seconds) for the page metric storage lock.
*/
export default async function detect( {
serveTime,
@@ -138,12 +185,23 @@ export default async function detect( {
restApiNonce,
pageMetricsSlug,
pageMetricsNonce,
- neededMinimumViewportWidths, // TODO: The name is not great here.
+ neededMinimumViewportWidths,
+ storageLockTTL,
} ) {
- const runTime = new Date().valueOf();
+ const currentTime = getCurrentTime();
+
+ // As an alternative to this, the ilo_print_detection_script() function can short-circuit if the
+ // ilo_is_page_metric_storage_locked() function returns true. However, the downside with that is page caching could
+ // result in metrics being missed being gathered when a user navigates around a site and primes the page cache.
+ if ( isStorageLocked( currentTime, storageLockTTL ) ) {
+ if ( isDebug ) {
+ warn( 'Aborted detection due to storage being locked.' );
+ }
+ return;
+ }
// Abort running detection logic if it was served in a cached page.
- if ( runTime - serveTime > detectionTimeWindow ) {
+ if ( currentTime - serveTime > detectionTimeWindow ) {
if ( isDebug ) {
warn(
'Aborted detection due to being outside detection time window.'
@@ -351,6 +409,11 @@ export default async function detect( {
},
body: JSON.stringify( pageMetrics ),
} );
+
+ if ( response.status === 200 ) {
+ setStorageLock( getCurrentTime() );
+ }
+
if ( isDebug ) {
const body = await response.json();
if ( response.status === 200 ) {
From acf17f97e6ef66a1a499fa04f47efc2d471a02a4 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 10:33:22 -0800
Subject: [PATCH 41/62] Ensure grouped page metrics are sorted by timestamp
before unshifting
---
.../image-loading-optimization/storage/data.php | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 8a8573b7c6..e5294c7380 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -110,6 +110,18 @@ function ilo_unshift_page_metrics( $page_metrics, $validated_page_metric ) {
foreach ( $grouped_page_metrics as &$breakpoint_page_metrics ) {
if ( count( $breakpoint_page_metrics ) > $sample_size ) {
+
+ // Sort page metrics in descending order by timestamp.
+ usort(
+ $breakpoint_page_metrics,
+ static function ( $a, $b ) {
+ if ( ! isset( $a['timestamp'] ) || ! isset( $b['timestamp'] ) ) {
+ return 0;
+ }
+ return $b['timestamp'] <=> $a['timestamp'];
+ }
+ );
+
$breakpoint_page_metrics = array_slice( $breakpoint_page_metrics, 0, $sample_size );
}
}
From 6cd8198532b7076770d93b2003364949d0d3030f Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 10:36:18 -0800
Subject: [PATCH 42/62] Use microtime(true) instead of time()
---
.../images/image-loading-optimization/detection.php | 7 +++----
.../image-loading-optimization/detection/detect.js | 4 ++--
.../image-loading-optimization/storage/data.php | 2 +-
.../image-loading-optimization/storage/lock.php | 12 ++++++------
.../image-loading-optimization/storage/post-type.php | 2 +-
.../image-loading-optimization/storage/rest-api.php | 2 +-
6 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 287ef79425..ac251f83b9 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -18,11 +18,12 @@
function ilo_print_detection_script() {
$query_vars = ilo_get_normalized_query_vars();
$slug = ilo_get_page_metrics_slug( $query_vars );
+ $microtime = microtime( true );
// Abort if we already have all the sample size we need for all breakpoints.
$needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths(
ilo_get_page_metrics_data( $slug ),
- time(),
+ $microtime,
ilo_get_breakpoint_max_widths(),
ilo_get_page_metrics_breakpoint_sample_size(),
ilo_get_page_metric_freshness_ttl()
@@ -31,8 +32,6 @@ function ilo_print_detection_script() {
return;
}
- $serve_time = ceil( microtime( true ) * 1000 );
-
/**
* Filters the time window between serve time and run time in which loading detection is allowed to run.
*
@@ -49,7 +48,7 @@ function ilo_print_detection_script() {
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
$detect_args = array(
- 'serveTime' => $serve_time,
+ 'serveTime' => $microtime * 1000, // In milliseconds for comparison with `Date.now()` in JavaScript.
'detectionTimeWindow' => $detection_time_window,
'isDebug' => WP_DEBUG,
'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 11bc7d0c3b..803919f28c 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -160,14 +160,14 @@ function isViewportNeeded( viewportWidth, neededMinimumViewportWidths ) {
* @return {number} Current time in milliseconds.
*/
function getCurrentTime() {
- return new Date().valueOf();
+ return Date.now();
}
/**
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
*
* @param {Object} args Args.
- * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `ceil( microtime( true ) * 1000 )`.
+ * @param {number} args.serveTime The serve time of the page in milliseconds from PHP via `microtime( true ) * 1000`.
* @param {number} args.detectionTimeWindow The number of milliseconds between now and when the page was first generated in which detection should proceed.
* @param {boolean} args.isDebug Whether to show debug messages.
* @param {string} args.restApiEndpoint URL for where to send the detection data.
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index e5294c7380..e1a747310f 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -225,7 +225,7 @@ static function ( $breakpoint ) {
* Get needed minimum viewport widths.
*
* @param array $page_metrics Page metrics.
- * @param int $current_time Current time.
+ * @param float $current_time Current time as returned by microtime(true).
* @param int[] $breakpoint_max_widths Breakpoint max widths.
* @param int $sample_size Sample size for viewports in a breakpoint.
* @param int $freshness_ttl Freshness TTL for a page metric.
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index 1ae536397b..f0084b9d38 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -11,9 +11,9 @@
}
/**
- * Gets the TTL for the page metric storage lock.
+ * Gets the TTL (in seconds) for the page metric storage lock.
*
- * @return int TTL.
+ * @return int TTL in seconds.
*/
function ilo_get_page_metric_storage_lock_ttl() {
@@ -47,7 +47,7 @@ function ilo_set_page_metric_storage_lock() {
if ( 0 === $ttl ) {
delete_transient( $key );
} else {
- set_transient( $key, time(), $ttl );
+ set_transient( $key, microtime( true ), $ttl );
}
}
@@ -61,9 +61,9 @@ function ilo_is_page_metric_storage_locked() {
if ( 0 === $ttl ) {
return false;
}
- $locked_time = (int) get_transient( ilo_get_page_metric_storage_lock_transient_key() );
- if ( 0 === $locked_time ) {
+ $locked_time = get_transient( ilo_get_page_metric_storage_lock_transient_key() );
+ if ( false === $locked_time ) {
return false;
}
- return time() - $locked_time < $ttl;
+ return microtime( true ) - floatval( $locked_time ) < $ttl;
}
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index b5b5a6db29..3654cccd1e 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -140,7 +140,7 @@ function ilo_get_page_metrics_data( $slug ) {
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
function ilo_store_page_metric( $url, $slug, array $validated_page_metric ) {
- $validated_page_metric['timestamp'] = time();
+ $validated_page_metric['timestamp'] = microtime( true );
// TODO: What about storing a version identifier?
$post_data = array(
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 2e8a0518dd..0c84c79f51 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -157,7 +157,7 @@ function ilo_register_endpoint() {
function ilo_handle_rest_request( WP_REST_Request $request ) {
$needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths(
ilo_get_page_metrics_data( $request->get_param( 'slug' ) ),
- time(),
+ microtime( true ),
ilo_get_breakpoint_max_widths(),
ilo_get_page_metrics_breakpoint_sample_size(),
ilo_get_page_metric_freshness_ttl()
From 9d6707c54ed3e04cd6378f49187e30b54e14337e Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 10:42:03 -0800
Subject: [PATCH 43/62] Reduce code duplication with helper function
---
.../image-loading-optimization/detection.php | 8 +------
.../storage/data.php | 21 +++++++++++++++++++
.../storage/rest-api.php | 8 +------
3 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index ac251f83b9..f7e929480d 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -21,13 +21,7 @@ function ilo_print_detection_script() {
$microtime = microtime( true );
// Abort if we already have all the sample size we need for all breakpoints.
- $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths(
- ilo_get_page_metrics_data( $slug ),
- $microtime,
- ilo_get_breakpoint_max_widths(),
- ilo_get_page_metrics_breakpoint_sample_size(),
- ilo_get_page_metric_freshness_ttl()
- );
+ $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths_now_for_slug( $slug );
if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return;
}
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index e1a747310f..4ca3081001 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -255,6 +255,27 @@ function ilo_get_needed_minimum_viewport_widths( $page_metrics, $current_time, $
return $needed_minimum_viewport_widths;
}
+
+/**
+ * Get needed minimum viewport widths by slug for the current time.
+ *
+ * This is a convenience wrapper on top of ilo_get_needed_minimum_viewport_widths() to reduce code duplication.
+ *
+ * @see ilo_get_needed_minimum_viewport_widths()
+ *
+ * @param string $slug Page metrics slug.
+ * @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
+ */
+function ilo_get_needed_minimum_viewport_widths_now_for_slug( $slug ) {
+ return ilo_get_needed_minimum_viewport_widths(
+ ilo_get_page_metrics_data( $slug ),
+ microtime( true ),
+ ilo_get_breakpoint_max_widths(),
+ ilo_get_page_metrics_breakpoint_sample_size(),
+ ilo_get_page_metric_freshness_ttl()
+ );
+}
+
/**
* Checks whether there is a page metric needed for one of the breakpoints.
*
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 0c84c79f51..9feea484df 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -155,13 +155,7 @@ function ilo_register_endpoint() {
* @return WP_REST_Response|WP_Error Response.
*/
function ilo_handle_rest_request( WP_REST_Request $request ) {
- $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths(
- ilo_get_page_metrics_data( $request->get_param( 'slug' ) ),
- microtime( true ),
- ilo_get_breakpoint_max_widths(),
- ilo_get_page_metrics_breakpoint_sample_size(),
- ilo_get_page_metric_freshness_ttl()
- );
+ $needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths_now_for_slug( $request->get_param( 'slug' ) );
if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return new WP_Error(
'no_page_metric_needed',
From 99ef2996caf805cf2ca0d253374ee66e901cbf84 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 11:28:41 -0800
Subject: [PATCH 44/62] Prevent optimizing search results and add
ilo_can_optimize_response filter
---
.../image-loading-optimization/detection.php | 6 +++--
.../storage/data.php | 23 ++++++++++++++++---
2 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index f7e929480d..a8e014e0f5 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -12,10 +12,12 @@
/**
* Prints the script for detecting loaded images and the LCP element.
- *
- * @todo This script should not be printed if the page was requested with non-removal (non-canonical) query args.
*/
function ilo_print_detection_script() {
+ if ( ! ilo_can_optimize_response() ) {
+ return;
+ }
+
$query_vars = ilo_get_normalized_query_vars();
$slug = ilo_get_page_metrics_slug( $query_vars );
$microtime = microtime( true );
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 4ca3081001..7c4c97b6b3 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -26,6 +26,26 @@ function ilo_get_page_metric_freshness_ttl() {
return (int) apply_filters( 'ilo_page_metric_freshness_ttl', DAY_IN_SECONDS );
}
+/**
+ * Determines whether the current response can be optimized.
+ *
+ * Only search results are not eligible by default for optimization. This is because there is no predictability in
+ * whether posts in the loop will have featured images assigned or not. If a theme template for search results doesn't
+ * even show featured images, then this isn't an issue.
+ *
+ * @return bool Whether response can be optimized.
+ */
+function ilo_can_optimize_response() {
+ $able = ! is_search();
+
+ /**
+ * Filters whether the current response can be optimized.
+ *
+ * @param bool $able Whether response can be optimized.
+ */
+ return (bool) apply_filters( 'ilo_can_optimize_response', $able );
+}
+
/**
* Gets the normalized query vars for the current request.
*
@@ -45,8 +65,6 @@ function ilo_get_normalized_query_vars() {
$normalized_query_vars = array(
'error' => 404,
);
- } elseif ( array_key_exists( 's', $normalized_query_vars ) ) {
- $normalized_query_vars['s'] = '...';
}
return $normalized_query_vars;
@@ -255,7 +273,6 @@ function ilo_get_needed_minimum_viewport_widths( $page_metrics, $current_time, $
return $needed_minimum_viewport_widths;
}
-
/**
* Get needed minimum viewport widths by slug for the current time.
*
From 8cb2dcf73ad67a21733f668c8f8554b0bc97baf0 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 11:34:11 -0800
Subject: [PATCH 45/62] Add TODO for ilo_get_normalized_query_vars
---
modules/images/image-loading-optimization/storage/data.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 7c4c97b6b3..b0c2662e38 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -51,6 +51,8 @@ function ilo_can_optimize_response() {
*
* This is used as a cache key for stored page metrics.
*
+ * TODO: For non-singular requests, consider adding the post IDs from The Loop to ensure publishing a new post will invalidate the cache.
+ *
* @return array Normalized query vars.
*/
function ilo_get_normalized_query_vars() {
From a4ffbaef191b7d2b859c0a8bf08834fd57defedf Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 12:55:45 -0800
Subject: [PATCH 46/62] Reference JSON Schema for defintion of function arg
array shape
---
.../image-loading-optimization/storage/data.php | 2 +-
.../image-loading-optimization/storage/post-type.php | 12 +-----------
2 files changed, 2 insertions(+), 12 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index b0c2662e38..804452bf24 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -119,7 +119,7 @@ function ilo_verify_page_metrics_storage_nonce( $nonce, $slug ) {
* Unshift a new page metric onto an array of page metrics.
*
* @param array $page_metrics Page metrics.
- * @param array $validated_page_metric Validated page metric.
+ * @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return array Updated page metrics.
*/
function ilo_unshift_page_metrics( $page_metrics, $validated_page_metric ) {
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index 3654cccd1e..5c030941c3 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -124,19 +124,9 @@ function ilo_get_page_metrics_data( $slug ) {
/**
* Stores page metric by merging it with the other page metrics for a given URL.
*
- * The $validated_page_metric parameter has the following array shape:
- *
- * {
- * 'viewport': array{
- * 'width': int,
- * 'height': int
- * },
- * 'elements': array
- * }
- *
* @param string $url URL for the page metrics. This is used purely as metadata.
* @param string $slug Page metrics slug (computed from query vars).
- * @param array $validated_page_metric Page metric, already validated by REST API.
+ * @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
function ilo_store_page_metric( $url, $slug, array $validated_page_metric ) {
From ec6e17757c51b558f5bda72c56361af89e52ce4c Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 13:17:42 -0800
Subject: [PATCH 47/62] Add array type declarations
---
.../images/image-loading-optimization/storage/data.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 804452bf24..73283a4066 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -80,7 +80,7 @@ function ilo_get_normalized_query_vars() {
* @param array $query_vars Normalized query vars.
* @return string Slug.
*/
-function ilo_get_page_metrics_slug( $query_vars ) {
+function ilo_get_page_metrics_slug( array $query_vars ) {
return md5( wp_json_encode( $query_vars ) );
}
@@ -122,7 +122,7 @@ function ilo_verify_page_metrics_storage_nonce( $nonce, $slug ) {
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return array Updated page metrics.
*/
-function ilo_unshift_page_metrics( $page_metrics, $validated_page_metric ) {
+function ilo_unshift_page_metrics( array $page_metrics, array $validated_page_metric ) {
array_unshift( $page_metrics, $validated_page_metric );
$breakpoints = ilo_get_breakpoint_max_widths();
$sample_size = ilo_get_page_metrics_breakpoint_sample_size();
@@ -251,7 +251,7 @@ static function ( $breakpoint ) {
* @param int $freshness_ttl Freshness TTL for a page metric.
* @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
*/
-function ilo_get_needed_minimum_viewport_widths( $page_metrics, $current_time, $breakpoint_max_widths, $sample_size, $freshness_ttl ) {
+function ilo_get_needed_minimum_viewport_widths( array $page_metrics, $current_time, array $breakpoint_max_widths, $sample_size, $freshness_ttl ) {
$metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoint_max_widths );
$needed_minimum_viewport_widths = array();
foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $viewport_page_metrics ) {
@@ -301,7 +301,7 @@ function ilo_get_needed_minimum_viewport_widths_now_for_slug( $slug ) {
* @param array $needed_minimum_viewport_widths Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
* @return bool Whether a page metric is needed.
*/
-function ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) {
+function ilo_needs_page_metric_for_breakpoint( array $needed_minimum_viewport_widths ) {
foreach ( $needed_minimum_viewport_widths as list( $minimum_viewport_width, $is_needed ) ) {
if ( $is_needed ) {
return true;
From 817d6825b6542f6bb2e0cbfeefaf4e93d00dd504 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 14:11:15 -0800
Subject: [PATCH 48/62] Add PHP type declarations
---
.../image-loading-optimization/detection.php | 2 +-
.../image-loading-optimization/hooks.php | 10 +++---
.../storage/data.php | 34 +++++++++----------
.../storage/lock.php | 8 ++---
.../storage/post-type.php | 10 +++---
.../storage/rest-api.php | 4 +--
.../image-loading-optimization/load-tests.php | 2 +-
7 files changed, 35 insertions(+), 35 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index a8e014e0f5..d445b1a230 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -13,7 +13,7 @@
/**
* Prints the script for detecting loaded images and the LCP element.
*/
-function ilo_print_detection_script() {
+function ilo_print_detection_script() /*: void (in PHP 7.1) */ {
if ( ! ilo_can_optimize_response() ) {
return;
}
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 1f002dab9d..60565a6119 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -28,19 +28,19 @@
* @since n.e.x.t
* @link https://core.trac.wordpress.org/ticket/43258
*
- * @param mixed $passthrough Optional. Filter value. Default null.
- * @return mixed Unmodified value of $passthrough.
+ * @param string $passthrough Optional. Filter value. Default null.
+ * @return string Unmodified value of $passthrough.
*/
-function ilo_buffer_output( $passthrough = null ) {
+function ilo_buffer_output( string $passthrough ): string {
ob_start(
- static function ( $output ) {
+ static function ( string $output ): string {
/**
* Filters the template output buffer prior to sending to the client.
*
* @param string $output Output buffer.
* @return string Filtered output buffer.
*/
- return apply_filters( 'perflab_template_output_buffer', $output );
+ return (string) apply_filters( 'perflab_template_output_buffer', $output );
}
);
return $passthrough;
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 73283a4066..e91dc17f2a 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -17,7 +17,7 @@
*
* @return int Expiration TTL in seconds.
*/
-function ilo_get_page_metric_freshness_ttl() {
+function ilo_get_page_metric_freshness_ttl(): int {
/**
* Filters the freshness age (TTL) for a given page metric.
*
@@ -35,7 +35,7 @@ function ilo_get_page_metric_freshness_ttl() {
*
* @return bool Whether response can be optimized.
*/
-function ilo_can_optimize_response() {
+function ilo_can_optimize_response(): bool {
$able = ! is_search();
/**
@@ -55,7 +55,7 @@ function ilo_can_optimize_response() {
*
* @return array Normalized query vars.
*/
-function ilo_get_normalized_query_vars() {
+function ilo_get_normalized_query_vars(): array {
global $wp;
// Note that the order of this array is naturally normalized since it is
@@ -80,7 +80,7 @@ function ilo_get_normalized_query_vars() {
* @param array $query_vars Normalized query vars.
* @return string Slug.
*/
-function ilo_get_page_metrics_slug( array $query_vars ) {
+function ilo_get_page_metrics_slug( array $query_vars ): string {
return md5( wp_json_encode( $query_vars ) );
}
@@ -95,7 +95,7 @@ function ilo_get_page_metrics_slug( array $query_vars ) {
* @param string $slug Page metrics slug.
* @return string Nonce.
*/
-function ilo_get_page_metrics_storage_nonce( $slug ) {
+function ilo_get_page_metrics_storage_nonce( string $slug ): string {
return wp_create_nonce( "store_page_metrics:{$slug}" );
}
@@ -107,12 +107,12 @@ function ilo_get_page_metrics_storage_nonce( $slug ) {
*
* @param string $nonce Page metrics storage nonce.
* @param string $slug Page metrics slug.
- * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago,
- * 2 if the nonce is valid and generated between 12-24 hours ago.
- * False if the nonce is invalid.
+ * @return int 1 if the nonce is valid and generated between 0-12 hours ago,
+ * 2 if the nonce is valid and generated between 12-24 hours ago.
+ * 0 if the nonce is invalid.
*/
-function ilo_verify_page_metrics_storage_nonce( $nonce, $slug ) {
- return wp_verify_nonce( $nonce, "store_page_metrics:{$slug}" );
+function ilo_verify_page_metrics_storage_nonce( string $nonce, string $slug ): int {
+ return (int) wp_verify_nonce( $nonce, "store_page_metrics:{$slug}" );
}
/**
@@ -122,7 +122,7 @@ function ilo_verify_page_metrics_storage_nonce( $nonce, $slug ) {
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return array Updated page metrics.
*/
-function ilo_unshift_page_metrics( array $page_metrics, array $validated_page_metric ) {
+function ilo_unshift_page_metrics( array $page_metrics, array $validated_page_metric ): array {
array_unshift( $page_metrics, $validated_page_metric );
$breakpoints = ilo_get_breakpoint_max_widths();
$sample_size = ilo_get_page_metrics_breakpoint_sample_size();
@@ -163,7 +163,7 @@ static function ( $a, $b ) {
*
* @return int[] Breakpoint max widths, sorted in ascending order.
*/
-function ilo_get_breakpoint_max_widths() {
+function ilo_get_breakpoint_max_widths(): array {
/**
* Filters the breakpoint max widths to group page metrics for various viewports.
@@ -190,7 +190,7 @@ static function ( $breakpoint_max_width ) {
*
* @return int Sample size.
*/
-function ilo_get_page_metrics_breakpoint_sample_size() {
+function ilo_get_page_metrics_breakpoint_sample_size(): int {
/**
* Filters the sample size for a breakpoint's page metrics on a given URL.
*
@@ -209,7 +209,7 @@ function ilo_get_page_metrics_breakpoint_sample_size() {
* the breakpoints reflect the max inclusive boundaries whereas the return value is the groups of page
* metrics with viewports on either side of the breakpoint boundaries.
*/
-function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ) {
+function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ): array {
// Convert breakpoint max widths into viewport minimum widths.
$viewport_minimum_widths = array_map(
@@ -251,7 +251,7 @@ static function ( $breakpoint ) {
* @param int $freshness_ttl Freshness TTL for a page metric.
* @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
*/
-function ilo_get_needed_minimum_viewport_widths( array $page_metrics, $current_time, array $breakpoint_max_widths, $sample_size, $freshness_ttl ) {
+function ilo_get_needed_minimum_viewport_widths( array $page_metrics, float $current_time, array $breakpoint_max_widths, int $sample_size, int $freshness_ttl ): array {
$metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoint_max_widths );
$needed_minimum_viewport_widths = array();
foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $viewport_page_metrics ) {
@@ -285,7 +285,7 @@ function ilo_get_needed_minimum_viewport_widths( array $page_metrics, $current_t
* @param string $slug Page metrics slug.
* @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
*/
-function ilo_get_needed_minimum_viewport_widths_now_for_slug( $slug ) {
+function ilo_get_needed_minimum_viewport_widths_now_for_slug( string $slug ): array {
return ilo_get_needed_minimum_viewport_widths(
ilo_get_page_metrics_data( $slug ),
microtime( true ),
@@ -301,7 +301,7 @@ function ilo_get_needed_minimum_viewport_widths_now_for_slug( $slug ) {
* @param array $needed_minimum_viewport_widths Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
* @return bool Whether a page metric is needed.
*/
-function ilo_needs_page_metric_for_breakpoint( array $needed_minimum_viewport_widths ) {
+function ilo_needs_page_metric_for_breakpoint( array $needed_minimum_viewport_widths ): bool {
foreach ( $needed_minimum_viewport_widths as list( $minimum_viewport_width, $is_needed ) ) {
if ( $is_needed ) {
return true;
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index f0084b9d38..b9794394f5 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -15,7 +15,7 @@
*
* @return int TTL in seconds.
*/
-function ilo_get_page_metric_storage_lock_ttl() {
+function ilo_get_page_metric_storage_lock_ttl(): int {
/**
* Filters how long a given IP is locked from submitting another metric-storage REST API request.
@@ -33,7 +33,7 @@ function ilo_get_page_metric_storage_lock_ttl() {
* @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
* @return string Transient key.
*/
-function ilo_get_page_metric_storage_lock_transient_key() {
+function ilo_get_page_metric_storage_lock_transient_key(): string {
$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
}
@@ -41,7 +41,7 @@ function ilo_get_page_metric_storage_lock_transient_key() {
/**
* Sets page metric storage lock (for the current IP).
*/
-function ilo_set_page_metric_storage_lock() {
+function ilo_set_page_metric_storage_lock() /*: void (in PHP 7.1) */ {
$ttl = ilo_get_page_metric_storage_lock_ttl();
$key = ilo_get_page_metric_storage_lock_transient_key();
if ( 0 === $ttl ) {
@@ -56,7 +56,7 @@ function ilo_set_page_metric_storage_lock() {
*
* @return bool Whether locked.
*/
-function ilo_is_page_metric_storage_locked() {
+function ilo_is_page_metric_storage_locked(): bool {
$ttl = ilo_get_page_metric_storage_lock_ttl();
if ( 0 === $ttl ) {
return false;
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index 5c030941c3..1e0be232e6 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -17,7 +17,7 @@
*
* This the configuration for this post type is similar to the oembed_cache in core.
*/
-function ilo_register_page_metrics_post_type() {
+function ilo_register_page_metrics_post_type() /*: void (in PHP 7.1) */ {
register_post_type(
ILO_PAGE_METRICS_POST_TYPE,
array(
@@ -43,7 +43,7 @@ function ilo_register_page_metrics_post_type() {
* @param string $slug Page metrics slug.
* @return WP_Post|null Post object if exists.
*/
-function ilo_get_page_metrics_post( $slug ) {
+function ilo_get_page_metrics_post( string $slug ) /*: ?WP_Post (in PHP 7.1) */ {
$post_query = new WP_Query(
array(
'post_type' => ILO_PAGE_METRICS_POST_TYPE,
@@ -72,7 +72,7 @@ function ilo_get_page_metrics_post( $slug ) {
* @param WP_Post $post Page metrics post.
* @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
*/
-function ilo_parse_stored_page_metrics( WP_Post $post ) {
+function ilo_parse_stored_page_metrics( WP_Post $post ) /*: array|WP_Error (in PHP 8) */ {
$page_metrics = json_decode( $post->post_content, true );
if ( json_last_error() ) {
return new WP_Error(
@@ -109,7 +109,7 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) {
* @param string $slug Page metrics slug.
* @return array Page metrics data, or empty array if invalid.
*/
-function ilo_get_page_metrics_data( $slug ) {
+function ilo_get_page_metrics_data( string $slug ): array {
$post = ilo_get_page_metrics_post( $slug );
if ( ! ( $post instanceof WP_Post ) ) {
return array();
@@ -129,7 +129,7 @@ function ilo_get_page_metrics_data( $slug ) {
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
-function ilo_store_page_metric( $url, $slug, array $validated_page_metric ) {
+function ilo_store_page_metric( string $url, string $slug, array $validated_page_metric ) /*: int|WP_Error (in PHP 8) */ {
$validated_page_metric['timestamp'] = microtime( true );
// TODO: What about storing a version identifier?
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 9feea484df..71c1f5300f 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -17,7 +17,7 @@
/**
* Register endpoint for storage of page metric.
*/
-function ilo_register_endpoint() {
+function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
$dom_rect_schema = array(
'type' => 'object',
@@ -154,7 +154,7 @@ function ilo_register_endpoint() {
* @param WP_REST_Request $request Request.
* @return WP_REST_Response|WP_Error Response.
*/
-function ilo_handle_rest_request( WP_REST_Request $request ) {
+function ilo_handle_rest_request( WP_REST_Request $request ) /*: WP_REST_Response|WP_Error (in PHP 8) */ {
$needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths_now_for_slug( $request->get_param( 'slug' ) );
if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return new WP_Error(
diff --git a/tests/modules/images/image-loading-optimization/load-tests.php b/tests/modules/images/image-loading-optimization/load-tests.php
index 3fb443947d..1015d12b7d 100644
--- a/tests/modules/images/image-loading-optimization/load-tests.php
+++ b/tests/modules/images/image-loading-optimization/load-tests.php
@@ -42,7 +42,7 @@ function ( $buffer ) use ( $original, $expected ) {
);
$original_ob_level = ob_get_level();
- ilo_buffer_output();
+ ilo_buffer_output( '' );
$this->assertSame( $original_ob_level + 1, ob_get_level(), 'Expected call to ob_start().' );
echo $original;
From caaef872dbd05595a730a91ad9efee2ad47caefd Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 18:10:24 -0800
Subject: [PATCH 49/62] Fix placement of ilo_breakpoint_max_widths filter
---
.../images/image-loading-optimization/storage/data.php | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index e91dc17f2a..96f642c0a9 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -165,15 +165,15 @@ static function ( $a, $b ) {
*/
function ilo_get_breakpoint_max_widths(): array {
- /**
- * Filters the breakpoint max widths to group page metrics for various viewports.
- *
- * @param int[] $breakpoint_max_widths Max widths for viewport breakpoints.
- */
$breakpoint_max_widths = array_map(
static function ( $breakpoint_max_width ) {
return (int) $breakpoint_max_width;
},
+ /**
+ * Filters the breakpoint max widths to group page metrics for various viewports.
+ *
+ * @param int[] $breakpoint_max_widths Max widths for viewport breakpoints.
+ */
(array) apply_filters( 'ilo_breakpoint_max_widths', array( 480 ) )
);
From eadec5592a8b103e3522fa9f29b75e5b8dcea2e9 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 18:11:43 -0800
Subject: [PATCH 50/62] Use 3rd person singular for function phpdoc
Co-authored-by: Felix Arntz
---
modules/images/image-loading-optimization/storage/data.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 96f642c0a9..b90953e45d 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -85,7 +85,7 @@ function ilo_get_page_metrics_slug( array $query_vars ): string {
}
/**
- * Compute nonce for storing page metrics for a specific slug.
+ * Computes nonce for storing page metrics for a specific slug.
*
* This is used in the REST API to authenticate the storage of new page metrics from a given URL.
*
@@ -100,7 +100,7 @@ function ilo_get_page_metrics_storage_nonce( string $slug ): string {
}
/**
- * Verify nonce for storing page metrics for a specific slug.
+ * Verifies nonce for storing page metrics for a specific slug.
*
* @see wp_verify_nonce()
* @see ilo_get_page_metrics_storage_nonce()
@@ -116,7 +116,7 @@ function ilo_verify_page_metrics_storage_nonce( string $nonce, string $slug ): i
}
/**
- * Unshift a new page metric onto an array of page metrics.
+ * Unshifts a new page metric onto an array of page metrics.
*
* @param array $page_metrics Page metrics.
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
From 02dbb359fa726ed889475f9cad66bb362f9b36e8 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 18:15:25 -0800
Subject: [PATCH 51/62] Use 3rd person singular in phpdoc for additional
functions
---
modules/images/image-loading-optimization/storage/data.php | 4 ++--
.../images/image-loading-optimization/storage/post-type.php | 4 ++--
.../images/image-loading-optimization/storage/rest-api.php | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index b90953e45d..25d3bca571 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -242,7 +242,7 @@ static function ( $breakpoint ) {
}
/**
- * Get needed minimum viewport widths.
+ * Gets needed minimum viewport widths.
*
* @param array $page_metrics Page metrics.
* @param float $current_time Current time as returned by microtime(true).
@@ -276,7 +276,7 @@ function ilo_get_needed_minimum_viewport_widths( array $page_metrics, float $cur
}
/**
- * Get needed minimum viewport widths by slug for the current time.
+ * Gets needed minimum viewport widths by slug for the current time.
*
* This is a convenience wrapper on top of ilo_get_needed_minimum_viewport_widths() to reduce code duplication.
*
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index 1e0be232e6..c3a840e240 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -13,7 +13,7 @@
const ILO_PAGE_METRICS_POST_TYPE = 'ilo_page_metrics';
/**
- * Register post type for page metrics storage.
+ * Registers post type for page metrics storage.
*
* This the configuration for this post type is similar to the oembed_cache in core.
*/
@@ -38,7 +38,7 @@ function ilo_register_page_metrics_post_type() /*: void (in PHP 7.1) */ {
add_action( 'init', 'ilo_register_page_metrics_post_type' );
/**
- * Get page metrics post.
+ * Gets page metrics post.
*
* @param string $slug Page metrics slug.
* @return WP_Post|null Post object if exists.
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 71c1f5300f..9726f04087 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -15,7 +15,7 @@
const ILO_PAGE_METRICS_ROUTE = '/page-metrics';
/**
- * Register endpoint for storage of page metric.
+ * Registers endpoint for storage of page metric.
*/
function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
@@ -149,7 +149,7 @@ function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
add_action( 'rest_api_init', 'ilo_register_endpoint' );
/**
- * Handle REST API request to store metrics.
+ * Handles REST API request to store metrics.
*
* @param WP_REST_Request $request Request.
* @return WP_REST_Response|WP_Error Response.
From 06d7fad7f75e02a0f3b2e572ba6d289065515fd1 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 18:17:39 -0800
Subject: [PATCH 52/62] Account for isStorageLocked with storageLockTTL of 0
Co-authored-by: Felix Arntz
---
modules/images/image-loading-optimization/detection/detect.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 803919f28c..58a0034376 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -15,6 +15,10 @@ const storageLockTimeSessionKey = 'iloStorageLockTime';
* @return {boolean} Whether storage is locked.
*/
function isStorageLocked( currentTime, storageLockTTL ) {
+ if ( ! storageLockTTL ) {
+ return false;
+ }
+
try {
const storageLockTime = parseInt(
sessionStorage.getItem( storageLockTimeSessionKey )
From da0b4208b73694d1e1800cb04178140b9837b094 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 19:55:14 -0800
Subject: [PATCH 53/62] Ensure storage lock TTL is at least zero and add filter
example
---
.../detection/detect.js | 2 +-
.../image-loading-optimization/storage/lock.php | 15 ++++++++++++---
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 58a0034376..96d0587a4b 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -15,7 +15,7 @@ const storageLockTimeSessionKey = 'iloStorageLockTime';
* @return {boolean} Whether storage is locked.
*/
function isStorageLocked( currentTime, storageLockTTL ) {
- if ( ! storageLockTTL ) {
+ if ( storageLockTTL === 0 ) {
return false;
}
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index b9794394f5..eb6b21ca68 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -13,18 +13,24 @@
/**
* Gets the TTL (in seconds) for the page metric storage lock.
*
- * @return int TTL in seconds.
+ * @return int TTL in seconds, greater than or equal to zero.
*/
function ilo_get_page_metric_storage_lock_ttl(): int {
/**
* Filters how long a given IP is locked from submitting another metric-storage REST API request.
*
- * Filtering the TTL to zero will disable any metric storage locking. This is useful during development.
+ * Filtering the TTL to zero will disable any metric storage locking. This is useful, for example, to disable
+ * locking when a user is logged-in with code like the following:
+ *
+ * add_filter( 'ilo_metrics_storage_lock_ttl', static function ( $ttl ) {
+ * return is_user_logged_in() ? 0 : $ttl;
+ * } );
*
* @param int $ttl TTL.
*/
- return (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
+ $ttl = (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
+ return max( 0, $ttl );
}
/**
@@ -40,6 +46,9 @@ function ilo_get_page_metric_storage_lock_transient_key(): string {
/**
* Sets page metric storage lock (for the current IP).
+ *
+ * If the storage lock TTL is greater than zero, then a transient is set with the current timestamp and expiring at TTL
+ * seconds. Otherwise, if the current TTL is zero, then any transient is deleted.
*/
function ilo_set_page_metric_storage_lock() /*: void (in PHP 7.1) */ {
$ttl = ilo_get_page_metric_storage_lock_ttl();
From 19ce75252cd1ef2a414e421d1c8a71024a5d1a32 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 20:23:08 -0800
Subject: [PATCH 54/62] Add since and access private tags
---
.../image-loading-optimization/detection.php | 3 ++
.../image-loading-optimization/hooks.php | 5 ++-
.../storage/data.php | 45 +++++++++++++++++++
.../storage/lock.php | 11 +++++
.../storage/post-type.php | 15 +++++++
.../storage/rest-api.php | 6 +++
6 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index d445b1a230..db8b3a658a 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -12,6 +12,9 @@
/**
* Prints the script for detecting loaded images and the LCP element.
+ *
+ * @since n.e.x.t
+ * @access private
*/
function ilo_print_detection_script() /*: void (in PHP 7.1) */ {
if ( ! ilo_can_optimize_response() ) {
diff --git a/modules/images/image-loading-optimization/hooks.php b/modules/images/image-loading-optimization/hooks.php
index 60565a6119..426f20b646 100644
--- a/modules/images/image-loading-optimization/hooks.php
+++ b/modules/images/image-loading-optimization/hooks.php
@@ -17,15 +17,14 @@
*
* This is a hack which would eventually be replaced with something like this in wp-includes/template-loader.php:
*
- * ```
* $template = apply_filters( 'template_include', $template );
* + ob_start( 'wp_template_output_buffer_callback' );
* if ( $template ) {
* include $template;
* } elseif ( current_user_can( 'switch_themes' ) ) {
- * ```
*
* @since n.e.x.t
+ * @access private
* @link https://core.trac.wordpress.org/ticket/43258
*
* @param string $passthrough Optional. Filter value. Default null.
@@ -37,6 +36,8 @@ static function ( string $output ): string {
/**
* Filters the template output buffer prior to sending to the client.
*
+ * @since n.e.x.t
+ *
* @param string $output Output buffer.
* @return string Filtered output buffer.
*/
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 25d3bca571..b562d2784e 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -15,12 +15,17 @@
*
* When a page metric expires it is eligible to be replaced by a newer one if its viewport lies within the same breakpoint.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return int Expiration TTL in seconds.
*/
function ilo_get_page_metric_freshness_ttl(): int {
/**
* Filters the freshness age (TTL) for a given page metric.
*
+ * @since n.e.x.t
+ *
* @param int $ttl Expiration TTL in seconds.
*/
return (int) apply_filters( 'ilo_page_metric_freshness_ttl', DAY_IN_SECONDS );
@@ -33,6 +38,9 @@ function ilo_get_page_metric_freshness_ttl(): int {
* whether posts in the loop will have featured images assigned or not. If a theme template for search results doesn't
* even show featured images, then this isn't an issue.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return bool Whether response can be optimized.
*/
function ilo_can_optimize_response(): bool {
@@ -41,6 +49,8 @@ function ilo_can_optimize_response(): bool {
/**
* Filters whether the current response can be optimized.
*
+ * @since n.e.x.t
+ *
* @param bool $able Whether response can be optimized.
*/
return (bool) apply_filters( 'ilo_can_optimize_response', $able );
@@ -53,6 +63,9 @@ function ilo_can_optimize_response(): bool {
*
* TODO: For non-singular requests, consider adding the post IDs from The Loop to ensure publishing a new post will invalidate the cache.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return array Normalized query vars.
*/
function ilo_get_normalized_query_vars(): array {
@@ -75,6 +88,9 @@ function ilo_get_normalized_query_vars(): array {
/**
* Gets slug for page metrics.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @see ilo_get_normalized_query_vars()
*
* @param array $query_vars Normalized query vars.
@@ -89,6 +105,9 @@ function ilo_get_page_metrics_slug( array $query_vars ): string {
*
* This is used in the REST API to authenticate the storage of new page metrics from a given URL.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @see wp_create_nonce()
* @see ilo_verify_page_metrics_storage_nonce()
*
@@ -102,6 +121,9 @@ function ilo_get_page_metrics_storage_nonce( string $slug ): string {
/**
* Verifies nonce for storing page metrics for a specific slug.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @see wp_verify_nonce()
* @see ilo_get_page_metrics_storage_nonce()
*
@@ -118,6 +140,9 @@ function ilo_verify_page_metrics_storage_nonce( string $nonce, string $slug ): i
/**
* Unshifts a new page metric onto an array of page metrics.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param array $page_metrics Page metrics.
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return array Updated page metrics.
@@ -161,6 +186,9 @@ static function ( $a, $b ) {
* 3. 481-576 (phablets)
* 4. >576 (desktop)
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return int[] Breakpoint max widths, sorted in ascending order.
*/
function ilo_get_breakpoint_max_widths(): array {
@@ -188,12 +216,17 @@ static function ( $breakpoint_max_width ) {
* 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 page metrics stored for a given URL: 3 for mobile and 3 for desktop.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return int Sample size.
*/
function ilo_get_page_metrics_breakpoint_sample_size(): int {
/**
* Filters the sample size for a breakpoint's page metrics on a given URL.
*
+ * @since n.e.x.t
+ *
* @param int $sample_size Sample size.
*/
return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 3 );
@@ -202,6 +235,9 @@ function ilo_get_page_metrics_breakpoint_sample_size(): int {
/**
* Groups page metrics by breakpoint.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param array $page_metrics Page metrics.
* @param int[] $breakpoints Viewport breakpoint max widths, sorted in ascending order.
* @return array Page metrics grouped by breakpoint. The array keys are the minimum widths for a viewport to lie within
@@ -244,6 +280,9 @@ static function ( $breakpoint ) {
/**
* Gets needed minimum viewport widths.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param array $page_metrics Page metrics.
* @param float $current_time Current time as returned by microtime(true).
* @param int[] $breakpoint_max_widths Breakpoint max widths.
@@ -280,6 +319,9 @@ function ilo_get_needed_minimum_viewport_widths( array $page_metrics, float $cur
*
* This is a convenience wrapper on top of ilo_get_needed_minimum_viewport_widths() to reduce code duplication.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @see ilo_get_needed_minimum_viewport_widths()
*
* @param string $slug Page metrics slug.
@@ -298,6 +340,9 @@ function ilo_get_needed_minimum_viewport_widths_now_for_slug( string $slug ): ar
/**
* Checks whether there is a page metric needed for one of the breakpoints.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param array $needed_minimum_viewport_widths Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
* @return bool Whether a page metric is needed.
*/
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index eb6b21ca68..5b9307fdad 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -13,6 +13,9 @@
/**
* Gets the TTL (in seconds) for the page metric storage lock.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return int TTL in seconds, greater than or equal to zero.
*/
function ilo_get_page_metric_storage_lock_ttl(): int {
@@ -27,6 +30,8 @@ function ilo_get_page_metric_storage_lock_ttl(): int {
* return is_user_logged_in() ? 0 : $ttl;
* } );
*
+ * @since n.e.x.t
+ *
* @param int $ttl TTL.
*/
$ttl = (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
@@ -49,6 +54,9 @@ function ilo_get_page_metric_storage_lock_transient_key(): string {
*
* If the storage lock TTL is greater than zero, then a transient is set with the current timestamp and expiring at TTL
* seconds. Otherwise, if the current TTL is zero, then any transient is deleted.
+ *
+ * @since n.e.x.t
+ * @access private
*/
function ilo_set_page_metric_storage_lock() /*: void (in PHP 7.1) */ {
$ttl = ilo_get_page_metric_storage_lock_ttl();
@@ -63,6 +71,9 @@ function ilo_set_page_metric_storage_lock() /*: void (in PHP 7.1) */ {
/**
* Checks whether page metric storage is locked (for the current IP).
*
+ * @since n.e.x.t
+ * @access private
+ *
* @return bool Whether locked.
*/
function ilo_is_page_metric_storage_locked(): bool {
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index c3a840e240..fce053b38d 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -16,6 +16,9 @@
* Registers post type for page metrics storage.
*
* This the configuration for this post type is similar to the oembed_cache in core.
+ *
+ * @since n.e.x.t
+ * @access private
*/
function ilo_register_page_metrics_post_type() /*: void (in PHP 7.1) */ {
register_post_type(
@@ -40,6 +43,9 @@ function ilo_register_page_metrics_post_type() /*: void (in PHP 7.1) */ {
/**
* Gets page metrics post.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param string $slug Page metrics slug.
* @return WP_Post|null Post object if exists.
*/
@@ -69,6 +75,9 @@ function ilo_get_page_metrics_post( string $slug ) /*: ?WP_Post (in PHP 7.1) */
/**
* Parses post content in page metrics post.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param WP_Post $post Page metrics post.
* @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
*/
@@ -103,6 +112,9 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) /*: array|WP_Error (in P
*
* This is a convenience abstractions for lower-level functions.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @see ilo_get_page_metrics_post()
* @see ilo_parse_stored_page_metrics()
*
@@ -124,6 +136,9 @@ function ilo_get_page_metrics_data( string $slug ): array {
/**
* Stores page metric by merging it with the other page metrics for a given URL.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param string $url URL for the page metrics. This is used purely as metadata.
* @param string $slug Page metrics slug (computed from query vars).
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 9726f04087..8cd9848c22 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -16,6 +16,9 @@
/**
* Registers endpoint for storage of page metric.
+ *
+ * @since n.e.x.t
+ * @access private
*/
function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
@@ -151,6 +154,9 @@ function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
/**
* Handles REST API request to store metrics.
*
+ * @since n.e.x.t
+ * @access private
+ *
* @param WP_REST_Request $request Request.
* @return WP_REST_Response|WP_Error Response.
*/
From 19a2c3b274b553074f212b053c66f13fe665a148 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Tue, 14 Nov 2023 20:26:13 -0800
Subject: [PATCH 55/62] Use IMAGE_LOADING_OPTIMIZATION_VERSION constant for
script ver arg
---
modules/images/image-loading-optimization/detection.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index db8b3a658a..47e770e9a9 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -60,7 +60,7 @@ function ilo_print_detection_script() /*: void (in PHP 7.1) */ {
wp_print_inline_script_tag(
sprintf(
'import detect from %s; detect( %s );',
- wp_json_encode( add_query_arg( 'ver', PERFLAB_VERSION, plugin_dir_url( __FILE__ ) . 'detection/detect.js' ) ),
+ wp_json_encode( add_query_arg( 'ver', IMAGE_LOADING_OPTIMIZATION_VERSION, plugin_dir_url( __FILE__ ) . 'detection/detect.js' ) ),
wp_json_encode( $detect_args )
),
array( 'type' => 'module' )
From f566663483409664c0776989df918a9a08635dff Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 09:52:00 -0800
Subject: [PATCH 56/62] Move error emitting to ilo_parse_stored_page_metrics()
---
.../storage/data.php | 3 +-
.../storage/post-type.php | 57 +++++--------------
2 files changed, 17 insertions(+), 43 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index b562d2784e..211d1210f0 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -328,8 +328,9 @@ function ilo_get_needed_minimum_viewport_widths( array $page_metrics, float $cur
* @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
*/
function ilo_get_needed_minimum_viewport_widths_now_for_slug( string $slug ): array {
+ $post = ilo_get_page_metrics_post( $slug );
return ilo_get_needed_minimum_viewport_widths(
- ilo_get_page_metrics_data( $slug ),
+ $post instanceof WP_Post ? ilo_parse_stored_page_metrics( $post ) : array(),
microtime( true ),
ilo_get_breakpoint_max_widths(),
ilo_get_page_metrics_breakpoint_sample_size(),
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index fce053b38d..e712678d2b 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -79,13 +79,19 @@ function ilo_get_page_metrics_post( string $slug ) /*: ?WP_Post (in PHP 7.1) */
* @access private
*
* @param WP_Post $post Page metrics post.
- * @return array|WP_Error Page metrics when valid, or WP_Error otherwise.
+ * @return array Page metrics.
*/
-function ilo_parse_stored_page_metrics( WP_Post $post ) /*: array|WP_Error (in PHP 8) */ {
+function ilo_parse_stored_page_metrics( WP_Post $post ): array {
+ $this_function = __FUNCTION__;
+ $trigger_error = static function ( $error ) use ( $this_function ) {
+ if ( function_exists( 'wp_trigger_error' ) ) {
+ wp_trigger_error( $this_function, esc_html( $error ), E_USER_WARNING );
+ }
+ };
+
$page_metrics = json_decode( $post->post_content, true );
if ( json_last_error() ) {
- return new WP_Error(
- 'page_metrics_json_parse_error',
+ $trigger_error(
sprintf(
/* translators: 1: Post type slug, 2: JSON error message */
__( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
@@ -93,46 +99,20 @@ function ilo_parse_stored_page_metrics( WP_Post $post ) /*: array|WP_Error (in P
json_last_error_msg()
)
);
- }
- if ( ! is_array( $page_metrics ) ) {
- return new WP_Error(
- 'page_metrics_invalid_data_format',
+ $page_metrics = array();
+ } elseif ( ! is_array( $page_metrics ) ) {
+ $trigger_error(
sprintf(
/* translators: %s is post type slug */
__( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
ILO_PAGE_METRICS_POST_TYPE
)
);
+ $page_metrics = array();
}
return $page_metrics;
}
-/**
- * Gets page metrics for a slug.
- *
- * This is a convenience abstractions for lower-level functions.
- *
- * @since n.e.x.t
- * @access private
- *
- * @see ilo_get_page_metrics_post()
- * @see ilo_parse_stored_page_metrics()
- *
- * @param string $slug Page metrics slug.
- * @return array Page metrics data, or empty array if invalid.
- */
-function ilo_get_page_metrics_data( string $slug ): array {
- $post = ilo_get_page_metrics_post( $slug );
- if ( ! ( $post instanceof WP_Post ) ) {
- return array();
- }
- $data = ilo_parse_stored_page_metrics( $post );
- if ( ! is_array( $data ) ) {
- return array();
- }
- return $data;
-}
-
/**
* Stores page metric by merging it with the other page metrics for a given URL.
*
@@ -157,14 +137,7 @@ function ilo_store_page_metric( string $url, string $slug, array $validated_page
if ( $post instanceof WP_Post ) {
$post_data['ID'] = $post->ID;
$post_data['post_name'] = $post->post_name;
-
- $page_metrics = ilo_parse_stored_page_metrics( $post );
- if ( $page_metrics instanceof WP_Error ) {
- if ( function_exists( 'wp_trigger_error' ) ) {
- wp_trigger_error( __FUNCTION__, esc_html( $page_metrics->get_error_message() ) );
- }
- $page_metrics = array();
- }
+ $page_metrics = ilo_parse_stored_page_metrics( $post );
} else {
$post_data['post_name'] = $slug;
$page_metrics = array();
From b228934d938040485671559145b5044c2b656497 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 11:24:18 -0800
Subject: [PATCH 57/62] Elaborate on return value phpdoc for
ilo_get_page_metric_storage_lock_ttl()
Co-authored-by: Felix Arntz
---
modules/images/image-loading-optimization/storage/lock.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index 5b9307fdad..97902d4a30 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -16,7 +16,7 @@
* @since n.e.x.t
* @access private
*
- * @return int TTL in seconds, greater than or equal to zero.
+ * @return int TTL in seconds, greater than or equal to zero. A value of zero means that the storage lock should be disabled and thus that transients must not be used.
*/
function ilo_get_page_metric_storage_lock_ttl(): int {
From 95d940603cecfeacbdddc64d04384acce303cc54 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 11:27:11 -0800
Subject: [PATCH 58/62] Remove overkill PHP 7.1+ type declaration comments
---
modules/images/image-loading-optimization/detection.php | 2 +-
modules/images/image-loading-optimization/storage/lock.php | 2 +-
.../images/image-loading-optimization/storage/post-type.php | 6 +++---
.../images/image-loading-optimization/storage/rest-api.php | 4 ++--
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 47e770e9a9..90a1ffc461 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -16,7 +16,7 @@
* @since n.e.x.t
* @access private
*/
-function ilo_print_detection_script() /*: void (in PHP 7.1) */ {
+function ilo_print_detection_script() {
if ( ! ilo_can_optimize_response() ) {
return;
}
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index 97902d4a30..298689243c 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -58,7 +58,7 @@ function ilo_get_page_metric_storage_lock_transient_key(): string {
* @since n.e.x.t
* @access private
*/
-function ilo_set_page_metric_storage_lock() /*: void (in PHP 7.1) */ {
+function ilo_set_page_metric_storage_lock() {
$ttl = ilo_get_page_metric_storage_lock_ttl();
$key = ilo_get_page_metric_storage_lock_transient_key();
if ( 0 === $ttl ) {
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index e712678d2b..bb7f4e9b75 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -20,7 +20,7 @@
* @since n.e.x.t
* @access private
*/
-function ilo_register_page_metrics_post_type() /*: void (in PHP 7.1) */ {
+function ilo_register_page_metrics_post_type() {
register_post_type(
ILO_PAGE_METRICS_POST_TYPE,
array(
@@ -49,7 +49,7 @@ function ilo_register_page_metrics_post_type() /*: void (in PHP 7.1) */ {
* @param string $slug Page metrics slug.
* @return WP_Post|null Post object if exists.
*/
-function ilo_get_page_metrics_post( string $slug ) /*: ?WP_Post (in PHP 7.1) */ {
+function ilo_get_page_metrics_post( string $slug ) {
$post_query = new WP_Query(
array(
'post_type' => ILO_PAGE_METRICS_POST_TYPE,
@@ -124,7 +124,7 @@ function ilo_parse_stored_page_metrics( WP_Post $post ): array {
* @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
-function ilo_store_page_metric( string $url, string $slug, array $validated_page_metric ) /*: int|WP_Error (in PHP 8) */ {
+function ilo_store_page_metric( string $url, string $slug, array $validated_page_metric ) {
$validated_page_metric['timestamp'] = microtime( true );
// TODO: What about storing a version identifier?
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 8cd9848c22..a9c68ef84d 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -20,7 +20,7 @@
* @since n.e.x.t
* @access private
*/
-function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
+function ilo_register_endpoint() {
$dom_rect_schema = array(
'type' => 'object',
@@ -160,7 +160,7 @@ function ilo_register_endpoint() /*: void (in PHP 7.1) */ {
* @param WP_REST_Request $request Request.
* @return WP_REST_Response|WP_Error Response.
*/
-function ilo_handle_rest_request( WP_REST_Request $request ) /*: WP_REST_Response|WP_Error (in PHP 8) */ {
+function ilo_handle_rest_request( WP_REST_Request $request ) {
$needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths_now_for_slug( $request->get_param( 'slug' ) );
if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return new WP_Error(
From 9876aa44911a17fe5cae2843a34f5eb5c15368ef Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 12:03:32 -0800
Subject: [PATCH 59/62] Rename 'page metrics' to 'URL metrics'
---
.../image-loading-optimization/detection.php | 12 +-
.../detection/detect.js | 36 ++---
.../storage/data.php | 148 +++++++++---------
.../storage/lock.php | 26 +--
.../storage/post-type.php | 68 ++++----
.../storage/rest-api.php | 32 ++--
6 files changed, 161 insertions(+), 161 deletions(-)
diff --git a/modules/images/image-loading-optimization/detection.php b/modules/images/image-loading-optimization/detection.php
index 90a1ffc461..c54229fc86 100644
--- a/modules/images/image-loading-optimization/detection.php
+++ b/modules/images/image-loading-optimization/detection.php
@@ -22,12 +22,12 @@ function ilo_print_detection_script() {
}
$query_vars = ilo_get_normalized_query_vars();
- $slug = ilo_get_page_metrics_slug( $query_vars );
+ $slug = ilo_get_url_metrics_slug( $query_vars );
$microtime = microtime( true );
// Abort if we already have all the sample size we need for all breakpoints.
$needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths_now_for_slug( $slug );
- if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
+ if ( ! ilo_needs_url_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return;
}
@@ -50,12 +50,12 @@ function ilo_print_detection_script() {
'serveTime' => $microtime * 1000, // In milliseconds for comparison with `Date.now()` in JavaScript.
'detectionTimeWindow' => $detection_time_window,
'isDebug' => WP_DEBUG,
- 'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_PAGE_METRICS_ROUTE ),
+ 'restApiEndpoint' => rest_url( ILO_REST_API_NAMESPACE . ILO_URL_METRICS_ROUTE ),
'restApiNonce' => wp_create_nonce( 'wp_rest' ),
- 'pageMetricsSlug' => $slug,
- 'pageMetricsNonce' => ilo_get_page_metrics_storage_nonce( $slug ),
+ 'urlMetricsSlug' => $slug,
+ 'urlMetricsNonce' => ilo_get_url_metrics_storage_nonce( $slug ),
'neededMinimumViewportWidths' => $needed_minimum_viewport_widths,
- 'storageLockTTL' => ilo_get_page_metric_storage_lock_ttl(),
+ 'storageLockTTL' => ilo_get_url_metric_storage_lock_ttl(),
);
wp_print_inline_script_tag(
sprintf(
diff --git a/modules/images/image-loading-optimization/detection/detect.js b/modules/images/image-loading-optimization/detection/detect.js
index 96d0587a4b..512832e7f2 100644
--- a/modules/images/image-loading-optimization/detection/detect.js
+++ b/modules/images/image-loading-optimization/detection/detect.js
@@ -93,7 +93,7 @@ function error( ...message ) {
*/
/**
- * @typedef {Object} PageMetrics
+ * @typedef {Object} URLMetrics
* @property {string} url - URL of the page.
* @property {Object} viewport - Viewport.
* @property {number} viewport.width - Viewport width.
@@ -137,11 +137,11 @@ function getBreadcrumbs( leafElement ) {
}
/**
- * Checks whether the page 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 {Array[]} neededMinimumViewportWidths - Needed minimum viewport widths, in ascending order.
- * @return {boolean} Whether page metrics are needed.
+ * @return {boolean} Whether URL metrics are needed.
*/
function isViewportNeeded( viewportWidth, neededMinimumViewportWidths ) {
let lastWasNeeded = false;
@@ -176,10 +176,10 @@ function getCurrentTime() {
* @param {boolean} args.isDebug Whether to show debug messages.
* @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.pageMetricsSlug Slug for page metrics.
- * @param {string} args.pageMetricsNonce Nonce for page metrics storage.
- * @param {Array[]} args.neededMinimumViewportWidths Needed minimum viewport widths for page metrics.
- * @param {number} args.storageLockTTL The TTL (in seconds) for the page metric storage lock.
+ * @param {string} args.urlMetricsSlug Slug for URL metrics.
+ * @param {string} args.urlMetricsNonce Nonce for URL metrics storage.
+ * @param {Array[]} args.neededMinimumViewportWidths Needed minimum viewport widths for URL metrics.
+ * @param {number} args.storageLockTTL The TTL (in seconds) for the URL metric storage lock.
*/
export default async function detect( {
serveTime,
@@ -187,15 +187,15 @@ export default async function detect( {
isDebug,
restApiEndpoint,
restApiNonce,
- pageMetricsSlug,
- pageMetricsNonce,
+ urlMetricsSlug,
+ urlMetricsNonce,
neededMinimumViewportWidths,
storageLockTTL,
} ) {
const currentTime = getCurrentTime();
// As an alternative to this, the ilo_print_detection_script() function can short-circuit if the
- // ilo_is_page_metric_storage_locked() function returns true. However, the downside with that is page caching could
+ // ilo_is_url_metric_storage_locked() function returns true. However, the downside with that is page caching could
// result in metrics being missed being gathered when a user navigates around a site and primes the page cache.
if ( isStorageLocked( currentTime, storageLockTTL ) ) {
if ( isDebug ) {
@@ -227,7 +227,7 @@ export default async function detect( {
if ( ! isViewportNeeded( win.innerWidth, neededMinimumViewportWidths ) ) {
if ( isDebug ) {
- log( 'No need for page metrics from the current viewport.' );
+ log( 'No need for URL metrics from the current viewport.' );
}
return;
}
@@ -354,11 +354,11 @@ export default async function detect( {
log( 'Detection is stopping.' );
}
- /** @type {PageMetrics} */
- const pageMetrics = {
+ /** @type {URLMetrics} */
+ const urlMetrics = {
url: win.location.href,
- slug: pageMetricsSlug,
- nonce: pageMetricsNonce,
+ slug: urlMetricsSlug,
+ nonce: urlMetricsNonce,
viewport: {
width: win.innerWidth,
height: win.innerHeight,
@@ -396,11 +396,11 @@ export default async function detect( {
boundingClientRect: elementIntersection.boundingClientRect,
};
- pageMetrics.elements.push( elementMetrics );
+ urlMetrics.elements.push( elementMetrics );
}
if ( isDebug ) {
- log( 'Page metrics:', pageMetrics );
+ log( 'URL metrics:', urlMetrics );
}
// TODO: Wait until idle? Yield to main?
@@ -411,7 +411,7 @@ export default async function detect( {
'Content-Type': 'application/json',
'X-WP-Nonce': restApiNonce,
},
- body: JSON.stringify( pageMetrics ),
+ body: JSON.stringify( urlMetrics ),
} );
if ( response.status === 200 ) {
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 211d1210f0..731275d3ea 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -11,24 +11,24 @@
}
/**
- * Gets the freshness age (TTL) for a given page metric.
+ * Gets the freshness age (TTL) for a given URL metric.
*
- * When a page 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 n.e.x.t
* @access private
*
* @return int Expiration TTL in seconds.
*/
-function ilo_get_page_metric_freshness_ttl(): int {
+function ilo_get_url_metric_freshness_ttl(): int {
/**
- * Filters the freshness age (TTL) for a given page metric.
+ * Filters the freshness age (TTL) for a given URL metric.
*
* @since n.e.x.t
*
* @param int $ttl Expiration TTL in seconds.
*/
- return (int) apply_filters( 'ilo_page_metric_freshness_ttl', DAY_IN_SECONDS );
+ return (int) apply_filters( 'ilo_url_metric_freshness_ttl', DAY_IN_SECONDS );
}
/**
@@ -59,7 +59,7 @@ function ilo_can_optimize_response(): bool {
/**
* Gets the normalized query vars for the current request.
*
- * This is used as a cache key for stored page 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.
*
@@ -86,7 +86,7 @@ function ilo_get_normalized_query_vars(): array {
}
/**
- * Gets slug for page metrics.
+ * Gets slug for URL metrics.
*
* @since n.e.x.t
* @access private
@@ -96,69 +96,69 @@ function ilo_get_normalized_query_vars(): array {
* @param array $query_vars Normalized query vars.
* @return string Slug.
*/
-function ilo_get_page_metrics_slug( array $query_vars ): string {
+function ilo_get_url_metrics_slug( array $query_vars ): string {
return md5( wp_json_encode( $query_vars ) );
}
/**
- * Computes nonce for storing page 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 page 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 n.e.x.t
* @access private
*
* @see wp_create_nonce()
- * @see ilo_verify_page_metrics_storage_nonce()
+ * @see ilo_verify_url_metrics_storage_nonce()
*
- * @param string $slug Page metrics slug.
+ * @param string $slug URL metrics slug.
* @return string Nonce.
*/
-function ilo_get_page_metrics_storage_nonce( string $slug ): string {
- return wp_create_nonce( "store_page_metrics:{$slug}" );
+function ilo_get_url_metrics_storage_nonce( string $slug ): string {
+ return wp_create_nonce( "store_url_metrics:{$slug}" );
}
/**
- * Verifies nonce for storing page metrics for a specific slug.
+ * Verifies nonce for storing URL metrics for a specific slug.
*
* @since n.e.x.t
* @access private
*
* @see wp_verify_nonce()
- * @see ilo_get_page_metrics_storage_nonce()
+ * @see ilo_get_url_metrics_storage_nonce()
*
- * @param string $nonce Page metrics storage nonce.
- * @param string $slug Page metrics slug.
+ * @param string $nonce URL metrics storage nonce.
+ * @param string $slug URL metrics slug.
* @return int 1 if the nonce is valid and generated between 0-12 hours ago,
* 2 if the nonce is valid and generated between 12-24 hours ago.
* 0 if the nonce is invalid.
*/
-function ilo_verify_page_metrics_storage_nonce( string $nonce, string $slug ): int {
- return (int) wp_verify_nonce( $nonce, "store_page_metrics:{$slug}" );
+function ilo_verify_url_metrics_storage_nonce( string $nonce, string $slug ): int {
+ return (int) wp_verify_nonce( $nonce, "store_url_metrics:{$slug}" );
}
/**
- * Unshifts a new page metric onto an array of page metrics.
+ * Unshifts a new URL metric onto an array of URL metrics.
*
* @since n.e.x.t
* @access private
*
- * @param array $page_metrics Page metrics.
- * @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
- * @return array Updated page metrics.
+ * @param array $url_metrics URL metrics.
+ * @param array $validated_url_metric Validated URL metric. See JSON Schema defined in ilo_register_endpoint().
+ * @return array Updated URL metrics.
*/
-function ilo_unshift_page_metrics( array $page_metrics, array $validated_page_metric ): array {
- array_unshift( $page_metrics, $validated_page_metric );
- $breakpoints = ilo_get_breakpoint_max_widths();
- $sample_size = ilo_get_page_metrics_breakpoint_sample_size();
- $grouped_page_metrics = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoints );
+function ilo_unshift_url_metrics( array $url_metrics, array $validated_url_metric ): array {
+ array_unshift( $url_metrics, $validated_url_metric );
+ $breakpoints = ilo_get_breakpoint_max_widths();
+ $sample_size = ilo_get_url_metrics_breakpoint_sample_size();
+ $grouped_url_metrics = ilo_group_url_metrics_by_breakpoint( $url_metrics, $breakpoints );
- foreach ( $grouped_page_metrics as &$breakpoint_page_metrics ) {
- if ( count( $breakpoint_page_metrics ) > $sample_size ) {
+ foreach ( $grouped_url_metrics as &$breakpoint_url_metrics ) {
+ if ( count( $breakpoint_url_metrics ) > $sample_size ) {
- // Sort page metrics in descending order by timestamp.
+ // Sort URL metrics in descending order by timestamp.
usort(
- $breakpoint_page_metrics,
+ $breakpoint_url_metrics,
static function ( $a, $b ) {
if ( ! isset( $a['timestamp'] ) || ! isset( $b['timestamp'] ) ) {
return 0;
@@ -167,15 +167,15 @@ static function ( $a, $b ) {
}
);
- $breakpoint_page_metrics = array_slice( $breakpoint_page_metrics, 0, $sample_size );
+ $breakpoint_url_metrics = array_slice( $breakpoint_url_metrics, 0, $sample_size );
}
}
- return array_merge( ...$grouped_page_metrics );
+ return array_merge( ...$grouped_url_metrics );
}
/**
- * Gets the breakpoint max widths to group page metrics for various viewports.
+ * Gets the breakpoint max widths to group URL metrics for various viewports.
*
* Each max with 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
@@ -198,7 +198,7 @@ static function ( $breakpoint_max_width ) {
return (int) $breakpoint_max_width;
},
/**
- * Filters the breakpoint max widths to group page metrics for various viewports.
+ * Filters the breakpoint max widths to group URL metrics for various viewports.
*
* @param int[] $breakpoint_max_widths Max widths for viewport breakpoints.
*/
@@ -210,42 +210,42 @@ static function ( $breakpoint_max_width ) {
}
/**
- * Gets the sample size for a breakpoint's page metrics on a given URL.
+ * Gets the sample size for a breakpoint's URL metrics on a given URL.
*
- * A breakpoint divides page 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 page 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 n.e.x.t
* @access private
*
* @return int Sample size.
*/
-function ilo_get_page_metrics_breakpoint_sample_size(): int {
+function ilo_get_url_metrics_breakpoint_sample_size(): int {
/**
- * Filters the sample size for a breakpoint's page metrics on a given URL.
+ * Filters the sample size for a breakpoint's URL metrics on a given URL.
*
* @since n.e.x.t
*
* @param int $sample_size Sample size.
*/
- return (int) apply_filters( 'ilo_page_metrics_breakpoint_sample_size', 3 );
+ return (int) apply_filters( 'ilo_url_metrics_breakpoint_sample_size', 3 );
}
/**
- * Groups page metrics by breakpoint.
+ * Groups URL metrics by breakpoint.
*
* @since n.e.x.t
* @access private
*
- * @param array $page_metrics Page metrics.
+ * @param array $url_metrics URL metrics.
* @param int[] $breakpoints Viewport breakpoint max widths, sorted in ascending order.
- * @return array Page metrics grouped by breakpoint. The array keys are the minimum widths for a viewport to lie within
+ * @return array URL metrics grouped by breakpoint. The array keys are the minimum widths for a viewport to lie within
* the breakpoint. The returned array is always one larger than the provided array of breakpoints, since
* the breakpoints reflect the max inclusive boundaries whereas the return value is the groups of page
* metrics with viewports on either side of the breakpoint boundaries.
*/
-function ilo_group_page_metrics_by_breakpoint( array $page_metrics, array $breakpoints ): array {
+function ilo_group_url_metrics_by_breakpoint( array $url_metrics, array $breakpoints ): array {
// Convert breakpoint max widths into viewport minimum widths.
$viewport_minimum_widths = array_map(
@@ -257,11 +257,11 @@ static function ( $breakpoint ) {
$grouped = array_fill_keys( array_merge( array( 0 ), $viewport_minimum_widths ), array() );
- foreach ( $page_metrics as $page_metric ) {
- if ( ! isset( $page_metric['viewport']['width'] ) ) {
+ foreach ( $url_metrics as $url_metric ) {
+ if ( ! isset( $url_metric['viewport']['width'] ) ) {
continue;
}
- $viewport_width = $page_metric['viewport']['width'];
+ $viewport_width = $url_metric['viewport']['width'];
$current_minimum_viewport = 0;
foreach ( $viewport_minimum_widths as $viewport_minimum_width ) {
@@ -272,7 +272,7 @@ static function ( $breakpoint ) {
}
}
- $grouped[ $current_minimum_viewport ][] = $page_metric;
+ $grouped[ $current_minimum_viewport ][] = $url_metric;
}
return $grouped;
}
@@ -283,31 +283,31 @@ static function ( $breakpoint ) {
* @since n.e.x.t
* @access private
*
- * @param array $page_metrics Page metrics.
+ * @param array $url_metrics URL metrics.
* @param float $current_time Current time as returned by microtime(true).
* @param int[] $breakpoint_max_widths Breakpoint max widths.
* @param int $sample_size Sample size for viewports in a breakpoint.
- * @param int $freshness_ttl Freshness TTL for a page metric.
- * @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
+ * @param int $freshness_ttl Freshness TTL for a URL metric.
+ * @return array Array of tuples mapping minimum viewport width to whether URL metric(s) are needed.
*/
-function ilo_get_needed_minimum_viewport_widths( array $page_metrics, float $current_time, array $breakpoint_max_widths, int $sample_size, int $freshness_ttl ): array {
- $metrics_by_breakpoint = ilo_group_page_metrics_by_breakpoint( $page_metrics, $breakpoint_max_widths );
+function ilo_get_needed_minimum_viewport_widths( array $url_metrics, float $current_time, array $breakpoint_max_widths, int $sample_size, int $freshness_ttl ): array {
+ $metrics_by_breakpoint = ilo_group_url_metrics_by_breakpoint( $url_metrics, $breakpoint_max_widths );
$needed_minimum_viewport_widths = array();
- foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $viewport_page_metrics ) {
- $needs_page_metrics = false;
- if ( count( $viewport_page_metrics ) < $sample_size ) {
- $needs_page_metrics = true;
+ foreach ( $metrics_by_breakpoint as $minimum_viewport_width => $viewport_url_metrics ) {
+ $needs_url_metrics = false;
+ if ( count( $viewport_url_metrics ) < $sample_size ) {
+ $needs_url_metrics = true;
} else {
- foreach ( $viewport_page_metrics as $page_metric ) {
- if ( isset( $page_metric['timestamp'] ) && $page_metric['timestamp'] + $freshness_ttl < $current_time ) {
- $needs_page_metrics = true;
+ foreach ( $viewport_url_metrics as $url_metric ) {
+ if ( isset( $url_metric['timestamp'] ) && $url_metric['timestamp'] + $freshness_ttl < $current_time ) {
+ $needs_url_metrics = true;
break;
}
}
}
$needed_minimum_viewport_widths[] = array(
$minimum_viewport_width,
- $needs_page_metrics,
+ $needs_url_metrics,
);
}
@@ -324,30 +324,30 @@ function ilo_get_needed_minimum_viewport_widths( array $page_metrics, float $cur
*
* @see ilo_get_needed_minimum_viewport_widths()
*
- * @param string $slug Page metrics slug.
- * @return array Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
+ * @param string $slug URL metrics slug.
+ * @return array Array of tuples mapping minimum viewport width to whether URL metric(s) are needed.
*/
function ilo_get_needed_minimum_viewport_widths_now_for_slug( string $slug ): array {
- $post = ilo_get_page_metrics_post( $slug );
+ $post = ilo_get_url_metrics_post( $slug );
return ilo_get_needed_minimum_viewport_widths(
- $post instanceof WP_Post ? ilo_parse_stored_page_metrics( $post ) : array(),
+ $post instanceof WP_Post ? ilo_parse_stored_url_metrics( $post ) : array(),
microtime( true ),
ilo_get_breakpoint_max_widths(),
- ilo_get_page_metrics_breakpoint_sample_size(),
- ilo_get_page_metric_freshness_ttl()
+ ilo_get_url_metrics_breakpoint_sample_size(),
+ ilo_get_url_metric_freshness_ttl()
);
}
/**
- * Checks whether there is a page metric needed for one of the breakpoints.
+ * Checks whether there is a URL metric needed for one of the breakpoints.
*
* @since n.e.x.t
* @access private
*
- * @param array $needed_minimum_viewport_widths Array of tuples mapping minimum viewport width to whether page metric(s) are needed.
- * @return bool Whether a page metric is needed.
+ * @param array $needed_minimum_viewport_widths Array of tuples mapping minimum viewport width to whether URL metric(s) are needed.
+ * @return bool Whether a URL metric is needed.
*/
-function ilo_needs_page_metric_for_breakpoint( array $needed_minimum_viewport_widths ): bool {
+function ilo_needs_url_metric_for_breakpoint( array $needed_minimum_viewport_widths ): bool {
foreach ( $needed_minimum_viewport_widths as list( $minimum_viewport_width, $is_needed ) ) {
if ( $is_needed ) {
return true;
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index 298689243c..d30fe8d75c 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -11,14 +11,14 @@
}
/**
- * Gets the TTL (in seconds) for the page metric storage lock.
+ * Gets the TTL (in seconds) for the URL metric storage lock.
*
* @since n.e.x.t
* @access private
*
* @return int TTL in seconds, greater than or equal to zero. A value of zero means that the storage lock should be disabled and thus that transients must not be used.
*/
-function ilo_get_page_metric_storage_lock_ttl(): int {
+function ilo_get_url_metric_storage_lock_ttl(): int {
/**
* Filters how long a given IP is locked from submitting another metric-storage REST API request.
@@ -39,18 +39,18 @@ function ilo_get_page_metric_storage_lock_ttl(): int {
}
/**
- * Gets transient key for locking page metric storage (for the current IP).
+ * Gets transient key for locking URL metric storage (for the current IP).
*
* @todo Should the URL be included in the key? Or should a user only be allowed to store one metric?
* @return string Transient key.
*/
-function ilo_get_page_metric_storage_lock_transient_key(): string {
+function ilo_get_url_metric_storage_lock_transient_key(): string {
$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
- return 'page_metrics_storage_lock_' . wp_hash( $ip_address );
+ return 'url_metrics_storage_lock_' . wp_hash( $ip_address );
}
/**
- * Sets page metric storage lock (for the current IP).
+ * Sets URL metric storage lock (for the current IP).
*
* If the storage lock TTL is greater than zero, then a transient is set with the current timestamp and expiring at TTL
* seconds. Otherwise, if the current TTL is zero, then any transient is deleted.
@@ -58,9 +58,9 @@ function ilo_get_page_metric_storage_lock_transient_key(): string {
* @since n.e.x.t
* @access private
*/
-function ilo_set_page_metric_storage_lock() {
- $ttl = ilo_get_page_metric_storage_lock_ttl();
- $key = ilo_get_page_metric_storage_lock_transient_key();
+function ilo_set_url_metric_storage_lock() {
+ $ttl = ilo_get_url_metric_storage_lock_ttl();
+ $key = ilo_get_url_metric_storage_lock_transient_key();
if ( 0 === $ttl ) {
delete_transient( $key );
} else {
@@ -69,19 +69,19 @@ function ilo_set_page_metric_storage_lock() {
}
/**
- * Checks whether page metric storage is locked (for the current IP).
+ * Checks whether URL metric storage is locked (for the current IP).
*
* @since n.e.x.t
* @access private
*
* @return bool Whether locked.
*/
-function ilo_is_page_metric_storage_locked(): bool {
- $ttl = ilo_get_page_metric_storage_lock_ttl();
+function ilo_is_url_metric_storage_locked(): bool {
+ $ttl = ilo_get_url_metric_storage_lock_ttl();
if ( 0 === $ttl ) {
return false;
}
- $locked_time = get_transient( ilo_get_page_metric_storage_lock_transient_key() );
+ $locked_time = get_transient( ilo_get_url_metric_storage_lock_transient_key() );
if ( false === $locked_time ) {
return false;
}
diff --git a/modules/images/image-loading-optimization/storage/post-type.php b/modules/images/image-loading-optimization/storage/post-type.php
index bb7f4e9b75..a95a71176d 100644
--- a/modules/images/image-loading-optimization/storage/post-type.php
+++ b/modules/images/image-loading-optimization/storage/post-type.php
@@ -10,23 +10,23 @@
exit; // Exit if accessed directly.
}
-const ILO_PAGE_METRICS_POST_TYPE = 'ilo_page_metrics';
+const ILO_URL_METRICS_POST_TYPE = 'ilo_url_metrics';
/**
- * Registers post type for page metrics storage.
+ * Registers post type for URL metrics storage.
*
* This the configuration for this post type is similar to the oembed_cache in core.
*
* @since n.e.x.t
* @access private
*/
-function ilo_register_page_metrics_post_type() {
+function ilo_register_url_metrics_post_type() {
register_post_type(
- ILO_PAGE_METRICS_POST_TYPE,
+ ILO_URL_METRICS_POST_TYPE,
array(
'labels' => array(
- 'name' => __( 'Page Metrics', 'performance-lab' ),
- 'singular_name' => __( 'Page Metrics', 'performance-lab' ),
+ 'name' => __( 'URL Metrics', 'performance-lab' ),
+ 'singular_name' => __( 'URL Metrics', 'performance-lab' ),
),
'public' => false,
'hierarchical' => false,
@@ -38,21 +38,21 @@ function ilo_register_page_metrics_post_type() {
)
);
}
-add_action( 'init', 'ilo_register_page_metrics_post_type' );
+add_action( 'init', 'ilo_register_url_metrics_post_type' );
/**
- * Gets page metrics post.
+ * Gets URL metrics post.
*
* @since n.e.x.t
* @access private
*
- * @param string $slug Page metrics slug.
+ * @param string $slug URL metrics slug.
* @return WP_Post|null Post object if exists.
*/
-function ilo_get_page_metrics_post( string $slug ) {
+function ilo_get_url_metrics_post( string $slug ) {
$post_query = new WP_Query(
array(
- 'post_type' => ILO_PAGE_METRICS_POST_TYPE,
+ 'post_type' => ILO_URL_METRICS_POST_TYPE,
'post_status' => 'publish',
'name' => $slug,
'posts_per_page' => 1,
@@ -73,15 +73,15 @@ function ilo_get_page_metrics_post( string $slug ) {
}
/**
- * Parses post content in page metrics post.
+ * Parses post content in URL metrics post.
*
* @since n.e.x.t
* @access private
*
- * @param WP_Post $post Page metrics post.
- * @return array Page metrics.
+ * @param WP_Post $post URL metrics post.
+ * @return array URL metrics.
*/
-function ilo_parse_stored_page_metrics( WP_Post $post ): array {
+function ilo_parse_stored_url_metrics( WP_Post $post ): array {
$this_function = __FUNCTION__;
$trigger_error = static function ( $error ) use ( $this_function ) {
if ( function_exists( 'wp_trigger_error' ) ) {
@@ -89,63 +89,63 @@ function ilo_parse_stored_page_metrics( WP_Post $post ): array {
}
};
- $page_metrics = json_decode( $post->post_content, true );
+ $url_metrics = json_decode( $post->post_content, true );
if ( json_last_error() ) {
$trigger_error(
sprintf(
/* translators: 1: Post type slug, 2: JSON error message */
__( 'Contents of %1$s post type not valid JSON: %2$s', 'performance-lab' ),
- ILO_PAGE_METRICS_POST_TYPE,
+ ILO_URL_METRICS_POST_TYPE,
json_last_error_msg()
)
);
- $page_metrics = array();
- } elseif ( ! is_array( $page_metrics ) ) {
+ $url_metrics = array();
+ } elseif ( ! is_array( $url_metrics ) ) {
$trigger_error(
sprintf(
/* translators: %s is post type slug */
__( 'Contents of %s post type was not a JSON array.', 'performance-lab' ),
- ILO_PAGE_METRICS_POST_TYPE
+ ILO_URL_METRICS_POST_TYPE
)
);
- $page_metrics = array();
+ $url_metrics = array();
}
- return $page_metrics;
+ return $url_metrics;
}
/**
- * Stores page metric by merging it with the other page metrics for a given URL.
+ * Stores URL metric by merging it with the other URL metrics for a given URL.
*
* @since n.e.x.t
* @access private
*
- * @param string $url URL for the page metrics. This is used purely as metadata.
- * @param string $slug Page metrics slug (computed from query vars).
- * @param array $validated_page_metric Validated page metric. See JSON Schema defined in ilo_register_endpoint().
+ * @param string $url URL for the URL metrics. This is used purely as metadata.
+ * @param string $slug URL metrics slug (computed from query vars).
+ * @param array $validated_url_metric Validated URL metric. See JSON Schema defined in ilo_register_endpoint().
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
-function ilo_store_page_metric( string $url, string $slug, array $validated_page_metric ) {
- $validated_page_metric['timestamp'] = microtime( true );
+function ilo_store_url_metric( string $url, string $slug, array $validated_url_metric ) {
+ $validated_url_metric['timestamp'] = microtime( true );
// TODO: What about storing a version identifier?
$post_data = array(
'post_title' => $url, // TODO: Should we keep this? It can help with debugging.
);
- $post = ilo_get_page_metrics_post( $slug );
+ $post = ilo_get_url_metrics_post( $slug );
if ( $post instanceof WP_Post ) {
$post_data['ID'] = $post->ID;
$post_data['post_name'] = $post->post_name;
- $page_metrics = ilo_parse_stored_page_metrics( $post );
+ $url_metrics = ilo_parse_stored_url_metrics( $post );
} else {
$post_data['post_name'] = $slug;
- $page_metrics = array();
+ $url_metrics = array();
}
- $page_metrics = ilo_unshift_page_metrics( $page_metrics, $validated_page_metric );
+ $url_metrics = ilo_unshift_url_metrics( $url_metrics, $validated_url_metric );
- $post_data['post_content'] = wp_json_encode( $page_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
+ $post_data['post_content'] = wp_json_encode( $url_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.
$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
if ( $has_kses ) {
@@ -153,7 +153,7 @@ function ilo_store_page_metric( string $url, string $slug, array $validated_page
kses_remove_filters();
}
- $post_data['post_type'] = ILO_PAGE_METRICS_POST_TYPE;
+ $post_data['post_type'] = ILO_URL_METRICS_POST_TYPE;
$post_data['post_status'] = 'publish';
if ( isset( $post_data['ID'] ) ) {
$result = wp_update_post( wp_slash( $post_data ), true );
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index a9c68ef84d..423355f344 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -12,10 +12,10 @@
const ILO_REST_API_NAMESPACE = 'image-loading-optimization/v1';
-const ILO_PAGE_METRICS_ROUTE = '/page-metrics';
+const ILO_URL_METRICS_ROUTE = '/url-metrics';
/**
- * Registers endpoint for storage of page metric.
+ * Registers endpoint for storage of URL metric.
*
* @since n.e.x.t
* @access private
@@ -39,7 +39,7 @@ function ilo_register_endpoint() {
register_rest_route(
ILO_REST_API_NAMESPACE,
- ILO_PAGE_METRICS_ROUTE,
+ ILO_URL_METRICS_ROUTE,
array(
'methods' => 'POST',
'callback' => static function ( WP_REST_Request $request ) {
@@ -47,10 +47,10 @@ function ilo_register_endpoint() {
},
'permission_callback' => static function () {
// Needs to be available to unauthenticated visitors.
- if ( ilo_is_page_metric_storage_locked() ) {
+ if ( ilo_is_url_metric_storage_locked() ) {
return new WP_Error(
- 'page_metric_storage_locked',
- __( 'Page metric storage is presently locked for the current IP.', 'performance-lab' ),
+ 'url_metric_storage_locked',
+ __( 'URL metric storage is presently locked for the current IP.', 'performance-lab' ),
array( 'status' => 403 )
);
}
@@ -78,8 +78,8 @@ function ilo_register_endpoint() {
'required' => true,
'pattern' => '^[0-9a-f]+$',
'validate_callback' => static function ( $nonce, WP_REST_Request $request ) {
- if ( ! ilo_verify_page_metrics_storage_nonce( $nonce, $request->get_param( 'slug' ) ) ) {
- return new WP_Error( 'invalid_nonce', __( 'Page metrics nonce verification failure.', 'performance-lab' ) );
+ if ( ! ilo_verify_url_metrics_storage_nonce( $nonce, $request->get_param( 'slug' ) ) ) {
+ return new WP_Error( 'invalid_nonce', __( 'URL metrics nonce verification failure.', 'performance-lab' ) );
}
return true;
},
@@ -162,21 +162,21 @@ function ilo_register_endpoint() {
*/
function ilo_handle_rest_request( WP_REST_Request $request ) {
$needed_minimum_viewport_widths = ilo_get_needed_minimum_viewport_widths_now_for_slug( $request->get_param( 'slug' ) );
- if ( ! ilo_needs_page_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
+ if ( ! ilo_needs_url_metric_for_breakpoint( $needed_minimum_viewport_widths ) ) {
return new WP_Error(
- 'no_page_metric_needed',
- __( 'No page metric needed for any of the breakpoints.', 'performance-lab' ),
+ 'no_url_metric_needed',
+ __( 'No URL metric needed for any of the breakpoints.', 'performance-lab' ),
array( 'status' => 403 )
);
}
- ilo_set_page_metric_storage_lock();
- $new_page_metric = wp_array_slice_assoc( $request->get_json_params(), array( 'viewport', 'elements' ) );
+ ilo_set_url_metric_storage_lock();
+ $new_url_metric = wp_array_slice_assoc( $request->get_json_params(), array( 'viewport', 'elements' ) );
- $result = ilo_store_page_metric(
+ $result = ilo_store_url_metric(
$request->get_param( 'url' ),
$request->get_param( 'slug' ),
- $new_page_metric
+ $new_url_metric
);
if ( $result instanceof WP_Error ) {
@@ -187,7 +187,7 @@ function ilo_handle_rest_request( WP_REST_Request $request ) {
array(
'success' => true,
'post_id' => $result,
- 'data' => ilo_parse_stored_page_metrics( ilo_get_page_metrics_post( $request->get_param( 'slug' ) ) ), // TODO: Remove this debug data.
+ 'data' => ilo_parse_stored_url_metrics( ilo_get_url_metrics_post( $request->get_param( 'slug' ) ) ), // TODO: Remove this debug data.
)
);
}
From 82b6210d897dfec56dbe6d07abf30a023e454d11 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 12:04:12 -0800
Subject: [PATCH 60/62] Remove unnecessary curly braces
---
modules/images/image-loading-optimization/storage/data.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/images/image-loading-optimization/storage/data.php b/modules/images/image-loading-optimization/storage/data.php
index 731275d3ea..0f091a324b 100644
--- a/modules/images/image-loading-optimization/storage/data.php
+++ b/modules/images/image-loading-optimization/storage/data.php
@@ -115,7 +115,7 @@ function ilo_get_url_metrics_slug( array $query_vars ): string {
* @return string Nonce.
*/
function ilo_get_url_metrics_storage_nonce( string $slug ): string {
- return wp_create_nonce( "store_url_metrics:{$slug}" );
+ return wp_create_nonce( "store_url_metrics:$slug" );
}
/**
@@ -134,7 +134,7 @@ function ilo_get_url_metrics_storage_nonce( string $slug ): string {
* 0 if the nonce is invalid.
*/
function ilo_verify_url_metrics_storage_nonce( string $nonce, string $slug ): int {
- return (int) wp_verify_nonce( $nonce, "store_url_metrics:{$slug}" );
+ return (int) wp_verify_nonce( $nonce, "store_url_metrics:$slug" );
}
/**
From 1168a5a4b383be56b03398492dc5773e669e54c2 Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 12:06:50 -0800
Subject: [PATCH 61/62] Rename filter to ilo_url_metric_storage_lock_ttl
---
modules/images/image-loading-optimization/storage/lock.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/storage/lock.php b/modules/images/image-loading-optimization/storage/lock.php
index d30fe8d75c..2d8992e31f 100644
--- a/modules/images/image-loading-optimization/storage/lock.php
+++ b/modules/images/image-loading-optimization/storage/lock.php
@@ -34,7 +34,7 @@ function ilo_get_url_metric_storage_lock_ttl(): int {
*
* @param int $ttl TTL.
*/
- $ttl = (int) apply_filters( 'ilo_metrics_storage_lock_ttl', MINUTE_IN_SECONDS );
+ $ttl = (int) apply_filters( 'ilo_url_metric_storage_lock_ttl', MINUTE_IN_SECONDS );
return max( 0, $ttl );
}
From 698d89f6b8ce3a0cb90eec3f35eced3b4c22b7ca Mon Sep 17 00:00:00 2001
From: Weston Ruter
Date: Wed, 15 Nov 2023 12:57:22 -0800
Subject: [PATCH 62/62] Follow AIP-136
---
.../storage/rest-api.php | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/modules/images/image-loading-optimization/storage/rest-api.php b/modules/images/image-loading-optimization/storage/rest-api.php
index 423355f344..aaa7c98500 100644
--- a/modules/images/image-loading-optimization/storage/rest-api.php
+++ b/modules/images/image-loading-optimization/storage/rest-api.php
@@ -10,9 +10,24 @@
exit; // Exit if accessed directly.
}
+/**
+ * Namespace for image-loading-optimization.
+ *
+ * @var string
+ */
const ILO_REST_API_NAMESPACE = 'image-loading-optimization/v1';
-const ILO_URL_METRICS_ROUTE = '/url-metrics';
+/**
+ * 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
+ * create a new `ilo_url_metrics` post, or it will update an existing post if one already exists for the provided slug.
+ *
+ * @link https://google.aip.dev/136
+ * @var string
+ */
+const ILO_URL_METRICS_ROUTE = '/url-metrics:store';
/**
* Registers endpoint for storage of URL metric.