Bug 947721 - Fix function CancelAsyncRequest and add the same function for the child process. r=sworkman
authorDragana Damjanovic <dd.mozilla@gmail.com>
Tue, 26 Aug 2014 05:09:00 -0400
changeset 211334 6dd877592b3569d9dda4d58630d6ee4eb9cbca35
parent 211333 a6832b6e110dd36c579d277835be5c6f9f5dddad
child 211335 5b105c3b51be0d72cab021c47a26295ec571b77e
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-esr52@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssworkman
bugs947721
milestone34.0a1
Bug 947721 - Fix function CancelAsyncRequest and add the same function for the child process. r=sworkman
netwerk/dns/ChildDNSService.cpp
netwerk/dns/ChildDNSService.h
netwerk/dns/DNSListenerProxy.cpp
netwerk/dns/DNSListenerProxy.h
netwerk/dns/DNSRequestChild.cpp
netwerk/dns/DNSRequestChild.h
netwerk/dns/DNSRequestParent.cpp
netwerk/dns/DNSRequestParent.h
netwerk/dns/PDNSRequest.ipdl
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsIDNSListener.idl
netwerk/test/unit/test_dns_cancel.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/test_dns_cancel_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/netwerk/dns/ChildDNSService.cpp
+++ b/netwerk/dns/ChildDNSService.cpp
@@ -6,17 +6,16 @@
 #include "nsIDNSListener.h"
 #include "nsNetUtil.h"
 #include "nsIThread.h"
 #include "nsThreadUtils.h"
 #include "nsIXPConnect.h"
 #include "nsIPrefService.h"
 #include "nsIProtocolProxyService.h"
 #include "mozilla/net/NeckoChild.h"
-#include "mozilla/net/DNSRequestChild.h"
 #include "mozilla/net/DNSListenerProxy.h"
 
 namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 // ChildDNSService
 //-----------------------------------------------------------------------------
@@ -39,25 +38,37 @@ ChildDNSService* ChildDNSService::GetSin
 NS_IMPL_ISUPPORTS(ChildDNSService,
                   nsIDNSService,
                   nsPIDNSService,
                   nsIObserver)
 
 ChildDNSService::ChildDNSService()
   : mFirstTime(true)
   , mOffline(false)
+  , mPendingRequestsLock("DNSPendingRequestsLock")
 {
   MOZ_ASSERT(IsNeckoChild());
 }
 
 ChildDNSService::~ChildDNSService()
 {
 
 }
 
+void
+ChildDNSService::GetDNSRecordHashKey(const nsACString &aHost,
+                                     uint32_t aFlags,
+                                     nsIDNSListener* aListener,
+                                     nsACString &aHashKey)
+{
+  aHashKey.Assign(aHost);
+  aHashKey.AppendInt(aFlags);
+  aHashKey.AppendPrintf("%p", aListener);
+}
+
 //-----------------------------------------------------------------------------
 // ChildDNSService::nsIDNSService
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 ChildDNSService::AsyncResolve(const nsACString  &hostname,
                               uint32_t           flags,
                               nsIDNSListener    *listener,
@@ -65,22 +76,28 @@ ChildDNSService::AsyncResolve(const nsAC
                               nsICancelable    **result)
 {
   NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
 
   if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
   }
 
+  // We need original flags for the pending requests hash.
+  uint32_t originalFlags = flags;
+
   // Support apps being 'offline' even if parent is not: avoids DNS traffic by
   // apps that have been told they are offline.
   if (mOffline) {
     flags |= RESOLVE_OFFLINE;
   }
 
+  // We need original listener for the pending requests hash.
+  nsIDNSListener *originalListener = listener;
+
   // make sure JS callers get notification on the main thread
   nsCOMPtr<nsIEventTarget> target = target_;
   nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
   if (wrappedListener && !target) {
     nsCOMPtr<nsIThread> mainThread;
     NS_GetMainThread(getter_AddRefs(mainThread));
     target = do_QueryInterface(mainThread);
   }
@@ -88,36 +105,56 @@ ChildDNSService::AsyncResolve(const nsAC
     // Guarantee listener freed on main thread.  Not sure we need this in child
     // (or in parent in nsDNSService.cpp) but doesn't hurt.
     listener = new DNSListenerProxy(listener, target);
   }
 
   nsRefPtr<DNSRequestChild> childReq =
     new DNSRequestChild(nsCString(hostname), flags, listener, target);
 
+  {
+    MutexAutoLock lock(mPendingRequestsLock);
+    nsCString key;
+    GetDNSRecordHashKey(hostname, originalFlags, originalListener, key);
+    nsTArray<nsRefPtr<DNSRequestChild>> *hashEntry;
+    if (mPendingRequests.Get(key, &hashEntry)) {
+      hashEntry->AppendElement(childReq);
+    } else {
+      hashEntry = new nsTArray<nsRefPtr<DNSRequestChild>>();
+      hashEntry->AppendElement(childReq);
+      mPendingRequests.Put(key, hashEntry);
+    }
+  }
+
   childReq->StartRequest();
 
   childReq.forget(result);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::CancelAsyncResolve(const nsACString  &aHostname,
                                     uint32_t           aFlags,
                                     nsIDNSListener    *aListener,
                                     nsresult           aReason)
 {
   if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
   }
 
-  // TODO: keep a hashtable of pending requests, so we can obey cancel semantics
-  // (call OnLookupComplete with aReason).  Also possible we could send IPDL to
-  // parent to cancel.
-  return NS_ERROR_NOT_AVAILABLE;
+  MutexAutoLock lock(mPendingRequestsLock);
+  nsTArray<nsRefPtr<DNSRequestChild>> *hashEntry;
+  nsCString key;
+  GetDNSRecordHashKey(aHostname, aFlags, aListener, key);
+  if (mPendingRequests.Get(key, &hashEntry)) {
+    // We cancel just one.
+    hashEntry->ElementAt(0)->Cancel(aReason);
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::Resolve(const nsACString &hostname,
                          uint32_t          flags,
                          nsIDNSRecord    **result)
 {
   // not planning to ever support this, since sync IPDL is evil.
@@ -135,16 +172,49 @@ ChildDNSService::GetDNSCacheEntries(nsTA
 
 NS_IMETHODIMP
 ChildDNSService::GetMyHostName(nsACString &result)
 {
   // TODO: get value from parent during PNecko construction?
   return NS_ERROR_NOT_AVAILABLE;
 }
 
+void
+ChildDNSService::NotifyRequestDone(DNSRequestChild *aDnsRequest)
+{
+  // We need the original flags and listener for the pending requests hash.
+  uint32_t originalFlags = aDnsRequest->mFlags & ~RESOLVE_OFFLINE;
+  nsCOMPtr<nsIDNSListener> originalListener = aDnsRequest->mListener;
+  nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(originalListener);
+  if (wrapper) {
+    wrapper->GetOriginalListener(getter_AddRefs(originalListener));
+    if (NS_WARN_IF(!originalListener)) {
+      MOZ_ASSERT(originalListener);
+      return;
+    }
+  }
+
+  MutexAutoLock lock(mPendingRequestsLock);
+
+  nsCString key;
+  GetDNSRecordHashKey(aDnsRequest->mHost, originalFlags, originalListener, key);
+
+  nsTArray<nsRefPtr<DNSRequestChild>> *hashEntry;
+
+  if (mPendingRequests.Get(key, &hashEntry)) {
+    int idx;
+    if ((idx = hashEntry->IndexOf(aDnsRequest))) {
+      hashEntry->RemoveElementAt(idx);
+      if (hashEntry->IsEmpty()) {
+        mPendingRequests.Remove(key);
+      }
+    }
+  }
+}
+
 //-----------------------------------------------------------------------------
 // ChildDNSService::nsPIDNSService
 //-----------------------------------------------------------------------------
 
 nsresult
 ChildDNSService::Init()
 {
   // Disable prefetching either by explicit preference or if a manual proxy
--- a/netwerk/dns/ChildDNSService.h
+++ b/netwerk/dns/ChildDNSService.h
@@ -6,16 +6,20 @@
 
 #ifndef mozilla_net_ChildDNSService_h
 #define mozilla_net_ChildDNSService_h
 
 
 #include "nsPIDNSService.h"
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "DNSRequestChild.h"
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
 
 namespace mozilla {
 namespace net {
 
 class ChildDNSService MOZ_FINAL
   : public nsPIDNSService
   , public nsIObserver
 {
@@ -25,19 +29,29 @@ public:
   NS_DECL_NSPIDNSSERVICE
   NS_DECL_NSIDNSSERVICE
   NS_DECL_NSIOBSERVER
 
   ChildDNSService();
 
   static ChildDNSService* GetSingleton();
 
+  void NotifyRequestDone(DNSRequestChild *aDnsRequest);
 private:
   virtual ~ChildDNSService();
 
+  void MOZ_ALWAYS_INLINE GetDNSRecordHashKey(const nsACString &aHost,
+                                             uint32_t aFlags,
+                                             nsIDNSListener* aListener,
+                                             nsACString &aHashKey);
+
   bool mFirstTime;
   bool mOffline;
   bool mDisablePrefetch;
+
+  // We need to remember pending dns requests to be able to cancel them.
+  nsClassHashtable<nsCStringHashKey, nsTArray<nsRefPtr<DNSRequestChild>>> mPendingRequests;
+  Mutex mPendingRequestsLock;
 };
 
 } // namespace net
 } // namespace mozilla
 #endif // mozilla_net_ChildDNSService_h
--- a/netwerk/dns/DNSListenerProxy.cpp
+++ b/netwerk/dns/DNSListenerProxy.cpp
@@ -6,17 +6,19 @@
 
 #include "mozilla/net/DNSListenerProxy.h"
 #include "nsICancelable.h"
 #include "nsIEventTarget.h"
 
 namespace mozilla {
 namespace net {
 
-NS_IMPL_ISUPPORTS(DNSListenerProxy, nsIDNSListener)
+NS_IMPL_ISUPPORTS(DNSListenerProxy,
+                  nsIDNSListener,
+                  nsIDNSListenerProxy)
 
 NS_IMETHODIMP
 DNSListenerProxy::OnLookupComplete(nsICancelable* aRequest,
                                    nsIDNSRecord* aRecord,
                                    nsresult aStatus)
 {
   nsRefPtr<OnLookupCompleteRunnable> r =
     new OnLookupCompleteRunnable(mListener, aRequest, aRecord, aStatus);
@@ -25,12 +27,17 @@ DNSListenerProxy::OnLookupComplete(nsICa
 
 NS_IMETHODIMP
 DNSListenerProxy::OnLookupCompleteRunnable::Run()
 {
   mListener->OnLookupComplete(mRequest, mRecord, mStatus);
   return NS_OK;
 }
 
-
+NS_IMETHODIMP
+DNSListenerProxy::GetOriginalListener(nsIDNSListener **aOriginalListener)
+{
+  NS_IF_ADDREF(*aOriginalListener = mListener);
+  return NS_OK;
+}
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/dns/DNSListenerProxy.h
+++ b/netwerk/dns/DNSListenerProxy.h
@@ -13,30 +13,33 @@
 #include "nsThreadUtils.h"
 
 class nsIEventTarget;
 class nsICancelable;
 
 namespace mozilla {
 namespace net {
 
-class DNSListenerProxy MOZ_FINAL : public nsIDNSListener
+class DNSListenerProxy MOZ_FINAL
+    : public nsIDNSListener
+    , public nsIDNSListenerProxy
 {
 public:
   DNSListenerProxy(nsIDNSListener* aListener, nsIEventTarget* aTargetThread)
     // Sometimes aListener is a main-thread only object like XPCWrappedJS, and
     // sometimes it's a threadsafe object like nsSOCKSSocketInfo. Use a main-
     // thread pointer holder, but disable strict enforcement of thread invariants.
     // The AddRef implementation of XPCWrappedJS will assert if we go wrong here.
     : mListener(new nsMainThreadPtrHolder<nsIDNSListener>(aListener, false))
     , mTargetThread(aTargetThread)
   { }
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIDNSLISTENER
+  NS_DECL_NSIDNSLISTENERPROXY
 
   class OnLookupCompleteRunnable : public nsRunnable
   {
   public:
     OnLookupCompleteRunnable(const nsMainThreadPtrHandle<nsIDNSListener>& aListener,
                              nsICancelable* aRequest,
                              nsIDNSRecord* aRecord,
                              nsresult aStatus)
--- a/netwerk/dns/DNSRequestChild.cpp
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/net/ChildDNSService.h"
 #include "mozilla/net/DNSRequestChild.h"
 #include "mozilla/net/NeckoChild.h"
+#include "mozilla/unused.h"
 #include "nsIDNSRecord.h"
 #include "nsHostResolver.h"
 #include "nsTArray.h"
 #include "nsNetAddr.h"
 #include "nsIThread.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla::ipc;
@@ -142,59 +144,87 @@ NS_IMETHODIMP
 ChildDNSRecord::ReportUnusable(uint16_t aPort)
 {
   // "We thank you for your feedback" == >/dev/null
   // TODO: we could send info back to parent.
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
+// CancelDNSRequestEvent
+//-----------------------------------------------------------------------------
+
+class CancelDNSRequestEvent : public nsRunnable
+{
+public:
+  CancelDNSRequestEvent(DNSRequestChild* aDnsReq, nsresult aReason)
+    : mDnsRequest(aDnsReq)
+    , mReasonForCancel(aReason)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    if (mDnsRequest->mIPCOpen) {
+      // Send request to Parent process.
+      mDnsRequest->SendCancelDNSRequest(mDnsRequest->mHost, mDnsRequest->mFlags,
+                                      mReasonForCancel);
+    }
+    return NS_OK;
+  }
+private:
+  nsRefPtr<DNSRequestChild> mDnsRequest;
+  nsresult mReasonForCancel;
+};
+
+//-----------------------------------------------------------------------------
 // DNSRequestChild
 //-----------------------------------------------------------------------------
 
 DNSRequestChild::DNSRequestChild(const nsCString& aHost,
                                  const uint32_t& aFlags,
                                  nsIDNSListener *aListener,
                                  nsIEventTarget *target)
   : mListener(aListener)
   , mTarget(target)
   , mResultStatus(NS_OK)
   , mHost(aHost)
   , mFlags(aFlags)
+  , mIPCOpen(false)
 {
 }
 
 void
 DNSRequestChild::StartRequest()
 {
   // we can only do IPDL on the main thread
   if (!NS_IsMainThread()) {
     NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &DNSRequestChild::StartRequest));
     return;
   }
 
   // Send request to Parent process.
   gNeckoChild->SendPDNSRequestConstructor(this, mHost, mFlags);
+  mIPCOpen = true;
 
   // IPDL holds a reference until IPDL channel gets destroyed
   AddIPDLReference();
 }
 
 void
 DNSRequestChild::CallOnLookupComplete()
 {
   MOZ_ASSERT(mListener);
-
   mListener->OnLookupComplete(this, mResultRecord, mResultStatus);
 }
 
 bool
-DNSRequestChild::Recv__delete__(const DNSRequestResponse& reply)
+DNSRequestChild::RecvLookupCompleted(const DNSRequestResponse& reply)
 {
+  mIPCOpen = false;
   MOZ_ASSERT(mListener);
 
   switch (reply.type()) {
   case DNSRequestResponse::TDNSRecord: {
     mResultRecord = new ChildDNSRecord(reply.get_DNSRecord(), mFlags);
     break;
   }
   case DNSRequestResponse::Tnsresult: {
@@ -218,31 +248,54 @@ DNSRequestChild::Recv__delete__(const DN
   if (targetIsMain) {
     CallOnLookupComplete();
   } else {
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &DNSRequestChild::CallOnLookupComplete);
     mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
   }
 
+  unused << Send__delete__(this);
+
   return true;
 }
 
+void
+DNSRequestChild::ReleaseIPDLReference()
+{
+  // Request is done or destroyed. Remove it from the hash table.
+  nsRefPtr<ChildDNSService> dnsServiceChild =
+    dont_AddRef(ChildDNSService::GetSingleton());
+  dnsServiceChild->NotifyRequestDone(this);
+
+  Release();
+}
+
+void
+DNSRequestChild::ActorDestroy(ActorDestroyReason why)
+{
+  mIPCOpen = false;
+}
+
 //-----------------------------------------------------------------------------
 // DNSRequestChild::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(DNSRequestChild,
                   nsICancelable)
 
 //-----------------------------------------------------------------------------
 // DNSRequestChild::nsICancelable
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 DNSRequestChild::Cancel(nsresult reason)
 {
-  // for now Cancel is a no-op
+  if(mIPCOpen) {
+    // We can only do IPDL on the main thread
+    NS_DispatchToMainThread(
+      new CancelDNSRequestEvent(this, reason));
+  }
   return NS_OK;
 }
 
 //------------------------------------------------------------------------------
 }} // mozilla::net
--- a/netwerk/dns/DNSRequestChild.h
+++ b/netwerk/dns/DNSRequestChild.h
@@ -25,35 +25,34 @@ public:
   NS_DECL_NSICANCELABLE
 
   DNSRequestChild(const nsCString& aHost, const uint32_t& aFlags,
                   nsIDNSListener *aListener, nsIEventTarget *target);
 
   void AddIPDLReference() {
     AddRef();
   }
-  void ReleaseIPDLReference() {
-    // we don't need an 'mIPCOpen' variable until/unless we add calls that might
-    // try to send IPDL msgs to parent after ReleaseIPDLReference is called
-    // (when IPDL channel torn down).
-    Release();
-  }
+  void ReleaseIPDLReference();
 
   // Sends IPDL request to parent
   void StartRequest();
   void CallOnLookupComplete();
 
-private:
+protected:
+  friend class CancelDNSRequestEvent;
+  friend class ChildDNSService;
   virtual ~DNSRequestChild() {}
 
-  virtual bool Recv__delete__(const DNSRequestResponse& reply) MOZ_OVERRIDE;
+  virtual bool RecvLookupCompleted(const DNSRequestResponse& reply) MOZ_OVERRIDE;
+  virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   nsCOMPtr<nsIDNSListener>  mListener;
   nsCOMPtr<nsIEventTarget>  mTarget;
   nsCOMPtr<nsIDNSRecord>    mResultRecord;
   nsresult                  mResultStatus;
   nsCString                 mHost;
   uint16_t                  mFlags;
+  bool                      mIPCOpen;
 };
 
 } // namespace net
 } // namespace mozilla
 #endif // mozilla_net_DNSRequestChild_h
--- a/netwerk/dns/DNSRequestParent.cpp
+++ b/netwerk/dns/DNSRequestParent.cpp
@@ -39,20 +39,41 @@ DNSRequestParent::DoAsyncResolve(const n
   if (NS_SUCCEEDED(rv)) {
     nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
     nsCOMPtr<nsICancelable> unused;
     rv = dns->AsyncResolve(hostname, flags, this, mainThread,
                            getter_AddRefs(unused));
   }
 
   if (NS_FAILED(rv) && !mIPCClosed) {
-    unused << Send__delete__(this, DNSRequestResponse(rv));
+    mIPCClosed = true;
+    unused << SendLookupCompleted(DNSRequestResponse(rv));
   }
 }
 
+bool
+DNSRequestParent::RecvCancelDNSRequest(const nsCString& hostName,
+                                       const uint32_t& flags,
+                                       const nsresult& reason)
+{
+  nsresult rv;
+  nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+  if (NS_SUCCEEDED(rv)) {
+    rv = dns->CancelAsyncResolve(hostName, flags, this, reason);
+  }
+  return true;
+}
+
+bool
+DNSRequestParent::Recv__delete__()
+{
+  mIPCClosed = true;
+  return true;
+}
+
 void
 DNSRequestParent::ActorDestroy(ActorDestroyReason why)
 {
   // We may still have refcount>0 if DNS hasn't called our OnLookupComplete
   // yet, but child process has crashed.  We must not send any more msgs
   // to child, or IPDL will kill chrome process, too.
   mIPCClosed = true;
 }
@@ -87,19 +108,20 @@ DNSRequestParent::OnLookupComplete(nsICa
 
     // Get IP addresses for hostname (use port 80 as dummy value for NetAddr)
     NetAddrArray array;
     NetAddr addr;
     while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
       array.AppendElement(addr);
     }
 
-    unused << Send__delete__(this, DNSRequestResponse(DNSRecord(cname, array)));
+    unused << SendLookupCompleted(DNSRequestResponse(DNSRecord(cname, array)));
   } else {
-    unused << Send__delete__(this, DNSRequestResponse(status));
+    unused << SendLookupCompleted(DNSRequestResponse(status));
   }
 
+  mIPCClosed = true;
   return NS_OK;
 }
 
 
 
 }} // mozilla::net
--- a/netwerk/dns/DNSRequestParent.h
+++ b/netwerk/dns/DNSRequestParent.h
@@ -21,16 +21,23 @@ class DNSRequestParent
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDNSLISTENER
 
   DNSRequestParent();
 
   void DoAsyncResolve(const nsACString  &hostname, uint32_t flags);
 
+  // Pass args here rather than storing them in the parent; they are only
+  // needed if the request is to be canceled.
+  bool RecvCancelDNSRequest(const nsCString& hostName,
+                            const uint32_t& flags,
+                            const nsresult& reason);
+  bool Recv__delete__();
+
 protected:
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 private:
   virtual ~DNSRequestParent();
 
   uint32_t mFlags;
   bool mIPCClosed;  // true if IPDL channel has been closed (child crash)
 };
--- a/netwerk/dns/PDNSRequest.ipdl
+++ b/netwerk/dns/PDNSRequest.ipdl
@@ -13,19 +13,23 @@ include "mozilla/net/NeckoMessageUtils.h
 
 namespace mozilla {
 namespace net {
 
 async protocol PDNSRequest
 {
   manager PNecko;
 
-//parent:
+parent:
   // constructor in PNecko takes AsyncResolve args that initialize request
 
+  // Pass args here rather than storing them in the parent; they are only
+  // needed if the request is to be canceled.
+  CancelDNSRequest(nsCString hostName, uint32_t flags, nsresult reason);
+   __delete__();
 
 child:
-  __delete__(DNSRequestResponse reply);
+  LookupCompleted(DNSRequestResponse reply);
 
 };
 
 } //namespace net
 } //namespace mozilla
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -309,16 +309,22 @@ nsDNSAsyncRequest::OnLookupComplete(nsHo
     // 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)
 {
+    nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(mListener);
+    if (wrapper) {
+        nsCOMPtr<nsIDNSListener> originalListener;
+        wrapper->GetOriginalListener(getter_AddRefs(originalListener));
+        return aListener == originalListener;
+    }
     return (aListener == mListener);
 }
 
 size_t
 nsDNSAsyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
 {
     size_t n = mallocSizeOf(this);
 
--- a/netwerk/dns/nsIDNSListener.idl
+++ b/netwerk/dns/nsIDNSListener.idl
@@ -23,8 +23,24 @@ interface nsIDNSListener : nsISupports
      *        this parameter is null if there was an error.
      * @param aStatus
      *        if the lookup failed, this parameter gives the reason.
      */
     void onLookupComplete(in nsICancelable aRequest,
                           in nsIDNSRecord  aRecord,
                           in nsresult      aStatus);
 };
+
+/**
+ * nsIDNSListenerProxy:
+ *
+ * Must be implemented by classes that wrap the original listener passed to
+ * nsIDNSService.AsyncResolve, so we have access to original listener for
+ * comparison purposes.
+ */
+[uuid(60eff0e4-6f7c-493c-add9-1cbea59063ad)]
+interface nsIDNSListenerProxy : nsISupports
+{
+  /*
+   * The original nsIDNSListener which requested hostname resolution.
+   */
+  readonly attribute nsIDNSListener originalListener;
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_dns_cancel.js
@@ -0,0 +1,91 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+var hostname1 = "mozilla.org";
+var hostname2 = "mozilla.com";
+
+var requestList1Canceled1;
+var requestList1Canceled2;
+var requestList1NotCanceled;
+
+var requestList2Canceled;
+var requestList2NotCanceled;
+
+var listener1 = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    // One request should be resolved and two request should be canceled.
+    if (inRequest == requestList1Canceled1 ||
+        inRequest == requestList1Canceled2) {
+      // This request is canceled.
+      do_check_eq(inStatus, Cr.NS_ERROR_ABORT);
+
+      do_test_finished();
+    } else if (inRequest == requestList1NotCanceled) {
+      // This request should not be canceled.
+      do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+      do_test_finished();
+    }
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIDNSListener) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+var listener2 = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    // One request should be resolved and the other canceled.
+    if (inRequest == requestList2Canceled) {
+      // This request is canceled.
+      do_check_eq(inStatus, Cr.NS_ERROR_ABORT);
+
+      do_test_finished();
+    } else {
+      // The request should not be canceled.
+      do_check_neq(inStatus, Cr.NS_ERROR_ABORT);
+
+      do_test_finished();
+    }
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIDNSListener) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+function run_test() {
+  var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+  var mainThread = threadManager.currentThread;
+
+  var flags = Ci.nsIDNSService.RESOLVE_BYPASS_CACHE;
+
+  // This one will be canceled with cancelAsyncResolve.
+  requestList1Canceled1 = dns.asyncResolve(hostname2, flags, listener1, mainThread);
+  dns.cancelAsyncResolve(hostname2, flags, listener1, Cr.NS_ERROR_ABORT);
+
+  // This one will not be canceled.
+  requestList1NotCanceled = dns.asyncResolve(hostname1, flags, listener1, mainThread);
+
+  // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+  requestList1Canceled2 = dns.asyncResolve(hostname1, flags, listener1, mainThread);
+  requestList1Canceled2.cancel(Cr.NS_ERROR_ABORT);
+
+  // This one will not be canceled.
+  requestList2NotCanceled = dns.asyncResolve(hostname1, flags, listener2, mainThread);
+
+  // This one will be canceled with cancel(Cr.NS_ERROR_ABORT).
+  requestList2Canceled = dns.asyncResolve(hostname2, flags, listener2, mainThread);
+  requestList2Canceled.cancel(Cr.NS_ERROR_ABORT);
+
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+  do_test_pending();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -156,16 +156,17 @@ skip-if = bits != 32
 [test_channel_close.js]
 [test_compareURIs.js]
 [test_compressappend.js]
 [test_content_encoding_gzip.js]
 [test_content_sniffer.js]
 [test_cookie_header.js]
 [test_cookiejars.js]
 [test_cookiejars_safebrowsing.js]
+[test_dns_cancel.js]
 [test_data_protocol.js]
 [test_dns_service.js]
 [test_dns_localredirect.js]
 [test_dns_proxy_bypass.js]
 [test_duplicate_headers.js]
 [test_chunked_responses.js]
 [test_content_length_underrun.js]
 [test_event_sink.js]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_cancel_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+  run_test_in_child("../unit/test_dns_cancel.js");
+}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -5,16 +5,17 @@ support-files = disabled_test_bug528292_
 	child_app_offline.js
 
 [test_bug248970_cookie_wrap.js]
 [test_cacheflags_wrap.js]
 [test_cache_jar_wrap.js]
 [test_channel_close_wrap.js]
 [test_cookie_header_wrap.js]
 [test_cookiejars_wrap.js]
+[test_dns_cancel_wrap.js]
 [test_dns_service_wrap.js]
 [test_duplicate_headers_wrap.js]
 [test_event_sink_wrap.js]
 [test_head_wrap.js]
 [test_headers_wrap.js]
 [test_httpsuspend_wrap.js]
 [test_post_wrap.js]
 [test_progress_wrap.js]