Bug 622232: Cancel DNS prefetches for HTML Anchor Elems after a tab is closed; r=mcmanus sr=bz
authorSteve Workman <sworkman@mozilla.com>
Fri, 20 Jan 2012 15:14:46 -0800
changeset 86511 49c9b4e6325bcf30209daf1d5956017223fce744
parent 86510 6ae43e3c400be7d33ba7b60c7e94cc1ff12c31d0
child 86512 0286995894b721ce49b392e0bc4d32eef9b46e1b
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, bz
bugs622232
milestone12.0a1
Bug 622232: Cancel DNS prefetches for HTML Anchor Elems after a tab is closed; r=mcmanus sr=bz
content/base/src/Link.h
content/html/content/src/nsHTMLAnchorElement.cpp
content/html/content/src/nsHTMLDNSPrefetch.cpp
content/html/content/src/nsHTMLDNSPrefetch.h
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsHostResolver.cpp
netwerk/dns/nsHostResolver.h
netwerk/dns/nsIDNSService.idl
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
--- a/content/base/src/Link.h
+++ b/content/base/src/Link.h
@@ -46,18 +46,18 @@
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/IHistory.h"
 
 namespace mozilla {
 namespace dom {
 
 #define MOZILLA_DOM_LINK_IMPLEMENTATION_IID \
-  { 0xa687a99c, 0x3893, 0x45c0, \
-    {0x8e, 0xab, 0xb8, 0xf7, 0xd7, 0x9e, 0x9e, 0x7b } }
+  { 0x7EA57721, 0xE373, 0x458E, \
+    {0x8F, 0x44, 0xF8, 0x96, 0x56, 0xB4, 0x14, 0xF5 } }
 
 class Link : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_LINK_IMPLEMENTATION_IID)
 
   static const nsLinkState defaultState = eLinkState_Unknown;
 
@@ -108,16 +108,34 @@ public:
    *        true if ResetLinkState should notify the owning document about style
    *        changes or false if it should not.
    */
   void ResetLinkState(bool aNotify);
   
   // This method nevers returns a null element.
   Element* GetElement() const { return mElement; }
 
+  /**
+   * DNS prefetch has been deferred until later, e.g. page load complete.
+   */
+  virtual void OnDNSPrefetchDeferred() { /*do nothing*/ }
+  
+  /**
+   * DNS prefetch has been submitted to Host Resolver.
+   */
+  virtual void OnDNSPrefetchRequested() { /*do nothing*/ }
+
+  /**
+   * Checks if DNS Prefetching is ok
+   * 
+   * @returns boolean
+   *          Defaults to true; should be overridden for specialised cases
+   */
+  virtual bool HasDeferredDNSPrefetchRequest() { return true; }
+
 protected:
   virtual ~Link();
 
   bool HasCachedURI() const { return !!mCachedURI; }
 
 private:
   /**
    * Unregisters from History so this node no longer gets notifications about
--- a/content/html/content/src/nsHTMLAnchorElement.cpp
+++ b/content/html/content/src/nsHTMLAnchorElement.cpp
@@ -135,24 +135,37 @@ public:
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   virtual nsEventStates IntrinsicState() const;
 
   virtual nsXPCClassInfo* GetClassInfo();
+  
+  virtual void OnDNSPrefetchDeferred();
+  virtual void OnDNSPrefetchRequested();
+  virtual bool HasDeferredDNSPrefetchRequest();
 };
 
+// Indicates that a DNS Prefetch has been requested from this Anchor elem
+#define HTML_ANCHOR_DNS_PREFETCH_REQUESTED \
+  (1 << ELEMENT_TYPE_SPECIFIC_BITS_OFFSET)
+// Indicates that a DNS Prefetch was added to the deferral queue
+#define HTML_ANCHOR_DNS_PREFETCH_DEFERRED \
+  (1 << (ELEMENT_TYPE_SPECIFIC_BITS_OFFSET+1))
+
+// Make sure we have enough space for those bits
+PR_STATIC_ASSERT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET+1 < 32);
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Anchor)
 
 nsHTMLAnchorElement::nsHTMLAnchorElement(already_AddRefed<nsINodeInfo> aNodeInfo)
-  : nsGenericHTMLElement(aNodeInfo),
-    Link(this)
+  : nsGenericHTMLElement(aNodeInfo)
+  , Link(this)
 {
 }
 
 nsHTMLAnchorElement::~nsHTMLAnchorElement()
 {
 }
 
 
@@ -197,16 +210,36 @@ nsHTMLAnchorElement::GetDraggable(bool* 
                                nsGkAtoms::_false, eIgnoreCase);
     return NS_OK;
   }
 
   // no href, so just use the same behavior as other elements
   return nsGenericHTMLElement::GetDraggable(aDraggable);
 }
 
+void
+nsHTMLAnchorElement::OnDNSPrefetchRequested()
+{
+  UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+  SetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+}
+
+void
+nsHTMLAnchorElement::OnDNSPrefetchDeferred()
+{
+  UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+  SetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+}
+
+bool
+nsHTMLAnchorElement::HasDeferredDNSPrefetchRequest()
+{
+  return HasFlag(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+}
+
 nsresult
 nsHTMLAnchorElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                 nsIContent* aBindingParent,
                                 bool aCompileEventHandlers)
 {
   Link::ResetLinkState(false);
 
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
@@ -223,16 +256,31 @@ nsHTMLAnchorElement::BindToTree(nsIDocum
   }
 
   return rv;
 }
 
 void
 nsHTMLAnchorElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
+  // Cancel any DNS prefetches
+  // Note: Must come before ResetLinkState.  If called after, it will recreate
+  // mCachedURI based on data that is invalid - due to a call to GetHostname.
+
+  // If prefetch was deferred, clear flag and move on
+  if (HasFlag(HTML_ANCHOR_DNS_PREFETCH_DEFERRED))
+    UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+  // Else if prefetch was requested, clear flag and send cancellation
+  else if (HasFlag(HTML_ANCHOR_DNS_PREFETCH_REQUESTED)) {
+    UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+    // Possible that hostname could have changed since binding, but since this
+    // covers common cases, most DNS prefetch requests will be canceled
+    nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
+  }
+  
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false);
   
   nsIDocument* doc = GetCurrentDoc();
   if (doc) {
     doc->UnregisterPendingLinkUpdate(this);
   }
--- a/content/html/content/src/nsHTMLDNSPrefetch.cpp
+++ b/content/html/content/src/nsHTMLDNSPrefetch.cpp
@@ -160,54 +160,112 @@ nsHTMLDNSPrefetch::PrefetchMedium(Link *
 
 nsresult
 nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
 {
   return Prefetch(aElement, 0);
 }
 
 nsresult
-nsHTMLDNSPrefetch::Prefetch(nsAString &hostname, PRUint16 flags)
+nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname, PRUint16 flags)
 {
   if (IsNeckoChild()) {
     // We need to check IsEmpty() because net_IsValidHostName()
     // considers empty strings to be valid hostnames
     if (!hostname.IsEmpty() &&
         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
       gNeckoChild->SendHTMLDNSPrefetch(nsAutoString(hostname), flags);
     }
     return NS_OK;
   }
 
   if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   nsCOMPtr<nsICancelable> tmpOutstanding;
-  return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname), flags | nsIDNSService::RESOLVE_SPECULATE,
-                                   sDNSListener, nsnull, getter_AddRefs(tmpOutstanding));
+  return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname),
+                                   flags | nsIDNSService::RESOLVE_SPECULATE,
+                                   sDNSListener, nsnull, 
+                                   getter_AddRefs(tmpOutstanding));
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchLow(nsAString &hostname)
+nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname)
 {
   return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchMedium(nsAString &hostname)
+nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname)
 {
   return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchHigh(nsAString &hostname)
+nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname)
 {
   return Prefetch(hostname, 0);
 }
 
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
+                                  PRUint16 flags,
+                                  nsresult aReason)
+{
+  if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
+    return NS_ERROR_NOT_AVAILABLE;
+
+  nsAutoString hostname;
+  nsresult rv = aElement->GetHostname(hostname);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return CancelPrefetch(hostname, flags, aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
+                                  PRUint16 flags,
+                                  nsresult aReason)
+{
+  // Forward this request to Necko Parent if we're a child process
+  if (IsNeckoChild()) {
+    // We need to check IsEmpty() because net_IsValidHostName()
+    // considers empty strings to be valid hostnames
+    if (!hostname.IsEmpty() &&
+        net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
+      gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname), flags,
+                                             aReason);
+    }
+    return NS_OK;
+  }
+
+  if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
+    return NS_ERROR_NOT_AVAILABLE;
+
+  // Forward cancellation to DNS service
+  return sDNSService->CancelAsyncResolve(NS_ConvertUTF16toUTF8(hostname),
+                                         flags
+                                         | nsIDNSService::RESOLVE_SPECULATE,
+                                         sDNSListener, aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetchLow(Link *aElement, nsresult aReason)
+{
+  return CancelPrefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW,
+                        aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname, nsresult aReason)
+{
+  return CancelPrefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW,
+                        aReason);
+}
+
+
 /////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsHTMLDNSPrefetch::nsListener,
                               nsIDNSListener)
 
 NS_IMETHODIMP
 nsHTMLDNSPrefetch::nsListener::OnLookupComplete(nsICancelable *request,
                                               nsIDNSRecord  *rec,
@@ -252,16 +310,18 @@ nsHTMLDNSPrefetch::nsDeferrals::Flush()
 }
 
 nsresult
 nsHTMLDNSPrefetch::nsDeferrals::Add(PRUint16 flags, Link *aElement)
 {
   // 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");
 
+  aElement->OnDNSPrefetchDeferred();
+
   if (((mHead + 1) & sMaxDeferredMask) == mTail)
     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
     
   mEntries[mHead].mFlags = flags;
   mEntries[mHead].mElement = do_GetWeakReference(aElement);
   mHead = (mHead + 1) & sMaxDeferredMask;
 
   if (!mActiveLoaderCount && !mTimerArmed && mTimer) {
@@ -278,30 +338,38 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQu
   NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::SubmitQueue must be on main thread");
   nsCString hostName;
   if (!sDNSService) return;
 
   while (mHead != mTail) {
     nsCOMPtr<nsIContent> content = do_QueryReferent(mEntries[mTail].mElement);
     if (content) {
       nsCOMPtr<Link> link = do_QueryInterface(content);
-      nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nsnull);
-      if (hrefURI)
-        hrefURI->GetAsciiHost(hostName);
+      // Only prefetch here if request was deferred and deferral not cancelled
+      if (link && link->HasDeferredDNSPrefetchRequest()) {
+        nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nsnull);
+        if (hrefURI)
+          hrefURI->GetAsciiHost(hostName);
 
-      if (!hostName.IsEmpty()) {
-        if (IsNeckoChild()) {
-          gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
+        if (!hostName.IsEmpty()) {
+          if (IsNeckoChild()) {
+            gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
                                            mEntries[mTail].mFlags);
-        } else {
-          nsCOMPtr<nsICancelable> tmpOutstanding;
+          } else {
+            nsCOMPtr<nsICancelable> tmpOutstanding;
 
-          sDNSService->AsyncResolve(hostName, 
-                                  mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE,
-                                  sDNSListener, nsnull, getter_AddRefs(tmpOutstanding));
+            nsresult rv = sDNSService->AsyncResolve(hostName, 
+                                    mEntries[mTail].mFlags
+                                    | nsIDNSService::RESOLVE_SPECULATE,
+                                    sDNSListener, nsnull,
+                                    getter_AddRefs(tmpOutstanding));
+            // Tell link that deferred prefetch was requested
+            if (NS_SUCCEEDED(rv))
+              link->OnDNSPrefetchRequested();
+          }
         }
       }
     }
     
     mEntries[mTail].mElement = nsnull;
     mTail = (mTail + 1) & sMaxDeferredMask;
   }
   
--- a/content/html/content/src/nsHTMLDNSPrefetch.h
+++ b/content/html/content/src/nsHTMLDNSPrefetch.h
@@ -79,23 +79,32 @@ public:
   // complete, while the string versions submit the lookup to 
   // the DNS system immediately. The URI version is somewhat lighter
   // weight, but its request is also more likely to be dropped due to a 
   // full queue and it may only be used from the main thread.
 
   static nsresult PrefetchHigh(mozilla::dom::Link *aElement);
   static nsresult PrefetchMedium(mozilla::dom::Link *aElement);
   static nsresult PrefetchLow(mozilla::dom::Link *aElement);
-  static nsresult PrefetchHigh(nsAString &host);
-  static nsresult PrefetchMedium(nsAString &host);
-  static nsresult PrefetchLow(nsAString &host);
+  static nsresult PrefetchHigh(const nsAString &host);
+  static nsresult PrefetchMedium(const nsAString &host);
+  static nsresult PrefetchLow(const nsAString &host);
+  static nsresult CancelPrefetchLow(const nsAString &host, nsresult aReason);
+  static nsresult CancelPrefetchLow(mozilla::dom::Link *aElement,
+                                    nsresult aReason);
 
 private:
-  static nsresult Prefetch(nsAString &host, PRUint16 flags);
+  static nsresult Prefetch(const nsAString &host, PRUint16 flags);
   static nsresult Prefetch(mozilla::dom::Link *aElement, PRUint16 flags);
+  static nsresult CancelPrefetch(const nsAString &hostname,
+                                 PRUint16 flags,
+                                 nsresult aReason);
+  static nsresult CancelPrefetch(mozilla::dom::Link *aElement,
+                                 PRUint16 flags,
+                                 nsresult aReason);
   
 public:
   class nsListener : public nsIDNSListener
   {
     // This class exists to give a safe callback no-op DNSListener
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIDNSLISTENER
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -275,16 +275,20 @@ public:
         : mResolver(res)
         , mHost(host)
         , mListener(listener)
         , mFlags(flags)
         , mAF(af) {}
     ~nsDNSAsyncRequest() {}
 
     void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
+    // Returns TRUE if the DNS listener arg is the same as the member listener
+    // Used in Cancellations to remove DNS requests associated with a
+    // particular hostname and nsIDNSListener
+    bool EqualsAsyncListener(nsIDNSListener *aListener);
 
     nsRefPtr<nsHostResolver> mResolver;
     nsCString                mHost; // hostname we're resolving
     nsCOMPtr<nsIDNSListener> mListener;
     PRUint16                 mFlags;
     PRUint16                 mAF;
 };
 
@@ -307,16 +311,22 @@ nsDNSAsyncRequest::OnLookupComplete(nsHo
     mListener->OnLookupComplete(this, rec, status);
     mListener = nsnull;
 
     // release the reference to ourselves that was added before we were
     // handed off to the host resolver.
     NS_RELEASE_THIS();
 }
 
+bool
+nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
+{
+    return (aListener == mListener);
+}
+
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest, nsICancelable)
 
 NS_IMETHODIMP
 nsDNSAsyncRequest::Cancel(nsresult reason)
 {
     NS_ENSURE_ARG(NS_FAILED(reason));
     mResolver->DetachCallback(mHost.get(), mFlags, mAF, this, reason);
     return NS_OK;
@@ -329,16 +339,17 @@ class nsDNSSyncRequest : public nsResolv
 public:
     nsDNSSyncRequest(PRMonitor *mon)
         : mDone(false)
         , mStatus(NS_OK)
         , mMonitor(mon) {}
     virtual ~nsDNSSyncRequest() {}
 
     void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
+    bool EqualsAsyncListener(nsIDNSListener *aListener);
 
     bool                   mDone;
     nsresult               mStatus;
     nsRefPtr<nsHostRecord> mHostRecord;
 
 private:
     PRMonitor             *mMonitor;
 };
@@ -352,16 +363,23 @@ nsDNSSyncRequest::OnLookupComplete(nsHos
     PR_EnterMonitor(mMonitor);
     mDone = true;
     mStatus = status;
     mHostRecord = hostRecord;
     PR_Notify(mMonitor);
     PR_ExitMonitor(mMonitor);
 }
 
+bool
+nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
+{
+    // Sync request: no listener to compare
+    return false;
+}
+
 //-----------------------------------------------------------------------------
 
 nsDNSService::nsDNSService()
     : mLock("nsDNSServer.mLock")
     , mFirstTime(true)
 {
 }
 
@@ -599,16 +617,52 @@ nsDNSService::AsyncResolve(const nsACStr
     if (NS_FAILED(rv)) {
         NS_RELEASE(req);
         NS_RELEASE(*result);
     }
     return rv;
 }
 
 NS_IMETHODIMP
+nsDNSService::CancelAsyncResolve(const nsACString  &aHostname,
+                                 PRUint32           aFlags,
+                                 nsIDNSListener    *aListener,
+                                 nsresult           aReason)
+{
+    // grab reference to global host resolver and IDN service.  beware
+    // simultaneous shutdown!!
+    nsRefPtr<nsHostResolver> res;
+    nsCOMPtr<nsIIDNService> idn;
+    {
+        MutexAutoLock lock(mLock);
+
+        if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE))
+            return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+
+        res = mResolver;
+        idn = mIDN;
+    }
+    if (!res)
+        return NS_ERROR_OFFLINE;
+
+    nsCString hostname(aHostname);
+
+    nsCAutoString hostACE;
+    if (idn && !IsASCII(aHostname)) {
+        if (NS_SUCCEEDED(idn->ConvertUTF8toACE(aHostname, hostACE)))
+            hostname = hostACE;
+    }
+
+    PRUint16 af = GetAFForLookup(hostname, aFlags);
+
+    res->CancelAsyncRequest(hostname.get(), aFlags, af, aListener, aReason);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDNSService::Resolve(const nsACString &hostname,
                       PRUint32          flags,
                       nsIDNSRecord    **result)
 {
     // grab reference to global host resolver and IDN service.  beware
     // simultaneous shutdown!!
     nsRefPtr<nsHostResolver> res;
     nsCOMPtr<nsIIDNService> idn;
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -929,16 +929,60 @@ nsHostResolver::OnLookupComplete(nsHostR
             callback->OnLookupComplete(this, rec, status);
             // NOTE: callback must not be dereferenced after this point!!
         }
     }
 
     NS_RELEASE(rec);
 }
 
+void
+nsHostResolver::CancelAsyncRequest(const char            *host,
+                                   PRUint16               flags,
+                                   PRUint16               af,
+                                   nsIDNSListener        *aListener,
+                                   nsresult               status)
+
+{
+    MutexAutoLock lock(mLock);
+
+    // Lookup the host record associated with host, flags & address family
+    nsHostKey key = { host, flags, af };
+    nsHostDBEnt *he = static_cast<nsHostDBEnt *>
+                      (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
+    if (he && he->rec) {
+        nsHostRecord* recPtr = NULL;
+        PRCList *node = he->rec->callbacks.next;
+        // Remove the first nsDNSAsyncRequest callback which matches the
+        // supplied listener object
+        while (node != &he->rec->callbacks) {
+            nsResolveHostCallback *callback
+                = static_cast<nsResolveHostCallback *>(node);
+            if (callback && (callback->EqualsAsyncListener(aListener))) {
+                // Remove from the list of callbacks
+                PR_REMOVE_LINK(callback);
+                recPtr = he->rec;
+                callback->OnLookupComplete(this, recPtr, status);
+                break;
+            }
+            node = node->next;
+        }
+
+        // If there are no more callbacks, remove the hash table entry
+        if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
+            PL_DHashTableOperate(&mDB, (nsHostKey *)recPtr, PL_DHASH_REMOVE);
+            // If record is on a Queue, remove it and then deref it
+            if (recPtr->next != recPtr) {
+                PR_REMOVE_LINK(recPtr);
+                NS_RELEASE(recPtr);
+            }
+        }
+    }
+}
+
 //----------------------------------------------------------------------------
 
 void
 nsHostResolver::ThreadFunc(void *arg)
 {
     LOG(("nsHostResolver::ThreadFunc entering\n"));
 #if defined(RES_RETRY_ON_FAILURE)
     nsResState rs;
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -41,16 +41,17 @@
 #include "nscore.h"
 #include "nsAtomicRefcnt.h"
 #include "prclist.h"
 #include "prnetdb.h"
 #include "pldhash.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/Mutex.h"
 #include "nsISupportsImpl.h"
+#include "nsIDNSListener.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 class nsHostResolver;
 class nsHostRecord;
 class nsResolveHostCallback;
 
 /* XXX move this someplace more generic */
@@ -176,16 +177,30 @@ public:
      * @param record
      *        the host record containing the results of the lookup
      * @param status
      *        if successful, |record| contains non-null results
      */
     virtual void OnLookupComplete(nsHostResolver *resolver,
                                   nsHostRecord   *record,
                                   nsresult        status) = 0;
+    /**
+     * EqualsAsyncListener
+     *
+     * Determines if the listener argument matches the listener member var.
+     * For subclasses not implementing a member listener, should return false.
+     * For subclasses having a member listener, the function should check if
+     * they are the same.  Used for cases where a pointer to an object
+     * implementing nsResolveHostCallback is unknown, but a pointer to
+     * the original listener is known.
+     *
+     * @param aListener
+     *        nsIDNSListener object associated with the original request
+     */
+    virtual bool EqualsAsyncListener(nsIDNSListener *aListener) = 0;
 };
 
 /**
  * nsHostResolver - an asynchronous host name resolver.
  */
 class nsHostResolver
 {
     typedef mozilla::CondVar CondVar;
@@ -231,16 +246,28 @@ public:
      */
     void DetachCallback(const char            *hostname,
                         PRUint16               flags,
                         PRUint16               af,
                         nsResolveHostCallback *callback,
                         nsresult               status);
 
     /**
+     * Cancels an async request associated with the hostname, flags,
+     * address family and listener.  Cancels first callback found which matches
+     * these criteria.  These parameters should correspond to the parameters
+     * passed to ResolveHost.  If this is the last callback associated with the
+     * host record, it is removed from any request queues it might be on. 
+     */
+    void CancelAsyncRequest(const char            *host,
+                            PRUint16               flags,
+                            PRUint16               af,
+                            nsIDNSListener        *aListener,
+                            nsresult               status);
+    /**
      * values for the flags parameter passed to ResolveHost and DetachCallback
      * that may be bitwise OR'd together.
      *
      * NOTE: in this implementation, these flags correspond exactly in value
      *       to the flags defined on nsIDNSService.
      */
     enum {
         RES_BYPASS_CACHE = 1 << 0,
--- a/netwerk/dns/nsIDNSService.idl
+++ b/netwerk/dns/nsIDNSService.idl
@@ -41,17 +41,17 @@
 interface nsICancelable;
 interface nsIEventTarget;
 interface nsIDNSRecord;
 interface nsIDNSListener;
 
 /**
  * nsIDNSService
  */
-[scriptable, uuid(c1a56a45-8fa3-44e6-9f01-38c91c858cf9)]
+[scriptable, uuid(F6E05CC3-8A13-463D-877F-D59B20B59724)]
 interface nsIDNSService : nsISupports
 {
     /**
      * kicks off an asynchronous host lookup.
      *
      * @param aHostName
      *        the hostname or IP-address-literal to resolve.
      * @param aFlags
@@ -68,16 +68,36 @@ interface nsIDNSService : nsISupports
      * @return An object that can be used to cancel the host lookup.
      */
     nsICancelable asyncResolve(in AUTF8String       aHostName,
                                in unsigned long     aFlags,
                                in nsIDNSListener    aListener,
                                in nsIEventTarget    aListenerTarget);
 
     /**
+     * Attempts to cancel a previously requested async DNS lookup
+     *
+     * @param aHostName
+     *        the hostname or IP-address-literal to resolve.
+     * @param aFlags
+     *        a bitwise OR of the RESOLVE_ prefixed constants defined below.
+     * @param aListener
+     *        the original listener which was to be notified about the host lookup
+     *        result - used to match request information to requestor.
+     * @param aReason
+     *        nsresult reason for the cancellation
+     *
+     * @return An object that can be used to cancel the host lookup.
+     */
+    void cancelAsyncResolve(in AUTF8String       aHostName,
+                            in unsigned long     aFlags,
+                            in nsIDNSListener    aListener,
+                            in nsresult          aReason);
+    
+    /**
      * called to synchronously resolve a hostname.  warning this method may
      * block the calling thread for a long period of time.  it is extremely
      * unwise to call this function on the UI thread of an application.
      *
      * @param aHostName
      *        the hostname or IP-address-literal to resolve.
      * @param aFlags
      *        a bitwise OR of the RESOLVE_ prefixed constants defined below.
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -140,15 +140,23 @@ NeckoParent::DeallocPWebSocket(PWebSocke
   p->Release();
   return true;
 }
 
 bool
 NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
                                  const PRUint16& flags)
 {
-  nsAutoString h(hostname);
-  nsHTMLDNSPrefetch::Prefetch(h, flags);
+  nsHTMLDNSPrefetch::Prefetch(hostname, flags);
+  return true;
+}
+
+bool
+NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname,
+                                 const PRUint16& flags,
+                                 const nsresult& reason)
+{
+  nsHTMLDNSPrefetch::CancelPrefetch(hostname, flags, reason);
   return true;
 }
 
 }} // mozilla::net
 
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -63,14 +63,18 @@ protected:
   virtual PWyciwygChannelParent* AllocPWyciwygChannel();
   virtual bool DeallocPWyciwygChannel(PWyciwygChannelParent*);
   virtual PFTPChannelParent* AllocPFTPChannel();
   virtual bool DeallocPFTPChannel(PFTPChannelParent*);
   virtual PWebSocketParent* AllocPWebSocket(PBrowserParent* browser);
   virtual bool DeallocPWebSocket(PWebSocketParent*);
   virtual bool RecvHTMLDNSPrefetch(const nsString& hostname,
                                    const PRUint16& flags);
+  virtual bool RecvCancelHTMLDNSPrefetch(const nsString& hostname,
+                                         const PRUint16& flags,
+                                         const nsresult& reason);
+
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -64,16 +64,17 @@ parent:
   __delete__();
 
   PCookieService();
   PWyciwygChannel();
   PFTPChannel();
   PWebSocket(PBrowser browser);
 
   HTMLDNSPrefetch(nsString hostname, PRUint16 flags);
+  CancelHTMLDNSPrefetch(nsString hostname, PRUint16 flags, nsresult reason);
 
 both:
   PHttpChannel(nullable PBrowser browser);
 };
 
 
 } // namespace net
 } // namespace mozilla