Bug 461555: Don't clear out the parser until all deferred scripts have executed to ensure that a document.write in a deferred script doesn't clear the page. r/sr=mrbkap
authorJonas Sicking <jonas@sicking.cc>
Wed, 14 Jan 2009 17:25:21 -0800
changeset 24229 d524c8d6a4fb17242935b456c709272148b9e6c1
parent 24228 b352dec7730e9aef354775d6f646782d3616a202
child 24230 089a09bb1969f32ad9611e18ce930ef4e7e3338d
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs461555
milestone1.9.2a1pre
Bug 461555: Don't clear out the parser until all deferred scripts have executed to ensure that a document.write in a deferred script doesn't clear the page. r/sr=mrbkap
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
content/base/src/nsDocument.cpp
content/base/src/nsScriptLoader.h
content/base/test/Makefile.in
content/base/test/test_bug28293.html
content/base/test/test_bug461555.html
content/html/document/src/nsHTMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.h
parser/htmlparser/public/nsIContentSink.h
parser/htmlparser/src/nsParser.cpp
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -344,29 +344,31 @@ nsContentSink::ScriptAvailable(nsresult 
                                PRInt32 aLineNo)
 {
   PRUint32 count = mScriptElements.Count();
   if (mParser && NS_SUCCEEDED(aResult)) {
     // Only notify the parser about scripts that are actually going to run.
     mParser->ScriptExecuting();
   }
 
-  if (count == 0) {
-    return NS_OK;
-  }
-
   // 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.
-  NS_ASSERTION(mScriptElements.IndexOf(aElement) == PRUint32(count - 1) ||
+  NS_ASSERTION(count == 0 ||
+               mScriptElements.IndexOf(aElement) == PRUint32(count - 1) ||
                mScriptElements.IndexOf(aElement) == PRUint32(-1),
                "script found at unexpected position");
 
   // Check if this is the element we were waiting for
-  if (aElement != mScriptElements[count - 1]) {
+  if (count == 0 || aElement != mScriptElements[count - 1]) {
+    if (mDidGetReadyToCallDidBuildModelCall &&
+        !mScriptLoader->HasPendingOrCurrentScripts() &&
+        mParser && mParser->IsParserEnabled()) {
+      ContinueInterruptedParsingAsync();
+    }
     return NS_OK;
   }
 
   if (mParser && !mParser->IsParserEnabled()) {
     // make sure to unblock the parser before evaluating the script,
     // we must unblock the parser even if loading the script failed or
     // if the script was empty, if we don't, the parser will never be
     // unblocked.
@@ -401,16 +403,21 @@ nsContentSink::ScriptEvaluated(nsresult 
 {
   if (mParser) {
     mParser->ScriptDidExecute();
   }
 
   // Check if this is the element we were waiting for
   PRInt32 count = mScriptElements.Count();
   if (count == 0 || aElement != mScriptElements[count - 1]) {
+    if (mDidGetReadyToCallDidBuildModelCall &&
+        !mScriptLoader->HasPendingOrCurrentScripts() &&
+        mParser && mParser->IsParserEnabled()) {
+      ContinueInterruptedParsingAsync();
+    }
     return NS_OK;
   }
 
   // Pop the script element stack
   mScriptElements.RemoveObjectAt(count - 1); 
 
   if (NS_SUCCEEDED(aResult)) {
     PostEvaluateScript(aElement);
@@ -1744,16 +1751,34 @@ void
 nsContentSink::ContinueInterruptedParsingAsync()
 {
   nsCOMPtr<nsIRunnable> ev = new nsRunnableMethod<nsContentSink>(this,
     &nsContentSink::ContinueInterruptedParsingIfEnabled);
 
   NS_DispatchToCurrentThread(ev);
 }
 
+PRBool
+nsContentSink::ReadyToCallDidBuildModelImpl()
+{
+  if (!mDidGetReadyToCallDidBuildModelCall) {
+    if (mDocument) {
+      mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);
+    }
+
+    if (mScriptLoader) {
+      mScriptLoader->EndDeferringScripts();
+    }
+  }
+
+  mDidGetReadyToCallDidBuildModelCall = PR_TRUE;
+  
+  return !mScriptLoader || !mScriptLoader->HasPendingOrCurrentScripts();
+}
+
 // URIs: action, href, src, longdesc, usemap, cite
 PRBool 
 IsAttrURI(nsIAtom *aName)
 {
   return (aName == nsGkAtoms::action ||
           aName == nsGkAtoms::href ||
           aName == nsGkAtoms::src ||
           aName == nsGkAtoms::longdesc ||
--- a/content/base/src/nsContentSink.h
+++ b/content/base/src/nsContentSink.h
@@ -133,16 +133,17 @@ class nsContentSink : public nsICSSLoade
 
   // nsIContentSink implementation helpers
   NS_HIDDEN_(nsresult) WillParseImpl(void);
   NS_HIDDEN_(nsresult) WillInterruptImpl(void);
   NS_HIDDEN_(nsresult) WillResumeImpl(void);
   NS_HIDDEN_(nsresult) DidProcessATokenImpl(void);
   NS_HIDDEN_(void) WillBuildModelImpl(void);
   NS_HIDDEN_(void) DidBuildModelImpl(void);
+  NS_HIDDEN_(PRBool) ReadyToCallDidBuildModelImpl(void);
   NS_HIDDEN_(void) DropParserAndPerfHint(void);
 
   void NotifyAppend(nsIContent* aContent, PRUint32 aStartIndex);
 
   // nsIDocumentObserver
   virtual void BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType);
   virtual void EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType);
 
@@ -345,16 +346,18 @@ protected:
   PRUint8 mDynamicLowerValue : 1;
   PRUint8 mParsing : 1;
   PRUint8 mDroppedTimer : 1;
   PRUint8 mChangeScrollPosWhenScrollingToRef : 1;
   // If true, we deferred starting layout until sheets load
   PRUint8 mDeferredLayoutStart : 1;
   // If true, we deferred notifications until sheets load
   PRUint8 mDeferredFlushTags : 1;
+  // If true, we did get a ReadyToCallDidBuildModel call
+  PRUint8 mDidGetReadyToCallDidBuildModelCall : 1;
   
   // -- Can interrupt parsing members --
   PRUint32 mDelayTimerStart;
 
   // Interrupt parsing during token procesing after # of microseconds
   PRInt32 mMaxTokenProcessingTime;
 
   // Switch between intervals when time is exceeded
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -3961,38 +3961,32 @@ nsDocument::DispatchContentLoadedEvents(
           }
         }
       }
       
       parent = parent->GetParentDocument();
     } while (parent);
   }
 
-  if (mScriptLoader) {
-    mScriptLoader->EndDeferringScripts();
-  }
-
   UnblockOnload(PR_TRUE);
 }
 
 void
 nsDocument::EndLoad()
 {
   // Drop the ref to our parser, if any, but keep hold of the sink so that we
   // can flush it from FlushPendingNotifications as needed.  We might have to
   // do that to get a StartLayout() to happen.
   if (mParser) {
     mWeakSink = do_GetWeakReference(mParser->GetContentSink());
     mParser = nsnull;
   }
   
   NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
   
-  SetReadyStateInternal(READYSTATE_INTERACTIVE);
-
   if (!mSynchronousDOMContentLoaded) {
     nsRefPtr<nsIRunnable> ev =
       new nsRunnableMethod<nsDocument>(this,
                                        &nsDocument::DispatchContentLoadedEvents);
     NS_DispatchToCurrentThread(ev);
   } else {
     DispatchContentLoadedEvents();
   }
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -204,16 +204,24 @@ public:
    * queue.
    *
    * WARNING: This function will syncronously execute content scripts, so be
    * prepared that the world might change around you.
    */
   void EndDeferringScripts();
 
   /**
+   * Returns the number of pending scripts, deferred or not.
+   */
+  PRUint32 HasPendingOrCurrentScripts()
+  {
+    return mCurrentScript || GetFirstPendingRequest();
+  }
+
+  /**
    * 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);
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -277,16 +277,17 @@ include $(topsrcdir)/config/rules.mk
 		bug455629-helper.svg \
 		test_bug473162-1.html \
 		test_bug473162-2.html \
 		test_XHRSendData.html \
 		file_XHRSendData.sjs \
 		file_XHRSendData_doc.xml \
 		file_XHRSendData_doc.xml^headers^ \
 		test_bug466751.xhtml \
+		test_bug461555.html \
 		$(NULL)
 
 # Disabled for now. Mochitest isn't reliable enough for these.
 # test_bug444546.html \
 # bug444546.sjs \
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/content/base/test/test_bug28293.html
+++ b/content/base/test/test_bug28293.html
@@ -32,17 +32,17 @@ onload = function () {
   s.textContent="res+='i';done()";
   s.defer = true;
   document.body.appendChild(s);
 
   res+='4';
 }
 
 function done() {
-  is(res, "ABCDEFGHIJ1abcdefM2g34hi", "scripts executed in the wrong order");
+  is(res, "ABCDEFGHIJabcdef1M2g34hi", "scripts executed in the wrong order");
   ok(!fHadExecuted, "Dynamic script executed too late");
   SimpleTest.finish();
 }
 </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=28293">Mozilla Bug 28293</a>
 
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug461555.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=461555
+-->
+<head>
+  <title>Test for Bug 461555</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=461555">Mozilla Bug 461555</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function writeIt(n) {
+  document.write("<span>" + n + "</span>");
+}
+
+var recur = 0;
+function externalScript() {
+  if (++recur == 3) {
+    return;
+  }
+
+  base = (recur-1) * 4
+
+  writeIt(6 + base);
+  s = document.createElement("script");
+  s.src = "data:text/plain,writeIt(" + (8+base) + ");writeIt(" + (9+base) + ");externalScript();";
+  document.body.appendChild(s);
+  writeIt(7 + base);
+}
+
+function done() {
+  nodes = document.getElementsByTagName('span');
+  is(nodes.length, 13, "wrong length");
+  for (i = 0; i < nodes.length; ++i) {
+    is(nodes[i].textContent, i+1, "wrong order");
+  }
+  SimpleTest.finish();
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+  done();
+}, false);
+
+writeIt(1);
+
+</script>
+<script defer>
+writeIt(3);
+</script>
+<script>
+writeIt(2);
+</script>
+<script defer src="data:text/plain,writeIt(4);writeIt(5);"></script>
+<script defer src="data:text/plain,externalScript();"></script>
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -180,16 +180,17 @@ public:
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIContentSink
   NS_IMETHOD WillParse(void);
   NS_IMETHOD WillBuildModel(void);
   NS_IMETHOD DidBuildModel(void);
+  virtual PRBool ReadyToCallDidBuildModel(void);
   NS_IMETHOD WillInterrupt(void);
   NS_IMETHOD WillResume(void);
   NS_IMETHOD SetParser(nsIParser* aParser);
   virtual void FlushPendingNotifications(mozFlushType aType);
   NS_IMETHOD SetDocumentCharset(nsACString& aCharset);
   virtual nsISupports *GetTarget();
 
   // nsIHTMLContentSink
@@ -1832,16 +1833,22 @@ HTMLContentSink::DidBuildModel(void)
   
   mDocument->EndLoad();
 
   DropParserAndPerfHint();
 
   return NS_OK;
 }
 
+PRBool
+HTMLContentSink::ReadyToCallDidBuildModel()
+{
+  return ReadyToCallDidBuildModelImpl();
+}
+
 NS_IMETHODIMP
 HTMLContentSink::SetParser(nsIParser* aParser)
 {
   NS_PRECONDITION(aParser, "Should have a parser here!");
   mParser = aParser;
   return NS_OK;
 }
 
--- a/content/xml/document/src/nsXMLContentSink.cpp
+++ b/content/xml/document/src/nsXMLContentSink.cpp
@@ -378,16 +378,22 @@ nsXMLContentSink::DidBuildModel()
     mDocument->EndLoad();
   }
 
   DropParserAndPerfHint();
 
   return NS_OK;
 }
 
+PRBool
+nsXMLContentSink::ReadyToCallDidBuildModel()
+{
+  return ReadyToCallDidBuildModelImpl();
+}
+
 NS_IMETHODIMP
 nsXMLContentSink::OnDocumentCreated(nsIDocument* aResultDocument)
 {
   NS_ENSURE_ARG(aResultDocument);
 
   nsCOMPtr<nsIContentViewer> contentViewer;
   mDocShell->GetContentViewer(getter_AddRefs(contentViewer));
   if (contentViewer) {
--- a/content/xml/document/src/nsXMLContentSink.h
+++ b/content/xml/document/src/nsXMLContentSink.h
@@ -88,16 +88,17 @@ public:
                                                      nsContentSink)
 
   NS_DECL_NSIEXPATSINK
 
   // nsIContentSink
   NS_IMETHOD WillParse(void);
   NS_IMETHOD WillBuildModel(void);
   NS_IMETHOD DidBuildModel(void);
+  virtual PRBool ReadyToCallDidBuildModel(void);
   NS_IMETHOD WillInterrupt(void);
   NS_IMETHOD WillResume(void);
   NS_IMETHOD SetParser(nsIParser* aParser);  
   virtual void FlushPendingNotifications(mozFlushType aType);
   NS_IMETHOD SetDocumentCharset(nsACString& aCharset);
   virtual nsISupports *GetTarget();
 
   // nsITransformObserver
--- a/parser/htmlparser/public/nsIContentSink.h
+++ b/parser/htmlparser/public/nsIContentSink.h
@@ -51,18 +51,18 @@
  */
 #include "nsISupports.h"
 #include "nsStringGlue.h"
 #include "mozFlushType.h"
 
 class nsIParser;
 
 #define NS_ICONTENT_SINK_IID \
-{ 0x94ec4df1, 0x6885, 0x4b1f, \
- { 0x85, 0x10, 0xe3, 0x5f, 0x4f, 0x36, 0xea, 0xaa } }
+{ 0xcfa3643b, 0xee60, 0x4bf0, \
+  { 0xbc, 0x83, 0x49, 0x95, 0xdb, 0xbc, 0xda, 0x75 } }
 
 class nsIContentSink : public nsISupports {
 public:
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENT_SINK_IID)
 
   /**
    * This method is called by the parser when it is entered from
@@ -84,16 +84,22 @@ public:
    * This method gets called when the parser concludes the process
    * of building the content model via the content sink.
    *
    * @update 5/7/98 gess
    */
   NS_IMETHOD DidBuildModel()=0;
 
   /**
+   * Thie method gets caller right before DidBuildModel is called.
+   * If false
+   */
+  virtual PRBool ReadyToCallDidBuildModel() { return PR_TRUE; };
+
+  /**
    * This method gets called when the parser gets i/o blocked,
    * and wants to notify the sink that it may be a while before
    * more data is available.
    *
    * @update 5/7/98 gess
    */
   NS_IMETHOD WillInterrupt(void)=0;
 
--- a/parser/htmlparser/src/nsParser.cpp
+++ b/parser/htmlparser/src/nsParser.cpp
@@ -1520,17 +1520,22 @@ nsParser::WillBuildModel(nsString& aFile
  */
 nsresult
 nsParser::DidBuildModel(nsresult anErrorCode)
 {
   nsresult result = anErrorCode;
 
   if (IsComplete()) {
     if (mParserContext && !mParserContext->mPrevContext) {
-      if (mParserContext->mDTD) {
+      // If mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING then we got in
+      // here through Terminate() and so we always want to Call DidBuildModel
+      // on the DTD, even if the sink says 'no'.
+      if (mParserContext->mDTD && mSink &&
+          (mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING ||
+           mSink->ReadyToCallDidBuildModel())) {
         result = mParserContext->mDTD->DidBuildModel(anErrorCode,PR_TRUE,this,mSink);
       }
 
       //Ref. to bug 61462.
       mParserContext->mRequest = 0;
     }
   }