litespeedtech / litespeedtech/lscache_wp
JavaScript DOMContentLiteSpeedLoaded event firing too soon?
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 257
- Forks
- 123
- PR merge metrics
- No merged PRs in 30d
Description
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()?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with assets/js/js_delay.js around the cited litespeed_load_one() and litespeed_inline2src() code, then reproduce the event-ordering case using the inline scripts from the issue. Compare the Blob/data URL path with assigning script text directly, and verify that the listener is registered before DOMContentLiteSpeedLoaded fires without breaking other delayed-script behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100