skip to Main Content

I have a very interesting task:

I have few "images" like that:

var image = new Image();
image.src='https://www.hootv.lt/bitrix/spread.php?s=QklUUklYX1NNX1VJREgBbjVkZXNEQ2IzVnU4ckRSSmlCbW82R0xKdmNWcUV2UzUBMAEvAQExATECQklUUklYX1NNX1VJREwBMzcwNjU0NTQyMDcBMAEvAQExATECQklUUklYX1NNX1VJREQBMjVwbmJzNjNkY2VrNGRuZ2VmaGEyYXZsMm1taGh6YWMBMTcyNjMzNjUzMAEvAQExATECQklUUklYX1NNX05DQwFZATE3MjYzMzY1MzABLwEBAQJCSVRSSVhfU01fQ0MBATABLwEBAQI%3D&k=c545bd21253d81d854fc3698d4b20854';

An image src is a PHP page which returns nothing. I have to check whenever all images generate error, than continue executing script.

So my script should look like something like that:

<script> - My code
var image = new Image();
image.src='url1';

var image = new Image();
image.src='url2';

var image = new Image();
image.src='url3';

...

var image = new Image();
image.src='url33';
</script>

(after all images generate onerror event continue with)

<script> - CMS generated code I cannot change
windows.location.href = "..."; (i cannot change line, all I can do is inject the script above)
</script>

I tried the following but obviously it’s not working:

while (!image.error) {
  //wait
}

3

Answers


  1. Chosen as BEST ANSWER

    First of all I would like to say big thanks to @CherryDT and @AztecCodes for being so involved in my problem!

    It seems like the problem I faced doesn't have a straightforward solution so I ended up modifying CMS source code for my needs.


  2. New Approach:
    Under these constraints consider using a MutationObserver to monitor changes in the document.scripts collection. When the unwanted script is added, remove it. Once image processing is complete re-insert the script.

    Please check out the Answer from CherryDT because his approach could help too.

    The adjusted Script:

    (function() {
        const targetScriptSrc = "distinct_script_source.js";
        let targetScript = null;
    
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                if (mutation.type === "childList") {
                    Array.from(mutation.addedNodes).forEach(node => {
                        if (node.tagName === 'SCRIPT' && node.src.includes(targetScriptSrc)) {
                            targetScript = node;
                            node.remove();
                        }
                    });
                }
            });
        });
    
        observer.observe(document, { childList: true, subtree: true });
    
        var urls = [
            'url1',
            'url2',
            'url3',
            'url33'
        ];
    
        function loadImage(src) {
            return new Promise((resolve, reject) => {
                var img = new Image();
                img.src = src;
                img.onload = () => resolve(img);
                img.onerror = () => reject(src);
            });
        }
    
        var promises = urls.map(url => loadImage(url).catch(e => e));
    
        Promise.allSettled(promises).then(results => {
            var failedImages = results.filter(result => result.status === 'rejected');
            
            if (failedImages.length === urls.length && targetScript) {
                document.body.appendChild(targetScript);
            }
            observer.disconnect(); // Stop observing once done
        });
    })();
    

    Update:

    Because of Browser limitations here is an adjusted version of the script:

    (function() {
        var urls = [
            'url1',
            'url2',
            'url3',
            'url33'
        ];
    
        function loadImage(src) {
            return new Promise((resolve, reject) => {
                var img = new Image();
                img.src = src;
                img.onload = () => resolve(img);
                img.onerror = () => reject(src);
            });
        }
    
        var promises = urls.map(url => loadImage(url).catch(e => e));
    
        Promise.allSettled(promises).then(results => {
            var failedImages = results.filter(result => result.status === 'rejected');
            
            if (failedImages.length === urls.length) {
                appendNextScript();
            }
        });
    
        function appendNextScript() {
            var script = document.createElement('script');
            script.src = 'path_to_next_script.js'; // Replace with the path to your external script
            document.body.appendChild(script);
        }
    })();
    
    Login or Signup to reply.
  3. This is a tricky problem. I have a solution but it is a hack (of course), because this is not something that normally needs to be done. I don’t even know whether this will work on all browsers, and for how long it will work (but I tested on Chrome 116 and Firefox 117 and it was OK in those).

    First a big warning:

    This is not recommended. It’s a big hack. Use at your own risk.

    But I understand that desparate times call for desparate measures. I hope this is a temporary solution and you’ll spend some time later to figure out how to solve the issue in your CMS (perhaps you can write a plugin which hooks the part of the CMS that inserts this code and stops it from doing so).


    You can’t stop the code below from running, at least I couldn’t come up with any way to do that (even a MutationObserver will fire after the code ran, I checked). So we can try to do the next best thing, which is stopping the redirection from executing.

    I tried redefining window.location and window.location.href but that’s not possible because those properties are defined with configurable: false by default. We also can’t play tricks on the Window or Location prototypes. But what we can do is overwrite the redirection with our own redirection, but this time to a page that returns a status code 204 which will essentially cancel the navigation!

    This is what I came up with, after some playing around and testing ideas:

    window.addEventListener(
      'beforeunload',
      () => setTimeout(() => location.href = 'https://www.google.com/gen_204', 1),
      { once: true }
    )
    

    This will cancel the next navigation attempt. You can later navigate yourself. If the URL that the page redirects to is not fixed and you need to know what it was so you can redirect to it later, you can save it within the event handler, and access that variable later (e.g. after all your images failed to load):

    let redirectTarget
    
    window.addEventListener(
      'beforeunload',
      () => {
        redirectTarget = location.href
        setTimeout(() => location.href = 'https://www.google.com/gen_204', 1)
      },
      { once: true }
    )
    

    A few things to note here:

    • This relies on the URL https://www.google.com/gen_204 to return status 204. It’s probably better if you add your endpoint that returns status 204 and use that instead.
    • This will cancel the next navigation attempt that happens, regardless of what caused it. This means that in case that script tag is not on the page for some reason, it would instead cancel the next link click or even the user’s own navigation attempts from typing a new URL in the address bar! So if there is any possibility of the redirection not existing on the page below, you should probably add a DOMContentLoaded event handler that removes the beforeunload event handler.
    • This might stop working anytime. The fact that this can even interfere with the user’s own navigation actions sounds to me as if it could be abused as well, so it’s possible that browser vendors will make this trick intentionally stop working at some point.
    • Yes, the setTimeout is required. Attempting to change location from within the invokation of the beforeunload handler is silently ignored by the browser.

    Note: I didn’t explain how to actually wait for the images to fail loading, only how to stop the navigation (the trickier part). For how to do the waiting, check out AztecCodes’ answer.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search