Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preload links LCP picture elements #1707

Merged

Conversation

b1ink0
Copy link
Contributor

@b1ink0 b1ink0 commented Nov 27, 2024

Fixes #1312

This PR fixes the issue of preloading fallback image instead of preloading the images in the source element when wrapped in picture element.

Example Picture element

<picture class="wp-picture-7" style="display: contents;">
   <source 
        type="image/avif" 
        srcset="
          http://localhost:8888/wp-content/uploads/2024/11/demo-img-1024x683-jpg.avif 1024w, 
          http://localhost:8888/wp-content/uploads/2024/11/demo-img-300x200-jpg.avif 300w, 
          http://localhost:8888/wp-content/uploads/2024/11/demo-img-768x512-jpg.avif 768w, 
          http://localhost:8888/wp-content/uploads/2024/11/demo-img-1536x1024-jpg.avif 1536w, 
          http://localhost:8888/wp-content/uploads/2024/11/demo-img-2048x1365-jpg.avif 2048w
        " 
        sizes="(max-width: 645px) 100vw, 645px"
   >
   <img 
        data-od-removed-fetchpriority="high" 
        data-od-xpath="/*[1][self::HTML]/*[2][self::BODY]/*[1][self::DIV]/*[2][self::MAIN]/*[1][self::DIV]/*[3][self::DIV]/*[2][self::FIGURE]/*[1][self::PICTURE]/*[2][self::IMG]" 
        data-dominant-color="6a6a6a" 
        data-has-transparency="false" 
        style="--dominant-color: #6a6a6a;" 
        decoding="async" 
        width="1024" 
        height="683" 
        src="http://localhost:8888/wp-content/uploads/2024/11/demo-img-1024x683.jpg" 
        alt="" 
        class="wp-image-7 not-transparent" 
        srcset="
            http://localhost:8888/wp-content/uploads/2024/11/demo-img-1024x683.jpg 1024w,
            http://localhost:8888/wp-content/uploads/2024/11/demo-img-300x200.jpg 300w,
            http://localhost:8888/wp-content/uploads/2024/11/demo-img-768x512.jpg 768w,
            http://localhost:8888/wp-content/uploads/2024/11/demo-img-1536x1024.jpg 1536w,
            http://localhost:8888/wp-content/uploads/2024/11/demo-img-2048x1365.jpg 2048w
        " 
       sizes="(max-width: 645px) 100vw, 645px"
    >
</picture>

Preload Link Before

<link 
    data-od-added-tag="" 
    rel="preload" 
    fetchpriority="high" 
    as="image" 
    href="http://localhost:8888/wp-content/uploads/2024/11/demo-img-1024x683.jpg" 
    imagesrcset="
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-1024x683.jpg 1024w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-300x200.jpg 300w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-768x512.jpg 768w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-1536x1024.jpg 1536w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-2048x1365.jpg 2048w
    " 
    imagesizes="(max-width: 645px) 100vw, 645px" media="screen and (max-width: 480px)"
>

Preload Link After

<link 
    data-od-added-tag="" 
    rel="preload" 
    fetchpriority="high" 
    as="image" 
    imagesrcset="
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-1024x683-jpg.avif 1024w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-300x200-jpg.avif 300w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-768x512-jpg.avif 768w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-1536x1024-jpg.avif 1536w,
        http://localhost:8888/wp-content/uploads/2024/11/demo-img-2048x1365-jpg.avif 2048w
    " 
    imagesizes="(max-width: 645px) 100vw, 645px" type="image/avif" media="screen and (max-width: 480px)"
>

This PR also introduces a public OD_HTML_Tag_Processor::get_breadcrumbs() method which mirrors the functionality of WP_HTML_Processor::get_breadcrumbs(). As part of that, a bug was discovered in how OD_HTML_Tag_Processor handles implicitly-closed P tags which resulted in duplicate XPaths being generated.

Additionally, when an IMG had a referrerpolicy attribute this is copied to the preload LINK the same as the crossorigin attribute is copied.

Copy link

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: b1ink0 <b1ink0@git.wordpress.org>
Co-authored-by: westonruter <westonruter@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

Comment on lines 22 to 35
/**
* Flag to indicate whether we're within a `<picture>` element.
*
* @var bool
*/
private $is_within_picture = false;

/**
* Collected `<source>` elements within the current `<picture>`.
*
* @var array<int, array{srcset?: string|true|null,sizes?: string|true|null,type?: string|true|null,media?: string|true|null,crossorigin?: string|true|null}>
*/
private $collected_sources = array();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these need to be member variables? They're only ever used in one method, so wouldn't they make more sense as just regular variables?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, well I see it's because you're relying on this being invoked multiple times, so you're wanting to persist this state across multiple tag visits.

// Opening <picture> tag.
$this->is_within_picture = true;
$this->collected_sources = array();
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this condition should never occur because in the optimization loop it only ever visits open tags:

} while ( $processor->next_open_tag() );

Comment on lines 64 to 70
$this->collected_sources[] = array(
'srcset' => $processor->get_attribute( 'srcset' ),
'sizes' => $processor->get_attribute( 'sizes' ),
'type' => $processor->get_attribute( 'type' ),
'media' => $processor->get_attribute( 'media' ),
'crossorigin' => $this->get_attribute_value( $processor, 'crossorigin' ),
);
Copy link
Member

@westonruter westonruter Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs some additional logic. We need to prevent a scenario where there is a PICTURE with the first SOURCE being an AVIF image and then the second being a WebP, with the third being a JPEG fallback. We must only ever add a preload link for the first SOURCE because otherwise if a browser supports AVIF and WebP then they could end up preloading both but only the first would be used.

See comments:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi can you please complete this comment it seems to be cutoff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have identified different cases which we need to handle.

  1. Picture element with multiple source tags without media attribute.
  • Select first source
  1. Picture element with multiple source tags with media attribute.
  • Add preload link for every source
  1. Picture element with no source tags.
  • Add preload link using img tags srcset
  1. Picture element with multiple source but some have media attribute and some don't have media attribute.
  • How should we handle this case?

Did I miss some cases?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Sorry for the poor comment. Not sure how that happened.

  1. Picture element with multiple source tags without media attribute.

And naturally with type attributes.

2. Picture element with multiple source tags with media attribute.

And without type attributes.

3. Picture element with no source tags.

This would be an error scenario. A PICTURE tag without a SOURCE is not valid. So just do nothing in this case.

4. Picture element with multiple source but some have media attribute and some don't have media attribute.

I think this would be an unusual case. I suggest we skip this scenario.

In fact, I suggest we may want to skip handling PICTURE tags that have SOURCE tags with media attributes entirely. This greatly complicate things, since we'd need to make sure that only one of the SOURCE tags ever get preloaded, but this would require computing some negated media query or something. As I recall, when a PICTURE decides which SOURCE to use, it will iterate over the SOURCE tags until it arrives at the first one that has a matching media and has a supported type. But this first-match logic is difficult to replicate for preload links.

Since our Modern Image Formats plugin only outputs SOURCE tags with type attributes, this would be the most important to support.

We can leave support for media attributes for a future enhancement, especially as we find there is a need for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gemini concurs that preloading SOURCE with media attributes is not feasible, after initially getting it wrong: https://g.co/gemini/share/57ac56545028

So yeah, for PICTURE, grab the first SOURCE child and only preload it if none of the SOURCE tags have a media attribute. The only way to properly prioritize such an image is to ensure that fetchpriority=high is on the IMG tag, which we should make sure is happening in addition to adding preload links.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now implemented this in df1d90e

So yeah, for PICTURE, grab the first SOURCE child and only preload it if none of the SOURCE tags have a media attribute.

But I have a doubt about the part

The only way to properly prioritize such an image is to ensure that fetchpriority=high is on the IMG tag, which we should make sure is happening in addition to adding preload links.

Currently preload link has fetchpriority=high set but the image of that picture element has data-od-removed-fetchpriority="high" should we add fetchpriority=high to the image and remove the added data-od-removed-fetchpriority aatribute?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I just tested this out and it is actually working currently as expected. Chrome is reporting that the LCP element is the PICTURE > IMG tag, not the PICTURE wrapper tag:

image

So when all viewport groups are populated with URL Metrics, it is resulting in the following markup in trunk:

<picture class="wp-picture-394" style="display: contents"
  ><source
    type="image/webp"
    srcset="
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-1024x683-jpg.webp  1024w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-300x200-jpg.webp    300w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-768x512-jpg.webp    768w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-1536x1024-jpg.webp 1536w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-jpg.webp           1620w
    "
    sizes="(max-width: 1024px) 100vw, 1024px" />
  <img
    data-od-fetchpriority-already-added=""
    data-od-xpath="/*[1][self::HTML]/*[2][self::BODY]/*[3][self::DIV]/*[3][self::DIV]/*[1][self::DIV]/*[1][self::MAIN]/*[1][self::ARTICLE]/*[2][self::DIV]/*[1][self::FIGURE]/*[1][self::PICTURE]/*[2][self::IMG]"
    data-dominant-color="8a9464"
    data-has-transparency="false"
    style="--dominant-color: #8a9464"
    fetchpriority="high"
    decoding="async"
    width="1024"
    height="683"
    src="http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-1024x683.jpg"
    alt=""
    class="wp-image-394 not-transparent"
    srcset="
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-1024x683.jpg  1024w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-300x200.jpg    300w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-768x512.jpg    768w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-1536x1024.jpg 1536w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi-1568x1045.jpg 1568w,
      http://localhost:8888/wp-content/uploads/2024/11/1620px-Australasian_swamphen_Porphyrio_melanotus_Tiritiri_Matangi.jpg           1620w
    "
    sizes="(max-width: 1024px) 100vw, 1024px"
/></picture>

I believe you saw data-od-removed-fetchpriority="high" being added because you didn't have URL Metrics collected for some of the viewport groups, and currently the OD_URL_Metric_Group_Collection::get_common_lcp_element() method is defined so that all URL Metrics groups must be populated in order for the method to return an element. I think this should be changed to align better with how we determine whether to do lazy-loading: if both the narrowest viewport group (i.e. mobile) and the widest viewport group (i.e. desktop) are populated with URL Metrics and these groups both return an LCP Element via $group->get_lcp_element() which has the same XPath, then we should consider that it is likely for all of the viewport groups to have the same LCP element (i.e. phablet and tablet).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've filed this in #1710.

$tag = $processor->get_tag();

// Handle opening and closing `<picture>` tags.
if ( 'PICTURE' === $tag ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of relying on this __invoke function being called on the PICTURE and then also on the child SOURCE tags (and IMG) tag, I suggest instead that when 'PICTURE' === $tag that this here call a new method like process_picture() and that this method would:

  1. Call $processor->set_bookmark( 'img-prioritizer-picture' )
  2. Add a while( $processor->next_tag() loop. Note that our subclass's next_tag() method always visits tag closers until $processor->get_tag() === 'picture' && $processor->is_tag_closer().
  3. Gather up the attributes data from the SOURCE tags and the final IMG tag.
  4. Finally reset the processor back to where you started: $processor->set_bookmark('img-prioritizer-picture').
  5. Process the data collected from the child tags, and call $context->link_collection->add_link(), leaving the other location as-is for just for processing bare IMG tags.

This approach of a sub-loop was also taken by Embed Optimizer in embed_optimizer_update_markup().

Comment on lines 191 to 225
if ( $this->is_within_picture && count( $this->collected_sources ) > 0 ) {
foreach ( $this->collected_sources as $source ) {
$link_attributes = array_merge(
array(
'rel' => 'preload',
'fetchpriority' => 'high',
'as' => 'image',
),
array_filter(
array(
'href' => isset( $source['srcset'] ) && is_string( $source['srcset'] )
? explode( ' ', $source['srcset'] )[0]
: '',
'imagesrcset' => isset( $source['srcset'] ) && is_string( $source['srcset'] ) ? $source['srcset'] : '',
'imagesizes' => isset( $source['sizes'] ) && is_string( $source['sizes'] ) ? $source['sizes'] : '',
'type' => isset( $source['type'] ) && is_string( $source['type'] ) ? $source['type'] : '',
'media' => isset( $source['media'] ) && is_string( $source['media'] ) ? 'screen and ' . $source['media'] : 'screen',
),
static function ( string $value ): bool {
return '' !== $value;
}
)
);

if ( isset( $source['crossorigin'] ) ) {
$link_attributes['crossorigin'] = 'use-credentials' === $source['crossorigin'] ? 'use-credentials' : 'anonymous';
}

$context->link_collection->add_link(
$link_attributes,
$group->get_minimum_viewport_width(),
$group->get_maximum_viewport_width()
);
}
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above, I think this logic should rather go into a process_picture() method along with the other new logic above.

@westonruter westonruter added this to the image-prioritizer n.e.x.t milestone Nov 27, 2024
@westonruter westonruter added [Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) [Type] Enhancement A suggestion for improvement of an existing feature labels Nov 27, 2024
@westonruter
Copy link
Member

We'll also need to add unit test cases specifically to test the various PICTURE scenarios.

@westonruter westonruter changed the title Image Prioritizer fails to preload LCP images for picture elements Add preload links LCP picture elements Nov 27, 2024

if ( 'PICTURE' === $tag ) {
$this->process_picture( $processor, $context );
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to return true because we need to capture this tag in URL Metrics, correct? Or maybe we don't if the browser reports that the IMG inside of the PICTURE is the LCP element. We should double check which element is reported as LCP.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes its need to be true now the process_picture return will handle should URL Metrics should be tracked or not.

Comment on lines 38 to 45
}

if ( 'IMG' !== $tag ) {
return false;
}

return $this->process_img( $processor, $context );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well refactor this a bit so the cases are consistent:

Suggested change
}
if ( 'IMG' !== $tag ) {
return false;
}
return $this->process_img( $processor, $context );
}
} elseif ( 'IMG' === $tag ) {
return $this->process_img( $processor, $context );
}
return false;
}

Comment on lines 354 to 363
$steps = explode( '/', $xpath );
if ( count( $steps ) < 3 ) {
// There is no parent.
return null;
}
$second_last_step = $steps[ count( $steps ) - 2 ];
if ( (bool) preg_match( '/\[self::([^\]]+)\]/', $second_last_step, $matches ) ) {
return $matches[1];
}
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently a private OD_HTML_Tag_Processor::get_breadcrumbs() method which returns an array of tuples for the tag name and index for each ancestor tag. I think we could reuse that here. I suggest:

  1. Rename the existing OD_HTML_Tag_Processor::get_breadcrumbs() to get_indexed_breadcrumbs().
  2. Add a new public method OD_HTML_Tag_Processor::get_breadcrumbs() which simply returns $this->open_stack_tags, which would be essentially providing the WP_HTML_Processor::get_breadcrumbs() method in the WP_HTML_Tag_Processor context.
  3. This get_parent_tag_name() method could then be implemented as:
$breadcrumbs = $context->processor->get_breadcrumbs();
$length      = count( $breadcrumbs );
if ( $length < 2 ) {
    return null;
}
return $breadcrumbs[ $length - 2 ];

Comment on lines 67 to 73
if ( 'PICTURE' === $parent_tag ) {
return true;
}

$this->pre_img_process( $processor, $context, $xpath );

$this->add_preload_link_for_img( $processor, $context, $xpath );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( 'PICTURE' === $parent_tag ) {
return true;
}
$this->pre_img_process( $processor, $context, $xpath );
$this->add_preload_link_for_img( $processor, $context, $xpath );
if ( 'PICTURE' !== $parent_tag ) {
$this->pre_img_process( $processor, $context, $xpath );
$this->add_preload_link_for_img( $processor, $context, $xpath );
}


$this->add_preload_link_for_picture( $context, $img_xpath, $collected_sources[0] );

return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe we need to track the PICTURE in URL Metrics because only the IMG is reported as the LCP element. I don't believe that either PICTURE or SOURCE matter in terms of the intersection observer either.

Suggested change
return true;
return false;


// Ensure that all <source> elements have a type attribute and no media attribute.
if ( null !== $media || null === $type ) {
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem: When this happens, the IMG will no longer get its necessary optimizations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were the necessary optimizations happening in the pre_img_process function? if thats the case then as the logic of that function is now moved directly into the process_img function the optimizations will still happening even if this function returns here.

Comment on lines 119 to 130
// Process the <img> element within the <picture>.
if ( 'IMG' === $tag && ! $processor->is_tag_closer() ) {
// Skip empty src attributes and data: URLs.
$src = trim( (string) $processor->get_attribute( 'src' ) );
if ( '' === $src || $this->is_data_url( $src ) ) {
return false;
}

$img_xpath = $processor->get_xpath();

$this->pre_img_process( $processor, $context, $img_xpath );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this here? Couldn't we just rather leave the existing IMG handling in place when __invoke() lands on an IMG tag? We would just modify the logic there to only add preload links if the parent is not a PICTURE.

Suggested change
// Process the <img> element within the <picture>.
if ( 'IMG' === $tag && ! $processor->is_tag_closer() ) {
// Skip empty src attributes and data: URLs.
$src = trim( (string) $processor->get_attribute( 'src' ) );
if ( '' === $src || $this->is_data_url( $src ) ) {
return false;
}
$img_xpath = $processor->get_xpath();
$this->pre_img_process( $processor, $context, $img_xpath );
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed the pre_img_process as this processed directly in process_img but the below logic is still there as it is needed for images xpath which is required for adding preload links in process_picture.

if ( 'IMG' === $tag && ! $processor->is_tag_closer() ) {
    // Skip empty src attributes and data: URLs.
    $src = trim( (string) $processor->get_attribute( 'src' ) );
    if ( '' === $src || $this->is_data_url( $src ) ) {
        return false;
    }
    $img_xpath = $processor->get_xpath();
}

* @param OD_Tag_Visitor_Context $context Tag visitor context.
* @param string $xpath XPath of the element.
*/
private function pre_img_process( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_Context $context, string $xpath ): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per above, I don't think this needs to be in another method. It can remain in process_img(), with one change: it should only proceed to add preload links for the IMG if the parent is not a PICTURE.

* @param string $xpath XPath of the element.
* @param array{srcset?: string|true|null,sizes?: string|true|null,type?: string|true|null,media?: string|true|null,crossorigin?: string|true|null} $source Collected sources from the <source> elements.
*/
private function add_preload_link_for_picture( OD_Tag_Visitor_Context $context, string $xpath, array $source ): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic could be put inside process_picture(), unless there is an opportunity to refactor it so that it can be reused both for PICTURE and IMG.

* @param OD_Tag_Visitor_Context $context Tag visitor context.
* @param string $xpath XPath of the element.
*/
private function add_preload_link_for_img( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_Context $context, string $xpath ): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per above, I think this logic can remain in process_img but just to wrap it in a check like if ( 'PICTURE' !== $parent_tag ).

Also, I think this logic could be put inside process_img(), unless there is an opportunity to refactor it so that it can be reused both for PICTURE and IMG.

@westonruter
Copy link
Member

This is looking pretty good! The biggest remaining piece is to add test cases for the new optimization code.

@b1ink0 b1ink0 requested a review from westonruter December 3, 2024 17:57
@@ -296,7 +296,7 @@ public function next_token(): bool {
$i = array_search( 'P', $this->open_stack_tags, true );
if ( false !== $i ) {
array_splice( $this->open_stack_tags, (int) $i );
array_splice( $this->open_stack_indices, count( $this->open_stack_tags ) );
array_splice( $this->open_stack_indices, count( $this->open_stack_tags ) + 1 );
Copy link
Member

@westonruter westonruter Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing a classic off-by-one error.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@westonruter westonruter merged commit 3c5dd7f into WordPress:trunk Dec 5, 2024
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) [Type] Enhancement A suggestion for improvement of an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Image Prioritizer fails to preload LCP images for picture elements
2 participants