Bug 1333990: Part 2c - Interrupt the flush loop after inserting document element. r=hsivonen
In order to asynchronously load content scripts that need to run very early in
the page load cycle, we need to be able to block further parsing from the
document-element-inserted observer, before any page scripts are loaded.
Interrupting the flush loop after the document element is inserted allows
the observers to run, and temporarily block further parsing if necessary.
MozReview-Commit-ID: A6D2T52Mlx4
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -435,41 +435,43 @@ nsHtml5TreeOpExecutor::RunFlushLoop()
// Avoid bothering the rest of the engine with a doc update if there's
// nothing to do.
return;
}
mFlushState = eInFlush;
nsIContent* scriptElement = nullptr;
+ bool interrupted = false;
BeginDocUpdate();
uint32_t numberOfOpsToFlush = mOpQueue.Length();
const nsHtml5TreeOperation* first = mOpQueue.Elements();
const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
if (MOZ_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate().
break;
}
NS_ASSERTION(mFlushState == eInDocUpdate,
"Tried to perform tree op outside update batch.");
- nsresult rv = iter->Perform(this, &scriptElement);
+ nsresult rv = iter->Perform(this, &scriptElement, &interrupted);
if (NS_FAILED(rv)) {
MarkAsBroken(rv);
break;
}
// Be sure not to check the deadline if the last op was just performed.
if (MOZ_UNLIKELY(iter == last)) {
break;
- } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
- NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+ } else if (MOZ_UNLIKELY(interrupted) ||
+ MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED)) {
mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
EndDocUpdate();
mFlushState = eNotFlushing;
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
printf("REFLUSH SCHEDULED (executing ops): %d\n",
@@ -541,33 +543,34 @@ nsHtml5TreeOpExecutor::FlushDocumentWrit
NS_ASSERTION(!mReadingFromStage,
"Got doc write flush when reading from stage");
#ifdef DEBUG
mStage.AssertEmpty();
#endif
nsIContent* scriptElement = nullptr;
+ bool interrupted = false;
BeginDocUpdate();
uint32_t numberOfOpsToFlush = mOpQueue.Length();
const nsHtml5TreeOperation* start = mOpQueue.Elements();
const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
iter < end;
++iter) {
if (MOZ_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate().
break;
}
NS_ASSERTION(mFlushState == eInDocUpdate,
"Tried to perform tree op outside update batch.");
- rv = iter->Perform(this, &scriptElement);
+ rv = iter->Perform(this, &scriptElement, &interrupted);
if (NS_FAILED(rv)) {
MarkAsBroken(rv);
break;
}
}
mOpQueue.Clear();
@@ -602,33 +605,48 @@ nsHtml5TreeOpExecutor::IsScriptEnabled()
if (!globalObject) {
globalObject = mDocShell->GetScriptGlobalObject();
}
NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
}
void
-nsHtml5TreeOpExecutor::StartLayout() {
+nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
if (mLayoutStarted || !mDocument) {
return;
}
EndDocUpdate();
if (MOZ_UNLIKELY(!mParser)) {
// got terminate
return;
}
nsContentSink::StartLayout(false);
+ *aInterrupted = mParser && !GetParser()->IsParserEnabled();
+
BeginDocUpdate();
}
+void
+nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
+ // Pausing the document update allows JS to run, and potentially block
+ // further parsing.
+ EndDocUpdate();
+
+ if (MOZ_LIKELY(mParser)) {
+ *aInterrupted = !GetParser()->IsParserEnabled();
+
+ BeginDocUpdate();
+ }
+}
+
/**
* 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.
*
* The reason why this is called as a tail call when mFlushState is set to
* eNotFlushing is to allow re-entry to Flush() but only after the current
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -165,17 +165,19 @@ class nsHtml5TreeOpExecutor final : publ
}
void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine);
bool IsScriptEnabled();
virtual nsresult MarkAsBroken(nsresult aReason) override;
- void StartLayout();
+ void StartLayout(bool* aInterrupted);
+
+ void PauseDocUpdate(bool* aInterrupted);
void FlushSpeculativeLoads();
void RunFlushLoop();
nsresult FlushDocumentWrite();
void MaybeSuspend();
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -633,17 +633,18 @@ nsHtml5TreeOperation::MarkMalformedIfScr
if (sele) {
// Make sure to serialize this script correctly, for nice round tripping.
sele->SetIsMalformed();
}
}
nsresult
nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder,
- nsIContent** aScriptElement)
+ nsIContent** aScriptElement,
+ bool* aInterrupted)
{
switch(mOpCode) {
case eTreeOpUninitialized: {
MOZ_CRASH("eTreeOpUninitialized");
}
case eTreeOpAppend: {
nsIContent* node = *(mOne.node);
nsIContent* parent = *(mTwo.node);
@@ -662,17 +663,20 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
case eTreeOpFosterParent: {
nsIContent* node = *(mOne.node);
nsIContent* parent = *(mTwo.node);
nsIContent* table = *(mThree.node);
return FosterParent(node, parent, table, aBuilder);
}
case eTreeOpAppendToDocument: {
nsIContent* node = *(mOne.node);
- return AppendToDocument(node, aBuilder);
+ nsresult rv = AppendToDocument(node, aBuilder);
+
+ aBuilder->PauseDocUpdate(aInterrupted);
+ return rv;
}
case eTreeOpAddAttributes: {
nsIContent* node = *(mOne.node);
nsHtml5HtmlAttributes* attributes = mTwo.attributes;
return AddAttributes(node, attributes, aBuilder);
}
case eTreeOpDocumentMode: {
aBuilder->SetDocumentMode(mOne.mode);
@@ -990,17 +994,17 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
nsIContent* node = *(mOne.node);
int32_t lineNumber = mFour.integer;
nsAutoString val(NS_LITERAL_STRING("line"));
val.AppendInt(lineNumber);
node->SetAttr(kNameSpaceID_None, nsGkAtoms::id, val, true);
return NS_OK;
}
case eTreeOpStartLayout: {
- aBuilder->StartLayout(); // this causes a notification flush anyway
+ aBuilder->StartLayout(aInterrupted); // this causes a notification flush anyway
return NS_OK;
}
default: {
MOZ_CRASH("Bogus tree op");
}
}
return NS_OK; // keep compiler happy
}
--- a/parser/html/nsHtml5TreeOperation.h
+++ b/parser/html/nsHtml5TreeOperation.h
@@ -484,17 +484,18 @@ class nsHtml5TreeOperation {
NS_ASSERTION(IsRunScript(),
"Setting a snapshot for a tree operation other than eTreeOpRunScript!");
NS_PRECONDITION(aSnapshot, "Initialized tree op with null snapshot.");
mTwo.state = aSnapshot;
mFour.integer = aLine;
}
nsresult Perform(nsHtml5TreeOpExecutor* aBuilder,
- nsIContent** aScriptElement);
+ nsIContent** aScriptElement,
+ bool* aInterrupted);
private:
// possible optimization:
// Make the queue take items the size of pointer and make the op code
// decide how many operands it dequeues after it.
eHtml5TreeOperation mOpCode;
union {
nsIContent** node;