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