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"
32 using namespace mozilla
;
34 #if defined(PR_LOGGING)
36 // To enable logging (see prlog.h for full details):
38 // set NSPR_LOG_MODULES=nsPrefetch:5
39 // set NSPR_LOG_FILE=prefetch.log
41 // this enables PR_LOG_ALWAYS level information and places all output in
44 static PRLogModuleInfo
*gPrefetchLog
;
48 #define LOG(args) PR_LOG(gPrefetchLog, 4, args)
51 #define LOG_ENABLED() PR_LOG_TEST(gPrefetchLog, 4)
53 #define PREFETCH_PREF "network.prefetch-next"
55 //-----------------------------------------------------------------------------
57 //-----------------------------------------------------------------------------
59 static inline uint32_t
60 PRTimeToSeconds(PRTime t_usec
)
62 PRTime usec_per_sec
= PR_USEC_PER_SEC
;
63 return uint32_t(t_usec
/= usec_per_sec
);
66 #define NowInSeconds() PRTimeToSeconds(PR_Now())
68 //-----------------------------------------------------------------------------
69 // nsPrefetchQueueEnumerator
70 //-----------------------------------------------------------------------------
71 class nsPrefetchQueueEnumerator MOZ_FINAL
: public nsISimpleEnumerator
75 NS_DECL_NSISIMPLEENUMERATOR
76 nsPrefetchQueueEnumerator(nsPrefetchService
*aService
);
79 ~nsPrefetchQueueEnumerator();
83 nsRefPtr
<nsPrefetchService
> mService
;
84 nsRefPtr
<nsPrefetchNode
> mCurrent
;
88 //-----------------------------------------------------------------------------
89 // nsPrefetchQueueEnumerator <public>
90 //-----------------------------------------------------------------------------
91 nsPrefetchQueueEnumerator::nsPrefetchQueueEnumerator(nsPrefetchService
*aService
)
98 nsPrefetchQueueEnumerator::~nsPrefetchQueueEnumerator()
102 //-----------------------------------------------------------------------------
103 // nsPrefetchQueueEnumerator::nsISimpleEnumerator
104 //-----------------------------------------------------------------------------
106 nsPrefetchQueueEnumerator::HasMoreElements(bool *aHasMore
)
108 *aHasMore
= (mCurrent
!= nullptr);
113 nsPrefetchQueueEnumerator::GetNext(nsISupports
**aItem
)
115 if (!mCurrent
) return NS_ERROR_FAILURE
;
117 NS_ADDREF(*aItem
= static_cast<nsIStreamListener
*>(mCurrent
.get()));
124 //-----------------------------------------------------------------------------
125 // nsPrefetchQueueEnumerator <private>
126 //-----------------------------------------------------------------------------
129 nsPrefetchQueueEnumerator::Increment()
132 // If the service is currently serving a request, it won't be in
133 // the pending queue, so we return it first. If it isn't, we'll
134 // just start with the pending queue.
136 mCurrent
= mService
->GetCurrentNode();
138 mCurrent
= mService
->GetQueueHead();
143 if (mCurrent
== mService
->GetCurrentNode()) {
144 // If we just returned the node being processed by the service,
145 // start with the pending queue
146 mCurrent
= mService
->GetQueueHead();
149 // Otherwise just advance to the next item in the queue
150 mCurrent
= mCurrent
->mNext
;
155 //-----------------------------------------------------------------------------
156 // nsPrefetchQueueEnumerator::nsISupports
157 //-----------------------------------------------------------------------------
159 NS_IMPL_ISUPPORTS(nsPrefetchQueueEnumerator
, nsISimpleEnumerator
)
161 //-----------------------------------------------------------------------------
162 // nsPrefetchNode <public>
163 //-----------------------------------------------------------------------------
165 nsPrefetchNode::nsPrefetchNode(nsPrefetchService
*aService
,
167 nsIURI
*aReferrerURI
,
171 , mReferrerURI(aReferrerURI
)
176 mSource
= do_GetWeakReference(aSource
);
180 nsPrefetchNode::OpenChannel()
182 nsCOMPtr
<nsINode
> source
= do_QueryReferent(mSource
);
184 // Don't attempt to prefetch if we don't have a source node
185 // (which should never happen).
186 return NS_ERROR_FAILURE
;
188 nsCOMPtr
<nsILoadGroup
> loadGroup
= source
->OwnerDoc()->GetDocumentLoadGroup();
189 nsresult rv
= NS_NewChannel(getter_AddRefs(mChannel
),
191 nullptr, loadGroup
, this,
192 nsIRequest::LOAD_BACKGROUND
|
193 nsICachingChannel::LOAD_ONLY_IF_MODIFIED
);
194 NS_ENSURE_SUCCESS(rv
, rv
);
196 // configure HTTP specific stuff
197 nsCOMPtr
<nsIHttpChannel
> httpChannel
=
198 do_QueryInterface(mChannel
);
200 httpChannel
->SetReferrer(mReferrerURI
);
201 httpChannel
->SetRequestHeader(
202 NS_LITERAL_CSTRING("X-Moz"),
203 NS_LITERAL_CSTRING("prefetch"),
207 rv
= mChannel
->AsyncOpen(this, nullptr);
208 NS_ENSURE_SUCCESS(rv
, rv
);
214 nsPrefetchNode::CancelChannel(nsresult error
)
216 mChannel
->Cancel(error
);
222 //-----------------------------------------------------------------------------
223 // nsPrefetchNode::nsISupports
224 //-----------------------------------------------------------------------------
226 NS_IMPL_ISUPPORTS(nsPrefetchNode
,
229 nsIInterfaceRequestor
,
231 nsIRedirectResultListener
)
233 //-----------------------------------------------------------------------------
234 // nsPrefetchNode::nsIStreamListener
235 //-----------------------------------------------------------------------------
238 nsPrefetchNode::OnStartRequest(nsIRequest
*aRequest
,
239 nsISupports
*aContext
)
243 nsCOMPtr
<nsICachingChannel
> cachingChannel
=
244 do_QueryInterface(aRequest
, &rv
);
245 if (NS_FAILED(rv
)) return rv
;
247 // no need to prefetch a document that is already in the cache
249 if (NS_SUCCEEDED(cachingChannel
->IsFromCache(&fromCache
)) &&
251 LOG(("document is already in the cache; canceling prefetch\n"));
252 return NS_BINDING_ABORTED
;
256 // no need to prefetch a document that must be requested fresh each
259 nsCOMPtr
<nsISupports
> cacheToken
;
260 cachingChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
262 return NS_ERROR_ABORT
; // bail, no cache entry
264 nsCOMPtr
<nsICacheEntry
> entryInfo
=
265 do_QueryInterface(cacheToken
, &rv
);
266 if (NS_FAILED(rv
)) return rv
;
269 if (NS_SUCCEEDED(entryInfo
->GetExpirationTime(&expTime
))) {
270 if (NowInSeconds() >= expTime
) {
271 LOG(("document cannot be reused from cache; "
272 "canceling prefetch\n"));
273 return NS_BINDING_ABORTED
;
281 nsPrefetchNode::OnDataAvailable(nsIRequest
*aRequest
,
282 nsISupports
*aContext
,
283 nsIInputStream
*aStream
,
287 uint32_t bytesRead
= 0;
288 aStream
->ReadSegments(NS_DiscardSegment
, nullptr, aCount
, &bytesRead
);
289 mBytesRead
+= bytesRead
;
290 LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead
, aOffset
));
296 nsPrefetchNode::OnStopRequest(nsIRequest
*aRequest
,
297 nsISupports
*aContext
,
300 LOG(("done prefetching [status=%x]\n", aStatus
));
302 if (mBytesRead
== 0 && aStatus
== NS_OK
) {
303 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
304 // specified), but the object should report loadedSize as if it
306 mChannel
->GetContentLength(&mBytesRead
);
309 mService
->NotifyLoadCompleted(this);
310 mService
->ProcessNextURI();
314 //-----------------------------------------------------------------------------
315 // nsPrefetchNode::nsIInterfaceRequestor
316 //-----------------------------------------------------------------------------
319 nsPrefetchNode::GetInterface(const nsIID
&aIID
, void **aResult
)
321 if (aIID
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
323 *aResult
= static_cast<nsIChannelEventSink
*>(this);
327 if (aIID
.Equals(NS_GET_IID(nsIRedirectResultListener
))) {
329 *aResult
= static_cast<nsIRedirectResultListener
*>(this);
333 return NS_ERROR_NO_INTERFACE
;
336 //-----------------------------------------------------------------------------
337 // nsPrefetchNode::nsIChannelEventSink
338 //-----------------------------------------------------------------------------
341 nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel
*aOldChannel
,
342 nsIChannel
*aNewChannel
,
344 nsIAsyncVerifyRedirectCallback
*callback
)
346 nsCOMPtr
<nsIURI
> newURI
;
347 nsresult rv
= aNewChannel
->GetURI(getter_AddRefs(newURI
));
352 rv
= newURI
->SchemeIs("http", &match
);
353 if (NS_FAILED(rv
) || !match
) {
354 rv
= newURI
->SchemeIs("https", &match
);
355 if (NS_FAILED(rv
) || !match
) {
356 LOG(("rejected: URL is not of type http/https\n"));
357 return NS_ERROR_ABORT
;
361 // HTTP request headers are not automatically forwarded to the new channel.
362 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aNewChannel
);
363 NS_ENSURE_STATE(httpChannel
);
365 httpChannel
->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
366 NS_LITERAL_CSTRING("prefetch"),
369 // Assign to mChannel after we get notification about success of the
370 // redirect in OnRedirectResult.
371 mRedirectChannel
= aNewChannel
;
373 callback
->OnRedirectVerifyCallback(NS_OK
);
377 //-----------------------------------------------------------------------------
378 // nsPrefetchNode::nsIRedirectResultListener
379 //-----------------------------------------------------------------------------
382 nsPrefetchNode::OnRedirectResult(bool proceeding
)
384 if (proceeding
&& mRedirectChannel
)
385 mChannel
= mRedirectChannel
;
387 mRedirectChannel
= nullptr;
392 //-----------------------------------------------------------------------------
393 // nsPrefetchService <public>
394 //-----------------------------------------------------------------------------
396 nsPrefetchService::nsPrefetchService()
397 : mQueueHead(nullptr)
398 , mQueueTail(nullptr)
400 , mHaveProcessed(false)
405 nsPrefetchService::~nsPrefetchService()
407 Preferences::RemoveObserver(this, PREFETCH_PREF
);
408 // cannot reach destructor if prefetch in progress (listener owns reference
414 nsPrefetchService::Init()
416 #if defined(PR_LOGGING)
418 gPrefetchLog
= PR_NewLogModule("nsPrefetch");
423 // read prefs and hook up pref observer
424 mDisabled
= !Preferences::GetBool(PREFETCH_PREF
, !mDisabled
);
425 Preferences::AddWeakObserver(this, PREFETCH_PREF
);
427 // Observe xpcom-shutdown event
428 nsCOMPtr
<nsIObserverService
> observerService
=
429 mozilla::services::GetObserverService();
430 if (!observerService
)
431 return NS_ERROR_FAILURE
;
433 rv
= observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
434 NS_ENSURE_SUCCESS(rv
, rv
);
437 AddProgressListener();
443 nsPrefetchService::ProcessNextURI()
446 nsCOMPtr
<nsIURI
> uri
, referrer
;
448 mCurrentNode
= nullptr;
451 rv
= DequeueNode(getter_AddRefs(mCurrentNode
));
453 if (NS_FAILED(rv
)) break;
455 #if defined(PR_LOGGING)
458 mCurrentNode
->mURI
->GetSpec(spec
);
459 LOG(("ProcessNextURI [%s]\n", spec
.get()));
464 // if opening the channel fails, then just skip to the next uri
466 nsRefPtr
<nsPrefetchNode
> node
= mCurrentNode
;
467 rv
= node
->OpenChannel();
469 while (NS_FAILED(rv
));
473 nsPrefetchService::NotifyLoadRequested(nsPrefetchNode
*node
)
475 nsCOMPtr
<nsIObserverService
> observerService
=
476 mozilla::services::GetObserverService();
477 if (!observerService
)
480 observerService
->NotifyObservers(static_cast<nsIStreamListener
*>(node
),
481 "prefetch-load-requested", nullptr);
485 nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode
*node
)
487 nsCOMPtr
<nsIObserverService
> observerService
=
488 mozilla::services::GetObserverService();
489 if (!observerService
)
492 observerService
->NotifyObservers(static_cast<nsIStreamListener
*>(node
),
493 "prefetch-load-completed", nullptr);
496 //-----------------------------------------------------------------------------
497 // nsPrefetchService <private>
498 //-----------------------------------------------------------------------------
501 nsPrefetchService::AddProgressListener()
503 // Register as an observer for the document loader
504 nsCOMPtr
<nsIWebProgress
> progress
=
505 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID
);
507 progress
->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT
);
511 nsPrefetchService::RemoveProgressListener()
513 // Register as an observer for the document loader
514 nsCOMPtr
<nsIWebProgress
> progress
=
515 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID
);
517 progress
->RemoveProgressListener(this);
521 nsPrefetchService::EnqueueNode(nsPrefetchNode
*aNode
)
530 mQueueTail
->mNext
= aNode
;
538 nsPrefetchService::EnqueueURI(nsIURI
*aURI
,
539 nsIURI
*aReferrerURI
,
541 nsPrefetchNode
**aNode
)
543 nsPrefetchNode
*node
= new nsPrefetchNode(this, aURI
, aReferrerURI
,
546 return NS_ERROR_OUT_OF_MEMORY
;
548 NS_ADDREF(*aNode
= node
);
550 return EnqueueNode(node
);
554 nsPrefetchService::DequeueNode(nsPrefetchNode
**node
)
557 return NS_ERROR_NOT_AVAILABLE
;
559 // give the ref to the caller
561 mQueueHead
= mQueueHead
->mNext
;
562 (*node
)->mNext
= nullptr;
565 mQueueTail
= nullptr;
571 nsPrefetchService::EmptyQueue()
574 nsRefPtr
<nsPrefetchNode
> node
;
575 DequeueNode(getter_AddRefs(node
));
576 } while (mQueueHead
);
580 nsPrefetchService::StartPrefetching()
583 // at initialization time we might miss the first DOCUMENT START
584 // notification, so we have to be careful to avoid letting our
585 // stop count go negative.
590 LOG(("StartPrefetching [stopcount=%d]\n", mStopCount
));
592 // only start prefetching after we've received enough DOCUMENT
593 // STOP notifications. we do this inorder to defer prefetching
594 // until after all sub-frames have finished loading.
595 if (mStopCount
== 0 && !mCurrentNode
) {
596 mHaveProcessed
= true;
602 nsPrefetchService::StopPrefetching()
606 LOG(("StopPrefetching [stopcount=%d]\n", mStopCount
));
608 // only kill the prefetch queue if we've actually started prefetching.
612 mCurrentNode
->CancelChannel(NS_BINDING_ABORTED
);
613 mCurrentNode
= nullptr;
617 //-----------------------------------------------------------------------------
618 // nsPrefetchService::nsISupports
619 //-----------------------------------------------------------------------------
621 NS_IMPL_ISUPPORTS(nsPrefetchService
,
623 nsIWebProgressListener
,
625 nsISupportsWeakReference
)
627 //-----------------------------------------------------------------------------
628 // nsPrefetchService::nsIPrefetchService
629 //-----------------------------------------------------------------------------
632 nsPrefetchService::Prefetch(nsIURI
*aURI
,
633 nsIURI
*aReferrerURI
,
639 NS_ENSURE_ARG_POINTER(aURI
);
640 NS_ENSURE_ARG_POINTER(aReferrerURI
);
642 #if defined(PR_LOGGING)
646 LOG(("PrefetchURI [%s]\n", spec
.get()));
651 LOG(("rejected: prefetch service is disabled\n"));
652 return NS_ERROR_ABORT
;
656 // XXX we should really be asking the protocol handler if it supports
657 // caching, so we can determine if there is any value to prefetching.
658 // for now, we'll only prefetch http links since we know that's the
659 // most common case. ignore https links since https content only goes
660 // into the memory cache.
662 // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
663 // or possibly nsIRequest::loadFlags to determine if this URI should be
667 rv
= aURI
->SchemeIs("http", &match
);
668 if (NS_FAILED(rv
) || !match
) {
669 rv
= aURI
->SchemeIs("https", &match
);
670 if (NS_FAILED(rv
) || !match
) {
671 LOG(("rejected: URL is not of type http/https\n"));
672 return NS_ERROR_ABORT
;
677 // the referrer URI must be http:
679 rv
= aReferrerURI
->SchemeIs("http", &match
);
680 if (NS_FAILED(rv
) || !match
) {
681 rv
= aReferrerURI
->SchemeIs("https", &match
);
682 if (NS_FAILED(rv
) || !match
) {
683 LOG(("rejected: referrer URL is neither http nor https\n"));
684 return NS_ERROR_ABORT
;
688 // skip URLs that contain query strings, except URLs for which prefetching
689 // has been explicitly requested.
691 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aURI
, &rv
));
692 if (NS_FAILED(rv
)) return rv
;
694 rv
= url
->GetQuery(query
);
695 if (NS_FAILED(rv
) || !query
.IsEmpty()) {
696 LOG(("rejected: URL has a query string\n"));
697 return NS_ERROR_ABORT
;
702 // cancel if being prefetched
706 if (NS_SUCCEEDED(mCurrentNode
->mURI
->Equals(aURI
, &equals
)) && equals
) {
707 LOG(("rejected: URL is already being prefetched\n"));
708 return NS_ERROR_ABORT
;
713 // cancel if already on the prefetch queue
715 nsPrefetchNode
*node
= mQueueHead
;
716 for (; node
; node
= node
->mNext
) {
718 if (NS_SUCCEEDED(node
->mURI
->Equals(aURI
, &equals
)) && equals
) {
719 LOG(("rejected: URL is already on prefetch queue\n"));
720 return NS_ERROR_ABORT
;
724 nsRefPtr
<nsPrefetchNode
> enqueuedNode
;
725 rv
= EnqueueURI(aURI
, aReferrerURI
, aSource
,
726 getter_AddRefs(enqueuedNode
));
727 NS_ENSURE_SUCCESS(rv
, rv
);
729 NotifyLoadRequested(enqueuedNode
);
731 // if there are no pages loading, kick off the request immediately
732 if (mStopCount
== 0 && mHaveProcessed
)
739 nsPrefetchService::PrefetchURI(nsIURI
*aURI
,
740 nsIURI
*aReferrerURI
,
744 return Prefetch(aURI
, aReferrerURI
, aSource
, aExplicit
);
748 nsPrefetchService::EnumerateQueue(nsISimpleEnumerator
**aEnumerator
)
750 *aEnumerator
= new nsPrefetchQueueEnumerator(this);
751 if (!*aEnumerator
) return NS_ERROR_OUT_OF_MEMORY
;
753 NS_ADDREF(*aEnumerator
);
758 //-----------------------------------------------------------------------------
759 // nsPrefetchService::nsIWebProgressListener
760 //-----------------------------------------------------------------------------
763 nsPrefetchService::OnProgressChange(nsIWebProgress
*aProgress
,
764 nsIRequest
*aRequest
,
765 int32_t curSelfProgress
,
766 int32_t maxSelfProgress
,
767 int32_t curTotalProgress
,
768 int32_t maxTotalProgress
)
770 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
775 nsPrefetchService::OnStateChange(nsIWebProgress
* aWebProgress
,
776 nsIRequest
*aRequest
,
777 uint32_t progressStateFlags
,
780 if (progressStateFlags
& STATE_IS_DOCUMENT
) {
781 if (progressStateFlags
& STATE_STOP
)
783 else if (progressStateFlags
& STATE_START
)
792 nsPrefetchService::OnLocationChange(nsIWebProgress
* aWebProgress
,
793 nsIRequest
* aRequest
,
797 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
802 nsPrefetchService::OnStatusChange(nsIWebProgress
* aWebProgress
,
803 nsIRequest
* aRequest
,
805 const char16_t
* aMessage
)
807 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
812 nsPrefetchService::OnSecurityChange(nsIWebProgress
*aWebProgress
,
813 nsIRequest
*aRequest
,
816 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
820 //-----------------------------------------------------------------------------
821 // nsPrefetchService::nsIObserver
822 //-----------------------------------------------------------------------------
825 nsPrefetchService::Observe(nsISupports
*aSubject
,
827 const char16_t
*aData
)
829 LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic
));
831 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
836 else if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
837 if (Preferences::GetBool(PREFETCH_PREF
, false)) {
839 LOG(("enabling prefetching\n"));
841 AddProgressListener();
846 LOG(("disabling prefetching\n"));
850 RemoveProgressListener();
858 // vim: ts=4 sw=4 expandtab