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