Bug 551344 part 4 - Address more review comments from sicking in the C++ parts of the HTML5 parser. r=jonas.

--HG--
extra : rebase_source : b15efefa4ba578dead7645770f5ce3fa8becefda
This commit is contained in:
Henri Sivonen 2010-04-16 13:52:06 +03:00
parent bec27dec6f
commit 5c3712d80a
5 changed files with 182 additions and 136 deletions

View File

@ -123,7 +123,10 @@ public:
* If the read value does not belong to this character set, one should
* replace it with the Unicode special 0xFFFD. When an actual input error is
* encountered, like a format error, the converter stop and return error.
* Hoever, we should keep in mind that we need to be lax in decoding.
* However, we should keep in mind that we need to be lax in decoding. When
* a decoding error is returned to the caller, it is the caller's
* responsibility to advance over the bad byte and reset the decoder before
* trying to call the decoder again.
*
* Converter required behavior:
* In this order: when output space is full - return right away. When input
@ -144,7 +147,9 @@ public:
* NS_PARTIAL_MORE_OUTPUT if only a partial conversion
* was done; more output space is needed to continue
* NS_ERROR_ILLEGAL_INPUT if an illegal input sequence
* was encountered and the behavior was set to "signal"
* was encountered and the behavior was set to "signal";
* the caller must skip over one byte, reset the decoder
* and retry.
*/
NS_IMETHOD Convert(const char * aSrc, PRInt32 * aSrcLength,
PRUnichar * aDest, PRInt32 * aDestLength) = 0;

View File

@ -2873,15 +2873,13 @@ pref("accelerometer.enabled", true);
pref("html5.enable", false);
// Toggle which thread the HTML5 parser uses for stream 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", 120);
// Time in milliseconds between the time a network buffer is seen and the
// timer firing when the timer hasn't fired previously in this parse in the
// off-the-main-thread HTML5 parser.
pref("html5.flushtimer.initialdelay", 200);
// Time in milliseconds between the time a network buffer is seen and the
// timer firing when the timer has already fired previously in this parse.
pref("html5.flushtimer.subsequentdelay", 120);
// Push/Pop/Replace State prefs
pref("browser.history.allowPushState", true);

View File

@ -304,7 +304,10 @@ class nsHtml5Parser : public nsIParser,
void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, PRInt32 aLine);
void DropStreamParser() {
mStreamParser = nsnull;
if (mStreamParser) {
mStreamParser->DropTimer();
mStreamParser = nsnull;
}
}
void StartTokenizer(PRBool aScriptingEnabled);

View File

@ -54,22 +54,43 @@
static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID);
PRInt32 nsHtml5StreamParser::sTimerStartDelay = 200;
PRInt32 nsHtml5StreamParser::sTimerContinueDelay = 150;
PRInt32 nsHtml5StreamParser::sTimerInterval = 100;
PRInt32 nsHtml5StreamParser::sTimerInitialDelay = 200;
PRInt32 nsHtml5StreamParser::sTimerSubsequentDelay = 100;
// static
void
nsHtml5StreamParser::InitializeStatics()
{
nsContentUtils::AddIntPrefVarCache("html5.flushtimer.startdelay",
&sTimerStartDelay);
nsContentUtils::AddIntPrefVarCache("html5.flushtimer.continuedelay",
&sTimerContinueDelay);
nsContentUtils::AddIntPrefVarCache("html5.flushtimer.interval",
&sTimerInterval);
nsContentUtils::AddIntPrefVarCache("html5.flushtimer.initialdelay",
&sTimerInitialDelay);
nsContentUtils::AddIntPrefVarCache("html5.flushtimer.subsequentdelay",
&sTimerSubsequentDelay);
}
/*
* Note that nsHtml5StreamParser implements cycle collecting AddRef and
* Release. Therefore, nsHtml5StreamParser must never be refcounted from
* the parser thread!
*
* To work around this limitation, runnables posted by the main thread to the
* parser thread hold their reference to the stream parser in an
* nsHtml5RefPtr. Upon creation, nsHtml5RefPtr addrefs the object it holds
* just like a regular nsRefPtr. This is OK, since the creation of the
* runnable and the nsHtml5RefPtr happens on the main thread.
*
* When the runnable is done on the parser thread, the destructor of
* nsHtml5RefPtr runs there. It doesn't call Release on the held object
* directly. Instead, it posts another runnable back to the main thread where
* that runnable calls Release on the wrapped object.
*
* When posting runnables in the other direction, the runnables have to be
* created on the main thread when nsHtml5StreamParser is instantiated and
* held for the lifetime of the nsHtml5StreamParser. This works, because the
* same runnabled can be dispatched multiple times and currently runnables
* posted from the parser thread to main thread don't need to wrap any
* runnable-specific data. (In the other direction, the runnables most notably
* wrap the byte data of the stream.)
*/
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5StreamParser)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5StreamParser)
@ -83,10 +104,7 @@ 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;
}
tmp->DropTimer();
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest)
tmp->mOwner = nsnull;
@ -169,10 +187,11 @@ nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
, mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mFlushTimer->SetTarget(mThread);
mAtomTable.Init(); // we aren't checking for OOM anyway...
#ifdef DEBUG
mAtomTable.SetPermittedLookupThread(mThread);
#endif
#ifdef DEBUG
mAtomTable.SetPermittedLookupThread(mThread);
#endif
mTokenizer->setInterner(&mAtomTable);
mTokenizer->setEncodingDeclarationHandler(this);
@ -198,6 +217,8 @@ nsHtml5StreamParser::~nsHtml5StreamParser()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mTokenizer->end();
NS_ASSERTION(!mFlushTimer, "Flush timer was not dropped before dtor!");
#ifdef DEBUG
mRequest = nsnull;
mObserver = nsnull;
mUnicodeDecoder = nsnull;
@ -208,10 +229,7 @@ nsHtml5StreamParser::~nsHtml5StreamParser()
mTreeBuilder = nsnull;
mTokenizer = nsnull;
mOwner = nsnull;
if (mFlushTimer) {
mFlushTimer->Cancel();
mFlushTimer = nsnull;
}
#endif
}
nsresult
@ -540,11 +558,6 @@ 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;
@ -658,6 +671,18 @@ nsHtml5StreamParser::DoDataAvailable(PRUint8* aBuffer, PRUint32 aLength)
}
ParseAvailableData();
if (mFlushTimerArmed || mSpeculating) {
return;
}
mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback,
static_cast<void*> (this),
mFlushTimerEverFired ?
sTimerInitialDelay :
sTimerSubsequentDelay,
nsITimer::TYPE_ONE_SHOT);
mFlushTimerArmed = PR_TRUE;
}
class nsHtml5DataAvailable : public nsRunnable
@ -748,16 +773,29 @@ nsHtml5StreamParser::internalEncodingDeclaration(nsString* aEncoding)
}
mTreeBuilder->NeedsCharsetSwitchTo(preferred);
mTreeBuilder->Flush();
FlushTreeOpsAndDisarmTimer();
Interrupt();
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
// the tree op executor will cause the stream parser to terminate
// if the charset switch request is accepted or it'll uninterrupt
// if the request failed.
}
void
nsHtml5StreamParser::FlushTreeOpsAndDisarmTimer()
{
NS_ASSERTION(IsParserThread(), "Wrong thread!");
if (mFlushTimerArmed) {
// avoid calling Cancel if the flush timer isn't armed to avoid acquiring
// a mutex
mFlushTimer->Cancel();
mFlushTimerArmed = PR_FALSE;
}
mTreeBuilder->Flush();
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
}
void
nsHtml5StreamParser::ParseAvailableData()
{
@ -794,10 +832,7 @@ nsHtml5StreamParser::ParseAvailableData()
mAtEOF = PR_TRUE;
mTokenizer->eof();
mTreeBuilder->StreamEnded();
mTreeBuilder->Flush();
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
FlushTreeOpsAndDisarmTimer();
return; // no more data and not expecting more
default:
NS_NOTREACHED("It should be impossible to reach this.");
@ -826,10 +861,7 @@ nsHtml5StreamParser::ParseAvailableData()
mTreeBuilder->newSnapshot());
mTreeBuilder->AddSnapshotToScript(speculation->GetSnapshot(),
speculation->GetStartLineNumber());
mTreeBuilder->Flush();
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
FlushTreeOpsAndDisarmTimer();
mTreeBuilder->SetOpSink(speculation);
mSpeculations.AppendElement(speculation); // adopts the pointer
mSpeculating = PR_TRUE;
@ -853,6 +885,7 @@ public:
NS_IMETHODIMP Run()
{
mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
mStreamParser->Uninterrupt();
mStreamParser->ParseAvailableData();
return NS_OK;
}
@ -943,11 +976,7 @@ nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer,
mTreeBuilder->SetOpSink(mExecutor->GetStage());
mExecutor->StartReadingFromStage();
mSpeculating = PR_FALSE;
mFlushTimer->Cancel();
mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback,
static_cast<void*> (this),
sTimerContinueDelay,
nsITimer::TYPE_ONE_SHOT);
// Copy state over
mLastWasCR = aLastWasCR;
mTokenizer->loadState(aTokenizer);
@ -968,17 +997,11 @@ nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer,
mTreeBuilder->SetOpSink(mExecutor->GetStage());
mExecutor->StartReadingFromStage();
mSpeculating = PR_FALSE;
mFlushTimer->Cancel();
mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback,
static_cast<void*> (this),
sTimerContinueDelay,
nsITimer::TYPE_ONE_SHOT);
}
}
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");
}
// A stream event might run before this event runs, but that's harmless.
#ifdef DEBUG
@ -991,29 +1014,83 @@ void
nsHtml5StreamParser::ContinueAfterFailedCharsetSwitch()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mozilla::MutexAutoLock tokenizerAutoLock(mTokenizerMutex);
Uninterrupt();
nsCOMPtr<nsIRunnable> event = new nsHtml5StreamParserContinuation(this);
if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch nsHtml5StreamParserContinuation");
}
}
class nsHtml5TimerKungFu : public nsRunnable
{
private:
nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
public:
nsHtml5TimerKungFu(nsHtml5StreamParser* aStreamParser)
: mStreamParser(aStreamParser)
{}
NS_IMETHODIMP Run()
{
if (mStreamParser->mFlushTimer) {
mStreamParser->mFlushTimer->Cancel();
mStreamParser->mFlushTimer = nsnull;
}
return NS_OK;
}
};
void
nsHtml5StreamParser::DropTimer()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
/*
* Simply nulling out the timer wouldn't work, because if the timer is
* armed, it needs to be canceled first. Simply canceling it first wouldn't
* work, because nsTimerImpl::Cancel is not safe for calling from outside
* the thread where nsTimerImpl::Fire would run. It's not safe to
* dispatch a runnable to cancel the timer from the destructor of this
* class, because the timer has a weak (void*) pointer back to this instance
* of the stream parser and having the timer fire before the runnable
* cancels it would make the timer access a deleted object.
*
* This DropTimer method addresses these issues. This method must be called
* on the main thread before the destructor of this class is reached.
* The nsHtml5TimerKungFu object has an nsHtml5RefPtr that addrefs this
* stream parser object to keep it alive until the runnable is done.
* The runnable cancels the timer on the parser thread, drops the timer
* and lets nsHtml5RefPtr send a runnable back to the main thread to
* release the stream parser.
*/
if (mFlushTimer) {
nsCOMPtr<nsIRunnable> event = new nsHtml5TimerKungFu(this);
if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch TimerKungFu event");
}
}
}
// 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();
(static_cast<nsHtml5StreamParser*> (aClosure))->TimerFlush();
}
void
nsHtml5StreamParser::TimerFlush()
{
NS_ASSERTION(IsParserThread(), "Wrong thread!");
mTokenizerMutex.AssertCurrentThreadOwns();
mozilla::MutexAutoLock autoLock(mTokenizerMutex);
if (mSpeculating) {
NS_ASSERTION(!mSpeculating, "Flush timer fired while speculating.");
// The timer fired if we got here. No need to cancel it. Mark it as
// not armed, though.
mFlushTimerArmed = PR_FALSE;
mFlushTimerEverFired = PR_TRUE;
if (IsTerminatedOrInterrupted()) {
return;
}
@ -1031,52 +1108,3 @@ nsHtml5StreamParser::TimerFlush()
}
}
}
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();
// 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 the document 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

@ -110,7 +110,7 @@ class nsHtml5StreamParser : public nsIStreamListener,
friend class nsHtml5RequestStopper;
friend class nsHtml5DataAvailable;
friend class nsHtml5StreamParserContinuation;
friend class nsHtml5StreamParserTimerFlusher;
friend class nsHtml5TimerKungFu;
public:
NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
@ -175,8 +175,7 @@ class nsHtml5StreamParser : public nsIStreamListener,
PRBool aLastWasCR);
/**
* Uninterrupts and continues the stream parser if the charset switch
* failed.
* Continues the stream parser if the charset switch failed.
*/
void ContinueAfterFailedCharsetSwitch();
@ -185,6 +184,8 @@ class nsHtml5StreamParser : public nsIStreamListener,
mTerminated = PR_TRUE;
}
void DropTimer();
private:
#ifdef DEBUG
@ -195,19 +196,31 @@ class nsHtml5StreamParser : public nsIStreamListener,
}
#endif
/**
* Marks the stream parser as interrupted. If you ever add calls to this
* method, be sure to review Uninterrupt usage very, very carefully to
* avoid having a previous in-flight runnable cancel your Interrupt()
* call on the other thread too soon.
*/
void Interrupt() {
mozilla::MutexAutoLock autoLock(mTerminatedMutex);
mInterrupted = PR_TRUE;
}
void Uninterrupt() {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(IsParserThread(), "Wrong thread!");
mTokenizerMutex.AssertCurrentThreadOwns();
// Not acquiring mTerminatedMutex because mTokenizerMutex is already
// held at this point and is already stronger.
mInterrupted = PR_FALSE;
}
/**
* Flushes the tree ops from the tree builder and disarms the flush
* timer.
*/
void FlushTreeOpsAndDisarmTimer();
void ParseAvailableData();
void DoStopRequest();
@ -311,12 +324,6 @@ class nsHtml5StreamParser : public nsIStreamListener,
*/
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.
@ -466,24 +473,29 @@ class nsHtml5StreamParser : public nsIStreamListener,
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.
* Keeps track whether mFlushTimer has been armed. Unfortunately,
* nsITimer doesn't enable querying this from the timer itself.
*/
static PRInt32 sTimerStartDelay;
PRBool mFlushTimerArmed;
/**
* The pref html5.flushtimer.continuedelay: Time in milliseconds between
* the return to non-speculating more and the first time the flush timer
* fires thereafter.
* False initially and true after the timer has fired at least once.
*/
static PRInt32 sTimerContinueDelay;
PRBool mFlushTimerEverFired;
/**
* The pref html5.flushtimer.interval: Time in milliseconds between
* timer firings once the timer has starting firing.
* The pref html5.flushtimer.initialdelay: Time in milliseconds between
* the time a network buffer is seen and the timer firing when the
* timer hasn't fired previously in this parse.
*/
static PRInt32 sTimerInterval;
static PRInt32 sTimerInitialDelay;
/**
* The pref html5.flushtimer.subsequentdelay: Time in milliseconds between
* the time a network buffer is seen and the timer firing when the
* timer has already fired previously in this parse.
*/
static PRInt32 sTimerSubsequentDelay;
};
#endif // nsHtml5StreamParser_h__