summaryrefslogtreecommitdiffstats
path: root/dom/html/nsHTMLDNSPrefetch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/nsHTMLDNSPrefetch.cpp')
-rw-r--r--dom/html/nsHTMLDNSPrefetch.cpp474
1 files changed, 474 insertions, 0 deletions
diff --git a/dom/html/nsHTMLDNSPrefetch.cpp b/dom/html/nsHTMLDNSPrefetch.cpp
new file mode 100644
index 000000000..2ddc8476d
--- /dev/null
+++ b/dom/html/nsHTMLDNSPrefetch.cpp
@@ -0,0 +1,474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "base/basictypes.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsURLHelper.h"
+
+#include "nsHTMLDNSPrefetch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIProtocolHandler.h"
+
+#include "nsIDNSListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWebProgress.h"
+#include "nsCURILoader.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsThreadUtils.h"
+#include "nsITimer.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/Link.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+bool sDisablePrefetchHTTPSPref;
+static bool sInitialized = false;
+static nsIDNSService *sDNSService = nullptr;
+static nsHTMLDNSPrefetch::nsDeferrals *sPrefetches = nullptr;
+static nsHTMLDNSPrefetch::nsListener *sDNSListener = nullptr;
+
+nsresult
+nsHTMLDNSPrefetch::Initialize()
+{
+ if (sInitialized) {
+ NS_WARNING("Initialize() called twice");
+ return NS_OK;
+ }
+
+ sPrefetches = new nsHTMLDNSPrefetch::nsDeferrals();
+ NS_ADDREF(sPrefetches);
+
+ sDNSListener = new nsHTMLDNSPrefetch::nsListener();
+ NS_ADDREF(sDNSListener);
+
+ sPrefetches->Activate();
+
+ Preferences::AddBoolVarCache(&sDisablePrefetchHTTPSPref,
+ "network.dns.disablePrefetchFromHTTPS");
+
+ // Default is false, so we need an explicit call to prime the cache.
+ sDisablePrefetchHTTPSPref =
+ Preferences::GetBool("network.dns.disablePrefetchFromHTTPS", true);
+
+ NS_IF_RELEASE(sDNSService);
+ nsresult rv;
+ rv = CallGetService(kDNSServiceCID, &sDNSService);
+ if (NS_FAILED(rv)) return rv;
+
+ if (IsNeckoChild())
+ NeckoChild::InitNeckoChild();
+
+ sInitialized = true;
+ return NS_OK;
+}
+
+nsresult
+nsHTMLDNSPrefetch::Shutdown()
+{
+ if (!sInitialized) {
+ NS_WARNING("Not Initialized");
+ return NS_OK;
+ }
+ sInitialized = false;
+ NS_IF_RELEASE(sDNSService);
+ NS_IF_RELEASE(sPrefetches);
+ NS_IF_RELEASE(sDNSListener);
+
+ return NS_OK;
+}
+
+bool
+nsHTMLDNSPrefetch::IsAllowed (nsIDocument *aDocument)
+{
+ // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
+ return aDocument->IsDNSPrefetchAllowed() && aDocument->GetWindow();
+}
+
+nsresult
+nsHTMLDNSPrefetch::Prefetch(Link *aElement, uint16_t flags)
+{
+ if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return sPrefetches->Add(flags, aElement);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchLow(Link *aElement)
+{
+ return Prefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchMedium(Link *aElement)
+{
+ return Prefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
+{
+ return Prefetch(aElement, 0);
+}
+
+nsresult
+nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname, uint16_t 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))) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ 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, nullptr,
+ getter_AddRefs(tmpOutstanding));
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname)
+{
+ return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname)
+{
+ return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname)
+{
+ return Prefetch(hostname, 0);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
+ uint16_t flags,
+ nsresult aReason)
+{
+ if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoString hostname;
+ aElement->GetHostname(hostname);
+ return CancelPrefetch(hostname, flags, aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
+ uint16_t 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))) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ 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_ISUPPORTS(nsHTMLDNSPrefetch::nsListener,
+ nsIDNSListener)
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsListener::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+nsHTMLDNSPrefetch::nsDeferrals::nsDeferrals()
+ : mHead(0),
+ mTail(0),
+ mActiveLoaderCount(0),
+ mTimerArmed(false)
+{
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+}
+
+nsHTMLDNSPrefetch::nsDeferrals::~nsDeferrals()
+{
+ if (mTimerArmed) {
+ mTimerArmed = false;
+ mTimer->Cancel();
+ }
+
+ Flush();
+}
+
+NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsDeferrals,
+ nsIWebProgressListener,
+ nsISupportsWeakReference,
+ nsIObserver)
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Flush()
+{
+ while (mHead != mTail) {
+ mEntries[mTail].mElement = nullptr;
+ mTail = (mTail + 1) & sMaxDeferredMask;
+ }
+}
+
+nsresult
+nsHTMLDNSPrefetch::nsDeferrals::Add(uint16_t 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) {
+ mTimerArmed = true;
+ mTimer->InitWithFuncCallback(Tick, this, 2000, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue()
+{
+ 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);
+ // Only prefetch here if request was deferred and deferral not cancelled
+ if (link && link->HasDeferredDNSPrefetchRequest()) {
+ nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nullptr);
+ bool isLocalResource = false;
+ nsresult rv = NS_OK;
+
+ hostName.Truncate();
+ if (hrefURI) {
+ hrefURI->GetAsciiHost(hostName);
+ rv = NS_URIChainHasFlags(hrefURI,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &isLocalResource);
+ }
+
+ if (!hostName.IsEmpty() && NS_SUCCEEDED(rv) && !isLocalResource) {
+ if (IsNeckoChild()) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
+ mEntries[mTail].mFlags);
+ }
+ } else {
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ rv = sDNSService->AsyncResolve(hostName,
+ mEntries[mTail].mFlags
+ | nsIDNSService::RESOLVE_SPECULATE,
+ sDNSListener, nullptr,
+ getter_AddRefs(tmpOutstanding));
+ // Tell link that deferred prefetch was requested
+ if (NS_SUCCEEDED(rv))
+ link->OnDNSPrefetchRequested();
+ }
+ }
+ }
+ }
+
+ mEntries[mTail].mElement = nullptr;
+ mTail = (mTail + 1) & sMaxDeferredMask;
+ }
+
+ if (mTimerArmed) {
+ mTimerArmed = false;
+ mTimer->Cancel();
+ }
+}
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Activate()
+{
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+ if (progress)
+ progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ // Register as an observer for xpcom shutdown events so we can drop any element refs
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->AddObserver(this, "xpcom-shutdown", true);
+}
+
+// nsITimer related method
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Tick(nsITimer *aTimer, void *aClosure)
+{
+ nsHTMLDNSPrefetch::nsDeferrals *self = (nsHTMLDNSPrefetch::nsDeferrals *) aClosure;
+
+ NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Tick must be on main thread");
+ NS_ASSERTION(self->mTimerArmed, "Timer is not armed");
+
+ self->mTimerArmed = false;
+
+ // If the queue is not submitted here because there are outstanding pages being loaded,
+ // there is no need to rearm the timer as the queue will be submtited when those
+ // loads complete.
+ if (!self->mActiveLoaderCount)
+ self->SubmitQueue();
+}
+
+//////////// nsIWebProgressListener methods
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus)
+{
+ // The FIFO has no lock, so it can only be accessed on main thread
+ NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::OnStateChange must be on main thread");
+
+ if (progressStateFlags & STATE_IS_DOCUMENT) {
+ if (progressStateFlags & STATE_STOP) {
+
+ // Initialization may have missed a STATE_START notification, so do
+ // not go negative
+ if (mActiveLoaderCount)
+ mActiveLoaderCount--;
+
+ if (!mActiveLoaderCount)
+ SubmitQueue();
+ }
+ else if (progressStateFlags & STATE_START)
+ mActiveLoaderCount++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *location,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ return NS_OK;
+}
+
+//////////// nsIObserver method
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, "xpcom-shutdown"))
+ Flush();
+
+ return NS_OK;
+}