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/. */
7 #include "HTMLDNSPrefetch.h"
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"
22 #include "nsNetUtil.h"
24 #include "nsIProtocolHandler.h"
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"
36 #include "nsIObserverService.h"
38 #include "mozilla/Components.h"
39 #include "mozilla/Preferences.h"
40 #include "mozilla/StaticPrefs_network.h"
42 using namespace mozilla::net
;
44 namespace mozilla::dom
{
46 class NoOpDNSListener final
: public nsIDNSListener
{
47 // This class exists to give a safe callback no-op DNSListener
49 NS_DECL_THREADSAFE_ISUPPORTS
50 NS_DECL_NSIDNSLISTENER
52 NoOpDNSListener() = default;
55 ~NoOpDNSListener() = default;
58 NS_IMPL_ISUPPORTS(NoOpDNSListener
, nsIDNSListener
)
61 NoOpDNSListener::OnLookupComplete(nsICancelable
* request
, nsIDNSRecord
* rec
,
66 // This is just a (size) optimization and could be avoided by storing the
67 // SupportsDNSPrefetch pointer of the element in the prefetch queue, but given
68 // we need this for GetURIForDNSPrefetch...
69 static SupportsDNSPrefetch
& ToSupportsDNSPrefetch(Element
& aElement
) {
70 if (auto* link
= HTMLLinkElement::FromNode(aElement
)) {
73 auto* anchor
= HTMLAnchorElement::FromNode(aElement
);
74 MOZ_DIAGNOSTIC_ASSERT(anchor
);
78 nsIURI
* SupportsDNSPrefetch::GetURIForDNSPrefetch(Element
& aElement
) {
79 MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement
) == this);
80 if (auto* link
= HTMLLinkElement::FromNode(aElement
)) {
81 return link
->GetURI();
83 auto* anchor
= HTMLAnchorElement::FromNode(aElement
);
84 MOZ_DIAGNOSTIC_ASSERT(anchor
);
85 return anchor
->GetURI();
88 class DeferredDNSPrefetches final
: public nsIWebProgressListener
,
89 public nsSupportsWeakReference
,
93 NS_DECL_NSIWEBPROGRESSLISTENER
96 DeferredDNSPrefetches();
99 nsresult
Add(nsIDNSService::DNSFlags flags
, SupportsDNSPrefetch
&, Element
&);
101 void RemoveUnboundLinks();
104 ~DeferredDNSPrefetches();
108 void SubmitQueueEntry(Element
&, nsIDNSService::DNSFlags aFlags
);
112 uint32_t mActiveLoaderCount
;
114 nsCOMPtr
<nsITimer
> mTimer
;
116 static void Tick(nsITimer
* aTimer
, void* aClosure
);
118 static const int sMaxDeferred
= 512; // keep power of 2 for masking
119 static const int sMaxDeferredMask
= (sMaxDeferred
- 1);
121 struct deferred_entry
{
122 nsIDNSService::DNSFlags mFlags
;
123 // SupportsDNSPrefetch clears this raw pointer in Destroyed().
125 } mEntries
[sMaxDeferred
];
128 static NS_DEFINE_CID(kDNSServiceCID
, NS_DNSSERVICE_CID
);
129 static bool sInitialized
= false;
130 static nsIDNSService
* sDNSService
= nullptr;
131 static DeferredDNSPrefetches
* sPrefetches
= nullptr;
132 static NoOpDNSListener
* sDNSListener
= nullptr;
134 nsresult
HTMLDNSPrefetch::Initialize() {
136 NS_WARNING("Initialize() called twice");
140 sPrefetches
= new DeferredDNSPrefetches();
141 NS_ADDREF(sPrefetches
);
143 sDNSListener
= new NoOpDNSListener();
144 NS_ADDREF(sDNSListener
);
146 sPrefetches
->Activate();
148 if (IsNeckoChild()) NeckoChild::InitNeckoChild();
154 nsresult
HTMLDNSPrefetch::Shutdown() {
156 NS_WARNING("Not Initialized");
159 sInitialized
= false;
160 NS_IF_RELEASE(sDNSService
);
161 NS_IF_RELEASE(sPrefetches
);
162 NS_IF_RELEASE(sDNSListener
);
167 static bool EnsureDNSService() {
172 NS_IF_RELEASE(sDNSService
);
174 rv
= CallGetService(kDNSServiceCID
, &sDNSService
);
179 return !!sDNSService
;
182 bool HTMLDNSPrefetch::IsAllowed(Document
* aDocument
) {
183 // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
184 return aDocument
->IsDNSPrefetchAllowed() && aDocument
->GetWindow();
187 static nsIDNSService::DNSFlags
GetDNSFlagsFromElement(Element
& aElement
) {
188 nsIChannel
* channel
= aElement
.OwnerDoc()->GetChannel();
190 return nsIDNSService::RESOLVE_DEFAULT_FLAGS
;
192 return nsIDNSService::GetFlagsFromTRRMode(channel
->GetTRRMode());
195 nsIDNSService::DNSFlags
HTMLDNSPrefetch::PriorityToDNSServiceFlags(
196 Priority aPriority
) {
199 return nsIDNSService::RESOLVE_PRIORITY_LOW
;
200 case Priority::Medium
:
201 return nsIDNSService::RESOLVE_PRIORITY_MEDIUM
;
203 return nsIDNSService::RESOLVE_DEFAULT_FLAGS
;
205 MOZ_ASSERT_UNREACHABLE("Unknown priority");
206 return nsIDNSService::RESOLVE_DEFAULT_FLAGS
;
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
;
215 return sPrefetches
->Add(
216 GetDNSFlagsFromElement(aElement
) | PriorityToDNSServiceFlags(aPriority
),
217 aSupports
, aElement
);
220 nsresult
HTMLDNSPrefetch::Prefetch(
221 const nsAString
& hostname
, bool isHttps
,
222 const OriginAttributes
& aPartitionedPrincipalOriginAttributes
,
223 nsIDNSService::DNSFlags 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
231 gNeckoChild
->SendHTMLDNSPrefetch(
232 hostname
, isHttps
, aPartitionedPrincipalOriginAttributes
, flags
);
238 if (!(sInitialized
&& sPrefetches
&& sDNSListener
) || !EnsureDNSService())
239 return NS_ERROR_NOT_AVAILABLE
;
241 nsCOMPtr
<nsICancelable
> tmpOutstanding
;
242 nsresult rv
= sDNSService
->AsyncResolveNative(
243 NS_ConvertUTF16toUTF8(hostname
), nsIDNSService::RESOLVE_TYPE_DEFAULT
,
244 flags
| nsIDNSService::RESOLVE_SPECULATE
, nullptr, sDNSListener
, nullptr,
245 aPartitionedPrincipalOriginAttributes
, getter_AddRefs(tmpOutstanding
));
250 if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
251 StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
252 Unused
<< sDNSService
->AsyncResolveNative(
253 NS_ConvertUTF16toUTF8(hostname
), nsIDNSService::RESOLVE_TYPE_HTTPSSVC
,
254 flags
| nsIDNSService::RESOLVE_SPECULATE
, nullptr, sDNSListener
,
255 nullptr, aPartitionedPrincipalOriginAttributes
,
256 getter_AddRefs(tmpOutstanding
));
262 nsresult
HTMLDNSPrefetch::Prefetch(
263 const nsAString
& hostname
, bool isHttps
,
264 const OriginAttributes
& aPartitionedPrincipalOriginAttributes
,
265 nsIRequest::TRRMode aMode
, Priority aPriority
) {
266 return Prefetch(hostname
, isHttps
, aPartitionedPrincipalOriginAttributes
,
267 nsIDNSService::GetFlagsFromTRRMode(aMode
) |
268 PriorityToDNSServiceFlags(aPriority
));
271 nsresult
HTMLDNSPrefetch::CancelPrefetch(SupportsDNSPrefetch
& aSupports
,
272 Element
& aElement
, Priority aPriority
,
274 MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement
) == &aSupports
);
276 if (!(sInitialized
&& sPrefetches
&& sDNSListener
) || !EnsureDNSService()) {
277 return NS_ERROR_NOT_AVAILABLE
;
280 nsIDNSService::DNSFlags flags
=
281 GetDNSFlagsFromElement(aElement
) | PriorityToDNSServiceFlags(aPriority
);
283 nsIURI
* uri
= aSupports
.GetURIForDNSPrefetch(aElement
);
288 nsAutoCString hostname
;
289 uri
->GetAsciiHost(hostname
);
291 nsAutoString protocol
;
292 bool isHttps
= uri
->SchemeIs("https");
295 StoragePrincipalHelper::GetOriginAttributesForNetworkState(
296 aElement
.OwnerDoc(), oa
);
298 return CancelPrefetch(NS_ConvertUTF8toUTF16(hostname
), isHttps
, oa
, flags
,
302 nsresult
HTMLDNSPrefetch::CancelPrefetch(
303 const nsAString
& hostname
, bool isHttps
,
304 const OriginAttributes
& aPartitionedPrincipalOriginAttributes
,
305 nsIDNSService::DNSFlags flags
, nsresult aReason
) {
306 // Forward this request to Necko Parent if we're a child process
307 if (IsNeckoChild()) {
308 // We need to check IsEmpty() because net_IsValidHostName()
309 // considers empty strings to be valid hostnames
310 if (!hostname
.IsEmpty() &&
311 net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname
))) {
312 // during shutdown gNeckoChild might be null
314 gNeckoChild
->SendCancelHTMLDNSPrefetch(
315 hostname
, isHttps
, aPartitionedPrincipalOriginAttributes
, flags
,
322 if (!(sInitialized
&& sPrefetches
&& sDNSListener
) || !EnsureDNSService()) {
323 return NS_ERROR_NOT_AVAILABLE
;
326 // Forward cancellation to DNS service
327 nsresult rv
= sDNSService
->CancelAsyncResolveNative(
328 NS_ConvertUTF16toUTF8(hostname
), nsIDNSService::RESOLVE_TYPE_DEFAULT
,
329 flags
| nsIDNSService::RESOLVE_SPECULATE
,
330 nullptr, // AdditionalInfo
331 sDNSListener
, aReason
, aPartitionedPrincipalOriginAttributes
);
333 if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
334 StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
335 Unused
<< sDNSService
->CancelAsyncResolveNative(
336 NS_ConvertUTF16toUTF8(hostname
), nsIDNSService::RESOLVE_TYPE_HTTPSSVC
,
337 flags
| nsIDNSService::RESOLVE_SPECULATE
,
338 nullptr, // AdditionalInfo
339 sDNSListener
, aReason
, aPartitionedPrincipalOriginAttributes
);
344 nsresult
HTMLDNSPrefetch::CancelPrefetch(
345 const nsAString
& hostname
, bool isHttps
,
346 const OriginAttributes
& aPartitionedPrincipalOriginAttributes
,
347 nsIRequest::TRRMode aTRRMode
, Priority aPriority
, nsresult aReason
) {
348 return CancelPrefetch(hostname
, isHttps
,
349 aPartitionedPrincipalOriginAttributes
,
350 nsIDNSService::GetFlagsFromTRRMode(aTRRMode
) |
351 PriorityToDNSServiceFlags(aPriority
),
355 void HTMLDNSPrefetch::ElementDestroyed(Element
& aElement
,
356 SupportsDNSPrefetch
& aSupports
) {
357 MOZ_ASSERT(&ToSupportsDNSPrefetch(aElement
) == &aSupports
);
358 MOZ_ASSERT(aSupports
.IsInDNSPrefetch());
360 // Clean up all the possible links at once.
361 sPrefetches
->RemoveUnboundLinks();
365 void SupportsDNSPrefetch::TryDNSPrefetch(Element
& aOwner
) {
366 MOZ_ASSERT(aOwner
.IsInComposedDoc());
367 if (HTMLDNSPrefetch::IsAllowed(aOwner
.OwnerDoc())) {
368 HTMLDNSPrefetch::Prefetch(*this, aOwner
, HTMLDNSPrefetch::Priority::Low
);
372 void SupportsDNSPrefetch::CancelDNSPrefetch(Element
& aOwner
) {
373 // If prefetch was deferred, clear flag and move on
374 if (mDNSPrefetchDeferred
) {
375 mDNSPrefetchDeferred
= false;
376 // Else if prefetch was requested, clear flag and send cancellation
377 } else if (mDNSPrefetchRequested
) {
378 mDNSPrefetchRequested
= false;
379 // Possible that hostname could have changed since binding, but since this
380 // covers common cases, most DNS prefetch requests will be canceled
381 HTMLDNSPrefetch::CancelPrefetch(
382 *this, aOwner
, HTMLDNSPrefetch::Priority::Low
, NS_ERROR_ABORT
);
386 DeferredDNSPrefetches::DeferredDNSPrefetches()
387 : mHead(0), mTail(0), mActiveLoaderCount(0), mTimerArmed(false) {
388 mTimer
= NS_NewTimer();
391 DeferredDNSPrefetches::~DeferredDNSPrefetches() {
400 NS_IMPL_ISUPPORTS(DeferredDNSPrefetches
, nsIWebProgressListener
,
401 nsISupportsWeakReference
, nsIObserver
)
403 void DeferredDNSPrefetches::Flush() {
404 for (; mHead
!= mTail
; mTail
= (mTail
+ 1) & sMaxDeferredMask
) {
405 Element
* element
= mEntries
[mTail
].mElement
;
407 ToSupportsDNSPrefetch(*element
).ClearIsInDNSPrefetch();
409 mEntries
[mTail
].mElement
= nullptr;
413 nsresult
DeferredDNSPrefetches::Add(nsIDNSService::DNSFlags flags
,
414 SupportsDNSPrefetch
& aSupports
,
416 // The FIFO has no lock, so it can only be accessed on main thread
417 NS_ASSERTION(NS_IsMainThread(),
418 "DeferredDNSPrefetches::Add must be on main thread");
420 aSupports
.DNSPrefetchRequestDeferred();
422 if (((mHead
+ 1) & sMaxDeferredMask
) == mTail
) {
423 return NS_ERROR_DNS_LOOKUP_QUEUE_FULL
;
426 aSupports
.SetIsInDNSPrefetch();
427 mEntries
[mHead
].mFlags
= flags
;
428 mEntries
[mHead
].mElement
= &aElement
;
429 mHead
= (mHead
+ 1) & sMaxDeferredMask
;
431 if (!mActiveLoaderCount
&& !mTimerArmed
&& mTimer
) {
433 mTimer
->InitWithNamedFuncCallback(
434 Tick
, this, 2000, nsITimer::TYPE_ONE_SHOT
,
435 "HTMLDNSPrefetch::DeferredDNSPrefetches::Tick");
441 void DeferredDNSPrefetches::SubmitQueue() {
442 NS_ASSERTION(NS_IsMainThread(),
443 "DeferredDNSPrefetches::SubmitQueue must be on main thread");
444 if (!EnsureDNSService()) {
448 for (; mHead
!= mTail
; mTail
= (mTail
+ 1) & sMaxDeferredMask
) {
449 Element
* element
= mEntries
[mTail
].mElement
;
453 SubmitQueueEntry(*element
, mEntries
[mTail
].mFlags
);
454 mEntries
[mTail
].mElement
= nullptr;
463 void DeferredDNSPrefetches::SubmitQueueEntry(Element
& aElement
,
464 nsIDNSService::DNSFlags aFlags
) {
465 auto& supports
= ToSupportsDNSPrefetch(aElement
);
466 supports
.ClearIsInDNSPrefetch();
468 // Only prefetch here if request was deferred and deferral not cancelled
469 if (!supports
.IsDNSPrefetchRequestDeferred()) {
473 nsIURI
* uri
= supports
.GetURIForDNSPrefetch(aElement
);
478 nsAutoCString hostName
;
479 uri
->GetAsciiHost(hostName
);
480 if (hostName
.IsEmpty()) {
484 bool isLocalResource
= false;
485 nsresult rv
= NS_URIChainHasFlags(
486 uri
, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE
, &isLocalResource
);
487 if (NS_FAILED(rv
) || isLocalResource
) {
492 StoragePrincipalHelper::GetOriginAttributesForNetworkState(
493 aElement
.OwnerDoc(), oa
);
495 bool isHttps
= uri
->SchemeIs("https");
497 if (IsNeckoChild()) {
498 // during shutdown gNeckoChild might be null
500 gNeckoChild
->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName
), isHttps
,
501 oa
, mEntries
[mTail
].mFlags
);
504 nsCOMPtr
<nsICancelable
> tmpOutstanding
;
506 rv
= sDNSService
->AsyncResolveNative(
507 hostName
, nsIDNSService::RESOLVE_TYPE_DEFAULT
,
508 mEntries
[mTail
].mFlags
| nsIDNSService::RESOLVE_SPECULATE
, nullptr,
509 sDNSListener
, nullptr, oa
, getter_AddRefs(tmpOutstanding
));
514 // Fetch HTTPS RR if needed.
515 if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
516 StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
517 sDNSService
->AsyncResolveNative(
518 hostName
, nsIDNSService::RESOLVE_TYPE_HTTPSSVC
,
519 mEntries
[mTail
].mFlags
| nsIDNSService::RESOLVE_SPECULATE
, nullptr,
520 sDNSListener
, nullptr, oa
, getter_AddRefs(tmpOutstanding
));
524 // Tell element that deferred prefetch was requested.
525 supports
.DNSPrefetchRequestStarted();
528 void DeferredDNSPrefetches::Activate() {
529 // Register as an observer for the document loader
530 nsCOMPtr
<nsIWebProgress
> progress
= components::DocLoader::Service();
532 progress
->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT
);
534 // Register as an observer for xpcom shutdown events so we can drop any
536 nsCOMPtr
<nsIObserverService
> observerService
=
537 mozilla::services::GetObserverService();
539 observerService
->AddObserver(this, "xpcom-shutdown", true);
542 void DeferredDNSPrefetches::RemoveUnboundLinks() {
543 uint16_t tail
= mTail
;
544 while (mHead
!= tail
) {
545 Element
* element
= mEntries
[tail
].mElement
;
546 if (element
&& !element
->IsInComposedDoc()) {
547 ToSupportsDNSPrefetch(*element
).ClearIsInDNSPrefetch();
548 mEntries
[tail
].mElement
= nullptr;
550 tail
= (tail
+ 1) & sMaxDeferredMask
;
554 // nsITimer related method
556 void DeferredDNSPrefetches::Tick(nsITimer
* aTimer
, void* aClosure
) {
557 auto* self
= static_cast<DeferredDNSPrefetches
*>(aClosure
);
559 NS_ASSERTION(NS_IsMainThread(),
560 "DeferredDNSPrefetches::Tick must be on main thread");
561 NS_ASSERTION(self
->mTimerArmed
, "Timer is not armed");
563 self
->mTimerArmed
= false;
565 // If the queue is not submitted here because there are outstanding pages
566 // being loaded, there is no need to rearm the timer as the queue will be
567 // submtited when those loads complete.
568 if (!self
->mActiveLoaderCount
) {
573 //////////// nsIWebProgressListener methods
576 DeferredDNSPrefetches::OnStateChange(nsIWebProgress
* aWebProgress
,
577 nsIRequest
* aRequest
,
578 uint32_t progressStateFlags
,
580 // The FIFO has no lock, so it can only be accessed on main thread
581 NS_ASSERTION(NS_IsMainThread(),
582 "DeferredDNSPrefetches::OnStateChange must be on main thread");
584 if (progressStateFlags
& STATE_IS_DOCUMENT
) {
585 if (progressStateFlags
& STATE_STOP
) {
586 // Initialization may have missed a STATE_START notification, so do
588 if (mActiveLoaderCount
) mActiveLoaderCount
--;
590 if (!mActiveLoaderCount
) {
593 } else if (progressStateFlags
& STATE_START
)
594 mActiveLoaderCount
++;
601 DeferredDNSPrefetches::OnProgressChange(nsIWebProgress
* aProgress
,
602 nsIRequest
* aRequest
,
603 int32_t curSelfProgress
,
604 int32_t maxSelfProgress
,
605 int32_t curTotalProgress
,
606 int32_t maxTotalProgress
) {
611 DeferredDNSPrefetches::OnLocationChange(nsIWebProgress
* aWebProgress
,
612 nsIRequest
* aRequest
, nsIURI
* location
,
618 DeferredDNSPrefetches::OnStatusChange(nsIWebProgress
* aWebProgress
,
619 nsIRequest
* aRequest
, nsresult aStatus
,
620 const char16_t
* aMessage
) {
625 DeferredDNSPrefetches::OnSecurityChange(nsIWebProgress
* aWebProgress
,
626 nsIRequest
* aRequest
, uint32_t aState
) {
631 DeferredDNSPrefetches::OnContentBlockingEvent(nsIWebProgress
* aWebProgress
,
632 nsIRequest
* aRequest
,
637 //////////// nsIObserver method
640 DeferredDNSPrefetches::Observe(nsISupports
* subject
, const char* topic
,
641 const char16_t
* data
) {
642 if (!strcmp(topic
, "xpcom-shutdown")) Flush();
647 } // namespace mozilla::dom