From 44fd52082e13b498bdfc3e1c36131a21043e01be Mon Sep 17 00:00:00 2001 From: "mrbkap%gmail.com" Date: Tue, 24 Jan 2006 01:25:14 +0000 Subject: [PATCH] bug 321781: document.close() causes weird things to happen when asynchronous things were document.written. r=sicking sr=jst git-svn-id: svn://10.0.0.236/trunk@188077 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/content/base/src/nsContentSink.h | 4 +- .../html/document/src/nsHTMLContentSink.cpp | 8 ++- .../html/document/src/nsHTMLDocument.cpp | 72 +++++++++++++++---- .../html/document/src/nsHTMLDocument.h | 29 +++++++- .../html/document/src/nsIHTMLDocument.h | 17 ++++- mozilla/parser/htmlparser/public/nsIParser.h | 37 +++++----- mozilla/parser/htmlparser/src/nsParser.cpp | 43 ++++++++--- mozilla/parser/htmlparser/src/nsParser.h | 3 +- 8 files changed, 161 insertions(+), 52 deletions(-) diff --git a/mozilla/content/base/src/nsContentSink.h b/mozilla/content/base/src/nsContentSink.h index cfefd12f5bc..b36d03ae72b 100644 --- a/mozilla/content/base/src/nsContentSink.h +++ b/mozilla/content/base/src/nsContentSink.h @@ -103,8 +103,8 @@ protected: void StartLayout(PRBool aIsFrameset); // Overridable hooks into script evaluation - virtual void PreEvaluateScript() {return;} - virtual void PostEvaluateScript() {return;} + virtual void PreEvaluateScript() {return;} + virtual void PostEvaluateScript(nsIScriptElement *aElement) {return;} nsCOMPtr mDocument; nsCOMPtr mParser; diff --git a/mozilla/content/html/document/src/nsHTMLContentSink.cpp b/mozilla/content/html/document/src/nsHTMLContentSink.cpp index ed838b38d69..7122d42818a 100644 --- a/mozilla/content/html/document/src/nsHTMLContentSink.cpp +++ b/mozilla/content/html/document/src/nsHTMLContentSink.cpp @@ -411,7 +411,7 @@ protected: // nsContentSink overrides virtual void PreEvaluateScript(); - virtual void PostEvaluateScript(); + virtual void PostEvaluateScript(nsIScriptElement *aElement); void UpdateAllContexts(); void NotifyAppend(nsIContent* aContent, @@ -3810,8 +3810,9 @@ HTMLContentSink::PreEvaluateScript() } void -HTMLContentSink::PostEvaluateScript() +HTMLContentSink::PostEvaluateScript(nsIScriptElement *aElement) { + mHTMLDocument->ScriptExecuted(aElement); } nsresult @@ -3852,6 +3853,9 @@ HTMLContentSink::ProcessSCRIPTEndTag(nsGenericHTMLElement *content, mScriptElements.AppendObject(sele); } + // Notify our document that we're loading this script. + mHTMLDocument->ScriptLoading(sele); + // Now tell the script that it's ready to go. This will execute the script // and call our ScriptAvailable method. content->DoneAddingChildren(aHaveNotified); diff --git a/mozilla/content/html/document/src/nsHTMLDocument.cpp b/mozilla/content/html/document/src/nsHTMLDocument.cpp index e94c8a07293..621af96b1a5 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.cpp +++ b/mozilla/content/html/document/src/nsHTMLDocument.cpp @@ -121,6 +121,7 @@ #include "nsIJSContextStack.h" #include "nsIDocumentViewer.h" #include "nsIWyciwygChannel.h" +#include "nsIScriptElement.h" #include "nsIPrompt.h" //AHMED 12-2 @@ -970,7 +971,7 @@ nsHTMLDocument::DocumentWriteTerminationFunc(nsISupports *aRef) // cancel the load that was initiated by the location="..." in the // script that was written out by document.write(). - if (!htmldoc->mWriteLevel && !htmldoc->mIsWriting) { + if (!htmldoc->mWriteLevel && htmldoc->mWriteState != eDocumentOpened) { // Release the documents parser so that the call to EndLoad() // doesn't just return early and set the termination function again. @@ -983,7 +984,7 @@ nsHTMLDocument::DocumentWriteTerminationFunc(nsISupports *aRef) void nsHTMLDocument::EndLoad() { - if (mParser) { + if (mParser && mWriteState != eDocumentClosed) { nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1"); @@ -1024,6 +1025,13 @@ nsHTMLDocument::EndLoad() } } + // Reset this now, since we're really done "loading" this document.written + // document. + NS_ASSERTION(mWriteState == eNotWriting || mWriteState == ePendingClose || + mWriteState == eDocumentClosed, "EndLoad called early"); + mWriteState = eNotWriting; + + // Note: nsDocument::EndLoad nulls out mParser. nsDocument::EndLoad(); } @@ -2047,7 +2055,7 @@ nsHTMLDocument::OpenCommon(const nsACString& aContentType, PRBool aReplace) // This will be propagated to the parser when someone actually calls write() mContentType = aContentType; - mIsWriting = 1; + mWriteState = eDocumentOpened; if (NS_SUCCEEDED(rv)) { nsCOMPtr sink; @@ -2141,21 +2149,24 @@ nsHTMLDocument::Close() { nsresult rv = NS_OK; - if (mParser && mIsWriting) { + if (mParser && mWriteState == eDocumentOpened) { ++mWriteLevel; if (mContentType.EqualsLiteral("text/html")) { rv = mParser->Parse(NS_LITERAL_STRING(""), - GenerateParserKey(), + mParser->GetRootContextKey(), mContentType, PR_FALSE, PR_TRUE); } else { - rv = mParser->Parse(EmptyString(), GenerateParserKey(), + rv = mParser->Parse(EmptyString(), mParser->GetRootContextKey(), mContentType, PR_FALSE, PR_TRUE); } --mWriteLevel; - mIsWriting = 0; - mParser->Terminate(); - mParser = nsnull; + + mPendingScripts.RemoveElement(GenerateParserKey()); + + mWriteState = mPendingScripts.Count() == 0 + ? eDocumentClosed + : ePendingClose; // XXX Make sure that all the document.written content is // reflowed. We should remove this call once we change @@ -2202,6 +2213,15 @@ nsHTMLDocument::WriteCommon(const nsAString& aText, { nsresult rv = NS_OK; + void *key = GenerateParserKey(); + if (mWriteState == eDocumentClosed || + (mWriteState == ePendingClose && + mPendingScripts.IndexOf(key) == kNotFound)) { + mWriteState = eDocumentClosed; + mParser->Terminate(); + NS_ASSERTION(!mParser, "mParser should have been null'd out"); + } + if (!mParser) { rv = Open(); @@ -2238,14 +2258,12 @@ nsHTMLDocument::WriteCommon(const nsAString& aText, // why pay that price when we don't need to? if (aNewlineTerminate) { rv = mParser->Parse(aText + new_line, - GenerateParserKey(), - mContentType, PR_FALSE, - (!mIsWriting || (mWriteLevel > 1))); + key, mContentType, PR_FALSE, + (mWriteState == eNotWriting || (mWriteLevel > 1))); } else { rv = mParser->Parse(aText, - GenerateParserKey(), - mContentType, PR_FALSE, - (!mIsWriting || (mWriteLevel > 1))); + key, mContentType, PR_FALSE, + (mWriteState == eNotWriting || (mWriteLevel > 1))); } --mWriteLevel; @@ -2524,6 +2542,30 @@ nsHTMLDocument::GetElementsByName(const nsAString& aElementName, return NS_OK; } +void +nsHTMLDocument::ScriptLoading(nsIScriptElement *aScript) +{ + if (mWriteState == eNotWriting) { + return; + } + + mPendingScripts.AppendElement(aScript); +} + +void +nsHTMLDocument::ScriptExecuted(nsIScriptElement *aScript) +{ + if (mWriteState == eNotWriting) { + return; + } + + mPendingScripts.RemoveElement(aScript); + if (mPendingScripts.Count() == 0 && mWriteState == ePendingClose) { + // The last pending script just finished, terminate our parser now. + mWriteState = eDocumentClosed; + } +} + void nsHTMLDocument::AddedForm() { diff --git a/mozilla/content/html/document/src/nsHTMLDocument.h b/mozilla/content/html/document/src/nsHTMLDocument.h index 770022a6413..213f9b28b20 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.h +++ b/mozilla/content/html/document/src/nsHTMLDocument.h @@ -1,4 +1,5 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -44,6 +45,7 @@ #include "nsIDOMHTMLBodyElement.h" #include "nsIDOMHTMLMapElement.h" #include "nsIDOMHTMLCollection.h" +#include "nsIScriptElement.h" #include "jsapi.h" #include "rdf.h" #include "nsRDFCID.h" @@ -187,6 +189,9 @@ public: nsIDOMHTMLFormElement *aForm, nsISupports **aResult); + virtual void ScriptLoading(nsIScriptElement *aScript); + virtual void ScriptExecuted(nsIScriptElement *aScript); + virtual void AddedForm(); virtual void RemovedForm(); virtual PRInt32 GetNumFormsSynchronous(); @@ -290,8 +295,28 @@ protected: void StartAutodetection(nsIDocShell *aDocShell, nsACString& aCharset, const char* aCommand); - PRUint32 mIsWriting : 1; - PRUint32 mWriteLevel : 31; + // mWriteState tracks the status of this document if the document is being + // entirely created by script. In the normal load case, mWriteState will be + // eNotWriting. Once document.open has been called (either implicitly or + // explicitly), mWriteState will be eDocumentOpened. When document.close has + // been called, mWriteState will become eDocumentClosed if there have been no + // external script loads in the meantime. If there have been, then mWriteState + // becomes ePendingClose, indicating that we might still be writing, but that + // we shouldn't process any further close() calls. + enum { + eNotWriting, + eDocumentOpened, + ePendingClose, + eDocumentClosed + } mWriteState; + + // Tracks if we are currently processing any document.write calls (either + // implicit or explicit). Note that if a write call writes out something which + // would block the parser, then mWriteLevel will be incorrect until the parser + // finishes processing that script. + PRUint32 mWriteLevel; + + nsSmallVoidArray mPendingScripts; // Load flags of the document's channel PRUint32 mLoadFlags; diff --git a/mozilla/content/html/document/src/nsIHTMLDocument.h b/mozilla/content/html/document/src/nsIHTMLDocument.h index e716ab4c64b..4aaf9d023a5 100644 --- a/mozilla/content/html/document/src/nsIHTMLDocument.h +++ b/mozilla/content/html/document/src/nsIHTMLDocument.h @@ -52,9 +52,12 @@ class nsIStyleSheet; class nsICSSLoader; class nsIContent; class nsIDOMHTMLBodyElement; +class nsIScriptElement; #define NS_IHTMLDOCUMENT_IID \ -{0x83f3c1d2, 0x0a60, 0x42db, {0xaf, 0x75, 0xd5, 0x54, 0xfe, 0x70, 0x8d, 0x25}} +{ 0x4daadd67, 0x61b4, 0x4423, \ + { 0xae, 0x1a, 0x61, 0x6f, 0xed, 0x5d, 0x72, 0x3c } } + /** @@ -86,6 +89,18 @@ public: nsIDOMHTMLFormElement *aForm, nsISupports **aResult) = 0; + /** + * Called from the script loader to notify this document that a new + * script is being loaded. + */ + virtual void ScriptLoading(nsIScriptElement *aScript) = 0; + + /** + * Called from the script loader to notify this document that a script + * just finished executing. + */ + virtual void ScriptExecuted(nsIScriptElement *aScript) = 0; + /** * Called when form->BindToTree() is called so that document knows * immediately when a form is added diff --git a/mozilla/parser/htmlparser/public/nsIParser.h b/mozilla/parser/htmlparser/public/nsIParser.h index b36d721e89a..bfb57dd2d39 100644 --- a/mozilla/parser/htmlparser/public/nsIParser.h +++ b/mozilla/parser/htmlparser/public/nsIParser.h @@ -1,4 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -40,7 +41,6 @@ /** * MODULE NOTES: - * @update gess 4/1/98 * * This class defines the iparser interface. This XPCOM * inteface is all that parser clients ever need to see. @@ -54,10 +54,10 @@ #include "nsStringGlue.h" #include "nsVoidArray.h" -// {22039D29-2798-4412-9EA6-A11B41BA27D0} -#define NS_IPARSER_IID \ -{ 0x22039d29, 0x2798, 0x4412, \ -{ 0x9e, 0xa6, 0xa1, 0x1b, 0x41, 0xba, 0x27, 0xd0 } } +// {6BE162F1-0A2E-4517-B3C5-EF5302DD2FEF} +#define NS_IPARSER_IID \ +{ 0x6be162f1, 0xa2e, 0x4517, \ +{ 0xb3, 0xc5, 0xef, 0x53, 0x2, 0xdd, 0x2f, 0xef } } // {41421C60-310A-11d4-816F-000064657374} #define NS_IDEBUG_DUMP_CONTENT_IID \ @@ -78,13 +78,6 @@ enum eParserCommands { eViewErrors }; -enum eCRCQuality { - eCRCGood = 0, - eCRCFair, - eCRCPoor -}; - - enum eParserDocType { ePlainText = 0, eXML, @@ -136,8 +129,6 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIDebugDumpContent, NS_IDEBUG_DUMP_CONTENT_IID) /** * This class defines the iparser interface. This XPCOM * inteface is all that parser clients ever need to see. - * - * @update gess 3/25/98 */ class nsIParser : public nsISupports { public: @@ -146,7 +137,7 @@ class nsIParser : public nsISupports { /** * Call this method if you have a DTD that you want to share with the parser. - * Registered DTD's get remembered until the system shuts down. + * Registered DTD's get remembered until the system shuts down. * * @update gess 3/25/98 * @param aDTD -- ptr DTD that you're publishing the services of @@ -213,11 +204,11 @@ class nsIParser : public nsISupports { */ NS_IMETHOD GetDTD(nsIDTD** aDTD) = 0; - /****************************************************************************************** - * Parse methods always begin with an input source, and perform conversions - * until you wind up being emitted to the given contentsink (which may or may not - * be a proxy for the NGLayout content model). - ******************************************************************************************/ + /************************************************************************** + * Parse methods always begin with an input source, and perform + * conversions until you wind up being emitted to the given contentsink + * (which may or may not be a proxy for the NGLayout content model). + ************************************************************************/ // Call this method to resume the parser from the blocked state. NS_IMETHOD ContinueParsing() = 0; @@ -258,6 +249,10 @@ class nsIParser : public nsISupports { PRBool aEnableVerify, PRBool aLastCall, nsDTDMode aMode = eDTDMode_autodetect) = 0; + + // Return a key, suitable for passing into one of the Parse methods above, + // that will cause this parser to use the root context. + NS_IMETHOD_(void *) GetRootContextKey() = 0; NS_IMETHOD Terminate(void) = 0; diff --git a/mozilla/parser/htmlparser/src/nsParser.cpp b/mozilla/parser/htmlparser/src/nsParser.cpp index 19e30f06fb0..40b19410365 100644 --- a/mozilla/parser/htmlparser/src/nsParser.cpp +++ b/mozilla/parser/htmlparser/src/nsParser.cpp @@ -1222,6 +1222,21 @@ nsParser::SetUnusedInput(nsString& aBuffer) mUnusedInput = aBuffer; } +NS_IMETHODIMP_(void *) +nsParser::GetRootContextKey() +{ + CParserContext* pc = mParserContext; + if (!pc) { + return nsnull; + } + + while (pc->mPrevContext) { + pc = pc->mPrevContext; + } + + return pc->mKey; +} + /** * Call this when you want to *force* the parser to terminate the * parsing process altogether. This is binary -- so once you terminate @@ -1516,7 +1531,6 @@ nsParser::Parse(nsIInputStream* aStream, * In particular, this method should be called by the DOM when it has an HTML * string to feed to the parser in real-time. * - * @update gess5/11/98 * @param aSourceBuffer contains a string-full of real content * @param aMimeType tells us what type of content to expect in the given string */ @@ -1555,9 +1569,16 @@ nsParser::Parse(const nsAString& aSourceBuffer, mFlags &= ~NS_PARSER_FLAG_DTD_VERIFICATION; } - CParserContext* pc = nsnull; + // Note: The following code will always find the parser context associated + // with the given key, even if that context has been suspended (e.g., for + // another document.write call). This doesn't appear to be exactly what IE + // does in the case where this happens, but this makes more sense. + CParserContext* pc = mParserContext; + while (pc && pc->mKey != aKey) { + pc = pc->mPrevContext; + } - if (!mParserContext || mParserContext->mKey != aKey) { + if (!pc) { // Only make a new context if we don't have one, OR if we do, but has a // different context key. nsScanner* theScanner = new nsScanner(mUnusedInput, mCharset, mCharsetSource); @@ -1620,15 +1641,21 @@ nsParser::Parse(const nsAString& aSourceBuffer, // Do not interrupt document.write() - bug 95487 result = ResumeParse(PR_FALSE, PR_FALSE, PR_FALSE); } else { - mParserContext->mScanner->Append(aSourceBuffer); - if (!mParserContext->mPrevContext) { + pc->mScanner->Append(aSourceBuffer); + if (!pc->mPrevContext) { // Set stream listener state to eOnStop, on the final context - Fix 68160, // to guarantee DidBuildModel() call - Fix 36148 if (aLastCall) { - mParserContext->mStreamListenerState = eOnStop; - mParserContext->mScanner->SetIncremental(PR_FALSE); + pc->mStreamListenerState = eOnStop; + pc->mScanner->SetIncremental(PR_FALSE); + } + + if (pc == mParserContext) { + // If pc is not mParserContext, then this call to ResumeParse would + // do the wrong thing and try to continue parsing using + // mParserContext. We need to wait to actually resume parsing on pc. + ResumeParse(PR_FALSE, PR_FALSE, PR_FALSE); } - ResumeParse(PR_FALSE, PR_FALSE, PR_FALSE); } } } diff --git a/mozilla/parser/htmlparser/src/nsParser.h b/mozilla/parser/htmlparser/src/nsParser.h index f771393ceb0..179f40679b7 100644 --- a/mozilla/parser/htmlparser/src/nsParser.h +++ b/mozilla/parser/htmlparser/src/nsParser.h @@ -37,7 +37,6 @@ /** * MODULE NOTES: - * @update gess 4/1/98 * * This class does two primary jobs: * 1) It iterates the tokens provided during the @@ -228,6 +227,8 @@ class nsParser : public nsIParser, PRBool aLastCall, nsDTDMode aMode = eDTDMode_autodetect); + NS_IMETHOD_(void *) GetRootContextKey(); + /** * This method needs documentation */