Bug 454376 imgLoader.cpp does not compile with Sun Studio 12 on Solaris r=joedraw...
[wine-gecko.git] / modules / libpr0n / src / imgLoader.cpp
blob39bf5483abe6de851d9283fb6e05bd4712936e47
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 2001
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Stuart Parmenter <pavlov@netscape.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "imgLoader.h"
42 #include "nsCOMPtr.h"
44 #include "nsNetUtil.h"
45 #include "nsIHttpChannel.h"
46 #include "nsICachingChannel.h"
47 #include "nsIObserverService.h"
48 #include "nsIPrefBranch.h"
49 #include "nsIPrefService.h"
50 #include "nsIProxyObjectManager.h"
51 #include "nsIServiceManager.h"
52 #include "nsIFileURL.h"
53 #include "nsThreadUtils.h"
54 #include "nsXPIDLString.h"
55 #include "nsCRT.h"
57 #include "netCore.h"
59 #include "imgRequest.h"
60 #include "imgRequestProxy.h"
62 #include "ImageErrors.h"
63 #include "ImageLogging.h"
65 #include "nsIComponentRegistrar.h"
67 // we want to explore making the document own the load group
68 // so we can associate the document URI with the load group.
69 // until this point, we have an evil hack:
70 #include "nsIHttpChannelInternal.h"
72 #if defined(DEBUG_pavlov) || defined(DEBUG_timeless)
73 #include "nsISimpleEnumerator.h"
74 #include "nsXPCOM.h"
75 #include "nsISupportsPrimitives.h"
76 #include "nsXPIDLString.h"
77 #include "nsComponentManagerUtils.h"
79 static void PrintImageDecoders()
81 nsCOMPtr<nsIComponentRegistrar> compMgr;
82 if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) || !compMgr)
83 return;
84 nsCOMPtr<nsISimpleEnumerator> enumer;
85 if (NS_FAILED(compMgr->EnumerateContractIDs(getter_AddRefs(enumer))) || !enumer)
86 return;
88 nsCString str;
89 nsCOMPtr<nsISupports> s;
90 PRBool more = PR_FALSE;
91 while (NS_SUCCEEDED(enumer->HasMoreElements(&more)) && more) {
92 enumer->GetNext(getter_AddRefs(s));
93 if (s) {
94 nsCOMPtr<nsISupportsCString> ss(do_QueryInterface(s));
96 nsCAutoString xcs;
97 ss->GetData(xcs);
99 NS_NAMED_LITERAL_CSTRING(decoderContract, "@mozilla.org/image/decoder;2?type=");
101 if (StringBeginsWith(xcs, decoderContract)) {
102 printf("Have decoder for mime type: %s\n", xcs.get()+decoderContract.Length());
107 #endif
109 static PRBool NewRequestAndEntry(nsIURI *uri, imgRequest **request, imgCacheEntry **entry)
111 // If file, force revalidation on expiration
112 PRBool isFile;
113 uri->SchemeIs("file", &isFile);
115 *request = new imgRequest();
116 if (!*request)
117 return PR_FALSE;
119 *entry = new imgCacheEntry(*request, /* mustValidateIfExpired = */ isFile);
120 if (!*entry) {
121 delete *request;
122 return PR_FALSE;
125 NS_ADDREF(*request);
126 NS_ADDREF(*entry);
128 return PR_TRUE;
131 static PRBool ShouldRevalidateEntry(imgCacheEntry *aEntry,
132 nsLoadFlags aFlags,
133 PRBool aHasExpired)
135 PRBool bValidateEntry = PR_FALSE;
137 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE)
138 return PR_FALSE;
140 if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
141 bValidateEntry = PR_TRUE;
144 // The cache entry has expired... Determine whether the stale cache
145 // entry can be used without validation...
147 else if (aHasExpired) {
149 // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
150 // entries to be used unless they have been explicitly marked to
151 // indicate that revalidation is necessary.
153 if (aFlags & (nsIRequest::VALIDATE_NEVER |
154 nsIRequest::VALIDATE_ONCE_PER_SESSION))
156 bValidateEntry = aEntry->GetMustValidateIfExpired();
159 // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
160 // the entry must be revalidated.
162 else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
163 bValidateEntry = PR_TRUE;
167 return bValidateEntry;
170 static nsresult NewImageChannel(nsIChannel **aResult,
171 nsIURI *aURI,
172 nsIURI *aInitialDocumentURI,
173 nsIURI *aReferringURI,
174 nsILoadGroup *aLoadGroup,
175 nsLoadFlags aLoadFlags)
177 nsresult rv;
178 nsCOMPtr<nsIChannel> newChannel;
179 nsCOMPtr<nsIHttpChannel> newHttpChannel;
181 nsCOMPtr<nsIInterfaceRequestor> callbacks;
183 if (aLoadGroup) {
184 // Get the notification callbacks from the load group for the new channel.
186 // XXX: This is not exactly correct, because the network request could be
187 // referenced by multiple windows... However, the new channel needs
188 // something. So, using the 'first' notification callbacks is better
189 // than nothing...
191 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
194 // Pass in a NULL loadgroup because this is the underlying network request.
195 // This request may be referenced by several proxy image requests (psossibly
196 // in different documents).
197 // If all of the proxy requests are canceled then this request should be
198 // canceled too.
200 rv = NS_NewChannel(aResult,
201 aURI, // URI
202 nsnull, // Cached IOService
203 nsnull, // LoadGroup
204 callbacks, // Notification Callbacks
205 aLoadFlags);
206 if (NS_FAILED(rv))
207 return rv;
209 // Initialize HTTP-specific attributes
210 newHttpChannel = do_QueryInterface(*aResult);
211 if (newHttpChannel) {
212 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
213 NS_LITERAL_CSTRING("image/png,image/*;q=0.8,*/*;q=0.5"),
214 PR_FALSE);
216 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(newHttpChannel);
217 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
218 httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
219 newHttpChannel->SetReferrer(aReferringURI);
222 // Image channels are loaded by default with reduced priority.
223 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
224 if (p) {
225 PRUint32 priority = nsISupportsPriority::PRIORITY_LOW;
227 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND)
228 ++priority; // further reduce priority for background loads
230 p->AdjustPriority(priority);
233 return NS_OK;
236 static PRUint32 SecondsFromPRTime(PRTime prTime)
238 return PRUint32(PRInt64(prTime) / PRInt64(PR_USEC_PER_SEC));
241 imgCacheEntry::imgCacheEntry(imgRequest *request, PRBool mustValidateIfExpired /* = PR_FALSE */)
242 : mRequest(request),
243 mDataSize(0),
244 mTouchedTime(SecondsFromPRTime(PR_Now())),
245 mExpiryTime(0),
246 mMustValidateIfExpired(mustValidateIfExpired),
247 mEvicted(PR_FALSE)
250 void imgCacheEntry::TouchWithSize(PRInt32 diff)
252 LOG_SCOPE(gImgLog, "imgCacheEntry::TouchWithSize");
254 mTouchedTime = SecondsFromPRTime(PR_Now());
256 if (!Evicted()) {
257 nsCOMPtr<nsIURI> uri;
258 mRequest->GetURI(getter_AddRefs(uri));
259 imgLoader::CacheEntriesChanged(uri, diff);
263 void imgCacheEntry::Touch(PRBool updateTime /* = PR_TRUE */)
265 LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
267 if (updateTime)
268 mTouchedTime = SecondsFromPRTime(PR_Now());
270 if (!Evicted()) {
271 nsCOMPtr<nsIURI> uri;
272 mRequest->GetURI(getter_AddRefs(uri));
273 imgLoader::CacheEntriesChanged(uri);
277 imgCacheQueue::imgCacheQueue()
278 : mDirty(PR_FALSE),
279 mSize(0)
282 void imgCacheQueue::UpdateSize(PRInt32 diff)
284 mSize += diff;
287 PRUint32 imgCacheQueue::GetSize() const
289 return mSize;
292 #include <algorithm>
293 using namespace std;
295 void imgCacheQueue::Remove(imgCacheEntry *entry)
297 queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry);
298 if (it != mQueue.end()) {
299 mSize -= (*it)->GetDataSize();
300 mQueue.erase(it);
301 MarkDirty();
305 void imgCacheQueue::Push(imgCacheEntry *entry)
307 mSize += entry->GetDataSize();
309 nsRefPtr<imgCacheEntry> refptr(entry);
310 mQueue.push_back(refptr);
311 MarkDirty();
314 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop()
316 if (mQueue.empty())
317 return nsnull;
318 if (IsDirty())
319 Refresh();
321 nsRefPtr<imgCacheEntry> entry = mQueue[0];
322 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
323 mQueue.pop_back();
325 mSize -= entry->GetDataSize();
326 imgCacheEntry *ret = entry;
327 NS_ADDREF(ret);
328 return ret;
331 void imgCacheQueue::Refresh()
333 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
334 mDirty = PR_FALSE;
337 void imgCacheQueue::MarkDirty()
339 mDirty = PR_TRUE;
342 PRBool imgCacheQueue::IsDirty()
344 return mDirty;
347 PRUint32 imgCacheQueue::GetNumElements() const
349 return mQueue.size();
352 imgCacheQueue::iterator imgCacheQueue::begin()
354 return mQueue.begin();
356 imgCacheQueue::const_iterator imgCacheQueue::begin() const
358 return mQueue.begin();
361 imgCacheQueue::iterator imgCacheQueue::end()
363 return mQueue.end();
365 imgCacheQueue::const_iterator imgCacheQueue::end() const
367 return mQueue.end();
370 nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
371 imgIDecoderObserver *aObserver,
372 nsLoadFlags aLoadFlags, imgIRequest *aProxyRequest,
373 imgIRequest **_retval)
375 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest);
377 /* XXX If we move decoding onto separate threads, we should save off the
378 calling thread here and pass it off to |proxyRequest| so that it call
379 proxy calls to |aObserver|.
382 imgRequestProxy *proxyRequest;
383 if (aProxyRequest) {
384 proxyRequest = static_cast<imgRequestProxy *>(aProxyRequest);
385 } else {
386 NS_NEWXPCOM(proxyRequest, imgRequestProxy);
387 if (!proxyRequest) return NS_ERROR_OUT_OF_MEMORY;
389 NS_ADDREF(proxyRequest);
391 /* It is important to call |SetLoadFlags()| before calling |Init()| because
392 |Init()| adds the request to the loadgroup.
394 proxyRequest->SetLoadFlags(aLoadFlags);
396 // init adds itself to imgRequest's list of observers
397 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aObserver);
398 if (NS_FAILED(rv)) {
399 NS_RELEASE(proxyRequest);
400 return rv;
403 // transfer reference to caller
404 *_retval = static_cast<imgIRequest*>(proxyRequest);
406 return NS_OK;
409 class imgCacheObserver : public nsIObserver
411 public:
412 NS_DECL_ISUPPORTS
413 NS_DECL_NSIOBSERVER
414 private:
415 imgLoader mLoader;
418 NS_IMPL_ISUPPORTS1(imgCacheObserver, nsIObserver)
420 NS_IMETHODIMP
421 imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aSomeData)
423 if (strcmp(aTopic, "memory-pressure") == 0) {
424 mLoader.ClearCache(PR_FALSE);
425 mLoader.ClearCache(PR_TRUE);
426 } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
427 strcmp(aTopic, "chrome-flush-caches") == 0) {
428 mLoader.ClearCache(PR_TRUE);
430 return NS_OK;
433 class imgCacheExpirationTracker : public nsExpirationTracker<imgCacheEntry, 3>
435 enum { TIMEOUT_SECONDS = 10 };
436 public:
437 imgCacheExpirationTracker();
439 protected:
440 void NotifyExpired(imgCacheEntry *entry);
443 imgCacheExpirationTracker::imgCacheExpirationTracker()
444 : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000)
447 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry)
449 // We can be called multiple times on the same entry. Don't do work multiple
450 // times.
451 if (!entry->Evicted())
452 imgLoader::RemoveFromCache(entry);
454 imgLoader::VerifyCacheSizes();
457 imgCacheObserver *gCacheObserver;
458 imgCacheExpirationTracker *gCacheTracker;
460 imgLoader::imgCacheTable imgLoader::sCache;
461 imgCacheQueue imgLoader::sCacheQueue;
463 imgLoader::imgCacheTable imgLoader::sChromeCache;
464 imgCacheQueue imgLoader::sChromeCacheQueue;
466 PRFloat64 imgLoader::sCacheTimeWeight;
467 PRUint32 imgLoader::sCacheMaxSize;
469 NS_IMPL_ISUPPORTS4(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference)
471 imgLoader::imgLoader()
473 /* member initializers and constructor code */
474 #ifdef DEBUG_pavlov
475 PrintImageDecoders();
476 #endif
479 imgLoader::~imgLoader()
481 /* destructor code */
484 void imgLoader::VerifyCacheSizes()
486 if (!gCacheTracker)
487 return;
489 PRUint32 queuesize = sCacheQueue.GetNumElements() + sChromeCacheQueue.GetNumElements();
490 PRUint32 cachesize = sCache.Count() + sChromeCache.Count();
491 PRUint32 trackersize = 0;
492 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(gCacheTracker); it.Next(); )
493 trackersize++;
494 NS_ASSERTION(queuesize == cachesize, "Queue and cache sizes out of sync!");
495 NS_ASSERTION(queuesize == trackersize, "Queue and tracker sizes out of sync!");
498 imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI)
500 PRBool chrome = PR_FALSE;
501 aURI->SchemeIs("chrome", &chrome);
502 if (chrome)
503 return sChromeCache;
504 else
505 return sCache;
508 imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI)
510 PRBool chrome = PR_FALSE;
511 aURI->SchemeIs("chrome", &chrome);
512 if (chrome)
513 return sChromeCacheQueue;
514 else
515 return sCacheQueue;
518 nsresult imgLoader::InitCache()
520 nsresult rv;
521 nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1", &rv);
522 if (NS_FAILED(rv))
523 return rv;
525 gCacheObserver = new imgCacheObserver();
526 if (!gCacheObserver)
527 return NS_ERROR_OUT_OF_MEMORY;
528 NS_ADDREF(gCacheObserver);
530 os->AddObserver(gCacheObserver, "memory-pressure", PR_FALSE);
531 os->AddObserver(gCacheObserver, "chrome-flush-skin-caches", PR_FALSE);
532 os->AddObserver(gCacheObserver, "chrome-flush-caches", PR_FALSE);
534 gCacheTracker = new imgCacheExpirationTracker();
535 if (!gCacheTracker)
536 return NS_ERROR_OUT_OF_MEMORY;
538 if (!sCache.Init())
539 return NS_ERROR_OUT_OF_MEMORY;
540 if (!sChromeCache.Init())
541 return NS_ERROR_OUT_OF_MEMORY;
543 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
544 if (NS_FAILED(rv))
545 return rv;
547 PRInt32 timeweight;
548 rv = prefs->GetIntPref("image.cache.timeweight", &timeweight);
549 if (NS_SUCCEEDED(rv))
550 sCacheTimeWeight = timeweight / 1000.0;
551 else
552 sCacheTimeWeight = 0.5;
554 PRInt32 cachesize;
555 rv = prefs->GetIntPref("image.cache.size", &cachesize);
556 if (NS_SUCCEEDED(rv))
557 sCacheMaxSize = cachesize;
558 else
559 sCacheMaxSize = 5 * 1024 * 1024;
561 return NS_OK;
564 /* void clearCache (in boolean chrome); */
565 NS_IMETHODIMP imgLoader::ClearCache(PRBool chrome)
567 if (chrome)
568 return ClearChromeImageCache();
569 else
570 return ClearImageCache();
573 /* void removeEntry(in nsIURI uri); */
574 NS_IMETHODIMP imgLoader::RemoveEntry(nsIURI *uri)
576 if (RemoveFromCache(uri))
577 return NS_OK;
579 return NS_ERROR_NOT_AVAILABLE;
582 /* imgIRequest findEntry(in nsIURI uri); */
583 NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI *uri, nsIProperties **_retval)
585 nsRefPtr<imgCacheEntry> entry;
586 nsCAutoString spec;
587 imgCacheTable &cache = GetCache(uri);
589 uri->GetSpec(spec);
590 *_retval = nsnull;
592 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
593 if (gCacheTracker)
594 gCacheTracker->MarkUsed(entry);
595 nsRefPtr<imgRequest> request = getter_AddRefs(entry->GetRequest());
596 if (request) {
597 *_retval = request->Properties();
598 NS_ADDREF(*_retval);
602 return NS_OK;
605 void imgLoader::Shutdown()
607 ClearChromeImageCache();
608 ClearImageCache();
609 NS_IF_RELEASE(gCacheObserver);
610 delete gCacheTracker;
611 gCacheTracker = nsnull;
614 nsresult imgLoader::ClearChromeImageCache()
616 return EvictEntries(sChromeCache, sChromeCacheQueue);
619 nsresult imgLoader::ClearImageCache()
621 return EvictEntries(sCache, sCacheQueue);
624 PRBool imgLoader::PutIntoCache(nsIURI *key, imgCacheEntry *entry)
626 LOG_STATIC_FUNC(gImgLog, "imgLoader::PutIntoCache");
628 imgCacheTable &cache = GetCache(key);
630 nsCAutoString spec;
631 key->GetSpec(spec);
633 // Check to see if this request already exists in the cache and is being
634 // loaded on a different thread. If so, don't allow this entry to be added to
635 // the cache.
636 nsRefPtr<imgCacheEntry> tmpCacheEntry;
637 if (cache.Get(spec, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
638 PR_LOG(gImgLog, PR_LOG_DEBUG,
639 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", nsnull));
640 nsRefPtr<imgRequest> tmpRequest = getter_AddRefs(tmpCacheEntry->GetRequest());
641 void *cacheId = NS_GetCurrentThread();
643 if (!tmpRequest->IsReusable(cacheId))
644 return PR_FALSE;
646 if (gCacheTracker)
647 gCacheTracker->MarkUsed(tmpCacheEntry);
649 return PR_TRUE;
652 PR_LOG(gImgLog, PR_LOG_DEBUG,
653 ("[this=%p] imgLoader::PutIntoCache -- Element NOT already in the cache", nsnull));
655 if (!cache.Put(spec, entry))
656 return PR_FALSE;
658 imgCacheQueue &queue = GetCacheQueue(key);
659 queue.Push(entry);
661 if (gCacheTracker)
662 gCacheTracker->AddObject(entry);
664 CheckCacheLimits(cache, queue);
666 return PR_TRUE;
669 void imgLoader::CacheEntriesChanged(nsIURI *uri, PRInt32 sizediff /* = 0 */)
671 imgCacheQueue &queue = GetCacheQueue(uri);
672 queue.MarkDirty();
673 queue.UpdateSize(sizediff);
676 void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue)
678 if (queue.GetNumElements() == 0)
679 NS_ASSERTION(queue.GetSize() == 0,
680 "imgLoader::CheckCacheLimits -- incorrect cache size");
682 // Remove entries from the cache until we're back under our desired size.
683 while (queue.GetSize() >= sCacheMaxSize) {
684 // Remove the first entry in the queue.
685 nsRefPtr<imgCacheEntry> entry(queue.Pop());
687 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
689 if (entry)
690 RemoveFromCache(entry);
694 PRBool imgLoader::ValidateRequestWithNewChannel(imgRequest *request,
695 nsIURI *aURI,
696 nsIURI *aInitialDocumentURI,
697 nsIURI *aReferrerURI,
698 nsILoadGroup *aLoadGroup,
699 imgIDecoderObserver *aObserver,
700 nsISupports *aCX,
701 nsLoadFlags aLoadFlags,
702 imgIRequest *aExistingRequest,
703 imgIRequest **aProxyRequest)
705 // now we need to insert a new channel request object inbetween the real
706 // request and the proxy that basically delays loading the image until it
707 // gets a 304 or figures out that this needs to be a new request
709 nsresult rv;
711 if (request->mValidator) {
712 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
713 aLoadFlags, aExistingRequest,
714 reinterpret_cast<imgIRequest **>(aProxyRequest));
716 if (*aProxyRequest)
717 request->mValidator->AddProxy(static_cast<imgRequestProxy*>(*aProxyRequest));
719 return NS_SUCCEEDED(rv);
721 } else {
722 nsCOMPtr<nsIChannel> newChannel;
723 rv = NewImageChannel(getter_AddRefs(newChannel),
724 aURI,
725 aInitialDocumentURI,
726 aReferrerURI,
727 aLoadGroup,
728 aLoadFlags);
729 if (NS_FAILED(rv)) {
730 return PR_FALSE;
733 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(newChannel));
735 if (cacheChan) {
736 // since this channel supports nsICachingChannel, we can ask it
737 // to only stream us data if the data comes off the net.
738 PRUint32 loadFlags;
739 if (NS_SUCCEEDED(newChannel->GetLoadFlags(&loadFlags)))
740 newChannel->SetLoadFlags(loadFlags | nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
743 nsCOMPtr<imgIRequest> req;
744 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
745 aLoadFlags, aExistingRequest, getter_AddRefs(req));
746 if (NS_FAILED(rv)) {
747 return PR_FALSE;
750 imgCacheValidator *hvc = new imgCacheValidator(request, aCX);
751 if (!hvc) {
752 return PR_FALSE;
755 NS_ADDREF(hvc);
756 request->mValidator = hvc;
758 hvc->AddProxy(static_cast<imgRequestProxy*>
759 (static_cast<imgIRequest*>(req.get())));
761 rv = newChannel->AsyncOpen(static_cast<nsIStreamListener *>(hvc), nsnull);
762 if (NS_SUCCEEDED(rv))
763 NS_ADDREF(*aProxyRequest = req.get());
765 NS_RELEASE(hvc);
767 return NS_SUCCEEDED(rv);
771 PRBool imgLoader::ValidateEntry(imgCacheEntry *aEntry,
772 nsIURI *aURI,
773 nsIURI *aInitialDocumentURI,
774 nsIURI *aReferrerURI,
775 nsILoadGroup *aLoadGroup,
776 imgIDecoderObserver *aObserver,
777 nsISupports *aCX,
778 nsLoadFlags aLoadFlags,
779 PRBool aCanMakeNewChannel,
780 imgIRequest *aExistingRequest,
781 imgIRequest **aProxyRequest)
783 LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
785 PRBool hasExpired;
786 PRUint32 expirationTime = aEntry->GetExpiryTime();
787 if (expirationTime <= SecondsFromPRTime(PR_Now())) {
788 hasExpired = PR_TRUE;
789 } else {
790 hasExpired = PR_FALSE;
793 nsresult rv;
795 // Special treatment for file URLs - aEntry has expired if file has changed
796 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
797 if (fileUrl) {
798 PRUint32 lastModTime = aEntry->GetTouchedTime();
800 nsCOMPtr<nsIFile> theFile;
801 rv = fileUrl->GetFile(getter_AddRefs(theFile));
802 if (NS_SUCCEEDED(rv)) {
803 PRInt64 fileLastMod;
804 rv = theFile->GetLastModifiedTime(&fileLastMod);
805 if (NS_SUCCEEDED(rv)) {
806 // nsIFile uses millisec, NSPR usec
807 fileLastMod *= 1000;
808 hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
813 nsRefPtr<imgRequest> request(aEntry->GetRequest());
815 if (!request)
816 return PR_FALSE;
818 PRBool validateRequest = PR_FALSE;
820 // If the request's loadId is the same as the aCX, then it is ok to use
821 // this one because it has already been validated for this context.
823 // XXX: nsnull seems to be a 'special' key value that indicates that NO
824 // validation is required.
826 void *key = (void *)aCX;
827 if (request->mLoadId != key) {
828 // Determine whether the cache aEntry must be revalidated...
829 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
831 PR_LOG(gImgLog, PR_LOG_DEBUG,
832 ("imgLoader::ValidateEntry validating cache entry. "
833 "validateRequest = %d", validateRequest));
835 #if defined(PR_LOGGING)
836 else if (!key) {
837 nsCAutoString spec;
838 aURI->GetSpec(spec);
840 PR_LOG(gImgLog, PR_LOG_DEBUG,
841 ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
842 "because of NULL LoadID", spec.get()));
844 #endif
847 // Get the current thread... This is used as a cacheId to prevent
848 // sharing requests which are being loaded across multiple threads...
850 void *cacheId = NS_GetCurrentThread();
851 if (!request->IsReusable(cacheId)) {
853 // The current request is still being loaded and lives on a different
854 // event queue.
856 // Since its event queue is NOT active, do not reuse this imgRequest.
857 // PutIntoCache() will also ensure that we don't cache it.
859 PR_LOG(gImgLog, PR_LOG_DEBUG,
860 ("imgLoader::ValidateEntry -- DANGER!! Unable to use cached "
861 "imgRequest [request=%p]\n", address_of(request)));
863 return PR_FALSE;
866 if (validateRequest && aCanMakeNewChannel) {
867 LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate");
869 return ValidateRequestWithNewChannel(request, aURI, aInitialDocumentURI,
870 aReferrerURI, aLoadGroup, aObserver,
871 aCX, aLoadFlags, aExistingRequest,
872 aProxyRequest);
875 return !validateRequest;
879 PRBool imgLoader::RemoveFromCache(nsIURI *aKey)
881 LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache uri");
882 if (!aKey) return PR_FALSE;
884 imgCacheTable &cache = GetCache(aKey);
885 imgCacheQueue &queue = GetCacheQueue(aKey);
887 nsCAutoString spec;
888 aKey->GetSpec(spec);
890 nsRefPtr<imgCacheEntry> entry;
891 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
892 if (gCacheTracker)
893 gCacheTracker->RemoveObject(entry);
894 cache.Remove(spec);
895 queue.Remove(entry);
896 entry->SetEvicted(PR_TRUE);
897 return PR_TRUE;
899 else
900 return PR_FALSE;
903 PRBool imgLoader::RemoveFromCache(imgCacheEntry *entry)
905 LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry");
906 PRBool ret = PR_FALSE;
907 nsRefPtr<imgRequest> request(getter_AddRefs(entry->GetRequest()));
908 if (request) {
909 nsCOMPtr<nsIURI> key;
910 if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(key))) && key)
911 ret = RemoveFromCache(key);
914 return ret;
917 nsresult imgLoader::EvictEntries(imgCacheTable &aCacheToClear, imgCacheQueue &aQueueToClear)
919 LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries");
921 // We have to make a temporary, since RemoveFromCache removes the element
922 // from the queue, invalidating iterators.
923 nsTArray<nsRefPtr<imgCacheEntry> > entries;
924 for (imgCacheQueue::iterator it = aQueueToClear.begin(); it != aQueueToClear.end(); ++it)
925 entries.AppendElement(*it);
927 for (PRUint32 i = 0; i < entries.Length(); ++i)
928 if (!RemoveFromCache(entries[i]))
929 return NS_ERROR_FAILURE;
931 return NS_OK;
934 #define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \
935 nsIRequest::LOAD_FROM_CACHE)
937 #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \
938 nsIRequest::VALIDATE_NEVER | \
939 nsIRequest::VALIDATE_ONCE_PER_SESSION)
942 /* imgIRequest loadImage (in nsIURI aURI, in nsIURI initialDocumentURI, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */
944 NS_IMETHODIMP imgLoader::LoadImage(nsIURI *aURI,
945 nsIURI *aInitialDocumentURI,
946 nsIURI *aReferrerURI,
947 nsILoadGroup *aLoadGroup,
948 imgIDecoderObserver *aObserver,
949 nsISupports *aCX,
950 nsLoadFlags aLoadFlags,
951 nsISupports *aCacheKey,
952 imgIRequest *aRequest,
953 imgIRequest **_retval)
955 VerifyCacheSizes();
957 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
959 if (!aURI)
960 return NS_ERROR_NULL_POINTER;
962 #if defined(PR_LOGGING)
963 nsCAutoString spec;
964 aURI->GetSpec(spec);
965 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", spec.get());
966 #endif
968 *_retval = nsnull;
970 nsRefPtr<imgRequest> request;
972 nsresult rv;
973 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
975 // Get the default load flags from the loadgroup (if possible)...
976 if (aLoadGroup) {
977 aLoadGroup->GetLoadFlags(&requestFlags);
980 // Merge the default load flags with those passed in via aLoadFlags.
981 // Currently, *only* the caching, validation and background load flags
982 // are merged...
984 // The flags in aLoadFlags take precedence over the default flags!
986 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
987 // Override the default caching flags...
988 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
989 (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
991 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
992 // Override the default validation flags...
993 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
994 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
996 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
997 // Propagate background loading...
998 requestFlags |= nsIRequest::LOAD_BACKGROUND;
1001 nsRefPtr<imgCacheEntry> entry;
1003 // If we're bypassing the cache, we are guaranteed to want a new cache entry,
1004 // since we're going to do a new load.
1005 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1006 RemoveFromCache(aURI);
1007 } else {
1008 // Look in the cache for our URI, and then validate it.
1009 // XXX For now ignore aCacheKey. We will need it in the future
1010 // for correctly dealing with image load requests that are a result
1011 // of post data.
1012 imgCacheTable &cache = GetCache(aURI);
1013 nsCAutoString spec;
1015 aURI->GetSpec(spec);
1017 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
1018 if (gCacheTracker)
1019 gCacheTracker->MarkUsed(entry);
1021 if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI, aLoadGroup, aObserver, aCX,
1022 requestFlags, PR_TRUE, aRequest, _retval)) {
1023 request = getter_AddRefs(entry->GetRequest());
1025 entry->Touch();
1026 #ifdef DEBUG_joe
1027 printf("CACHEGET: %d %s %d\n", time(NULL), spec.get(), entry->GetDataSize());
1028 #endif
1030 else
1031 entry = nsnull;
1035 // If we didn't get a cache hit, we need to load from the network.
1036 if (!request) {
1037 LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|");
1039 nsCOMPtr<nsIChannel> newChannel;
1040 rv = NewImageChannel(getter_AddRefs(newChannel),
1041 aURI,
1042 aInitialDocumentURI,
1043 aReferrerURI,
1044 aLoadGroup,
1045 requestFlags);
1046 if (NS_FAILED(rv))
1047 return NS_ERROR_FAILURE;
1049 if (!NewRequestAndEntry(aURI, getter_AddRefs(request), getter_AddRefs(entry)))
1050 return NS_ERROR_OUT_OF_MEMORY;
1052 PR_LOG(gImgLog, PR_LOG_DEBUG,
1053 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request.get()));
1055 // Create a loadgroup for this new channel. This way if the channel
1056 // is redirected, we'll have a way to cancel the resulting channel.
1057 nsCOMPtr<nsILoadGroup> loadGroup =
1058 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1059 newChannel->SetLoadGroup(loadGroup);
1061 void *cacheId = NS_GetCurrentThread();
1062 request->Init(aURI, loadGroup, entry, cacheId, aCX);
1064 // create the proxy listener
1065 ProxyListener *pl = new ProxyListener(static_cast<nsIStreamListener *>(request.get()));
1066 if (!pl) {
1067 request->Cancel(NS_ERROR_OUT_OF_MEMORY);
1068 return NS_ERROR_OUT_OF_MEMORY;
1071 NS_ADDREF(pl);
1073 PR_LOG(gImgLog, PR_LOG_DEBUG,
1074 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this));
1076 nsresult openRes = newChannel->AsyncOpen(static_cast<nsIStreamListener *>(pl), nsnull);
1078 NS_RELEASE(pl);
1080 if (NS_FAILED(openRes)) {
1081 PR_LOG(gImgLog, PR_LOG_DEBUG,
1082 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n",
1083 this, openRes));
1084 request->Cancel(openRes);
1085 return openRes;
1088 // Try to add the new request into the cache.
1089 PutIntoCache(aURI, entry);
1091 // If we did get a cache hit, use it.
1092 } else {
1093 // XXX: Should this be executed if an expired cache entry does not have a caching channel??
1094 LOG_MSG_WITH_PARAM(gImgLog,
1095 "imgLoader::LoadImage |cache hit|", "request", request);
1097 // Update the request's LoadId
1098 request->SetLoadId(aCX);
1101 // If we didn't get a proxy when validating the cache entry, we need to create one.
1102 if (!*_retval) {
1103 LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request.");
1105 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1106 requestFlags, aRequest, _retval);
1107 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(*_retval);
1109 // Note that it's OK to add here even if the request is done. If it is,
1110 // it'll send a OnStopRequest() to the proxy in NotifyProxyListener and the
1111 // proxy will be removed from the loadgroup.
1112 proxy->AddToLoadGroup();
1114 request->NotifyProxyListener(proxy);
1116 return rv;
1119 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
1121 return NS_OK;
1124 /* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */
1125 NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
1127 NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer");
1129 nsresult rv;
1130 nsRefPtr<imgRequest> request;
1132 nsCOMPtr<nsIURI> uri;
1133 channel->GetURI(getter_AddRefs(uri));
1135 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
1136 channel->GetLoadFlags(&requestFlags);
1138 nsRefPtr<imgCacheEntry> entry;
1140 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1141 RemoveFromCache(uri);
1142 } else {
1143 // Look in the cache for our URI, and then validate it.
1144 // XXX For now ignore aCacheKey. We will need it in the future
1145 // for correctly dealing with image load requests that are a result
1146 // of post data.
1147 imgCacheTable &cache = GetCache(uri);
1148 nsCAutoString spec;
1150 uri->GetSpec(spec);
1152 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
1153 if (gCacheTracker)
1154 gCacheTracker->MarkUsed(entry);
1156 // We don't want to kick off another network load. So we ask
1157 // ValidateEntry to only do validation without creating a new proxy. If
1158 // it says that the entry isn't valid any more, we'll only use the entry
1159 // we're getting if the channel is loading from the cache anyways.
1161 // XXX -- should this be changed? it's pretty much verbatim from the old
1162 // code, but seems nonsensical.
1163 if (ValidateEntry(entry, uri, nsnull, nsnull, nsnull, aObserver, aCX,
1164 requestFlags, PR_FALSE, nsnull, nsnull)) {
1165 request = getter_AddRefs(entry->GetRequest());
1166 } else {
1167 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(channel));
1168 PRBool bUseCacheCopy;
1170 if (cacheChan)
1171 cacheChan->IsFromCache(&bUseCacheCopy);
1172 else
1173 bUseCacheCopy = PR_FALSE;
1175 if (!bUseCacheCopy)
1176 entry = nsnull;
1177 else {
1178 request = getter_AddRefs(entry->GetRequest());
1184 nsCOMPtr<nsILoadGroup> loadGroup;
1185 channel->GetLoadGroup(getter_AddRefs(loadGroup));
1187 if (request) {
1188 // we have this in our cache already.. cancel the current (document) load
1190 channel->Cancel(NS_IMAGELIB_ERROR_LOAD_ABORTED); // this should fire an OnStopRequest
1192 *listener = nsnull; // give them back a null nsIStreamListener
1193 } else {
1195 // Get the current Thread... This is used as a cacheId to prevent
1196 // sharing requests which are being loaded across multiple threads...
1197 nsIThread *thread = NS_GetCurrentThread();
1199 NewRequestAndEntry(uri, getter_AddRefs(request), getter_AddRefs(entry));
1201 // XXX(darin): I'm not sure that using the original URI is correct here.
1202 // Perhaps we should use the same URI that indexes the cache? Or, perhaps
1203 // the cache should use the original URI? See bug 89419.
1204 nsCOMPtr<nsIURI> originalURI;
1205 channel->GetOriginalURI(getter_AddRefs(originalURI));
1206 request->Init(originalURI, channel, entry, thread, aCX);
1208 ProxyListener *pl = new ProxyListener(static_cast<nsIStreamListener *>(request.get()));
1209 if (!pl)
1210 return NS_ERROR_OUT_OF_MEMORY;
1212 NS_ADDREF(pl);
1214 *listener = static_cast<nsIStreamListener*>(pl);
1215 NS_ADDREF(*listener);
1217 NS_RELEASE(pl);
1219 // Try to add the new request into the cache.
1220 PutIntoCache(uri, entry);
1223 // XXX: It looks like the wrong load flags are being passed in...
1224 requestFlags &= 0xFFFF;
1226 rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
1227 requestFlags, nsnull, _retval);
1228 request->NotifyProxyListener(static_cast<imgRequestProxy*>(*_retval));
1230 return rv;
1234 NS_IMETHODIMP imgLoader::SupportImageWithMimeType(const char* aMimeType, PRBool *_retval)
1236 *_retval = PR_FALSE;
1237 nsCOMPtr<nsIComponentRegistrar> reg;
1238 nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
1239 if (NS_FAILED(rv))
1240 return rv;
1241 nsCAutoString mimeType(aMimeType);
1242 ToLowerCase(mimeType);
1243 nsCAutoString decoderId(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + mimeType);
1244 return reg->IsContractIDRegistered(decoderId.get(), _retval);
1247 NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
1248 const PRUint8* aContents,
1249 PRUint32 aLength,
1250 nsACString& aContentType)
1252 return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
1255 /* static */
1256 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, PRUint32 aLength, nsACString& aContentType)
1258 /* Is it a GIF? */
1259 if (aLength >= 4 && !nsCRT::strncmp(aContents, "GIF8", 4)) {
1260 aContentType.AssignLiteral("image/gif");
1263 /* or a PNG? */
1264 else if (aLength >= 4 && ((unsigned char)aContents[0]==0x89 &&
1265 (unsigned char)aContents[1]==0x50 &&
1266 (unsigned char)aContents[2]==0x4E &&
1267 (unsigned char)aContents[3]==0x47))
1269 aContentType.AssignLiteral("image/png");
1272 /* maybe a JPEG (JFIF)? */
1273 /* JFIF files start with SOI APP0 but older files can start with SOI DQT
1274 * so we test for SOI followed by any marker, i.e. FF D8 FF
1275 * this will also work for SPIFF JPEG files if they appear in the future.
1277 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
1279 else if (aLength >= 3 &&
1280 ((unsigned char)aContents[0])==0xFF &&
1281 ((unsigned char)aContents[1])==0xD8 &&
1282 ((unsigned char)aContents[2])==0xFF)
1284 aContentType.AssignLiteral("image/jpeg");
1287 /* or how about ART? */
1288 /* ART begins with JG (4A 47). Major version offset 2.
1289 * Minor version offset 3. Offset 4 must be NULL.
1291 else if (aLength >= 5 &&
1292 ((unsigned char) aContents[0])==0x4a &&
1293 ((unsigned char) aContents[1])==0x47 &&
1294 ((unsigned char) aContents[4])==0x00 )
1296 aContentType.AssignLiteral("image/x-jg");
1299 else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) {
1300 aContentType.AssignLiteral("image/bmp");
1303 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
1304 // CURs begin with 2-byte 0 followed by 2-byte 2.
1305 else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
1306 !memcmp(aContents, "\000\000\002\000", 4))) {
1307 aContentType.AssignLiteral("image/x-icon");
1310 else if (aLength >= 8 && !nsCRT::strncmp(aContents, "#define ", 8)) {
1311 aContentType.AssignLiteral("image/x-xbitmap");
1313 else {
1314 /* none of the above? I give up */
1315 return NS_ERROR_NOT_AVAILABLE;
1318 return NS_OK;
1322 * proxy stream listener class used to handle multipart/x-mixed-replace
1325 #include "nsIRequest.h"
1326 #include "nsIStreamConverterService.h"
1327 #include "nsXPIDLString.h"
1329 NS_IMPL_ISUPPORTS2(ProxyListener, nsIStreamListener, nsIRequestObserver)
1331 ProxyListener::ProxyListener(nsIStreamListener *dest) :
1332 mDestListener(dest)
1334 /* member initializers and constructor code */
1337 ProxyListener::~ProxyListener()
1339 /* destructor code */
1343 /** nsIRequestObserver methods **/
1345 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
1346 NS_IMETHODIMP ProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
1348 if (!mDestListener)
1349 return NS_ERROR_FAILURE;
1351 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
1352 if (channel) {
1353 nsCAutoString contentType;
1354 nsresult rv = channel->GetContentType(contentType);
1356 if (!contentType.IsEmpty()) {
1357 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
1358 in the pipeline to handle the content and pass it along to our
1359 original listener.
1361 if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) {
1363 nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv));
1364 if (NS_SUCCEEDED(rv)) {
1365 nsCOMPtr<nsIStreamListener> toListener(mDestListener);
1366 nsCOMPtr<nsIStreamListener> fromListener;
1368 rv = convServ->AsyncConvertData("multipart/x-mixed-replace",
1369 "*/*",
1370 toListener,
1371 nsnull,
1372 getter_AddRefs(fromListener));
1373 if (NS_SUCCEEDED(rv))
1374 mDestListener = fromListener;
1380 return mDestListener->OnStartRequest(aRequest, ctxt);
1383 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
1384 NS_IMETHODIMP ProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
1386 if (!mDestListener)
1387 return NS_ERROR_FAILURE;
1389 return mDestListener->OnStopRequest(aRequest, ctxt, status);
1392 /** nsIStreamListener methods **/
1394 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
1395 NS_IMETHODIMP ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
1397 if (!mDestListener)
1398 return NS_ERROR_FAILURE;
1400 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
1404 * http validate class. check a channel for a 304
1407 NS_IMPL_ISUPPORTS2(imgCacheValidator, nsIStreamListener, nsIRequestObserver)
1409 imgLoader imgCacheValidator::sImgLoader;
1411 imgCacheValidator::imgCacheValidator(imgRequest *request, void *aContext) :
1412 mRequest(request),
1413 mContext(aContext)
1416 imgCacheValidator::~imgCacheValidator()
1418 /* destructor code */
1419 if (mRequest) {
1420 mRequest->mValidator = nsnull;
1424 void imgCacheValidator::AddProxy(imgRequestProxy *aProxy)
1426 // aProxy needs to be in the loadgroup since we're validating from
1427 // the network.
1428 aProxy->AddToLoadGroup();
1430 mProxies.AppendObject(aProxy);
1433 /** nsIRequestObserver methods **/
1435 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
1436 NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
1438 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
1439 if (cacheChan) {
1440 PRBool isFromCache;
1441 if (NS_SUCCEEDED(cacheChan->IsFromCache(&isFromCache)) && isFromCache) {
1443 PRUint32 count = mProxies.Count();
1444 for (PRInt32 i = count-1; i>=0; i--) {
1445 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
1446 mRequest->NotifyProxyListener(proxy);
1449 mRequest->SetLoadId(mContext);
1450 mRequest->mValidator = nsnull;
1452 mRequest = nsnull;
1454 return NS_OK;
1458 // fun stuff.
1459 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
1460 nsRefPtr<imgCacheEntry> entry;
1461 nsCOMPtr<nsIURI> uri;
1463 // Doom the old request's cache entry
1464 mRequest->RemoveFromCache();
1466 mRequest->GetURI(getter_AddRefs(uri));
1468 mRequest->mValidator = nsnull;
1469 mRequest = nsnull;
1471 imgRequest *request;
1473 if (!NewRequestAndEntry(uri, &request, getter_AddRefs(entry)))
1474 return NS_ERROR_OUT_OF_MEMORY;
1476 // XXX(darin): I'm not sure that using the original URI is correct here.
1477 // Perhaps we should use the same URI that indexes the cache? Or, perhaps
1478 // the cache should use the original URI? See bug 89419.
1479 nsCOMPtr<nsIURI> originalURI;
1480 channel->GetOriginalURI(getter_AddRefs(originalURI));
1481 request->Init(originalURI, channel, entry, NS_GetCurrentThread(), mContext);
1483 ProxyListener *pl = new ProxyListener(static_cast<nsIStreamListener *>(request));
1484 if (!pl) {
1485 NS_RELEASE(request);
1486 return NS_ERROR_OUT_OF_MEMORY;
1489 mDestListener = static_cast<nsIStreamListener*>(pl);
1491 PRUint32 count = mProxies.Count();
1492 for (PRInt32 i = count-1; i>=0; i--) {
1493 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
1494 proxy->ChangeOwner(request);
1495 request->NotifyProxyListener(proxy);
1498 // Try to add the new request into the cache.
1499 sImgLoader.PutIntoCache(uri, entry);
1501 NS_RELEASE(request);
1503 if (!mDestListener)
1504 return NS_OK;
1506 return mDestListener->OnStartRequest(aRequest, ctxt);
1509 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
1510 NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
1512 if (!mDestListener)
1513 return NS_OK;
1515 return mDestListener->OnStopRequest(aRequest, ctxt, status);
1518 /** nsIStreamListener methods **/
1521 // XXX see bug 113959
1522 static NS_METHOD dispose_of_data(nsIInputStream* in, void* closure,
1523 const char* fromRawSegment, PRUint32 toOffset,
1524 PRUint32 count, PRUint32 *writeCount)
1526 *writeCount = count;
1527 return NS_OK;
1530 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
1531 NS_IMETHODIMP imgCacheValidator::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
1533 #ifdef DEBUG
1534 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
1535 if (cacheChan) {
1536 PRBool isFromCache;
1537 if (NS_SUCCEEDED(cacheChan->IsFromCache(&isFromCache)) && isFromCache)
1538 NS_ERROR("OnDataAvailable not suppressed by LOAD_ONLY_IF_MODIFIED load flag");
1540 #endif
1542 if (!mDestListener) {
1543 // XXX see bug 113959
1544 PRUint32 _retval;
1545 inStr->ReadSegments(dispose_of_data, nsnull, count, &_retval);
1546 return NS_OK;
1549 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);