From f7e733583b9c8e58d016a3473e0a895477307463 Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Tue, 17 Nov 2009 10:52:30 +0200 Subject: [PATCH] Bug 502568 - HTML5 parser should flush occasionally when loading pure text. r=bnewman. --HG-- extra : rebase_source : f1de8b7729f1570bf859578bf04192a4c5b6df05 --- modules/libpref/src/init/all.js | 9 + parser/html/nsAHtml5TreeOpSink.h | 10 +- parser/html/nsHtml5Module.cpp | 1 + parser/html/nsHtml5Speculation.cpp | 10 +- parser/html/nsHtml5Speculation.h | 7 +- parser/html/nsHtml5StreamParser.cpp | 129 ++++++++- parser/html/nsHtml5StreamParser.h | 46 ++++ parser/html/nsHtml5TreeBuilderCppSupplement.h | 62 ++--- parser/html/nsHtml5TreeBuilderHSupplement.h | 6 +- parser/html/nsHtml5TreeOpExecutor.cpp | 45 +--- parser/html/nsHtml5TreeOpExecutor.h | 30 +-- parser/html/nsHtml5TreeOpStage.cpp | 13 +- parser/html/nsHtml5TreeOpStage.h | 10 +- parser/html/nsHtml5TreeOperation.cpp | 246 ++++++++++++------ parser/html/nsHtml5TreeOperation.h | 60 ++++- 15 files changed, 444 insertions(+), 240 deletions(-) diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index dc7397ab3422..db58a86c31cc 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -2824,3 +2824,12 @@ pref("geo.enabled", true); pref("html5.enable", false); // Toggle which thread the HTML5 parser uses for streama parsing pref("html5.offmainthread", true); +// Time in milliseconds between the start of the network stream and the +// first time the flush timer fires in the off-the-main-thread HTML5 parser. +pref("html5.flushtimer.startdelay", 200); +// Time in milliseconds between the return to non-speculating more and the +// first time the flush timer fires thereafter. +pref("html5.flushtimer.continuedelay", 150); +// Time in milliseconds between timer firings once the timer has starting +// firing. +pref("html5.flushtimer.interval", 100); diff --git a/parser/html/nsAHtml5TreeOpSink.h b/parser/html/nsAHtml5TreeOpSink.h index a4040f1cad6e..c8280e2f36e3 100644 --- a/parser/html/nsAHtml5TreeOpSink.h +++ b/parser/html/nsAHtml5TreeOpSink.h @@ -48,15 +48,9 @@ class nsAHtml5TreeOpSink { /** * Flush the operations from the tree operations from the argument - * queue if flushing is not expensive. + * queue into this sink unconditionally. */ - virtual void MaybeFlush(nsTArray& aOpQueue) = 0; - - /** - * Flush the operations from the tree operations from the argument - * queue unconditionally. - */ - virtual void ForcedFlush(nsTArray& aOpQueue) = 0; + virtual void MoveOpsFrom(nsTArray& aOpQueue) = 0; }; diff --git a/parser/html/nsHtml5Module.cpp b/parser/html/nsHtml5Module.cpp index 33f8e8a66dd2..bdf462253c38 100644 --- a/parser/html/nsHtml5Module.cpp +++ b/parser/html/nsHtml5Module.cpp @@ -71,6 +71,7 @@ nsHtml5Module::InitializeStatics() nsHtml5Tokenizer::initializeStatics(); nsHtml5TreeBuilder::initializeStatics(); nsHtml5UTF16Buffer::initializeStatics(); + nsHtml5StreamParser::InitializeStatics(); #ifdef DEBUG sNsHtml5ModuleInitialized = PR_TRUE; #endif diff --git a/parser/html/nsHtml5Speculation.cpp b/parser/html/nsHtml5Speculation.cpp index 14afa1b8a0e0..c81bd3d9a7e8 100644 --- a/parser/html/nsHtml5Speculation.cpp +++ b/parser/html/nsHtml5Speculation.cpp @@ -55,13 +55,7 @@ nsHtml5Speculation::~nsHtml5Speculation() } void -nsHtml5Speculation::MaybeFlush(nsTArray& aOpQueue) -{ - // No-op -} - -void -nsHtml5Speculation::ForcedFlush(nsTArray& aOpQueue) +nsHtml5Speculation::MoveOpsFrom(nsTArray& aOpQueue) { if (mOpQueue.IsEmpty()) { mOpQueue.SwapElements(aOpQueue); @@ -73,5 +67,5 @@ nsHtml5Speculation::ForcedFlush(nsTArray& aOpQueue) void nsHtml5Speculation::FlushToSink(nsAHtml5TreeOpSink* aSink) { - aSink->ForcedFlush(mOpQueue); + aSink->MoveOpsFrom(mOpQueue); } diff --git a/parser/html/nsHtml5Speculation.h b/parser/html/nsHtml5Speculation.h index 07b3b7d867f6..ae9974a270a2 100644 --- a/parser/html/nsHtml5Speculation.h +++ b/parser/html/nsHtml5Speculation.h @@ -71,16 +71,11 @@ class nsHtml5Speculation : public nsAHtml5TreeOpSink return mSnapshot; } - /** - * No-op. - */ - virtual void MaybeFlush(nsTArray& aOpQueue); - /** * Flush the operations from the tree operations from the argument * queue unconditionally. */ - virtual void ForcedFlush(nsTArray& aOpQueue); + virtual void MoveOpsFrom(nsTArray& aOpQueue); void FlushToSink(nsAHtml5TreeOpSink* aSink); diff --git a/parser/html/nsHtml5StreamParser.cpp b/parser/html/nsHtml5StreamParser.cpp index 228d8936ebf9..cbb1457f3da3 100644 --- a/parser/html/nsHtml5StreamParser.cpp +++ b/parser/html/nsHtml5StreamParser.cpp @@ -54,6 +54,22 @@ static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID); +PRInt32 nsHtml5StreamParser::sTimerStartDelay = 200; +PRInt32 nsHtml5StreamParser::sTimerContinueDelay = 150; +PRInt32 nsHtml5StreamParser::sTimerInterval = 100; + +// static +void +nsHtml5StreamParser::InitializeStatics() +{ + nsContentUtils::AddIntPrefVarCache("html5.flushtimer.startdelay", + &sTimerStartDelay); + nsContentUtils::AddIntPrefVarCache("html5.flushtimer.continuedelay", + &sTimerContinueDelay); + nsContentUtils::AddIntPrefVarCache("html5.flushtimer.interval", + &sTimerInterval); +} + NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5StreamParser) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5StreamParser) @@ -67,6 +83,10 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5StreamParser) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5StreamParser) + if (tmp->mFlushTimer) { + tmp->mFlushTimer->Cancel(); + tmp->mFlushTimer = nsnull; + } NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest) tmp->mOwner = nsnull; @@ -125,6 +145,7 @@ nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor, , mTerminatedMutex("nsHtml5StreamParser mTerminatedMutex") , mThread(nsHtml5Module::GetStreamParserThread()) , mExecutorFlusher(new nsHtml5ExecutorFlusher(aExecutor)) + , mFlushTimer(do_CreateInstance("@mozilla.org/timer;1")) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); mAtomTable.Init(); // we aren't checking for OOM anyway... @@ -150,6 +171,10 @@ nsHtml5StreamParser::~nsHtml5StreamParser() mTreeBuilder = nsnull; mTokenizer = nsnull; mOwner = nsnull; + if (mFlushTimer) { + mFlushTimer->Cancel(); + mFlushTimer = nsnull; + } } void @@ -483,6 +508,11 @@ nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) */ mExecutor->WillBuildModel(eDTDMode_unknown); + mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback, + static_cast (this), + sTimerStartDelay, + nsITimer::TYPE_ONE_SHOT); + nsresult rv = NS_OK; mReparseForbidden = PR_FALSE; @@ -719,7 +749,6 @@ nsHtml5StreamParser::ParseAvailableData() mFirstBuffer->setStart(0); mFirstBuffer->setEnd(0); } - mTreeBuilder->Flush(); return; // no more data for now but expecting more case STREAM_ENDED: if (mAtEOF) { @@ -772,7 +801,6 @@ nsHtml5StreamParser::ParseAvailableData() if (IsTerminatedOrInterrupted()) { return; } - mTreeBuilder->MaybeFlush(); } continue; } @@ -873,6 +901,11 @@ nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer, mTreeBuilder->SetOpSink(mExecutor->GetStage()); mExecutor->StartReadingFromStage(); mSpeculating = PR_FALSE; + mFlushTimer->Cancel(); // just in case + mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback, + static_cast (this), + sTimerContinueDelay, + nsITimer::TYPE_ONE_SHOT); // Copy state over mLastWasCR = aLastWasCR; mTokenizer->loadState(aTokenizer); @@ -890,6 +923,11 @@ nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer, mTreeBuilder->SetOpSink(mExecutor->GetStage()); mExecutor->StartReadingFromStage(); mSpeculating = PR_FALSE; + mFlushTimer->Cancel(); // just in case + mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback, + static_cast (this), + sTimerContinueDelay, + nsITimer::TYPE_ONE_SHOT); } } Uninterrupt(); @@ -912,6 +950,89 @@ nsHtml5StreamParser::ContinueAfterFailedCharsetSwitch() Uninterrupt(); nsCOMPtr event = new nsHtml5StreamParserContinuation(this); if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) { - NS_WARNING("Failed to dispatch ParseAvailableData event"); - } + NS_WARNING("Failed to dispatch nsHtml5StreamParserContinuation"); + } } + +// Using a static, because the method name Notify is taken by the chardet +// callback. +void +nsHtml5StreamParser::TimerCallback(nsITimer* aTimer, void* aClosure) +{ + (static_cast (aClosure))->PostTimerFlush(); +} + +void +nsHtml5StreamParser::TimerFlush() +{ + NS_ASSERTION(IsParserThread(), "Wrong thread!"); + mTokenizerMutex.AssertCurrentThreadOwns(); + + if (mSpeculating) { + return; + } + + // we aren't speculating and we don't know when new data is + // going to arrive. Send data to the main thread. + // However, don't do if the current element on the stack is a + // foster-parenting element and there's pending text, because flushing in + // that case would make the tree shape dependent on where the flush points + // fall. + if (mTreeBuilder->IsDiscretionaryFlushSafe()) { + mTreeBuilder->flushCharacters(); + if (mTreeBuilder->Flush()) { + if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) { + NS_WARNING("failed to dispatch executor flush event"); + } + } + } +} + +class nsHtml5StreamParserTimerFlusher : public nsRunnable +{ +private: + nsHtml5RefPtr mStreamParser; +public: + nsHtml5StreamParserTimerFlusher(nsHtml5StreamParser* aStreamParser) + : mStreamParser(aStreamParser) + {} + NS_IMETHODIMP Run() + { + mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex); + mStreamParser->TimerFlush(); + return NS_OK; + } +}; + +void +nsHtml5StreamParser::PostTimerFlush() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + mFlushTimer->Cancel(); // just in case + + // The following line reads a mutex-protected variable without acquiring + // the mutex. This is OK, because failure to exit early here is harmless. + // The early exit here is merely an optimization. Note that parser thread + // may have set mSpeculating to true where it previously was false--not + // the other way round. mSpeculating is set to false only on the main thread. + if (mSpeculating) { + // No need for timer flushes when speculating + return; + } + + // Schedule the next timer shot + mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback, + static_cast (this), + sTimerInterval, + nsITimer::TYPE_ONE_SHOT); + + // TODO: (If mDocument isn't in the frontmost tab or If the user isn't + // interacting with the browser) and this isn't every nth timer flush, return + + nsCOMPtr event = new nsHtml5StreamParserTimerFlusher(this); + if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch nsHtml5StreamParserTimerFlusher"); + } +} + diff --git a/parser/html/nsHtml5StreamParser.h b/parser/html/nsHtml5StreamParser.h index e362a425fc35..cfcea78372cb 100644 --- a/parser/html/nsHtml5StreamParser.h +++ b/parser/html/nsHtml5StreamParser.h @@ -52,6 +52,7 @@ #include "mozilla/Mutex.h" #include "nsHtml5AtomTable.h" #include "nsHtml5Speculation.h" +#include "nsITimer.h" class nsHtml5Parser; @@ -106,12 +107,15 @@ class nsHtml5StreamParser : public nsIStreamListener, friend class nsHtml5RequestStopper; friend class nsHtml5DataAvailable; friend class nsHtml5StreamParserContinuation; + friend class nsHtml5StreamParserTimerFlusher; public: NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHtml5StreamParser, nsIStreamListener) + static void InitializeStatics(); + nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor, nsHtml5Parser* aOwner); @@ -301,6 +305,23 @@ class nsHtml5StreamParser : public nsIStreamListener, nsresult SetupDecodingFromBom(const char* aCharsetName, const char* aDecoderCharsetName); + /** + * Callback for mFlushTimer. + */ + static void TimerCallback(nsITimer* aTimer, void* aClosure); + + /** + * Main thread entry point for (maybe) flushing the ops and posting + * a flush runnable back on the main thread. + */ + void PostTimerFlush(); + + /** + * Parser thread entry point for (maybe) flushing the ops and posting + * a flush runnable back on the main thread. + */ + void TimerFlush(); + nsCOMPtr mRequest; nsCOMPtr mObserver; @@ -435,6 +456,31 @@ class nsHtml5StreamParser : public nsIStreamListener, * The document wrapped by the speculative loader. */ nsCOMPtr mDocument; + + /** + * Timer for flushing tree ops once in a while when not speculating. + */ + nsCOMPtr mFlushTimer; + + /** + * The pref html5.flushtimer.startdelay: Time in milliseconds between + * the start of the network stream and the first time the flush timer + * fires. + */ + static PRInt32 sTimerStartDelay; + + /** + * The pref html5.flushtimer.continuedelay: Time in milliseconds between + * the return to non-speculating more and the first time the flush timer + * fires thereafter. + */ + static PRInt32 sTimerContinueDelay; + + /** + * The pref html5.flushtimer.interval: Time in milliseconds between + * timer firings once the timer has starting firing. + */ + static PRInt32 sTimerInterval; }; #endif // nsHtml5StreamParser_h__ diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h index b0c0f3b064e1..600ca4ec9687 100644 --- a/parser/html/nsHtml5TreeBuilderCppSupplement.h +++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -314,15 +314,9 @@ nsHtml5TreeBuilder::insertFosterParentedCharacters(PRUnichar* aBuffer, PRInt32 a PRUnichar* bufferCopy = new PRUnichar[aLength]; memcpy(bufferCopy, aBuffer, aLength * sizeof(PRUnichar)); - nsIContent** text = AllocateContentHandle(); - nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(); NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpCreateTextNode, bufferCopy, aLength, text); - - treeOp = mOpQueue.AppendElement(); - NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpFosterParent, text, aStackParent, aTable); + treeOp->Init(eTreeOpFosterParentText, bufferCopy, aLength, aStackParent, aTable); } void @@ -346,15 +340,9 @@ nsHtml5TreeBuilder::appendCharacters(nsIContent** aParent, PRUnichar* aBuffer, P PRUnichar* bufferCopy = new PRUnichar[aLength]; memcpy(bufferCopy, aBuffer, aLength * sizeof(PRUnichar)); - nsIContent** text = AllocateContentHandle(); - nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(); NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpCreateTextNode, bufferCopy, aLength, text); - - treeOp = mOpQueue.AppendElement(); - NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpAppend, text, aParent); + treeOp->Init(eTreeOpAppendText, bufferCopy, aLength, aParent); } void @@ -366,15 +354,9 @@ nsHtml5TreeBuilder::appendComment(nsIContent** aParent, PRUnichar* aBuffer, PRIn PRUnichar* bufferCopy = new PRUnichar[aLength]; memcpy(bufferCopy, aBuffer, aLength * sizeof(PRUnichar)); - nsIContent** comment = AllocateContentHandle(); - nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(); NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpCreateComment, bufferCopy, aLength, comment); - - treeOp = mOpQueue.AppendElement(); - NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpAppend, comment, aParent); + treeOp->Init(eTreeOpAppendComment, bufferCopy, aLength, aParent); } void @@ -385,15 +367,9 @@ nsHtml5TreeBuilder::appendCommentToDocument(PRUnichar* aBuffer, PRInt32 aStart, PRUnichar* bufferCopy = new PRUnichar[aLength]; memcpy(bufferCopy, aBuffer, aLength * sizeof(PRUnichar)); - nsIContent** comment = AllocateContentHandle(); - nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(); NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpCreateComment, bufferCopy, aLength, comment); - - treeOp = mOpQueue.AppendElement(); - NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpAppendToDocument, comment); + treeOp->Init(eTreeOpAppendCommentToDocument, bufferCopy, aLength); } void @@ -442,15 +418,9 @@ nsHtml5TreeBuilder::appendDoctypeToDocument(nsIAtom* aName, nsString* aPublicId, { NS_PRECONDITION(aName, "Null name"); - nsIContent** content = AllocateContentHandle(); - nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(); NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(aName, *aPublicId, *aSystemId, content); - - treeOp = mOpQueue.AppendElement(); - NS_ASSERTION(treeOp, "Tree op allocation failed."); - treeOp->Init(eTreeOpAppendToDocument, content); + treeOp->Init(aName, *aPublicId, *aSystemId); // nsXMLContentSink can flush here, but what's the point? // It can also interrupt here, but we can't. } @@ -614,16 +584,14 @@ nsHtml5TreeBuilder::HasScript() return mOpQueue.ElementAt(len - 1).IsRunScript(); } -void +PRBool nsHtml5TreeBuilder::Flush() { - mOpSink->ForcedFlush(mOpQueue); -} - -void -nsHtml5TreeBuilder::MaybeFlush() -{ - mOpSink->MaybeFlush(mOpQueue); + PRBool hasOps = !mOpQueue.IsEmpty(); + if (hasOps) { + mOpSink->MoveOpsFrom(mOpQueue); + } + return hasOps; } void @@ -676,6 +644,14 @@ nsHtml5TreeBuilder::DropSpeculativeLoader() { mSpeculativeLoader = nsnull; } +PRBool +nsHtml5TreeBuilder::IsDiscretionaryFlushSafe() +{ + return !(charBufferLen && + currentPtr >= 0 && + stack[currentPtr]->fosterParenting); +} + // DocumentModeHandler void nsHtml5TreeBuilder::documentMode(nsHtml5DocumentMode m) diff --git a/parser/html/nsHtml5TreeBuilderHSupplement.h b/parser/html/nsHtml5TreeBuilderHSupplement.h index 02059423fd8d..d3061df1ebe0 100644 --- a/parser/html/nsHtml5TreeBuilderHSupplement.h +++ b/parser/html/nsHtml5TreeBuilderHSupplement.h @@ -63,6 +63,8 @@ ~nsHtml5TreeBuilder(); + PRBool IsDiscretionaryFlushSafe(); + PRBool HasScript(); void SetOpSink(nsAHtml5TreeOpSink* aOpSink) { @@ -77,9 +79,7 @@ void DropSpeculativeLoader(); - void Flush(); - - void MaybeFlush(); + PRBool Flush(); void SetDocumentCharset(nsACString& aCharset); diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp index 13606390bdbb..ba896e52893f 100644 --- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -60,7 +60,6 @@ #define NS_HTML5_TREE_OP_EXECUTOR_MAX_QUEUE_TIME 3000UL // milliseconds #define NS_HTML5_TREE_OP_EXECUTOR_DEFAULT_QUEUE_LENGTH 200 #define NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH 100 -#define NS_HTML5_TREE_OP_EXECUTOR_MAX_TIME_WITHOUT_FLUSH 5000 // milliseconds NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5TreeOpExecutor) @@ -74,39 +73,21 @@ NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFlushTimer) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mOwnedElements) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mOwnedNonElements) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) - if (tmp->mFlushTimer) { - tmp->mFlushTimer->Cancel(); - } - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFlushTimer) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedElements) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedNonElements) NS_IMPL_CYCLE_COLLECTION_UNLINK_END nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor() - : mFlushTimer(do_CreateInstance("@mozilla.org/timer;1")) { - // zeroing operator new for everything else + // zeroing operator new for everything } nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() { NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue."); - if (mFlushTimer) { - mFlushTimer->Cancel(); // XXX why is this even necessary? it is, though. - } - mFlushTimer = nsnull; -} - -static void -TimerCallbackFunc(nsITimer* aTimer, void* aClosure) -{ - (static_cast (aClosure))->Flush(); } // nsIContentSink @@ -283,7 +264,6 @@ void nsHtml5TreeOpExecutor::Flush() { if (!mParser) { - mFlushTimer->Cancel(); return; } if (mFlushState != eNotFlushing) { @@ -296,7 +276,7 @@ nsHtml5TreeOpExecutor::Flush() nsCOMPtr parserKungFuDeathGrip(mParser); if (mReadingFromStage) { - mStage.RetrieveOperations(mOpQueue); + mStage.MoveOpsTo(mOpQueue); } nsIContent* scriptElement = nsnull; @@ -348,23 +328,11 @@ nsHtml5TreeOpExecutor::Flush() return; } - ScheduleTimer(); - if (scriptElement) { RunScript(scriptElement); // must be tail call when mFlushState is eNotFlushing } } -void -nsHtml5TreeOpExecutor::ScheduleTimer() -{ - mFlushTimer->Cancel(); - mFlushTimer->InitWithFuncCallback(TimerCallbackFunc, - static_cast (this), - NS_HTML5_TREE_OP_EXECUTOR_MAX_TIME_WITHOUT_FLUSH, - nsITimer::TYPE_ONE_SHOT); -} - nsresult nsHtml5TreeOpExecutor::ProcessBASETag(nsIContent* aContent) { @@ -514,7 +482,6 @@ nsHtml5TreeOpExecutor::Start() { NS_PRECONDITION(!mStarted, "Tried to start when already started."); mStarted = PR_TRUE; - ScheduleTimer(); } void @@ -572,13 +539,7 @@ nsHtml5TreeOpExecutor::Reset() { } void -nsHtml5TreeOpExecutor::MaybeFlush(nsTArray& aOpQueue) -{ - // no-op -} - -void -nsHtml5TreeOpExecutor::ForcedFlush(nsTArray& aOpQueue) +nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray& aOpQueue) { NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution."); if (mOpQueue.IsEmpty()) { diff --git a/parser/html/nsHtml5TreeOpExecutor.h b/parser/html/nsHtml5TreeOpExecutor.h index 452f6d7aeb78..d0987c66c08d 100644 --- a/parser/html/nsHtml5TreeOpExecutor.h +++ b/parser/html/nsHtml5TreeOpExecutor.h @@ -50,7 +50,6 @@ #include "nsContentSink.h" #include "nsNodeInfoManager.h" #include "nsHtml5DocumentMode.h" -#include "nsITimer.h" #include "nsIScriptElement.h" #include "nsIParser.h" #include "nsCOMArray.h" @@ -95,18 +94,12 @@ class nsHtml5TreeOpExecutor : public nsContentSink, PRBool mHasProcessedBase; PRBool mReadingFromStage; - nsCOMPtr mFlushTimer; nsTArray mOpQueue; nsTArray mElementsSeenInThisAppendBatch; nsTArray mPendingNotifications; nsHtml5StreamParser* mStreamParser; nsCOMArray mOwnedElements; - // This could be optimized away by introducing more tree ops so that - // non-elements wouldn't use the handle setup but the text node / comment - // / doctype operand would be remembered by the tree op executor. - nsCOMArray mOwnedNonElements; - /** * Whether the parser has started */ @@ -300,21 +293,21 @@ class nsHtml5TreeOpExecutor : public nsContentSink, mFlushState = eInDocUpdate; } - inline PRBool HaveNotified(nsIContent* aElement) { - NS_PRECONDITION(aElement, "HaveNotified called with null argument."); + inline PRBool HaveNotified(nsIContent* aNode) { + NS_PRECONDITION(aNode, "HaveNotified called with null argument."); const nsHtml5PendingNotification* start = mPendingNotifications.Elements(); const nsHtml5PendingNotification* end = start + mPendingNotifications.Length(); for (;;) { - nsIContent* parent = aElement->GetParent(); + nsIContent* parent = aNode->GetParent(); if (!parent) { return PR_TRUE; } for (nsHtml5PendingNotification* iter = (nsHtml5PendingNotification*)start; iter < end; ++iter) { if (iter->Contains(parent)) { - return iter->HaveNotifiedIndex(parent->IndexOf(aElement)); + return iter->HaveNotifiedIndex(parent->IndexOf(aNode)); } } - aElement = parent; + aNode = parent; } } @@ -359,22 +352,13 @@ class nsHtml5TreeOpExecutor : public nsContentSink, mOwnedElements.AppendObject(aContent); } - inline void HoldNonElement(nsIContent* aContent) { - mOwnedNonElements.AppendObject(aContent); - } - // The following two methods are for the main-thread case - /** - * No-op - */ - virtual void MaybeFlush(nsTArray& aOpQueue); - /** * Flush the operations from the tree operations from the argument * queue unconditionally. */ - virtual void ForcedFlush(nsTArray& aOpQueue); + virtual void MoveOpsFrom(nsTArray& aOpQueue); nsAHtml5TreeOpSink* GetStage() { return &mStage; @@ -386,8 +370,6 @@ class nsHtml5TreeOpExecutor : public nsContentSink, void StreamEnded(); - void ScheduleTimer(); - #ifdef DEBUG void AssertStageEmpty() { mStage.AssertEmpty(); diff --git a/parser/html/nsHtml5TreeOpStage.cpp b/parser/html/nsHtml5TreeOpStage.cpp index d211d9eee3f8..a44eb54d076b 100644 --- a/parser/html/nsHtml5TreeOpStage.cpp +++ b/parser/html/nsHtml5TreeOpStage.cpp @@ -47,16 +47,7 @@ nsHtml5TreeOpStage::~nsHtml5TreeOpStage() } void -nsHtml5TreeOpStage::MaybeFlush(nsTArray& aOpQueue) -{ - mozilla::MutexAutoLock autoLock(mMutex); - if (mOpQueue.IsEmpty()) { - mOpQueue.SwapElements(aOpQueue); - } -} - -void -nsHtml5TreeOpStage::ForcedFlush(nsTArray& aOpQueue) +nsHtml5TreeOpStage::MoveOpsFrom(nsTArray& aOpQueue) { mozilla::MutexAutoLock autoLock(mMutex); if (mOpQueue.IsEmpty()) { @@ -67,7 +58,7 @@ nsHtml5TreeOpStage::ForcedFlush(nsTArray& aOpQueue) } void -nsHtml5TreeOpStage::RetrieveOperations(nsTArray& aOpQueue) +nsHtml5TreeOpStage::MoveOpsTo(nsTArray& aOpQueue) { mozilla::MutexAutoLock autoLock(mMutex); if (aOpQueue.IsEmpty()) { diff --git a/parser/html/nsHtml5TreeOpStage.h b/parser/html/nsHtml5TreeOpStage.h index 271f6ef847c9..6cd1ecd004d0 100644 --- a/parser/html/nsHtml5TreeOpStage.h +++ b/parser/html/nsHtml5TreeOpStage.h @@ -50,22 +50,16 @@ class nsHtml5TreeOpStage : public nsAHtml5TreeOpSink { ~nsHtml5TreeOpStage(); - /** - * Flush the operations from the tree operations from the argument - * queue if flushing is not expensive. - */ - virtual void MaybeFlush(nsTArray& aOpQueue); - /** * Flush the operations from the tree operations from the argument * queue unconditionally. */ - virtual void ForcedFlush(nsTArray& aOpQueue); + virtual void MoveOpsFrom(nsTArray& aOpQueue); /** * Retrieve the staged operations into the argument. */ - void RetrieveOperations(nsTArray& aOpQueue); + void MoveOpsTo(nsTArray& aOpQueue); #ifdef DEBUG void AssertEmpty(); diff --git a/parser/html/nsHtml5TreeOperation.cpp b/parser/html/nsHtml5TreeOperation.cpp index 378470c91102..154e238e0b67 100644 --- a/parser/html/nsHtml5TreeOperation.cpp +++ b/parser/html/nsHtml5TreeOperation.cpp @@ -57,6 +57,7 @@ #include "nsIFormControl.h" #include "nsIStyleSheetLinkingElement.h" #include "nsIDOMDocumentType.h" +#include "nsIMutationObserver.h" /** * Helper class that opens a notification batch if the current doc @@ -105,11 +106,13 @@ nsHtml5TreeOperation::~nsHtml5TreeOperation() case eTreeOpCreateElement: delete mThree.attributes; break; - case eTreeOpCreateDoctype: + case eTreeOpAppendDoctypeToDocument: delete mTwo.stringPair; break; - case eTreeOpCreateTextNode: - case eTreeOpCreateComment: + case eTreeOpFosterParentText: + case eTreeOpAppendText: + case eTreeOpAppendComment: + case eTreeOpAppendCommentToDocument: delete[] mTwo.unicharPtr; break; case eTreeOpSetDocumentCharset: @@ -121,6 +124,107 @@ nsHtml5TreeOperation::~nsHtml5TreeOperation() } } +nsresult +nsHtml5TreeOperation::AppendTextToTextNode(PRUnichar* aBuffer, + PRInt32 aLength, + nsIContent* aTextNode, + nsHtml5TreeOpExecutor* aBuilder) +{ + NS_PRECONDITION(aTextNode, "Got null text node."); + + if (aBuilder->HaveNotified(aTextNode)) { + // This text node has already been notified on, so it's necessary to + // notify on the append + nsresult rv = NS_OK; + PRUint32 oldLength = aTextNode->TextLength(); + CharacterDataChangeInfo info = { + PR_TRUE, + oldLength, + oldLength, + aLength + }; + nsNodeUtils::CharacterDataWillChange(aTextNode, &info); + + rv = aTextNode->AppendText(aBuffer, aLength, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + nsNodeUtils::CharacterDataChanged(aTextNode, &info); + return rv; + } + + return aTextNode->AppendText(aBuffer, aLength, PR_FALSE); +} + + +nsresult +nsHtml5TreeOperation::AppendText(PRUnichar* aBuffer, + PRInt32 aLength, + nsIContent* aParent, + nsHtml5TreeOpExecutor* aBuilder) +{ + nsresult rv = NS_OK; + nsIContent* lastChild = aParent->GetLastChild(); + if (lastChild && lastChild->IsNodeOfType(nsINode::eTEXT)) { + nsHtml5OtherDocUpdate update(aParent->GetOwnerDoc(), + aBuilder->GetDocument()); + return AppendTextToTextNode(aBuffer, + aLength, + lastChild, + aBuilder); + } + + nsCOMPtr text; + NS_NewTextNode(getter_AddRefs(text), aBuilder->GetNodeInfoManager()); + NS_ASSERTION(text, "Infallible malloc failed?"); + rv = text->SetText(aBuffer, aLength, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + return Append(text, aParent, aBuilder); +} + +nsresult +nsHtml5TreeOperation::Append(nsIContent* aNode, + nsIContent* aParent, + nsHtml5TreeOpExecutor* aBuilder) +{ + nsresult rv = NS_OK; + nsIDocument* executorDoc = aBuilder->GetDocument(); + NS_ASSERTION(executorDoc, "Null doc on executor"); + nsIDocument* parentDoc = aParent->GetOwnerDoc(); + NS_ASSERTION(parentDoc, "Null owner doc on old node."); + + if (NS_LIKELY(executorDoc == parentDoc)) { + // the usual case. the parent is in the parser's doc + aBuilder->PostPendingAppendNotification(aParent, aNode); + rv = aParent->AppendChildTo(aNode, PR_FALSE); + return rv; + } + + // The parent has been moved to another doc + parentDoc->BeginUpdate(UPDATE_CONTENT_MODEL); + + PRUint32 childCount = aParent->GetChildCount(); + rv = aParent->AppendChildTo(aNode, PR_FALSE); + nsNodeUtils::ContentAppended(aParent, childCount); + + parentDoc->EndUpdate(UPDATE_CONTENT_MODEL); + return rv; +} + +nsresult +nsHtml5TreeOperation::AppendToDocument(nsIContent* aNode, + nsHtml5TreeOpExecutor* aBuilder) +{ + nsresult rv = NS_OK; + aBuilder->FlushPendingAppendNotifications(); + nsIDocument* doc = aBuilder->GetDocument(); + PRUint32 childCount = doc->GetChildCount(); + rv = doc->AppendChildTo(aNode, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + nsNodeUtils::ContentInserted(doc, aNode, childCount); + return rv; +} + nsresult nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, nsIContent** aScriptElement) @@ -130,28 +234,7 @@ nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, case eTreeOpAppend: { nsIContent* node = *(mOne.node); nsIContent* parent = *(mTwo.node); - - nsIDocument* executorDoc = aBuilder->GetDocument(); - NS_ASSERTION(executorDoc, "Null doc on executor"); - nsIDocument* parentDoc = parent->GetOwnerDoc(); - NS_ASSERTION(parentDoc, "Null owner doc on old node."); - - if (NS_LIKELY(executorDoc == parentDoc)) { - // the usual case. the parent is in the parser's doc - aBuilder->PostPendingAppendNotification(parent, node); - rv = parent->AppendChildTo(node, PR_FALSE); - return rv; - } - - // The parent has been moved to another doc - parentDoc->BeginUpdate(UPDATE_CONTENT_MODEL); - - PRUint32 childCount = parent->GetChildCount(); - rv = parent->AppendChildTo(node, PR_FALSE); - nsNodeUtils::ContentAppended(parent, childCount); - - parentDoc->EndUpdate(UPDATE_CONTENT_MODEL); - return rv; + return Append(node, parent, aBuilder); } case eTreeOpDetach: { nsIContent* node = *(mOne.node); @@ -209,37 +292,11 @@ nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, return rv; } - nsIDocument* executorDoc = aBuilder->GetDocument(); - NS_ASSERTION(executorDoc, "Null doc on executor"); - nsIDocument* parentDoc = parent->GetOwnerDoc(); - NS_ASSERTION(parentDoc, "Null owner doc on old node."); - - if (NS_LIKELY(executorDoc == parentDoc)) { - // the usual case. the parent is in the parser's doc - aBuilder->PostPendingAppendNotification(parent, node); - rv = parent->AppendChildTo(node, PR_FALSE); - return rv; - } - - // The parent has been moved to another doc - parentDoc->BeginUpdate(UPDATE_CONTENT_MODEL); - - PRUint32 childCount = parent->GetChildCount(); - rv = parent->AppendChildTo(node, PR_FALSE); - nsNodeUtils::ContentAppended(parent, childCount); - - parentDoc->EndUpdate(UPDATE_CONTENT_MODEL); - return rv; + return Append(node, parent, aBuilder); } case eTreeOpAppendToDocument: { nsIContent* node = *(mOne.node); - aBuilder->FlushPendingAppendNotifications(); - nsIDocument* doc = aBuilder->GetDocument(); - PRUint32 childCount = doc->GetChildCount(); - rv = doc->AppendChildTo(node, PR_FALSE); - NS_ENSURE_SUCCESS(rv, rv); - nsNodeUtils::ContentInserted(doc, node, childCount); - return rv; + return AppendToDocument(node, aBuilder); } case eTreeOpAddAttributes: { nsIContent* node = *(mOne.node); @@ -348,41 +405,81 @@ nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, } return rv; } - case eTreeOpCreateTextNode: { - nsIContent** target = mOne.node; + case eTreeOpAppendText: { + nsIContent* parent = *mOne.node; PRUnichar* buffer = mTwo.unicharPtr; PRInt32 length = mInt; - - nsCOMPtr text; - NS_NewTextNode(getter_AddRefs(text), aBuilder->GetNodeInfoManager()); - // XXX nsresult and comment null check? - text->SetText(buffer, length, PR_FALSE); - // XXX nsresult - - aBuilder->HoldNonElement(*target = text); - return rv; + return AppendText(buffer, length, parent, aBuilder); } - case eTreeOpCreateComment: { - nsIContent** target = mOne.node; + case eTreeOpFosterParentText: { + nsIContent* stackParent = *mOne.node; + PRUnichar* buffer = mTwo.unicharPtr; + PRInt32 length = mInt; + nsIContent* table = *mThree.node; + + nsIContent* foster = table->GetParent(); + + if (foster && foster->IsNodeOfType(nsINode::eELEMENT)) { + aBuilder->FlushPendingAppendNotifications(); + + nsHtml5OtherDocUpdate update(foster->GetOwnerDoc(), + aBuilder->GetDocument()); + + PRUint32 pos = foster->IndexOf(table); + + nsIContent* previousSibling = foster->GetChildAt(pos - 1); + if (previousSibling && previousSibling->IsNodeOfType(nsINode::eTEXT)) { + return AppendTextToTextNode(buffer, + length, + previousSibling, + aBuilder); + } + + nsCOMPtr text; + NS_NewTextNode(getter_AddRefs(text), aBuilder->GetNodeInfoManager()); + NS_ASSERTION(text, "Infallible malloc failed?"); + rv = text->SetText(buffer, length, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = foster->InsertChildAt(text, pos, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + nsNodeUtils::ContentInserted(foster, text, pos); + return rv; + } + + return AppendText(buffer, length, stackParent, aBuilder); + } + case eTreeOpAppendComment: { + nsIContent* parent = *mOne.node; PRUnichar* buffer = mTwo.unicharPtr; PRInt32 length = mInt; nsCOMPtr comment; NS_NewCommentNode(getter_AddRefs(comment), aBuilder->GetNodeInfoManager()); - // XXX nsresult and comment null check? - comment->SetText(buffer, length, PR_FALSE); - // XXX nsresult + NS_ASSERTION(comment, "Infallible malloc failed?"); + rv = comment->SetText(buffer, length, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); - aBuilder->HoldNonElement(*target = comment); - return rv; + return Append(comment, parent, aBuilder); } - case eTreeOpCreateDoctype: { + case eTreeOpAppendCommentToDocument: { + PRUnichar* buffer = mTwo.unicharPtr; + PRInt32 length = mInt; + + nsCOMPtr comment; + NS_NewCommentNode(getter_AddRefs(comment), aBuilder->GetNodeInfoManager()); + NS_ASSERTION(comment, "Infallible malloc failed?"); + rv = comment->SetText(buffer, length, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + return AppendToDocument(comment, aBuilder); + } + case eTreeOpAppendDoctypeToDocument: { nsCOMPtr name = Reget(mOne.atom); nsHtml5TreeOperationStringPair* pair = mTwo.stringPair; nsString publicId; nsString systemId; pair->Get(publicId, systemId); - nsIContent** target = mThree.node; // Adapted from nsXMLContentSink // Create a new doctype node @@ -400,8 +497,7 @@ nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, voidString); NS_ASSERTION(docType, "Doctype creation failed."); nsCOMPtr asContent = do_QueryInterface(docType); - aBuilder->HoldNonElement(*target = asContent); - return rv; + return AppendToDocument(asContent, aBuilder); } case eTreeOpRunScript: { nsIContent* node = *(mOne.node); diff --git a/parser/html/nsHtml5TreeOperation.h b/parser/html/nsHtml5TreeOperation.h index 2ee7b5da7edd..013825f5c01b 100644 --- a/parser/html/nsHtml5TreeOperation.h +++ b/parser/html/nsHtml5TreeOperation.h @@ -59,9 +59,11 @@ enum eHtml5TreeOperation { eTreeOpDocumentMode, eTreeOpCreateElement, eTreeOpSetFormElement, - eTreeOpCreateTextNode, - eTreeOpCreateComment, - eTreeOpCreateDoctype, + eTreeOpAppendText, + eTreeOpFosterParentText, + eTreeOpAppendComment, + eTreeOpAppendCommentToDocument, + eTreeOpAppendDoctypeToDocument, // Gecko-specific on-pop ops eTreeOpRunScript, eTreeOpDoneAddingChildren, @@ -188,12 +190,38 @@ class nsHtml5TreeOperation { inline void Init(eHtml5TreeOperation aOpCode, PRUnichar* aBuffer, PRInt32 aLength, - nsIContent** aTarget) { + nsIContent** aStackParent, + nsIContent** aTable) { + NS_PRECONDITION(mOpCode == eTreeOpUninitialized, + "Op code must be uninitialized when initializing."); + NS_PRECONDITION(aBuffer, "Initialized tree op with null buffer."); + mOpCode = aOpCode; + mOne.node = aStackParent; + mTwo.unicharPtr = aBuffer; + mThree.node = aTable; + mInt = aLength; + } + + inline void Init(eHtml5TreeOperation aOpCode, + PRUnichar* aBuffer, + PRInt32 aLength, + nsIContent** aParent) { + NS_PRECONDITION(mOpCode == eTreeOpUninitialized, + "Op code must be uninitialized when initializing."); + NS_PRECONDITION(aBuffer, "Initialized tree op with null buffer."); + mOpCode = aOpCode; + mOne.node = aParent; + mTwo.unicharPtr = aBuffer; + mInt = aLength; + } + + inline void Init(eHtml5TreeOperation aOpCode, + PRUnichar* aBuffer, + PRInt32 aLength) { NS_PRECONDITION(mOpCode == eTreeOpUninitialized, "Op code must be uninitialized when initializing."); NS_PRECONDITION(aBuffer, "Initialized tree op with null buffer."); mOpCode = aOpCode; - mOne.node = aTarget; mTwo.unicharPtr = aBuffer; mInt = aLength; } @@ -210,13 +238,12 @@ class nsHtml5TreeOperation { inline void Init(nsIAtom* aName, const nsAString& aPublicId, - const nsAString& aSystemId, nsIContent** aTarget) { + const nsAString& aSystemId) { NS_PRECONDITION(mOpCode == eTreeOpUninitialized, "Op code must be uninitialized when initializing."); - mOpCode = eTreeOpCreateDoctype; + mOpCode = eTreeOpAppendDoctypeToDocument; mOne.atom = aName; mTwo.stringPair = new nsHtml5TreeOperationStringPair(aPublicId, aSystemId); - mThree.node = aTarget; } inline void Init(eHtml5TreeOperation aOpCode, const nsACString& aString) { @@ -270,6 +297,23 @@ class nsHtml5TreeOperation { } private: + + nsresult AppendTextToTextNode(PRUnichar* aBuffer, + PRInt32 aLength, + nsIContent* aTextNode, + nsHtml5TreeOpExecutor* aBuilder); + + nsresult AppendText(PRUnichar* aBuffer, + PRInt32 aLength, + nsIContent* aParent, + nsHtml5TreeOpExecutor* aBuilder); + + nsresult Append(nsIContent* aNode, + nsIContent* aParent, + nsHtml5TreeOpExecutor* aBuilder); + + nsresult AppendToDocument(nsIContent* aNode, + nsHtml5TreeOpExecutor* aBuilder); // possible optimization: // Make the queue take items the size of pointer and make the op code