|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "nsPrefetchService.h" |
|
6 #include "nsICacheEntry.h" |
|
7 #include "nsIServiceManager.h" |
|
8 #include "nsICategoryManager.h" |
|
9 #include "nsIObserverService.h" |
|
10 #include "nsIWebProgress.h" |
|
11 #include "nsCURILoader.h" |
|
12 #include "nsICachingChannel.h" |
|
13 #include "nsICacheVisitor.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 "prlog.h" |
|
25 #include "plstr.h" |
|
26 #include "nsIAsyncVerifyRedirectCallback.h" |
|
27 #include "mozilla/Preferences.h" |
|
28 #include "mozilla/Attributes.h" |
|
29 #include "nsIDOMNode.h" |
|
30 #include "nsINode.h" |
|
31 #include "nsIDocument.h" |
|
32 |
|
33 using namespace mozilla; |
|
34 |
|
35 #if defined(PR_LOGGING) |
|
36 // |
|
37 // To enable logging (see prlog.h for full details): |
|
38 // |
|
39 // set NSPR_LOG_MODULES=nsPrefetch:5 |
|
40 // set NSPR_LOG_FILE=prefetch.log |
|
41 // |
|
42 // this enables PR_LOG_ALWAYS level information and places all output in |
|
43 // the file http.log |
|
44 // |
|
45 static PRLogModuleInfo *gPrefetchLog; |
|
46 #endif |
|
47 |
|
48 #undef LOG |
|
49 #define LOG(args) PR_LOG(gPrefetchLog, 4, args) |
|
50 |
|
51 #undef LOG_ENABLED |
|
52 #define LOG_ENABLED() PR_LOG_TEST(gPrefetchLog, 4) |
|
53 |
|
54 #define PREFETCH_PREF "network.prefetch-next" |
|
55 |
|
56 //----------------------------------------------------------------------------- |
|
57 // helpers |
|
58 //----------------------------------------------------------------------------- |
|
59 |
|
60 static inline uint32_t |
|
61 PRTimeToSeconds(PRTime t_usec) |
|
62 { |
|
63 PRTime usec_per_sec = PR_USEC_PER_SEC; |
|
64 return uint32_t(t_usec /= usec_per_sec); |
|
65 } |
|
66 |
|
67 #define NowInSeconds() PRTimeToSeconds(PR_Now()) |
|
68 |
|
69 //----------------------------------------------------------------------------- |
|
70 // nsPrefetchQueueEnumerator |
|
71 //----------------------------------------------------------------------------- |
|
72 class nsPrefetchQueueEnumerator MOZ_FINAL : public nsISimpleEnumerator |
|
73 { |
|
74 public: |
|
75 NS_DECL_ISUPPORTS |
|
76 NS_DECL_NSISIMPLEENUMERATOR |
|
77 nsPrefetchQueueEnumerator(nsPrefetchService *aService); |
|
78 ~nsPrefetchQueueEnumerator(); |
|
79 |
|
80 private: |
|
81 void Increment(); |
|
82 |
|
83 nsRefPtr<nsPrefetchService> mService; |
|
84 nsRefPtr<nsPrefetchNode> mCurrent; |
|
85 bool mStarted; |
|
86 }; |
|
87 |
|
88 //----------------------------------------------------------------------------- |
|
89 // nsPrefetchQueueEnumerator <public> |
|
90 //----------------------------------------------------------------------------- |
|
91 nsPrefetchQueueEnumerator::nsPrefetchQueueEnumerator(nsPrefetchService *aService) |
|
92 : mService(aService) |
|
93 , mStarted(false) |
|
94 { |
|
95 Increment(); |
|
96 } |
|
97 |
|
98 nsPrefetchQueueEnumerator::~nsPrefetchQueueEnumerator() |
|
99 { |
|
100 } |
|
101 |
|
102 //----------------------------------------------------------------------------- |
|
103 // nsPrefetchQueueEnumerator::nsISimpleEnumerator |
|
104 //----------------------------------------------------------------------------- |
|
105 NS_IMETHODIMP |
|
106 nsPrefetchQueueEnumerator::HasMoreElements(bool *aHasMore) |
|
107 { |
|
108 *aHasMore = (mCurrent != nullptr); |
|
109 return NS_OK; |
|
110 } |
|
111 |
|
112 NS_IMETHODIMP |
|
113 nsPrefetchQueueEnumerator::GetNext(nsISupports **aItem) |
|
114 { |
|
115 if (!mCurrent) return NS_ERROR_FAILURE; |
|
116 |
|
117 NS_ADDREF(*aItem = static_cast<nsIStreamListener*>(mCurrent.get())); |
|
118 |
|
119 Increment(); |
|
120 |
|
121 return NS_OK; |
|
122 } |
|
123 |
|
124 //----------------------------------------------------------------------------- |
|
125 // nsPrefetchQueueEnumerator <private> |
|
126 //----------------------------------------------------------------------------- |
|
127 |
|
128 void |
|
129 nsPrefetchQueueEnumerator::Increment() |
|
130 { |
|
131 if (!mStarted) { |
|
132 // If the service is currently serving a request, it won't be in |
|
133 // the pending queue, so we return it first. If it isn't, we'll |
|
134 // just start with the pending queue. |
|
135 mStarted = true; |
|
136 mCurrent = mService->GetCurrentNode(); |
|
137 if (!mCurrent) |
|
138 mCurrent = mService->GetQueueHead(); |
|
139 return; |
|
140 } |
|
141 |
|
142 if (mCurrent) { |
|
143 if (mCurrent == mService->GetCurrentNode()) { |
|
144 // If we just returned the node being processed by the service, |
|
145 // start with the pending queue |
|
146 mCurrent = mService->GetQueueHead(); |
|
147 } |
|
148 else { |
|
149 // Otherwise just advance to the next item in the queue |
|
150 mCurrent = mCurrent->mNext; |
|
151 } |
|
152 } |
|
153 } |
|
154 |
|
155 //----------------------------------------------------------------------------- |
|
156 // nsPrefetchQueueEnumerator::nsISupports |
|
157 //----------------------------------------------------------------------------- |
|
158 |
|
159 NS_IMPL_ISUPPORTS(nsPrefetchQueueEnumerator, nsISimpleEnumerator) |
|
160 |
|
161 //----------------------------------------------------------------------------- |
|
162 // nsPrefetchNode <public> |
|
163 //----------------------------------------------------------------------------- |
|
164 |
|
165 nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService, |
|
166 nsIURI *aURI, |
|
167 nsIURI *aReferrerURI, |
|
168 nsIDOMNode *aSource) |
|
169 : mNext(nullptr) |
|
170 , mURI(aURI) |
|
171 , mReferrerURI(aReferrerURI) |
|
172 , mService(aService) |
|
173 , mChannel(nullptr) |
|
174 , mBytesRead(0) |
|
175 { |
|
176 mSource = do_GetWeakReference(aSource); |
|
177 } |
|
178 |
|
179 nsresult |
|
180 nsPrefetchNode::OpenChannel() |
|
181 { |
|
182 nsCOMPtr<nsINode> source = do_QueryReferent(mSource); |
|
183 if (!source) { |
|
184 // Don't attempt to prefetch if we don't have a source node |
|
185 // (which should never happen). |
|
186 return NS_ERROR_FAILURE; |
|
187 } |
|
188 nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup(); |
|
189 nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), |
|
190 mURI, |
|
191 nullptr, loadGroup, this, |
|
192 nsIRequest::LOAD_BACKGROUND | |
|
193 nsICachingChannel::LOAD_ONLY_IF_MODIFIED); |
|
194 NS_ENSURE_SUCCESS(rv, rv); |
|
195 |
|
196 // configure HTTP specific stuff |
|
197 nsCOMPtr<nsIHttpChannel> httpChannel = |
|
198 do_QueryInterface(mChannel); |
|
199 if (httpChannel) { |
|
200 httpChannel->SetReferrer(mReferrerURI); |
|
201 httpChannel->SetRequestHeader( |
|
202 NS_LITERAL_CSTRING("X-Moz"), |
|
203 NS_LITERAL_CSTRING("prefetch"), |
|
204 false); |
|
205 } |
|
206 |
|
207 rv = mChannel->AsyncOpen(this, nullptr); |
|
208 NS_ENSURE_SUCCESS(rv, rv); |
|
209 |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
213 nsresult |
|
214 nsPrefetchNode::CancelChannel(nsresult error) |
|
215 { |
|
216 mChannel->Cancel(error); |
|
217 mChannel = nullptr; |
|
218 |
|
219 return NS_OK; |
|
220 } |
|
221 |
|
222 //----------------------------------------------------------------------------- |
|
223 // nsPrefetchNode::nsISupports |
|
224 //----------------------------------------------------------------------------- |
|
225 |
|
226 NS_IMPL_ISUPPORTS(nsPrefetchNode, |
|
227 nsIRequestObserver, |
|
228 nsIStreamListener, |
|
229 nsIInterfaceRequestor, |
|
230 nsIChannelEventSink, |
|
231 nsIRedirectResultListener) |
|
232 |
|
233 //----------------------------------------------------------------------------- |
|
234 // nsPrefetchNode::nsIStreamListener |
|
235 //----------------------------------------------------------------------------- |
|
236 |
|
237 NS_IMETHODIMP |
|
238 nsPrefetchNode::OnStartRequest(nsIRequest *aRequest, |
|
239 nsISupports *aContext) |
|
240 { |
|
241 nsresult rv; |
|
242 |
|
243 nsCOMPtr<nsICachingChannel> cachingChannel = |
|
244 do_QueryInterface(aRequest, &rv); |
|
245 if (NS_FAILED(rv)) return rv; |
|
246 |
|
247 // no need to prefetch a document that is already in the cache |
|
248 bool fromCache; |
|
249 if (NS_SUCCEEDED(cachingChannel->IsFromCache(&fromCache)) && |
|
250 fromCache) { |
|
251 LOG(("document is already in the cache; canceling prefetch\n")); |
|
252 return NS_BINDING_ABORTED; |
|
253 } |
|
254 |
|
255 // |
|
256 // no need to prefetch a document that must be requested fresh each |
|
257 // and every time. |
|
258 // |
|
259 nsCOMPtr<nsISupports> cacheToken; |
|
260 cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); |
|
261 if (!cacheToken) |
|
262 return NS_ERROR_ABORT; // bail, no cache entry |
|
263 |
|
264 nsCOMPtr<nsICacheEntry> entryInfo = |
|
265 do_QueryInterface(cacheToken, &rv); |
|
266 if (NS_FAILED(rv)) return rv; |
|
267 |
|
268 uint32_t expTime; |
|
269 if (NS_SUCCEEDED(entryInfo->GetExpirationTime(&expTime))) { |
|
270 if (NowInSeconds() >= expTime) { |
|
271 LOG(("document cannot be reused from cache; " |
|
272 "canceling prefetch\n")); |
|
273 return NS_BINDING_ABORTED; |
|
274 } |
|
275 } |
|
276 |
|
277 return NS_OK; |
|
278 } |
|
279 |
|
280 NS_IMETHODIMP |
|
281 nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest, |
|
282 nsISupports *aContext, |
|
283 nsIInputStream *aStream, |
|
284 uint64_t aOffset, |
|
285 uint32_t aCount) |
|
286 { |
|
287 uint32_t bytesRead = 0; |
|
288 aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead); |
|
289 mBytesRead += bytesRead; |
|
290 LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead, aOffset)); |
|
291 return NS_OK; |
|
292 } |
|
293 |
|
294 |
|
295 NS_IMETHODIMP |
|
296 nsPrefetchNode::OnStopRequest(nsIRequest *aRequest, |
|
297 nsISupports *aContext, |
|
298 nsresult aStatus) |
|
299 { |
|
300 LOG(("done prefetching [status=%x]\n", aStatus)); |
|
301 |
|
302 if (mBytesRead == 0 && aStatus == NS_OK) { |
|
303 // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was |
|
304 // specified), but the object should report loadedSize as if it |
|
305 // did. |
|
306 mChannel->GetContentLength(&mBytesRead); |
|
307 } |
|
308 |
|
309 mService->NotifyLoadCompleted(this); |
|
310 mService->ProcessNextURI(); |
|
311 return NS_OK; |
|
312 } |
|
313 |
|
314 //----------------------------------------------------------------------------- |
|
315 // nsPrefetchNode::nsIInterfaceRequestor |
|
316 //----------------------------------------------------------------------------- |
|
317 |
|
318 NS_IMETHODIMP |
|
319 nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult) |
|
320 { |
|
321 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { |
|
322 NS_ADDREF_THIS(); |
|
323 *aResult = static_cast<nsIChannelEventSink *>(this); |
|
324 return NS_OK; |
|
325 } |
|
326 |
|
327 if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) { |
|
328 NS_ADDREF_THIS(); |
|
329 *aResult = static_cast<nsIRedirectResultListener *>(this); |
|
330 return NS_OK; |
|
331 } |
|
332 |
|
333 return NS_ERROR_NO_INTERFACE; |
|
334 } |
|
335 |
|
336 //----------------------------------------------------------------------------- |
|
337 // nsPrefetchNode::nsIChannelEventSink |
|
338 //----------------------------------------------------------------------------- |
|
339 |
|
340 NS_IMETHODIMP |
|
341 nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel, |
|
342 nsIChannel *aNewChannel, |
|
343 uint32_t aFlags, |
|
344 nsIAsyncVerifyRedirectCallback *callback) |
|
345 { |
|
346 nsCOMPtr<nsIURI> newURI; |
|
347 nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI)); |
|
348 if (NS_FAILED(rv)) |
|
349 return rv; |
|
350 |
|
351 bool match; |
|
352 rv = newURI->SchemeIs("http", &match); |
|
353 if (NS_FAILED(rv) || !match) { |
|
354 rv = newURI->SchemeIs("https", &match); |
|
355 if (NS_FAILED(rv) || !match) { |
|
356 LOG(("rejected: URL is not of type http/https\n")); |
|
357 return NS_ERROR_ABORT; |
|
358 } |
|
359 } |
|
360 |
|
361 // HTTP request headers are not automatically forwarded to the new channel. |
|
362 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel); |
|
363 NS_ENSURE_STATE(httpChannel); |
|
364 |
|
365 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), |
|
366 NS_LITERAL_CSTRING("prefetch"), |
|
367 false); |
|
368 |
|
369 // Assign to mChannel after we get notification about success of the |
|
370 // redirect in OnRedirectResult. |
|
371 mRedirectChannel = aNewChannel; |
|
372 |
|
373 callback->OnRedirectVerifyCallback(NS_OK); |
|
374 return NS_OK; |
|
375 } |
|
376 |
|
377 //----------------------------------------------------------------------------- |
|
378 // nsPrefetchNode::nsIRedirectResultListener |
|
379 //----------------------------------------------------------------------------- |
|
380 |
|
381 NS_IMETHODIMP |
|
382 nsPrefetchNode::OnRedirectResult(bool proceeding) |
|
383 { |
|
384 if (proceeding && mRedirectChannel) |
|
385 mChannel = mRedirectChannel; |
|
386 |
|
387 mRedirectChannel = nullptr; |
|
388 |
|
389 return NS_OK; |
|
390 } |
|
391 |
|
392 //----------------------------------------------------------------------------- |
|
393 // nsPrefetchService <public> |
|
394 //----------------------------------------------------------------------------- |
|
395 |
|
396 nsPrefetchService::nsPrefetchService() |
|
397 : mQueueHead(nullptr) |
|
398 , mQueueTail(nullptr) |
|
399 , mStopCount(0) |
|
400 , mHaveProcessed(false) |
|
401 , mDisabled(true) |
|
402 { |
|
403 } |
|
404 |
|
405 nsPrefetchService::~nsPrefetchService() |
|
406 { |
|
407 Preferences::RemoveObserver(this, PREFETCH_PREF); |
|
408 // cannot reach destructor if prefetch in progress (listener owns reference |
|
409 // to this service) |
|
410 EmptyQueue(); |
|
411 } |
|
412 |
|
413 nsresult |
|
414 nsPrefetchService::Init() |
|
415 { |
|
416 #if defined(PR_LOGGING) |
|
417 if (!gPrefetchLog) |
|
418 gPrefetchLog = PR_NewLogModule("nsPrefetch"); |
|
419 #endif |
|
420 |
|
421 nsresult rv; |
|
422 |
|
423 // read prefs and hook up pref observer |
|
424 mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled); |
|
425 Preferences::AddWeakObserver(this, PREFETCH_PREF); |
|
426 |
|
427 // Observe xpcom-shutdown event |
|
428 nsCOMPtr<nsIObserverService> observerService = |
|
429 mozilla::services::GetObserverService(); |
|
430 if (!observerService) |
|
431 return NS_ERROR_FAILURE; |
|
432 |
|
433 rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); |
|
434 NS_ENSURE_SUCCESS(rv, rv); |
|
435 |
|
436 if (!mDisabled) |
|
437 AddProgressListener(); |
|
438 |
|
439 return NS_OK; |
|
440 } |
|
441 |
|
442 void |
|
443 nsPrefetchService::ProcessNextURI() |
|
444 { |
|
445 nsresult rv; |
|
446 nsCOMPtr<nsIURI> uri, referrer; |
|
447 |
|
448 mCurrentNode = nullptr; |
|
449 |
|
450 do { |
|
451 rv = DequeueNode(getter_AddRefs(mCurrentNode)); |
|
452 |
|
453 if (NS_FAILED(rv)) break; |
|
454 |
|
455 #if defined(PR_LOGGING) |
|
456 if (LOG_ENABLED()) { |
|
457 nsAutoCString spec; |
|
458 mCurrentNode->mURI->GetSpec(spec); |
|
459 LOG(("ProcessNextURI [%s]\n", spec.get())); |
|
460 } |
|
461 #endif |
|
462 |
|
463 // |
|
464 // if opening the channel fails, then just skip to the next uri |
|
465 // |
|
466 nsRefPtr<nsPrefetchNode> node = mCurrentNode; |
|
467 rv = node->OpenChannel(); |
|
468 } |
|
469 while (NS_FAILED(rv)); |
|
470 } |
|
471 |
|
472 void |
|
473 nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node) |
|
474 { |
|
475 nsCOMPtr<nsIObserverService> observerService = |
|
476 mozilla::services::GetObserverService(); |
|
477 if (!observerService) |
|
478 return; |
|
479 |
|
480 observerService->NotifyObservers(static_cast<nsIStreamListener*>(node), |
|
481 "prefetch-load-requested", nullptr); |
|
482 } |
|
483 |
|
484 void |
|
485 nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node) |
|
486 { |
|
487 nsCOMPtr<nsIObserverService> observerService = |
|
488 mozilla::services::GetObserverService(); |
|
489 if (!observerService) |
|
490 return; |
|
491 |
|
492 observerService->NotifyObservers(static_cast<nsIStreamListener*>(node), |
|
493 "prefetch-load-completed", nullptr); |
|
494 } |
|
495 |
|
496 //----------------------------------------------------------------------------- |
|
497 // nsPrefetchService <private> |
|
498 //----------------------------------------------------------------------------- |
|
499 |
|
500 void |
|
501 nsPrefetchService::AddProgressListener() |
|
502 { |
|
503 // Register as an observer for the document loader |
|
504 nsCOMPtr<nsIWebProgress> progress = |
|
505 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); |
|
506 if (progress) |
|
507 progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT); |
|
508 } |
|
509 |
|
510 void |
|
511 nsPrefetchService::RemoveProgressListener() |
|
512 { |
|
513 // Register as an observer for the document loader |
|
514 nsCOMPtr<nsIWebProgress> progress = |
|
515 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); |
|
516 if (progress) |
|
517 progress->RemoveProgressListener(this); |
|
518 } |
|
519 |
|
520 nsresult |
|
521 nsPrefetchService::EnqueueNode(nsPrefetchNode *aNode) |
|
522 { |
|
523 NS_ADDREF(aNode); |
|
524 |
|
525 if (!mQueueTail) { |
|
526 mQueueHead = aNode; |
|
527 mQueueTail = aNode; |
|
528 } |
|
529 else { |
|
530 mQueueTail->mNext = aNode; |
|
531 mQueueTail = aNode; |
|
532 } |
|
533 |
|
534 return NS_OK; |
|
535 } |
|
536 |
|
537 nsresult |
|
538 nsPrefetchService::EnqueueURI(nsIURI *aURI, |
|
539 nsIURI *aReferrerURI, |
|
540 nsIDOMNode *aSource, |
|
541 nsPrefetchNode **aNode) |
|
542 { |
|
543 nsPrefetchNode *node = new nsPrefetchNode(this, aURI, aReferrerURI, |
|
544 aSource); |
|
545 if (!node) |
|
546 return NS_ERROR_OUT_OF_MEMORY; |
|
547 |
|
548 NS_ADDREF(*aNode = node); |
|
549 |
|
550 return EnqueueNode(node); |
|
551 } |
|
552 |
|
553 nsresult |
|
554 nsPrefetchService::DequeueNode(nsPrefetchNode **node) |
|
555 { |
|
556 if (!mQueueHead) |
|
557 return NS_ERROR_NOT_AVAILABLE; |
|
558 |
|
559 // give the ref to the caller |
|
560 *node = mQueueHead; |
|
561 mQueueHead = mQueueHead->mNext; |
|
562 (*node)->mNext = nullptr; |
|
563 |
|
564 if (!mQueueHead) |
|
565 mQueueTail = nullptr; |
|
566 |
|
567 return NS_OK; |
|
568 } |
|
569 |
|
570 void |
|
571 nsPrefetchService::EmptyQueue() |
|
572 { |
|
573 do { |
|
574 nsRefPtr<nsPrefetchNode> node; |
|
575 DequeueNode(getter_AddRefs(node)); |
|
576 } while (mQueueHead); |
|
577 } |
|
578 |
|
579 void |
|
580 nsPrefetchService::StartPrefetching() |
|
581 { |
|
582 // |
|
583 // at initialization time we might miss the first DOCUMENT START |
|
584 // notification, so we have to be careful to avoid letting our |
|
585 // stop count go negative. |
|
586 // |
|
587 if (mStopCount > 0) |
|
588 mStopCount--; |
|
589 |
|
590 LOG(("StartPrefetching [stopcount=%d]\n", mStopCount)); |
|
591 |
|
592 // only start prefetching after we've received enough DOCUMENT |
|
593 // STOP notifications. we do this inorder to defer prefetching |
|
594 // until after all sub-frames have finished loading. |
|
595 if (mStopCount == 0 && !mCurrentNode) { |
|
596 mHaveProcessed = true; |
|
597 ProcessNextURI(); |
|
598 } |
|
599 } |
|
600 |
|
601 void |
|
602 nsPrefetchService::StopPrefetching() |
|
603 { |
|
604 mStopCount++; |
|
605 |
|
606 LOG(("StopPrefetching [stopcount=%d]\n", mStopCount)); |
|
607 |
|
608 // only kill the prefetch queue if we've actually started prefetching. |
|
609 if (!mCurrentNode) |
|
610 return; |
|
611 |
|
612 mCurrentNode->CancelChannel(NS_BINDING_ABORTED); |
|
613 mCurrentNode = nullptr; |
|
614 EmptyQueue(); |
|
615 } |
|
616 |
|
617 //----------------------------------------------------------------------------- |
|
618 // nsPrefetchService::nsISupports |
|
619 //----------------------------------------------------------------------------- |
|
620 |
|
621 NS_IMPL_ISUPPORTS(nsPrefetchService, |
|
622 nsIPrefetchService, |
|
623 nsIWebProgressListener, |
|
624 nsIObserver, |
|
625 nsISupportsWeakReference) |
|
626 |
|
627 //----------------------------------------------------------------------------- |
|
628 // nsPrefetchService::nsIPrefetchService |
|
629 //----------------------------------------------------------------------------- |
|
630 |
|
631 nsresult |
|
632 nsPrefetchService::Prefetch(nsIURI *aURI, |
|
633 nsIURI *aReferrerURI, |
|
634 nsIDOMNode *aSource, |
|
635 bool aExplicit) |
|
636 { |
|
637 nsresult rv; |
|
638 |
|
639 NS_ENSURE_ARG_POINTER(aURI); |
|
640 NS_ENSURE_ARG_POINTER(aReferrerURI); |
|
641 |
|
642 #if defined(PR_LOGGING) |
|
643 if (LOG_ENABLED()) { |
|
644 nsAutoCString spec; |
|
645 aURI->GetSpec(spec); |
|
646 LOG(("PrefetchURI [%s]\n", spec.get())); |
|
647 } |
|
648 #endif |
|
649 |
|
650 if (mDisabled) { |
|
651 LOG(("rejected: prefetch service is disabled\n")); |
|
652 return NS_ERROR_ABORT; |
|
653 } |
|
654 |
|
655 // |
|
656 // XXX we should really be asking the protocol handler if it supports |
|
657 // caching, so we can determine if there is any value to prefetching. |
|
658 // for now, we'll only prefetch http links since we know that's the |
|
659 // most common case. ignore https links since https content only goes |
|
660 // into the memory cache. |
|
661 // |
|
662 // XXX we might want to either leverage nsIProtocolHandler::protocolFlags |
|
663 // or possibly nsIRequest::loadFlags to determine if this URI should be |
|
664 // prefetched. |
|
665 // |
|
666 bool match; |
|
667 rv = aURI->SchemeIs("http", &match); |
|
668 if (NS_FAILED(rv) || !match) { |
|
669 rv = aURI->SchemeIs("https", &match); |
|
670 if (NS_FAILED(rv) || !match) { |
|
671 LOG(("rejected: URL is not of type http/https\n")); |
|
672 return NS_ERROR_ABORT; |
|
673 } |
|
674 } |
|
675 |
|
676 // |
|
677 // the referrer URI must be http: |
|
678 // |
|
679 rv = aReferrerURI->SchemeIs("http", &match); |
|
680 if (NS_FAILED(rv) || !match) { |
|
681 rv = aReferrerURI->SchemeIs("https", &match); |
|
682 if (NS_FAILED(rv) || !match) { |
|
683 LOG(("rejected: referrer URL is neither http nor https\n")); |
|
684 return NS_ERROR_ABORT; |
|
685 } |
|
686 } |
|
687 |
|
688 // skip URLs that contain query strings, except URLs for which prefetching |
|
689 // has been explicitly requested. |
|
690 if (!aExplicit) { |
|
691 nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv)); |
|
692 if (NS_FAILED(rv)) return rv; |
|
693 nsAutoCString query; |
|
694 rv = url->GetQuery(query); |
|
695 if (NS_FAILED(rv) || !query.IsEmpty()) { |
|
696 LOG(("rejected: URL has a query string\n")); |
|
697 return NS_ERROR_ABORT; |
|
698 } |
|
699 } |
|
700 |
|
701 // |
|
702 // cancel if being prefetched |
|
703 // |
|
704 if (mCurrentNode) { |
|
705 bool equals; |
|
706 if (NS_SUCCEEDED(mCurrentNode->mURI->Equals(aURI, &equals)) && equals) { |
|
707 LOG(("rejected: URL is already being prefetched\n")); |
|
708 return NS_ERROR_ABORT; |
|
709 } |
|
710 } |
|
711 |
|
712 // |
|
713 // cancel if already on the prefetch queue |
|
714 // |
|
715 nsPrefetchNode *node = mQueueHead; |
|
716 for (; node; node = node->mNext) { |
|
717 bool equals; |
|
718 if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) { |
|
719 LOG(("rejected: URL is already on prefetch queue\n")); |
|
720 return NS_ERROR_ABORT; |
|
721 } |
|
722 } |
|
723 |
|
724 nsRefPtr<nsPrefetchNode> enqueuedNode; |
|
725 rv = EnqueueURI(aURI, aReferrerURI, aSource, |
|
726 getter_AddRefs(enqueuedNode)); |
|
727 NS_ENSURE_SUCCESS(rv, rv); |
|
728 |
|
729 NotifyLoadRequested(enqueuedNode); |
|
730 |
|
731 // if there are no pages loading, kick off the request immediately |
|
732 if (mStopCount == 0 && mHaveProcessed) |
|
733 ProcessNextURI(); |
|
734 |
|
735 return NS_OK; |
|
736 } |
|
737 |
|
738 NS_IMETHODIMP |
|
739 nsPrefetchService::PrefetchURI(nsIURI *aURI, |
|
740 nsIURI *aReferrerURI, |
|
741 nsIDOMNode *aSource, |
|
742 bool aExplicit) |
|
743 { |
|
744 return Prefetch(aURI, aReferrerURI, aSource, aExplicit); |
|
745 } |
|
746 |
|
747 NS_IMETHODIMP |
|
748 nsPrefetchService::EnumerateQueue(nsISimpleEnumerator **aEnumerator) |
|
749 { |
|
750 *aEnumerator = new nsPrefetchQueueEnumerator(this); |
|
751 if (!*aEnumerator) return NS_ERROR_OUT_OF_MEMORY; |
|
752 |
|
753 NS_ADDREF(*aEnumerator); |
|
754 |
|
755 return NS_OK; |
|
756 } |
|
757 |
|
758 //----------------------------------------------------------------------------- |
|
759 // nsPrefetchService::nsIWebProgressListener |
|
760 //----------------------------------------------------------------------------- |
|
761 |
|
762 NS_IMETHODIMP |
|
763 nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress, |
|
764 nsIRequest *aRequest, |
|
765 int32_t curSelfProgress, |
|
766 int32_t maxSelfProgress, |
|
767 int32_t curTotalProgress, |
|
768 int32_t maxTotalProgress) |
|
769 { |
|
770 NS_NOTREACHED("notification excluded in AddProgressListener(...)"); |
|
771 return NS_OK; |
|
772 } |
|
773 |
|
774 NS_IMETHODIMP |
|
775 nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress, |
|
776 nsIRequest *aRequest, |
|
777 uint32_t progressStateFlags, |
|
778 nsresult aStatus) |
|
779 { |
|
780 if (progressStateFlags & STATE_IS_DOCUMENT) { |
|
781 if (progressStateFlags & STATE_STOP) |
|
782 StartPrefetching(); |
|
783 else if (progressStateFlags & STATE_START) |
|
784 StopPrefetching(); |
|
785 } |
|
786 |
|
787 return NS_OK; |
|
788 } |
|
789 |
|
790 |
|
791 NS_IMETHODIMP |
|
792 nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress, |
|
793 nsIRequest* aRequest, |
|
794 nsIURI *location, |
|
795 uint32_t aFlags) |
|
796 { |
|
797 NS_NOTREACHED("notification excluded in AddProgressListener(...)"); |
|
798 return NS_OK; |
|
799 } |
|
800 |
|
801 NS_IMETHODIMP |
|
802 nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress, |
|
803 nsIRequest* aRequest, |
|
804 nsresult aStatus, |
|
805 const char16_t* aMessage) |
|
806 { |
|
807 NS_NOTREACHED("notification excluded in AddProgressListener(...)"); |
|
808 return NS_OK; |
|
809 } |
|
810 |
|
811 NS_IMETHODIMP |
|
812 nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress, |
|
813 nsIRequest *aRequest, |
|
814 uint32_t state) |
|
815 { |
|
816 NS_NOTREACHED("notification excluded in AddProgressListener(...)"); |
|
817 return NS_OK; |
|
818 } |
|
819 |
|
820 //----------------------------------------------------------------------------- |
|
821 // nsPrefetchService::nsIObserver |
|
822 //----------------------------------------------------------------------------- |
|
823 |
|
824 NS_IMETHODIMP |
|
825 nsPrefetchService::Observe(nsISupports *aSubject, |
|
826 const char *aTopic, |
|
827 const char16_t *aData) |
|
828 { |
|
829 LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic)); |
|
830 |
|
831 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { |
|
832 StopPrefetching(); |
|
833 EmptyQueue(); |
|
834 mDisabled = true; |
|
835 } |
|
836 else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { |
|
837 if (Preferences::GetBool(PREFETCH_PREF, false)) { |
|
838 if (mDisabled) { |
|
839 LOG(("enabling prefetching\n")); |
|
840 mDisabled = false; |
|
841 AddProgressListener(); |
|
842 } |
|
843 } |
|
844 else { |
|
845 if (!mDisabled) { |
|
846 LOG(("disabling prefetching\n")); |
|
847 StopPrefetching(); |
|
848 EmptyQueue(); |
|
849 mDisabled = true; |
|
850 RemoveProgressListener(); |
|
851 } |
|
852 } |
|
853 } |
|
854 |
|
855 return NS_OK; |
|
856 } |
|
857 |
|
858 // vim: ts=4 sw=4 expandtab |