From 1823eaba29a661e85cc4bdc5770386d34ecace2f Mon Sep 17 00:00:00 2001 From: "kmcclusk%netscape.com" Date: Thu, 21 Jun 2001 02:06:23 +0000 Subject: [PATCH] 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 git-svn-id: svn://10.0.0.236/trunk@97651 18797224-902f-48f8-a5cc-f745e15eee43 --- .../content/base/src/nsPlainTextSerializer.h | 4 + .../html/document/src/nsHTMLContentSink.cpp | 298 +++++++++++++++++- .../src/nsHTMLFragmentContentSink.cpp | 4 + mozilla/htmlparser/robot/nsRobotSink.cpp | 4 + mozilla/htmlparser/src/CNavDTD.cpp | 29 +- mozilla/htmlparser/src/CNavDTD.h | 1 - mozilla/htmlparser/src/nsHTMLNullSink.cpp | 4 + mozilla/htmlparser/src/nsIHTMLContentSink.h | 28 ++ mozilla/htmlparser/src/nsIParser.h | 16 +- mozilla/htmlparser/src/nsLoggingSink.h | 4 + mozilla/htmlparser/src/nsParser.cpp | 226 ++++++++++++- mozilla/htmlparser/src/nsParser.h | 57 +++- .../parser/htmlparser/robot/nsRobotSink.cpp | 4 + mozilla/parser/htmlparser/src/CNavDTD.cpp | 29 +- mozilla/parser/htmlparser/src/CNavDTD.h | 1 - .../parser/htmlparser/src/nsHTMLNullSink.cpp | 4 + .../htmlparser/src/nsIHTMLContentSink.h | 28 ++ mozilla/parser/htmlparser/src/nsIParser.h | 16 +- mozilla/parser/htmlparser/src/nsLoggingSink.h | 4 + mozilla/parser/htmlparser/src/nsParser.cpp | 226 ++++++++++++- mozilla/parser/htmlparser/src/nsParser.h | 57 +++- 21 files changed, 999 insertions(+), 45 deletions(-) diff --git a/mozilla/content/base/src/nsPlainTextSerializer.h b/mozilla/content/base/src/nsPlainTextSerializer.h index 6c29593033d..9e933677470 100644 --- a/mozilla/content/base/src/nsPlainTextSerializer.h +++ b/mozilla/content/base/src/nsPlainTextSerializer.h @@ -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, diff --git a/mozilla/content/html/document/src/nsHTMLContentSink.cpp b/mozilla/content/html/document/src/nsHTMLContentSink.cpp index 5ce5d9a9e6f..013a511b66c 100644 --- a/mozilla/content/html/document/src/nsHTMLContentSink.cpp +++ b/mozilla/content/html/document/src/nsHTMLContentSink.cpp @@ -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 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 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 loadGroup; + if (mDocument) { + rv = mDocument->GetDocumentLoadGroup(getter_AddRefs(loadGroup)); + if (NS_FAILED(rv)) return rv; + } + + if (loadGroup) { + nsCOMPtr 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 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; +} + diff --git a/mozilla/content/html/document/src/nsHTMLFragmentContentSink.cpp b/mozilla/content/html/document/src/nsHTMLFragmentContentSink.cpp index 0ffa2f14331..b6d249afa82 100644 --- a/mozilla/content/html/document/src/nsHTMLFragmentContentSink.cpp +++ b/mozilla/content/html/document/src/nsHTMLFragmentContentSink.cpp @@ -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); diff --git a/mozilla/htmlparser/robot/nsRobotSink.cpp b/mozilla/htmlparser/robot/nsRobotSink.cpp index cdb838e8f1d..8867d75dcbd 100644 --- a/mozilla/htmlparser/robot/nsRobotSink.cpp +++ b/mozilla/htmlparser/robot/nsRobotSink.cpp @@ -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; } diff --git a/mozilla/htmlparser/src/CNavDTD.cpp b/mozilla/htmlparser/src/CNavDTD.cpp index b58d1314a4d..8190603af98 100644 --- a/mozilla/htmlparser/src/CNavDTD.cpp +++ b/mozilla/htmlparser/src/CNavDTD.cpp @@ -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(); diff --git a/mozilla/htmlparser/src/CNavDTD.h b/mozilla/htmlparser/src/CNavDTD.h index 9bb624aeeb5..f9f719817a5 100644 --- a/mozilla/htmlparser/src/CNavDTD.h +++ b/mozilla/htmlparser/src/CNavDTD.h @@ -108,7 +108,6 @@ class nsITokenizer; class nsCParserNode; class nsTokenAllocator; - /*************************************************************** Now the main event: CNavDTD. diff --git a/mozilla/htmlparser/src/nsHTMLNullSink.cpp b/mozilla/htmlparser/src/nsHTMLNullSink.cpp index b5f366a91c3..d461b1da3ab 100644 --- a/mozilla/htmlparser/src/nsHTMLNullSink.cpp +++ b/mozilla/htmlparser/src/nsHTMLNullSink.cpp @@ -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); diff --git a/mozilla/htmlparser/src/nsIHTMLContentSink.h b/mozilla/htmlparser/src/nsIHTMLContentSink.h index 2c5536ecb2f..dcdd1515886 100644 --- a/mozilla/htmlparser/src/nsIHTMLContentSink.h +++ b/mozilla/htmlparser/src/nsIHTMLContentSink.h @@ -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); diff --git a/mozilla/htmlparser/src/nsIParser.h b/mozilla/htmlparser/src/nsIParser.h index c4a7b28aada..bc21410892a 100644 --- a/mozilla/htmlparser/src/nsIParser.h +++ b/mozilla/htmlparser/src/nsIParser.h @@ -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 diff --git a/mozilla/htmlparser/src/nsLoggingSink.h b/mozilla/htmlparser/src/nsLoggingSink.h index bd434e3358c..a42f1b87bde 100644 --- a/mozilla/htmlparser/src/nsLoggingSink.h +++ b/mozilla/htmlparser/src/nsLoggingSink.h @@ -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); diff --git a/mozilla/htmlparser/src/nsParser.cpp b/mozilla/htmlparser/src/nsParser.cpp index 7d44da2c20b..2139faeedaa 100644 --- a/mozilla/htmlparser/src/nsParser.cpp +++ b/mozilla/htmlparser/src/nsParser.cpp @@ -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; } /** diff --git a/mozilla/htmlparser/src/nsParser.h b/mozilla/htmlparser/src/nsParser.h index fecad3542e1..083d3e56d95 100644 --- a/mozilla/htmlparser/src/nsParser.h +++ b/mozilla/htmlparser/src/nsParser.h @@ -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 mEventQueue; + PRPackedBool mPendingContinueEvent; + PRPackedBool mCanInterrupt; + +public: + MOZ_TIMER_DECLARE(mParseTime) MOZ_TIMER_DECLARE(mDTDTime) MOZ_TIMER_DECLARE(mTokenizeTime) diff --git a/mozilla/parser/htmlparser/robot/nsRobotSink.cpp b/mozilla/parser/htmlparser/robot/nsRobotSink.cpp index cdb838e8f1d..8867d75dcbd 100644 --- a/mozilla/parser/htmlparser/robot/nsRobotSink.cpp +++ b/mozilla/parser/htmlparser/robot/nsRobotSink.cpp @@ -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; } diff --git a/mozilla/parser/htmlparser/src/CNavDTD.cpp b/mozilla/parser/htmlparser/src/CNavDTD.cpp index b58d1314a4d..8190603af98 100644 --- a/mozilla/parser/htmlparser/src/CNavDTD.cpp +++ b/mozilla/parser/htmlparser/src/CNavDTD.cpp @@ -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(); diff --git a/mozilla/parser/htmlparser/src/CNavDTD.h b/mozilla/parser/htmlparser/src/CNavDTD.h index 9bb624aeeb5..f9f719817a5 100644 --- a/mozilla/parser/htmlparser/src/CNavDTD.h +++ b/mozilla/parser/htmlparser/src/CNavDTD.h @@ -108,7 +108,6 @@ class nsITokenizer; class nsCParserNode; class nsTokenAllocator; - /*************************************************************** Now the main event: CNavDTD. diff --git a/mozilla/parser/htmlparser/src/nsHTMLNullSink.cpp b/mozilla/parser/htmlparser/src/nsHTMLNullSink.cpp index b5f366a91c3..d461b1da3ab 100644 --- a/mozilla/parser/htmlparser/src/nsHTMLNullSink.cpp +++ b/mozilla/parser/htmlparser/src/nsHTMLNullSink.cpp @@ -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); diff --git a/mozilla/parser/htmlparser/src/nsIHTMLContentSink.h b/mozilla/parser/htmlparser/src/nsIHTMLContentSink.h index 2c5536ecb2f..dcdd1515886 100644 --- a/mozilla/parser/htmlparser/src/nsIHTMLContentSink.h +++ b/mozilla/parser/htmlparser/src/nsIHTMLContentSink.h @@ -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); diff --git a/mozilla/parser/htmlparser/src/nsIParser.h b/mozilla/parser/htmlparser/src/nsIParser.h index c4a7b28aada..bc21410892a 100644 --- a/mozilla/parser/htmlparser/src/nsIParser.h +++ b/mozilla/parser/htmlparser/src/nsIParser.h @@ -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 diff --git a/mozilla/parser/htmlparser/src/nsLoggingSink.h b/mozilla/parser/htmlparser/src/nsLoggingSink.h index bd434e3358c..a42f1b87bde 100644 --- a/mozilla/parser/htmlparser/src/nsLoggingSink.h +++ b/mozilla/parser/htmlparser/src/nsLoggingSink.h @@ -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); diff --git a/mozilla/parser/htmlparser/src/nsParser.cpp b/mozilla/parser/htmlparser/src/nsParser.cpp index 7d44da2c20b..2139faeedaa 100644 --- a/mozilla/parser/htmlparser/src/nsParser.cpp +++ b/mozilla/parser/htmlparser/src/nsParser.cpp @@ -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; } /** diff --git a/mozilla/parser/htmlparser/src/nsParser.h b/mozilla/parser/htmlparser/src/nsParser.h index fecad3542e1..083d3e56d95 100644 --- a/mozilla/parser/htmlparser/src/nsParser.h +++ b/mozilla/parser/htmlparser/src/nsParser.h @@ -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 mEventQueue; + PRPackedBool mPendingContinueEvent; + PRPackedBool mCanInterrupt; + +public: + MOZ_TIMER_DECLARE(mParseTime) MOZ_TIMER_DECLARE(mDTDTime) MOZ_TIMER_DECLARE(mTokenizeTime)