Bug 622232: Cancel DNS prefetches for HTML Anchor Elems after a tab is closed; r=mcmanus sr=bz
☠☠ backed out by f79582c3fd75 ☠ ☠
authorSteve Workman <sworkman@mozilla.com>
Fri, 20 Jan 2012 15:14:46 -0800
changeset 85151 a57b82b18bde3e51f7fc44ff250690d18255374a
parent 85150 66dc5e17869162b8a3276b7692d7b050232131d9
child 85152 0ec0b8f17be98b0fbdaa19436cdacda22f424853
push id21906
push usermak77@bonardo.net
push dateTue, 24 Jan 2012 12:27:58 +0000
treeherdermozilla-central@6e3018bb5cfe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, bz
bugs622232
milestone12.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 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,111 @@ 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);
+  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 +309,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 +337,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