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
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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>
+