Bug 516406 - Make document.write() parser and stream parser have distinct tokenizers in the HTML5 parser. r=bnewman.
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user