Bug 502568 - HTML5 parser should flush occasionally when loading pure text. r=bnewman.

--HG--
extra : rebase_source : f1de8b7729f1570bf859578bf04192a4c5b6df05
This commit is contained in:
Henri Sivonen 2009-11-17 10:52:30 +02:00
parent 0f1b837b09
commit f7e733583b
15 changed files with 444 additions and 240 deletions

View File

@ -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);

View File

@ -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<nsHtml5TreeOperation>& aOpQueue) = 0;
/**
* Flush the operations from the tree operations from the argument
* queue unconditionally.
*/
virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue) = 0;
virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) = 0;
};

View File

@ -71,6 +71,7 @@ nsHtml5Module::InitializeStatics()
nsHtml5Tokenizer::initializeStatics();
nsHtml5TreeBuilder::initializeStatics();
nsHtml5UTF16Buffer::initializeStatics();
nsHtml5StreamParser::InitializeStatics();
#ifdef DEBUG
sNsHtml5ModuleInitialized = PR_TRUE;
#endif

View File

@ -55,13 +55,7 @@ nsHtml5Speculation::~nsHtml5Speculation()
}
void
nsHtml5Speculation::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
// No-op
}
void
nsHtml5Speculation::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
nsHtml5Speculation::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
if (mOpQueue.IsEmpty()) {
mOpQueue.SwapElements(aOpQueue);
@ -73,5 +67,5 @@ nsHtml5Speculation::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
void
nsHtml5Speculation::FlushToSink(nsAHtml5TreeOpSink* aSink)
{
aSink->ForcedFlush(mOpQueue);
aSink->MoveOpsFrom(mOpQueue);
}

View File

@ -71,16 +71,11 @@ class nsHtml5Speculation : public nsAHtml5TreeOpSink
return mSnapshot;
}
/**
* No-op.
*/
virtual void MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
/**
* Flush the operations from the tree operations from the argument
* queue unconditionally.
*/
virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
void FlushToSink(nsAHtml5TreeOpSink* aSink);

View File

@ -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<void*> (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<void*> (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<void*> (this),
sTimerContinueDelay,
nsITimer::TYPE_ONE_SHOT);
}
}
Uninterrupt();
@ -912,6 +950,89 @@ nsHtml5StreamParser::ContinueAfterFailedCharsetSwitch()
Uninterrupt();
nsCOMPtr<nsIRunnable> 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<nsHtml5StreamParser*> (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<nsHtml5StreamParser> 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<void*> (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<nsIRunnable> event = new nsHtml5StreamParserTimerFlusher(this);
if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch nsHtml5StreamParserTimerFlusher");
}
}

View File

@ -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<nsIRequest> mRequest;
nsCOMPtr<nsIRequestObserver> mObserver;
@ -435,6 +456,31 @@ class nsHtml5StreamParser : public nsIStreamListener,
* The document wrapped by the speculative loader.
*/
nsCOMPtr<nsIDocument> mDocument;
/**
* Timer for flushing tree ops once in a while when not speculating.
*/
nsCOMPtr<nsITimer> 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__

View File

@ -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)

View File

@ -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);

View File

@ -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<nsHtml5TreeOpExecutor*> (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<nsIParser> 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<void*> (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<nsHtml5TreeOperation>& aOpQueue)
{
// no-op
}
void
nsHtml5TreeOpExecutor::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
if (mOpQueue.IsEmpty()) {

View File

@ -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<nsITimer> mFlushTimer;
nsTArray<nsHtml5TreeOperation> mOpQueue;
nsTArray<nsIContentPtr> mElementsSeenInThisAppendBatch;
nsTArray<nsHtml5PendingNotification> mPendingNotifications;
nsHtml5StreamParser* mStreamParser;
nsCOMArray<nsIContent> 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<nsIContent> 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<nsHtml5TreeOperation>& aOpQueue);
/**
* Flush the operations from the tree operations from the argument
* queue unconditionally.
*/
virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
nsAHtml5TreeOpSink* GetStage() {
return &mStage;
@ -386,8 +370,6 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
void StreamEnded();
void ScheduleTimer();
#ifdef DEBUG
void AssertStageEmpty() {
mStage.AssertEmpty();

View File

@ -47,16 +47,7 @@ nsHtml5TreeOpStage::~nsHtml5TreeOpStage()
}
void
nsHtml5TreeOpStage::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
mozilla::MutexAutoLock autoLock(mMutex);
if (mOpQueue.IsEmpty()) {
mOpQueue.SwapElements(aOpQueue);
}
}
void
nsHtml5TreeOpStage::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
nsHtml5TreeOpStage::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
mozilla::MutexAutoLock autoLock(mMutex);
if (mOpQueue.IsEmpty()) {
@ -67,7 +58,7 @@ nsHtml5TreeOpStage::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
}
void
nsHtml5TreeOpStage::RetrieveOperations(nsTArray<nsHtml5TreeOperation>& aOpQueue)
nsHtml5TreeOpStage::MoveOpsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
mozilla::MutexAutoLock autoLock(mMutex);
if (aOpQueue.IsEmpty()) {

View File

@ -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<nsHtml5TreeOperation>& aOpQueue);
/**
* Flush the operations from the tree operations from the argument
* queue unconditionally.
*/
virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
/**
* Retrieve the staged operations into the argument.
*/
void RetrieveOperations(nsTArray<nsHtml5TreeOperation>& aOpQueue);
void MoveOpsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue);
#ifdef DEBUG
void AssertEmpty();

View File

@ -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<nsIContent> 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<nsIContent> 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<nsIContent> 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<nsIContent> 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<nsIContent> 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<nsIAtom> 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<nsIContent> asContent = do_QueryInterface(docType);
aBuilder->HoldNonElement(*target = asContent);
return rv;
return AppendToDocument(asContent, aBuilder);
}
case eTreeOpRunScript: {
nsIContent* node = *(mOne.node);

View File

@ -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