Issue #17045: UI Flickering on Checkstyle.org – Panel Resizes Erratically on Navigation#18036
Conversation
|
Github, generate website |
| <script type="text/javascript" src="$relativePath/js/checkstyle.js" defer async></script> | ||
| <script type="text/javascript" src="$relativePath/js/anchors.js"></script> | ||
| <script type="text/javascript" src="$relativePath/js/google-analytics.js"></script> | ||
| <script type="text/javascript" src="$relativePath/js/copy-clipboard.js"></script> |
There was a problem hiding this comment.
IMO, we should add defer async to the rest of the scripts too. I see no reason not to.
There was a problem hiding this comment.
adding defer I can understand but adding async will make the script execute in parallel with HTML parsing that means while the scripts are executing, the DOM might not have been parsed and we need the DOM to be parsed before executing the script. Please correct me if I am wrong.
There was a problem hiding this comment.
adding defer I can understand but adding async will make the script execute in parallel with HTML parsing that means while the scripts are executing, the DOM might not have been parsed and we need the DOM to be parsed before executing the script. Please correct me if I am wrong.
+1, I too think only defer is fine, async is not required it might cause conflict
There was a problem hiding this comment.
Correct, the script will execute before the DOM is parsed.
However, the execution of the script only registers the listeners (.addEventListener()). The callback function that is registered with each listener will trigger only when the corresponding event fires.
If we take the following code:
checkstyle/src/site/resources/js/checkstyle.js
Lines 8 to 18 in 0d604f6
The listener will be registered before the HTML parser is done, but the callback (everything within function () { ..} will run only when the "load" event fires. And the "load" event fires way after the DOM is parsed. Same applies for the DOMContentLoaded event.
There was a problem hiding this comment.
Shall we do that in a separate issue? I'll open it if you are okay with that. Just so this PR and issue remain scoped to the UI flickering issue.
There was a problem hiding this comment.
Let's merge this PR, and extend all others scripts in separate PR
There was a problem hiding this comment.
I created an issue for the other scripts:
There was a problem hiding this comment.
@Zopsss Please let me know if there's anything else to do.
There was a problem hiding this comment.
Sorry for the delay. Everything looks fine. Thanks a lot for detailed explanations!
|
Here is a video comparing https://checkstyle.sourceforge.io and the generated website in the above comment: Firefox on Ubuntu 24.04 |
|
@smita1078 , please looks at this change too. |
|
Converting this to a draft. There is a race condition I did not take into account. |
…es Erratically on Navigation
| if (document.readyState === "loading") { | ||
| document.addEventListener("DOMContentLoaded", setBodyColumnMargin); | ||
| } | ||
| else { | ||
| setBodyColumnMargin(); | ||
| } | ||
|
|
There was a problem hiding this comment.
This if-else is mandatory to prevent a race condition where the DOMContentLoaded event might fire before the listener is created. Taken straight out of the docs1:
Sometimes your script may run after the
DOMContentLoadedevent has already fired. This typically happens when the script runs asynchronously. Common scenarios include: [...]
- A script that is included via
<script async>.[...] In these cases, you should check the document's
readyStatebefore adding aDOMContentLoadedlistener, or your setup logic may not execute at all. [...]
Consider the following script file in isolation:function doSomething() { console.info("DOM loaded"); } if (document.readyState === "loading") { // Loading hasn't finished yet document.addEventListener("DOMContentLoaded", doSomething); } else { // `DOMContentLoaded` has already fired doSomething(); }The script can't enforce how it's included by the HTML. If it's included via
<script async>, or it's dynamically injected, then by the time it executes,DOMContentLoadedhas already fired. To ensure thatdoSomething()always runs when the script loads, we need to have two paths, one that immediately runsdoSomethingif the document is already loaded, and another that runsdoSomethingonce the document is loaded.
This race condition can happen with the DOMContentLoaded event, but not with load2:
The
loadevent is fired when the whole page has loaded, including all dependent resources such as stylesheets, scripts (including async, deferred, and module scripts), iframes,
Footnotes
|
Github, generate website |
|
@smita1078 , and see comment from you, please finish review |
|
Ok , looks like @smita1078 is busy with other activities, we moving forward, we can always improve in separate PRs |
Sorry, missed it!
Yes, I went through the discussion — the race condition that @stoyanK7 mentioned aligns with the concern I had earlier regarding potential conflicts when using async. I see that it’s already been taken into account in the latest changes. All good from my side 👍 |
Issue
Summary - What changed?
setBodyColumnMargin()fromwindow ("load")todocument ("DOMContentLoaded")The
loadevent fires only after the whole page has loaded - scripts, images, iframes, stylesheets,... 1. We could instead use theDOMContentLoadedevent that fires earlier - when the HTML document has been completely parsed, and all deferred scripts have downloaded and executed2. Even the docs suggest that usingloadis a common mistake2:We don't need a fully loaded page to set the
classNameof#leftColumnand#bodyColumn, just the DOM. Therefore, usingDOMContentLoadedis something we can do.asyncanddeferattributes to thecheckstyle.jsscript so that it's fetched in parallel to the HTML parserDoes this all work?
I see an improvement in 80% of cases. Though sometimes flickering still occurs (for instance, on the
Release Notespage). You just have to test on your browser.Footnotes
Window: load event - Web APIs | MDN ↩
Document: DOMContentLoaded event - Web APIs | MDN ↩ ↩2
HTML Standard 4.12.1 The script element ↩