Don't start layout for XML just because the root has been opened. And don't
authorbzbarsky@mit.edu
Tue, 10 Jul 2007 20:42:11 -0700
changeset 3317 7bcd7a075d3a9113a4fcef0c7d117aa3eaab3731
parent 3316 d8e7642fda323f34622c024f8e84e88548cdfdfc
child 3318 e3463b77b01963903138c935e6bb075935238299
push idunknown
push userunknown
push dateunknown
bugs380028
milestone1.9a7pre
Don't start layout for XML just because the root has been opened. And don't start layout until <html:head> closes if <html:head> gets opened. For XHTML documents, this should prevent flashes of unstyled content. Also, don't flush tags unless forced to if a sheet is pending. Bug 380028, r+sr=sicking
content/base/src/nsContentSink.cpp
content/base/src/nsContentSink.h
content/html/document/src/nsHTMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.h
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -264,24 +264,30 @@ NS_IMETHODIMP
 nsContentSink::StyleSheetLoaded(nsICSSStyleSheet* aSheet,
                                 PRBool aWasAlternate,
                                 nsresult aStatus)
 {
   if (!aWasAlternate) {
     NS_ASSERTION(mPendingSheetCount > 0, "How'd that happen?");
     --mPendingSheetCount;
 
-    if (mPendingSheetCount == 0 && mDeferredLayoutStart) {
-      // We might not have really started layout, since this sheet was still
-      // loading.  Do it now.  Probably doesn't matter whether we do this
-      // before or after we unblock scripts, but before feels saner.  Note that
-      // if mDeferredLayoutStart is true, that means any subclass StartLayout()
-      // stuff that needs to happen has already happened, so we don't need to
-      // worry about it.
-      StartLayout(PR_FALSE);
+    if (mPendingSheetCount == 0 &&
+        (mDeferredLayoutStart || mDeferredFlushTags)) {
+      if (mDeferredFlushTags) {
+        FlushTags();
+      }
+      if (mDeferredLayoutStart) {
+        // We might not have really started layout, since this sheet was still
+        // loading.  Do it now.  Probably doesn't matter whether we do this
+        // before or after we unblock scripts, but before feels saner.  Note
+        // that if mDeferredLayoutStart is true, that means any subclass
+        // StartLayout() stuff that needs to happen has already happened, so we
+        // don't need to worry about it.
+        StartLayout(PR_FALSE);
+      }
 
       // Go ahead and try to scroll to our ref if we have one
       TryToScrollToRef();
     }
     
     mScriptLoader->RemoveExecuteBlocker();
   }
 
@@ -1014,17 +1020,17 @@ nsContentSink::StartLayout(PRBool aIgnor
 {
   if (mLayoutStarted) {
     // Nothing to do here
     return;
   }
   
   mDeferredLayoutStart = PR_TRUE;
 
-  if (!aIgnorePendingSheets && mPendingSheetCount > 0) {
+  if (!aIgnorePendingSheets && WaitForPendingSheets()) {
     // Bail out; we'll start layout when the sheets load
     return;
   }
 
   mDeferredLayoutStart = PR_FALSE;
 
   // Notify on all our content.  If none of our presshells have started layout
   // yet it'll be a no-op except for updating our data structures, a la
@@ -1167,35 +1173,45 @@ nsContentSink::Notify(nsITimer *timer)
 
     mBackoffCount--;
     SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_REFLOW,
                ("nsContentSink::Notify: reflow on a timer: %d milliseconds "
                 "late, backoff count: %d", delay, mBackoffCount));
   }
 #endif
 
-  FlushTags();
+  if (WaitForPendingSheets()) {
+    mDeferredFlushTags = PR_TRUE;
+  } else {
+    FlushTags();
 
-  // Now try and scroll to the reference
-  // XXX Should we scroll unconditionally for history loads??
-  TryToScrollToRef();
+    // Now try and scroll to the reference
+    // XXX Should we scroll unconditionally for history loads??
+    TryToScrollToRef();
+  }
+
   mNotificationTimer = nsnull;
   MOZ_TIMER_DEBUGLOG(("Stop: nsHTMLContentSink::Notify()\n"));
   MOZ_TIMER_STOP(mWatch);
   return NS_OK;
 }
 
 PRBool
 nsContentSink::IsTimeToNotify()
 {
   if (!mNotifyOnTimer || !mLayoutStarted || !mBackoffCount ||
       mInMonolithicContainer) {
     return PR_FALSE;
   }
 
+  if (WaitForPendingSheets()) {
+    mDeferredFlushTags = PR_TRUE;
+    return PR_FALSE;
+  }
+
   PRTime now = PR_Now();
   PRInt64 interval, diff;
 
   LL_I2L(interval, GetNotificationInterval());
   LL_SUB(diff, now, mLastNotificationTime);
 
   if (LL_CMP(diff, >, interval)) {
     mBackoffCount--;
@@ -1208,17 +1224,19 @@ nsContentSink::IsTimeToNotify()
 nsresult
 nsContentSink::WillInterruptImpl()
 {
   nsresult result = NS_OK;
 
   SINK_TRACE(gContentSinkLogModuleInfo, SINK_TRACE_CALLS,
              ("nsContentSink::WillInterrupt: this=%p", this));
 #ifndef SINK_NO_INCREMENTAL
-  if (mNotifyOnTimer && mLayoutStarted) {
+  if (WaitForPendingSheets()) {
+    mDeferredFlushTags = PR_TRUE;
+  } else if (mNotifyOnTimer && mLayoutStarted) {
     if (mBackoffCount && !mInMonolithicContainer) {
       PRInt64 now = PR_Now();
       PRInt64 interval = GetNotificationInterval();
       PRInt64 diff = now - mLastNotificationTime;
 
       // If it's already time for us to have a notification
       if (diff > interval || mDroppedTimer) {
         mBackoffCount--;
--- a/content/base/src/nsContentSink.h
+++ b/content/base/src/nsContentSink.h
@@ -206,16 +206,20 @@ protected:
   // Overridable hooks into script evaluation
   virtual void PreEvaluateScript()                            {return;}
   virtual void PostEvaluateScript(nsIScriptElement *aElement) {return;}
 
   virtual nsresult FlushTags() = 0;
 
   void TryToScrollToRef();
 
+  // Later on we might want to make this more involved somehow
+  // (e.g. stop waiting after some timeout or whatnot).
+  PRBool WaitForPendingSheets() { return mPendingSheetCount > 0; }
+
 private:
   // People shouldn't be allocating this class directly.  All subclasses should
   // be allocated using a zeroing operator new.
   void* operator new(size_t sz) CPP_THROW_NEW;  // Not to be implemented
 
 protected:
 
   void ContinueInterruptedParsingAsync();
@@ -271,17 +275,19 @@ protected:
   PRUint8 mInTitle : 1;
   PRUint8 mChangeScrollPosWhenScrollingToRef : 1;
   // If true, we deferred starting layout until sheets load
   PRUint8 mDeferredLayoutStart : 1;
   // true if an <link rel="offline-resource"> nodes have been encountered.
   PRUint8 mHaveOfflineResources : 1;
   // true if offline-resource links should be saved to the offline cache
   PRUint8 mSaveOfflineResources : 1;
-
+  // If true, we deferred notifications until sheets load
+  PRUint8 mDeferredFlushTags : 1;
+  
   // -- Can interrupt parsing members --
   PRUint32 mDelayTimerStart;
 
   // Interrupt parsing during token procesing after # of microseconds
   PRInt32 mMaxTokenProcessingTime;
 
   // Switch between intervals when time is exceeded
   PRInt32 mDynamicIntervalSwitchThreshold;
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -1335,16 +1335,17 @@ SinkContext::AddText(const nsAString& aT
  * they are visible in the tree. Specifically, make sure
  * that they are all added to their respective parents.
  * Also, do notification at the top for all content that
  * has been newly added so that the frame tree is complete.
  */
 nsresult
 SinkContext::FlushTags()
 {
+  mSink->mDeferredFlushTags = PR_FALSE;
   PRBool oldBeganUpdate = mSink->mBeganUpdate;
   PRUint32 oldUpdates = mSink->mUpdatesInNotification;
 
   ++(mSink->mInNotification);
   mSink->mUpdatesInNotification = 0;
   {
     // Scope so we call EndUpdate before we decrease mInNotification
     mozAutoDocUpdate updateBatch(mSink->mDocument, UPDATE_CONTENT_MODEL,
--- a/content/xml/document/src/nsXMLContentSink.cpp
+++ b/content/xml/document/src/nsXMLContentSink.cpp
@@ -1061,27 +1061,34 @@ nsXMLContentSink::HandleStartElement(con
       NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
 
       parent->AppendChildTo(content, PR_FALSE);
     }
   }
 
   // Some HTML nodes need DoneCreatingElement() called to initialize
   // properly (eg form state restoration).
-  if (nodeInfo->NamespaceID() == kNameSpaceID_XHTML &&
-      (nodeInfo->NameAtom() == nsGkAtoms::input ||
-       nodeInfo->NameAtom() == nsGkAtoms::button)) {
-    content->DoneCreatingElement();
+  if (nodeInfo->NamespaceID() == kNameSpaceID_XHTML) {
+    if (nodeInfo->NameAtom() == nsGkAtoms::input ||
+        nodeInfo->NameAtom() == nsGkAtoms::button) {
+      content->DoneCreatingElement();
+    } else if (nodeInfo->NameAtom() == nsGkAtoms::head && !mCurrentHead) {
+      mCurrentHead = content;
+    }
   }
 
   if (IsMonolithicContainer(nodeInfo)) {
     mInMonolithicContainer++;
   }
 
-  MaybeStartLayout(PR_FALSE);
+  if (content != mDocElement && !mCurrentHead) {
+    // This isn't the root and we're not inside an XHTML <head>.
+    // Might need to start layout
+    MaybeStartLayout(PR_FALSE);
+  }
 
   return aInterruptable && NS_SUCCEEDED(result) ? DidProcessATokenImpl() :
                                                   result;
 }
 
 NS_IMETHODIMP
 nsXMLContentSink::HandleEndElement(const PRUnichar *aName)
 {
@@ -1117,20 +1124,27 @@ nsXMLContentSink::HandleEndElement(const
                                  getter_AddRefs(debugTagAtom),
                                  &debugNameSpaceID);
   NS_ASSERTION(content->NodeInfo()->Equals(debugTagAtom, debugNameSpaceID),
                "Wrong element being closed");
 #endif  
 
   result = CloseElement(content);
 
+  if (mCurrentHead == content) {
+    mCurrentHead = nsnull;
+  }
+  
   if (mDocElement == content) {
     // XXXbz for roots that don't want to be appended on open, we
     // probably need to deal here.... (and stop appending them on open).
     mState = eXMLContentSinkState_InEpilog;
+
+    // We might have had no occasion to start layout yet.  Do so now.
+    MaybeStartLayout(PR_FALSE);
   }
 
   PRInt32 stackLen = mContentStack.Length();
   if (mNotifyLevel >= stackLen) {
     if (numFlushed < content->GetChildCount()) {
     	  NotifyAppend(content, numFlushed);
     }
     mNotifyLevel = stackLen - 1;
@@ -1586,16 +1600,17 @@ nsXMLContentSink::FlushPendingNotificati
  * they are visible in the tree. Specifically, make sure
  * that they are all added to their respective parents.
  * Also, do notification at the top for all content that
  * has been newly added so that the frame tree is complete.
  */
 nsresult
 nsXMLContentSink::FlushTags()
 {
+  mDeferredFlushTags = PR_FALSE;
   PRBool oldBeganUpdate = mBeganUpdate;
   PRUint32 oldUpdates = mUpdatesInNotification;
 
   mUpdatesInNotification = 0;
   ++mInNotification;
   {
     // Scope so we call EndUpdate before we decrease mInNotification
     mozAutoDocUpdate updateBatch(mDocument, UPDATE_CONTENT_MODEL, PR_TRUE);
--- a/content/xml/document/src/nsXMLContentSink.h
+++ b/content/xml/document/src/nsXMLContentSink.h
@@ -178,16 +178,17 @@ protected:
                               PRUint32 aAttsCount, PRInt32 aIndex, 
                               PRUint32 aLineNumber,
                               PRBool aInterruptable);
   nsresult HandleEndElement(const PRUnichar *aName, PRBool aInterruptable);
   nsresult HandleCharacterData(const PRUnichar *aData, PRUint32 aLength,
                                PRBool aInterruptable);
 
   nsIContent*      mDocElement;
+  nsCOMPtr<nsIContent> mCurrentHead;  // When set, we're in an XHTML <haed>
   PRUnichar*       mText;
 
   XMLContentSinkState mState;
 
   nsString mTitleText; 
   
   PRInt32 mTextLength;
   PRInt32 mTextSize;