Images sometimes don't load in Safari

Created on 7 May 2022, about 2 years ago
Updated 4 December 2023, 6 months ago

Problem/Motivation

We are experiencing an issue where images don't always appear in safari (almost 75% of the time they're missing). Only occurs when using blazy as a formatter.

🐛 Bug report
Status

Fixed

Version

2.0

Component

Regression

Created by

🇺🇸United States smustgrave

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇺🇸United States recrit

    @gausarts I had some more time to dig into this a little more. On Safari with responsive images that have the attribute loading="lazy":

    • The image is loaded by the browser. If you listen on image.onload() then you can see the image get loaded. The image data-src is swapped to the src attribute either by the browser or Blazy.
    • The "blazy.done" event is never triggered for the image. This is why my images were never showing. I have some CSS that hides the picture element until it is processed on "blazy.done". Since "blazy.done" never triggers, the my classes never get updated to show the image.

    Fix for my use case: I added an event listener for the image "load" event which does get fired in Safari.

  • 🇮🇩Indonesia gausarts

    @rescrit, very much helpful indeed, and should narrow down more.

    That is why original blazy works fine, and we got regression with the new implementation.

    However, Image.onload which is the original implementation was NOT recommended by FF here:
    https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Author_fast-lo...

      // "Note that lazily-loaded images may not be available when the load event is
      // fired. You can determine if a given image is loaded by checking to see if
      // the value of its Boolean complete property is true."
    

    More here:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.17-rc1/js/base/io/...

    Unfortunately FF and Safari result in different things.

    That event caused too early decision on the loaded element, and given various workaround before blazy:2.6.
    Later implemented here:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.17-rc1/js/dblazy.j...

    Image.onload was actually made as fallback, but Safari didn't consume it:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.17-rc1/js/dblazy.j...

    Meaning Safari only consumes one of the first 2 conditions, but never makes it into Promise.then ... chain:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.17-rc1/js/dblazy.j...

    If you could provide a console log for this, that would be much helpful.
    Ensures you have IMG element on the page with regular blazy attributes and match the URL below:

    console.log('loading' in HTMLImageElement.prototype);
    
    var imgX = new Image();
    
    // Please change it to the correct URL matching the blazy IMG on the page:
    imgX.src = '/site/default/files/TEST.jpg';
    
    function decodeTest(img) {
      if (img.decoded || img.complete) {
        console.log('A: decoded called');
        console.log(img.decoded);
        console.log(img.complete);
    
        return Promise.resolve(img);
      }
    
      if ('decode' in img) {
        img.decoding = 'async';
    
        console.log('B: decode called');
        console.log(img.decode);
        console.log(img.decode());
    
        return img.decode();
      }
    
      return new Promise(function (resolve, reject) {
        img.onload = function () {
          console.log('C: onload called');
          resolve(img);
        };
        img.onerror = reject();
      });
    }
    
    decodeTest(imgX)
      .then(function () {
        console.log('D: success called');
      })
      .catch(function () {
        console.log('E: error called');
      })
      .finally(function () {
        console.log('F: finally called');
      });
    

    Thanks again!

  • 🇺🇸United States recrit

    @gausarts I tested the code that you provided on a page that had no images. I updated the imgX.src to my test image.
    Results were the same in all browsers - Chrome, FF, and Safari

    loading: true
    B: decode called
    decode() { [native code] }
    Promise {<pending>}
    D: success called
    F: finally called
    
  • 🇺🇸United States recrit

    @gausarts Thinking about this some more, I'm not sure that test will capture this bug.
    On my test site,
    - Safari for a visible IMG - does get the "blazy.done" event triggered correctly.
    - Safari for a visible responsive image PICTURE > IMG - does get the "blazy.done" event triggered correctly.
    - ISSUE: Safari for a responsive image PICTURE > IMG that is hidden with CSS - does NOT get the "blazy.done" event triggered.

  • 🇮🇩Indonesia gausarts

    Thanks a lot for helpful debugs.

    Almost a breeze except for such an edge case which was actually fixed by enabling loadInvisible option at Blazy UI at old bLazy. But Safari never consumes it, unfortunately.

    > appears that Safari loads the images immediately on page load even before the image is in view. ... So perhaps this is a race condition on Safari? Safari loads the image before the blazy module's JS can register listeners?
    Makes sense, similar to chrome which hard-code/ set-stone 8000px to start loading images. Temporary solution was a non-official loading value named defer #3120696: Delay native lazy loading till one is hit , only considered useful for Blur due to expensive animations.

    Mind trying out two existing solutions to narrow down issues more?

    • At Blazy UI 2.17-RCs, there is a new option named Add is-b-visible class. Basically the option will not unobserve/ self-destroy, and continue observing the class change instead, meant for animation, non-grid elements. No expensive operations, just setting/removing that class.
    • At formatters, under Loading priority option, choose defer, temporarily. I know, invalid value, but this fixes 8000px threshold, I saw it in source code, but forgot where the link is, or read more here.

    Does any help fix this issue? Of course, no real solutions, yet just so we know where to spot the actual issue.
    If no joy, at least we can look into another problem.

  • 🇺🇸United States recrit

    @gausarts thanks for the feedback. I tested with 2.17-rc3. I tried your suggestions 1 and 2, but the "blazy.done" event still did not fire. The image does lazy load as you scroll close it but the "blazy.done" did not fire.

  • 🇮🇩Indonesia gausarts

    Good news, thanks :)

    > but the "blazy.done" did not fire.
    That should narrow it down even more.

    This is the responsible method:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.x/js/src/plugin/bl...
    _erCounted = me[ok ? 'success' : 'error'](el, status, parent, opts);

    I will try splitting it, in case it is a culprit into:

        if (ok) {
          _erCounted = me.success(el, status, parent, opts);
        }
        else {
          _erCounted = me.error(el, status, parent, opts);
        }

    I should also change them into private functions to avoid confusions by calling them directly out of the workflows.

    Another potential culprit, has been remove here just as recently:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.x/js/src/base/blaz...

    We'll hopefully spot it before full release 2.17. Thanks again.

  • 🇺🇸United States recrit

    @gausarts This is getting closer.

    "2. Another potential culprit, has been removed here just as recently:"
    I applied the patch for https://git.drupalcode.org/project/blazy/-/commit/d443c5605038dec5905b2d... to 2.17-rc3.
    I did not have any of configuration changes from #39 🐛 Images sometimes don't load in Safari Active .
    Result: The image properly lazy loads once you scroll close to it, however the "blazy.done" event still does not fire.

    =====
    Completely, off topic. I created a plan issue for blazy_video_embed_field to improve the integration - https://www.drupal.org/project/blazy_video_embed_field/issues/3382655 🌱 Improve Integration with Blazy 2.16+ development Active . There is now a 3.0.x branch and I have added you as a maintainer in case you wanted to start moving VEF integration to there.

    • gausarts committed cd72c4a5 on 8.x-2.x
      - Safari guess works for #3279316.
      - Fixed for Instagram within more...
  • 🇮🇩Indonesia gausarts

    > I did not have any of configuration changes from #39.
    @recrit , please try it :) I am curious for blazy.done, since that Add is-b-visible class option is almost similar to 2 other lazy load scripts:

    • old Blazy with loadInvisible here.
    • lazysizes with loadHidden here.
    • current blazy with visibleClass here.

    They all do the same thing, continue watching for the hidden/ not yet visible element to be loaded, otherwise locked out to just load visible ones which as already reported failed without enabling loadInvisible option. The obvious difference is current blazy has a bonus is-b-visible class for when being visible.

    If that still doesn't trigger the blazy.done event, I'll be informed to look into another spot.

    > Result: The image properly lazy loads once you scroll close to it..
    A breeze, thanks. Looks like an unnecessary lock, a premature optimization.

    > however the "blazy.done" event still does not fire.
    May I know how you call it? If any difference, please try dBlazy.on(el, 'blazy.done', done);, without jQuery, where el is .b-lazy Element, not jQuery's.

  • 🇺🇸United States recrit

    @gausarts I'm listening for "blazy.done" with native JS - Element.addEventListener.

    Test 1: "is-b-visible" enabled
    - Only visible (ie not hidden) images got this class added, but the class was not added to a hidden PICTURE > IMG that was in the view port.
    - blazy.done did not trigger.
    - image load event fires for all hidden images whenever Safari decides to load it - some times on page load, sometimes when you scroll to it.

    Test 2: "is-b-visible" enabled and "defer" as the loading attribute
    - Same as test 1 for the "is-b-visible".
    - blazy.done did trigger for an image once I scrolled to it. However, after that initial image then Safari loaded all the other hidden images on the page. The blazy.done event did not fire for those other images.
    - image load event fires for all hidden images whenever Safari decides to load it - some times on page load, sometimes when you scroll to it.

  • 🇮🇩Indonesia gausarts

    Noted. Thanks for thorough debugs. Much appreciated. That should help me pinpoint better, although rather blindly without such machine :)
    FYI, Blazy was last time I checked fine with Safari for windows, dead in 5.6 IIRC. And fine with Safari on Linux. Obviously not fine with the real one.

    • gausarts committed bbffa28a on 8.x-2.x
      Issue #3279316 by smustgrave, recrit, Chris--S, hunterbuchanan, Luispe:...
  • 🇮🇩Indonesia gausarts

    @recrit, mind checking out the latest DEV?

    If this is still an issue, I will postpone it to focus on other issues :)

    Thanks.

  • 🇺🇸United States recrit

    @gausarts same results.

    "is-b-visible" enabled and "defer" as the loading attribute:
    - Only visible (ie not hidden) images got this class added. The class was not added to a hidden PICTURE > IMG that was in the view port.
    Structure: PICTURE (hidden with CSS "hidden" class) > IMG.
    - blazy.done did trigger for an image once I scrolled to it. However, after that initial image then Safari loaded all the other hidden images on the page. The blazy.done event did not fire for those other images.
    - image load event fires for all hidden images whenever Safari decides to load it - some times on page load, sometimes when you scroll to it.

    This worked with the original blazy JS since there was the "loadInvisible" option. The new JS seems to watch for items that are visible (CSS visible) and not just images within the view port.

    Either way, my workaround is working. I am listener for both events "blazy.done" and image element "load". The "load" event fires in Safari when ever it decides to load the responsive image.

  • 🇮🇩Indonesia gausarts

    Got it, thanks.

    Will give it another look before postponing it tomorrow.

    > PICTURE (hidden with CSS "hidden" class) > IMG.
    This is useful, I will reproduce it better. The latest DEV only accounts for .media element being hidden.

    > The new JS seems to watch for items that are visible (CSS visible)

    We'll see to it. My recommended solution for now till further fixes is to toggle visually-hidden class instead. And enforced elements to always display:block, even when they were set to none. This way IO can always have bounding rect info to check for its visibility.

    • gausarts committed 4410d2e7 on 8.x-2.x
      Issue #3279316 by smustgrave, recrit, Chris--S, hunterbuchanan, Luispe:...
  • Status changed to Postponed 9 months ago
  • 🇮🇩Indonesia gausarts

    I just remembered, years ago when I worked with Responsive image, I noted:
    // Image decode fails with Responsive image, assumes ok, no side effects.

    The latest code is here:
    https://git.drupalcode.org/project/blazy/-/blob/8.x-2.17-rc4/js/src/plug...

    And you said the problem was around PICTURE/ Responsive image. That note would explain it.
    Perhaps we are now dealing with those neglected side effects. I'll be darned :)

    Anyway, I did my best solution for now on the latest commits with some delegated observed elements, the closest parent of the hidden elements. On my Linux box, blazy:done, the latest, or blazy.done, the older, event was fired with these hidden elements:

    PICTURE.hidden > IMG.b-lazy
    .media {display: none} > IMG.b-lazy 

    Not sure on Safari. If you could verify, that would be great.

    However I am postponing this till we got more free time to work on this.
    Patches are very much welcome if anyone beat me to it.

    Thanks.

  • 🇺🇸United States recrit

    @gausarts - thanks for all the improvements to blazy for this issue!
    I tested in Safari with blazy 2.17 - the "blazy:done" event fires correctly for the hidden responsive image. I tested with my default configuration - "is-b-visible" as disabled and loading attribute as "lazy". The Safari's actual HTTP request and IMG load event still fires whenever Safari decides to do it - on page load or when you scroll close to it. However, the blazy:done event fires correctly when scrolling close to the image even if Safari has already loaded it.
    I feel like this is much better and might be as good as this scenario can get.

  • 🇮🇩Indonesia gausarts

    @recrit, I apologize, I didn't quite understand your sentences, especially the However part. :)

    But that sounds this issue is fixed?

    Or there are remaining issues to fix?

    Thank you.

  • 🇺🇸United States recrit

    @gausarts no problem. Yes, i feel like it is fixed. The "blazy:done" event is triggering correctly - as you scroll close to the image, the "blazy:done" event is triggered. The caveat that i was trying to explain - Safari may have already loaded the image by then since I am still using the default attribute for loading="lazy".

  • Status changed to Fixed 9 months ago
  • 🇮🇩Indonesia gausarts

    Got it, thanks.

    > Safari may have already loaded the image by then ...
    Yes, likely native default 8000px threshold hit, see #27. To verify if any race/ discrepancy from dBlazy.decode Promise, check out Network tab. That tab would tell the exact moment when the actual HTTP request was hit. If any issues, defer Loading priority is the only current solution for 8000px. Feel free to create a new thread with the caveat if any better/ potential solutions.

    Phew, almost 2-year standing an issue :)

    Thank you @recrit, and all for helping me out when I was blind, real hard without real Safari :)

  • Automatically closed - issue fixed for 2 weeks with no activity.

  • Status changed to Fixed 6 months ago
  • 🇵🇹Portugal utneon

    I'm having this problem, I tried the following:


    Protocols h2 h2c http/1.1
    #LP:21-01-2019, fix safari bug
    Header unset Upgrade

    But images sometimes fail to load and only happens in Safari + Mac OS.

    Any update on this issue? Thank you!

Production build 0.69.0 2024