prism: Defer breaks autoload (when loading page without having resources cached)

Information:

  • Prism version: 1.17.1
  • Plugins: autoloader
  • Environment: Browser

Description When the page is loaded without the JS resources being cached and the scripts are marked with defer then the syntax highlighting is not applied.

Example Without defer (workking): https://jsfiddle.net/f5vj903e/
With defer (not working): https://jsfiddle.net/f5vj903e/1/

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 29 (15 by maintainers)

Commits related to this issue

Most upvoted comments

Alright, I think I know what causes the race condition.

Since the readyState of the document is interactive, Prism Core uses requestAnimationFrame to highlight all code blocks. The problem with this is that this means that all plugins have to loaded and executed before the next frame. This is the reason why some plugins are sometimes active and sometimes not.

(You can verify that this is indeed what’s going on by setting a breakpoint in e.g prism-linenumbers.min.js and you’ll see that the breakpoint sometimes hits after all code blocks have been highlighted.)

This is a bit of an issue because we’ll have to wait for resources that might be loaded. Unfortunately, defer doesn’t work on inline scripts:

<script defer data-manual src="https://cdn.jsdelivr.net/npm/prismjs@1.17.1/prism.min.js" integrity="sha384-ccmyu9Bl8HZLIVEUqF+ZzcZTBPB8VgMI2lQpOsNDOvro/1SfRnz3qkub0eUxof1s" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/prismjs@1.17.1/plugins/autoloader/prism-autoloader.min.js" integrity="sha384-xF5Qt8AUh+k8ZzozF9d1iDRKeeP1m9PPJKKhy3R/O4+5JccihNLvy1fIuGnkye7+" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/prismjs@1.17.1/plugins/line-numbers/prism-line-numbers.min.js" integrity="sha384-g0u6zLvZF3g2I+/vETu7YYk74PXoDBNGy5qtL04/uW6PJGMDeax43A5hFa55xaAT" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/prismjs@1.17.1/plugins/toolbar/prism-toolbar.min.js" integrity="sha384-Q7kIVs9c3Zyij/OpKzuVFFpRTTXHL3qytFywO0AtJdmisfKKSEsnoc7pHfkxgXZX" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/prismjs@1.17.1/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js" integrity="sha384-V+ZXzru/5DpvSuLtIS2SctoAyvpigW+zF56aiGLPc+PvQxO3EkpeYDUw6t1Y6d9y" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/prismjs@1.17.1/plugins/show-language/prism-show-language.min.js" integrity="sha384-xnJRKz3VKJloaoR0FNJVmbJ55eTSeuztu0Okhd9vvz2hblDQc0UJWVkj3sIikslo" crossorigin="anonymous"></script>
<script defer>Prism.highlightAll();</script>
<!-- defer won't do anything -->

So, instead of using the requestAnimationFrame path, we should use the DOMContentLoaded path in Prism Core because deferred scripts are guaranteed to be executed before that event. And to figure out whether the current script is deferred or not, we need currentScript. (yay, full circle) I’m imagining something along the lines of the following as a fix.

	if (document.readyState !== 'loading' && !script.defer) {
		// requestAnimationFrame
	} else {
		document.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback);
	}

Have I verified that this fix works? No. Why not? Funny story. As it turns out: the moment you add an inline script everything works. No race condition, it just works. Srsly, try it. Just add <script>/* how? */</script> anywhere in the test page. (btw <script></script> doesn’t work. Seems like someone at Mozilla wanted to optimize) How’s that as a fix? Just add <script>/**/</script>.

Ok, I just verified that the fix works. I’ll make a PR.

So I created a standalone HTML page that displays the bug (not minimal, but I think other scripts help bringing the issues out). testpage.zip

It helps if you put the page on a (slow) webserver. As locally I can’t seem to reproduce any of the issues except the wrong script tag issue, but online they still all happen.