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

GatsbyImage not working with JavaScript disabled via CSP (uBlock Origin, Noscript addons) #38065

Closed
2 tasks done
yaroslav opened this issue May 6, 2023 · 11 comments
Closed
2 tasks done
Labels
type: bug An issue or pull request relating to a bug in Gatsby

Comments

@yaroslav
Copy link

yaroslav commented May 6, 2023

Preliminary Checks

Description

It seems that while GatsbyImage is designed to have the <noscript> tag support and work with both JavaScript enabled or disabled, this is not always the case.

Many prosumers seem to use uBlock Origin (gorhill/uBlock#308) or NoScript for blocking JavaScript for a single specific website. They seem to work by setting policies for a specific page that completely block JavaScript. However, with that blocking enabled, GatsbyImage images stop showing.

Reproduction Link

gatsbyjs.com

Steps to Reproduce

It works:

  1. Use Firefox Stable or Developer Edition
  2. Go to about:config and set javascript.enabled to false.
  3. Refresh the build and see that images prepared via GatsbyImage are WORKING (positive).

It does not, however:

  1. Get Firefox (Chrome works too).
  2. Go to gatsbyjs.com.
  3. Notice that the Gatsby + Netlify image banner is there and looking good.
  4. Install the NoScript extension, or
  5. Install uBlock Origin, and disable JavaScript for that website (click extension icon, click the </> looking icon, reload the page).
  6. After refreshing the page, see that the banner completely disappears.

The issue is reproduceable for me on both production Gatsby websites (Cloud and not-cloud) as well as on local gatsby serve builds.

Expected Result

Images still show up using what is in <noscript> tag.

Actual Result

Despite <noscript> getting used by the browser, the image in that tag seems to have the opacity or 0, so all I get is an empty placeholder.

Environment

Latest Firefox (Stable or Developer Edition)
Latest Google Chrome stable
with NoScript (https://noscript.net/) or uBlock Origin (https://ublockorigin.com/)

Config Flags

No response

@yaroslav yaroslav added the type: bug An issue or pull request relating to a bug in Gatsby label May 6, 2023
@gatsbot gatsbot bot added the status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer label May 6, 2023
@simevidas
Copy link

I use the uBlock Origin extension to disable JavaScript for specific websites in my desktop browser. I can reproduce the issue on https://gatsbyjs.com.

Screenshot 2023-05-07 at 00 01 22

@LekoArts LekoArts removed the status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer label May 8, 2023
@LekoArts
Copy link
Contributor

LekoArts commented May 8, 2023

Hi, thanks for the issue!

Unfortunately that is out of our control and I recommend opening an issue on the uBlock repository.
When JS is disabled, we apply this CSS in the <head>:

<noscript>
    <style>
        .gatsby-image-wrapper noscript [data-main-image] {
            opacity: 1!important
        }

        .gatsby-image-wrapper [data-placeholder-image] {
            opacity: 0!important
        }
    </style>
</noscript>

This way the image is shown inside the other <noscript> tag.

For some reason uBlock wraps this with a <span> if you apply this "JS disabled" option:

<span style="display: inline !important;"><style>.gatsby-image-wrapper noscript [data-main-image]{opacity:1!important}.gatsby-image-wrapper [data-placeholder-image]{opacity:0!important}</style></span>

That's not valid HTML and thus not executed. Nothing we can do on our side.

@LekoArts LekoArts closed this as not planned Won't fix, can't repro, duplicate, stale May 8, 2023
@yaroslav
Copy link
Author

yaroslav commented May 8, 2023

Thanks for checking it out, @LekoArts!

@simevidas
Copy link

simevidas commented May 8, 2023

Why do you have noscript in the CSS selector inside the <noscript> element? When you place <style> inside <noscript>, that style is only applied to the page when JavaScript is disabled. You don’t need to further qualify the selector with noscript.

uBlock Origin replaces <noscript> with a <span> in order to make the no-JS content active when the user disables JavaScript via uBlock Origin. It’s a workaround that is necessary because there is no standard way to activate <noscript> from a browser extension.

Please try removing the unnecessary noscript from the selector, and check if that fixes this issue.

@yaroslav
Copy link
Author

yaroslav commented May 8, 2023

@simevidas you don't seem to understand the explanation. @LekoArts is right.

uBlock Origin breaks the page not in one, but in two ways.

  1. The <noscript> in head is erroneously replaced with a <span>. That set of rules need to be there, because in case there is no JavaScript, they hide all the "dynamic" image loading with placeholders, and instead enable oldschool image serving: .gatsby-image-wrapper noscript [data-main-image]{opacity:1!important}.gatsby-image-wrapper [data-placeholder-image]{opacity:0!important}. I'm not sure that a span in the head is the reason of failure here, maybe the rules are applied. However, we have problem number two:
  2. It replaces, not adds to, but replaces, all other <noscript> elements with <span style="display: inline !important">. Because of that, a selector like .gatsby-image-wrapper noscript [data-main-image] no longer works, because there is no noscript tag anymore.

This is, most definitely, an issue of uBO. I don't think a web developer should change their selectors because the addon chooses to maul the DOM. Built-in browser methods of shutting down JavaScript work as expected.

Issue number one can be fixed easily: changing a selector at https://github.com/gorhill/uBlock/blob/master/src/js/scriptlets/noscript-spoof.js#L33 to only target noscript tags in body.

To fix issue number two, the whole method of replacing DOM needs to be changed.

@simevidas
Copy link

simevidas commented May 8, 2023

@yaroslav uBO replaces <noscript> with <span>. This is not done erroneously, but on purpose. uBO does this because there is no standard way to activate <noscript> content from a browser extension.

As a result, when a user disables JS for a website via uBO, there will be no <noscript> elements on the page.

In order to be compatible with such users, a website must not rely on the existence of <noscript> elements on the page.

@yaroslav
Copy link
Author

yaroslav commented May 8, 2023

there is no standard way

The standard way to handle clients with no JavaScript available or disable exists and is very thoroughly documented.

https://html.spec.whatwg.org/multipage/scripting.html#the-noscript-element

Are you suggesting to replace an actual standard with the support for a very specific browser add-on where the JavaScript blocking DOM modifications are not even documented?

@simevidas
Copy link

I’m suggesting to remove the unnecessary noscript from the selector, and check if that fixes this issue.

@LekoArts
Copy link
Contributor

LekoArts commented May 9, 2023

The noscript tag is there for a reason. With JS enabled we change the opacity like so:

.then(() => {
target.style.opacity = 1;
if (placeholder) {
placeholder.style.opacity = 0;
placeholder.style.transition = "opacity 500ms linear";
}

If we'd change the the selector, all images would be visible directly, without a blur-up effect. Thus the noscript tag inside the selector is necessary.

@krystian3w
Copy link

test:

.gatsby-image-wrapper :is(noscript, span[style="display: inline !important;"]) [data-main-image]{opacity:1!important}
.gatsby-image-wrapper [data-placeholder-image]{opacity:0!important}

of course this cuts off support of old browsers: https://caniuse.com/css-matches-pseudo The worst is using :matches for some 3 Opera releases.

Rather, the project is not so crazy that it would add to the html code by itself style="display: inline !important;".

The idea of NoScript detection I don't have - as they do not use the identical method as uBo.

@simevidas
Copy link

@LekoArts If we remove noscript from the selector, we get this:

<noscript>
    <style>
        .gatsby-image-wrapper [data-main-image] {
            opacity: 1!important
        }

        .gatsby-image-wrapper [data-placeholder-image] {
            opacity: 0!important
        }
    </style>
</noscript>

There are two scenarios:

  1. If JavaScript is enabled, the browser will ignore the <noscript> element, so the contained styles will be ignored too. Therefore, opacity: 1 will not apply to the image, so the image will not be rendered immediately. Then, the JavaScript code will animate the image into view.

  2. If JavaScript is disabled, the browser (or browser extension) will activate the <noscript> element, and the contained styles will apply. Therefore, opacity: 1 will apply to the image, so the image will be rendered immediately. This is what we want. Render the image immediately if JavaScript is disabled.

To sum up, noscript in the selector is not necessary because the entire style is ignored when JavaScript is disabled.

Please try removing noscript and check if it fixes the issue with uBlock Origin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug An issue or pull request relating to a bug in Gatsby
Projects
None yet
Development

No branches or pull requests

4 participants