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/. */
5 
6 #include "nsPrefetchService.h"
7 #include "nsICacheEntry.h"
8 #include "nsIServiceManager.h"
9 #include "nsICategoryManager.h"
10 #include "nsIObserverService.h"
11 #include "nsIWebProgress.h"
12 #include "nsCURILoader.h"
13 #include "nsICacheInfoChannel.h"
14 #include "nsIHttpChannel.h"
15 #include "nsIURL.h"
16 #include "nsISimpleEnumerator.h"
17 #include "nsNetUtil.h"
18 #include "nsString.h"
19 #include "nsXPIDLString.h"
20 #include "nsReadableUtils.h"
21 #include "nsStreamUtils.h"
22 #include "nsAutoPtr.h"
23 #include "prtime.h"
24 #include "mozilla/Logging.h"
25 #include "plstr.h"
26 #include "nsIAsyncVerifyRedirectCallback.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/Attributes.h"
29 #include "mozilla/CORSMode.h"
30 #include "mozilla/dom/HTMLLinkElement.h"
31 #include "nsIDOMNode.h"
32 #include "nsINode.h"
33 #include "nsIDocument.h"
34 #include "nsContentUtils.h"
35 
36 using namespace mozilla;
37 
38 //
39 // To enable logging (see mozilla/Logging.h for full details):
40 //
41 //    set MOZ_LOG=nsPrefetch:5
42 //    set MOZ_LOG_FILE=prefetch.log
43 //
44 // this enables LogLevel::Debug level information and places all output in
45 // the file prefetch.log
46 //
47 static LazyLogModule gPrefetchLog("nsPrefetch");
48 
49 #undef LOG
50 #define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
51 
52 #undef LOG_ENABLED
53 #define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
54 
55 #define PREFETCH_PREF "network.prefetch-next"
56 #define PARALLELISM_PREF "network.prefetch-next.parallelism"
57 #define AGGRESSIVE_PREF "network.prefetch-next.aggressive"
58 
59 //-----------------------------------------------------------------------------
60 // helpers
61 //-----------------------------------------------------------------------------
62 
63 static inline uint32_t
PRTimeToSeconds(PRTime t_usec)64 PRTimeToSeconds(PRTime t_usec)
65 {
66     PRTime usec_per_sec = PR_USEC_PER_SEC;
67     return uint32_t(t_usec /= usec_per_sec);
68 }
69 
70 #define NowInSeconds() PRTimeToSeconds(PR_Now())
71 
72 //-----------------------------------------------------------------------------
73 // nsPrefetchNode <public>
74 //-----------------------------------------------------------------------------
75 
nsPrefetchNode(nsPrefetchService * aService,nsIURI * aURI,nsIURI * aReferrerURI,nsIDOMNode * aSource)76 nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
77                                nsIURI *aURI,
78                                nsIURI *aReferrerURI,
79                                nsIDOMNode *aSource)
80     : mURI(aURI)
81     , mReferrerURI(aReferrerURI)
82     , mService(aService)
83     , mChannel(nullptr)
84     , mBytesRead(0)
85     , mShouldFireLoadEvent(false)
86 {
87     nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
88     mSources.AppendElement(source);
89 }
90 
91 nsresult
OpenChannel()92 nsPrefetchNode::OpenChannel()
93 {
94     if (mSources.IsEmpty()) {
95         // Don't attempt to prefetch if we don't have a source node
96         // (which should never happen).
97         return NS_ERROR_FAILURE;
98     }
99     nsCOMPtr<nsINode> source;
100     while (!mSources.IsEmpty() && !(source = do_QueryReferent(mSources.ElementAt(0)))) {
101         // If source is null remove it.
102         // (which should never happen).
103         mSources.RemoveElementAt(0);
104     }
105 
106     if (!source) {
107         // Don't attempt to prefetch if we don't have a source node
108         // (which should never happen).
109 
110         return NS_ERROR_FAILURE;
111     }
112     nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
113     CORSMode corsMode = CORS_NONE;
114     net::ReferrerPolicy referrerPolicy = net::RP_Unset;
115     if (source->IsHTMLElement(nsGkAtoms::link)) {
116       dom::HTMLLinkElement* link = static_cast<dom::HTMLLinkElement*>(source.get());
117       corsMode = link->GetCORSMode();
118       referrerPolicy = link->GetLinkReferrerPolicy();
119     }
120 
121     if (referrerPolicy == net::RP_Unset) {
122       referrerPolicy = source->OwnerDoc()->GetReferrerPolicy();
123     }
124 
125     uint32_t securityFlags;
126     if (corsMode == CORS_NONE) {
127       securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
128     } else {
129       securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
130       if (corsMode == CORS_USE_CREDENTIALS) {
131         securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
132       }
133     }
134     nsresult rv = NS_NewChannelInternal(getter_AddRefs(mChannel),
135                                         mURI,
136                                         source,
137                                         source->NodePrincipal(),
138                                         nullptr,   //aTriggeringPrincipal
139                                         securityFlags,
140                                         nsIContentPolicy::TYPE_OTHER,
141                                         loadGroup, // aLoadGroup
142                                         this,      // aCallbacks
143                                         nsIRequest::LOAD_BACKGROUND |
144                                         nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
145 
146     NS_ENSURE_SUCCESS(rv, rv);
147 
148     // configure HTTP specific stuff
149     nsCOMPtr<nsIHttpChannel> httpChannel =
150         do_QueryInterface(mChannel);
151     if (httpChannel) {
152         httpChannel->SetReferrerWithPolicy(mReferrerURI, referrerPolicy);
153         httpChannel->SetRequestHeader(
154             NS_LITERAL_CSTRING("X-Moz"),
155             NS_LITERAL_CSTRING("prefetch"),
156             false);
157     }
158 
159     rv = mChannel->AsyncOpen2(this);
160     if (NS_WARN_IF(NS_FAILED(rv))) {
161       // Drop the ref to the channel, because we don't want to end up with
162       // cycles through it.
163       mChannel = nullptr;
164     }
165     return rv;
166 }
167 
168 nsresult
CancelChannel(nsresult error)169 nsPrefetchNode::CancelChannel(nsresult error)
170 {
171     mChannel->Cancel(error);
172     mChannel = nullptr;
173 
174     return NS_OK;
175 }
176 
177 //-----------------------------------------------------------------------------
178 // nsPrefetchNode::nsISupports
179 //-----------------------------------------------------------------------------
180 
NS_IMPL_ISUPPORTS(nsPrefetchNode,nsIRequestObserver,nsIStreamListener,nsIInterfaceRequestor,nsIChannelEventSink,nsIRedirectResultListener)181 NS_IMPL_ISUPPORTS(nsPrefetchNode,
182                   nsIRequestObserver,
183                   nsIStreamListener,
184                   nsIInterfaceRequestor,
185                   nsIChannelEventSink,
186                   nsIRedirectResultListener)
187 
188 //-----------------------------------------------------------------------------
189 // nsPrefetchNode::nsIStreamListener
190 //-----------------------------------------------------------------------------
191 
192 NS_IMETHODIMP
193 nsPrefetchNode::OnStartRequest(nsIRequest *aRequest,
194                                nsISupports *aContext)
195 {
196     nsresult rv;
197 
198     nsCOMPtr<nsIHttpChannel> httpChannel =
199         do_QueryInterface(aRequest, &rv);
200     if (NS_FAILED(rv)) return rv;
201 
202     // if the load is cross origin without CORS, or the CORS access is rejected,
203     // always fire load event to avoid leaking site information.
204     nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
205     mShouldFireLoadEvent = loadInfo->GetTainting() == LoadTainting::Opaque ||
206                            (loadInfo->GetTainting() == LoadTainting::CORS &&
207                             (NS_FAILED(httpChannel->GetStatus(&rv)) ||
208                              NS_FAILED(rv)));
209 
210     // no need to prefetch http error page
211     bool requestSucceeded;
212     if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
213         !requestSucceeded) {
214       return NS_BINDING_ABORTED;
215     }
216 
217     nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
218         do_QueryInterface(aRequest, &rv);
219     if (NS_FAILED(rv)) return rv;
220 
221     // no need to prefetch a document that is already in the cache
222     bool fromCache;
223     if (NS_SUCCEEDED(cacheInfoChannel->IsFromCache(&fromCache)) &&
224         fromCache) {
225         LOG(("document is already in the cache; canceling prefetch\n"));
226         // although it's canceled we still want to fire load event
227         mShouldFireLoadEvent = true;
228         return NS_BINDING_ABORTED;
229     }
230 
231     //
232     // no need to prefetch a document that must be requested fresh each
233     // and every time.
234     //
235     uint32_t expTime;
236     if (NS_SUCCEEDED(cacheInfoChannel->GetCacheTokenExpirationTime(&expTime))) {
237         if (NowInSeconds() >= expTime) {
238             LOG(("document cannot be reused from cache; "
239                  "canceling prefetch\n"));
240             return NS_BINDING_ABORTED;
241         }
242     }
243 
244     return NS_OK;
245 }
246 
247 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aStream,uint64_t aOffset,uint32_t aCount)248 nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest,
249                                 nsISupports *aContext,
250                                 nsIInputStream *aStream,
251                                 uint64_t aOffset,
252                                 uint32_t aCount)
253 {
254     uint32_t bytesRead = 0;
255     aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
256     mBytesRead += bytesRead;
257     LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead, aOffset));
258     return NS_OK;
259 }
260 
261 
262 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus)263 nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
264                               nsISupports *aContext,
265                               nsresult aStatus)
266 {
267     LOG(("done prefetching [status=%x]\n", aStatus));
268 
269     if (mBytesRead == 0 && aStatus == NS_OK && mChannel) {
270         // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
271         // specified), but the object should report loadedSize as if it
272         // did.
273         mChannel->GetContentLength(&mBytesRead);
274     }
275 
276     mService->NotifyLoadCompleted(this);
277     mService->DispatchEvent(this, mShouldFireLoadEvent || NS_SUCCEEDED(aStatus));
278     mService->ProcessNextURI(this);
279     return NS_OK;
280 }
281 
282 //-----------------------------------------------------------------------------
283 // nsPrefetchNode::nsIInterfaceRequestor
284 //-----------------------------------------------------------------------------
285 
286 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)287 nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult)
288 {
289     if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
290         NS_ADDREF_THIS();
291         *aResult = static_cast<nsIChannelEventSink *>(this);
292         return NS_OK;
293     }
294 
295     if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
296         NS_ADDREF_THIS();
297         *aResult = static_cast<nsIRedirectResultListener *>(this);
298         return NS_OK;
299     }
300 
301     return NS_ERROR_NO_INTERFACE;
302 }
303 
304 //-----------------------------------------------------------------------------
305 // nsPrefetchNode::nsIChannelEventSink
306 //-----------------------------------------------------------------------------
307 
308 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * callback)309 nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
310                                        nsIChannel *aNewChannel,
311                                        uint32_t aFlags,
312                                        nsIAsyncVerifyRedirectCallback *callback)
313 {
314     nsCOMPtr<nsIURI> newURI;
315     nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
316     if (NS_FAILED(rv))
317         return rv;
318 
319     bool match;
320     rv = newURI->SchemeIs("http", &match);
321     if (NS_FAILED(rv) || !match) {
322         rv = newURI->SchemeIs("https", &match);
323         if (NS_FAILED(rv) || !match) {
324             LOG(("rejected: URL is not of type http/https\n"));
325             return NS_ERROR_ABORT;
326         }
327     }
328 
329     // HTTP request headers are not automatically forwarded to the new channel.
330     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
331     NS_ENSURE_STATE(httpChannel);
332 
333     httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
334                                   NS_LITERAL_CSTRING("prefetch"),
335                                   false);
336 
337     // Assign to mChannel after we get notification about success of the
338     // redirect in OnRedirectResult.
339     mRedirectChannel = aNewChannel;
340 
341     callback->OnRedirectVerifyCallback(NS_OK);
342     return NS_OK;
343 }
344 
345 //-----------------------------------------------------------------------------
346 // nsPrefetchNode::nsIRedirectResultListener
347 //-----------------------------------------------------------------------------
348 
349 NS_IMETHODIMP
OnRedirectResult(bool proceeding)350 nsPrefetchNode::OnRedirectResult(bool proceeding)
351 {
352     if (proceeding && mRedirectChannel)
353         mChannel = mRedirectChannel;
354 
355     mRedirectChannel = nullptr;
356 
357     return NS_OK;
358 }
359 
360 //-----------------------------------------------------------------------------
361 // nsPrefetchService <public>
362 //-----------------------------------------------------------------------------
363 
nsPrefetchService()364 nsPrefetchService::nsPrefetchService()
365     : mMaxParallelism(6)
366     , mStopCount(0)
367     , mHaveProcessed(false)
368     , mDisabled(true)
369     , mAggressive(false)
370 {
371 }
372 
~nsPrefetchService()373 nsPrefetchService::~nsPrefetchService()
374 {
375     Preferences::RemoveObserver(this, PREFETCH_PREF);
376     Preferences::RemoveObserver(this, PARALLELISM_PREF);
377     Preferences::RemoveObserver(this, AGGRESSIVE_PREF);
378     // cannot reach destructor if prefetch in progress (listener owns reference
379     // to this service)
380     EmptyQueue();
381 }
382 
383 nsresult
Init()384 nsPrefetchService::Init()
385 {
386     nsresult rv;
387 
388     // read prefs and hook up pref observer
389     mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled);
390     Preferences::AddWeakObserver(this, PREFETCH_PREF);
391 
392     mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
393     if (mMaxParallelism < 1) {
394         mMaxParallelism = 1;
395     }
396     Preferences::AddWeakObserver(this, PARALLELISM_PREF);
397 
398     mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
399     Preferences::AddWeakObserver(this, AGGRESSIVE_PREF);
400 
401     // Observe xpcom-shutdown event
402     nsCOMPtr<nsIObserverService> observerService =
403       mozilla::services::GetObserverService();
404     if (!observerService)
405       return NS_ERROR_FAILURE;
406 
407     rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
408     NS_ENSURE_SUCCESS(rv, rv);
409 
410     if (!mDisabled)
411         AddProgressListener();
412 
413     return NS_OK;
414 }
415 
416 void
ProcessNextURI(nsPrefetchNode * aFinished)417 nsPrefetchService::ProcessNextURI(nsPrefetchNode *aFinished)
418 {
419     nsresult rv;
420 
421     if (aFinished) {
422         mCurrentNodes.RemoveElement(aFinished);
423     }
424 
425     if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
426         // We already have enough prefetches going on, so hold off
427         // for now.
428         return;
429     }
430 
431     do {
432         if (mQueue.empty()) {
433           break;
434         }
435         RefPtr<nsPrefetchNode> node = mQueue.front().forget();
436         mQueue.pop_front();
437 
438         if (LOG_ENABLED()) {
439             LOG(("ProcessNextURI [%s]\n",
440                  node->mURI->GetSpecOrDefault().get())); }
441 
442         //
443         // if opening the channel fails (e.g. security check returns an error),
444         // send an error event and then just skip to the next uri
445         //
446         rv = node->OpenChannel();
447         if (NS_SUCCEEDED(rv)) {
448             mCurrentNodes.AppendElement(node);
449         } else {
450           DispatchEvent(node, false);
451         }
452     }
453     while (NS_FAILED(rv));
454 }
455 
456 void
NotifyLoadRequested(nsPrefetchNode * node)457 nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node)
458 {
459     nsCOMPtr<nsIObserverService> observerService =
460       mozilla::services::GetObserverService();
461     if (!observerService)
462       return;
463 
464     observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
465                                      "prefetch-load-requested", nullptr);
466 }
467 
468 void
NotifyLoadCompleted(nsPrefetchNode * node)469 nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node)
470 {
471     nsCOMPtr<nsIObserverService> observerService =
472       mozilla::services::GetObserverService();
473     if (!observerService)
474       return;
475 
476     observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
477                                      "prefetch-load-completed", nullptr);
478 }
479 
480 void
DispatchEvent(nsPrefetchNode * node,bool aSuccess)481 nsPrefetchService::DispatchEvent(nsPrefetchNode *node, bool aSuccess)
482 {
483     for (uint32_t i = 0; i < node->mSources.Length(); i++) {
484       nsCOMPtr<nsINode> domNode = do_QueryReferent(node->mSources.ElementAt(i));
485       if (domNode && domNode->IsInComposedDoc()) {
486         nsContentUtils::DispatchTrustedEvent(domNode->OwnerDoc(),
487                                              domNode,
488                                              aSuccess ?
489                                               NS_LITERAL_STRING("load") :
490                                               NS_LITERAL_STRING("error"),
491                                              /* aCanBubble = */ false,
492                                              /* aCancelable = */ false);
493       }
494     }
495 }
496 
497 //-----------------------------------------------------------------------------
498 // nsPrefetchService <private>
499 //-----------------------------------------------------------------------------
500 
501 void
AddProgressListener()502 nsPrefetchService::AddProgressListener()
503 {
504     // Register as an observer for the document loader
505     nsCOMPtr<nsIWebProgress> progress =
506         do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
507     if (progress)
508         progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
509 }
510 
511 void
RemoveProgressListener()512 nsPrefetchService::RemoveProgressListener()
513 {
514     // Register as an observer for the document loader
515     nsCOMPtr<nsIWebProgress> progress =
516         do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
517     if (progress)
518         progress->RemoveProgressListener(this);
519 }
520 
521 nsresult
EnqueueURI(nsIURI * aURI,nsIURI * aReferrerURI,nsIDOMNode * aSource,nsPrefetchNode ** aNode)522 nsPrefetchService::EnqueueURI(nsIURI *aURI,
523                               nsIURI *aReferrerURI,
524                               nsIDOMNode *aSource,
525                               nsPrefetchNode **aNode)
526 {
527     RefPtr<nsPrefetchNode> node = new nsPrefetchNode(this, aURI, aReferrerURI,
528                                                      aSource);
529     mQueue.push_back(node);
530     node.forget(aNode);
531     return NS_OK;
532 }
533 
534 void
EmptyQueue()535 nsPrefetchService::EmptyQueue()
536 {
537     while (!mQueue.empty()) {
538         mQueue.pop_back();
539     }
540 }
541 
542 void
StartPrefetching()543 nsPrefetchService::StartPrefetching()
544 {
545     //
546     // at initialization time we might miss the first DOCUMENT START
547     // notification, so we have to be careful to avoid letting our
548     // stop count go negative.
549     //
550     if (mStopCount > 0)
551         mStopCount--;
552 
553     LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
554 
555     // only start prefetching after we've received enough DOCUMENT
556     // STOP notifications.  we do this inorder to defer prefetching
557     // until after all sub-frames have finished loading.
558     if (!mStopCount) {
559         mHaveProcessed = true;
560         while (!mQueue.empty() && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
561             ProcessNextURI(nullptr);
562         }
563     }
564 }
565 
566 void
StopPrefetching()567 nsPrefetchService::StopPrefetching()
568 {
569     mStopCount++;
570 
571     LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
572 
573     // only kill the prefetch queue if we are actively prefetching right now
574     if (mCurrentNodes.IsEmpty()) {
575         return;
576     }
577 
578     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
579         mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
580     }
581     mCurrentNodes.Clear();
582     EmptyQueue();
583 }
584 
585 //-----------------------------------------------------------------------------
586 // nsPrefetchService::nsISupports
587 //-----------------------------------------------------------------------------
588 
NS_IMPL_ISUPPORTS(nsPrefetchService,nsIPrefetchService,nsIWebProgressListener,nsIObserver,nsISupportsWeakReference)589 NS_IMPL_ISUPPORTS(nsPrefetchService,
590                   nsIPrefetchService,
591                   nsIWebProgressListener,
592                   nsIObserver,
593                   nsISupportsWeakReference)
594 
595 //-----------------------------------------------------------------------------
596 // nsPrefetchService::nsIPrefetchService
597 //-----------------------------------------------------------------------------
598 
599 nsresult
600 nsPrefetchService::Prefetch(nsIURI *aURI,
601                             nsIURI *aReferrerURI,
602                             nsIDOMNode *aSource,
603                             bool aExplicit)
604 {
605     nsresult rv;
606 
607     NS_ENSURE_ARG_POINTER(aURI);
608     NS_ENSURE_ARG_POINTER(aReferrerURI);
609 
610     if (LOG_ENABLED()) {
611         LOG(("PrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
612     }
613 
614     if (mDisabled) {
615         LOG(("rejected: prefetch service is disabled\n"));
616         return NS_ERROR_ABORT;
617     }
618 
619     //
620     // XXX we should really be asking the protocol handler if it supports
621     // caching, so we can determine if there is any value to prefetching.
622     // for now, we'll only prefetch http links since we know that's the
623     // most common case.  ignore https links since https content only goes
624     // into the memory cache.
625     //
626     // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
627     // or possibly nsIRequest::loadFlags to determine if this URI should be
628     // prefetched.
629     //
630     bool match;
631     rv = aURI->SchemeIs("http", &match);
632     if (NS_FAILED(rv) || !match) {
633         rv = aURI->SchemeIs("https", &match);
634         if (NS_FAILED(rv) || !match) {
635             LOG(("rejected: URL is not of type http/https\n"));
636             return NS_ERROR_ABORT;
637         }
638     }
639 
640     //
641     // the referrer URI must be http:
642     //
643     rv = aReferrerURI->SchemeIs("http", &match);
644     if (NS_FAILED(rv) || !match) {
645         rv = aReferrerURI->SchemeIs("https", &match);
646         if (NS_FAILED(rv) || !match) {
647             LOG(("rejected: referrer URL is neither http nor https\n"));
648             return NS_ERROR_ABORT;
649         }
650     }
651 
652     // skip URLs that contain query strings, except URLs for which prefetching
653     // has been explicitly requested.
654     if (!aExplicit) {
655         nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
656         if (NS_FAILED(rv)) return rv;
657         nsAutoCString query;
658         rv = url->GetQuery(query);
659         if (NS_FAILED(rv) || !query.IsEmpty()) {
660             LOG(("rejected: URL has a query string\n"));
661             return NS_ERROR_ABORT;
662         }
663     }
664 
665     //
666     // Check whether it is being prefetched.
667     //
668     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
669         bool equals;
670         if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
671             equals) {
672             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
673             if (mCurrentNodes[i]->mSources.IndexOf(source) ==
674                 mCurrentNodes[i]->mSources.NoIndex) {
675                 LOG(("URL is already being prefetched, add a new reference "
676                      "document\n"));
677                 mCurrentNodes[i]->mSources.AppendElement(source);
678                 return NS_OK;
679             } else {
680                 LOG(("URL is already being prefetched by this document"));
681                 return NS_ERROR_ABORT;
682             }
683         }
684     }
685 
686     //
687     // Check whether it is on the prefetch queue.
688     //
689     for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mQueue.begin();
690          nodeIt != mQueue.end(); nodeIt++) {
691         bool equals;
692         RefPtr<nsPrefetchNode> node = nodeIt->get();
693         if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
694             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
695             if (node->mSources.IndexOf(source) ==
696                 node->mSources.NoIndex) {
697                 LOG(("URL is already being prefetched, add a new reference "
698                      "document\n"));
699                 node->mSources.AppendElement(do_GetWeakReference(aSource));
700                 return NS_OK;
701             } else {
702                 LOG(("URL is already being prefetched by this document"));
703                 return NS_ERROR_ABORT;
704             }
705 
706         }
707     }
708 
709     RefPtr<nsPrefetchNode> enqueuedNode;
710     rv = EnqueueURI(aURI, aReferrerURI, aSource,
711                     getter_AddRefs(enqueuedNode));
712     NS_ENSURE_SUCCESS(rv, rv);
713 
714     NotifyLoadRequested(enqueuedNode);
715 
716     // if there are no pages loading, kick off the request immediately
717     if (mStopCount == 0 && mHaveProcessed) {
718         ProcessNextURI(nullptr);
719     }
720 
721     return NS_OK;
722 }
723 
724 NS_IMETHODIMP
CancelPrefetchURI(nsIURI * aURI,nsIDOMNode * aSource)725 nsPrefetchService::CancelPrefetchURI(nsIURI* aURI,
726                                      nsIDOMNode* aSource)
727 {
728     NS_ENSURE_ARG_POINTER(aURI);
729 
730     if (LOG_ENABLED()) {
731         LOG(("CancelPrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
732     }
733 
734     //
735     // look in current prefetches
736     //
737     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
738         bool equals;
739         if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
740             equals) {
741             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
742             if (mCurrentNodes[i]->mSources.IndexOf(source) !=
743                 mCurrentNodes[i]->mSources.NoIndex) {
744                 mCurrentNodes[i]->mSources.RemoveElement(source);
745                 if (mCurrentNodes[i]->mSources.IsEmpty()) {
746                     mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
747                     mCurrentNodes.RemoveElementAt(i);
748                 }
749                 return NS_OK;
750             }
751             return NS_ERROR_FAILURE;
752         }
753     }
754 
755     //
756     // look into the prefetch queue
757     //
758     for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mQueue.begin();
759          nodeIt != mQueue.end(); nodeIt++) {
760         bool equals;
761         RefPtr<nsPrefetchNode> node = nodeIt->get();
762         if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
763             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
764             if (node->mSources.IndexOf(source) !=
765                 node->mSources.NoIndex) {
766 
767 #ifdef DEBUG
768                 int32_t inx = node->mSources.IndexOf(source);
769                 nsCOMPtr<nsIDOMNode> domNode =
770                     do_QueryReferent(node->mSources.ElementAt(inx));
771                 MOZ_ASSERT(domNode);
772 #endif
773 
774                 node->mSources.RemoveElement(source);
775                 if (node->mSources.IsEmpty()) {
776                     mQueue.erase(nodeIt);
777                 }
778                 return NS_OK;
779             }
780             return NS_ERROR_FAILURE;
781         }
782     }
783 
784     // not found!
785     return NS_ERROR_FAILURE;
786 }
787 
788 NS_IMETHODIMP
PrefetchURI(nsIURI * aURI,nsIURI * aReferrerURI,nsIDOMNode * aSource,bool aExplicit)789 nsPrefetchService::PrefetchURI(nsIURI *aURI,
790                                nsIURI *aReferrerURI,
791                                nsIDOMNode *aSource,
792                                bool aExplicit)
793 {
794     return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
795 }
796 
797 NS_IMETHODIMP
HasMoreElements(bool * aHasMore)798 nsPrefetchService::HasMoreElements(bool *aHasMore)
799 {
800     *aHasMore = (mCurrentNodes.Length() || !mQueue.empty());
801     return NS_OK;
802 }
803 
804 //-----------------------------------------------------------------------------
805 // nsPrefetchService::nsIWebProgressListener
806 //-----------------------------------------------------------------------------
807 
808 NS_IMETHODIMP
OnProgressChange(nsIWebProgress * aProgress,nsIRequest * aRequest,int32_t curSelfProgress,int32_t maxSelfProgress,int32_t curTotalProgress,int32_t maxTotalProgress)809 nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress,
810                                   nsIRequest *aRequest,
811                                   int32_t curSelfProgress,
812                                   int32_t maxSelfProgress,
813                                   int32_t curTotalProgress,
814                                   int32_t maxTotalProgress)
815 {
816     NS_NOTREACHED("notification excluded in AddProgressListener(...)");
817     return NS_OK;
818 }
819 
820 NS_IMETHODIMP
OnStateChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t progressStateFlags,nsresult aStatus)821 nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress,
822                                  nsIRequest *aRequest,
823                                  uint32_t progressStateFlags,
824                                  nsresult aStatus)
825 {
826     if (mAggressive) {
827         LOG(("Document load state is ignored in aggressive mode"));
828         return NS_OK;
829     }
830 
831     if (progressStateFlags & STATE_IS_DOCUMENT) {
832         if (progressStateFlags & STATE_STOP)
833             StartPrefetching();
834         else if (progressStateFlags & STATE_START)
835             StopPrefetching();
836     }
837 
838     return NS_OK;
839 }
840 
841 
842 NS_IMETHODIMP
OnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * location,uint32_t aFlags)843 nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
844                                     nsIRequest* aRequest,
845                                     nsIURI *location,
846                                     uint32_t aFlags)
847 {
848     NS_NOTREACHED("notification excluded in AddProgressListener(...)");
849     return NS_OK;
850 }
851 
852 NS_IMETHODIMP
OnStatusChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsresult aStatus,const char16_t * aMessage)853 nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
854                                   nsIRequest* aRequest,
855                                   nsresult aStatus,
856                                   const char16_t* aMessage)
857 {
858     NS_NOTREACHED("notification excluded in AddProgressListener(...)");
859     return NS_OK;
860 }
861 
862 NS_IMETHODIMP
OnSecurityChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t state)863 nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress,
864                                     nsIRequest *aRequest,
865                                     uint32_t state)
866 {
867     NS_NOTREACHED("notification excluded in AddProgressListener(...)");
868     return NS_OK;
869 }
870 
871 //-----------------------------------------------------------------------------
872 // nsPrefetchService::nsIObserver
873 //-----------------------------------------------------------------------------
874 
875 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)876 nsPrefetchService::Observe(nsISupports     *aSubject,
877                            const char      *aTopic,
878                            const char16_t *aData)
879 {
880     LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
881 
882     if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
883         StopPrefetching();
884         EmptyQueue();
885         mDisabled = true;
886     }
887     else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
888         const nsCString converted = NS_ConvertUTF16toUTF8(aData);
889         const char* pref = converted.get();
890         if (!strcmp(pref, PREFETCH_PREF)) {
891             if (Preferences::GetBool(PREFETCH_PREF, false)) {
892                 if (mDisabled) {
893                     LOG(("enabling prefetching\n"));
894                     mDisabled = false;
895                     AddProgressListener();
896                 }
897             } else {
898                 if (!mDisabled) {
899                     LOG(("disabling prefetching\n"));
900                     StopPrefetching();
901                     EmptyQueue();
902                     mDisabled = true;
903                     RemoveProgressListener();
904                 }
905             }
906         } else if (!strcmp(pref, PARALLELISM_PREF)) {
907             mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
908             if (mMaxParallelism < 1) {
909                 mMaxParallelism = 1;
910             }
911             // If our parallelism has increased, go ahead and kick off enough
912             // prefetches to fill up our allowance. If we're now over our
913             // allowance, we'll just silently let some of them finish to get
914             // back below our limit.
915             while (!mQueue.empty() && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
916                 ProcessNextURI(nullptr);
917             }
918         } else if (!strcmp(pref, AGGRESSIVE_PREF)) {
919           mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
920           // in aggressive mode, clear stop count and start prefetching immediately
921           if (mAggressive) {
922             mStopCount = 0;
923             StartPrefetching();
924           }
925         }
926     }
927 
928     return NS_OK;
929 }
930 
931 // vim: ts=4 sw=4 expandtab
932