Evangelism/Firefox3.5/35Days/Articles/Defer on script elements

From MozillaWiki
Jump to: navigation, search


defer attribute for the <script> element

In HTML, the script element allows authors to include dynamic script in their documents. The defer attribute is boolean attribute that indicate how the script should be executed. It was first introduced in Internet Explorer 4, and added in the HTML 4 specification.

If the defer attribute is present, then the script is executed when the page has finished parsing. The element must be added to the end of the list of scripts that will execute when the document has finished parsing. Think about a FIFO processing queue : the first script element to be added to the queue will be the first script to be executed, then processing proceeds sequentially in the same order.

First test

Here is a simple first test to see how the attribute works. The following lines are in the head element of a page :

<script>
	var test1 = "Test 1 : fail";
</script>
<script defer>
  	console.log(test1);
</script>
<script>
	test1 = "Test 1 : pass";
</script>

If the defer attribute for the script element is correctly implemented in the browser which renders the page, the second script element is executed after all the others, and the Firebug console should display "Test 1 : pass".
Otherwise, the console displays "Test 1 : fail" because the scripts are executed in the same order than in the source code.

The correct syntax for XHTML documents is

<script defer="defer"></script>

Second test

This second test is a way to see how the feature works in a webpage with multiples script elements inserted :

  • inline in the head and body elements
  • external via src attribute in head and body elements
  • with dynamic DOM insertion

Here follow the partial source code of this webpage :

<!doctype html>
<html>
    <head>
        <title> Test 2 </title>
        <script> var test2 = "Test 2 :\n\n"; </script>
        <script> document.addEventListener("DOMContentLoaded",
                function(){
                        test2 += "\tDOMContentLoaded\n";
                }, false);
        </script>
        <script defer> test2 += "\tInline HEAD deferred\n"; </script>
        <script> test2 += "\tInline HEAD\n"; </script>
        <script src="script1.js" defer>
                // External HEAD deferred (script1.js) 
        </script>
        <script src="script2.js">
                // External HEAD  (script2.js)
        </script>
	<script>
            // Dynamic DOM insertion of a script (script3.js)
            head = document.getElementsByTagName('head')[0];
            script3 = document.createElement('script');
            script3.setAttribute('src', 'script3.js');
            head.appendChild(script3);
            // Dynamic DOM insertion of a deferred script (script4.js)
            script4 = document.createElement('script');
            script4.setAttribute('defer', 'defer');
            script4.setAttribute('src', 'script4.js');
            head.appendChild(script4);
	</script>
	<script defer>
            // Deferred dynamic DOM insertion of a script (script5.js)
            head = document.getElementsByTagName('head')[0];
            script5 = document.createElement('script');
            script5.setAttribute('src', 'script5.js');
            head.appendChild(script5);
            // Deferred dynamic DOM insertion of a deferred script (script6.js)
            script6 = document.createElement('script');
            script6.setAttribute('defer', 'defer');
            script6.setAttribute('src', 'script6.js');
            head.appendChild(script6);
	</script>
    </head>
    <body onload="test2 += '\tBody onLoad\n';">
        <script defer> test2 += "\tInline BODY deferred\n"; </script>
        <script> test2 += "\tInline BODY\n"; </script>

	... other body content ...

		<a onclick="alert(test2);">Launch test 2</a>

	... other body content ...

        <script src="script7.js" defer>
                // External BODY deferred (script7.js)
        </script>
        <script src="script8.js">
                // External BODY (script8.js)
        </script>
    </body>
</html>

By clicking the link "Launch test 2", a pop-up appears with a list in it. This list shows the order of script elements loaded during the session.

The test displays also the DOMContentLoaded and body.onload events when they are fired.

If the defer attribute is correctly implemented in the browser, all the deferred lines should be near the bottom of the list.

Results of the second test for each browser are below (deferred scripts are in green color) :

  • The defer attribute behavior in the Firefox 3.5 browser is correct :
    1. Inline HEAD
    2. External HEAD (script2.js)
    3. Dynamic DOM insertion of a script (script3.js)
    4. Inline BODY
    5. External BODY (script8.js)
    6. Inline HEAD deferred
    7. External HEAD deferred (script1.js)
    8. Dynamic DOM insertion of a deferred script (script4.js)
    9. Inline BODY deferred
    10. External BODY deferred (script7.js)
    11. Deferred dynamic DOM insertion of a script (script5.js)
    12. Deferred dynamic DOM insertion of a deferred script (script6.js)
    13. DOMContentLoaded
    14. Body onLoad
  • The defer attribute behavior in the IE 8 browser is erratic : the order is different at each reload :
    1. Inline HEAD
    2. External HEAD (script2.js)
    3. Inline BODY
    4. External BODY (script8.js)
    5. Dynamic DOM insertion of a script (script3.js)
    6. Dynamic DOM insertion of a deferred script (script4.js)
    7. Inline HEAD deferred
    8. External HEAD deferred (script1.js)
    9. Inline BODY deferred
    10. External BODY deferred (script7.js)
    11. Body onLoad
    12. Deferred dynamic DOM insertion of a script (script5.js)
    13. Deferred dynamic DOM insertion of a deferred script (script6.js)
  • The defer attribute behavior in a WebKit browser (Safari 4.0) is erratic : the order is different at each reload :
    1. Inline HEAD deferred
    2. Inline HEAD
    3. External HEAD deferred (script1.js)
    4. External HEAD (script2.js)
    5. Inline BODY deferred
    6. Inline BODY
    7. External BODY deferred (script7.js)
    8. Deferred dynamic DOM insertion of a script (script5.js)
    9. Dynamic DOM insertion of a deferred script (script4.js)
    10. Deferred dynamic DOM insertion of a deferred script (script6.js)
    11. Dynamic DOM insertion of a script (script3.js)
    12. External BODY (script8.js)
    13. DOMContentLoaded
    14. Body onLoad
  • The defer attribute behavior in the Opera 10.00 Beta browser :
    1. Inline HEAD deferred
    2. Inline HEAD
    3. External HEAD deferred (script1.js)
    4. External HEAD (script2.js)
    5. Dynamic DOM insertion of a script (script3.js)
    6. Dynamic DOM insertion of a deferred script (script4.js)
    7. Deferred dynamic DOM insertion of a script (script5.js)
    8. Deferred dynamic DOM insertion of a deferred script (script6.js)
    9. Inline BODY deferred
    10. Inline BODY
    11. External BODY deferred (script7.js)
    12. External BODY (script8.js)
    13. DOMContentLoaded
    14. Body onLoad

Conclusion

Currently, between the 4 recent browsers tested, Firefox 3.5 only properly supports the defer attribute.

Inspiration and further reading

  • The fixed bug 28293 - (defer) scripts with defer attribute not deferred : bug 28293