Bug 1646700 [wpt PR 24235] - Update picture-in-picture idlharness test, a=testonly
[gecko.git] / image / imgLoader.cpp
blobd54678fc896eb30f10aed3cf2575c1a7b4663685
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 // Undefine windows version of LoadImage because our code uses that name.
8 #undef LoadImage
10 #include "imgLoader.h"
12 #include <algorithm>
13 #include <utility>
15 #include "DecoderFactory.h"
16 #include "Image.h"
17 #include "ImageLogging.h"
18 #include "ReferrerInfo.h"
19 #include "imgRequestProxy.h"
20 #include "mozilla/Attributes.h"
21 #include "mozilla/BasePrincipal.h"
22 #include "mozilla/ChaosMode.h"
23 #include "mozilla/ClearOnShutdown.h"
24 #include "mozilla/LoadInfo.h"
25 #include "mozilla/NullPrincipal.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/StaticPrefs_image.h"
28 #include "mozilla/StaticPrefs_network.h"
29 #include "mozilla/dom/ContentParent.h"
30 #include "mozilla/dom/nsMixedContentBlocker.h"
31 #include "mozilla/image/ImageMemoryReporter.h"
32 #include "mozilla/layers/CompositorManagerChild.h"
33 #include "nsCOMPtr.h"
34 #include "nsCRT.h"
35 #include "nsContentPolicyUtils.h"
36 #include "nsContentUtils.h"
37 #include "nsIApplicationCache.h"
38 #include "nsIApplicationCacheContainer.h"
39 #include "nsIAsyncVerifyRedirectCallback.h"
40 #include "nsICacheInfoChannel.h"
41 #include "nsIChannelEventSink.h"
42 #include "nsIClassOfService.h"
43 #include "nsIFile.h"
44 #include "nsIFileURL.h"
45 #include "nsIHttpChannel.h"
46 #include "nsIInterfaceRequestor.h"
47 #include "nsIInterfaceRequestorUtils.h"
48 #include "nsIMemoryReporter.h"
49 #include "nsINetworkPredictor.h"
50 #include "nsIProgressEventSink.h"
51 #include "nsIProtocolHandler.h"
52 #include "nsImageModule.h"
53 #include "nsMimeTypes.h"
54 #include "nsNetCID.h"
55 #include "nsNetUtil.h"
56 #include "nsQueryObject.h"
57 #include "nsReadableUtils.h"
58 #include "nsStreamUtils.h"
59 #include "prtime.h"
61 // we want to explore making the document own the load group
62 // so we can associate the document URI with the load group.
63 // until this point, we have an evil hack:
64 #include "nsIHttpChannelInternal.h"
65 #include "nsILoadGroupChild.h"
66 #include "nsIDocShell.h"
68 using namespace mozilla;
69 using namespace mozilla::dom;
70 using namespace mozilla::image;
71 using namespace mozilla::net;
73 MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)
75 class imgMemoryReporter final : public nsIMemoryReporter {
76 ~imgMemoryReporter() = default;
78 public:
79 NS_DECL_ISUPPORTS
81 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
82 nsISupports* aData, bool aAnonymize) override {
83 MOZ_ASSERT(NS_IsMainThread());
85 layers::CompositorManagerChild* manager =
86 mozilla::layers::CompositorManagerChild::GetInstance();
87 if (!manager || !StaticPrefs::image_mem_debug_reporting()) {
88 layers::SharedSurfacesMemoryReport sharedSurfaces;
89 FinishCollectReports(aHandleReport, aData, aAnonymize, sharedSurfaces);
90 return NS_OK;
93 RefPtr<imgMemoryReporter> self(this);
94 nsCOMPtr<nsIHandleReportCallback> handleReport(aHandleReport);
95 nsCOMPtr<nsISupports> data(aData);
96 manager->SendReportSharedSurfacesMemory(
97 [=](layers::SharedSurfacesMemoryReport aReport) {
98 self->FinishCollectReports(handleReport, data, aAnonymize, aReport);
100 [=](mozilla::ipc::ResponseRejectReason&& aReason) {
101 layers::SharedSurfacesMemoryReport sharedSurfaces;
102 self->FinishCollectReports(handleReport, data, aAnonymize,
103 sharedSurfaces);
105 return NS_OK;
108 void FinishCollectReports(
109 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
110 bool aAnonymize, layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
111 nsTArray<ImageMemoryCounter> chrome;
112 nsTArray<ImageMemoryCounter> content;
113 nsTArray<ImageMemoryCounter> uncached;
115 for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
116 for (auto iter = mKnownLoaders[i]->mChromeCache.Iter(); !iter.Done();
117 iter.Next()) {
118 imgCacheEntry* entry = iter.UserData();
119 RefPtr<imgRequest> req = entry->GetRequest();
120 RecordCounterForRequest(req, &chrome, !entry->HasNoProxies());
122 for (auto iter = mKnownLoaders[i]->mCache.Iter(); !iter.Done();
123 iter.Next()) {
124 imgCacheEntry* entry = iter.UserData();
125 RefPtr<imgRequest> req = entry->GetRequest();
126 RecordCounterForRequest(req, &content, !entry->HasNoProxies());
128 MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex);
129 for (auto iter = mKnownLoaders[i]->mUncachedImages.Iter(); !iter.Done();
130 iter.Next()) {
131 nsPtrHashKey<imgRequest>* entry = iter.Get();
132 RefPtr<imgRequest> req = entry->GetKey();
133 RecordCounterForRequest(req, &uncached, req->HasConsumers());
137 // Note that we only need to anonymize content image URIs.
139 ReportCounterArray(aHandleReport, aData, chrome, "images/chrome",
140 /* aAnonymize */ false, aSharedSurfaces);
142 ReportCounterArray(aHandleReport, aData, content, "images/content",
143 aAnonymize, aSharedSurfaces);
145 // Uncached images may be content or chrome, so anonymize them.
146 ReportCounterArray(aHandleReport, aData, uncached, "images/uncached",
147 aAnonymize, aSharedSurfaces);
149 // Report any shared surfaces that were not merged with the surface cache.
150 ImageMemoryReporter::ReportSharedSurfaces(aHandleReport, aData,
151 aSharedSurfaces);
153 nsCOMPtr<nsIMemoryReporterManager> imgr =
154 do_GetService("@mozilla.org/memory-reporter-manager;1");
155 if (imgr) {
156 imgr->EndReport();
160 static int64_t ImagesContentUsedUncompressedDistinguishedAmount() {
161 size_t n = 0;
162 for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length();
163 i++) {
164 for (auto iter = imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Iter();
165 !iter.Done(); iter.Next()) {
166 imgCacheEntry* entry = iter.UserData();
167 if (entry->HasNoProxies()) {
168 continue;
171 RefPtr<imgRequest> req = entry->GetRequest();
172 RefPtr<image::Image> image = req->GetImage();
173 if (!image) {
174 continue;
177 // Both this and EntryImageSizes measure
178 // images/content/raster/used/decoded memory. This function's
179 // measurement is secondary -- the result doesn't go in the "explicit"
180 // tree -- so we use moz_malloc_size_of instead of ImagesMallocSizeOf to
181 // prevent DMD from seeing it reported twice.
182 SizeOfState state(moz_malloc_size_of);
183 ImageMemoryCounter counter(req, image, state, /* aIsUsed = */ true);
185 n += counter.Values().DecodedHeap();
186 n += counter.Values().DecodedNonHeap();
187 n += counter.Values().DecodedUnknown();
190 return n;
193 void RegisterLoader(imgLoader* aLoader) {
194 mKnownLoaders.AppendElement(aLoader);
197 void UnregisterLoader(imgLoader* aLoader) {
198 mKnownLoaders.RemoveElement(aLoader);
201 private:
202 nsTArray<imgLoader*> mKnownLoaders;
204 struct MemoryTotal {
205 MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) {
206 if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
207 if (aImageCounter.IsUsed()) {
208 mUsedRasterCounter += aImageCounter.Values();
209 } else {
210 mUnusedRasterCounter += aImageCounter.Values();
212 } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) {
213 if (aImageCounter.IsUsed()) {
214 mUsedVectorCounter += aImageCounter.Values();
215 } else {
216 mUnusedVectorCounter += aImageCounter.Values();
218 } else if (aImageCounter.Type() == imgIContainer::TYPE_REQUEST) {
219 // Nothing to do, we did not get to the point of having an image.
220 } else {
221 MOZ_CRASH("Unexpected image type");
224 return *this;
227 const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; }
228 const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; }
229 const MemoryCounter& UsedVector() const { return mUsedVectorCounter; }
230 const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; }
232 private:
233 MemoryCounter mUsedRasterCounter;
234 MemoryCounter mUnusedRasterCounter;
235 MemoryCounter mUsedVectorCounter;
236 MemoryCounter mUnusedVectorCounter;
239 // Reports all images of a single kind, e.g. all used chrome images.
240 void ReportCounterArray(nsIHandleReportCallback* aHandleReport,
241 nsISupports* aData,
242 nsTArray<ImageMemoryCounter>& aCounterArray,
243 const char* aPathPrefix, bool aAnonymize,
244 layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
245 MemoryTotal summaryTotal;
246 MemoryTotal nonNotableTotal;
248 // Report notable images, and compute total and non-notable aggregate sizes.
249 for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
250 ImageMemoryCounter& counter = aCounterArray[i];
252 if (aAnonymize) {
253 counter.URI().Truncate();
254 counter.URI().AppendPrintf("<anonymized-%u>", i);
255 } else {
256 // The URI could be an extremely long data: URI. Truncate if needed.
257 static const size_t max = 256;
258 if (counter.URI().Length() > max) {
259 counter.URI().Truncate(max);
260 counter.URI().AppendLiteral(" (truncated)");
262 counter.URI().ReplaceChar('/', '\\');
265 summaryTotal += counter;
267 if (counter.IsNotable() || StaticPrefs::image_mem_debug_reporting()) {
268 ReportImage(aHandleReport, aData, aPathPrefix, counter,
269 aSharedSurfaces);
270 } else {
271 ImageMemoryReporter::TrimSharedSurfaces(counter, aSharedSurfaces);
272 nonNotableTotal += counter;
276 // Report non-notable images in aggregate.
277 ReportTotal(aHandleReport, aData, /* aExplicit = */ true, aPathPrefix,
278 "<non-notable images>/", nonNotableTotal);
280 // Report a summary in aggregate, outside of the explicit tree.
281 ReportTotal(aHandleReport, aData, /* aExplicit = */ false, aPathPrefix, "",
282 summaryTotal);
285 static void ReportImage(nsIHandleReportCallback* aHandleReport,
286 nsISupports* aData, const char* aPathPrefix,
287 const ImageMemoryCounter& aCounter,
288 layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
289 nsAutoCString pathPrefix("explicit/"_ns);
290 pathPrefix.Append(aPathPrefix);
292 switch (aCounter.Type()) {
293 case imgIContainer::TYPE_RASTER:
294 pathPrefix.AppendLiteral("/raster/");
295 break;
296 case imgIContainer::TYPE_VECTOR:
297 pathPrefix.AppendLiteral("/vector/");
298 break;
299 case imgIContainer::TYPE_REQUEST:
300 pathPrefix.AppendLiteral("/request/");
301 break;
302 default:
303 pathPrefix.AppendLiteral("/unknown=");
304 pathPrefix.AppendInt(aCounter.Type());
305 pathPrefix.AppendLiteral("/");
306 break;
309 pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
310 if (aCounter.IsValidating()) {
311 pathPrefix.AppendLiteral("validating/");
313 if (aCounter.HasError()) {
314 pathPrefix.AppendLiteral("err/");
317 pathPrefix.AppendLiteral("progress=");
318 pathPrefix.AppendInt(aCounter.Progress(), 16);
319 pathPrefix.AppendLiteral("/");
321 pathPrefix.AppendLiteral("image(");
322 pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
323 pathPrefix.AppendLiteral("x");
324 pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
325 pathPrefix.AppendLiteral(", ");
327 if (aCounter.URI().IsEmpty()) {
328 pathPrefix.AppendLiteral("<unknown URI>");
329 } else {
330 pathPrefix.Append(aCounter.URI());
333 pathPrefix.AppendLiteral(")/");
335 ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter, aSharedSurfaces);
337 ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
340 static void ReportSurfaces(
341 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
342 const nsACString& aPathPrefix, const ImageMemoryCounter& aCounter,
343 layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
344 for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
345 nsAutoCString surfacePathPrefix(aPathPrefix);
346 if (counter.IsLocked()) {
347 surfacePathPrefix.AppendLiteral("locked/");
348 } else {
349 surfacePathPrefix.AppendLiteral("unlocked/");
351 if (counter.IsFactor2()) {
352 surfacePathPrefix.AppendLiteral("factor2/");
354 if (counter.CannotSubstitute()) {
355 surfacePathPrefix.AppendLiteral("cannot_substitute/");
357 surfacePathPrefix.AppendLiteral("types=");
358 surfacePathPrefix.AppendInt(counter.Values().SurfaceTypes(), 16);
359 surfacePathPrefix.AppendLiteral("/surface(");
360 surfacePathPrefix.AppendInt(counter.Key().Size().width);
361 surfacePathPrefix.AppendLiteral("x");
362 surfacePathPrefix.AppendInt(counter.Key().Size().height);
364 if (!counter.IsFinished()) {
365 surfacePathPrefix.AppendLiteral(", incomplete");
368 if (counter.Values().ExternalHandles() > 0) {
369 surfacePathPrefix.AppendLiteral(", handles:");
370 surfacePathPrefix.AppendInt(
371 uint32_t(counter.Values().ExternalHandles()));
374 ImageMemoryReporter::AppendSharedSurfacePrefix(surfacePathPrefix, counter,
375 aSharedSurfaces);
377 if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
378 PlaybackType playback = counter.Key().Playback();
379 if (playback == PlaybackType::eAnimated) {
380 if (StaticPrefs::image_mem_debug_reporting()) {
381 surfacePathPrefix.AppendPrintf(
382 " (animation %4u)", uint32_t(counter.Values().FrameIndex()));
383 } else {
384 surfacePathPrefix.AppendLiteral(" (animation)");
388 if (counter.Key().Flags() != DefaultSurfaceFlags()) {
389 surfacePathPrefix.AppendLiteral(", flags:");
390 surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
391 /* aRadix = */ 16);
394 if (counter.Key().SVGContext()) {
395 const SVGImageContext& context = counter.Key().SVGContext().ref();
396 surfacePathPrefix.AppendLiteral(", svgContext:[ ");
397 if (context.GetViewportSize()) {
398 const CSSIntSize& size = context.GetViewportSize().ref();
399 surfacePathPrefix.AppendLiteral("viewport=(");
400 surfacePathPrefix.AppendInt(size.width);
401 surfacePathPrefix.AppendLiteral("x");
402 surfacePathPrefix.AppendInt(size.height);
403 surfacePathPrefix.AppendLiteral(") ");
405 if (context.GetPreserveAspectRatio()) {
406 nsAutoString aspect;
407 context.GetPreserveAspectRatio()->ToString(aspect);
408 surfacePathPrefix.AppendLiteral("preserveAspectRatio=(");
409 LossyAppendUTF16toASCII(aspect, surfacePathPrefix);
410 surfacePathPrefix.AppendLiteral(") ");
412 if (context.GetContextPaint()) {
413 const SVGEmbeddingContextPaint* paint = context.GetContextPaint();
414 surfacePathPrefix.AppendLiteral("contextPaint=(");
415 if (paint->GetFill()) {
416 surfacePathPrefix.AppendLiteral(" fill=");
417 surfacePathPrefix.AppendInt(paint->GetFill()->ToABGR(), 16);
419 if (paint->GetFillOpacity()) {
420 surfacePathPrefix.AppendLiteral(" fillOpa=");
421 surfacePathPrefix.AppendFloat(paint->GetFillOpacity());
423 if (paint->GetStroke()) {
424 surfacePathPrefix.AppendLiteral(" stroke=");
425 surfacePathPrefix.AppendInt(paint->GetStroke()->ToABGR(), 16);
427 if (paint->GetStrokeOpacity()) {
428 surfacePathPrefix.AppendLiteral(" strokeOpa=");
429 surfacePathPrefix.AppendFloat(paint->GetStrokeOpacity());
431 surfacePathPrefix.AppendLiteral(" ) ");
433 surfacePathPrefix.AppendLiteral("]");
435 } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
436 surfacePathPrefix.AppendLiteral(", compositing frame");
437 } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) {
438 surfacePathPrefix.AppendLiteral(", compositing prev frame");
439 } else {
440 MOZ_ASSERT_UNREACHABLE("Unknown counter type");
443 surfacePathPrefix.AppendLiteral(")/");
445 ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values());
449 static void ReportTotal(nsIHandleReportCallback* aHandleReport,
450 nsISupports* aData, bool aExplicit,
451 const char* aPathPrefix, const char* aPathInfix,
452 const MemoryTotal& aTotal) {
453 nsAutoCString pathPrefix;
454 if (aExplicit) {
455 pathPrefix.AppendLiteral("explicit/");
457 pathPrefix.Append(aPathPrefix);
459 nsAutoCString rasterUsedPrefix(pathPrefix);
460 rasterUsedPrefix.AppendLiteral("/raster/used/");
461 rasterUsedPrefix.Append(aPathInfix);
462 ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster());
464 nsAutoCString rasterUnusedPrefix(pathPrefix);
465 rasterUnusedPrefix.AppendLiteral("/raster/unused/");
466 rasterUnusedPrefix.Append(aPathInfix);
467 ReportValues(aHandleReport, aData, rasterUnusedPrefix,
468 aTotal.UnusedRaster());
470 nsAutoCString vectorUsedPrefix(pathPrefix);
471 vectorUsedPrefix.AppendLiteral("/vector/used/");
472 vectorUsedPrefix.Append(aPathInfix);
473 ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector());
475 nsAutoCString vectorUnusedPrefix(pathPrefix);
476 vectorUnusedPrefix.AppendLiteral("/vector/unused/");
477 vectorUnusedPrefix.Append(aPathInfix);
478 ReportValues(aHandleReport, aData, vectorUnusedPrefix,
479 aTotal.UnusedVector());
482 static void ReportValues(nsIHandleReportCallback* aHandleReport,
483 nsISupports* aData, const nsACString& aPathPrefix,
484 const MemoryCounter& aCounter) {
485 ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);
487 ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "decoded-heap",
488 "Decoded image data which is stored on the heap.",
489 aCounter.DecodedHeap());
491 ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
492 "decoded-nonheap",
493 "Decoded image data which isn't stored on the heap.",
494 aCounter.DecodedNonHeap());
496 // We don't know for certain whether or not it is on the heap, so let's
497 // just report it as non-heap for reporting purposes.
498 ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
499 "decoded-unknown",
500 "Decoded image data which is unknown to be on the heap or not.",
501 aCounter.DecodedUnknown());
504 static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
505 nsISupports* aData,
506 const nsACString& aPathPrefix,
507 const MemoryCounter& aCounter) {
508 ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "source",
509 "Raster image source data and vector image documents.",
510 aCounter.Source());
513 static void ReportValue(nsIHandleReportCallback* aHandleReport,
514 nsISupports* aData, int32_t aKind,
515 const nsACString& aPathPrefix,
516 const char* aPathSuffix, const char* aDescription,
517 size_t aValue) {
518 if (aValue == 0) {
519 return;
522 nsAutoCString desc(aDescription);
523 nsAutoCString path(aPathPrefix);
524 path.Append(aPathSuffix);
526 aHandleReport->Callback(EmptyCString(), path, aKind, UNITS_BYTES, aValue,
527 desc, aData);
530 static void RecordCounterForRequest(imgRequest* aRequest,
531 nsTArray<ImageMemoryCounter>* aArray,
532 bool aIsUsed) {
533 SizeOfState state(ImagesMallocSizeOf);
534 RefPtr<image::Image> image = aRequest->GetImage();
535 if (image) {
536 ImageMemoryCounter counter(aRequest, image, state, aIsUsed);
537 aArray->AppendElement(std::move(counter));
538 } else {
539 // We can at least record some information about the image from the
540 // request, and mark it as not knowing the image type yet.
541 ImageMemoryCounter counter(aRequest, state, aIsUsed);
542 aArray->AppendElement(std::move(counter));
547 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
549 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, nsIProgressEventSink,
550 nsIChannelEventSink, nsIInterfaceRequestor)
552 NS_IMETHODIMP
553 nsProgressNotificationProxy::OnProgress(nsIRequest* request, int64_t progress,
554 int64_t progressMax) {
555 nsCOMPtr<nsILoadGroup> loadGroup;
556 request->GetLoadGroup(getter_AddRefs(loadGroup));
558 nsCOMPtr<nsIProgressEventSink> target;
559 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
560 NS_GET_IID(nsIProgressEventSink),
561 getter_AddRefs(target));
562 if (!target) {
563 return NS_OK;
565 return target->OnProgress(mImageRequest, progress, progressMax);
568 NS_IMETHODIMP
569 nsProgressNotificationProxy::OnStatus(nsIRequest* request, nsresult status,
570 const char16_t* statusArg) {
571 nsCOMPtr<nsILoadGroup> loadGroup;
572 request->GetLoadGroup(getter_AddRefs(loadGroup));
574 nsCOMPtr<nsIProgressEventSink> target;
575 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
576 NS_GET_IID(nsIProgressEventSink),
577 getter_AddRefs(target));
578 if (!target) {
579 return NS_OK;
581 return target->OnStatus(mImageRequest, status, statusArg);
584 NS_IMETHODIMP
585 nsProgressNotificationProxy::AsyncOnChannelRedirect(
586 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
587 nsIAsyncVerifyRedirectCallback* cb) {
588 // Tell the original original callbacks about it too
589 nsCOMPtr<nsILoadGroup> loadGroup;
590 newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
591 nsCOMPtr<nsIChannelEventSink> target;
592 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
593 NS_GET_IID(nsIChannelEventSink),
594 getter_AddRefs(target));
595 if (!target) {
596 cb->OnRedirectVerifyCallback(NS_OK);
597 return NS_OK;
600 // Delegate to |target| if set, reusing |cb|
601 return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
604 NS_IMETHODIMP
605 nsProgressNotificationProxy::GetInterface(const nsIID& iid, void** result) {
606 if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
607 *result = static_cast<nsIProgressEventSink*>(this);
608 NS_ADDREF_THIS();
609 return NS_OK;
611 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
612 *result = static_cast<nsIChannelEventSink*>(this);
613 NS_ADDREF_THIS();
614 return NS_OK;
616 if (mOriginalCallbacks) {
617 return mOriginalCallbacks->GetInterface(iid, result);
619 return NS_NOINTERFACE;
622 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,
623 imgLoader* aLoader, const ImageCacheKey& aKey,
624 imgRequest** aRequest, imgCacheEntry** aEntry) {
625 RefPtr<imgRequest> request = new imgRequest(aLoader, aKey);
626 RefPtr<imgCacheEntry> entry =
627 new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
628 aLoader->AddToUncachedImages(request);
629 request.forget(aRequest);
630 entry.forget(aEntry);
633 static bool ShouldRevalidateEntry(imgCacheEntry* aEntry, nsLoadFlags aFlags,
634 bool aHasExpired) {
635 bool bValidateEntry = false;
637 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) {
638 return false;
641 if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
642 bValidateEntry = true;
643 } else if (aEntry->GetMustValidate()) {
644 bValidateEntry = true;
645 } else if (aHasExpired) {
646 // The cache entry has expired... Determine whether the stale cache
647 // entry can be used without validation...
648 if (aFlags &
649 (nsIRequest::VALIDATE_NEVER | nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
650 // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
651 // entries to be used unless they have been explicitly marked to
652 // indicate that revalidation is necessary.
653 bValidateEntry = false;
655 } else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
656 // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
657 // the entry must be revalidated.
658 bValidateEntry = true;
662 return bValidateEntry;
665 /* Call content policies on cached images that went through a redirect */
666 static bool ShouldLoadCachedImage(imgRequest* aImgRequest,
667 Document* aLoadingDocument,
668 nsIPrincipal* aTriggeringPrincipal,
669 nsContentPolicyType aPolicyType,
670 bool aSendCSPViolationReports) {
671 /* Call content policies on cached images - Bug 1082837
672 * Cached images are keyed off of the first uri in a redirect chain.
673 * Hence content policies don't get a chance to test the intermediate hops
674 * or the final desitnation. Here we test the final destination using
675 * mFinalURI off of the imgRequest and passing it into content policies.
676 * For Mixed Content Blocker, we do an additional check to determine if any
677 * of the intermediary hops went through an insecure redirect with the
678 * mHadInsecureRedirect flag
680 bool insecureRedirect = aImgRequest->HadInsecureRedirect();
681 nsCOMPtr<nsIURI> contentLocation;
682 aImgRequest->GetFinalURI(getter_AddRefs(contentLocation));
683 nsresult rv;
685 nsCOMPtr<nsIPrincipal> loadingPrincipal =
686 aLoadingDocument ? aLoadingDocument->NodePrincipal()
687 : aTriggeringPrincipal;
688 // If there is no context and also no triggeringPrincipal, then we use a fresh
689 // nullPrincipal as the loadingPrincipal because we can not create a loadinfo
690 // without a valid loadingPrincipal.
691 if (!loadingPrincipal) {
692 loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
695 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
696 loadingPrincipal, aTriggeringPrincipal, aLoadingDocument,
697 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, aPolicyType);
699 secCheckLoadInfo->SetSendCSPViolationEvents(aSendCSPViolationReports);
701 int16_t decision = nsIContentPolicy::REJECT_REQUEST;
702 rv = NS_CheckContentLoadPolicy(contentLocation, secCheckLoadInfo,
703 EmptyCString(), // mime guess
704 &decision, nsContentUtils::GetContentPolicy());
705 if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
706 return false;
709 // We call all Content Policies above, but we also have to call mcb
710 // individually to check the intermediary redirect hops are secure.
711 if (insecureRedirect) {
712 // Bug 1314356: If the image ended up in the cache upgraded by HSTS and the
713 // page uses upgrade-inscure-requests it had an insecure redirect
714 // (http->https). We need to invalidate the image and reload it because
715 // mixed content blocker only bails if upgrade-insecure-requests is set on
716 // the doc and the resource load is http: which would result in an incorrect
717 // mixed content warning.
718 nsCOMPtr<nsIDocShell> docShell =
719 NS_CP_GetDocShellFromContext(ToSupports(aLoadingDocument));
720 if (docShell) {
721 Document* document = docShell->GetDocument();
722 if (document && document->GetUpgradeInsecureRequests(false)) {
723 return false;
727 if (!aTriggeringPrincipal || !aTriggeringPrincipal->IsSystemPrincipal()) {
728 // reset the decision for mixed content blocker check
729 decision = nsIContentPolicy::REJECT_REQUEST;
730 rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect, contentLocation,
731 secCheckLoadInfo,
732 EmptyCString(), // mime guess
733 true, // aReportError
734 &decision);
735 if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
736 return false;
741 return true;
744 // Returns true if this request is compatible with the given CORS mode on the
745 // given loading principal, and false if the request may not be reused due
746 // to CORS. Also checks the Referrer Policy, since requests with different
747 // referrers/policies may generate different responses.
748 static bool ValidateSecurityInfo(imgRequest* request, bool forcePrincipalCheck,
749 int32_t corsmode,
750 nsIPrincipal* triggeringPrincipal,
751 Document* aLoadingDocument,
752 nsContentPolicyType aPolicyType) {
753 // If the entry's CORS mode doesn't match, or the CORS mode matches but the
754 // document principal isn't the same, we can't use this request.
755 if (request->GetCORSMode() != corsmode) {
756 return false;
758 if (request->GetCORSMode() != imgIRequest::CORS_NONE || forcePrincipalCheck) {
759 nsCOMPtr<nsIPrincipal> otherprincipal = request->GetTriggeringPrincipal();
761 // If we previously had a principal, but we don't now, we can't use this
762 // request.
763 if (otherprincipal && !triggeringPrincipal) {
764 return false;
767 if (otherprincipal && triggeringPrincipal) {
768 bool equals = false;
769 otherprincipal->Equals(triggeringPrincipal, &equals);
770 if (!equals) {
771 return false;
776 // Content Policy Check on Cached Images
777 return ShouldLoadCachedImage(request, aLoadingDocument, triggeringPrincipal,
778 aPolicyType,
779 /* aSendCSPViolationReports */ false);
782 static nsresult NewImageChannel(
783 nsIChannel** aResult,
784 // If aForcePrincipalCheckForCacheEntry is true, then we will
785 // force a principal check even when not using CORS before
786 // assuming we have a cache hit on a cache entry that we
787 // create for this channel. This is an out param that should
788 // be set to true if this channel ends up depending on
789 // aTriggeringPrincipal and false otherwise.
790 bool* aForcePrincipalCheckForCacheEntry, nsIURI* aURI,
791 nsIURI* aInitialDocumentURI, int32_t aCORSMode,
792 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
793 nsLoadFlags aLoadFlags, nsContentPolicyType aPolicyType,
794 nsIPrincipal* aTriggeringPrincipal, nsINode* aRequestingNode,
795 bool aRespectPrivacy) {
796 MOZ_ASSERT(aResult);
798 nsresult rv;
799 nsCOMPtr<nsIHttpChannel> newHttpChannel;
801 nsCOMPtr<nsIInterfaceRequestor> callbacks;
803 if (aLoadGroup) {
804 // Get the notification callbacks from the load group for the new channel.
806 // XXX: This is not exactly correct, because the network request could be
807 // referenced by multiple windows... However, the new channel needs
808 // something. So, using the 'first' notification callbacks is better
809 // than nothing...
811 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
814 // Pass in a nullptr loadgroup because this is the underlying network
815 // request. This request may be referenced by several proxy image requests
816 // (possibly in different documents).
817 // If all of the proxy requests are canceled then this request should be
818 // canceled too.
821 nsSecurityFlags securityFlags =
822 aCORSMode == imgIRequest::CORS_NONE
823 ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
824 : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
825 if (aCORSMode == imgIRequest::CORS_ANONYMOUS) {
826 securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
827 } else if (aCORSMode == imgIRequest::CORS_USE_CREDENTIALS) {
828 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
830 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
832 // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a
833 // node and a principal. This is for things like background images that are
834 // specified by user stylesheets, where the document is being styled, but
835 // the principal is that of the user stylesheet.
836 if (aRequestingNode && aTriggeringPrincipal) {
837 rv = NS_NewChannelWithTriggeringPrincipal(aResult, aURI, aRequestingNode,
838 aTriggeringPrincipal,
839 securityFlags, aPolicyType,
840 nullptr, // PerformanceStorage
841 nullptr, // loadGroup
842 callbacks, aLoadFlags);
844 if (NS_FAILED(rv)) {
845 return rv;
848 if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
849 // If this is a favicon loading, we will use the originAttributes from the
850 // triggeringPrincipal as the channel's originAttributes. This allows the
851 // favicon loading from XUL will use the correct originAttributes.
853 nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
854 rv = loadInfo->SetOriginAttributes(
855 aTriggeringPrincipal->OriginAttributesRef());
857 } else {
858 // either we are loading something inside a document, in which case
859 // we should always have a requestingNode, or we are loading something
860 // outside a document, in which case the triggeringPrincipal and
861 // triggeringPrincipal should always be the systemPrincipal.
862 // However, there are exceptions: one is Notifications which create a
863 // channel in the parent process in which case we can't get a
864 // requestingNode.
865 rv = NS_NewChannel(aResult, aURI, nsContentUtils::GetSystemPrincipal(),
866 securityFlags, aPolicyType,
867 nullptr, // nsICookieJarSettings
868 nullptr, // PerformanceStorage
869 nullptr, // loadGroup
870 callbacks, aLoadFlags);
872 if (NS_FAILED(rv)) {
873 return rv;
876 // Use the OriginAttributes from the loading principal, if one is available,
877 // and adjust the private browsing ID based on what kind of load the caller
878 // has asked us to perform.
879 OriginAttributes attrs;
880 if (aTriggeringPrincipal) {
881 attrs = aTriggeringPrincipal->OriginAttributesRef();
883 attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0;
885 nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
886 rv = loadInfo->SetOriginAttributes(attrs);
889 if (NS_FAILED(rv)) {
890 return rv;
893 // only inherit if we have a principal
894 *aForcePrincipalCheckForCacheEntry =
895 aTriggeringPrincipal && nsContentUtils::ChannelShouldInheritPrincipal(
896 aTriggeringPrincipal, aURI,
897 /* aInheritForAboutBlank */ false,
898 /* aForceInherit */ false);
900 // Initialize HTTP-specific attributes
901 newHttpChannel = do_QueryInterface(*aResult);
902 if (newHttpChannel) {
903 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
904 do_QueryInterface(newHttpChannel);
905 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
906 rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
907 MOZ_ASSERT(NS_SUCCEEDED(rv));
908 if (aReferrerInfo) {
909 DebugOnly<nsresult> rv = newHttpChannel->SetReferrerInfo(aReferrerInfo);
910 MOZ_ASSERT(NS_SUCCEEDED(rv));
914 // Image channels are loaded by default with reduced priority.
915 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
916 if (p) {
917 uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
919 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
920 ++priority; // further reduce priority for background loads
923 p->AdjustPriority(priority);
926 // Create a new loadgroup for this new channel, using the old group as
927 // the parent. The indirection keeps the channel insulated from cancels,
928 // but does allow a way for this revalidation to be associated with at
929 // least one base load group for scheduling/caching purposes.
931 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
932 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
933 if (childLoadGroup) {
934 childLoadGroup->SetParentLoadGroup(aLoadGroup);
936 (*aResult)->SetLoadGroup(loadGroup);
938 return NS_OK;
941 static uint32_t SecondsFromPRTime(PRTime aTime) {
942 return nsContentUtils::SecondsFromPRTime(aTime);
945 /* static */
946 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
947 bool forcePrincipalCheck)
948 : mLoader(loader),
949 mRequest(request),
950 mDataSize(0),
951 mTouchedTime(SecondsFromPRTime(PR_Now())),
952 mLoadTime(SecondsFromPRTime(PR_Now())),
953 mExpiryTime(0),
954 mMustValidate(false),
955 // We start off as evicted so we don't try to update the cache.
956 // PutIntoCache will set this to false.
957 mEvicted(true),
958 mHasNoProxies(true),
959 mForcePrincipalCheck(forcePrincipalCheck) {}
961 imgCacheEntry::~imgCacheEntry() {
962 LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
965 void imgCacheEntry::Touch(bool updateTime /* = true */) {
966 LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
968 if (updateTime) {
969 mTouchedTime = SecondsFromPRTime(PR_Now());
972 UpdateCache();
975 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) {
976 // Don't update the cache if we've been removed from it or it doesn't care
977 // about our size or usage.
978 if (!Evicted() && HasNoProxies()) {
979 mLoader->CacheEntriesChanged(mRequest->IsChrome(), diff);
983 void imgCacheEntry::UpdateLoadTime() {
984 mLoadTime = SecondsFromPRTime(PR_Now());
987 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) {
988 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
989 if (hasNoProxies) {
990 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true", "uri",
991 mRequest->CacheKey().URI());
992 } else {
993 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false",
994 "uri", mRequest->CacheKey().URI());
998 mHasNoProxies = hasNoProxies;
1001 imgCacheQueue::imgCacheQueue() : mDirty(false), mSize(0) {}
1003 void imgCacheQueue::UpdateSize(int32_t diff) { mSize += diff; }
1005 uint32_t imgCacheQueue::GetSize() const { return mSize; }
1007 void imgCacheQueue::Remove(imgCacheEntry* entry) {
1008 uint64_t index = mQueue.IndexOf(entry);
1009 if (index == queueContainer::NoIndex) {
1010 return;
1013 mSize -= mQueue[index]->GetDataSize();
1015 // If the queue is clean and this is the first entry,
1016 // then we can efficiently remove the entry without
1017 // dirtying the sort order.
1018 if (!IsDirty() && index == 0) {
1019 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1020 mQueue.RemoveLastElement();
1021 return;
1024 // Remove from the middle of the list. This potentially
1025 // breaks the binary heap sort order.
1026 mQueue.RemoveElementAt(index);
1028 // If we only have one entry or the queue is empty, though,
1029 // then the sort order is still effectively good. Simply
1030 // refresh the list to clear the dirty flag.
1031 if (mQueue.Length() <= 1) {
1032 Refresh();
1033 return;
1036 // Otherwise we must mark the queue dirty and potentially
1037 // trigger an expensive sort later.
1038 MarkDirty();
1041 void imgCacheQueue::Push(imgCacheEntry* entry) {
1042 mSize += entry->GetDataSize();
1044 RefPtr<imgCacheEntry> refptr(entry);
1045 mQueue.AppendElement(std::move(refptr));
1046 // If we're not dirty already, then we can efficiently add this to the
1047 // binary heap immediately. This is only O(log n).
1048 if (!IsDirty()) {
1049 std::push_heap(mQueue.begin(), mQueue.end(),
1050 imgLoader::CompareCacheEntries);
1054 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() {
1055 if (mQueue.IsEmpty()) {
1056 return nullptr;
1058 if (IsDirty()) {
1059 Refresh();
1062 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1063 RefPtr<imgCacheEntry> entry = mQueue.PopLastElement();
1065 mSize -= entry->GetDataSize();
1066 return entry.forget();
1069 void imgCacheQueue::Refresh() {
1070 // Resort the list. This is an O(3 * n) operation and best avoided
1071 // if possible.
1072 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1073 mDirty = false;
1076 void imgCacheQueue::MarkDirty() { mDirty = true; }
1078 bool imgCacheQueue::IsDirty() { return mDirty; }
1080 uint32_t imgCacheQueue::GetNumElements() const { return mQueue.Length(); }
1082 bool imgCacheQueue::Contains(imgCacheEntry* aEntry) const {
1083 return mQueue.Contains(aEntry);
1086 imgCacheQueue::iterator imgCacheQueue::begin() { return mQueue.begin(); }
1088 imgCacheQueue::const_iterator imgCacheQueue::begin() const {
1089 return mQueue.begin();
1092 imgCacheQueue::iterator imgCacheQueue::end() { return mQueue.end(); }
1094 imgCacheQueue::const_iterator imgCacheQueue::end() const {
1095 return mQueue.end();
1098 nsresult imgLoader::CreateNewProxyForRequest(
1099 imgRequest* aRequest, nsIURI* aURI, nsILoadGroup* aLoadGroup,
1100 Document* aLoadingDocument, imgINotificationObserver* aObserver,
1101 nsLoadFlags aLoadFlags, imgRequestProxy** _retval) {
1102 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest",
1103 "imgRequest", aRequest);
1105 /* XXX If we move decoding onto separate threads, we should save off the
1106 calling thread here and pass it off to |proxyRequest| so that it call
1107 proxy calls to |aObserver|.
1110 RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy();
1112 /* It is important to call |SetLoadFlags()| before calling |Init()| because
1113 |Init()| adds the request to the loadgroup.
1115 proxyRequest->SetLoadFlags(aLoadFlags);
1117 // init adds itself to imgRequest's list of observers
1118 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aLoadingDocument, aURI,
1119 aObserver);
1120 if (NS_WARN_IF(NS_FAILED(rv))) {
1121 return rv;
1124 proxyRequest.forget(_retval);
1125 return NS_OK;
1128 class imgCacheExpirationTracker final
1129 : public nsExpirationTracker<imgCacheEntry, 3> {
1130 enum { TIMEOUT_SECONDS = 10 };
1132 public:
1133 imgCacheExpirationTracker();
1135 protected:
1136 void NotifyExpired(imgCacheEntry* entry) override;
1139 imgCacheExpirationTracker::imgCacheExpirationTracker()
1140 : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000,
1141 "imgCacheExpirationTracker") {}
1143 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) {
1144 // Hold on to a reference to this entry, because the expiration tracker
1145 // mechanism doesn't.
1146 RefPtr<imgCacheEntry> kungFuDeathGrip(entry);
1148 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1149 RefPtr<imgRequest> req = entry->GetRequest();
1150 if (req) {
1151 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired",
1152 "entry", req->CacheKey().URI());
1156 // We can be called multiple times on the same entry. Don't do work multiple
1157 // times.
1158 if (!entry->Evicted()) {
1159 entry->Loader()->RemoveFromCache(entry);
1162 entry->Loader()->VerifyCacheSizes();
1165 ///////////////////////////////////////////////////////////////////////////////
1166 // imgLoader
1167 ///////////////////////////////////////////////////////////////////////////////
1169 double imgLoader::sCacheTimeWeight;
1170 uint32_t imgLoader::sCacheMaxSize;
1171 imgMemoryReporter* imgLoader::sMemReporter;
1173 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
1174 nsISupportsWeakReference, nsIObserver)
1176 static imgLoader* gNormalLoader = nullptr;
1177 static imgLoader* gPrivateBrowsingLoader = nullptr;
1179 /* static */
1180 mozilla::CORSMode imgLoader::ConvertToCORSMode(uint32_t aImgCORS) {
1181 switch (aImgCORS) {
1182 case imgIRequest::CORS_NONE:
1183 return CORSMode::CORS_NONE;
1184 case imgIRequest::CORS_ANONYMOUS:
1185 return CORSMode::CORS_ANONYMOUS;
1186 case imgIRequest::CORS_USE_CREDENTIALS:
1187 return CORSMode::CORS_USE_CREDENTIALS;
1190 MOZ_ASSERT(false, "Unexpected imgIRequest CORS value");
1191 return CORSMode::CORS_NONE;
1194 /* static */
1195 already_AddRefed<imgLoader> imgLoader::CreateImageLoader() {
1196 // In some cases, such as xpctests, XPCOM modules are not automatically
1197 // initialized. We need to make sure that our module is initialized before
1198 // we hand out imgLoader instances and code starts using them.
1199 mozilla::image::EnsureModuleInitialized();
1201 RefPtr<imgLoader> loader = new imgLoader();
1202 loader->Init();
1204 return loader.forget();
1207 imgLoader* imgLoader::NormalLoader() {
1208 if (!gNormalLoader) {
1209 gNormalLoader = CreateImageLoader().take();
1211 return gNormalLoader;
1214 imgLoader* imgLoader::PrivateBrowsingLoader() {
1215 if (!gPrivateBrowsingLoader) {
1216 gPrivateBrowsingLoader = CreateImageLoader().take();
1217 gPrivateBrowsingLoader->RespectPrivacyNotifications();
1219 return gPrivateBrowsingLoader;
1222 imgLoader::imgLoader()
1223 : mUncachedImagesMutex("imgLoader::UncachedImages"),
1224 mRespectPrivacy(false) {
1225 sMemReporter->AddRef();
1226 sMemReporter->RegisterLoader(this);
1229 imgLoader::~imgLoader() {
1230 ClearChromeImageCache();
1231 ClearImageCache();
1233 // If there are any of our imgRequest's left they are in the uncached
1234 // images set, so clear their pointer to us.
1235 MutexAutoLock lock(mUncachedImagesMutex);
1236 for (auto iter = mUncachedImages.Iter(); !iter.Done(); iter.Next()) {
1237 nsPtrHashKey<imgRequest>* entry = iter.Get();
1238 RefPtr<imgRequest> req = entry->GetKey();
1239 req->ClearLoader();
1242 sMemReporter->UnregisterLoader(this);
1243 sMemReporter->Release();
1246 void imgLoader::VerifyCacheSizes() {
1247 #ifdef DEBUG
1248 if (!mCacheTracker) {
1249 return;
1252 uint32_t cachesize = mCache.Count() + mChromeCache.Count();
1253 uint32_t queuesize =
1254 mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
1255 uint32_t trackersize = 0;
1256 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
1257 it.Next();) {
1258 trackersize++;
1260 MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
1261 MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
1262 #endif
1265 imgLoader::imgCacheTable& imgLoader::GetCache(bool aForChrome) {
1266 return aForChrome ? mChromeCache : mCache;
1269 imgLoader::imgCacheTable& imgLoader::GetCache(const ImageCacheKey& aKey) {
1270 return GetCache(aKey.IsChrome());
1273 imgCacheQueue& imgLoader::GetCacheQueue(bool aForChrome) {
1274 return aForChrome ? mChromeCacheQueue : mCacheQueue;
1277 imgCacheQueue& imgLoader::GetCacheQueue(const ImageCacheKey& aKey) {
1278 return GetCacheQueue(aKey.IsChrome());
1281 void imgLoader::GlobalInit() {
1282 sCacheTimeWeight = StaticPrefs::image_cache_timeweight_AtStartup() / 1000.0;
1283 int32_t cachesize = StaticPrefs::image_cache_size_AtStartup();
1284 sCacheMaxSize = cachesize > 0 ? cachesize : 0;
1286 sMemReporter = new imgMemoryReporter();
1287 RegisterStrongAsyncMemoryReporter(sMemReporter);
1288 RegisterImagesContentUsedUncompressedDistinguishedAmount(
1289 imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
1292 void imgLoader::ShutdownMemoryReporter() {
1293 UnregisterImagesContentUsedUncompressedDistinguishedAmount();
1294 UnregisterStrongMemoryReporter(sMemReporter);
1297 nsresult imgLoader::InitCache() {
1298 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1299 if (!os) {
1300 return NS_ERROR_FAILURE;
1303 os->AddObserver(this, "memory-pressure", false);
1304 os->AddObserver(this, "chrome-flush-caches", false);
1305 os->AddObserver(this, "last-pb-context-exited", false);
1306 os->AddObserver(this, "profile-before-change", false);
1307 os->AddObserver(this, "xpcom-shutdown", false);
1309 mCacheTracker = MakeUnique<imgCacheExpirationTracker>();
1311 return NS_OK;
1314 nsresult imgLoader::Init() {
1315 InitCache();
1317 return NS_OK;
1320 NS_IMETHODIMP
1321 imgLoader::RespectPrivacyNotifications() {
1322 mRespectPrivacy = true;
1323 return NS_OK;
1326 NS_IMETHODIMP
1327 imgLoader::Observe(nsISupports* aSubject, const char* aTopic,
1328 const char16_t* aData) {
1329 if (strcmp(aTopic, "memory-pressure") == 0) {
1330 MinimizeCaches();
1331 } else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1332 MinimizeCaches();
1333 ClearChromeImageCache();
1334 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
1335 if (mRespectPrivacy) {
1336 ClearImageCache();
1337 ClearChromeImageCache();
1339 } else if (strcmp(aTopic, "profile-before-change") == 0) {
1340 mCacheTracker = nullptr;
1341 } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
1342 mCacheTracker = nullptr;
1343 ShutdownMemoryReporter();
1345 } else {
1346 // (Nothing else should bring us here)
1347 MOZ_ASSERT(0, "Invalid topic received");
1350 return NS_OK;
1353 NS_IMETHODIMP
1354 imgLoader::ClearCache(bool chrome) {
1355 if (XRE_IsParentProcess()) {
1356 bool privateLoader = this == gPrivateBrowsingLoader;
1357 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1358 Unused << cp->SendClearImageCache(privateLoader, chrome);
1362 if (chrome) {
1363 return ClearChromeImageCache();
1365 return ClearImageCache();
1368 NS_IMETHODIMP
1369 imgLoader::RemoveEntriesFromPrincipal(nsIPrincipal* aPrincipal) {
1370 nsAutoString origin;
1371 nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, origin);
1372 if (NS_WARN_IF(NS_FAILED(rv))) {
1373 return rv;
1376 AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1378 imgCacheTable& cache = GetCache(aPrincipal->IsSystemPrincipal());
1379 for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1380 auto& key = iter.Key();
1382 if (key.OriginAttributesRef() !=
1383 BasePrincipal::Cast(aPrincipal)->OriginAttributesRef()) {
1384 continue;
1387 nsAutoString imageOrigin;
1388 nsresult rv = nsContentUtils::GetUTFOrigin(key.URI(), imageOrigin);
1389 if (NS_WARN_IF(NS_FAILED(rv))) {
1390 continue;
1393 if (imageOrigin == origin) {
1394 entriesToBeRemoved.AppendElement(iter.Data());
1398 for (auto& entry : entriesToBeRemoved) {
1399 if (!RemoveFromCache(entry)) {
1400 NS_WARNING(
1401 "Couldn't remove an entry from the cache in "
1402 "RemoveEntriesFromPrincipal()\n");
1406 return NS_OK;
1409 NS_IMETHODIMP
1410 imgLoader::RemoveEntry(nsIURI* aURI, Document* aDoc) {
1411 if (aURI) {
1412 OriginAttributes attrs;
1413 if (aDoc) {
1414 nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1415 if (principal) {
1416 attrs = principal->OriginAttributesRef();
1420 ImageCacheKey key(aURI, attrs, aDoc);
1421 if (RemoveFromCache(key)) {
1422 return NS_OK;
1425 return NS_ERROR_NOT_AVAILABLE;
1428 NS_IMETHODIMP
1429 imgLoader::FindEntryProperties(nsIURI* uri, Document* aDoc,
1430 nsIProperties** _retval) {
1431 *_retval = nullptr;
1433 OriginAttributes attrs;
1434 if (aDoc) {
1435 nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1436 if (principal) {
1437 attrs = principal->OriginAttributesRef();
1441 ImageCacheKey key(uri, attrs, aDoc);
1442 imgCacheTable& cache = GetCache(key);
1444 RefPtr<imgCacheEntry> entry;
1445 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1446 if (mCacheTracker && entry->HasNoProxies()) {
1447 mCacheTracker->MarkUsed(entry);
1450 RefPtr<imgRequest> request = entry->GetRequest();
1451 if (request) {
1452 nsCOMPtr<nsIProperties> properties = request->Properties();
1453 properties.forget(_retval);
1457 return NS_OK;
1460 NS_IMETHODIMP_(void)
1461 imgLoader::ClearCacheForControlledDocument(Document* aDoc) {
1462 MOZ_ASSERT(aDoc);
1463 AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1464 imgCacheTable& cache = GetCache(false);
1465 for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1466 auto& key = iter.Key();
1467 if (key.ControlledDocument() == aDoc) {
1468 entriesToBeRemoved.AppendElement(iter.Data());
1471 for (auto& entry : entriesToBeRemoved) {
1472 if (!RemoveFromCache(entry)) {
1473 NS_WARNING(
1474 "Couldn't remove an entry from the cache in "
1475 "ClearCacheForControlledDocument()\n");
1480 void imgLoader::Shutdown() {
1481 NS_IF_RELEASE(gNormalLoader);
1482 gNormalLoader = nullptr;
1483 NS_IF_RELEASE(gPrivateBrowsingLoader);
1484 gPrivateBrowsingLoader = nullptr;
1487 nsresult imgLoader::ClearChromeImageCache() {
1488 return EvictEntries(mChromeCache);
1491 nsresult imgLoader::ClearImageCache() { return EvictEntries(mCache); }
1493 void imgLoader::MinimizeCaches() {
1494 EvictEntries(mCacheQueue);
1495 EvictEntries(mChromeCacheQueue);
1498 bool imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) {
1499 imgCacheTable& cache = GetCache(aKey);
1501 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::PutIntoCache", "uri",
1502 aKey.URI());
1504 // Check to see if this request already exists in the cache. If so, we'll
1505 // replace the old version.
1506 RefPtr<imgCacheEntry> tmpCacheEntry;
1507 if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1508 MOZ_LOG(
1509 gImgLog, LogLevel::Debug,
1510 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
1511 nullptr));
1512 RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1514 // If it already exists, and we're putting the same key into the cache, we
1515 // should remove the old version.
1516 MOZ_LOG(gImgLog, LogLevel::Debug,
1517 ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element",
1518 nullptr));
1520 RemoveFromCache(aKey);
1521 } else {
1522 MOZ_LOG(gImgLog, LogLevel::Debug,
1523 ("[this=%p] imgLoader::PutIntoCache --"
1524 " Element NOT already in the cache",
1525 nullptr));
1528 cache.Put(aKey, RefPtr{entry});
1530 // We can be called to resurrect an evicted entry.
1531 if (entry->Evicted()) {
1532 entry->SetEvicted(false);
1535 // If we're resurrecting an entry with no proxies, put it back in the
1536 // tracker and queue.
1537 if (entry->HasNoProxies()) {
1538 nsresult addrv = NS_OK;
1540 if (mCacheTracker) {
1541 addrv = mCacheTracker->AddObject(entry);
1544 if (NS_SUCCEEDED(addrv)) {
1545 imgCacheQueue& queue = GetCacheQueue(aKey);
1546 queue.Push(entry);
1550 RefPtr<imgRequest> request = entry->GetRequest();
1551 request->SetIsInCache(true);
1552 RemoveFromUncachedImages(request);
1554 return true;
1557 bool imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) {
1558 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasNoProxies", "uri",
1559 aRequest->CacheKey().URI());
1561 aEntry->SetHasNoProxies(true);
1563 if (aEntry->Evicted()) {
1564 return false;
1567 imgCacheQueue& queue = GetCacheQueue(aRequest->IsChrome());
1569 nsresult addrv = NS_OK;
1571 if (mCacheTracker) {
1572 addrv = mCacheTracker->AddObject(aEntry);
1575 if (NS_SUCCEEDED(addrv)) {
1576 queue.Push(aEntry);
1579 imgCacheTable& cache = GetCache(aRequest->IsChrome());
1580 CheckCacheLimits(cache, queue);
1582 return true;
1585 bool imgLoader::SetHasProxies(imgRequest* aRequest) {
1586 VerifyCacheSizes();
1588 const ImageCacheKey& key = aRequest->CacheKey();
1589 imgCacheTable& cache = GetCache(key);
1591 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies", "uri",
1592 key.URI());
1594 RefPtr<imgCacheEntry> entry;
1595 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1596 // Make sure the cache entry is for the right request
1597 RefPtr<imgRequest> entryRequest = entry->GetRequest();
1598 if (entryRequest == aRequest && entry->HasNoProxies()) {
1599 imgCacheQueue& queue = GetCacheQueue(key);
1600 queue.Remove(entry);
1602 if (mCacheTracker) {
1603 mCacheTracker->RemoveObject(entry);
1606 entry->SetHasNoProxies(false);
1608 return true;
1612 return false;
1615 void imgLoader::CacheEntriesChanged(bool aForChrome,
1616 int32_t aSizeDiff /* = 0 */) {
1617 imgCacheQueue& queue = GetCacheQueue(aForChrome);
1618 // We only need to dirty the queue if there is any sorting
1619 // taking place. Empty or single-entry lists can't become
1620 // dirty.
1621 if (queue.GetNumElements() > 1) {
1622 queue.MarkDirty();
1624 queue.UpdateSize(aSizeDiff);
1627 void imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue) {
1628 if (queue.GetNumElements() == 0) {
1629 NS_ASSERTION(queue.GetSize() == 0,
1630 "imgLoader::CheckCacheLimits -- incorrect cache size");
1633 // Remove entries from the cache until we're back at our desired max size.
1634 while (queue.GetSize() > sCacheMaxSize) {
1635 // Remove the first entry in the queue.
1636 RefPtr<imgCacheEntry> entry(queue.Pop());
1638 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1640 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1641 RefPtr<imgRequest> req = entry->GetRequest();
1642 if (req) {
1643 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::CheckCacheLimits",
1644 "entry", req->CacheKey().URI());
1648 if (entry) {
1649 // We just popped this entry from the queue, so pass AlreadyRemoved
1650 // to avoid searching the queue again in RemoveFromCache.
1651 RemoveFromCache(entry, QueueState::AlreadyRemoved);
1656 bool imgLoader::ValidateRequestWithNewChannel(
1657 imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1658 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
1659 imgINotificationObserver* aObserver, Document* aLoadingDocument,
1660 uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
1661 nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest,
1662 nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode, bool aLinkPreload,
1663 bool* aNewChannelCreated) {
1664 // now we need to insert a new channel request object in between the real
1665 // request and the proxy that basically delays loading the image until it
1666 // gets a 304 or figures out that this needs to be a new request
1668 nsresult rv;
1670 // If we're currently in the middle of validating this request, just hand
1671 // back a proxy to it; the required work will be done for us.
1672 if (imgCacheValidator* validator = request->GetValidator()) {
1673 rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
1674 aObserver, aLoadFlags, aProxyRequest);
1675 if (NS_FAILED(rv)) {
1676 return false;
1679 if (*aProxyRequest) {
1680 imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1682 // We will send notifications from imgCacheValidator::OnStartRequest().
1683 // In the mean time, we must defer notifications because we are added to
1684 // the imgRequest's proxy list, and we can get extra notifications
1685 // resulting from methods such as StartDecoding(). See bug 579122.
1686 proxy->MarkValidating();
1688 if (aLinkPreload) {
1689 MOZ_ASSERT(aLoadingDocument);
1690 proxy->PrioritizeAsPreload();
1691 auto preloadKey = PreloadHashKey::CreateAsImage(
1692 aURI, aTriggeringPrincipal, ConvertToCORSMode(aCORSMode));
1693 proxy->NotifyOpen(preloadKey, aLoadingDocument, true);
1696 // Attach the proxy without notifying
1697 validator->AddProxy(proxy);
1700 return NS_SUCCEEDED(rv);
1702 // We will rely on Necko to cache this request when it's possible, and to
1703 // tell imgCacheValidator::OnStartRequest whether the request came from its
1704 // cache.
1705 nsCOMPtr<nsIChannel> newChannel;
1706 bool forcePrincipalCheck;
1707 rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
1708 aInitialDocumentURI, aCORSMode, aReferrerInfo,
1709 aLoadGroup, aLoadFlags, aLoadPolicyType,
1710 aTriggeringPrincipal, aLoadingDocument, mRespectPrivacy);
1711 if (NS_FAILED(rv)) {
1712 return false;
1715 if (aNewChannelCreated) {
1716 *aNewChannelCreated = true;
1719 RefPtr<imgRequestProxy> req;
1720 rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
1721 aObserver, aLoadFlags, getter_AddRefs(req));
1722 if (NS_FAILED(rv)) {
1723 return false;
1726 // Make sure that OnStatus/OnProgress calls have the right request set...
1727 RefPtr<nsProgressNotificationProxy> progressproxy =
1728 new nsProgressNotificationProxy(newChannel, req);
1729 if (!progressproxy) {
1730 return false;
1733 RefPtr<imgCacheValidator> hvc =
1734 new imgCacheValidator(progressproxy, this, request, aLoadingDocument,
1735 aInnerWindowId, forcePrincipalCheck);
1737 // Casting needed here to get past multiple inheritance.
1738 nsCOMPtr<nsIStreamListener> listener =
1739 do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
1740 NS_ENSURE_TRUE(listener, false);
1742 // We must set the notification callbacks before setting up the
1743 // CORS listener, because that's also interested inthe
1744 // notification callbacks.
1745 newChannel->SetNotificationCallbacks(hvc);
1747 request->SetValidator(hvc);
1749 // We will send notifications from imgCacheValidator::OnStartRequest().
1750 // In the mean time, we must defer notifications because we are added to
1751 // the imgRequest's proxy list, and we can get extra notifications
1752 // resulting from methods such as StartDecoding(). See bug 579122.
1753 req->MarkValidating();
1755 if (aLinkPreload) {
1756 MOZ_ASSERT(aLoadingDocument);
1757 req->PrioritizeAsPreload();
1758 auto preloadKey = PreloadHashKey::CreateAsImage(
1759 aURI, aTriggeringPrincipal, ConvertToCORSMode(aCORSMode));
1760 req->NotifyOpen(preloadKey, aLoadingDocument, true);
1763 // Add the proxy without notifying
1764 hvc->AddProxy(req);
1766 mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
1767 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1768 aLoadGroup);
1769 rv = newChannel->AsyncOpen(listener);
1770 if (NS_WARN_IF(NS_FAILED(rv))) {
1771 req->CancelAndForgetObserver(rv);
1772 // This will notify any current or future <link preload> tags. Pass the
1773 // non-open channel so that we can read loadinfo and referrer info of that
1774 // channel.
1775 req->NotifyStart(newChannel);
1776 // Use the non-channel overload of this method to force the notification to
1777 // happen. The preload request has not been assigned a channel.
1778 req->NotifyStop(rv);
1779 return false;
1782 req.forget(aProxyRequest);
1783 return true;
1786 bool imgLoader::ValidateEntry(
1787 imgCacheEntry* aEntry, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1788 nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
1789 imgINotificationObserver* aObserver, Document* aLoadingDocument,
1790 nsLoadFlags aLoadFlags, nsContentPolicyType aLoadPolicyType,
1791 bool aCanMakeNewChannel, bool* aNewChannelCreated,
1792 imgRequestProxy** aProxyRequest, nsIPrincipal* aTriggeringPrincipal,
1793 int32_t aCORSMode, bool aLinkPreload) {
1794 LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
1796 // If the expiration time is zero, then the request has not gotten far enough
1797 // to know when it will expire.
1798 uint32_t expiryTime = aEntry->GetExpiryTime();
1799 bool hasExpired =
1800 expiryTime != 0 && expiryTime <= SecondsFromPRTime(PR_Now());
1802 nsresult rv;
1804 // Special treatment for file URLs - aEntry has expired if file has changed
1805 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
1806 if (fileUrl) {
1807 uint32_t lastModTime = aEntry->GetLoadTime();
1809 nsCOMPtr<nsIFile> theFile;
1810 rv = fileUrl->GetFile(getter_AddRefs(theFile));
1811 if (NS_SUCCEEDED(rv)) {
1812 PRTime fileLastMod;
1813 rv = theFile->GetLastModifiedTime(&fileLastMod);
1814 if (NS_SUCCEEDED(rv)) {
1815 // nsIFile uses millisec, NSPR usec
1816 fileLastMod *= 1000;
1817 hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
1822 RefPtr<imgRequest> request(aEntry->GetRequest());
1824 if (!request) {
1825 return false;
1828 if (!ValidateSecurityInfo(request, aEntry->ForcePrincipalCheck(), aCORSMode,
1829 aTriggeringPrincipal, aLoadingDocument,
1830 aLoadPolicyType)) {
1831 return false;
1834 // data URIs are immutable and by their nature can't leak data, so we can
1835 // just return true in that case. Doing so would mean that shift-reload
1836 // doesn't reload data URI documents/images though (which is handy for
1837 // debugging during gecko development) so we make an exception in that case.
1838 nsAutoCString scheme;
1839 aURI->GetScheme(scheme);
1840 if (scheme.EqualsLiteral("data") &&
1841 !(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) {
1842 return true;
1845 bool validateRequest = false;
1847 // If the request's loadId is the same as the aLoadingDocument, then it is ok
1848 // to use this one because it has already been validated for this context.
1850 // XXX: nullptr seems to be a 'special' key value that indicates that NO
1851 // validation is required.
1852 // XXX: we also check the window ID because the loadID() can return a reused
1853 // pointer of a document. This can still happen for non-document image
1854 // cache entries.
1855 void* key = (void*)aLoadingDocument;
1856 uint64_t innerWindowID =
1857 aLoadingDocument ? aLoadingDocument->InnerWindowID() : 0;
1858 if (request->LoadId() != key || request->InnerWindowID() != innerWindowID) {
1859 // If we would need to revalidate this entry, but we're being told to
1860 // bypass the cache, we don't allow this entry to be used.
1861 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1862 return false;
1865 if (MOZ_UNLIKELY(ChaosMode::isActive(ChaosFeature::ImageCache))) {
1866 if (ChaosMode::randomUint32LessThan(4) < 1) {
1867 return false;
1871 // Determine whether the cache aEntry must be revalidated...
1872 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
1874 MOZ_LOG(gImgLog, LogLevel::Debug,
1875 ("imgLoader::ValidateEntry validating cache entry. "
1876 "validateRequest = %d",
1877 validateRequest));
1878 } else if (!key && MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1879 MOZ_LOG(gImgLog, LogLevel::Debug,
1880 ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
1881 "because of NULL LoadID",
1882 aURI->GetSpecOrDefault().get()));
1885 // We can't use a cached request if it comes from a different
1886 // application cache than this load is expecting.
1887 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
1888 nsCOMPtr<nsIApplicationCache> requestAppCache;
1889 nsCOMPtr<nsIApplicationCache> groupAppCache;
1890 if ((appCacheContainer = do_GetInterface(request->GetRequest()))) {
1891 appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
1893 if ((appCacheContainer = do_QueryInterface(aLoadGroup))) {
1894 appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
1897 if (requestAppCache != groupAppCache) {
1898 MOZ_LOG(gImgLog, LogLevel::Debug,
1899 ("imgLoader::ValidateEntry - Unable to use cached imgRequest "
1900 "[request=%p] because of mismatched application caches\n",
1901 address_of(request)));
1902 return false;
1905 // If the original request is still transferring don't kick off a validation
1906 // network request because it is a bit silly to issue a validation request if
1907 // the original request hasn't even finished yet. So just return true
1908 // indicating the caller can create a new proxy for the request and use it as
1909 // is.
1910 // This is an optimization but it's also required for correctness. If we don't
1911 // do this then when firing the load complete notification for the original
1912 // request that can unblock load for the document and then spin the event loop
1913 // (see the stack in bug 1641682) which then the OnStartRequest for the
1914 // validation request can fire which can call UpdateProxies and can sync
1915 // notify on the progress tracker about all existing state, which includes
1916 // load complete, so we fire a second load complete notification for the
1917 // image.
1918 // In addition, we want to validate if the original request encountered
1919 // an error for two reasons. The first being if the error was a network error
1920 // then trying to re-fetch the image might succeed. The second is more
1921 // complicated. We decide if we should fire the load or error event for img
1922 // elements depending on if the image has error in its status at the time when
1923 // the load complete notification is received, and we set error status on an
1924 // image if it encounters a network error or a decode error with no real way
1925 // to tell them apart. So if we load an image that will produce a decode error
1926 // the first time we will usually fire the load event, and then decode enough
1927 // to encounter the decode error and set the error status on the image. The
1928 // next time we reference the image in the same document the load complete
1929 // notification is replayed and this time the error status from the decode is
1930 // already present so we fire the error event instead of the load event. This
1931 // is a bug (bug 1645576) that we should fix. In order to avoid that bug in
1932 // some cases (specifically the cases when we hit this code and try to
1933 // validate the request) we make sure to validate. This avoids the bug because
1934 // when the load complete notification arrives the proxy is marked as
1935 // validating so it lies about its status and returns nothing.
1936 bool requestComplete = false;
1937 RefPtr<ProgressTracker> tracker;
1938 RefPtr<mozilla::image::Image> image = request->GetImage();
1939 if (image) {
1940 tracker = image->GetProgressTracker();
1941 } else {
1942 tracker = request->GetProgressTracker();
1944 if (tracker) {
1945 if (tracker->GetProgress() & (FLAG_LOAD_COMPLETE | FLAG_HAS_ERROR)) {
1946 requestComplete = true;
1949 if (!requestComplete) {
1950 return true;
1953 if (validateRequest && aCanMakeNewChannel) {
1954 LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate");
1956 return ValidateRequestWithNewChannel(
1957 request, aURI, aInitialDocumentURI, aReferrerInfo, aLoadGroup,
1958 aObserver, aLoadingDocument, innerWindowID, aLoadFlags, aLoadPolicyType,
1959 aProxyRequest, aTriggeringPrincipal, aCORSMode, aLinkPreload,
1960 aNewChannelCreated);
1963 return !validateRequest;
1966 bool imgLoader::RemoveFromCache(const ImageCacheKey& aKey) {
1967 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache", "uri",
1968 aKey.URI());
1970 imgCacheTable& cache = GetCache(aKey);
1971 imgCacheQueue& queue = GetCacheQueue(aKey);
1973 RefPtr<imgCacheEntry> entry;
1974 cache.Remove(aKey, getter_AddRefs(entry));
1975 if (entry) {
1976 MOZ_ASSERT(!entry->Evicted(), "Evicting an already-evicted cache entry!");
1978 // Entries with no proxies are in the tracker.
1979 if (entry->HasNoProxies()) {
1980 if (mCacheTracker) {
1981 mCacheTracker->RemoveObject(entry);
1983 queue.Remove(entry);
1986 entry->SetEvicted(true);
1988 RefPtr<imgRequest> request = entry->GetRequest();
1989 request->SetIsInCache(false);
1990 AddToUncachedImages(request);
1992 return true;
1994 return false;
1997 bool imgLoader::RemoveFromCache(imgCacheEntry* entry, QueueState aQueueState) {
1998 LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry");
2000 RefPtr<imgRequest> request = entry->GetRequest();
2001 if (request) {
2002 const ImageCacheKey& key = request->CacheKey();
2003 imgCacheTable& cache = GetCache(key);
2004 imgCacheQueue& queue = GetCacheQueue(key);
2006 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache",
2007 "entry's uri", key.URI());
2009 cache.Remove(key);
2011 if (entry->HasNoProxies()) {
2012 LOG_STATIC_FUNC(gImgLog,
2013 "imgLoader::RemoveFromCache removing from tracker");
2014 if (mCacheTracker) {
2015 mCacheTracker->RemoveObject(entry);
2017 // Only search the queue to remove the entry if its possible it might
2018 // be in the queue. If we know its not in the queue this would be
2019 // wasted work.
2020 MOZ_ASSERT_IF(aQueueState == QueueState::AlreadyRemoved,
2021 !queue.Contains(entry));
2022 if (aQueueState == QueueState::MaybeExists) {
2023 queue.Remove(entry);
2027 entry->SetEvicted(true);
2028 request->SetIsInCache(false);
2029 AddToUncachedImages(request);
2031 return true;
2034 return false;
2037 nsresult imgLoader::EvictEntries(imgCacheTable& aCacheToClear) {
2038 LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries table");
2040 // We have to make a temporary, since RemoveFromCache removes the element
2041 // from the queue, invalidating iterators.
2042 nsTArray<RefPtr<imgCacheEntry> > entries;
2043 for (auto iter = aCacheToClear.Iter(); !iter.Done(); iter.Next()) {
2044 RefPtr<imgCacheEntry>& data = iter.Data();
2045 entries.AppendElement(data);
2048 for (uint32_t i = 0; i < entries.Length(); ++i) {
2049 if (!RemoveFromCache(entries[i])) {
2050 return NS_ERROR_FAILURE;
2054 MOZ_ASSERT(aCacheToClear.Count() == 0);
2056 return NS_OK;
2059 nsresult imgLoader::EvictEntries(imgCacheQueue& aQueueToClear) {
2060 LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries queue");
2062 // We have to make a temporary, since RemoveFromCache removes the element
2063 // from the queue, invalidating iterators.
2064 nsTArray<RefPtr<imgCacheEntry> > entries(aQueueToClear.GetNumElements());
2065 for (auto i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i) {
2066 entries.AppendElement(*i);
2069 // Iterate in reverse order to minimize array copying.
2070 for (auto& entry : entries) {
2071 if (!RemoveFromCache(entry)) {
2072 return NS_ERROR_FAILURE;
2076 MOZ_ASSERT(aQueueToClear.GetNumElements() == 0);
2078 return NS_OK;
2081 void imgLoader::AddToUncachedImages(imgRequest* aRequest) {
2082 MutexAutoLock lock(mUncachedImagesMutex);
2083 mUncachedImages.PutEntry(aRequest);
2086 void imgLoader::RemoveFromUncachedImages(imgRequest* aRequest) {
2087 MutexAutoLock lock(mUncachedImagesMutex);
2088 mUncachedImages.RemoveEntry(aRequest);
2091 bool imgLoader::PreferLoadFromCache(nsIURI* aURI) const {
2092 // If we are trying to load an image from a protocol that doesn't support
2093 // caching (e.g. thumbnails via the moz-page-thumb:// protocol, or icons via
2094 // the moz-extension:// protocol), load it directly from the cache to prevent
2095 // re-decoding the image. See Bug 1373258.
2096 // TODO: Bug 1406134
2097 return aURI->SchemeIs("moz-page-thumb") || aURI->SchemeIs("moz-extension");
2100 #define LOAD_FLAGS_CACHE_MASK \
2101 (nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FROM_CACHE)
2103 #define LOAD_FLAGS_VALIDATE_MASK \
2104 (nsIRequest::VALIDATE_ALWAYS | nsIRequest::VALIDATE_NEVER | \
2105 nsIRequest::VALIDATE_ONCE_PER_SESSION)
2107 NS_IMETHODIMP
2108 imgLoader::LoadImageXPCOM(
2109 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo,
2110 nsIPrincipal* aTriggeringPrincipal, nsILoadGroup* aLoadGroup,
2111 imgINotificationObserver* aObserver, Document* aLoadingDocument,
2112 nsLoadFlags aLoadFlags, nsISupports* aCacheKey,
2113 nsContentPolicyType aContentPolicyType, imgIRequest** _retval) {
2114 // Optional parameter, so defaults to 0 (== TYPE_INVALID)
2115 if (!aContentPolicyType) {
2116 aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2118 imgRequestProxy* proxy;
2119 nsresult rv = LoadImage(
2120 aURI, aInitialDocumentURI, aReferrerInfo, aTriggeringPrincipal, 0,
2121 aLoadGroup, aObserver, aLoadingDocument, aLoadingDocument, aLoadFlags,
2122 aCacheKey, aContentPolicyType, EmptyString(),
2123 /* aUseUrgentStartForChannel */ false, /* aListPreload */ false, &proxy);
2124 *_retval = proxy;
2125 return rv;
2128 nsresult imgLoader::LoadImage(
2129 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo,
2130 nsIPrincipal* aTriggeringPrincipal, uint64_t aRequestContextID,
2131 nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
2132 nsINode* aContext, Document* aLoadingDocument, nsLoadFlags aLoadFlags,
2133 nsISupports* aCacheKey, nsContentPolicyType aContentPolicyType,
2134 const nsAString& initiatorType, bool aUseUrgentStartForChannel,
2135 bool aLinkPreload, imgRequestProxy** _retval) {
2136 VerifyCacheSizes();
2138 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
2140 if (!aURI) {
2141 return NS_ERROR_NULL_POINTER;
2144 #ifdef MOZ_GECKO_PROFILER
2145 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("imgLoader::LoadImage", NETWORK,
2146 aURI->GetSpecOrDefault());
2147 #endif
2149 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", aURI);
2151 *_retval = nullptr;
2153 RefPtr<imgRequest> request;
2155 nsresult rv;
2156 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2158 #ifdef DEBUG
2159 bool isPrivate = false;
2161 if (aLoadingDocument) {
2162 isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadingDocument);
2163 } else if (aLoadGroup) {
2164 isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadGroup);
2166 MOZ_ASSERT(isPrivate == mRespectPrivacy);
2168 if (aLoadingDocument) {
2169 // The given load group should match that of the document if given. If
2170 // that isn't the case, then we need to add more plumbing to ensure we
2171 // block the document as well.
2172 nsCOMPtr<nsILoadGroup> docLoadGroup =
2173 aLoadingDocument->GetDocumentLoadGroup();
2174 MOZ_ASSERT(docLoadGroup == aLoadGroup);
2176 #endif
2178 // Get the default load flags from the loadgroup (if possible)...
2179 if (aLoadGroup) {
2180 aLoadGroup->GetLoadFlags(&requestFlags);
2181 if (PreferLoadFromCache(aURI)) {
2182 requestFlags |= nsIRequest::LOAD_FROM_CACHE;
2186 // Merge the default load flags with those passed in via aLoadFlags.
2187 // Currently, *only* the caching, validation and background load flags
2188 // are merged...
2190 // The flags in aLoadFlags take precedence over the default flags!
2192 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
2193 // Override the default caching flags...
2194 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
2195 (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
2197 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
2198 // Override the default validation flags...
2199 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
2200 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
2202 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
2203 // Propagate background loading...
2204 requestFlags |= nsIRequest::LOAD_BACKGROUND;
2207 if (aLinkPreload) {
2208 // Set background loading if it is <link rel=preload>
2209 requestFlags |= nsIRequest::LOAD_BACKGROUND;
2212 int32_t corsmode = imgIRequest::CORS_NONE;
2213 if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) {
2214 corsmode = imgIRequest::CORS_ANONYMOUS;
2215 } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) {
2216 corsmode = imgIRequest::CORS_USE_CREDENTIALS;
2219 // Look in the preloaded images of loading document first.
2220 if (StaticPrefs::network_preload() && !aLinkPreload && aLoadingDocument) {
2221 auto key = PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal,
2222 ConvertToCORSMode(corsmode));
2223 if (RefPtr<PreloaderBase> preload =
2224 aLoadingDocument->Preloads().LookupPreload(key)) {
2225 RefPtr<imgRequestProxy> proxy = do_QueryObject(preload);
2226 MOZ_ASSERT(proxy);
2228 MOZ_LOG(gImgLog, LogLevel::Debug,
2229 ("[this=%p] imgLoader::LoadImage -- preloaded [proxy=%p]"
2230 " [document=%p]\n",
2231 this, proxy.get(), aLoadingDocument));
2233 // Removing the preload for this image to be in parity with Chromium. Any
2234 // following regular image request will be reloaded using the regular
2235 // path: image cache, http cache, network. Any following `<link
2236 // rel=preload as=image>` will start a new image preload that can be
2237 // satisfied from http cache or network.
2239 // There is a spec discussion for "preload cache", see
2240 // https://github.com/w3c/preload/issues/97. And it is also not clear how
2241 // preload image interacts with list of available images, see
2242 // https://github.com/whatwg/html/issues/4474.
2243 proxy->RemoveSelf(aLoadingDocument);
2244 proxy->NotifyUsage();
2246 imgRequest* request = proxy->GetOwner();
2247 nsresult rv =
2248 CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
2249 aObserver, requestFlags, _retval);
2250 NS_ENSURE_SUCCESS(rv, rv);
2252 imgRequestProxy* newProxy = *_retval;
2253 if (imgCacheValidator* validator = request->GetValidator()) {
2254 newProxy->MarkValidating();
2255 // Attach the proxy without notifying and this will add us to the load
2256 // group.
2257 validator->AddProxy(newProxy);
2258 } else {
2259 // It's OK to add here even if the request is done. If it is, it'll send
2260 // a OnStopRequest()and the proxy will be removed from the loadgroup in
2261 // imgRequestProxy::OnLoadComplete.
2262 newProxy->AddToLoadGroup();
2263 newProxy->NotifyListener();
2266 return NS_OK;
2270 RefPtr<imgCacheEntry> entry;
2272 // Look in the cache for our URI, and then validate it.
2273 // XXX For now ignore aCacheKey. We will need it in the future
2274 // for correctly dealing with image load requests that are a result
2275 // of post data.
2276 OriginAttributes attrs;
2277 if (aTriggeringPrincipal) {
2278 attrs = aTriggeringPrincipal->OriginAttributesRef();
2280 ImageCacheKey key(aURI, attrs, aLoadingDocument);
2281 imgCacheTable& cache = GetCache(key);
2283 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2284 bool newChannelCreated = false;
2285 if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerInfo,
2286 aLoadGroup, aObserver, aLoadingDocument, requestFlags,
2287 aContentPolicyType, true, &newChannelCreated, _retval,
2288 aTriggeringPrincipal, corsmode, aLinkPreload)) {
2289 request = entry->GetRequest();
2291 // If this entry has no proxies, its request has no reference to the
2292 // entry.
2293 if (entry->HasNoProxies()) {
2294 LOG_FUNC_WITH_PARAM(gImgLog,
2295 "imgLoader::LoadImage() adding proxyless entry",
2296 "uri", key.URI());
2297 MOZ_ASSERT(!request->HasCacheEntry(),
2298 "Proxyless entry's request has cache entry!");
2299 request->SetCacheEntry(entry);
2301 if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2302 mCacheTracker->MarkUsed(entry);
2306 entry->Touch();
2308 if (!newChannelCreated) {
2309 // This is ugly but it's needed to report CSP violations. We have 3
2310 // scenarios:
2311 // - we don't have cache. We are not in this if() stmt. A new channel is
2312 // created and that triggers the CSP checks.
2313 // - We have a cache entry and this is blocked by CSP directives.
2314 DebugOnly<bool> shouldLoad = ShouldLoadCachedImage(
2315 request, aLoadingDocument, aTriggeringPrincipal, aContentPolicyType,
2316 /* aSendCSPViolationReports */ true);
2317 MOZ_ASSERT(shouldLoad);
2319 } else {
2320 // We can't use this entry. We'll try to load it off the network, and if
2321 // successful, overwrite the old entry in the cache with a new one.
2322 entry = nullptr;
2326 // Keep the channel in this scope, so we can adjust its notificationCallbacks
2327 // later when we create the proxy.
2328 nsCOMPtr<nsIChannel> newChannel;
2329 // If we didn't get a cache hit, we need to load from the network.
2330 if (!request) {
2331 LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|");
2333 bool forcePrincipalCheck;
2334 rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
2335 aInitialDocumentURI, corsmode, aReferrerInfo,
2336 aLoadGroup, requestFlags, aContentPolicyType,
2337 aTriggeringPrincipal, aContext, mRespectPrivacy);
2338 if (NS_FAILED(rv)) {
2339 return NS_ERROR_FAILURE;
2342 MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy);
2344 NewRequestAndEntry(forcePrincipalCheck, this, key, getter_AddRefs(request),
2345 getter_AddRefs(entry));
2347 MOZ_LOG(gImgLog, LogLevel::Debug,
2348 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest"
2349 " [request=%p]\n",
2350 this, request.get()));
2352 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
2353 if (cos) {
2354 if (aUseUrgentStartForChannel && !aLinkPreload) {
2355 cos->AddClassFlags(nsIClassOfService::UrgentStart);
2358 if (StaticPrefs::network_http_tailing_enabled() &&
2359 aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
2360 cos->AddClassFlags(nsIClassOfService::Throttleable |
2361 nsIClassOfService::Tail);
2362 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
2363 if (httpChannel) {
2364 Unused << httpChannel->SetRequestContextID(aRequestContextID);
2369 nsCOMPtr<nsILoadGroup> channelLoadGroup;
2370 newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
2371 rv = request->Init(aURI, aURI, /* aHadInsecureRedirect = */ false,
2372 channelLoadGroup, newChannel, entry, aLoadingDocument,
2373 aTriggeringPrincipal, corsmode, aReferrerInfo);
2374 if (NS_FAILED(rv)) {
2375 return NS_ERROR_FAILURE;
2378 // Add the initiator type for this image load
2379 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel);
2380 if (timedChannel) {
2381 timedChannel->SetInitiatorType(initiatorType);
2384 // create the proxy listener
2385 nsCOMPtr<nsIStreamListener> listener = new ProxyListener(request.get());
2387 MOZ_LOG(gImgLog, LogLevel::Debug,
2388 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n",
2389 this));
2391 mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
2392 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
2393 aLoadGroup);
2395 nsresult openRes = newChannel->AsyncOpen(listener);
2397 if (NS_FAILED(openRes)) {
2398 MOZ_LOG(
2399 gImgLog, LogLevel::Debug,
2400 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%" PRIx32
2401 "\n",
2402 this, static_cast<uint32_t>(openRes)));
2403 request->CancelAndAbort(openRes);
2404 return openRes;
2407 // Try to add the new request into the cache.
2408 PutIntoCache(key, entry);
2409 } else {
2410 LOG_MSG_WITH_PARAM(gImgLog, "imgLoader::LoadImage |cache hit|", "request",
2411 request);
2414 // If we didn't get a proxy when validating the cache entry, we need to
2415 // create one.
2416 if (!*_retval) {
2417 // ValidateEntry() has three return values: "Is valid," "might be valid --
2418 // validating over network", and "not valid." If we don't have a _retval,
2419 // we know ValidateEntry is not validating over the network, so it's safe
2420 // to SetLoadId here because we know this request is valid for this context.
2422 // Note, however, that this doesn't guarantee the behaviour we want (one
2423 // URL maps to the same image on a page) if we load the same image in a
2424 // different tab (see bug 528003), because its load id will get re-set, and
2425 // that'll cause us to validate over the network.
2426 request->SetLoadId(aLoadingDocument);
2428 LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request.");
2429 rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
2430 aObserver, requestFlags, _retval);
2431 if (NS_FAILED(rv)) {
2432 return rv;
2435 imgRequestProxy* proxy = *_retval;
2437 // Make sure that OnStatus/OnProgress calls have the right request set, if
2438 // we did create a channel here.
2439 if (newChannel) {
2440 nsCOMPtr<nsIInterfaceRequestor> requestor(
2441 new nsProgressNotificationProxy(newChannel, proxy));
2442 if (!requestor) {
2443 return NS_ERROR_OUT_OF_MEMORY;
2445 newChannel->SetNotificationCallbacks(requestor);
2448 if (aLinkPreload) {
2449 MOZ_ASSERT(aLoadingDocument);
2450 proxy->PrioritizeAsPreload();
2451 auto preloadKey = PreloadHashKey::CreateAsImage(
2452 aURI, aTriggeringPrincipal, ConvertToCORSMode(corsmode));
2453 proxy->NotifyOpen(preloadKey, aLoadingDocument, true);
2456 // Note that it's OK to add here even if the request is done. If it is,
2457 // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
2458 // the proxy will be removed from the loadgroup.
2459 proxy->AddToLoadGroup();
2461 // If we're loading off the network, explicitly don't notify our proxy,
2462 // because necko (or things called from necko, such as imgCacheValidator)
2463 // are going to call our notifications asynchronously, and we can't make it
2464 // further asynchronous because observers might rely on imagelib completing
2465 // its work between the channel's OnStartRequest and OnStopRequest.
2466 if (!newChannel) {
2467 proxy->NotifyListener();
2470 return rv;
2473 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
2475 return NS_OK;
2478 NS_IMETHODIMP
2479 imgLoader::LoadImageWithChannelXPCOM(nsIChannel* channel,
2480 imgINotificationObserver* aObserver,
2481 Document* aLoadingDocument,
2482 nsIStreamListener** listener,
2483 imgIRequest** _retval) {
2484 nsresult result;
2485 imgRequestProxy* proxy;
2486 result = LoadImageWithChannel(channel, aObserver, aLoadingDocument, listener,
2487 &proxy);
2488 *_retval = proxy;
2489 return result;
2492 nsresult imgLoader::LoadImageWithChannel(nsIChannel* channel,
2493 imgINotificationObserver* aObserver,
2494 Document* aLoadingDocument,
2495 nsIStreamListener** listener,
2496 imgRequestProxy** _retval) {
2497 NS_ASSERTION(channel,
2498 "imgLoader::LoadImageWithChannel -- NULL channel pointer");
2500 MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
2502 LOG_SCOPE(gImgLog, "imgLoader::LoadImageWithChannel");
2503 RefPtr<imgRequest> request;
2505 nsCOMPtr<nsIURI> uri;
2506 channel->GetURI(getter_AddRefs(uri));
2508 NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
2509 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2511 OriginAttributes attrs = loadInfo->GetOriginAttributes();
2513 ImageCacheKey key(uri, attrs, aLoadingDocument);
2515 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2516 channel->GetLoadFlags(&requestFlags);
2518 if (PreferLoadFromCache(uri)) {
2519 requestFlags |= nsIRequest::LOAD_FROM_CACHE;
2522 RefPtr<imgCacheEntry> entry;
2524 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
2525 RemoveFromCache(key);
2526 } else {
2527 // Look in the cache for our URI, and then validate it.
2528 // XXX For now ignore aCacheKey. We will need it in the future
2529 // for correctly dealing with image load requests that are a result
2530 // of post data.
2531 imgCacheTable& cache = GetCache(key);
2532 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2533 // We don't want to kick off another network load. So we ask
2534 // ValidateEntry to only do validation without creating a new proxy. If
2535 // it says that the entry isn't valid any more, we'll only use the entry
2536 // we're getting if the channel is loading from the cache anyways.
2538 // XXX -- should this be changed? it's pretty much verbatim from the old
2539 // code, but seems nonsensical.
2541 // Since aCanMakeNewChannel == false, we don't need to pass content policy
2542 // type/principal/etc
2544 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2545 // if there is a loadInfo, use the right contentType, otherwise
2546 // default to the internal image type
2547 nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
2549 if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver,
2550 aLoadingDocument, requestFlags, policyType, false,
2551 nullptr, nullptr, nullptr, imgIRequest::CORS_NONE,
2552 false)) {
2553 request = entry->GetRequest();
2554 } else {
2555 nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(channel));
2556 bool bUseCacheCopy;
2558 if (cacheChan) {
2559 cacheChan->IsFromCache(&bUseCacheCopy);
2560 } else {
2561 bUseCacheCopy = false;
2564 if (!bUseCacheCopy) {
2565 entry = nullptr;
2566 } else {
2567 request = entry->GetRequest();
2571 if (request && entry) {
2572 // If this entry has no proxies, its request has no reference to
2573 // the entry.
2574 if (entry->HasNoProxies()) {
2575 LOG_FUNC_WITH_PARAM(
2576 gImgLog,
2577 "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri",
2578 key.URI());
2579 MOZ_ASSERT(!request->HasCacheEntry(),
2580 "Proxyless entry's request has cache entry!");
2581 request->SetCacheEntry(entry);
2583 if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2584 mCacheTracker->MarkUsed(entry);
2591 nsCOMPtr<nsILoadGroup> loadGroup;
2592 channel->GetLoadGroup(getter_AddRefs(loadGroup));
2594 #ifdef DEBUG
2595 if (aLoadingDocument) {
2596 // The load group of the channel should always match that of the
2597 // document if given. If that isn't the case, then we need to add more
2598 // plumbing to ensure we block the document as well.
2599 nsCOMPtr<nsILoadGroup> docLoadGroup =
2600 aLoadingDocument->GetDocumentLoadGroup();
2601 MOZ_ASSERT(docLoadGroup == loadGroup);
2603 #endif
2605 // Filter out any load flags not from nsIRequest
2606 requestFlags &= nsIRequest::LOAD_REQUESTMASK;
2608 nsresult rv = NS_OK;
2609 if (request) {
2610 // we have this in our cache already.. cancel the current (document) load
2612 // this should fire an OnStopRequest
2613 channel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
2615 *listener = nullptr; // give them back a null nsIStreamListener
2617 rv = CreateNewProxyForRequest(request, uri, loadGroup, aLoadingDocument,
2618 aObserver, requestFlags, _retval);
2619 static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
2620 } else {
2621 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2622 nsCOMPtr<nsIURI> originalURI;
2623 channel->GetOriginalURI(getter_AddRefs(originalURI));
2625 // XXX(seth): We should be able to just use |key| here, except that |key| is
2626 // constructed above with the *current URI* and not the *original URI*. I'm
2627 // pretty sure this is a bug, and it's preventing us from ever getting a
2628 // cache hit in LoadImageWithChannel when redirects are involved.
2629 ImageCacheKey originalURIKey(originalURI, attrs, aLoadingDocument);
2631 // Default to doing a principal check because we don't know who
2632 // started that load and whether their principal ended up being
2633 // inherited on the channel.
2634 NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true, this,
2635 originalURIKey, getter_AddRefs(request),
2636 getter_AddRefs(entry));
2638 // No principal specified here, because we're not passed one.
2639 // In LoadImageWithChannel, the redirects that may have been
2640 // associated with this load would have gone through necko.
2641 // We only have the final URI in ImageLib and hence don't know
2642 // if the request went through insecure redirects. But if it did,
2643 // the necko cache should have handled that (since all necko cache hits
2644 // including the redirects will go through content policy). Hence, we
2645 // can set aHadInsecureRedirect to false here.
2646 rv = request->Init(originalURI, uri, /* aHadInsecureRedirect = */ false,
2647 channel, channel, entry, aLoadingDocument, nullptr,
2648 imgIRequest::CORS_NONE, nullptr);
2649 NS_ENSURE_SUCCESS(rv, rv);
2651 RefPtr<ProxyListener> pl =
2652 new ProxyListener(static_cast<nsIStreamListener*>(request.get()));
2653 pl.forget(listener);
2655 // Try to add the new request into the cache.
2656 PutIntoCache(originalURIKey, entry);
2658 rv = CreateNewProxyForRequest(request, originalURI, loadGroup,
2659 aLoadingDocument, aObserver, requestFlags,
2660 _retval);
2662 // Explicitly don't notify our proxy, because we're loading off the
2663 // network, and necko (or things called from necko, such as
2664 // imgCacheValidator) are going to call our notifications asynchronously,
2665 // and we can't make it further asynchronous because observers might rely
2666 // on imagelib completing its work between the channel's OnStartRequest and
2667 // OnStopRequest.
2670 if (NS_FAILED(rv)) {
2671 return rv;
2674 (*_retval)->AddToLoadGroup();
2675 return rv;
2678 bool imgLoader::SupportImageWithMimeType(const char* aMimeType,
2679 AcceptedMimeTypes aAccept
2680 /* = AcceptedMimeTypes::IMAGES */) {
2681 nsAutoCString mimeType(aMimeType);
2682 ToLowerCase(mimeType);
2684 if (aAccept == AcceptedMimeTypes::IMAGES_AND_DOCUMENTS &&
2685 mimeType.EqualsLiteral("image/svg+xml")) {
2686 return true;
2689 DecoderType type = DecoderFactory::GetDecoderType(mimeType.get());
2690 return type != DecoderType::UNKNOWN;
2693 NS_IMETHODIMP
2694 imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
2695 const uint8_t* aContents, uint32_t aLength,
2696 nsACString& aContentType) {
2697 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2698 if (channel) {
2699 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2700 if (loadInfo->GetSkipContentSniffing()) {
2701 return NS_ERROR_NOT_AVAILABLE;
2704 return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
2707 /* static */
2708 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents,
2709 uint32_t aLength,
2710 nsACString& aContentType) {
2711 /* Is it a GIF? */
2712 if (aLength >= 6 &&
2713 (!strncmp(aContents, "GIF87a", 6) || !strncmp(aContents, "GIF89a", 6))) {
2714 aContentType.AssignLiteral(IMAGE_GIF);
2716 /* or a PNG? */
2717 } else if (aLength >= 8 && ((unsigned char)aContents[0] == 0x89 &&
2718 (unsigned char)aContents[1] == 0x50 &&
2719 (unsigned char)aContents[2] == 0x4E &&
2720 (unsigned char)aContents[3] == 0x47 &&
2721 (unsigned char)aContents[4] == 0x0D &&
2722 (unsigned char)aContents[5] == 0x0A &&
2723 (unsigned char)aContents[6] == 0x1A &&
2724 (unsigned char)aContents[7] == 0x0A)) {
2725 aContentType.AssignLiteral(IMAGE_PNG);
2727 /* maybe a JPEG (JFIF)? */
2728 /* JFIF files start with SOI APP0 but older files can start with SOI DQT
2729 * so we test for SOI followed by any marker, i.e. FF D8 FF
2730 * this will also work for SPIFF JPEG files if they appear in the future.
2732 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
2734 } else if (aLength >= 3 && ((unsigned char)aContents[0]) == 0xFF &&
2735 ((unsigned char)aContents[1]) == 0xD8 &&
2736 ((unsigned char)aContents[2]) == 0xFF) {
2737 aContentType.AssignLiteral(IMAGE_JPEG);
2739 /* or how about ART? */
2740 /* ART begins with JG (4A 47). Major version offset 2.
2741 * Minor version offset 3. Offset 4 must be nullptr.
2743 } else if (aLength >= 5 && ((unsigned char)aContents[0]) == 0x4a &&
2744 ((unsigned char)aContents[1]) == 0x47 &&
2745 ((unsigned char)aContents[4]) == 0x00) {
2746 aContentType.AssignLiteral(IMAGE_ART);
2748 } else if (aLength >= 2 && !strncmp(aContents, "BM", 2)) {
2749 aContentType.AssignLiteral(IMAGE_BMP);
2751 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
2752 // CURs begin with 2-byte 0 followed by 2-byte 2.
2753 } else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
2754 !memcmp(aContents, "\000\000\002\000", 4))) {
2755 aContentType.AssignLiteral(IMAGE_ICO);
2757 // WebPs always begin with RIFF, a 32-bit length, and WEBP.
2758 } else if (aLength >= 12 && !memcmp(aContents, "RIFF", 4) &&
2759 !memcmp(aContents + 8, "WEBP", 4)) {
2760 aContentType.AssignLiteral(IMAGE_WEBP);
2762 } else {
2763 /* none of the above? I give up */
2764 return NS_ERROR_NOT_AVAILABLE;
2767 return NS_OK;
2771 * proxy stream listener class used to handle multipart/x-mixed-replace
2774 #include "nsIRequest.h"
2775 #include "nsIStreamConverterService.h"
2777 NS_IMPL_ISUPPORTS(ProxyListener, nsIStreamListener,
2778 nsIThreadRetargetableStreamListener, nsIRequestObserver)
2780 ProxyListener::ProxyListener(nsIStreamListener* dest) : mDestListener(dest) {
2781 /* member initializers and constructor code */
2784 ProxyListener::~ProxyListener() { /* destructor code */
2787 /** nsIRequestObserver methods **/
2789 NS_IMETHODIMP
2790 ProxyListener::OnStartRequest(nsIRequest* aRequest) {
2791 if (!mDestListener) {
2792 return NS_ERROR_FAILURE;
2795 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2796 if (channel) {
2797 // We need to set the initiator type for the image load
2798 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel);
2799 if (timedChannel) {
2800 nsAutoString type;
2801 timedChannel->GetInitiatorType(type);
2802 if (type.IsEmpty()) {
2803 timedChannel->SetInitiatorType(u"img"_ns);
2807 nsAutoCString contentType;
2808 nsresult rv = channel->GetContentType(contentType);
2810 if (!contentType.IsEmpty()) {
2811 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
2812 in the pipeline to handle the content and pass it along to our
2813 original listener.
2815 if ("multipart/x-mixed-replace"_ns.Equals(contentType)) {
2816 nsCOMPtr<nsIStreamConverterService> convServ(
2817 do_GetService("@mozilla.org/streamConverters;1", &rv));
2818 if (NS_SUCCEEDED(rv)) {
2819 nsCOMPtr<nsIStreamListener> toListener(mDestListener);
2820 nsCOMPtr<nsIStreamListener> fromListener;
2822 rv = convServ->AsyncConvertData("multipart/x-mixed-replace", "*/*",
2823 toListener, nullptr,
2824 getter_AddRefs(fromListener));
2825 if (NS_SUCCEEDED(rv)) {
2826 mDestListener = fromListener;
2833 return mDestListener->OnStartRequest(aRequest);
2836 NS_IMETHODIMP
2837 ProxyListener::OnStopRequest(nsIRequest* aRequest, nsresult status) {
2838 if (!mDestListener) {
2839 return NS_ERROR_FAILURE;
2842 return mDestListener->OnStopRequest(aRequest, status);
2845 /** nsIStreamListener methods **/
2847 NS_IMETHODIMP
2848 ProxyListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
2849 uint64_t sourceOffset, uint32_t count) {
2850 if (!mDestListener) {
2851 return NS_ERROR_FAILURE;
2854 return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
2857 /** nsThreadRetargetableStreamListener methods **/
2858 NS_IMETHODIMP
2859 ProxyListener::CheckListenerChain() {
2860 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2861 nsresult rv = NS_OK;
2862 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2863 do_QueryInterface(mDestListener, &rv);
2864 if (retargetableListener) {
2865 rv = retargetableListener->CheckListenerChain();
2867 MOZ_LOG(
2868 gImgLog, LogLevel::Debug,
2869 ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%" PRIx32
2870 "]",
2871 (NS_SUCCEEDED(rv) ? "success" : "failure"), this,
2872 (nsIStreamListener*)mDestListener, static_cast<uint32_t>(rv)));
2873 return rv;
2877 * http validate class. check a channel for a 304
2880 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
2881 nsIThreadRetargetableStreamListener, nsIChannelEventSink,
2882 nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
2884 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
2885 imgLoader* loader, imgRequest* request,
2886 Document* aDocument,
2887 uint64_t aInnerWindowId,
2888 bool forcePrincipalCheckForCacheEntry)
2889 : mProgressProxy(progress),
2890 mRequest(request),
2891 mDocument(aDocument),
2892 mInnerWindowId(aInnerWindowId),
2893 mImgLoader(loader),
2894 mHadInsecureRedirect(false) {
2895 NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader,
2896 mRequest->CacheKey(), getter_AddRefs(mNewRequest),
2897 getter_AddRefs(mNewEntry));
2900 imgCacheValidator::~imgCacheValidator() {
2901 if (mRequest) {
2902 // If something went wrong, and we never unblocked the requests waiting on
2903 // validation, now is our last chance. We will cancel the new request and
2904 // switch the waiting proxies to it.
2905 UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ false);
2909 void imgCacheValidator::AddProxy(imgRequestProxy* aProxy) {
2910 // aProxy needs to be in the loadgroup since we're validating from
2911 // the network.
2912 aProxy->AddToLoadGroup();
2914 mProxies.AppendElement(aProxy);
2917 void imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) {
2918 mProxies.RemoveElement(aProxy);
2921 void imgCacheValidator::UpdateProxies(bool aCancelRequest, bool aSyncNotify) {
2922 MOZ_ASSERT(mRequest);
2924 // Clear the validator before updating the proxies. The notifications may
2925 // clone an existing request, and its state could be inconsistent.
2926 mRequest->SetValidator(nullptr);
2927 mRequest = nullptr;
2929 // If an error occurred, we will want to cancel the new request, and make the
2930 // validating proxies point to it. Any proxies still bound to the original
2931 // request which are not validating should remain untouched.
2932 if (aCancelRequest) {
2933 MOZ_ASSERT(mNewRequest);
2934 mNewRequest->CancelAndAbort(NS_BINDING_ABORTED);
2937 // We have finished validating the request, so we can safely take ownership
2938 // of the proxy list. imgRequestProxy::SyncNotifyListener can mutate the list
2939 // if imgRequestProxy::CancelAndForgetObserver is called by its owner. Note
2940 // that any potential notifications should still be suppressed in
2941 // imgRequestProxy::ChangeOwner because we haven't cleared the validating
2942 // flag yet, and thus they will remain deferred.
2943 AutoTArray<RefPtr<imgRequestProxy>, 4> proxies(std::move(mProxies));
2945 for (auto& proxy : proxies) {
2946 // First update the state of all proxies before notifying any of them
2947 // to ensure a consistent state (e.g. in case the notification causes
2948 // other proxies to be touched indirectly.)
2949 MOZ_ASSERT(proxy->IsValidating());
2950 MOZ_ASSERT(proxy->NotificationsDeferred(),
2951 "Proxies waiting on cache validation should be "
2952 "deferring notifications!");
2953 if (mNewRequest) {
2954 proxy->ChangeOwner(mNewRequest);
2956 proxy->ClearValidating();
2959 mNewRequest = nullptr;
2960 mNewEntry = nullptr;
2962 for (auto& proxy : proxies) {
2963 if (aSyncNotify) {
2964 // Notify synchronously, because the caller knows we are already in an
2965 // asynchronously-called function (e.g. OnStartRequest).
2966 proxy->SyncNotifyListener();
2967 } else {
2968 // Notify asynchronously, because the caller does not know our current
2969 // call state (e.g. ~imgCacheValidator).
2970 proxy->NotifyListener();
2975 /** nsIRequestObserver methods **/
2977 NS_IMETHODIMP
2978 imgCacheValidator::OnStartRequest(nsIRequest* aRequest) {
2979 // We may be holding on to a document, so ensure that it's released.
2980 RefPtr<Document> document = mDocument.forget();
2982 // If for some reason we don't still have an existing request (probably
2983 // because OnStartRequest got delivered more than once), just bail.
2984 if (!mRequest) {
2985 MOZ_ASSERT_UNREACHABLE("OnStartRequest delivered more than once?");
2986 aRequest->Cancel(NS_BINDING_ABORTED);
2987 return NS_ERROR_FAILURE;
2990 // If this request is coming from cache and has the same URI as our
2991 // imgRequest, the request all our proxies are pointing at is valid, and all
2992 // we have to do is tell them to notify their listeners.
2993 nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(aRequest));
2994 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2995 if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) {
2996 bool isFromCache = false;
2997 cacheChan->IsFromCache(&isFromCache);
2999 nsCOMPtr<nsIURI> channelURI;
3000 channel->GetURI(getter_AddRefs(channelURI));
3002 nsCOMPtr<nsIURI> finalURI;
3003 mRequest->GetFinalURI(getter_AddRefs(finalURI));
3005 bool sameURI = false;
3006 if (channelURI && finalURI) {
3007 channelURI->Equals(finalURI, &sameURI);
3010 if (isFromCache && sameURI) {
3011 // We don't need to load this any more.
3012 aRequest->Cancel(NS_BINDING_ABORTED);
3013 mNewRequest = nullptr;
3015 // Clear the validator before updating the proxies. The notifications may
3016 // clone an existing request, and its state could be inconsistent.
3017 mRequest->SetLoadId(document);
3018 mRequest->SetInnerWindowID(mInnerWindowId);
3019 UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true);
3020 return NS_OK;
3024 // We can't load out of cache. We have to create a whole new request for the
3025 // data that's coming in off the channel.
3026 nsCOMPtr<nsIURI> uri;
3027 mRequest->GetURI(getter_AddRefs(uri));
3029 LOG_MSG_WITH_PARAM(gImgLog,
3030 "imgCacheValidator::OnStartRequest creating new request",
3031 "uri", uri);
3033 int32_t corsmode = mRequest->GetCORSMode();
3034 nsCOMPtr<nsIReferrerInfo> referrerInfo = mRequest->GetReferrerInfo();
3035 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
3036 mRequest->GetTriggeringPrincipal();
3038 // Doom the old request's cache entry
3039 mRequest->RemoveFromCache();
3041 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
3042 nsCOMPtr<nsIURI> originalURI;
3043 channel->GetOriginalURI(getter_AddRefs(originalURI));
3044 nsresult rv = mNewRequest->Init(originalURI, uri, mHadInsecureRedirect,
3045 aRequest, channel, mNewEntry, document,
3046 triggeringPrincipal, corsmode, referrerInfo);
3047 if (NS_FAILED(rv)) {
3048 UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ true);
3049 return rv;
3052 mDestListener = new ProxyListener(mNewRequest);
3054 // Try to add the new request into the cache. Note that the entry must be in
3055 // the cache before the proxies' ownership changes, because adding a proxy
3056 // changes the caching behaviour for imgRequests.
3057 mImgLoader->PutIntoCache(mNewRequest->CacheKey(), mNewEntry);
3058 UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true);
3059 return mDestListener->OnStartRequest(aRequest);
3062 NS_IMETHODIMP
3063 imgCacheValidator::OnStopRequest(nsIRequest* aRequest, nsresult status) {
3064 // Be sure we've released the document that we may have been holding on to.
3065 mDocument = nullptr;
3067 if (!mDestListener) {
3068 return NS_OK;
3071 return mDestListener->OnStopRequest(aRequest, status);
3074 /** nsIStreamListener methods **/
3076 NS_IMETHODIMP
3077 imgCacheValidator::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
3078 uint64_t sourceOffset, uint32_t count) {
3079 if (!mDestListener) {
3080 // XXX see bug 113959
3081 uint32_t _retval;
3082 inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
3083 return NS_OK;
3086 return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
3089 /** nsIThreadRetargetableStreamListener methods **/
3091 NS_IMETHODIMP
3092 imgCacheValidator::CheckListenerChain() {
3093 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
3094 nsresult rv = NS_OK;
3095 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
3096 do_QueryInterface(mDestListener, &rv);
3097 if (retargetableListener) {
3098 rv = retargetableListener->CheckListenerChain();
3100 MOZ_LOG(
3101 gImgLog, LogLevel::Debug,
3102 ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %" PRId32 "=%s",
3103 this, static_cast<uint32_t>(rv),
3104 NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
3105 return rv;
3108 /** nsIInterfaceRequestor methods **/
3110 NS_IMETHODIMP
3111 imgCacheValidator::GetInterface(const nsIID& aIID, void** aResult) {
3112 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3113 return QueryInterface(aIID, aResult);
3116 return mProgressProxy->GetInterface(aIID, aResult);
3119 // These functions are materially the same as the same functions in imgRequest.
3120 // We duplicate them because we're verifying whether cache loads are necessary,
3121 // not unconditionally loading.
3123 /** nsIChannelEventSink methods **/
3124 NS_IMETHODIMP
3125 imgCacheValidator::AsyncOnChannelRedirect(
3126 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
3127 nsIAsyncVerifyRedirectCallback* callback) {
3128 // Note all cache information we get from the old channel.
3129 mNewRequest->SetCacheValidation(mNewEntry, oldChannel);
3131 // If the previous URI is a non-HTTPS URI, record that fact for later use by
3132 // security code, which needs to know whether there is an insecure load at any
3133 // point in the redirect chain.
3134 nsCOMPtr<nsIURI> oldURI;
3135 bool schemeLocal = false;
3136 if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) ||
3137 NS_FAILED(NS_URIChainHasFlags(
3138 oldURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
3139 (!oldURI->SchemeIs("https") && !oldURI->SchemeIs("chrome") &&
3140 !schemeLocal)) {
3141 mHadInsecureRedirect = true;
3144 // Prepare for callback
3145 mRedirectCallback = callback;
3146 mRedirectChannel = newChannel;
3148 return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
3149 this);
3152 NS_IMETHODIMP
3153 imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) {
3154 // If we've already been told to abort, just do so.
3155 if (NS_FAILED(aResult)) {
3156 mRedirectCallback->OnRedirectVerifyCallback(aResult);
3157 mRedirectCallback = nullptr;
3158 mRedirectChannel = nullptr;
3159 return NS_OK;
3162 // make sure we have a protocol that returns data rather than opens
3163 // an external application, e.g. mailto:
3164 nsCOMPtr<nsIURI> uri;
3165 mRedirectChannel->GetURI(getter_AddRefs(uri));
3166 bool doesNotReturnData = false;
3167 NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
3168 &doesNotReturnData);
3170 nsresult result = NS_OK;
3172 if (doesNotReturnData) {
3173 result = NS_ERROR_ABORT;
3176 mRedirectCallback->OnRedirectVerifyCallback(result);
3177 mRedirectCallback = nullptr;
3178 mRedirectChannel = nullptr;
3179 return NS_OK;