Added code to interrupt the parser's processing of tokens if a threshold is exceeded to improve interactivity during long page loads. Turned OFF by default. Can be enabled through a pref. bug 76722 r=harishd@netscape.com,rickg@netscape.com sr=vidur@netscape.com,attinasi@netscape.com a=chofmann@netscape.com

This commit is contained in:
kmcclusk%netscape.com 2001-06-21 02:06:23 +00:00
parent 32a7a16306
commit e52b604682
21 changed files with 999 additions and 45 deletions

View File

@ -102,6 +102,10 @@ public:
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition) { return NS_OK; }
NS_IMETHOD EndContext(PRInt32 aPosition) { return NS_OK; }
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
// nsIHTMLToTextSink
NS_IMETHOD Initialize(nsAWritableString* aOutString,

View File

@ -170,6 +170,35 @@ static PRLogModuleInfo* gSinkLogModuleInfo;
#define NS_SINK_FLAG_SCRIPT_ENABLED 0x8
#define NS_SINK_FLAG_FRAMES_ENABLED 0x10
#define NS_SINK_FLAG_CAN_INTERRUPT_PARSER 0x20 //Interrupt parsing when mMaxTokenProcessingTime is exceeded
// Timer used to determine how long the content sink
// spends processing a tokens
class nsDelayTimer
{
public:
void Start(void) {
mStart = PR_IntervalToMicroseconds(PR_IntervalNow());
}
// Determine if the current time - start time is greater
// then aMaxDelayInMicroseconds
PRBool HasExceeded(PRUint32 aMaxDelayInMicroseconds) {
PRUint32 stop = PR_IntervalToMicroseconds(PR_IntervalNow());
if ((stop - mStart) > aMaxDelayInMicroseconds) {
return PR_TRUE;
}
return PR_FALSE;
}
private:
PRUint32 mStart;
};
class SinkContext;
@ -209,6 +238,11 @@ public:
NS_IMETHOD AddComment(const nsIParserNode& aNode);
NS_IMETHOD AddProcessingInstruction(const nsIParserNode& aNode);
NS_IMETHOD AddDocTypeDecl(const nsIParserNode& aNode, PRInt32 aMode=0);
NS_IMETHOD WillProcessTokens(void);
NS_IMETHOD DidProcessTokens(void);
NS_IMETHOD WillProcessAToken(void);
NS_IMETHOD DidProcessAToken(void);
// nsIHTMLContentSink
NS_IMETHOD BeginContext(PRInt32 aID);
@ -356,6 +390,7 @@ public:
nsSupportsArray mScriptElements;
PRBool mParserBlocked;
PRBool mNeedToBlockParser;
nsCOMPtr<nsIRequest> mDummyParserRequest;
nsCString mRef;
@ -368,6 +403,10 @@ public:
PRInt32 mInMonolithicContainer;
PRUint32 mFlags;
// Can interrupt parsing members
nsDelayTimer mDelayTimer;
PRInt32 mMaxTokenProcessingTime; // Interrupt parsing during token procesing after # of microseconds
void StartLayout();
void ScrollToRef();
@ -410,13 +449,134 @@ public:
nsIContent* aChildContent,
PRInt32 aIndexInContainer);
PRBool IsMonolithicContainer(nsHTMLTag aTag);
// CanInterrupt parsing related routines
nsresult AddDummyParserRequest(void);
nsresult RemoveDummyParserRequest(void);
#ifdef NS_DEBUG
void ForceReflow();
#endif
MOZ_TIMER_DECLARE(mWatch) // Measures content model creation time for current document
};
//----------------------------------------------------------------------
//
// DummyParserRequest
//
// This is a dummy request implementation that we add to the document's load
// group. It ensures that EndDocumentLoad() in the docshell doesn't fire
// before we've finished all of parsing and tokenizing of the document.
//
class DummyParserRequest : public nsIChannel
{
protected:
DummyParserRequest(nsIHTMLContentSink* aSink);
virtual ~DummyParserRequest();
static PRInt32 gRefCnt;
static nsIURI* gURI;
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsIHTMLContentSink* mSink; // Weak reference
public:
static nsresult
Create(nsIRequest** aResult, nsIHTMLContentSink* aSink);
NS_DECL_ISUPPORTS
// nsIRequest
NS_IMETHOD GetName(PRUnichar* *result) {
*result = ToNewUnicode(NS_LITERAL_STRING("about:layout-dummy-request"));
return NS_OK;
}
NS_IMETHOD IsPending(PRBool *_retval) { *_retval = PR_TRUE; return NS_OK; }
NS_IMETHOD GetStatus(nsresult *status) { *status = NS_OK; return NS_OK; }
NS_IMETHOD Cancel(nsresult status);
NS_IMETHOD Suspend(void) { return NS_OK; }
NS_IMETHOD Resume(void) { return NS_OK; }
// nsIChannel
NS_IMETHOD GetOriginalURI(nsIURI* *aOriginalURI) { *aOriginalURI = gURI; NS_ADDREF(*aOriginalURI); return NS_OK; }
NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) { gURI = aOriginalURI; NS_ADDREF(gURI); return NS_OK; }
NS_IMETHOD GetURI(nsIURI* *aURI) { *aURI = gURI; NS_ADDREF(*aURI); return NS_OK; }
NS_IMETHOD SetURI(nsIURI* aURI) { gURI = aURI; NS_ADDREF(gURI); return NS_OK; }
NS_IMETHOD Open(nsIInputStream **_retval) { *_retval = nsnull; return NS_OK; }
NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) { return NS_OK; }
NS_IMETHOD GetLoadFlags(nsLoadFlags *aLoadFlags) { *aLoadFlags = nsIRequest::LOAD_NORMAL; return NS_OK; }
NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
NS_IMETHOD GetOwner(nsISupports * *aOwner) { *aOwner = nsnull; return NS_OK; }
NS_IMETHOD SetOwner(nsISupports * aOwner) { return NS_OK; }
NS_IMETHOD GetLoadGroup(nsILoadGroup * *aLoadGroup) { *aLoadGroup = mLoadGroup; NS_IF_ADDREF(*aLoadGroup); return NS_OK; }
NS_IMETHOD SetLoadGroup(nsILoadGroup * aLoadGroup) { mLoadGroup = aLoadGroup; return NS_OK; }
NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor * *aNotificationCallbacks) { *aNotificationCallbacks = nsnull; return NS_OK; }
NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor * aNotificationCallbacks) { return NS_OK; }
NS_IMETHOD GetSecurityInfo(nsISupports * *aSecurityInfo) { *aSecurityInfo = nsnull; return NS_OK; }
NS_IMETHOD GetContentType(char * *aContentType) { *aContentType = nsnull; return NS_OK; }
NS_IMETHOD SetContentType(const char * aContentType) { return NS_OK; }
NS_IMETHOD GetContentLength(PRInt32 *aContentLength) { return NS_OK; }
NS_IMETHOD SetContentLength(PRInt32 aContentLength) { return NS_OK; }
};
PRInt32 DummyParserRequest::gRefCnt;
nsIURI* DummyParserRequest::gURI;
NS_IMPL_ADDREF(DummyParserRequest);
NS_IMPL_RELEASE(DummyParserRequest);
NS_IMPL_QUERY_INTERFACE2(DummyParserRequest, nsIRequest, nsIChannel);
nsresult
DummyParserRequest::Create(nsIRequest** aResult, nsIHTMLContentSink* aSink)
{
DummyParserRequest* request = new DummyParserRequest(aSink);
if (!request)
return NS_ERROR_OUT_OF_MEMORY;
return request->QueryInterface(NS_GET_IID(nsIRequest), (void**) aResult);
}
DummyParserRequest::DummyParserRequest(nsIHTMLContentSink* aSink)
{
NS_INIT_REFCNT();
if (gRefCnt++ == 0) {
nsresult rv;
rv = NS_NewURI(&gURI, "about:parser-dummy-request", nsnull);
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create about:parser-dummy-request");
}
mSink = aSink;
}
DummyParserRequest::~DummyParserRequest()
{
if (--gRefCnt == 0) {
NS_IF_RELEASE(gURI);
}
}
NS_IMETHODIMP
DummyParserRequest::Cancel(nsresult status)
{
// Cancel parser
nsresult rv = NS_OK;
HTMLContentSink* sink = NS_STATIC_CAST(HTMLContentSink*, mSink);
if ((sink) && (sink->mParser)) {
sink->mParser->CancelParsingEvents();
}
return rv;
}
class SinkContext {
public:
SinkContext(HTMLContentSink* aSink);
@ -2151,6 +2311,7 @@ HTMLContentSink::HTMLContentSink() {
mFlags=0;
mNeedToBlockParser = PR_FALSE;
mParserBlocked = PR_FALSE;
mDummyParserRequest = nsnull;
}
HTMLContentSink::~HTMLContentSink()
@ -2329,16 +2490,42 @@ HTMLContentSink::Init(nsIDocument* aDoc,
// The mNotificationInterval has a dramatic effect on how long it
// takes to initially display content for slow connections.
// The current value of 1/4 of second provides good
// The current value provides good
// incremental display of content without causing an increase
// in page load time. If this value is set below 1/10 of second
// it starts to impact page load performance.
// see bugzilla bug 72138 for more info.
mNotificationInterval = 250000;
mNotificationInterval = 120000;
if (prefs) {
prefs->GetIntPref("content.notify.interval", &mNotificationInterval);
}
// The mMaxTokenProcessingTime controls how long we stay away from
// the event loop when processing token. A lower value
// makes the app more responsive, but may increase page load time.
// The content sink mNotificationInterval gates how frequently the content
// is processed so it will also affect how interactive the app is during
// page load also. The mNotification prevents contents flushes from happening
// too frequently. while mMaxTokenProcessingTime prevents flushes from happening
// too infrequently.
// The current ratio of 3 to 1 was determined to be the lowest mMaxTokenProcessingTime
// which does not impact page load performance.
// See bugzilla bug 76722 for details.
mMaxTokenProcessingTime = mNotificationInterval * 3;
PRBool enableInterruptParsing = PR_FALSE;
if (prefs) {
prefs->GetBoolPref("content.interrupt.parsing", &enableInterruptParsing);
prefs->GetIntPref("content.max.tokenizing.time", &mMaxTokenProcessingTime);
}
if (enableInterruptParsing) {
mFlags |= NS_SINK_FLAG_CAN_INTERRUPT_PARSER;
}
// Changed from 8192 to greatly improve page loading performance on large
// pages. See bugzilla bug 77540.
mMaxTextRun = 8191;
@ -2420,6 +2607,16 @@ HTMLContentSink::Init(nsIDocument* aDoc,
NS_IMETHODIMP
HTMLContentSink::WillBuildModel(void)
{
if (mFlags & NS_SINK_FLAG_CAN_INTERRUPT_PARSER) {
nsresult rv = AddDummyParserRequest();
NS_ASSERTION(NS_SUCCEEDED(rv), "Adding dummy parser request failed");
if (NS_FAILED(rv)) {
// Don't return the error result, just reset flag which indicates that it can
// interrupt parsing. If AddDummyParserRequests fails it should not affect
// WillBuildModel.
mFlags &= ~NS_SINK_FLAG_CAN_INTERRUPT_PARSER;
}
}
// Notify document that the load is beginning
mDocument->BeginLoad();
return NS_OK;
@ -2428,6 +2625,7 @@ HTMLContentSink::WillBuildModel(void)
NS_IMETHODIMP
HTMLContentSink::DidBuildModel(PRInt32 aQualityLevel)
{
// NRA Dump stopwatch stop info here
#ifdef MOZ_PERF_METRICS
MOZ_TIMER_DEBUGLOG(("Stop: nsHTMLContentSink::DidBuildModel(), this=%p\n", this));
@ -2500,6 +2698,14 @@ HTMLContentSink::DidBuildModel(PRInt32 aQualityLevel)
// Drop our reference to the parser to get rid of a circular
// reference.
NS_IF_RELEASE(mParser);
if (mFlags & NS_SINK_FLAG_CAN_INTERRUPT_PARSER) {
// Note: Don't return value from RemoveDummyParserRequest,
// If RemoveDummyParserRequests fails it should not affect
// DidBuildModel. The remove can fail if the parser request
// was already removed by a DummyParserRequest::Cancel
RemoveDummyParserRequest();
}
return NS_OK;
}
@ -3111,7 +3317,7 @@ HTMLContentSink::CloseMap(const nsIParserNode& aNode)
NS_IMETHODIMP
HTMLContentSink::GetPref(PRInt32 aTag,PRBool& aPref) {
nsHTMLTag theHTMLTag = nsHTMLTag(aTag);
if (theHTMLTag == eHTMLTag_script) {
aPref = mFlags & NS_SINK_FLAG_SCRIPT_ENABLED;
}
@ -3494,6 +3700,33 @@ HTMLContentSink::AddDocTypeDecl(const nsIParserNode& aNode, PRInt32 aMode)
}
NS_IMETHODIMP
HTMLContentSink::WillProcessTokens(void) {
if (mFlags & NS_SINK_FLAG_CAN_INTERRUPT_PARSER) {
mDelayTimer.Start();
}
return NS_OK;
}
NS_IMETHODIMP
HTMLContentSink::DidProcessTokens(void) {
return NS_OK;
}
NS_IMETHODIMP
HTMLContentSink::WillProcessAToken(void) {
return NS_OK;
}
NS_IMETHODIMP
HTMLContentSink::DidProcessAToken(void) {
if ((mFlags & NS_SINK_FLAG_CAN_INTERRUPT_PARSER) && (mDelayTimer.HasExceeded(mMaxTokenProcessingTime))) {
return NS_ERROR_HTMLPARSER_INTERRUPTED;
}
return NS_OK;
}
void
HTMLContentSink::StartLayout()
{
@ -4881,3 +5114,62 @@ HTMLContentSink::DumpContentModel()
}
return result;
}
// If the content sink can interrupt the parser (@see mCanInteruptParsing)
// then it needs to schedule a dummy parser request to delay the document
// from firing onload handlers and other document done actions until all of the
// parsing has completed.
nsresult
HTMLContentSink::AddDummyParserRequest(void)
{
nsresult rv = NS_OK;
NS_ASSERTION(nsnull == mDummyParserRequest,"Already have a dummy parser request");
rv = DummyParserRequest::Create(getter_AddRefs(mDummyParserRequest), this);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsILoadGroup> loadGroup;
if (mDocument) {
rv = mDocument->GetDocumentLoadGroup(getter_AddRefs(loadGroup));
if (NS_FAILED(rv)) return rv;
}
if (loadGroup) {
nsCOMPtr<nsIChannel> channel = do_QueryInterface(mDummyParserRequest);
if (channel) {
rv = channel->SetLoadGroup(loadGroup);
if (NS_FAILED(rv)) return rv;
rv = loadGroup->AddRequest(mDummyParserRequest, nsnull);
if (NS_FAILED(rv)) return rv;
} else {
return NS_ERROR_FAILURE;
}
}
return rv;
}
nsresult
HTMLContentSink::RemoveDummyParserRequest(void)
{
nsresult rv = NS_OK;
nsCOMPtr<nsILoadGroup> loadGroup;
if (mDocument) {
rv = mDocument->GetDocumentLoadGroup(getter_AddRefs(loadGroup));
if (NS_FAILED(rv)) return rv;
}
if (loadGroup && mDummyParserRequest) {
rv = loadGroup->RemoveRequest(mDummyParserRequest, nsnull, NS_OK);
if (NS_FAILED(rv)) {
return rv;
}
mDummyParserRequest = nsnull;
}
return rv;
}

View File

@ -96,6 +96,10 @@ public:
NS_IMETHOD OpenMap(const nsIParserNode& aNode);
NS_IMETHOD CloseMap(const nsIParserNode& aNode);
NS_IMETHOD FlushPendingNotifications() { return NS_OK; }
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
NS_IMETHOD DoFragment(PRBool aFlag);

View File

@ -86,6 +86,10 @@ public:
NS_IMETHOD WillResume(void) { return NS_OK; }
NS_IMETHOD SetParser(nsIParser* aParser) { return NS_OK; }
NS_IMETHOD FlushPendingNotifications() { return NS_OK; }
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition){ return NS_OK; }

View File

@ -520,8 +520,12 @@ nsresult CNavDTD::BuildModel(nsIParser* aParser,nsITokenizer* aTokenizer,nsIToke
mTokenizer->PushTokenFront(theToken); //this token should get pushed on the context stack.
}
}
mSink->WillProcessTokens();
while(NS_SUCCEEDED(result)){
//Currently nsIHTMLContentSink does nothing with a call to WillProcessAToken.
//mSink->WillProcessAToken();
#if 0
int n=aTokenizer->GetCount();
@ -544,12 +548,34 @@ nsresult CNavDTD::BuildModel(nsIParser* aParser,nsITokenizer* aTokenizer,nsIToke
result=mDTDState;
break;
}
if ((NS_ERROR_HTMLPARSER_INTERRUPTED == mSink->DidProcessAToken())) {
// The content sink has requested that DTD interrupt processing tokens
// So we need to make sure the parser is in a state where it can be
// interrupted.
// The mParser->CanInterrupt will return TRUE if BuildModel was called
// from a place in the parser where it prepared to handle a return value of
// NS_ERROR_HTMLPARSER_INTERRUPTED.
// If the parser has mPrevContext then it may be processing
// Script so we should not allow it to be interrupted.
if ((mParser->CanInterrupt()) &&
(nsnull == mParser->PeekContext()->mPrevContext) &&
(eHTMLTag_unknown==mSkipTarget)) {
result = NS_ERROR_HTMLPARSER_INTERRUPTED;
break;
}
}
}//while
mTokenizer=oldTokenizer;
//Currently nsIHTMLContentSink does nothing with a call to DidProcessATokens().
//mSink->DidProcessTokens();
}
}
}
else result=NS_ERROR_HTMLPARSER_BADTOKENIZER;
return result;
}
@ -2219,7 +2245,6 @@ nsresult CNavDTD::HandleAttributeToken(CToken* aToken) {
return NS_OK;
}
/**
* This method gets called when a script token has been
* encountered in the parse process. n
@ -2236,6 +2261,8 @@ nsresult CNavDTD::HandleScriptToken(const nsIParserNode *aNode) {
nsresult result=AddLeaf(aNode);
mParser->SetCanInterrupt(PR_FALSE);
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: CNavDTD::HandleScriptToken(), this=%p\n", this));
START_TIMER();

View File

@ -108,7 +108,6 @@ class nsITokenizer;
class nsCParserNode;
class nsTokenAllocator;
/***************************************************************
Now the main event: CNavDTD.

View File

@ -71,6 +71,10 @@ public:
NS_IMETHOD OpenFrameset(const nsIParserNode& aNode);
NS_IMETHOD CloseFrameset(const nsIParserNode& aNode);
NS_IMETHOD GetPref(PRInt32 aTag,PRBool& aPref) { return NS_OK; }
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition);

View File

@ -227,6 +227,34 @@ public:
*/
NS_IMETHOD GetPref(PRInt32 aTag,PRBool& aPref)=0;
/**
* This method is called when parser is about to begin
* synchronously processing a chunk of tokens.
*/
NS_IMETHOD WillProcessTokens(void)=0;
/**
* This method is called when parser has
* completed processing a chunk of tokens. The processing of the
* tokens may be interrupted by returning NS_ERROR_HTMLPARSER_INTERRUPTED from
* DidProcessAToken.
*/
NS_IMETHOD DidProcessTokens()=0;
/**
* This method is called when parser is about to
* process a single token
*/
NS_IMETHOD WillProcessAToken(void)=0;
/**
* This method is called when parser has completed
* the processing for a single token.
* @return NS_OK if processing should not be interrupted
* NS_ERROR_HTMLPARSER_INTERRUPTED if the parsing should be interrupted
*/
NS_IMETHOD DidProcessAToken(void)=0;
};
extern NS_HTMLPARS nsresult NS_NewHTMLNullSink(nsIContentSink** aInstancePtrResult);

View File

@ -110,7 +110,7 @@ public:
virtual PRUint32 GetSize(void)=0;
};
/**
/**
* FOR DEBUG PURPOSE ONLY
*
* Use this interface to query objects that contain content information.
@ -233,6 +233,7 @@ class nsIParser : public nsISupports {
virtual void UnblockParser() =0;
virtual PRBool IsParserEnabled() =0;
virtual PRBool IsComplete() =0;
virtual nsresult Parse(nsIURI* aURL,nsIRequestObserver* aListener = nsnull,PRBool aEnableVerify=PR_FALSE, void* aKey=0,nsDTDMode aMode=eDTDMode_autodetect) = 0;
virtual nsresult Parse(nsIInputStream& aStream, const nsString& aMimeType,PRBool aEnableVerify=PR_FALSE, void* aKey=0,nsDTDMode aMode=eDTDMode_autodetect) = 0;
@ -276,6 +277,18 @@ class nsIParser : public nsISupports {
eParserCommands aCommand,
const nsString* aMimeType=nsnull,
nsDTDMode aDTDMode=eDTDMode_unknown)=0;
/**
* Call this method to cancel any pending parsing events.
* Parsing events may be pending if all of the document's content
* has been passed to the parser but the parser has been interrupted
* because processing the tokens took too long.
*
* @update kmcclusk 05/18/01
* @return NS_OK if succeeded else ERROR.
*/
NS_IMETHOD CancelParsingEvents()=0;
};
/* ===========================================================*
@ -304,6 +317,7 @@ class nsIParser : public nsISupports {
#define NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_HTMLPARSER,1016)
#define NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_HTMLPARSER,1017)
#define NS_ERROR_HTMLPARSER_CONTINUE NS_OK

View File

@ -76,6 +76,10 @@ public:
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition);
NS_IMETHOD EndContext(PRInt32 aPosition);
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
// nsILoggingSink
NS_IMETHOD SetOutputStream(PRFileDesc *aStream,PRBool autoDelete=PR_FALSE);

View File

@ -46,6 +46,8 @@
#include "prenv.h"
#include "nsParserCIID.h"
#include "nsCOMPtr.h"
#include "nsIEventQueue.h"
#include "nsIEventQueueService.h"
//#define rickgdebug
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
@ -57,6 +59,7 @@ static NS_DEFINE_CID(kWellFormedDTDCID, NS_WELLFORMEDDTD_CID);
static NS_DEFINE_CID(kNavDTDCID, NS_CNAVDTD_CID);
static NS_DEFINE_CID(kCOtherDTDCID, NS_COTHER_DTD_CID);
static NS_DEFINE_CID(kViewSourceDTDCID, NS_VIEWSOURCE_DTD_CID);
static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
static const char* kNullURL = "Error: Null URL given";
static const char* kOnStartNotCalled = "Error: OnStartRequest() must be called before OnDataAvailable()";
@ -150,6 +153,99 @@ public:
nsIDTD *mOtherDTD; //it's ok to leak this; the deque contains a copy too.
};
//-------------- Begin ParseContinue Event Definition ------------------------
/*
The parser can be explicitly interrupted by passing a return value of NS_ERROR_HTMLPARSER_INTERRUPTED
from BuildModel on the DTD. This will cause the parser to stop processing and allow
the application to return to the event loop. The data which was left at the time of
interruption will be processed the next time OnDataAvailable is called. If the parser
has received its final chunk of data then OnDataAvailable will no longer be called by the
networking module, so the parser will schedule a nsParserContinueEvent which will call
the parser to process the remaining data after returning to the event loop. If the parser
is interrupted while processing the remaining data it will schedule another
ParseContinueEvent. The processing of data followed by scheduling of the continue events
will proceed until either:
1) All of the remaining data can be processed without interrupting
2) The parser has been cancelled.
This capability is currently used in CNavDTD and nsHTMLContentSink. The nsHTMLContentSink is
notified by CNavDTD when a chunk of tokens is going to be processed and when each token
is processed. The nsHTML content sink records the time when the chunk has started
processing and will return NS_ERROR_HTMLPARSER_INTERRUPTED if the token processing time
has exceeded a threshold called max tokenizing processing time. This allows the content
sink to limit how much data is processed in a single chunk which in turn gates how much
time is spent away from the event loop. Processing smaller chunks of data also reduces
the time spent in subsequent reflows.
This capability is most apparent when loading large documents. If the maximum token
processing time is set small enough the application will remain responsive during
document load.
A side-effect of this capability is that document load is not complete when the last chunk
of data is passed to OnDataAvailable since the parser may have been interrupted when
the last chunk of data arrived. The document is complete when all of the document has
been tokenized and there aren't any pending nsParserContinueEvents. This can cause
problems if the application assumes that it can monitor the load requests to determine
when the document load has been completed. This is what happens in Mozilla. The document
is considered completely loaded when all of the load requests have been satisfied. To delay the
document load until all of the parsing has been completed the nsHTMLContentSink adds a
dummy parser load request which is not removed until the nsHTMLContentSink's DidBuildModel
is called. The CNavDTD will not call DidBuildModel until the final chunk of data has been
passed to the parser through the OnDataAvailable and there aren't any pending
nsParserContineEvents.
Currently the parser is ignores requests to be interrupted during the processing of script.
This is because a document.write followed by JavaScript calls to manipulate the DOM may
fail if the parser was interrupted during the document.write.
For more details @see bugzilla bug 76722
*/
struct nsParserContinueEvent : public PLEvent {
nsParserContinueEvent(nsIParser* aParser);
~nsParserContinueEvent() { }
void HandleEvent() {
if (mParser) {
nsParser* parser = NS_STATIC_CAST(nsParser*, mParser);
parser->HandleParserContinueEvent();
NS_RELEASE(mParser);
}
};
nsIParser* mParser;
};
static void PR_CALLBACK HandlePLEvent(nsParserContinueEvent* aEvent)
{
NS_ASSERTION(nsnull != aEvent,"Event is null");
aEvent->HandleEvent();
}
static void PR_CALLBACK DestroyPLEvent(nsParserContinueEvent* aEvent)
{
NS_ASSERTION(nsnull != aEvent,"Event is null");
delete aEvent;
}
nsParserContinueEvent::nsParserContinueEvent(nsIParser* aParser)
{
NS_ASSERTION(aParser, "null parameter");
mParser = aParser;
PL_InitEvent(this, aParser,
(PLHandleEventProc) ::HandlePLEvent,
(PLDestroyEventProc) ::DestroyPLEvent);
}
//-------------- End ParseContinue Event Definition ------------------------
static CSharedParserObjects* gSharedParserObjects=0;
@ -247,11 +343,24 @@ nsParser::nsParser(nsITokenObserver* anObserver) {
mCommand=eViewNormal;
mParserEnabled=PR_TRUE;
mBundle=nsnull;
mPendingContinueEvent=PR_FALSE;
mCanInterrupt=PR_FALSE;
MOZ_TIMER_DEBUGLOG(("Reset: Parse Time: nsParser::nsParser(), this=%p\n", this));
MOZ_TIMER_RESET(mParseTime);
MOZ_TIMER_RESET(mDTDTime);
MOZ_TIMER_RESET(mTokenizeTime);
nsresult rv = NS_OK;
if (mEventQueue == nsnull) {
// Cache the event queue of the current UI thread
NS_WITH_SERVICE(nsIEventQueueService, eventService, kEventQueueServiceCID, &rv);
if (NS_SUCCEEDED(rv) && (eventService)) { // XXX this implies that the UI is the current thread.
rv = eventService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(mEventQueue));
}
NS_ASSERTION(mEventQueue, "event queue is null");
}
}
/**
@ -286,6 +395,11 @@ nsParser::~nsParser() {
//don't forget to add code here to delete
//what may be several contexts...
delete mParserContext;
if (mPendingContinueEvent) {
NS_ASSERTION(mEventQueue != nsnull,"Event queue is null");
mEventQueue->RevokeEvents(this);
}
}
@ -339,6 +453,24 @@ nsresult nsParser::QueryInterface(const nsIID& aIID, void** aInstancePtr)
return NS_OK;
}
// The parser continue event is posted only if
// all of the data to parse has been passed to ::OnDataAvailable
// and the parser has been interrupted by the content sink
// because the processing of tokens took too long.
nsresult
nsParser::PostContinueEvent()
{
if ((! mPendingContinueEvent) && (mEventQueue)) {
nsParserContinueEvent* ev = new nsParserContinueEvent(NS_STATIC_CAST(nsIParser*, this));
NS_ENSURE_TRUE(ev,NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(this);
mEventQueue->PostEvent(ev);
mPendingContinueEvent = PR_TRUE;
}
return NS_OK;
}
/**
*
@ -1240,6 +1372,19 @@ NS_IMETHODIMP nsParser::CreateCompatibleDTD(nsIDTD** aDTD,
}
NS_IMETHODIMP
nsParser::CancelParsingEvents() {
if (mPendingContinueEvent) {
NS_ASSERTION(mEventQueue,"Event queue is null");
// Revoke all pending continue parsing events
if (mEventQueue != nsnull) {
mEventQueue->RevokeEvents(this);
}
mPendingContinueEvent=PR_FALSE;
}
return NS_OK;
}
//#define TEST_DOCTYPES
#ifdef TEST_DOCTYPES
static const char* doctypes[] = {
@ -1433,13 +1578,15 @@ nsresult nsParser::DidBuildModel(nsresult anErrorCode) {
//One last thing...close any open containers.
nsresult result=anErrorCode;
if(mParserContext && !mParserContext->mPrevContext) {
if(mParserContext->mDTD) {
result=mParserContext->mDTD->DidBuildModel(anErrorCode,PRBool(0==mParserContext->mPrevContext),this,mSink);
}
//Ref. to bug 61462.
NS_IF_RELEASE(mBundle);
}//if
if (IsComplete()) {
if(mParserContext && !mParserContext->mPrevContext) {
if(mParserContext->mDTD) {
result=mParserContext->mDTD->DidBuildModel(anErrorCode,PRBool(0==mParserContext->mPrevContext),this,mSink);
}
//Ref. to bug 61462.
NS_IF_RELEASE(mBundle);
}//if
}
return result;
}
@ -1540,7 +1687,7 @@ nsresult nsParser::ContinueParsing(){
if(result!=NS_OK)
result=mInternalState;
return result;
}
@ -1582,6 +1729,29 @@ PRBool nsParser::IsParserEnabled() {
return mParserEnabled;
}
/**
* Call this to query whether the parser thinks it's done with parsing.
*
* @update rickg 5/12/01
* @return complete state
*/
PRBool nsParser::IsComplete() {
return (! mPendingContinueEvent);
}
void nsParser::HandleParserContinueEvent() {
mPendingContinueEvent = PR_FALSE;
ContinueParsing();
}
PRBool nsParser::CanInterrupt(void) {
return mCanInterrupt;
}
void nsParser::SetCanInterrupt(PRBool aCanInterrupt) {
mCanInterrupt = aCanInterrupt;
}
/**
* This is the main controlling routine in the parsing process.
@ -1679,7 +1849,16 @@ aMimeType,PRBool aVerifyEnabled,PRBool aLastCall,nsDTDMode aMode){
//NOTE: Make sure that updates to this method don't cause
// bug #2361 to break again!
nsresult result=NS_OK;
nsresult result=NS_OK;
if(aLastCall && (0==aSourceBuffer.Length())) {
// Nothing is being passed to the parser so return
// immediately. mUnusedInput will get processed when
// some data is actually passed in.
return result;
}
nsParser* me = this;
// Maintain a reference to ourselves so we don't go away
// till we're completely done.
@ -1687,7 +1866,7 @@ aMimeType,PRBool aVerifyEnabled,PRBool aLastCall,nsDTDMode aMode){
if(aSourceBuffer.Length() || mUnusedInput.Length()) {
mDTDVerification=aVerifyEnabled;
CParserContext* pc=0;
CParserContext* pc=0;
if((!mParserContext) || (mParserContext->mKey!=aKey)) {
//only make a new context if we dont have one, OR if we do, but has a different context key...
@ -1867,10 +2046,19 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
}
}
//Only allow parsing to be interuptted in the subsequent call
//to build model.
SetCanInterrupt(PR_TRUE);
nsresult theTokenizerResult=Tokenize(aIsFinalChunk); // kEOF==2152596456
result=BuildModel();
theIterationIsOk=PRBool(kEOF!=theTokenizerResult);
if(result==NS_ERROR_HTMLPARSER_INTERRUPTED) {
if(aIsFinalChunk)
PostContinueEvent();
}
SetCanInterrupt(PR_FALSE);
theIterationIsOk=PRBool((kEOF!=theTokenizerResult) && (result!=NS_ERROR_HTMLPARSER_INTERRUPTED));
// Make sure not to stop parsing too early. Therefore, before shutting down the
// parser, it's important to check whether the input buffer has been scanned to
@ -1895,7 +2083,7 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
break;
}
else if((NS_OK==result) && (theTokenizerResult==kEOF)){
else if(((NS_OK==result) && (theTokenizerResult==kEOF)) || (result==NS_ERROR_HTMLPARSER_INTERRUPTED)){
PRBool theContextIsStringBased=PRBool(CParserContext::eCTString==mParserContext->mContextType);
if( (eOnStop==mParserContext->mStreamListenerState) ||
@ -1918,11 +2106,11 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
MOZ_TIMER_LOG(("Tokenize Time: "));
MOZ_TIMER_PRINT(mTokenizeTime);
return result;
return NS_OK;
}
}
else {
else {
CParserContext* theContext=PopContext();
if(theContext) {
@ -1933,6 +2121,8 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
delete theContext;
}
result = mInternalState;
aIsFinalChunk=(mParserContext && mParserContext->mStreamListenerState==eOnStop)? PR_TRUE:PR_FALSE;
//...then intentionally fall through to WillInterruptParse()...
}
@ -1940,10 +2130,12 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
}
if(kEOF==theTokenizerResult) {
if((kEOF==theTokenizerResult) || (result==NS_ERROR_HTMLPARSER_INTERRUPTED)) {
result = (result == NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result;
mParserContext->mDTD->WillInterruptParse();
}
}//while
}//if
else {
@ -1954,7 +2146,7 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: nsParser::ResumeParse(), this=%p\n", this));
MOZ_TIMER_STOP(mParseTime);
return result;
return (result==NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result;
}
/**

View File

@ -70,6 +70,7 @@
#include "nsDTDUtils.h"
#include "nsTimer.h"
#include "nsIProgressEventSink.h"
#include "nsIEventQueue.h"
class IContentSink;
class nsIDTD;
@ -225,6 +226,14 @@ class nsParser : public nsIParser,
*/
virtual PRBool IsParserEnabled();
/**
* Call this to query whether the parser thinks it's done with parsing.
*
* @update rickg 5/12/01
* @return complete state
*/
virtual PRBool IsComplete();
/**
* This rather arcane method (hack) is used as a signal between the
* DTD and the parser. It allows the DTD to tell the parser that content
@ -319,6 +328,44 @@ class nsParser : public nsIParser,
const nsString* aMimeType=nsnull,
nsDTDMode aDTDMode=eDTDMode_unknown);
/**
* Removes continue parsing events
* @update kmcclusk 5/18/98
*/
NS_IMETHODIMP CancelParsingEvents();
/**
* Indicates whether the parser is in a state where it
* can be interrupted.
* @return PR_TRUE if parser can be interrupted, PR_FALSE if it can not be interrupted.
* @update kmcclusk 5/18/98
*/
PRBool CanInterrupt(void);
/**
* Set to parser state to indicate whether parsing tokens can be interrupted
* @param aCanInterrupt PR_TRUE if parser can be interrupted, PR_FALSE if it can not be interrupted.
* @update kmcclusk 5/18/98
*/
void SetCanInterrupt(PRBool aCanInterrupt);
/**
* This is called when the final chunk has been
* passed to the parser and the content sink has
* interrupted token processing. It schedules
* a ParserContinue PL_Event which will ask the parser
* to HandleParserContinueEvent when it is handled.
* @update kmcclusk6/1/2001
*/
nsresult PostContinueEvent();
/**
* Fired when the continue parse event is triggered.
* @update kmcclusk 5/18/98
*/
void HandleParserContinueEvent(void);
protected:
/**
@ -383,6 +430,8 @@ private:
* @return TRUE if all went well
*/
PRBool DidTokenize(PRBool aIsFinalChunk = PR_FALSE);
protected:
//*********************************************
// And now, some data members...
@ -396,6 +445,7 @@ protected:
nsIRequestObserver* mObserver;
nsIProgressEventSink* mProgressEventSink;
nsIContentSink* mSink;
nsIParserFilter* mParserFilter;
PRBool mDTDVerification;
eParserCommands mCommand;
@ -412,7 +462,12 @@ protected:
nsParserBundle* mBundle;
nsTokenAllocator mTokenAllocator;
public:
nsCOMPtr<nsIEventQueue> mEventQueue;
PRPackedBool mPendingContinueEvent;
PRPackedBool mCanInterrupt;
public:
MOZ_TIMER_DECLARE(mParseTime)
MOZ_TIMER_DECLARE(mDTDTime)
MOZ_TIMER_DECLARE(mTokenizeTime)

View File

@ -86,6 +86,10 @@ public:
NS_IMETHOD WillResume(void) { return NS_OK; }
NS_IMETHOD SetParser(nsIParser* aParser) { return NS_OK; }
NS_IMETHOD FlushPendingNotifications() { return NS_OK; }
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition){ return NS_OK; }

View File

@ -520,8 +520,12 @@ nsresult CNavDTD::BuildModel(nsIParser* aParser,nsITokenizer* aTokenizer,nsIToke
mTokenizer->PushTokenFront(theToken); //this token should get pushed on the context stack.
}
}
mSink->WillProcessTokens();
while(NS_SUCCEEDED(result)){
//Currently nsIHTMLContentSink does nothing with a call to WillProcessAToken.
//mSink->WillProcessAToken();
#if 0
int n=aTokenizer->GetCount();
@ -544,12 +548,34 @@ nsresult CNavDTD::BuildModel(nsIParser* aParser,nsITokenizer* aTokenizer,nsIToke
result=mDTDState;
break;
}
if ((NS_ERROR_HTMLPARSER_INTERRUPTED == mSink->DidProcessAToken())) {
// The content sink has requested that DTD interrupt processing tokens
// So we need to make sure the parser is in a state where it can be
// interrupted.
// The mParser->CanInterrupt will return TRUE if BuildModel was called
// from a place in the parser where it prepared to handle a return value of
// NS_ERROR_HTMLPARSER_INTERRUPTED.
// If the parser has mPrevContext then it may be processing
// Script so we should not allow it to be interrupted.
if ((mParser->CanInterrupt()) &&
(nsnull == mParser->PeekContext()->mPrevContext) &&
(eHTMLTag_unknown==mSkipTarget)) {
result = NS_ERROR_HTMLPARSER_INTERRUPTED;
break;
}
}
}//while
mTokenizer=oldTokenizer;
//Currently nsIHTMLContentSink does nothing with a call to DidProcessATokens().
//mSink->DidProcessTokens();
}
}
}
else result=NS_ERROR_HTMLPARSER_BADTOKENIZER;
return result;
}
@ -2219,7 +2245,6 @@ nsresult CNavDTD::HandleAttributeToken(CToken* aToken) {
return NS_OK;
}
/**
* This method gets called when a script token has been
* encountered in the parse process. n
@ -2236,6 +2261,8 @@ nsresult CNavDTD::HandleScriptToken(const nsIParserNode *aNode) {
nsresult result=AddLeaf(aNode);
mParser->SetCanInterrupt(PR_FALSE);
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: CNavDTD::HandleScriptToken(), this=%p\n", this));
START_TIMER();

View File

@ -108,7 +108,6 @@ class nsITokenizer;
class nsCParserNode;
class nsTokenAllocator;
/***************************************************************
Now the main event: CNavDTD.

View File

@ -71,6 +71,10 @@ public:
NS_IMETHOD OpenFrameset(const nsIParserNode& aNode);
NS_IMETHOD CloseFrameset(const nsIParserNode& aNode);
NS_IMETHOD GetPref(PRInt32 aTag,PRBool& aPref) { return NS_OK; }
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition);

View File

@ -227,6 +227,34 @@ public:
*/
NS_IMETHOD GetPref(PRInt32 aTag,PRBool& aPref)=0;
/**
* This method is called when parser is about to begin
* synchronously processing a chunk of tokens.
*/
NS_IMETHOD WillProcessTokens(void)=0;
/**
* This method is called when parser has
* completed processing a chunk of tokens. The processing of the
* tokens may be interrupted by returning NS_ERROR_HTMLPARSER_INTERRUPTED from
* DidProcessAToken.
*/
NS_IMETHOD DidProcessTokens()=0;
/**
* This method is called when parser is about to
* process a single token
*/
NS_IMETHOD WillProcessAToken(void)=0;
/**
* This method is called when parser has completed
* the processing for a single token.
* @return NS_OK if processing should not be interrupted
* NS_ERROR_HTMLPARSER_INTERRUPTED if the parsing should be interrupted
*/
NS_IMETHOD DidProcessAToken(void)=0;
};
extern NS_HTMLPARS nsresult NS_NewHTMLNullSink(nsIContentSink** aInstancePtrResult);

View File

@ -110,7 +110,7 @@ public:
virtual PRUint32 GetSize(void)=0;
};
/**
/**
* FOR DEBUG PURPOSE ONLY
*
* Use this interface to query objects that contain content information.
@ -233,6 +233,7 @@ class nsIParser : public nsISupports {
virtual void UnblockParser() =0;
virtual PRBool IsParserEnabled() =0;
virtual PRBool IsComplete() =0;
virtual nsresult Parse(nsIURI* aURL,nsIRequestObserver* aListener = nsnull,PRBool aEnableVerify=PR_FALSE, void* aKey=0,nsDTDMode aMode=eDTDMode_autodetect) = 0;
virtual nsresult Parse(nsIInputStream& aStream, const nsString& aMimeType,PRBool aEnableVerify=PR_FALSE, void* aKey=0,nsDTDMode aMode=eDTDMode_autodetect) = 0;
@ -276,6 +277,18 @@ class nsIParser : public nsISupports {
eParserCommands aCommand,
const nsString* aMimeType=nsnull,
nsDTDMode aDTDMode=eDTDMode_unknown)=0;
/**
* Call this method to cancel any pending parsing events.
* Parsing events may be pending if all of the document's content
* has been passed to the parser but the parser has been interrupted
* because processing the tokens took too long.
*
* @update kmcclusk 05/18/01
* @return NS_OK if succeeded else ERROR.
*/
NS_IMETHOD CancelParsingEvents()=0;
};
/* ===========================================================*
@ -304,6 +317,7 @@ class nsIParser : public nsISupports {
#define NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_HTMLPARSER,1016)
#define NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_HTMLPARSER,1017)
#define NS_ERROR_HTMLPARSER_CONTINUE NS_OK

View File

@ -76,6 +76,10 @@ public:
NS_IMETHOD DoFragment(PRBool aFlag);
NS_IMETHOD BeginContext(PRInt32 aPosition);
NS_IMETHOD EndContext(PRInt32 aPosition);
NS_IMETHOD WillProcessTokens(void) { return NS_OK; }
NS_IMETHOD DidProcessTokens(void) { return NS_OK; }
NS_IMETHOD WillProcessAToken(void) { return NS_OK; }
NS_IMETHOD DidProcessAToken(void) { return NS_OK; }
// nsILoggingSink
NS_IMETHOD SetOutputStream(PRFileDesc *aStream,PRBool autoDelete=PR_FALSE);

View File

@ -46,6 +46,8 @@
#include "prenv.h"
#include "nsParserCIID.h"
#include "nsCOMPtr.h"
#include "nsIEventQueue.h"
#include "nsIEventQueueService.h"
//#define rickgdebug
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
@ -57,6 +59,7 @@ static NS_DEFINE_CID(kWellFormedDTDCID, NS_WELLFORMEDDTD_CID);
static NS_DEFINE_CID(kNavDTDCID, NS_CNAVDTD_CID);
static NS_DEFINE_CID(kCOtherDTDCID, NS_COTHER_DTD_CID);
static NS_DEFINE_CID(kViewSourceDTDCID, NS_VIEWSOURCE_DTD_CID);
static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
static const char* kNullURL = "Error: Null URL given";
static const char* kOnStartNotCalled = "Error: OnStartRequest() must be called before OnDataAvailable()";
@ -150,6 +153,99 @@ public:
nsIDTD *mOtherDTD; //it's ok to leak this; the deque contains a copy too.
};
//-------------- Begin ParseContinue Event Definition ------------------------
/*
The parser can be explicitly interrupted by passing a return value of NS_ERROR_HTMLPARSER_INTERRUPTED
from BuildModel on the DTD. This will cause the parser to stop processing and allow
the application to return to the event loop. The data which was left at the time of
interruption will be processed the next time OnDataAvailable is called. If the parser
has received its final chunk of data then OnDataAvailable will no longer be called by the
networking module, so the parser will schedule a nsParserContinueEvent which will call
the parser to process the remaining data after returning to the event loop. If the parser
is interrupted while processing the remaining data it will schedule another
ParseContinueEvent. The processing of data followed by scheduling of the continue events
will proceed until either:
1) All of the remaining data can be processed without interrupting
2) The parser has been cancelled.
This capability is currently used in CNavDTD and nsHTMLContentSink. The nsHTMLContentSink is
notified by CNavDTD when a chunk of tokens is going to be processed and when each token
is processed. The nsHTML content sink records the time when the chunk has started
processing and will return NS_ERROR_HTMLPARSER_INTERRUPTED if the token processing time
has exceeded a threshold called max tokenizing processing time. This allows the content
sink to limit how much data is processed in a single chunk which in turn gates how much
time is spent away from the event loop. Processing smaller chunks of data also reduces
the time spent in subsequent reflows.
This capability is most apparent when loading large documents. If the maximum token
processing time is set small enough the application will remain responsive during
document load.
A side-effect of this capability is that document load is not complete when the last chunk
of data is passed to OnDataAvailable since the parser may have been interrupted when
the last chunk of data arrived. The document is complete when all of the document has
been tokenized and there aren't any pending nsParserContinueEvents. This can cause
problems if the application assumes that it can monitor the load requests to determine
when the document load has been completed. This is what happens in Mozilla. The document
is considered completely loaded when all of the load requests have been satisfied. To delay the
document load until all of the parsing has been completed the nsHTMLContentSink adds a
dummy parser load request which is not removed until the nsHTMLContentSink's DidBuildModel
is called. The CNavDTD will not call DidBuildModel until the final chunk of data has been
passed to the parser through the OnDataAvailable and there aren't any pending
nsParserContineEvents.
Currently the parser is ignores requests to be interrupted during the processing of script.
This is because a document.write followed by JavaScript calls to manipulate the DOM may
fail if the parser was interrupted during the document.write.
For more details @see bugzilla bug 76722
*/
struct nsParserContinueEvent : public PLEvent {
nsParserContinueEvent(nsIParser* aParser);
~nsParserContinueEvent() { }
void HandleEvent() {
if (mParser) {
nsParser* parser = NS_STATIC_CAST(nsParser*, mParser);
parser->HandleParserContinueEvent();
NS_RELEASE(mParser);
}
};
nsIParser* mParser;
};
static void PR_CALLBACK HandlePLEvent(nsParserContinueEvent* aEvent)
{
NS_ASSERTION(nsnull != aEvent,"Event is null");
aEvent->HandleEvent();
}
static void PR_CALLBACK DestroyPLEvent(nsParserContinueEvent* aEvent)
{
NS_ASSERTION(nsnull != aEvent,"Event is null");
delete aEvent;
}
nsParserContinueEvent::nsParserContinueEvent(nsIParser* aParser)
{
NS_ASSERTION(aParser, "null parameter");
mParser = aParser;
PL_InitEvent(this, aParser,
(PLHandleEventProc) ::HandlePLEvent,
(PLDestroyEventProc) ::DestroyPLEvent);
}
//-------------- End ParseContinue Event Definition ------------------------
static CSharedParserObjects* gSharedParserObjects=0;
@ -247,11 +343,24 @@ nsParser::nsParser(nsITokenObserver* anObserver) {
mCommand=eViewNormal;
mParserEnabled=PR_TRUE;
mBundle=nsnull;
mPendingContinueEvent=PR_FALSE;
mCanInterrupt=PR_FALSE;
MOZ_TIMER_DEBUGLOG(("Reset: Parse Time: nsParser::nsParser(), this=%p\n", this));
MOZ_TIMER_RESET(mParseTime);
MOZ_TIMER_RESET(mDTDTime);
MOZ_TIMER_RESET(mTokenizeTime);
nsresult rv = NS_OK;
if (mEventQueue == nsnull) {
// Cache the event queue of the current UI thread
NS_WITH_SERVICE(nsIEventQueueService, eventService, kEventQueueServiceCID, &rv);
if (NS_SUCCEEDED(rv) && (eventService)) { // XXX this implies that the UI is the current thread.
rv = eventService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(mEventQueue));
}
NS_ASSERTION(mEventQueue, "event queue is null");
}
}
/**
@ -286,6 +395,11 @@ nsParser::~nsParser() {
//don't forget to add code here to delete
//what may be several contexts...
delete mParserContext;
if (mPendingContinueEvent) {
NS_ASSERTION(mEventQueue != nsnull,"Event queue is null");
mEventQueue->RevokeEvents(this);
}
}
@ -339,6 +453,24 @@ nsresult nsParser::QueryInterface(const nsIID& aIID, void** aInstancePtr)
return NS_OK;
}
// The parser continue event is posted only if
// all of the data to parse has been passed to ::OnDataAvailable
// and the parser has been interrupted by the content sink
// because the processing of tokens took too long.
nsresult
nsParser::PostContinueEvent()
{
if ((! mPendingContinueEvent) && (mEventQueue)) {
nsParserContinueEvent* ev = new nsParserContinueEvent(NS_STATIC_CAST(nsIParser*, this));
NS_ENSURE_TRUE(ev,NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(this);
mEventQueue->PostEvent(ev);
mPendingContinueEvent = PR_TRUE;
}
return NS_OK;
}
/**
*
@ -1240,6 +1372,19 @@ NS_IMETHODIMP nsParser::CreateCompatibleDTD(nsIDTD** aDTD,
}
NS_IMETHODIMP
nsParser::CancelParsingEvents() {
if (mPendingContinueEvent) {
NS_ASSERTION(mEventQueue,"Event queue is null");
// Revoke all pending continue parsing events
if (mEventQueue != nsnull) {
mEventQueue->RevokeEvents(this);
}
mPendingContinueEvent=PR_FALSE;
}
return NS_OK;
}
//#define TEST_DOCTYPES
#ifdef TEST_DOCTYPES
static const char* doctypes[] = {
@ -1433,13 +1578,15 @@ nsresult nsParser::DidBuildModel(nsresult anErrorCode) {
//One last thing...close any open containers.
nsresult result=anErrorCode;
if(mParserContext && !mParserContext->mPrevContext) {
if(mParserContext->mDTD) {
result=mParserContext->mDTD->DidBuildModel(anErrorCode,PRBool(0==mParserContext->mPrevContext),this,mSink);
}
//Ref. to bug 61462.
NS_IF_RELEASE(mBundle);
}//if
if (IsComplete()) {
if(mParserContext && !mParserContext->mPrevContext) {
if(mParserContext->mDTD) {
result=mParserContext->mDTD->DidBuildModel(anErrorCode,PRBool(0==mParserContext->mPrevContext),this,mSink);
}
//Ref. to bug 61462.
NS_IF_RELEASE(mBundle);
}//if
}
return result;
}
@ -1540,7 +1687,7 @@ nsresult nsParser::ContinueParsing(){
if(result!=NS_OK)
result=mInternalState;
return result;
}
@ -1582,6 +1729,29 @@ PRBool nsParser::IsParserEnabled() {
return mParserEnabled;
}
/**
* Call this to query whether the parser thinks it's done with parsing.
*
* @update rickg 5/12/01
* @return complete state
*/
PRBool nsParser::IsComplete() {
return (! mPendingContinueEvent);
}
void nsParser::HandleParserContinueEvent() {
mPendingContinueEvent = PR_FALSE;
ContinueParsing();
}
PRBool nsParser::CanInterrupt(void) {
return mCanInterrupt;
}
void nsParser::SetCanInterrupt(PRBool aCanInterrupt) {
mCanInterrupt = aCanInterrupt;
}
/**
* This is the main controlling routine in the parsing process.
@ -1679,7 +1849,16 @@ aMimeType,PRBool aVerifyEnabled,PRBool aLastCall,nsDTDMode aMode){
//NOTE: Make sure that updates to this method don't cause
// bug #2361 to break again!
nsresult result=NS_OK;
nsresult result=NS_OK;
if(aLastCall && (0==aSourceBuffer.Length())) {
// Nothing is being passed to the parser so return
// immediately. mUnusedInput will get processed when
// some data is actually passed in.
return result;
}
nsParser* me = this;
// Maintain a reference to ourselves so we don't go away
// till we're completely done.
@ -1687,7 +1866,7 @@ aMimeType,PRBool aVerifyEnabled,PRBool aLastCall,nsDTDMode aMode){
if(aSourceBuffer.Length() || mUnusedInput.Length()) {
mDTDVerification=aVerifyEnabled;
CParserContext* pc=0;
CParserContext* pc=0;
if((!mParserContext) || (mParserContext->mKey!=aKey)) {
//only make a new context if we dont have one, OR if we do, but has a different context key...
@ -1867,10 +2046,19 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
}
}
//Only allow parsing to be interuptted in the subsequent call
//to build model.
SetCanInterrupt(PR_TRUE);
nsresult theTokenizerResult=Tokenize(aIsFinalChunk); // kEOF==2152596456
result=BuildModel();
theIterationIsOk=PRBool(kEOF!=theTokenizerResult);
if(result==NS_ERROR_HTMLPARSER_INTERRUPTED) {
if(aIsFinalChunk)
PostContinueEvent();
}
SetCanInterrupt(PR_FALSE);
theIterationIsOk=PRBool((kEOF!=theTokenizerResult) && (result!=NS_ERROR_HTMLPARSER_INTERRUPTED));
// Make sure not to stop parsing too early. Therefore, before shutting down the
// parser, it's important to check whether the input buffer has been scanned to
@ -1895,7 +2083,7 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
break;
}
else if((NS_OK==result) && (theTokenizerResult==kEOF)){
else if(((NS_OK==result) && (theTokenizerResult==kEOF)) || (result==NS_ERROR_HTMLPARSER_INTERRUPTED)){
PRBool theContextIsStringBased=PRBool(CParserContext::eCTString==mParserContext->mContextType);
if( (eOnStop==mParserContext->mStreamListenerState) ||
@ -1918,11 +2106,11 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
MOZ_TIMER_LOG(("Tokenize Time: "));
MOZ_TIMER_PRINT(mTokenizeTime);
return result;
return NS_OK;
}
}
else {
else {
CParserContext* theContext=PopContext();
if(theContext) {
@ -1933,6 +2121,8 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
delete theContext;
}
result = mInternalState;
aIsFinalChunk=(mParserContext && mParserContext->mStreamListenerState==eOnStop)? PR_TRUE:PR_FALSE;
//...then intentionally fall through to WillInterruptParse()...
}
@ -1940,10 +2130,12 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
}
if(kEOF==theTokenizerResult) {
if((kEOF==theTokenizerResult) || (result==NS_ERROR_HTMLPARSER_INTERRUPTED)) {
result = (result == NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result;
mParserContext->mDTD->WillInterruptParse();
}
}//while
}//if
else {
@ -1954,7 +2146,7 @@ nsresult nsParser::ResumeParse(PRBool allowIteration, PRBool aIsFinalChunk) {
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: nsParser::ResumeParse(), this=%p\n", this));
MOZ_TIMER_STOP(mParseTime);
return result;
return (result==NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result;
}
/**

View File

@ -70,6 +70,7 @@
#include "nsDTDUtils.h"
#include "nsTimer.h"
#include "nsIProgressEventSink.h"
#include "nsIEventQueue.h"
class IContentSink;
class nsIDTD;
@ -225,6 +226,14 @@ class nsParser : public nsIParser,
*/
virtual PRBool IsParserEnabled();
/**
* Call this to query whether the parser thinks it's done with parsing.
*
* @update rickg 5/12/01
* @return complete state
*/
virtual PRBool IsComplete();
/**
* This rather arcane method (hack) is used as a signal between the
* DTD and the parser. It allows the DTD to tell the parser that content
@ -319,6 +328,44 @@ class nsParser : public nsIParser,
const nsString* aMimeType=nsnull,
nsDTDMode aDTDMode=eDTDMode_unknown);
/**
* Removes continue parsing events
* @update kmcclusk 5/18/98
*/
NS_IMETHODIMP CancelParsingEvents();
/**
* Indicates whether the parser is in a state where it
* can be interrupted.
* @return PR_TRUE if parser can be interrupted, PR_FALSE if it can not be interrupted.
* @update kmcclusk 5/18/98
*/
PRBool CanInterrupt(void);
/**
* Set to parser state to indicate whether parsing tokens can be interrupted
* @param aCanInterrupt PR_TRUE if parser can be interrupted, PR_FALSE if it can not be interrupted.
* @update kmcclusk 5/18/98
*/
void SetCanInterrupt(PRBool aCanInterrupt);
/**
* This is called when the final chunk has been
* passed to the parser and the content sink has
* interrupted token processing. It schedules
* a ParserContinue PL_Event which will ask the parser
* to HandleParserContinueEvent when it is handled.
* @update kmcclusk6/1/2001
*/
nsresult PostContinueEvent();
/**
* Fired when the continue parse event is triggered.
* @update kmcclusk 5/18/98
*/
void HandleParserContinueEvent(void);
protected:
/**
@ -383,6 +430,8 @@ private:
* @return TRUE if all went well
*/
PRBool DidTokenize(PRBool aIsFinalChunk = PR_FALSE);
protected:
//*********************************************
// And now, some data members...
@ -396,6 +445,7 @@ protected:
nsIRequestObserver* mObserver;
nsIProgressEventSink* mProgressEventSink;
nsIContentSink* mSink;
nsIParserFilter* mParserFilter;
PRBool mDTDVerification;
eParserCommands mCommand;
@ -412,7 +462,12 @@ protected:
nsParserBundle* mBundle;
nsTokenAllocator mTokenAllocator;
public:
nsCOMPtr<nsIEventQueue> mEventQueue;
PRPackedBool mPendingContinueEvent;
PRPackedBool mCanInterrupt;
public:
MOZ_TIMER_DECLARE(mParseTime)
MOZ_TIMER_DECLARE(mDTDTime)
MOZ_TIMER_DECLARE(mTokenizeTime)