1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "HTMLDNSPrefetch.h"
8 
9 #include "base/basictypes.h"
10 #include "mozilla/dom/Element.h"
11 #include "mozilla/dom/HTMLLinkElement.h"
12 #include "mozilla/dom/HTMLAnchorElement.h"
13 #include "mozilla/net/NeckoCommon.h"
14 #include "mozilla/net/NeckoChild.h"
15 #include "mozilla/OriginAttributes.h"
16 #include "mozilla/StoragePrincipalHelper.h"
17 #include "nsURLHelper.h"
18 
19 #include "nsCOMPtr.h"
20 #include "nsString.h"
21 
22 #include "nsNetUtil.h"
23 #include "nsNetCID.h"
24 #include "nsIProtocolHandler.h"
25 
26 #include "nsIDNSListener.h"
27 #include "nsIWebProgressListener.h"
28 #include "nsIWebProgress.h"
29 #include "nsIDNSRecord.h"
30 #include "nsIDNSService.h"
31 #include "nsICancelable.h"
32 #include "nsGkAtoms.h"
33 #include "mozilla/dom/Document.h"
34 #include "nsThreadUtils.h"
35 #include "nsITimer.h"
36 #include "nsIObserverService.h"
37 
38 #include "mozilla/Components.h"
39 #include "mozilla/Preferences.h"
40 #include "mozilla/StaticPrefs_network.h"
41 
42 using namespace mozilla::net;
43 
44 namespace mozilla {
45 namespace dom {
46 
47 class NoOpDNSListener final : public nsIDNSListener {
48   // This class exists to give a safe callback no-op DNSListener
49  public:
50   NS_DECL_THREADSAFE_ISUPPORTS
51   NS_DECL_NSIDNSLISTENER
52 
53   NoOpDNSListener() = default;
54 
55  private:
56   ~NoOpDNSListener() = default;
57 };
58 
NS_IMPL_ISUPPORTS(NoOpDNSListener,nsIDNSListener)59 NS_IMPL_ISUPPORTS(NoOpDNSListener, nsIDNSListener)
60 
61 NS_IMETHODIMP
62 NoOpDNSListener::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
63                                   nsresult status) {
64   return NS_OK;
65 }
66 
67 // This is just a (size) optimization and could be avoided by storing the
68 // SupportsDNSPrefetch pointer of the element in the prefetch queue, but given
69 // we need this for GetURIForDNSPrefetch...
ToSupportsDNSPrefetch(Element & aElement)70 static SupportsDNSPrefetch& ToSupportsDNSPrefetch(Element& aElement) {
71   if (auto* link = HTMLLinkElement::FromNode(aElement)) {
72     return *link;
73   }
74   auto* anchor = HTMLAnchorElement::FromNode(aElement);
75   MOZ_DIAGNOSTIC_ASSERT(anchor);
76   return *anchor;
77 }
78 
GetURIForDNSPrefetch(Element & aElement)79 nsIURI* SupportsDNSPrefetch::GetURIForDNSPrefetch(Element& aElement) {
80   MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == this);
81   if (auto* link = HTMLLinkElement::FromNode(aElement)) {
82     return link->GetURI();
83   }
84   auto* anchor = HTMLAnchorElement::FromNode(aElement);
85   MOZ_DIAGNOSTIC_ASSERT(anchor);
86   return anchor->GetURI();
87 }
88 
89 class DeferredDNSPrefetches final : public nsIWebProgressListener,
90                                     public nsSupportsWeakReference,
91                                     public nsIObserver {
92  public:
93   NS_DECL_ISUPPORTS
94   NS_DECL_NSIWEBPROGRESSLISTENER
95   NS_DECL_NSIOBSERVER
96 
97   DeferredDNSPrefetches();
98 
99   void Activate();
100   nsresult Add(uint32_t flags, SupportsDNSPrefetch&, Element&);
101 
102   void RemoveUnboundLinks();
103 
104  private:
105   ~DeferredDNSPrefetches();
106   void Flush();
107 
108   void SubmitQueue();
109   void SubmitQueueEntry(Element&, uint32_t aFlags);
110 
111   uint16_t mHead;
112   uint16_t mTail;
113   uint32_t mActiveLoaderCount;
114 
115   nsCOMPtr<nsITimer> mTimer;
116   bool mTimerArmed;
117   static void Tick(nsITimer* aTimer, void* aClosure);
118 
119   static const int sMaxDeferred = 512;  // keep power of 2 for masking
120   static const int sMaxDeferredMask = (sMaxDeferred - 1);
121 
122   struct deferred_entry {
123     uint32_t mFlags;
124     // SupportsDNSPrefetch clears this raw pointer in Destroyed().
125     Element* mElement;
126   } mEntries[sMaxDeferred];
127 };
128 
129 static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
130 static bool sInitialized = false;
131 static nsIDNSService* sDNSService = nullptr;
132 static DeferredDNSPrefetches* sPrefetches = nullptr;
133 static NoOpDNSListener* sDNSListener = nullptr;
134 
Initialize()135 nsresult HTMLDNSPrefetch::Initialize() {
136   if (sInitialized) {
137     NS_WARNING("Initialize() called twice");
138     return NS_OK;
139   }
140 
141   sPrefetches = new DeferredDNSPrefetches();
142   NS_ADDREF(sPrefetches);
143 
144   sDNSListener = new NoOpDNSListener();
145   NS_ADDREF(sDNSListener);
146 
147   sPrefetches->Activate();
148 
149   if (IsNeckoChild()) NeckoChild::InitNeckoChild();
150 
151   sInitialized = true;
152   return NS_OK;
153 }
154 
Shutdown()155 nsresult HTMLDNSPrefetch::Shutdown() {
156   if (!sInitialized) {
157     NS_WARNING("Not Initialized");
158     return NS_OK;
159   }
160   sInitialized = false;
161   NS_IF_RELEASE(sDNSService);
162   NS_IF_RELEASE(sPrefetches);
163   NS_IF_RELEASE(sDNSListener);
164 
165   return NS_OK;
166 }
167 
EnsureDNSService()168 static bool EnsureDNSService() {
169   if (sDNSService) {
170     return true;
171   }
172 
173   NS_IF_RELEASE(sDNSService);
174   nsresult rv;
175   rv = CallGetService(kDNSServiceCID, &sDNSService);
176   if (NS_FAILED(rv)) {
177     return false;
178   }
179 
180   return !!sDNSService;
181 }
182 
IsAllowed(Document * aDocument)183 bool HTMLDNSPrefetch::IsAllowed(Document* aDocument) {
184   // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
185   return aDocument->IsDNSPrefetchAllowed() && aDocument->GetWindow();
186 }
187 
GetDNSFlagsFromElement(Element & aElement)188 static uint32_t GetDNSFlagsFromElement(Element& aElement) {
189   nsIChannel* channel = aElement.OwnerDoc()->GetChannel();
190   if (!channel) {
191     return 0;
192   }
193   return nsIDNSService::GetFlagsFromTRRMode(channel->GetTRRMode());
194 }
195 
PriorityToDNSServiceFlags(Priority aPriority)196 uint32_t HTMLDNSPrefetch::PriorityToDNSServiceFlags(Priority aPriority) {
197   switch (aPriority) {
198     case Priority::Low:
199       return uint32_t(nsIDNSService::RESOLVE_PRIORITY_LOW);
200     case Priority::Medium:
201       return uint32_t(nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
202     case Priority::High:
203       return 0u;
204   }
205   MOZ_ASSERT_UNREACHABLE("Unknown priority");
206   return 0u;
207 }
208 
Prefetch(SupportsDNSPrefetch & aSupports,Element & aElement,Priority aPriority)209 nsresult HTMLDNSPrefetch::Prefetch(SupportsDNSPrefetch& aSupports,
210                                    Element& aElement, Priority aPriority) {
211   MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
212   if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
213     return NS_ERROR_NOT_AVAILABLE;
214   }
215   return sPrefetches->Add(
216       GetDNSFlagsFromElement(aElement) | PriorityToDNSServiceFlags(aPriority),
217       aSupports, aElement);
218 }
219 
Prefetch(const nsAString & hostname,bool isHttps,const OriginAttributes & aPartitionedPrincipalOriginAttributes,uint32_t flags)220 nsresult HTMLDNSPrefetch::Prefetch(
221     const nsAString& hostname, bool isHttps,
222     const OriginAttributes& aPartitionedPrincipalOriginAttributes,
223     uint32_t flags) {
224   if (IsNeckoChild()) {
225     // We need to check IsEmpty() because net_IsValidHostName()
226     // considers empty strings to be valid hostnames
227     if (!hostname.IsEmpty() &&
228         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
229       // during shutdown gNeckoChild might be null
230       if (gNeckoChild) {
231         gNeckoChild->SendHTMLDNSPrefetch(nsString(hostname), isHttps,
232                                          aPartitionedPrincipalOriginAttributes,
233                                          flags);
234       }
235     }
236     return NS_OK;
237   }
238 
239   if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService())
240     return NS_ERROR_NOT_AVAILABLE;
241 
242   nsCOMPtr<nsICancelable> tmpOutstanding;
243   nsresult rv = sDNSService->AsyncResolveNative(
244       NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_DEFAULT,
245       flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, sDNSListener, nullptr,
246       aPartitionedPrincipalOriginAttributes, getter_AddRefs(tmpOutstanding));
247   if (NS_FAILED(rv)) {
248     return rv;
249   }
250 
251   if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
252       StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
253     Unused << sDNSService->AsyncResolveNative(
254         NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
255         flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, sDNSListener,
256         nullptr, aPartitionedPrincipalOriginAttributes,
257         getter_AddRefs(tmpOutstanding));
258   }
259 
260   return NS_OK;
261 }
262 
Prefetch(const nsAString & hostname,bool isHttps,const OriginAttributes & aPartitionedPrincipalOriginAttributes,nsIRequest::TRRMode aMode,Priority aPriority)263 nsresult HTMLDNSPrefetch::Prefetch(
264     const nsAString& hostname, bool isHttps,
265     const OriginAttributes& aPartitionedPrincipalOriginAttributes,
266     nsIRequest::TRRMode aMode, Priority aPriority) {
267   return Prefetch(hostname, isHttps, aPartitionedPrincipalOriginAttributes,
268                   nsIDNSService::GetFlagsFromTRRMode(aMode) |
269                       PriorityToDNSServiceFlags(aPriority));
270 }
271 
CancelPrefetch(SupportsDNSPrefetch & aSupports,Element & aElement,Priority aPriority,nsresult aReason)272 nsresult HTMLDNSPrefetch::CancelPrefetch(SupportsDNSPrefetch& aSupports,
273                                          Element& aElement, Priority aPriority,
274                                          nsresult aReason) {
275   MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
276 
277   if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
278     return NS_ERROR_NOT_AVAILABLE;
279   }
280 
281   uint32_t flags =
282       GetDNSFlagsFromElement(aElement) | PriorityToDNSServiceFlags(aPriority);
283 
284   nsIURI* uri = aSupports.GetURIForDNSPrefetch(aElement);
285   if (!uri) {
286     return NS_OK;
287   }
288 
289   nsAutoCString hostname;
290   uri->GetAsciiHost(hostname);
291 
292   nsAutoString protocol;
293   bool isHttps = uri->SchemeIs("https");
294 
295   OriginAttributes oa;
296   StoragePrincipalHelper::GetOriginAttributesForNetworkState(
297       aElement.OwnerDoc(), oa);
298 
299   return CancelPrefetch(NS_ConvertUTF8toUTF16(hostname), isHttps, oa, flags,
300                         aReason);
301 }
302 
CancelPrefetch(const nsAString & hostname,bool isHttps,const OriginAttributes & aPartitionedPrincipalOriginAttributes,uint32_t flags,nsresult aReason)303 nsresult HTMLDNSPrefetch::CancelPrefetch(
304     const nsAString& hostname, bool isHttps,
305     const OriginAttributes& aPartitionedPrincipalOriginAttributes,
306     uint32_t flags, nsresult aReason) {
307   // Forward this request to Necko Parent if we're a child process
308   if (IsNeckoChild()) {
309     // We need to check IsEmpty() because net_IsValidHostName()
310     // considers empty strings to be valid hostnames
311     if (!hostname.IsEmpty() &&
312         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
313       // during shutdown gNeckoChild might be null
314       if (gNeckoChild) {
315         gNeckoChild->SendCancelHTMLDNSPrefetch(
316             nsString(hostname), isHttps, aPartitionedPrincipalOriginAttributes,
317             flags, aReason);
318       }
319     }
320     return NS_OK;
321   }
322 
323   if (!(sInitialized && sPrefetches && sDNSListener) || !EnsureDNSService()) {
324     return NS_ERROR_NOT_AVAILABLE;
325   }
326 
327   // Forward cancellation to DNS service
328   nsresult rv = sDNSService->CancelAsyncResolveNative(
329       NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_DEFAULT,
330       flags | nsIDNSService::RESOLVE_SPECULATE,
331       nullptr,  // resolverInfo
332       sDNSListener, aReason, aPartitionedPrincipalOriginAttributes);
333 
334   if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
335       StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
336     Unused << sDNSService->CancelAsyncResolveNative(
337         NS_ConvertUTF16toUTF8(hostname), nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
338         flags | nsIDNSService::RESOLVE_SPECULATE,
339         nullptr,  // resolverInfo
340         sDNSListener, aReason, aPartitionedPrincipalOriginAttributes);
341   }
342   return rv;
343 }
344 
CancelPrefetch(const nsAString & hostname,bool isHttps,const OriginAttributes & aPartitionedPrincipalOriginAttributes,nsIRequest::TRRMode aTRRMode,Priority aPriority,nsresult aReason)345 nsresult HTMLDNSPrefetch::CancelPrefetch(
346     const nsAString& hostname, bool isHttps,
347     const OriginAttributes& aPartitionedPrincipalOriginAttributes,
348     nsIRequest::TRRMode aTRRMode, Priority aPriority, nsresult aReason) {
349   return CancelPrefetch(hostname, isHttps,
350                         aPartitionedPrincipalOriginAttributes,
351                         nsIDNSService::GetFlagsFromTRRMode(aTRRMode) |
352                             PriorityToDNSServiceFlags(aPriority),
353                         aReason);
354 }
355 
ElementDestroyed(Element & aElement,SupportsDNSPrefetch & aSupports)356 void HTMLDNSPrefetch::ElementDestroyed(Element& aElement,
357                                        SupportsDNSPrefetch& aSupports) {
358   MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement) == &aSupports);
359   MOZ_ASSERT(aSupports.IsInDNSPrefetch());
360   if (sPrefetches) {
361     // Clean up all the possible links at once.
362     sPrefetches->RemoveUnboundLinks();
363   }
364 }
365 
TryDNSPrefetch(Element & aOwner)366 void SupportsDNSPrefetch::TryDNSPrefetch(Element& aOwner) {
367   MOZ_ASSERT(aOwner.IsInComposedDoc());
368   if (HTMLDNSPrefetch::IsAllowed(aOwner.OwnerDoc())) {
369     HTMLDNSPrefetch::Prefetch(*this, aOwner, HTMLDNSPrefetch::Priority::Low);
370   }
371 }
372 
CancelDNSPrefetch(Element & aOwner)373 void SupportsDNSPrefetch::CancelDNSPrefetch(Element& aOwner) {
374   // If prefetch was deferred, clear flag and move on
375   if (mDNSPrefetchDeferred) {
376     mDNSPrefetchDeferred = false;
377     // Else if prefetch was requested, clear flag and send cancellation
378   } else if (mDNSPrefetchRequested) {
379     mDNSPrefetchRequested = false;
380     // Possible that hostname could have changed since binding, but since this
381     // covers common cases, most DNS prefetch requests will be canceled
382     HTMLDNSPrefetch::CancelPrefetch(
383         *this, aOwner, HTMLDNSPrefetch::Priority::Low, NS_ERROR_ABORT);
384   }
385 }
386 
DeferredDNSPrefetches()387 DeferredDNSPrefetches::DeferredDNSPrefetches()
388     : mHead(0), mTail(0), mActiveLoaderCount(0), mTimerArmed(false) {
389   mTimer = NS_NewTimer();
390 }
391 
~DeferredDNSPrefetches()392 DeferredDNSPrefetches::~DeferredDNSPrefetches() {
393   if (mTimerArmed) {
394     mTimerArmed = false;
395     mTimer->Cancel();
396   }
397 
398   Flush();
399 }
400 
NS_IMPL_ISUPPORTS(DeferredDNSPrefetches,nsIWebProgressListener,nsISupportsWeakReference,nsIObserver)401 NS_IMPL_ISUPPORTS(DeferredDNSPrefetches, nsIWebProgressListener,
402                   nsISupportsWeakReference, nsIObserver)
403 
404 void DeferredDNSPrefetches::Flush() {
405   for (; mHead != mTail; mTail = (mTail + 1) & sMaxDeferredMask) {
406     Element* element = mEntries[mTail].mElement;
407     if (element) {
408       ToSupportsDNSPrefetch(*element).ClearIsInDNSPrefetch();
409     }
410     mEntries[mTail].mElement = nullptr;
411   }
412 }
413 
Add(uint32_t flags,SupportsDNSPrefetch & aSupports,Element & aElement)414 nsresult DeferredDNSPrefetches::Add(uint32_t flags,
415                                     SupportsDNSPrefetch& aSupports,
416                                     Element& aElement) {
417   // The FIFO has no lock, so it can only be accessed on main thread
418   NS_ASSERTION(NS_IsMainThread(),
419                "DeferredDNSPrefetches::Add must be on main thread");
420 
421   aSupports.DNSPrefetchRequestDeferred();
422 
423   if (((mHead + 1) & sMaxDeferredMask) == mTail) {
424     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
425   }
426 
427   aSupports.SetIsInDNSPrefetch();
428   mEntries[mHead].mFlags = flags;
429   mEntries[mHead].mElement = &aElement;
430   mHead = (mHead + 1) & sMaxDeferredMask;
431 
432   if (!mActiveLoaderCount && !mTimerArmed && mTimer) {
433     mTimerArmed = true;
434     mTimer->InitWithNamedFuncCallback(
435         Tick, this, 2000, nsITimer::TYPE_ONE_SHOT,
436         "HTMLDNSPrefetch::DeferredDNSPrefetches::Tick");
437   }
438 
439   return NS_OK;
440 }
441 
SubmitQueue()442 void DeferredDNSPrefetches::SubmitQueue() {
443   NS_ASSERTION(NS_IsMainThread(),
444                "DeferredDNSPrefetches::SubmitQueue must be on main thread");
445   if (!EnsureDNSService()) {
446     return;
447   }
448 
449   for (; mHead != mTail; mTail = (mTail + 1) & sMaxDeferredMask) {
450     Element* element = mEntries[mTail].mElement;
451     if (!element) {
452       continue;
453     }
454     SubmitQueueEntry(*element, mEntries[mTail].mFlags);
455     mEntries[mTail].mElement = nullptr;
456   }
457 
458   if (mTimerArmed) {
459     mTimerArmed = false;
460     mTimer->Cancel();
461   }
462 }
463 
SubmitQueueEntry(Element & aElement,uint32_t aFlags)464 void DeferredDNSPrefetches::SubmitQueueEntry(Element& aElement,
465                                              uint32_t aFlags) {
466   auto& supports = ToSupportsDNSPrefetch(aElement);
467   supports.ClearIsInDNSPrefetch();
468 
469   // Only prefetch here if request was deferred and deferral not cancelled
470   if (!supports.IsDNSPrefetchRequestDeferred()) {
471     return;
472   }
473 
474   nsIURI* uri = supports.GetURIForDNSPrefetch(aElement);
475   if (!uri) {
476     return;
477   }
478 
479   nsAutoCString hostName;
480   uri->GetAsciiHost(hostName);
481   if (hostName.IsEmpty()) {
482     return;
483   }
484 
485   bool isLocalResource = false;
486   nsresult rv = NS_URIChainHasFlags(
487       uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &isLocalResource);
488   if (NS_FAILED(rv) || isLocalResource) {
489     return;
490   }
491 
492   OriginAttributes oa;
493   StoragePrincipalHelper::GetOriginAttributesForNetworkState(
494       aElement.OwnerDoc(), oa);
495 
496   bool isHttps = uri->SchemeIs("https");
497 
498   if (IsNeckoChild()) {
499     // during shutdown gNeckoChild might be null
500     if (gNeckoChild) {
501       gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName), isHttps,
502                                        oa, mEntries[mTail].mFlags);
503     }
504   } else {
505     nsCOMPtr<nsICancelable> tmpOutstanding;
506 
507     rv = sDNSService->AsyncResolveNative(
508         hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT,
509         mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr,
510         sDNSListener, nullptr, oa, getter_AddRefs(tmpOutstanding));
511     if (NS_FAILED(rv)) {
512       return;
513     }
514 
515     // Fetch HTTPS RR if needed.
516     if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
517         StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
518       sDNSService->AsyncResolveNative(
519           hostName, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
520           mEntries[mTail].mFlags | nsIDNSService::RESOLVE_SPECULATE, nullptr,
521           sDNSListener, nullptr, oa, getter_AddRefs(tmpOutstanding));
522     }
523   }
524 
525   // Tell element that deferred prefetch was requested.
526   supports.DNSPrefetchRequestStarted();
527 }
528 
Activate()529 void DeferredDNSPrefetches::Activate() {
530   // Register as an observer for the document loader
531   nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
532   if (progress)
533     progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
534 
535   // Register as an observer for xpcom shutdown events so we can drop any
536   // element refs
537   nsCOMPtr<nsIObserverService> observerService =
538       mozilla::services::GetObserverService();
539   if (observerService)
540     observerService->AddObserver(this, "xpcom-shutdown", true);
541 }
542 
RemoveUnboundLinks()543 void DeferredDNSPrefetches::RemoveUnboundLinks() {
544   uint16_t tail = mTail;
545   while (mHead != tail) {
546     Element* element = mEntries[tail].mElement;
547     if (element && !element->IsInComposedDoc()) {
548       ToSupportsDNSPrefetch(*element).ClearIsInDNSPrefetch();
549       mEntries[tail].mElement = nullptr;
550     }
551     tail = (tail + 1) & sMaxDeferredMask;
552   }
553 }
554 
555 // nsITimer related method
556 
Tick(nsITimer * aTimer,void * aClosure)557 void DeferredDNSPrefetches::Tick(nsITimer* aTimer, void* aClosure) {
558   auto* self = static_cast<DeferredDNSPrefetches*>(aClosure);
559 
560   NS_ASSERTION(NS_IsMainThread(),
561                "DeferredDNSPrefetches::Tick must be on main thread");
562   NS_ASSERTION(self->mTimerArmed, "Timer is not armed");
563 
564   self->mTimerArmed = false;
565 
566   // If the queue is not submitted here because there are outstanding pages
567   // being loaded, there is no need to rearm the timer as the queue will be
568   // submtited when those loads complete.
569   if (!self->mActiveLoaderCount) {
570     self->SubmitQueue();
571   }
572 }
573 
574 //////////// nsIWebProgressListener methods
575 
576 NS_IMETHODIMP
OnStateChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t progressStateFlags,nsresult aStatus)577 DeferredDNSPrefetches::OnStateChange(nsIWebProgress* aWebProgress,
578                                      nsIRequest* aRequest,
579                                      uint32_t progressStateFlags,
580                                      nsresult aStatus) {
581   // The FIFO has no lock, so it can only be accessed on main thread
582   NS_ASSERTION(NS_IsMainThread(),
583                "DeferredDNSPrefetches::OnStateChange must be on main thread");
584 
585   if (progressStateFlags & STATE_IS_DOCUMENT) {
586     if (progressStateFlags & STATE_STOP) {
587       // Initialization may have missed a STATE_START notification, so do
588       // not go negative
589       if (mActiveLoaderCount) mActiveLoaderCount--;
590 
591       if (!mActiveLoaderCount) {
592         SubmitQueue();
593       }
594     } else if (progressStateFlags & STATE_START)
595       mActiveLoaderCount++;
596   }
597 
598   return NS_OK;
599 }
600 
601 NS_IMETHODIMP
OnProgressChange(nsIWebProgress * aProgress,nsIRequest * aRequest,int32_t curSelfProgress,int32_t maxSelfProgress,int32_t curTotalProgress,int32_t maxTotalProgress)602 DeferredDNSPrefetches::OnProgressChange(nsIWebProgress* aProgress,
603                                         nsIRequest* aRequest,
604                                         int32_t curSelfProgress,
605                                         int32_t maxSelfProgress,
606                                         int32_t curTotalProgress,
607                                         int32_t maxTotalProgress) {
608   return NS_OK;
609 }
610 
611 NS_IMETHODIMP
OnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * location,uint32_t aFlags)612 DeferredDNSPrefetches::OnLocationChange(nsIWebProgress* aWebProgress,
613                                         nsIRequest* aRequest, nsIURI* location,
614                                         uint32_t aFlags) {
615   return NS_OK;
616 }
617 
618 NS_IMETHODIMP
OnStatusChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsresult aStatus,const char16_t * aMessage)619 DeferredDNSPrefetches::OnStatusChange(nsIWebProgress* aWebProgress,
620                                       nsIRequest* aRequest, nsresult aStatus,
621                                       const char16_t* aMessage) {
622   return NS_OK;
623 }
624 
625 NS_IMETHODIMP
OnSecurityChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aState)626 DeferredDNSPrefetches::OnSecurityChange(nsIWebProgress* aWebProgress,
627                                         nsIRequest* aRequest, uint32_t aState) {
628   return NS_OK;
629 }
630 
631 NS_IMETHODIMP
OnContentBlockingEvent(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aEvent)632 DeferredDNSPrefetches::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
633                                               nsIRequest* aRequest,
634                                               uint32_t aEvent) {
635   return NS_OK;
636 }
637 
638 //////////// nsIObserver method
639 
640 NS_IMETHODIMP
Observe(nsISupports * subject,const char * topic,const char16_t * data)641 DeferredDNSPrefetches::Observe(nsISupports* subject, const char* topic,
642                                const char16_t* data) {
643   if (!strcmp(topic, "xpcom-shutdown")) Flush();
644 
645   return NS_OK;
646 }
647 
648 }  // namespace dom
649 }  // namespace mozilla
650