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