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

This commit is contained in:
Blake Kaplan 2008-09-29 21:19:43 -07:00
Родитель 4aadfb492e
Коммит 428671d01a
8 изменённых файлов: 759 добавлений и 107 удалений

Просмотреть файл

@ -96,6 +96,11 @@ public:
mElement->ScriptEvaluated(aResult, mElement, mIsInline); mElement->ScriptEvaluated(aResult, mElement, mIsInline);
} }
PRBool IsPreload()
{
return mElement == nsnull;
}
nsCOMPtr<nsIScriptElement> mElement; nsCOMPtr<nsIScriptElement> mElement;
PRPackedBool mLoading; // Are we still waiting for a load to complete? PRPackedBool mLoading; // Are we still waiting for a load to complete?
PRPackedBool mDefer; // Is execution defered? PRPackedBool mDefer; // Is execution defered?
@ -189,6 +194,72 @@ IsScriptEventHandler(nsIScriptElement *aScriptElement)
return PR_FALSE; 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 nsresult
nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
{ {
@ -376,74 +447,55 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
nsCOMPtr<nsIContent> eltContent(do_QueryInterface(aElement)); nsCOMPtr<nsIContent> eltContent(do_QueryInterface(aElement));
eltContent->SetScriptTypeID(typeID); 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 // 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); NS_ENSURE_TRUE(request, NS_ERROR_OUT_OF_MEMORY);
request->mDefer = mDeferEnabled && aElement->GetScriptDeferred(); request->mDefer = mDeferEnabled && aElement->GetScriptDeferred();
PRBool hadPendingRequests = !!GetFirstPendingRequest();
// First check to see if this is an external script // First check to see if this is an external script
nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
if (scriptURI) { 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->mURI = scriptURI;
request->mIsInline = PR_FALSE; request->mIsInline = PR_FALSE;
request->mLoading = PR_TRUE; request->mLoading = PR_TRUE;
nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup(); rv = StartLoad(request, type);
nsCOMPtr<nsIStreamLoader> loader; if (NS_FAILED(rv)) {
return rv;
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 = NS_NewStreamLoader(getter_AddRefs(loader), this);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->AsyncOpen(loader, request);
NS_ENSURE_SUCCESS(rv, rv);
} else { } else {
request->mLoading = PR_FALSE; request->mLoading = PR_FALSE;
request->mIsInline = PR_TRUE; request->mIsInline = PR_TRUE;
@ -814,8 +866,11 @@ nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
aString); aString);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
mRequests.RemoveObject(request); if (!mRequests.RemoveObject(request)) {
FireScriptAvailable(rv, request); mPreloads.RemoveElement(request, PreloadRequestComparator());
} else {
FireScriptAvailable(rv, request);
}
} }
// Process our request and/or any pending ones // Process our request and/or any pending ones
@ -860,7 +915,14 @@ nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
if (aStringLen) { if (aStringLen) {
// Check the charset attribute to determine script charset. // Check the charset attribute to determine script charset.
nsAutoString hintCharset; 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, rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
aRequest->mScriptText); aRequest->mScriptText);
@ -875,7 +937,8 @@ nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
// inserting the request in the array. However it's an unlikely case // 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 // so if you see this assertion it is likely something else that is
// wrong, especially if you see it more than once. // 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!"); "aRequest should be pending!");
// Mark this as loaded // Mark this as loaded
@ -918,9 +981,33 @@ void
nsScriptLoader::EndDeferringScripts() nsScriptLoader::EndDeferringScripts()
{ {
mDeferEnabled = PR_FALSE; 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; mRequests[i]->mDefer = PR_FALSE;
} }
ProcessPendingRequests(); 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;
}

Просмотреть файл

@ -205,7 +205,22 @@ public:
*/ */
void EndDeferringScripts(); 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: 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 * 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 * are any. Note that this is a no-op if there aren't any currently pending
@ -253,6 +268,25 @@ protected:
nsIDocument* mDocument; // [WEAK] nsIDocument* mDocument; // [WEAK]
nsCOMArray<nsIScriptLoaderObserver> mObservers; nsCOMArray<nsIScriptLoaderObserver> mObservers;
nsCOMArray<nsScriptLoadRequest> mRequests; 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; nsCOMPtr<nsIScriptElement> mCurrentScript;
// XXXbz do we want to cycle-collect these or something? Not sure. // XXXbz do we want to cycle-collect these or something? Not sure.
nsTArray< nsRefPtr<nsScriptLoader> > mPendingChildLoaders; nsTArray< nsRefPtr<nsScriptLoader> > mPendingChildLoaders;

Просмотреть файл

@ -63,7 +63,7 @@ CParserContext::CParserContext(nsScanner* aScanner,
mParserCommand(aCommand), mParserCommand(aCommand),
mMultipart(PR_TRUE), mMultipart(PR_TRUE),
mCopyUnused(aCopyUnused), mCopyUnused(aCopyUnused),
mTransferBufferSize(eTransferBufferSize) mNumConsumed(0)
{ {
MOZ_COUNT_CTOR(CParserContext); MOZ_COUNT_CTOR(CParserContext);
} }

Просмотреть файл

@ -60,20 +60,17 @@
*/ */
class CParserContext { class CParserContext {
public: public:
enum eContextType {eCTNone,eCTURL,eCTString,eCTStream};
enum {eTransferBufferSize=4096}; CParserContext(nsScanner* aScanner,
enum eContextType {eCTNone,eCTURL,eCTString,eCTStream}; 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(); ~CParserContext();
nsresult GetTokenizer(PRInt32 aType, nsresult GetTokenizer(PRInt32 aType,
@ -83,26 +80,26 @@ public:
nsCOMPtr<nsIRequest> mRequest; // provided by necko to differnciate different input streams nsCOMPtr<nsIRequest> mRequest; // provided by necko to differnciate different input streams
// why is mRequest strongly referenced? see bug 102376. // why is mRequest strongly referenced? see bug 102376.
nsCOMPtr<nsIDTD> mDTD; nsCOMPtr<nsIDTD> mDTD;
nsCOMPtr<nsIRequestObserver> mListener; nsCOMPtr<nsIRequestObserver> mListener;
nsAutoArrayPtr<char> mTransferBuffer;
void* mKey; void* mKey;
nsCOMPtr<nsITokenizer> mTokenizer; nsCOMPtr<nsITokenizer> mTokenizer;
CParserContext* mPrevContext; CParserContext* mPrevContext;
nsAutoPtr<nsScanner> mScanner; nsAutoPtr<nsScanner> mScanner;
nsCString mMimeType; nsCString mMimeType;
nsDTDMode mDTDMode; nsDTDMode mDTDMode;
eParserDocType mDocType; eParserDocType mDocType;
eStreamState mStreamListenerState; //this is really only here for debug purposes. eStreamState mStreamListenerState;
eContextType mContextType; eContextType mContextType;
eAutoDetectResult mAutoDetectStatus; eAutoDetectResult mAutoDetectStatus;
eParserCommands mParserCommand; //tells us to viewcontent/viewsource/viewerrors... eParserCommands mParserCommand;
PRPackedBool mMultipart; PRPackedBool mMultipart;
PRPackedBool mCopyUnused; PRPackedBool mCopyUnused;
PRUint32 mTransferBufferSize;
PRUint32 mNumConsumed;
}; };
#endif #endif

Просмотреть файл

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- 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 ***** /* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
* *
@ -52,6 +52,9 @@
#include "nsIInputStream.h" #include "nsIInputStream.h"
#include "CNavDTD.h" #include "CNavDTD.h"
#include "prenv.h" #include "prenv.h"
#include "prlock.h"
#include "prcvar.h"
#include "nsAutoLock.h"
#include "nsParserCIID.h" #include "nsParserCIID.h"
#include "nsReadableUtils.h" #include "nsReadableUtils.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
@ -61,6 +64,11 @@
#include "nsISupportsPrimitives.h" #include "nsISupportsPrimitives.h"
#include "nsIFragmentContentSink.h" #include "nsIFragmentContentSink.h"
#include "nsStreamUtils.h" #include "nsStreamUtils.h"
#include "nsHTMLTokenizer.h"
#include "nsIDocument.h"
#include "nsNetUtil.h"
#include "nsScriptLoader.h"
#include "nsDataHashtable.h"
#ifdef MOZ_VIEW_SOURCE #ifdef MOZ_VIEW_SOURCE
#include "nsViewSourceHTML.h" #include "nsViewSourceHTML.h"
@ -155,6 +163,478 @@ public:
//-------------- End ParseContinue Event Definition ------------------------ //-------------- 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; nsICharsetAlias* nsParser::sCharsetAliasService = nsnull;
nsICharsetConverterManager* nsParser::sCharsetConverterManager = nsnull; nsICharsetConverterManager* nsParser::sCharsetConverterManager = nsnull;
@ -321,6 +801,10 @@ nsParser::Cleanup()
// destroyed since this flag implies a pending nsParserContinueEvent, which // destroyed since this flag implies a pending nsParserContinueEvent, which
// has an owning reference to |this|. // has an owning reference to |this|.
NS_ASSERTION(!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT), "bad"); 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_CLASS(nsParser)
@ -1007,6 +1491,26 @@ nsParser::DidBuildModel(nsresult anErrorCode)
return result; 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, * This method adds a new parser context to the list,
@ -1107,6 +1611,9 @@ nsParser::Terminate(void)
// will reset it so DidBuildModel will call DidBuildModel on the DTD. Note: // will reset it so DidBuildModel will call DidBuildModel on the DTD. Note:
// The IsComplete() call inside of DidBuildModel looks at the pendingContinueEvents flag. // The IsComplete() call inside of DidBuildModel looks at the pendingContinueEvents flag.
CancelParsingEvents(); CancelParsingEvents();
if (mSpeculativeScriptThread) {
mSpeculativeScriptThread->StopParsing(PR_FALSE);
}
// If we got interrupted in the middle of a document.write, then we might // 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 // have more than one parser context on our parsercontext stack. This has
@ -1163,6 +1670,10 @@ nsParser::ContinueInterruptedParsing()
} }
#endif #endif
if (mSpeculativeScriptThread) {
mSpeculativeScriptThread->StopParsing(PR_FALSE);
}
PRBool isFinalChunk = mParserContext && PRBool isFinalChunk = mParserContext &&
mParserContext->mStreamListenerState == eOnStop; mParserContext->mStreamListenerState == eOnStop;
@ -1295,6 +1806,7 @@ nsParser::Parse(nsIURI* aURL,
{ {
NS_PRECONDITION(aURL, "Error: Null URL given"); NS_PRECONDITION(aURL, "Error: Null URL given");
NS_ASSERTION(!mSpeculativeScriptThread, "Can't reuse a parser like this");
nsresult result=kBadURL; nsresult result=kBadURL;
mObserver = aListener; mObserver = aListener;
@ -1362,6 +1874,10 @@ nsParser::Parse(const nsAString& aSourceBuffer,
return result; return result;
} }
if (mSpeculativeScriptThread) {
mSpeculativeScriptThread->StopParsing(PR_TRUE);
}
// Hack to pass on to the dtd the caller's desire to // Hack to pass on to the dtd the caller's desire to
// parse a fragment without worrying about containment rules // parse a fragment without worrying about containment rules
if (aMode == eDTDMode_fragment) if (aMode == eDTDMode_fragment)
@ -1482,6 +1998,8 @@ nsParser::ParseFragment(const nsAString& aSourceBuffer,
// Disable observers for fragments // Disable observers for fragments
mFlags &= ~NS_PARSER_FLAG_OBSERVERS_ENABLED; mFlags &= ~NS_PARSER_FLAG_OBSERVERS_ENABLED;
NS_ASSERTION(!mSpeculativeScriptThread, "Can't reuse a parser like this");
for (theIndex = 0; theIndex < theCount; theIndex++) { for (theIndex = 0; theIndex < theCount; theIndex++) {
theContext.AppendLiteral("<"); theContext.AppendLiteral("<");
theContext.Append(aTagStack[theCount - theIndex - 1]); theContext.Append(aTagStack[theCount - theIndex - 1]);
@ -1622,6 +2140,8 @@ nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk,
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: nsParser::ResumeParse(), this=%p\n", this)); MOZ_TIMER_DEBUGLOG(("Start: Parse Time: nsParser::ResumeParse(), this=%p\n", this));
MOZ_TIMER_START(mParseTime); MOZ_TIMER_START(mParseTime);
NS_ASSERTION(!mSpeculativeScriptThread || !mSpeculativeScriptThread->Parsing(), "Bad races happening");
result = WillBuildModel(mParserContext->mScanner->GetFilename()); result = WillBuildModel(mParserContext->mScanner->GetFilename());
if (NS_FAILED(result)) { if (NS_FAILED(result)) {
mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE; mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE;
@ -1671,6 +2191,7 @@ nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk,
} }
BlockParser(); BlockParser();
SpeculativelyParse();
return NS_OK; return NS_OK;
} }
if (NS_ERROR_HTMLPARSER_STOPPARSING == result) { if (NS_ERROR_HTMLPARSER_STOPPARSING == result) {
@ -2260,6 +2781,11 @@ nsParser::OnDataAvailable(nsIRequest *request, nsISupports* aContext,
if (theContext) { if (theContext) {
theContext->mStreamListenerState = eOnDataAvail; theContext->mStreamListenerState = eOnDataAvail;
if ((mFlags & NS_PARSER_FLAG_PARSER_ENABLED) &&
mSpeculativeScriptThread) {
mSpeculativeScriptThread->StopParsing(PR_FALSE);
}
if (eInvalidDetect == theContext->mAutoDetectStatus) { if (eInvalidDetect == theContext->mAutoDetectStatus) {
if (theContext->mScanner) { if (theContext->mScanner) {
nsScannerIterator iter; nsScannerIterator iter;
@ -2304,6 +2830,10 @@ nsParser::OnStopRequest(nsIRequest *request, nsISupports* aContext,
{ {
nsresult rv = NS_OK; nsresult rv = NS_OK;
if (mSpeculativeScriptThread) {
mSpeculativeScriptThread->StopParsing(PR_FALSE);
}
if (eOnStart == mParserContext->mStreamListenerState) { if (eOnStart == mParserContext->mStreamListenerState) {
// If you're here, then OnDataAvailable() never got called. Prior // If you're here, then OnDataAvailable() never got called. Prior
// to necko, we never dealt with this case, but the problem may // to necko, we never dealt with this case, but the problem may
@ -2417,9 +2947,11 @@ nsresult nsParser::Tokenize(PRBool aIsFinalChunk)
MOZ_TIMER_START(mTokenizeTime); MOZ_TIMER_START(mTokenizeTime);
mParserContext->mNumConsumed = 0;
WillTokenize(aIsFinalChunk); WillTokenize(aIsFinalChunk);
while (NS_SUCCEEDED(result)) { while (NS_SUCCEEDED(result)) {
mParserContext->mScanner->Mark(); mParserContext->mNumConsumed += mParserContext->mScanner->Mark();
result = theTokenizer->ConsumeToken(*mParserContext->mScanner, result = theTokenizer->ConsumeToken(*mParserContext->mScanner,
flushTokens); flushTokens);
if (NS_FAILED(result)) { if (NS_FAILED(result)) {
@ -2436,7 +2968,7 @@ nsresult nsParser::Tokenize(PRBool aIsFinalChunk)
// Flush tokens on seeing </SCRIPT> -- Ref: Bug# 22485 -- // Flush tokens on seeing </SCRIPT> -- Ref: Bug# 22485 --
// Also remember to update the marked position. // Also remember to update the marked position.
mFlags |= NS_PARSER_FLAG_FLUSH_TOKENS; mFlags |= NS_PARSER_FLAG_FLUSH_TOKENS;
mParserContext->mScanner->Mark(); mParserContext->mNumConsumed += mParserContext->mScanner->Mark();
break; break;
} }
} }

Просмотреть файл

@ -95,6 +95,7 @@ class nsICharsetAlias;
class nsIDTD; class nsIDTD;
class nsScanner; class nsScanner;
class nsIProgressEventSink; class nsIProgressEventSink;
class nsSpeculativeScriptThread;
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning( disable : 4275 ) #pragma warning( disable : 4275 )
@ -406,7 +407,9 @@ class nsParser : public nsIParser,
* @return * @return
*/ */
nsresult DidBuildModel(nsresult anErrorCode); nsresult DidBuildModel(nsresult anErrorCode);
void SpeculativelyParse();
private: private:
/******************************************* /*******************************************
@ -457,6 +460,7 @@ protected:
nsCOMPtr<nsIRequestObserver> mObserver; nsCOMPtr<nsIRequestObserver> mObserver;
nsCOMPtr<nsIContentSink> mSink; nsCOMPtr<nsIContentSink> mSink;
nsIRunnable* mContinueEvent; // weak ref nsIRunnable* mContinueEvent; // weak ref
nsRefPtr<nsSpeculativeScriptThread> mSpeculativeScriptThread;
nsCOMPtr<nsIParserFilter> mParserFilter; nsCOMPtr<nsIParserFilter> mParserFilter;
nsTokenAllocator mTokenAllocator; nsTokenAllocator mTokenAllocator;

Просмотреть файл

@ -146,16 +146,15 @@ nsScanner::nsScanner(nsString& aFilename,PRBool aCreateStream,
SetDocumentCharset(aCharset, aSource); SetDocumentCharset(aCharset, aSource);
} }
nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , PRInt32 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 NS_OK;
if( aSource < mCharsetSource) // priority is lower the the current one , just
return res;
nsICharsetAlias* calias = nsParser::GetCharsetAliasService(); nsICharsetAlias* calias = nsParser::GetCharsetAliasService();
NS_ASSERTION(calias, "Must have the charset alias service!"); NS_ASSERTION(calias, "Must have the charset alias service!");
nsresult res = NS_OK;
if (!mCharset.IsEmpty()) if (!mCharset.IsEmpty())
{ {
PRBool same; PRBool same;
@ -185,17 +184,8 @@ nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , PRInt32 aSou
NS_ASSERTION(nsParser::GetCharsetConverterManager(), NS_ASSERTION(nsParser::GetCharsetConverterManager(),
"Must have the charset converter manager!"); "Must have the charset converter manager!");
nsIUnicodeDecoder * decoder = nsnull; return nsParser::GetCharsetConverterManager()->
res = nsParser::GetCharsetConverterManager()-> GetUnicodeDecoderRaw(mCharset.get(), getter_AddRefs(mUnicodeDecoder));
GetUnicodeDecoderRaw(mCharset.get(), &decoder);
if(NS_SUCCEEDED(res) && (nsnull != decoder))
{
NS_IF_RELEASE(mUnicodeDecoder);
mUnicodeDecoder = decoder;
}
return res;
} }
@ -213,8 +203,6 @@ nsScanner::~nsScanner() {
} }
MOZ_COUNT_DTOR(nsScanner); MOZ_COUNT_DTOR(nsScanner);
NS_IF_RELEASE(mUnicodeDecoder);
} }
/** /**
@ -244,14 +232,21 @@ void nsScanner::RewindToMark(void){
* @param * @param
* @return * @return
*/ */
void nsScanner::Mark() { PRInt32 nsScanner::Mark() {
PRInt32 distance = 0;
if (mSlidingBuffer) { if (mSlidingBuffer) {
nsScannerIterator oldStart;
mSlidingBuffer->BeginReading(oldStart);
distance = Distance(oldStart, mCurrentPosition);
mSlidingBuffer->DiscardPrefix(mCurrentPosition); mSlidingBuffer->DiscardPrefix(mCurrentPosition);
mSlidingBuffer->BeginReading(mCurrentPosition); mSlidingBuffer->BeginReading(mCurrentPosition);
mMarkPosition = mCurrentPosition; mMarkPosition = mCurrentPosition;
} }
return distance;
} }
/** /**
* Insert data to our underlying input buffer as * Insert data to our underlying input buffer as

Просмотреть файл

@ -207,7 +207,7 @@ class nsScanner {
* @param * @param
* @return * @return
*/ */
void Mark(void); PRInt32 Mark(void);
/** /**
* Resets current offset position of input stream to marked position. * Resets current offset position of input stream to marked position.
@ -338,8 +338,11 @@ class nsScanner {
PRInt32 mFirstNonWhitespacePosition; PRInt32 mFirstNonWhitespacePosition;
PRInt32 mCharsetSource; PRInt32 mCharsetSource;
nsCString mCharset; nsCString mCharset;
nsIUnicodeDecoder *mUnicodeDecoder; nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
nsParser *mParser; nsParser *mParser;
private:
nsScanner &operator =(const nsScanner &); // Not implemented.
}; };
#endif #endif