Bug 516406 - Make document.write() parser and stream parser have distinct tokenizers in the HTML5 parser. r=bnewman.

This commit is contained in:
Henri Sivonen
2009-09-21 16:18:20 +03:00
parent 4384f2fdf0
commit 2cf48a3ad5
39 changed files with 1546 additions and 614 deletions

View File

@@ -91,13 +91,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContent
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
: mSuppressEOF(PR_FALSE)
, mHasProcessedBase(PR_FALSE)
, mNeedsFlush(PR_FALSE)
: mHasProcessedBase(PR_FALSE)
, mReadingFromStage(PR_FALSE)
, mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
, mNeedsCharsetSwitch(PR_FALSE)
{
}
nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
@@ -106,12 +103,13 @@ nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
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))->DeferredTimerFlush();
(static_cast<nsHtml5TreeOpExecutor*> (aClosure))->Flush();
}
// nsIContentSink
@@ -126,13 +124,9 @@ nsHtml5TreeOpExecutor::WillParse()
NS_IMETHODIMP
nsHtml5TreeOpExecutor::DidBuildModel(PRBool aTerminated)
{
NS_ASSERTION(mLifeCycle == STREAM_ENDING, "Bad life cycle.");
NS_PRECONDITION(mLifeCycle == PARSING,
"Bad life cycle.");
mLifeCycle = TERMINATED;
if (!mSuppressEOF) {
GetTokenizer()->eof();
Flush();
}
GetTokenizer()->end();
// This is comes from nsXMLContentSink
DidBuildModelImpl(aTerminated);
mDocument->ScriptLoader()->RemoveObserver(this);
@@ -272,42 +266,73 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet(nsIContent* aElement)
void
nsHtml5TreeOpExecutor::Flush()
{
mNeedsFlush = PR_FALSE;
FillQueue();
nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
if (mLifeCycle == TERMINATED) {
mFlushTimer->Cancel();
return;
}
NS_ASSERTION(mParser, "mParser was nulled out but life cycle wasn't terminated.");
if (mReadingFromStage) {
mStage.RetrieveOperations(mOpQueue);
}
MOZ_AUTO_DOC_UPDATE(GetDocument(), UPDATE_CONTENT_MODEL, PR_TRUE);
PRIntervalTime flushStart = 0;
PRUint32 opQueueLength = mOpQueue.Length();
if (opQueueLength > NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) { // avoid computing averages with too few ops
flushStart = PR_IntervalNow();
}
mElementsSeenInThisAppendBatch.SetCapacity(opQueueLength * 2);
// XXX alloc failure
const nsHtml5TreeOperation* start = mOpQueue.Elements();
const nsHtml5TreeOperation* end = start + opQueueLength;
for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)start; iter < end; ++iter) {
iter->Perform(this);
}
FlushPendingAppendNotifications();
#ifdef DEBUG_hsivonen
if (mOpQueue.Length() > sInsertionBatchMaxLength) {
sInsertionBatchMaxLength = opQueueLength;
}
#endif
mOpQueue.Clear();
if (flushStart) {
PRUint32 delta = PR_IntervalToMilliseconds(PR_IntervalNow() - flushStart);
sTreeOpQueueMaxLength = delta ?
(PRUint32)((NS_HTML5_TREE_OP_EXECUTOR_MAX_QUEUE_TIME * (PRUint64)opQueueLength) / delta) :
0;
if (sTreeOpQueueMaxLength < NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) {
sTreeOpQueueMaxLength = NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH;
{ // scope for the auto update so that it ends before we try to run the
// script
MOZ_AUTO_DOC_UPDATE(GetDocument(), UPDATE_CONTENT_MODEL, PR_TRUE);
PRIntervalTime flushStart = 0;
PRUint32 opQueueLength = mOpQueue.Length();
if (opQueueLength > NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) { // avoid computing averages with too few ops
flushStart = PR_IntervalNow();
}
mElementsSeenInThisAppendBatch.SetCapacity(opQueueLength * 2);
// XXX alloc failure
const nsHtml5TreeOperation* start = mOpQueue.Elements();
const nsHtml5TreeOperation* end = start + opQueueLength;
for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)start; iter < end; ++iter) {
iter->Perform(this);
}
FlushPendingAppendNotifications();
#ifdef DEBUG_hsivonen
printf("QUEUE MAX LENGTH: %d\n", sTreeOpQueueMaxLength);
if (mOpQueue.Length() > sInsertionBatchMaxLength) {
sInsertionBatchMaxLength = opQueueLength;
}
#endif
mOpQueue.Clear();
if (flushStart) {
PRUint32 delta = PR_IntervalToMilliseconds(PR_IntervalNow() - flushStart);
sTreeOpQueueMaxLength = delta ?
(PRUint32)((NS_HTML5_TREE_OP_EXECUTOR_MAX_QUEUE_TIME * (PRUint64)opQueueLength) / delta) :
0;
if (sTreeOpQueueMaxLength < NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) {
sTreeOpQueueMaxLength = NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH;
}
#ifdef DEBUG_hsivonen
printf("QUEUE MAX LENGTH: %d\n", sTreeOpQueueMaxLength);
#endif
}
}
mFlushTimer->InitWithFuncCallback(TimerCallbackFunc, static_cast<void*> (this), NS_HTML5_TREE_OP_EXECUTOR_MAX_TIME_WITHOUT_FLUSH, nsITimer::TYPE_ONE_SHOT);
ScheduleTimer();
if (mScriptElement) {
NS_ASSERTION(!mCallDidBuildModel, "Had a script element and DidBuildModel call");
ExecuteScript();
} else if (mCallDidBuildModel) {
mCallDidBuildModel = PR_FALSE;
DidBuildModel(PR_FALSE);
}
}
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
@@ -382,55 +407,27 @@ nsHtml5TreeOpExecutor::DocumentMode(nsHtml5DocumentMode m)
htmlDocument->SetCompatibilityMode(mode);
}
nsresult
nsHtml5TreeOpExecutor::MaybePerformCharsetSwitch()
{
if (!mNeedsCharsetSwitch) {
return NS_ERROR_HTMLPARSER_CONTINUE;
}
// this code comes from nsObserverBase.cpp
nsresult rv = NS_OK;
nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
if (!wss) {
return NS_ERROR_HTMLPARSER_CONTINUE;
}
#ifndef DONT_INFORM_WEBSHELL
// ask the webshellservice to load the URL
if (NS_FAILED(rv = wss->SetRendering(PR_FALSE))) {
// do nothing and fall thru
} else if (NS_FAILED(rv = wss->StopDocumentLoad())) {
rv = wss->SetRendering(PR_TRUE); // turn on the rendering so at least we will see something.
} else if (NS_FAILED(rv = wss->ReloadDocument(mPendingCharset.get(), kCharsetFromMetaTag))) {
rv = wss->SetRendering(PR_TRUE); // turn on the rendering so at least we will see something.
} else {
rv = NS_ERROR_HTMLPARSER_STOPPARSING; // We're reloading a new document...stop loading the current.
}
#endif
// if our reload request is not accepted, we should tell parser to go on
if (rv != NS_ERROR_HTMLPARSER_STOPPARSING)
mNeedsCharsetSwitch = PR_FALSE;
rv = NS_ERROR_HTMLPARSER_CONTINUE;
return rv;
}
/**
* This method executes a script element set by nsHtml5TreeBuilder. The reason
* why this code is here and not in the tree builder is to allow the control
* to return from the tokenizer before scripts run. This way, the tokenizer
* is not invoked re-entrantly although the parser is.
* The reason why this code is here and not in the tree builder even in the
* main-thread case is to allow the control to return from the tokenizer
* before scripts run. This way, the tokenizer is not invoked re-entrantly
* although the parser is.
*/
void
nsHtml5TreeOpExecutor::ExecuteScript()
{
NS_PRECONDITION(mScriptElement, "Trying to run a script without having one!");
Flush();
#ifdef GATHER_DOCWRITE_STATISTICS
if (!mSnapshot) {
mSnapshot = mTreeBuilder->newSnapshot();
}
#endif
mReadingFromStage = PR_FALSE;
NS_ASSERTION(mScriptElement, "No script to run");
nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(mScriptElement);
// Notify our document that we're loading this script.
if (mLifeCycle == TERMINATED) {
NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
// We got here not because of an end tag but because the tree builder
// popped an incomplete script element on EOF. Returning here to avoid
// calling back into mParser anymore. mParser has been nulled out by now.
return;
}
// Notify our document that we're loading this script.
nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document.");
htmlDocument->ScriptLoading(sele);
@@ -439,17 +436,18 @@ nsHtml5TreeOpExecutor::ExecuteScript()
// or return NS_ERROR_HTMLPARSER_BLOCK. Or neither if the script doesn't
// need executing.
nsresult rv = mScriptElement->DoneAddingChildren(PR_TRUE);
mScriptElement = nsnull;
// If the act of insertion evaluated the script, we're fine.
// Else, block the parser till the script has loaded.
if (rv == NS_ERROR_HTMLPARSER_BLOCK) {
mScriptElements.AppendObject(sele);
mParser->BlockParser();
} else {
} else if (mLifeCycle != TERMINATED) {
// This may have already happened if the script executed, but in case
// it didn't then remove the element so that it doesn't get stuck forever.
htmlDocument->ScriptExecuted(sele);
static_cast<nsHtml5Parser*> (mParser.get())->MaybePostContinueEvent();
}
mScriptElement = nsnull;
}
nsresult
@@ -460,30 +458,40 @@ nsHtml5TreeOpExecutor::Init(nsIDocument* aDoc,
{
nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
NS_ENSURE_SUCCESS(rv, rv);
aDoc->AddObserver(this);
mCanInterruptParser = PR_FALSE; // without this, nsContentSink calls
// UnblockOnload from DropParserAndPerfHint
return rv;
}
void
nsHtml5TreeOpExecutor::Start()
{
mNeedsFlush = PR_FALSE;
mNeedsCharsetSwitch = PR_FALSE;
mPendingCharset.Truncate();
NS_PRECONDITION(mLifeCycle == NOT_STARTED, "Tried to start when already started.");
mLifeCycle = PARSING;
mScriptElement = nsnull;
ScheduleTimer();
}
void
nsHtml5TreeOpExecutor::End()
nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding)
{
mFlushTimer->Cancel();
}
void
nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const nsACString& aEncoding)
{
mNeedsCharsetSwitch = PR_TRUE;
mPendingCharset.Assign(aEncoding);
nsresult rv = NS_OK;
nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
if (!wss) {
return;
}
#ifndef DONT_INFORM_WEBSHELL
// ask the webshellservice to load the URL
if (NS_FAILED(rv = wss->SetRendering(PR_FALSE))) {
// do nothing and fall thru
} else if (NS_FAILED(rv = wss->StopDocumentLoad())) {
rv = wss->SetRendering(PR_TRUE); // turn on the rendering so at least we will see something.
} else if (NS_FAILED(rv = wss->ReloadDocument(aEncoding, kCharsetFromMetaTag))) {
rv = wss->SetRendering(PR_TRUE); // turn on the rendering so at least we will see something.
}
// if the charset switch was accepted, wss has called Terminate() on the
// parser by now
#endif
}
nsHtml5Tokenizer*
@@ -492,54 +500,42 @@ nsHtml5TreeOpExecutor::GetTokenizer()
return (static_cast<nsHtml5Parser*> (mParser.get()))->GetTokenizer();
}
void
nsHtml5TreeOpExecutor::MaybeSuspend() {
if (!mNeedsFlush) {
mNeedsFlush = !!(mTreeBuilder->GetOpQueueLength() >= sTreeOpQueueMaxLength);
}
if (DidProcessATokenImpl() == NS_ERROR_HTMLPARSER_INTERRUPTED || mNeedsFlush) {
// We've been in the parser for too long and/or the op queue is becoming too
// long to flush in one go it it grows further.
static_cast<nsHtml5Parser*>(mParser.get())->Suspend();
mTreeBuilder->ReqSuspension();
}
}
void
nsHtml5TreeOpExecutor::MaybeExecuteScript() {
if (!mTreeBuilder->HasScript()) {
return;
}
Flush(); // Let the doc update end before we start executing the script
NS_ASSERTION(mScriptElement, "No script to run");
ExecuteScript();
if (mStreamParser) {
mStreamParser->Suspend();
}
}
void
nsHtml5TreeOpExecutor::DeferredTimerFlush() {
if (mTreeBuilder->GetOpQueueLength() > 0) {
mNeedsFlush = PR_TRUE;
}
}
void
nsHtml5TreeOpExecutor::FillQueue() {
mTreeBuilder->SwapQueue(mOpQueue);
}
void
nsHtml5TreeOpExecutor::Reset() {
mSuppressEOF = PR_FALSE;
mHasProcessedBase = PR_FALSE;
mNeedsFlush = PR_FALSE;
mReadingFromStage = PR_FALSE;
mOpQueue.Clear();
mPendingCharset.Truncate();
mNeedsCharsetSwitch = PR_FALSE;
mLifeCycle = NOT_STARTED;
mScriptElement = nsnull;
mCallDidBuildModel = PR_FALSE;
}
void
nsHtml5TreeOpExecutor::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
// no-op
}
void
nsHtml5TreeOpExecutor::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
if (mOpQueue.IsEmpty()) {
mOpQueue.SwapElements(aOpQueue);
return;
}
mOpQueue.MoveElementsFrom(aOpQueue);
}
void
nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState)
{
static_cast<nsHtml5Parser*> (mParser.get())->InitializeDocWriteParserState(aState);
}
void
nsHtml5TreeOpExecutor::StreamEnded()
{
mCallDidBuildModel = PR_TRUE;
}
PRUint32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxLength = NS_HTML5_TREE_OP_EXECUTOR_DEFAULT_QUEUE_LENGTH;