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