ewww-image-optimizer icon indicating copy to clipboard operation
ewww-image-optimizer copied to clipboard

Add filters to inline scripts in BODY tag for compatibility with page optimization plugins

Open galbaras opened this issue 4 years ago • 7 comments

The issue The attributes of the inline script in the BODY tag for detecting WebP support are fixed, and cannot be changed to accommodate page optimization plugins.

Enqueued scripts can be modified via the script_loader_tag filter or wp_script_add_data(), but literal insertion can only processed by buffering the entire page and post-processing it, which is bad for performance.

Proposed solution Apply filters to literal script HTML strings before adding them to the page.

galbaras avatar Sep 16 '21 00:09 galbaras

Hi @galbaras did you have a particular use case in mind? Normally I'd just be like "you bet, let's filter all the things!" But in this case, escaping the filtered attributes to make it safe for output is going to require a reworking of the code involved, so it's not quite as simple as one would expect.

nosilver4u avatar Sep 16 '21 14:09 nosilver4u

Can you at least provide a filter for adding script attributes? This should be easier, no?

The use case is targeting page optimization plugins, like Autoptimize, WP Fastest Cache or LiteSpeed Cache, each with its own unique ways of excluding scripts from being combined, minified and/or deferred.

galbaras avatar Sep 16 '21 23:09 galbaras

Unfortunately, it doesn't really matter whether the filter is for the existing attrs or just for adding new ones, we still have to escape "user-supplied" data, and that will require a reworking of that code. Just trying to set clear expectations about this not being a quick fix/implementation.

Any page optimization plugins can easily exclude our inline scripts with the string "ewww_webp" (like WP Rocket did) and definitely should exclude scripts that use data-cfasync="false" (like WPFC naturally did).

nosilver4u avatar Sep 16 '21 23:09 nosilver4u

As it turns out, LiteSpeed Cache doesn't accept arbitrary strings, only script names, which it compares against the value of src.

I think that using esc_attr( apply_filters( ... ) ) should do the trick for you. Would this work?

$body_tags        = $this->get_elements_from_html( $buffer, 'body' );
$script_attr        = esc_attr( apply_filters( 'ewwwio_js_webp_script_attr', 'data-cfasync="false"' );
$body_webp_script = '<script ' . $script_attr . '>if(ewww_webp_supported){document.body.classList.add("webp-support");}</script>';

galbaras avatar Sep 17 '21 06:09 galbaras

The esc_attr() function is only for the attribute value, so it will escape/encode quotes and break the markup. That's precisely what makes it tricky.

What is LS Cache doing with inline scripts? Is it combining them, deferring them, or something else?

nosilver4u avatar Sep 17 '21 13:09 nosilver4u

How about passing an array of name/value pairs and combining them into a string after sanitizing each separately?

LS Cache is doing all of the above to scripts, and I can't disable inline scripts completely in this case. The plugin authors have already agreed to accommodate my specific request, but this may not be good enough for others.

WordPress provides ways to modify enqueued scripts, but you're bypassing those mechanisms (for good reason), but this creates an inconsistent situation, where the script can't be modified in the same way(s), and that's sometimes needed.

galbaras avatar Sep 17 '21 14:09 galbaras

How about passing an array of name/value pairs and combining them into a string after sanitizing each separately?

Right, that's exactly the method I was thinking of. Feel free to submit a pull request if you'd like it sooner :)

nosilver4u avatar Sep 17 '21 14:09 nosilver4u

Finally committed this, will be in the next release. The filtered attributes will be applied to all three JS WebP scripts, since they should typically all be handled the same. That is, no defer and no minify, and definitely no delaying.

nosilver4u avatar Mar 23 '23 20:03 nosilver4u