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 2012-01-20 15:14:46 -08:00
parent dbe0e87287
commit 3627f3a4b8
11 changed files with 329 additions and 28 deletions

View File

@ -51,8 +51,8 @@ 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
{
@ -113,6 +113,24 @@ public:
// 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();

View File

@ -140,14 +140,27 @@ public:
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)
{
}
@ -202,6 +215,26 @@ nsHTMLAnchorElement::GetDraggable(bool* aDraggable)
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,
@ -228,6 +261,21 @@ 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 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);

View File

@ -165,7 +165,7 @@ nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
}
nsresult
nsHTMLDNSPrefetch::Prefetch(nsAString &hostname, PRUint16 flags)
nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname, PRUint16 flags)
{
if (IsNeckoChild()) {
// We need to check IsEmpty() because net_IsValidHostName()
@ -181,28 +181,86 @@ nsHTMLDNSPrefetch::Prefetch(nsAString &hostname, PRUint16 flags)
return NS_ERROR_NOT_AVAILABLE;
nsCOMPtr<nsICancelable> tmpOutstanding;
return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname), flags | nsIDNSService::RESOLVE_SPECULATE,
sDNSListener, nsnull, getter_AddRefs(tmpOutstanding));
return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname),
flags | nsIDNSService::RESOLVE_SPECULATE,
sDNSListener, nsnull,
getter_AddRefs(tmpOutstanding));
}
nsresult
nsHTMLDNSPrefetch::PrefetchLow(nsAString &hostname)
nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname)
{
return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW);
}
nsresult
nsHTMLDNSPrefetch::PrefetchMedium(nsAString &hostname)
nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname)
{
return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
}
nsresult
nsHTMLDNSPrefetch::PrefetchHigh(nsAString &hostname)
nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname)
{
return Prefetch(hostname, 0);
}
nsresult
nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
PRUint16 flags,
nsresult aReason)
{
if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
return NS_ERROR_NOT_AVAILABLE;
nsAutoString hostname;
nsresult rv = aElement->GetHostname(hostname);
NS_ENSURE_SUCCESS(rv, rv);
return CancelPrefetch(hostname, flags, aReason);
}
nsresult
nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
PRUint16 flags,
nsresult aReason)
{
// Forward this request to Necko Parent if we're a child process
if (IsNeckoChild()) {
// We need to check IsEmpty() because net_IsValidHostName()
// considers empty strings to be valid hostnames
if (!hostname.IsEmpty() &&
net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname), flags,
aReason);
}
return NS_OK;
}
if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
return NS_ERROR_NOT_AVAILABLE;
// Forward cancellation to DNS service
return sDNSService->CancelAsyncResolve(NS_ConvertUTF16toUTF8(hostname),
flags
| nsIDNSService::RESOLVE_SPECULATE,
sDNSListener, aReason);
}
nsresult
nsHTMLDNSPrefetch::CancelPrefetchLow(Link *aElement, nsresult aReason)
{
return CancelPrefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW,
aReason);
}
nsresult
nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname, nsresult aReason)
{
return CancelPrefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW,
aReason);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPL_THREADSAFE_ISUPPORTS1(nsHTMLDNSPrefetch::nsListener,
@ -257,6 +315,8 @@ 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;
@ -283,20 +343,28 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue()
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();
}
}
}
}

View File

@ -84,13 +84,22 @@ public:
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

View File

@ -280,6 +280,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
@ -312,6 +316,12 @@ nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver,
NS_RELEASE_THIS();
}
bool
nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
{
return (aListener == mListener);
}
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest, nsICancelable)
NS_IMETHODIMP
@ -334,6 +344,7 @@ public:
virtual ~nsDNSSyncRequest() {}
void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult);
bool EqualsAsyncListener(nsIDNSListener *aListener);
bool mDone;
nsresult mStatus;
@ -357,6 +368,13 @@ nsDNSSyncRequest::OnLookupComplete(nsHostResolver *resolver,
PR_ExitMonitor(mMonitor);
}
bool
nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
{
// Sync request: no listener to compare
return false;
}
//-----------------------------------------------------------------------------
nsDNSService::nsDNSService()
@ -603,6 +621,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

@ -934,6 +934,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"
@ -181,6 +182,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;
};
/**
@ -235,6 +250,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

@ -145,8 +145,16 @@ 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;
}

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);