Bug 185236 part 5. Fire load and error events on stylesheet linking elements. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 26 Sep 2011 17:27:13 -0400
changeset 77617 c8e237c03adb5feb6a144c8f3bb55b13f773401e
parent 77616 231da6eb82249cfc7fdccb858adcede1adc47ea7
child 77618 97983a84f0c3b972986e7d7c992f02b804f9dae6
push id21221
push userbzbarsky@mozilla.com
push dateTue, 27 Sep 2011 07:08:27 +0000
treeherdermozilla-central@d305835a6726 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs185236
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 185236 part 5. Fire load and error events on stylesheet linking elements. r=peterv
content/base/test/test_bug364413.xhtml
layout/style/Loader.cpp
layout/style/test/Makefile.in
layout/style/test/test_load_events_on_stylesheets.html
--- a/content/base/test/test_bug364413.xhtml
+++ b/content/base/test/test_bug364413.xhtml
@@ -7,17 +7,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <head>
   <title>Test for Bug 364413</title>
   <!-- XHTML needs explicit script elements -->
   <script type="text/javascript" src="/MochiKit/Base.js"></script>
   <script type="text/javascript" src="/MochiKit/Iter.js"></script>
   <script type="text/javascript" src="/MochiKit/DOM.js"></script>
   <script type="text/javascript" src="/MochiKit/Style.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
-  <link rel="stylesheet" type="text/css" href="SimpleTest/test.css" />
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364413">Mozilla Bug 364413</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <div id="test1" foobar:foo="foo"/>
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -76,16 +76,17 @@
 #include "nsCSSStyleSheet.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsCSSParser.h"
 #include "mozilla/css/ImportRule.h"
 #include "nsThreadUtils.h"
 #include "nsGkAtoms.h"
 #include "nsDocShellCID.h"
+#include "nsIThreadInternal.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPrototypeCache.h"
 #endif
 
 #include "nsIMediaList.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsIDOMCSSStyleSheet.h"
@@ -127,17 +128,18 @@
 namespace mozilla {
 namespace css {
 
 /*********************************************
  * Data needed to properly load a stylesheet *
  *********************************************/
 
 class SheetLoadData : public nsIRunnable,
-                      public nsIUnicharStreamLoaderObserver
+                      public nsIUnicharStreamLoaderObserver,
+                      public nsIThreadObserver
 {
 public:
   virtual ~SheetLoadData(void);
   // Data for loading a sheet linked from a document
   SheetLoadData(Loader* aLoader,
                 const nsSubstring& aTitle,
                 nsIURI* aURI,
                 nsCSSStyleSheet* aSheet,
@@ -162,18 +164,21 @@ public:
                 PRBool aAllowUnsafeRules,
                 PRBool aUseSystemPrincipal,
                 const nsCString& aCharset,
                 nsICSSLoaderObserver* aObserver,
                 nsIPrincipal* aLoaderPrincipal);
 
   already_AddRefed<nsIURI> GetReferrerURI();
 
+  void ScheduleLoadEventIfNeeded(nsresult aStatus);
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
+  NS_DECL_NSITHREADOBSERVER
   NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
 
   // Hold a ref to the CSSLoader so we can call back to it to let it
   // know the load finished
   Loader*                    mLoader; // strong ref
 
   // Title needed to pull datas out of the pending datas table when
   // the preferred title is changed
@@ -254,16 +259,25 @@ public:
   nsCOMPtr<nsICSSLoaderObserver>        mObserver;
 
   // The principal that identifies who started loading us.
   nsCOMPtr<nsIPrincipal>                mLoaderPrincipal;
 
   // The charset to use if the transport and sheet don't indicate one.
   // May be empty.  Must be empty if mOwningElement is non-null.
   nsCString                             mCharsetHint;
+
+  // The status our load ended up with; this determines whether we
+  // should fire error events or load events.  This gets initialized
+  // by ScheduleLoadEventIfNeeded, and is only used after that has
+  // been called.
+  nsresult                              mStatus;
+
+private:
+  void FireLoadEvent(nsIThreadInternal* aThread);
 };
 
 #ifdef MOZ_LOGGING
 // #define FORCE_PR_LOG /* Allow logging in the release build */
 #endif /* MOZ_LOGGING */
 #include "prlog.h"
 
 #ifdef PR_LOGGING
@@ -305,17 +319,18 @@ static const char* const gStateStrings[]
   "eSheetLoading",
   "eSheetComplete"
 };
 #endif
 
 /********************************
  * SheetLoadData implementation *
  ********************************/
-NS_IMPL_ISUPPORTS2(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable)
+NS_IMPL_ISUPPORTS3(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable,
+                   nsIThreadObserver)
 
 SheetLoadData::SheetLoadData(Loader* aLoader,
                              const nsSubstring& aTitle,
                              nsIURI* aURI,
                              nsCSSStyleSheet* aSheet,
                              nsIStyleSheetLinkingElement* aOwningElement,
                              PRBool aIsAlternate,
                              nsICSSLoaderObserver* aObserver,
@@ -432,16 +447,88 @@ SheetLoadData::~SheetLoadData()
 
 NS_IMETHODIMP
 SheetLoadData::Run()
 {
   mLoader->HandleLoadEvent(this);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
+                                  PRBool aMayWait,
+                                  PRUint32 aRecursionDepth)
+{
+  // We want to fire our load even before or after event processing,
+  // whichever comes first.
+  FireLoadEvent(aThread);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
+                                     PRUint32 aRecursionDepth)
+{
+  // We want to fire our load even before or after event processing,
+  // whichever comes first.
+  FireLoadEvent(aThread);
+  return NS_OK;
+}
+
+void
+SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
+{
+  
+  // First remove ourselves as a thread observer.  But we need to keep
+  // ourselves alive while doing that!
+  nsRefPtr<SheetLoadData> kungFuDeathGrip(this);
+  aThread->RemoveObserver(this);
+
+  // Now fire the event
+  nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
+  NS_ASSERTION(node, "How did that happen???");
+
+  nsContentUtils::DispatchTrustedEvent(node->GetOwnerDoc(),
+                                       node,
+                                       NS_SUCCEEDED(mStatus) ?
+                                         NS_LITERAL_STRING("load") :
+                                         NS_LITERAL_STRING("error"),
+                                       PR_FALSE, PR_FALSE);
+
+  // And unblock onload
+  if (mLoader->mDocument) {
+    mLoader->mDocument->UnblockOnload(PR_TRUE);
+  }  
+}
+
+void
+SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
+{
+  if (!mOwningElement) {
+    return;
+  }
+
+  mStatus = aStatus;
+
+  nsCOMPtr<nsIThread> thread = do_GetMainThread();
+  nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
+  if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
+    // Make sure to block onload here
+    if (mLoader->mDocument) {
+      mLoader->mDocument->BlockOnload();
+    }
+  }
+}
+
 /*************************
  * Loader Implementation *
  *************************/
 
 Loader::Loader(void)
   : mDocument(nsnull)
   , mDatasToNotifyOn(0)
   , mCompatMode(eCompatibility_FullStandards)
@@ -1656,16 +1743,17 @@ Loader::DoSheetComplete(SheetLoadData* a
 
   // Go through and deal with the whole linked list.
   SheetLoadData* data = aLoadData;
   while (data) {
     NS_ABORT_IF_FALSE(!data->mSheet->IsModified(),
                       "should not get marked modified during parsing");
     if (!data->mSheetAlreadyComplete) {
       data->mSheet->SetComplete();
+      data->ScheduleLoadEventIfNeeded(aStatus);
     }
     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....
@@ -1684,17 +1772,20 @@ Loader::DoSheetComplete(SheetLoadData* a
         --(data->mParentData->mPendingChildren) == 0 &&
         !mParsingDatas.Contains(data->mParentData)) {
       DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
     }
 
     data = data->mNext;
   }
 
-  // Now that it's marked complete, put the sheet in our cache
+  // Now that it's marked complete, put the sheet in our cache.
+  // If we ever start doing this for failure aStatus, we'll need to
+  // adjust the PostLoadEvent code that thinks anything already
+  // complete must have loaded succesfully.
   if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
 #ifdef MOZ_XUL
     if (IsChromeURI(aLoadData->mURI)) {
       nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
       if (cache && cache->IsEnabled()) {
         if (!cache->GetStyleSheet(aLoadData->mURI)) {
           LOG(("  Putting sheet in XUL prototype cache"));
           cache->PutStyleSheet(aLoadData->mSheet);
@@ -2156,16 +2247,22 @@ Loader::PostLoadEvent(nsIURI* aURI,
     // We'll unblock onload when we handle the event.
     if (mDocument) {
       mDocument->BlockOnload();
     }
 
     // We want to notify the observer for this data.
     evt->mMustNotify = PR_TRUE;
     evt->mSheetAlreadyComplete = PR_TRUE;
+
+    // If we get to this code, aSheet loaded correctly at some point, so
+    // we can just use NS_OK for the status.  Note that we do this here
+    // and not from inside our SheetComplete so that we don't end up
+    // running the load event async.
+    evt->ScheduleLoadEventIfNeeded(NS_OK);
   }
 
   return rv;
 }
 
 void
 Loader::HandleLoadEvent(SheetLoadData* aEvent)
 {
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -221,16 +221,17 @@ GARBAGE += css_properties.js
 		ccd-quirks.html \
 		ccd-standards.html \
 		ccd.sjs \
 		visited-pref-iframe.html \
 		visited-lying-inner.html \
 		visited_image_loading.sjs \
 		visited_image_loading_frame.html \
 		visited_image_loading_frame_empty.html \
+		test_load_events_on_stylesheets.html \
 		$(NULL)
 
 _VISITED_REFTEST_FILES = \
 		$(shell find $(topsrcdir)/layout/reftests/css-visited/ -name '*.html' -o -name '*.xhtml') \
 		$(topsrcdir)/layout/reftests/svg/pseudo-classes-02.svg \
 		$(topsrcdir)/layout/reftests/svg/pseudo-classes-02-ref.svg \
 		$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_load_events_on_stylesheets.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=185236
+-->
+<head>
+  <title>Test for Bug 185236</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script>
+    var pendingEventCounter = 0;
+    var messagePosted = false;
+    SimpleTest.waitForExplicitFinish();
+    addLoadEvent(function() {
+      is(messagePosted, true, "Should have gotten onmessage event");
+      is(pendingEventCounter, 0,
+         "How did onload for the page fire before onload for all the stylesheets?");
+      SimpleTest.finish();
+    });
+    // Count the link we're about to parse
+    pendingEventCounter = 1;
+  </script>
+  <link rel="stylesheet" href="data:text/css,*{}"
+        onload="--pendingEventCounter;
+                ok(true, 'Load event firing on basic stylesheet')"
+        onerror="--pendingEventCounter;
+                 ok(false, 'Error event firing on basic stylesheet')">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=185236">Mozilla Bug 185236</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 185236 **/
+// Verify that there are no in-flight sheet loads right now; we should have
+// waited for them when we hit the script tag
+is(pendingEventCounter, 0, "There should be no pending events");
+
+// Test sheet that will already be complete when we write it out
+++pendingEventCounter;
+
+// Make sure that a postMessage we do right now fires after the onload handler
+// for the stylesheet.  If we ever change the timing of sheet onload, we will
+// need to change that.
+window.onmessage = function() {
+  messagePosted = true;
+  // There are 4 pending events: two from the two direct example.com loads,
+  // and 2 from the two data:text/css loads that import things
+  is(pendingEventCounter, 4, "Load event for sheet should have fired");
+}
+window.postMessage("", "*");
+
+document.write('<link rel="stylesheet" href="data:text/css,*{}"\
+                onload="--pendingEventCounter;\
+                        ok(true, \'Load event firing on basic stylesheet\')"\
+                onerror="--pendingEventCounter;\
+                        ok(false, \'Error event firing on basic stylesheet\')">');
+
+// Make sure we have that second stylesheet
+is(document.styleSheets.length, 3, "Should have three stylesheets");
+
+// Make sure that the second stylesheet is all loaded
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+is(document.styleSheets[2].cssRules.length, 1, "Should have one rule");
+
+// Make sure the load event for that stylesheet has not fired yet
+is(pendingEventCounter, 1, "There should be one pending event");
+
+++pendingEventCounter;
+document.write('<style\
+                onload="--pendingEventCounter;\
+                        ok(true, \'Load event firing on inline stylesheet\')"\
+                onerror="--pendingEventCounter;\
+                        ok(false, \'Error event firing on inline stylesheet\')"></style>');
+
+// Make sure the load event for that second stylesheet has not fired yet
+is(pendingEventCounter, 2, "There should be two pending events");
+
+++pendingEventCounter;
+document.write('<link rel="stylesheet" href="http://www.example.com"\
+                onload="--pendingEventCounter;\
+                        ok(false, \'Load event firing on broken stylesheet\')"\
+                onerror="--pendingEventCounter;\
+                        ok(true, \'Error event firing on broken stylesheet\')">');
+
+++pendingEventCounter;
+var link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "http://www.example.com";
+link.onload = function() { --pendingEventCounter;
+  ok(false, 'Load event firing on broken stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+  ok(true, 'Error event firing on broken stylesheet');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,*{}";
+link.onload = function() { --pendingEventCounter;
+  ok(true, 'Load event firing on external stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+  ok(false, 'Error event firing on external stylesheet');
+}
+document.body.appendChild(link);
+
+// Make sure we have that last stylesheet
+is(document.styleSheets.length, 7, "Should have seven stylesheets here");
+
+// Make sure that the sixth stylesheet is all loaded
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+is(document.styleSheets[6].cssRules.length, 1, "Should have one rule");
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,@import url('data:text/css,*{}')";
+link.onload = function() { --pendingEventCounter;
+  ok(true, 'Load event firing on external stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+  ok(false, 'Error event firing on external stylesheet');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,@import url('http://www.example.com')";
+link.onload = function() { --pendingEventCounter;
+  ok(false, 'Load event firing on broken stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+  ok(true, 'Error event firing on broken stylesheet');
+}
+document.body.appendChild(link);
+
+// Make sure the load events for all those stylesheets have not fired yet
+is(pendingEventCounter, 7, "There should be one pending event");
+
+</script>
+</pre>
+</body>
+</html>