Skip to content

Commit

Permalink
Merge pull request #360 from WordPress/enhancement/341-wepb-hero
Browse files Browse the repository at this point in the history
Provide fallback images when webp is not supported by the browser
  • Loading branch information
felixarntz committed Jul 13, 2022
2 parents 0014eac + 7e70d49 commit d68fa73
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 4 deletions.
79 changes: 79 additions & 0 deletions modules/images/webp-uploads/fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
window.wpPerfLab = window.wpPerfLab || {};

( function( document ) {
window.wpPerfLab.webpUploadsFallbackWebpImages = function( media ) {
for ( var i = 0; i < media.length; i++ ) {
try {
if ( ! media[i].media_details.sources || ! media[i].media_details.sources['image/jpeg'] ) {
continue;
}

var ext = media[i].media_details.sources['image/jpeg'].file.match( /\.\w+$/i );
if ( ! ext || ! ext[0] ) {
continue;
}

var images = document.querySelectorAll( 'img.wp-image-' + media[i].id );
for ( var j = 0; j < images.length; j++ ) {
images[j].src = images[j].src.replace( /\.webp$/i, ext[0] );
var srcset = images[j].getAttribute( 'srcset' );
if ( srcset ) {
images[j].setAttribute( 'srcset', srcset.replace( /\.webp(\s)/ig, ext[0] + '$1' ) );
}
}
} catch ( e ) {
}
}
};

var restApi = document.getElementById( 'webpUploadsFallbackWebpImages' ).getAttribute( 'data-rest-api' );

var loadMediaDetails = function( nodes ) {
var ids = [];
for ( var i = 0; i < nodes.length; i++ ) {
var node = nodes[i];
var srcset = node.getAttribute( 'srcset' ) || '';

if (
node.nodeName !== "IMG" ||
( ! node.src.match( /\.webp$/i ) && ! srcset.match( /\.webp\s+/ ) )
) {
continue;
}

var attachment = node.className.match( /wp-image-(\d+)/i );
if ( attachment && attachment[1] && ids.indexOf( attachment[1] ) === -1 ) {
ids.push( attachment[1] );
}
}

for ( var page = 0, pages = Math.ceil( ids.length / 100 ); page < pages; page++ ) {
var pageIds = [];
for ( var i = 0; i < 100 && i + page * 100 < ids.length; i++ ) {
pageIds.push( ids[ i + page * 100 ] );
}

var jsonp = document.createElement( 'script' );
jsonp.src = restApi + 'wp/v2/media/?_fields=id,media_details&_jsonp=wpPerfLab.webpUploadsFallbackWebpImages&per_page=100&include=' + pageIds.join( ',' );
document.body.appendChild( jsonp );
}
};

try {
// Loop through already available images.
loadMediaDetails( document.querySelectorAll( 'img' ) );

// Start the mutation observer to update images added dynamically.
var observer = new MutationObserver( function( mutationList ) {
for ( var i = 0; i < mutationList.length; i++ ) {
loadMediaDetails( mutationList[i].addedNodes );
}
} );

observer.observe( document.body, {
subtree: true,
childList: true,
} );
} catch ( e ) {
}
} )( document );
64 changes: 60 additions & 4 deletions modules/images/webp-uploads/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,13 @@ function webp_uploads_update_image_references( $content ) {
*
* @since 1.0.0
*
* @param string $image An <img> tag where the urls would be updated.
* @param string $context The context where this is function is being used.
* @param int $attachment_id The ID of the attachment being modified.
* @param string $original_image An <img> tag where the urls would be updated.
* @param string $context The context where this is function is being used.
* @param int $attachment_id The ID of the attachment being modified.
* @return string The updated img tag.
*/
function webp_uploads_img_tag_update_mime_type( $image, $context, $attachment_id ) {
function webp_uploads_img_tag_update_mime_type( $original_image, $context, $attachment_id ) {
$image = $original_image;
$metadata = wp_get_attachment_metadata( $attachment_id );
if ( empty( $metadata['file'] ) ) {
return $image;
Expand Down Expand Up @@ -621,6 +622,16 @@ function webp_uploads_img_tag_update_mime_type( $image, $context, $attachment_id
}
}

if (
! has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) &&
$image !== $original_image &&
'the_content' === $context &&
'image/jpeg' === $original_mime &&
'image/webp' === $target_mime
) {
add_action( 'wp_footer', 'webp_uploads_wepb_fallback' );
}

return $image;
}

Expand All @@ -640,6 +651,51 @@ function webp_uploads_update_featured_image( $html, $post_id, $attachment_id ) {
}
add_filter( 'post_thumbnail_html', 'webp_uploads_update_featured_image', 10, 3 );

/**
* Adds a fallback mechanism to replace webp images with jpeg alternatives on older browsers.
*
* @since n.e.x.t
*/
function webp_uploads_wepb_fallback() {
// Get mime type transofrms for the site.
$transforms = webp_uploads_get_upload_image_mime_transforms();

// We need to add fallback only if jpeg alternatives for the webp images are enabled for the server.
$preserve_jpegs_for_jpeg_transforms = isset( $transforms['image/jpeg'] ) && in_array( 'image/jpeg', $transforms['image/jpeg'], true ) && in_array( 'image/webp', $transforms['image/jpeg'], true );
$preserve_jpegs_for_webp_transforms = isset( $transforms['image/webp'] ) && in_array( 'image/jpeg', $transforms['image/webp'], true ) && in_array( 'image/webp', $transforms['image/webp'], true );
if ( ! $preserve_jpegs_for_jpeg_transforms && ! $preserve_jpegs_for_webp_transforms ) {
return;
}

ob_start();

?>
( function( d, i, s, p ) {
s = d.createElement( s );
s.src = '<?php echo esc_url_raw( plugins_url( '/fallback.js', __FILE__ ) ); ?>';

i = d.createElement( i );
i.src = p + 'jIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA==';
i.onload = function() {
i.src = p + 'h4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA=';
};

i.onerror = function() {
d.body.appendChild( s );
};
} )( document, 'img', 'script', 'data:image/webp;base64,UklGR' );
<?php
$javascript = ob_get_clean();

wp_print_inline_script_tag(
preg_replace( '/\s+/', '', $javascript ),
array(
'id' => 'webpUploadsFallbackWebpImages',
'data-rest-api' => esc_url_raw( trailingslashit( get_rest_url() ) ),
)
);
}

/**
* Returns an array of image size names that have secondary mime type output enabled. Core sizes and
* core theme sizes are enabled by default.
Expand Down
38 changes: 38 additions & 0 deletions tests/modules/images/webp-uploads/load-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,44 @@ function() {
$this->assertNotSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
}

/**
* Tests that the fallback script is added when a post with updated images is rendered.
*
* @test
*/
public function it_should_add_fallback_script_if_content_has_updated_images() {
$attachment_id = $this->factory->attachment->create_upload_object(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg'
);

apply_filters(
'the_content',
sprintf(
'<p>before image</p>%s<p>after image</p>',
wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) )
)
);

$this->assertTrue( has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) === 10 );

$footer = get_echo( 'wp_footer' );
$this->assertStringContainsString( 'data:image/webp;base64,UklGR', $footer );
}

/**
* Tests that the fallback script is not added when a post with no updated images is rendered.
*
* @test
*/
public function it_should_not_add_fallback_script_if_content_has_no_updated_images() {
apply_filters( 'the_content', '<p>no image</p>' );

$this->assertFalse( has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) );

$footer = get_echo( 'wp_footer' );
$this->assertStringNotContainsString( 'data:image/webp;base64,UklGR', $footer );
}

/**
* Tests whether additional mime types generated only for allowed image sizes or not when the filter is used.
*
Expand Down

0 comments on commit d68fa73

Please sign in to comment.