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 54236 0ab712643a66d6adabc32f6375caca032f9000a1
parent 54235 268ef4ccb5ff2fd61890d4d97a0b009d0e403223
child 54237 bc15c280c430d693e5cb77225ffc8f78e0567e0d
push idunknown
push userunknown
push dateunknown
reviewersjonas, blocking2.0-beta7
bugs591981
milestone2.0b7pre
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 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;
@@ -802,81 +831,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
@@ -1031,19 +1050,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();
 
@@ -1104,19 +1127,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;
 }
 
@@ -1151,25 +1175,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
@@ -1179,18 +1201,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) }