Bug 905002: backout bug 879717 until we can fix the mac-only regression rs=roc
[gecko.git] / image / src / imgLoader.cpp
blobc743e099b7dd106f89cd29390eafcbb5dbcb6773
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Attributes.h"
7 #include "mozilla/Preferences.h"
8 #include "mozilla/ClearOnShutdown.h"
10 #include "ImageLogging.h"
11 #include "imgLoader.h"
12 #include "imgRequestProxy.h"
14 #include "RasterImage.h"
16 #include "nsCOMPtr.h"
18 #include "nsContentUtils.h"
19 #include "nsCrossSiteListenerProxy.h"
20 #include "nsNetUtil.h"
21 #include "nsMimeTypes.h"
22 #include "nsStreamUtils.h"
23 #include "nsIHttpChannel.h"
24 #include "nsICachingChannel.h"
25 #include "nsIInterfaceRequestor.h"
26 #include "nsIProgressEventSink.h"
27 #include "nsIChannelEventSink.h"
28 #include "nsIAsyncVerifyRedirectCallback.h"
29 #include "nsIServiceManager.h"
30 #include "nsIFileURL.h"
31 #include "nsThreadUtils.h"
32 #include "nsXPIDLString.h"
33 #include "nsCRT.h"
34 #include "nsIDocument.h"
35 #include "nsPIDOMWindow.h"
37 #include "netCore.h"
39 #include "nsURILoader.h"
41 #include "nsIComponentRegistrar.h"
43 #include "nsIApplicationCache.h"
44 #include "nsIApplicationCacheContainer.h"
46 #include "nsIMemoryReporter.h"
48 // we want to explore making the document own the load group
49 // so we can associate the document URI with the load group.
50 // until this point, we have an evil hack:
51 #include "nsIHttpChannelInternal.h"
52 #include "nsIContentSecurityPolicy.h"
53 #include "nsIChannelPolicy.h"
54 #include "nsILoadContext.h"
55 #include "nsILoadGroupChild.h"
57 using namespace mozilla;
58 using namespace mozilla::image;
60 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(ImagesMallocSizeOf)
62 class imgMemoryReporter MOZ_FINAL :
63 public nsIMemoryMultiReporter
65 public:
66 imgMemoryReporter()
70 NS_DECL_ISUPPORTS
72 NS_IMETHOD GetName(nsACString &name)
74 name.Assign("images");
75 return NS_OK;
78 NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *callback,
79 nsISupports *closure)
81 AllSizes chrome;
82 AllSizes content;
84 for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
85 mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryAllSizes, &chrome);
86 mKnownLoaders[i]->mCache.EnumerateRead(EntryAllSizes, &content);
89 #define REPORT(_path, _kind, _amount, _desc) \
90 do { \
91 nsresult rv; \
92 rv = callback->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
93 _kind, nsIMemoryReporter::UNITS_BYTES, _amount, \
94 NS_LITERAL_CSTRING(_desc), closure); \
95 NS_ENSURE_SUCCESS(rv, rv); \
96 } while (0)
98 REPORT("explicit/images/chrome/used/raw",
99 nsIMemoryReporter::KIND_HEAP, chrome.mUsedRaw,
100 "Memory used by in-use chrome images (compressed data).");
102 REPORT("explicit/images/chrome/used/uncompressed-heap",
103 nsIMemoryReporter::KIND_HEAP, chrome.mUsedUncompressedHeap,
104 "Memory used by in-use chrome images (uncompressed data).");
106 REPORT("explicit/images/chrome/used/uncompressed-nonheap",
107 nsIMemoryReporter::KIND_NONHEAP, chrome.mUsedUncompressedNonheap,
108 "Memory used by in-use chrome images (uncompressed data).");
110 REPORT("explicit/images/chrome/unused/raw",
111 nsIMemoryReporter::KIND_HEAP, chrome.mUnusedRaw,
112 "Memory used by not in-use chrome images (compressed data).");
114 REPORT("explicit/images/chrome/unused/uncompressed-heap",
115 nsIMemoryReporter::KIND_HEAP, chrome.mUnusedUncompressedHeap,
116 "Memory used by not in-use chrome images (uncompressed data).");
118 REPORT("explicit/images/chrome/unused/uncompressed-nonheap",
119 nsIMemoryReporter::KIND_NONHEAP, chrome.mUnusedUncompressedNonheap,
120 "Memory used by not in-use chrome images (uncompressed data).");
122 REPORT("explicit/images/content/used/raw",
123 nsIMemoryReporter::KIND_HEAP, content.mUsedRaw,
124 "Memory used by in-use content images (compressed data).");
126 REPORT("explicit/images/content/used/uncompressed-heap",
127 nsIMemoryReporter::KIND_HEAP, content.mUsedUncompressedHeap,
128 "Memory used by in-use content images (uncompressed data).");
130 REPORT("explicit/images/content/used/uncompressed-nonheap",
131 nsIMemoryReporter::KIND_NONHEAP, content.mUsedUncompressedNonheap,
132 "Memory used by in-use content images (uncompressed data).");
134 REPORT("explicit/images/content/unused/raw",
135 nsIMemoryReporter::KIND_HEAP, content.mUnusedRaw,
136 "Memory used by not in-use content images (compressed data).");
138 REPORT("explicit/images/content/unused/uncompressed-heap",
139 nsIMemoryReporter::KIND_HEAP, content.mUnusedUncompressedHeap,
140 "Memory used by not in-use content images (uncompressed data).");
142 REPORT("explicit/images/content/unused/uncompressed-nonheap",
143 nsIMemoryReporter::KIND_NONHEAP, content.mUnusedUncompressedNonheap,
144 "Memory used by not in-use content images (uncompressed data).");
146 #undef REPORT
148 return NS_OK;
151 static int64_t GetImagesContentUsedUncompressed()
153 size_t n = 0;
154 for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); i++) {
155 imgLoader::sMemReporter->mKnownLoaders[i]->mCache.EnumerateRead(EntryUsedUncompressedSize, &n);
157 return n;
160 void RegisterLoader(imgLoader* aLoader)
162 mKnownLoaders.AppendElement(aLoader);
165 void UnregisterLoader(imgLoader* aLoader)
167 mKnownLoaders.RemoveElement(aLoader);
170 private:
171 nsTArray<imgLoader*> mKnownLoaders;
173 struct AllSizes {
174 size_t mUsedRaw;
175 size_t mUsedUncompressedHeap;
176 size_t mUsedUncompressedNonheap;
177 size_t mUnusedRaw;
178 size_t mUnusedUncompressedHeap;
179 size_t mUnusedUncompressedNonheap;
181 AllSizes() {
182 memset(this, 0, sizeof(*this));
186 static PLDHashOperator EntryAllSizes(const nsACString&,
187 imgCacheEntry *entry,
188 void *userArg)
190 nsRefPtr<imgRequest> req = entry->GetRequest();
191 Image *image = static_cast<Image*>(req->mImage.get());
192 if (image) {
193 AllSizes *sizes = static_cast<AllSizes*>(userArg);
194 if (entry->HasNoProxies()) {
195 sizes->mUnusedRaw +=
196 image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf);
197 sizes->mUnusedUncompressedHeap +=
198 image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf);
199 sizes->mUnusedUncompressedNonheap += image->NonHeapSizeOfDecoded();
200 } else {
201 sizes->mUsedRaw +=
202 image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf);
203 sizes->mUsedUncompressedHeap +=
204 image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf);
205 sizes->mUsedUncompressedNonheap += image->NonHeapSizeOfDecoded();
209 return PL_DHASH_NEXT;
212 static PLDHashOperator EntryUsedUncompressedSize(const nsACString&,
213 imgCacheEntry *entry,
214 void *userArg)
216 if (!entry->HasNoProxies()) {
217 size_t *n = static_cast<size_t*>(userArg);
218 nsRefPtr<imgRequest> req = entry->GetRequest();
219 Image *image = static_cast<Image*>(req->mImage.get());
220 if (image) {
221 // Both this and EntryAllSizes measure images-content-used-uncompressed
222 // memory. This function's measurement is secondary -- the result
223 // doesn't go in the "explicit" tree -- so we use moz_malloc_size_of
224 // instead of ImagesMallocSizeOf to prevent DMD from seeing it reported
225 // twice.
226 *n += image->HeapSizeOfDecodedWithComputedFallback(moz_malloc_size_of);
227 *n += image->NonHeapSizeOfDecoded();
231 return PL_DHASH_NEXT;
235 // This is used by telemetry.
236 NS_MEMORY_REPORTER_IMPLEMENT(
237 ImagesContentUsedUncompressed,
238 "images-content-used-uncompressed",
239 KIND_OTHER,
240 UNITS_BYTES,
241 imgMemoryReporter::GetImagesContentUsedUncompressed,
242 "This is the sum of the 'explicit/images/content/used/uncompressed-heap' "
243 "and 'explicit/images/content/used/uncompressed-nonheap' numbers. However, "
244 "it is measured at a different time and so may give slightly different "
245 "results.")
247 NS_IMPL_ISUPPORTS1(imgMemoryReporter, nsIMemoryMultiReporter)
249 NS_IMPL_ISUPPORTS3(nsProgressNotificationProxy,
250 nsIProgressEventSink,
251 nsIChannelEventSink,
252 nsIInterfaceRequestor)
254 NS_IMETHODIMP
255 nsProgressNotificationProxy::OnProgress(nsIRequest* request,
256 nsISupports* ctxt,
257 uint64_t progress,
258 uint64_t progressMax)
260 nsCOMPtr<nsILoadGroup> loadGroup;
261 request->GetLoadGroup(getter_AddRefs(loadGroup));
263 nsCOMPtr<nsIProgressEventSink> target;
264 NS_QueryNotificationCallbacks(mOriginalCallbacks,
265 loadGroup,
266 NS_GET_IID(nsIProgressEventSink),
267 getter_AddRefs(target));
268 if (!target)
269 return NS_OK;
270 return target->OnProgress(mImageRequest, ctxt, progress, progressMax);
273 NS_IMETHODIMP
274 nsProgressNotificationProxy::OnStatus(nsIRequest* request,
275 nsISupports* ctxt,
276 nsresult status,
277 const PRUnichar* statusArg)
279 nsCOMPtr<nsILoadGroup> loadGroup;
280 request->GetLoadGroup(getter_AddRefs(loadGroup));
282 nsCOMPtr<nsIProgressEventSink> target;
283 NS_QueryNotificationCallbacks(mOriginalCallbacks,
284 loadGroup,
285 NS_GET_IID(nsIProgressEventSink),
286 getter_AddRefs(target));
287 if (!target)
288 return NS_OK;
289 return target->OnStatus(mImageRequest, ctxt, status, statusArg);
292 NS_IMETHODIMP
293 nsProgressNotificationProxy::AsyncOnChannelRedirect(nsIChannel *oldChannel,
294 nsIChannel *newChannel,
295 uint32_t flags,
296 nsIAsyncVerifyRedirectCallback *cb)
298 // Tell the original original callbacks about it too
299 nsCOMPtr<nsILoadGroup> loadGroup;
300 newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
301 nsCOMPtr<nsIChannelEventSink> target;
302 NS_QueryNotificationCallbacks(mOriginalCallbacks,
303 loadGroup,
304 NS_GET_IID(nsIChannelEventSink),
305 getter_AddRefs(target));
306 if (!target) {
307 cb->OnRedirectVerifyCallback(NS_OK);
308 return NS_OK;
311 // Delegate to |target| if set, reusing |cb|
312 return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
315 NS_IMETHODIMP
316 nsProgressNotificationProxy::GetInterface(const nsIID& iid,
317 void** result)
319 if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
320 *result = static_cast<nsIProgressEventSink*>(this);
321 NS_ADDREF_THIS();
322 return NS_OK;
324 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
325 *result = static_cast<nsIChannelEventSink*>(this);
326 NS_ADDREF_THIS();
327 return NS_OK;
329 if (mOriginalCallbacks)
330 return mOriginalCallbacks->GetInterface(iid, result);
331 return NS_NOINTERFACE;
334 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader,
335 imgRequest **aRequest, imgCacheEntry **aEntry)
337 nsRefPtr<imgRequest> request = new imgRequest(aLoader);
338 nsRefPtr<imgCacheEntry> entry = new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
339 request.forget(aRequest);
340 entry.forget(aEntry);
343 static bool ShouldRevalidateEntry(imgCacheEntry *aEntry,
344 nsLoadFlags aFlags,
345 bool aHasExpired)
347 bool bValidateEntry = false;
349 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE)
350 return false;
352 if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
353 bValidateEntry = true;
355 else if (aEntry->GetMustValidate()) {
356 bValidateEntry = true;
359 // The cache entry has expired... Determine whether the stale cache
360 // entry can be used without validation...
362 else if (aHasExpired) {
364 // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
365 // entries to be used unless they have been explicitly marked to
366 // indicate that revalidation is necessary.
368 if (aFlags & (nsIRequest::VALIDATE_NEVER |
369 nsIRequest::VALIDATE_ONCE_PER_SESSION))
371 bValidateEntry = false;
374 // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
375 // the entry must be revalidated.
377 else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
378 bValidateEntry = true;
382 return bValidateEntry;
385 // Returns true if this request is compatible with the given CORS mode on the
386 // given loading principal, and false if the request may not be reused due
387 // to CORS.
388 static bool
389 ValidateCORSAndPrincipal(imgRequest* request, bool forcePrincipalCheck,
390 int32_t corsmode, nsIPrincipal* loadingPrincipal)
392 // If the entry's CORS mode doesn't match, or the CORS mode matches but the
393 // document principal isn't the same, we can't use this request.
394 if (request->GetCORSMode() != corsmode) {
395 return false;
396 } else if (request->GetCORSMode() != imgIRequest::CORS_NONE ||
397 forcePrincipalCheck) {
398 nsCOMPtr<nsIPrincipal> otherprincipal = request->GetLoadingPrincipal();
400 // If we previously had a principal, but we don't now, we can't use this
401 // request.
402 if (otherprincipal && !loadingPrincipal) {
403 return false;
406 if (otherprincipal && loadingPrincipal) {
407 bool equals = false;
408 otherprincipal->Equals(loadingPrincipal, &equals);
409 return equals;
413 return true;
416 static nsresult NewImageChannel(nsIChannel **aResult,
417 // If aForcePrincipalCheckForCacheEntry is
418 // true, then we will force a principal check
419 // even when not using CORS before assuming we
420 // have a cache hit on a cache entry that we
421 // create for this channel. This is an out
422 // param that should be set to true if this
423 // channel ends up depending on
424 // aLoadingPrincipal and false otherwise.
425 bool *aForcePrincipalCheckForCacheEntry,
426 nsIURI *aURI,
427 nsIURI *aInitialDocumentURI,
428 nsIURI *aReferringURI,
429 nsILoadGroup *aLoadGroup,
430 const nsCString& aAcceptHeader,
431 nsLoadFlags aLoadFlags,
432 nsIChannelPolicy *aPolicy,
433 nsIPrincipal *aLoadingPrincipal)
435 nsresult rv;
436 nsCOMPtr<nsIChannel> newChannel;
437 nsCOMPtr<nsIHttpChannel> newHttpChannel;
439 nsCOMPtr<nsIInterfaceRequestor> callbacks;
441 if (aLoadGroup) {
442 // Get the notification callbacks from the load group for the new channel.
444 // XXX: This is not exactly correct, because the network request could be
445 // referenced by multiple windows... However, the new channel needs
446 // something. So, using the 'first' notification callbacks is better
447 // than nothing...
449 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
452 // Pass in a NULL loadgroup because this is the underlying network request.
453 // This request may be referenced by several proxy image requests (psossibly
454 // in different documents).
455 // If all of the proxy requests are canceled then this request should be
456 // canceled too.
458 rv = NS_NewChannel(aResult,
459 aURI, // URI
460 nullptr, // Cached IOService
461 nullptr, // LoadGroup
462 callbacks, // Notification Callbacks
463 aLoadFlags,
464 aPolicy);
465 if (NS_FAILED(rv))
466 return rv;
468 *aForcePrincipalCheckForCacheEntry = false;
470 // Initialize HTTP-specific attributes
471 newHttpChannel = do_QueryInterface(*aResult);
472 if (newHttpChannel) {
473 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
474 aAcceptHeader,
475 false);
477 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(newHttpChannel);
478 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
479 httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
480 newHttpChannel->SetReferrer(aReferringURI);
483 // Image channels are loaded by default with reduced priority.
484 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
485 if (p) {
486 uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
488 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND)
489 ++priority; // further reduce priority for background loads
491 p->AdjustPriority(priority);
494 bool setOwner = nsContentUtils::SetUpChannelOwner(aLoadingPrincipal,
495 *aResult, aURI, false);
496 *aForcePrincipalCheckForCacheEntry = setOwner;
498 return NS_OK;
501 static uint32_t SecondsFromPRTime(PRTime prTime)
503 return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
506 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest *request, bool forcePrincipalCheck)
507 : mLoader(loader),
508 mRequest(request),
509 mDataSize(0),
510 mTouchedTime(SecondsFromPRTime(PR_Now())),
511 mExpiryTime(0),
512 mMustValidate(false),
513 // We start off as evicted so we don't try to update the cache. PutIntoCache
514 // will set this to false.
515 mEvicted(true),
516 mHasNoProxies(true),
517 mForcePrincipalCheck(forcePrincipalCheck)
520 imgCacheEntry::~imgCacheEntry()
522 LOG_FUNC(GetImgLog(), "imgCacheEntry::~imgCacheEntry()");
525 void imgCacheEntry::Touch(bool updateTime /* = true */)
527 LOG_SCOPE(GetImgLog(), "imgCacheEntry::Touch");
529 if (updateTime)
530 mTouchedTime = SecondsFromPRTime(PR_Now());
532 UpdateCache();
535 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */)
537 // Don't update the cache if we've been removed from it or it doesn't care
538 // about our size or usage.
539 if (!Evicted() && HasNoProxies()) {
540 nsCOMPtr<nsIURI> uri;
541 mRequest->GetURI(getter_AddRefs(uri));
542 mLoader->CacheEntriesChanged(uri, diff);
546 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies)
548 #if defined(PR_LOGGING)
549 nsCOMPtr<nsIURI> uri;
550 mRequest->GetURI(getter_AddRefs(uri));
551 nsAutoCString spec;
552 if (uri)
553 uri->GetSpec(spec);
554 if (hasNoProxies)
555 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies true", "uri", spec.get());
556 else
557 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies false", "uri", spec.get());
558 #endif
560 mHasNoProxies = hasNoProxies;
563 imgCacheQueue::imgCacheQueue()
564 : mDirty(false),
565 mSize(0)
568 void imgCacheQueue::UpdateSize(int32_t diff)
570 mSize += diff;
573 uint32_t imgCacheQueue::GetSize() const
575 return mSize;
578 #include <algorithm>
579 using namespace std;
581 void imgCacheQueue::Remove(imgCacheEntry *entry)
583 queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry);
584 if (it != mQueue.end()) {
585 mSize -= (*it)->GetDataSize();
586 mQueue.erase(it);
587 MarkDirty();
591 void imgCacheQueue::Push(imgCacheEntry *entry)
593 mSize += entry->GetDataSize();
595 nsRefPtr<imgCacheEntry> refptr(entry);
596 mQueue.push_back(refptr);
597 MarkDirty();
600 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop()
602 if (mQueue.empty())
603 return nullptr;
604 if (IsDirty())
605 Refresh();
607 nsRefPtr<imgCacheEntry> entry = mQueue[0];
608 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
609 mQueue.pop_back();
611 mSize -= entry->GetDataSize();
612 return entry.forget();
615 void imgCacheQueue::Refresh()
617 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
618 mDirty = false;
621 void imgCacheQueue::MarkDirty()
623 mDirty = true;
626 bool imgCacheQueue::IsDirty()
628 return mDirty;
631 uint32_t imgCacheQueue::GetNumElements() const
633 return mQueue.size();
636 imgCacheQueue::iterator imgCacheQueue::begin()
638 return mQueue.begin();
640 imgCacheQueue::const_iterator imgCacheQueue::begin() const
642 return mQueue.begin();
645 imgCacheQueue::iterator imgCacheQueue::end()
647 return mQueue.end();
649 imgCacheQueue::const_iterator imgCacheQueue::end() const
651 return mQueue.end();
654 nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
655 imgINotificationObserver *aObserver,
656 nsLoadFlags aLoadFlags, imgRequestProxy **_retval)
658 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest);
660 /* XXX If we move decoding onto separate threads, we should save off the
661 calling thread here and pass it off to |proxyRequest| so that it call
662 proxy calls to |aObserver|.
665 imgRequestProxy *proxyRequest = new imgRequestProxy();
666 NS_ADDREF(proxyRequest);
668 /* It is important to call |SetLoadFlags()| before calling |Init()| because
669 |Init()| adds the request to the loadgroup.
671 proxyRequest->SetLoadFlags(aLoadFlags);
673 nsCOMPtr<nsIURI> uri;
674 aRequest->GetURI(getter_AddRefs(uri));
676 // init adds itself to imgRequest's list of observers
677 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver);
678 if (NS_FAILED(rv)) {
679 NS_RELEASE(proxyRequest);
680 return rv;
683 // transfer reference to caller
684 *_retval = proxyRequest;
686 return NS_OK;
689 class imgCacheObserver MOZ_FINAL : public nsIObserver
691 public:
692 NS_DECL_ISUPPORTS
693 NS_DECL_NSIOBSERVER
696 NS_IMPL_ISUPPORTS1(imgCacheObserver, nsIObserver)
698 NS_IMETHODIMP
699 imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aSomeData)
701 if (strcmp(aTopic, "memory-pressure") == 0) {
702 DiscardTracker::DiscardAll();
704 return NS_OK;
707 class imgCacheExpirationTracker MOZ_FINAL
708 : public nsExpirationTracker<imgCacheEntry, 3>
710 enum { TIMEOUT_SECONDS = 10 };
711 public:
712 imgCacheExpirationTracker();
714 protected:
715 void NotifyExpired(imgCacheEntry *entry);
718 imgCacheExpirationTracker::imgCacheExpirationTracker()
719 : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000)
722 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry)
724 // Hold on to a reference to this entry, because the expiration tracker
725 // mechanism doesn't.
726 nsRefPtr<imgCacheEntry> kungFuDeathGrip(entry);
728 #if defined(PR_LOGGING)
729 nsRefPtr<imgRequest> req(entry->GetRequest());
730 if (req) {
731 nsCOMPtr<nsIURI> uri;
732 req->GetURI(getter_AddRefs(uri));
733 nsAutoCString spec;
734 uri->GetSpec(spec);
735 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheExpirationTracker::NotifyExpired", "entry", spec.get());
737 #endif
739 // We can be called multiple times on the same entry. Don't do work multiple
740 // times.
741 if (!entry->Evicted())
742 entry->Loader()->RemoveFromCache(entry);
744 entry->Loader()->VerifyCacheSizes();
747 imgCacheObserver *gCacheObserver;
749 double imgLoader::sCacheTimeWeight;
750 uint32_t imgLoader::sCacheMaxSize;
751 imgMemoryReporter* imgLoader::sMemReporter;
753 NS_IMPL_ISUPPORTS5(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference, nsIObserver)
755 imgLoader::imgLoader()
756 : mRespectPrivacy(false)
758 sMemReporter->AddRef();
759 sMemReporter->RegisterLoader(this);
762 already_AddRefed<imgLoader>
763 imgLoader::GetInstance()
765 static StaticRefPtr<imgLoader> singleton;
766 if (!singleton) {
767 singleton = imgLoader::Create();
768 if (!singleton)
769 return nullptr;
770 ClearOnShutdown(&singleton);
772 nsRefPtr<imgLoader> loader = singleton.get();
773 return loader.forget();
776 imgLoader::~imgLoader()
778 ClearChromeImageCache();
779 ClearImageCache();
780 sMemReporter->UnregisterLoader(this);
781 sMemReporter->Release();
784 void imgLoader::VerifyCacheSizes()
786 #ifdef DEBUG
787 if (!mCacheTracker)
788 return;
790 uint32_t cachesize = mCache.Count() + mChromeCache.Count();
791 uint32_t queuesize = mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
792 uint32_t trackersize = 0;
793 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker); it.Next(); )
794 trackersize++;
795 NS_ABORT_IF_FALSE(queuesize == trackersize, "Queue and tracker sizes out of sync!");
796 NS_ABORT_IF_FALSE(queuesize <= cachesize, "Queue has more elements than cache!");
797 #endif
800 imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI)
802 bool chrome = false;
803 aURI->SchemeIs("chrome", &chrome);
804 if (chrome)
805 return mChromeCache;
806 else
807 return mCache;
810 imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI)
812 bool chrome = false;
813 aURI->SchemeIs("chrome", &chrome);
814 if (chrome)
815 return mChromeCacheQueue;
816 else
817 return mCacheQueue;
820 void imgLoader::GlobalInit()
822 gCacheObserver = new imgCacheObserver();
823 NS_ADDREF(gCacheObserver);
825 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
826 if (os)
827 os->AddObserver(gCacheObserver, "memory-pressure", false);
829 int32_t timeweight;
830 nsresult rv = Preferences::GetInt("image.cache.timeweight", &timeweight);
831 if (NS_SUCCEEDED(rv))
832 sCacheTimeWeight = timeweight / 1000.0;
833 else
834 sCacheTimeWeight = 0.5;
836 int32_t cachesize;
837 rv = Preferences::GetInt("image.cache.size", &cachesize);
838 if (NS_SUCCEEDED(rv))
839 sCacheMaxSize = cachesize;
840 else
841 sCacheMaxSize = 5 * 1024 * 1024;
843 sMemReporter = new imgMemoryReporter();
844 NS_RegisterMemoryMultiReporter(sMemReporter);
845 NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(ImagesContentUsedUncompressed));
848 nsresult imgLoader::InitCache()
850 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
851 if (!os)
852 return NS_ERROR_FAILURE;
854 os->AddObserver(this, "memory-pressure", false);
855 os->AddObserver(this, "chrome-flush-skin-caches", false);
856 os->AddObserver(this, "chrome-flush-caches", false);
857 os->AddObserver(this, "last-pb-context-exited", false);
858 os->AddObserver(this, "profile-before-change", false);
859 os->AddObserver(this, "xpcom-shutdown", false);
861 mCacheTracker = new imgCacheExpirationTracker();
863 mCache.Init();
864 mChromeCache.Init();
866 return NS_OK;
869 nsresult imgLoader::Init()
871 InitCache();
873 ReadAcceptHeaderPref();
875 Preferences::AddWeakObserver(this, "image.http.accept");
877 return NS_OK;
880 NS_IMETHODIMP
881 imgLoader::RespectPrivacyNotifications()
883 mRespectPrivacy = true;
884 return NS_OK;
887 NS_IMETHODIMP
888 imgLoader::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData)
890 // We listen for pref change notifications...
891 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
892 if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "image.http.accept")) {
893 ReadAcceptHeaderPref();
896 } else if (strcmp(aTopic, "memory-pressure") == 0) {
897 MinimizeCaches();
898 } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
899 strcmp(aTopic, "chrome-flush-caches") == 0) {
900 MinimizeCaches();
901 ClearChromeImageCache();
902 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
903 if (mRespectPrivacy) {
904 ClearImageCache();
905 ClearChromeImageCache();
907 } else if (strcmp(aTopic, "profile-before-change") == 0 ||
908 strcmp(aTopic, "xpcom-shutdown") == 0) {
909 mCacheTracker = nullptr;
912 // (Nothing else should bring us here)
913 else {
914 NS_ABORT_IF_FALSE(0, "Invalid topic received");
917 return NS_OK;
920 void imgLoader::ReadAcceptHeaderPref()
922 nsAdoptingCString accept = Preferences::GetCString("image.http.accept");
923 if (accept)
924 mAcceptHeader = accept;
925 else
926 mAcceptHeader = IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5";
929 /* void clearCache (in boolean chrome); */
930 NS_IMETHODIMP imgLoader::ClearCache(bool chrome)
932 if (chrome)
933 return ClearChromeImageCache();
934 else
935 return ClearImageCache();
938 /* void removeEntry(in nsIURI uri); */
939 NS_IMETHODIMP imgLoader::RemoveEntry(nsIURI *uri)
941 if (RemoveFromCache(uri))
942 return NS_OK;
944 return NS_ERROR_NOT_AVAILABLE;
947 /* imgIRequest findEntry(in nsIURI uri); */
948 NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI *uri, nsIProperties **_retval)
950 nsRefPtr<imgCacheEntry> entry;
951 nsAutoCString spec;
952 imgCacheTable &cache = GetCache(uri);
954 uri->GetSpec(spec);
955 *_retval = nullptr;
957 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
958 if (mCacheTracker && entry->HasNoProxies())
959 mCacheTracker->MarkUsed(entry);
961 nsRefPtr<imgRequest> request = entry->GetRequest();
962 if (request) {
963 *_retval = request->Properties();
964 NS_ADDREF(*_retval);
968 return NS_OK;
971 void imgLoader::Shutdown()
973 NS_RELEASE(gCacheObserver);
976 nsresult imgLoader::ClearChromeImageCache()
978 return EvictEntries(mChromeCache);
981 nsresult imgLoader::ClearImageCache()
983 return EvictEntries(mCache);
986 void imgLoader::MinimizeCaches()
988 EvictEntries(mCacheQueue);
989 EvictEntries(mChromeCacheQueue);
992 bool imgLoader::PutIntoCache(nsIURI *key, imgCacheEntry *entry)
994 imgCacheTable &cache = GetCache(key);
996 nsAutoCString spec;
997 key->GetSpec(spec);
999 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::PutIntoCache", "uri", spec.get());
1001 // Check to see if this request already exists in the cache and is being
1002 // loaded on a different thread. If so, don't allow this entry to be added to
1003 // the cache.
1004 nsRefPtr<imgCacheEntry> tmpCacheEntry;
1005 if (cache.Get(spec, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1006 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1007 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", nullptr));
1008 nsRefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1010 // If it already exists, and we're putting the same key into the cache, we
1011 // should remove the old version.
1012 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1013 ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element", nullptr));
1015 RemoveFromCache(key);
1016 } else {
1017 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1018 ("[this=%p] imgLoader::PutIntoCache -- Element NOT already in the cache", nullptr));
1021 cache.Put(spec, entry);
1023 // We can be called to resurrect an evicted entry.
1024 if (entry->Evicted())
1025 entry->SetEvicted(false);
1027 // If we're resurrecting an entry with no proxies, put it back in the
1028 // tracker and queue.
1029 if (entry->HasNoProxies()) {
1030 nsresult addrv = NS_OK;
1032 if (mCacheTracker)
1033 addrv = mCacheTracker->AddObject(entry);
1035 if (NS_SUCCEEDED(addrv)) {
1036 imgCacheQueue &queue = GetCacheQueue(key);
1037 queue.Push(entry);
1041 nsRefPtr<imgRequest> request = entry->GetRequest();
1042 request->SetIsInCache(true);
1044 return true;
1047 bool imgLoader::SetHasNoProxies(nsIURI *key, imgCacheEntry *entry)
1049 #if defined(PR_LOGGING)
1050 nsAutoCString spec;
1051 key->GetSpec(spec);
1053 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasNoProxies", "uri", spec.get());
1054 #endif
1056 if (entry->Evicted())
1057 return false;
1059 imgCacheQueue &queue = GetCacheQueue(key);
1061 nsresult addrv = NS_OK;
1063 if (mCacheTracker)
1064 addrv = mCacheTracker->AddObject(entry);
1066 if (NS_SUCCEEDED(addrv)) {
1067 queue.Push(entry);
1068 entry->SetHasNoProxies(true);
1071 imgCacheTable &cache = GetCache(key);
1072 CheckCacheLimits(cache, queue);
1074 return true;
1077 bool imgLoader::SetHasProxies(nsIURI *key)
1079 VerifyCacheSizes();
1081 imgCacheTable &cache = GetCache(key);
1083 nsAutoCString spec;
1084 key->GetSpec(spec);
1086 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasProxies", "uri", spec.get());
1088 nsRefPtr<imgCacheEntry> entry;
1089 if (cache.Get(spec, getter_AddRefs(entry)) && entry && entry->HasNoProxies()) {
1090 imgCacheQueue &queue = GetCacheQueue(key);
1091 queue.Remove(entry);
1093 if (mCacheTracker)
1094 mCacheTracker->RemoveObject(entry);
1096 entry->SetHasNoProxies(false);
1098 return true;
1101 return false;
1104 void imgLoader::CacheEntriesChanged(nsIURI *uri, int32_t sizediff /* = 0 */)
1106 imgCacheQueue &queue = GetCacheQueue(uri);
1107 queue.MarkDirty();
1108 queue.UpdateSize(sizediff);
1111 void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue)
1113 if (queue.GetNumElements() == 0)
1114 NS_ASSERTION(queue.GetSize() == 0,
1115 "imgLoader::CheckCacheLimits -- incorrect cache size");
1117 // Remove entries from the cache until we're back under our desired size.
1118 while (queue.GetSize() >= sCacheMaxSize) {
1119 // Remove the first entry in the queue.
1120 nsRefPtr<imgCacheEntry> entry(queue.Pop());
1122 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1124 #if defined(PR_LOGGING)
1125 nsRefPtr<imgRequest> req(entry->GetRequest());
1126 if (req) {
1127 nsCOMPtr<nsIURI> uri;
1128 req->GetURI(getter_AddRefs(uri));
1129 nsAutoCString spec;
1130 uri->GetSpec(spec);
1131 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::CheckCacheLimits", "entry", spec.get());
1133 #endif
1135 if (entry)
1136 RemoveFromCache(entry);
1140 bool imgLoader::ValidateRequestWithNewChannel(imgRequest *request,
1141 nsIURI *aURI,
1142 nsIURI *aInitialDocumentURI,
1143 nsIURI *aReferrerURI,
1144 nsILoadGroup *aLoadGroup,
1145 imgINotificationObserver *aObserver,
1146 nsISupports *aCX,
1147 nsLoadFlags aLoadFlags,
1148 imgRequestProxy **aProxyRequest,
1149 nsIChannelPolicy *aPolicy,
1150 nsIPrincipal* aLoadingPrincipal,
1151 int32_t aCORSMode)
1153 // now we need to insert a new channel request object inbetween the real
1154 // request and the proxy that basically delays loading the image until it
1155 // gets a 304 or figures out that this needs to be a new request
1157 nsresult rv;
1159 // If we're currently in the middle of validating this request, just hand
1160 // back a proxy to it; the required work will be done for us.
1161 if (request->mValidator) {
1162 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1163 aLoadFlags, aProxyRequest);
1164 if (NS_FAILED(rv)) {
1165 return false;
1168 if (*aProxyRequest) {
1169 imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1171 // We will send notifications from imgCacheValidator::OnStartRequest().
1172 // In the mean time, we must defer notifications because we are added to
1173 // the imgRequest's proxy list, and we can get extra notifications
1174 // resulting from methods such as RequestDecode(). See bug 579122.
1175 proxy->SetNotificationsDeferred(true);
1177 // Attach the proxy without notifying
1178 request->mValidator->AddProxy(proxy);
1181 return NS_SUCCEEDED(rv);
1183 } else {
1184 // We will rely on Necko to cache this request when it's possible, and to
1185 // tell imgCacheValidator::OnStartRequest whether the request came from its
1186 // cache.
1187 nsCOMPtr<nsIChannel> newChannel;
1188 bool forcePrincipalCheck;
1189 rv = NewImageChannel(getter_AddRefs(newChannel),
1190 &forcePrincipalCheck,
1191 aURI,
1192 aInitialDocumentURI,
1193 aReferrerURI,
1194 aLoadGroup,
1195 mAcceptHeader,
1196 aLoadFlags,
1197 aPolicy,
1198 aLoadingPrincipal);
1199 if (NS_FAILED(rv)) {
1200 return false;
1203 nsRefPtr<imgRequestProxy> req;
1204 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1205 aLoadFlags, getter_AddRefs(req));
1206 if (NS_FAILED(rv)) {
1207 return false;
1210 // Make sure that OnStatus/OnProgress calls have the right request set...
1211 nsRefPtr<nsProgressNotificationProxy> progressproxy =
1212 new nsProgressNotificationProxy(newChannel, req);
1213 if (!progressproxy)
1214 return false;
1216 nsRefPtr<imgCacheValidator> hvc =
1217 new imgCacheValidator(progressproxy, this, request, aCX, forcePrincipalCheck);
1219 nsCOMPtr<nsIStreamListener> listener = hvc.get();
1221 // We must set the notification callbacks before setting up the
1222 // CORS listener, because that's also interested inthe
1223 // notification callbacks.
1224 newChannel->SetNotificationCallbacks(hvc);
1226 if (aCORSMode != imgIRequest::CORS_NONE) {
1227 bool withCredentials = aCORSMode == imgIRequest::CORS_USE_CREDENTIALS;
1228 nsRefPtr<nsCORSListenerProxy> corsproxy =
1229 new nsCORSListenerProxy(hvc, aLoadingPrincipal, withCredentials);
1230 rv = corsproxy->Init(newChannel);
1231 if (NS_FAILED(rv)) {
1232 return false;
1235 listener = corsproxy;
1238 request->mValidator = hvc;
1240 imgRequestProxy* proxy = static_cast<imgRequestProxy*>
1241 (static_cast<imgIRequest*>(req.get()));
1243 // We will send notifications from imgCacheValidator::OnStartRequest().
1244 // In the mean time, we must defer notifications because we are added to
1245 // the imgRequest's proxy list, and we can get extra notifications
1246 // resulting from methods such as RequestDecode(). See bug 579122.
1247 proxy->SetNotificationsDeferred(true);
1249 // Add the proxy without notifying
1250 hvc->AddProxy(proxy);
1252 rv = newChannel->AsyncOpen(listener, nullptr);
1253 if (NS_SUCCEEDED(rv))
1254 NS_ADDREF(*aProxyRequest = req.get());
1256 return NS_SUCCEEDED(rv);
1260 bool imgLoader::ValidateEntry(imgCacheEntry *aEntry,
1261 nsIURI *aURI,
1262 nsIURI *aInitialDocumentURI,
1263 nsIURI *aReferrerURI,
1264 nsILoadGroup *aLoadGroup,
1265 imgINotificationObserver *aObserver,
1266 nsISupports *aCX,
1267 nsLoadFlags aLoadFlags,
1268 bool aCanMakeNewChannel,
1269 imgRequestProxy **aProxyRequest,
1270 nsIChannelPolicy *aPolicy,
1271 nsIPrincipal* aLoadingPrincipal,
1272 int32_t aCORSMode)
1274 LOG_SCOPE(GetImgLog(), "imgLoader::ValidateEntry");
1276 bool hasExpired;
1277 uint32_t expirationTime = aEntry->GetExpiryTime();
1278 if (expirationTime <= SecondsFromPRTime(PR_Now())) {
1279 hasExpired = true;
1280 } else {
1281 hasExpired = false;
1284 nsresult rv;
1286 // Special treatment for file URLs - aEntry has expired if file has changed
1287 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
1288 if (fileUrl) {
1289 uint32_t lastModTime = aEntry->GetTouchedTime();
1291 nsCOMPtr<nsIFile> theFile;
1292 rv = fileUrl->GetFile(getter_AddRefs(theFile));
1293 if (NS_SUCCEEDED(rv)) {
1294 PRTime fileLastMod;
1295 rv = theFile->GetLastModifiedTime(&fileLastMod);
1296 if (NS_SUCCEEDED(rv)) {
1297 // nsIFile uses millisec, NSPR usec
1298 fileLastMod *= 1000;
1299 hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
1304 nsRefPtr<imgRequest> request(aEntry->GetRequest());
1306 if (!request)
1307 return false;
1309 if (!ValidateCORSAndPrincipal(request, aEntry->ForcePrincipalCheck(),
1310 aCORSMode, aLoadingPrincipal))
1311 return false;
1313 // Never validate data URIs.
1314 nsAutoCString scheme;
1315 aURI->GetScheme(scheme);
1316 if (scheme.EqualsLiteral("data"))
1317 return true;
1319 bool validateRequest = false;
1321 // If the request's loadId is the same as the aCX, then it is ok to use
1322 // this one because it has already been validated for this context.
1324 // XXX: nullptr seems to be a 'special' key value that indicates that NO
1325 // validation is required.
1327 void *key = (void *)aCX;
1328 if (request->mLoadId != key) {
1329 // If we would need to revalidate this entry, but we're being told to
1330 // bypass the cache, we don't allow this entry to be used.
1331 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)
1332 return false;
1334 // Determine whether the cache aEntry must be revalidated...
1335 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
1337 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1338 ("imgLoader::ValidateEntry validating cache entry. "
1339 "validateRequest = %d", validateRequest));
1341 #if defined(PR_LOGGING)
1342 else if (!key) {
1343 nsAutoCString spec;
1344 aURI->GetSpec(spec);
1346 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1347 ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
1348 "because of NULL LoadID", spec.get()));
1350 #endif
1352 // We can't use a cached request if it comes from a different
1353 // application cache than this load is expecting.
1354 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
1355 nsCOMPtr<nsIApplicationCache> requestAppCache;
1356 nsCOMPtr<nsIApplicationCache> groupAppCache;
1357 if ((appCacheContainer = do_GetInterface(request->mRequest)))
1358 appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
1359 if ((appCacheContainer = do_QueryInterface(aLoadGroup)))
1360 appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
1362 if (requestAppCache != groupAppCache) {
1363 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1364 ("imgLoader::ValidateEntry - Unable to use cached imgRequest "
1365 "[request=%p] because of mismatched application caches\n",
1366 address_of(request)));
1367 return false;
1370 if (validateRequest && aCanMakeNewChannel) {
1371 LOG_SCOPE(GetImgLog(), "imgLoader::ValidateRequest |cache hit| must validate");
1373 return ValidateRequestWithNewChannel(request, aURI, aInitialDocumentURI,
1374 aReferrerURI, aLoadGroup, aObserver,
1375 aCX, aLoadFlags, aProxyRequest, aPolicy,
1376 aLoadingPrincipal, aCORSMode);
1379 return !validateRequest;
1383 bool imgLoader::RemoveFromCache(nsIURI *aKey)
1385 if (!aKey) return false;
1387 imgCacheTable &cache = GetCache(aKey);
1388 imgCacheQueue &queue = GetCacheQueue(aKey);
1390 nsAutoCString spec;
1391 aKey->GetSpec(spec);
1393 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "uri", spec.get());
1395 nsRefPtr<imgCacheEntry> entry;
1396 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
1397 cache.Remove(spec);
1399 NS_ABORT_IF_FALSE(!entry->Evicted(), "Evicting an already-evicted cache entry!");
1401 // Entries with no proxies are in the tracker.
1402 if (entry->HasNoProxies()) {
1403 if (mCacheTracker)
1404 mCacheTracker->RemoveObject(entry);
1405 queue.Remove(entry);
1408 entry->SetEvicted(true);
1410 nsRefPtr<imgRequest> request = entry->GetRequest();
1411 request->SetIsInCache(false);
1413 return true;
1415 else
1416 return false;
1419 bool imgLoader::RemoveFromCache(imgCacheEntry *entry)
1421 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache entry");
1423 nsRefPtr<imgRequest> request = entry->GetRequest();
1424 if (request) {
1425 nsCOMPtr<nsIURI> key;
1426 if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(key))) && key) {
1427 imgCacheTable &cache = GetCache(key);
1428 imgCacheQueue &queue = GetCacheQueue(key);
1429 nsAutoCString spec;
1430 key->GetSpec(spec);
1432 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "entry's uri", spec.get());
1434 cache.Remove(spec);
1436 if (entry->HasNoProxies()) {
1437 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache removing from tracker");
1438 if (mCacheTracker)
1439 mCacheTracker->RemoveObject(entry);
1440 queue.Remove(entry);
1443 entry->SetEvicted(true);
1444 request->SetIsInCache(false);
1446 return true;
1450 return false;
1453 static PLDHashOperator EnumEvictEntries(const nsACString&,
1454 nsRefPtr<imgCacheEntry> &aData,
1455 void *data)
1457 nsTArray<nsRefPtr<imgCacheEntry> > *entries =
1458 reinterpret_cast<nsTArray<nsRefPtr<imgCacheEntry> > *>(data);
1460 entries->AppendElement(aData);
1462 return PL_DHASH_NEXT;
1465 nsresult imgLoader::EvictEntries(imgCacheTable &aCacheToClear)
1467 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries table");
1469 // We have to make a temporary, since RemoveFromCache removes the element
1470 // from the queue, invalidating iterators.
1471 nsTArray<nsRefPtr<imgCacheEntry> > entries;
1472 aCacheToClear.Enumerate(EnumEvictEntries, &entries);
1474 for (uint32_t i = 0; i < entries.Length(); ++i)
1475 if (!RemoveFromCache(entries[i]))
1476 return NS_ERROR_FAILURE;
1478 return NS_OK;
1481 nsresult imgLoader::EvictEntries(imgCacheQueue &aQueueToClear)
1483 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries queue");
1485 // We have to make a temporary, since RemoveFromCache removes the element
1486 // from the queue, invalidating iterators.
1487 nsTArray<nsRefPtr<imgCacheEntry> > entries(aQueueToClear.GetNumElements());
1488 for (imgCacheQueue::const_iterator i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i)
1489 entries.AppendElement(*i);
1491 for (uint32_t i = 0; i < entries.Length(); ++i)
1492 if (!RemoveFromCache(entries[i]))
1493 return NS_ERROR_FAILURE;
1495 return NS_OK;
1498 #define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \
1499 nsIRequest::LOAD_FROM_CACHE)
1501 #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \
1502 nsIRequest::VALIDATE_NEVER | \
1503 nsIRequest::VALIDATE_ONCE_PER_SESSION)
1505 NS_IMETHODIMP imgLoader::LoadImageXPCOM(nsIURI *aURI,
1506 nsIURI *aInitialDocumentURI,
1507 nsIURI *aReferrerURI,
1508 nsIPrincipal* aLoadingPrincipal,
1509 nsILoadGroup *aLoadGroup,
1510 imgINotificationObserver *aObserver,
1511 nsISupports *aCX,
1512 nsLoadFlags aLoadFlags,
1513 nsISupports *aCacheKey,
1514 nsIChannelPolicy *aPolicy,
1515 imgIRequest **_retval)
1517 imgRequestProxy *proxy;
1518 nsresult result = LoadImage(aURI,
1519 aInitialDocumentURI,
1520 aReferrerURI,
1521 aLoadingPrincipal,
1522 aLoadGroup,
1523 aObserver,
1524 aCX,
1525 aLoadFlags,
1526 aCacheKey,
1527 aPolicy,
1528 &proxy);
1529 *_retval = proxy;
1530 return result;
1535 /* imgIRequest loadImage(in nsIURI aURI, in nsIURI aInitialDocumentURL, in nsIURI aReferrerURI, in nsIPrincipal aLoadingPrincipal, in nsILoadGroup aLoadGroup, in imgINotificationObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in nsIChannelPolicy channelPolicy); */
1537 nsresult imgLoader::LoadImage(nsIURI *aURI,
1538 nsIURI *aInitialDocumentURI,
1539 nsIURI *aReferrerURI,
1540 nsIPrincipal* aLoadingPrincipal,
1541 nsILoadGroup *aLoadGroup,
1542 imgINotificationObserver *aObserver,
1543 nsISupports *aCX,
1544 nsLoadFlags aLoadFlags,
1545 nsISupports *aCacheKey,
1546 nsIChannelPolicy *aPolicy,
1547 imgRequestProxy **_retval)
1549 VerifyCacheSizes();
1551 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
1553 if (!aURI)
1554 return NS_ERROR_NULL_POINTER;
1556 nsAutoCString spec;
1557 aURI->GetSpec(spec);
1558 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage", "aURI", spec.get());
1560 *_retval = nullptr;
1562 nsRefPtr<imgRequest> request;
1564 nsresult rv;
1565 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
1567 #ifdef DEBUG
1568 bool isPrivate = false;
1570 if (aLoadGroup) {
1571 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1572 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1573 if (callbacks) {
1574 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
1575 isPrivate = loadContext && loadContext->UsePrivateBrowsing();
1578 MOZ_ASSERT(isPrivate == mRespectPrivacy);
1579 #endif
1581 // Get the default load flags from the loadgroup (if possible)...
1582 if (aLoadGroup) {
1583 aLoadGroup->GetLoadFlags(&requestFlags);
1586 // Merge the default load flags with those passed in via aLoadFlags.
1587 // Currently, *only* the caching, validation and background load flags
1588 // are merged...
1590 // The flags in aLoadFlags take precedence over the default flags!
1592 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
1593 // Override the default caching flags...
1594 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
1595 (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
1597 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
1598 // Override the default validation flags...
1599 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
1600 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
1602 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
1603 // Propagate background loading...
1604 requestFlags |= nsIRequest::LOAD_BACKGROUND;
1607 int32_t corsmode = imgIRequest::CORS_NONE;
1608 if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) {
1609 corsmode = imgIRequest::CORS_ANONYMOUS;
1610 } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) {
1611 corsmode = imgIRequest::CORS_USE_CREDENTIALS;
1614 nsRefPtr<imgCacheEntry> entry;
1616 // Look in the cache for our URI, and then validate it.
1617 // XXX For now ignore aCacheKey. We will need it in the future
1618 // for correctly dealing with image load requests that are a result
1619 // of post data.
1620 imgCacheTable &cache = GetCache(aURI);
1622 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
1623 if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI,
1624 aLoadGroup, aObserver, aCX, requestFlags, true,
1625 _retval, aPolicy, aLoadingPrincipal, corsmode)) {
1626 request = entry->GetRequest();
1628 // If this entry has no proxies, its request has no reference to the entry.
1629 if (entry->HasNoProxies()) {
1630 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage() adding proxyless entry", "uri", spec.get());
1631 NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!");
1632 request->SetCacheEntry(entry);
1634 if (mCacheTracker)
1635 mCacheTracker->MarkUsed(entry);
1638 entry->Touch();
1640 #ifdef DEBUG_joe
1641 printf("CACHEGET: %d %s %d\n", time(NULL), spec.get(), entry->SizeOfData());
1642 #endif
1644 else {
1645 // We can't use this entry. We'll try to load it off the network, and if
1646 // successful, overwrite the old entry in the cache with a new one.
1647 entry = nullptr;
1651 // Keep the channel in this scope, so we can adjust its notificationCallbacks
1652 // later when we create the proxy.
1653 nsCOMPtr<nsIChannel> newChannel;
1654 // If we didn't get a cache hit, we need to load from the network.
1655 if (!request) {
1656 LOG_SCOPE(GetImgLog(), "imgLoader::LoadImage |cache miss|");
1658 bool forcePrincipalCheck;
1659 rv = NewImageChannel(getter_AddRefs(newChannel),
1660 &forcePrincipalCheck,
1661 aURI,
1662 aInitialDocumentURI,
1663 aReferrerURI,
1664 aLoadGroup,
1665 mAcceptHeader,
1666 requestFlags,
1667 aPolicy,
1668 aLoadingPrincipal);
1669 if (NS_FAILED(rv))
1670 return NS_ERROR_FAILURE;
1672 MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy);
1674 NewRequestAndEntry(forcePrincipalCheck, this, getter_AddRefs(request), getter_AddRefs(entry));
1676 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1677 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request.get()));
1679 // Create a loadgroup for this new channel. This way if the channel
1680 // is redirected, we'll have a way to cancel the resulting channel.
1681 // Inform the new loadgroup of the old one so they can still be correlated
1682 // together as a logical group.
1683 nsCOMPtr<nsILoadGroup> loadGroup =
1684 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1685 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
1686 if (childLoadGroup)
1687 childLoadGroup->SetParentLoadGroup(aLoadGroup);
1688 newChannel->SetLoadGroup(loadGroup);
1690 request->Init(aURI, aURI, loadGroup, newChannel, entry, aCX,
1691 aLoadingPrincipal, corsmode);
1693 // Pass the inner window ID of the loading document, if possible.
1694 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
1695 if (doc) {
1696 request->SetInnerWindowID(doc->InnerWindowID());
1699 // create the proxy listener
1700 nsCOMPtr<nsIStreamListener> pl = new ProxyListener(request.get());
1702 // See if we need to insert a CORS proxy between the proxy listener and the
1703 // request.
1704 nsCOMPtr<nsIStreamListener> listener = pl;
1705 if (corsmode != imgIRequest::CORS_NONE) {
1706 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1707 ("[this=%p] imgLoader::LoadImage -- Setting up a CORS load",
1708 this));
1709 bool withCredentials = corsmode == imgIRequest::CORS_USE_CREDENTIALS;
1711 nsRefPtr<nsCORSListenerProxy> corsproxy =
1712 new nsCORSListenerProxy(pl, aLoadingPrincipal, withCredentials);
1713 rv = corsproxy->Init(newChannel);
1714 if (NS_FAILED(rv)) {
1715 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1716 ("[this=%p] imgLoader::LoadImage -- nsCORSListenerProxy "
1717 "creation failed: 0x%x\n", this, rv));
1718 request->CancelAndAbort(rv);
1719 return NS_ERROR_FAILURE;
1722 listener = corsproxy;
1725 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1726 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this));
1728 nsresult openRes = newChannel->AsyncOpen(listener, nullptr);
1730 if (NS_FAILED(openRes)) {
1731 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1732 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n",
1733 this, openRes));
1734 request->CancelAndAbort(openRes);
1735 return openRes;
1738 // Try to add the new request into the cache.
1739 PutIntoCache(aURI, entry);
1740 } else {
1741 LOG_MSG_WITH_PARAM(GetImgLog(),
1742 "imgLoader::LoadImage |cache hit|", "request", request);
1746 // If we didn't get a proxy when validating the cache entry, we need to create one.
1747 if (!*_retval) {
1748 // ValidateEntry() has three return values: "Is valid," "might be valid --
1749 // validating over network", and "not valid." If we don't have a _retval,
1750 // we know ValidateEntry is not validating over the network, so it's safe
1751 // to SetLoadId here because we know this request is valid for this context.
1753 // Note, however, that this doesn't guarantee the behaviour we want (one
1754 // URL maps to the same image on a page) if we load the same image in a
1755 // different tab (see bug 528003), because its load id will get re-set, and
1756 // that'll cause us to validate over the network.
1757 request->SetLoadId(aCX);
1759 LOG_MSG(GetImgLog(), "imgLoader::LoadImage", "creating proxy request.");
1760 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1761 requestFlags, _retval);
1762 if (NS_FAILED(rv)) {
1763 return rv;
1766 imgRequestProxy *proxy = *_retval;
1768 // Make sure that OnStatus/OnProgress calls have the right request set, if
1769 // we did create a channel here.
1770 if (newChannel) {
1771 nsCOMPtr<nsIInterfaceRequestor> requestor(
1772 new nsProgressNotificationProxy(newChannel, proxy));
1773 if (!requestor)
1774 return NS_ERROR_OUT_OF_MEMORY;
1775 newChannel->SetNotificationCallbacks(requestor);
1778 // Note that it's OK to add here even if the request is done. If it is,
1779 // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
1780 // the proxy will be removed from the loadgroup.
1781 proxy->AddToLoadGroup();
1783 // If we're loading off the network, explicitly don't notify our proxy,
1784 // because necko (or things called from necko, such as imgCacheValidator)
1785 // are going to call our notifications asynchronously, and we can't make it
1786 // further asynchronous because observers might rely on imagelib completing
1787 // its work between the channel's OnStartRequest and OnStopRequest.
1788 if (!newChannel)
1789 proxy->NotifyListener();
1791 return rv;
1794 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
1796 return NS_OK;
1799 /* imgIRequest loadImageWithChannelXPCOM(in nsIChannel channel, in imgINotificationObserver aObserver, in nsISupports cx, out nsIStreamListener); */
1800 NS_IMETHODIMP imgLoader::LoadImageWithChannelXPCOM(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
1802 nsresult result;
1803 imgRequestProxy* proxy;
1804 result = LoadImageWithChannel(channel,
1805 aObserver,
1806 aCX,
1807 listener,
1808 &proxy);
1809 *_retval = proxy;
1810 return result;
1813 nsresult imgLoader::LoadImageWithChannel(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgRequestProxy **_retval)
1815 NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer");
1817 MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
1819 nsRefPtr<imgRequest> request;
1821 nsCOMPtr<nsIURI> uri;
1822 channel->GetURI(getter_AddRefs(uri));
1824 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
1825 channel->GetLoadFlags(&requestFlags);
1827 nsRefPtr<imgCacheEntry> entry;
1829 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1830 RemoveFromCache(uri);
1831 } else {
1832 // Look in the cache for our URI, and then validate it.
1833 // XXX For now ignore aCacheKey. We will need it in the future
1834 // for correctly dealing with image load requests that are a result
1835 // of post data.
1836 imgCacheTable &cache = GetCache(uri);
1837 nsAutoCString spec;
1839 uri->GetSpec(spec);
1841 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
1842 // We don't want to kick off another network load. So we ask
1843 // ValidateEntry to only do validation without creating a new proxy. If
1844 // it says that the entry isn't valid any more, we'll only use the entry
1845 // we're getting if the channel is loading from the cache anyways.
1847 // XXX -- should this be changed? it's pretty much verbatim from the old
1848 // code, but seems nonsensical.
1849 if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver, aCX,
1850 requestFlags, false, nullptr, nullptr, nullptr,
1851 imgIRequest::CORS_NONE)) {
1852 request = entry->GetRequest();
1853 } else {
1854 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(channel));
1855 bool bUseCacheCopy;
1857 if (cacheChan)
1858 cacheChan->IsFromCache(&bUseCacheCopy);
1859 else
1860 bUseCacheCopy = false;
1862 if (!bUseCacheCopy) {
1863 entry = nullptr;
1864 } else {
1865 request = entry->GetRequest();
1869 if (request && entry) {
1870 // If this entry has no proxies, its request has no reference to the entry.
1871 if (entry->HasNoProxies()) {
1872 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri", spec.get());
1873 NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!");
1874 request->SetCacheEntry(entry);
1876 if (mCacheTracker)
1877 mCacheTracker->MarkUsed(entry);
1883 nsCOMPtr<nsILoadGroup> loadGroup;
1884 channel->GetLoadGroup(getter_AddRefs(loadGroup));
1886 // Filter out any load flags not from nsIRequest
1887 requestFlags &= nsIRequest::LOAD_REQUESTMASK;
1889 nsresult rv = NS_OK;
1890 if (request) {
1891 // we have this in our cache already.. cancel the current (document) load
1893 channel->Cancel(NS_ERROR_PARSED_DATA_CACHED); // this should fire an OnStopRequest
1895 *listener = nullptr; // give them back a null nsIStreamListener
1897 rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
1898 requestFlags, _retval);
1899 static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
1900 } else {
1901 // Default to doing a principal check because we don't know who
1902 // started that load and whether their principal ended up being
1903 // inherited on the channel.
1904 NewRequestAndEntry(true, this, getter_AddRefs(request), getter_AddRefs(entry));
1906 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
1907 nsCOMPtr<nsIURI> originalURI;
1908 channel->GetOriginalURI(getter_AddRefs(originalURI));
1910 // No principal specified here, because we're not passed one.
1911 request->Init(originalURI, uri, channel, channel, entry,
1912 aCX, nullptr, imgIRequest::CORS_NONE);
1914 ProxyListener *pl = new ProxyListener(static_cast<nsIStreamListener *>(request.get()));
1915 NS_ADDREF(pl);
1917 *listener = static_cast<nsIStreamListener*>(pl);
1918 NS_ADDREF(*listener);
1920 NS_RELEASE(pl);
1922 // Try to add the new request into the cache.
1923 PutIntoCache(originalURI, entry);
1925 rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
1926 requestFlags, _retval);
1928 // Explicitly don't notify our proxy, because we're loading off the
1929 // network, and necko (or things called from necko, such as
1930 // imgCacheValidator) are going to call our notifications asynchronously,
1931 // and we can't make it further asynchronous because observers might rely
1932 // on imagelib completing its work between the channel's OnStartRequest and
1933 // OnStopRequest.
1936 return rv;
1939 bool imgLoader::SupportImageWithMimeType(const char* aMimeType)
1941 nsAutoCString mimeType(aMimeType);
1942 ToLowerCase(mimeType);
1943 return Image::GetDecoderType(mimeType.get()) != Image::eDecoderType_unknown;
1946 NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
1947 const uint8_t* aContents,
1948 uint32_t aLength,
1949 nsACString& aContentType)
1951 return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
1954 /* static */
1955 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType)
1957 /* Is it a GIF? */
1958 if (aLength >= 6 && (!nsCRT::strncmp(aContents, "GIF87a", 6) ||
1959 !nsCRT::strncmp(aContents, "GIF89a", 6)))
1961 aContentType.AssignLiteral(IMAGE_GIF);
1964 /* or a PNG? */
1965 else if (aLength >= 8 && ((unsigned char)aContents[0]==0x89 &&
1966 (unsigned char)aContents[1]==0x50 &&
1967 (unsigned char)aContents[2]==0x4E &&
1968 (unsigned char)aContents[3]==0x47 &&
1969 (unsigned char)aContents[4]==0x0D &&
1970 (unsigned char)aContents[5]==0x0A &&
1971 (unsigned char)aContents[6]==0x1A &&
1972 (unsigned char)aContents[7]==0x0A))
1974 aContentType.AssignLiteral(IMAGE_PNG);
1977 /* maybe a JPEG (JFIF)? */
1978 /* JFIF files start with SOI APP0 but older files can start with SOI DQT
1979 * so we test for SOI followed by any marker, i.e. FF D8 FF
1980 * this will also work for SPIFF JPEG files if they appear in the future.
1982 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
1984 else if (aLength >= 3 &&
1985 ((unsigned char)aContents[0])==0xFF &&
1986 ((unsigned char)aContents[1])==0xD8 &&
1987 ((unsigned char)aContents[2])==0xFF)
1989 aContentType.AssignLiteral(IMAGE_JPEG);
1992 /* or how about ART? */
1993 /* ART begins with JG (4A 47). Major version offset 2.
1994 * Minor version offset 3. Offset 4 must be NULL.
1996 else if (aLength >= 5 &&
1997 ((unsigned char) aContents[0])==0x4a &&
1998 ((unsigned char) aContents[1])==0x47 &&
1999 ((unsigned char) aContents[4])==0x00 )
2001 aContentType.AssignLiteral(IMAGE_ART);
2004 else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) {
2005 aContentType.AssignLiteral(IMAGE_BMP);
2008 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
2009 // CURs begin with 2-byte 0 followed by 2-byte 2.
2010 else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
2011 !memcmp(aContents, "\000\000\002\000", 4))) {
2012 aContentType.AssignLiteral(IMAGE_ICO);
2015 else {
2016 /* none of the above? I give up */
2017 return NS_ERROR_NOT_AVAILABLE;
2020 return NS_OK;
2024 * proxy stream listener class used to handle multipart/x-mixed-replace
2027 #include "nsIRequest.h"
2028 #include "nsIStreamConverterService.h"
2029 #include "nsXPIDLString.h"
2031 NS_IMPL_ISUPPORTS2(ProxyListener, nsIStreamListener, nsIRequestObserver)
2033 ProxyListener::ProxyListener(nsIStreamListener *dest) :
2034 mDestListener(dest)
2036 /* member initializers and constructor code */
2039 ProxyListener::~ProxyListener()
2041 /* destructor code */
2045 /** nsIRequestObserver methods **/
2047 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
2048 NS_IMETHODIMP ProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
2050 if (!mDestListener)
2051 return NS_ERROR_FAILURE;
2053 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2054 if (channel) {
2055 nsAutoCString contentType;
2056 nsresult rv = channel->GetContentType(contentType);
2058 if (!contentType.IsEmpty()) {
2059 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
2060 in the pipeline to handle the content and pass it along to our
2061 original listener.
2063 if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) {
2065 nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv));
2066 if (NS_SUCCEEDED(rv)) {
2067 nsCOMPtr<nsIStreamListener> toListener(mDestListener);
2068 nsCOMPtr<nsIStreamListener> fromListener;
2070 rv = convServ->AsyncConvertData("multipart/x-mixed-replace",
2071 "*/*",
2072 toListener,
2073 nullptr,
2074 getter_AddRefs(fromListener));
2075 if (NS_SUCCEEDED(rv))
2076 mDestListener = fromListener;
2082 return mDestListener->OnStartRequest(aRequest, ctxt);
2085 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
2086 NS_IMETHODIMP ProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
2088 if (!mDestListener)
2089 return NS_ERROR_FAILURE;
2091 return mDestListener->OnStopRequest(aRequest, ctxt, status);
2094 /** nsIStreamListener methods **/
2096 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
2097 NS_IMETHODIMP
2098 ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
2099 nsIInputStream *inStr, uint64_t sourceOffset,
2100 uint32_t count)
2102 if (!mDestListener)
2103 return NS_ERROR_FAILURE;
2105 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
2109 * http validate class. check a channel for a 304
2112 NS_IMPL_ISUPPORTS5(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
2113 nsIChannelEventSink, nsIInterfaceRequestor,
2114 nsIAsyncVerifyRedirectCallback)
2116 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
2117 imgLoader* loader, imgRequest *request,
2118 void *aContext, bool forcePrincipalCheckForCacheEntry)
2119 : mProgressProxy(progress),
2120 mRequest(request),
2121 mContext(aContext),
2122 mImgLoader(loader)
2124 NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest),
2125 getter_AddRefs(mNewEntry));
2128 imgCacheValidator::~imgCacheValidator()
2130 if (mRequest) {
2131 mRequest->mValidator = nullptr;
2135 void imgCacheValidator::AddProxy(imgRequestProxy *aProxy)
2137 // aProxy needs to be in the loadgroup since we're validating from
2138 // the network.
2139 aProxy->AddToLoadGroup();
2141 mProxies.AppendObject(aProxy);
2144 /** nsIRequestObserver methods **/
2146 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
2147 NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
2149 // If this request is coming from cache and has the same URI as our
2150 // imgRequest, the request all our proxies are pointing at is valid, and all
2151 // we have to do is tell them to notify their listeners.
2152 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
2153 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2154 if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) {
2155 bool isFromCache = false;
2156 cacheChan->IsFromCache(&isFromCache);
2158 nsCOMPtr<nsIURI> channelURI;
2159 bool sameURI = false;
2160 channel->GetURI(getter_AddRefs(channelURI));
2161 if (channelURI)
2162 channelURI->Equals(mRequest->mCurrentURI, &sameURI);
2164 if (isFromCache && sameURI) {
2165 uint32_t count = mProxies.Count();
2166 for (int32_t i = count-1; i>=0; i--) {
2167 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
2169 // Proxies waiting on cache validation should be deferring notifications.
2170 // Undefer them.
2171 NS_ABORT_IF_FALSE(proxy->NotificationsDeferred(),
2172 "Proxies waiting on cache validation should be "
2173 "deferring notifications!");
2174 proxy->SetNotificationsDeferred(false);
2176 // Notify synchronously, because we're already in OnStartRequest, an
2177 // asynchronously-called function.
2178 proxy->SyncNotifyListener();
2181 // We don't need to load this any more.
2182 aRequest->Cancel(NS_BINDING_ABORTED);
2184 mRequest->SetLoadId(mContext);
2185 mRequest->mValidator = nullptr;
2187 mRequest = nullptr;
2189 mNewRequest = nullptr;
2190 mNewEntry = nullptr;
2192 return NS_OK;
2196 // We can't load out of cache. We have to create a whole new request for the
2197 // data that's coming in off the channel.
2198 nsCOMPtr<nsIURI> uri;
2199 mRequest->GetURI(getter_AddRefs(uri));
2201 #if defined(PR_LOGGING)
2202 nsAutoCString spec;
2203 uri->GetSpec(spec);
2204 LOG_MSG_WITH_PARAM(GetImgLog(), "imgCacheValidator::OnStartRequest creating new request", "uri", spec.get());
2205 #endif
2207 int32_t corsmode = mRequest->GetCORSMode();
2208 nsCOMPtr<nsIPrincipal> loadingPrincipal = mRequest->GetLoadingPrincipal();
2210 // Doom the old request's cache entry
2211 mRequest->RemoveFromCache();
2213 mRequest->mValidator = nullptr;
2214 mRequest = nullptr;
2216 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2217 nsCOMPtr<nsIURI> originalURI;
2218 channel->GetOriginalURI(getter_AddRefs(originalURI));
2219 mNewRequest->Init(originalURI, uri, aRequest, channel, mNewEntry,
2220 mContext, loadingPrincipal,
2221 corsmode);
2223 mDestListener = new ProxyListener(mNewRequest);
2225 // Try to add the new request into the cache. Note that the entry must be in
2226 // the cache before the proxies' ownership changes, because adding a proxy
2227 // changes the caching behaviour for imgRequests.
2228 mImgLoader->PutIntoCache(originalURI, mNewEntry);
2230 uint32_t count = mProxies.Count();
2231 for (int32_t i = count-1; i>=0; i--) {
2232 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
2233 proxy->ChangeOwner(mNewRequest);
2235 // Notify synchronously, because we're already in OnStartRequest, an
2236 // asynchronously-called function.
2237 proxy->SetNotificationsDeferred(false);
2238 proxy->SyncNotifyListener();
2241 mNewRequest = nullptr;
2242 mNewEntry = nullptr;
2244 return mDestListener->OnStartRequest(aRequest, ctxt);
2247 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
2248 NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
2250 if (!mDestListener)
2251 return NS_OK;
2253 return mDestListener->OnStopRequest(aRequest, ctxt, status);
2256 /** nsIStreamListener methods **/
2259 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
2260 NS_IMETHODIMP
2261 imgCacheValidator::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
2262 nsIInputStream *inStr,
2263 uint64_t sourceOffset, uint32_t count)
2265 if (!mDestListener) {
2266 // XXX see bug 113959
2267 uint32_t _retval;
2268 inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
2269 return NS_OK;
2272 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
2275 /** nsIInterfaceRequestor methods **/
2277 NS_IMETHODIMP imgCacheValidator::GetInterface(const nsIID & aIID, void **aResult)
2279 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)))
2280 return QueryInterface(aIID, aResult);
2282 return mProgressProxy->GetInterface(aIID, aResult);
2285 // These functions are materially the same as the same functions in imgRequest.
2286 // We duplicate them because we're verifying whether cache loads are necessary,
2287 // not unconditionally loading.
2289 /** nsIChannelEventSink methods **/
2290 NS_IMETHODIMP imgCacheValidator::AsyncOnChannelRedirect(nsIChannel *oldChannel,
2291 nsIChannel *newChannel, uint32_t flags,
2292 nsIAsyncVerifyRedirectCallback *callback)
2294 // Note all cache information we get from the old channel.
2295 mNewRequest->SetCacheValidation(mNewEntry, oldChannel);
2297 // Prepare for callback
2298 mRedirectCallback = callback;
2299 mRedirectChannel = newChannel;
2301 return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
2304 NS_IMETHODIMP imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult)
2306 // If we've already been told to abort, just do so.
2307 if (NS_FAILED(aResult)) {
2308 mRedirectCallback->OnRedirectVerifyCallback(aResult);
2309 mRedirectCallback = nullptr;
2310 mRedirectChannel = nullptr;
2311 return NS_OK;
2314 // make sure we have a protocol that returns data rather than opens
2315 // an external application, e.g. mailto:
2316 nsCOMPtr<nsIURI> uri;
2317 mRedirectChannel->GetURI(getter_AddRefs(uri));
2318 bool doesNotReturnData = false;
2319 NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
2320 &doesNotReturnData);
2322 nsresult result = NS_OK;
2324 if (doesNotReturnData) {
2325 result = NS_ERROR_ABORT;
2328 mRedirectCallback->OnRedirectVerifyCallback(result);
2329 mRedirectCallback = nullptr;
2330 mRedirectChannel = nullptr;
2331 return NS_OK;