1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "nsPrefetchService.h"
6 #include "nsICacheEntry.h"
7 #include "nsIServiceManager.h"
8 #include "nsICategoryManager.h"
9 #include "nsIObserverService.h"
10 #include "nsIWebProgress.h"
11 #include "nsCURILoader.h"
12 #include "nsICachingChannel.h"
13 #include "nsIHttpChannel.h"
15 #include "nsISimpleEnumerator.h"
16 #include "nsNetUtil.h"
18 #include "nsXPIDLString.h"
19 #include "nsReadableUtils.h"
20 #include "nsStreamUtils.h"
21 #include "nsAutoPtr.h"
25 #include "nsIAsyncVerifyRedirectCallback.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/Attributes.h"
28 #include "nsIDOMNode.h"
30 #include "nsIDocument.h"
31 #include "nsContentUtils.h"
33 using namespace mozilla
;
35 #if defined(PR_LOGGING)
37 // To enable logging (see prlog.h for full details):
39 // set NSPR_LOG_MODULES=nsPrefetch:5
40 // set NSPR_LOG_FILE=prefetch.log
42 // this enables PR_LOG_ALWAYS level information and places all output in
45 static PRLogModuleInfo
*gPrefetchLog
;
49 #define LOG(args) PR_LOG(gPrefetchLog, 4, args)
52 #define LOG_ENABLED() PR_LOG_TEST(gPrefetchLog, 4)
54 #define PREFETCH_PREF "network.prefetch-next"
56 //-----------------------------------------------------------------------------
58 //-----------------------------------------------------------------------------
60 static inline uint32_t
61 PRTimeToSeconds(PRTime t_usec
)
63 PRTime usec_per_sec
= PR_USEC_PER_SEC
;
64 return uint32_t(t_usec
/= usec_per_sec
);
67 #define NowInSeconds() PRTimeToSeconds(PR_Now())
69 //-----------------------------------------------------------------------------
70 // nsPrefetchQueueEnumerator
71 //-----------------------------------------------------------------------------
72 class nsPrefetchQueueEnumerator MOZ_FINAL
: public nsISimpleEnumerator
76 NS_DECL_NSISIMPLEENUMERATOR
77 explicit nsPrefetchQueueEnumerator(nsPrefetchService
*aService
);
80 ~nsPrefetchQueueEnumerator();
84 nsRefPtr
<nsPrefetchService
> mService
;
85 nsRefPtr
<nsPrefetchNode
> mCurrent
;
89 //-----------------------------------------------------------------------------
90 // nsPrefetchQueueEnumerator <public>
91 //-----------------------------------------------------------------------------
92 nsPrefetchQueueEnumerator::nsPrefetchQueueEnumerator(nsPrefetchService
*aService
)
99 nsPrefetchQueueEnumerator::~nsPrefetchQueueEnumerator()
103 //-----------------------------------------------------------------------------
104 // nsPrefetchQueueEnumerator::nsISimpleEnumerator
105 //-----------------------------------------------------------------------------
107 nsPrefetchQueueEnumerator::HasMoreElements(bool *aHasMore
)
109 *aHasMore
= (mCurrent
!= nullptr);
114 nsPrefetchQueueEnumerator::GetNext(nsISupports
**aItem
)
116 if (!mCurrent
) return NS_ERROR_FAILURE
;
118 NS_ADDREF(*aItem
= static_cast<nsIStreamListener
*>(mCurrent
.get()));
125 //-----------------------------------------------------------------------------
126 // nsPrefetchQueueEnumerator <private>
127 //-----------------------------------------------------------------------------
130 nsPrefetchQueueEnumerator::Increment()
133 // If the service is currently serving a request, it won't be in
134 // the pending queue, so we return it first. If it isn't, we'll
135 // just start with the pending queue.
137 mCurrent
= mService
->GetCurrentNode();
139 mCurrent
= mService
->GetQueueHead();
144 if (mCurrent
== mService
->GetCurrentNode()) {
145 // If we just returned the node being processed by the service,
146 // start with the pending queue
147 mCurrent
= mService
->GetQueueHead();
150 // Otherwise just advance to the next item in the queue
151 mCurrent
= mCurrent
->mNext
;
156 //-----------------------------------------------------------------------------
157 // nsPrefetchQueueEnumerator::nsISupports
158 //-----------------------------------------------------------------------------
160 NS_IMPL_ISUPPORTS(nsPrefetchQueueEnumerator
, nsISimpleEnumerator
)
162 //-----------------------------------------------------------------------------
163 // nsPrefetchNode <public>
164 //-----------------------------------------------------------------------------
166 nsPrefetchNode::nsPrefetchNode(nsPrefetchService
*aService
,
168 nsIURI
*aReferrerURI
,
172 , mReferrerURI(aReferrerURI
)
177 mSource
= do_GetWeakReference(aSource
);
181 nsPrefetchNode::OpenChannel()
183 nsCOMPtr
<nsINode
> source
= do_QueryReferent(mSource
);
185 // Don't attempt to prefetch if we don't have a source node
186 // (which should never happen).
187 return NS_ERROR_FAILURE
;
189 nsCOMPtr
<nsILoadGroup
> loadGroup
= source
->OwnerDoc()->GetDocumentLoadGroup();
190 nsresult rv
= NS_NewChannel(getter_AddRefs(mChannel
),
192 nsContentUtils::GetSystemPrincipal(),
193 nsILoadInfo::SEC_NORMAL
,
194 nsIContentPolicy::TYPE_OTHER
,
195 loadGroup
, // aLoadGroup
197 nsIRequest::LOAD_BACKGROUND
|
198 nsICachingChannel::LOAD_ONLY_IF_MODIFIED
);
200 NS_ENSURE_SUCCESS(rv
, rv
);
202 // configure HTTP specific stuff
203 nsCOMPtr
<nsIHttpChannel
> httpChannel
=
204 do_QueryInterface(mChannel
);
206 httpChannel
->SetReferrer(mReferrerURI
);
207 httpChannel
->SetRequestHeader(
208 NS_LITERAL_CSTRING("X-Moz"),
209 NS_LITERAL_CSTRING("prefetch"),
213 rv
= mChannel
->AsyncOpen(this, nullptr);
214 NS_ENSURE_SUCCESS(rv
, rv
);
220 nsPrefetchNode::CancelChannel(nsresult error
)
222 mChannel
->Cancel(error
);
228 //-----------------------------------------------------------------------------
229 // nsPrefetchNode::nsISupports
230 //-----------------------------------------------------------------------------
232 NS_IMPL_ISUPPORTS(nsPrefetchNode
,
235 nsIInterfaceRequestor
,
237 nsIRedirectResultListener
)
239 //-----------------------------------------------------------------------------
240 // nsPrefetchNode::nsIStreamListener
241 //-----------------------------------------------------------------------------
244 nsPrefetchNode::OnStartRequest(nsIRequest
*aRequest
,
245 nsISupports
*aContext
)
249 nsCOMPtr
<nsICachingChannel
> cachingChannel
=
250 do_QueryInterface(aRequest
, &rv
);
251 if (NS_FAILED(rv
)) return rv
;
253 // no need to prefetch a document that is already in the cache
255 if (NS_SUCCEEDED(cachingChannel
->IsFromCache(&fromCache
)) &&
257 LOG(("document is already in the cache; canceling prefetch\n"));
258 return NS_BINDING_ABORTED
;
262 // no need to prefetch a document that must be requested fresh each
265 nsCOMPtr
<nsISupports
> cacheToken
;
266 cachingChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
268 return NS_ERROR_ABORT
; // bail, no cache entry
270 nsCOMPtr
<nsICacheEntry
> entryInfo
=
271 do_QueryInterface(cacheToken
, &rv
);
272 if (NS_FAILED(rv
)) return rv
;
275 if (NS_SUCCEEDED(entryInfo
->GetExpirationTime(&expTime
))) {
276 if (NowInSeconds() >= expTime
) {
277 LOG(("document cannot be reused from cache; "
278 "canceling prefetch\n"));
279 return NS_BINDING_ABORTED
;
287 nsPrefetchNode::OnDataAvailable(nsIRequest
*aRequest
,
288 nsISupports
*aContext
,
289 nsIInputStream
*aStream
,
293 uint32_t bytesRead
= 0;
294 aStream
->ReadSegments(NS_DiscardSegment
, nullptr, aCount
, &bytesRead
);
295 mBytesRead
+= bytesRead
;
296 LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead
, aOffset
));
302 nsPrefetchNode::OnStopRequest(nsIRequest
*aRequest
,
303 nsISupports
*aContext
,
306 LOG(("done prefetching [status=%x]\n", aStatus
));
308 if (mBytesRead
== 0 && aStatus
== NS_OK
) {
309 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
310 // specified), but the object should report loadedSize as if it
312 mChannel
->GetContentLength(&mBytesRead
);
315 mService
->NotifyLoadCompleted(this);
316 mService
->ProcessNextURI();
320 //-----------------------------------------------------------------------------
321 // nsPrefetchNode::nsIInterfaceRequestor
322 //-----------------------------------------------------------------------------
325 nsPrefetchNode::GetInterface(const nsIID
&aIID
, void **aResult
)
327 if (aIID
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
329 *aResult
= static_cast<nsIChannelEventSink
*>(this);
333 if (aIID
.Equals(NS_GET_IID(nsIRedirectResultListener
))) {
335 *aResult
= static_cast<nsIRedirectResultListener
*>(this);
339 return NS_ERROR_NO_INTERFACE
;
342 //-----------------------------------------------------------------------------
343 // nsPrefetchNode::nsIChannelEventSink
344 //-----------------------------------------------------------------------------
347 nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel
*aOldChannel
,
348 nsIChannel
*aNewChannel
,
350 nsIAsyncVerifyRedirectCallback
*callback
)
352 nsCOMPtr
<nsIURI
> newURI
;
353 nsresult rv
= aNewChannel
->GetURI(getter_AddRefs(newURI
));
358 rv
= newURI
->SchemeIs("http", &match
);
359 if (NS_FAILED(rv
) || !match
) {
360 rv
= newURI
->SchemeIs("https", &match
);
361 if (NS_FAILED(rv
) || !match
) {
362 LOG(("rejected: URL is not of type http/https\n"));
363 return NS_ERROR_ABORT
;
367 // HTTP request headers are not automatically forwarded to the new channel.
368 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aNewChannel
);
369 NS_ENSURE_STATE(httpChannel
);
371 httpChannel
->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
372 NS_LITERAL_CSTRING("prefetch"),
375 // Assign to mChannel after we get notification about success of the
376 // redirect in OnRedirectResult.
377 mRedirectChannel
= aNewChannel
;
379 callback
->OnRedirectVerifyCallback(NS_OK
);
383 //-----------------------------------------------------------------------------
384 // nsPrefetchNode::nsIRedirectResultListener
385 //-----------------------------------------------------------------------------
388 nsPrefetchNode::OnRedirectResult(bool proceeding
)
390 if (proceeding
&& mRedirectChannel
)
391 mChannel
= mRedirectChannel
;
393 mRedirectChannel
= nullptr;
398 //-----------------------------------------------------------------------------
399 // nsPrefetchService <public>
400 //-----------------------------------------------------------------------------
402 nsPrefetchService::nsPrefetchService()
403 : mQueueHead(nullptr)
404 , mQueueTail(nullptr)
406 , mHaveProcessed(false)
411 nsPrefetchService::~nsPrefetchService()
413 Preferences::RemoveObserver(this, PREFETCH_PREF
);
414 // cannot reach destructor if prefetch in progress (listener owns reference
420 nsPrefetchService::Init()
422 #if defined(PR_LOGGING)
424 gPrefetchLog
= PR_NewLogModule("nsPrefetch");
429 // read prefs and hook up pref observer
430 mDisabled
= !Preferences::GetBool(PREFETCH_PREF
, !mDisabled
);
431 Preferences::AddWeakObserver(this, PREFETCH_PREF
);
433 // Observe xpcom-shutdown event
434 nsCOMPtr
<nsIObserverService
> observerService
=
435 mozilla::services::GetObserverService();
436 if (!observerService
)
437 return NS_ERROR_FAILURE
;
439 rv
= observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
440 NS_ENSURE_SUCCESS(rv
, rv
);
443 AddProgressListener();
449 nsPrefetchService::ProcessNextURI()
452 nsCOMPtr
<nsIURI
> uri
, referrer
;
454 mCurrentNode
= nullptr;
457 rv
= DequeueNode(getter_AddRefs(mCurrentNode
));
459 if (NS_FAILED(rv
)) break;
461 #if defined(PR_LOGGING)
464 mCurrentNode
->mURI
->GetSpec(spec
);
465 LOG(("ProcessNextURI [%s]\n", spec
.get()));
470 // if opening the channel fails, then just skip to the next uri
472 nsRefPtr
<nsPrefetchNode
> node
= mCurrentNode
;
473 rv
= node
->OpenChannel();
475 while (NS_FAILED(rv
));
479 nsPrefetchService::NotifyLoadRequested(nsPrefetchNode
*node
)
481 nsCOMPtr
<nsIObserverService
> observerService
=
482 mozilla::services::GetObserverService();
483 if (!observerService
)
486 observerService
->NotifyObservers(static_cast<nsIStreamListener
*>(node
),
487 "prefetch-load-requested", nullptr);
491 nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode
*node
)
493 nsCOMPtr
<nsIObserverService
> observerService
=
494 mozilla::services::GetObserverService();
495 if (!observerService
)
498 observerService
->NotifyObservers(static_cast<nsIStreamListener
*>(node
),
499 "prefetch-load-completed", nullptr);
502 //-----------------------------------------------------------------------------
503 // nsPrefetchService <private>
504 //-----------------------------------------------------------------------------
507 nsPrefetchService::AddProgressListener()
509 // Register as an observer for the document loader
510 nsCOMPtr
<nsIWebProgress
> progress
=
511 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID
);
513 progress
->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT
);
517 nsPrefetchService::RemoveProgressListener()
519 // Register as an observer for the document loader
520 nsCOMPtr
<nsIWebProgress
> progress
=
521 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID
);
523 progress
->RemoveProgressListener(this);
527 nsPrefetchService::EnqueueNode(nsPrefetchNode
*aNode
)
536 mQueueTail
->mNext
= aNode
;
544 nsPrefetchService::EnqueueURI(nsIURI
*aURI
,
545 nsIURI
*aReferrerURI
,
547 nsPrefetchNode
**aNode
)
549 nsPrefetchNode
*node
= new nsPrefetchNode(this, aURI
, aReferrerURI
,
552 return NS_ERROR_OUT_OF_MEMORY
;
554 NS_ADDREF(*aNode
= node
);
556 return EnqueueNode(node
);
560 nsPrefetchService::DequeueNode(nsPrefetchNode
**node
)
563 return NS_ERROR_NOT_AVAILABLE
;
565 // give the ref to the caller
567 mQueueHead
= mQueueHead
->mNext
;
568 (*node
)->mNext
= nullptr;
571 mQueueTail
= nullptr;
577 nsPrefetchService::EmptyQueue()
580 nsRefPtr
<nsPrefetchNode
> node
;
581 DequeueNode(getter_AddRefs(node
));
582 } while (mQueueHead
);
586 nsPrefetchService::StartPrefetching()
589 // at initialization time we might miss the first DOCUMENT START
590 // notification, so we have to be careful to avoid letting our
591 // stop count go negative.
596 LOG(("StartPrefetching [stopcount=%d]\n", mStopCount
));
598 // only start prefetching after we've received enough DOCUMENT
599 // STOP notifications. we do this inorder to defer prefetching
600 // until after all sub-frames have finished loading.
601 if (mStopCount
== 0 && !mCurrentNode
) {
602 mHaveProcessed
= true;
608 nsPrefetchService::StopPrefetching()
612 LOG(("StopPrefetching [stopcount=%d]\n", mStopCount
));
614 // only kill the prefetch queue if we've actually started prefetching.
618 mCurrentNode
->CancelChannel(NS_BINDING_ABORTED
);
619 mCurrentNode
= nullptr;
623 //-----------------------------------------------------------------------------
624 // nsPrefetchService::nsISupports
625 //-----------------------------------------------------------------------------
627 NS_IMPL_ISUPPORTS(nsPrefetchService
,
629 nsIWebProgressListener
,
631 nsISupportsWeakReference
)
633 //-----------------------------------------------------------------------------
634 // nsPrefetchService::nsIPrefetchService
635 //-----------------------------------------------------------------------------
638 nsPrefetchService::Prefetch(nsIURI
*aURI
,
639 nsIURI
*aReferrerURI
,
645 NS_ENSURE_ARG_POINTER(aURI
);
646 NS_ENSURE_ARG_POINTER(aReferrerURI
);
648 #if defined(PR_LOGGING)
652 LOG(("PrefetchURI [%s]\n", spec
.get()));
657 LOG(("rejected: prefetch service is disabled\n"));
658 return NS_ERROR_ABORT
;
662 // XXX we should really be asking the protocol handler if it supports
663 // caching, so we can determine if there is any value to prefetching.
664 // for now, we'll only prefetch http links since we know that's the
665 // most common case. ignore https links since https content only goes
666 // into the memory cache.
668 // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
669 // or possibly nsIRequest::loadFlags to determine if this URI should be
673 rv
= aURI
->SchemeIs("http", &match
);
674 if (NS_FAILED(rv
) || !match
) {
675 rv
= aURI
->SchemeIs("https", &match
);
676 if (NS_FAILED(rv
) || !match
) {
677 LOG(("rejected: URL is not of type http/https\n"));
678 return NS_ERROR_ABORT
;
683 // the referrer URI must be http:
685 rv
= aReferrerURI
->SchemeIs("http", &match
);
686 if (NS_FAILED(rv
) || !match
) {
687 rv
= aReferrerURI
->SchemeIs("https", &match
);
688 if (NS_FAILED(rv
) || !match
) {
689 LOG(("rejected: referrer URL is neither http nor https\n"));
690 return NS_ERROR_ABORT
;
694 // skip URLs that contain query strings, except URLs for which prefetching
695 // has been explicitly requested.
697 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aURI
, &rv
));
698 if (NS_FAILED(rv
)) return rv
;
700 rv
= url
->GetQuery(query
);
701 if (NS_FAILED(rv
) || !query
.IsEmpty()) {
702 LOG(("rejected: URL has a query string\n"));
703 return NS_ERROR_ABORT
;
708 // cancel if being prefetched
712 if (NS_SUCCEEDED(mCurrentNode
->mURI
->Equals(aURI
, &equals
)) && equals
) {
713 LOG(("rejected: URL is already being prefetched\n"));
714 return NS_ERROR_ABORT
;
719 // cancel if already on the prefetch queue
721 nsPrefetchNode
*node
= mQueueHead
;
722 for (; node
; node
= node
->mNext
) {
724 if (NS_SUCCEEDED(node
->mURI
->Equals(aURI
, &equals
)) && equals
) {
725 LOG(("rejected: URL is already on prefetch queue\n"));
726 return NS_ERROR_ABORT
;
730 nsRefPtr
<nsPrefetchNode
> enqueuedNode
;
731 rv
= EnqueueURI(aURI
, aReferrerURI
, aSource
,
732 getter_AddRefs(enqueuedNode
));
733 NS_ENSURE_SUCCESS(rv
, rv
);
735 NotifyLoadRequested(enqueuedNode
);
737 // if there are no pages loading, kick off the request immediately
738 if (mStopCount
== 0 && mHaveProcessed
)
745 nsPrefetchService::PrefetchURI(nsIURI
*aURI
,
746 nsIURI
*aReferrerURI
,
750 return Prefetch(aURI
, aReferrerURI
, aSource
, aExplicit
);
754 nsPrefetchService::EnumerateQueue(nsISimpleEnumerator
**aEnumerator
)
756 *aEnumerator
= new nsPrefetchQueueEnumerator(this);
757 if (!*aEnumerator
) return NS_ERROR_OUT_OF_MEMORY
;
759 NS_ADDREF(*aEnumerator
);
764 //-----------------------------------------------------------------------------
765 // nsPrefetchService::nsIWebProgressListener
766 //-----------------------------------------------------------------------------
769 nsPrefetchService::OnProgressChange(nsIWebProgress
*aProgress
,
770 nsIRequest
*aRequest
,
771 int32_t curSelfProgress
,
772 int32_t maxSelfProgress
,
773 int32_t curTotalProgress
,
774 int32_t maxTotalProgress
)
776 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
781 nsPrefetchService::OnStateChange(nsIWebProgress
* aWebProgress
,
782 nsIRequest
*aRequest
,
783 uint32_t progressStateFlags
,
786 if (progressStateFlags
& STATE_IS_DOCUMENT
) {
787 if (progressStateFlags
& STATE_STOP
)
789 else if (progressStateFlags
& STATE_START
)
798 nsPrefetchService::OnLocationChange(nsIWebProgress
* aWebProgress
,
799 nsIRequest
* aRequest
,
803 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
808 nsPrefetchService::OnStatusChange(nsIWebProgress
* aWebProgress
,
809 nsIRequest
* aRequest
,
811 const char16_t
* aMessage
)
813 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
818 nsPrefetchService::OnSecurityChange(nsIWebProgress
*aWebProgress
,
819 nsIRequest
*aRequest
,
822 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
826 //-----------------------------------------------------------------------------
827 // nsPrefetchService::nsIObserver
828 //-----------------------------------------------------------------------------
831 nsPrefetchService::Observe(nsISupports
*aSubject
,
833 const char16_t
*aData
)
835 LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic
));
837 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
842 else if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
843 if (Preferences::GetBool(PREFETCH_PREF
, false)) {
845 LOG(("enabling prefetching\n"));
847 AddProgressListener();
852 LOG(("disabling prefetching\n"));
856 RemoveProgressListener();
864 // vim: ts=4 sw=4 expandtab