Bug 602838 part 1 - Execute in insertion order script-inserted external scripts that have the async DOM attribute reporting false. r=jonas, a=blocking2.0-beta8.
authorHenri Sivonen <hsivonen@iki.fi>
Wed, 13 Oct 2010 10:12:55 +0300
changeset 57321 fce2fc592595
parent 57320 e7dc2d0fed3a
child 57322 be59b0a650ad
push id16873
push userhsivonen@iki.fi
push date2010-11-11 07:53 +0000
treeherdermozilla-central@be59b0a650ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonas, blocking2
bugs602838
milestone2.0b8pre
Bug 602838 part 1 - Execute in insertion order script-inserted external scripts that have the async DOM attribute reporting false. r=jonas, a=blocking2.0-beta8.
content/base/src/nsScriptLoader.cpp
content/base/src/nsScriptLoader.h
content/base/test/Makefile.in
content/base/test/script_bug602838.sjs
content/base/test/test_bug602838.html
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -164,16 +164,20 @@ nsScriptLoader::~nsScriptLoader()
   for (PRUint32 i = 0; i < mDeferRequests.Length(); i++) {
     mDeferRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
   for (PRUint32 i = 0; i < mAsyncRequests.Length(); i++) {
     mAsyncRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
+  for (PRUint32 i = 0; i < mNonAsyncExternalScriptInsertedRequests.Length(); i++) {
+    mNonAsyncExternalScriptInsertedRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
+  }
+
   // Unblock the kids, in case any of them moved to a different document
   // subtree in the meantime and therefore aren't actually going away.
   for (PRUint32 j = 0; j < mPendingChildLoaders.Length(); ++j) {
     mPendingChildLoaders[j]->RemoveExecuteBlocker();
   }  
 }
 
 NS_IMPL_ISUPPORTS1(nsScriptLoader, nsIStreamLoaderObserver)
@@ -566,41 +570,52 @@ nsScriptLoader::ProcessScriptElement(nsI
       request->mIsInline = PR_FALSE;
       request->mLoading = PR_TRUE;
       rv = StartLoad(request, type);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     request->mJSVersion = version;
 
-    PRBool async = !aElement->GetParserCreated() || aElement->GetScriptAsync();
-
-    // we now have a request that may or may not be still loading
-    if (!async && aElement->GetScriptDeferred()) {
+    if (aElement->GetScriptAsync()) {
+      mAsyncRequests.AppendElement(request);
+      if (!request->mLoading) {
+        // The script is available already. Run it ASAP when the event
+        // loop gets a chance to spin.
+        ProcessPendingRequestsAsync();
+      }
+      return NS_OK;
+    }
+    if (!aElement->GetParserCreated()) {
+      // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
+      // for RequireJS work with their Gecko-sniffed code path. See
+      // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
+      mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
+      if (!request->mLoading) {
+        // The script is available already. Run it ASAP when the event
+        // loop gets a chance to spin.
+        ProcessPendingRequestsAsync();
+      }
+      return NS_OK;
+    }
+    // we now have a parser-inserted request that may or may not be still
+    // loading
+    if (aElement->GetScriptDeferred()) {
       // We don't want to run this yet.
       // If we come here, the script is a parser-created script and it has
       // the defer attribute but not the async attribute. Since a
       // a parser-inserted script is being run, we came here by the parser
       // running the script, which means the parser is still alive and the
       // parse is ongoing.
       NS_ASSERTION(mDocument->GetCurrentContentSink() ||
                    aElement->GetParserCreated() == FROM_PARSER_XSLT,
           "Non-XSLT Defer script on a document without an active parser; bug 592366.");
       mDeferRequests.AppendElement(request);
       return NS_OK;
     }
-    if (async) {
-      mAsyncRequests.AppendElement(request);
-      if (!request->mLoading) {
-        // The script is available already. Run it ASAP when the event
-        // loop gets a chance to spin.
-        ProcessPendingRequestsAsync();
-      }
-      return NS_OK;
-    }
 
     if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
       // Need to maintain order for XSLT-inserted scripts
       NS_ASSERTION(!mParserBlockingRequest,
           "Parser-blocking scripts and XSLT scripts in the same doc!");
       mXSLTRequests.AppendElement(request);
       if (!request->mLoading) {
         // The script is available already. Run it ASAP when the event
@@ -902,32 +917,44 @@ nsScriptLoader::ProcessPendingRequests()
       request.swap(mAsyncRequests[i]);
       mAsyncRequests.RemoveElementAt(i);
       ProcessRequest(request);
       continue;
     }
     ++i;
   }
 
+  while (mEnabled && !mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
+         !mNonAsyncExternalScriptInsertedRequests[0]->mLoading) {
+    // Violate the HTML5 spec and execute these in the insertion order in
+    // order to make LABjs and the "order" plug-in for RequireJS work with
+    // their Gecko-sniffed code path. See
+    // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
+    request.swap(mNonAsyncExternalScriptInsertedRequests[0]);
+    mNonAsyncExternalScriptInsertedRequests.RemoveElementAt(0);
+    ProcessRequest(request);
+  }
+
   if (mDocumentParsingDone && mXSLTRequests.IsEmpty()) {
     while (!mDeferRequests.IsEmpty() && !mDeferRequests[0]->mLoading) {
       request.swap(mDeferRequests[0]);
       mDeferRequests.RemoveElementAt(0);
       ProcessRequest(request);
     }
   }
 
   while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
     nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
     mPendingChildLoaders.RemoveElementAt(0);
     child->RemoveExecuteBlocker();
   }
 
   if (mDocumentParsingDone && mDocument &&
       !mParserBlockingRequest && mAsyncRequests.IsEmpty() &&
+      mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
       mXSLTRequests.IsEmpty() && mDeferRequests.IsEmpty()) {
     // No more pending scripts; time to unblock onload.
     // OK to unblock onload synchronously here, since callers must be
     // prepared for the world changing anyway.
     mDocumentParsingDone = PR_FALSE;
     mDocument->UnblockOnload(PR_TRUE);
   }
 }
@@ -1089,16 +1116,17 @@ nsScriptLoader::OnStreamComplete(nsIStre
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
   nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
                                      aString);
   if (NS_FAILED(rv)) {
     if (mDeferRequests.RemoveElement(request) ||
         mAsyncRequests.RemoveElement(request) ||
+        mNonAsyncExternalScriptInsertedRequests.RemoveElement(request) ||
         mXSLTRequests.RemoveElement(request)) {
       FireScriptAvailable(rv, request);
     } else if (mParserBlockingRequest == request) {
       mParserBlockingRequest = nsnull;
       // nsContentSink::ScriptAvailable unblocks the parser
       FireScriptAvailable(rv, request);
     } else {
       mPreloads.RemoveElement(request, PreloadRequestComparator());
@@ -1167,16 +1195,17 @@ nsScriptLoader::PrepareLoadedRequest(nsS
   }
 
   // This assertion could fire errorously if we ran out of memory when
   // inserting the request in the array. However it's an unlikely case
   // so if you see this assertion it is likely something else that is
   // wrong, especially if you see it more than once.
   NS_ASSERTION(mDeferRequests.IndexOf(aRequest) >= 0 ||
                mAsyncRequests.IndexOf(aRequest) >= 0 ||
+               mNonAsyncExternalScriptInsertedRequests.IndexOf(aRequest) >= 0 ||
                mXSLTRequests.IndexOf(aRequest) >= 0 ||
                mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
                mParserBlockingRequest,
                "aRequest should be pending!");
 
   // Mark this as loaded
   aRequest->mLoading = PR_FALSE;
 
@@ -1220,16 +1249,17 @@ nsScriptLoader::ParsingComplete(PRBool a
     // Have to check because we apparently get ParsingComplete
     // without BeginDeferringScripts in some cases
     mDocumentParsingDone = PR_TRUE;
   }
   mDeferEnabled = PR_FALSE;
   if (aTerminated) {
     mDeferRequests.Clear();
     mAsyncRequests.Clear();
+    mNonAsyncExternalScriptInsertedRequests.Clear();
     mXSLTRequests.Clear();
     mParserBlockingRequest = nsnull;
   }
 
   // Have to call this even if aTerminated so we'll correctly unblock
   // onload and all.
   ProcessPendingRequests();
 }
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -292,16 +292,17 @@ private:
   nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
                                 nsIStreamLoader* aLoader,
                                 nsresult aStatus,
                                 PRUint32 aStringLen,
                                 const PRUint8* aString);
 
   nsIDocument* mDocument;                   // [WEAK]
   nsCOMArray<nsIScriptLoaderObserver> mObservers;
+  nsTArray<nsRefPtr<nsScriptLoadRequest> > mNonAsyncExternalScriptInsertedRequests;
   nsTArray<nsRefPtr<nsScriptLoadRequest> > mAsyncRequests;
   nsTArray<nsRefPtr<nsScriptLoadRequest> > mDeferRequests;
   nsTArray<nsRefPtr<nsScriptLoadRequest> > mXSLTRequests;
   nsRefPtr<nsScriptLoadRequest> mParserBlockingRequest;
 
   // In mRequests, the additional information here is stored by the element.
   struct PreloadInfo {
     nsRefPtr<nsScriptLoadRequest> mRequest;
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -426,16 +426,18 @@ include $(topsrcdir)/config/rules.mk
 		test_bug578096.html \
 		test_bug598877.html \
 		test_bug600466.html \
 		test_bug600468.html \
 		test_bug600471.html \
 		test_bug601803.html \
 		file_bug601803a.html \
 		file_bug601803b.html \
+		test_bug602838.html \
+		script_bug602838.sjs \
 		test_bug604660.html \
 		file_bug604660-1.xml \
 		file_bug604660-2.xsl \
 		file_bug604660-3.js \
 		file_bug604660-4.js \
 		file_bug604660-5.xml \
 		file_bug604660-6.xsl \
 		test_bug605982.html \
new file mode 100644
--- /dev/null
+++ b/content/base/test/script_bug602838.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response)
+{
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/javascript", false);
+  response.write("ok(asyncRan, 'Async script should have run first.'); firstRan = true;");
+  response.processAsync();
+  var timer = Components.classes["@mozilla.org/timer;1"]
+    .createInstance(Components.interfaces.nsITimer);
+  timer.initWithCallback(function() {
+      response.finish();
+    }, 200, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug602838.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602838
+-->
+<head>
+  <title>Test for Bug 602838</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602838">Mozilla Bug 602838</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 602838 **/
+SimpleTest.waitForExplicitFinish();
+var firstRan = false;
+var asyncRan = false;
+
+var s = document.createElement("script");
+s.src = "script_bug602838.sjs";
+document.body.appendChild(s);
+
+s = document.createElement("script");
+s.src = "data:text/javascript,ok(firstRan, 'The first script should have run'); SimpleTest.finish();";
+document.body.appendChild(s);
+
+s = document.createElement("script");
+s.src = "data:text/javascript,ok(!firstRan, 'Non-async should not have run yet.'); asyncRan = true;";
+s.async = true;
+document.body.appendChild(s);
+
+</script>
+</pre>
+</body>
+</html>
+