Bug 591981 - Make script-inserted inline scripts run right away, make script-inserted external scripts behave like async scripts and make document.write writing an inline script return at a predictable time. r=jonas, a=blocking2.0-beta7.
authorHenri Sivonen <hsivonen@iki.fi>
Wed, 01 Sep 2010 14:41:12 +0300
changeset 54758 a60414d076b5ea630e7afdbc4dd43093342fedbf
parent 54757 6e9809698df7ee0089eaa43b343b171e1f35aba4
child 54769 287acb4cc03bbf765b1fafd39869f18a40596b5d
push idunknown
push userunknown
push dateunknown
reviewersjonas, blocking2
bugs591981
milestone2.0b7pre
first release with
nightly linux32
a60414d076b5 / 4.0b7pre / 20100929030647 / files
nightly linux64
a60414d076b5 / 4.0b7pre / 20100929030730 / files
nightly mac
a60414d076b5 / 4.0b7pre / 20100929030851 / files
nightly win32
a60414d076b5 / 4.0b7pre / 20100929041446 / files
nightly win64
a60414d076b5 / 4.0b7pre / 20100929010320 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 591981 - Make script-inserted inline scripts run right away, make script-inserted external scripts behave like async scripts and make document.write writing an inline script return at a predictable time. r=jonas, a=blocking2.0-beta7.
content/base/public/nsIScriptElement.h
content/base/src/nsContentSink.cpp
content/base/src/nsScriptElement.cpp
content/base/src/nsScriptElement.h
content/base/src/nsScriptLoader.cpp
content/base/src/nsScriptLoader.h
content/base/test/test_bug28293.html
content/base/test/test_bug28293.xhtml
content/html/content/src/nsHTMLScriptElement.cpp
content/html/content/test/test_bug300691-2.html
content/svg/content/src/nsSVGScriptElement.cpp
content/test/reftest/bug591981-1.html
content/test/reftest/bug591981-2.html
content/test/reftest/bug591981-ref.html
content/test/reftest/bug591981-script.js
content/test/reftest/reftest.list
dom/tests/mochitest/bugs/child_bug260264.html
dom/tests/mochitest/bugs/grandchild_bug260264.html
dom/tests/mochitest/bugs/test_bug260264.html
dom/tests/mochitest/bugs/utils_bug260264.js
--- a/content/base/public/nsIScriptElement.h
+++ b/content/base/public/nsIScriptElement.h
@@ -40,36 +40,38 @@
 #define nsIScriptElement_h___
 
 #include "nsISupports.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsIScriptLoaderObserver.h"
 #include "nsWeakPtr.h"
 #include "nsIParser.h"
+#include "nsContentCreatorFunctions.h"
 
 #define NS_ISCRIPTELEMENT_IID \
 { 0x6d625b30, 0xfac4, 0x11de, \
 { 0x8a, 0x39, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
 
 /**
  * Internal interface implemented by script elements
  */
 class nsIScriptElement : public nsIScriptLoaderObserver {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID)
 
-  nsIScriptElement()
+  nsIScriptElement(PRUint32 aFromParser)
     : mLineNumber(0),
-      mIsEvaluated(PR_FALSE),
+      mAlreadyStarted(PR_FALSE),
       mMalformed(PR_FALSE),
       mDoneAddingChildren(PR_TRUE),
       mFrozen(PR_FALSE),
       mDefer(PR_FALSE),
       mAsync(PR_FALSE),
+      mParserCreated((PRUint8)aFromParser),
       mCreatorParser(nsnull)
   {
   }
 
   /**
    * Content type identifying the scripting language. Can be empty, in
    * which case javascript will be assumed.
    */
@@ -112,16 +114,25 @@ public:
    * Is the script async. Currently only supported by HTML scripts.
    */
   PRBool GetScriptAsync()
   {
     NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
     return mAsync;  
   }
 
+  /**
+   * Returns a constant defined in nsContentCreatorFunctions.h. Non-zero
+   * values mean parser-created and zero means not parser-created.
+   */
+  PRUint32 GetParserCreated()
+  {
+    return mParserCreated;
+  }
+
   void SetScriptLineNumber(PRUint32 aLineNumber)
   {
     mLineNumber = aLineNumber;
   }
   PRUint32 GetScriptLineNumber()
   {
     return mLineNumber;
   }
@@ -132,17 +143,25 @@ public:
   }
   PRBool IsMalformed()
   {
     return mMalformed;
   }
 
   void PreventExecution()
   {
-    mIsEvaluated = PR_TRUE;
+    mAlreadyStarted = PR_TRUE;
+  }
+
+  void LoseParserInsertedness()
+  {
+    mFrozen = PR_FALSE;
+    mUri = nsnull;
+    mCreatorParser = nsnull;
+    mParserCreated = NS_NOT_FROM_PARSER;
   }
 
   void SetCreatorParser(nsIParser* aParser)
   {
     mCreatorParser = getter_AddRefs(NS_GetWeakReference(aParser));
   }
 
   /**
@@ -180,17 +199,17 @@ protected:
   /**
    * The start line number of the script.
    */
   PRUint32 mLineNumber;
   
   /**
    * The "already started" flag per HTML5.
    */
-  PRPackedBool mIsEvaluated;
+  PRPackedBool mAlreadyStarted;
   
   /**
    * The script didn't have an end tag.
    */
   PRPackedBool mMalformed;
   
   /**
    * False if parser-inserted but the parser hasn't triggered running yet.
@@ -208,16 +227,21 @@ protected:
   PRPackedBool mDefer;
   
   /**
    * The effective asyncness.
    */
   PRPackedBool mAsync;
   
   /**
+   * Whether this element was parser-created.
+   */
+  PRUint8 mParserCreated;
+
+  /**
    * The effective src (or null if no src).
    */
   nsCOMPtr<nsIURI> mUri;
   
   /**
    * The creator parser of a non-defer, non-async parser-inserted script.
    */
   nsWeakPtr mCreatorParser;
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -361,18 +361,18 @@ nsContentSink::ScriptAvailable(nsresult 
                                nsIScriptElement *aElement,
                                PRBool aIsInline,
                                nsIURI *aURI,
                                PRInt32 aLineNo)
 {
   PRUint32 count = mScriptElements.Count();
 
   // aElement will not be in mScriptElements if a <script> was added
-  // using the DOM during loading, or if the script was inline and thus
-  // never blocked.
+  // using the DOM during loading or if DoneAddingChildren did not return
+  // NS_ERROR_HTMLPARSER_BLOCK.
   NS_ASSERTION(count == 0 ||
                mScriptElements.IndexOf(aElement) == PRInt32(count - 1) ||
                mScriptElements.IndexOf(aElement) == -1,
                "script found at unexpected position");
 
   // Check if this is the element we were waiting for
   if (count == 0 || aElement != mScriptElements[count - 1]) {
     return NS_OK;
--- a/content/base/src/nsScriptElement.cpp
+++ b/content/base/src/nsScriptElement.cpp
@@ -173,32 +173,32 @@ nsresult
 nsScriptElement::MaybeProcessScript()
 {
   nsCOMPtr<nsIContent> cont =
     do_QueryInterface((nsIScriptElement*) this);
 
   NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this),
                "You forgot to add self as observer");
 
-  if (mIsEvaluated || !mDoneAddingChildren || !cont->IsInDoc() ||
+  if (mAlreadyStarted || !mDoneAddingChildren || !cont->IsInDoc() ||
       mMalformed || !HasScriptContent()) {
     return NS_OK;
   }
 
   FreezeUriAsyncDefer();
 
   if (InNonScriptingContainer(cont)) {
     // Make sure to flag ourselves as evaluated
-    mIsEvaluated = PR_TRUE;
+    mAlreadyStarted = PR_TRUE;
     return NS_OK;
   }
 
   nsresult scriptresult = NS_OK;
   nsRefPtr<nsScriptLoader> loader = cont->GetOwnerDoc()->ScriptLoader();
-  mIsEvaluated = PR_TRUE;
+  mAlreadyStarted = PR_TRUE;
   scriptresult = loader->ProcessScriptElement(this);
 
   // The only error we don't ignore is NS_ERROR_HTMLPARSER_BLOCK
   // However we don't want to override other success values
   // (such as NS_CONTENT_SCRIPT_IS_EVENTHANDLER)
   if (NS_FAILED(scriptresult) &&
       scriptresult != NS_ERROR_HTMLPARSER_BLOCK) {
     scriptresult = NS_OK;
--- a/content/base/src/nsScriptElement.h
+++ b/content/base/src/nsScriptElement.h
@@ -54,16 +54,21 @@ public:
   NS_DECL_NSISCRIPTLOADEROBSERVER
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
 
+  nsScriptElement(PRUint32 aFromParser)
+    : nsIScriptElement(aFromParser)
+  {
+  }
+
 protected:
   // Internal methods
 
   /**
    * Check if this element contains any script, linked or inline
    */
   virtual PRBool HasScriptContent() = 0;
 
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -70,16 +70,17 @@
 #include "nsIParser.h"
 #include "nsThreadUtils.h"
 #include "nsDocShellCID.h"
 #include "nsIContentSecurityPolicy.h"
 #include "prlog.h"
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 #include "nsCRT.h"
+#include "nsContentCreatorFunctions.h"
 
 #include "mozilla/FunctionTimer.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
 #endif
 
 using namespace mozilla::dom;
@@ -112,17 +113,16 @@ public:
 
   PRBool IsPreload()
   {
     return mElement == nsnull;
   }
 
   nsCOMPtr<nsIScriptElement> mElement;
   PRPackedBool mLoading;             // Are we still waiting for a load to complete?
-  PRPackedBool mDefer;               // Is execution defered?
   PRPackedBool mIsInline;            // Is the script inline or loaded?
   nsString mScriptText;              // Holds script for loaded scripts
   PRUint32 mJSVersion;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIURI> mFinalURI;
   PRInt32 mLineNo;
 };
 
@@ -135,31 +135,35 @@ NS_IMPL_THREADSAFE_ISUPPORTS0(nsScriptLo
 //
 //////////////////////////////////////////////////////////////
 
 nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
   : mDocument(aDocument),
     mBlockerCount(0),
     mEnabled(PR_TRUE),
     mDeferEnabled(PR_FALSE),
-    mUnblockOnloadWhenDoneProcessing(PR_FALSE)
+    mDocumentParsingDone(PR_FALSE)
 {
   // enable logging for CSP
 #ifdef PR_LOGGING
   if (!gCspPRLog)
     gCspPRLog = PR_NewLogModule("CSP");
 #endif
 }
 
 nsScriptLoader::~nsScriptLoader()
 {
   mObservers.Clear();
 
-  for (PRInt32 i = 0; i < mRequests.Count(); i++) {
-    mRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
+  if (mParserBlockingRequest) {
+    mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
+  }
+
+  for (PRInt32 i = 0; i < mDeferRequests.Count(); i++) {
+    mDeferRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
   for (PRInt32 i = 0; i < mAsyncRequests.Count(); i++) {
     mAsyncRequests[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.
@@ -334,16 +338,33 @@ PRBool
 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
                                              nsIURI * const &aURI) const
 {
   PRBool same;
   return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
          same;
 }
 
+class nsScriptRequestProcessor : public nsRunnable
+{
+private:
+  nsRefPtr<nsScriptLoader> mLoader;
+  nsRefPtr<nsScriptLoadRequest> mRequest;
+public:
+  nsScriptRequestProcessor(nsScriptLoader* aLoader,
+                           nsScriptLoadRequest* aRequest)
+    : mLoader(aLoader)
+    , mRequest(aRequest)
+  {}
+  NS_IMETHODIMP Run()
+  {
+    return mLoader->ProcessRequest(mRequest);
+  }
+};
+
 nsresult
 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
 {
   // We need a document to evaluate scripts.
   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
 
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
@@ -510,145 +531,153 @@ nsScriptLoader::ProcessScriptElement(nsI
       !nsContentUtils::IsChromeDoc(mDocument)) {
     NS_WARNING("Untrusted language called from non-chrome - ignored");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsCOMPtr<nsIContent> eltContent(do_QueryInterface(aElement));
   eltContent->SetScriptTypeID(typeID);
 
-  PRBool hadPendingRequests = !!GetFirstPendingRequest();
+  // Step 9. in the HTML5 spec
 
-  // Did we preload this request?
   nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
   nsRefPtr<nsScriptLoadRequest> request;
   if (scriptURI) {
+    // external script
     nsTArray<PreloadInfo>::index_type i =
       mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
     if (i != nsTArray<PreloadInfo>::NoIndex) {
+      // preloaded
+      // note that a script-inserted script can steal a preload!
       request = mPreloads[i].mRequest;
       request->mElement = aElement;
-      request->mJSVersion = version;
-      request->mDefer = mDeferEnabled && aElement->GetScriptDeferred() &&
-        !aElement->GetScriptAsync();
+      // XXX what if the charset attribute of the element and the charset
+      // of the preload don't match?
       mPreloads.RemoveElementAt(i);
+      rv = CheckContentPolicy(mDocument, aElement, request->mURI, type);
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+      // not preloaded
+      request = new nsScriptLoadRequest(aElement, version);
+      NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
+      request->mURI = scriptURI;
+      request->mIsInline = PR_FALSE;
+      request->mLoading = PR_TRUE;
+      rv = StartLoad(request, type);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
-      rv = CheckContentPolicy(mDocument, aElement, request->mURI, type);
-      if (NS_FAILED(rv)) {
-        // Note, we're dropping our last ref to request here.
-        return rv;
-      }
+    request->mJSVersion = version;
+
+    PRBool async = !aElement->GetParserCreated() || aElement->GetScriptAsync();
 
-      // Can we run the script now?
-      // This is true if we're done loading, the script isn't deferred and
-      // there are either no scripts or stylesheets to wait for, or the
-      // script is async
-      PRBool readyToRun =
-        !request->mLoading && !request->mDefer &&
-        ((!hadPendingRequests && ReadyToExecuteScripts()) ||
-         aElement->GetScriptAsync());
-
-      if (readyToRun && nsContentUtils::IsSafeToRunScript()) {
+    // we now have a request that may or may not be still loading
+    if (!async && 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(),
+          "Defer script on a document without an active parser; bug 592366.");
+      mDeferRequests.AppendObject(request);
+      return NS_OK;
+    }
+    if (async) {
+      mAsyncRequests.AppendObject(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 (!request->mLoading) {
+      // The request has already been loaded. If the script comes from the
+      // network stream, cheat for performance reasons and avoid a trip
+      // through the event loop.
+      if (aElement->GetParserCreated() == NS_FROM_PARSER_NETWORK) {
         return ProcessRequest(request);
       }
+      // Otherwise, we've got a document.written script, make a trip through
+      // the event loop to hide the preload effects from the scripts on the
+      // Web page.
+      NS_ASSERTION(!mParserBlockingRequest,
+          "There can be only one parser-blocking script at a time");
+      mParserBlockingRequest = request;
+      ProcessPendingRequestsAsync();
+      return NS_ERROR_HTMLPARSER_BLOCK;
+    }
+    // The script hasn't loaded yet and is parser-inserted and non-async.
+    // It'll be executed when it has loaded.
+    NS_ASSERTION(!mParserBlockingRequest,
+        "There can be only one parser-blocking script at a time");
+    mParserBlockingRequest = request;
+    return NS_ERROR_HTMLPARSER_BLOCK;
+  }
 
-      // Not done loading yet. Move into the real requests queue and wait.
-      if (aElement->GetScriptAsync()) {
-        mAsyncRequests.AppendObject(request);
-      }
-      else {
-        mRequests.AppendObject(request);
-      }
+  // inline script
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  rv = mDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-      if (readyToRun) {
-        nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this,
-          &nsScriptLoader::ProcessPendingRequests));
-      }
+  if (csp) {
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("New ScriptLoader i ****with CSP****"));
+    PRBool inlineOK;
+    // this call will send violation reports when necessary
+    rv = csp->GetAllowsInlineScript(&inlineOK);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-      return request->mDefer || aElement->GetScriptAsync() ?
-        NS_OK : NS_ERROR_HTMLPARSER_BLOCK;
+    if (!inlineOK) {
+      PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP blocked inline scripts (2)"));
+      return NS_ERROR_FAILURE;
     }
   }
 
-  // Create a request object for this script
   request = new nsScriptLoadRequest(aElement, version);
   NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
-
-  // First check to see if this is an external script
-  if (scriptURI) {
-    request->mDefer = mDeferEnabled && aElement->GetScriptDeferred() &&
-      !aElement->GetScriptAsync();
-    request->mURI = scriptURI;
-    request->mIsInline = PR_FALSE;
-    request->mLoading = PR_TRUE;
-
-    rv = StartLoad(request, type);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-  } else {
-    // in-line script
-    nsCOMPtr<nsIContentSecurityPolicy> csp;
-    nsresult rv = mDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (csp) {
-      PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("New ScriptLoader i ****with CSP****"));
-      PRBool inlineOK;
-      // this call will send violation reports when necessary
-      rv = csp->GetAllowsInlineScript(&inlineOK);
-      NS_ENSURE_SUCCESS(rv, rv);
+  request->mJSVersion = version;
+  request->mLoading = PR_FALSE;
+  request->mIsInline = PR_TRUE;
+  request->mURI = mDocument->GetDocumentURI();
+  request->mLineNo = aElement->GetScriptLineNumber();
 
-      if (!inlineOK) {
-        PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP blocked inline scripts (2)"));
-        return NS_ERROR_FAILURE;
-      }
-    }
-
-    request->mDefer = PR_FALSE;
-    request->mLoading = PR_FALSE;
-    request->mIsInline = PR_TRUE;
-    request->mURI = mDocument->GetDocumentURI();
-
-    request->mLineNo = aElement->GetScriptLineNumber();
-
-    // If we've got existing pending requests, add ourselves
-    // to this list.
-    if (!hadPendingRequests && ReadyToExecuteScripts() &&
-        nsContentUtils::IsSafeToRunScript()) {
-      return ProcessRequest(request);
-    }
-  }
-
-  // Add the request to our requests list
-  NS_ENSURE_TRUE(aElement->GetScriptAsync() ?
-                 mAsyncRequests.AppendObject(request) :
-                 mRequests.AppendObject(request),
-                 NS_ERROR_OUT_OF_MEMORY);
-
-  if (request->mDefer || aElement->GetScriptAsync()) {
+  if (aElement->GetParserCreated() == NS_NOT_FROM_PARSER) {
+    NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+        "A script-inserted script is inserted without an update batch?");
+    nsContentUtils::AddScriptRunner(new nsScriptRequestProcessor(this,
+                                                                 request));
     return NS_OK;
   }
-
-  // If there weren't any pending requests before, and this one is
-  // ready to execute, do that as soon as it's safe.
-  if (!request->mLoading && !hadPendingRequests && ReadyToExecuteScripts()) {
-    nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this,
-      &nsScriptLoader::ProcessPendingRequests));
+  if (aElement->GetParserCreated() == NS_FROM_PARSER_NETWORK &&
+      !ReadyToExecuteScripts()) {
+    NS_ASSERTION(!mParserBlockingRequest,
+        "There can be only one parser-blocking script at a time");
+    mParserBlockingRequest = request;
+    return NS_ERROR_HTMLPARSER_BLOCK;
   }
-
-  // Added as pending request, now we can send blocking back
-  return NS_ERROR_HTMLPARSER_BLOCK;
+  // We now have a document.written inline script or we have an inline script
+  // from the network but there is no style sheet that is blocking scripts.
+  // Don't check for style sheets blocking scripts in the document.write
+  // case to avoid style sheet network activity affecting when
+  // document.write returns. It's not really necessary to do this if
+  // there's no document.write currently on the call stack. However,
+  // this way matches IE more closely than checking if document.write
+  // is on the call stack.
+  NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+      "Not safe to run a parser-inserted script?");
+  return ProcessRequest(request);
 }
 
 nsresult
 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
 {
-  NS_ASSERTION(ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript(),
-               "Caller forgot to check ReadyToExecuteScripts()");
+  NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+               "Processing requests when running scripts is unsafe.");
 
   NS_ENSURE_ARG(aRequest);
   nsAFlatString* script;
   nsAutoString textData;
 
   NS_TIME_FUNCTION;
 
   nsCOMPtr<nsIDocument> doc;
@@ -799,81 +828,71 @@ nsScriptLoader::EvaluateScript(nsScriptL
   if (stid == nsIProgrammingLanguage::JAVASCRIPT) {
     NS_ASSERTION(!::JS_IsExceptionPending(cx),
                  "JS_ReportPendingException wasn't called");
     ::JS_EndRequest(cx);
   }
   return rv;
 }
 
-nsScriptLoadRequest*
-nsScriptLoader::GetFirstPendingRequest()
-{
-  for (PRInt32 i = 0; i < mRequests.Count(); ++i) {
-    if (!mRequests[i]->mDefer) {
-      return mRequests[i];
-    }
-  }
-
-  return nsnull;
-}
-
 void
 nsScriptLoader::ProcessPendingRequestsAsync()
 {
-  if (GetFirstPendingRequest() || !mPendingChildLoaders.IsEmpty()) {
+  if (mParserBlockingRequest || !mPendingChildLoaders.IsEmpty()) {
     nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this,
       &nsScriptLoader::ProcessPendingRequests);
 
     NS_DispatchToCurrentThread(ev);
   }
 }
 
 void
 nsScriptLoader::ProcessPendingRequests()
 {
-  while (1) {
-    nsRefPtr<nsScriptLoadRequest> request;
-    if (ReadyToExecuteScripts()) {
-      request = GetFirstPendingRequest();
-      if (request && !request->mLoading) {
-        mRequests.RemoveObject(request);
-      }
-      else {
-        request = nsnull;
-      }
-    }
+  nsCOMPtr<nsScriptLoadRequest> request;
+  if (mParserBlockingRequest &&
+      !mParserBlockingRequest->mLoading &&
+      ReadyToExecuteScripts()) {
+    request.swap(mParserBlockingRequest);
+    // nsContentSink::ScriptAvailable unblocks the parser
+    ProcessRequest(request);
+  }
 
-    for (PRInt32 i = 0;
-         !request && mEnabled && i < mAsyncRequests.Count();
-         ++i) {
-      if (!mAsyncRequests[i]->mLoading) {
-        request = mAsyncRequests[i];
-        mAsyncRequests.RemoveObjectAt(i);
-      }
+  PRInt32 i = 0;
+  while (mEnabled && i < mAsyncRequests.Count()) {
+    if (!mAsyncRequests[i]->mLoading) {
+      request = mAsyncRequests[i];
+      mAsyncRequests.RemoveObjectAt(i);
+      ProcessRequest(request);
+      continue;
     }
+    ++i;
+  }
 
-    if (!request)
-      break;
-
-    ProcessRequest(request);
+  if (mDocumentParsingDone) {
+    while (mDeferRequests.Count() && !mDeferRequests[0]->mLoading) {
+      request = mDeferRequests[0];
+      mDeferRequests.RemoveObjectAt(0);
+      ProcessRequest(request);
+    }
   }
 
   while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
     nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
     mPendingChildLoaders.RemoveElementAt(0);
     child->RemoveExecuteBlocker();
   }
 
-  if (mUnblockOnloadWhenDoneProcessing && mDocument &&
-      !GetFirstPendingRequest() && !mAsyncRequests.Count()) {
+  if (mDocumentParsingDone && mDocument &&
+      !mParserBlockingRequest && !mAsyncRequests.Count() &&
+      !mDeferRequests.Count()) {
     // No more pending scripts; time to unblock onload.
     // OK to unblock onload synchronously here, since callers must be
     // prepared for the world changing anyway.
-    mUnblockOnloadWhenDoneProcessing = PR_FALSE;
+    mDocumentParsingDone = PR_FALSE;
     mDocument->UnblockOnload(PR_TRUE);
   }
 }
 
 PRBool
 nsScriptLoader::ReadyToExecuteScripts()
 {
   // Make sure the SelfReadyToExecuteScripts check is first, so that
@@ -1028,19 +1047,23 @@ nsScriptLoader::OnStreamComplete(nsIStre
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   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 (mRequests.RemoveObject(request) ||
+    if (mDeferRequests.RemoveObject(request) ||
         mAsyncRequests.RemoveObject(request)) {
       FireScriptAvailable(rv, request);
+    } else if (mParserBlockingRequest == request) {
+      mParserBlockingRequest = nsnull;
+      // nsContentSink::ScriptAvailable unblocks the parser
+      FireScriptAvailable(rv, request);
     } else {
       mPreloads.RemoveElement(request, PreloadRequestComparator());
     }
   }
 
   // Process our request and/or any pending ones
   ProcessPendingRequests();
 
@@ -1101,19 +1124,20 @@ nsScriptLoader::PrepareLoadedRequest(nsS
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   // 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(mRequests.IndexOf(aRequest) >= 0 ||
+  NS_ASSERTION(mDeferRequests.IndexOf(aRequest) >= 0 ||
                mAsyncRequests.IndexOf(aRequest) >= 0 ||
-               mPreloads.Contains(aRequest, PreloadRequestComparator()),
+               mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
+               mParserBlockingRequest,
                "aRequest should be pending!");
 
   // Mark this as loaded
   aRequest->mLoading = PR_FALSE;
 
   return NS_OK;
 }
 
@@ -1148,25 +1172,23 @@ nsScriptLoader::ShouldExecuteScript(nsID
 }
 
 void
 nsScriptLoader::ParsingComplete(PRBool aTerminated)
 {
   if (mDeferEnabled) {
     // Have to check because we apparently get ParsingComplete
     // without BeginDeferringScripts in some cases
-    mUnblockOnloadWhenDoneProcessing = PR_TRUE;
+    mDocumentParsingDone = PR_TRUE;
   }
   mDeferEnabled = PR_FALSE;
   if (aTerminated) {
-    mRequests.Clear();
-  } else {
-    for (PRUint32 i = 0; i < (PRUint32)mRequests.Count(); ++i) {
-      mRequests[i]->mDefer = PR_FALSE;
-    }
+    mDeferRequests.Clear();
+    mAsyncRequests.Clear();
+    mParserBlockingRequest = nsnull;
   }
 
   // Have to call this even if aTerminated so we'll correctly unblock
   // onload and all.
   ProcessPendingRequests();
 }
 
 void
@@ -1176,18 +1198,16 @@ nsScriptLoader::PreloadURI(nsIURI *aURI,
   nsRefPtr<nsScriptLoadRequest> request = new nsScriptLoadRequest(nsnull, 0);
   if (!request) {
     return;
   }
 
   request->mURI = aURI;
   request->mIsInline = PR_FALSE;
   request->mLoading = PR_TRUE;
-  request->mDefer = PR_FALSE; // This is computed later when we go to execute the
-                              // script.
   nsresult rv = StartLoad(request, aType);
   if (NS_FAILED(rv)) {
     return;
   }
 
   PreloadInfo *pi = mPreloads.AppendElement();
   pi->mRequest = request;
   pi->mCharset = aCharset;
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -55,16 +55,17 @@
 class nsScriptLoadRequest;
 
 //////////////////////////////////////////////////////////////
 // Script loader implementation
 //////////////////////////////////////////////////////////////
 
 class nsScriptLoader : public nsIStreamLoaderObserver
 {
+  friend class nsScriptRequestProcessor;
 public:
   nsScriptLoader(nsIDocument* aDocument);
   virtual ~nsScriptLoader();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLOADEROBSERVER
 
   /**
@@ -219,30 +220,30 @@ public:
    */
   void ParsingComplete(PRBool aTerminated);
 
   /**
    * Returns the number of pending scripts, deferred or not.
    */
   PRUint32 HasPendingOrCurrentScripts()
   {
-    return mCurrentScript || GetFirstPendingRequest();
+    return mCurrentScript || mParserBlockingRequest;
   }
 
   /**
    * Adds aURI to the preload list and starts loading it.
    *
    * @param aURI The URI of the external script.
    * @param aCharset The charset parameter for the script.
    * @param aType The type parameter for the script.
    */
   virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
                           const nsAString &aType);
 
-protected:
+private:
   /**
    * Helper function to check the content policy for a given request.
    */
   static nsresult CheckContentPolicy(nsIDocument* aDocument,
                                      nsISupports *aContext,
                                      nsIURI *aURI,
                                      const nsAString &aType);
 
@@ -289,23 +290,21 @@ protected:
                           const nsAFlatString& aScript);
 
   nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
                                 nsIStreamLoader* aLoader,
                                 nsresult aStatus,
                                 PRUint32 aStringLen,
                                 const PRUint8* aString);
 
-  // Returns the first pending (non deferred) request
-  nsScriptLoadRequest* GetFirstPendingRequest();
-
   nsIDocument* mDocument;                   // [WEAK]
   nsCOMArray<nsIScriptLoaderObserver> mObservers;
-  nsCOMArray<nsScriptLoadRequest> mRequests;
   nsCOMArray<nsScriptLoadRequest> mAsyncRequests;
+  nsCOMArray<nsScriptLoadRequest> mDeferRequests;
+  nsCOMPtr<nsScriptLoadRequest> mParserBlockingRequest;
 
   // In mRequests, the additional information here is stored by the element.
   struct PreloadInfo {
     nsRefPtr<nsScriptLoadRequest> mRequest;
     nsString mCharset;
   };
 
   struct PreloadRequestComparator {
@@ -321,12 +320,12 @@ protected:
   nsTArray<PreloadInfo> mPreloads;
 
   nsCOMPtr<nsIScriptElement> mCurrentScript;
   // XXXbz do we want to cycle-collect these or something?  Not sure.
   nsTArray< nsRefPtr<nsScriptLoader> > mPendingChildLoaders;
   PRUint32 mBlockerCount;
   PRPackedBool mEnabled;
   PRPackedBool mDeferEnabled;
-  PRPackedBool mUnblockOnloadWhenDoneProcessing;
+  PRPackedBool mDocumentParsingDone;
 };
 
 #endif //__nsScriptLoader_h__
--- a/content/base/test/test_bug28293.html
+++ b/content/base/test/test_bug28293.html
@@ -4,46 +4,42 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=28293
 -->
 <head>
   <title>Test for Bug 28293</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script>
+scriptInsertedExternalExecuted = false;
 res = 'A';
 
 SimpleTest.waitForExplicitFinish();
 onload = function () {
 
   res+='2';
 
   s = document.createElement('script');
   s.textContent="res+='g';";
   s.defer = true;
   document.body.appendChild(s);
 
   res+='3';
 
   s = document.createElement('script');
-  s.src="file_bug28293.sjs?res+='h';";
-  s.defer = true;
-  document.body.appendChild(s);
-
-  s = document.createElement('script');
   s.textContent="res+='i';done()";
   s.defer = true;
   document.body.appendChild(s);
 
   res+='4';
 }
 
 function done() {
-  is(res, "AacBCDEFGeHIJfbd1M2g34hi", "scripts executed in the wrong order");
-  ok(!fHadExecuted, "Dynamic script executed too late");
+  is(res, "AacBCDEFGeHIJb1M2g3i", "scripts executed in the wrong order");
+  ok(scriptInsertedExternalExecuted, "Dynamic script did not block load");
   SimpleTest.finish();
 }
 </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=28293">Mozilla Bug 28293</a>
 
 <script defer>
@@ -55,21 +51,16 @@ res += 'c';
 </script>
 <script>
 res += 'B';
 </script>
 <script>
 res += 'C';
 
 s = document.createElement('script');
-s.src="file_bug28293.sjs?res+='d';";
-s.defer = true;
-document.body.appendChild(s);
-
-s = document.createElement('script');
 s.textContent="res+='D';";
 document.body.appendChild(s);
 
 res += 'E';
 </script>
 <script>
 res += 'F';
 document.addEventListener("DOMContentLoaded", function() {
@@ -82,18 +73,15 @@ res += 'G';
 </script>
 <script defer>
 res += 'e';
 </script>
 <script src="file_bug28293.sjs?res+='H';"></script>
 <script>
 res += 'I';
 s = document.createElement('script');
-s.src="file_bug28293.sjs?fHadExecuted=(res.indexOf('f')>=0);";
+s.src="file_bug28293.sjs?scriptInsertedExternalExecuted=true;";
 document.body.appendChild(s);
 res += 'J';
 </script>
-<script defer>
-res += 'f';
-</script>
 
 </body>
 </html>
--- a/content/base/test/test_bug28293.xhtml
+++ b/content/base/test/test_bug28293.xhtml
@@ -3,46 +3,42 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=28293
 -->
 <head>
   <title>Test for Bug 28293</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script>
+scriptInsertedExternalExecuted = false;
 res = 'A';
 
 SimpleTest.waitForExplicitFinish();
 onload = function () {
 
   res+='2';
 
   s = document.createElement('script');
   s.textContent="res+='g';";
   s.defer = true;
   document.body.appendChild(s);
 
   res+='3';
 
   s = document.createElement('script');
-  s.src="file_bug28293.sjs?res+='h';";
-  s.defer = true;
-  document.body.appendChild(s);
-
-  s = document.createElement('script');
   s.textContent="res+='i';done()";
   s.defer = true;
   document.body.appendChild(s);
 
   res+='4';
 }
 
 function done() {
-  is(res, "AacBCDEFGeHIJfbd1M2g34hi", "scripts executed in the wrong order");
-  ok(!fHadExecuted, "Dynamic script executed too late");
+  is(res, "AacBCDEFGeHIJb1M2g3i", "scripts executed in the wrong order");
+  ok(scriptInsertedExternalExecuted, "Dynamic script did not block load");
   SimpleTest.finish();
 }
 </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=28293">Mozilla Bug 28293</a>
 
 <script defer="defer">
@@ -54,21 +50,16 @@ res += 'c';
 </script>
 <script>
 res += 'B';
 </script>
 <script>
 res += 'C';
 
 s = document.createElement('script');
-s.src="file_bug28293.sjs?res+='d';";
-s.defer = true;
-document.body.appendChild(s);
-
-s = document.createElement('script');
 s.textContent="res+='D';";
 document.body.appendChild(s);
 
 res += 'E';
 </script>
 <script>
 res += 'F';
 document.addEventListener("DOMContentLoaded", function() {
@@ -82,19 +73,16 @@ res += 'G';
 <script defer="defer">
 res += 'e';
 </script>
 <script src="file_bug28293.sjs?res+='H';"></script>
 <script>
 <![CDATA[
 res += 'I';
 s = document.createElement('script');
-s.src="file_bug28293.sjs?fHadExecuted=(res.indexOf('f')>=0);";
+s.src="file_bug28293.sjs?scriptInsertedExternalExecuted=true;";
 document.body.appendChild(s);
 res += 'J';
 ]]>
 </script>
-<script defer="defer">
-res += 'f';
-</script>
 
 </body>
 </html>
--- a/content/html/content/src/nsHTMLScriptElement.cpp
+++ b/content/html/content/src/nsHTMLScriptElement.cpp
@@ -358,16 +358,17 @@ protected:
 
 
 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Script)
 
 
 nsHTMLScriptElement::nsHTMLScriptElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                                          PRUint32 aFromParser)
   : nsGenericHTMLElement(aNodeInfo)
+  , nsScriptElement(aFromParser)
 {
   mDoneAddingChildren = !aFromParser;
   AddMutationObserver(this);
 }
 
 nsHTMLScriptElement::~nsHTMLScriptElement()
 {
 }
@@ -423,17 +424,17 @@ nsHTMLScriptElement::Clone(nsINodeInfo *
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsCOMPtr<nsINode> kungFuDeathGrip = it;
   nsresult rv = CopyInnerTo(it);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The clone should be marked evaluated if we are.
-  it->mIsEvaluated = mIsEvaluated;
+  it->mAlreadyStarted = mAlreadyStarted;
   it->mLineNumber = mLineNumber;
   it->mMalformed = mMalformed;
 
   kungFuDeathGrip.swap(*aResult);
 
   return NS_OK;
 }
 
@@ -472,21 +473,20 @@ nsHTMLScriptElement::SetInnerHTML(const 
   return nsContentUtils::SetNodeTextContent(this, aInnerHTML, PR_TRUE);
 }
 
 nsresult
 nsHTMLScriptElement::DoneAddingChildren(PRBool aHaveNotified)
 {
   mDoneAddingChildren = PR_TRUE;
   nsresult rv = MaybeProcessScript();
-  if (!mIsEvaluated) {
-    // Need to thaw the script uri here to allow another script to cause
+  if (!mAlreadyStarted) {
+    // Need to lose parser-insertedness here to allow another script to cause
     // execution later.
-    mFrozen = PR_FALSE;
-    mUri = nsnull;
+    LoseParserInsertedness();
   }
   return rv;
 }
 
 PRBool
 nsHTMLScriptElement::IsDoneAddingChildren()
 {
   return mDoneAddingChildren;
@@ -550,17 +550,17 @@ nsHTMLScriptElement::MaybeProcessScript(
 {
   nsresult rv = nsScriptElement::MaybeProcessScript();
   if (rv == NS_CONTENT_SCRIPT_IS_EVENTHANDLER) {
     // Don't return NS_CONTENT_SCRIPT_IS_EVENTHANDLER since callers can't deal
     rv = NS_OK;
 
     // We tried to evaluate the script but realized it was an eventhandler
     // mEvaluated will already be set at this point
-    NS_ASSERTION(mIsEvaluated, "should have set mIsEvaluated already");
+    NS_ASSERTION(mAlreadyStarted, "should have set mIsEvaluated already");
     NS_ASSERTION(!mScriptEventHandler, "how could we have an SEH already?");
 
     mScriptEventHandler = new nsHTMLScriptEventHandler(this);
     NS_ENSURE_TRUE(mScriptEventHandler, NS_ERROR_OUT_OF_MEMORY);
 
     nsAutoString event_val;
     GetAttr(kNameSpaceID_None, nsGkAtoms::event, event_val);
     mScriptEventHandler->ParseEventString(event_val);
--- a/content/html/content/test/test_bug300691-2.html
+++ b/content/html/content/test/test_bug300691-2.html
@@ -4,24 +4,25 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=300691
 -->
 <head>
   <title>Test for Bug 300691</title>
   <script type="text/javascript" src="/MochiKit/packed.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>
+<body onload="done();">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=300691">Mozilla Bug 300691</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="text/javascript">
+  SimpleTest.waitForExplicitFinish();
   // First, setup.  We'll be toggling these variables as we go.
   var test1Ran = false;
   var test2Ran = false;
   var test3Ran = false;
   var test4Ran = false;
   var test5Ran = false;
   var test6Ran = false;
   var test7Ran = false;
@@ -107,16 +108,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   $("test8").insertBefore(document.createTextNode("test8Ran = true"),
                           $("test8").firstChild);
   is(test8Ran, true, "Should be 8!");
 
   $("test9").firstChild.data = "test9Ran = true";
   is(test9Ran, true, "Should be 9!");
 </script>
 <script type="text/javascript">
+function done() {
   is(test1Ran, true, "Should have run!");
   is(test3Ran, false, "Already executed test3 script once");
   is(test4Ran, false,
      "Should have executed whitespace-only script already");
   is(test5Ran, false,
      "Should have executed once the whitespace node was added");
   is(test6Ran, false,
      "Should have executed once the whitespace node was added 2");
@@ -125,13 +127,15 @@ https://bugzilla.mozilla.org/show_bug.cg
   is(test12Ran, true, "Setting src should execute script");
   is(test13Ran, true, "Setting src attribute should execute script");
   is(test14aRan, true, "src attribute takes precedence over inline content");
   is(test14bRan, false, "src attribute takes precedence over inline content 2");
   is(test15aRan, true,
      "src attribute load should have started before the attribute got removed");
   is(test15bRan, false,
      "src attribute still got executed, so this shouldn't have been");
+  SimpleTest.finish();
+}
 </script>
 </pre>
 </body>
 </html>
 
--- a/content/svg/content/src/nsSVGScriptElement.cpp
+++ b/content/svg/content/src/nsSVGScriptElement.cpp
@@ -130,16 +130,17 @@ NS_INTERFACE_TABLE_HEAD(nsSVGScriptEleme
 NS_INTERFACE_MAP_END_INHERITING(nsSVGScriptElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGScriptElement::nsSVGScriptElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                                        PRUint32 aFromParser)
   : nsSVGScriptElementBase(aNodeInfo)
+  , nsScriptElement(aFromParser)
 {
   mDoneAddingChildren = !aFromParser;
   AddMutationObserver(this);
 }
 
 //----------------------------------------------------------------------
 // nsIDOMNode methods
 
@@ -155,17 +156,17 @@ nsSVGScriptElement::Clone(nsINodeInfo *a
   }
 
   nsCOMPtr<nsINode> kungFuDeathGrip = it;
   nsresult rv = it->Init();
   rv |= CopyInnerTo(it);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The clone should be marked evaluated if we are.
-  it->mIsEvaluated = mIsEvaluated;
+  it->mAlreadyStarted = mAlreadyStarted;
   it->mLineNumber = mLineNumber;
   it->mMalformed = mMalformed;
 
   kungFuDeathGrip.swap(*aResult);
 
   return NS_OK;
 }
 
@@ -273,21 +274,20 @@ nsSVGScriptElement::GetStringInfo()
 //----------------------------------------------------------------------
 // nsIContent methods
 
 nsresult
 nsSVGScriptElement::DoneAddingChildren(PRBool aHaveNotified)
 {
   mDoneAddingChildren = PR_TRUE;
   nsresult rv = MaybeProcessScript();
-  if (!mIsEvaluated) {
-    // Need to thaw the script uri here to allow another script to cause
+  if (!mAlreadyStarted) {
+    // Need to lose parser-insertedness here to allow another script to cause
     // execution later.
-    mFrozen = PR_FALSE;
-    mUri = nsnull;
+    LoseParserInsertedness();
   }
   return rv;
 }
 
 PRBool
 nsSVGScriptElement::IsDoneAddingChildren()
 {
   return mDoneAddingChildren;
new file mode 100644
--- /dev/null
+++ b/content/test/reftest/bug591981-1.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Script-inserted script</title>
+</head>
+<body>
+<div></div>
+<script>
+function log(text) {
+  var p = document.createElement("p");
+  p.appendChild(document.createTextNode(text));
+  document.getElementsByTagName("div")[0].appendChild(p);
+}
+
+var head = document.getElementsByTagName("head")[0];
+
+var external = document.createElement("script");
+external.src = "bug591981-script.js";
+head.insertBefore(external, head.firstChild); // what jQuery does
+
+var internal = document.createElement("script");
+var data = "log('internal')";
+try {
+  internal.text = data;
+} catch(e) {
+  internal.appendChild(document.createTextNode(data));
+}
+head.insertBefore(internal, head.firstChild); // what jQuery does
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/test/reftest/bug591981-2.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Script trying to execute parser-inserted non-executed scripts</title>
+</head>
+<body>
+<div></div>
+<script></script>
+<script></script>
+<script>
+function log(text) {
+  var p = document.createElement("p");
+  p.appendChild(document.createTextNode(text));
+  document.getElementsByTagName("div")[0].appendChild(p);
+}
+
+var head = document.getElementsByTagName("head")[0];
+
+var external = document.getElementsByTagName("script")[0];
+external.src = "bug591981-script.js";
+
+var internal = document.getElementsByTagName("script")[1];
+var data = "log('internal')";
+try {
+  internal.text = data;
+} catch(e) {
+  internal.appendChild(document.createTextNode(data));
+}
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/test/reftest/bug591981-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Script-inserted script</title>
+</head>
+<body>
+<div><p>internal</p><p>external</p></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/test/reftest/bug591981-script.js
@@ -0,0 +1,1 @@
+log("external");
--- a/content/test/reftest/reftest.list
+++ b/content/test/reftest/reftest.list
@@ -1,6 +1,8 @@
 == bug453105.html bug453105-ref.html
 == optiontext.html optiontext-ref.html
 == bug456008.xhtml bug456008-ref.html
 == bug439965.html bug439965-ref.html
 == bug427779.xml bug427779-ref.xml
 == bug559996.html bug559996-ref.html
+== bug591981-1.html bug591981-ref.html
+== bug591981-2.html bug591981-ref.html
--- a/dom/tests/mochitest/bugs/child_bug260264.html
+++ b/dom/tests/mochitest/bugs/child_bug260264.html
@@ -1,10 +1,11 @@
 <html>
   <head>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
     <script type="application/javascript" src="utils_bug260264.js"></script>
   </head>
   <body>
     <iframe id="frame"></iframe>
     <script type="application/javascript">
       document.getElementById("frame").src =
         alter_file(alter_host(location.href, "mochi.test:8888"),
                    "grandchild_bug260264.html");
--- a/dom/tests/mochitest/bugs/grandchild_bug260264.html
+++ b/dom/tests/mochitest/bugs/grandchild_bug260264.html
@@ -1,10 +1,11 @@
 <html>
   <head>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
     <script type="application/javascript" src="utils_bug260264.js"></script>
   </head>
   <body>
     <a id="nested link" href="javascript:(function(){})()">nested link</a>
     <script type="application/javascript">
       var event = location.hash.split("#").pop();
       send(document.getElementById("nested link"), event, function() {
         var popup = window.open("http://example.com"),
--- a/dom/tests/mochitest/bugs/test_bug260264.html
+++ b/dom/tests/mochitest/bugs/test_bug260264.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=260264
 -->
 <head>
   <title>Test for Bug 260264</title>
   <script type="application/javascript" src="/MochiKit/packed.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="application/javascript" src="utils_bug260264.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=260264">Mozilla Bug 260264</a>
 <p id="display">
   <a id="link" href="javascript:(function(){})()">link</a>
 </p>
--- a/dom/tests/mochitest/bugs/utils_bug260264.js
+++ b/dom/tests/mochitest/bugs/utils_bug260264.js
@@ -1,15 +1,8 @@
-(function() {
-  // For sendMouseEvent:
-  document.getElementsByTagName("head").item(0)
-    .appendChild(document.createElement("script")).src =
-      "/tests/SimpleTest/EventUtils.js";
-})();
-
 /**
  * Dispatches |handler| to |element|, as if fired in response to |event|.
  */
 function send(element, event, handler) {
   function unique_handler() { return handler.apply(this, arguments) }
   element.addEventListener(event, unique_handler, false);
   try { sendMouseEvent({ type: event }, element.id) }
   finally { element.removeEventListener(event, unique_handler, false) }