Skip to content

Commit

Permalink
Lightbox: Re-use existing Tag Processor instance.
Browse files Browse the repository at this point in the history
Resolves WordPress/gutenberg#55123

Replaces existing single-letter-named Tag Processor instances with
a single processor that's reuse throughout the function.

As a work-in-progress (WIP) patch, this is a preliminary exploration
and not ready for review or merging.
  • Loading branch information
dmsnell committed Oct 12, 2023
1 parent a8d5328 commit f924e03
Showing 1 changed file with 91 additions and 55 deletions.
146 changes: 91 additions & 55 deletions src/wp-includes/blocks/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,31 @@ function block_core_image_get_lightbox_settings( $block ) {
*/
function block_core_image_render_lightbox( $block_content, $block ) {
$processor = new WP_HTML_Tag_Processor( $block_content );
if ( ! $processor->next_tag() ) {
return $block_content;
}

/*
* This code jumps around a fair amount, so bookmarking the first
* tag provides the ability to "reset" to and start scanning
* again without recreating a new Tag Processor instance.
*/
$processor->set_bookmark( 'first tag' );

$aria_label = __( 'Enlarge image' );
// The first tag that was already found could be an image, but it may not be.
if ( 'IMG' !== $processor->get_tag() && ! $processor->next_tag( 'IMG' ) ) {
return $block_content;
}

$processor->next_tag( 'img' );
$alt_attribute = $processor->get_attribute( 'alt' );
$alt_attribute = is_string( $alt_attribute ) ? trim( $alt_attribute ) : null;

// An empty alt attribute `alt=""` is valid for decorative images.
if ( is_string( $alt_attribute ) ) {
$alt_attribute = trim( $alt_attribute );
}

// It only makes sense to append the alt text to the button aria-label when the alt text is non-empty.
if ( $alt_attribute ) {
if ( ! empty( $alt_attribute ) ) {
/* translators: %s: Image alt text. */
$aria_label = sprintf( __( 'Enlarge image: %s' ), $alt_attribute );
} else {
$aria_label = __( 'Enlarge image' );
}

// Currently, we are only enabling the zoom animation.
Expand All @@ -163,12 +173,16 @@ function block_core_image_render_lightbox( $block_content, $block ) {
$scale_attr = false;
}

$w = new WP_HTML_Tag_Processor( $block_content );
$w->next_tag( 'figure' );
$w->add_class( 'wp-lightbox-container' );
$w->set_attribute( 'data-wp-interactive', true );
// Jump back to the first tag and find the first figure.
$processor->seek( 'first tag' );
if ( 'FIGURE' !== $processor->get_tag() ) {
$processor->next_tag( 'FIGURE' );
}

$w->set_attribute(
$processor->set_bookmark( 'first figure' );
$processor->add_class( 'wp-lightbox-container' );
$processor->set_attribute( 'data-wp-interactive', true );
$processor->set_attribute(
'data-wp-context',
sprintf(
'{ "core":
Expand Down Expand Up @@ -196,14 +210,16 @@ function block_core_image_render_lightbox( $block_content, $block ) {
__( 'Enlarged image' )
)
);
$w->next_tag( 'img' );
$w->set_attribute( 'data-wp-init', 'effects.core.image.setCurrentSrc' );
$w->set_attribute( 'data-wp-on--load', 'actions.core.image.handleLoad' );
$w->set_attribute( 'data-wp-effect', 'effects.core.image.setButtonStyles' );

// Assume that an image appears within the currently-matched FIGURE element. It may not.
$processor->next_tag( 'img' );
$processor->set_attribute( 'data-wp-init', 'effects.core.image.setCurrentSrc' );
$processor->set_attribute( 'data-wp-on--load', 'actions.core.image.handleLoad' );
$processor->set_attribute( 'data-wp-effect', 'effects.core.image.setButtonStyles' );
$w->set_attribute( 'data-wp-effect--setStylesOnResize', 'effects.core.image.setStylesOnResize' );
$body_content = $w->get_updated_html();
$body_content = $processor->get_updated_html();

// Wrap the image in the body content with a button.
// Insert a button in the body content before the image.
$img = null;
preg_match( '/<img[^>]+>/', $body_content, $img );

Expand All @@ -222,42 +238,62 @@ function block_core_image_render_lightbox( $block_content, $block ) {

$body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );

// We need both a responsive image and an enlarged image to animate
// the zoom seamlessly on slow internet connections; the responsive
// image is a copy of the one in the body, which animates immediately
// as the lightbox is opened, while the enlarged one is a full-sized
// version that will likely still be loading as the animation begins.
$m = new WP_HTML_Tag_Processor( $block_content );
$m->next_tag( 'figure' );
$m->add_class( 'responsive-image' );
$m->next_tag( 'img' );
// We want to set the 'src' attribute to an empty string in the responsive image
// because otherwise, as of this writing, the wp_filter_content_tags() function in
// WordPress will automatically add a 'srcset' attribute to the image, which will at
// times cause the incorrectly sized image to be loaded in the lightbox on Firefox.
// Because of this, we bind the 'src' attribute explicitly the current src to reliably
// use the exact same image as in the content when the lightbox is first opened while
// we wait for the larger image to load.
$m->set_attribute( 'src', '' );
$m->set_attribute( 'data-wp-bind--src', 'context.core.image.imageCurrentSrc' );
$m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' );
$initial_image_content = $m->get_updated_html();

$q = new WP_HTML_Tag_Processor( $block_content );
$q->next_tag( 'figure' );
$q->add_class( 'enlarged-image' );
$q->next_tag( 'img' );

// We set the 'src' attribute to an empty string to prevent the browser from loading the image
// on initial page load, then bind the attribute to a selector that returns the full-sized image src when
// the lightbox is opened. We could use 'loading=lazy' in combination with the 'hidden' attribute to
// accomplish the same behavior, but that approach breaks progressive loading of the image in Safari
// and Chrome (see https://github.com/WordPress/gutenberg/pull/52765#issuecomment-1674008151). Until that
// is resolved, manually setting the 'src' seems to be the best solution to load the large image on demand.
$q->set_attribute( 'src', '' );
$q->set_attribute( 'data-wp-bind--src', 'selectors.core.image.enlargedImgSrc' );
$q->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' );
$enlarged_image_content = $q->get_updated_html();
/*
* This code needs to generate a responsive image and an enlarged image
* to animate zoom seamlessly on slow internet connections; the responsive
* image is a copy of the one in the body, which animates immediately
* as the lightbox is opened, while the enlarged one is a full-sized
* version that will likely still be loading as the animation begins.
*
* In order to reuse the existing Tag Processor, changes made before
* need to be undone before setting the new changes here.
*/
$processor->seek( 'first figure' );
$processor->remove_class( 'wp-lightbox-container' );
$processor->remove_attribute( 'data-wp-context' );
$processor->remove_attribute( 'data-wp-interactive' );
$processor->add_class( 'responsive-image' );

$processor->seek( 'first figure' );
$processor->next_tag( 'img' );
$processor->remove_attribute( 'data-wp-init' );
$processor->remove_attribute( 'data-wp-on--load' );
$processor->remove_attribute( 'data-wp-effect' );

/*
* The 'src' attribute needs to be an empty string in the responsive image because
* otherwise, as of this writing, the wp_filter_content_tags() function in WordPress
* will automatically add a 'srcset' attribute to the image, which will at times
* cause the incorrectly sized image to be loaded in the lightbox on Firefox.
*
* Because of this, the 'src' attribute needs to be explicitly bound to the current
* src to reliably use the exact same image as in the content when the lightbox is
* first opened while waiting for the larger image to load.
*/
$processor->set_attribute( 'src', '' );
$processor->set_attribute( 'data-wp-bind--src', 'context.core.image.imageCurrentSrc' );
$processor->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' );
$initial_image_content = $processor->get_updated_html();

/*
* Reusing the existing Tag Processor again requires resetting state.
*/
$processor->seek( 'first figure' );
$processor->remove_class( 'responsive-image' );
$processor->add_class( 'enlarged-image' );

/*
* It's necessary to set the 'src' attribute to an empty string to prevent the browser from loading the image
* on initial page load, then to bind the attribute to a selector that returns the full-sized image src when
* the lightbox is opened. The combination of 'loading=lazy' with the 'hidden' attribute could be used to
* accomplish the same behavior, but that approach breaks progressive loading of the image in Safari
* and Chrome (see https://github.com/WordPress/gutenberg/pull/52765#issuecomment-1674008151). Until that
* is resolved, manually setting the 'src' loads the large image on demand without causing renderering issues.
*/
$processor->next_tag( 'img' );
$processor->set_attribute( 'data-wp-bind--src', 'selectors.core.image.enlargedImgSrc' );
$processor->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' );
$enlarged_image_content = $processor->get_updated_html();

// If the current theme does NOT have a `theme.json`, or the colors are not defined,
// we need to set the background color & close button color to some default values
Expand Down

0 comments on commit f924e03

Please sign in to comment.