Bug 482919 - Add speculative parsing to the HTML5 parser. r=bnewman.
authorHenri Sivonen <hsivonen@iki.fi>
Mon, 12 Oct 2009 16:08:04 +0300
changeset 34981 e04af661ed402d97aae6d0481a2c82bb9a8b9629
parent 34980 7cda86954b4c1cc995840a948cad3c448017b40d
child 34982 8f141d7e1cb1cc419df2f6b684a1d4946d5d837c
push idunknown
push userunknown
push dateunknown
reviewersbnewman
bugs482919
milestone1.9.3a1pre
Bug 482919 - Add speculative parsing to the HTML5 parser. r=bnewman.
parser/html/Makefile.in
parser/html/nsHtml5Parser.cpp
parser/html/nsHtml5Parser.h
parser/html/nsHtml5Speculation.cpp
parser/html/nsHtml5Speculation.h
parser/html/nsHtml5SpeculativeLoader.cpp
parser/html/nsHtml5SpeculativeLoader.h
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
parser/html/nsHtml5Tokenizer.cpp
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/nsHtml5TreeOperation.cpp
parser/html/nsHtml5TreeOperation.h
parser/html/nsHtml5UTF16BufferCppSupplement.h
parser/html/nsHtml5UTF16BufferHSupplement.h
--- a/parser/html/Makefile.in
+++ b/parser/html/Makefile.in
@@ -68,16 +68,18 @@ CPPSRCS		= \
 		nsHtml5ReleasableAttributeName.cpp \
 		nsHtml5ReleasableElementName.cpp \
 		nsHtml5MetaScanner.cpp \
 		nsHtml5TreeOperation.cpp \
 		nsHtml5TreeOpStage.cpp \
 		nsHtml5StateSnapshot.cpp \
 		nsHtml5TreeOpExecutor.cpp \
 		nsHtml5StreamParser.cpp \
+		nsHtml5Speculation.cpp \
+		nsHtml5SpeculativeLoader.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../content/base/src \
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -114,21 +114,17 @@ nsHtml5Parser::nsHtml5Parser()
   mAtomTable.Init(); // we aren't checking for OOM anyway...
   mTokenizer->setInterner(&mAtomTable);
   // There's a zeroing operator new for everything else
 }
 
 nsHtml5Parser::~nsHtml5Parser()
 {
   mTokenizer->end();
-  while (mFirstBuffer) {
-     nsHtml5UTF16Buffer* old = mFirstBuffer;
-     mFirstBuffer = mFirstBuffer->next;
-     delete old;
-  }
+  mFirstBuffer = nsnull;
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::SetContentSink(nsIContentSink* aSink)
 {
   NS_ASSERTION(aSink == static_cast<nsIContentSink*> (mExecutor), 
                "Attempt to set a foreign sink.");
 }
@@ -156,17 +152,17 @@ nsHtml5Parser::SetCommand(eParserCommand
 {
   NS_ASSERTION(aParserCommand == eViewNormal, 
                "Parser command was not eViewNormal.");
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::SetDocumentCharset(const nsACString& aCharset, PRInt32 aCharsetSource)
 {
-  NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED,
+  NS_PRECONDITION(!mExecutor->HasStarted(),
                   "Document charset set too late.");
   NS_PRECONDITION(mStreamParser, "Tried to set charset on a script-only parser.");
   mStreamParser->SetDocumentCharset(aCharset, aCharsetSource);
   mExecutor->SetDocumentCharset((nsACString&)aCharset);
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::SetParserFilter(nsIParserFilter* aFilter)
@@ -191,16 +187,20 @@ nsHtml5Parser::GetDTD(nsIDTD** aDTD)
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHtml5Parser::GetStreamListener(nsIStreamListener** aListener)
 {
   if (!mStreamParser) {
     mStreamParser = new nsHtml5StreamParser(mExecutor, this);
+    nsIDocument* doc = mExecutor->GetDocument();
+    if (doc) {
+      mStreamParser->SetSpeculativeLoaderWithDocument(doc);
+    }
   }
   NS_ADDREF(*aListener = mStreamParser);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHtml5Parser::ContinueParsing()
 {
@@ -257,20 +257,24 @@ nsHtml5Parser::Parse(nsIURI* aURL, // le
                      nsIRequestObserver* aObserver,
                      void* aKey,
                      nsDTDMode aMode) // legacy; ignored
 {
   /*
    * Do NOT cause WillBuildModel to be called synchronously from here!
    * The document won't be ready for it until OnStartRequest!
    */
-  NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED, 
+  NS_PRECONDITION(!mExecutor->HasStarted(), 
                   "Tried to start parse without initializing the parser properly.");
   if (!mStreamParser) {
     mStreamParser = new nsHtml5StreamParser(mExecutor, this);
+    nsIDocument* doc = mExecutor->GetDocument();
+    if (doc) {
+      mStreamParser->SetSpeculativeLoaderWithDocument(doc);
+    }
   }
   mStreamParser->SetObserver(aObserver);
   mExecutor->SetStreamParser(mStreamParser);
   mExecutor->SetParser(this);
   mRootContextKey = aKey;
   return NS_OK;
 }
 
@@ -288,66 +292,61 @@ nsHtml5Parser::Parse(const nsAString& aS
   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);
   nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor);
 
   // Return early if the parser has processed EOF
-  switch (mExecutor->GetLifeCycle()) {
-    case TERMINATED:
-      return NS_OK;
-    case NOT_STARTED:
-      NS_ASSERTION(!mStreamParser,
-                   "Had stream parser but document.write started life cycle.");
-      mExecutor->SetParser(this);
-      mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled());
-      mTokenizer->start();
-      mExecutor->Start();
-      /*
-       * If you move the following line, be very careful not to cause 
-       * WillBuildModel to be called before the document has had its 
-       * script global object set.
-       */
-      mExecutor->WillBuildModel(eDTDMode_unknown);
-      break;
-    default:
-      break;
+  if (!mExecutor->HasStarted()) {
+    NS_ASSERTION(!mStreamParser,
+                 "Had stream parser but document.write started life cycle.");
+    mExecutor->SetParser(this);
+    mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled());
+    mTokenizer->start();
+    mExecutor->Start();
+    /*
+     * If you move the following line, be very careful not to cause 
+     * WillBuildModel to be called before the document has had its 
+     * script global object set.
+     */
+    mExecutor->WillBuildModel(eDTDMode_unknown);
   }
-
+  if (mExecutor->IsComplete()) {
+    return NS_OK;
+  }
   if (aLastCall && aSourceBuffer.IsEmpty() && aKey == GetRootContextKey()) {
     // document.close()
       NS_ASSERTION(!mStreamParser,
                    "Had stream parser but got document.close().");
     mDocumentClosed = PR_TRUE;
     MaybePostContinueEvent();
     return NS_OK;
   }
 
   PRInt32 lineNumberSave = mTokenizer->getLineNumber();
 
   if (!aSourceBuffer.IsEmpty()) {
-    nsHtml5UTF16Buffer* buffer = new nsHtml5UTF16Buffer(aSourceBuffer.Length());
+    nsRefPtr<nsHtml5UTF16Buffer> buffer = new nsHtml5UTF16Buffer(aSourceBuffer.Length());
     memcpy(buffer->getBuffer(), aSourceBuffer.BeginReading(), aSourceBuffer.Length() * sizeof(PRUnichar));
     buffer->setEnd(aSourceBuffer.Length());
     if (!mBlocked) {
       // mExecutor->WillResume();
       while (buffer->hasMore()) {
         buffer->adjust(mLastWasCR);
         mLastWasCR = PR_FALSE;
         if (buffer->hasMore()) {
           mLastWasCR = mTokenizer->tokenizeBuffer(buffer);
           if (mTreeBuilder->HasScript()) {
-            mTreeBuilder->Flush(); // moves ops to executor queue
-            mExecutor->Flush(); // executes the queue
-            // Is mBlocked always true here?
+            mTreeBuilder->flushCharacters(); // Flush trailing characters
+            mTreeBuilder->Flush(); // Move ops to the executor
+            mExecutor->Flush(); // run the ops    
           }
           if (mBlocked) {
-            // XXX is the tail insertion and script exec in the wrong order?
             // mExecutor->WillInterrupt();
             break;
           }
           // Ignore suspension requests
         }
       }
     }
 
@@ -383,26 +382,27 @@ nsHtml5Parser::Parse(const nsAString& aS
       if (searchBuf == mLastBuffer || !aKey) {
         // key was not found or we have a first-level write after document.open
         // we'll insert to the head of the queue
         nsHtml5UTF16Buffer* keyHolder = new nsHtml5UTF16Buffer(aKey);
         keyHolder->next = mFirstBuffer;
         buffer->next = keyHolder;
         mFirstBuffer = buffer;
       }
-      MaybePostContinueEvent();
-    } else {
-      delete buffer;
+      if (!mStreamParser) {
+        MaybePostContinueEvent();
+      }
+    } else { // buffer didn't have more
+      // Scripting semantics require a forced tree builder flush here
+      mTreeBuilder->flushCharacters(); // Flush trailing characters
+      mTreeBuilder->Flush(); // Move ops to the executor
+      mExecutor->Flush(); // run the ops    
     }
   }
 
-  // Scripting semantics require a forced tree builder flush here
-  // TODO: Also flush the pending text node from tree builder
-  mTreeBuilder->Flush();
-  mExecutor->Flush();
   mTokenizer->setLineNumber(lineNumberSave);
   return NS_OK;
 }
 
 /**
  * This magic value is passed to the previous method on document.close()
  */
 NS_IMETHODIMP_(void *)
@@ -411,17 +411,17 @@ nsHtml5Parser::GetRootContextKey()
   return mRootContextKey;
 }
 
 NS_IMETHODIMP
 nsHtml5Parser::Terminate(void)
 {
   // We should only call DidBuildModel once, so don't do anything if this is
   // the second time that Terminate has been called.
-  if (mExecutor->GetLifeCycle() == TERMINATED) {
+  if (mExecutor->IsComplete()) {
     return NS_OK;
   }
   // XXX - [ until we figure out a way to break parser-sink circularity ]
   // Hack - Hold a reference until we are completely done...
   nsCOMPtr<nsIParser> kungFuDeathGrip(this);
   nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(mStreamParser);
   nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor);
   // CancelParsingEvents must be called to avoid leaking the nsParser object
@@ -469,17 +469,17 @@ nsHtml5Parser::ParseFragment(const nsASt
   mExecutor->SetBaseUriFromDocument();
   mExecutor->SetParser(this);
   mExecutor->SetNodeInfoManager(target->GetOwnerDoc()->NodeInfoManager());
 
   nsIContent* weakTarget = target;
   mTreeBuilder->setFragmentContext(aContextLocalName, aContextNamespace, &weakTarget, aQuirks);
   mFragmentMode = PR_TRUE;
   
-  NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED, "Tried to start parse without initializing the parser properly.");
+  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());
@@ -492,17 +492,16 @@ nsHtml5Parser::ParseFragment(const nsASt
       }
     }
   }
   mTokenizer->eof();
   mTreeBuilder->StreamEnded();
   mTreeBuilder->Flush();
   mExecutor->Flush();
   mTokenizer->end();
-  mExecutor->SetLifeCycle(TERMINATED);
   mExecutor->DropParserAndPerfHint();
   mAtomTable.Clear();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHtml5Parser::BuildModel(void)
 {
@@ -525,21 +524,17 @@ nsHtml5Parser::Reset()
   mFragmentMode = PR_FALSE;
   UnblockParser();
   mDocumentClosed = PR_FALSE;
   mStreamParser = nsnull;
   mRootContextKey = nsnull;
   mContinueEvent = nsnull;  // weak ref
   mAtomTable.Clear(); // should be already cleared in the fragment case anyway
   // Portable parser objects
-  while (mFirstBuffer->next) {
-    nsHtml5UTF16Buffer* oldBuf = mFirstBuffer;
-    mFirstBuffer = mFirstBuffer->next;
-    delete oldBuf;
-  }
+  mFirstBuffer->next = nsnull;
   mFirstBuffer->setStart(0);
   mFirstBuffer->setEnd(0);
 }
 
 PRBool
 nsHtml5Parser::CanInterrupt()
 {
   return !mFragmentMode;
@@ -563,68 +558,61 @@ void
 nsHtml5Parser::ParseUntilScript()
 {
   NS_PRECONDITION(!mFragmentMode, "ParseUntilScript called in fragment mode.");
 
   if (mBlocked) {
     return;
   }
 
-  switch (mExecutor->GetLifeCycle()) {
-    case TERMINATED:
-      return;
-    case NOT_STARTED:
-      NS_NOTREACHED("Bad life cycle!");
-      break;
-    default:
-      break;
+  if (mExecutor->IsComplete()) {
+    return;
   }
+  NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
 
   mExecutor->WillResume();
   for (;;) {
     if (!mFirstBuffer->hasMore()) {
       if (mFirstBuffer == mLastBuffer) {
-        switch (mExecutor->GetLifeCycle()) {
-          case TERMINATED:
-            // something like cache manisfests stopped the parse in mid-flight
-            return;
-          case PARSING:
-            if (mDocumentClosed) {
-              NS_ASSERTION(!mStreamParser,
-                           "This should only happen with script-created parser.");
-              mTokenizer->eof();
-              mTreeBuilder->StreamEnded();
-              mTreeBuilder->Flush();
-              mExecutor->Flush();
-              mTokenizer->end();
-              return;            
-            } else {
-              // never release the last buffer. instead just zero its indeces for refill
-              mFirstBuffer->setStart(0);
-              mFirstBuffer->setEnd(0);
-              if (mStreamParser) {
-                mStreamParser->ContinueAfterScripts(mTokenizer, 
-                                                    mTreeBuilder, 
-                                                    mLastWasCR);
-              }
-              return; // no more data for now but expecting more
-            }
-          default:
-            NS_NOTREACHED("It should be impossible to reach this.");
-            return;
+        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();
+          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.");
+          if (mStreamParser && 
+              mReturnToStreamParserPermitted && 
+              !mExecutor->IsScriptExecuting()) {
+            mReturnToStreamParserPermitted = PR_FALSE;
+            mStreamParser->ContinueAfterScripts(mTokenizer, 
+                                                mTreeBuilder, 
+                                                mLastWasCR);
+          }
+          return; // no more data for now but expecting more
         }
       } else {
-        nsHtml5UTF16Buffer* oldBuf = mFirstBuffer;
         mFirstBuffer = mFirstBuffer->next;
-        delete oldBuf;
         continue;
       }
     }
 
-    if (mBlocked || (mExecutor->GetLifeCycle() == TERMINATED)) {
+    if (mBlocked || mExecutor->IsComplete()) {
       return;
     }
 
     // now we have a non-empty buffer
     mFirstBuffer->adjust(mLastWasCR);
     mLastWasCR = PR_FALSE;
     if (mFirstBuffer->hasMore()) {
       mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
@@ -639,17 +627,17 @@ nsHtml5Parser::ParseUntilScript()
     }
     continue;
   }
 }
 
 void
 nsHtml5Parser::MaybePostContinueEvent()
 {
-  NS_PRECONDITION(mExecutor->GetLifeCycle() != TERMINATED, 
+  NS_PRECONDITION(!mExecutor->IsComplete(), 
                   "Tried to post continue event when the parser is done.");
   if (mContinueEvent) {
     return; // we already have a pending event
   }
   // This creates a reference cycle between this and the event that is
   // broken when the event fires.
   nsCOMPtr<nsIRunnable> event = new nsHtml5ParserContinueEvent(this);
   if (NS_FAILED(NS_DispatchToCurrentThread(event))) {
@@ -660,24 +648,28 @@ nsHtml5Parser::MaybePostContinueEvent()
 }
 
 nsresult
 nsHtml5Parser::Initialize(nsIDocument* aDoc,
                           nsIURI* aURI,
                           nsISupports* aContainer,
                           nsIChannel* aChannel)
 {
+  if (mStreamParser && aDoc) {
+    mStreamParser->SetSpeculativeLoaderWithDocument(aDoc);
+  }
   return mExecutor->Init(aDoc, aURI, aContainer, aChannel);
 }
 
 void
 nsHtml5Parser::StartTokenizer(PRBool aScriptingEnabled) {
   mTreeBuilder->setScriptingEnabled(aScriptingEnabled);
   mTokenizer->start();
 }
 
 void
 nsHtml5Parser::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState)
 {
   mTokenizer->resetToDataState();
   mTreeBuilder->loadState(aState, &mAtomTable);
   mLastWasCR = PR_FALSE;
+  mReturnToStreamParserPermitted = PR_TRUE;
 }
--- a/parser/html/nsHtml5Parser.h
+++ b/parser/html/nsHtml5Parser.h
@@ -326,17 +326,17 @@ class nsHtml5Parser : public nsIParser {
     // Gecko integration
     void*                         mRootContextKey;
     nsIRunnable*                  mContinueEvent;  // weak ref
 
     // Portable parser objects
     /**
      * The first buffer in the pending UTF-16 buffer queue
      */
-    nsHtml5UTF16Buffer*           mFirstBuffer; // manually managed strong ref
+    nsRefPtr<nsHtml5UTF16Buffer>  mFirstBuffer;
 
     /**
      * The last buffer in the pending UTF-16 buffer queue
      */
     nsHtml5UTF16Buffer*           mLastBuffer; // weak ref; always points to
                       // a buffer of the size NS_HTML5_PARSER_READ_BUFFER_SIZE
 
     /**
@@ -353,16 +353,21 @@ class nsHtml5Parser : public nsIParser {
      * The HTML5 tokenizer
      */
     const nsAutoPtr<nsHtml5Tokenizer>   mTokenizer;
 
     /**
      * The stream parser.
      */
     nsRefPtr<nsHtml5StreamParser>       mStreamParser;
+    
+    /**
+     * Whether it's OK to transfer parsing back to the stream parser
+     */
+    PRBool                              mReturnToStreamParserPermitted;
 
     /**
      * The scoped atom table
      */
     nsHtml5AtomTable                    mAtomTable;
 
 };
 #endif
new file mode 100644
--- /dev/null
+++ b/parser/html/nsHtml5Speculation.cpp
@@ -0,0 +1,75 @@
+/* ***** 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 ***** */
+
+#include "nsHtml5Speculation.h"
+
+nsHtml5Speculation::nsHtml5Speculation(nsHtml5UTF16Buffer* aBuffer, 
+                                       PRInt32 aStart, 
+                                       nsAHtml5TreeBuilderState* aSnapshot)
+  : mBuffer(aBuffer)
+  , mStart(aStart)
+  , mSnapshot(aSnapshot)
+{
+  MOZ_COUNT_CTOR(nsHtml5Speculation);
+}
+
+nsHtml5Speculation::~nsHtml5Speculation()
+{
+  MOZ_COUNT_DTOR(nsHtml5Speculation);
+}
+
+void
+nsHtml5Speculation::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+{
+  // No-op
+}
+
+void
+nsHtml5Speculation::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+{
+  if (mOpQueue.IsEmpty()) {
+    mOpQueue.SwapElements(aOpQueue);
+    return;
+  }
+  mOpQueue.MoveElementsFrom(aOpQueue);
+}
+
+void
+nsHtml5Speculation::FlushToSink(nsAHtml5TreeOpSink* aSink)
+{
+  aSink->ForcedFlush(mOpQueue);
+}
new file mode 100644
--- /dev/null
+++ b/parser/html/nsHtml5Speculation.h
@@ -0,0 +1,98 @@
+/* ***** 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 nsHtml5Speculation_h__
+#define nsHtml5Speculation_h__
+
+#include "nsHtml5UTF16Buffer.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsAHtml5TreeOpSink.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+
+class nsHtml5Speculation : public nsAHtml5TreeOpSink
+{
+  public:
+    nsHtml5Speculation(nsHtml5UTF16Buffer* aBuffer, 
+                       PRInt32 aStart, 
+                       nsAHtml5TreeBuilderState* aSnapshot);
+    
+    ~nsHtml5Speculation();
+
+    nsHtml5UTF16Buffer* GetBuffer() {
+      return mBuffer;
+    }
+    
+    PRInt32 GetStart() {
+      return mStart;
+    }
+    
+    nsAHtml5TreeBuilderState* GetSnapshot() {
+      return mSnapshot;
+    }
+
+    /**
+     * No-op.
+     */
+    virtual void MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
+
+    /**
+     * Flush the operations from the tree operations from the argument
+     * queue unconditionally.
+     */
+    virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
+    
+    void FlushToSink(nsAHtml5TreeOpSink* aSink);
+
+  private:
+    /**
+     * The first buffer in the pending UTF-16 buffer queue
+     */
+    nsRefPtr<nsHtml5UTF16Buffer>        mBuffer;
+    
+    /**
+     * The start index of this speculation in the first buffer
+     */
+    PRInt32                             mStart;
+    
+    nsAutoPtr<nsAHtml5TreeBuilderState> mSnapshot;
+
+    nsTArray<nsHtml5TreeOperation>      mOpQueue;
+};
+
+#endif // nsHtml5Speculation_h__
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/parser/html/nsHtml5SpeculativeLoader.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "nsICSSLoader.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsICSSLoaderObserver.h"
+
+/**
+ * Used if we need to pass an nsICSSLoaderObserver as parameter,
+ * but don't really need its services
+ */
+class nsHtml5DummyCSSLoaderObserver : public nsICSSLoaderObserver {
+public:
+  NS_IMETHOD
+  StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aWasAlternate, nsresult aStatus) {
+      return NS_OK;
+  }
+  NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS1(nsHtml5DummyCSSLoaderObserver, nsICSSLoaderObserver)
+
+nsHtml5SpeculativeLoader::nsHtml5SpeculativeLoader(nsIDocument* aDocument)
+  : mDocument(aDocument)
+{
+  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)
+{
+  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
+nsHtml5SpeculativeLoader::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
+nsHtml5SpeculativeLoader::PreloadStyle(const nsAString& aURL,
+                                       const nsAString& aCharset)
+{
+  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+  if (!uri) {
+    return;
+  }
+  nsCOMPtr<nsICSSLoaderObserver> obs = new nsHtml5DummyCSSLoaderObserver();
+  mDocument->CSSLoader()->LoadSheet(uri, mDocument->NodePrincipal(),
+                                    NS_LossyConvertUTF16toASCII(aCharset),
+                                    obs);
+}
+
+void
+nsHtml5SpeculativeLoader::PreloadImage(const nsAString& aURL)
+{
+  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+  if (!uri) {
+    return;
+  }
+  mDocument->MaybePreLoadImage(uri);
+}
new file mode 100644
--- /dev/null
+++ b/parser/html/nsHtml5SpeculativeLoader.h
@@ -0,0 +1,85 @@
+/* ***** 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 "nsIDocument.h"
+#include "nsHashSets.h"
+
+class nsHtml5SpeculativeLoader
+{
+  public:
+    nsHtml5SpeculativeLoader(nsIDocument* aDocument);
+    ~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);
+
+  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 document to use as the context for preloading.
+     */
+    nsCOMPtr<nsIDocument> mDocument;
+    
+    /**
+     * URLs already preloaded/preloading.
+     */
+    nsCStringHashSet mPreloadedURLs;
+};
+
+#endif // nsHtml5SpeculativeLoader_h__
\ No newline at end of file
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -67,27 +67,36 @@ NS_INTERFACE_MAP_END
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5StreamParser)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5StreamParser)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOwner)
   tmp->mExecutorFlusher = nsnull;
   tmp->mExecutor = nsnull;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
+  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)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(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));
   }
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)  
+  // hack: count the strongly owned edge wrapped in the speculative loader
+  if (tmp->mDocument) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 
+      "mTreeBuilder->mSpeculativeLoader->mDocument");
+    cb.NoteXPCOMChild(tmp->mDocument);    
+  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 class nsHtml5ExecutorFlusher : public nsRunnable
 {
   private:
     nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
   public:
     nsHtml5ExecutorFlusher(nsHtml5TreeOpExecutor* aExecutor)
@@ -104,16 +113,17 @@ nsHtml5StreamParser::nsHtml5StreamParser
                                          nsHtml5Parser* aOwner)
   : mFirstBuffer(new nsHtml5UTF16Buffer(NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE))
   , mLastBuffer(mFirstBuffer)
   , mExecutor(aExecutor)
   , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor->GetStage()))
   , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder))
   , mTokenizerMutex("nsHtml5StreamParser mTokenizerMutex")
   , mOwner(aOwner)
+  , mSpeculationMutex("nsHtml5StreamParser mSpeculationMutex")
   , mTerminatedMutex("nsHtml5StreamParser mTerminatedMutex")
   , mThread(nsHtml5Module::GetStreamParserThread())
   , mExecutorFlusher(new nsHtml5ExecutorFlusher(aExecutor))
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   mAtomTable.Init(); // we aren't checking for OOM anyway...
   #ifdef DEBUG
     mAtomTable.SetPermittedLookupThread(mThread);
@@ -127,27 +137,30 @@ nsHtml5StreamParser::~nsHtml5StreamParse
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   mTokenizer->end();
   mRequest = nsnull;
   mObserver = nsnull;
   mUnicodeDecoder = nsnull;
   mSniffingBuffer = nsnull;
   mMetaScanner = nsnull;
-  while (mFirstBuffer) {
-     nsHtml5UTF16Buffer* old = mFirstBuffer;
-     mFirstBuffer = mFirstBuffer->next;
-     delete old;
-  }
+  mFirstBuffer = nsnull;
   mExecutor = nsnull;
   mTreeBuilder = nsnull;
   mTokenizer = nsnull;
   mOwner = nsnull;
 }
 
+void
+nsHtml5StreamParser::SetSpeculativeLoaderWithDocument(nsIDocument* aDocument) {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  mDocument = aDocument;
+  mTreeBuilder->SetSpeculativeLoaderWithDocument(aDocument);
+}
+
 nsresult
 nsHtml5StreamParser::GetChannel(nsIChannel** aChannel)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   return mRequest ? CallQueryInterface(mRequest, aChannel) :
                     NS_ERROR_NOT_AVAILABLE;
 }
 
@@ -439,17 +452,17 @@ nsHtml5StreamParser::WriteStreamBytes(co
 }
 
 // nsIRequestObserver methods:
 nsresult
 nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   NS_PRECONDITION(STREAM_NOT_STARTED == mStreamState,
                   "Got OnStartRequest when the stream had already started.");
-  NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED, 
+  NS_PRECONDITION(!mExecutor->HasStarted(), 
                   "Got OnStartRequest at the wrong stage in the executor life cycle.");
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   if (mObserver) {
     mObserver->OnStartRequest(aRequest, aContext);
   }
   mRequest = aRequest;
 
   mStreamState = STREAM_BEING_READ;
@@ -498,39 +511,48 @@ nsHtml5StreamParser::OnStartRequest(nsIR
 }
 
 void
 nsHtml5StreamParser::DoStopRequest()
 {
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
   NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
                   "Stream ended without being open.");
+  mTokenizerMutex.AssertCurrentThreadOwns();
+
+  if (IsTerminated()) {
+    return;
+  }
+
   if (!mUnicodeDecoder) {
     PRUint32 writeCount;
     FinalizeSniffing(nsnull, 0, &writeCount, 0);
     // dropped nsresult here
   }
 
   mStreamState = STREAM_ENDED;
-  
-  if (!mWaitingForScripts) {
-    ParseUntilScript();
-  }  
+
+  if (IsTerminatedOrInterrupted()) {
+    return;
+  }
+
+  ParseAvailableData(); 
 }
 
 class nsHtml5RequestStopper : public nsRunnable
 {
   private:
     nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
   public:
     nsHtml5RequestStopper(nsHtml5StreamParser* aStreamParser)
       : mStreamParser(aStreamParser)
     {}
     NS_IMETHODIMP Run()
     {
+      mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
       mStreamParser->DoStopRequest();
       return NS_OK;
     }
 };
 
 nsresult
 nsHtml5StreamParser::OnStopRequest(nsIRequest* aRequest,
                              nsISupports* aContext,
@@ -549,24 +571,33 @@ nsHtml5StreamParser::OnStopRequest(nsIRe
 }
 
 void
 nsHtml5StreamParser::DoDataAvailable(PRUint8* aBuffer, PRUint32 aLength)
 {
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
   NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
                   "DoDataAvailable called when stream not open.");
+  mTokenizerMutex.AssertCurrentThreadOwns();
+
+  if (IsTerminated()) {
+    return;
+  }
+
   PRUint32 writeCount;
   HasDecoder() ? WriteStreamBytes(aBuffer, aLength, &writeCount) :
                  SniffStreamBytes(aBuffer, aLength, &writeCount);
   // dropping nsresult here
   NS_ASSERTION(writeCount == aLength, "Wrong number of stream bytes written/sniffed.");
-  if (!mWaitingForScripts) {
-    ParseUntilScript();
+
+  if (IsTerminatedOrInterrupted()) {
+    return;
   }
+
+  ParseAvailableData();
 }
 
 class nsHtml5DataAvailable : public nsRunnable
 {
   private:
     nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
     nsAutoArrayPtr<PRUint8>            mData;
     PRUint32                           mLength;
@@ -575,16 +606,17 @@ class nsHtml5DataAvailable : public nsRu
                          PRUint8*             aData,
                          PRUint32             aLength)
       : mStreamParser(aStreamParser)
       , mData(aData)
       , mLength(aLength)
     {}
     NS_IMETHODIMP Run()
     {
+      mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
       mStreamParser->DoDataAvailable(mData, mLength);
       return NS_OK;
     }
 };
 
 // nsIStreamListener method:
 nsresult
 nsHtml5StreamParser::OnDataAvailable(nsIRequest* aRequest,
@@ -643,140 +675,206 @@ nsHtml5StreamParser::internalEncodingDec
   // we still want to reparse
   mTreeBuilder->NeedsCharsetSwitchTo(newEncoding);
   mTreeBuilder->Flush();
   // the tree op executor will cause the stream parser to terminate
   // if the charset switch request is accepted
 }
 
 void
-nsHtml5StreamParser::ParseUntilScript()
+nsHtml5StreamParser::ParseAvailableData()
 {
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
-  if (IsTerminated()) {
+  mTokenizerMutex.AssertCurrentThreadOwns();
+
+  if (IsTerminatedOrInterrupted()) {
     return;
   }
-
-  // TODO: Relax this mutex so that the parser doesn't speculate to
-  // completion when it's already known that the speculation will fail.
-  // Maybe have another boolean and mutex for checking IsSpeculationFailing()
-  // or something like that instead of trying to be fancy with this mutex.
-  mozilla::MutexAutoLock autoLock(mTokenizerMutex);
-
-  mWaitingForScripts = PR_FALSE;
-
+  
   for (;;) {
     if (!mFirstBuffer->hasMore()) {
       if (mFirstBuffer == mLastBuffer) {
         switch (mStreamState) {
           case STREAM_BEING_READ:
-            // never release the last buffer. instead just zero its indeces for refill
-            mFirstBuffer->setStart(0);
-            mFirstBuffer->setEnd(0);
+            // never release the last buffer.
+            if (!mSpeculating) {
+              // reuse buffer space if not speculating
+              mFirstBuffer->setStart(0);
+              mFirstBuffer->setEnd(0);
+            }
             mTreeBuilder->Flush();
             return; // no more data for now but expecting more
           case STREAM_ENDED:
-            Terminate(); // TODO Don't terminate if this is a speculation
+            if (mAtEOF) {
+                return;
+            }
+            mAtEOF = PR_TRUE;
             mTokenizer->eof();
             mTreeBuilder->StreamEnded();
             mTreeBuilder->Flush();
             if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
               NS_WARNING("failed to dispatch executor flush event");
-            }  
+            }
             return; // no more data and not expecting more
           default:
             NS_NOTREACHED("It should be impossible to reach this.");
             return;
         }
       } else {
-        nsHtml5UTF16Buffer* oldBuf = mFirstBuffer;
         mFirstBuffer = mFirstBuffer->next;
-        delete oldBuf;
         continue;
       }
     }
 
     // now we have a non-empty buffer
     mFirstBuffer->adjust(mLastWasCR);
     mLastWasCR = PR_FALSE;
     if (mFirstBuffer->hasMore()) {
       mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
       // At this point, internalEncodingDeclaration() may have called 
       // Terminate, but that never happens together with script.
       // Can't assert that here, though, because it's possible that the main
       // thread has called Terminate() while this thread was parsing.
-      if (IsTerminated()) {
-        return;
-      }
       if (mTreeBuilder->HasScript()) {
-        mTreeBuilder->AddSnapshotToScript(mTreeBuilder->newSnapshot());
+        mozilla::MutexAutoLock speculationAutoLock(mSpeculationMutex);
+        nsHtml5Speculation* speculation = 
+          new nsHtml5Speculation(mFirstBuffer,
+                                 mFirstBuffer->getStart(),
+                                 mTreeBuilder->newSnapshot());
+        mTreeBuilder->AddSnapshotToScript(speculation->GetSnapshot());
         mTreeBuilder->Flush();
         if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
           NS_WARNING("failed to dispatch executor flush event");
-        }  
-        // XXX start speculation
-        mWaitingForScripts = PR_TRUE;
-        return; // ContinueAfterScripts() will re-enable this parser
+        }
+        mTreeBuilder->SetOpSink(speculation);
+        mSpeculations.AppendElement(speculation); // adopts the pointer
+        mSpeculating = PR_TRUE;
+      }
+      if (IsTerminatedOrInterrupted()) {
+        return;
       }
       mTreeBuilder->MaybeFlush();
     }
     continue;
   }
 }
 
-class nsHtml5ContinueAfterScript : public nsRunnable
+class nsHtml5StreamParserContinuation : public nsRunnable
 {
 private:
   nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
 public:
-  nsHtml5ContinueAfterScript(nsHtml5StreamParser* aStreamParser)
+  nsHtml5StreamParserContinuation(nsHtml5StreamParser* aStreamParser)
     : mStreamParser(aStreamParser)
   {}
   NS_IMETHODIMP Run()
   {
-    mStreamParser->ParseUntilScript();
+    mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
+    mStreamParser->ParseAvailableData();
     return NS_OK;
   }
 };
 
 void
 nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer, 
                                           nsHtml5TreeBuilder* aTreeBuilder,
                                           PRBool aLastWasCR)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  mExecutor->StartReadingFromStage();
-  // TODO:
-  // test if the state of the argument tokenizer and tree builder match
-  // the earliest speculation.
-  // If not, rendez-vous at barrier, zaps all speculations, rewind the stream 
-  // and copy over the state.
-  // If yes:
-  // If there are multiple speculations or the stream parser has terminated.
-  // load the tree op queue from the earliest speculation into the tree op 
-  // executor and discard the stream data for that speculation. Return.
-  // Otherwise, rendez-vous at barrier, load the tree op queue from the 
-  // speculation into the tree op executor, set the tree op executor to read 
-  // from the stage, set the stream parser tree builder to write to stage,
-  // discard the stream data for the speculation.
-  
+  #ifdef DEBUG
+    mExecutor->AssertStageEmpty();
+  #endif
+  PRBool speculationFailed = PR_FALSE;
   {
-    mozilla::MutexAutoLock autoLock(mTokenizerMutex); 
+    mozilla::MutexAutoLock speculationAutoLock(mSpeculationMutex);
+    if (mSpeculations.IsEmpty()) {
+      // Not quite sure how exactly this happens...
+      // Maybe an artifact of defer scripts?
+      return;
+    }
+    nsHtml5Speculation* speculation = mSpeculations.ElementAt(0);
+    if (aLastWasCR || 
+        !aTokenizer->isInDataState() || 
+        !aTreeBuilder->snapshotMatches(speculation->GetSnapshot())) {
+      speculationFailed = PR_TRUE;
+      // We've got a failed speculation :-(
+      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");
+        }
+        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 
+      // release the the speculation mutex and acquire the tokenizer 
+      // mutex. (Just acquiring the other mutex here would deadlock)
+    }
+  }
+  {
+    mozilla::MutexAutoLock tokenizerAutoLock(mTokenizerMutex);
     #ifdef DEBUG
       nsCOMPtr<nsIThread> mainThread;
       NS_GetMainThread(getter_AddRefs(mainThread));
       mAtomTable.SetPermittedLookupThread(mainThread);
     #endif
-
-    // Approximation: Copy state over for now unconditionally.
-    mLastWasCR = aLastWasCR;
-    mTokenizer->loadState(aTokenizer);
-    mTreeBuilder->loadState(aTreeBuilder, &mAtomTable);
-    
+    // In principle, the speculation mutex should be acquired here,
+    // but there's no point, because the parser thread only acquires it
+    // when it has also acquired the tokenizer mutex and we are already
+    // holding the tokenizer mutex.
+    if (speculationFailed) {
+      // Rewind the stream
+      mAtEOF = PR_FALSE;
+      nsHtml5Speculation* speculation = mSpeculations.ElementAt(0);
+      mFirstBuffer = speculation->GetBuffer();
+      mFirstBuffer->setStart(speculation->GetStart());
+      nsHtml5UTF16Buffer* buffer = mFirstBuffer->next;
+      while (buffer) {
+        buffer->setStart(0);
+        buffer = buffer->next;
+      }
+      
+      mSpeculations.Clear(); // potentially a huge number of destructors 
+                             // run here synchronously on the main thread...
+      
+      mTreeBuilder->SetOpSink(mExecutor->GetStage());
+      mExecutor->StartReadingFromStage();
+      mSpeculating = PR_FALSE;
+      // 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");
+      }
+      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;
+      }
+    }
+    Uninterrupt();
+    nsCOMPtr<nsIRunnable> event = new nsHtml5StreamParserContinuation(this);
+    if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
+      NS_WARNING("Failed to dispatch ParseAvailableData event");
+    }
+    // A stream event might run before this event runs, but that's harmless.
     #ifdef DEBUG
       mAtomTable.SetPermittedLookupThread(mThread);
     #endif
   }
-  nsCOMPtr<nsIRunnable> event = new nsHtml5ContinueAfterScript(this);
-  if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
-    NS_WARNING("Failed to dispatch ParseUntilScript event");
-  }
 }
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -46,16 +46,17 @@
 #include "nsHtml5MetaScanner.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsHtml5TreeOpExecutor.h"
 #include "nsHtml5UTF16Buffer.h"
 #include "nsIInputStream.h"
 #include "nsICharsetAlias.h"
 #include "mozilla/Mutex.h"
 #include "nsHtml5AtomTable.h"
+#include "nsHtml5Speculation.h"
 
 class nsHtml5Parser;
 
 #define NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE 1024
 #define NS_HTML5_STREAM_PARSER_SNIFFING_BUFFER_SIZE 512
 
 enum eBomState {
   /**
@@ -99,17 +100,17 @@ enum eHtml5StreamState {
   STREAM_ENDED = 2
 };
 
 class nsHtml5StreamParser : public nsIStreamListener,
                             public nsICharsetDetectionObserver {
 
   friend class nsHtml5RequestStopper;
   friend class nsHtml5DataAvailable;
-  friend class nsHtml5ContinueAfterScript;
+  friend class nsHtml5StreamParserContinuation;
 
   public:
     NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHtml5StreamParser, nsIStreamListener)
 
     nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
                         nsHtml5Parser* aOwner);
@@ -149,54 +150,73 @@ class nsHtml5StreamParser : public nsISt
       mCharset = aCharset;
       mCharsetSource = aSource;
     }
     
     inline void SetObserver(nsIRequestObserver* aObserver) {
       NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
       mObserver = aObserver;
     }
-    
+
+    void SetSpeculativeLoaderWithDocument(nsIDocument* aDocument);
+
     nsresult GetChannel(nsIChannel** aChannel);
 
     /**
      * The owner parser must call this after script execution
      * when no scripts are executing and the document.written 
      * buffer has been exhausted.
      */
     void ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer, 
                               nsHtml5TreeBuilder* aTreeBuilder,
                               PRBool aLastWasCR);
 
     void Terminate() {
       mozilla::MutexAutoLock autoLock(mTerminatedMutex);
       mTerminated = PR_TRUE;
-      // TODO: Make sure this object stays alive as long as there are 
-      // in-flight runnables coming this way
     }
     
   private:
 
 #ifdef DEBUG
     PRBool IsParserThread() {
       PRBool ret;
       mThread->IsOnCurrentThread(&ret);
       return ret;
     }
 #endif
 
-    void ParseUntilScript();
+    void Interrupt() {
+      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+      mozilla::MutexAutoLock autoLock(mTerminatedMutex);
+      mInterrupted = PR_TRUE;
+    }
+
+    void Uninterrupt() {
+      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+      mTokenizerMutex.AssertCurrentThreadOwns();
+      // Not acquiring mTerminatedMutex because mTokenizerMutex is already
+      // held at this point and is already stronger.
+      mInterrupted = PR_FALSE;      
+    }
+
+    void ParseAvailableData();
     
     void DoStopRequest();
     
     void DoDataAvailable(PRUint8* aBuffer, PRUint32 aLength);
 
+    PRBool IsTerminatedOrInterrupted() {
+      mozilla::MutexAutoLock autoLock(mTerminatedMutex);
+      return mTerminated || mInterrupted;
+    }
+
     PRBool IsTerminated() {
       mozilla::MutexAutoLock autoLock(mTerminatedMutex);
-      return mTerminated;    
+      return mTerminated;
     }
 
     /**
      * True when there is a Unicode decoder already
      */
     inline PRBool HasDecoder() {
       return !!mUnicodeDecoder;
     }
@@ -319,17 +339,17 @@ class nsHtml5StreamParser : public nsISt
      * Whether reparse is forbidden
      */
     PRBool                        mReparseForbidden;
 
     // Portable parser objects
     /**
      * The first buffer in the pending UTF-16 buffer queue
      */
-    nsHtml5UTF16Buffer*           mFirstBuffer; // manually managed strong ref
+    nsRefPtr<nsHtml5UTF16Buffer>  mFirstBuffer;
 
     /**
      * The last buffer in the pending UTF-16 buffer queue
      */
     nsHtml5UTF16Buffer*           mLastBuffer; // weak ref; always points to
                       // a buffer of the size NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE
 
     /**
@@ -344,17 +364,17 @@ class nsHtml5StreamParser : public nsISt
 
     /**
      * The HTML5 tokenizer
      */
     nsAutoPtr<nsHtml5Tokenizer>   mTokenizer;
 
     /**
      * Makes sure the main thread can't mess the tokenizer state while it's
-     * tokenizing
+     * tokenizing. This mutex also protects the current speculation.
      */
     mozilla::Mutex                mTokenizerMutex;
 
     /**
      * The scoped atom table
      */
     nsHtml5AtomTable              mAtomTable;
 
@@ -369,27 +389,47 @@ class nsHtml5StreamParser : public nsISt
     PRBool                        mLastWasCR;
 
     /**
      * For tracking stream life cycle
      */
     eHtml5StreamState             mStreamState;
     
     /**
-     * Whether we are waiting for scripts to be done.
+     * Whether we are speculating.
+     */
+    PRBool                        mSpeculating;
+
+    /**
+     * Whether the tokenizer has reached EOF. (Reset when stream rewinded.)
      */
-    PRBool                        mWaitingForScripts;
+    PRBool                        mAtEOF;
+
+    /**
+     * The speculations. The mutex protects the nsTArray itself.
+     * To access the queue of current speculation, mTokenizerMutex must be 
+     * obtained.
+     * The current speculation is the last element
+     */
+    nsTArray<nsAutoPtr<nsHtml5Speculation> >  mSpeculations;
+    mozilla::Mutex                            mSpeculationMutex;
 
     /**
      * True to terminate early; protected by mTerminatedMutex
      */
     PRBool                        mTerminated;
+    PRBool                        mInterrupted;
     mozilla::Mutex                mTerminatedMutex;
     
     /**
      * The thread this stream parser runs on.
      */
     nsCOMPtr<nsIThread>           mThread;
     
     nsCOMPtr<nsIRunnable>         mExecutorFlusher;
+    
+    /**
+     * The document wrapped by the speculative loader.
+     */
+    nsCOMPtr<nsIDocument>         mDocument;
 };
 
 #endif // nsHtml5StreamParser_h__
--- a/parser/html/nsHtml5Tokenizer.cpp
+++ b/parser/html/nsHtml5Tokenizer.cpp
@@ -3484,16 +3484,17 @@ nsHtml5Tokenizer::loadState(nsHtml5Token
 }
 
 void 
 nsHtml5Tokenizer::initializeWithoutStarting()
 {
   confident = PR_FALSE;
   strBuf = jArray<PRUnichar,PRInt32>(64);
   longStrBuf = jArray<PRUnichar,PRInt32>(1024);
+  line = 1;
   resetToDataState();
 }
 
 void 
 nsHtml5Tokenizer::setEncodingDeclarationHandler(nsHtml5StreamParser* encodingDeclarationHandler)
 {
   this->encodingDeclarationHandler = encodingDeclarationHandler;
 }
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -41,16 +41,17 @@
 #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)
   : scriptingEnabled(PR_FALSE)
   , fragment(PR_FALSE)
   , contextNode(nsnull)
   , formPointer(nsnull)
@@ -67,19 +68,153 @@ nsHtml5TreeBuilder::nsHtml5TreeBuilder(n
 
 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;
+  }
+};
+
 nsIContent**
 nsHtml5TreeBuilder::createElement(PRInt32 aNamespace, nsIAtom* aName, nsHtml5HtmlAttributes* aAttributes)
 {
+  NS_PRECONDITION(aAttributes, "Got null attributes.");
+  
+  // Start wall of code for speculative loading
+  
+  if (mSpeculativeLoader) {
+    switch (aNamespace) {
+      case kNameSpaceID_XHTML:
+        if (nsHtml5Atoms::img == aName) {
+          nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
+          if (url) {
+            Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
+          }
+        } else if (nsHtml5Atoms::script == aName) {
+          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()));
+          }
+        } 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()));
+            }
+          }
+        } else if (nsHtml5Atoms::video == aName) {
+          nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
+          if (url) {
+            Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
+          }
+        }
+        break;
+      case kNameSpaceID_SVG:
+        if (nsHtml5Atoms::image == aName) {
+          nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
+          if (url) {
+            Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
+          }
+        } else if (nsHtml5Atoms::script == aName) {
+          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()));
+          }
+        } else if (nsHtml5Atoms::style == aName) {
+          nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
+          if (url) {
+            Dispatch(new nsHtml5SpeculativeStyle(mSpeculativeLoader, 
+                                                 *url,
+                                                 EmptyString()));
+          }
+        }        
+        break;
+    }
+  }
+
+  // End wall of code for speculative loading
+  
   nsIContent** content = AllocateContentHandle();
   nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
   // XXX if null, OOM!
   treeOp->Init(aNamespace, aName, aAttributes, content);
   return content;
 }
 
 nsIContent**
@@ -287,17 +422,17 @@ nsHtml5TreeBuilder::elementPopped(PRInt3
   if (aNamespace == kNameSpaceID_MathML) {
     return;
   }
   // we now have only SVG and HTML
   if (aName == nsHtml5Atoms::script) {
     requestSuspension();
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     // XXX if null, OOM!
-    treeOp->Init(eTreeOpRunScript, aElement, nsnull);
+    treeOp->InitScript(aElement);
     return;
   }
   if (aName == nsHtml5Atoms::title) {
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     // XXX if null, OOM!
     treeOp->Init(eTreeOpDoneAddingChildren, aElement);
     return;
   }
@@ -421,17 +556,17 @@ nsHtml5TreeBuilder::SetDocumentCharset(n
   nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
   // XXX if null, OOM!
   treeOp->Init(eTreeOpSetDocumentCharset, aCharset);  
 }
 
 void
 nsHtml5TreeBuilder::StreamEnded()
 {
-  // The fragement mode calls DidBuildModel from nsHtml5Parser. 
+  // The fragment mode calls DidBuildModel from nsHtml5Parser. 
   // Letting DidBuildModel be called from the executor in the fragment case
   // confuses the EndLoad logic of nsHTMLDocument, since nsHTMLDocument
   // thinks it is dealing with document.written content as opposed to 
   // innerHTML content.
   if (!fragment) {
     nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
     // XXX if null, OOM!
     treeOp->Init(eTreeOpStreamEnded);
@@ -445,19 +580,31 @@ nsHtml5TreeBuilder::NeedsCharsetSwitchTo
   // XXX if null, OOM!
   treeOp->Init(eTreeOpNeedsCharsetSwitchTo, aCharset);  
 }
 
 void
 nsHtml5TreeBuilder::AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot)
 {
   NS_PRECONDITION(HasScript(), "No script to add a snapshot to!");
+  NS_PRECONDITION(aSnapshot, "Got null snapshot.");
   mOpQueue.ElementAt(mOpQueue.Length() - 1).SetSnapshot(aSnapshot);
 }
 
+void
+nsHtml5TreeBuilder::SetSpeculativeLoaderWithDocument(nsIDocument* aDocument) {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  mSpeculativeLoader = new nsHtml5SpeculativeLoader(aDocument);
+}
+
+void
+nsHtml5TreeBuilder::DropSpeculativeLoader() {
+  mSpeculativeLoader = nsnull;
+}
+
 // DocumentModeHandler
 void
 nsHtml5TreeBuilder::documentMode(nsHtml5DocumentMode m)
 {
   nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
   // XXX if null, OOM!
   treeOp->Init(m);
 }
--- a/parser/html/nsHtml5TreeBuilderHSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderHSupplement.h
@@ -38,17 +38,18 @@
 #define NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH 512
 
   private:
 
     nsTArray<nsHtml5TreeOperation>         mOpQueue;
     nsAHtml5TreeOpSink*                    mOpSink;
     nsAutoArrayPtr<nsIContent*>            mHandles;
     PRInt32                                mHandlesUsed;
-    nsTArray<nsAutoArrayPtr<nsIContent*> > mOldHandles;              
+    nsTArray<nsAutoArrayPtr<nsIContent*> > mOldHandles;
+    nsRefPtr<nsHtml5SpeculativeLoader>     mSpeculativeLoader;
 #ifdef DEBUG
     PRBool                                 mActive;
 #endif
 
     // DocumentModeHandler
     /**
      * Tree builder uses this to report quirkiness of the document
      */
@@ -63,19 +64,29 @@
     ~nsHtml5TreeBuilder();
     
     PRBool HasScript();
     
     void SetOpSink(nsAHtml5TreeOpSink* aOpSink) {
       mOpSink = aOpSink;
     }
     
+    void SetSpeculativeLoaderWithDocument(nsIDocument* aDocument);
+
+    void DropSpeculativeLoader();
+
     void Flush();
     
     void MaybeFlush();
     
     void SetDocumentCharset(nsACString& aCharset);
 
     void StreamEnded();
 
     void NeedsCharsetSwitchTo(const nsACString& aEncoding);
 
     void AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot);
+
+    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
@@ -86,20 +86,19 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   }
   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()
-  : mHasProcessedBase(PR_FALSE)
-  , mReadingFromStage(PR_FALSE)
-  , mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
+  : mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
 {
+  // zeroing operator new for everything else
 }
 
 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
 {
   NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
   if (mFlushTimer) {
     mFlushTimer->Cancel(); // XXX why is this even necessary? it is, though.
   }
@@ -119,19 +118,18 @@ 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(mLifeCycle == PARSING, 
+  NS_PRECONDITION(mStarted && mParser, 
                   "Bad life cycle.");
-  mLifeCycle = TERMINATED;
   // This is comes from nsXMLContentSink
   DidBuildModelImpl(aTerminated);
   mDocument->ScriptLoader()->RemoveObserver(this);
   nsContentSink::StartLayout(PR_FALSE);
   ScrollToRef();
   mDocument->RemoveObserver(this);
   mDocument->EndLoad();
   static_cast<nsHtml5Parser*> (mParser.get())->DropStreamParser();
@@ -261,25 +259,23 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet(
       mScriptLoader->AddExecuteBlocker();
     }
   }
 }
 
 void
 nsHtml5TreeOpExecutor::Flush()
 {
-  nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
-  nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
-
-  if (mLifeCycle == TERMINATED) {
+  if (!mParser) {
     mFlushTimer->Cancel();
     return;
   }
 
-  NS_ASSERTION(mParser, "mParser was nulled out but life cycle wasn't terminated.");
+  nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
+  nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
 
   if (mReadingFromStage) {
     mStage.RetrieveOperations(mOpQueue);
   }
   
   { // scope for the auto update so that it ends before we try to run the 
     // script
     MOZ_AUTO_DOC_UPDATE(GetDocument(), UPDATE_CONTENT_MODEL, PR_TRUE);
@@ -415,17 +411,17 @@ nsHtml5TreeOpExecutor::DocumentMode(nsHt
  */
 void
 nsHtml5TreeOpExecutor::ExecuteScript()
 {
   mReadingFromStage = PR_FALSE;
   NS_ASSERTION(mScriptElement, "No script to run");
 
   nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(mScriptElement);
-  if (mLifeCycle == TERMINATED) {
+  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.
     return;
   }
   // Notify our document that we're loading this script.
   nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
@@ -437,21 +433,22 @@ nsHtml5TreeOpExecutor::ExecuteScript()
   // need executing.
   nsresult rv = mScriptElement->DoneAddingChildren(PR_TRUE);
   mScriptElement = nsnull;
   // 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 if (mLifeCycle != TERMINATED) {
+  } 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);
-    static_cast<nsHtml5Parser*> (mParser.get())->MaybePostContinueEvent();
+    // mParser may have been nulled out by now, but nsContentSink deals
+    ContinueInterruptedParsingAsync();
   }
 }
 
 nsresult
 nsHtml5TreeOpExecutor::Init(nsIDocument* aDoc,
                             nsIURI* aURI,
                             nsISupports* aContainer,
                             nsIChannel* aChannel)
@@ -461,18 +458,18 @@ nsHtml5TreeOpExecutor::Init(nsIDocument*
   mCanInterruptParser = PR_FALSE; // without this, nsContentSink calls
                                   // UnblockOnload from DropParserAndPerfHint
   return rv;
 }
 
 void
 nsHtml5TreeOpExecutor::Start()
 {
-  NS_PRECONDITION(mLifeCycle == NOT_STARTED, "Tried to start when already started.");
-  mLifeCycle = PARSING;
+  NS_PRECONDITION(!mStarted, "Tried to start when already started.");
+  mStarted = PR_TRUE;
   mScriptElement = nsnull;
   ScheduleTimer();
 }
 
 void
 nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding)
 {
   nsresult rv = NS_OK;
@@ -500,17 +497,17 @@ nsHtml5TreeOpExecutor::GetTokenizer()
   return (static_cast<nsHtml5Parser*> (mParser.get()))->GetTokenizer();
 }
 
 void
 nsHtml5TreeOpExecutor::Reset() {
   mHasProcessedBase = PR_FALSE;
   mReadingFromStage = PR_FALSE;
   mOpQueue.Clear();
-  mLifeCycle = NOT_STARTED;
+  mStarted = PR_FALSE;
   mScriptElement = nsnull;
   mCallDidBuildModel = PR_FALSE;
 }
 
 void
 nsHtml5TreeOpExecutor::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
 {
   // no-op
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -56,34 +56,16 @@
 #include "nsCOMArray.h"
 #include "nsAHtml5TreeOpSink.h"
 #include "nsHtml5TreeOpStage.h"
 
 class nsHtml5TreeBuilder;
 class nsHtml5Tokenizer;
 class nsHtml5StreamParser;
 
-enum eHtml5ParserLifecycle {
-  /**
-   * The parser has told the tokenizer to start yet.
-   */
-  NOT_STARTED = 0,
-
-  /**
-   * The parser has started the tokenizer and as far as the executor is
-   * aware, the stream hasn't ended.
-   */
-  PARSING = 1,
-
-  /**
-   * The parse has ended.
-   */
-  TERMINATED = 2
-};
-
 typedef nsIContent* nsIContentPtr;
 
 class nsHtml5TreeOpExecutor : public nsContentSink,
                               public nsIContentSink,
                               public nsAHtml5TreeOpSink
 {
   public:
     NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
@@ -114,19 +96,19 @@ class nsHtml5TreeOpExecutor : public nsC
     nsCOMArray<nsIContent>               mOwnedElements;
     
     // This could be optimized away by introducing more tree ops so that 
     // non-elements wouldn't use the handle setup but the text node / comment
     // / doctype operand would be remembered by the tree op executor.
     nsCOMArray<nsIContent>               mOwnedNonElements;
   
     /**
-     * The current point on parser life cycle
+     * Whether the parser has started
      */
-    eHtml5ParserLifecycle         mLifeCycle;
+    PRBool                        mStarted;
 
     /**
      * Script to run ASAP
      */
     nsCOMPtr<nsIContent>          mScriptElement;
     
     nsHtml5TreeOpStage            mStage;
     
@@ -321,25 +303,21 @@ class nsHtml5TreeOpExecutor : public nsC
     
 #ifdef DEBUG
     PRBool HasScriptElement() {
       return !!mScriptElement;
     }
 #endif
 
     PRBool IsComplete() {
-      return (mLifeCycle == TERMINATED);
+      return !mParser;
     }
     
-    eHtml5ParserLifecycle GetLifeCycle() {
-      return mLifeCycle;
-    }
-    
-    void SetLifeCycle(eHtml5ParserLifecycle aLifeCycle) {
-      mLifeCycle = aLifeCycle;
+    PRBool HasStarted() {
+      return mStarted;
     }
     
     void ExecuteScript();
     
     void MaybePreventExecution() {
       if (mScriptElement) {
         nsCOMPtr<nsIScriptElement> script = do_QueryInterface(mScriptElement);
         NS_ASSERTION(script, "mScriptElement didn't QI to nsIScriptElement!");
@@ -378,15 +356,21 @@ class nsHtml5TreeOpExecutor : public nsC
     void StartReadingFromStage() {
       mReadingFromStage = PR_TRUE;
     }
 
     void StreamEnded();
     
     void ScheduleTimer();
 
+#ifdef DEBUG
+    void AssertStageEmpty() {
+      mStage.AssertEmpty();
+    }
+#endif
+
   private:
 
     nsHtml5Tokenizer* GetTokenizer();
         
 };
 
 #endif // nsHtml5TreeOpExecutor_h__
--- a/parser/html/nsHtml5TreeOpStage.cpp
+++ b/parser/html/nsHtml5TreeOpStage.cpp
@@ -71,8 +71,18 @@ nsHtml5TreeOpStage::RetrieveOperations(n
 {
   mozilla::MutexAutoLock autoLock(mMutex);
   if (aOpQueue.IsEmpty()) {
     mOpQueue.SwapElements(aOpQueue);
     return;
   }
   aOpQueue.MoveElementsFrom(mOpQueue);
 }
+
+#ifdef DEBUG
+void
+nsHtml5TreeOpStage::AssertEmpty()
+{
+  mozilla::MutexAutoLock autoLock(mMutex);
+  // This shouldn't really need the mutex
+  NS_ASSERTION(mOpQueue.IsEmpty(), "The stage was supposed to be empty.");
+}
+#endif
--- a/parser/html/nsHtml5TreeOpStage.h
+++ b/parser/html/nsHtml5TreeOpStage.h
@@ -62,15 +62,19 @@ class nsHtml5TreeOpStage : public nsAHtm
      */
     virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
     
     /**
      * Retrieve the staged operations into the argument.
      */
     void RetrieveOperations(nsTArray<nsHtml5TreeOperation>& aOpQueue);
 
+#ifdef DEBUG
+    void AssertEmpty();
+#endif
+
   private:
     nsTArray<nsHtml5TreeOperation> mOpQueue;
     mozilla::Mutex                 mMutex;
     
 };
 
 #endif /* nsHtml5TreeOpStage_h___ */
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -83,19 +83,16 @@ nsHtml5TreeOperation::~nsHtml5TreeOperat
     case eTreeOpCreateTextNode:
     case eTreeOpCreateComment:
       delete[] mTwo.unicharPtr;
       break;
     case eTreeOpSetDocumentCharset:
     case eTreeOpNeedsCharsetSwitchTo:
       delete[] mOne.charPtr;
       break;
-    case eTreeOpRunScript:
-      delete mTwo.state;
-      break;
     default: // keep the compiler happy
       break;
   }
 }
 
 nsresult
 nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder)
 {
--- a/parser/html/nsHtml5TreeOperation.h
+++ b/parser/html/nsHtml5TreeOperation.h
@@ -143,16 +143,24 @@ class nsHtml5TreeOperation {
 
     inline void Init(nsHtml5DocumentMode aMode) {
       NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
         "Op code must be uninitialized when initializing.");
       mOpCode = eTreeOpDocumentMode;
       mOne.mode = aMode;
     }
     
+    inline void InitScript(nsIContent** aNode) {
+      NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+        "Op code must be uninitialized when initializing.");
+      mOpCode = eTreeOpRunScript;
+      mOne.node = aNode;
+      mTwo.state = nsnull;
+    }
+    
     inline void Init(PRInt32 aNamespace, 
                      nsIAtom* aName, 
                      nsHtml5HtmlAttributes* aAttributes,
                      nsIContent** aTarget) {
       NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
         "Op code must be uninitialized when initializing.");
       mOpCode = eTreeOpCreateElement;
       mInt = aNamespace;
--- a/parser/html/nsHtml5UTF16BufferCppSupplement.h
+++ b/parser/html/nsHtml5UTF16BufferCppSupplement.h
@@ -57,8 +57,34 @@ nsHtml5UTF16Buffer::nsHtml5UTF16Buffer(v
   MOZ_COUNT_CTOR(nsHtml5UTF16Buffer);
 }
 
 nsHtml5UTF16Buffer::~nsHtml5UTF16Buffer()
 {
   MOZ_COUNT_DTOR(nsHtml5UTF16Buffer);
   delete[] buffer;
 }
+
+// Not using macros for AddRef and Release in order to be able to refcount on
+// and create on different threads.
+
+nsrefcnt
+nsHtml5UTF16Buffer::AddRef()
+{
+  NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "Illegal refcount.");
+  ++mRefCnt;
+  NS_LOG_ADDREF(this, mRefCnt, "nsHtml5UTF16Buffer", sizeof(*this));
+  return mRefCnt;
+}
+
+nsrefcnt
+nsHtml5UTF16Buffer::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "Release without AddRef.");
+  --mRefCnt;
+  NS_LOG_RELEASE(this, mRefCnt, "nsHtml5UTF16Buffer");
+  if (mRefCnt == 0) {
+    mRefCnt = 1; /* stabilize */
+    delete this;
+    return 0;
+  }
+  return mRefCnt;                              
+}
--- a/parser/html/nsHtml5UTF16BufferHSupplement.h
+++ b/parser/html/nsHtml5UTF16BufferHSupplement.h
@@ -34,10 +34,15 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
   public:
     nsHtml5UTF16Buffer(PRInt32 size);
     nsHtml5UTF16Buffer(void* key);
     ~nsHtml5UTF16Buffer();
-    nsHtml5UTF16Buffer* next;
+    nsRefPtr<nsHtml5UTF16Buffer> next;
     void* key;
+    nsrefcnt AddRef();
+    nsrefcnt Release();
+  private:
+    nsAutoRefCnt mRefCnt;
+