Bug 364315 - Speculatively look for URLs in the document while the parser waits for a script to download and execute. This should show a decent speedup, especially on mobile. Currently, this only finds other <script>s to preload, but hopefully we'll extend it to images and stylesheets as well. r+sr=jst
☠☠ backed out by 996d2802d3f2 ☠ ☠
authorBlake Kaplan <mrbkap@gmail.com>
Mon, 29 Sep 2008 21:19:43 -0700
changeset 19898 961d90be2ba80d1ecafb00edc027908beca1e733
parent 19897 38a48d48587618f7a96c879afc96184593c5d9ea
child 19899 6c57ab47ea2f8b612abe2c3ecc9df16c88f7a33b
child 19915 996d2802d3f2762122f9cb25a4b2302d2facbb8f
push idunknown
push userunknown
push dateunknown
bugs364315
milestone1.9.1b1pre
Bug 364315 - Speculatively look for URLs in the document while the parser waits for a script to download and execute. This should show a decent speedup, especially on mobile. Currently, this only finds other <script>s to preload, but hopefully we'll extend it to images and stylesheets as well. r+sr=jst
content/base/src/nsScriptLoader.cpp
content/base/src/nsScriptLoader.h
parser/htmlparser/src/CParserContext.cpp
parser/htmlparser/src/CParserContext.h
parser/htmlparser/src/nsParser.cpp
parser/htmlparser/src/nsParser.h
parser/htmlparser/src/nsScanner.cpp
parser/htmlparser/src/nsScanner.h
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -91,16 +91,21 @@ public:
   {
     mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
   }
   void FireScriptEvaluated(nsresult aResult)
   {
     mElement->ScriptEvaluated(aResult, mElement, mIsInline);
   }
 
+  PRBool IsPreload()
+  {
+    return mElement == nsnull;
+  }
+
   nsCOMPtr<nsIScriptElement> mElement;
   PRPackedBool mLoading;             // Are we still waiting for a load to complete?
   PRPackedBool mDefer;               // Is execution defered?
   PRPackedBool mIsInline;            // Is the script inline or loaded?
   nsString mScriptText;              // Holds script for loaded scripts
   PRUint32 mJSVersion;
   nsCOMPtr<nsIURI> mURI;
   PRInt32 mLineNo;
@@ -185,16 +190,82 @@ IsScriptEventHandler(nsIScriptElement *a
 
     return PR_TRUE;
   }
 
   return PR_FALSE;
 }
 
 nsresult
+nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType)
+{
+  // Check that the containing page is allowed to load this URI.
+  nsresult rv = nsContentUtils::GetSecurityManager()->
+    CheckLoadURIWithPrincipal(mDocument->NodePrincipal(), aRequest->mURI,
+                              nsIScriptSecurityManager::ALLOW_CHROME);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // After the security manager, the content-policy stuff gets a veto
+  PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
+  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
+                                 aRequest->mURI,
+                                 mDocument->NodePrincipal(),
+                                 aRequest->mElement,
+                                 NS_LossyConvertUTF16toASCII(aType),
+                                 nsnull,    //extra
+                                 &shouldLoad,
+                                 nsContentUtils::GetContentPolicy(),
+                                 nsContentUtils::GetSecurityManager());
+  if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+    if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
+      return NS_ERROR_CONTENT_BLOCKED;
+    }
+    return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+  }
+
+  nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
+  nsCOMPtr<nsIStreamLoader> loader;
+
+  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->GetScriptGlobalObject()));
+  nsIDocShell *docshell = window->GetDocShell();
+
+  nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
+
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_NewChannel(getter_AddRefs(channel),
+                     aRequest->mURI, nsnull, loadGroup,
+                     prompter, nsIRequest::LOAD_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+  if (httpChannel) {
+    // HTTP content negotation has little value in this context.
+    httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+                                  NS_LITERAL_CSTRING("*/*"),
+                                  PR_FALSE);
+    httpChannel->SetReferrer(mDocument->GetDocumentURI());
+  }
+
+  rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return channel->AsyncOpen(loader, aRequest);
+}
+
+PRBool
+nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
+                                             nsIURI * const &aURI) const
+{
+  PRBool same;
+  return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
+         same;
+}
+
+nsresult
 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
 {
   // We need a document to evaluate scripts.
   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
 
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
     return NS_ERROR_NOT_AVAILABLE;
@@ -371,84 +442,65 @@ nsScriptLoader::ProcessScriptElement(nsI
       !nsContentUtils::IsChromeDoc(mDocument)) {
     NS_WARNING("Untrusted language called from non-chrome - ignored");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsCOMPtr<nsIContent> eltContent(do_QueryInterface(aElement));
   eltContent->SetScriptTypeID(typeID);
 
+  PRBool hadPendingRequests = !!GetFirstPendingRequest();
+
+  // Did we preload this request?
+  nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
+  nsRefPtr<nsScriptLoadRequest> request;
+  if (scriptURI) {
+    nsTArray<PreloadInfo>::index_type i =
+      mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
+    if (i != nsTArray<PreloadInfo>::NoIndex) {
+      request = mPreloads[i].mRequest;
+      request->mElement = aElement;
+      request->mJSVersion = version;
+      request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
+      mPreloads.RemoveElementAt(i);
+
+      if (!request->mLoading && !request->mDefer && !hadPendingRequests &&
+            ReadyToExecuteScripts() && nsContentUtils::IsSafeToRunScript()) {
+        return ProcessRequest(request);
+      }
+
+      // Not done loading yet. Move into the real requests queue and wait.
+      mRequests.AppendObject(request);
+
+      if (!request->mLoading && !hadPendingRequests && ReadyToExecuteScripts() &&
+          !request->mDefer) {
+        nsContentUtils::AddScriptRunner(new nsRunnableMethod<nsScriptLoader>(this,
+          &nsScriptLoader::ProcessPendingRequests));
+      }
+
+      return request->mDefer ? NS_OK : NS_ERROR_HTMLPARSER_BLOCK;
+    }
+  }
+
   // Create a request object for this script
-  nsRefPtr<nsScriptLoadRequest> request = new nsScriptLoadRequest(aElement, version);
+  request = new nsScriptLoadRequest(aElement, version);
   NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
 
   request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
 
-  PRBool hadPendingRequests = !!GetFirstPendingRequest();
-
   // First check to see if this is an external script
-  nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
   if (scriptURI) {
-    // Check that the containing page is allowed to load this URI.
-    rv = nsContentUtils::GetSecurityManager()->
-      CheckLoadURIWithPrincipal(mDocument->NodePrincipal(), scriptURI,
-                                nsIScriptSecurityManager::ALLOW_CHROME);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // After the security manager, the content-policy stuff gets a veto
-    PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
-    rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
-                                   scriptURI,
-                                   mDocument->NodePrincipal(),
-                                   aElement,
-                                   NS_LossyConvertUTF16toASCII(type),
-                                   nsnull,    //extra
-                                   &shouldLoad,
-                                   nsContentUtils::GetContentPolicy(),
-                                   nsContentUtils::GetSecurityManager());
-    if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
-      if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
-        return NS_ERROR_CONTENT_BLOCKED;
-      }
-      return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
-    }
-
     request->mURI = scriptURI;
     request->mIsInline = PR_FALSE;
     request->mLoading = PR_TRUE;
 
-    nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
-    nsCOMPtr<nsIStreamLoader> loader;
-
-    nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(globalObject));
-    nsIDocShell *docshell = window->GetDocShell();
-
-    nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
-
-    nsCOMPtr<nsIChannel> channel;
-    rv = NS_NewChannel(getter_AddRefs(channel),
-                       scriptURI, nsnull, loadGroup,
-                       prompter, nsIRequest::LOAD_NORMAL);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
-    if (httpChannel) {
-      // HTTP content negotation has little value in this context.
-      httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
-                                    NS_LITERAL_CSTRING("*/*"),
-                                    PR_FALSE);
-      httpChannel->SetReferrer(mDocument->GetDocumentURI());
+    rv = StartLoad(request, type);
+    if (NS_FAILED(rv)) {
+      return rv;
     }
-
-    rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = channel->AsyncOpen(loader, request);
-    NS_ENSURE_SUCCESS(rv, rv);
   } else {
     request->mLoading = PR_FALSE;
     request->mIsInline = PR_TRUE;
     request->mURI = mDocument->GetDocumentURI();
 
     request->mLineNo = aElement->GetScriptLineNumber();
 
     // If we've got existing pending requests, add ourselves
@@ -809,18 +861,21 @@ nsScriptLoader::OnStreamComplete(nsIStre
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
   nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
                                      aString);
   if (NS_FAILED(rv)) {
-    mRequests.RemoveObject(request);
-    FireScriptAvailable(rv, request);
+    if (!mRequests.RemoveObject(request)) {
+      mPreloads.RemoveElement(request, PreloadRequestComparator());
+    } else {
+      FireScriptAvailable(rv, request);
+    }
   }
 
   // Process our request and/or any pending ones
   ProcessPendingRequests();
 
   return NS_OK;
 }
 
@@ -855,32 +910,40 @@ nsScriptLoader::PrepareLoadedRequest(nsS
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
   if (aStringLen) {
     // Check the charset attribute to determine script charset.
     nsAutoString hintCharset;
-    aRequest->mElement->GetScriptCharset(hintCharset);
+    if (!aRequest->IsPreload()) {
+      aRequest->mElement->GetScriptCharset(hintCharset);
+    } else {
+      nsTArray<PreloadInfo>::index_type i =
+        mPreloads.IndexOf(aRequest, 0, PreloadRequestComparator());
+      NS_ASSERTION(i != mPreloads.NoIndex, "Incorrect preload bookkeeping");
+      hintCharset = mPreloads[i].mCharset;
+    }
     rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
                         aRequest->mScriptText);
 
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!ShouldExecuteScript(mDocument, channel)) {
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   // This assertion could fire errorously if we ran out of memory when
   // inserting the request in the array. However it's an unlikely case
   // so if you see this assertion it is likely something else that is
   // wrong, especially if you see it more than once.
-  NS_ASSERTION(mRequests.IndexOf(aRequest) >= 0,
+  NS_ASSERTION(mRequests.IndexOf(aRequest) >= 0 ||
+               mPreloads.Contains(aRequest, PreloadRequestComparator()),
                "aRequest should be pending!");
 
   // Mark this as loaded
   aRequest->mLoading = PR_FALSE;
 
   return NS_OK;
 }
 
@@ -913,14 +976,38 @@ nsScriptLoader::ShouldExecuteScript(nsID
   rv = channelPrincipal->Subsumes(docPrincipal, &subsumes);
   return NS_SUCCEEDED(rv) && subsumes;
 }
 
 void
 nsScriptLoader::EndDeferringScripts()
 {
   mDeferEnabled = PR_FALSE;
-  for (PRUint32 i = 0; i < mRequests.Count(); ++i) {
+  for (PRUint32 i = 0; i < (PRUint32)mRequests.Count(); ++i) {
     mRequests[i]->mDefer = PR_FALSE;
   }
 
   ProcessPendingRequests();
 }
+
+void
+nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
+                           const nsAString &aType)
+{
+  nsRefPtr<nsScriptLoadRequest> request = new nsScriptLoadRequest(nsnull, 0);
+  if (!request) {
+    return;
+  }
+
+  request->mURI = aURI;
+  request->mIsInline = PR_FALSE;
+  request->mLoading = PR_TRUE;
+  request->mDefer = PR_FALSE; // This is computed later when we go to execute the
+                              // script.
+  nsresult rv = StartLoad(request, aType);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  PreloadInfo *pi = mPreloads.AppendElement();
+  pi->mRequest = request;
+  pi->mCharset = aCharset;
+}
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -200,18 +200,33 @@ public:
    * Stops defering scripts and immediately processes the mDeferredRequests
    * queue.
    *
    * WARNING: This function will syncronously execute content scripts, so be
    * prepared that the world might change around you.
    */
   void EndDeferringScripts();
 
+  /**
+   * Adds aURI to the preload list and starts loading it.
+   *
+   * @param aURI The URI of the external script.
+   * @param aCharset The charset parameter for the script.
+   * @param aType The type parameter for the script.
+   */
+  virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
+                          const nsAString &aType);
+
 protected:
   /**
+   * Start a load for aRequest's URI.
+   */
+  nsresult StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType);
+
+  /**
    * Process any pending requests asyncronously (i.e. off an event) if there
    * are any. Note that this is a no-op if there aren't any currently pending
    * requests.
    */
   virtual void ProcessPendingRequestsAsync();
 
   /**
    * If true, the loader is ready to execute scripts, and so are all its
@@ -248,16 +263,35 @@ protected:
                                 const PRUint8* aString);
 
   // Returns the first pending (non deferred) request
   nsScriptLoadRequest* GetFirstPendingRequest();
 
   nsIDocument* mDocument;                   // [WEAK]
   nsCOMArray<nsIScriptLoaderObserver> mObservers;
   nsCOMArray<nsScriptLoadRequest> mRequests;
+
+  // In mRequests, the additional information here is stored by the element.
+  struct PreloadInfo {
+    nsRefPtr<nsScriptLoadRequest> mRequest;
+    nsString mCharset;
+  };
+
+  struct PreloadRequestComparator {
+    PRBool Equals(const PreloadInfo &aPi, nsScriptLoadRequest * const &aRequest)
+        const
+    {
+      return aRequest == aPi.mRequest;
+    }
+  };
+  struct PreloadURIComparator {
+    PRBool Equals(const PreloadInfo &aPi, nsIURI * const &aURI) const;
+  };
+  nsTArray<PreloadInfo> mPreloads;
+
   nsCOMPtr<nsIScriptElement> mCurrentScript;
   // XXXbz do we want to cycle-collect these or something?  Not sure.
   nsTArray< nsRefPtr<nsScriptLoader> > mPendingChildLoaders;
   PRUint32 mBlockerCount;
   PRPackedBool mEnabled;
   PRPackedBool mDeferEnabled;
 };
 
--- a/parser/htmlparser/src/CParserContext.cpp
+++ b/parser/htmlparser/src/CParserContext.cpp
@@ -58,17 +58,17 @@ CParserContext::CParserContext(nsScanner
     mScanner(aScanner),
     mDTDMode(eDTDMode_unknown),
     mStreamListenerState(eNone),
     mContextType(eCTNone),
     mAutoDetectStatus(aStatus),
     mParserCommand(aCommand),
     mMultipart(PR_TRUE),
     mCopyUnused(aCopyUnused),
-    mTransferBufferSize(eTransferBufferSize)
+    mNumConsumed(0)
 { 
   MOZ_COUNT_CTOR(CParserContext); 
 } 
 
 CParserContext::~CParserContext()
 {
   // It's ok to simply ingore the PrevContext.
   MOZ_COUNT_DTOR(CParserContext);
--- a/parser/htmlparser/src/CParserContext.h
+++ b/parser/htmlparser/src/CParserContext.h
@@ -55,54 +55,51 @@
 #include "nsAutoPtr.h"
 
 /**
  * Note that the parser is given FULL access to all
  * data in a parsercontext. Hey, that what it's for!
  */
 
 class CParserContext {
-
 public:
-
-    enum {eTransferBufferSize=4096};
-    enum eContextType {eCTNone,eCTURL,eCTString,eCTStream};
+   enum eContextType {eCTNone,eCTURL,eCTString,eCTStream};
 
-   CParserContext(  nsScanner* aScanner,
-                    void* aKey=0, 
-                    eParserCommands aCommand=eViewNormal,
-                    nsIRequestObserver* aListener=0, 
-                    nsIDTD *aDTD=0, 
-                    eAutoDetectResult aStatus=eUnknownDetect, 
-                    PRBool aCopyUnused=PR_FALSE); 
-    
+   CParserContext(nsScanner* aScanner,
+                  void* aKey = 0,
+                  eParserCommands aCommand = eViewNormal,
+                  nsIRequestObserver* aListener = 0,
+                  nsIDTD* aDTD = 0,
+                  eAutoDetectResult aStatus = eUnknownDetect,
+                  PRBool aCopyUnused = PR_FALSE);
+
     ~CParserContext();
 
     nsresult GetTokenizer(PRInt32 aType,
                           nsIContentSink* aSink,
                           nsITokenizer*& aTokenizer);
     void  SetMimeType(const nsACString& aMimeType);
 
     nsCOMPtr<nsIRequest> mRequest; // provided by necko to differnciate different input streams
                                    // why is mRequest strongly referenced? see bug 102376.
-    nsCOMPtr<nsIDTD>	 mDTD;
+    nsCOMPtr<nsIDTD>     mDTD;
     nsCOMPtr<nsIRequestObserver> mListener;
-    nsAutoArrayPtr<char> mTransferBuffer;
     void*                mKey;
     nsCOMPtr<nsITokenizer> mTokenizer;
     CParserContext*      mPrevContext;
     nsAutoPtr<nsScanner> mScanner;
-    
+
     nsCString            mMimeType;
     nsDTDMode            mDTDMode;
-    
+
     eParserDocType       mDocType;
-    eStreamState         mStreamListenerState; //this is really only here for debug purposes.
+    eStreamState         mStreamListenerState;
     eContextType         mContextType;
     eAutoDetectResult    mAutoDetectStatus;
-    eParserCommands      mParserCommand;   //tells us to viewcontent/viewsource/viewerrors...
+    eParserCommands      mParserCommand;
 
     PRPackedBool         mMultipart;
     PRPackedBool         mCopyUnused;
-    PRUint32             mTransferBufferSize;
+
+    PRUint32             mNumConsumed;
 };
 
 #endif
--- a/parser/htmlparser/src/nsParser.cpp
+++ b/parser/htmlparser/src/nsParser.cpp
@@ -1,10 +1,10 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set sw=2 ts=2 et tw=78: */
+/* 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/
  *
@@ -47,25 +47,33 @@
 #include "nsIChannel.h"
 #include "nsICachingChannel.h"
 #include "nsICacheEntryDescriptor.h"
 #include "nsICharsetAlias.h"
 #include "nsICharsetConverterManager.h"
 #include "nsIInputStream.h"
 #include "CNavDTD.h"
 #include "prenv.h"
+#include "prlock.h"
+#include "prcvar.h"
+#include "nsAutoLock.h"
 #include "nsParserCIID.h"
 #include "nsReadableUtils.h"
 #include "nsCOMPtr.h"
 #include "nsExpatDriver.h"
 #include "nsIServiceManager.h"
 #include "nsICategoryManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIFragmentContentSink.h"
 #include "nsStreamUtils.h"
+#include "nsHTMLTokenizer.h"
+#include "nsIDocument.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsDataHashtable.h"
 
 #ifdef MOZ_VIEW_SOURCE
 #include "nsViewSourceHTML.h"
 #endif
 
 #define NS_PARSER_FLAG_PARSER_ENABLED         0x00000002
 #define NS_PARSER_FLAG_OBSERVERS_ENABLED      0x00000004
 #define NS_PARSER_FLAG_PENDING_CONTINUE_EVENT 0x00000008
@@ -150,16 +158,488 @@ public:
   {
     mParser->HandleParserContinueEvent(this);
     return NS_OK;
   }
 };
 
 //-------------- End ParseContinue Event Definition ------------------------
 
+template <class Type>
+class Holder {
+public:
+  typedef void (*Reaper)(Type *);
+
+  Holder(Reaper aReaper)
+    : mHoldee(nsnull), mReaper(aReaper)
+  {
+  }
+
+  ~Holder() {
+    if (mHoldee) {
+      mReaper(mHoldee);
+    }
+  }
+
+  Type *get() {
+    return mHoldee;
+  }
+  const Holder &operator =(Type *aHoldee) {
+    if (mHoldee && aHoldee != mHoldee) {
+      mReaper(mHoldee);
+    }
+    mHoldee = aHoldee;
+    return *this;
+  }
+
+private:
+  Type *mHoldee;
+  Reaper mReaper;
+};
+
+class nsSpeculativeScriptThread : public nsIRunnable {
+public:
+  nsSpeculativeScriptThread(nsTokenAllocator *aTokenAllocator)
+    : mLock(PR_DestroyLock),
+      mCVar(PR_DestroyCondVar),
+      mKeepParsing(0),
+      mCurrentlyParsing(0),
+      mNumURIs(0),
+      mNumConsumed(0),
+      mTokenAllocator(aTokenAllocator) {
+  }
+
+  ~nsSpeculativeScriptThread() {
+    if (mThread) {
+      mThread->Shutdown();
+    }
+  }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+
+  nsresult StartParsing(nsParser *aParser);
+  void StopParsing(PRBool aFromDocWrite);
+
+  enum PrefetchType { SCRIPT, STYLESHEET, IMAGE };
+  struct PrefetchEntry {
+    PrefetchType type;
+    nsString uri;
+    nsString charset;
+    nsString elementType;
+  };
+
+  nsIDocument *GetDocument() {
+    NS_ASSERTION(NS_IsMainThread(), "Potential threadsafety hazard");
+    return mDocument;
+  }
+
+  PRBool Parsing() {
+    return mCurrentlyParsing;
+  }
+
+  CParserContext *Context() {
+    return mContext;
+  }
+
+  typedef nsDataHashtable<nsCStringHashKey, PRBool> PreloadedType;
+  PreloadedType& GetPreloadedURIs() {
+    return mPreloadedURIs;
+  }
+
+private:
+
+  nsresult ProcessToken(CToken *aToken);
+
+  void AddToPrefetchList(const nsAString &src,
+                         const nsAString &charset,
+                         const nsAString &elementType,
+                         PrefetchType type);
+
+  // The following members are shared across the main thread and the
+  // speculatively parsing thread.
+  Holder<PRLock> mLock;
+  Holder<PRCondVar> mCVar;
+
+  PRUint32 mKeepParsing;
+  PRUint32 mCurrentlyParsing;
+  nsRefPtr<nsHTMLTokenizer> mTokenizer;
+  nsAutoPtr<nsScanner> mScanner;
+
+  enum { kBatchPrefetchURIs = 5 };
+  nsAutoTArray<PrefetchEntry, kBatchPrefetchURIs> mURIs;
+  PRUint16 mNumURIs;
+
+  // Number of characters consumed by the last speculative parse.
+  PRUint32 mNumConsumed;
+
+  // These members are only accessed on the main thread.
+  nsCOMPtr<nsIThread> mThread;
+  nsCOMPtr<nsIDocument> mDocument;
+  CParserContext *mContext;
+  PreloadedType mPreloadedURIs;
+
+  // These members are only accessed on the speculatively parsing thread.
+  nsTokenAllocator *mTokenAllocator;
+};
+
+class nsPreloadURIs : public nsIRunnable {
+public:
+  nsPreloadURIs(nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry, 5> &aURIs,
+                nsSpeculativeScriptThread *aScriptThread)
+    : mURIs(aURIs),
+      mScriptThread(aScriptThread) {
+  }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+
+  static void PreloadURIs(const nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry, 5> &aURIs,
+                          nsSpeculativeScriptThread *aScriptThread);
+
+private:
+  nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry, 5> mURIs;
+  nsRefPtr<nsSpeculativeScriptThread> mScriptThread;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsPreloadURIs, nsIRunnable)
+
+NS_IMETHODIMP
+nsPreloadURIs::Run()
+{
+  PreloadURIs(mURIs, mScriptThread);
+  return NS_OK;
+}
+
+void
+nsPreloadURIs::PreloadURIs(const nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry, 5> &aURIs,
+                           nsSpeculativeScriptThread *aScriptThread)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Touching non-threadsafe objects off thread");
+
+  nsIDocument *doc = aScriptThread->GetDocument();
+  NS_ASSERTION(doc, "We shouldn't have started preloading without a document");
+
+  // Note: Per the code in the HTML content sink, we should be keeping track
+  // of each <base href> as it comes. However, because we do our speculative
+  // parsing off the main thread, this is hard to emulate. For now, just load
+  // the URIs using the document's base URI at the potential cost of being
+  // wrong and having to re-load a given relative URI later.
+  nsIURI *base = doc->GetBaseURI();
+  const nsCString &charset = doc->GetDocumentCharacterSet();
+  nsSpeculativeScriptThread::PreloadedType &alreadyPreloaded =
+    aScriptThread->GetPreloadedURIs();
+  for (PRUint32 i = 0, e = aURIs.Length(); i < e; ++i) {
+    const nsSpeculativeScriptThread::PrefetchEntry &pe = aURIs[i];
+    if (pe.type != nsSpeculativeScriptThread::SCRIPT) {
+      continue;
+    }
+
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = NS_NewURI(getter_AddRefs(uri), pe.uri, charset.get(), base);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to create a URI");
+      continue;
+    }
+
+    nsCAutoString spec;
+    uri->GetSpec(spec);
+    PRBool answer;
+    if (alreadyPreloaded.Get(spec, &answer)) {
+      // Already preloaded. Don't preload again.
+      continue;
+    }
+
+    alreadyPreloaded.Put(spec, PR_TRUE);
+
+    doc->ScriptLoader()->PreloadURI(uri, pe.charset, pe.elementType);
+  }
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsSpeculativeScriptThread, nsIRunnable)
+
+NS_IMETHODIMP
+nsSpeculativeScriptThread::Run()
+{
+  nsScannerIterator start;
+  mScanner->CurrentPosition(start);
+  mTokenizer->WillTokenize(PR_FALSE, mTokenAllocator);
+  while (mKeepParsing) {
+    PRBool flushTokens = PR_FALSE;
+    nsresult rv = mTokenizer->ConsumeToken(*mScanner, flushTokens);
+    if (rv == kEOF) {
+      break;
+    }
+
+    // TODO Don't pop the tokens.
+    CToken *token;
+    while (mKeepParsing && NS_SUCCEEDED(rv) && (token = mTokenizer->PopToken())) {
+      rv = ProcessToken(token);
+    }
+  }
+  mTokenizer->DidTokenize(PR_FALSE);
+
+  nsAutoLock al(mLock.get());
+
+  nsScannerIterator end;
+  mScanner->CurrentPosition(end);
+
+  mNumConsumed = Distance(start, end);
+
+  mCurrentlyParsing = 0;
+  PR_NotifyCondVar(mCVar.get());
+  return NS_OK;
+}
+
+nsresult
+nsSpeculativeScriptThread::StartParsing(nsParser *aParser)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Called on the wrong thread");
+  NS_ASSERTION(!mCurrentlyParsing, "Bad race happening");
+
+  nsIContentSink *sink = aParser->GetContentSink();
+  if (!sink) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(sink->GetTarget());
+  if (!doc) {
+    return NS_OK;
+  }
+
+  nsAutoString toScan;
+  CParserContext *context = aParser->PeekContext();
+  if (!mThread) {
+    nsresult rv = NS_NewThread(getter_AddRefs(mThread), nsnull);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mLock = PR_NewLock();
+    if (!mLock.get()) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    mCVar = PR_NewCondVar(mLock.get());
+    if (!mCVar.get()) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    if (!mPreloadedURIs.Init(15)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    mTokenizer = new nsHTMLTokenizer(context->mDTDMode, context->mDocType,
+                                     context->mParserCommand, 0);
+    if (!mTokenizer) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    mTokenizer->CopyState(context->mTokenizer);
+    context->mScanner->CopyUnusedData(toScan);
+  } else if (context == mContext) {
+    // Don't parse the same part of the document twice.
+    nsScannerIterator end;
+    context->mScanner->EndReading(end);
+
+    nsScannerIterator start;
+    context->mScanner->CurrentPosition(start);
+
+    if (mNumConsumed > context->mNumConsumed) {
+      // We consumed more the last time we tried speculatively parsing than we
+      // did the last time we actually parsed. 
+      PRUint32 distance = Distance(start, end);
+      start.advance(PR_MIN(mNumConsumed - context->mNumConsumed, distance));
+    }
+
+    if (start == end) {
+      // We're at the end of this context's buffer, nothing else to do.
+      return NS_OK;
+    }
+
+    CopyUnicodeTo(start, end, toScan);
+  } else {
+    // Grab all of the context.
+    context->mScanner->CopyUnusedData(toScan);
+    if (toScan.IsEmpty()) {
+      // Nothing to parse, don't do anything.
+      return NS_OK;
+    }
+  }
+
+  nsCAutoString charset;
+  PRInt32 source;
+  aParser->GetDocumentCharset(charset, source);
+
+  mScanner = new nsScanner(toScan, charset, source);
+  if (!mScanner) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  mDocument.swap(doc);
+  mKeepParsing = 1;
+  mCurrentlyParsing = 1;
+  mContext = context;
+  return mThread->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void
+nsSpeculativeScriptThread::StopParsing(PRBool aFromDocWrite)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Can't stop parsing from another thread");
+
+  if (!mThread) {
+    // If we bailed early out of StartParsing, don't do anything.
+    return;
+  }
+
+  {
+    nsAutoLock al(mLock.get());
+
+    mKeepParsing = 0;
+    if (mCurrentlyParsing) {
+      PR_WaitCondVar(mCVar.get(), PR_INTERVAL_NO_TIMEOUT);
+      NS_ASSERTION(!mCurrentlyParsing, "Didn't actually stop parsing?");
+    }
+  }
+
+  // The thread is now idle. It is now safe to touch mContext on the main
+  // thread.
+  if (mNumURIs) {
+    nsPreloadURIs::PreloadURIs(mURIs, this);
+    mNumURIs = 0;
+    mURIs.Clear();
+  }
+
+  // Note: Currently, we pop the tokens off (see the comment in Run) so this
+  // isn't a problem. If and when we actually use the tokens created
+  // off-thread, we'll need to use aFromDocWrite for real.
+  (void)aFromDocWrite;
+}
+
+nsresult
+nsSpeculativeScriptThread::ProcessToken(CToken *aToken)
+{
+  // Only called on the speculative script thread.
+
+  CHTMLToken *token = static_cast<CHTMLToken *>(aToken);
+  switch (static_cast<eHTMLTokenTypes>(token->GetTokenType())) {
+    case eToken_start: {
+        CStartToken *start = static_cast<CStartToken *>(aToken);
+        nsHTMLTag tag = static_cast<nsHTMLTag>(start->GetTypeID());
+        PRInt16 attrs = start->GetAttributeCount();
+        PRInt16 i = 0;
+        nsAutoString src;
+        nsAutoString elementType;
+        nsAutoString charset;
+        PrefetchType ptype;
+
+        switch (tag) {
+#if 0 // TODO Support stylesheet and image preloading.
+          case eHTMLTag_link: {
+            // If this is a <link rel=stylesheet> find the src.
+            PRBool isRelStylesheet = PR_FALSE;
+            for (; i < attrs; ++i) {
+              CAttributeToken *attr = static_cast<CAttributeToken *>(mTokenizer->PopToken());
+              NS_ASSERTION(attr->GetTokenType() == eToken_attribute, "Weird token");
+
+              if (attr->GetKey().EqualsLiteral("rel")) {
+                if (!attr->GetValue().EqualsLiteral("stylesheet")) {
+                  IF_FREE(attr, mTokenAllocator);
+                  break;
+                }
+                isRelStylesheet = PR_TRUE;
+              } else if (attr->GetKey().EqualsLiteral("src")) {
+                src.Assign(attr->GetValue());
+                if (isRelStylesheet) {
+                  IF_FREE(attr, mTokenAllocator);
+                  break;
+                }
+              }
+
+              IF_FREE(attr, mTokenAllocator);
+            }
+
+            if (isRelStylesheet && !src.IsEmpty()) {
+              AddToPrefetchList(src, STYLESHEET);
+            }
+            break;
+          }
+
+          case eHTMLTag_style:
+            ptype = STYLESHEET;
+          case eHTMLTag_img:
+            if (tag == eHTMLTag_img)
+              ptype = IMAGE;
+#endif
+          case eHTMLTag_script:
+            if (tag == eHTMLTag_script)
+              ptype = SCRIPT;
+
+            for (; i < attrs; ++i) {
+              CAttributeToken *attr = static_cast<CAttributeToken *>(mTokenizer->PopToken());
+              NS_ASSERTION(attr->GetTokenType() == eToken_attribute, "Weird token");
+
+              if (attr->GetKey().EqualsLiteral("src")) {
+                src.Assign(attr->GetValue());
+              } else if (attr->GetKey().EqualsLiteral("charset")) {
+                charset.Assign(attr->GetValue());
+              } else if (attr->GetKey().EqualsLiteral("type")) {
+                elementType.Assign(attr->GetValue());
+              }
+              IF_FREE(attr, mTokenAllocator);
+            }
+
+            if (!src.IsEmpty()) {
+              AddToPrefetchList(src, charset, elementType, ptype);
+            }
+            break;
+
+          default:
+            break;
+        }
+
+        for (; i < attrs; ++i) {
+          CToken *attr = mTokenizer->PopToken();
+          if (!attr) {
+            break;
+          }
+          NS_ASSERTION(attr->GetTokenType() == eToken_attribute, "Weird token");
+          IF_FREE(attr, mTokenAllocator);
+        }
+
+        break;
+      }
+
+    default:
+      break;
+  }
+
+  IF_FREE(aToken, mTokenAllocator);
+  return NS_OK;
+}
+
+void
+nsSpeculativeScriptThread::AddToPrefetchList(const nsAString &src,
+                                      const nsAString &charset,
+                                      const nsAString &elementType,
+                                      PrefetchType type)
+{
+  PrefetchEntry *pe = mURIs.InsertElementAt(mNumURIs++);
+  pe->type = type;
+  pe->uri = src;
+  pe->charset = charset;
+  pe->elementType = elementType;
+
+  if (mNumURIs == kBatchPrefetchURIs) {
+    nsCOMPtr<nsIRunnable> r = new nsPreloadURIs(mURIs, this);
+
+    mNumURIs = 0;
+    mURIs.Clear();
+    NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
+  }
+}
+
 nsICharsetAlias* nsParser::sCharsetAliasService = nsnull;
 nsICharsetConverterManager* nsParser::sCharsetConverterManager = nsnull;
 
 /**
  *  This gets called when the htmlparser module is initialized.
  */
 // static
 nsresult
@@ -316,16 +796,20 @@ nsParser::Cleanup()
     delete mParserContext;
     mParserContext = pc;
   }
 
   // It should not be possible for this flag to be set when we are getting
   // destroyed since this flag implies a pending nsParserContinueEvent, which
   // has an owning reference to |this|.
   NS_ASSERTION(!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT), "bad");
+  if (mSpeculativeScriptThread) {
+    mSpeculativeScriptThread->StopParsing(PR_FALSE);
+    mSpeculativeScriptThread = nsnull;
+  }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsParser)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsParser)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSink)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -1002,16 +1486,36 @@ nsParser::DidBuildModel(nsresult anError
       //Ref. to bug 61462.
       mParserContext->mRequest = 0;
     }
   }
 
   return result;
 }
 
+void
+nsParser::SpeculativelyParse()
+{
+  if (mParserContext->mParserCommand == eViewNormal &&
+      !mParserContext->mMimeType.EqualsLiteral("text/html")) {
+    return;
+  }
+
+  if (!mSpeculativeScriptThread) {
+    mSpeculativeScriptThread = new nsSpeculativeScriptThread(&mTokenAllocator);
+    if (!mSpeculativeScriptThread) {
+      return;
+    }
+  }
+
+  nsresult rv = mSpeculativeScriptThread->StartParsing(this);
+  if (NS_FAILED(rv)) {
+    mSpeculativeScriptThread = nsnull;
+  }
+}
 
 /**
  * This method adds a new parser context to the list,
  * pushing the current one to the next position.
  *
  * @param   ptr to new context
  */
 void
@@ -1102,16 +1606,19 @@ nsParser::Terminate(void)
   mInternalState = result = NS_ERROR_HTMLPARSER_STOPPARSING;
 
   // CancelParsingEvents must be called to avoid leaking the nsParser object
   // @see bug 108049
   // If NS_PARSER_FLAG_PENDING_CONTINUE_EVENT is set then CancelParsingEvents
   // will reset it so DidBuildModel will call DidBuildModel on the DTD. Note:
   // The IsComplete() call inside of DidBuildModel looks at the pendingContinueEvents flag.
   CancelParsingEvents();
+  if (mSpeculativeScriptThread) {
+    mSpeculativeScriptThread->StopParsing(PR_FALSE);
+  }
 
   // If we got interrupted in the middle of a document.write, then we might
   // have more than one parser context on our parsercontext stack. This has
   // the effect of making DidBuildModel a no-op, meaning that we never call
   // our sink's DidBuildModel and break the reference cycle, causing a leak.
   // Since we're getting terminated, we manually clean up our context stack.
   while (mParserContext && mParserContext->mPrevContext) {
     CParserContext *prev = mParserContext->mPrevContext;
@@ -1158,16 +1665,20 @@ nsParser::ContinueInterruptedParsing()
   nsCOMPtr<nsIParser> kungFuDeathGrip(this);
 
 #ifdef DEBUG
   if (!(mFlags & NS_PARSER_FLAG_PARSER_ENABLED)) {
     NS_WARNING("Don't call ContinueInterruptedParsing on a blocked parser.");
   }
 #endif
 
+  if (mSpeculativeScriptThread) {
+    mSpeculativeScriptThread->StopParsing(PR_FALSE);
+  }
+
   PRBool isFinalChunk = mParserContext &&
                         mParserContext->mStreamListenerState == eOnStop;
 
   result = ResumeParse(PR_TRUE, isFinalChunk); // Ref. bug 57999
 
   if (result != NS_OK) {
     result=mInternalState;
   }
@@ -1290,16 +1801,17 @@ nsParser::SetCanInterrupt(PRBool aCanInt
 NS_IMETHODIMP
 nsParser::Parse(nsIURI* aURL,
                 nsIRequestObserver* aListener,
                 void* aKey,
                 nsDTDMode aMode)
 {
 
   NS_PRECONDITION(aURL, "Error: Null URL given");
+  NS_ASSERTION(!mSpeculativeScriptThread, "Can't reuse a parser like this");
 
   nsresult result=kBadURL;
   mObserver = aListener;
 
   if (aURL) {
     nsCAutoString spec;
     nsresult rv = aURL->GetSpec(spec);
     if (rv != NS_OK) {
@@ -1357,16 +1869,20 @@ nsParser::Parse(const nsAString& aSource
     // Nothing is being passed to the parser so return
     // immediately. mUnusedInput will get processed when
     // some data is actually passed in.
     // But if this is the last call, make sure to finish up
     // stuff correctly.
     return result;
   }
 
+  if (mSpeculativeScriptThread) {
+    mSpeculativeScriptThread->StopParsing(PR_TRUE);
+  }
+
   // Hack to pass on to the dtd the caller's desire to
   // parse a fragment without worrying about containment rules
   if (aMode == eDTDMode_fragment)
     mCommand = eViewFragment;
 
   // Maintain a reference to ourselves so we don't go away
   // till we're completely done.
   nsCOMPtr<nsIParser> kungFuDeathGrip(this);
@@ -1477,16 +1993,18 @@ nsParser::ParseFragment(const nsAString&
   nsresult result = NS_OK;
   nsAutoString  theContext;
   PRUint32 theCount = aTagStack.Length();
   PRUint32 theIndex = 0;
 
   // Disable observers for fragments
   mFlags &= ~NS_PARSER_FLAG_OBSERVERS_ENABLED;
 
+  NS_ASSERTION(!mSpeculativeScriptThread, "Can't reuse a parser like this");
+
   for (theIndex = 0; theIndex < theCount; theIndex++) {
     theContext.AppendLiteral("<");
     theContext.Append(aTagStack[theCount - theIndex - 1]);
     theContext.AppendLiteral(">");
   }
 
   if (theCount == 0) {
     // Ensure that the buffer is not empty. Because none of the DTDs care
@@ -1617,16 +2135,18 @@ nsParser::ResumeParse(PRBool allowIterat
 {
   nsresult result = NS_OK;
 
   if ((mFlags & NS_PARSER_FLAG_PARSER_ENABLED) &&
       mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
     MOZ_TIMER_DEBUGLOG(("Start: Parse Time: nsParser::ResumeParse(), this=%p\n", this));
     MOZ_TIMER_START(mParseTime);
 
+    NS_ASSERTION(!mSpeculativeScriptThread || !mSpeculativeScriptThread->Parsing(), "Bad races happening");
+
     result = WillBuildModel(mParserContext->mScanner->GetFilename());
     if (NS_FAILED(result)) {
       mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE;
       return result;
     }
 
     if (mParserContext->mDTD) {
       mParserContext->mDTD->WillResumeParse(mSink);
@@ -1666,16 +2186,17 @@ nsParser::ResumeParse(PRBool allowIterat
         // If we're told to block the parser, we disable all further parsing
         // (and cache any data coming in) until the parser is re-enabled.
         if (NS_ERROR_HTMLPARSER_BLOCK == result) {
           if (mParserContext->mDTD) {
             mParserContext->mDTD->WillInterruptParse(mSink);
           }
 
           BlockParser();
+          SpeculativelyParse();
           return NS_OK;
         }
         if (NS_ERROR_HTMLPARSER_STOPPARSING == result) {
           // Note: Parser Terminate() calls DidBuildModel.
           if (mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
             DidBuildModel(mStreamStatus);
             mInternalState = result;
           }
@@ -2255,16 +2776,21 @@ nsParser::OnDataAvailable(nsIRequest *re
 
   while (theContext && theContext->mRequest != request) {
     theContext = theContext->mPrevContext;
   }
 
   if (theContext) {
     theContext->mStreamListenerState = eOnDataAvail;
 
+    if ((mFlags & NS_PARSER_FLAG_PARSER_ENABLED) &&
+        mSpeculativeScriptThread) {
+      mSpeculativeScriptThread->StopParsing(PR_FALSE);
+    }
+
     if (eInvalidDetect == theContext->mAutoDetectStatus) {
       if (theContext->mScanner) {
         nsScannerIterator iter;
         theContext->mScanner->EndReading(iter);
         theContext->mScanner->SetPosition(iter, PR_TRUE);
       }
     }
 
@@ -2299,16 +2825,20 @@ nsParser::OnDataAvailable(nsIRequest *re
  *  has been collected from the net.
  */
 nsresult
 nsParser::OnStopRequest(nsIRequest *request, nsISupports* aContext,
                         nsresult status)
 {
   nsresult rv = NS_OK;
 
+  if (mSpeculativeScriptThread) {
+    mSpeculativeScriptThread->StopParsing(PR_FALSE);
+  }
+
   if (eOnStart == mParserContext->mStreamListenerState) {
     // If you're here, then OnDataAvailable() never got called.  Prior
     // to necko, we never dealt with this case, but the problem may
     // have existed.  Everybody can live with an empty input stream, so
     // just resume parsing.
     rv = ResumeParse(PR_TRUE, PR_TRUE);
   }
 
@@ -2412,36 +2942,38 @@ nsresult nsParser::Tokenize(PRBool aIsFi
       // Reset since the tokens have been flushed.
       mFlags &= ~NS_PARSER_FLAG_FLUSH_TOKENS;
     }
 
     PRBool flushTokens = PR_FALSE;
 
     MOZ_TIMER_START(mTokenizeTime);
 
+    mParserContext->mNumConsumed = 0;
+
     WillTokenize(aIsFinalChunk);
     while (NS_SUCCEEDED(result)) {
-      mParserContext->mScanner->Mark();
+      mParserContext->mNumConsumed += mParserContext->mScanner->Mark();
       result = theTokenizer->ConsumeToken(*mParserContext->mScanner,
                                           flushTokens);
       if (NS_FAILED(result)) {
         mParserContext->mScanner->RewindToMark();
         if (kEOF == result){
           break;
         }
         if (NS_ERROR_HTMLPARSER_STOPPARSING == result) {
           result = Terminate();
           break;
         }
       } else if (flushTokens && (mFlags & NS_PARSER_FLAG_OBSERVERS_ENABLED)) {
         // I added the extra test of NS_PARSER_FLAG_OBSERVERS_ENABLED to fix Bug# 23931.
         // Flush tokens on seeing </SCRIPT> -- Ref: Bug# 22485 --
         // Also remember to update the marked position.
         mFlags |= NS_PARSER_FLAG_FLUSH_TOKENS;
-        mParserContext->mScanner->Mark();
+        mParserContext->mNumConsumed += mParserContext->mScanner->Mark();
         break;
       }
     }
     DidTokenize(aIsFinalChunk);
 
     MOZ_TIMER_STOP(mTokenizeTime);
   } else {
     result = mInternalState = NS_ERROR_HTMLPARSER_BADTOKENIZER;
--- a/parser/htmlparser/src/nsParser.h
+++ b/parser/htmlparser/src/nsParser.h
@@ -90,16 +90,17 @@
 #include "nsIUnicharStreamListener.h"
 #include "nsCycleCollectionParticipant.h"
 
 class nsICharsetConverterManager;
 class nsICharsetAlias;
 class nsIDTD;
 class nsScanner;
 class nsIProgressEventSink;
+class nsSpeculativeScriptThread;
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4275 )
 #endif
 
 
 class nsParser : public nsIParser,
                  public nsIStreamListener{
@@ -401,17 +402,19 @@ class nsParser : public nsIParser,
 
     /**
      * 
      * @update	gess5/18/98
      * @param 
      * @return
      */
     nsresult DidBuildModel(nsresult anErrorCode);
-    
+
+    void SpeculativelyParse();
+
 private:
 
     /*******************************************
       These are the tokenization methods...
      *******************************************/
 
     /**
      *  Part of the code sandwich, this gets called right before
@@ -452,16 +455,17 @@ protected:
     // And now, some data members...
     //*********************************************
     
       
     CParserContext*              mParserContext;
     nsCOMPtr<nsIRequestObserver> mObserver;
     nsCOMPtr<nsIContentSink>     mSink;
     nsIRunnable*                 mContinueEvent;  // weak ref
+    nsRefPtr<nsSpeculativeScriptThread> mSpeculativeScriptThread;
    
     nsCOMPtr<nsIParserFilter> mParserFilter;
     nsTokenAllocator          mTokenAllocator;
     
     eParserCommands     mCommand;
     nsresult            mInternalState;
     PRInt32             mStreamStatus;
     PRInt32             mCharsetSource;
--- a/parser/htmlparser/src/nsScanner.cpp
+++ b/parser/htmlparser/src/nsScanner.cpp
@@ -141,26 +141,25 @@ nsScanner::nsScanner(nsString& aFilename
   mFirstNonWhitespacePosition = -1;
   mCountRemaining = 0;
 
   mUnicodeDecoder = 0;
   mCharsetSource = kCharsetUninitialized;
   SetDocumentCharset(aCharset, aSource);
 }
 
-nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , PRInt32 aSource) {
-
-  nsresult res = NS_OK;
-
-  if( aSource < mCharsetSource) // priority is lower the the current one , just
-    return res;
+nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , PRInt32 aSource)
+{
+  if (aSource < mCharsetSource) // priority is lower the the current one , just
+    return NS_OK;
 
   nsICharsetAlias* calias = nsParser::GetCharsetAliasService();
   NS_ASSERTION(calias, "Must have the charset alias service!");
 
+  nsresult res = NS_OK;
   if (!mCharset.IsEmpty())
   {
     PRBool same;
     res = calias->Equals(aCharset, mCharset, &same);
     if(NS_SUCCEEDED(res) && same)
     {
       return NS_OK; // no difference, don't change it
     }
@@ -180,27 +179,18 @@ nsresult nsScanner::SetDocumentCharset(c
     mCharset.Assign(charsetName);
   }
 
   mCharsetSource = aSource;
 
   NS_ASSERTION(nsParser::GetCharsetConverterManager(),
                "Must have the charset converter manager!");
 
-  nsIUnicodeDecoder * decoder = nsnull;
-  res = nsParser::GetCharsetConverterManager()->
-    GetUnicodeDecoderRaw(mCharset.get(), &decoder);
-  if(NS_SUCCEEDED(res) && (nsnull != decoder))
-  {
-     NS_IF_RELEASE(mUnicodeDecoder);
-
-     mUnicodeDecoder = decoder;
-  }
-
-  return res;
+  return nsParser::GetCharsetConverterManager()->
+    GetUnicodeDecoderRaw(mCharset.get(), getter_AddRefs(mUnicodeDecoder));
 }
 
 
 /**
  *  default destructor
  *  
  *  @update  gess 3/25/98
  *  @param   
@@ -208,18 +198,16 @@ nsresult nsScanner::SetDocumentCharset(c
  */
 nsScanner::~nsScanner() {
 
   if (mSlidingBuffer) {
     delete mSlidingBuffer;
   }
 
   MOZ_COUNT_DTOR(nsScanner);
-
-  NS_IF_RELEASE(mUnicodeDecoder);
 }
 
 /**
  *  Resets current offset position of input stream to marked position. 
  *  This allows us to back up to this point if the need should arise, 
  *  such as when tokenization gets interrupted.
  *  NOTE: IT IS REALLY BAD FORM TO CALL RELEASE WITHOUT CALLING MARK FIRST!
  *
@@ -239,24 +227,31 @@ void nsScanner::RewindToMark(void){
  *  Records current offset position in input stream. This allows us
  *  to back up to this point if the need should arise, such as when
  *  tokenization gets interrupted.
  *
  *  @update  gess 7/29/98
  *  @param   
  *  @return  
  */
-void nsScanner::Mark() {
+PRInt32 nsScanner::Mark() {
+  PRInt32 distance = 0;
   if (mSlidingBuffer) {
+    nsScannerIterator oldStart;
+    mSlidingBuffer->BeginReading(oldStart);
+
+    distance = Distance(oldStart, mCurrentPosition);
+
     mSlidingBuffer->DiscardPrefix(mCurrentPosition);
     mSlidingBuffer->BeginReading(mCurrentPosition);
     mMarkPosition = mCurrentPosition;
   }
+
+  return distance;
 }
- 
 
 /** 
  * Insert data to our underlying input buffer as
  * if it were read from an input stream.
  *
  * @update  harishd 01/12/99
  * @return  error code 
  */
--- a/parser/htmlparser/src/nsScanner.h
+++ b/parser/htmlparser/src/nsScanner.h
@@ -202,17 +202,17 @@ class nsScanner {
        *  Records current offset position in input stream. This allows us
        *  to back up to this point if the need should arise, such as when
        *  tokenization gets interrupted.
        *  
        *  @update  gess 5/12/98
        *  @param   
        *  @return  
        */
-      void Mark(void);
+      PRInt32 Mark(void);
 
       /**
        *  Resets current offset position of input stream to marked position. 
        *  This allows us to back up to this point if the need should arise, 
        *  such as when tokenization gets interrupted.
        *  NOTE: IT IS REALLY BAD FORM TO CALL RELEASE WITHOUT CALLING MARK FIRST!
        *  
        *  @update  gess 5/12/98
@@ -333,15 +333,18 @@ class nsScanner {
       nsScannerIterator            mEndPosition;     // The current end of the scanner buffer
       nsString        mFilename;
       PRUint32        mCountRemaining; // The number of bytes still to be read
                                        // from the scanner buffer
       PRPackedBool    mIncremental;
       PRInt32         mFirstNonWhitespacePosition;
       PRInt32         mCharsetSource;
       nsCString       mCharset;
-      nsIUnicodeDecoder *mUnicodeDecoder;
+      nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
       nsParser        *mParser;
+
+  private:
+      nsScanner &operator =(const nsScanner &); // Not implemented.
 };
 
 #endif