Bug 1406161 - Part 4: Move nsPingListener and nsRefreshTimer to separate files. r?smaug draft
authorSamael Wang <freesamael@gmail.com>
Mon, 18 Dec 2017 20:19:36 -0600
changeset 716074 c132ca0c0f7800f749382163f8d10f834652837b
parent 716073 fe6a3a6e0fec765fdc896934a99c14eefde01517
child 716075 fba96e869602a66bcd0ae1d86f0ce9a6590a5a21
push id94319
push userbmo:sawang@mozilla.com
push dateFri, 05 Jan 2018 03:38:14 +0000
reviewerssmaug
bugs1406161
milestone59.0a1
Bug 1406161 - Part 4: Move nsPingListener and nsRefreshTimer to separate files. r?smaug MozReview-Commit-ID: 9Z6i9yWoQwj
docshell/base/moz.build
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsPingListener.cpp
docshell/base/nsPingListener.h
docshell/base/nsRefreshTimer.cpp
docshell/base/nsRefreshTimer.h
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -90,16 +90,18 @@ UNIFIED_SOURCES += [
     'nsDefaultURIFixup.cpp',
     'nsDocShell.cpp',
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
     'nsDocShellLoadInfo.cpp',
     'nsDocShellTransferableHooks.cpp',
     'nsDocShellTreeOwner.cpp',
     'nsDSURIContentListener.cpp',
+    'nsPingListener.cpp',
+    'nsRefreshTimer.cpp',
     'nsWebNavigationInfo.cpp',
     'PendingGlobalHistoryEntry.cpp',
     'SerializedLoadContext.cpp',
 ]
 
 if not CONFIG['MOZ_PLACES']:
     UNIFIED_SOURCES += ['nsDownloadHistory.cpp']
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -129,17 +129,16 @@
 #include "nsIStructuredCloneContainer.h"
 #include "nsISupportsPrimitives.h"
 #include "nsITabChild.h"
 #include "nsITextToSubURI.h"
 #include "nsITimedChannel.h"
 #include "nsITimer.h"
 #include "nsITransportSecurityInfo.h"
 #include "nsIUploadChannel.h"
-#include "nsIUploadChannel2.h"
 #include "nsIURIFixup.h"
 #include "nsIURILoader.h"
 #include "nsIURL.h"
 #include "nsIViewSourceChannel.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIWebBrowserChrome3.h"
 #include "nsIWebBrowserChromeFocus.h"
 #include "nsIWebBrowserFind.h"
@@ -181,23 +180,23 @@
 #include "nsError.h"
 #include "nsEscape.h"
 #include "nsFocusManager.h"
 #include "nsGlobalWindow.h"
 #include "nsJSEnvironment.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsObjectLoadingContent.h"
+#include "nsPingListener.h"
 #include "nsPoint.h"
 #include "nsQueryObject.h"
 #include "nsRect.h"
+#include "nsRefreshTimer.h"
 #include "nsSandboxFlags.h"
 #include "nsSHistory.h"
-#include "nsStreamUtils.h"
-#include "nsStringStream.h"
 #include "nsStructuredCloneContainer.h"
 #include "nsSubDocumentFrame.h"
 #include "nsView.h"
 #include "nsViewManager.h"
 #include "nsViewSourceHandler.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsWidgetsCID.h"
 #include "nsXULAppAPI.h"
@@ -278,395 +277,16 @@ FavorPerformanceHint(bool aPerfOverStarv
   if (appShell) {
     appShell->FavorPerformanceHint(
       aPerfOverStarvation,
       Preferences::GetUint("docshell.event_starvation_delay_hint",
                            NS_EVENT_STARVATION_DELAY_HINT));
   }
 }
 
-//*****************************************************************************
-// <a ping> support
-//*****************************************************************************
-
-#define PREF_PINGS_ENABLED           "browser.send_pings"
-#define PREF_PINGS_MAX_PER_LINK      "browser.send_pings.max_per_link"
-#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
-
-// Check prefs to see if pings are enabled and if so what restrictions might
-// be applied.
-//
-// @param maxPerLink
-//   This parameter returns the number of pings that are allowed per link click
-//
-// @param requireSameHost
-//   This parameter returns true if pings are restricted to the same host as
-//   the document in which the click occurs.  If the same host restriction is
-//   imposed, then we still allow for pings to cross over to different
-//   protocols and ports for flexibility and because it is not possible to send
-//   a ping via FTP.
-//
-// @returns
-//   true if pings are enabled and false otherwise.
-//
-static bool
-PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost)
-{
-  bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
-
-  *aMaxPerLink = 1;
-  *aRequireSameHost = true;
-
-  if (allow) {
-    Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
-    Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
-  }
-
-  return allow;
-}
-
-typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
-                                    nsIURI* uri, nsIIOService* ios);
-
-static bool
-IsElementAnchor(nsIContent* aContent)
-{
-  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
-  // or XHTML namespace.
-  return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
-}
-
-static void
-ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure)
-{
-  // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
-  //       since we'd still need to parse the resulting string.  Instead, we
-  //       just parse the raw attribute.  It might be nice if the content node
-  //       implemented an interface that exposed an enumeration of nsIURIs.
-
-  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
-  // or XHTML namespace.
-  if (!IsElementAnchor(aContent)) {
-    return;
-  }
-
-  nsAutoString value;
-  aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ping, value);
-  if (value.IsEmpty()) {
-    return;
-  }
-
-  nsCOMPtr<nsIIOService> ios = do_GetIOService();
-  if (!ios) {
-    return;
-  }
-
-  nsIDocument* doc = aContent->OwnerDoc();
-  nsAutoCString charset;
-  doc->GetDocumentCharacterSet()->Name(charset);
-
-  nsWhitespaceTokenizer tokenizer(value);
-
-  while (tokenizer.hasMoreTokens()) {
-    nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI();
-    ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()),
-                charset.get(), baseURI, getter_AddRefs(uri));
-    // if we can't generate a valid URI, then there is nothing to do
-    if (!uri) {
-      continue;
-    }
-    // Explicitly not allow loading data: URIs
-    bool isDataScheme =
-      (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme);
-
-    if (!isDataScheme) {
-      aCallback(aClosure, aContent, uri, ios);
-    }
-  }
-}
-
-//----------------------------------------------------------------------
-
-// We wait this many milliseconds before killing the ping channel...
-#define PING_TIMEOUT 10000
-
-static void
-OnPingTimeout(nsITimer* aTimer, void* aClosure)
-{
-  nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
-  if (loadGroup) {
-    loadGroup->Cancel(NS_ERROR_ABORT);
-  }
-}
-
-class nsPingListener final
-  : public nsIStreamListener
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIREQUESTOBSERVER
-  NS_DECL_NSISTREAMLISTENER
-
-  nsPingListener()
-  {
-  }
-
-  void SetLoadGroup(nsILoadGroup* aLoadGroup) {
-    mLoadGroup = aLoadGroup;
-  }
-
-  nsresult StartTimeout(DocGroup* aDocGroup);
-
-private:
-  ~nsPingListener();
-
-  nsCOMPtr<nsILoadGroup> mLoadGroup;
-  nsCOMPtr<nsITimer> mTimer;
-};
-
-NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
-
-nsPingListener::~nsPingListener()
-{
-  if (mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
-  }
-}
-
-nsresult
-nsPingListener::StartTimeout(DocGroup* aDocGroup)
-{
-  NS_ENSURE_ARG(aDocGroup);
-
-  return NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer),
-                                     OnPingTimeout,
-                                     mLoadGroup,
-                                     PING_TIMEOUT,
-                                     nsITimer::TYPE_ONE_SHOT,
-                                     "nsPingListener::StartTimeout",
-                                     aDocGroup->EventTargetFor(TaskCategory::Network));
-}
-
-NS_IMETHODIMP
-nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
-                                nsIInputStream* aStream, uint64_t aOffset,
-                                uint32_t aCount)
-{
-  uint32_t result;
-  return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
-}
-
-NS_IMETHODIMP
-nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
-                              nsresult aStatus)
-{
-  mLoadGroup = nullptr;
-
-  if (mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
-  }
-
-  return NS_OK;
-}
-
-struct MOZ_STACK_CLASS SendPingInfo
-{
-  int32_t numPings;
-  int32_t maxPings;
-  bool requireSameHost;
-  nsIURI* target;
-  nsIURI* referrer;
-  nsIDocShell* docShell;
-  uint32_t referrerPolicy;
-};
-
-static void
-SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
-         nsIIOService* aIOService)
-{
-  SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
-  if (info->maxPings > -1 && info->numPings >= info->maxPings) {
-    return;
-  }
-
-  nsIDocument* doc = aContent->OwnerDoc();
-
-  nsCOMPtr<nsIChannel> chan;
-  NS_NewChannel(getter_AddRefs(chan),
-                aURI,
-                doc,
-                info->requireSameHost
-                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
-                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
-                nsIContentPolicy::TYPE_PING,
-                nullptr, // aLoadGroup
-                nullptr, // aCallbacks
-                nsIRequest::LOAD_NORMAL, // aLoadFlags,
-                aIOService);
-
-  if (!chan) {
-    return;
-  }
-
-  // Don't bother caching the result of this URI load, but do not exempt
-  // it from Safe Browsing.
-  chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI);
-
-  nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
-  if (!httpChan) {
-    return;
-  }
-
-  // This is needed in order for 3rd-party cookie blocking to work.
-  nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
-  nsresult rv;
-  if (httpInternal) {
-    rv = httpInternal->SetDocumentURI(doc->GetDocumentURI());
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-  }
-
-  rv = httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  // Remove extraneous request headers (to reduce request size)
-  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
-                                  EmptyCString(), false);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"),
-                                  EmptyCString(), false);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"),
-                                  EmptyCString(), false);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  // Always send a Ping-To header.
-  nsAutoCString pingTo;
-  if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
-    rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-  }
-
-  nsCOMPtr<nsIScriptSecurityManager> sm =
-    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
-
-  if (sm && info->referrer) {
-    bool referrerIsSecure;
-    uint32_t flags = nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
-    rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure);
-
-    // Default to sending less data if NS_URIChainHasFlags() fails.
-    referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;
-
-    bool sameOrigin =
-      NS_SUCCEEDED(sm->CheckSameOriginURI(info->referrer, aURI, false));
-
-    // If both the address of the document containing the hyperlink being
-    // audited and "ping URL" have the same origin or the document containing
-    // the hyperlink being audited was not retrieved over an encrypted
-    // connection, send a Ping-From header.
-    if (sameOrigin || !referrerIsSecure) {
-      nsAutoCString pingFrom;
-      if (NS_SUCCEEDED(info->referrer->GetSpec(pingFrom))) {
-        rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"),
-                                        pingFrom, false);
-        MOZ_ASSERT(NS_SUCCEEDED(rv));
-      }
-    }
-
-    // If the document containing the hyperlink being audited was not retrieved
-    // over an encrypted connection and its address does not have the same
-    // origin as "ping URL", send a referrer.
-    if (!sameOrigin && !referrerIsSecure) {
-      rv = httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-    }
-  }
-
-  nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
-  if (!uploadChan) {
-    return;
-  }
-
-  NS_NAMED_LITERAL_CSTRING(uploadData, "PING");
-
-  nsCOMPtr<nsIInputStream> uploadStream;
-  rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  uploadChan->ExplicitSetUploadStream(uploadStream,
-                                      NS_LITERAL_CSTRING("text/ping"),
-                                      uploadData.Length(),
-                                      NS_LITERAL_CSTRING("POST"), false);
-
-  // The channel needs to have a loadgroup associated with it, so that we can
-  // cancel the channel and any redirected channels it may create.
-  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
-  if (!loadGroup) {
-    return;
-  }
-  nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
-  loadGroup->SetNotificationCallbacks(callbacks);
-  chan->SetLoadGroup(loadGroup);
-
-  RefPtr<nsPingListener> pingListener = new nsPingListener();
-  chan->AsyncOpen2(pingListener);
-
-  // Even if AsyncOpen failed, we still count this as a successful ping.  It's
-  // possible that AsyncOpen may have failed after triggering some background
-  // process that may have written something to the network.
-  info->numPings++;
-
-  // Prevent ping requests from stalling and never being garbage collected...
-  if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
-    // If we failed to setup the timer, then we should just cancel the channel
-    // because we won't be able to ensure that it goes away in a timely manner.
-    chan->Cancel(NS_ERROR_ABORT);
-    return;
-  }
-  // if the channel openend successfully, then make the pingListener hold
-  // a strong reference to the loadgroup which is released in ::OnStopRequest
-  pingListener->SetLoadGroup(loadGroup);
-}
-
-// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
-static void
-DispatchPings(nsIDocShell* aDocShell,
-              nsIContent* aContent,
-              nsIURI* aTarget,
-              nsIURI* aReferrer,
-              uint32_t aReferrerPolicy)
-{
-  SendPingInfo info;
-
-  if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
-    return;
-  }
-  if (info.maxPings == 0) {
-    return;
-  }
-
-  info.numPings = 0;
-  info.target = aTarget;
-  info.referrer = aReferrer;
-  info.referrerPolicy = aReferrerPolicy;
-  info.docShell = aDocShell;
-
-  ForEachPing(aContent, SendPing, &info);
-}
-
 static nsDOMNavigationTiming::Type
 ConvertLoadTypeToNavigationType(uint32_t aLoadType)
 {
   // Not initialized, assume it's normal load.
   if (aLoadType == 0) {
     aLoadType = LOAD_NORMAL;
   }
 
@@ -13891,60 +13511,16 @@ NS_IMETHODIMP
 nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState)
 {
   if (mOSHE) {
     mOSHE->SetLayoutHistoryState(aLayoutHistoryState);
   }
   return NS_OK;
 }
 
-nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell,
-                               nsIURI* aURI,
-                               nsIPrincipal* aPrincipal,
-                               int32_t aDelay, bool aRepeat, bool aMetaRefresh)
-  : mDocShell(aDocShell), mURI(aURI), mPrincipal(aPrincipal),
-    mDelay(aDelay), mRepeat(aRepeat),
-    mMetaRefresh(aMetaRefresh)
-{
-}
-
-nsRefreshTimer::~nsRefreshTimer()
-{
-}
-
-NS_IMPL_ADDREF(nsRefreshTimer)
-NS_IMPL_RELEASE(nsRefreshTimer)
-
-NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
-  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
-  NS_INTERFACE_MAP_ENTRY(nsINamed)
-NS_INTERFACE_MAP_END_THREADSAFE
-
-NS_IMETHODIMP
-nsRefreshTimer::Notify(nsITimer* aTimer)
-{
-  NS_ASSERTION(mDocShell, "DocShell is somehow null");
-
-  if (mDocShell && aTimer) {
-    // Get the delay count to determine load type
-    uint32_t delay = 0;
-    aTimer->GetDelay(&delay);
-    mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, mMetaRefresh, aTimer);
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsRefreshTimer::GetName(nsACString& aName)
-{
-  aName.AssignLiteral("nsRefreshTimer");
-  return NS_OK;
-}
-
 nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy(
     nsIInterfaceRequestor* aRequestor)
 {
   if (aRequestor) {
     mWeakPtr = do_GetWeakReference(aRequestor);
   }
 }
 
@@ -14446,16 +14022,24 @@ nsDocShell::OnLinkClick(nsIContent* aCon
   nsCOMPtr<nsIRunnable> ev =
     new OnLinkClickEvent(this, aContent, aURI, target.get(), aFileName,
                          aPostDataStream, aPostDataStreamLength,
                          aHeadersDataStream, noOpenerImplied,
                          aIsTrusted, aTriggeringPrincipal);
   return DispatchToTabGroup(TaskCategory::UI, ev.forget());
 }
 
+static bool
+IsElementAnchorOrArea(nsIContent* aContent)
+{
+  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+  // or XHTML namespace.
+  return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area);
+}
+
 NS_IMETHODIMP
 nsDocShell::OnLinkClickSync(nsIContent* aContent,
                             nsIURI* aURI,
                             const char16_t* aTargetSpec,
                             const nsAString& aFileName,
                             nsIInputStream* aPostDataStream,
                             int64_t aPostDataStreamLength,
                             nsIInputStream* aHeadersDataStream,
@@ -14504,17 +14088,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
         if (NS_SUCCEEDED(rv) && !isExposed) {
           return extProtService->LoadURI(aURI, this);
         }
       }
     }
   }
 
   uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
-  if (IsElementAnchor(aContent)) {
+  if (IsElementAnchorOrArea(aContent)) {
     MOZ_ASSERT(aContent->IsHTMLElement());
     nsAutoString referrer;
     aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer);
     nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(referrer);
     while (tok.hasMoreTokens()) {
       const nsAString& token = tok.nextToken();
       if (token.LowerCaseEqualsLiteral("noreferrer")) {
         flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
@@ -14551,17 +14135,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
   }
 
   nsCOMPtr<nsIURI> referer = refererDoc->GetDocumentURI();
   uint32_t refererPolicy = refererDoc->GetReferrerPolicy();
 
   // get referrer attribute from clicked link and parse it
   // if per element referrer is enabled, the element referrer overrules
   // the document wide referrer
-  if (IsElementAnchor(aContent)) {
+  if (IsElementAnchorOrArea(aContent)) {
     net::ReferrerPolicy refPolEnum = aContent->AsElement()->GetReferrerPolicyAsEnum();
     if (refPolEnum != net::RP_Unset) {
       refererPolicy = refPolEnum;
     }
   }
 
   // referer could be null here in some odd cases, but that's ok,
   // we'll just load the link w/o sending a referer in those cases.
@@ -14620,17 +14204,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
                              true,                      // first party site
                              VoidString(),              // No srcdoc
                              this,                      // We are the source
                              nullptr,                   // baseURI not needed
                              true,                      // Check for prerendered doc
                              aDocShell,                 // DocShell out-param
                              aRequest);                 // Request out-param
   if (NS_SUCCEEDED(rv)) {
-    DispatchPings(this, aContent, aURI, referer, refererPolicy);
+    nsPingListener::DispatchPings(this, aContent, aURI, referer, refererPolicy);
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::OnOverLink(nsIContent* aContent,
                        nsIURI* aURI,
                        const char16_t* aTargetSpec)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -26,23 +26,21 @@
 #include "nsIDocShell.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDOMStorageManager.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILinkHandler.h"
 #include "nsILoadContext.h"
 #include "nsILoadURIDelegate.h"
-#include "nsINamed.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIRefreshURI.h"
 #include "nsIScrollable.h"
 #include "nsITabParent.h"
 #include "nsITextScroll.h"
-#include "nsITimer.h"
 #include "nsIWebNavigation.h"
 #include "nsIWebPageDescriptor.h"
 #include "nsIWebProgressListener.h"
 #include "nsIWebShellServices.h"
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsContentPolicyUtils.h"
@@ -110,44 +108,16 @@ class OnLinkClickEvent;
 
 /* internally used ViewMode types */
 enum ViewMode
 {
   viewNormal = 0x0,
   viewSource = 0x1
 };
 
-class nsRefreshTimer : public nsITimerCallback
-                     , public nsINamed
-{
-public:
-  nsRefreshTimer(nsDocShell* aDocShell,
-                 nsIURI* aURI,
-                 nsIPrincipal* aPrincipal,
-                 int32_t aDelay,
-                 bool aRepeat,
-                 bool aMetaRefresh);
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSITIMERCALLBACK
-  NS_DECL_NSINAMED
-
-  int32_t GetDelay() { return mDelay ;}
-
-  RefPtr<nsDocShell> mDocShell;
-  nsCOMPtr<nsIURI> mURI;
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  int32_t mDelay;
-  bool mRepeat;
-  bool mMetaRefresh;
-
-private:
-  virtual ~nsRefreshTimer();
-};
-
 enum eCharsetReloadState
 {
   eCharsetReloadInit,
   eCharsetReloadRequested,
   eCharsetReloadStopOrigional
 };
 
 class nsDocShell final
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsPingListener.cpp
@@ -0,0 +1,370 @@
+/* -*- 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 "nsPingListener.h"
+
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/DocGroup.h"
+
+#include "nsIDocument.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsIUploadChannel2.h"
+
+#include "nsDocument.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsWhitespaceTokenizer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
+
+//*****************************************************************************
+// <a ping> support
+//*****************************************************************************
+
+#define PREF_PINGS_ENABLED           "browser.send_pings"
+#define PREF_PINGS_MAX_PER_LINK      "browser.send_pings.max_per_link"
+#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
+
+// Check prefs to see if pings are enabled and if so what restrictions might
+// be applied.
+//
+// @param maxPerLink
+//   This parameter returns the number of pings that are allowed per link click
+//
+// @param requireSameHost
+//   This parameter returns true if pings are restricted to the same host as
+//   the document in which the click occurs.  If the same host restriction is
+//   imposed, then we still allow for pings to cross over to different
+//   protocols and ports for flexibility and because it is not possible to send
+//   a ping via FTP.
+//
+// @returns
+//   true if pings are enabled and false otherwise.
+//
+static bool
+PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost)
+{
+  bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
+
+  *aMaxPerLink = 1;
+  *aRequireSameHost = true;
+
+  if (allow) {
+    Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
+    Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
+  }
+
+  return allow;
+}
+
+// We wait this many milliseconds before killing the ping channel...
+#define PING_TIMEOUT 10000
+
+static void
+OnPingTimeout(nsITimer* aTimer, void* aClosure)
+{
+  nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
+  if (loadGroup) {
+    loadGroup->Cancel(NS_ERROR_ABORT);
+  }
+}
+
+struct MOZ_STACK_CLASS SendPingInfo
+{
+  int32_t numPings;
+  int32_t maxPings;
+  bool requireSameHost;
+  nsIURI* target;
+  nsIURI* referrer;
+  nsIDocShell* docShell;
+  uint32_t referrerPolicy;
+};
+
+static void
+SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
+         nsIIOService* aIOService)
+{
+  SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
+  if (info->maxPings > -1 && info->numPings >= info->maxPings) {
+    return;
+  }
+
+  nsIDocument* doc = aContent->OwnerDoc();
+
+  nsCOMPtr<nsIChannel> chan;
+  NS_NewChannel(getter_AddRefs(chan),
+                aURI,
+                doc,
+                info->requireSameHost
+                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                nsIContentPolicy::TYPE_PING,
+                nullptr, // aLoadGroup
+                nullptr, // aCallbacks
+                nsIRequest::LOAD_NORMAL, // aLoadFlags,
+                aIOService);
+
+  if (!chan) {
+    return;
+  }
+
+  // Don't bother caching the result of this URI load, but do not exempt
+  // it from Safe Browsing.
+  chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI);
+
+  nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
+  if (!httpChan) {
+    return;
+  }
+
+  // This is needed in order for 3rd-party cookie blocking to work.
+  nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
+  nsresult rv;
+  if (httpInternal) {
+    rv = httpInternal->SetDocumentURI(doc->GetDocumentURI());
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  rv = httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  // Remove extraneous request headers (to reduce request size)
+  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
+                                  EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"),
+                                  EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"),
+                                  EmptyCString(), false);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  // Always send a Ping-To header.
+  nsAutoCString pingTo;
+  if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
+    rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  nsCOMPtr<nsIScriptSecurityManager> sm =
+    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+  if (sm && info->referrer) {
+    bool referrerIsSecure;
+    uint32_t flags = nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT;
+    rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure);
+
+    // Default to sending less data if NS_URIChainHasFlags() fails.
+    referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;
+
+    bool sameOrigin =
+      NS_SUCCEEDED(sm->CheckSameOriginURI(info->referrer, aURI, false));
+
+    // If both the address of the document containing the hyperlink being
+    // audited and "ping URL" have the same origin or the document containing
+    // the hyperlink being audited was not retrieved over an encrypted
+    // connection, send a Ping-From header.
+    if (sameOrigin || !referrerIsSecure) {
+      nsAutoCString pingFrom;
+      if (NS_SUCCEEDED(info->referrer->GetSpec(pingFrom))) {
+        rv = httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"),
+                                        pingFrom, false);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+      }
+    }
+
+    // If the document containing the hyperlink being audited was not retrieved
+    // over an encrypted connection and its address does not have the same
+    // origin as "ping URL", send a referrer.
+    if (!sameOrigin && !referrerIsSecure) {
+      rv = httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+
+  nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
+  if (!uploadChan) {
+    return;
+  }
+
+  NS_NAMED_LITERAL_CSTRING(uploadData, "PING");
+
+  nsCOMPtr<nsIInputStream> uploadStream;
+  rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  uploadChan->ExplicitSetUploadStream(uploadStream,
+                                      NS_LITERAL_CSTRING("text/ping"),
+                                      uploadData.Length(),
+                                      NS_LITERAL_CSTRING("POST"), false);
+
+  // The channel needs to have a loadgroup associated with it, so that we can
+  // cancel the channel and any redirected channels it may create.
+  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+  if (!loadGroup) {
+    return;
+  }
+  nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
+  loadGroup->SetNotificationCallbacks(callbacks);
+  chan->SetLoadGroup(loadGroup);
+
+  RefPtr<nsPingListener> pingListener = new nsPingListener();
+  chan->AsyncOpen2(pingListener);
+
+  // Even if AsyncOpen failed, we still count this as a successful ping.  It's
+  // possible that AsyncOpen may have failed after triggering some background
+  // process that may have written something to the network.
+  info->numPings++;
+
+  // Prevent ping requests from stalling and never being garbage collected...
+  if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
+    // If we failed to setup the timer, then we should just cancel the channel
+    // because we won't be able to ensure that it goes away in a timely manner.
+    chan->Cancel(NS_ERROR_ABORT);
+    return;
+  }
+  // if the channel openend successfully, then make the pingListener hold
+  // a strong reference to the loadgroup which is released in ::OnStopRequest
+  pingListener->SetLoadGroup(loadGroup);
+}
+
+typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
+                                    nsIURI* uri, nsIIOService* ios);
+
+static void
+ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure)
+{
+  // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
+  //       since we'd still need to parse the resulting string.  Instead, we
+  //       just parse the raw attribute.  It might be nice if the content node
+  //       implemented an interface that exposed an enumeration of nsIURIs.
+
+  // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+  // or XHTML namespace.
+  if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area)) {
+    return;
+  }
+
+  nsAutoString value;
+  aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ping, value);
+  if (value.IsEmpty()) {
+    return;
+  }
+
+  nsCOMPtr<nsIIOService> ios = do_GetIOService();
+  if (!ios) {
+    return;
+  }
+
+  nsIDocument* doc = aContent->OwnerDoc();
+  nsAutoCString charset;
+  doc->GetDocumentCharacterSet()->Name(charset);
+
+  nsWhitespaceTokenizer tokenizer(value);
+
+  while (tokenizer.hasMoreTokens()) {
+    nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI();
+    ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()),
+                charset.get(), baseURI, getter_AddRefs(uri));
+    // if we can't generate a valid URI, then there is nothing to do
+    if (!uri) {
+      continue;
+    }
+    // Explicitly not allow loading data: URIs
+    bool isDataScheme =
+      (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme);
+
+    if (!isDataScheme) {
+      aCallback(aClosure, aContent, uri, ios);
+    }
+  }
+}
+
+// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
+/*static*/ void
+nsPingListener::DispatchPings(nsIDocShell* aDocShell,
+                              nsIContent* aContent,
+                              nsIURI* aTarget,
+                              nsIURI* aReferrer,
+                              uint32_t aReferrerPolicy)
+{
+  SendPingInfo info;
+
+  if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
+    return;
+  }
+  if (info.maxPings == 0) {
+    return;
+  }
+
+  info.numPings = 0;
+  info.target = aTarget;
+  info.referrer = aReferrer;
+  info.referrerPolicy = aReferrerPolicy;
+  info.docShell = aDocShell;
+
+  ForEachPing(aContent, SendPing, &info);
+}
+
+nsPingListener::~nsPingListener()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+}
+
+nsresult
+nsPingListener::StartTimeout(DocGroup* aDocGroup)
+{
+  NS_ENSURE_ARG(aDocGroup);
+
+  return NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer),
+                                     OnPingTimeout,
+                                     mLoadGroup,
+                                     PING_TIMEOUT,
+                                     nsITimer::TYPE_ONE_SHOT,
+                                     "nsPingListener::StartTimeout",
+                                     aDocGroup->EventTargetFor(TaskCategory::Network));
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+                                nsIInputStream* aStream, uint64_t aOffset,
+                                uint32_t aCount)
+{
+  uint32_t result;
+  return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+                              nsresult aStatus)
+{
+  mLoadGroup = nullptr;
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsPingListener.h
@@ -0,0 +1,55 @@
+/* -*- 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/. */
+
+#ifndef nsPingListener_h__
+#define nsPingListener_h__
+
+#include "nsIStreamListener.h"
+
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DocGroup;
+}
+}
+
+class nsIContent;
+class nsIDocShell;
+class nsILoadGroup;
+class nsITimer;
+class nsIURI;
+
+class nsPingListener final : public nsIStreamListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+  nsPingListener()
+  {
+  }
+
+  void SetLoadGroup(nsILoadGroup* aLoadGroup) {
+    mLoadGroup = aLoadGroup;
+  }
+
+  nsresult StartTimeout(mozilla::dom::DocGroup* aDocGroup);
+
+  static void DispatchPings(nsIDocShell* aDocShell,
+              nsIContent* aContent,
+              nsIURI* aTarget,
+              nsIURI* aReferrer,
+              uint32_t aReferrerPolicy);
+private:
+  ~nsPingListener();
+
+  nsCOMPtr<nsILoadGroup> mLoadGroup;
+  nsCOMPtr<nsITimer> mTimer;
+};
+
+#endif /* nsPingListener_h__ */
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.cpp
@@ -0,0 +1,56 @@
+/* -*- 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 "nsRefreshTimer.h"
+
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+#include "nsDocShell.h"
+
+NS_IMPL_ADDREF(nsRefreshTimer)
+NS_IMPL_RELEASE(nsRefreshTimer)
+
+NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell,
+                               nsIURI* aURI,
+                               nsIPrincipal* aPrincipal,
+                               int32_t aDelay, bool aRepeat, bool aMetaRefresh)
+  : mDocShell(aDocShell), mURI(aURI), mPrincipal(aPrincipal),
+    mDelay(aDelay), mRepeat(aRepeat),
+    mMetaRefresh(aMetaRefresh)
+{
+}
+
+nsRefreshTimer::~nsRefreshTimer()
+{
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::Notify(nsITimer* aTimer)
+{
+  NS_ASSERTION(mDocShell, "DocShell is somehow null");
+
+  if (mDocShell && aTimer) {
+    // Get the delay count to determine load type
+    uint32_t delay = 0;
+    aTimer->GetDelay(&delay);
+    mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, mMetaRefresh, aTimer);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::GetName(nsACString& aName)
+{
+  aName.AssignLiteral("nsRefreshTimer");
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+
+#ifndef nsRefreshTimer_h__
+#define nsRefreshTimer_h__
+
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+
+class nsDocShell;
+class nsIURI;
+class nsIPrincipal;
+
+class nsRefreshTimer : public nsITimerCallback
+                     , public nsINamed
+{
+public:
+  nsRefreshTimer(nsDocShell* aDocShell,
+                 nsIURI* aURI,
+                 nsIPrincipal* aPrincipal,
+                 int32_t aDelay,
+                 bool aRepeat,
+                 bool aMetaRefresh);
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSINAMED
+
+  int32_t GetDelay() { return mDelay ;}
+
+  RefPtr<nsDocShell> mDocShell;
+  nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  int32_t mDelay;
+  bool mRepeat;
+  bool mMetaRefresh;
+
+private:
+  virtual ~nsRefreshTimer();
+};
+
+#endif /* nsRefreshTimer_h__ */