Bug 1829125 - Align the PHC area to the jemalloc chunk size r=glandium
[gecko.git] / uriloader / prefetch / nsPrefetchService.cpp
blob635e4e34a948093d7f7378f977e2e039a2bc3559
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"
22 #include "nsIURL.h"
23 #include "nsISupportsPriority.h"
24 #include "nsNetUtil.h"
25 #include "nsString.h"
26 #include "nsReadableUtils.h"
27 #include "nsStreamUtils.h"
28 #include "prtime.h"
29 #include "mozilla/Logging.h"
30 #include "nsIAsyncVerifyRedirectCallback.h"
31 #include "nsINode.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsContentUtils.h"
34 #include "mozilla/AsyncEventDispatcher.h"
35 #include "nsICachingChannel.h"
36 #include "nsHttp.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");
52 #undef LOG
53 #define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
55 #undef LOG_ENABLED
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)
69 : mURI(aURI),
70 mReferrerInfo(aReferrerInfo),
71 mPolicyType(aPolicyType),
72 mPreload(aPreload),
73 mService(aService),
74 mChannel(nullptr),
75 mBytesRead(0),
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);
95 if (!source) {
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;
110 } else {
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
123 this, // aCallbacks
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);
130 if (httpChannel) {
131 DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(mReferrerInfo);
132 MOZ_ASSERT(NS_SUCCEEDED(success));
134 // https://fetch.spec.whatwg.org/#http-sec-purpose
135 success =
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.
150 mChannel = nullptr;
152 return rv;
155 nsresult nsPrefetchNode::CancelChannel(nsresult error) {
156 mChannel->Cancel(error);
157 mChannel = nullptr;
159 return NS_OK;
162 //-----------------------------------------------------------------------------
163 // nsPrefetchNode::nsISupports
164 //-----------------------------------------------------------------------------
166 NS_IMPL_ISUPPORTS(nsPrefetchNode, nsIRequestObserver, nsIStreamListener,
167 nsIInterfaceRequestor, nsIChannelEventSink,
168 nsIRedirectResultListener)
170 //-----------------------------------------------------------------------------
171 // nsPrefetchNode::nsIStreamListener
172 //-----------------------------------------------------------------------------
174 NS_IMETHODIMP
175 nsPrefetchNode::OnStartRequest(nsIRequest* aRequest) {
176 nsresult rv;
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)) ||
192 !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
201 bool fromCache;
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
211 // and every time.
213 uint32_t expTime;
214 if (NS_SUCCEEDED(cacheInfoChannel->GetCacheTokenExpirationTime(&expTime))) {
215 if (mozilla::net::NowInSeconds() >= expTime) {
216 LOG(
217 ("document cannot be reused from cache; "
218 "canceling prefetch\n"));
219 return NS_BINDING_ABORTED;
223 return NS_OK;
226 NS_IMETHODIMP
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));
233 return NS_OK;
236 NS_IMETHODIMP
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
244 // did.
245 mChannel->GetContentLength(&mBytesRead);
248 mService->NotifyLoadCompleted(this);
249 mService->DispatchEvent(this, mShouldFireLoadEvent || NS_SUCCEEDED(aStatus));
250 mService->RemoveNodeAndMaybeStartNextPrefetchURI(this);
251 return NS_OK;
254 //-----------------------------------------------------------------------------
255 // nsPrefetchNode::nsIInterfaceRequestor
256 //-----------------------------------------------------------------------------
258 NS_IMETHODIMP
259 nsPrefetchNode::GetInterface(const nsIID& aIID, void** aResult) {
260 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
261 NS_ADDREF_THIS();
262 *aResult = static_cast<nsIChannelEventSink*>(this);
263 return NS_OK;
266 if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
267 NS_ADDREF_THIS();
268 *aResult = static_cast<nsIRedirectResultListener*>(this);
269 return NS_OK;
272 return NS_ERROR_NO_INTERFACE;
275 //-----------------------------------------------------------------------------
276 // nsPrefetchNode::nsIChannelEventSink
277 //-----------------------------------------------------------------------------
279 NS_IMETHODIMP
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);
305 return NS_OK;
308 //-----------------------------------------------------------------------------
309 // nsPrefetchNode::nsIRedirectResultListener
310 //-----------------------------------------------------------------------------
312 NS_IMETHODIMP
313 nsPrefetchNode::OnRedirectResult(nsresult status) {
314 if (NS_SUCCEEDED(status) && mRedirectChannel) mChannel = mRedirectChannel;
316 mRedirectChannel = nullptr;
318 return NS_OK;
321 //-----------------------------------------------------------------------------
322 // nsPrefetchService <public>
323 //-----------------------------------------------------------------------------
325 nsPrefetchService::nsPrefetchService()
326 : mMaxParallelism(6),
327 mStopCount(0),
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
337 // to this service)
338 EmptyPrefetchQueue();
341 nsresult nsPrefetchService::Init() {
342 nsresult rv;
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) {
350 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();
369 return NS_OK;
372 void nsPrefetchService::RemoveNodeAndMaybeStartNextPrefetchURI(
373 nsPrefetchNode* aFinished) {
374 if (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
386 // for now.
387 return;
390 nsresult rv;
392 do {
393 if (mPrefetchQueue.empty()) {
394 break;
396 RefPtr<nsPrefetchNode> node = std::move(mPrefetchQueue.front());
397 mPrefetchQueue.pop_front();
399 if (LOG_ENABLED()) {
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);
411 } else {
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",
425 nullptr);
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",
436 nullptr);
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();
461 if (progress)
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,
473 nsINode* aSource,
474 nsPrefetchNode** aNode) {
475 RefPtr<nsPrefetchNode> node = new nsPrefetchNode(
476 this, aURI, aReferrerInfo, aSource, nsIContentPolicy::TYPE_OTHER, false);
477 mPrefetchQueue.push_back(node);
478 node.forget(aNode);
479 return NS_OK;
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.
501 if (!mStopCount) {
502 mHaveProcessed = true;
503 while (!mPrefetchQueue.empty() &&
504 mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
505 ProcessNextPrefetchURI();
510 void nsPrefetchService::StopPrefetching() {
511 mStopCount++;
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) {
520 StopAll();
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);
532 if (!aPreload) {
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();
562 if (!referrer) {
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;
571 return NS_OK;
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,
587 nsINode* aSource,
588 nsContentPolicyType aPolicyType) {
589 NS_ENSURE_ARG_POINTER(aURI);
590 NS_ENSURE_ARG_POINTER(aReferrerInfo);
591 if (LOG_ENABLED()) {
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);
605 if (LOG_ENABLED()) {
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);
615 if (NS_FAILED(rv)) {
616 return rv;
619 // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
620 // or possibly nsIRequest::loadFlags to determine if this URI should be
621 // prefetched.
624 // skip URLs that contain query strings, except URLs for which prefetching
625 // has been explicitly requested.
626 if (!aExplicit) {
627 nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
628 if (NS_FAILED(rv)) return rv;
629 nsAutoCString query;
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) {
641 bool equals;
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) {
646 LOG(
647 ("URL is already being prefetched, add a new reference "
648 "document\n"));
649 mCurrentNodes[i]->mSources.AppendElement(source);
650 return NS_OK;
651 } else {
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++) {
664 bool equals;
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) {
669 LOG(
670 ("URL is already being prefetched, add a new reference "
671 "document\n"));
672 node->mSources.AppendElement(do_GetWeakReference(aSource));
673 return NS_OK;
674 } else {
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();
692 return NS_OK;
695 NS_IMETHODIMP
696 nsPrefetchService::CancelPrefetchPreloadURI(nsIURI* aURI, nsINode* aSource) {
697 NS_ENSURE_ARG_POINTER(aURI);
699 if (LOG_ENABLED()) {
700 LOG(("CancelPrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
704 // look in current prefetches
706 for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
707 bool equals;
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);
717 return NS_OK;
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++) {
729 bool equals;
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) {
734 #ifdef DEBUG
735 int32_t inx = node->mSources.IndexOf(source);
736 nsCOMPtr<nsINode> domNode =
737 do_QueryReferent(node->mSources.ElementAt(inx));
738 MOZ_ASSERT(domNode);
739 #endif
741 node->mSources.RemoveElement(source);
742 if (node->mSources.IsEmpty()) {
743 mPrefetchQueue.erase(nodeIt);
745 return NS_OK;
747 return NS_ERROR_FAILURE;
751 // not found!
752 return NS_ERROR_FAILURE;
755 NS_IMETHODIMP
756 nsPrefetchService::PreloadURI(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
757 nsINode* aSource,
758 nsContentPolicyType aPolicyType) {
759 return Preload(aURI, aReferrerInfo, aSource, aPolicyType);
762 NS_IMETHODIMP
763 nsPrefetchService::PrefetchURI(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
764 nsINode* aSource, bool aExplicit) {
765 return Prefetch(aURI, aReferrerInfo, aSource, aExplicit);
768 NS_IMETHODIMP
769 nsPrefetchService::HasMoreElements(bool* aHasMore) {
770 *aHasMore = (mCurrentNodes.Length() || !mPrefetchQueue.empty());
771 return NS_OK;
774 //-----------------------------------------------------------------------------
775 // nsPrefetchService::nsIWebProgressListener
776 //-----------------------------------------------------------------------------
778 NS_IMETHODIMP
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(...)");
786 return NS_OK;
789 NS_IMETHODIMP
790 nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress,
791 nsIRequest* aRequest,
792 uint32_t progressStateFlags,
793 nsresult aStatus) {
794 if (progressStateFlags & STATE_IS_DOCUMENT) {
795 if (progressStateFlags & STATE_STOP)
796 StartPrefetching();
797 else if (progressStateFlags & STATE_START)
798 StopPrefetching();
801 return NS_OK;
804 NS_IMETHODIMP
805 nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
806 nsIRequest* aRequest, nsIURI* location,
807 uint32_t aFlags) {
808 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
809 return NS_OK;
812 NS_IMETHODIMP
813 nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
814 nsIRequest* aRequest, nsresult aStatus,
815 const char16_t* aMessage) {
816 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
817 return NS_OK;
820 NS_IMETHODIMP
821 nsPrefetchService::OnSecurityChange(nsIWebProgress* aWebProgress,
822 nsIRequest* aRequest, uint32_t aState) {
823 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
824 return NS_OK;
827 NS_IMETHODIMP
828 nsPrefetchService::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
829 nsIRequest* aRequest,
830 uint32_t aEvent) {
831 MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
832 return NS_OK;
835 //-----------------------------------------------------------------------------
836 // nsPrefetchService::nsIObserver
837 //-----------------------------------------------------------------------------
839 NS_IMETHODIMP
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)) {
845 StopAll();
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();
857 } else {
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) {
868 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
882 if (mAggressive) {
883 while (mStopCount && !mPrefetchQueue.empty() &&
884 mCurrentNodes.Length() <
885 static_cast<uint32_t>(mMaxParallelism)) {
886 ProcessNextPrefetchURI();
892 return NS_OK;
895 // vim: ts=4 sw=2 expandtab