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

Provide fallback JPEG images in frontend when WebP is not supported by the browser #360

Merged
merged 27 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
36cd431
Add the initial version of webp-hero script.
eugene-manuilov May 24, 2022
11197de
Fix formatting issues.
eugene-manuilov May 24, 2022
8262fca
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jun 9, 2022
e06409b
Update webp-hero hook to be a fallback.
eugene-manuilov Jun 9, 2022
6637709
Add mime types trasform checks to make sure we add the fallback if jp…
eugene-manuilov Jun 10, 2022
5cb1283
Updated the fallback script to process images that was added while we…
eugene-manuilov Jun 10, 2022
7044a6d
Updated the fallback script to use REST API to get image details.
eugene-manuilov Jun 10, 2022
45a5fde
Updated the fallback script to pull media details in batches.
eugene-manuilov Jun 10, 2022
0d91c53
Fix js issues that prevents images to work in IE9.
eugene-manuilov Jun 10, 2022
6ffde35
Address code review feedback.
eugene-manuilov Jun 13, 2022
829390a
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jun 15, 2022
9b280a6
Address code review feedback.
eugene-manuilov Jun 15, 2022
5365f64
Removed unnecessary check.
eugene-manuilov Jun 15, 2022
d97c430
Address code review feedback.
eugene-manuilov Jun 22, 2022
4fbb95e
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jun 22, 2022
49fca5e
Update the condition to register fallback hook only if it hasn't been…
eugene-manuilov Jun 22, 2022
e44a8a2
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jun 24, 2022
4db66ba
Add phpunit tests.
eugene-manuilov Jun 24, 2022
74fe886
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jul 7, 2022
f205815
Address code review feedback.
eugene-manuilov Jul 8, 2022
ea38eae
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jul 8, 2022
a1fb857
Avoid JS error in case JPEG source does not exist for an image.
felixarntz Jul 12, 2022
a9bf111
Make printing inline script tag more defensive.
felixarntz Jul 12, 2022
6871972
Use get_echo() utility in tests.
felixarntz Jul 12, 2022
9429f1b
Fix syntax error :D
felixarntz Jul 12, 2022
696ade3
Merge remote-tracking branch 'origin/trunk' into enhancement/341-wepb…
eugene-manuilov Jul 13, 2022
7e70d49
Merge branch 'enhancement/341-wepb-hero' of github.com:WordPress/perf…
eugene-manuilov Jul 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 67 additions & 0 deletions modules/images/webp-uploads/fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
( function( d ) {
eugene-manuilov marked this conversation as resolved.
Show resolved Hide resolved
window.webpUploadsFallbackWebpImages = function( media ) {
eugene-manuilov marked this conversation as resolved.
Show resolved Hide resolved
for ( var i = 0; i < media.length; i++ ) {
try {
var ext = media[i].media_details.sources['image/jpeg'].file.match( /\.\w+$/i );
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
if ( ! ext || ! ext[0] ) {
continue;
}

var images = d.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] );
Copy link
Contributor

Choose a reason for hiding this comment

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

Will the file name always match?

The thought behind the question: Someone uploads the image hamilton-the-musical.jpg, this will generate fallback image hamilton-the-musical.webp. Later the image hamilton-the-musical.jpeg is uploaded (note the e), this will generate images of a different name for the fallback -- probably hamilton-the-musical-2.webp.

🔢 The same question applies for srcset.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually a good point but I think it is out of scope for this issue. The problem is bigger that just properly replacing webp files here. I tried to replicate your edge case and it looks like in this case webp images for the second image will override webp images for the first image 🤔.

I uploaded 20180607_144302.jpg image first, webp images have been created correctly. Then I uploaded the same image but with another extension 20180607_144302.jpeg and in this time webp images overriden webp images generated for the first image. Here is what I got on the server:

-rw-r--r-- 1 www-data www-data 161891 Jul  8 18:15 20180607_144302-1024x576.jpeg
-rw-r--r-- 1 www-data www-data 161891 Jul  8 18:13 20180607_144302-1024x576.jpg
-rw-r--r-- 1 www-data www-data 170276 Jul  8 18:14 20180607_144302-1024x576.webp
-rw-r--r-- 1 www-data www-data   8220 Jul  8 18:15 20180607_144302-150x150.jpeg
-rw-r--r-- 1 www-data www-data   8220 Jul  8 18:13 20180607_144302-150x150.jpg
-rw-r--r-- 1 www-data www-data   8724 Jul  8 18:14 20180607_144302-150x150.webp
-rw-r--r-- 1 www-data www-data 330858 Jul  8 18:15 20180607_144302-1536x864.jpeg
-rw-r--r-- 1 www-data www-data 330858 Jul  8 18:14 20180607_144302-1536x864.jpg
-rw-r--r-- 1 www-data www-data 331106 Jul  8 18:14 20180607_144302-1536x864.webp
-rw-r--r-- 1 www-data www-data 529186 Jul  8 18:15 20180607_144302-2048x1152.jpeg
-rw-r--r-- 1 www-data www-data 529186 Jul  8 18:14 20180607_144302-2048x1152.jpg
-rw-r--r-- 1 www-data www-data 506556 Jul  8 18:14 20180607_144302-2048x1152.webp
-rw-r--r-- 1 www-data www-data  17533 Jul  8 18:15 20180607_144302-300x169.jpeg
-rw-r--r-- 1 www-data www-data  17533 Jul  8 18:13 20180607_144302-300x169.jpg
-rw-r--r-- 1 www-data www-data  18692 Jul  8 18:14 20180607_144302-300x169.webp
-rw-r--r-- 1 www-data www-data  96605 Jul  8 18:15 20180607_144302-768x432.jpeg
-rw-r--r-- 1 www-data www-data  96605 Jul  8 18:14 20180607_144302-768x432.jpg
-rw-r--r-- 1 www-data www-data 102924 Jul  8 18:14 20180607_144302-768x432.webp
-rw-r--r-- 1 www-data www-data 934789 Jul  8 18:15 20180607_144302.jpeg
-rw-r--r-- 1 www-data www-data 934789 Jul  8 18:13 20180607_144302.jpg
-rw-r--r-- 1 www-data www-data 826980 Jul  8 18:14 20180607_144302.webp

This doesn't look right although this is a very unlikely edge case. I think it is worth creating a separate issue for it 🤔.

cc @felixarntz @adamsilverstein

Copy link
Member

Choose a reason for hiding this comment

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

This was previously discussed and should have been fixed in the plugin - see #358 - not sure why you still see this happening. I'm not sure this fix is great though, it skips the duplicate generation; instead behavior should be like or use wp_unique_filename.

For core I am working on including this in the core patch as well, I tried one approach in 93542af (#2393) - that isn't quite finished - note there can actually be three variations - cat.jpe, cat.jpeg, and cat.jpg - yes ".jpe" is also valid!

Another option would be to incorporate the fix into WP_Image_Editor::generate_filename or save - again the tricky part is making sure overwriting works when you want it to (ie. regenerate images).

Copy link
Contributor

Choose a reason for hiding this comment

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

This is actually a good point but I think it is out of scope for this issue. The problem is bigger that just properly replacing webp files here.

Thanks for testing this. I'm happy if y'all decide it is out of scope for this ticket. It seems like a much bigger problem than I thought when asking the question :)

Copy link
Member

Choose a reason for hiding this comment

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

The problem of avoiding the override was indeed fixed in #358 - however that shouldn't have affected the PR here anyway, since realistically the problem is only on the JPEG (jpeg/jpg/jpe) side. Now that #358 is in place, no conflicting WebP image would be created so we should be good for now.

With that said, I agree this is a valid point, and I think a much more stable implementation for the replacement would be to replace the full file name for the image/webp version per size with its corresponding full file name for the image/jpeg version. Let's open a follow-up issue to add that.

var srcset = images[j].getAttribute( 'srcset' );
if ( srcset ) {
images[j].setAttribute( 'srcset', srcset.replace( /\.webp(\s)/ig, ext[0] + '$1' ) );
}
}
} catch ( e ) {
}
}
};

var restApi = d.getElementById( 'webpUploadsFallbackWebpImages' ).getAttribute( 'data-rest-api' );
eugene-manuilov marked this conversation as resolved.
Show resolved Hide resolved

var loadMediaDetails = function( nodes ) {
var ids = [];
for ( var i = 0; i < nodes.length; i++ ) {
if ( nodes[i].nodeName !== "IMG" || ! nodes[i].src.match( /\.webp$/i ) ) {
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

var attachment = nodes[i].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 = d.createElement( 'script' );
jsonp.src = restApi + 'wp/v2/media/?_fields=id,media_details&_jsonp=webpUploadsFallbackWebpImages&per_page=100&include=' + pageIds.join( ',' );
d.body.appendChild( jsonp );
}
};

try {
// Loop through already available images.
loadMediaDetails( d.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( d.body, {
subtree: true,
childList: true,
} );
} catch ( e ) {
}
} )( document );
45 changes: 45 additions & 0 deletions modules/images/webp-uploads/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,48 @@ function webp_uploads_update_featured_image( $html, $post_id, $attachment_id ) {
return webp_uploads_img_tag_update_mime_type( $html, 'post_thumbnail_html', $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 ) {
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
return;
}

$fallback_url = plugins_url( '/fallback.js', __FILE__ );

$script = <<<EOL
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
( function( d, i, s, p ) {
s = d.createElement( s );
s.src = '{$fallback_url}';
eugene-manuilov marked this conversation as resolved.
Show resolved Hide resolved

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

i.onerror = function() {
d.body.appendChild( s );
};
} )( document, 'img', 'script', 'data:image/webp;base64,' );
EOL;

wp_print_inline_script_tag(
preg_replace( '/\s+/', '', $script ),
array(
'id' => 'webpUploadsFallbackWebpImages',
'data-rest-api' => get_rest_url(),
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
)
);
}
add_action( 'wp_footer', 'webp_uploads_wepb_fallback' );
eugene-manuilov marked this conversation as resolved.
Show resolved Hide resolved