Bug 541079 - Make app cache selection happen before speculative loads in the HTML5 parser. r=bnewman.
authorHenri Sivonen <hsivonen@iki.fi>
Tue, 02 Feb 2010 09:43:18 +0200
changeset 38132 18a259aece76a4423a51454f9f72eea07c6c953e
parent 38131 9e1011e386979f20ddf4704f6282f56c7afef1de
child 38133 e24c4971852772eefd87e6f49ee2a0639807e9ee
push idunknown
push userunknown
push dateunknown
reviewersbnewman
bugs541079
milestone1.9.3a2pre
Bug 541079 - Make app cache selection happen before speculative loads in the HTML5 parser. r=bnewman.
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
parser/html/nsHtml5Parser.cpp
parser/html/nsHtml5SpeculativeLoader.cpp
parser/html/nsHtml5SpeculativeLoader.h
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeBuilderHSupplement.h
parser/html/nsHtml5TreeOperation.cpp
parser/html/nsHtml5TreeOperation.h
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -1082,21 +1082,32 @@ nsContentSink::ProcessOfflineManifest(ns
   }
 
   // Don't bother processing offline manifest for documents
   // without a docshell
   if (!mDocShell) {
     return;
   }
 
-  nsresult rv;
-
   // Check for a manifest= attribute.
   nsAutoString manifestSpec;
   aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
+  ProcessOfflineManifest(manifestSpec);
+}
+
+void
+nsContentSink::ProcessOfflineManifest(const nsAString& aManifestSpec)
+{
+  // Don't bother processing offline manifest for documents
+  // without a docshell
+  if (!mDocShell) {
+    return;
+  }
+
+  nsresult rv;
 
   // Grab the application cache the document was loaded from, if any.
   nsCOMPtr<nsIApplicationCache> applicationCache;
 
   nsCOMPtr<nsIApplicationCacheChannel> applicationCacheChannel =
     do_QueryInterface(mDocument->GetChannel());
   if (applicationCacheChannel) {
     PRBool loadedFromApplicationCache;
@@ -1110,31 +1121,31 @@ nsContentSink::ProcessOfflineManifest(ns
       rv = applicationCacheChannel->GetApplicationCache(
         getter_AddRefs(applicationCache));
       if (NS_FAILED(rv)) {
         return;
       }
     }
   }
 
-  if (manifestSpec.IsEmpty() && !applicationCache) {
+  if (aManifestSpec.IsEmpty() && !applicationCache) {
     // Not loaded from an application cache, and no manifest
     // attribute.  Nothing to do here.
     return;
   }
 
   CacheSelectionAction action = CACHE_SELECTION_NONE;
   nsCOMPtr<nsIURI> manifestURI;
 
-  if (manifestSpec.IsEmpty()) {
+  if (aManifestSpec.IsEmpty()) {
     action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;
   }
   else {
     nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),
-                                              manifestSpec, mDocument,
+                                              aManifestSpec, mDocument,
                                               mDocumentURI);
     if (!manifestURI) {
       return;
     }
 
     // Documents must list a manifest from the same origin
     rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, PR_TRUE);
     if (NS_FAILED(rv)) {
--- a/content/base/src/nsContentSink.h
+++ b/content/base/src/nsContentSink.h
@@ -242,16 +242,20 @@ protected:
                                        nsIURI **aManifestURI,
                                        CacheSelectionAction *aAction);
 
 public:
   // Searches for the offline cache manifest attribute and calls one
   // of the above defined methods to select the document's application
   // cache, let it be associated with the document and eventually
   // schedule the cache update process.
+  void ProcessOfflineManifest(const nsAString& aManifestSpec);
+
+  // Extracts the manifest attribute from the element if it is the root 
+  // element and calls the above method.
   void ProcessOfflineManifest(nsIContent *aElement);
 
 protected:
   // Tries to scroll to the URI's named anchor. Once we've successfully
   // done that, further calls to this method will be ignored.
   void ScrollToRef();
   nsresult RefreshIfEnabled(nsIViewManager* vm);
 
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -80,17 +80,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mExecutor)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mStreamParser)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 nsHtml5Parser::nsHtml5Parser()
   : mFirstBuffer(new nsHtml5UTF16Buffer(0))
   , mLastBuffer(mFirstBuffer)
   , mExecutor(new nsHtml5TreeOpExecutor())
-  , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor))
+  , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nsnull))
   , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder))
   , mRootContextLineNumber(1)
 {
   mAtomTable.Init(); // we aren't checking for OOM anyway...
   mTokenizer->setInterner(&mAtomTable);
   // There's a zeroing operator new for everything else
 }
 
@@ -657,19 +657,16 @@ nsHtml5Parser::ParseUntilBlocked()
 }
 
 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();
 }
--- a/parser/html/nsHtml5SpeculativeLoader.cpp
+++ b/parser/html/nsHtml5SpeculativeLoader.cpp
@@ -38,34 +38,35 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsHtml5SpeculativeLoader.h"
 #include "nsICSSLoader.h"
 #include "nsNetUtil.h"
 #include "nsScriptLoader.h"
 #include "nsICSSLoaderObserver.h"
+#include "nsIDocument.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)
+nsHtml5SpeculativeLoader::nsHtml5SpeculativeLoader(nsHtml5TreeOpExecutor* aExecutor)
+  : mExecutor(aExecutor)
 {
   MOZ_COUNT_CTOR(nsHtml5SpeculativeLoader);
   mPreloadedURLs.Init(23); // Mean # of preloadable resources per page on dmoz
 }
 
 nsHtml5SpeculativeLoader::~nsHtml5SpeculativeLoader()
 {
   MOZ_COUNT_DTOR(nsHtml5SpeculativeLoader);
@@ -73,18 +74,22 @@ nsHtml5SpeculativeLoader::~nsHtml5Specul
 
 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();
+  nsIDocument* doc = mExecutor->GetDocument();
+  if (!doc) {
+    return nsnull;
+  }
+  nsIURI* base = doc->GetBaseURI();
+  const nsCString& charset = doc->GetDocumentCharacterSet();
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to create a URI");
     return nsnull;
   }
   nsCAutoString spec;
   uri->GetSpec(spec);
@@ -101,34 +106,50 @@ 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);
+  nsIDocument* doc = mExecutor->GetDocument();
+  if (doc) {
+    doc->ScriptLoader()->PreloadURI(uri, aCharset, aType);
+  }
 }
 
 void
 nsHtml5SpeculativeLoader::PreloadStyle(const nsAString& aURL,
                                        const nsAString& aCharset)
 {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   if (!uri) {
     return;
   }
   nsCOMPtr<nsICSSLoaderObserver> obs = new nsHtml5DummyCSSLoaderObserver();
-  mDocument->CSSLoader()->LoadSheet(uri, mDocument->NodePrincipal(),
-                                    NS_LossyConvertUTF16toASCII(aCharset),
-                                    obs);
+  nsIDocument* doc = mExecutor->GetDocument();
+  if (doc) {
+    doc->CSSLoader()->LoadSheet(uri, 
+                                doc->NodePrincipal(),
+                                NS_LossyConvertUTF16toASCII(aCharset),
+                                obs);
+  }
 }
 
 void
 nsHtml5SpeculativeLoader::PreloadImage(const nsAString& aURL)
 {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   if (!uri) {
     return;
   }
-  mDocument->MaybePreLoadImage(uri);
+  nsIDocument* doc = mExecutor->GetDocument();
+  if (doc) {
+    doc->MaybePreLoadImage(uri);
+  }
 }
+
+void
+nsHtml5SpeculativeLoader::ProcessManifest(const nsAString& aURL)
+{
+  mExecutor->ProcessOfflineManifest(aURL);
+}
--- a/parser/html/nsHtml5SpeculativeLoader.h
+++ b/parser/html/nsHtml5SpeculativeLoader.h
@@ -37,49 +37,51 @@
 
 #ifndef nsHtml5SpeculativeLoader_h__
 #define nsHtml5SpeculativeLoader_h__
 
 #include "mozilla/Mutex.h"
 #include "nsIURI.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
-#include "nsIDocument.h"
+#include "nsHtml5TreeOpExecutor.h"
 #include "nsHashSets.h"
 
 class nsHtml5SpeculativeLoader
 {
   public:
-    nsHtml5SpeculativeLoader(nsIDocument* aDocument);
+    nsHtml5SpeculativeLoader(nsHtml5TreeOpExecutor* aExecutor);
     ~nsHtml5SpeculativeLoader();
 
     NS_IMETHOD_(nsrefcnt) AddRef(void);
     NS_IMETHOD_(nsrefcnt) Release(void);
 
     void PreloadScript(const nsAString& aURL,
                        const nsAString& aCharset,
                        const nsAString& aType);
 
     void PreloadStyle(const nsAString& aURL, const nsAString& aCharset);
 
     void PreloadImage(const nsAString& aURL);
 
+    void ProcessManifest(const nsAString& aURL);
+
   private:
     
     /**
      * Get a nsIURI for an nsString if the URL hasn't been preloaded yet.
      */
     already_AddRefed<nsIURI> ConvertIfNotPreloadedYet(const nsAString& aURL);
   
     nsAutoRefCnt   mRefCnt;
     
     /**
-     * The document to use as the context for preloading.
+     * The executor to use as the context for preloading.
      */
-    nsCOMPtr<nsIDocument> mDocument;
+    nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
     
     /**
      * 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
@@ -45,16 +45,17 @@
 #include "nsContentUtils.h"
 #include "nsHtml5Tokenizer.h"
 #include "nsIHttpChannel.h"
 #include "nsHtml5Parser.h"
 #include "nsHtml5TreeBuilder.h"
 #include "nsHtml5AtomTable.h"
 #include "nsHtml5Module.h"
 #include "nsHtml5RefPtr.h"
+#include "nsHtml5SpeculativeLoader.h"
 
 static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID);
 
 PRInt32 nsHtml5StreamParser::sTimerStartDelay = 200;
 PRInt32 nsHtml5StreamParser::sTimerContinueDelay = 150;
 PRInt32 nsHtml5StreamParser::sTimerInterval = 100;
 
 // static
@@ -86,45 +87,43 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
     tmp->mFlushTimer->Cancel();
     tmp->mFlushTimer = nsnull;
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest)
   tmp->mOwner = nsnull;
   tmp->mExecutorFlusher = nsnull;
   tmp->mExecutor = nsnull;
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChardet)
   tmp->mTreeBuilder->DropSpeculativeLoader();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5StreamParser)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mObserver)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRequest)
   if (tmp->mOwner) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOwner");
     cb.NoteXPCOMChild(static_cast<nsIParser*> (tmp->mOwner));
   }
   // hack: count the strongly owned edge wrapped in the runnable
   if (tmp->mExecutorFlusher) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExecutorFlusher->mExecutor");
     cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
   }
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
   // hack: count self if held by mChardet
   if (tmp->mChardet) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 
       "mChardet->mObserver");
     cb.NoteXPCOMChild(static_cast<nsIStreamListener*>(tmp));
   }
   // hack: count the strongly owned edge wrapped in the speculative loader
-  if (tmp->mDocument) {
+  if (tmp->mTreeBuilder->HasSpeculativeLoader()) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 
-      "mTreeBuilder->mSpeculativeLoader->mDocument");
-    cb.NoteXPCOMChild(tmp->mDocument);    
+      "mTreeBuilder->mSpeculativeLoader->mExecutor");
+    cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 class nsHtml5ExecutorFlusher : public nsRunnable
 {
   private:
     nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
   public:
@@ -138,17 +137,18 @@ class nsHtml5ExecutorFlusher : public ns
     }
 };
 
 nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
                                          nsHtml5Parser* aOwner)
   : mFirstBuffer(new nsHtml5UTF16Buffer(NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE))
   , mLastBuffer(mFirstBuffer)
   , mExecutor(aExecutor)
-  , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor->GetStage()))
+  , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor->GetStage(), 
+                                        new nsHtml5SpeculativeLoader(mExecutor)))
   , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder))
   , mTokenizerMutex("nsHtml5StreamParser mTokenizerMutex")
   , mOwner(aOwner)
   , mSpeculationMutex("nsHtml5StreamParser mSpeculationMutex")
   , mTerminatedMutex("nsHtml5StreamParser mTerminatedMutex")
   , mThread(nsHtml5Module::GetStreamParserThread())
   , mExecutorFlusher(new nsHtml5ExecutorFlusher(aExecutor))
   , mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
@@ -194,23 +194,16 @@ nsHtml5StreamParser::~nsHtml5StreamParse
   mTokenizer = nsnull;
   mOwner = nsnull;
   if (mFlushTimer) {
     mFlushTimer->Cancel();
     mFlushTimer = 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;
 }
 
@@ -1034,17 +1027,17 @@ nsHtml5StreamParser::PostTimerFlush()
   }
 
   // Schedule the next timer shot
   mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback, 
                                     static_cast<void*> (this), 
                                     sTimerInterval, 
                                     nsITimer::TYPE_ONE_SHOT);
 
-  // TODO: (If mDocument isn't in the frontmost tab or If the user isn't 
+  // TODO: (If the document isn't in the frontmost tab or If the user isn't 
   // interacting with the browser) and this isn't every nth timer flush, return
 
   nsCOMPtr<nsIRunnable> event = new nsHtml5StreamParserTimerFlusher(this);
   if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
     NS_WARNING("Failed to dispatch nsHtml5StreamParserTimerFlusher");
   }
 }
 
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -156,18 +156,16 @@ class nsHtml5StreamParser : public nsISt
       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, 
@@ -449,21 +447,16 @@ class nsHtml5StreamParser : public nsISt
     /**
      * The thread this stream parser runs on.
      */
     nsCOMPtr<nsIThread>           mThread;
     
     nsCOMPtr<nsIRunnable>         mExecutorFlusher;
     
     /**
-     * The document wrapped by the speculative loader.
-     */
-    nsCOMPtr<nsIDocument>         mDocument;
-
-    /**
      * The chardet instance if chardet is enabled.
      */
     nsCOMPtr<nsICharsetDetector>  mChardet;
 
     /**
      * Timer for flushing tree ops once in a while when not speculating.
      */
     nsCOMPtr<nsITimer>            mFlushTimer;
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -45,25 +45,27 @@
 #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)
+nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink,
+                                       nsHtml5SpeculativeLoader* aSpeculativeLoader)
   : scriptingEnabled(PR_FALSE)
   , fragment(PR_FALSE)
   , contextNode(nsnull)
   , formPointer(nsnull)
   , headPointer(nsnull)
   , mOpSink(aOpSink)
   , mHandles(new nsIContent*[NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH])
   , mHandlesUsed(0)
+  , mSpeculativeLoader(aSpeculativeLoader)
   , mCurrentHtmlScriptIsAsyncOrDefer(PR_FALSE)
 #ifdef DEBUG
   , mActive(PR_FALSE)
 #endif
 {
   MOZ_COUNT_CTOR(nsHtml5TreeBuilder);
 }
 
@@ -132,16 +134,34 @@ public:
   {}
   NS_IMETHODIMP Run()
   {
     mSpeculativeLoader->PreloadImage(mURL);
     return NS_OK;
   }
 };
 
+class nsHtml5SpeculativeManifest : public nsRunnable
+{
+private:
+  nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
+  nsString                           mURL;
+public:
+  nsHtml5SpeculativeManifest(nsHtml5SpeculativeLoader* aSpeculativeLoader,
+                          const nsAString& aURL)
+    : mSpeculativeLoader(aSpeculativeLoader)
+    , mURL(aURL)
+  {}
+  NS_IMETHODIMP Run()
+  {
+    mSpeculativeLoader->ProcessManifest(mURL);
+    return NS_OK;
+  }
+};
+
 nsIContent**
 nsHtml5TreeBuilder::createElement(PRInt32 aNamespace, nsIAtom* aName, nsHtml5HtmlAttributes* aAttributes)
 {
   NS_PRECONDITION(aAttributes, "Got null attributes.");
   NS_PRECONDITION(aName, "Got null name.");
   NS_PRECONDITION(aNamespace == kNameSpaceID_XHTML || 
                   aNamespace == kNameSpaceID_SVG || 
                   aNamespace == kNameSpaceID_MathML,
@@ -196,16 +216,21 @@ nsHtml5TreeBuilder::createElement(PRInt3
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
           if (url) {
             Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
           }
         } else if (nsHtml5Atoms::style == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
+        } else if (nsHtml5Atoms::html == aName) {
+          nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_MANIFEST);
+          if (url) {
+            Dispatch(new nsHtml5SpeculativeManifest(mSpeculativeLoader, *url));
+          }
         }
         break;
       case kNameSpaceID_SVG:
         if (nsHtml5Atoms::image == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
             Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
           }
@@ -247,16 +272,23 @@ nsHtml5TreeBuilder::createElement(PRInt3
       NS_ASSERTION(treeOp, "Tree op allocation failed.");
       treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
       if (aNamespace == kNameSpaceID_XHTML) {
         mCurrentHtmlScriptIsAsyncOrDefer = 
           aAttributes->contains(nsHtml5AttributeName::ATTR_SRC) &&
           (aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
            aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER));
       }
+    } else if (aNamespace == kNameSpaceID_XHTML && nsHtml5Atoms::html == aName) {
+      nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_MANIFEST);
+      if (url) {
+        nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+        NS_ASSERTION(treeOp, "Tree op allocation failed.");
+        treeOp->Init(eTreeOpProcessOfflineManifest, *url);
+      }
     }
   }
 
   // End wall of code for speculative loading
   
   return content;
 }
 
@@ -434,28 +466,16 @@ nsHtml5TreeBuilder::appendDoctypeToDocum
   treeOp->Init(aName, *aPublicId, *aSystemId);
   // nsXMLContentSink can flush here, but what's the point?
   // It can also interrupt here, but we can't.
 }
 
 void
 nsHtml5TreeBuilder::elementPushed(PRInt32 aNamespace, nsIAtom* aName, nsIContent** aElement)
 {
-  NS_ASSERTION(aNamespace == kNameSpaceID_XHTML || aNamespace == kNameSpaceID_SVG || aNamespace == kNameSpaceID_MathML, "Element isn't HTML, SVG or MathML!");
-  NS_ASSERTION(aName, "Element doesn't have local name!");
-  NS_ASSERTION(aElement, "No element!");
-  // Give autoloading links a chance to fire
-  if (aNamespace == kNameSpaceID_XHTML) {
-    if (aName == nsHtml5Atoms::html) {
-      nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
-      NS_ASSERTION(treeOp, "Tree op allocation failed.");
-      treeOp->Init(eTreeOpProcessOfflineManifest, aElement);
-      return;
-    }
-  }
 }
 
 void
 nsHtml5TreeBuilder::elementPopped(PRInt32 aNamespace, nsIAtom* aName, nsIContent** aElement)
 {
   NS_ASSERTION(aNamespace == kNameSpaceID_XHTML || aNamespace == kNameSpaceID_SVG || aNamespace == kNameSpaceID_MathML, "Element isn't HTML, SVG or MathML!");
   NS_ASSERTION(aName, "Element doesn't have local name!");
   NS_ASSERTION(aElement, "No element!");
@@ -660,22 +680,16 @@ void
 nsHtml5TreeBuilder::AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot, PRInt32 aLine)
 {
   NS_PRECONDITION(HasScript(), "No script to add a snapshot to!");
   NS_PRECONDITION(aSnapshot, "Got null snapshot.");
   mOpQueue.ElementAt(mOpQueue.Length() - 1).SetSnapshot(aSnapshot, aLine);
 }
 
 void
-nsHtml5TreeBuilder::SetSpeculativeLoaderWithDocument(nsIDocument* aDocument) {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  mSpeculativeLoader = new nsHtml5SpeculativeLoader(aDocument);
-}
-
-void
 nsHtml5TreeBuilder::DropSpeculativeLoader() {
   mSpeculativeLoader = nsnull;
 }
 
 PRBool 
 nsHtml5TreeBuilder::IsDiscretionaryFlushSafe()
 {
   return !(charBufferLen && 
--- a/parser/html/nsHtml5TreeBuilderHSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderHSupplement.h
@@ -55,34 +55,37 @@
      * Tree builder uses this to report quirkiness of the document
      */
     void documentMode(nsHtml5DocumentMode m);
 
     nsIContent** AllocateContentHandle();
     
   public:
 
-    nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink);
+    nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink, 
+                       nsHtml5SpeculativeLoader* aSpeculativeLoader);
 
     ~nsHtml5TreeBuilder();
     
     PRBool IsDiscretionaryFlushSafe();
 
     PRBool HasScript();
     
     void SetOpSink(nsAHtml5TreeOpSink* aOpSink) {
       mOpSink = aOpSink;
     }
 
     void ClearOps() {
       mOpQueue.Clear();
     }
     
-    void SetSpeculativeLoaderWithDocument(nsIDocument* aDocument);
-
+    PRBool HasSpeculativeLoader() {
+      return !!mSpeculativeLoader;
+    }
+    
     void DropSpeculativeLoader();
 
     PRBool Flush();
     
     void SetDocumentCharset(nsACString& aCharset);
 
     void StreamEnded();
 
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -114,16 +114,19 @@ nsHtml5TreeOperation::~nsHtml5TreeOperat
     case eTreeOpAppendComment:
     case eTreeOpAppendCommentToDocument:
       delete[] mTwo.unicharPtr;
       break;
     case eTreeOpSetDocumentCharset:
     case eTreeOpNeedsCharsetSwitchTo:
       delete[] mOne.charPtr;
       break;
+    case eTreeOpProcessOfflineManifest:
+      nsMemory::Free(mOne.unicharPtr);
+      break;
     default: // keep the compiler happy
       break;
   }
 }
 
 nsresult
 nsHtml5TreeOperation::AppendTextToTextNode(PRUnichar* aBuffer,
                                            PRInt32 aLength,
@@ -549,18 +552,19 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
       return rv;
     }
     case eTreeOpProcessMeta: {
       nsIContent* node = *(mOne.node);
       rv = aBuilder->ProcessMETATag(node);
       return rv;
     }
     case eTreeOpProcessOfflineManifest: {
-      nsIContent* node = *(mOne.node);
-      aBuilder->ProcessOfflineManifest(node);
+      PRUnichar* str = mOne.unicharPtr;
+      nsDependentString dependentString(str);
+      aBuilder->ProcessOfflineManifest(dependentString);
       return rv;
     }
     case eTreeOpMarkMalformedIfScript: {
       nsIContent* node = *(mOne.node);
       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(node);
       if (sele) {
         // Make sure to serialize this script correctly, for nice round tripping.
         sele->SetIsMalformed();
--- a/parser/html/nsHtml5TreeOperation.h
+++ b/parser/html/nsHtml5TreeOperation.h
@@ -36,16 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
  
 #ifndef nsHtml5TreeOperation_h__
 #define nsHtml5TreeOperation_h__
 
 #include "nsIContent.h"
 #include "nsHtml5DocumentMode.h"
 #include "nsHtml5HtmlAttributes.h"
+#include "nsXPCOMStrings.h"
 
 class nsHtml5TreeOpExecutor;
 class nsHtml5StateSnapshot;
 
 enum eHtml5TreeOperation {
 #ifdef DEBUG
   eTreeOpUninitialized,
 #endif
@@ -258,16 +259,25 @@ class nsHtml5TreeOperation {
         str[i] = start[i];
       }
       str[len] = '\0';
 
       mOpCode = aOpCode;
       mOne.charPtr = str;
     }
 
+    inline void Init(eHtml5TreeOperation aOpCode, const nsAString& aString) {
+      NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+        "Op code must be uninitialized when initializing.");
+
+      PRUnichar* str = NS_StringCloneData(aString);
+      mOpCode = aOpCode;
+      mOne.unicharPtr = str;
+    }
+    
     inline void Init(eHtml5TreeOperation aOpCode,
                      nsIContent** aNode,
                      PRInt32 aInt) {
       NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
         "Op code must be uninitialized when initializing.");
       NS_PRECONDITION(aNode, "Initialized tree op with null node.");
       mOpCode = aOpCode;
       mOne.node = aNode;