Bug 1333990: Part 2c - Interrupt the flush loop after inserting document element. r=hsivonen draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 15 Mar 2017 12:22:35 -0700
changeset 499607 57dbae3233a179a82b2e0bd0326f8fb149cb6f74
parent 499606 9d471599ff42e7fd9a05875912eef65b57405e18
child 499608 a02fb4e13a7822d3e0c2eb24e64b1a2705f7ffb7
push id49456
push usermaglione.k@gmail.com
push dateThu, 16 Mar 2017 00:44:19 +0000
reviewershsivonen
bugs1333990
milestone54.0a1
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
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
parser/html/nsHtml5TreeOperation.cpp
parser/html/nsHtml5TreeOperation.h
--- 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;