Skip to content

JavaScript DOMContentLiteSpeedLoaded event firing too soon? #950

@frzsombor

Description

@frzsombor

I have the "Load JS Deferred" option set to "Delayed" and the last INLINE script on my page is something like this:

<script type="litespeed/javascript">
(function(){
    debugger; //  FIRST BREAK: Check if function runs
    document.addEventListener('DOMContentLiteSpeedLoaded', function () {
        debugger; // SECOND BREAK: Check if event triggered
    });
})();
</script>

I also have this (non-delayed) JS for testing in the page source:

<script data-no-optimize="1">
  document.addEventListener('DOMContentLiteSpeedLoaded', function () {
    console.log('DOMContentLiteSpeedLoaded fired');
  });
</script>

I found that by the time "FIRST BREAK" breaks into the debugger, "DOMContentLiteSpeedLoaded fired" is already logged on the console, and "SECOND BREAK" never happens. Which means the js_delay.js file here fires the "DOMContentLiteSpeedLoaded" event BEFORE the last delayed inline JS is actually finishes executing.

Now just my thoughts (correct me anywhere I'm wrong):

The litespeed_load_one() function works as expected for EXTERNAL js files, because it resolves on the script's load event - and for external scripts, "load" fires when the script is downloaded and the synchronous code finished executing.

But the function has issues with INLINE scripts. Because in that case the function resolves here as soon as the inline script is injected in the DOM, but because of how litespeed_inline2src() works, this does NOT mean that the inline script finished sync execution.

Looking at this test code, it works as expected:

console.log('before');
const script = document.createElement('script');
script.textContent = `console.log('inline script')`;
randomElem.after(script);
console.log('after');

This works well, because when using after() to add an inline script in the DOM, the sync execution only continues (with logging "after") when the inserted script finished sync execution.

However - and I think the issue lies here - litespeed_inline2src() uses a special way to create a Blob URL or data URL, therefore the script will be treated as an external script - therefore async by default. Then litespeed_load_one() injects the script tag which will load async, but after insertion the function will incorrectly resolve immediately here, because script "is_inline".

Because of this, we can't depend on listening for "DOMContentLiteSpeedLoaded" in inline scripts because if the script is one of the last ones on the page, LSCache JS might fire this event before the custom script adds the listener.

EDIT1:
What makes the situation even more difficult is that it looks like the same thing is happening even if I remove if (is_inline) resolve(); here. It looks like for DOM-inserted external scripts are always async, their execution is queued and the browser is allowed to fire "load", then schedule execution as a separate task. So basically for me it looks like here "load" only means "resource is ready & execution has been queued" and NOT a hard execution barrier.

EDIT2 (SOLUTION):
After "EDIT1" I also changed e2.src = litespeed_inline2src(e.textContent); to e2.textContent = e.textContent; here and finally this DID solve the issue! With this, now all code runs in the expected order! Event listener added correctly, before LSC fires "DOMContentLiteSpeedLoaded".

Now I only have the remaining questions:
Why litespeed_inline2src() was even introduced and used?
And what issues can this native solution cause when used instead of litespeed_inline2src()?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions