1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPrefetchService.h"
8 #include "mozilla/AsyncEventDispatcher.h"
9 #include "mozilla/Attributes.h"
10 #include "mozilla/CORSMode.h"
11 #include "mozilla/Components.h"
12 #include "mozilla/dom/ClientInfo.h"
13 #include "mozilla/dom/HTMLLinkElement.h"
14 #include "mozilla/dom/ServiceWorkerDescriptor.h"
15 #include "mozilla/Preferences.h"
16 #include "ReferrerInfo.h"
18 #include "nsIObserverService.h"
19 #include "nsIWebProgress.h"
20 #include "nsICacheInfoChannel.h"
21 #include "nsIHttpChannel.h"
23 #include "nsISupportsPriority.h"
24 #include "nsNetUtil.h"
26 #include "nsReadableUtils.h"
27 #include "nsStreamUtils.h"
29 #include "mozilla/Logging.h"
30 #include "nsIAsyncVerifyRedirectCallback.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsContentUtils.h"
34 #include "mozilla/AsyncEventDispatcher.h"
35 #include "nsICachingChannel.h"
38 using namespace mozilla
;
39 using namespace mozilla::dom
;
42 // To enable logging (see mozilla/Logging.h for full details):
44 // set MOZ_LOG=nsPrefetch:5
45 // set MOZ_LOG_FILE=prefetch.log
47 // this enables LogLevel::Debug level information and places all output in
48 // the file prefetch.log
50 static LazyLogModule
gPrefetchLog("nsPrefetch");
53 #define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
56 #define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
58 #define PREFETCH_PREF "network.prefetch-next"
59 #define PARALLELISM_PREF "network.prefetch-next.parallelism"
60 #define AGGRESSIVE_PREF "network.prefetch-next.aggressive"
62 //-----------------------------------------------------------------------------
63 // nsPrefetchNode <public>
64 //-----------------------------------------------------------------------------
66 nsPrefetchNode::nsPrefetchNode(nsPrefetchService
* aService
, nsIURI
* aURI
,
67 nsIReferrerInfo
* aReferrerInfo
, nsINode
* aSource
,
68 nsContentPolicyType aPolicyType
, bool aPreload
)
70 mReferrerInfo(aReferrerInfo
),
71 mPolicyType(aPolicyType
),
76 mShouldFireLoadEvent(false) {
77 nsWeakPtr source
= do_GetWeakReference(aSource
);
78 mSources
.AppendElement(source
);
81 nsresult
nsPrefetchNode::OpenChannel() {
82 if (mSources
.IsEmpty()) {
83 // Don't attempt to prefetch if we don't have a source node
84 // (which should never happen).
85 return NS_ERROR_FAILURE
;
87 nsCOMPtr
<nsINode
> source
;
88 while (!mSources
.IsEmpty() &&
89 !(source
= do_QueryReferent(mSources
.ElementAt(0)))) {
90 // If source is null remove it.
91 // (which should never happen).
92 mSources
.RemoveElementAt(0);
96 // Don't attempt to prefetch if we don't have a source node
97 // (which should never happen).
99 return NS_ERROR_FAILURE
;
101 nsCOMPtr
<nsILoadGroup
> loadGroup
= source
->OwnerDoc()->GetDocumentLoadGroup();
102 CORSMode corsMode
= CORS_NONE
;
103 if (auto* link
= dom::HTMLLinkElement::FromNode(source
)) {
104 corsMode
= link
->GetCORSMode();
107 uint32_t securityFlags
;
108 if (corsMode
== CORS_NONE
) {
109 securityFlags
= nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
;
111 securityFlags
= nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
;
112 if (corsMode
== CORS_USE_CREDENTIALS
) {
113 securityFlags
|= nsILoadInfo::SEC_COOKIES_INCLUDE
;
116 nsresult rv
= NS_NewChannelInternal(
117 getter_AddRefs(mChannel
), mURI
, source
, source
->NodePrincipal(),
118 nullptr, // aTriggeringPrincipal
119 Maybe
<ClientInfo
>(), Maybe
<ServiceWorkerDescriptor
>(), securityFlags
,
120 mPolicyType
, source
->OwnerDoc()->CookieJarSettings(),
121 nullptr, // aPerformanceStorage
122 loadGroup
, // aLoadGroup
124 nsIRequest::LOAD_BACKGROUND
| nsICachingChannel::LOAD_ONLY_IF_MODIFIED
);
126 NS_ENSURE_SUCCESS(rv
, rv
);
128 // configure HTTP specific stuff
129 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mChannel
);
131 DebugOnly
<nsresult
> success
= httpChannel
->SetReferrerInfo(mReferrerInfo
);
132 MOZ_ASSERT(NS_SUCCEEDED(success
));
134 // https://fetch.spec.whatwg.org/#http-sec-purpose
136 httpChannel
->SetRequestHeader("Sec-Purpose"_ns
, "prefetch"_ns
, false);
137 MOZ_ASSERT(NS_SUCCEEDED(success
));
140 // Reduce the priority of prefetch network requests.
141 nsCOMPtr
<nsISupportsPriority
> priorityChannel
= do_QueryInterface(mChannel
);
142 if (priorityChannel
) {
143 priorityChannel
->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST
);
146 rv
= mChannel
->AsyncOpen(this);
147 if (NS_WARN_IF(NS_FAILED(rv
))) {
148 // Drop the ref to the channel, because we don't want to end up with
149 // cycles through it.
155 nsresult
nsPrefetchNode::CancelChannel(nsresult error
) {
156 mChannel
->Cancel(error
);
162 //-----------------------------------------------------------------------------
163 // nsPrefetchNode::nsISupports
164 //-----------------------------------------------------------------------------
166 NS_IMPL_ISUPPORTS(nsPrefetchNode
, nsIRequestObserver
, nsIStreamListener
,
167 nsIInterfaceRequestor
, nsIChannelEventSink
,
168 nsIRedirectResultListener
)
170 //-----------------------------------------------------------------------------
171 // nsPrefetchNode::nsIStreamListener
172 //-----------------------------------------------------------------------------
175 nsPrefetchNode::OnStartRequest(nsIRequest
* aRequest
) {
178 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
, &rv
);
179 if (NS_FAILED(rv
)) return rv
;
181 // if the load is cross origin without CORS, or the CORS access is rejected,
182 // always fire load event to avoid leaking site information.
183 nsCOMPtr
<nsILoadInfo
> loadInfo
= httpChannel
->LoadInfo();
184 mShouldFireLoadEvent
=
185 loadInfo
->GetTainting() == LoadTainting::Opaque
||
186 (loadInfo
->GetTainting() == LoadTainting::CORS
&&
187 (NS_FAILED(httpChannel
->GetStatus(&rv
)) || NS_FAILED(rv
)));
189 // no need to prefetch http error page
190 bool requestSucceeded
;
191 if (NS_FAILED(httpChannel
->GetRequestSucceeded(&requestSucceeded
)) ||
193 return NS_BINDING_ABORTED
;
196 nsCOMPtr
<nsICacheInfoChannel
> cacheInfoChannel
=
197 do_QueryInterface(aRequest
, &rv
);
198 if (NS_FAILED(rv
)) return rv
;
200 // no need to prefetch a document that is already in the cache
202 if (NS_SUCCEEDED(cacheInfoChannel
->IsFromCache(&fromCache
)) && fromCache
) {
203 LOG(("document is already in the cache; canceling prefetch\n"));
204 // although it's canceled we still want to fire load event
205 mShouldFireLoadEvent
= true;
206 return NS_BINDING_ABORTED
;
210 // no need to prefetch a document that must be requested fresh each
214 if (NS_SUCCEEDED(cacheInfoChannel
->GetCacheTokenExpirationTime(&expTime
))) {
215 if (mozilla::net::NowInSeconds() >= expTime
) {
217 ("document cannot be reused from cache; "
218 "canceling prefetch\n"));
219 return NS_BINDING_ABORTED
;
227 nsPrefetchNode::OnDataAvailable(nsIRequest
* aRequest
, nsIInputStream
* aStream
,
228 uint64_t aOffset
, uint32_t aCount
) {
229 uint32_t bytesRead
= 0;
230 aStream
->ReadSegments(NS_DiscardSegment
, nullptr, aCount
, &bytesRead
);
231 mBytesRead
+= bytesRead
;
232 LOG(("prefetched %u bytes [offset=%" PRIu64
"]\n", bytesRead
, aOffset
));
237 nsPrefetchNode::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatus
) {
238 LOG(("done prefetching [status=%" PRIx32
"]\n",
239 static_cast<uint32_t>(aStatus
)));
241 if (mBytesRead
== 0 && aStatus
== NS_OK
&& mChannel
) {
242 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
243 // specified), but the object should report loadedSize as if it
245 mChannel
->GetContentLength(&mBytesRead
);
248 mService
->NotifyLoadCompleted(this);
249 mService
->DispatchEvent(this, mShouldFireLoadEvent
|| NS_SUCCEEDED(aStatus
));
250 mService
->RemoveNodeAndMaybeStartNextPrefetchURI(this);
254 //-----------------------------------------------------------------------------
255 // nsPrefetchNode::nsIInterfaceRequestor
256 //-----------------------------------------------------------------------------
259 nsPrefetchNode::GetInterface(const nsIID
& aIID
, void** aResult
) {
260 if (aIID
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
262 *aResult
= static_cast<nsIChannelEventSink
*>(this);
266 if (aIID
.Equals(NS_GET_IID(nsIRedirectResultListener
))) {
268 *aResult
= static_cast<nsIRedirectResultListener
*>(this);
272 return NS_ERROR_NO_INTERFACE
;
275 //-----------------------------------------------------------------------------
276 // nsPrefetchNode::nsIChannelEventSink
277 //-----------------------------------------------------------------------------
280 nsPrefetchNode::AsyncOnChannelRedirect(
281 nsIChannel
* aOldChannel
, nsIChannel
* aNewChannel
, uint32_t aFlags
,
282 nsIAsyncVerifyRedirectCallback
* callback
) {
283 nsCOMPtr
<nsIURI
> newURI
;
284 nsresult rv
= aNewChannel
->GetURI(getter_AddRefs(newURI
));
285 if (NS_FAILED(rv
)) return rv
;
287 if (!newURI
->SchemeIs("http") && !newURI
->SchemeIs("https")) {
288 LOG(("rejected: URL is not of type http/https\n"));
289 return NS_ERROR_ABORT
;
292 // HTTP request headers are not automatically forwarded to the new channel.
293 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aNewChannel
);
294 NS_ENSURE_STATE(httpChannel
);
296 // https://fetch.spec.whatwg.org/#http-sec-purpose
297 rv
= httpChannel
->SetRequestHeader("Sec-Purpose"_ns
, "prefetch"_ns
, false);
298 MOZ_ASSERT(NS_SUCCEEDED(rv
));
300 // Assign to mChannel after we get notification about success of the
301 // redirect in OnRedirectResult.
302 mRedirectChannel
= aNewChannel
;
304 callback
->OnRedirectVerifyCallback(NS_OK
);
308 //-----------------------------------------------------------------------------
309 // nsPrefetchNode::nsIRedirectResultListener
310 //-----------------------------------------------------------------------------
313 nsPrefetchNode::OnRedirectResult(nsresult status
) {
314 if (NS_SUCCEEDED(status
) && mRedirectChannel
) mChannel
= mRedirectChannel
;
316 mRedirectChannel
= nullptr;
321 //-----------------------------------------------------------------------------
322 // nsPrefetchService <public>
323 //-----------------------------------------------------------------------------
325 nsPrefetchService::nsPrefetchService()
326 : mMaxParallelism(6),
328 mHaveProcessed(false),
329 mPrefetchDisabled(true),
330 mAggressive(false) {}
332 nsPrefetchService::~nsPrefetchService() {
333 Preferences::RemoveObserver(this, PREFETCH_PREF
);
334 Preferences::RemoveObserver(this, PARALLELISM_PREF
);
335 Preferences::RemoveObserver(this, AGGRESSIVE_PREF
);
336 // cannot reach destructor if prefetch in progress (listener owns reference
338 EmptyPrefetchQueue();
341 nsresult
nsPrefetchService::Init() {
344 // read prefs and hook up pref observer
345 mPrefetchDisabled
= !Preferences::GetBool(PREFETCH_PREF
, !mPrefetchDisabled
);
346 Preferences::AddWeakObserver(this, PREFETCH_PREF
);
348 mMaxParallelism
= Preferences::GetInt(PARALLELISM_PREF
, mMaxParallelism
);
349 if (mMaxParallelism
< 1) {
352 Preferences::AddWeakObserver(this, PARALLELISM_PREF
);
354 mAggressive
= Preferences::GetBool(AGGRESSIVE_PREF
, false);
355 Preferences::AddWeakObserver(this, AGGRESSIVE_PREF
);
357 // Observe xpcom-shutdown event
358 nsCOMPtr
<nsIObserverService
> observerService
=
359 mozilla::services::GetObserverService();
360 if (!observerService
) return NS_ERROR_FAILURE
;
362 rv
= observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
363 NS_ENSURE_SUCCESS(rv
, rv
);
365 if (!mPrefetchDisabled
) {
366 AddProgressListener();
372 void nsPrefetchService::RemoveNodeAndMaybeStartNextPrefetchURI(
373 nsPrefetchNode
* aFinished
) {
375 mCurrentNodes
.RemoveElement(aFinished
);
378 if ((!mStopCount
&& mHaveProcessed
) || mAggressive
) {
379 ProcessNextPrefetchURI();
383 void nsPrefetchService::ProcessNextPrefetchURI() {
384 if (mCurrentNodes
.Length() >= static_cast<uint32_t>(mMaxParallelism
)) {
385 // We already have enough prefetches going on, so hold off
393 if (mPrefetchQueue
.empty()) {
396 RefPtr
<nsPrefetchNode
> node
= std::move(mPrefetchQueue
.front());
397 mPrefetchQueue
.pop_front();
400 LOG(("ProcessNextPrefetchURI [%s]\n",
401 node
->mURI
->GetSpecOrDefault().get()));
405 // if opening the channel fails (e.g. security check returns an error),
406 // send an error event and then just skip to the next uri
408 rv
= node
->OpenChannel();
409 if (NS_SUCCEEDED(rv
)) {
410 mCurrentNodes
.AppendElement(node
);
412 DispatchEvent(node
, false);
414 } while (NS_FAILED(rv
));
417 void nsPrefetchService::NotifyLoadRequested(nsPrefetchNode
* node
) {
418 nsCOMPtr
<nsIObserverService
> observerService
=
419 mozilla::services::GetObserverService();
420 if (!observerService
) return;
422 observerService
->NotifyObservers(
423 static_cast<nsIStreamListener
*>(node
),
424 (node
->mPreload
) ? "preload-load-requested" : "prefetch-load-requested",
428 void nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode
* node
) {
429 nsCOMPtr
<nsIObserverService
> observerService
=
430 mozilla::services::GetObserverService();
431 if (!observerService
) return;
433 observerService
->NotifyObservers(
434 static_cast<nsIStreamListener
*>(node
),
435 (node
->mPreload
) ? "preload-load-completed" : "prefetch-load-completed",
439 void nsPrefetchService::DispatchEvent(nsPrefetchNode
* node
, bool aSuccess
) {
440 for (uint32_t i
= 0; i
< node
->mSources
.Length(); i
++) {
441 nsCOMPtr
<nsINode
> domNode
= do_QueryReferent(node
->mSources
.ElementAt(i
));
442 if (domNode
&& domNode
->IsInComposedDoc()) {
443 // We don't dispatch synchronously since |node| might be in a DocGroup
444 // that we're not allowed to touch. (Our network request happens in the
445 // DocGroup of one of the mSources nodes--not necessarily this one).
446 RefPtr
<AsyncEventDispatcher
> dispatcher
= new AsyncEventDispatcher(
447 domNode
, aSuccess
? u
"load"_ns
: u
"error"_ns
, CanBubble::eNo
);
448 dispatcher
->RequireNodeInDocument();
449 dispatcher
->PostDOMEvent();
454 //-----------------------------------------------------------------------------
455 // nsPrefetchService <private>
456 //-----------------------------------------------------------------------------
458 void nsPrefetchService::AddProgressListener() {
459 // Register as an observer for the document loader
460 nsCOMPtr
<nsIWebProgress
> progress
= components::DocLoader::Service();
462 progress
->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT
);
465 void nsPrefetchService::RemoveProgressListener() {
466 // Register as an observer for the document loader
467 nsCOMPtr
<nsIWebProgress
> progress
= components::DocLoader::Service();
468 if (progress
) progress
->RemoveProgressListener(this);
471 nsresult
nsPrefetchService::EnqueueURI(nsIURI
* aURI
,
472 nsIReferrerInfo
* aReferrerInfo
,
474 nsPrefetchNode
** aNode
) {
475 RefPtr
<nsPrefetchNode
> node
= new nsPrefetchNode(
476 this, aURI
, aReferrerInfo
, aSource
, nsIContentPolicy::TYPE_OTHER
, false);
477 mPrefetchQueue
.push_back(node
);
482 void nsPrefetchService::EmptyPrefetchQueue() {
483 while (!mPrefetchQueue
.empty()) {
484 mPrefetchQueue
.pop_back();
488 void nsPrefetchService::StartPrefetching() {
490 // at initialization time we might miss the first DOCUMENT START
491 // notification, so we have to be careful to avoid letting our
492 // stop count go negative.
494 if (mStopCount
> 0) mStopCount
--;
496 LOG(("StartPrefetching [stopcount=%d]\n", mStopCount
));
498 // only start prefetching after we've received enough DOCUMENT
499 // STOP notifications. we do this inorder to defer prefetching
500 // until after all sub-frames have finished loading.
502 mHaveProcessed
= true;
503 while (!mPrefetchQueue
.empty() &&
504 mCurrentNodes
.Length() < static_cast<uint32_t>(mMaxParallelism
)) {
505 ProcessNextPrefetchURI();
510 void nsPrefetchService::StopPrefetching() {
513 LOG(("StopPrefetching [stopcount=%d]\n", mStopCount
));
515 // When we start a load, we need to stop all prefetches that has been
516 // added by the old load, therefore call StopAll only at the moment we
517 // switch to a new page load (i.e. mStopCount == 1).
518 // TODO: do not stop prefetches that are relevant for the new load.
519 if (mStopCount
== 1) {
524 void nsPrefetchService::StopCurrentPrefetchsPreloads(bool aPreload
) {
525 for (int32_t i
= mCurrentNodes
.Length() - 1; i
>= 0; --i
) {
526 if (mCurrentNodes
[i
]->mPreload
== aPreload
) {
527 mCurrentNodes
[i
]->CancelChannel(NS_BINDING_ABORTED
);
528 mCurrentNodes
.RemoveElementAt(i
);
533 EmptyPrefetchQueue();
537 void nsPrefetchService::StopAll() {
538 for (uint32_t i
= 0; i
< mCurrentNodes
.Length(); ++i
) {
539 mCurrentNodes
[i
]->CancelChannel(NS_BINDING_ABORTED
);
541 mCurrentNodes
.Clear();
542 EmptyPrefetchQueue();
545 nsresult
nsPrefetchService::CheckURIScheme(nsIURI
* aURI
,
546 nsIReferrerInfo
* aReferrerInfo
) {
548 // XXX we should really be asking the protocol handler if it supports
549 // caching, so we can determine if there is any value to prefetching.
550 // for now, we'll only prefetch http and https links since we know that's
551 // the most common case.
553 if (!aURI
->SchemeIs("http") && !aURI
->SchemeIs("https")) {
554 LOG(("rejected: URL is not of type http/https\n"));
555 return NS_ERROR_ABORT
;
559 // the referrer URI must be http:
561 nsCOMPtr
<nsIURI
> referrer
= aReferrerInfo
->GetOriginalReferrer();
563 return NS_ERROR_ABORT
;
566 if (!referrer
->SchemeIs("http") && !referrer
->SchemeIs("https")) {
567 LOG(("rejected: referrer URL is neither http nor https\n"));
568 return NS_ERROR_ABORT
;
574 //-----------------------------------------------------------------------------
575 // nsPrefetchService::nsISupports
576 //-----------------------------------------------------------------------------
578 NS_IMPL_ISUPPORTS(nsPrefetchService
, nsIPrefetchService
, nsIWebProgressListener
,
579 nsIObserver
, nsISupportsWeakReference
)
581 //-----------------------------------------------------------------------------
582 // nsPrefetchService::nsIPrefetchService
583 //-----------------------------------------------------------------------------
585 nsresult
nsPrefetchService::Preload(nsIURI
* aURI
,
586 nsIReferrerInfo
* aReferrerInfo
,
588 nsContentPolicyType aPolicyType
) {
589 NS_ENSURE_ARG_POINTER(aURI
);
590 NS_ENSURE_ARG_POINTER(aReferrerInfo
);
592 LOG(("PreloadURI [%s]\n", aURI
->GetSpecOrDefault().get()));
595 LOG(("rejected: preload service is deprecated\n"));
596 return NS_ERROR_ABORT
;
599 nsresult
nsPrefetchService::Prefetch(nsIURI
* aURI
,
600 nsIReferrerInfo
* aReferrerInfo
,
601 nsINode
* aSource
, bool aExplicit
) {
602 NS_ENSURE_ARG_POINTER(aURI
);
603 NS_ENSURE_ARG_POINTER(aReferrerInfo
);
606 LOG(("PrefetchURI [%s]\n", aURI
->GetSpecOrDefault().get()));
609 if (mPrefetchDisabled
) {
610 LOG(("rejected: prefetch service is disabled\n"));
611 return NS_ERROR_ABORT
;
614 nsresult rv
= CheckURIScheme(aURI
, aReferrerInfo
);
619 // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
620 // or possibly nsIRequest::loadFlags to determine if this URI should be
624 // skip URLs that contain query strings, except URLs for which prefetching
625 // has been explicitly requested.
627 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aURI
, &rv
));
628 if (NS_FAILED(rv
)) return rv
;
630 rv
= url
->GetQuery(query
);
631 if (NS_FAILED(rv
) || !query
.IsEmpty()) {
632 LOG(("rejected: URL has a query string\n"));
633 return NS_ERROR_ABORT
;
638 // Check whether it is being prefetched.
640 for (uint32_t i
= 0; i
< mCurrentNodes
.Length(); ++i
) {
642 if (NS_SUCCEEDED(mCurrentNodes
[i
]->mURI
->Equals(aURI
, &equals
)) && equals
) {
643 nsWeakPtr source
= do_GetWeakReference(aSource
);
644 if (mCurrentNodes
[i
]->mSources
.IndexOf(source
) ==
645 mCurrentNodes
[i
]->mSources
.NoIndex
) {
647 ("URL is already being prefetched, add a new reference "
649 mCurrentNodes
[i
]->mSources
.AppendElement(source
);
652 LOG(("URL is already being prefetched by this document"));
653 return NS_ERROR_ABORT
;
659 // Check whether it is on the prefetch queue.
661 for (std::deque
<RefPtr
<nsPrefetchNode
>>::iterator nodeIt
=
662 mPrefetchQueue
.begin();
663 nodeIt
!= mPrefetchQueue
.end(); nodeIt
++) {
665 RefPtr
<nsPrefetchNode
> node
= nodeIt
->get();
666 if (NS_SUCCEEDED(node
->mURI
->Equals(aURI
, &equals
)) && equals
) {
667 nsWeakPtr source
= do_GetWeakReference(aSource
);
668 if (node
->mSources
.IndexOf(source
) == node
->mSources
.NoIndex
) {
670 ("URL is already being prefetched, add a new reference "
672 node
->mSources
.AppendElement(do_GetWeakReference(aSource
));
675 LOG(("URL is already being prefetched by this document"));
676 return NS_ERROR_ABORT
;
681 RefPtr
<nsPrefetchNode
> enqueuedNode
;
682 rv
= EnqueueURI(aURI
, aReferrerInfo
, aSource
, getter_AddRefs(enqueuedNode
));
683 NS_ENSURE_SUCCESS(rv
, rv
);
685 NotifyLoadRequested(enqueuedNode
);
687 // if there are no pages loading, kick off the request immediately
688 if ((!mStopCount
&& mHaveProcessed
) || mAggressive
) {
689 ProcessNextPrefetchURI();
696 nsPrefetchService::CancelPrefetchPreloadURI(nsIURI
* aURI
, nsINode
* aSource
) {
697 NS_ENSURE_ARG_POINTER(aURI
);
700 LOG(("CancelPrefetchURI [%s]\n", aURI
->GetSpecOrDefault().get()));
704 // look in current prefetches
706 for (uint32_t i
= 0; i
< mCurrentNodes
.Length(); ++i
) {
708 if (NS_SUCCEEDED(mCurrentNodes
[i
]->mURI
->Equals(aURI
, &equals
)) && equals
) {
709 nsWeakPtr source
= do_GetWeakReference(aSource
);
710 if (mCurrentNodes
[i
]->mSources
.IndexOf(source
) !=
711 mCurrentNodes
[i
]->mSources
.NoIndex
) {
712 mCurrentNodes
[i
]->mSources
.RemoveElement(source
);
713 if (mCurrentNodes
[i
]->mSources
.IsEmpty()) {
714 mCurrentNodes
[i
]->CancelChannel(NS_BINDING_ABORTED
);
715 mCurrentNodes
.RemoveElementAt(i
);
719 return NS_ERROR_FAILURE
;
724 // look into the prefetch queue
726 for (std::deque
<RefPtr
<nsPrefetchNode
>>::iterator nodeIt
=
727 mPrefetchQueue
.begin();
728 nodeIt
!= mPrefetchQueue
.end(); nodeIt
++) {
730 RefPtr
<nsPrefetchNode
> node
= nodeIt
->get();
731 if (NS_SUCCEEDED(node
->mURI
->Equals(aURI
, &equals
)) && equals
) {
732 nsWeakPtr source
= do_GetWeakReference(aSource
);
733 if (node
->mSources
.IndexOf(source
) != node
->mSources
.NoIndex
) {
735 int32_t inx
= node
->mSources
.IndexOf(source
);
736 nsCOMPtr
<nsINode
> domNode
=
737 do_QueryReferent(node
->mSources
.ElementAt(inx
));
741 node
->mSources
.RemoveElement(source
);
742 if (node
->mSources
.IsEmpty()) {
743 mPrefetchQueue
.erase(nodeIt
);
747 return NS_ERROR_FAILURE
;
752 return NS_ERROR_FAILURE
;
756 nsPrefetchService::PreloadURI(nsIURI
* aURI
, nsIReferrerInfo
* aReferrerInfo
,
758 nsContentPolicyType aPolicyType
) {
759 return Preload(aURI
, aReferrerInfo
, aSource
, aPolicyType
);
763 nsPrefetchService::PrefetchURI(nsIURI
* aURI
, nsIReferrerInfo
* aReferrerInfo
,
764 nsINode
* aSource
, bool aExplicit
) {
765 return Prefetch(aURI
, aReferrerInfo
, aSource
, aExplicit
);
769 nsPrefetchService::HasMoreElements(bool* aHasMore
) {
770 *aHasMore
= (mCurrentNodes
.Length() || !mPrefetchQueue
.empty());
774 //-----------------------------------------------------------------------------
775 // nsPrefetchService::nsIWebProgressListener
776 //-----------------------------------------------------------------------------
779 nsPrefetchService::OnProgressChange(nsIWebProgress
* aProgress
,
780 nsIRequest
* aRequest
,
781 int32_t curSelfProgress
,
782 int32_t maxSelfProgress
,
783 int32_t curTotalProgress
,
784 int32_t maxTotalProgress
) {
785 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
790 nsPrefetchService::OnStateChange(nsIWebProgress
* aWebProgress
,
791 nsIRequest
* aRequest
,
792 uint32_t progressStateFlags
,
794 if (progressStateFlags
& STATE_IS_DOCUMENT
) {
795 if (progressStateFlags
& STATE_STOP
)
797 else if (progressStateFlags
& STATE_START
)
805 nsPrefetchService::OnLocationChange(nsIWebProgress
* aWebProgress
,
806 nsIRequest
* aRequest
, nsIURI
* location
,
808 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
813 nsPrefetchService::OnStatusChange(nsIWebProgress
* aWebProgress
,
814 nsIRequest
* aRequest
, nsresult aStatus
,
815 const char16_t
* aMessage
) {
816 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
821 nsPrefetchService::OnSecurityChange(nsIWebProgress
* aWebProgress
,
822 nsIRequest
* aRequest
, uint32_t aState
) {
823 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
828 nsPrefetchService::OnContentBlockingEvent(nsIWebProgress
* aWebProgress
,
829 nsIRequest
* aRequest
,
831 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
835 //-----------------------------------------------------------------------------
836 // nsPrefetchService::nsIObserver
837 //-----------------------------------------------------------------------------
840 nsPrefetchService::Observe(nsISupports
* aSubject
, const char* aTopic
,
841 const char16_t
* aData
) {
842 LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic
));
844 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
846 mPrefetchDisabled
= true;
847 } else if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
848 const nsCString converted
= NS_ConvertUTF16toUTF8(aData
);
849 const char* pref
= converted
.get();
850 if (!strcmp(pref
, PREFETCH_PREF
)) {
851 if (Preferences::GetBool(PREFETCH_PREF
, false)) {
852 if (mPrefetchDisabled
) {
853 LOG(("enabling prefetching\n"));
854 mPrefetchDisabled
= false;
855 AddProgressListener();
858 if (!mPrefetchDisabled
) {
859 LOG(("disabling prefetching\n"));
860 StopCurrentPrefetchsPreloads(false);
861 mPrefetchDisabled
= true;
862 RemoveProgressListener();
865 } else if (!strcmp(pref
, PARALLELISM_PREF
)) {
866 mMaxParallelism
= Preferences::GetInt(PARALLELISM_PREF
, mMaxParallelism
);
867 if (mMaxParallelism
< 1) {
870 // If our parallelism has increased, go ahead and kick off enough
871 // prefetches to fill up our allowance. If we're now over our
872 // allowance, we'll just silently let some of them finish to get
873 // back below our limit.
874 while (((!mStopCount
&& mHaveProcessed
) || mAggressive
) &&
875 !mPrefetchQueue
.empty() &&
876 mCurrentNodes
.Length() < static_cast<uint32_t>(mMaxParallelism
)) {
877 ProcessNextPrefetchURI();
879 } else if (!strcmp(pref
, AGGRESSIVE_PREF
)) {
880 mAggressive
= Preferences::GetBool(AGGRESSIVE_PREF
, false);
881 // in aggressive mode, start prefetching immediately
883 while (mStopCount
&& !mPrefetchQueue
.empty() &&
884 mCurrentNodes
.Length() <
885 static_cast<uint32_t>(mMaxParallelism
)) {
886 ProcessNextPrefetchURI();
895 // vim: ts=4 sw=2 expandtab