Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Attributes.h"
8 #include "mozilla/Preferences.h"
9 #include "mozilla/ClearOnShutdown.h"
11 #include "ImageLogging.h"
12 #include "nsPrintfCString.h"
13 #include "imgLoader.h"
14 #include "imgRequestProxy.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 "nsIFileURL.h"
30 #include "nsCRT.h"
31 #include "nsIDocument.h"
32 #include "nsINetworkSeer.h"
33 #include "nsIConsoleService.h"
35 #include "nsIApplicationCache.h"
36 #include "nsIApplicationCacheContainer.h"
38 #include "nsIMemoryReporter.h"
39 #include "Image.h"
40 #include "DiscardTracker.h"
42 // we want to explore making the document own the load group
43 // so we can associate the document URI with the load group.
44 // until this point, we have an evil hack:
45 #include "nsIHttpChannelInternal.h"
46 #include "nsILoadContext.h"
47 #include "nsILoadGroupChild.h"
49 using namespace mozilla;
50 using namespace mozilla::image;
52 MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)
54 class imgMemoryReporter MOZ_FINAL : public nsIMemoryReporter
55 {
56 public:
57 NS_DECL_ISUPPORTS
59 NS_IMETHOD CollectReports(nsIMemoryReporterCallback *callback,
60 nsISupports *closure)
61 {
62 AllSizes chrome;
63 AllSizes content;
65 for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
66 mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryAllSizes, &chrome);
67 mKnownLoaders[i]->mCache.EnumerateRead(EntryAllSizes, &content);
68 }
70 #define REPORT(_path, _kind, _amount, _desc) \
71 do { \
72 nsresult rv; \
73 rv = callback->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
74 _kind, UNITS_BYTES, _amount, \
75 NS_LITERAL_CSTRING(_desc), closure); \
76 NS_ENSURE_SUCCESS(rv, rv); \
77 } while (0)
79 REPORT("explicit/images/chrome/used/raw",
80 KIND_HEAP, chrome.mUsedRaw,
81 "Memory used by in-use chrome images (compressed data).");
83 REPORT("explicit/images/chrome/used/uncompressed-heap",
84 KIND_HEAP, chrome.mUsedUncompressedHeap,
85 "Memory used by in-use chrome images (uncompressed data).");
87 REPORT("explicit/images/chrome/used/uncompressed-nonheap",
88 KIND_NONHEAP, chrome.mUsedUncompressedNonheap,
89 "Memory used by in-use chrome images (uncompressed data).");
91 REPORT("explicit/images/chrome/unused/raw",
92 KIND_HEAP, chrome.mUnusedRaw,
93 "Memory used by not in-use chrome images (compressed data).");
95 REPORT("explicit/images/chrome/unused/uncompressed-heap",
96 KIND_HEAP, chrome.mUnusedUncompressedHeap,
97 "Memory used by not in-use chrome images (uncompressed data).");
99 REPORT("explicit/images/chrome/unused/uncompressed-nonheap",
100 KIND_NONHEAP, chrome.mUnusedUncompressedNonheap,
101 "Memory used by not in-use chrome images (uncompressed data).");
103 REPORT("explicit/images/content/used/raw",
104 KIND_HEAP, content.mUsedRaw,
105 "Memory used by in-use content images (compressed data).");
107 REPORT("explicit/images/content/used/uncompressed-heap",
108 KIND_HEAP, content.mUsedUncompressedHeap,
109 "Memory used by in-use content images (uncompressed data).");
111 REPORT("explicit/images/content/used/uncompressed-nonheap",
112 KIND_NONHEAP, content.mUsedUncompressedNonheap,
113 "Memory used by in-use content images (uncompressed data).");
115 REPORT("explicit/images/content/unused/raw",
116 KIND_HEAP, content.mUnusedRaw,
117 "Memory used by not in-use content images (compressed data).");
119 REPORT("explicit/images/content/unused/uncompressed-heap",
120 KIND_HEAP, content.mUnusedUncompressedHeap,
121 "Memory used by not in-use content images (uncompressed data).");
123 REPORT("explicit/images/content/unused/uncompressed-nonheap",
124 KIND_NONHEAP, content.mUnusedUncompressedNonheap,
125 "Memory used by not in-use content images (uncompressed data).");
127 #undef REPORT
129 return NS_OK;
130 }
132 static int64_t ImagesContentUsedUncompressedDistinguishedAmount()
133 {
134 size_t n = 0;
135 for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); i++) {
136 imgLoader::sMemReporter->mKnownLoaders[i]->mCache.EnumerateRead(EntryUsedUncompressedSize, &n);
137 }
138 return n;
139 }
141 void RegisterLoader(imgLoader* aLoader)
142 {
143 mKnownLoaders.AppendElement(aLoader);
144 }
146 void UnregisterLoader(imgLoader* aLoader)
147 {
148 mKnownLoaders.RemoveElement(aLoader);
149 }
151 private:
152 nsTArray<imgLoader*> mKnownLoaders;
154 struct AllSizes {
155 size_t mUsedRaw;
156 size_t mUsedUncompressedHeap;
157 size_t mUsedUncompressedNonheap;
158 size_t mUnusedRaw;
159 size_t mUnusedUncompressedHeap;
160 size_t mUnusedUncompressedNonheap;
162 AllSizes() {
163 memset(this, 0, sizeof(*this));
164 }
165 };
167 static PLDHashOperator EntryAllSizes(const nsACString&,
168 imgCacheEntry *entry,
169 void *userArg)
170 {
171 nsRefPtr<imgRequest> req = entry->GetRequest();
172 Image *image = static_cast<Image*>(req->mImage.get());
173 if (image) {
174 AllSizes *sizes = static_cast<AllSizes*>(userArg);
175 if (entry->HasNoProxies()) {
176 sizes->mUnusedRaw +=
177 image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf);
178 sizes->mUnusedUncompressedHeap +=
179 image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf);
180 sizes->mUnusedUncompressedNonheap += image->NonHeapSizeOfDecoded();
181 } else {
182 sizes->mUsedRaw +=
183 image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf);
184 sizes->mUsedUncompressedHeap +=
185 image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf);
186 sizes->mUsedUncompressedNonheap += image->NonHeapSizeOfDecoded();
187 }
188 }
190 return PL_DHASH_NEXT;
191 }
193 static PLDHashOperator EntryUsedUncompressedSize(const nsACString&,
194 imgCacheEntry *entry,
195 void *userArg)
196 {
197 if (!entry->HasNoProxies()) {
198 size_t *n = static_cast<size_t*>(userArg);
199 nsRefPtr<imgRequest> req = entry->GetRequest();
200 Image *image = static_cast<Image*>(req->mImage.get());
201 if (image) {
202 // Both this and EntryAllSizes measure images-content-used-uncompressed
203 // memory. This function's measurement is secondary -- the result
204 // doesn't go in the "explicit" tree -- so we use moz_malloc_size_of
205 // instead of ImagesMallocSizeOf to prevent DMD from seeing it reported
206 // twice.
207 *n += image->HeapSizeOfDecodedWithComputedFallback(moz_malloc_size_of);
208 *n += image->NonHeapSizeOfDecoded();
209 }
210 }
212 return PL_DHASH_NEXT;
213 }
214 };
216 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
218 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy,
219 nsIProgressEventSink,
220 nsIChannelEventSink,
221 nsIInterfaceRequestor)
223 NS_IMETHODIMP
224 nsProgressNotificationProxy::OnProgress(nsIRequest* request,
225 nsISupports* ctxt,
226 uint64_t progress,
227 uint64_t progressMax)
228 {
229 nsCOMPtr<nsILoadGroup> loadGroup;
230 request->GetLoadGroup(getter_AddRefs(loadGroup));
232 nsCOMPtr<nsIProgressEventSink> target;
233 NS_QueryNotificationCallbacks(mOriginalCallbacks,
234 loadGroup,
235 NS_GET_IID(nsIProgressEventSink),
236 getter_AddRefs(target));
237 if (!target)
238 return NS_OK;
239 return target->OnProgress(mImageRequest, ctxt, progress, progressMax);
240 }
242 NS_IMETHODIMP
243 nsProgressNotificationProxy::OnStatus(nsIRequest* request,
244 nsISupports* ctxt,
245 nsresult status,
246 const char16_t* statusArg)
247 {
248 nsCOMPtr<nsILoadGroup> loadGroup;
249 request->GetLoadGroup(getter_AddRefs(loadGroup));
251 nsCOMPtr<nsIProgressEventSink> target;
252 NS_QueryNotificationCallbacks(mOriginalCallbacks,
253 loadGroup,
254 NS_GET_IID(nsIProgressEventSink),
255 getter_AddRefs(target));
256 if (!target)
257 return NS_OK;
258 return target->OnStatus(mImageRequest, ctxt, status, statusArg);
259 }
261 NS_IMETHODIMP
262 nsProgressNotificationProxy::AsyncOnChannelRedirect(nsIChannel *oldChannel,
263 nsIChannel *newChannel,
264 uint32_t flags,
265 nsIAsyncVerifyRedirectCallback *cb)
266 {
267 // Tell the original original callbacks about it too
268 nsCOMPtr<nsILoadGroup> loadGroup;
269 newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
270 nsCOMPtr<nsIChannelEventSink> target;
271 NS_QueryNotificationCallbacks(mOriginalCallbacks,
272 loadGroup,
273 NS_GET_IID(nsIChannelEventSink),
274 getter_AddRefs(target));
275 if (!target) {
276 cb->OnRedirectVerifyCallback(NS_OK);
277 return NS_OK;
278 }
280 // Delegate to |target| if set, reusing |cb|
281 return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
282 }
284 NS_IMETHODIMP
285 nsProgressNotificationProxy::GetInterface(const nsIID& iid,
286 void** result)
287 {
288 if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
289 *result = static_cast<nsIProgressEventSink*>(this);
290 NS_ADDREF_THIS();
291 return NS_OK;
292 }
293 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
294 *result = static_cast<nsIChannelEventSink*>(this);
295 NS_ADDREF_THIS();
296 return NS_OK;
297 }
298 if (mOriginalCallbacks)
299 return mOriginalCallbacks->GetInterface(iid, result);
300 return NS_NOINTERFACE;
301 }
303 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader,
304 imgRequest **aRequest, imgCacheEntry **aEntry)
305 {
306 nsRefPtr<imgRequest> request = new imgRequest(aLoader);
307 nsRefPtr<imgCacheEntry> entry = new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
308 request.forget(aRequest);
309 entry.forget(aEntry);
310 }
312 static bool ShouldRevalidateEntry(imgCacheEntry *aEntry,
313 nsLoadFlags aFlags,
314 bool aHasExpired)
315 {
316 bool bValidateEntry = false;
318 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE)
319 return false;
321 if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
322 bValidateEntry = true;
323 }
324 else if (aEntry->GetMustValidate()) {
325 bValidateEntry = true;
326 }
327 //
328 // The cache entry has expired... Determine whether the stale cache
329 // entry can be used without validation...
330 //
331 else if (aHasExpired) {
332 //
333 // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
334 // entries to be used unless they have been explicitly marked to
335 // indicate that revalidation is necessary.
336 //
337 if (aFlags & (nsIRequest::VALIDATE_NEVER |
338 nsIRequest::VALIDATE_ONCE_PER_SESSION))
339 {
340 bValidateEntry = false;
341 }
342 //
343 // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
344 // the entry must be revalidated.
345 //
346 else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
347 bValidateEntry = true;
348 }
349 }
351 return bValidateEntry;
352 }
354 // Returns true if this request is compatible with the given CORS mode on the
355 // given loading principal, and false if the request may not be reused due
356 // to CORS.
357 static bool
358 ValidateCORSAndPrincipal(imgRequest* request, bool forcePrincipalCheck,
359 int32_t corsmode, nsIPrincipal* loadingPrincipal)
360 {
361 // If the entry's CORS mode doesn't match, or the CORS mode matches but the
362 // document principal isn't the same, we can't use this request.
363 if (request->GetCORSMode() != corsmode) {
364 return false;
365 } else if (request->GetCORSMode() != imgIRequest::CORS_NONE ||
366 forcePrincipalCheck) {
367 nsCOMPtr<nsIPrincipal> otherprincipal = request->GetLoadingPrincipal();
369 // If we previously had a principal, but we don't now, we can't use this
370 // request.
371 if (otherprincipal && !loadingPrincipal) {
372 return false;
373 }
375 if (otherprincipal && loadingPrincipal) {
376 bool equals = false;
377 otherprincipal->Equals(loadingPrincipal, &equals);
378 return equals;
379 }
380 }
382 return true;
383 }
385 static nsresult NewImageChannel(nsIChannel **aResult,
386 // If aForcePrincipalCheckForCacheEntry is
387 // true, then we will force a principal check
388 // even when not using CORS before assuming we
389 // have a cache hit on a cache entry that we
390 // create for this channel. This is an out
391 // param that should be set to true if this
392 // channel ends up depending on
393 // aLoadingPrincipal and false otherwise.
394 bool *aForcePrincipalCheckForCacheEntry,
395 nsIURI *aURI,
396 nsIURI *aFirstPartyIsolationURI,
397 nsIURI *aReferringURI,
398 nsILoadGroup *aLoadGroup,
399 const nsCString& aAcceptHeader,
400 nsLoadFlags aLoadFlags,
401 nsIChannelPolicy *aPolicy,
402 nsIPrincipal *aLoadingPrincipal)
403 {
404 nsresult rv;
405 nsCOMPtr<nsIHttpChannel> newHttpChannel;
407 nsCOMPtr<nsIInterfaceRequestor> callbacks;
409 if (aLoadGroup) {
410 // Get the notification callbacks from the load group for the new channel.
411 //
412 // XXX: This is not exactly correct, because the network request could be
413 // referenced by multiple windows... However, the new channel needs
414 // something. So, using the 'first' notification callbacks is better
415 // than nothing...
416 //
417 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
418 }
420 // Pass in a nullptr loadgroup because this is the underlying network
421 // request. This request may be referenced by several proxy image requests
422 // (possibly in different documents).
423 // If all of the proxy requests are canceled then this request should be
424 // canceled too.
425 //
426 rv = NS_NewChannel(aResult,
427 aURI, // URI
428 nullptr, // Cached IOService
429 nullptr, // LoadGroup
430 callbacks, // Notification Callbacks
431 aLoadFlags,
432 aPolicy);
433 if (NS_FAILED(rv))
434 return rv;
436 *aForcePrincipalCheckForCacheEntry = false;
438 // Initialize HTTP-specific attributes
439 newHttpChannel = do_QueryInterface(*aResult);
440 if (newHttpChannel) {
441 newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
442 aAcceptHeader,
443 false);
445 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(newHttpChannel);
446 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
447 httpChannelInternal->SetDocumentURI(aFirstPartyIsolationURI);
448 newHttpChannel->SetReferrer(aReferringURI);
449 }
451 // Image channels are loaded by default with reduced priority.
452 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
453 if (p) {
454 uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
456 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND)
457 ++priority; // further reduce priority for background loads
459 p->AdjustPriority(priority);
460 }
462 bool setOwner = nsContentUtils::SetUpChannelOwner(aLoadingPrincipal,
463 *aResult, aURI, false);
464 *aForcePrincipalCheckForCacheEntry = setOwner;
466 // Create a new loadgroup for this new channel, using the old group as
467 // the parent. The indirection keeps the channel insulated from cancels,
468 // but does allow a way for this revalidation to be associated with at
469 // least one base load group for scheduling/caching purposes.
471 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
472 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
473 if (childLoadGroup) {
474 childLoadGroup->SetParentLoadGroup(aLoadGroup);
475 }
476 (*aResult)->SetLoadGroup(loadGroup);
478 return NS_OK;
479 }
481 static uint32_t SecondsFromPRTime(PRTime prTime)
482 {
483 return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
484 }
486 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest *request, bool forcePrincipalCheck)
487 : mLoader(loader),
488 mRequest(request),
489 mDataSize(0),
490 mTouchedTime(SecondsFromPRTime(PR_Now())),
491 mExpiryTime(0),
492 mMustValidate(false),
493 // We start off as evicted so we don't try to update the cache. PutIntoCache
494 // will set this to false.
495 mEvicted(true),
496 mHasNoProxies(true),
497 mForcePrincipalCheck(forcePrincipalCheck)
498 {}
500 imgCacheEntry::~imgCacheEntry()
501 {
502 LOG_FUNC(GetImgLog(), "imgCacheEntry::~imgCacheEntry()");
503 }
505 void imgCacheEntry::Touch(bool updateTime /* = true */)
506 {
507 LOG_SCOPE(GetImgLog(), "imgCacheEntry::Touch");
509 if (updateTime)
510 mTouchedTime = SecondsFromPRTime(PR_Now());
512 UpdateCache();
513 }
515 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */)
516 {
517 // Don't update the cache if we've been removed from it or it doesn't care
518 // about our size or usage.
519 if (!Evicted() && HasNoProxies()) {
520 nsRefPtr<ImageURL> uri;
521 mRequest->GetURI(getter_AddRefs(uri));
522 mLoader->CacheEntriesChanged(uri, diff);
523 }
524 }
526 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies)
527 {
528 #if defined(PR_LOGGING)
529 nsRefPtr<ImageURL> uri;
530 mRequest->GetURI(getter_AddRefs(uri));
531 nsAutoCString spec;
532 if (uri)
533 uri->GetSpec(spec);
534 if (hasNoProxies)
535 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies true", "uri", spec.get());
536 else
537 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies false", "uri", spec.get());
538 #endif
540 mHasNoProxies = hasNoProxies;
541 }
543 imgCacheQueue::imgCacheQueue()
544 : mDirty(false),
545 mSize(0)
546 {}
548 void imgCacheQueue::UpdateSize(int32_t diff)
549 {
550 mSize += diff;
551 }
553 uint32_t imgCacheQueue::GetSize() const
554 {
555 return mSize;
556 }
558 #include <algorithm>
559 using namespace std;
561 void imgCacheQueue::Remove(imgCacheEntry *entry)
562 {
563 queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry);
564 if (it != mQueue.end()) {
565 mSize -= (*it)->GetDataSize();
566 mQueue.erase(it);
567 MarkDirty();
568 }
569 }
571 void imgCacheQueue::Push(imgCacheEntry *entry)
572 {
573 mSize += entry->GetDataSize();
575 nsRefPtr<imgCacheEntry> refptr(entry);
576 mQueue.push_back(refptr);
577 MarkDirty();
578 }
580 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop()
581 {
582 if (mQueue.empty())
583 return nullptr;
584 if (IsDirty())
585 Refresh();
587 nsRefPtr<imgCacheEntry> entry = mQueue[0];
588 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
589 mQueue.pop_back();
591 mSize -= entry->GetDataSize();
592 return entry.forget();
593 }
595 void imgCacheQueue::Refresh()
596 {
597 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
598 mDirty = false;
599 }
601 void imgCacheQueue::MarkDirty()
602 {
603 mDirty = true;
604 }
606 bool imgCacheQueue::IsDirty()
607 {
608 return mDirty;
609 }
611 uint32_t imgCacheQueue::GetNumElements() const
612 {
613 return mQueue.size();
614 }
616 imgCacheQueue::iterator imgCacheQueue::begin()
617 {
618 return mQueue.begin();
619 }
620 imgCacheQueue::const_iterator imgCacheQueue::begin() const
621 {
622 return mQueue.begin();
623 }
625 imgCacheQueue::iterator imgCacheQueue::end()
626 {
627 return mQueue.end();
628 }
629 imgCacheQueue::const_iterator imgCacheQueue::end() const
630 {
631 return mQueue.end();
632 }
634 nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
635 imgINotificationObserver *aObserver,
636 nsLoadFlags aLoadFlags, imgRequestProxy **_retval)
637 {
638 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest);
640 /* XXX If we move decoding onto separate threads, we should save off the
641 calling thread here and pass it off to |proxyRequest| so that it call
642 proxy calls to |aObserver|.
643 */
645 imgRequestProxy *proxyRequest = new imgRequestProxy();
646 NS_ADDREF(proxyRequest);
648 /* It is important to call |SetLoadFlags()| before calling |Init()| because
649 |Init()| adds the request to the loadgroup.
650 */
651 proxyRequest->SetLoadFlags(aLoadFlags);
653 nsRefPtr<ImageURL> uri;
654 aRequest->GetURI(getter_AddRefs(uri));
656 // init adds itself to imgRequest's list of observers
657 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver);
658 if (NS_FAILED(rv)) {
659 NS_RELEASE(proxyRequest);
660 return rv;
661 }
663 // transfer reference to caller
664 *_retval = proxyRequest;
666 return NS_OK;
667 }
669 class imgCacheObserver MOZ_FINAL : public nsIObserver
670 {
671 public:
672 NS_DECL_ISUPPORTS
673 NS_DECL_NSIOBSERVER
674 };
676 NS_IMPL_ISUPPORTS(imgCacheObserver, nsIObserver)
678 NS_IMETHODIMP
679 imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData)
680 {
681 if (strcmp(aTopic, "memory-pressure") == 0) {
682 DiscardTracker::DiscardAll();
683 }
684 return NS_OK;
685 }
687 class imgCacheExpirationTracker MOZ_FINAL
688 : public nsExpirationTracker<imgCacheEntry, 3>
689 {
690 enum { TIMEOUT_SECONDS = 10 };
691 public:
692 imgCacheExpirationTracker();
694 protected:
695 void NotifyExpired(imgCacheEntry *entry);
696 };
698 imgCacheExpirationTracker::imgCacheExpirationTracker()
699 : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000)
700 {}
702 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry)
703 {
704 // Hold on to a reference to this entry, because the expiration tracker
705 // mechanism doesn't.
706 nsRefPtr<imgCacheEntry> kungFuDeathGrip(entry);
708 #if defined(PR_LOGGING)
709 nsRefPtr<imgRequest> req(entry->GetRequest());
710 if (req) {
711 nsRefPtr<ImageURL> uri;
712 req->GetURI(getter_AddRefs(uri));
713 nsAutoCString spec;
714 uri->GetSpec(spec);
715 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheExpirationTracker::NotifyExpired", "entry", spec.get());
716 }
717 #endif
719 // We can be called multiple times on the same entry. Don't do work multiple
720 // times.
721 if (!entry->Evicted())
722 entry->Loader()->RemoveFromCache(entry);
724 entry->Loader()->VerifyCacheSizes();
725 }
727 imgCacheObserver *gCacheObserver;
729 double imgLoader::sCacheTimeWeight;
730 uint32_t imgLoader::sCacheMaxSize;
731 imgMemoryReporter* imgLoader::sMemReporter;
733 nsCOMPtr<mozIThirdPartyUtil> imgLoader::sThirdPartyUtilSvc;
735 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference, nsIObserver)
737 static imgLoader* gSingleton = nullptr;
738 static imgLoader* gPBSingleton = nullptr;
740 imgLoader*
741 imgLoader::Singleton()
742 {
743 if (!gSingleton)
744 gSingleton = imgLoader::Create();
745 return gSingleton;
746 }
748 imgLoader*
749 imgLoader::PBSingleton()
750 {
751 if (!gPBSingleton) {
752 gPBSingleton = imgLoader::Create();
753 gPBSingleton->RespectPrivacyNotifications();
754 }
755 return gPBSingleton;
756 }
758 imgLoader::imgLoader()
759 : mRespectPrivacy(false)
760 {
761 sMemReporter->AddRef();
762 sMemReporter->RegisterLoader(this);
763 }
765 already_AddRefed<imgLoader>
766 imgLoader::GetInstance()
767 {
768 static StaticRefPtr<imgLoader> singleton;
769 if (!singleton) {
770 singleton = imgLoader::Create();
771 if (!singleton)
772 return nullptr;
773 ClearOnShutdown(&singleton);
774 }
775 nsRefPtr<imgLoader> loader = singleton.get();
776 return loader.forget();
777 }
779 imgLoader::~imgLoader()
780 {
781 ClearChromeImageCache();
782 ClearImageCache();
783 sMemReporter->UnregisterLoader(this);
784 sMemReporter->Release();
785 }
787 void imgLoader::VerifyCacheSizes()
788 {
789 #ifdef DEBUG
790 if (!mCacheTracker)
791 return;
793 uint32_t cachesize = mCache.Count() + mChromeCache.Count();
794 uint32_t queuesize = mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
795 uint32_t trackersize = 0;
796 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker); it.Next(); )
797 trackersize++;
798 NS_ABORT_IF_FALSE(queuesize == trackersize, "Queue and tracker sizes out of sync!");
799 NS_ABORT_IF_FALSE(queuesize <= cachesize, "Queue has more elements than cache!");
800 #endif
801 }
803 imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI)
804 {
805 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
806 bool chrome = false;
807 aURI->SchemeIs("chrome", &chrome);
808 return chrome ? mChromeCache : mCache;
809 }
811 imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI)
812 {
813 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
814 bool chrome = false;
815 aURI->SchemeIs("chrome", &chrome);
816 return chrome ? mChromeCacheQueue : mCacheQueue;
818 }
820 imgLoader::imgCacheTable & imgLoader::GetCache(ImageURL *aURI)
821 {
822 bool chrome = false;
823 aURI->SchemeIs("chrome", &chrome);
824 return chrome ? mChromeCache : mCache;
825 }
827 imgCacheQueue & imgLoader::GetCacheQueue(ImageURL *aURI)
828 {
829 bool chrome = false;
830 aURI->SchemeIs("chrome", &chrome);
831 return chrome ? mChromeCacheQueue : mCacheQueue;
832 }
834 void imgLoader::GlobalInit()
835 {
836 gCacheObserver = new imgCacheObserver();
837 NS_ADDREF(gCacheObserver);
839 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
840 if (os)
841 os->AddObserver(gCacheObserver, "memory-pressure", false);
843 int32_t timeweight;
844 nsresult rv = Preferences::GetInt("image.cache.timeweight", &timeweight);
845 if (NS_SUCCEEDED(rv))
846 sCacheTimeWeight = timeweight / 1000.0;
847 else
848 sCacheTimeWeight = 0.5;
850 int32_t cachesize;
851 rv = Preferences::GetInt("image.cache.size", &cachesize);
852 if (NS_SUCCEEDED(rv))
853 sCacheMaxSize = cachesize;
854 else
855 sCacheMaxSize = 5 * 1024 * 1024;
857 sMemReporter = new imgMemoryReporter();
858 RegisterStrongMemoryReporter(sMemReporter);
859 RegisterImagesContentUsedUncompressedDistinguishedAmount(imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
861 sThirdPartyUtilSvc = do_GetService(THIRDPARTYUTIL_CONTRACTID);
862 }
864 nsresult imgLoader::InitCache()
865 {
866 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
867 if (!os)
868 return NS_ERROR_FAILURE;
870 os->AddObserver(this, "memory-pressure", false);
871 os->AddObserver(this, "chrome-flush-skin-caches", false);
872 os->AddObserver(this, "chrome-flush-caches", false);
873 os->AddObserver(this, "last-pb-context-exited", false);
874 os->AddObserver(this, "profile-before-change", false);
875 os->AddObserver(this, "xpcom-shutdown", false);
877 mCacheTracker = new imgCacheExpirationTracker();
879 return NS_OK;
880 }
882 nsresult imgLoader::Init()
883 {
884 InitCache();
886 ReadAcceptHeaderPref();
888 Preferences::AddWeakObserver(this, "image.http.accept");
890 return NS_OK;
891 }
893 NS_IMETHODIMP
894 imgLoader::RespectPrivacyNotifications()
895 {
896 mRespectPrivacy = true;
897 return NS_OK;
898 }
900 NS_IMETHODIMP
901 imgLoader::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
902 {
903 // We listen for pref change notifications...
904 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
905 if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "image.http.accept")) {
906 ReadAcceptHeaderPref();
907 }
909 } else if (strcmp(aTopic, "memory-pressure") == 0) {
910 MinimizeCaches();
911 } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
912 strcmp(aTopic, "chrome-flush-caches") == 0) {
913 MinimizeCaches();
914 ClearChromeImageCache();
915 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
916 if (mRespectPrivacy) {
917 ClearImageCache();
918 ClearChromeImageCache();
919 }
920 } else if (strcmp(aTopic, "profile-before-change") == 0 ||
921 strcmp(aTopic, "xpcom-shutdown") == 0) {
922 mCacheTracker = nullptr;
923 }
925 // (Nothing else should bring us here)
926 else {
927 NS_ABORT_IF_FALSE(0, "Invalid topic received");
928 }
930 return NS_OK;
931 }
933 void imgLoader::ReadAcceptHeaderPref()
934 {
935 nsAdoptingCString accept = Preferences::GetCString("image.http.accept");
936 if (accept)
937 mAcceptHeader = accept;
938 else
939 mAcceptHeader = IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5";
940 }
942 /* void clearCache (in boolean chrome); */
943 NS_IMETHODIMP imgLoader::ClearCache(bool chrome)
944 {
945 if (chrome)
946 return ClearChromeImageCache();
947 else
948 return ClearImageCache();
949 }
951 /* void removeEntry(in nsIURI uri); */
952 NS_IMETHODIMP imgLoader::RemoveEntry(nsIURI *uri)
953 {
954 if (RemoveMatchingUrlsFromCache(uri))
955 return NS_OK;
957 return NS_ERROR_NOT_AVAILABLE;
958 }
960 static PLDHashOperator EnumAllEntries(const nsACString&,
961 nsRefPtr<imgCacheEntry> &aData,
962 void *data)
963 {
964 nsTArray<nsRefPtr<imgCacheEntry> > *entries =
965 reinterpret_cast<nsTArray<nsRefPtr<imgCacheEntry> > *>(data);
967 entries->AppendElement(aData);
969 return PL_DHASH_NEXT;
970 }
972 /* imgIRequest findEntry(in nsIURI uri); */
973 NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI *uri, nsIProperties **_retval)
974 {
975 nsRefPtr<imgCacheEntry> entry;
976 imgCacheTable &cache = GetCache(uri);
977 *_retval = nullptr;
979 // We must traverse the whole cache in O(N) looking for the first
980 // matching URI.
981 //
982 // TODO: For now, it's ok to pick at random here. The images should be
983 // identical unless there is a cache-tracking attack. And even if they
984 // are not identical due to attack, this code is only used for save
985 // dialogs at this point, so no differentiating info is leaked to
986 // content.
987 nsTArray<nsRefPtr<imgCacheEntry> > entries;
988 cache.Enumerate(EnumAllEntries, &entries);
990 for (uint32_t i = 0; i < entries.Length(); ++i) {
991 bool isEqual = false;
993 nsRefPtr<imgRequest> request = entries[i]->GetRequest();
994 if (request) {
995 request->mURI->Equals(uri, &isEqual);
996 if (isEqual) {
997 if (mCacheTracker && entries[i]->HasNoProxies()) {
998 mCacheTracker->MarkUsed(entries[i]);
999 }
1000 *_retval = request->Properties();
1001 NS_ADDREF(*_retval);
1002 break;
1003 }
1004 }
1005 }
1006 if (*_retval) {
1007 return NS_OK;
1008 }
1009 return NS_ERROR_NOT_AVAILABLE;
1010 }
1012 void imgLoader::Shutdown()
1013 {
1014 NS_IF_RELEASE(gSingleton);
1015 NS_IF_RELEASE(gPBSingleton);
1016 NS_RELEASE(gCacheObserver);
1017 sThirdPartyUtilSvc = nullptr;
1018 }
1020 nsresult imgLoader::ClearChromeImageCache()
1021 {
1022 return EvictEntries(mChromeCache);
1023 }
1025 nsresult imgLoader::ClearImageCache()
1026 {
1027 return EvictEntries(mCache);
1028 }
1030 void imgLoader::MinimizeCaches()
1031 {
1032 EvictEntries(mCacheQueue);
1033 EvictEntries(mChromeCacheQueue);
1034 }
1036 bool imgLoader::PutIntoCache(nsAutoCString key, imgCacheEntry *entry)
1037 {
1038 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::PutIntoCache", "uri", key.get());
1039 imgCacheTable &cache = GetCache(entry->mRequest->mURI);
1040 imgCacheQueue &queue = GetCacheQueue(entry->mRequest->mURI);
1042 // Check to see if this request already exists in the cache and is being
1043 // loaded on a different thread. If so, don't allow this entry to be added to
1044 // the cache.
1045 nsRefPtr<imgCacheEntry> tmpCacheEntry;
1046 if (cache.Get(key, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1047 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1048 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", nullptr));
1049 nsRefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1051 // If it already exists, and we're putting the same key into the cache, we
1052 // should remove the old version.
1053 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1054 ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element", nullptr));
1056 RemoveFromCache(key, cache, queue);
1057 } else {
1058 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1059 ("[this=%p] imgLoader::PutIntoCache -- Element NOT already in the cache", nullptr));
1060 }
1062 cache.Put(key, entry);
1064 // We can be called to resurrect an evicted entry.
1065 if (entry->Evicted())
1066 entry->SetEvicted(false);
1068 // If we're resurrecting an entry with no proxies, put it back in the
1069 // tracker and queue.
1070 if (entry->HasNoProxies()) {
1071 nsresult addrv = NS_OK;
1073 if (mCacheTracker)
1074 addrv = mCacheTracker->AddObject(entry);
1076 if (NS_SUCCEEDED(addrv)) {
1077 queue.Push(entry);
1078 }
1079 }
1081 nsRefPtr<imgRequest> request = entry->GetRequest();
1082 request->SetIsInCache(true);
1084 return true;
1085 }
1087 bool imgLoader::SetHasNoProxies(ImageURL *imgURI, imgCacheEntry *entry)
1088 {
1089 #if defined(PR_LOGGING)
1090 nsAutoCString spec;
1091 imgURI->GetSpec(spec);
1093 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasNoProxies", "uri", spec.get());
1094 #endif
1096 if (entry->Evicted())
1097 return false;
1099 imgCacheQueue &queue = GetCacheQueue(imgURI);
1101 nsresult addrv = NS_OK;
1103 if (mCacheTracker)
1104 addrv = mCacheTracker->AddObject(entry);
1106 if (NS_SUCCEEDED(addrv)) {
1107 queue.Push(entry);
1108 entry->SetHasNoProxies(true);
1109 }
1111 imgCacheTable &cache = GetCache(imgURI);
1112 CheckCacheLimits(cache, queue);
1114 return true;
1115 }
1117 bool imgLoader::SetHasProxies(nsIURI *firstPartyIsolationURI, ImageURL *imgURI)
1118 {
1119 VerifyCacheSizes();
1121 imgCacheTable &cache = GetCache(imgURI);
1123 nsAutoCString spec;
1124 imgURI->GetSpec(spec);
1126 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasProxies", "uri", spec.get());
1128 nsAutoCString key = GetCacheKey(firstPartyIsolationURI, imgURI, nullptr);
1129 nsRefPtr<imgCacheEntry> entry;
1130 if (cache.Get(key, getter_AddRefs(entry)) && entry && entry->HasNoProxies()) {
1131 imgCacheQueue &queue = GetCacheQueue(imgURI);
1132 queue.Remove(entry);
1134 if (mCacheTracker)
1135 mCacheTracker->RemoveObject(entry);
1137 entry->SetHasNoProxies(false);
1139 return true;
1140 }
1142 return false;
1143 }
1145 void imgLoader::CacheEntriesChanged(ImageURL *uri, int32_t sizediff /* = 0 */)
1146 {
1147 imgCacheQueue &queue = GetCacheQueue(uri);
1148 queue.MarkDirty();
1149 queue.UpdateSize(sizediff);
1150 }
1152 void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue)
1153 {
1154 if (queue.GetNumElements() == 0)
1155 NS_ASSERTION(queue.GetSize() == 0,
1156 "imgLoader::CheckCacheLimits -- incorrect cache size");
1158 // Remove entries from the cache until we're back under our desired size.
1159 while (queue.GetSize() >= sCacheMaxSize) {
1160 // Remove the first entry in the queue.
1161 nsRefPtr<imgCacheEntry> entry(queue.Pop());
1163 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1165 #if defined(PR_LOGGING)
1166 nsRefPtr<imgRequest> req(entry->GetRequest());
1167 if (req) {
1168 nsRefPtr<ImageURL> uri;
1169 req->GetURI(getter_AddRefs(uri));
1170 nsAutoCString spec;
1171 uri->GetSpec(spec);
1172 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::CheckCacheLimits", "entry", spec.get());
1173 }
1174 #endif
1176 if (entry)
1177 RemoveFromCache(entry);
1178 }
1179 }
1181 bool imgLoader::ValidateRequestWithNewChannel(imgRequest *request,
1182 nsIURI *aURI,
1183 nsIURI *aFirstPartyIsolationURI,
1184 nsIURI *aReferrerURI,
1185 nsILoadGroup *aLoadGroup,
1186 imgINotificationObserver *aObserver,
1187 nsISupports *aCX,
1188 nsLoadFlags aLoadFlags,
1189 imgRequestProxy **aProxyRequest,
1190 nsIChannelPolicy *aPolicy,
1191 nsIPrincipal* aLoadingPrincipal,
1192 int32_t aCORSMode)
1193 {
1194 // now we need to insert a new channel request object inbetween the real
1195 // request and the proxy that basically delays loading the image until it
1196 // gets a 304 or figures out that this needs to be a new request
1198 nsresult rv;
1200 // If we're currently in the middle of validating this request, just hand
1201 // back a proxy to it; the required work will be done for us.
1202 if (request->mValidator) {
1203 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1204 aLoadFlags, aProxyRequest);
1205 if (NS_FAILED(rv)) {
1206 return false;
1207 }
1209 if (*aProxyRequest) {
1210 imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1212 // We will send notifications from imgCacheValidator::OnStartRequest().
1213 // In the mean time, we must defer notifications because we are added to
1214 // the imgRequest's proxy list, and we can get extra notifications
1215 // resulting from methods such as RequestDecode(). See bug 579122.
1216 proxy->SetNotificationsDeferred(true);
1218 // Attach the proxy without notifying
1219 request->mValidator->AddProxy(proxy);
1220 }
1222 return NS_SUCCEEDED(rv);
1224 } else {
1225 // We will rely on Necko to cache this request when it's possible, and to
1226 // tell imgCacheValidator::OnStartRequest whether the request came from its
1227 // cache.
1228 nsCOMPtr<nsIChannel> newChannel;
1229 bool forcePrincipalCheck;
1230 rv = NewImageChannel(getter_AddRefs(newChannel),
1231 &forcePrincipalCheck,
1232 aURI,
1233 aFirstPartyIsolationURI,
1234 aReferrerURI,
1235 aLoadGroup,
1236 mAcceptHeader,
1237 aLoadFlags,
1238 aPolicy,
1239 aLoadingPrincipal);
1240 if (NS_FAILED(rv)) {
1241 return false;
1242 }
1244 nsRefPtr<imgRequestProxy> req;
1245 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1246 aLoadFlags, getter_AddRefs(req));
1247 if (NS_FAILED(rv)) {
1248 return false;
1249 }
1251 // Make sure that OnStatus/OnProgress calls have the right request set...
1252 nsRefPtr<nsProgressNotificationProxy> progressproxy =
1253 new nsProgressNotificationProxy(newChannel, req);
1254 if (!progressproxy)
1255 return false;
1257 nsRefPtr<imgCacheValidator> hvc =
1258 new imgCacheValidator(progressproxy, this, request, aCX, forcePrincipalCheck);
1260 // Casting needed here to get past multiple inheritance.
1261 nsCOMPtr<nsIStreamListener> listener =
1262 do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
1263 NS_ENSURE_TRUE(listener, false);
1265 // We must set the notification callbacks before setting up the
1266 // CORS listener, because that's also interested inthe
1267 // notification callbacks.
1268 newChannel->SetNotificationCallbacks(hvc);
1270 if (aCORSMode != imgIRequest::CORS_NONE) {
1271 bool withCredentials = aCORSMode == imgIRequest::CORS_USE_CREDENTIALS;
1272 nsRefPtr<nsCORSListenerProxy> corsproxy =
1273 new nsCORSListenerProxy(listener, aLoadingPrincipal, withCredentials);
1274 rv = corsproxy->Init(newChannel);
1275 if (NS_FAILED(rv)) {
1276 return false;
1277 }
1279 listener = corsproxy;
1280 }
1282 request->mValidator = hvc;
1284 imgRequestProxy* proxy = static_cast<imgRequestProxy*>
1285 (static_cast<imgIRequest*>(req.get()));
1287 // We will send notifications from imgCacheValidator::OnStartRequest().
1288 // In the mean time, we must defer notifications because we are added to
1289 // the imgRequest's proxy list, and we can get extra notifications
1290 // resulting from methods such as RequestDecode(). See bug 579122.
1291 proxy->SetNotificationsDeferred(true);
1293 // Add the proxy without notifying
1294 hvc->AddProxy(proxy);
1296 mozilla::net::SeerLearn(aURI, aFirstPartyIsolationURI,
1297 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
1299 rv = newChannel->AsyncOpen(listener, nullptr);
1300 if (NS_SUCCEEDED(rv))
1301 NS_ADDREF(*aProxyRequest = req.get());
1303 return NS_SUCCEEDED(rv);
1304 }
1305 }
1307 bool imgLoader::ValidateEntry(imgCacheEntry *aEntry,
1308 nsIURI *aURI,
1309 nsIURI *aFirstPartyIsolationURI,
1310 nsIURI *aReferrerURI,
1311 nsILoadGroup *aLoadGroup,
1312 imgINotificationObserver *aObserver,
1313 nsISupports *aCX,
1314 nsLoadFlags aLoadFlags,
1315 bool aCanMakeNewChannel,
1316 imgRequestProxy **aProxyRequest,
1317 nsIChannelPolicy *aPolicy,
1318 nsIPrincipal* aLoadingPrincipal,
1319 int32_t aCORSMode)
1320 {
1321 LOG_SCOPE(GetImgLog(), "imgLoader::ValidateEntry");
1323 bool hasExpired;
1324 uint32_t expirationTime = aEntry->GetExpiryTime();
1325 if (expirationTime <= SecondsFromPRTime(PR_Now())) {
1326 hasExpired = true;
1327 } else {
1328 hasExpired = false;
1329 }
1331 nsresult rv;
1333 // Special treatment for file URLs - aEntry has expired if file has changed
1334 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
1335 if (fileUrl) {
1336 uint32_t lastModTime = aEntry->GetTouchedTime();
1338 nsCOMPtr<nsIFile> theFile;
1339 rv = fileUrl->GetFile(getter_AddRefs(theFile));
1340 if (NS_SUCCEEDED(rv)) {
1341 PRTime fileLastMod;
1342 rv = theFile->GetLastModifiedTime(&fileLastMod);
1343 if (NS_SUCCEEDED(rv)) {
1344 // nsIFile uses millisec, NSPR usec
1345 fileLastMod *= 1000;
1346 hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
1347 }
1348 }
1349 }
1351 nsRefPtr<imgRequest> request(aEntry->GetRequest());
1353 if (!request)
1354 return false;
1356 if (!ValidateCORSAndPrincipal(request, aEntry->ForcePrincipalCheck(),
1357 aCORSMode, aLoadingPrincipal))
1358 return false;
1360 // Never validate data URIs.
1361 nsAutoCString scheme;
1362 aURI->GetScheme(scheme);
1363 if (scheme.EqualsLiteral("data"))
1364 return true;
1366 bool validateRequest = false;
1368 // If the request's loadId is the same as the aCX, then it is ok to use
1369 // this one because it has already been validated for this context.
1370 //
1371 // XXX: nullptr seems to be a 'special' key value that indicates that NO
1372 // validation is required.
1373 //
1374 void *key = (void *)aCX;
1375 if (request->mLoadId != key) {
1376 // If we would need to revalidate this entry, but we're being told to
1377 // bypass the cache, we don't allow this entry to be used.
1378 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)
1379 return false;
1381 // Determine whether the cache aEntry must be revalidated...
1382 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
1384 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1385 ("imgLoader::ValidateEntry validating cache entry. "
1386 "validateRequest = %d", validateRequest));
1387 }
1388 #if defined(PR_LOGGING)
1389 else if (!key) {
1390 nsAutoCString spec;
1391 aURI->GetSpec(spec);
1393 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1394 ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
1395 "because of NULL LoadID", spec.get()));
1396 }
1397 #endif
1399 // We can't use a cached request if it comes from a different
1400 // application cache than this load is expecting.
1401 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
1402 nsCOMPtr<nsIApplicationCache> requestAppCache;
1403 nsCOMPtr<nsIApplicationCache> groupAppCache;
1404 if ((appCacheContainer = do_GetInterface(request->mRequest)))
1405 appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
1406 if ((appCacheContainer = do_QueryInterface(aLoadGroup)))
1407 appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
1409 if (requestAppCache != groupAppCache) {
1410 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1411 ("imgLoader::ValidateEntry - Unable to use cached imgRequest "
1412 "[request=%p] because of mismatched application caches\n",
1413 address_of(request)));
1414 return false;
1415 }
1417 if (validateRequest && aCanMakeNewChannel) {
1418 LOG_SCOPE(GetImgLog(), "imgLoader::ValidateRequest |cache hit| must validate");
1420 return ValidateRequestWithNewChannel(request, aURI, aFirstPartyIsolationURI,
1421 aReferrerURI, aLoadGroup, aObserver,
1422 aCX, aLoadFlags, aProxyRequest, aPolicy,
1423 aLoadingPrincipal, aCORSMode);
1424 }
1426 return !validateRequest;
1427 }
1429 bool imgLoader::RemoveMatchingUrlsFromCache(nsIURI *aImgURI)
1430 {
1431 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
1433 if (!aImgURI) return false;
1435 bool rv = true;
1436 imgCacheTable &cache = GetCache(aImgURI);
1438 // We have to make a temporary, since RemoveFromCache removes the element
1439 // from the queue, invalidating iterators.
1440 nsTArray<nsRefPtr<imgCacheEntry> > entries;
1441 cache.Enumerate(EnumAllEntries, &entries);
1442 for (uint32_t i = 0; i < entries.Length(); ++i) {
1443 bool isEqual = false;
1445 entries[i]->mRequest->mURI->Equals(aImgURI, &isEqual);
1446 if (isEqual && !RemoveFromCache(entries[i]))
1447 rv = false;
1448 }
1450 return rv;
1451 }
1453 bool imgLoader::RemoveFromCache(nsAutoCString key,
1454 imgCacheTable &cache,
1455 imgCacheQueue &queue)
1456 {
1457 if (key.IsEmpty()) return false;
1459 nsRefPtr<imgCacheEntry> entry;
1460 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1461 cache.Remove(key);
1463 NS_ABORT_IF_FALSE(!entry->Evicted(), "Evicting an already-evicted cache entry!");
1465 // Entries with no proxies are in the tracker.
1466 if (entry->HasNoProxies()) {
1467 if (mCacheTracker)
1468 mCacheTracker->RemoveObject(entry);
1469 queue.Remove(entry);
1470 }
1472 entry->SetEvicted(true);
1474 nsRefPtr<imgRequest> request = entry->GetRequest();
1475 request->SetIsInCache(false);
1477 return true;
1478 }
1479 else
1480 return false;
1481 }
1483 bool imgLoader::RemoveFromCache(imgCacheEntry *entry)
1484 {
1485 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache entry");
1487 nsRefPtr<imgRequest> request = entry->GetRequest();
1488 if (request) {
1489 nsRefPtr<ImageURL> imgURI;
1490 if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(imgURI))) && imgURI) {
1491 nsCOMPtr<nsIURI> firstPartyIsolationURI = request->mFirstPartyIsolationURI;
1492 imgCacheTable &cache = GetCache(imgURI);
1493 imgCacheQueue &queue = GetCacheQueue(imgURI);
1494 nsAutoCString spec = GetCacheKey(firstPartyIsolationURI, imgURI, nullptr);
1496 LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "entry's uri", spec.get());
1498 cache.Remove(spec);
1500 if (entry->HasNoProxies()) {
1501 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache removing from tracker");
1502 if (mCacheTracker)
1503 mCacheTracker->RemoveObject(entry);
1504 queue.Remove(entry);
1505 }
1507 entry->SetEvicted(true);
1508 request->SetIsInCache(false);
1510 return true;
1511 }
1512 }
1514 return false;
1515 }
1517 nsresult imgLoader::EvictEntries(imgCacheTable &aCacheToClear)
1518 {
1519 nsresult rv = NS_OK;
1520 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries table");
1522 // We have to make a temporary, since RemoveFromCache removes the element
1523 // from the queue, invalidating iterators.
1524 nsTArray<nsRefPtr<imgCacheEntry> > entries;
1525 aCacheToClear.Enumerate(EnumAllEntries, &entries);
1527 for (uint32_t i = 0; i < entries.Length(); ++i)
1528 if (!RemoveFromCache(entries[i]))
1529 rv = NS_ERROR_FAILURE;
1531 return rv;
1532 }
1534 nsresult imgLoader::EvictEntries(imgCacheQueue &aQueueToClear)
1535 {
1536 LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries queue");
1538 // We have to make a temporary, since RemoveFromCache removes the element
1539 // from the queue, invalidating iterators.
1540 nsTArray<nsRefPtr<imgCacheEntry> > entries(aQueueToClear.GetNumElements());
1541 for (imgCacheQueue::const_iterator i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i)
1542 entries.AppendElement(*i);
1544 for (uint32_t i = 0; i < entries.Length(); ++i)
1545 if (!RemoveFromCache(entries[i]))
1546 return NS_ERROR_FAILURE;
1548 return NS_OK;
1549 }
1551 #define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \
1552 nsIRequest::LOAD_FROM_CACHE)
1554 #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \
1555 nsIRequest::VALIDATE_NEVER | \
1556 nsIRequest::VALIDATE_ONCE_PER_SESSION)
1558 NS_IMETHODIMP imgLoader::LoadImageXPCOM(nsIURI *aURI,
1559 nsIURI *aInitialDocumentURI,
1560 nsIURI *aReferrerURI,
1561 nsIPrincipal* aLoadingPrincipal,
1562 nsILoadGroup *aLoadGroup,
1563 imgINotificationObserver *aObserver,
1564 nsISupports *aCX,
1565 nsLoadFlags aLoadFlags,
1566 nsISupports *aCacheKey,
1567 nsIChannelPolicy *aPolicy,
1568 imgIRequest **_retval)
1569 {
1570 imgRequestProxy *proxy;
1571 nsresult result = LoadImage(aURI,
1572 aInitialDocumentURI,
1573 aReferrerURI,
1574 aLoadingPrincipal,
1575 aLoadGroup,
1576 aObserver,
1577 aCX,
1578 aLoadFlags,
1579 aCacheKey,
1580 aPolicy,
1581 EmptyString(),
1582 &proxy);
1583 *_retval = proxy;
1584 return result;
1585 }
1588 /* imgIRequest loadImage (in nsIURI aURI, in nsIURI aUrlBarURI, in nsIPrincipal loadingPrincipal, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */
1590 nsresult imgLoader::LoadImage(nsIURI *aURI,
1591 nsIURI *aFirstPartyIsolationURI,
1592 nsIURI *aReferrerURI,
1593 nsIPrincipal* aLoadingPrincipal,
1594 nsILoadGroup *aLoadGroup,
1595 imgINotificationObserver *aObserver,
1596 nsISupports *aCX,
1597 nsLoadFlags aLoadFlags,
1598 nsISupports *aCacheKey,
1599 nsIChannelPolicy *aPolicy,
1600 const nsAString& initiatorType,
1601 imgRequestProxy **_retval)
1602 {
1603 VerifyCacheSizes();
1605 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
1607 if (!aURI)
1608 return NS_ERROR_NULL_POINTER;
1610 bool isIsolated = false;
1611 nsAutoCString spec = GetCacheKey(aFirstPartyIsolationURI, aURI, &isIsolated);
1613 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage", "aURI", spec.get());
1615 *_retval = nullptr;
1617 nsRefPtr<imgRequest> request;
1619 nsresult rv;
1620 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
1622 #ifdef DEBUG
1623 bool isPrivate = false;
1625 if (aLoadGroup) {
1626 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1627 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1628 if (callbacks) {
1629 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
1630 isPrivate = loadContext && loadContext->UsePrivateBrowsing();
1631 }
1632 }
1633 MOZ_ASSERT(isPrivate == mRespectPrivacy);
1634 #endif
1636 // Get the default load flags from the loadgroup (if possible)...
1637 if (aLoadGroup) {
1638 aLoadGroup->GetLoadFlags(&requestFlags);
1639 }
1640 //
1641 // Merge the default load flags with those passed in via aLoadFlags.
1642 // Currently, *only* the caching, validation and background load flags
1643 // are merged...
1644 //
1645 // The flags in aLoadFlags take precedence over the default flags!
1646 //
1647 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
1648 // Override the default caching flags...
1649 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
1650 (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
1651 }
1652 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
1653 // Override the default validation flags...
1654 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
1655 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
1656 }
1657 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
1658 // Propagate background loading...
1659 requestFlags |= nsIRequest::LOAD_BACKGROUND;
1660 }
1662 int32_t corsmode = imgIRequest::CORS_NONE;
1663 if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) {
1664 corsmode = imgIRequest::CORS_ANONYMOUS;
1665 } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) {
1666 corsmode = imgIRequest::CORS_USE_CREDENTIALS;
1667 }
1669 nsRefPtr<imgCacheEntry> entry;
1671 // Look in the cache for our URI, and then validate it.
1672 // XXX For now ignore aCacheKey. We will need it in the future
1673 // for correctly dealing with image load requests that are a result
1674 // of post data.
1675 imgCacheTable &cache = GetCache(aURI);
1677 if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
1678 if (ValidateEntry(entry, aURI, aFirstPartyIsolationURI, aReferrerURI,
1679 aLoadGroup, aObserver, aCX, requestFlags, true,
1680 _retval, aPolicy, aLoadingPrincipal, corsmode)) {
1681 request = entry->GetRequest();
1683 // If this entry has no proxies, its request has no reference to the entry.
1684 if (entry->HasNoProxies()) {
1685 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage() adding proxyless entry", "uri", spec.get());
1686 NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!");
1687 request->SetCacheEntry(entry);
1689 if (mCacheTracker)
1690 mCacheTracker->MarkUsed(entry);
1691 }
1693 entry->Touch();
1695 #ifdef DEBUG_joe
1696 printf("CACHEGET: %d %s %d\n", time(nullptr), spec.get(), entry->SizeOfData());
1697 #endif
1698 }
1699 else {
1700 // We can't use this entry. We'll try to load it off the network, and if
1701 // successful, overwrite the old entry in the cache with a new one.
1702 entry = nullptr;
1703 }
1704 }
1706 // Keep the channel in this scope, so we can adjust its notificationCallbacks
1707 // later when we create the proxy.
1708 nsCOMPtr<nsIChannel> newChannel;
1709 // If we didn't get a cache hit, we need to load from the network.
1710 if (!request) {
1711 LOG_SCOPE(GetImgLog(), "imgLoader::LoadImage |cache miss|");
1713 bool forcePrincipalCheck;
1714 rv = NewImageChannel(getter_AddRefs(newChannel),
1715 &forcePrincipalCheck,
1716 aURI,
1717 aFirstPartyIsolationURI,
1718 aReferrerURI,
1719 aLoadGroup,
1720 mAcceptHeader,
1721 requestFlags,
1722 aPolicy,
1723 aLoadingPrincipal);
1724 if (NS_FAILED(rv))
1725 return NS_ERROR_FAILURE;
1727 MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy);
1729 NewRequestAndEntry(forcePrincipalCheck, this, getter_AddRefs(request), getter_AddRefs(entry));
1731 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1732 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request.get()));
1734 nsCOMPtr<nsILoadGroup> channelLoadGroup;
1735 newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
1736 request->Init(aURI, aURI, aFirstPartyIsolationURI, channelLoadGroup, newChannel, entry, aCX,
1737 aLoadingPrincipal, corsmode);
1739 // Add the initiator type for this image load
1740 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel);
1741 if (timedChannel) {
1742 timedChannel->SetInitiatorType(initiatorType);
1743 }
1745 // Pass the inner window ID of the loading document, if possible.
1746 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
1747 if (doc) {
1748 request->SetInnerWindowID(doc->InnerWindowID());
1749 }
1751 // create the proxy listener
1752 nsCOMPtr<nsIStreamListener> pl = new ProxyListener(request.get());
1754 // See if we need to insert a CORS proxy between the proxy listener and the
1755 // request.
1756 nsCOMPtr<nsIStreamListener> listener = pl;
1757 if (corsmode != imgIRequest::CORS_NONE) {
1758 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1759 ("[this=%p] imgLoader::LoadImage -- Setting up a CORS load",
1760 this));
1761 bool withCredentials = corsmode == imgIRequest::CORS_USE_CREDENTIALS;
1763 nsRefPtr<nsCORSListenerProxy> corsproxy =
1764 new nsCORSListenerProxy(pl, aLoadingPrincipal, withCredentials);
1765 rv = corsproxy->Init(newChannel);
1766 if (NS_FAILED(rv)) {
1767 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1768 ("[this=%p] imgLoader::LoadImage -- nsCORSListenerProxy "
1769 "creation failed: 0x%x\n", this, rv));
1770 request->CancelAndAbort(rv);
1771 return NS_ERROR_FAILURE;
1772 }
1774 listener = corsproxy;
1775 }
1777 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1778 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this));
1780 mozilla::net::SeerLearn(aURI, aFirstPartyIsolationURI,
1781 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
1783 nsresult openRes = newChannel->AsyncOpen(listener, nullptr);
1785 if (NS_FAILED(openRes)) {
1786 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
1787 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n",
1788 this, openRes));
1789 request->CancelAndAbort(openRes);
1790 return openRes;
1791 }
1793 if (isIsolated) // Try to add the new request into the cache.
1794 PutIntoCache(spec, entry);
1795 } else {
1796 LOG_MSG_WITH_PARAM(GetImgLog(),
1797 "imgLoader::LoadImage |cache hit|", "request", request);
1798 }
1801 // If we didn't get a proxy when validating the cache entry, we need to create one.
1802 if (!*_retval) {
1803 // ValidateEntry() has three return values: "Is valid," "might be valid --
1804 // validating over network", and "not valid." If we don't have a _retval,
1805 // we know ValidateEntry is not validating over the network, so it's safe
1806 // to SetLoadId here because we know this request is valid for this context.
1807 //
1808 // Note, however, that this doesn't guarantee the behaviour we want (one
1809 // URL maps to the same image on a page) if we load the same image in a
1810 // different tab (see bug 528003), because its load id will get re-set, and
1811 // that'll cause us to validate over the network.
1812 request->SetLoadId(aCX);
1814 LOG_MSG(GetImgLog(), "imgLoader::LoadImage", "creating proxy request.");
1815 rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1816 requestFlags, _retval);
1817 if (NS_FAILED(rv)) {
1818 return rv;
1819 }
1821 imgRequestProxy *proxy = *_retval;
1823 // Make sure that OnStatus/OnProgress calls have the right request set, if
1824 // we did create a channel here.
1825 if (newChannel) {
1826 nsCOMPtr<nsIInterfaceRequestor> requestor(
1827 new nsProgressNotificationProxy(newChannel, proxy));
1828 if (!requestor)
1829 return NS_ERROR_OUT_OF_MEMORY;
1830 newChannel->SetNotificationCallbacks(requestor);
1831 }
1833 // Note that it's OK to add here even if the request is done. If it is,
1834 // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
1835 // the proxy will be removed from the loadgroup.
1836 proxy->AddToLoadGroup();
1838 // If we're loading off the network, explicitly don't notify our proxy,
1839 // because necko (or things called from necko, such as imgCacheValidator)
1840 // are going to call our notifications asynchronously, and we can't make it
1841 // further asynchronous because observers might rely on imagelib completing
1842 // its work between the channel's OnStartRequest and OnStopRequest.
1843 if (!newChannel)
1844 proxy->NotifyListener();
1846 return rv;
1847 }
1849 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
1851 return NS_OK;
1852 }
1854 nsAutoCString imgLoader::GetCacheKey(nsIURI *firstPartyIsolationURI, ImageURL *imgURI,
1855 bool *isIsolated)
1856 {
1857 NS_ASSERTION(imgURI, "imgLoader::GetCacheKey -- NULL imgURI");
1858 if (isIsolated)
1859 *isIsolated = false;
1861 nsAutoCString spec;
1862 if (imgURI)
1863 imgURI->GetSpec(spec);
1865 nsAutoCString hostKey;
1866 if (firstPartyIsolationURI && sThirdPartyUtilSvc)
1867 sThirdPartyUtilSvc->GetFirstPartyHostForIsolation(firstPartyIsolationURI, hostKey);
1869 if (hostKey.Length() > 0) {
1870 if (isIsolated)
1871 *isIsolated = true;
1872 // Make a new key using host
1873 // FIXME: This might involve a couple more copies than necessary..
1874 // But man, 18 string types? Who knows which one I need to use to do
1875 // this cheaply..
1876 return hostKey + nsAutoCString("&") + spec;
1877 } else {
1878 // No hostKey found, so don't isolate image to a first party.
1879 return spec;
1880 }
1881 }
1883 nsAutoCString imgLoader::GetCacheKey(nsIURI *firstPartyIsolationURI, nsIURI* uri,
1884 bool *isIsolated) {
1885 nsRefPtr<ImageURL> imageURI = new ImageURL(uri);
1886 return GetCacheKey(firstPartyIsolationURI, imageURI, isIsolated);
1887 }
1889 /* imgIRequest loadImageWithChannelXPCOM(in nsIChannel channel, in imgINotificationObserver aObserver, in nsISupports cx, out nsIStreamListener); */
1890 NS_IMETHODIMP imgLoader::LoadImageWithChannelXPCOM(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
1891 {
1892 nsresult result;
1893 imgRequestProxy* proxy;
1894 result = LoadImageWithChannel(channel,
1895 aObserver,
1896 aCX,
1897 listener,
1898 &proxy);
1899 *_retval = proxy;
1900 return result;
1901 }
1903 nsresult imgLoader::LoadImageWithChannel(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgRequestProxy **_retval)
1904 {
1905 NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer");
1907 MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
1909 if (!sThirdPartyUtilSvc)
1910 return NS_ERROR_FAILURE;
1912 nsRefPtr<imgRequest> request;
1914 nsCOMPtr<nsIURI> uri;
1915 channel->GetURI(getter_AddRefs(uri));
1917 nsCOMPtr<nsIURI> firstPartyIsolationURI;
1918 sThirdPartyUtilSvc->GetFirstPartyIsolationURI(channel, nullptr,
1919 getter_AddRefs(firstPartyIsolationURI));
1921 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
1922 channel->GetLoadFlags(&requestFlags);
1924 nsRefPtr<imgCacheEntry> entry;
1925 imgCacheTable &cache = GetCache(uri);
1926 nsAutoCString key = GetCacheKey(firstPartyIsolationURI, uri, nullptr);
1928 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1929 imgCacheQueue &queue = GetCacheQueue(uri);
1930 RemoveFromCache(key, cache, queue);
1931 } else {
1932 // Look in the cache for our URI, and then validate it.
1933 // XXX For now ignore aCacheKey. We will need it in the future
1934 // for correctly dealing with image load requests that are a result
1935 // of post data.
1937 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1938 // We don't want to kick off another network load. So we ask
1939 // ValidateEntry to only do validation without creating a new proxy. If
1940 // it says that the entry isn't valid any more, we'll only use the entry
1941 // we're getting if the channel is loading from the cache anyways.
1942 //
1943 // XXX -- should this be changed? it's pretty much verbatim from the old
1944 // code, but seems nonsensical.
1945 if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver, aCX,
1946 requestFlags, false, nullptr, nullptr, nullptr,
1947 imgIRequest::CORS_NONE)) {
1948 request = entry->GetRequest();
1949 } else {
1950 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(channel));
1951 bool bUseCacheCopy;
1953 if (cacheChan)
1954 cacheChan->IsFromCache(&bUseCacheCopy);
1955 else
1956 bUseCacheCopy = false;
1958 if (!bUseCacheCopy) {
1959 entry = nullptr;
1960 } else {
1961 request = entry->GetRequest();
1962 }
1963 }
1965 if (request && entry) {
1966 // If this entry has no proxies, its request has no reference to the entry.
1967 if (entry->HasNoProxies()) {
1968 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri", key.get());
1969 NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!");
1970 request->SetCacheEntry(entry);
1972 if (mCacheTracker)
1973 mCacheTracker->MarkUsed(entry);
1974 }
1975 }
1976 }
1977 }
1979 nsCOMPtr<nsILoadGroup> loadGroup;
1980 channel->GetLoadGroup(getter_AddRefs(loadGroup));
1982 // Filter out any load flags not from nsIRequest
1983 requestFlags &= nsIRequest::LOAD_REQUESTMASK;
1985 nsresult rv = NS_OK;
1986 if (request) {
1987 // we have this in our cache already.. cancel the current (document) load
1989 channel->Cancel(NS_ERROR_PARSED_DATA_CACHED); // this should fire an OnStopRequest
1991 *listener = nullptr; // give them back a null nsIStreamListener
1993 rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
1994 requestFlags, _retval);
1995 static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
1996 } else {
1997 // Default to doing a principal check because we don't know who
1998 // started that load and whether their principal ended up being
1999 // inherited on the channel.
2000 NewRequestAndEntry(true, this, getter_AddRefs(request), getter_AddRefs(entry));
2002 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2003 nsCOMPtr<nsIURI> originalURI;
2004 channel->GetOriginalURI(getter_AddRefs(originalURI));
2006 // No principal specified here, because we're not passed one.
2007 request->Init(originalURI, uri, firstPartyIsolationURI, channel, channel, entry,
2008 aCX, nullptr, imgIRequest::CORS_NONE);
2010 ProxyListener *pl = new ProxyListener(static_cast<nsIStreamListener *>(request.get()));
2011 NS_ADDREF(pl);
2013 *listener = static_cast<nsIStreamListener*>(pl);
2014 NS_ADDREF(*listener);
2016 NS_RELEASE(pl);
2018 bool isIsolated = false;
2019 nsAutoCString cacheKey = GetCacheKey(firstPartyIsolationURI, originalURI, &isIsolated);
2020 if (isIsolated) // Try to add the new request into the cache.
2021 PutIntoCache(cacheKey, entry);
2023 rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
2024 requestFlags, _retval);
2026 // Explicitly don't notify our proxy, because we're loading off the
2027 // network, and necko (or things called from necko, such as
2028 // imgCacheValidator) are going to call our notifications asynchronously,
2029 // and we can't make it further asynchronous because observers might rely
2030 // on imagelib completing its work between the channel's OnStartRequest and
2031 // OnStopRequest.
2032 }
2034 return rv;
2035 }
2037 bool imgLoader::SupportImageWithMimeType(const char* aMimeType)
2038 {
2039 nsAutoCString mimeType(aMimeType);
2040 ToLowerCase(mimeType);
2041 return Image::GetDecoderType(mimeType.get()) != Image::eDecoderType_unknown;
2042 }
2044 NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
2045 const uint8_t* aContents,
2046 uint32_t aLength,
2047 nsACString& aContentType)
2048 {
2049 return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
2050 }
2052 /* static */
2053 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType)
2054 {
2055 /* Is it a GIF? */
2056 if (aLength >= 6 && (!nsCRT::strncmp(aContents, "GIF87a", 6) ||
2057 !nsCRT::strncmp(aContents, "GIF89a", 6)))
2058 {
2059 aContentType.AssignLiteral(IMAGE_GIF);
2060 }
2062 /* or a PNG? */
2063 else if (aLength >= 8 && ((unsigned char)aContents[0]==0x89 &&
2064 (unsigned char)aContents[1]==0x50 &&
2065 (unsigned char)aContents[2]==0x4E &&
2066 (unsigned char)aContents[3]==0x47 &&
2067 (unsigned char)aContents[4]==0x0D &&
2068 (unsigned char)aContents[5]==0x0A &&
2069 (unsigned char)aContents[6]==0x1A &&
2070 (unsigned char)aContents[7]==0x0A))
2071 {
2072 aContentType.AssignLiteral(IMAGE_PNG);
2073 }
2075 /* maybe a JPEG (JFIF)? */
2076 /* JFIF files start with SOI APP0 but older files can start with SOI DQT
2077 * so we test for SOI followed by any marker, i.e. FF D8 FF
2078 * this will also work for SPIFF JPEG files if they appear in the future.
2079 *
2080 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
2081 */
2082 else if (aLength >= 3 &&
2083 ((unsigned char)aContents[0])==0xFF &&
2084 ((unsigned char)aContents[1])==0xD8 &&
2085 ((unsigned char)aContents[2])==0xFF)
2086 {
2087 aContentType.AssignLiteral(IMAGE_JPEG);
2088 }
2090 /* or how about ART? */
2091 /* ART begins with JG (4A 47). Major version offset 2.
2092 * Minor version offset 3. Offset 4 must be nullptr.
2093 */
2094 else if (aLength >= 5 &&
2095 ((unsigned char) aContents[0])==0x4a &&
2096 ((unsigned char) aContents[1])==0x47 &&
2097 ((unsigned char) aContents[4])==0x00 )
2098 {
2099 aContentType.AssignLiteral(IMAGE_ART);
2100 }
2102 else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) {
2103 aContentType.AssignLiteral(IMAGE_BMP);
2104 }
2106 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
2107 // CURs begin with 2-byte 0 followed by 2-byte 2.
2108 else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
2109 !memcmp(aContents, "\000\000\002\000", 4))) {
2110 aContentType.AssignLiteral(IMAGE_ICO);
2111 }
2113 else {
2114 /* none of the above? I give up */
2115 return NS_ERROR_NOT_AVAILABLE;
2116 }
2118 return NS_OK;
2119 }
2121 /**
2122 * proxy stream listener class used to handle multipart/x-mixed-replace
2123 */
2125 #include "nsIRequest.h"
2126 #include "nsIStreamConverterService.h"
2128 NS_IMPL_ISUPPORTS(ProxyListener,
2129 nsIStreamListener,
2130 nsIThreadRetargetableStreamListener,
2131 nsIRequestObserver)
2133 ProxyListener::ProxyListener(nsIStreamListener *dest) :
2134 mDestListener(dest)
2135 {
2136 /* member initializers and constructor code */
2137 }
2139 ProxyListener::~ProxyListener()
2140 {
2141 /* destructor code */
2142 }
2145 /** nsIRequestObserver methods **/
2147 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
2148 NS_IMETHODIMP ProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
2149 {
2150 if (!mDestListener)
2151 return NS_ERROR_FAILURE;
2153 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2154 if (channel) {
2155 nsAutoCString contentType;
2156 nsresult rv = channel->GetContentType(contentType);
2158 if (!contentType.IsEmpty()) {
2159 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
2160 in the pipeline to handle the content and pass it along to our
2161 original listener.
2162 */
2163 if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) {
2165 nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv));
2166 if (NS_SUCCEEDED(rv)) {
2167 nsCOMPtr<nsIStreamListener> toListener(mDestListener);
2168 nsCOMPtr<nsIStreamListener> fromListener;
2170 rv = convServ->AsyncConvertData("multipart/x-mixed-replace",
2171 "*/*",
2172 toListener,
2173 nullptr,
2174 getter_AddRefs(fromListener));
2175 if (NS_SUCCEEDED(rv))
2176 mDestListener = fromListener;
2177 }
2178 }
2179 }
2180 }
2182 return mDestListener->OnStartRequest(aRequest, ctxt);
2183 }
2185 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
2186 NS_IMETHODIMP ProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
2187 {
2188 if (!mDestListener)
2189 return NS_ERROR_FAILURE;
2191 return mDestListener->OnStopRequest(aRequest, ctxt, status);
2192 }
2194 /** nsIStreamListener methods **/
2196 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
2197 NS_IMETHODIMP
2198 ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
2199 nsIInputStream *inStr, uint64_t sourceOffset,
2200 uint32_t count)
2201 {
2202 if (!mDestListener)
2203 return NS_ERROR_FAILURE;
2205 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
2206 }
2208 /** nsThreadRetargetableStreamListener methods **/
2209 NS_IMETHODIMP
2210 ProxyListener::CheckListenerChain()
2211 {
2212 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2213 nsresult rv = NS_OK;
2214 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2215 do_QueryInterface(mDestListener, &rv);
2216 if (retargetableListener) {
2217 rv = retargetableListener->CheckListenerChain();
2218 }
2219 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
2220 ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%x]",
2221 (NS_SUCCEEDED(rv) ? "success" : "failure"),
2222 this, (nsIStreamListener*)mDestListener, rv));
2223 return rv;
2224 }
2226 /**
2227 * http validate class. check a channel for a 304
2228 */
2230 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
2231 nsIThreadRetargetableStreamListener,
2232 nsIChannelEventSink, nsIInterfaceRequestor,
2233 nsIAsyncVerifyRedirectCallback)
2235 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
2236 imgLoader* loader, imgRequest *request,
2237 void *aContext, bool forcePrincipalCheckForCacheEntry)
2238 : mProgressProxy(progress),
2239 mRequest(request),
2240 mContext(aContext),
2241 mImgLoader(loader)
2242 {
2243 NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest),
2244 getter_AddRefs(mNewEntry));
2245 }
2247 imgCacheValidator::~imgCacheValidator()
2248 {
2249 if (mRequest) {
2250 mRequest->mValidator = nullptr;
2251 }
2252 }
2254 void imgCacheValidator::AddProxy(imgRequestProxy *aProxy)
2255 {
2256 // aProxy needs to be in the loadgroup since we're validating from
2257 // the network.
2258 aProxy->AddToLoadGroup();
2260 mProxies.AppendObject(aProxy);
2261 }
2263 /** nsIRequestObserver methods **/
2265 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
2266 NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
2267 {
2268 // If this request is coming from cache and has the same URI as our
2269 // imgRequest, the request all our proxies are pointing at is valid, and all
2270 // we have to do is tell them to notify their listeners.
2271 nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
2272 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2273 if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) {
2274 bool isFromCache = false;
2275 cacheChan->IsFromCache(&isFromCache);
2277 nsCOMPtr<nsIURI> channelURI;
2278 bool sameURI = false;
2279 channel->GetURI(getter_AddRefs(channelURI));
2280 if (channelURI)
2281 channelURI->Equals(mRequest->mCurrentURI, &sameURI);
2283 if (isFromCache && sameURI) {
2284 uint32_t count = mProxies.Count();
2285 for (int32_t i = count-1; i>=0; i--) {
2286 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
2288 // Proxies waiting on cache validation should be deferring notifications.
2289 // Undefer them.
2290 NS_ABORT_IF_FALSE(proxy->NotificationsDeferred(),
2291 "Proxies waiting on cache validation should be "
2292 "deferring notifications!");
2293 proxy->SetNotificationsDeferred(false);
2295 // Notify synchronously, because we're already in OnStartRequest, an
2296 // asynchronously-called function.
2297 proxy->SyncNotifyListener();
2298 }
2300 // We don't need to load this any more.
2301 aRequest->Cancel(NS_BINDING_ABORTED);
2303 mRequest->SetLoadId(mContext);
2304 mRequest->mValidator = nullptr;
2306 mRequest = nullptr;
2308 mNewRequest = nullptr;
2309 mNewEntry = nullptr;
2311 return NS_OK;
2312 }
2313 }
2315 // We can't load out of cache. We have to create a whole new request for the
2316 // data that's coming in off the channel.
2317 nsCOMPtr<nsIURI> uri;
2318 {
2319 nsRefPtr<ImageURL> imageURL;
2320 mRequest->GetURI(getter_AddRefs(imageURL));
2321 uri = imageURL->ToIURI();
2322 }
2324 #if defined(PR_LOGGING)
2325 nsAutoCString spec;
2326 uri->GetSpec(spec);
2327 LOG_MSG_WITH_PARAM(GetImgLog(), "imgCacheValidator::OnStartRequest creating new request", "uri", spec.get());
2328 #endif
2330 int32_t corsmode = mRequest->GetCORSMode();
2331 nsCOMPtr<nsIPrincipal> loadingPrincipal = mRequest->GetLoadingPrincipal();
2332 nsCOMPtr<nsIURI> firstPartyIsolationURI = mRequest->mFirstPartyIsolationURI;
2334 // Doom the old request's cache entry
2335 mRequest->RemoveFromCache();
2337 mRequest->mValidator = nullptr;
2338 mRequest = nullptr;
2340 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2341 nsCOMPtr<nsIURI> originalURI;
2342 channel->GetOriginalURI(getter_AddRefs(originalURI));
2343 mNewRequest->Init(originalURI, uri, firstPartyIsolationURI, aRequest, channel,
2344 mNewEntry, mContext, loadingPrincipal, corsmode);
2346 mDestListener = new ProxyListener(mNewRequest);
2348 // Try to add the new request into the cache. Note that the entry must be in
2349 // the cache before the proxies' ownership changes, because adding a proxy
2350 // changes the caching behaviour for imgRequests.
2351 bool isIsolated = false;
2352 nsAutoCString key = mImgLoader->GetCacheKey(firstPartyIsolationURI, originalURI,
2353 &isIsolated);
2354 if (isIsolated)
2355 mImgLoader->PutIntoCache(key, mNewEntry);
2357 uint32_t count = mProxies.Count();
2358 for (int32_t i = count-1; i>=0; i--) {
2359 imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
2360 proxy->ChangeOwner(mNewRequest);
2362 // Notify synchronously, because we're already in OnStartRequest, an
2363 // asynchronously-called function.
2364 proxy->SetNotificationsDeferred(false);
2365 proxy->SyncNotifyListener();
2366 }
2368 mNewRequest = nullptr;
2369 mNewEntry = nullptr;
2371 return mDestListener->OnStartRequest(aRequest, ctxt);
2372 }
2374 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
2375 NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
2376 {
2377 if (!mDestListener)
2378 return NS_OK;
2380 return mDestListener->OnStopRequest(aRequest, ctxt, status);
2381 }
2383 /** nsIStreamListener methods **/
2386 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
2387 NS_IMETHODIMP
2388 imgCacheValidator::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
2389 nsIInputStream *inStr,
2390 uint64_t sourceOffset, uint32_t count)
2391 {
2392 if (!mDestListener) {
2393 // XXX see bug 113959
2394 uint32_t _retval;
2395 inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
2396 return NS_OK;
2397 }
2399 return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
2400 }
2402 /** nsIThreadRetargetableStreamListener methods **/
2404 NS_IMETHODIMP
2405 imgCacheValidator::CheckListenerChain()
2406 {
2407 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2408 nsresult rv = NS_OK;
2409 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2410 do_QueryInterface(mDestListener, &rv);
2411 if (retargetableListener) {
2412 rv = retargetableListener->CheckListenerChain();
2413 }
2414 PR_LOG(GetImgLog(), PR_LOG_DEBUG,
2415 ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %d=%s",
2416 this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv));
2417 return rv;
2418 }
2420 /** nsIInterfaceRequestor methods **/
2422 NS_IMETHODIMP imgCacheValidator::GetInterface(const nsIID & aIID, void **aResult)
2423 {
2424 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)))
2425 return QueryInterface(aIID, aResult);
2427 return mProgressProxy->GetInterface(aIID, aResult);
2428 }
2430 // These functions are materially the same as the same functions in imgRequest.
2431 // We duplicate them because we're verifying whether cache loads are necessary,
2432 // not unconditionally loading.
2434 /** nsIChannelEventSink methods **/
2435 NS_IMETHODIMP imgCacheValidator::AsyncOnChannelRedirect(nsIChannel *oldChannel,
2436 nsIChannel *newChannel, uint32_t flags,
2437 nsIAsyncVerifyRedirectCallback *callback)
2438 {
2439 // Note all cache information we get from the old channel.
2440 mNewRequest->SetCacheValidation(mNewEntry, oldChannel);
2442 // Prepare for callback
2443 mRedirectCallback = callback;
2444 mRedirectChannel = newChannel;
2446 return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
2447 }
2449 NS_IMETHODIMP imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult)
2450 {
2451 // If we've already been told to abort, just do so.
2452 if (NS_FAILED(aResult)) {
2453 mRedirectCallback->OnRedirectVerifyCallback(aResult);
2454 mRedirectCallback = nullptr;
2455 mRedirectChannel = nullptr;
2456 return NS_OK;
2457 }
2459 // make sure we have a protocol that returns data rather than opens
2460 // an external application, e.g. mailto:
2461 nsCOMPtr<nsIURI> uri;
2462 mRedirectChannel->GetURI(getter_AddRefs(uri));
2463 bool doesNotReturnData = false;
2464 NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
2465 &doesNotReturnData);
2467 nsresult result = NS_OK;
2469 if (doesNotReturnData) {
2470 result = NS_ERROR_ABORT;
2471 }
2473 mRedirectCallback->OnRedirectVerifyCallback(result);
2474 mRedirectCallback = nullptr;
2475 mRedirectChannel = nullptr;
2476 return NS_OK;
2477 }