Add an API to observe all loads in a CSSLoader. Change XML prettyprinting tonot start layout till after the prettyprinter stylesheet is loaded, so we don'treflow and reconstruct the whole thing twice. Bug 380612, r+sr=sicking
authorbzbarsky@mit.edu
Wed, 30 May 2007 18:42:48 -0700
changeset 2026 ec0696ee7979595723aebd7c2e4ff4e50a06077a
parent 2025 1d40e45cf71c36080013bd00375ff68a16d92ad1
child 2027 1d2a1063c3026569636aa0e5468f06c197dbd552
push idunknown
push userunknown
push dateunknown
bugs380612
milestone1.9a5pre
Add an API to observe all loads in a CSSLoader. Change XML prettyprinting tonot start layout till after the prettyprinter stylesheet is loaded, so we don'treflow and reconstruct the whole thing twice. Bug 380612, r+sr=sicking
content/xml/document/src/nsXMLContentSink.cpp
content/xml/document/src/nsXMLContentSink.h
content/xml/document/src/nsXMLPrettyPrinter.cpp
content/xml/document/src/nsXMLPrettyPrinter.h
layout/style/nsCSSLoader.cpp
layout/style/nsCSSLoader.h
layout/style/nsICSSLoader.h
--- a/content/xml/document/src/nsXMLContentSink.cpp
+++ b/content/xml/document/src/nsXMLContentSink.cpp
@@ -246,17 +246,22 @@ nsXMLContentSink::MaybePrettyPrint()
   if (mCSSLoader) {
     mCSSLoader->SetEnabled(PR_TRUE);
   }
   
   nsCOMPtr<nsXMLPrettyPrinter> printer;
   nsresult rv = NS_NewXMLPrettyPrinter(getter_AddRefs(printer));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return printer->PrettyPrint(mDocument);
+  PRBool isPrettyPrinting;
+  rv = printer->PrettyPrint(mDocument, &isPrettyPrinting);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mPrettyPrinting = isPrettyPrinting;
+  return NS_OK;
 }
 
 static void
 CheckXSLTParamPI(nsIDOMProcessingInstruction* aPi,
                  nsIDocumentTransformer* aProcessor,
                  nsIDocument* aDocument)
 {
   nsAutoString target, data;
@@ -336,19 +341,35 @@ nsXMLContentSink::DidBuildModel()
       // documentElement?
       NS_ASSERTION(mDocument->IndexOf(mDocElement) != -1,
                    "mDocElement not in doc?");
     }
 
     // Check if we want to prettyprint
     MaybePrettyPrint();
 
-    StartLayout(PR_FALSE);
+    PRBool startLayout = PR_TRUE;
+    
+    if (mPrettyPrinting) {
+      NS_ASSERTION(!mPendingSheetCount, "Shouldn't have pending sheets here!");
+      
+      // We're pretty-printing now.  See whether we should wait up on
+      // stylesheet loads
+      if (mDocument->CSSLoader()->HasPendingLoads() &&
+          NS_SUCCEEDED(mDocument->CSSLoader()->AddObserver(this))) {
+        // wait for those sheets to load
+        startLayout = PR_FALSE;
+      }
+    }
+    
+    if (startLayout) {
+      StartLayout(PR_FALSE);
 
-    ScrollToRef();
+      ScrollToRef();
+    }
 
     mDocument->RemoveObserver(this);
 
     mDocument->EndLoad();
   }
 
   DropParserAndPerfHint();
 
@@ -423,16 +444,33 @@ nsXMLContentSink::OnTransformDone(nsresu
 
   ScrollToRef();
 
   originalDocument->EndLoad();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXMLContentSink::StyleSheetLoaded(nsICSSStyleSheet* aSheet,
+                                   PRBool aWasAlternate,
+                                   nsresult aStatus)
+{
+  if (!mPrettyPrinting) {
+    return nsContentSink::StyleSheetLoaded(aSheet, aWasAlternate, aStatus);
+  }
+
+  if (!mDocument->CSSLoader()->HasPendingLoads()) {
+    mDocument->CSSLoader()->RemoveObserver(this);
+    StartLayout(PR_FALSE);
+    ScrollToRef();
+  }
+
+  return NS_OK;
+}
 
 NS_IMETHODIMP
 nsXMLContentSink::WillInterrupt(void)
 {
   return WillInterruptImpl();
 }
 
 NS_IMETHODIMP
--- a/content/xml/document/src/nsXMLContentSink.h
+++ b/content/xml/document/src/nsXMLContentSink.h
@@ -96,16 +96,19 @@ public:
   virtual void FlushPendingNotifications(mozFlushType aType);
   NS_IMETHOD SetDocumentCharset(nsACString& aCharset);
   virtual nsISupports *GetTarget();
 
   // nsITransformObserver
   NS_IMETHOD OnDocumentCreated(nsIDocument *aResultDocument);
   NS_IMETHOD OnTransformDone(nsresult aResult, nsIDocument *aResultDocument);
 
+  // nsICSSLoaderObserver
+  NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aWasAlternate,
+                              nsresult aStatus);
   static void ParsePIData(const nsString &aData, nsString &aHref,
                           nsString &aTitle, nsString &aMedia,
                           PRBool &aIsAlternate);
 
 protected:
   // Start layout.  If aIgnorePendingSheets is true, this will happen even if
   // we still have stylesheet loads pending.  Otherwise, we'll wait until the
   // stylesheets are all done loading.
@@ -185,16 +188,18 @@ protected:
   PRInt32 mNotifyLevel;
 
   PRUint8 mConstrainSize : 1;
   PRUint8 mPrettyPrintXML : 1;
   PRUint8 mPrettyPrintHasSpecialRoot : 1;
   PRUint8 mPrettyPrintHasFactoredElements : 1;
   PRUint8 mHasProcessedBase : 1;
   PRUint8 mAllowAutoXLinks : 1;
-  PRUint8 unused : 2;  // bits available if someone needs one
+  PRUint8 mPrettyPrinting : 1;  // True if we called PrettyPrint() and it
+                                // decided we should in fact prettyprint.
+  PRUint8 unused : 1;  // bits available if someone needs one
   
   nsTArray<StackNode>              mContentStack;
 
   nsCOMPtr<nsIDocumentTransformer> mXSLTProcessor;
 };
 
 #endif // nsXMLContentSink_h__
--- a/content/xml/document/src/nsXMLPrettyPrinter.cpp
+++ b/content/xml/document/src/nsXMLPrettyPrinter.cpp
@@ -66,18 +66,21 @@ nsXMLPrettyPrinter::nsXMLPrettyPrinter()
 }
 
 nsXMLPrettyPrinter::~nsXMLPrettyPrinter()
 {
     NS_ASSERTION(!mDocument, "we shouldn't be referencing the document still");
 }
 
 nsresult
-nsXMLPrettyPrinter::PrettyPrint(nsIDocument* aDocument)
+nsXMLPrettyPrinter::PrettyPrint(nsIDocument* aDocument,
+                                PRBool* aDidPrettyPrint)
 {
+    *aDidPrettyPrint = PR_FALSE;
+    
     // Check for iframe with display:none. Such iframes don't have presshells
     if (!aDocument->GetPrimaryShell()) {
         return NS_OK;
     }
 
     // check if we're in an invisible iframe
     nsPIDOMWindow *internalWin = aDocument->GetWindow();
     nsCOMPtr<nsIDOMElement> frameElem;
@@ -114,16 +117,17 @@ nsXMLPrettyPrinter::PrettyPrint(nsIDocum
     }
 
     // check the pref
     if (!nsContentUtils::GetBoolPref("layout.xml.prettyprint", PR_TRUE)) {
         return NS_OK;
     }
 
     // Ok, we should prettyprint. Let's do it!
+    *aDidPrettyPrint = PR_TRUE;
     nsresult rv = NS_OK;
 
     // Load the XSLT
     nsCOMPtr<nsIURI> xslUri;
     rv = NS_NewURI(getter_AddRefs(xslUri),
                    NS_LITERAL_CSTRING("chrome://global/content/xml/XMLPrettyPrint.xsl"));
     NS_ENSURE_SUCCESS(rv, rv);
 
--- a/content/xml/document/src/nsXMLPrettyPrinter.h
+++ b/content/xml/document/src/nsXMLPrettyPrinter.h
@@ -67,18 +67,20 @@ public:
                                 nsIContent* aChild, PRInt32 aIndexInContainer);
     virtual void NodeWillBeDestroyed(const nsINode* aNode);
     
     /**
      * This will prettyprint the document if the document is loaded in a
      * displayed window.
      *
      * @param aDocument  document to prettyprint
+     * @param [out] aDidPrettyPrint if true, and error not returned, actually
+     *              went ahead with prettyprinting the document.
      */
-    nsresult PrettyPrint(nsIDocument* aDocument);
+    nsresult PrettyPrint(nsIDocument* aDocument, PRBool* aDidPrettyPrint);
 
 private:
     /**
      * Signals for unhooking by setting mUnhookPending if the node changed is
      * non-anonymous content.
      *
      * @param aContent  content that has changed
      */
--- a/layout/style/nsCSSLoader.cpp
+++ b/layout/style/nsCSSLoader.cpp
@@ -258,17 +258,18 @@ SheetLoadData::Run()
 
 // static
 nsCOMArray<nsICSSParser>* CSSLoaderImpl::gParsers = nsnull;
 
 CSSLoaderImpl::CSSLoaderImpl(void)
   : mDocument(nsnull), 
     mCaseSensitive(PR_FALSE),
     mEnabled(PR_TRUE), 
-    mCompatMode(eCompatibility_FullStandards)
+    mCompatMode(eCompatibility_FullStandards),
+    mDatasToNotifyOn(0)
 {
 }
 
 CSSLoaderImpl::~CSSLoaderImpl(void)
 {
   NS_ASSERTION((!mLoadingDatas.IsInitialized()) || mLoadingDatas.Count() == 0,
                "How did we get destroyed when there are loading data?");
   NS_ASSERTION((!mPendingDatas.IsInitialized()) || mPendingDatas.Count() == 0,
@@ -299,34 +300,26 @@ CSSLoaderImpl::Init(nsIDocument* aDocume
   // and hence the selected set makes no sense at this time.
   nsCOMPtr<nsIDOMNSDocumentStyle> domDoc(do_QueryInterface(mDocument));
   if (domDoc) {
     domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
   }
   return NS_OK;
 }
 
-PR_STATIC_CALLBACK(PLDHashOperator)
-StartAlternateLoads(nsURIAndPrincipalHashKey *aKey,
-                    SheetLoadData* &aData,
-                    void* aClosure)
-{
-  NS_STATIC_CAST(CSSLoaderImpl*,aClosure)->LoadSheet(aData, eSheetNeedsParser);
-  return PL_DHASH_REMOVE;
-}
-
 NS_IMETHODIMP
 CSSLoaderImpl::DropDocumentReference(void)
 {
   mDocument = nsnull;
   // Flush out pending datas just so we don't leak by accident.  These
   // loads should short-circuit through the mDocument check in
   // LoadSheet and just end up in SheetComplete immediately
-  if (mPendingDatas.IsInitialized())
-    mPendingDatas.Enumerate(StartAlternateLoads, this);
+  if (mPendingDatas.IsInitialized()) {
+    StartAlternateLoads();
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::SetCaseSensitive(PRBool aCaseSensitive)
 {
   mCaseSensitive = aCaseSensitive;
   return NS_OK;
@@ -335,32 +328,30 @@ CSSLoaderImpl::SetCaseSensitive(PRBool a
 NS_IMETHODIMP
 CSSLoaderImpl::SetCompatibilityMode(nsCompatibility aCompatMode)
 {
   mCompatMode = aCompatMode;
   return NS_OK;
 }
 
 PR_STATIC_CALLBACK(PLDHashOperator)
-StartNonAlternates(nsURIAndPrincipalHashKey *aKey,
-                   SheetLoadData* &aData,
-                   void* aClosure)
+CollectNonAlternates(nsURIAndPrincipalHashKey *aKey,
+                     SheetLoadData* &aData,
+                     void* aClosure)
 {
   NS_PRECONDITION(aData, "Must have a data");
-  NS_PRECONDITION(aClosure, "Must have a loader");
+  NS_PRECONDITION(aClosure, "Must have an array");
   
-  CSSLoaderImpl* loader = NS_STATIC_CAST(CSSLoaderImpl*, aClosure);
   // Note that we don't want to affect what the selected style set is,
   // so use PR_TRUE for aHasAlternateRel.
-  if (loader->IsAlternate(aData->mTitle, PR_TRUE)) {
+  if (aData->mLoader->IsAlternate(aData->mTitle, PR_TRUE)) {
     return PL_DHASH_NEXT;
   }
 
-  // Need to start the load
-  loader->LoadSheet(aData, eSheetNeedsParser);
+  NS_STATIC_CAST(CSSLoaderImpl::LoadDataArray*,aClosure)->AppendElement(aData);
   return PL_DHASH_REMOVE;
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::SetPreferredSheet(const nsAString& aTitle)
 {
 #ifdef DEBUG
   nsCOMPtr<nsIDOMNSDocumentStyle> doc(do_QueryInterface(mDocument));
@@ -373,18 +364,27 @@ CSSLoaderImpl::SetPreferredSheet(const n
     NS_ASSERTION(currentPreferred.Equals(aTitle),
                  "Unexpected argument to SetPreferredSheet");    
   }
 #endif
   
   mPreferredSheet = aTitle;
 
   // start any pending alternates that aren't alternates anymore
-  if (mPendingDatas.IsInitialized())
-    mPendingDatas.Enumerate(StartNonAlternates, this);
+  if (mPendingDatas.IsInitialized()) {
+    LoadDataArray arr(mPendingDatas.Count());
+    mPendingDatas.Enumerate(CollectNonAlternates, &arr);
+
+    mDatasToNotifyOn += arr.Length();
+    for (PRUint32 i = 0; i < arr.Length(); ++i) {
+      --mDatasToNotifyOn;
+      LoadSheet(arr[i], eSheetNeedsParser);
+    }
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::GetPreferredSheet(nsAString& aTitle)
 {
   aTitle.Assign(mPreferredSheet);
   return NS_OK;
@@ -1548,29 +1548,41 @@ CSSLoaderImpl::SheetComplete(SheetLoadDa
   // 8 is probably big enough for all our common cases.  It's not likely that
   // imports will nest more than 8 deep, and multiple sheets with the same URI
   // are rare.
   nsAutoTArray<nsRefPtr<SheetLoadData>, 8> datasToNotify;
   DoSheetComplete(aLoadData, aStatus, datasToNotify);
 
   // Now it's safe to go ahead and notify observers
   PRUint32 count = datasToNotify.Length();
+  mDatasToNotifyOn += count;
   for (PRUint32 i = 0; i < count; ++i) {
+    --mDatasToNotifyOn;
+    
     SheetLoadData* data = datasToNotify[i];
-    NS_ASSERTION(data && data->mMustNotify && data->mObserver,
-                 "How did this data get here?");
-    LOG(("  Notifying observer 0x%x for data 0x%s.  wasAlternate: %d",
-         data->mObserver.get(), data, data->mWasAlternate));
-    data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
-                                      aStatus);
+    NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
+    if (data->mObserver) {
+      LOG(("  Notifying observer 0x%x for data 0x%s.  wasAlternate: %d",
+           data->mObserver.get(), data, data->mWasAlternate));
+      data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
+                                        aStatus);
+    }
+
+    nsTObserverArray<nsICSSLoaderObserver>::ForwardIterator iter(mObservers);
+    nsCOMPtr<nsICSSLoaderObserver> obs;
+    while ((obs = iter.GetNext())) {
+      LOG(("  Notifying global observer 0x%x for data 0x%s.  wasAlternate: %d",
+           obs.get(), data, data->mWasAlternate));
+      obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
+    }
   }
 
   if (mLoadingDatas.Count() == 0 && mPendingDatas.Count() > 0) {
     LOG(("  No more loading sheets; starting alternates"));
-    mPendingDatas.Enumerate(StartAlternateLoads, this);
+    StartAlternateLoads();
   }
 }
 
 void
 CSSLoaderImpl::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
                                LoadDataArray& aDatasToNotify)
 {
   LOG(("CSSLoaderImpl::DoSheetComplete"));
@@ -1600,17 +1612,17 @@ CSSLoaderImpl::DoSheetComplete(SheetLoad
   }
   
   // Go through and deal with the whole linked list.
   SheetLoadData* data = aLoadData;
   while (data) {
 
     data->mSheet->SetModified(PR_FALSE); // it's clean
     data->mSheet->SetComplete();
-    if (data->mMustNotify && data->mObserver) {
+    if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
       // Don't notify here so we don't trigger script.  Remember the
       // info we need to notify, then do it later when it's safe.
       aDatasToNotify.AppendElement(data);
 
       // On append failure, just press on.  We'll fail to notify the observer,
       // but not much we can do about that....
     }
 
@@ -2136,94 +2148,142 @@ StopLoadingSheetCallback(nsURIAndPrincip
                          void* aClosure)
 {
   NS_PRECONDITION(aData, "Must have a data!");
   NS_PRECONDITION(aClosure, "Must have a loader");
 
   aData->mIsLoading = PR_FALSE; // we will handle the removal right here
   aData->mIsCancelled = PR_TRUE;
   
-  NS_STATIC_CAST(CSSLoaderImpl*,aClosure)->SheetComplete(aData,
-                                                         NS_BINDING_ABORTED);
+  NS_STATIC_CAST(CSSLoaderImpl::LoadDataArray*,aClosure)->AppendElement(aData);
 
   return PL_DHASH_REMOVE;
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::Stop()
 {
-  // Do the pending datas first, since finishing up all the loading
-  // datas will try to start pending loads.
-  if (mPendingDatas.IsInitialized() && mPendingDatas.Count() > 0) {
-    mPendingDatas.Enumerate(StopLoadingSheetCallback, this);
+  PRUint32 pendingCount =
+    mPendingDatas.IsInitialized() ?  mPendingDatas.Count() : 0;
+  PRUint32 loadingCount =
+    mLoadingDatas.IsInitialized() ? mLoadingDatas.Count() : 0;
+  LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
+  
+  if (pendingCount) {
+    mPendingDatas.Enumerate(StopLoadingSheetCallback, &arr);
   }
-  if (mLoadingDatas.IsInitialized() && mLoadingDatas.Count() > 0) {
-    mLoadingDatas.Enumerate(StopLoadingSheetCallback, this);
+  if (loadingCount) {
+    mLoadingDatas.Enumerate(StopLoadingSheetCallback, &arr);
   }
-  for (PRUint32 i = 0; i < mPostedEvents.Length(); ++i) {
+
+  PRUint32 i;
+  for (i = 0; i < mPostedEvents.Length(); ++i) {
     SheetLoadData* data = mPostedEvents[i];
     data->mIsCancelled = PR_TRUE;
-    // SheetComplete() calls Release(), so give this an extra ref.
-    NS_ADDREF(data);
-    data->mLoader->SheetComplete(data, NS_BINDING_ABORTED);
+    if (arr.AppendElement(data)) {
+      // SheetComplete() calls Release(), so give this an extra ref.
+      NS_ADDREF(data);
+    }
+#ifdef DEBUG
+    else {
+      NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
+                    "except we never check that preallocation succeeds.");
+    }
+#endif
   }
   mPostedEvents.Clear();
+
+  mDatasToNotifyOn += arr.Length();
+  for (i = 0; i < arr.Length(); ++i) {
+    --mDatasToNotifyOn;
+    SheetComplete(arr[i], NS_BINDING_ABORTED);
+  }
   return NS_OK;
 }
 
+struct StopLoadingSheetsByURIClosure {
+  StopLoadingSheetsByURIClosure(nsIURI* aURI,
+                                CSSLoaderImpl::LoadDataArray& aArray) :
+    uri(aURI), array(aArray)
+  {}
+  
+  nsIURI* uri;
+  CSSLoaderImpl::LoadDataArray& array;
+};
+
 PR_STATIC_CALLBACK(PLDHashOperator)
 StopLoadingSheetByURICallback(nsURIAndPrincipalHashKey* aKey,
                               SheetLoadData*& aData,
                               void* aClosure)
 {
   NS_PRECONDITION(aData, "Must have a data!");
   NS_PRECONDITION(aClosure, "Must have a loader");
 
+  StopLoadingSheetsByURIClosure* closure =
+    NS_STATIC_CAST(StopLoadingSheetsByURIClosure*, aClosure);
+
   PRBool equal;
-  if (NS_SUCCEEDED(aData->mURI->Equals(NS_STATIC_CAST(nsIURI*, aClosure),
-                                       &equal)) &&
+  if (NS_SUCCEEDED(aData->mURI->Equals(closure->uri, &equal)) &&
       equal) {
     aData->mIsLoading = PR_FALSE; // we will handle the removal right here
     aData->mIsCancelled = PR_TRUE;
 
-    aData->mLoader->SheetComplete(aData, NS_BINDING_ABORTED);
-
+    closure->array.AppendElement(aData);
     return PL_DHASH_REMOVE;
   }
 
   return PL_DHASH_NEXT;
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::StopLoadingSheet(nsIURI* aURL)
 {
   NS_ENSURE_TRUE(aURL, NS_ERROR_NULL_POINTER);
 
-  // Do the pending datas first, since finishing up all the loading
-  // datas will try to start pending loads.
-  if (mPendingDatas.IsInitialized() && mPendingDatas.Count() > 0) {
-    mPendingDatas.Enumerate(StopLoadingSheetByURICallback, aURL);
+  PRUint32 pendingCount =
+    mPendingDatas.IsInitialized() ?  mPendingDatas.Count() : 0;
+  PRUint32 loadingCount =
+    mLoadingDatas.IsInitialized() ? mLoadingDatas.Count() : 0;
+  LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
+
+  StopLoadingSheetsByURIClosure closure(aURL, arr);
+  if (pendingCount) {
+    mPendingDatas.Enumerate(StopLoadingSheetByURICallback, &closure);
   }
-  if (mLoadingDatas.IsInitialized() && mLoadingDatas.Count() > 0) {
-    mLoadingDatas.Enumerate(StopLoadingSheetByURICallback, aURL);
+  if (loadingCount) {
+    mLoadingDatas.Enumerate(StopLoadingSheetByURICallback, &closure);
   }
 
-  for (PRUint32 i = 0; i < mPostedEvents.Length(); ++i) {
-    SheetLoadData* curData = mPostedEvents[i-1];
+  PRUint32 i;
+  for (i = 0; i < mPostedEvents.Length(); ++i) {
+    SheetLoadData* curData = mPostedEvents[i];
     PRBool equal;
     if (curData->mURI && NS_SUCCEEDED(curData->mURI->Equals(aURL, &equal)) &&
         equal) {
       curData->mIsCancelled = PR_TRUE;
-      // SheetComplete() calls Release(), so give it an extra ref.
-      NS_ADDREF(curData);
-      SheetComplete(curData, NS_BINDING_ABORTED);
+      if (arr.AppendElement(curData)) {
+        // SheetComplete() calls Release(), so give this an extra ref.
+        NS_ADDREF(curData);
+      }
+#ifdef DEBUG
+      else {
+        NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
+                      "except we never check that preallocation succeeds.");
+      }
+#endif
     }
   }
   mPostedEvents.Clear();
-          
+
+  mDatasToNotifyOn += arr.Length();
+  for (i = 0; i < arr.Length(); ++i) {
+    --mDatasToNotifyOn;
+    SheetComplete(arr[i], NS_BINDING_ABORTED);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::GetEnabled(PRBool *aEnabled)
 {
   NS_ENSURE_ARG_POINTER(aEnabled);
   *aEnabled = mEnabled;
@@ -2231,8 +2291,61 @@ CSSLoaderImpl::GetEnabled(PRBool *aEnabl
 }
 
 NS_IMETHODIMP
 CSSLoaderImpl::SetEnabled(PRBool aEnabled)
 {
   mEnabled = aEnabled;
   return NS_OK;
 }
+
+NS_IMETHODIMP_(PRBool)
+CSSLoaderImpl::HasPendingLoads()
+{
+  return
+    (mLoadingDatas.IsInitialized() && mLoadingDatas.Count() != 0) ||
+    (mPendingDatas.IsInitialized() && mPendingDatas.Count() != 0) ||
+    mPostedEvents.Length() != 0 ||
+    mDatasToNotifyOn != 0;
+}
+
+NS_IMETHODIMP
+CSSLoaderImpl::AddObserver(nsICSSLoaderObserver* aObserver)
+{
+  NS_PRECONDITION(aObserver, "Must have observer");
+  if (mObservers.AppendObserver(aObserver)) {
+    NS_ADDREF(aObserver);
+    return NS_OK;
+  }
+
+  return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP_(void)
+CSSLoaderImpl::RemoveObserver(nsICSSLoaderObserver* aObserver)
+{
+  if (mObservers.RemoveObserver(aObserver)) {
+    NS_RELEASE(aObserver);
+  }
+}
+
+PR_STATIC_CALLBACK(PLDHashOperator)
+CollectLoadDatas(nsURIAndPrincipalHashKey *aKey,
+                 SheetLoadData* &aData,
+                 void* aClosure)
+{
+  NS_STATIC_CAST(CSSLoaderImpl::LoadDataArray*,aClosure)->AppendElement(aData);
+  return PL_DHASH_REMOVE;
+}
+
+void
+CSSLoaderImpl::StartAlternateLoads()
+{
+  NS_PRECONDITION(mPendingDatas.IsInitialized(), "Don't call me!");
+  LoadDataArray arr(mPendingDatas.Count());
+  mPendingDatas.Enumerate(CollectLoadDatas, &arr);
+
+  mDatasToNotifyOn += arr.Length();
+  for (PRUint32 i = 0; i < arr.Length(); ++i) {
+    --mDatasToNotifyOn;
+    LoadSheet(arr[i], eSheetNeedsParser);
+  }
+}
--- a/layout/style/nsCSSLoader.h
+++ b/layout/style/nsCSSLoader.h
@@ -67,16 +67,17 @@ class nsMediaList;
 #include "nsCOMArray.h"
 #include "nsString.h"
 #include "nsURIHashKey.h"
 #include "nsInterfaceHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "nsIPrincipal.h"
+#include "nsTObserverArray.h"
 
 /**
  * OVERALL ARCHITECTURE
  *
  * The CSS Loader gets requests to load various sorts of style sheets:
  * inline style from <style> elements, linked style, @import-ed child
  * sheets, non-document sheets.  The loader handles the following tasks:
  *
@@ -355,16 +356,20 @@ public:
    * When disabled, processing of new styles is disabled and an attempt
    * to do so will fail with a return code of
    * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable
    * currently loading styles or already processed styles.
    */
   NS_IMETHOD GetEnabled(PRBool *aEnabled);
   NS_IMETHOD SetEnabled(PRBool aEnabled);
 
+  NS_IMETHOD_(PRBool) HasPendingLoads();
+  NS_IMETHOD AddObserver(nsICSSLoaderObserver* aObserver);
+  NS_IMETHOD_(void) RemoveObserver(nsICSSLoaderObserver* aObserver);  
+
   // local helper methods (some are public for access from statics)
 
   // IsAlternate can change our currently selected style set if none
   // is selected and aHasAlternateRel is false.
   PRBool IsAlternate(const nsAString& aTitle, PRBool aHasAlternateRel);
 
 private:
   nsresult CheckLoadAllowed(nsIURI* aSourceURI,
@@ -412,46 +417,50 @@ private:
   // canceled at some point (in which case it will be sent with
   // NS_BINDING_ABORTED).  aWasAlternate indicates the state when the load was
   // initiated, not the state at some later time.  aURI should be the URI the
   // sheet was loaded from (may be null for inline sheets).
   nsresult PostLoadEvent(nsIURI* aURI,
                          nsICSSStyleSheet* aSheet,
                          nsICSSLoaderObserver* aObserver,
                          PRBool aWasAlternate);
+
+  // Start the loads of all the sheets in mPendingDatas
+  void StartAlternateLoads();
+  
 public:
   // Handle an event posted by PostLoadEvent
   void HandleLoadEvent(SheetLoadData* aEvent);
 
+protected:
   // Note: LoadSheet is responsible for releasing aLoadData and setting the
   // sheet to complete on failure.
   nsresult LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState);
 
-protected:
   friend class SheetLoadData;
 
   // Protected functions and members are ones that SheetLoadData needs
   // access to.
 
   // Parse the stylesheet in aLoadData.  The sheet data comes from aStream.
   // Set aCompleted to true if the parse finished, false otherwise (e.g. if the
   // sheet had an @import).  If aCompleted is true when this returns, then
   // ParseSheet also called SheetComplete on aLoadData
   nsresult ParseSheet(nsIUnicharInputStream* aStream,
                       SheetLoadData* aLoadData,
                       PRBool& aCompleted);
 
-public:
   // The load of the sheet in aLoadData is done, one way or another.  Do final
   // cleanup, including releasing aLoadData.
   void SheetComplete(SheetLoadData* aLoadData, nsresult aStatus);
 
-private:
+public:
   typedef nsTArray<nsRefPtr<SheetLoadData> > LoadDataArray;
   
+private:
   // The guts of SheetComplete.  This may be called recursively on parent datas
   // or datas that had glommed on to a single load.  The array is there so load
   // datas whose observers need to be notified can be added to it.
   void DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
                        LoadDataArray& aDatasToNotify);
 
   static nsCOMArray<nsICSSParser>* gParsers;  // array of idle CSS parsers
 
@@ -476,11 +485,20 @@ private:
   
   // We're not likely to have many levels of @import...  But likely to have
   // some.  Allocate some storage, what the hell.
   nsAutoVoidArray   mParsingDatas;
 
   // The array of posted stylesheet loaded events (SheetLoadDatas) we have.
   // Note that these are rare.
   LoadDataArray mPostedEvents;
+
+  // Number of datas still waiting to be notified on if we're notifying on a
+  // whole bunch at once (e.g. in one of the stop methods).  This is used to
+  // make sure that HasPendingLoads() won't return false until we're notifying
+  // on the last data we're working with.
+  PRUint32 mDatasToNotifyOn;
+
+  // Our array of "global" observers
+  nsTObserverArray<nsICSSLoaderObserver> mObservers;
 };
 
 #endif // nsCSSLoader_h__
--- a/layout/style/nsICSSLoader.h
+++ b/layout/style/nsICSSLoader.h
@@ -232,16 +232,44 @@ public:
    * Whether the loader is enabled or not.
    * When disabled, processing of new styles is disabled and an attempt
    * to do so will fail with a return code of
    * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable
    * currently loading styles or already processed styles.
    */
   NS_IMETHOD GetEnabled(PRBool *aEnabled) = 0;
   NS_IMETHOD SetEnabled(PRBool aEnabled) = 0;
+
+  /**
+   * Return true if this nsICSSLoader has pending loads (ones that would send
+   * notifications to an nsICSSLoaderObserver attached to this nsICSSLoader).
+   * If called from inside nsICSSLoaderObserver::StyleSheetLoaded, this will
+   * return PR_FALSE if and only if that is the last StyleSheetLoaded
+   * notification the CSSLoader knows it's going to send.  In other words, if
+   * two sheets load at once (via load coalescing, e.g.), HasPendingLoads()
+   * will return PR_TRUE during notification for the first one, and PR_FALSE
+   * during notification for the second one.
+   */
+  NS_IMETHOD_(PRBool) HasPendingLoads() = 0;
+
+  /**
+   * Add an observer to this nsICSSLoader.  The observer will be notified for
+   * all loads that would have notified their own observers (even if those
+   * loads don't have observers attached to them).  Load-specific observers
+   * will be notified before generic observers.  The CSSLoader holds a
+   * reference to the observer.
+   *
+   * aObserver must not be null.
+   */
+  NS_IMETHOD AddObserver(nsICSSLoaderObserver* aObserver) = 0;
+
+  /**
+   * Remove an observer added via AddObserver.
+   */
+  NS_IMETHOD_(void) RemoveObserver(nsICSSLoaderObserver* aObserver) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSLoader, NS_ICSS_LOADER_IID)
 
 nsresult 
 NS_NewCSSLoader(nsIDocument* aDocument, nsICSSLoader** aLoader);
 
 nsresult