Bug 622232: Cancel DNS prefetches for HTML Anchor Elems after a tab is closed; r=mcmanus sr=bz

This commit is contained in:
Steve Workman 2011-10-04 16:22:43 -07:00
parent 3cf23f6507
commit 06e7d450e9
11 changed files with 291 additions and 4 deletions

View File

@ -131,12 +131,18 @@ public:
virtual nsXPCClassInfo* GetClassInfo();
};
// Indicates if a DNS Prefetch has been requested from this Anchor elem
#define HTML_ANCHOR_DNS_PREFETCH_REQUESTED \
(1 << ELEMENT_TYPE_SPECIFIC_BITS_OFFSET)
// Make sure we have enough space for those bits
PR_STATIC_ASSERT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET < 32);
NS_IMPL_NS_NEW_HTML_ELEMENT(Anchor)
nsHTMLAnchorElement::nsHTMLAnchorElement(already_AddRefed<nsINodeInfo> aNodeInfo)
: nsGenericHTMLElement(aNodeInfo),
Link(this)
: nsGenericHTMLElement(aNodeInfo)
, Link(this)
{
}
@ -206,6 +212,7 @@ nsHTMLAnchorElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
// Prefetch links
if (aDocument && nsHTMLDNSPrefetch::IsAllowed(OwnerDoc())) {
nsHTMLDNSPrefetch::PrefetchLow(this);
SetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
}
return rv;
}
@ -213,6 +220,14 @@ nsHTMLAnchorElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
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 (HasFlag(HTML_ANCHOR_DNS_PREFETCH_REQUESTED)) {
nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
}
// 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);

View File

@ -192,7 +192,8 @@ nsHTMLDNSPrefetch::Prefetch(nsAString &hostname, PRUint16 flags)
return NS_ERROR_NOT_AVAILABLE;
nsCOMPtr<nsICancelable> tmpOutstanding;
return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname), flags | nsIDNSService::RESOLVE_SPECULATE,
return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname),
flags | nsIDNSService::RESOLVE_SPECULATE,
sDNSListener, nsnull, getter_AddRefs(tmpOutstanding));
}
@ -214,6 +215,71 @@ nsHTMLDNSPrefetch::PrefetchHigh(nsAString &hostname)
return Prefetch(hostname, 0);
}
nsresult
nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement, PRUint16 flags, nsresult aReason)
{
nsAutoString hostname;
nsresult rv = aElement->GetHostname(hostname);
if (IsNeckoChild()) {
// Instead of transporting the Link object to the other process
// we are using the hostname based function here, too. Compared to the
// IPC the performance hit should be negligible.
NS_ENSURE_SUCCESS(rv,rv);
// Forward the cancellation to the string based CancelPrefetch()
return CancelPrefetch(hostname, flags, aReason);
}
if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
return NS_ERROR_NOT_AVAILABLE;
// Attempt to remove the prefetch request from the Deferrals FIFO first ...
bool found = false;
rv = sPrefetches->Remove(flags, aElement, &found);
NS_ENSURE_SUCCESS(rv, rv);
// ... If no request was found, it may have been sent to the DNS Service.
// Forward the cancellation to the string based CancelPrefetch
if (!found)
rv = CancelPrefetch(hostname, flags, aReason);
return rv;
}
nsresult
nsHTMLDNSPrefetch::CancelPrefetch(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(nsAutoString(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(nsAString &hostname, nsresult aReason)
{
return CancelPrefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW, aReason);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPL_THREADSAFE_ISUPPORTS1(nsHTMLDNSPrefetch::nsListener,
@ -283,6 +349,42 @@ nsHTMLDNSPrefetch::nsDeferrals::Add(PRUint16 flags, Link *aElement)
return NS_OK;
}
nsresult
nsHTMLDNSPrefetch::nsDeferrals::Remove(PRUint16 aFlags, Link *aElement, bool *aFound)
{
// The FIFO has no lock, so it can only be accessed on main thread
NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Remove must be on main thread");
// Search the deferrals FIFO for this Link elem and remove it
// Note: Element removal will leave holes in the queue. However:
// -- FIFO is flushed in SubmitQueue, so holes are temporary.
// -- holes are only created if a tab is closed before page is loaded.
bool found = false;
PRUint16 curr = mTail;
while (curr != mHead) {
nsCOMPtr<nsIContent> content = do_QueryReferent(mEntries[curr].mElement);
if (content) {
nsCOMPtr<Link> link = do_QueryInterface(content);
if (link && (link == aElement) && (mEntries[curr].mFlags == aFlags)) {
// Null mElements will be ignored in SubmitQueue; requests won't be sent
mEntries[curr].mElement = NULL;
mEntries[curr].mFlags = 0;
found = true;
break;
}
}
curr = (curr + 1) & sMaxDeferredMask;
}
// Minor optimization: If we removed an element at the tail, increment the
// the tail end to shrink the FIFO.
if (found && (mTail != mHead))
mTail = (mTail + 1) & sMaxDeferredMask;
// Report "found" status back to caller
*aFound = found;
return NS_OK;
}
void
nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue()
{

View File

@ -87,10 +87,18 @@ public:
static nsresult PrefetchHigh(nsAString &host);
static nsresult PrefetchMedium(nsAString &host);
static nsresult PrefetchLow(nsAString &host);
static nsresult CancelPrefetchLow(nsAString &host, nsresult aReason);
static nsresult CancelPrefetchLow(mozilla::dom::Link *aElement, nsresult aReason);
private:
static nsresult Prefetch(nsAString &host, PRUint16 flags);
static nsresult Prefetch(mozilla::dom::Link *aElement, PRUint16 flags);
static nsresult CancelPrefetch(nsAString &hostname,
PRUint16 flags,
nsresult aReason);
static nsresult CancelPrefetch(mozilla::dom::Link *aElement,
PRUint16 flags,
nsresult aReason);
public:
class nsListener : public nsIDNSListener
@ -118,6 +126,7 @@ public:
void Activate();
nsresult Add(PRUint16 flags, mozilla::dom::Link *aElement);
nsresult Remove(PRUint16 aFlags, mozilla::dom::Link *aElement, bool *aFound);
private:
~nsDeferrals();

View File

@ -278,6 +278,10 @@ public:
~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
@ -310,6 +314,12 @@ nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver,
NS_RELEASE_THIS();
}
bool
nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
{
return (aListener == mListener);
}
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest, nsICancelable)
NS_IMETHODIMP
@ -332,6 +342,7 @@ public:
virtual ~nsDNSSyncRequest() {}
void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
bool EqualsAsyncListener(nsIDNSListener *aListener);
bool mDone;
nsresult mStatus;
@ -355,6 +366,13 @@ nsDNSSyncRequest::OnLookupComplete(nsHostResolver *resolver,
PR_ExitMonitor(mMonitor);
}
bool
nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
{
// Sync request: no listener to compare
return false;
}
//-----------------------------------------------------------------------------
nsDNSService::nsDNSService()
@ -584,6 +602,42 @@ nsDNSService::AsyncResolve(const nsACString &hostname,
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,

View File

@ -38,6 +38,7 @@
#include "nsIIDNService.h"
#include "nsIObserver.h"
#include "nsHostResolver.h"
#include "nsICancelable.h"
#include "nsAutoPtr.h"
#include "nsString.h"
#include "mozilla/Mutex.h"

View File

@ -891,6 +891,50 @@ nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, PRAddrInfo
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

View File

@ -46,6 +46,7 @@
#include "mozilla/CondVar.h"
#include "mozilla/Mutex.h"
#include "nsISupportsImpl.h"
#include "nsIDNSListener.h"
#include "nsString.h"
#include "nsTArray.h"
@ -191,6 +192,20 @@ public:
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;
};
/**
@ -244,6 +259,18 @@ public:
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.

View File

@ -46,7 +46,7 @@ interface nsIDNSListener;
/**
* nsIDNSService
*/
[scriptable, uuid(c1a56a45-8fa3-44e6-9f01-38c91c858cf9)]
[scriptable, uuid(F6E05CC3-8A13-463D-877F-D59B20B59724)]
interface nsIDNSService : nsISupports
{
/**
@ -72,6 +72,26 @@ interface nsIDNSService : nsISupports
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

View File

@ -150,5 +150,15 @@ NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
return true;
}
bool
NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname,
const PRUint16& flags,
const nsresult& reason)
{
nsAutoString h(hostname);
nsHTMLDNSPrefetch::CancelPrefetch(h, flags, reason);
return true;
}
}} // mozilla::net

View File

@ -68,6 +68,10 @@ protected:
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

View File

@ -69,6 +69,7 @@ parent:
PWebSocket(PBrowser browser);
HTMLDNSPrefetch(nsString hostname, PRUint16 flags);
CancelHTMLDNSPrefetch(nsString hostname, PRUint16 flags, nsresult reason);
both:
PHttpChannel(nullable PBrowser browser);