dom/html/nsHTMLDNSPrefetch.cpp
author Nicholas Nethercote <nnethercote@mozilla.com>
Fri, 03 Mar 2017 15:32:11 +1100
changeset 374836 4e196d802c7be7f3a3c147cc6c5b6406656584b1
parent 372214 c64f0271649030394d2a29872513c9ec36521fa0
permissions -rw-r--r--
Bug 1344118 - Fix the profiler's sleeping threads optimization. r=jseward. When ProfilerBuffer::reset() is called, DuplicateLastSample() will start failing for all sleeping threads because there will be no prior thread data in the buffer to duplicate. But the sampling loop doesn't detect such failure. This causes two problems: - Missing samples. - CPU usage goes through the roof, because each time around the sampling loop the length of the failing search increases. The fix is simple: detect failure in the sampling loop and do a normal sample in that case. The patch also removes ThreadInfo::DuplicateLastSample(), because it just calls onto ProfileBuffer::DuplicateLastSample().

/* -*- 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->InitWithNamedFuncCallback(Tick, this, 2000, nsITimer::TYPE_ONE_SHOT,
                                      "nsHTMLDNSPrefetch::nsDeferrals::Tick");
  }
  
  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;
}