Bug 543458 - Make the HTML5 tree op executor use nsContentSink code for deciding when to return to the event loop. r=bnewman.
authorHenri Sivonen <hsivonen@iki.fi>
Fri, 26 Feb 2010 11:18:38 +0200
changeset 39737 61fb9e7374ebf51c76b9f2b5b835d5f90173c2fc
parent 39736 e9b7e0b5821d9bbb5de369abd410b405a6edb5bb
child 39738 23edf99f47c84d350728efcb4e4716f5fd85a7ef
push idunknown
push userunknown
push dateunknown
reviewersbnewman
bugs543458
milestone1.9.3a4pre
Bug 543458 - Make the HTML5 tree op executor use nsContentSink code for deciding when to return to the event loop. r=bnewman.
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
modules/libpref/src/init/all.js
parser/html/Makefile.in
parser/html/nsHtml5AttributeName.h
parser/html/nsHtml5ElementName.h
parser/html/nsHtml5HtmlAttributes.h
parser/html/nsHtml5MetaScanner.h
parser/html/nsHtml5Module.cpp
parser/html/nsHtml5Parser.cpp
parser/html/nsHtml5Parser.h
parser/html/nsHtml5Portability.h
parser/html/nsHtml5SpeculativeLoad.cpp
parser/html/nsHtml5SpeculativeLoad.h
parser/html/nsHtml5SpeculativeLoader.cpp
parser/html/nsHtml5SpeculativeLoader.h
parser/html/nsHtml5StackNode.h
parser/html/nsHtml5StateSnapshot.h
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
parser/html/nsHtml5Tokenizer.h
parser/html/nsHtml5TreeBuilder.h
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeBuilderHSupplement.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
parser/html/nsHtml5TreeOpStage.cpp
parser/html/nsHtml5TreeOpStage.h
parser/html/nsHtml5UTF16Buffer.h
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -1723,21 +1723,23 @@ nsContentSink::WillBuildModelImpl()
 
     DoProcessLinkHeader();
   }
 }
 
 void
 nsContentSink::ContinueInterruptedParsingIfEnabled()
 {
+  // This shouldn't be called in the HTML5 case.
   if (mParser && mParser->IsParserEnabled()) {
     mParser->ContinueInterruptedParsing();
   }
 }
 
+// Overridden in the HTML5 case
 void
 nsContentSink::ContinueInterruptedParsingAsync()
 {
   nsCOMPtr<nsIRunnable> ev = new nsRunnableMethod<nsContentSink>(this,
     &nsContentSink::ContinueInterruptedParsingIfEnabled);
 
   NS_DispatchToCurrentThread(ev);
 }
--- a/content/base/src/nsContentSink.h
+++ b/content/base/src/nsContentSink.h
@@ -295,17 +295,17 @@ protected:
 
 private:
   // People shouldn't be allocating this class directly.  All subclasses should
   // be allocated using a zeroing operator new.
   void* operator new(size_t sz) CPP_THROW_NEW;  // Not to be implemented
 
 protected:
 
-  void ContinueInterruptedParsingAsync();
+  virtual void ContinueInterruptedParsingAsync();
   void ContinueInterruptedParsingIfEnabled();
 
   nsCOMPtr<nsIDocument>         mDocument;
   nsCOMPtr<nsIParser>           mParser;
   nsCOMPtr<nsIURI>              mDocumentURI;
   nsCOMPtr<nsIURI>              mDocumentBaseURI;
   nsCOMPtr<nsIDocShell>         mDocShell;
   nsRefPtr<mozilla::css::Loader> mCSSLoader;
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -2875,23 +2875,15 @@ pref("html5.offmainthread", true);
 // Time in milliseconds between the start of the network stream and the 
 // first time the flush timer fires in the off-the-main-thread HTML5 parser.
 pref("html5.flushtimer.startdelay", 200);
 // Time in milliseconds between the return to non-speculating more and the 
 // first time the flush timer fires thereafter.
 pref("html5.flushtimer.continuedelay", 150);
 // Time in milliseconds between timer firings once the timer has starting 
 // firing.
-pref("html5.flushtimer.interval", 100);
+pref("html5.flushtimer.interval", 120);
 
 // Push/Pop/Replace State prefs
 pref("browser.history.allowPushState", true);
 pref("browser.history.allowReplaceState", true);
 pref("browser.history.allowPopState", true);
 pref("browser.history.maxStateObjectSize", 655360);
-// Initial max length for number of tree ops in on flush.
-pref("html5.opqueue.initiallengthlimit", 200);
-// Maximum time in milliseconds to spend flushing the tree op queue when not forced to completion
-pref("html5.opqueue.maxtime", 100);
-// Minimun number of tree ops to flush regardless of time (takes precedence over the maxtime pref)
-pref("html5.opqueue.minlength", 100);
-// Maximum number of tree ops to flush regardless of time (takes precedence over the maxtime pref)
-pref("html5.opqueue.maxlength", 4500); // most top sites stay under this value
--- a/parser/html/Makefile.in
+++ b/parser/html/Makefile.in
@@ -69,17 +69,17 @@ CPPSRCS		= \
 		nsHtml5ReleasableElementName.cpp \
 		nsHtml5MetaScanner.cpp \
 		nsHtml5TreeOperation.cpp \
 		nsHtml5TreeOpStage.cpp \
 		nsHtml5StateSnapshot.cpp \
 		nsHtml5TreeOpExecutor.cpp \
 		nsHtml5StreamParser.cpp \
 		nsHtml5Speculation.cpp \
-		nsHtml5SpeculativeLoader.cpp \
+		nsHtml5SpeculativeLoad.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../content/base/src \
--- a/parser/html/nsHtml5AttributeName.h
+++ b/parser/html/nsHtml5AttributeName.h
@@ -42,17 +42,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
--- a/parser/html/nsHtml5ElementName.h
+++ b/parser/html/nsHtml5ElementName.h
@@ -42,17 +42,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
--- a/parser/html/nsHtml5HtmlAttributes.h
+++ b/parser/html/nsHtml5HtmlAttributes.h
@@ -43,17 +43,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
--- a/parser/html/nsHtml5MetaScanner.h
+++ b/parser/html/nsHtml5MetaScanner.h
@@ -43,17 +43,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
--- a/parser/html/nsHtml5Module.cpp
+++ b/parser/html/nsHtml5Module.cpp
@@ -67,17 +67,16 @@ nsHtml5Module::InitializeStatics()
   nsHtml5HtmlAttributes::initializeStatics();
   nsHtml5NamedCharacters::initializeStatics();
   nsHtml5Portability::initializeStatics();
   nsHtml5StackNode::initializeStatics();
   nsHtml5Tokenizer::initializeStatics();
   nsHtml5TreeBuilder::initializeStatics();
   nsHtml5UTF16Buffer::initializeStatics();
   nsHtml5StreamParser::InitializeStatics();
-  nsHtml5TreeOpExecutor::InitializeStatics();
 #ifdef DEBUG
   sNsHtml5ModuleInitialized = PR_TRUE;
 #endif
 }
 
 // static
 void
 nsHtml5Module::ReleaseStatics()
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -174,38 +174,18 @@ nsHtml5Parser::GetStreamListener(nsIStre
 {
   NS_IF_ADDREF(*aListener = mStreamParser);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHtml5Parser::ContinueInterruptedParsing()
 {
-  // If there are scripts executing, then the content sink is jumping the gun
-  // (probably due to a synchronous XMLHttpRequest) and will re-enable us
-  // later, see bug 460706.
-  if (mExecutor->IsScriptExecuting()) {
-    return NS_OK;
-  }
-  if (mExecutor->IsFlushing()) {
-    // A nested event loop dequeued the continue event and there aren't
-    // scripts executing. What's currently causing the flush is running to 
-    // completion or there will be a script later and the script will cause
-    // another continue event.
-    return NS_OK;
-  }
-  // If the stream has already finished, there's a good chance
-  // that we might start closing things down when the parser
-  // is reenabled. To make sure that we're not deleted across
-  // the reenabling process, hold a reference to ourselves.
-  nsCOMPtr<nsIParser> kungFuDeathGrip(this);
-  nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(mStreamParser);
-  nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor);
-  ParseUntilBlocked();
-  return NS_OK;
+  NS_NOTREACHED("Don't call. For interface compat only.");
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::BlockParser()
 {
   mBlocked = PR_TRUE;
 }
 
@@ -355,17 +335,16 @@ nsHtml5Parser::Parse(const nsAString& aS
     if (prevSearchBuf) {
       prevSearchBuf->next = buffer;
     } else {
       mFirstBuffer = buffer;
     }
   }
 
   if (!mBlocked) {
-    // mExecutor->WillResume();
     while (buffer->hasMore()) {
       buffer->adjust(mLastWasCR);
       mLastWasCR = PR_FALSE;
       if (buffer->hasMore()) {
 
         PRInt32 lineNumberSave;
         PRBool inRootContext = (!mStreamParser && (aKey == mRootContextKey));
         if (inRootContext) {
@@ -382,33 +361,32 @@ nsHtml5Parser::Parse(const nsAString& aS
           mRootContextLineNumber = mTokenizer->getLineNumber();
         } else {
           mTokenizer->setLineNumber(lineNumberSave);
         }
 
         if (mTreeBuilder->HasScript()) {
           // No need to flush characters, because an end tag was tokenized last
           mTreeBuilder->Flush(); // Move ops to the executor
-          mExecutor->Flush(PR_TRUE); // run the ops    
+          mExecutor->FlushDocumentWrite(); // run the ops    
         }
         if (mBlocked) {
-          // mExecutor->WillInterrupt();
           break;
         }
         // Ignore suspension requests
       }
     }
   }
 
   if (!mBlocked) { // buffer was tokenized to completion
     NS_ASSERTION(!buffer->hasMore(), "Buffer wasn't tokenized to completion?");  
     // Scripting semantics require a forced tree builder flush here
     mTreeBuilder->flushCharacters(); // Flush trailing characters
     mTreeBuilder->Flush(); // Move ops to the executor
-    mExecutor->Flush(PR_TRUE); // run the ops    
+    mExecutor->FlushDocumentWrite(); // run the ops    
   }
 
   return NS_OK;
 }
 
 /**
  * This magic value is passed to the previous method on document.close()
  */
@@ -493,17 +471,17 @@ nsHtml5Parser::ParseFragment(const nsASt
       if (buffer.hasMore()) {
         lastWasCR = mTokenizer->tokenizeBuffer(&buffer);
       }
     }
   }
   mTokenizer->eof();
   mTreeBuilder->StreamEnded();
   mTreeBuilder->Flush();
-  mExecutor->Flush(PR_TRUE);
+  mExecutor->FlushDocumentWrite();
   mTokenizer->end();
   mExecutor->DropParserAndPerfHint();
   mAtomTable.Clear();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHtml5Parser::BuildModel(void)
@@ -587,31 +565,30 @@ nsHtml5Parser::ParseUntilBlocked()
     return;
   }
 
   if (mExecutor->IsComplete()) {
     return;
   }
   NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
 
-  mExecutor->WillResume();
   for (;;) {
     if (!mFirstBuffer->hasMore()) {
       if (mFirstBuffer == mLastBuffer) {
         if (mExecutor->IsComplete()) {
           // something like cache manisfests stopped the parse in mid-flight
           return;
         }
         if (mDocumentClosed) {
           NS_ASSERTION(!mStreamParser,
                        "This should only happen with script-created parser.");
           mTokenizer->eof();
           mTreeBuilder->StreamEnded();
           mTreeBuilder->Flush();
-          mExecutor->Flush(PR_TRUE);
+          mExecutor->FlushDocumentWrite();
           mTokenizer->end();
           return;            
         } else {
           // never release the last buffer.
           NS_ASSERTION(!mLastBuffer->getStart(), 
             "Sentinel buffer had its indeces changed.");
           NS_ASSERTION(!mLastBuffer->getEnd(), 
             "Sentinel buffer had its indeces changed.");
@@ -646,20 +623,19 @@ nsHtml5Parser::ParseUntilBlocked()
         mTokenizer->setLineNumber(mRootContextLineNumber);
       }
       mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
       if (inRootContext) {
         mRootContextLineNumber = mTokenizer->getLineNumber();
       }
       if (mTreeBuilder->HasScript()) {
         mTreeBuilder->Flush();
-        mExecutor->Flush(PR_TRUE);
+        mExecutor->FlushDocumentWrite();
       }
       if (mBlocked) {
-        // mExecutor->WillInterrupt();
         return;
       }
     }
     continue;
   }
 }
 
 nsresult
--- a/parser/html/nsHtml5Parser.h
+++ b/parser/html/nsHtml5Parser.h
@@ -138,33 +138,32 @@ class nsHtml5Parser : public nsIParser,
     NS_IMETHOD GetDTD(nsIDTD** aDTD);
 
     /**
      * Get the stream parser for this parser
      */
     NS_IMETHOD GetStreamListener(nsIStreamListener** aListener);
 
     /**
-     * If scripts are not executing, maybe flushes tree builder and parses
-     * until suspension.
+     * Don't call. For interface compat only.
      */
-    NS_IMETHOD        ContinueInterruptedParsing();
+    NS_IMETHOD ContinueInterruptedParsing();
 
     /**
      * Blocks the parser.
      */
     NS_IMETHOD_(void) BlockParser();
 
     /**
      * Unblocks the parser.
      */
     NS_IMETHOD_(void) UnblockParser();
 
     /**
-     * Query whether the parser is enabled or not.
+     * Query whether the parser is enabled (i.e. not blocked) or not.
      */
     NS_IMETHOD_(PRBool) IsParserEnabled();
 
     /**
      * Query whether the parser thinks it's done with parsing.
      */
     NS_IMETHOD_(PRBool) IsComplete();
 
@@ -307,29 +306,27 @@ class nsHtml5Parser : public nsIParser,
     void DropStreamParser() {
       mStreamParser = nsnull;
     }
     
     void StartTokenizer(PRBool aScriptingEnabled);
     
     void ContinueAfterFailedCharsetSwitch();
 
-#ifdef DEBUG
-    PRBool HasStreamParser() {
-      return !!mStreamParser;
+    nsHtml5StreamParser* GetStreamParser() {
+      return mStreamParser;
     }
-#endif
-
-  private:
 
     /**
      * Parse until pending data is exhausted or a script blocks the parser
      */
     void ParseUntilBlocked();
 
+  private:
+
     // State variables
 
     /**
      * The charset source. This variable is used for script-created parsers
      * only. When parsing from the stream, this variable can have a bogus 
      * value.
      */
     PRInt32                       mCharsetSource;
--- a/parser/html/nsHtml5Portability.h
+++ b/parser/html/nsHtml5Portability.h
@@ -42,17 +42,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
new file mode 100644
--- /dev/null
+++ b/parser/html/nsHtml5SpeculativeLoad.cpp
@@ -0,0 +1,76 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsHtml5SpeculativeLoad.h"
+#include "nsHtml5TreeOpExecutor.h"
+
+nsHtml5SpeculativeLoad::nsHtml5SpeculativeLoad()
+#ifdef DEBUG
+ : mOpCode(eSpeculativeLoadUninitialized)
+#endif
+{
+  MOZ_COUNT_CTOR(nsHtml5SpeculativeLoad);
+}
+
+nsHtml5SpeculativeLoad::~nsHtml5SpeculativeLoad()
+{
+  MOZ_COUNT_DTOR(nsHtml5SpeculativeLoad);
+  NS_ASSERTION(mOpCode != eSpeculativeLoadUninitialized,
+               "Uninitialized speculative load.");
+}
+
+void
+nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
+{
+  switch (mOpCode) {
+    case eSpeculativeLoadImage:
+        aExecutor->PreloadImage(mUrl);
+      break;
+    case eSpeculativeLoadScript:
+        aExecutor->PreloadScript(mUrl, mCharset, mType);
+      break;
+    case eSpeculativeLoadStyle:
+        aExecutor->PreloadStyle(mUrl, mCharset);
+      break;
+    case eSpeculativeLoadManifest:  
+        aExecutor->ProcessOfflineManifest(mUrl);
+      break;
+    default:
+      NS_NOTREACHED("Bogus speculative load.");
+      break;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/parser/html/nsHtml5SpeculativeLoad.h
@@ -0,0 +1,113 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#ifndef nsHtml5SpeculativeLoad_h_
+#define nsHtml5SpeculativeLoad_h_
+
+#include "nsString.h"
+
+class nsHtml5TreeOpExecutor;
+
+enum eHtml5SpeculativeLoad {
+#ifdef DEBUG
+  eSpeculativeLoadUninitialized,
+#endif
+  eSpeculativeLoadImage,
+  eSpeculativeLoadScript,
+  eSpeculativeLoadStyle,
+  eSpeculativeLoadManifest  
+};
+
+class nsHtml5SpeculativeLoad {
+  public:
+    nsHtml5SpeculativeLoad();
+    ~nsHtml5SpeculativeLoad();
+    
+    inline void InitImage(const nsAString& aUrl) {
+      NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+                      "Trying to reinitialize a speculative load!");
+      mOpCode = eSpeculativeLoadImage;
+      mUrl.Assign(aUrl);
+    }
+
+    inline void InitScript(const nsAString& aUrl,
+                      const nsAString& aCharset,
+                      const nsAString& aType) {
+      NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+                      "Trying to reinitialize a speculative load!");
+      mOpCode = eSpeculativeLoadScript;
+      mUrl.Assign(aUrl);
+      mCharset.Assign(aCharset);
+      mType.Assign(aType);
+    }
+    
+    inline void InitStyle(const nsAString& aUrl, const nsAString& aCharset) {
+      NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+                      "Trying to reinitialize a speculative load!");
+      mOpCode = eSpeculativeLoadStyle;
+      mUrl.Assign(aUrl);
+      mCharset.Assign(aCharset);
+    }
+
+    /**
+     * "Speculative" manifest loads aren't truly speculative--if a manifest
+     * gets loaded, we are committed to it. There can never be a <script>
+     * before the manifest, so the situation of having to undo a manifest due
+     * to document.write() never arises. The reason why a parser
+     * thread-discovered manifest gets loaded via the speculative load queue
+     * as opposed to tree operation queue is that the manifest must get
+     * processed before any actually speculative loads such as scripts. Thus,
+     * manifests seen by the parser thread have to maintain the queue order
+     * relative to true speculative loads. See bug 541079.
+     */
+    inline void InitManifest(const nsAString& aUrl) {
+      NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+                      "Trying to reinitialize a speculative load!");
+      mOpCode = eSpeculativeLoadManifest;
+      mUrl.Assign(aUrl);
+    }
+
+    void Perform(nsHtml5TreeOpExecutor* aExecutor);
+    
+  private:
+    eHtml5SpeculativeLoad mOpCode;
+    nsString mUrl;
+    nsString mCharset;
+    nsString mType;
+};
+
+#endif // nsHtml5SpeculativeLoad_h_
deleted file mode 100644
--- a/parser/html/nsHtml5SpeculativeLoader.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set sw=2 ts=2 et tw=79: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Pierre Phaneuf <pp@ludusdesign.com>
- *   Henri Sivonen <hsivonen@iki.fi>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsHtml5SpeculativeLoader.h"
-#include "nsNetUtil.h"
-#include "nsScriptLoader.h"
-#include "nsIDocument.h"
-
-nsHtml5SpeculativeLoader::
-nsHtml5SpeculativeLoader(nsHtml5TreeOpExecutor* aExecutor)
-  : mExecutor(aExecutor)
-{
-  MOZ_COUNT_CTOR(nsHtml5SpeculativeLoader);
-  mPreloadedURLs.Init(23); // Mean # of preloadable resources per page on dmoz
-}
-
-nsHtml5SpeculativeLoader::~nsHtml5SpeculativeLoader()
-{
-  MOZ_COUNT_DTOR(nsHtml5SpeculativeLoader);
-}
-
-NS_IMPL_THREADSAFE_ADDREF(nsHtml5SpeculativeLoader)
-
-NS_IMPL_THREADSAFE_RELEASE(nsHtml5SpeculativeLoader)
-
-already_AddRefed<nsIURI>
-nsHtml5SpeculativeLoader::ConvertIfNotPreloadedYet(const nsAString& aURL)
-{
-  nsIDocument* doc = mExecutor->GetDocument();
-  if (!doc) {
-    return nsnull;
-  }
-  nsIURI* base = doc->GetBaseURI();
-  const nsCString& charset = doc->GetDocumentCharacterSet();
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to create a URI");
-    return nsnull;
-  }
-  nsCAutoString spec;
-  uri->GetSpec(spec);
-  if (mPreloadedURLs.Contains(spec)) {
-    return nsnull;
-  }
-  mPreloadedURLs.Put(spec);
-  nsIURI* retURI = uri;
-  NS_ADDREF(retURI);
-  return retURI;
-}
-
-void
-nsHtml5SpeculativeLoader::PreloadScript(const nsAString& aURL,
-                                        const nsAString& aCharset,
-                                        const nsAString& aType)
-{
-  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
-  if (!uri) {
-    return;
-  }
-  nsIDocument* doc = mExecutor->GetDocument();
-  if (doc) {
-    doc->ScriptLoader()->PreloadURI(uri, aCharset, aType);
-  }
-}
-
-void
-nsHtml5SpeculativeLoader::PreloadStyle(const nsAString& aURL,
-                                       const nsAString& aCharset)
-{
-  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
-  if (!uri) {
-    return;
-  }
-  nsIDocument* doc = mExecutor->GetDocument();
-  if (doc) {
-    doc->PreloadStyle(uri, aCharset);
-  }
-}
-
-void
-nsHtml5SpeculativeLoader::PreloadImage(const nsAString& aURL)
-{
-  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
-  if (!uri) {
-    return;
-  }
-  nsIDocument* doc = mExecutor->GetDocument();
-  if (doc) {
-    doc->MaybePreLoadImage(uri);
-  }
-}
-
-void
-nsHtml5SpeculativeLoader::ProcessManifest(const nsAString& aURL)
-{
-  mExecutor->ProcessOfflineManifest(aURL);
-}
deleted file mode 100644
--- a/parser/html/nsHtml5SpeculativeLoader.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is HTML Parser C++ Translator code.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Henri Sivonen <hsivonen@iki.fi>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef nsHtml5SpeculativeLoader_h__
-#define nsHtml5SpeculativeLoader_h__
-
-#include "mozilla/Mutex.h"
-#include "nsIURI.h"
-#include "nsString.h"
-#include "nsCOMPtr.h"
-#include "nsHtml5TreeOpExecutor.h"
-#include "nsHashSets.h"
-
-class nsHtml5SpeculativeLoader
-{
-  public:
-    nsHtml5SpeculativeLoader(nsHtml5TreeOpExecutor* aExecutor);
-    ~nsHtml5SpeculativeLoader();
-
-    NS_IMETHOD_(nsrefcnt) AddRef(void);
-    NS_IMETHOD_(nsrefcnt) Release(void);
-
-    void PreloadScript(const nsAString& aURL,
-                       const nsAString& aCharset,
-                       const nsAString& aType);
-
-    void PreloadStyle(const nsAString& aURL, const nsAString& aCharset);
-
-    void PreloadImage(const nsAString& aURL);
-
-    void ProcessManifest(const nsAString& aURL);
-
-  private:
-    
-    /**
-     * Get a nsIURI for an nsString if the URL hasn't been preloaded yet.
-     */
-    already_AddRefed<nsIURI> ConvertIfNotPreloadedYet(const nsAString& aURL);
-  
-    nsAutoRefCnt   mRefCnt;
-    
-    /**
-     * The executor to use as the context for preloading.
-     */
-    nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
-    
-    /**
-     * URLs already preloaded/preloading.
-     */
-    nsCStringHashSet mPreloadedURLs;
-};
-
-#endif // nsHtml5SpeculativeLoader_h__
-
--- a/parser/html/nsHtml5StackNode.h
+++ b/parser/html/nsHtml5StackNode.h
@@ -43,17 +43,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
--- a/parser/html/nsHtml5StateSnapshot.h
+++ b/parser/html/nsHtml5StateSnapshot.h
@@ -42,17 +42,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -46,17 +46,16 @@
 #include "nsContentUtils.h"
 #include "nsHtml5Tokenizer.h"
 #include "nsIHttpChannel.h"
 #include "nsHtml5Parser.h"
 #include "nsHtml5TreeBuilder.h"
 #include "nsHtml5AtomTable.h"
 #include "nsHtml5Module.h"
 #include "nsHtml5RefPtr.h"
-#include "nsHtml5SpeculativeLoader.h"
 
 static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID);
 
 PRInt32 nsHtml5StreamParser::sTimerStartDelay = 200;
 PRInt32 nsHtml5StreamParser::sTimerContinueDelay = 150;
 PRInt32 nsHtml5StreamParser::sTimerInterval = 100;
 
 // static
@@ -87,76 +86,91 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   if (tmp->mFlushTimer) {
     tmp->mFlushTimer->Cancel();
     tmp->mFlushTimer = nsnull;
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest)
   tmp->mOwner = nsnull;
   tmp->mExecutorFlusher = nsnull;
+  tmp->mLoadFlusher = nsnull;
   tmp->mExecutor = nsnull;
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChardet)
-  tmp->mTreeBuilder->DropSpeculativeLoader();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5StreamParser)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mObserver)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRequest)
   if (tmp->mOwner) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOwner");
     cb.NoteXPCOMChild(static_cast<nsIParser*> (tmp->mOwner));
   }
   // hack: count the strongly owned edge wrapped in the runnable
   if (tmp->mExecutorFlusher) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExecutorFlusher->mExecutor");
     cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
   }
+  // hack: count the strongly owned edge wrapped in the runnable
+  if (tmp->mLoadFlusher) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mLoadFlusher->mExecutor");
+    cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
+  }
   // hack: count self if held by mChardet
   if (tmp->mChardet) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 
       "mChardet->mObserver");
     cb.NoteXPCOMChild(static_cast<nsIStreamListener*>(tmp));
   }
-  // hack: count the strongly owned edge wrapped in the speculative loader
-  if (tmp->mTreeBuilder->HasSpeculativeLoader()) {
-    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 
-      "mTreeBuilder->mSpeculativeLoader->mExecutor");
-    cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
-  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 class nsHtml5ExecutorFlusher : public nsRunnable
 {
   private:
     nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
   public:
     nsHtml5ExecutorFlusher(nsHtml5TreeOpExecutor* aExecutor)
       : mExecutor(aExecutor)
     {}
     NS_IMETHODIMP Run()
     {
-      mExecutor->Flush(PR_FALSE);
+      mExecutor->RunFlushLoop();
+      return NS_OK;
+    }
+};
+
+class nsHtml5LoadFlusher : public nsRunnable
+{
+  private:
+    nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
+  public:
+    nsHtml5LoadFlusher(nsHtml5TreeOpExecutor* aExecutor)
+      : mExecutor(aExecutor)
+    {}
+    NS_IMETHODIMP Run()
+    {
+      mExecutor->FlushSpeculativeLoads();
       return NS_OK;
     }
 };
 
 nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
                                          nsHtml5Parser* aOwner)
   : mFirstBuffer(new nsHtml5UTF16Buffer(NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE))
   , mLastBuffer(mFirstBuffer)
   , mExecutor(aExecutor)
-  , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor->GetStage(), 
-                                        new nsHtml5SpeculativeLoader(mExecutor)))
+  , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor->GetStage(),
+                                        mExecutor->GetStage()))
   , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder))
   , mTokenizerMutex("nsHtml5StreamParser mTokenizerMutex")
   , mOwner(aOwner)
   , mSpeculationMutex("nsHtml5StreamParser mSpeculationMutex")
   , mTerminatedMutex("nsHtml5StreamParser mTerminatedMutex")
   , mThread(nsHtml5Module::GetStreamParserThread())
   , mExecutorFlusher(new nsHtml5ExecutorFlusher(aExecutor))
+  , mLoadFlusher(new nsHtml5LoadFlusher(aExecutor))
   , mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   mAtomTable.Init(); // we aren't checking for OOM anyway...
   #ifdef DEBUG
     mAtomTable.SetPermittedLookupThread(mThread);
   #endif
   mTokenizer->setInterner(&mAtomTable);
@@ -750,16 +764,23 @@ nsHtml5StreamParser::ParseAvailableData(
         switch (mStreamState) {
           case STREAM_BEING_READ:
             // never release the last buffer.
             if (!mSpeculating) {
               // reuse buffer space if not speculating
               mFirstBuffer->setStart(0);
               mFirstBuffer->setEnd(0);
             }
+            mTreeBuilder->FlushLoads();
+            // Dispatch this runnable unconditionally, because the loads
+            // that need flushing may have been flushed earlier even if the
+            // flush right above here did nothing.
+            if (NS_FAILED(NS_DispatchToMainThread(mLoadFlusher))) {
+              NS_WARNING("failed to dispatch load flush event");
+            }
             return; // no more data for now but expecting more
           case STREAM_ENDED:
             if (mAtEOF) {
                 return;
             }
             mAtEOF = PR_TRUE;
             mTokenizer->eof();
             mTreeBuilder->StreamEnded();
@@ -854,19 +875,22 @@ nsHtml5StreamParser::ContinueAfterScript
       Interrupt(); // Make the parser thread release the tokenizer mutex sooner
       // now fall out of the speculationAutoLock into the tokenizerAutoLock block
     } else {
       // We've got a successful speculation!
       if (mSpeculations.Length() > 1) {
         // the first speculation isn't the current speculation, so there's 
         // no need to bother the parser thread.
         speculation->FlushToSink(mExecutor);
-        if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
-          NS_WARNING("failed to dispatch executor flush event");
-        }
+        NS_ASSERTION(!mExecutor->IsScriptExecuting(),
+          "ParseUntilBlocked() was supposed to ensure we don't come "
+          "here when scripts are executing.");
+        NS_ASSERTION(mExecutor->IsInFlushLoop(), "How are we here if "
+          "RunFlushLoop() didn't call ParseUntilBlocked() which is the "
+          "only caller of this method?");
         mSpeculations.RemoveElementAt(0);
         return;
       }
       // else
       Interrupt(); // Make the parser thread release the tokenizer mutex sooner
       
       // now fall through
       // the first speculation is the current speculation. Need to 
@@ -915,19 +939,22 @@ nsHtml5StreamParser::ContinueAfterScript
       // Copy state over
       mLastWasCR = aLastWasCR;
       mTokenizer->loadState(aTokenizer);
       mTreeBuilder->loadState(aTreeBuilder, &mAtomTable);
     } else {    
       // We've got a successful speculation and at least a moment ago it was
       // the current speculation
       mSpeculations.ElementAt(0)->FlushToSink(mExecutor);
-      if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
-        NS_WARNING("failed to dispatch executor flush event");
-      }
+      NS_ASSERTION(!mExecutor->IsScriptExecuting(),
+        "ParseUntilBlocked() was supposed to ensure we don't come "
+        "here when scripts are executing.");
+      NS_ASSERTION(mExecutor->IsInFlushLoop(), "How are we here if "
+        "RunFlushLoop() didn't call ParseUntilBlocked() which is the "
+        "only caller of this method?");
       mSpeculations.RemoveElementAt(0);
       if (mSpeculations.IsEmpty()) {
         // yes, it was still the only speculation. Now stop speculating
         mTreeBuilder->SetOpSink(mExecutor->GetStage());
         mExecutor->StartReadingFromStage();
         mSpeculating = PR_FALSE;
         mFlushTimer->Cancel(); // just in case
         mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback, 
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -448,16 +448,18 @@ class nsHtml5StreamParser : public nsISt
     
     /**
      * The thread this stream parser runs on.
      */
     nsCOMPtr<nsIThread>           mThread;
     
     nsCOMPtr<nsIRunnable>         mExecutorFlusher;
     
+    nsCOMPtr<nsIRunnable>         mLoadFlusher;
+
     /**
      * The chardet instance if chardet is enabled.
      */
     nsCOMPtr<nsICharsetDetector>  mChardet;
 
     /**
      * Timer for flushing tree ops once in a while when not speculating.
      */
--- a/parser/html/nsHtml5Tokenizer.h
+++ b/parser/html/nsHtml5Tokenizer.h
@@ -45,17 +45,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
--- a/parser/html/nsHtml5TreeBuilder.h
+++ b/parser/html/nsHtml5TreeBuilder.h
@@ -51,17 +51,16 @@
 #include "nsHtml5PendingNotification.h"
 #include "nsHtml5StateSnapshot.h"
 #include "nsHtml5StackNode.h"
 #include "nsHtml5TreeOpExecutor.h"
 #include "nsHtml5StreamParser.h"
 #include "nsAHtml5TreeBuilderState.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5UTF16Buffer;
 class nsHtml5StateSnapshot;
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -41,127 +41,45 @@
 #include "nsContentErrors.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsEvent.h"
 #include "nsGUIEvent.h"
 #include "nsEventDispatcher.h"
 #include "nsContentUtils.h"
 #include "nsNodeUtils.h"
-#include "nsHtml5SpeculativeLoader.h"
 
 // this really should be autogenerated...
 jArray<PRUnichar,PRInt32> nsHtml5TreeBuilder::ISINDEX_PROMPT = jArray<PRUnichar,PRInt32>();
 nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink,
-                                       nsHtml5SpeculativeLoader* aSpeculativeLoader)
+                                       nsHtml5TreeOpStage* aStage)
   : scriptingEnabled(PR_FALSE)
   , fragment(PR_FALSE)
   , contextNode(nsnull)
   , formPointer(nsnull)
   , headPointer(nsnull)
   , mOpSink(aOpSink)
   , mHandles(new nsIContent*[NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH])
   , mHandlesUsed(0)
-  , mSpeculativeLoader(aSpeculativeLoader)
+  , mSpeculativeLoadStage(aStage)
   , mCurrentHtmlScriptIsAsyncOrDefer(PR_FALSE)
 #ifdef DEBUG
   , mActive(PR_FALSE)
 #endif
 {
   MOZ_COUNT_CTOR(nsHtml5TreeBuilder);
 }
 
 nsHtml5TreeBuilder::~nsHtml5TreeBuilder()
 {
   MOZ_COUNT_DTOR(nsHtml5TreeBuilder);
   NS_ASSERTION(!mActive, "nsHtml5TreeBuilder deleted without ever calling end() on it!");
   mOpQueue.Clear();
 }
 
-class nsHtml5SpeculativeScript : public nsRunnable
-{
-private:
-  nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
-  nsString                           mURL;
-  nsString                           mCharset;
-  nsString                           mType;
-public:
-  nsHtml5SpeculativeScript(nsHtml5SpeculativeLoader* aSpeculativeLoader,
-                           const nsAString& aURL,
-                           const nsAString& aCharset,
-                           const nsAString& aType)
-    : mSpeculativeLoader(aSpeculativeLoader)
-    , mURL(aURL)
-    , mCharset(aCharset)
-    , mType(aType)
-  {}
-  NS_IMETHODIMP Run()
-  {
-    mSpeculativeLoader->PreloadScript(mURL, mCharset, mType);
-    return NS_OK;
-  }
-};
-
-class nsHtml5SpeculativeStyle : public nsRunnable
-{
-private:
-  nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
-  nsString                           mURL;
-  nsString                           mCharset;
-public:
-  nsHtml5SpeculativeStyle(nsHtml5SpeculativeLoader* aSpeculativeLoader,
-                          const nsAString& aURL,
-                          const nsAString& aCharset)
-    : mSpeculativeLoader(aSpeculativeLoader)
-    , mURL(aURL)
-    , mCharset(aCharset)
-  {}
-  NS_IMETHODIMP Run()
-  {
-    mSpeculativeLoader->PreloadStyle(mURL, mCharset);
-    return NS_OK;
-  }
-};
-
-class nsHtml5SpeculativeImage : public nsRunnable
-{
-private:
-  nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
-  nsString                           mURL;
-public:
-  nsHtml5SpeculativeImage(nsHtml5SpeculativeLoader* aSpeculativeLoader,
-                          const nsAString& aURL)
-    : mSpeculativeLoader(aSpeculativeLoader)
-    , mURL(aURL)
-  {}
-  NS_IMETHODIMP Run()
-  {
-    mSpeculativeLoader->PreloadImage(mURL);
-    return NS_OK;
-  }
-};
-
-class nsHtml5SpeculativeManifest : public nsRunnable
-{
-private:
-  nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
-  nsString                           mURL;
-public:
-  nsHtml5SpeculativeManifest(nsHtml5SpeculativeLoader* aSpeculativeLoader,
-                          const nsAString& aURL)
-    : mSpeculativeLoader(aSpeculativeLoader)
-    , mURL(aURL)
-  {}
-  NS_IMETHODIMP Run()
-  {
-    mSpeculativeLoader->ProcessManifest(mURL);
-    return NS_OK;
-  }
-};
-
 nsIContent**
 nsHtml5TreeBuilder::createElement(PRInt32 aNamespace, nsIAtom* aName, nsHtml5HtmlAttributes* aAttributes)
 {
   NS_PRECONDITION(aAttributes, "Got null attributes.");
   NS_PRECONDITION(aName, "Got null name.");
   NS_PRECONDITION(aNamespace == kNameSpaceID_XHTML || 
                   aNamespace == kNameSpaceID_SVG || 
                   aNamespace == kNameSpaceID_MathML,
@@ -169,99 +87,94 @@ nsHtml5TreeBuilder::createElement(PRInt3
 
   nsIContent** content = AllocateContentHandle();
   nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
   NS_ASSERTION(treeOp, "Tree op allocation failed.");
   treeOp->Init(aNamespace, aName, aAttributes, content);
   
   // Start wall of code for speculative loading and line numbers
   
-  if (mSpeculativeLoader) {
+  if (mSpeculativeLoadStage) {
     switch (aNamespace) {
       case kNameSpaceID_XHTML:
         if (nsHtml5Atoms::img == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
           if (url) {
-            Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
+            mSpeculativeLoadQueue.AppendElement()->InitImage(*url);
           }
         } else if (nsHtml5Atoms::script == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
           if (url) {
             nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
             nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
-            Dispatch(new nsHtml5SpeculativeScript(mSpeculativeLoader, 
-                                                  *url,
-                                                  (charset) ? *charset : EmptyString(),
-                                                  (type) ? *type : EmptyString()));
+            mSpeculativeLoadQueue.AppendElement()->InitScript(*url,
+                                                   (charset) ? *charset : EmptyString(),
+                                                   (type) ? *type : EmptyString());
             mCurrentHtmlScriptIsAsyncOrDefer = 
               aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
               aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER);
           }
         } else if (nsHtml5Atoms::link == aName) {
           nsString* rel = aAttributes->getValue(nsHtml5AttributeName::ATTR_REL);
           // Not splitting on space here is bogus but the old parser didn't even
           // do a case-insensitive check.
           if (rel && rel->LowerCaseEqualsASCII("stylesheet")) {
             nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
             if (url) {
               nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
-              Dispatch(new nsHtml5SpeculativeStyle(mSpeculativeLoader, 
-                                                   *url,
-                                                   (charset) ? *charset : EmptyString()));
+              mSpeculativeLoadQueue.AppendElement()->InitStyle(*url,
+                                                    (charset) ? *charset : EmptyString());
             }
           }
         } else if (nsHtml5Atoms::video == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
           if (url) {
-            Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
+            mSpeculativeLoadQueue.AppendElement()->InitImage(*url);
           }
         } else if (nsHtml5Atoms::style == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
         } else if (nsHtml5Atoms::html == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_MANIFEST);
           if (url) {
-            Dispatch(new nsHtml5SpeculativeManifest(mSpeculativeLoader, *url));
+            mSpeculativeLoadQueue.AppendElement()->InitManifest(*url);
           }
         }
         break;
       case kNameSpaceID_SVG:
         if (nsHtml5Atoms::image == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
-            Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
+            mSpeculativeLoadQueue.AppendElement()->InitImage(*url);
           }
         } else if (nsHtml5Atoms::script == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
             nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
-            Dispatch(new nsHtml5SpeculativeScript(mSpeculativeLoader, 
-                                                  *url,
-                                                  EmptyString(),
-                                                  (type) ? *type : EmptyString()));
+            mSpeculativeLoadQueue.AppendElement()->InitScript(*url,
+                                                   EmptyString(),
+                                                   (type) ? *type : EmptyString());
           }
         } else if (nsHtml5Atoms::style == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
-            Dispatch(new nsHtml5SpeculativeStyle(mSpeculativeLoader, 
-                                                 *url,
-                                                 EmptyString()));
+            mSpeculativeLoadQueue.AppendElement()->InitStyle(*url, EmptyString());
           }
         }        
         break;
     }
   } else if (aNamespace != kNameSpaceID_MathML) {
     // No speculative loader--just line numbers and defer/async check
     if (nsHtml5Atoms::style == aName) {
       nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
@@ -631,24 +544,33 @@ nsHtml5TreeBuilder::HasScript()
     return PR_FALSE;
   }
   return mOpQueue.ElementAt(len - 1).IsRunScript();
 }
 
 PRBool
 nsHtml5TreeBuilder::Flush()
 {
+  FlushLoads();
   PRBool hasOps = !mOpQueue.IsEmpty();
   if (hasOps) {
     mOpSink->MoveOpsFrom(mOpQueue);
   }
   return hasOps;
 }
 
 void
+nsHtml5TreeBuilder::FlushLoads()
+{
+  if (!mSpeculativeLoadQueue.IsEmpty()) {
+    mSpeculativeLoadStage->MoveSpeculativeLoadsFrom(mSpeculativeLoadQueue);
+  }
+}
+
+void
 nsHtml5TreeBuilder::SetDocumentCharset(nsACString& aCharset, 
                                        PRInt32 aCharsetSource)
 {
   nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
   NS_ASSERTION(treeOp, "Tree op allocation failed.");
   treeOp->Init(eTreeOpSetDocumentCharset, aCharset, aCharsetSource);  
 }
 
@@ -678,21 +600,16 @@ nsHtml5TreeBuilder::NeedsCharsetSwitchTo
 void
 nsHtml5TreeBuilder::AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot, PRInt32 aLine)
 {
   NS_PRECONDITION(HasScript(), "No script to add a snapshot to!");
   NS_PRECONDITION(aSnapshot, "Got null snapshot.");
   mOpQueue.ElementAt(mOpQueue.Length() - 1).SetSnapshot(aSnapshot, aLine);
 }
 
-void
-nsHtml5TreeBuilder::DropSpeculativeLoader() {
-  mSpeculativeLoader = nsnull;
-}
-
 PRBool 
 nsHtml5TreeBuilder::IsDiscretionaryFlushSafe()
 {
   return !(charBufferLen && 
            currentPtr >= 0 && 
            stack[currentPtr]->fosterParenting);
 }
 
--- a/parser/html/nsHtml5TreeBuilderHSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderHSupplement.h
@@ -35,66 +35,57 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #define NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH 512
 
   private:
 
     nsTArray<nsHtml5TreeOperation>         mOpQueue;
+    nsTArray<nsHtml5SpeculativeLoad>       mSpeculativeLoadQueue;
     nsAHtml5TreeOpSink*                    mOpSink;
     nsAutoArrayPtr<nsIContent*>            mHandles;
     PRInt32                                mHandlesUsed;
     nsTArray<nsAutoArrayPtr<nsIContent*> > mOldHandles;
-    nsRefPtr<nsHtml5SpeculativeLoader>     mSpeculativeLoader;
+    nsHtml5TreeOpStage*                    mSpeculativeLoadStage;
     PRBool                                 mCurrentHtmlScriptIsAsyncOrDefer;
 #ifdef DEBUG
     PRBool                                 mActive;
 #endif
 
     // DocumentModeHandler
     /**
      * Tree builder uses this to report quirkiness of the document
      */
     void documentMode(nsHtml5DocumentMode m);
 
     nsIContent** AllocateContentHandle();
     
   public:
 
-    nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink, 
-                       nsHtml5SpeculativeLoader* aSpeculativeLoader);
+    nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink,
+                       nsHtml5TreeOpStage* aStage);
 
     ~nsHtml5TreeBuilder();
     
     PRBool IsDiscretionaryFlushSafe();
 
     PRBool HasScript();
     
     void SetOpSink(nsAHtml5TreeOpSink* aOpSink) {
       mOpSink = aOpSink;
     }
 
     void ClearOps() {
       mOpQueue.Clear();
     }
     
-    PRBool HasSpeculativeLoader() {
-      return !!mSpeculativeLoader;
-    }
-    
-    void DropSpeculativeLoader();
-
     PRBool Flush();
     
+    void FlushLoads();
+
     void SetDocumentCharset(nsACString& aCharset, PRInt32 aCharsetSource);
 
     void StreamEnded();
 
     void NeedsCharsetSwitchTo(const nsACString& aEncoding);
 
     void AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot, PRInt32 aLine);
-
-    inline void Dispatch(nsIRunnable* aEvent) {
-      if (NS_FAILED(NS_DispatchToMainThread(aEvent))) {
-        NS_WARNING("Failed to dispatch speculative load runnable.");
-      }
-    }
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -72,54 +72,35 @@ NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpE
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mOwnedElements)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedElements)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
-PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueLengthLimit = 200;
-PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxTime = 100; // milliseconds
-PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueMinLength = 100;
-PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxLength = 4500;
-
-// static
-void
-nsHtml5TreeOpExecutor::InitializeStatics()
+class nsHtml5ExecutorReflusher : public nsRunnable
 {
-  // Changes to the initial max length pref are intentionally allowed
-  // to reset the run-time-calibrated value.
-  nsContentUtils::AddIntPrefVarCache("html5.opqueue.initiallengthlimit", 
-                                     &sTreeOpQueueLengthLimit);
-  nsContentUtils::AddIntPrefVarCache("html5.opqueue.maxtime", 
-                                     &sTreeOpQueueMaxTime);
-  nsContentUtils::AddIntPrefVarCache("html5.opqueue.minlength",
-                                     &sTreeOpQueueMinLength);
-  nsContentUtils::AddIntPrefVarCache("html5.opqueue.maxlength",
-                                     &sTreeOpQueueMaxLength);
-  // Now do some sanity checking to prevent breaking the app via
-  // about:config so badly that it can't be recovered via about:config.
-  if (sTreeOpQueueMinLength <= 0) {
-    sTreeOpQueueMinLength = 200;
-  }
-  if (sTreeOpQueueLengthLimit < sTreeOpQueueMinLength) {
-    sTreeOpQueueLengthLimit = sTreeOpQueueMinLength;
-  }
-  if (sTreeOpQueueMaxLength < sTreeOpQueueMinLength) {
-    sTreeOpQueueMaxLength = sTreeOpQueueMinLength;
-  }
-  if (sTreeOpQueueMaxTime <= 0) {
-    sTreeOpQueueMaxTime = 200;
-  }
-}
+  private:
+    nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
+  public:
+    nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
+      : mExecutor(aExecutor)
+    {}
+    NS_IMETHODIMP Run()
+    {
+      mExecutor->RunFlushLoop();
+      return NS_OK;
+    }
+};
 
 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
 {
-  // zeroing operator new for everything
+  mPreloadedURLs.Init(23); // Mean # of preloadable resources per page on dmoz
+  // zeroing operator new for everything else
 }
 
 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
 {
   NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
 }
 
 // nsIContentSink
@@ -193,24 +174,25 @@ nsHtml5TreeOpExecutor::DidBuildModel(PRB
   }
 #endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHtml5TreeOpExecutor::WillInterrupt()
 {
-  return WillInterruptImpl();
+  NS_NOTREACHED("Don't call. For interface compat only.");
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsHtml5TreeOpExecutor::WillResume()
 {
-  WillResumeImpl();
-  return WillParseImpl();
+  NS_NOTREACHED("Don't call. For interface compat only.");
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsHtml5TreeOpExecutor::SetParser(nsIParser* aParser)
 {
   mParser = aParser;
   return NS_OK;
 }
@@ -288,16 +270,25 @@ void
 nsHtml5TreeOpExecutor::PostEvaluateScript(nsIScriptElement *aElement)
 {
   nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
   NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document.");
   htmlDocument->ScriptExecuted(aElement);
 }
 
 void
+nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
+{
+  nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);  
+  if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
+    NS_WARNING("failed to dispatch executor flush event");
+  }          
+}
+
+void
 nsHtml5TreeOpExecutor::UpdateStyleSheet(nsIContent* aElement)
 {
   // Break out of the doc update created by Flush() to zap a runnable 
   // waiting to call UpdateStyleSheet without the right observer
   EndDocUpdate();
 
   if (NS_UNLIKELY(!mParser)) {
     // EndDocUpdate ran stuff that called nsIParser::Terminate()
@@ -342,139 +333,277 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet(
       }
     }
   }
 
   // Re-open update
   BeginDocUpdate();
 }
 
-class nsHtml5ExecutorReflusher : public nsRunnable
+void
+nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
+{
+  if (NS_UNLIKELY(!mParser)) {
+    return;
+  }
+  nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+  mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
+  const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+  const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+  for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
+       iter < end; 
+       ++iter) {
+    iter->Perform(this);
+  }
+}
+
+class nsHtml5FlushLoopGuard
 {
   private:
     nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
+    #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+    PRUint32 mStartTime;
+    #endif
   public:
-    nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
+    nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
       : mExecutor(aExecutor)
-    {}
-    NS_IMETHODIMP Run()
+    #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+      , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
+    #endif
+    {
+      mExecutor->mRunFlushLoopOnStack = PR_TRUE;
+    }
+    ~nsHtml5FlushLoopGuard()
     {
-      mExecutor->Flush(PR_FALSE);
-      return NS_OK;
+      #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+        PRUint32 timeOffTheEventLoop = 
+          PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
+        if (timeOffTheEventLoop > 
+            nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
+          nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 
+            timeOffTheEventLoop;
+        }
+        printf("Longest time off the event loop: %d\n", 
+          nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
+      #endif
+
+      mExecutor->mRunFlushLoopOnStack = PR_FALSE;
     }
 };
 
+/**
+ * The purpose of the loop here is to avoid returning to the main event loop
+ */
 void
-nsHtml5TreeOpExecutor::Flush(PRBool aForceWholeQueue)
+nsHtml5TreeOpExecutor::RunFlushLoop()
+{
+  if (mRunFlushLoopOnStack) {
+    // There's already a RunFlushLoop() on the call stack.
+    return;
+  }
+  
+  nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
+  
+  nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
+
+  // Remember the entry time
+  (void) nsContentSink::WillParseImpl();
+
+  for (;;) {
+    if (!mParser) {
+      // Parse has terminated.
+      mOpQueue.Clear(); // clear in order to be able to assert in destructor
+      return;
+    }
+
+    if (!mParser->IsParserEnabled()) {
+      // The parser is blocked.
+      return;
+    }
+  
+    if (mFlushState != eNotFlushing) {
+      // XXX Can this happen? In case it can, let's avoid crashing.
+      return;
+    }
+    
+    // If there are scripts executing, then the content sink is jumping the gun
+    // (probably due to a synchronous XMLHttpRequest) and will re-enable us
+    // later, see bug 460706.
+    if (IsScriptExecuting()) {
+      return;
+    }
+
+    if (mReadingFromStage) {
+      nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+      mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
+      // Make sure speculative loads never start after the corresponding
+      // normal loads for the same URLs.
+      const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+      const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+      for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
+           iter < end;
+           ++iter) {
+        iter->Perform(this);
+      }
+    } else {
+      FlushSpeculativeLoads(); // Make sure speculative loads never start after
+                               // the corresponding normal loads for the same
+                               // URLs.
+      // Not sure if this grip is still needed, but previously, the code
+      // gripped before calling ParseUntilBlocked();
+      nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip = 
+        static_cast<nsHtml5Parser*> (mParser.get())->GetStreamParser();
+      // Now parse content left in the document.write() buffer queue if any.
+      // This may generate tree ops on its own or dequeue a speculation.
+      static_cast<nsHtml5Parser*> (mParser.get())->ParseUntilBlocked();
+    }
+
+    if (mOpQueue.IsEmpty()) {
+      // Avoid bothering the rest of the engine with a doc update if there's 
+      // nothing to do.
+      return;
+    }
+
+    mFlushState = eInFlush;
+
+    nsIContent* scriptElement = nsnull;
+    
+    BeginDocUpdate();
+
+    PRUint32 numberOfOpsToFlush = mOpQueue.Length();
+
+    mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2);
+
+    const nsHtml5TreeOperation* first = mOpQueue.Elements();
+    const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
+    for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)first;;) {
+      if (NS_UNLIKELY(!mParser)) {
+        // The previous tree op caused a call to nsIParser::Terminate().
+        break;
+      }
+      NS_ASSERTION(mFlushState == eInDocUpdate, 
+        "Tried to perform tree op outside update batch.");
+      iter->Perform(this, &scriptElement);
+
+      // Be sure not to check the deadline if the last op was just performed.
+      if (NS_UNLIKELY(iter == last)) {
+        break;
+      } else if (NS_UNLIKELY(nsContentSink::DidProcessATokenImpl() == 
+                             NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+        mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
+        
+        EndDocUpdate();
+
+        mFlushState = eNotFlushing;
+
+        #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+          printf("REFLUSH SCHEDULED (executing ops): %d\n", 
+            ++sTimesFlushLoopInterrupted);
+        #endif
+        nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+        return;
+      }
+      ++iter;
+    }
+    
+    mOpQueue.Clear();
+    
+    EndDocUpdate();
+
+    mFlushState = eNotFlushing;
+
+    if (NS_UNLIKELY(!mParser)) {
+      // The parse ended already.
+      return;
+    }
+
+    if (scriptElement) {
+      // must be tail call when mFlushState is eNotFlushing
+      RunScript(scriptElement);
+      
+      // The script execution machinery makes sure this doesn't get deflected.
+      if (nsContentSink::DidProcessATokenImpl() == 
+          NS_ERROR_HTMLPARSER_INTERRUPTED) {
+        #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+          printf("REFLUSH SCHEDULED (after script): %d\n", 
+            ++sTimesFlushLoopInterrupted);
+        #endif
+        nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+        return;      
+      }
+    }
+  }
+}
+
+void
+nsHtml5TreeOpExecutor::FlushDocumentWrite()
 {
   if (!mParser) {
+    // The parse has ended.
     mOpQueue.Clear(); // clear in order to be able to assert in destructor
     return;
   }
+  
+  FlushSpeculativeLoads(); // Make sure speculative loads never start after the 
+                // corresponding normal loads for the same URLs.
+
   if (mFlushState != eNotFlushing) {
+    // XXX Can this happen? In case it can, let's avoid crashing.
     return;
   }
-  
+
   mFlushState = eInFlush;
 
-  nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
+  // avoid crashing near EOF
+  nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
   nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
 
-  if (mReadingFromStage) {
-    mStage.MoveOpsTo(mOpQueue);
-  }
+  NS_ASSERTION(!mReadingFromStage,
+    "Got doc write flush when reading from stage");
+
+#ifdef DEBUG
+  mStage.AssertEmpty();
+#endif
   
   nsIContent* scriptElement = nsnull;
   
   BeginDocUpdate();
 
-  PRIntervalTime flushStart = 0;
   PRUint32 numberOfOpsToFlush = mOpQueue.Length();
-  PRBool reflushNeeded = PR_FALSE;
-
-#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
-  if (numberOfOpsToFlush > sOpQueueMaxLength) {
-    sOpQueueMaxLength = numberOfOpsToFlush;
-  }
-  printf("QUEUE LENGTH: %d\n", numberOfOpsToFlush);
-  printf("MAX QUEUE LENGTH: %d\n", sOpQueueMaxLength);
-#endif
-
-  if (aForceWholeQueue) {
-    if (numberOfOpsToFlush > (PRUint32)sTreeOpQueueMinLength) {
-      flushStart = PR_IntervalNow(); // compute averages only if enough ops
-    }    
-  } else {
-    if (numberOfOpsToFlush > (PRUint32)sTreeOpQueueMinLength) {
-      flushStart = PR_IntervalNow(); // compute averages only if enough ops
-      if (numberOfOpsToFlush > (PRUint32)sTreeOpQueueLengthLimit) {
-        numberOfOpsToFlush = (PRUint32)sTreeOpQueueLengthLimit;
-        reflushNeeded = PR_TRUE;
-      }
-    }
-  }
 
   mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2);
 
   const nsHtml5TreeOperation* start = mOpQueue.Elements();
   const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
-  for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)start; iter < end; ++iter) {
+  for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)start;
+       iter < end; 
+       ++iter) {
     if (NS_UNLIKELY(!mParser)) {
-      // The previous tree op caused a call to nsIParser::Terminate();
+      // The previous tree op caused a call to nsIParser::Terminate().
       break;
     }
-    NS_ASSERTION(mFlushState == eInDocUpdate, "Tried to perform tree op outside update batch.");
+    NS_ASSERTION(mFlushState == eInDocUpdate, 
+      "Tried to perform tree op outside update batch.");
     iter->Perform(this, &scriptElement);
   }
 
-  if (NS_LIKELY(mParser)) {
-    mOpQueue.RemoveElementsAt(0, numberOfOpsToFlush);  
-  } else {
-    mOpQueue.Clear(); // only for diagnostics in the destructor of this class
-  }
+  mOpQueue.Clear();
   
-  if (flushStart) {
-    PRUint32 delta = PR_IntervalToMilliseconds(PR_IntervalNow() - flushStart);
-    sTreeOpQueueLengthLimit = delta ?
-      (PRUint32)(((PRUint64)sTreeOpQueueMaxTime * (PRUint64)numberOfOpsToFlush)
-                 / delta) :
-      sTreeOpQueueMaxLength; // if the delta is less than one ms, use max
-    if (sTreeOpQueueLengthLimit < sTreeOpQueueMinLength) {
-      // both are signed and sTreeOpQueueMinLength is always positive, so this
-      // also takes care of the theoretical overflow of sTreeOpQueueLengthLimit
-      sTreeOpQueueLengthLimit = sTreeOpQueueMinLength;
-    }
-    if (sTreeOpQueueLengthLimit > sTreeOpQueueMaxLength) {
-      sTreeOpQueueLengthLimit = sTreeOpQueueMaxLength;
-    }
-#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
-    printf("FLUSH DURATION (millis): %d\n", delta);
-    printf("QUEUE NEW MAX LENGTH: %d\n", sTreeOpQueueLengthLimit);      
-#endif
-  }
-
   EndDocUpdate();
 
   mFlushState = eNotFlushing;
 
   if (NS_UNLIKELY(!mParser)) {
+    // Ending the doc update caused a call to nsIParser::Terminate().
     return;
   }
 
   if (scriptElement) {
-    NS_ASSERTION(!reflushNeeded, "Got scriptElement when queue not fully flushed.");
-    RunScript(scriptElement); // must be tail call when mFlushState is eNotFlushing
-  } else if (reflushNeeded) {
-#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
-    printf("REFLUSH SCHEDULED.\n");
-#endif
-    nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);  
-    if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
-      NS_WARNING("failed to dispatch executor flush event");
-    }
+    // must be tail call when mFlushState is eNotFlushing
+    RunScript(scriptElement);
   }
 }
 
 nsresult
 nsHtml5TreeOpExecutor::ProcessBASETag(nsIContent* aContent)
 {
   NS_ASSERTION(aContent, "missing base-element");
   if (mHasProcessedBase) {
@@ -626,32 +755,32 @@ nsHtml5TreeOpExecutor::RunScript(nsICont
   // 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.
     htmlDocument->ScriptExecuted(sele);
-    // mParser may have been nulled out by now, but nsContentSink deals
-    ContinueInterruptedParsingAsync();
+    
+    // mParser may have been nulled out by now, but the flusher deals
+
+    // If this event isn't needed, it doesn't do anything. It is sometimes
+    // necessary for the parse to continue after complex situations.
+    nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
   }
 }
 
 nsresult
 nsHtml5TreeOpExecutor::Init(nsIDocument* aDoc,
                             nsIURI* aURI,
                             nsISupports* aContainer,
                             nsIChannel* aChannel)
 {
-  nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
-  NS_ENSURE_SUCCESS(rv, rv);
-  mCanInterruptParser = PR_FALSE; // without this, nsContentSink calls
-                                  // UnblockOnload from DropParserAndPerfHint
-  return rv;
+  return nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
 }
 
 void
 nsHtml5TreeOpExecutor::Start()
 {
   NS_PRECONDITION(!mStarted, "Tried to start when already started.");
   mStarted = PR_TRUE;
 }
@@ -696,16 +825,17 @@ nsHtml5TreeOpExecutor::GetTokenizer()
 
 void
 nsHtml5TreeOpExecutor::Reset() {
   mHasProcessedBase = PR_FALSE;
   mReadingFromStage = PR_FALSE;
   mOpQueue.Clear();
   mStarted = PR_FALSE;
   mFlushState = eNotFlushing;
+  mRunFlushLoopOnStack = PR_FALSE;
   mFragmentMode = PR_FALSE;
 }
 
 void
 nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
 {
   NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
   if (mOpQueue.IsEmpty()) {
@@ -716,14 +846,72 @@ nsHtml5TreeOpExecutor::MoveOpsFrom(nsTAr
 }
 
 void
 nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, PRInt32 aLine)
 {
   static_cast<nsHtml5Parser*> (mParser.get())->InitializeDocWriteParserState(aState, aLine);
 }
 
+// Speculative loading
+
+already_AddRefed<nsIURI>
+nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
+{
+  nsIURI* base = mDocument->GetBaseURI();
+  const nsCString& charset = mDocument->GetDocumentCharacterSet();
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to create a URI");
+    return nsnull;
+  }
+  nsCAutoString spec;
+  uri->GetSpec(spec);
+  if (mPreloadedURLs.Contains(spec)) {
+    return nsnull;
+  }
+  mPreloadedURLs.Put(spec);
+  nsIURI* retURI = uri;
+  NS_ADDREF(retURI);
+  return retURI;
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
+                                     const nsAString& aCharset,
+                                     const nsAString& aType)
+{
+  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+  if (!uri) {
+    return;
+  }
+  mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
+                                    const nsAString& aCharset)
+{
+  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+  if (!uri) {
+    return;
+  }
+  mDocument->PreloadStyle(uri, aCharset);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL)
+{
+  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+  if (!uri) {
+    return;
+  }
+  mDocument->MaybePreLoadImage(uri);
+}
+
 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
-PRUint32 nsHtml5TreeOpExecutor::sOpQueueMaxLength = 0;
 PRUint32 nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
 PRUint32 nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
 PRUint32 nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
+PRUint32 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
+PRUint32 nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
 #endif
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -40,26 +40,29 @@
 
 #include "prtypes.h"
 #include "nsIAtom.h"
 #include "nsINameSpaceManager.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsTraceRefcnt.h"
 #include "nsHtml5TreeOperation.h"
+#include "nsHtml5SpeculativeLoad.h"
 #include "nsHtml5PendingNotification.h"
 #include "nsTArray.h"
 #include "nsContentSink.h"
 #include "nsNodeInfoManager.h"
 #include "nsHtml5DocumentMode.h"
 #include "nsIScriptElement.h"
 #include "nsIParser.h"
 #include "nsCOMArray.h"
 #include "nsAHtml5TreeOpSink.h"
 #include "nsHtml5TreeOpStage.h"
+#include "nsHashSets.h"
+#include "nsIURI.h"
 
 class nsHtml5TreeBuilder;
 class nsHtml5Tokenizer;
 class nsHtml5StreamParser;
 
 typedef nsIContent* nsIContentPtr;
 
 enum eHtml5FlushState {
@@ -68,57 +71,63 @@ enum eHtml5FlushState {
   eInDocUpdate = 2,  // inside an update batch on the document
   eNotifying = 3     // flushing pending append notifications
 };
 
 class nsHtml5TreeOpExecutor : public nsContentSink,
                               public nsIContentSink,
                               public nsAHtml5TreeOpSink
 {
+  friend class nsHtml5FlushLoopGuard;
+
   public:
     NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
 
-    static void InitializeStatics();
-
   private:
 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
-    static PRUint32    sOpQueueMaxLength;
     static PRUint32    sAppendBatchMaxSize;
     static PRUint32    sAppendBatchSlotsExamined;
     static PRUint32    sAppendBatchExaminations;
+    static PRUint32    sLongestTimeOffTheEventLoop;
+    static PRUint32    sTimesFlushLoopInterrupted;
 #endif
-    static PRInt32                      sTreeOpQueueLengthLimit;
-    static PRInt32                      sTreeOpQueueMaxTime;
-    static PRInt32                      sTreeOpQueueMinLength;
-    static PRInt32                      sTreeOpQueueMaxLength;
 
     /**
      * Whether EOF needs to be suppressed
      */
     PRBool                               mSuppressEOF;
     
     PRBool                               mHasProcessedBase;
     PRBool                               mReadingFromStage;
     nsTArray<nsHtml5TreeOperation>       mOpQueue;
     nsTArray<nsIContentPtr>              mElementsSeenInThisAppendBatch;
     nsTArray<nsHtml5PendingNotification> mPendingNotifications;
     nsHtml5StreamParser*                 mStreamParser;
     nsCOMArray<nsIContent>               mOwnedElements;
     
     /**
+     * URLs already preloaded/preloading.
+     */
+    nsCStringHashSet mPreloadedURLs;
+
+    /**
      * Whether the parser has started
      */
     PRBool                        mStarted;
 
     nsHtml5TreeOpStage            mStage;
 
     eHtml5FlushState              mFlushState;
 
+    PRBool                        mRunFlushLoopOnStack;
+
+    PRBool                        mCallContinueInterruptedParsingIfEnabled;
+
     PRBool                        mFragmentMode;
 
   public:
   
     nsHtml5TreeOpExecutor();
     virtual ~nsHtml5TreeOpExecutor();
   
     // nsIContentSink
@@ -178,16 +187,18 @@ class nsHtml5TreeOpExecutor : public nsC
      */
     virtual nsISupports *GetTarget();
   
     // nsContentSink methods
     virtual nsresult ProcessBASETag(nsIContent* aContent);
     virtual void UpdateChildCounts();
     virtual nsresult FlushTags();
     virtual void PostEvaluateScript(nsIScriptElement *aElement);
+    virtual void ContinueInterruptedParsingAsync();
+ 
     /**
      * Sets up style sheet load / parse
      */
     void UpdateStyleSheet(nsIContent* aElement);
 
     // Getters and setters for fields from nsContentSink
     nsIDocument* GetDocument() {
       return mDocument;
@@ -221,16 +232,18 @@ class nsHtml5TreeOpExecutor : public nsC
     }
     
     void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, PRInt32 aLine);
 
     PRBool IsScriptEnabled();
 
     void EnableFragmentMode() {
       mFragmentMode = PR_TRUE;
+      mCanInterruptParser = PR_FALSE; // prevent DropParserAndPerfHint
+                                      // from unblocking onload
     }
     
     PRBool IsFragmentMode() {
       return mFragmentMode;
     }
 
     inline void BeginDocUpdate() {
       NS_PRECONDITION(mFlushState == eInFlush, "Tried to double-open update.");
@@ -311,18 +324,22 @@ class nsHtml5TreeOpExecutor : public nsC
     }
 
     void StartLayout();
     
     void DocumentMode(nsHtml5DocumentMode m);
 
     nsresult Init(nsIDocument* aDoc, nsIURI* aURI,
                   nsISupports* aContainer, nsIChannel* aChannel);
+
+    void FlushSpeculativeLoads();
                   
-    void Flush(PRBool aForceWholeQueue);
+    void RunFlushLoop();
+
+    void FlushDocumentWrite();
 
     void MaybeSuspend();
 
     void Start();
 
     void NeedsCharsetSwitchTo(const char* aEncoding);
     
     PRBool IsComplete() {
@@ -331,48 +348,65 @@ class nsHtml5TreeOpExecutor : public nsC
     
     PRBool HasStarted() {
       return mStarted;
     }
     
     PRBool IsFlushing() {
       return mFlushState >= eInFlush;
     }
+
+#ifdef DEBUG
+    PRBool IsInFlushLoop() {
+      return mRunFlushLoopOnStack;
+    }
+#endif
     
     void RunScript(nsIContent* aScriptElement);
     
     void Reset();
     
     inline void HoldElement(nsIContent* aContent) {
       mOwnedElements.AppendObject(aContent);
     }
 
-    // The following two methods are for the main-thread case
-
     /**
      * Flush the operations from the tree operations from the argument
-     * queue unconditionally.
+     * queue unconditionally. (This is for the main thread case.)
      */
     virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
     
-    nsAHtml5TreeOpSink* GetStage() {
+    nsHtml5TreeOpStage* GetStage() {
       return &mStage;
     }
     
     void StartReadingFromStage() {
       mReadingFromStage = PR_TRUE;
     }
 
     void StreamEnded();
     
 #ifdef DEBUG
     void AssertStageEmpty() {
       mStage.AssertEmpty();
     }
 #endif
 
+    void PreloadScript(const nsAString& aURL,
+                       const nsAString& aCharset,
+                       const nsAString& aType);
+
+    void PreloadStyle(const nsAString& aURL, const nsAString& aCharset);
+
+    void PreloadImage(const nsAString& aURL);
+
   private:
 
     nsHtml5Tokenizer* GetTokenizer();
-        
+
+    /**
+     * Get a nsIURI for an nsString if the URL hasn't been preloaded yet.
+     */
+    already_AddRefed<nsIURI> ConvertIfNotPreloadedYet(const nsAString& aURL);
+
 };
 
 #endif // nsHtml5TreeOpExecutor_h__
--- a/parser/html/nsHtml5TreeOpStage.cpp
+++ b/parser/html/nsHtml5TreeOpStage.cpp
@@ -47,30 +47,58 @@ nsHtml5TreeOpStage::~nsHtml5TreeOpStage(
 }
 
 void
 nsHtml5TreeOpStage::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
 {
   mozilla::MutexAutoLock autoLock(mMutex);
   if (mOpQueue.IsEmpty()) {
     mOpQueue.SwapElements(aOpQueue);
-    return;
+  } else {
+    mOpQueue.MoveElementsFrom(aOpQueue);
   }
-  mOpQueue.MoveElementsFrom(aOpQueue);
 }
     
 void
-nsHtml5TreeOpStage::MoveOpsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+nsHtml5TreeOpStage::MoveOpsAndSpeculativeLoadsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue,
+    nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue)
 {
   mozilla::MutexAutoLock autoLock(mMutex);
   if (aOpQueue.IsEmpty()) {
     mOpQueue.SwapElements(aOpQueue);
-    return;
+  } else {
+    aOpQueue.MoveElementsFrom(mOpQueue);
+  }
+  if (aSpeculativeLoadQueue.IsEmpty()) {
+    mSpeculativeLoadQueue.SwapElements(aSpeculativeLoadQueue);
+  } else {
+    aSpeculativeLoadQueue.MoveElementsFrom(mSpeculativeLoadQueue);
   }
-  aOpQueue.MoveElementsFrom(mOpQueue);
+}
+
+void
+nsHtml5TreeOpStage::MoveSpeculativeLoadsFrom(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue)
+{
+  mozilla::MutexAutoLock autoLock(mMutex);
+  if (mSpeculativeLoadQueue.IsEmpty()) {
+    mSpeculativeLoadQueue.SwapElements(aSpeculativeLoadQueue);
+  } else {
+    mSpeculativeLoadQueue.MoveElementsFrom(aSpeculativeLoadQueue);
+  }
+}
+
+void
+nsHtml5TreeOpStage::MoveSpeculativeLoadsTo(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue)
+{
+  mozilla::MutexAutoLock autoLock(mMutex);
+  if (aSpeculativeLoadQueue.IsEmpty()) {
+    mSpeculativeLoadQueue.SwapElements(aSpeculativeLoadQueue);
+  } else {
+    aSpeculativeLoadQueue.MoveElementsFrom(mSpeculativeLoadQueue);
+  }
 }
 
 #ifdef DEBUG
 void
 nsHtml5TreeOpStage::AssertEmpty()
 {
   mozilla::MutexAutoLock autoLock(mMutex);
   // This shouldn't really need the mutex
--- a/parser/html/nsHtml5TreeOpStage.h
+++ b/parser/html/nsHtml5TreeOpStage.h
@@ -37,38 +37,51 @@
 
 #ifndef nsHtml5TreeOpStage_h___
 #define nsHtml5TreeOpStage_h___
 
 #include "mozilla/Mutex.h"
 #include "nsHtml5TreeOperation.h"
 #include "nsTArray.h"
 #include "nsAHtml5TreeOpSink.h"
+#include "nsHtml5SpeculativeLoad.h"
 
 class nsHtml5TreeOpStage : public nsAHtml5TreeOpSink {
   public:
   
     nsHtml5TreeOpStage();
     
     ~nsHtml5TreeOpStage();
   
     /**
      * Flush the operations from the tree operations from the argument
      * queue unconditionally.
      */
     virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
     
     /**
-     * Retrieve the staged operations into the argument.
+     * Retrieve the staged operations and speculative loads into the arguments.
      */
-    void MoveOpsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue);
+    void MoveOpsAndSpeculativeLoadsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue,
+        nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue);
+
+    /**
+     * Move the speculative loads from the argument into the staging queue.
+     */
+    void MoveSpeculativeLoadsFrom(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue);
+
+    /**
+     * Retrieve the staged speculative loads into the argument.
+     */
+    void MoveSpeculativeLoadsTo(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue);
 
 #ifdef DEBUG
     void AssertEmpty();
 #endif
 
   private:
     nsTArray<nsHtml5TreeOperation> mOpQueue;
-    mozilla::Mutex                 mMutex;
+    nsTArray<nsHtml5SpeculativeLoad> mSpeculativeLoadQueue;
+    mozilla::Mutex mMutex;
     
 };
 
 #endif /* nsHtml5TreeOpStage_h___ */
--- a/parser/html/nsHtml5UTF16Buffer.h
+++ b/parser/html/nsHtml5UTF16Buffer.h
@@ -42,17 +42,16 @@
 #include "nsHtml5NamedCharacters.h"
 #include "nsHtml5Atoms.h"
 #include "nsHtml5ByteReadable.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsAHtml5TreeBuilderState.h"
 #include "nsAHtml5EncodingDeclarationHandler.h"
 
 class nsHtml5StreamParser;
-class nsHtml5SpeculativeLoader;
 
 class nsHtml5Tokenizer;
 class nsHtml5TreeBuilder;
 class nsHtml5MetaScanner;
 class nsHtml5AttributeName;
 class nsHtml5ElementName;
 class nsHtml5HtmlAttributes;
 class nsHtml5StateSnapshot;