summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLDNSPrefetch.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/html/HTMLDNSPrefetch.cpp647
1 files changed, 647 insertions, 0 deletions
diff --git a/dom/html/HTMLDNSPrefetch.cpp b/dom/html/HTMLDNSPrefetch.cpp
new file mode 100644
index 0000000000..a4043195fe
--- /dev/null
+++ b/dom/html/HTMLDNSPrefetch.cpp
@@ -0,0 +1,647 @@
+/* -*- 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 "HTMLDNSPrefetch.h"
+
+#include "base/basictypes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsURLHelper.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 "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/Document.h"
+#include "nsThreadUtils.h"
+#include "nsITimer.h"
+#include "nsIObserverService.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+
+using namespace mozilla::net;
+
+namespace mozilla::dom {
+
+class NoOpDNSListener final : public nsIDNSListener {
+ // This class exists to give a safe callback no-op DNSListener
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ NoOpDNSListener() = default;
+
+ private:
+ ~NoOpDNSListener() = default;
+};
+
+NS_IMPL_ISUPPORTS(NoOpDNSListener, nsIDNSListener)
+
+NS_IMETHODIMP
+NoOpDNSListener::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ return NS_OK;
+}
+
+// This is just a (size) optimization and could be avoided by storing the
+// SupportsDNSPrefetch pointer of the element in the prefetch queue, but given
+// we need this for GetURIForDNSPrefetch...
+static SupportsDNSPrefetch& ToSupportsDNSPrefetch(Element& aElement) {
+ if (auto* link = HTMLLinkElement::FromNode(aElement)) {
+ return *link;
+ }
+ auto* anchor = HTMLAnchorElement::FromNode(aElement);
+ MOZ_DIAGNOSTIC_ASSERT(anchor);
+ return *anchor;
+}
+
+nsIURI* SupportsDNSPrefetch::GetURIForDNSPrefetch(Element& aElement) {
+ MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == this);
+ if (auto* link = HTMLLinkElement::FromNode(aElement)) {
+ return link->GetURI();
+ }
+ auto* anchor = HTMLAnchorElement::FromNode(aElement);
+ MOZ_DIAGNOSTIC_ASSERT(anchor);
+ return anchor->GetURI();
+}
+
+class DeferredDNSPrefetches final : public nsIWebProgressListener,
+ public nsSupportsWeakReference,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIOBSERVER
+
+ DeferredDNSPrefetches();
+
+ void Activate();
+ nsresult Add(nsIDNSService::DNSFlags flags, SupportsDNSPrefetch&, Element&);
+
+ void RemoveUnboundLinks();
+
+ private:
+ ~DeferredDNSPrefetches();
+ void Flush();
+
+ void SubmitQueue();
+ void SubmitQueueEntry(Element&, nsIDNSService::DNSFlags aFlags);
+
+ uint16_t mHead;
+ uint16_t mTail;
+ uint32_t mActiveLoaderCount;
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed;
+ static void Tick(nsITimer* aTimer, void* aClosure);
+
+ static const int sMaxDeferred = 512; // keep power of 2 for masking
+ static const int sMaxDeferredMask = (sMaxDeferred - 1);
+
+ struct deferred_entry {
+ nsIDNSService::DNSFlags mFlags;
+ // SupportsDNSPrefetch clears this raw pointer in Destroyed().
+ Element* mElement;
+ } mEntries[sMaxDeferred];
+};
+
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+static bool sInitialized = false;
+static nsIDNSService* sDNSService = nullptr;
+static DeferredDNSPrefetches* sPrefetches = nullptr;
+static NoOpDNSListener* sDNSListener = nullptr;
+
+nsresult HTMLDNSPrefetch::Initialize() {
+ if (sInitialized) {
+ NS_WARNING("Initialize() called twice");
+ return NS_OK;
+ }
+
+ sPrefetches = new DeferredDNSPrefetches();
+ NS_ADDREF(sPrefetches);
+
+ sDNSListener = new NoOpDNSListener();
+ NS_ADDREF(sDNSListener);
+
+ sPrefetches->Activate();
+
+ if (IsNeckoChild()) NeckoChild::InitNeckoChild();
+
+ sInitialized = true;
+ return NS_OK;
+}
+
+nsresult HTMLDNSPrefetch::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;
+}
+
+static bool EnsureDNSService() {
+ if (sDNSService) {
+ return true;
+ }
+
+ NS_IF_RELEASE(sDNSService);
+ nsresult rv;
+ rv = CallGetService(kDNSServiceCID, &sDNSService);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return !!sDNSService;
+}
+
+bool HTMLDNSPrefetch::IsAllowed(Document* aDocument) {
+ // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
+ return aDocument->IsDNSPrefetchAllowed() && aDocument->GetWindow();
+}
+
+static nsIDNSService::DNSFlags GetDNSFlagsFromElement(Element& aElement) {
+ nsIChannel* channel = aElement.OwnerDoc()->GetChannel();
+ if (!channel) {
+ return nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ }
+ return nsIDNSService::GetFlagsFromTRRMode(channel->GetTRRMode());
+}
+
+nsIDNSService::DNSFlags HTMLDNSPrefetch::PriorityToDNSServiceFlags(
+ Priority aPriority) {
+ switch (aPriority) {
+ case Priority::Low:
+ return nsIDNSService::RESOLVE_PRIORITY_LOW;
+ case Priority::Medium:
+ return nsIDNSService::RESOLVE_PRIORITY_MEDIUM;
+ case Priority::High:
+ return nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown priority");
+ return nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+}
+
+nsresult HTMLDNSPrefetch::Prefetch(SupportsDNSPrefetch& aSupports,
+ Element& aElement, Priority aPriority) {
+ MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
+ if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return sPrefetches->Add(
+ GetDNSFlagsFromElement(aElement) | PriorityToDNSServiceFlags(aPriority),
+ aSupports, aElement);
+}
+
+nsresult HTMLDNSPrefetch::Prefetch(
+ const nsAString& hostname, bool isHttps,
+ const OriginAttributes& aPartitionedPrincipalOriginAttributes,
+ nsIDNSService::DNSFlags 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(
+ hostname, isHttps, aPartitionedPrincipalOriginAttributes, flags);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ nsresult rv = sDNSService->AsyncResolveNative(
+ NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, sDNSListener, nullptr,
+ aPartitionedPrincipalOriginAttributes, getter_AddRefs(tmpOutstanding));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ Unused << sDNSService->AsyncResolveNative(
+ NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, sDNSListener,
+ nullptr, aPartitionedPrincipalOriginAttributes,
+ getter_AddRefs(tmpOutstanding));
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLDNSPrefetch::Prefetch(
+ const nsAString& hostname, bool isHttps,
+ const OriginAttributes& aPartitionedPrincipalOriginAttributes,
+ nsIRequest::TRRMode aMode, Priority aPriority) {
+ return Prefetch(hostname, isHttps, aPartitionedPrincipalOriginAttributes,
+ nsIDNSService::GetFlagsFromTRRMode(aMode) |
+ PriorityToDNSServiceFlags(aPriority));
+}
+
+nsresult HTMLDNSPrefetch::CancelPrefetch(SupportsDNSPrefetch& aSupports,
+ Element& aElement, Priority aPriority,
+ nsresult aReason) {
+ MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
+
+ if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIDNSService::DNSFlags flags =
+ GetDNSFlagsFromElement(aElement) | PriorityToDNSServiceFlags(aPriority);
+
+ nsIURI* uri = aSupports.GetURIForDNSPrefetch(aElement);
+ if (!uri) {
+ return NS_OK;
+ }
+
+ nsAutoCString hostname;
+ uri->GetAsciiHost(hostname);
+
+ nsAutoString protocol;
+ bool isHttps = uri->SchemeIs("https");
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ aElement.OwnerDoc(), oa);
+
+ return CancelPrefetch(NS_ConvertUTF8toUTF16(hostname), isHttps, oa, flags,
+ aReason);
+}
+
+nsresult HTMLDNSPrefetch::CancelPrefetch(
+ const nsAString& hostname, bool isHttps,
+ const OriginAttributes& aPartitionedPrincipalOriginAttributes,
+ nsIDNSService::DNSFlags 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(
+ hostname, isHttps, aPartitionedPrincipalOriginAttributes, flags,
+ aReason);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Forward cancellation to DNS service
+ nsresult rv = sDNSService->CancelAsyncResolveNative(
+ NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ flags | nsIDNSService::RESOLVE_SPECULATE,
+ nullptr, // AdditionalInfo
+ sDNSListener, aReason, aPartitionedPrincipalOriginAttributes);
+
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ Unused << sDNSService->CancelAsyncResolveNative(
+ NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ flags | nsIDNSService::RESOLVE_SPECULATE,
+ nullptr, // AdditionalInfo
+ sDNSListener, aReason, aPartitionedPrincipalOriginAttributes);
+ }
+ return rv;
+}
+
+nsresult HTMLDNSPrefetch::CancelPrefetch(
+ const nsAString& hostname, bool isHttps,
+ const OriginAttributes& aPartitionedPrincipalOriginAttributes,
+ nsIRequest::TRRMode aTRRMode, Priority aPriority, nsresult aReason) {
+ return CancelPrefetch(hostname, isHttps,
+ aPartitionedPrincipalOriginAttributes,
+ nsIDNSService::GetFlagsFromTRRMode(aTRRMode) |
+ PriorityToDNSServiceFlags(aPriority),
+ aReason);
+}
+
+void HTMLDNSPrefetch::ElementDestroyed(Element& aElement,
+ SupportsDNSPrefetch& aSupports) {
+ MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
+ MOZ_ASSERT(aSupports.IsInDNSPrefetch());
+ if (sPrefetches) {
+ // Clean up all the possible links at once.
+ sPrefetches->RemoveUnboundLinks();
+ }
+}
+
+void SupportsDNSPrefetch::TryDNSPrefetch(Element& aOwner) {
+ MOZ_ASSERT(aOwner.IsInComposedDoc());
+ if (HTMLDNSPrefetch::IsAllowed(aOwner.OwnerDoc())) {
+ HTMLDNSPrefetch::Prefetch(*this, aOwner, HTMLDNSPrefetch::Priority::Low);
+ }
+}
+
+void SupportsDNSPrefetch::CancelDNSPrefetch(Element& aOwner) {
+ // If prefetch was deferred, clear flag and move on
+ if (mDNSPrefetchDeferred) {
+ mDNSPrefetchDeferred = false;
+ // Else if prefetch was requested, clear flag and send cancellation
+ } else if (mDNSPrefetchRequested) {
+ mDNSPrefetchRequested = false;
+ // Possible that hostname could have changed since binding, but since this
+ // covers common cases, most DNS prefetch requests will be canceled
+ HTMLDNSPrefetch::CancelPrefetch(
+ *this, aOwner, HTMLDNSPrefetch::Priority::Low, NS_ERROR_ABORT);
+ }
+}
+
+DeferredDNSPrefetches::DeferredDNSPrefetches()
+ : mHead(0), mTail(0), mActiveLoaderCount(0), mTimerArmed(false) {
+ mTimer = NS_NewTimer();
+}
+
+DeferredDNSPrefetches::~DeferredDNSPrefetches() {
+ if (mTimerArmed) {
+ mTimerArmed = false;
+ mTimer->Cancel();
+ }
+
+ Flush();
+}
+
+NS_IMPL_ISUPPORTS(DeferredDNSPrefetches, nsIWebProgressListener,
+ nsISupportsWeakReference, nsIObserver)
+
+void DeferredDNSPrefetches::Flush() {
+ for (; mHead != mTail; mTail = (mTail + 1) & sMaxDeferredMask) {
+ Element* element = mEntries[mTail].mElement;
+ if (element) {
+ ToSupportsDNSPrefetch(*element).ClearIsInDNSPrefetch();
+ }
+ mEntries[mTail].mElement = nullptr;
+ }
+}
+
+nsresult DeferredDNSPrefetches::Add(nsIDNSService::DNSFlags flags,
+ SupportsDNSPrefetch& aSupports,
+ Element& aElement) {
+ // The FIFO has no lock, so it can only be accessed on main thread
+ NS_ASSERTION(NS_IsMainThread(),
+ "DeferredDNSPrefetches::Add must be on main thread");
+
+ aSupports.DNSPrefetchRequestDeferred();
+
+ if (((mHead + 1) & sMaxDeferredMask) == mTail) {
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+ }
+
+ aSupports.SetIsInDNSPrefetch();
+ mEntries[mHead].mFlags = flags;
+ mEntries[mHead].mElement = &aElement;
+ mHead = (mHead + 1) & sMaxDeferredMask;
+
+ if (!mActiveLoaderCount && !mTimerArmed && mTimer) {
+ mTimerArmed = true;
+ mTimer->InitWithNamedFuncCallback(
+ Tick, this, 2000, nsITimer::TYPE_ONE_SHOT,
+ "HTMLDNSPrefetch::DeferredDNSPrefetches::Tick");
+ }
+
+ return NS_OK;
+}
+
+void DeferredDNSPrefetches::SubmitQueue() {
+ NS_ASSERTION(NS_IsMainThread(),
+ "DeferredDNSPrefetches::SubmitQueue must be on main thread");
+ if (!EnsureDNSService()) {
+ return;
+ }
+
+ for (; mHead != mTail; mTail = (mTail + 1) & sMaxDeferredMask) {
+ Element* element = mEntries[mTail].mElement;
+ if (!element) {
+ continue;
+ }
+ SubmitQueueEntry(*element, mEntries[mTail].mFlags);
+ mEntries[mTail].mElement = nullptr;
+ }
+
+ if (mTimerArmed) {
+ mTimerArmed = false;
+ mTimer->Cancel();
+ }
+}
+
+void DeferredDNSPrefetches::SubmitQueueEntry(Element& aElement,
+ nsIDNSService::DNSFlags aFlags) {
+ auto& supports = ToSupportsDNSPrefetch(aElement);
+ supports.ClearIsInDNSPrefetch();
+
+ // Only prefetch here if request was deferred and deferral not cancelled
+ if (!supports.IsDNSPrefetchRequestDeferred()) {
+ return;
+ }
+
+ nsIURI* uri = supports.GetURIForDNSPrefetch(aElement);
+ if (!uri) {
+ return;
+ }
+
+ nsAutoCString hostName;
+ uri->GetAsciiHost(hostName);
+ if (hostName.IsEmpty()) {
+ return;
+ }
+
+ bool isLocalResource = false;
+ nsresult rv = NS_URIChainHasFlags(
+ uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &isLocalResource);
+ if (NS_FAILED(rv) || isLocalResource) {
+ return;
+ }
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(
+ aElement.OwnerDoc(), oa);
+
+ bool isHttps = uri->SchemeIs("https");
+
+ if (IsNeckoChild()) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName), isHttps,
+ oa, mEntries[mTail].mFlags);
+ }
+ } else {
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ rv = sDNSService->AsyncResolveNative(
+ hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr,
+ sDNSListener, nullptr, oa, getter_AddRefs(tmpOutstanding));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Fetch HTTPS RR if needed.
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ sDNSService->AsyncResolveNative(
+ hostName, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr,
+ sDNSListener, nullptr, oa, getter_AddRefs(tmpOutstanding));
+ }
+ }
+
+ // Tell element that deferred prefetch was requested.
+ supports.DNSPrefetchRequestStarted();
+}
+
+void DeferredDNSPrefetches::Activate() {
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
+ 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);
+}
+
+void DeferredDNSPrefetches::RemoveUnboundLinks() {
+ uint16_t tail = mTail;
+ while (mHead != tail) {
+ Element* element = mEntries[tail].mElement;
+ if (element && !element->IsInComposedDoc()) {
+ ToSupportsDNSPrefetch(*element).ClearIsInDNSPrefetch();
+ mEntries[tail].mElement = nullptr;
+ }
+ tail = (tail + 1) & sMaxDeferredMask;
+ }
+}
+
+// nsITimer related method
+
+void DeferredDNSPrefetches::Tick(nsITimer* aTimer, void* aClosure) {
+ auto* self = static_cast<DeferredDNSPrefetches*>(aClosure);
+
+ NS_ASSERTION(NS_IsMainThread(),
+ "DeferredDNSPrefetches::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
+DeferredDNSPrefetches::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(),
+ "DeferredDNSPrefetches::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
+DeferredDNSPrefetches::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DeferredDNSPrefetches::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* location,
+ uint32_t aFlags) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DeferredDNSPrefetches::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DeferredDNSPrefetches::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DeferredDNSPrefetches::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+//////////// nsIObserver method
+
+NS_IMETHODIMP
+DeferredDNSPrefetches::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, "xpcom-shutdown")) Flush();
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom