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