Bug 497861 - Wrong form state preservation on reparse in HTML5 parser. r=bnewman.
authorHenri Sivonen <hsivonen@iki.fi>
Fri, 06 Nov 2009 15:06:48 +0200
changeset 35590 334f57fa1adea59baff92aaec0e5c7d1544b022f
parent 35589 0c6ce6f93f19d2ad5b0e1fce81a72280596fbd1a
child 35591 a6f1a6787ae4dd157cdf942f0af277c8cd1e5e0c
push id10641
push userhsivonen@iki.fi
push dateFri, 11 Dec 2009 23:25:55 +0000
treeherdermozilla-central@ac25c2986e24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbnewman
bugs497861
milestone1.9.3a1pre
Bug 497861 - Wrong form state preservation on reparse in HTML5 parser. r=bnewman.
content/base/src/nsContentUtils.cpp
parser/html/nsHtml5Parser.cpp
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
parser/html/nsHtml5TreeOperation.cpp
parser/html/nsHtml5TreeOperation.h
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -1952,16 +1952,19 @@ nsContentUtils::GenerateStateKey(nsICont
   KeyAppendInt(partID, aKey);  // first append a partID
   // Make sure we can't possibly collide with an nsIStatefulFrame
   // special id of some sort
   KeyAppendInt(nsIStatefulFrame::eNoID, aKey);
   PRBool generatedUniqueKey = PR_FALSE;
 
   if (htmlDocument) {
     // Flush our content model so it'll be up to date
+    // If this becomes unnecessary and the following line is removed,
+    // please also remove the corresponding flush operation from
+    // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.)
     aContent->GetCurrentDoc()->FlushPendingNotifications(Flush_Content);
 
     nsContentList *htmlForms = htmlDocument->GetForms();
     nsContentList *htmlFormControls = htmlDocument->GetFormControls();
 
     NS_ENSURE_TRUE(htmlForms && htmlFormControls, NS_ERROR_OUT_OF_MEMORY);
 
     // If we have a form control and can calculate form information, use that
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -246,17 +246,17 @@ nsHtml5Parser::Parse(nsIURI* aURL, // le
 
 NS_IMETHODIMP
 nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
                      void* aKey,
                      const nsACString& aContentType, // ignored
                      PRBool aLastCall,
                      nsDTDMode aMode) // ignored
 {
-  NS_PRECONDITION(!mFragmentMode, "Document.write called in fragment mode!");
+  NS_PRECONDITION(!mExecutor->IsFragmentMode(), "Document.write called in fragment mode!");
 
   // Maintain a reference to ourselves so we don't go away
   // till we're completely done. The old parser grips itself in this method.
   nsCOMPtr<nsIParser> kungFuDeathGrip(this);
   
   // Gripping the other objects just in case, since the other old grip
   // required grips to these, too.
   nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(mStreamParser);
@@ -453,33 +453,32 @@ nsHtml5Parser::ParseFragment(const nsASt
 
   // Initialize() doesn't deal with base URI
   mExecutor->SetBaseUriFromDocument();
   mExecutor->SetParser(this);
   mExecutor->SetNodeInfoManager(target->GetOwnerDoc()->NodeInfoManager());
 
   nsIContent* weakTarget = target;
   mTreeBuilder->setFragmentContext(aContextLocalName, aContextNamespace, &weakTarget, aQuirks);
-  mFragmentMode = PR_TRUE;
+  mExecutor->EnableFragmentMode();
   
   NS_PRECONDITION(!mExecutor->HasStarted(), "Tried to start parse without initializing the parser properly.");
   mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled());
   mTokenizer->start();
   mExecutor->Start(); // Don't call WillBuildModel in fragment case
   if (!aSourceBuffer.IsEmpty()) {
     PRBool lastWasCR = PR_FALSE;
     nsHtml5UTF16Buffer buffer(aSourceBuffer.Length());
     memcpy(buffer.getBuffer(), aSourceBuffer.BeginReading(), aSourceBuffer.Length() * sizeof(PRUnichar));
     buffer.setEnd(aSourceBuffer.Length());
     while (buffer.hasMore()) {
       buffer.adjust(lastWasCR);
       lastWasCR = PR_FALSE;
       if (buffer.hasMore()) {
         lastWasCR = mTokenizer->tokenizeBuffer(&buffer);
-        mExecutor->MaybePreventExecution();
       }
     }
   }
   mTokenizer->eof();
   mTreeBuilder->StreamEnded();
   mTreeBuilder->Flush();
   mExecutor->Flush();
   mTokenizer->end();
@@ -502,34 +501,33 @@ nsHtml5Parser::CancelParsingEvents()
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void
 nsHtml5Parser::Reset()
 {
   mExecutor->Reset();
   mLastWasCR = PR_FALSE;
-  mFragmentMode = PR_FALSE;
   UnblockParser();
   mDocumentClosed = PR_FALSE;
   mStreamParser = nsnull;
   mRootContextLineNumber = 1;
   mParserInsertedScriptsBeingEvaluated = 0;
   mRootContextKey = nsnull;
   mAtomTable.Clear(); // should be already cleared in the fragment case anyway
   // Portable parser objects
   mFirstBuffer->next = nsnull;
   mFirstBuffer->setStart(0);
   mFirstBuffer->setEnd(0);
 }
 
 PRBool
 nsHtml5Parser::CanInterrupt()
 {
-  return !mFragmentMode;
+  return !mExecutor->IsFragmentMode();
 }
 
 PRBool
 nsHtml5Parser::IsInsertionPointDefined()
 {
   return !mExecutor->IsFlushing() &&
     (!mStreamParser || mParserInsertedScriptsBeingEvaluated);
 }
@@ -560,17 +558,17 @@ nsHtml5Parser::IsScriptCreated()
 }
 
 /* End nsIParser  */
 
 // not from interface
 void
 nsHtml5Parser::ParseUntilBlocked()
 {
-  NS_PRECONDITION(!mFragmentMode, "ParseUntilBlocked called in fragment mode.");
+  NS_PRECONDITION(!mExecutor->IsFragmentMode(), "ParseUntilBlocked called in fragment mode.");
 
   if (mBlocked) {
     return;
   }
 
   if (mExecutor->IsComplete()) {
     return;
   }
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -536,16 +536,24 @@ nsHtml5TreeBuilder::elementPopped(PRInt3
         aName == nsHtml5Atoms::applet) {
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     NS_ASSERTION(treeOp, "Tree op allocation failed.");
     treeOp->Init(eTreeOpDoneAddingChildren, aElement);
     return;
   }
   if (aName == nsHtml5Atoms::input ||
       aName == nsHtml5Atoms::button) {
+    if (!formPointer) {
+      // If form inputs don't belong to a form, their state preservation
+      // won't work right without an append notification flush at this 
+      // point. See bug 497861.
+      nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+      NS_ASSERTION(treeOp, "Tree op allocation failed.");
+      treeOp->Init(eTreeOpFlushPendingAppendNotifications);
+    }
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     NS_ASSERTION(treeOp, "Tree op allocation failed.");
     treeOp->Init(eTreeOpDoneCreatingElement, aElement);
     return;
   }
   if (aName == nsHtml5Atoms::base) {
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     NS_ASSERTION(treeOp, "Tree op allocation failed.");
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -70,27 +70,25 @@ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION
 NS_INTERFACE_TABLE_TAIL_INHERITING(nsContentSink)
 
 NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
 
 NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFlushTimer)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mOwnedElements)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mOwnedNonElements)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
   if (tmp->mFlushTimer) {
     tmp->mFlushTimer->Cancel();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFlushTimer)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mScriptElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedElements)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedNonElements)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
   : mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
 {
   // zeroing operator new for everything else
@@ -118,18 +116,27 @@ nsHtml5TreeOpExecutor::WillParse()
   NS_NOTREACHED("No one should call this");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 // This is called when the tree construction has ended
 NS_IMETHODIMP
 nsHtml5TreeOpExecutor::DidBuildModel(PRBool aTerminated)
 {
-  NS_PRECONDITION(mStarted && mParser, 
-                  "Bad life cycle.");
+  NS_PRECONDITION(mStarted, "Bad life cycle.");
+
+  // Break out of update batch if we are in one
+  EndDocUpdate();
+  
+  // If the above caused a call to nsIParser::Terminate(), let that call
+  // win.
+  if (!mParser) {
+    return NS_OK;
+  }
+  
   // This is comes from nsXMLContentSink
   DidBuildModelImpl(aTerminated);
   mDocument->ScriptLoader()->RemoveObserver(this);
   ScrollToRef();
   mDocument->RemoveObserver(this);
   mDocument->EndLoad();
   static_cast<nsHtml5Parser*> (mParser.get())->DropStreamParser();
   DropParserAndPerfHint();
@@ -274,52 +281,49 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet(
 
 void
 nsHtml5TreeOpExecutor::Flush()
 {
   if (!mParser) {
     mFlushTimer->Cancel();
     return;
   }
-  if (mFlushing) {
+  if (mFlushState != eNotFlushing) {
     return;
   }
   
-  mFlushing = PR_TRUE;
+  mFlushState = eInFlush;
 
   nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
   nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
 
   if (mReadingFromStage) {
     mStage.RetrieveOperations(mOpQueue);
   }
   
+  nsIContent* scriptElement = nsnull;
+  
   BeginDocUpdate();
 
   PRIntervalTime flushStart = 0;
   PRUint32 opQueueLength = mOpQueue.Length();
   if (opQueueLength > NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) { // avoid computing averages with too few ops
     flushStart = PR_IntervalNow();
   }
   mElementsSeenInThisAppendBatch.SetCapacity(opQueueLength * 2);
   // XXX alloc failure
   const nsHtml5TreeOperation* start = mOpQueue.Elements();
   const nsHtml5TreeOperation* end = start + opQueueLength;
   for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)start; iter < end; ++iter) {
     if (NS_UNLIKELY(!mParser)) {
       // The previous tree op caused a call to nsIParser::Terminate();
       break;
     }
-    iter->Perform(this);
-  }
-
-  if (NS_LIKELY(mParser)) {
-    FlushPendingAppendNotifications();
-  } else {
-    mPendingNotifications.Clear();
+    NS_ASSERTION(mFlushState == eInDocUpdate, "Tried to perform tree op outside update batch.");
+    iter->Perform(this, &scriptElement);
   }
 
 #ifdef DEBUG_hsivonen
   if (mOpQueue.Length() > sInsertionBatchMaxLength) {
     sInsertionBatchMaxLength = opQueueLength;
   }
 #endif
   mOpQueue.Clear();
@@ -333,46 +337,26 @@ nsHtml5TreeOpExecutor::Flush()
     }
 #ifdef DEBUG_hsivonen
     printf("QUEUE MAX LENGTH: %d\n", sTreeOpQueueMaxLength);
 #endif
   }
 
   EndDocUpdate();
 
-  mFlushing = PR_FALSE;
+  mFlushState = eNotFlushing;
 
   if (!mParser) {
     return;
   }
 
   ScheduleTimer();
-
-  if (!mCharsetSwitch.IsEmpty()) {
-    NS_ASSERTION(!mScriptElement, "Had a charset switch and a script");
-    NS_ASSERTION(!mCallDidBuildModel, "Had a charset switch and DidBuildModel call");
-    PerformCharsetSwitch();
-    mCharsetSwitch.Truncate();
-    if (mParser) {
-      // The charset switch was unsuccessful.
-      return (static_cast<nsHtml5Parser*> (mParser.get()))->ContinueAfterFailedCharsetSwitch();      
-    }
-  } else if (mCallDidBuildModel) {
-    mCallDidBuildModel = PR_FALSE;
-    // If we have a script element here, it must be malformed
-    #ifdef DEBUG
-    nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(mScriptElement);
-    if (sele) {
-      NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
-    }
-    #endif
-    mScriptElement = nsnull;
-    DidBuildModel(PR_FALSE);
-  } else if (mScriptElement) {
-    RunScript();
+  
+  if (scriptElement) {
+    RunScript(scriptElement); // must be tail call when mFlushState is eNotFlushing
   }
 }
 
 void
 nsHtml5TreeOpExecutor::ScheduleTimer()
 {
   mFlushTimer->Cancel();
   mFlushTimer->InitWithFuncCallback(TimerCallbackFunc, 
@@ -453,42 +437,56 @@ nsHtml5TreeOpExecutor::DocumentMode(nsHt
   htmlDocument->SetCompatibilityMode(mode);
 }
 
 /**
  * The reason why this code is here and not in the tree builder even in the 
  * main-thread case is to allow the control to return from the tokenizer 
  * before scripts run. This way, the tokenizer is not invoked re-entrantly 
  * although the parser is.
+ *
+ * The reason why this is called as a tail call when mFlushState is set to
+ * eNotFlushing is to allow re-entry to Flush() but only after the current 
+ * Flush() has cleared the op queue and is otherwise done cleaning up after 
+ * itself.
  */
 void
-nsHtml5TreeOpExecutor::RunScript()
+nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
 {
   mReadingFromStage = PR_FALSE;
-  NS_ASSERTION(mScriptElement, "No script to run");
+  NS_ASSERTION(aScriptElement, "No script to run");
+  NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
 
-  nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(mScriptElement);
+  nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
+
   if (!mParser) {
     NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
     // We got here not because of an end tag but because the tree builder
     // popped an incomplete script element on EOF. Returning here to avoid
-    // calling back into mParser anymore. mParser has been nulled out by now.
+    // calling back into mParser anymore.
     return;
   }
+  
+  if (mFragmentMode) {
+    // ending the doc update called nsIParser::Terminate or we are in the
+    // fragment mode
+    sele->PreventExecution();
+    return;
+  }
+
   sele->SetCreatorParser(mParser);
   // Notify our document that we're loading this script.
   nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
   NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document.");
   htmlDocument->ScriptLoading(sele);
-   // Copied from nsXMLContentSink
+  // Copied from nsXMLContentSink
   // Now tell the script that it's ready to go. This may execute the script
   // or return NS_ERROR_HTMLPARSER_BLOCK. Or neither if the script doesn't
   // need executing.
-  nsresult rv = mScriptElement->DoneAddingChildren(PR_TRUE);
-  mScriptElement = nsnull;
+  nsresult rv = aScriptElement->DoneAddingChildren(PR_TRUE);
   // If the act of insertion evaluated the script, we're fine.
   // Else, block the parser till the script has loaded.
   if (rv == NS_ERROR_HTMLPARSER_BLOCK) {
     mScriptElements.AppendObject(sele);
     mParser->BlockParser();
   } else {
     // This may have already happened if the script executed, but in case
     // it didn't then remove the element so that it doesn't get stuck forever.
@@ -511,95 +509,95 @@ nsHtml5TreeOpExecutor::Init(nsIDocument*
   return rv;
 }
 
 void
 nsHtml5TreeOpExecutor::Start()
 {
   NS_PRECONDITION(!mStarted, "Tried to start when already started.");
   mStarted = PR_TRUE;
-  mScriptElement = nsnull;
   ScheduleTimer();
 }
 
 void
 nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding)
 {
-  mCharsetSwitch.Assign(aEncoding);
-}
+  EndDocUpdate();
 
-void
-nsHtml5TreeOpExecutor::PerformCharsetSwitch()
-{
+  if(NS_UNLIKELY(!mParser)) {
+    // got terminate
+    return;
+  }
+  
   nsresult rv = NS_OK;
   nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
   if (!wss) {
     return;
   }
 #ifndef DONT_INFORM_WEBSHELL
   // ask the webshellservice to load the URL
   if (NS_FAILED(rv = wss->SetRendering(PR_FALSE))) {
     // do nothing and fall thru
   } else if (NS_FAILED(rv = wss->StopDocumentLoad())) {
     rv = wss->SetRendering(PR_TRUE); // turn on the rendering so at least we will see something.
-  } else if (NS_FAILED(rv = wss->ReloadDocument(mCharsetSwitch.get(), kCharsetFromMetaTag))) {
+  } else if (NS_FAILED(rv = wss->ReloadDocument(aEncoding, kCharsetFromMetaTag))) {
     rv = wss->SetRendering(PR_TRUE); // turn on the rendering so at least we will see something.
   }
   // if the charset switch was accepted, wss has called Terminate() on the
   // parser by now
 #endif
+
+  if (!mParser) {
+    // success
+    return;
+  }
+
+  (static_cast<nsHtml5Parser*> (mParser.get()))->ContinueAfterFailedCharsetSwitch();
+
+  BeginDocUpdate();
 }
 
 nsHtml5Tokenizer*
 nsHtml5TreeOpExecutor::GetTokenizer()
 {
   return (static_cast<nsHtml5Parser*> (mParser.get()))->GetTokenizer();
 }
 
 void
 nsHtml5TreeOpExecutor::Reset() {
   mHasProcessedBase = PR_FALSE;
   mReadingFromStage = PR_FALSE;
   mOpQueue.Clear();
   mStarted = PR_FALSE;
-  mScriptElement = nsnull;
-  mCallDidBuildModel = PR_FALSE;
-  mCharsetSwitch.Truncate();
-  mInDocumentUpdate = PR_FALSE;
-  mFlushing = PR_FALSE;
+  mFlushState = eNotFlushing;
+  mFragmentMode = PR_FALSE;
 }
 
 void
 nsHtml5TreeOpExecutor::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
 {
   // no-op
 }
 
 void
 nsHtml5TreeOpExecutor::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
 {
-  NS_PRECONDITION(!mFlushing, "mOpQueue modified during tree op execution.");
+  NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
   if (mOpQueue.IsEmpty()) {
     mOpQueue.SwapElements(aOpQueue);
     return;
   }
   mOpQueue.MoveElementsFrom(aOpQueue);
 }
 
 void
 nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, PRInt32 aLine)
 {
   static_cast<nsHtml5Parser*> (mParser.get())->InitializeDocWriteParserState(aState, aLine);
 }
 
-void
-nsHtml5TreeOpExecutor::StreamEnded()
-{
-  mCallDidBuildModel = PR_TRUE;
-}
-
 PRUint32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxLength = NS_HTML5_TREE_OP_EXECUTOR_DEFAULT_QUEUE_LENGTH;
 #ifdef DEBUG_hsivonen
 PRUint32 nsHtml5TreeOpExecutor::sInsertionBatchMaxLength = 0;
 PRUint32 nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
 PRUint32 nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
 PRUint32 nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
 #endif
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -58,16 +58,23 @@
 #include "nsHtml5TreeOpStage.h"
 
 class nsHtml5TreeBuilder;
 class nsHtml5Tokenizer;
 class nsHtml5StreamParser;
 
 typedef nsIContent* nsIContentPtr;
 
+enum eHtml5FlushState {
+  eNotFlushing = 0,  // not flushing
+  eInFlush = 1,      // the Flush() method is on the call stack
+  eInDocUpdate = 2,  // inside an update batch on the document
+  eNotifying = 3     // flushing pending append notifications
+};
+
 class nsHtml5TreeOpExecutor : public nsContentSink,
                               public nsIContentSink,
                               public nsAHtml5TreeOpSink
 {
   public:
     NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
@@ -100,33 +107,21 @@ class nsHtml5TreeOpExecutor : public nsC
     // / doctype operand would be remembered by the tree op executor.
     nsCOMArray<nsIContent>               mOwnedNonElements;
   
     /**
      * Whether the parser has started
      */
     PRBool                        mStarted;
 
-    /**
-     * Script to run ASAP
-     */
-    nsCOMPtr<nsIContent>          mScriptElement;
-    
     nsHtml5TreeOpStage            mStage;
 
-    PRBool                        mFlushing;
-    
-    PRBool                        mInDocumentUpdate;
+    eHtml5FlushState              mFlushState;
 
-    /**
-     * Used for deferring DidBuildModel call out of notification batch
-     */
-    PRBool                        mCallDidBuildModel;
-    
-    nsCString                     mCharsetSwitch;
+    PRBool                        mFragmentMode;
 
   public:
   
     nsHtml5TreeOpExecutor();
     virtual ~nsHtml5TreeOpExecutor();
   
     // nsIContentSink
 
@@ -215,35 +210,43 @@ class nsHtml5TreeOpExecutor : public nsC
     void SetNodeInfoManager(nsNodeInfoManager* aManager) {
       mNodeInfoManager = aManager;
     }
     
     void SetStreamParser(nsHtml5StreamParser* aStreamParser) {
       mStreamParser = aStreamParser;
     }
     
-    inline void SetScriptElement(nsIContent* aScript) {
-      mScriptElement = aScript;
-    }
-    
     void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, PRInt32 aLine);
 
     PRBool IsScriptEnabled();
 
+    void EnableFragmentMode() {
+      mFragmentMode = PR_TRUE;
+    }
+    
+    PRBool IsFragmentMode() {
+      return mFragmentMode;
+    }
+
     inline void BeginDocUpdate() {
-      NS_PRECONDITION(!mInDocumentUpdate, "Tried to double-open update.");
+      NS_PRECONDITION(mFlushState == eInFlush, "Tried to double-open update.");
       NS_PRECONDITION(mParser, "Started update without parser.");
-      mInDocumentUpdate = PR_TRUE;
+      mFlushState = eInDocUpdate;
       mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
     }
 
     inline void EndDocUpdate() {
-      if (mInDocumentUpdate) {
-        mInDocumentUpdate = PR_FALSE;
+      if (mFlushState >= eInDocUpdate) {
+        FlushPendingAppendNotifications();
+        if (NS_UNLIKELY(!mParser)) {
+          return;
+        }
         mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
+        mFlushState = eInFlush;
       }
     }
 
     void PostPendingAppendNotification(nsIContent* aParent, nsIContent* aChild) {
       PRBool newParent = PR_TRUE;
       const nsIContentPtr* first = mElementsSeenInThisAppendBatch.Elements();
       const nsIContentPtr* last = first + mElementsSeenInThisAppendBatch.Length() - 1;
       for (const nsIContentPtr* iter = last; iter >= first; --iter) {
@@ -263,28 +266,43 @@ class nsHtml5TreeOpExecutor : public nsC
         mPendingNotifications.AppendElement(aParent);
       }
 #ifdef DEBUG_hsivonen
       sAppendBatchExaminations++;
 #endif
     }
 
     void FlushPendingAppendNotifications() {
+      if (NS_UNLIKELY(mFlushState == eNotifying)) {
+        // nsIParser::Terminate() was called in response to iter->Fire below
+        // earlier in the call stack.
+        return;
+      }
+      NS_PRECONDITION(mFlushState == eInDocUpdate, "Notifications flushed outside update");
+      mFlushState = eNotifying;
       const nsHtml5PendingNotification* start = mPendingNotifications.Elements();
       const nsHtml5PendingNotification* end = start + mPendingNotifications.Length();
       for (nsHtml5PendingNotification* iter = (nsHtml5PendingNotification*)start; iter < end; ++iter) {
+        if (NS_UNLIKELY(!mParser)) {
+          // nsIParser::Terminate() was called in response to a notification
+          // this most likely means that the page is being navigated away from
+          // so just dropping the rest of the notifications on the floor
+          // instead of doing something fancy.
+          break;
+        }
         iter->Fire();
       }
       mPendingNotifications.Clear();
 #ifdef DEBUG_hsivonen
       if (mElementsSeenInThisAppendBatch.Length() > sAppendBatchMaxSize) {
         sAppendBatchMaxSize = mElementsSeenInThisAppendBatch.Length();
       }
 #endif
       mElementsSeenInThisAppendBatch.Clear();
+      mFlushState = eInDocUpdate;
     }
     
     inline PRBool HaveNotified(nsIContent* aElement) {
       NS_PRECONDITION(aElement, "HaveNotified called with null argument.");
       const nsHtml5PendingNotification* start = mPendingNotifications.Elements();
       const nsHtml5PendingNotification* end = start + mPendingNotifications.Length();
       for (;;) {
         nsIContent* parent = aElement->GetParent();
@@ -316,46 +334,29 @@ class nsHtml5TreeOpExecutor : public nsC
     void Flush();
 
     void MaybeSuspend();
 
     void Start();
 
     void NeedsCharsetSwitchTo(const char* aEncoding);
     
-    void PerformCharsetSwitch();
-    
-#ifdef DEBUG
-    PRBool HasScriptElement() {
-      return !!mScriptElement;
-    }
-#endif
-
     PRBool IsComplete() {
       return !mParser;
     }
     
     PRBool HasStarted() {
       return mStarted;
     }
     
     PRBool IsFlushing() {
-      return mFlushing;
+      return mFlushState >= eInFlush;
     }
     
-    void RunScript();
-    
-    void MaybePreventExecution() {
-      if (mScriptElement) {
-        nsCOMPtr<nsIScriptElement> script = do_QueryInterface(mScriptElement);
-        NS_ASSERTION(script, "mScriptElement didn't QI to nsIScriptElement!");
-        script->PreventExecution();
-        mScriptElement = nsnull;
-      }    
-    }
+    void RunScript(nsIContent* aScriptElement);
     
     void Reset();
     
     inline void HoldElement(nsIContent* aContent) {
       mOwnedElements.AppendObject(aContent);
     }
 
     inline void HoldNonElement(nsIContent* aContent) {
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -89,17 +89,18 @@ nsHtml5TreeOperation::~nsHtml5TreeOperat
       delete[] mOne.charPtr;
       break;
     default: // keep the compiler happy
       break;
   }
 }
 
 nsresult
-nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder)
+nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder,
+                              nsIContent** aScriptElement)
 {
   nsresult rv = NS_OK;
   switch(mOpCode) {
     case eTreeOpAppend: {
       nsIContent* node = *(mOne.node);
       nsIContent* parent = *(mTwo.node);
       aBuilder->PostPendingAppendNotification(parent, node);
       rv = parent->AppendChildTo(node, PR_FALSE);
@@ -320,29 +321,33 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
       return rv;
     }
     case eTreeOpRunScript: {
       nsIContent* node = *(mOne.node);
       nsAHtml5TreeBuilderState* snapshot = mTwo.state;
       if (snapshot) {
         aBuilder->InitializeDocWriteParserState(snapshot, mInt);
       }
-      aBuilder->SetScriptElement(node);
+      *aScriptElement = node;
       return rv;
     }
     case eTreeOpDoneAddingChildren: {
       nsIContent* node = *(mOne.node);
       node->DoneAddingChildren(aBuilder->HaveNotified(node));
       return rv;
     }
     case eTreeOpDoneCreatingElement: {
       nsIContent* node = *(mOne.node);
       node->DoneCreatingElement();
       return rv;
     }
+    case eTreeOpFlushPendingAppendNotifications: {
+      aBuilder->FlushPendingAppendNotifications();
+      return rv;
+    }
     case eTreeOpSetDocumentCharset: {
       char* str = mOne.charPtr;
       nsDependentCString dependentString(str);
       aBuilder->SetDocumentCharset(dependentString);
       return rv;
     }
     case eTreeOpNeedsCharsetSwitchTo: {
       char* str = mOne.charPtr;
@@ -375,21 +380,21 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(node);
       if (sele) {
         // Make sure to serialize this script correctly, for nice round tripping.
         sele->SetIsMalformed();
       }
       return rv;
     }
     case eTreeOpStreamEnded: {
-      aBuilder->StreamEnded();
+      aBuilder->DidBuildModel(PR_FALSE); // this causes a notifications flush anyway
       return rv;
     }
     case eTreeOpStartLayout: {
-      aBuilder->StartLayout(); // this causes a flush anyway
+      aBuilder->StartLayout(); // this causes a notification flush anyway
       return rv;
     }
     case eTreeOpDocumentMode: {
       aBuilder->DocumentMode(mOne.mode);
       return rv;
     }
     case eTreeOpSetStyleLineNumber: {
       nsIContent* node = *(mOne.node);
--- a/parser/html/nsHtml5TreeOperation.h
+++ b/parser/html/nsHtml5TreeOperation.h
@@ -61,16 +61,17 @@ enum eHtml5TreeOperation {
   eTreeOpSetFormElement,
   eTreeOpCreateTextNode,
   eTreeOpCreateComment,
   eTreeOpCreateDoctype,
   // Gecko-specific on-pop ops
   eTreeOpRunScript,
   eTreeOpDoneAddingChildren,
   eTreeOpDoneCreatingElement,
+  eTreeOpFlushPendingAppendNotifications,
   eTreeOpSetDocumentCharset,
   eTreeOpNeedsCharsetSwitchTo,
   eTreeOpUpdateStyleSheet,
   eTreeOpProcessBase,
   eTreeOpProcessMeta,
   eTreeOpProcessOfflineManifest,
   eTreeOpMarkMalformedIfScript,
   eTreeOpStreamEnded,
@@ -252,17 +253,17 @@ class nsHtml5TreeOperation {
     inline void SetSnapshot(nsAHtml5TreeBuilderState* aSnapshot, PRInt32 aLine) {
       NS_ASSERTION(IsRunScript(), 
         "Setting a snapshot for a tree operation other than eTreeOpRunScript!");
       NS_PRECONDITION(aSnapshot, "Initialized tree op with null snapshot.");
       mTwo.state = aSnapshot;
       mInt = aLine;
     }
 
-    nsresult Perform(nsHtml5TreeOpExecutor* aBuilder);
+    nsresult Perform(nsHtml5TreeOpExecutor* aBuilder, nsIContent** aScriptElement);
 
     inline already_AddRefed<nsIAtom> Reget(nsIAtom* aAtom) {
       if (!aAtom || aAtom->IsStaticAtom()) {
         return aAtom;
       }
       nsAutoString str;
       aAtom->ToString(str);
       return do_GetAtom(str);