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 201660 6dd877592b3569d9dda4d58630d6ee4eb9cbca35
parent 201659 a6832b6e110dd36c579d277835be5c6f9f5dddad
child 201661 5b105c3b51be0d72cab021c47a26295ec571b77e
push id27375
push userryanvm@gmail.com
push dateTue, 26 Aug 2014 19:56:59 +0000
treeherdermozilla-central@f9bfe115fee5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssworkman
bugs947721
milestone34.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 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]