content/html/content/src/nsHTMLDNSPrefetch.cpp
changeset 22391 a0c0ed9f461fc39635b2e99117fa3c1b85505ecd
parent 21285 cd9a392337b799124fce00d8ed9f5269c128dcb3
child 22686 ace5e9bbb02cc8345a3033067828aded486f7dc8
--- a/content/html/content/src/nsHTMLDNSPrefetch.cpp
+++ b/content/html/content/src/nsHTMLDNSPrefetch.cpp
@@ -38,36 +38,48 @@
 
 #include "nsHTMLDNSPrefetch.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 
 #include "nsNetUtil.h"
 
 #include "nsIDNSListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWebProgress.h"
+#include "nsCURILoader.h"
 #include "nsIDNSRecord.h"
 #include "nsIDNSService.h"
 #include "nsICancelable.h"
 #include "nsContentUtils.h"
 #include "nsGkAtoms.h"
 #include "nsIDocument.h"
+#include "nsThreadUtils.h"
 
 static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
 static PRBool sDisablePrefetchHTTPSPref;
 static PRBool sInitialized = PR_FALSE;
 static nsIDNSService *sDNSService = nsnull;
+static nsHTMLDNSPrefetch::nsDeferrals *sPrefetches = nsnull;
 
 nsresult
 nsHTMLDNSPrefetch::Initialize()
 {
   if (sInitialized) {
     NS_WARNING("Initialize() called twice");
     return NS_OK;
   }
   
+  sPrefetches = new nsHTMLDNSPrefetch::nsDeferrals();
+  if (!sPrefetches)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  NS_ADDREF(sPrefetches);
+  sPrefetches->Activate();
+
   nsContentUtils::AddBoolPrefVarCache("network.dns.disablePrefetchFromHTTPS", 
                                       &sDisablePrefetchHTTPSPref);
   
   // Default is false, so we need an explicit call to prime the cache.
   sDisablePrefetchHTTPSPref = 
     nsContentUtils::GetBoolPref("network.dns.disablePrefetchFromHTTPS", PR_TRUE);
   
   NS_IF_RELEASE(sDNSService);
@@ -83,52 +95,35 @@ nsresult
 nsHTMLDNSPrefetch::Shutdown()
 {
   if (!sInitialized) {
     NS_WARNING("Not Initialized");
     return NS_OK;
   }
   sInitialized = PR_FALSE;
   NS_IF_RELEASE(sDNSService);
-  return NS_OK;
-}
-
-nsHTMLDNSPrefetch::nsHTMLDNSPrefetch(nsAString &hostname, nsIDocument *aDocument)
-{
-  NS_ASSERTION(aDocument, "Document Required");
-  NS_ASSERTION(sInitialized, "nsHTMLDNSPrefetch is not initialized");
+  NS_IF_RELEASE(sPrefetches);
   
-  mAllowed = IsAllowed(aDocument);
-  CopyUTF16toUTF8(hostname, mHostname);
-}
-
-nsHTMLDNSPrefetch::nsHTMLDNSPrefetch(nsIURI *aURI, nsIDocument *aDocument)
-{
-  NS_ASSERTION(aDocument, "Document Required");
-  NS_ASSERTION(aURI, "URI Required");
-  NS_ASSERTION(sInitialized, "nsHTMLDNSPrefetch is not initialized");
-
-  mAllowed = IsAllowed(aDocument);
-  aURI->GetAsciiHost(mHostname);
+  return NS_OK;
 }
 
 PRBool
 nsHTMLDNSPrefetch::IsSecureBaseContext (nsIDocument *aDocument)
 {
   nsIURI *docURI = aDocument->GetDocumentURI();
   nsCAutoString scheme;
   docURI->GetScheme(scheme);
   return scheme.EqualsLiteral("https");
 }
 
 PRBool
 nsHTMLDNSPrefetch::IsAllowed (nsIDocument *aDocument)
 {
   if (IsSecureBaseContext(aDocument) && sDisablePrefetchHTTPSPref)
-      return PR_FALSE;
+    return PR_FALSE;
     
   // Check whether the x-dns-prefetch-control HTTP response header is set to override 
   // the default. This may also be set by meta tag. Chromium treats any value other
   // than 'on' (case insensitive) as 'off'.
 
   nsAutoString control;
   aDocument->GetHeaderData(nsGkAtoms::headerDNSPrefetchControl, control);
   
@@ -137,53 +132,200 @@ nsHTMLDNSPrefetch::IsAllowed (nsIDocumen
 
   // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
   if (!aDocument->GetWindow())
     return PR_FALSE;
 
   return PR_TRUE;
 }
 
-nsresult 
-nsHTMLDNSPrefetch::Prefetch(PRUint16 flags)
+nsresult
+nsHTMLDNSPrefetch::Prefetch(nsIURI *aURI, PRUint16 flags)
 {
-  if (mHostname.IsEmpty())
-    return NS_ERROR_NOT_AVAILABLE;
-  
-  if (!mAllowed)
+  if (!(sInitialized && sPrefetches && sDNSService))
     return NS_ERROR_NOT_AVAILABLE;
 
-  if (!sDNSService)
+  return sPrefetches->Add(flags, aURI);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchLow(nsIURI *aURI)
+{
+  return Prefetch(aURI, nsIDNSService::RESOLVE_PRIORITY_LOW);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchMedium(nsIURI *aURI)
+{
+  return Prefetch(aURI, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchHigh(nsIURI *aURI)
+{
+  return Prefetch(aURI, 0);
+}
+
+nsresult
+nsHTMLDNSPrefetch::Prefetch(nsAString &hostname, PRUint16 flags)
+{
+  if (!(sInitialized && sDNSService && sPrefetches))
     return NS_ERROR_NOT_AVAILABLE;
-  
+
   nsCOMPtr<nsICancelable> tmpOutstanding;
-  
-  return sDNSService->AsyncResolve(mHostname, flags, this, nsnull,
-                                   getter_AddRefs(tmpOutstanding));
+  return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname), flags,
+                                   sPrefetches, nsnull, getter_AddRefs(tmpOutstanding));
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchLow(nsAString &hostname)
+{
+  return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchMedium(nsAString &hostname)
+{
+  return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchLow()
+nsHTMLDNSPrefetch::PrefetchHigh(nsAString &hostname)
 {
-  return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW);
+  return Prefetch(hostname, 0);
 }
 
-nsresult
-nsHTMLDNSPrefetch::PrefetchMedium()
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+nsHTMLDNSPrefetch::nsDeferrals::nsDeferrals()
+  : mHead(0),
+    mTail(0),
+    mActiveLoaderCount(0)
 {
-  return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
 }
 
+NS_IMPL_THREADSAFE_ISUPPORTS3(nsHTMLDNSPrefetch::nsDeferrals,
+                              nsIDNSListener,
+                              nsIWebProgressListener,
+                              nsISupportsWeakReference)
+
 nsresult
-nsHTMLDNSPrefetch::PrefetchHigh()
+nsHTMLDNSPrefetch::nsDeferrals::Add(PRUint16 flags, nsIURI *aURI)
 {
-  return Prefetch(0);
+  // The FIFO has no lock, so it can only be accessed on main thread
+  NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Add must be on main thread");
+
+  if (((mHead + 1) & sMaxDeferredMask) == mTail)
+    return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+    
+  mEntries[mHead].mFlags = flags;
+  mEntries[mHead].mURI = aURI;
+  mHead = (mHead + 1) & sMaxDeferredMask;
+
+  return NS_OK;
 }
 
-NS_IMPL_THREADSAFE_ISUPPORTS1(nsHTMLDNSPrefetch, nsIDNSListener)
+void
+nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue()
+{
+  nsCString hostName;
+  if (!sDNSService) return;
+
+  while (mHead != mTail) {
+    mEntries[mTail].mURI->GetAsciiHost(hostName);
+    if (!hostName.IsEmpty()) {
+        
+      nsCOMPtr<nsICancelable> tmpOutstanding;
+
+      sDNSService->AsyncResolve(hostName, 
+                                mEntries[mTail].mFlags,
+                                this, nsnull, getter_AddRefs(tmpOutstanding));
+    }
+    mEntries[mTail].mURI = nsnull;
+    mTail = (mTail + 1) & sMaxDeferredMask;
+  }
+}
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Activate()
+{
+  // Register as an observer for the document loader  
+  nsCOMPtr<nsIWebProgress> progress = 
+    do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+  if (progress)
+    progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+}
+
+//////////// nsIDNSListener method
 
 NS_IMETHODIMP
-nsHTMLDNSPrefetch::OnLookupComplete(nsICancelable *request,
-                                    nsIDNSRecord  *rec,
-                                    nsresult       status)
+nsHTMLDNSPrefetch::nsDeferrals::OnLookupComplete(nsICancelable *request,
+                                                 nsIDNSRecord  *rec,
+                                                 nsresult       status)
 {
   return NS_OK;
 }
+
+//////////// nsIWebProgressListener methods
+
+NS_IMETHODIMP 
+nsHTMLDNSPrefetch::nsDeferrals::OnStateChange(nsIWebProgress* aWebProgress, 
+                                              nsIRequest *aRequest, 
+                                              PRUint32 progressStateFlags, 
+                                              nsresult aStatus)
+{
+  // The FIFO has no lock, so it can only be accessed on main thread
+  NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::OnStateChange must be on main thread");
+  
+  if (progressStateFlags & STATE_IS_DOCUMENT) {
+    if (progressStateFlags & STATE_STOP) {
+
+      // Initialization may have missed a STATE_START notification, so do
+      // not go negative
+      if (mActiveLoaderCount)
+        mActiveLoaderCount--;
+
+      if (!mActiveLoaderCount)
+        SubmitQueue();
+    }
+    else if (progressStateFlags & STATE_START)
+      mActiveLoaderCount++;
+  }
+            
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnProgressChange(nsIWebProgress *aProgress,
+                                                 nsIRequest *aRequest, 
+                                                 PRInt32 curSelfProgress, 
+                                                 PRInt32 maxSelfProgress, 
+                                                 PRInt32 curTotalProgress, 
+                                                 PRInt32 maxTotalProgress)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnLocationChange(nsIWebProgress* aWebProgress,
+                                                 nsIRequest* aRequest,
+                                                 nsIURI *location)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsHTMLDNSPrefetch::nsDeferrals::OnStatusChange(nsIWebProgress* aWebProgress,
+                                               nsIRequest* aRequest,
+                                               nsresult aStatus,
+                                               const PRUnichar* aMessage)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(nsIWebProgress *aWebProgress, 
+                                                 nsIRequest *aRequest, 
+                                                 PRUint32 state)
+{
+  return NS_OK;
+}