diff --git a/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php index bd455f1974..12dfb8f9f9 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-background-image-styled-tag-visitor.php @@ -143,6 +143,7 @@ private function get_common_lcp_element_external_background_image( OD_URL_Metric private function maybe_preload_external_lcp_background_image( OD_Tag_Visitor_Context $context ): void { // Gather the tuples of URL Metric group and the common LCP element external background image. // Note the groups of URL Metrics do not change across invocations, we just need to compute this once for all. + // TODO: Instead of populating this here, it could be done once per invocation during the od_start_template_optimization action since the page's OD_URL_Metric_Group_Collection is available there. if ( ! is_array( $this->group_common_lcp_element_external_background_images ) ) { $this->group_common_lcp_element_external_background_images = array(); foreach ( $context->url_metric_group_collection as $group ) { diff --git a/plugins/optimization-detective/class-od-html-tag-processor.php b/plugins/optimization-detective/class-od-html-tag-processor.php index d96e4eea8a..bb775441f4 100644 --- a/plugins/optimization-detective/class-od-html-tag-processor.php +++ b/plugins/optimization-detective/class-od-html-tag-processor.php @@ -702,29 +702,33 @@ public function is_admin_bar(): bool { } /** - * Append HTML to the HEAD. + * Appends raw HTML to the HEAD. * - * The provided HTML must be valid! No validation is performed. + * The provided HTML must be valid for insertion in the HEAD. No validation is currently performed. However, in the + * future the HTML Processor may be used to ensure the validity of the provided HTML. At that time, when invalid + * HTML is provided, this method may emit a `_doing_it_wrong()` warning. * * @since 0.4.0 * - * @param string $html HTML to inject. + * @param string $raw_html Raw HTML to inject. */ - public function append_head_html( string $html ): void { - $this->buffered_text_replacements[ self::END_OF_HEAD_BOOKMARK ][] = $html; + public function append_head_html( string $raw_html ): void { + $this->buffered_text_replacements[ self::END_OF_HEAD_BOOKMARK ][] = $raw_html; } /** - * Append HTML to the BODY. + * Appends raw HTML to the BODY. * - * The provided HTML must be valid! No validation is performed. + * The provided HTML must be valid for insertion in the BODY. No validation is currently performed. However, in the + * future the HTML Processor may be used to ensure the validity of the provided HTML. At that time, when invalid + * HTML is provided, this method may emit a `_doing_it_wrong()` warning. * * @since 0.4.0 * - * @param string $html HTML to inject. + * @param string $raw_html Raw HTML to inject. */ - public function append_body_html( string $html ): void { - $this->buffered_text_replacements[ self::END_OF_BODY_BOOKMARK ][] = $html; + public function append_body_html( string $raw_html ): void { + $this->buffered_text_replacements[ self::END_OF_BODY_BOOKMARK ][] = $raw_html; } /** diff --git a/plugins/optimization-detective/class-od-tag-visitor-context.php b/plugins/optimization-detective/class-od-tag-visitor-context.php index fae07d74c2..920ed35442 100644 --- a/plugins/optimization-detective/class-od-tag-visitor-context.php +++ b/plugins/optimization-detective/class-od-tag-visitor-context.php @@ -110,7 +110,7 @@ public function track_tag(): void { * @throws Error When property is unknown. */ public function __get( string $name ) { - // Note that there is intentionally not a case for 'visited_tag_state'. + // Note: There is intentionally not a 'visited_tag_state' case to expose $this->visited_tag_state. switch ( $name ) { case 'processor': return $this->processor; diff --git a/plugins/optimization-detective/class-od-template-optimization-context.php b/plugins/optimization-detective/class-od-template-optimization-context.php new file mode 100644 index 0000000000..66f6fe2ce6 --- /dev/null +++ b/plugins/optimization-detective/class-od-template-optimization-context.php @@ -0,0 +1,125 @@ + $normalized_query_vars Normalized query vars. + * @property-read non-empty-string $url_metrics_slug Slug for the od_url_metrics post. + * @property-read OD_Link_Collection $link_collection Link collection. + */ +final class OD_Template_Optimization_Context { + + /** + * URL Metric group collection. + * + * @since n.e.x.t + * @var OD_URL_Metric_Group_Collection + */ + private $url_metric_group_collection; + + /** + * ID for the od_url_metrics post which provided the URL Metrics in the collection. + * + * May be null if no post has been created yet. + * + * @since n.e.x.t + * @var positive-int|null + */ + private $url_metrics_id; + + /** + * Normalized query vars. + * + * @since n.e.x.t + * @var array + */ + private $normalized_query_vars; + + /** + * Slug for the od_url_metrics post. + * + * @since n.e.x.t + * @var non-empty-string + */ + private $url_metrics_slug; + + /** + * Link collection. + * + * @since n.e.x.t + * @var OD_Link_Collection + */ + private $link_collection; + + /** + * Constructor. + * + * @since n.e.x.t + * @access private + * + * @param OD_URL_Metric_Group_Collection $url_metric_group_collection URL Metric group collection. + * @param OD_Link_Collection $link_collection Link collection. + * @param array $normalized_query_vars Normalized query vars. + * @param non-empty-string $url_metrics_slug Slug for the od_url_metrics post. + * @param positive-int|null $url_metrics_id ID for the od_url_metrics post which provided the URL Metrics in the collection. May be null if no post has been created yet. + */ + public function __construct( OD_URL_Metric_Group_Collection $url_metric_group_collection, OD_Link_Collection $link_collection, array $normalized_query_vars, string $url_metrics_slug, ?int $url_metrics_id ) { + $this->url_metric_group_collection = $url_metric_group_collection; + $this->link_collection = $link_collection; + $this->normalized_query_vars = $normalized_query_vars; + $this->url_metrics_slug = $url_metrics_slug; + $this->url_metrics_id = $url_metrics_id; + } + + /** + * Gets a property. + * + * @since n.e.x.t + * + * @param string $name Property name. + * @return mixed Property value. + * + * @throws Error When property is unknown. + */ + public function __get( string $name ) { + // Note: There is intentionally not a 'processor' case to expose $this->processor. + switch ( $name ) { + case 'url_metrics_id': + return $this->url_metrics_id; + case 'url_metric_group_collection': + return $this->url_metric_group_collection; + case 'normalized_query_vars': + return $this->normalized_query_vars; + case 'url_metrics_slug': + return $this->url_metrics_slug; + case 'link_collection': + return $this->link_collection; + default: + throw new Error( + esc_html( + sprintf( + /* translators: %s is class member variable name */ + __( 'Unknown property %s.', 'optimization-detective' ), + __CLASS__ . '::$' . $name + ) + ) + ); + } + } +} diff --git a/plugins/optimization-detective/load.php b/plugins/optimization-detective/load.php index 3d69d6fa92..b396667797 100644 --- a/plugins/optimization-detective/load.php +++ b/plugins/optimization-detective/load.php @@ -124,6 +124,7 @@ class_alias( OD_URL_Metric_Group_Collection::class, 'OD_URL_Metrics_Group_Collec require_once __DIR__ . '/detection.php'; // Optimization logic. + require_once __DIR__ . '/class-od-template-optimization-context.php'; require_once __DIR__ . '/class-od-link-collection.php'; require_once __DIR__ . '/class-od-tag-visitor-registry.php'; require_once __DIR__ . '/class-od-visited-tag-state.php'; diff --git a/plugins/optimization-detective/optimization.php b/plugins/optimization-detective/optimization.php index e9240c05df..508f1cafc8 100644 --- a/plugins/optimization-detective/optimization.php +++ b/plugins/optimization-detective/optimization.php @@ -245,8 +245,16 @@ function od_optimize_template_output_buffer( string $buffer ): string { return $buffer; } - $slug = od_get_url_metrics_slug( od_get_normalized_query_vars() ); - $post = OD_URL_Metrics_Post_Type::get_post( $slug ); + $query_vars = od_get_normalized_query_vars(); + $slug = od_get_url_metrics_slug( $query_vars ); + $post = OD_URL_Metrics_Post_Type::get_post( $slug ); + + /** + * Post ID. + * + * @var positive-int|null $post_id + */ + $post_id = $post instanceof WP_Post ? $post->ID : null; $tag_visitor_registry = new OD_Tag_Visitor_Registry(); @@ -259,22 +267,40 @@ function od_optimize_template_output_buffer( string $buffer ): string { */ do_action( 'od_register_tag_visitors', $tag_visitor_registry ); - $current_etag = od_get_current_url_metrics_etag( $tag_visitor_registry, $wp_the_query, od_get_current_theme_template() ); - $group_collection = new OD_URL_Metric_Group_Collection( + $current_etag = od_get_current_url_metrics_etag( $tag_visitor_registry, $wp_the_query, od_get_current_theme_template() ); + $group_collection = new OD_URL_Metric_Group_Collection( $post instanceof WP_Post ? OD_URL_Metrics_Post_Type::get_url_metrics_from_post( $post ) : array(), $current_etag, od_get_breakpoint_max_widths(), od_get_url_metrics_breakpoint_sample_size(), od_get_url_metric_freshness_ttl() ); - $link_collection = new OD_Link_Collection(); + $link_collection = new OD_Link_Collection(); + + $template_optimization_context = new OD_Template_Optimization_Context( + $group_collection, + $link_collection, + $query_vars, + $slug, + $post_id + ); + + /** + * Fires before Optimization Detective starts optimizing the template. + * + * @since n.e.x.t + * + * @param OD_Template_Optimization_Context $template_optimization_context Template optimization context. + */ + do_action( 'od_start_template_optimization', $template_optimization_context ); + $visited_tag_state = new OD_Visited_Tag_State(); $tag_visitor_context = new OD_Tag_Visitor_Context( $processor, $group_collection, $link_collection, $visited_tag_state, - $post instanceof WP_Post && $post->ID > 0 ? $post->ID : null + $post_id ); $current_tag_bookmark = 'optimization_detective_current_tag'; $visitors = iterator_to_array( $tag_visitor_registry ); @@ -322,7 +348,23 @@ function od_optimize_template_output_buffer( string $buffer ): string { $visited_tag_state->reset(); } while ( $processor->next_tag( array( 'tag_closers' => 'skip' ) ) ); + // Inject detection script. + // TODO: When optimizing above, if we find that there is a stored LCP element but it fails to match, it should perhaps set $needs_detection to true and send the request with an override nonce. However, this would require backtracking and adding the data-od-xpath attributes. + if ( $needs_detection ) { + $processor->append_body_html( od_get_detection_script( $slug, $group_collection ) ); + } + + /** + * Fires after Optimization Detective finishes optimizing the template. + * + * @since n.e.x.t + * + * @param OD_Template_Optimization_Context $template_optimization_context Template optimization context. + */ + do_action( 'od_finish_template_optimization', $template_optimization_context ); + // Send any preload links in a Link response header and in a LINK tag injected at the end of the HEAD. + // Additional links may have been added at the od_finish_template_optimization action, so this must come after. if ( count( $link_collection ) > 0 ) { $response_header_links = $link_collection->get_response_header(); if ( ! is_null( $response_header_links ) && ! headers_sent() ) { @@ -331,11 +373,5 @@ function od_optimize_template_output_buffer( string $buffer ): string { $processor->append_head_html( $link_collection->get_html() ); } - // Inject detection script. - // TODO: When optimizing above, if we find that there is a stored LCP element but it fails to match, it should perhaps set $needs_detection to true and send the request with an override nonce. However, this would require backtracking and adding the data-od-xpath attributes. - if ( $needs_detection ) { - $processor->append_body_html( od_get_detection_script( $slug, $group_collection ) ); - } - return $processor->get_updated_html(); } diff --git a/plugins/optimization-detective/tests/test-cases/admin-bar/expected.html b/plugins/optimization-detective/tests/test-cases/admin-bar/expected.html index 532d16658f..cef1b2bb25 100644 --- a/plugins/optimization-detective/tests/test-cases/admin-bar/expected.html +++ b/plugins/optimization-detective/tests/test-cases/admin-bar/expected.html @@ -2,7 +2,8 @@ ... - + +