diff --git a/plugins/performance-lab/includes/server-timing/class-perflab-server-timing.php b/plugins/performance-lab/includes/server-timing/class-perflab-server-timing.php index 70ec0f4e98..8ac8cb928d 100644 --- a/plugins/performance-lab/includes/server-timing/class-perflab-server-timing.php +++ b/plugins/performance-lab/includes/server-timing/class-perflab-server-timing.php @@ -144,7 +144,10 @@ public function has_registered_metric( string $metric_slug ): bool { /** * Outputs the Server-Timing header. * - * This method must be called before rendering the page. + * This must run before the response body is flushed. When output buffering is enabled it is called at the + * {@see 'wp_finalized_template_enhancement_output_buffer'} action, after the template has been captured and + * enhanced; otherwise it is called at the {@see 'wp_before_include_template'} action, right before the template + * is included. Any extra arguments passed by those hooks are ignored. * * @since 1.8.0 */ @@ -236,56 +239,30 @@ public function use_output_buffer(): bool { /** * Adds hooks to send the Server-Timing header. * - * When output buffering is enabled, buffer as early as possible so that any other plugins that also do output - * buffering will be able to register Server-Timing metrics. The first output buffer callback to be registered - * is the last one to be called, so by starting the Server-Timing output buffer as soon as possible we can be - * assured that other plugins' output buffer callbacks will run before the Server-Timing one that sends the - * Server-Timing header. + * Since WordPress 6.9, this relies on the template enhancement output buffer introduced in Core-43258 (see + * r60936) rather than starting its own output buffer. + * + * When output buffering is enabled, the header is sent at the {@see 'wp_finalized_template_enhancement_output_buffer'} + * action so that metrics measured while the template is rendering are included. Registering a callback on that + * action also opts in to starting the core output buffer (see {@see wp_should_output_buffer_template_for_enhancement()}), + * and core ensures the {@see 'wp_template_enhancement_output_buffer'} filter (used by other consumers such as + * Optimization Detective) runs before this action, so their metrics are captured before the header is sent. + * + * When output buffering is disabled, the header is sent at the {@see 'wp_before_include_template'} action, right + * before the template is included. Note this action only fires when the resolved template is a readable file, + * unlike the previously used `template_include` filter which fired unconditionally. * * @since 3.2.0 + * @since n.e.x.t Relies on the WordPress 6.9 template enhancement output buffer instead of starting its own. */ public function add_hooks(): void { if ( $this->use_output_buffer() ) { - add_action( 'template_redirect', array( $this, 'start_output_buffer' ), PHP_INT_MIN ); + add_action( 'wp_finalized_template_enhancement_output_buffer', array( $this, 'send_header' ) ); } else { - add_filter( 'template_include', array( $this, 'on_template_include' ), PHP_INT_MAX ); + add_action( 'wp_before_include_template', array( $this, 'send_header' ) ); } } - /** - * Hook callback for the 'template_include' filter. - * - * This effectively initializes the class to send the Server-Timing header at the right point. - * - * This method is solely intended for internal use within WordPress. - * - * @since 1.8.0 - * - * @param mixed $passthrough Optional. Filter value. Default null. - * @return mixed Unmodified value of $passthrough. - */ - public function on_template_include( $passthrough = null ) { - $this->send_header(); - return $passthrough; - } - - /** - * Starts output buffering to send the Server-Timing header right before returning the buffer. - * - * @since 3.2.0 - */ - public function start_output_buffer(): void { - ob_start( - function ( string $output, ?int $phase ): string { - // Only send the header when the buffer is not being cleaned. - if ( ( $phase & PHP_OUTPUT_HANDLER_CLEAN ) === 0 ) { - $this->send_header(); - } - return $output; - } - ); - } - /** * Formats the header segment for a single metric. * diff --git a/plugins/performance-lab/tests/includes/server-timing/test-load.php b/plugins/performance-lab/tests/includes/server-timing/test-load.php index f55021f79e..075187f15b 100644 --- a/plugins/performance-lab/tests/includes/server-timing/test-load.php +++ b/plugins/performance-lab/tests/includes/server-timing/test-load.php @@ -17,8 +17,8 @@ public function test_perflab_server_timing(): void { $server_timing = perflab_server_timing(); $this->assertFalse( $server_timing->use_output_buffer() ); - $this->assertSame( PHP_INT_MAX, has_filter( 'template_include', array( $server_timing, 'on_template_include' ) ), 'template_include filter not added' ); - $this->assertFalse( has_action( 'template_redirect', array( $server_timing, 'start_output_buffer' ) ), 'template_redirect action added' ); + $this->assertSame( 10, has_action( 'wp_before_include_template', array( $server_timing, 'send_header' ) ), 'wp_before_include_template action not added' ); + $this->assertFalse( has_action( 'wp_finalized_template_enhancement_output_buffer', array( $server_timing, 'send_header' ) ), 'wp_finalized_template_enhancement_output_buffer action added' ); $server_timing2 = perflab_server_timing(); $this->assertSame( $server_timing, $server_timing2, 'Different instance returned' ); @@ -28,15 +28,15 @@ public function test_perflab_server_timing(): void { * @covers Perflab_Server_Timing::add_hooks */ public function test_perflab_server_timing_with_output_buffering(): void { - remove_all_actions( 'template_redirect' ); - remove_all_filters( 'template_include' ); + remove_all_actions( 'wp_before_include_template' ); + remove_all_actions( 'wp_finalized_template_enhancement_output_buffer' ); $server_timing = perflab_server_timing(); add_filter( 'perflab_server_timing_use_output_buffer', '__return_true' ); $this->assertTrue( $server_timing->use_output_buffer() ); $server_timing->add_hooks(); - $this->assertFalse( has_filter( 'template_include', array( $server_timing, 'on_template_include' ) ), 'template_include filter added' ); - $this->assertSame( PHP_INT_MIN, has_action( 'template_redirect', array( $server_timing, 'start_output_buffer' ) ), 'template_redirect action not added' ); + $this->assertFalse( has_action( 'wp_before_include_template', array( $server_timing, 'send_header' ) ), 'wp_before_include_template action added' ); + $this->assertSame( 10, has_action( 'wp_finalized_template_enhancement_output_buffer', array( $server_timing, 'send_header' ) ), 'wp_finalized_template_enhancement_output_buffer action not added' ); } public function test_perflab_server_timing_register_metric(): void {