Make WordPress Core


Ignore:
Timestamp:
01/11/2026 06:34:57 AM (2 months ago)
Author:
westonruter
Message:

Themes: Use WP_HTML_Tag_Processor to insert the block template skip link instead of JavaScript.

  • The skip link now works when JavaScript is turned off.
  • By removing the script, the amount of JavaScript sent to the client is reduced for a very marginal performance improvement.
  • A new wp-block-template-skip-link stylesheet is registered, with minification and path data for inlining.
  • The CSS for the skip link now has an RTL version generated, although it is not yet served when the styles are inlined. See #61625.
  • The wp_enqueue_block_template_skip_link() function now exclusively enqueues the stylesheet since the script is removed.
  • For backwards-compatibility, the skip link will continue to be omitted if the_block_template_skip_link() is unhooked from the wp_footer action or wp_enqueue_block_template_skip_link() is unhooked from wp_enqueue_scripts.

Developed in https://github.com/WordPress/wordpress-develop/pull/10676

Follow-up to [56932], [51003].

Props rutviksavsani, westonruter, dmsnell, whiteshadow01, Slieptsov.
See #59505, #53176.
Fixes #64361.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-template.php

    r61431 r61469  
    302302    // Wrap block template in .wp-site-blocks to allow for specific descendant styles
    303303    // (e.g. `.wp-site-blocks > *`).
    304     return '<div class="wp-site-blocks">' . $content . '</div>';
     304    $template_html = '<div class="wp-site-blocks">' . $content . '</div>';
     305
     306    // Back-compat for plugins that disable functionality by unhooking one of these actions.
     307    if (
     308        ! has_action( 'wp_footer', 'the_block_template_skip_link' ) ||
     309        ! has_action( 'wp_enqueue_scripts', 'wp_enqueue_block_template_skip_link' )
     310    ) {
     311        return $template_html;
     312    }
     313
     314    return _block_template_add_skip_link( $template_html );
     315}
     316
     317/**
     318 * Inserts the block template skip-link into the template HTML.
     319 *
     320 * When a `MAIN` element exists in the template, this function will ensure
     321 * that the element contains an `id` attribute, and it will insert a link to
     322 * that `MAIN` element before the first `DIV.wp-site-blocks` element, which
     323 * is the wrapper for all blocks in a block template as constructed by
     324 * {@see get_the_block_template_html()}.
     325 *
     326 * Example:
     327 *
     328 *     // Input.
     329 *     <div class="wp-site-blocks">
     330 *         <nav>...</nav>
     331 *         <main>
     332 *             <h2>...
     333 *
     334 *     // Output.
     335 *     <a href="#wp--skip-link--target" id="wp-skip-link" class="...">
     336 *     <div class="wp-site-blocks">
     337 *         <nav>...</nav>
     338 *         <main id="wp--skip-link--target">
     339 *             <h2>...
     340 *
     341 * When the `MAIN` element already contains a non-empty `id` value it will be
     342 * used instead of the default skip-link id.
     343 *
     344 * @access private
     345 * @since 7.0.0
     346 *
     347 * @param string $template_html Block template markup.
     348 * @return string Modified markup with skip link when applicable.
     349 */
     350function _block_template_add_skip_link( string $template_html ): string {
     351    // Anonymous subclass of WP_HTML_Tag_Processor to access protected bookmark spans.
     352    $processor = new class( $template_html ) extends WP_HTML_Tag_Processor {
     353        /**
     354         * Inserts text before the current token.
     355         *
     356         * @param string $text Text to insert.
     357         */
     358        public function insert_before( string $text ) {
     359            $this->set_bookmark( 'here' );
     360            $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->bookmarks['here']->start, 0, $text );
     361        }
     362    };
     363
     364    // Find and bookmark the first DIV.wp-site-blocks.
     365    if (
     366        ! $processor->next_tag(
     367            array(
     368                'tag_name'   => 'DIV',
     369                'class_name' => 'wp-site-blocks',
     370            )
     371        )
     372    ) {
     373        return $template_html;
     374    }
     375    $processor->set_bookmark( 'skip_link_insertion_point' );
     376
     377    // Ensure the MAIN element has an ID.
     378    if ( ! $processor->next_tag( 'MAIN' ) ) {
     379        return $template_html;
     380    }
     381
     382    $skip_link_target_id = $processor->get_attribute( 'id' );
     383    if ( ! is_string( $skip_link_target_id ) || '' === $skip_link_target_id ) {
     384        $skip_link_target_id = 'wp--skip-link--target';
     385        $processor->set_attribute( 'id', $skip_link_target_id );
     386    }
     387
     388    // Seek back to the bookmarked insertion point.
     389    $processor->seek( 'skip_link_insertion_point' );
     390
     391    $skip_link = sprintf(
     392        '<a class="skip-link screen-reader-text" id="wp-skip-link" href="%s">%s</a>',
     393        esc_url( '#' . $skip_link_target_id ),
     394        /* translators: Hidden accessibility text. */
     395        esc_html__( 'Skip to content' )
     396    );
     397    $processor->insert_before( $skip_link );
     398
     399    return $processor->get_updated_html();
    305400}
    306401
Note: See TracChangeset for help on using the changeset viewer.