Bug 1551001 [wpt PR 16740] - Don't mark disconnected tree-scopes for style update...
[gecko.git] / image / imgLoader.cpp
blob2257a23f5df07d170007ee63a04e430312d65424
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 "ImageLogging.h"
11 #include "imgLoader.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/ClearOnShutdown.h"
15 #include "mozilla/Move.h"
16 #include "mozilla/NullPrincipal.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/StaticPrefs.h"
19 #include "mozilla/ChaosMode.h"
20 #include "mozilla/LoadInfo.h"
21 #include "mozilla/StaticPrefs.h"
23 #include "nsImageModule.h"
24 #include "imgRequestProxy.h"
26 #include "nsCOMPtr.h"
28 #include "nsContentPolicyUtils.h"
29 #include "nsContentUtils.h"
30 #include "nsNetUtil.h"
31 #include "nsNetCID.h"
32 #include "nsIProtocolHandler.h"
33 #include "nsMimeTypes.h"
34 #include "nsStreamUtils.h"
35 #include "nsIHttpChannel.h"
36 #include "nsICacheInfoChannel.h"
37 #include "nsIClassOfService.h"
38 #include "nsIInterfaceRequestor.h"
39 #include "nsIInterfaceRequestorUtils.h"
40 #include "nsIProgressEventSink.h"
41 #include "nsIChannelEventSink.h"
42 #include "nsIAsyncVerifyRedirectCallback.h"
43 #include "nsIFileURL.h"
44 #include "nsIFile.h"
45 #include "nsCRT.h"
46 #include "nsINetworkPredictor.h"
47 #include "nsReadableUtils.h"
48 #include "mozilla/dom/ContentParent.h"
49 #include "mozilla/dom/nsMixedContentBlocker.h"
50 #include "mozilla/image/ImageMemoryReporter.h"
51 #include "mozilla/layers/CompositorManagerChild.h"
53 #include "nsIApplicationCache.h"
54 #include "nsIApplicationCacheContainer.h"
56 #include "nsIMemoryReporter.h"
57 #include "DecoderFactory.h"
58 #include "Image.h"
59 #include "prtime.h"
60 #include "ReferrerInfo.h"
62 // we want to explore making the document own the load group
63 // so we can associate the document URI with the load group.
64 // until this point, we have an evil hack:
65 #include "nsIHttpChannelInternal.h"
66 #include "nsILoadContext.h"
67 #include "nsILoadGroupChild.h"
68 #include "nsIDocShell.h"
70 using namespace mozilla;
71 using namespace mozilla::dom;
72 using namespace mozilla::image;
73 using namespace mozilla::net;
75 MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)
77 class imgMemoryReporter final : public nsIMemoryReporter {
78 ~imgMemoryReporter() = default;
80 public:
81 NS_DECL_ISUPPORTS
83 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
84 nsISupports* aData, bool aAnonymize) override {
85 MOZ_ASSERT(NS_IsMainThread());
87 layers::CompositorManagerChild* manager =
88 CompositorManagerChild::GetInstance();
89 if (!manager || !StaticPrefs::ImageMemDebugReporting()) {
90 layers::SharedSurfacesMemoryReport sharedSurfaces;
91 FinishCollectReports(aHandleReport, aData, aAnonymize, sharedSurfaces);
92 return NS_OK;
95 RefPtr<imgMemoryReporter> self(this);
96 nsCOMPtr<nsIHandleReportCallback> handleReport(aHandleReport);
97 nsCOMPtr<nsISupports> data(aData);
98 manager->SendReportSharedSurfacesMemory(
99 [=](layers::SharedSurfacesMemoryReport aReport) {
100 self->FinishCollectReports(handleReport, data, aAnonymize, aReport);
102 [=](mozilla::ipc::ResponseRejectReason&& aReason) {
103 layers::SharedSurfacesMemoryReport sharedSurfaces;
104 self->FinishCollectReports(handleReport, data, aAnonymize,
105 sharedSurfaces);
107 return NS_OK;
110 void FinishCollectReports(
111 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
112 bool aAnonymize, layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
113 nsTArray<ImageMemoryCounter> chrome;
114 nsTArray<ImageMemoryCounter> content;
115 nsTArray<ImageMemoryCounter> uncached;
117 for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
118 for (auto iter = mKnownLoaders[i]->mChromeCache.Iter(); !iter.Done();
119 iter.Next()) {
120 imgCacheEntry* entry = iter.UserData();
121 RefPtr<imgRequest> req = entry->GetRequest();
122 RecordCounterForRequest(req, &chrome, !entry->HasNoProxies());
124 for (auto iter = mKnownLoaders[i]->mCache.Iter(); !iter.Done();
125 iter.Next()) {
126 imgCacheEntry* entry = iter.UserData();
127 RefPtr<imgRequest> req = entry->GetRequest();
128 RecordCounterForRequest(req, &content, !entry->HasNoProxies());
130 MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex);
131 for (auto iter = mKnownLoaders[i]->mUncachedImages.Iter(); !iter.Done();
132 iter.Next()) {
133 nsPtrHashKey<imgRequest>* entry = iter.Get();
134 RefPtr<imgRequest> req = entry->GetKey();
135 RecordCounterForRequest(req, &uncached, req->HasConsumers());
139 // Note that we only need to anonymize content image URIs.
141 ReportCounterArray(aHandleReport, aData, chrome, "images/chrome",
142 /* aAnonymize */ false, aSharedSurfaces);
144 ReportCounterArray(aHandleReport, aData, content, "images/content",
145 aAnonymize, aSharedSurfaces);
147 // Uncached images may be content or chrome, so anonymize them.
148 ReportCounterArray(aHandleReport, aData, uncached, "images/uncached",
149 aAnonymize, aSharedSurfaces);
151 // Report any shared surfaces that were not merged with the surface cache.
152 ImageMemoryReporter::ReportSharedSurfaces(aHandleReport, aData,
153 aSharedSurfaces);
155 nsCOMPtr<nsIMemoryReporterManager> imgr =
156 do_GetService("@mozilla.org/memory-reporter-manager;1");
157 if (imgr) {
158 imgr->EndReport();
162 static int64_t ImagesContentUsedUncompressedDistinguishedAmount() {
163 size_t n = 0;
164 for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length();
165 i++) {
166 for (auto iter = imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Iter();
167 !iter.Done(); iter.Next()) {
168 imgCacheEntry* entry = iter.UserData();
169 if (entry->HasNoProxies()) {
170 continue;
173 RefPtr<imgRequest> req = entry->GetRequest();
174 RefPtr<image::Image> image = req->GetImage();
175 if (!image) {
176 continue;
179 // Both this and EntryImageSizes measure
180 // images/content/raster/used/decoded memory. This function's
181 // measurement is secondary -- the result doesn't go in the "explicit"
182 // tree -- so we use moz_malloc_size_of instead of ImagesMallocSizeOf to
183 // prevent DMD from seeing it reported twice.
184 SizeOfState state(moz_malloc_size_of);
185 ImageMemoryCounter counter(image, state, /* aIsUsed = */ true);
187 n += counter.Values().DecodedHeap();
188 n += counter.Values().DecodedNonHeap();
191 return n;
194 void RegisterLoader(imgLoader* aLoader) {
195 mKnownLoaders.AppendElement(aLoader);
198 void UnregisterLoader(imgLoader* aLoader) {
199 mKnownLoaders.RemoveElement(aLoader);
202 private:
203 nsTArray<imgLoader*> mKnownLoaders;
205 struct MemoryTotal {
206 MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) {
207 if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
208 if (aImageCounter.IsUsed()) {
209 mUsedRasterCounter += aImageCounter.Values();
210 } else {
211 mUnusedRasterCounter += aImageCounter.Values();
213 } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) {
214 if (aImageCounter.IsUsed()) {
215 mUsedVectorCounter += aImageCounter.Values();
216 } else {
217 mUnusedVectorCounter += aImageCounter.Values();
219 } else {
220 MOZ_CRASH("Unexpected image type");
223 return *this;
226 const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; }
227 const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; }
228 const MemoryCounter& UsedVector() const { return mUsedVectorCounter; }
229 const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; }
231 private:
232 MemoryCounter mUsedRasterCounter;
233 MemoryCounter mUnusedRasterCounter;
234 MemoryCounter mUsedVectorCounter;
235 MemoryCounter mUnusedVectorCounter;
238 // Reports all images of a single kind, e.g. all used chrome images.
239 void ReportCounterArray(nsIHandleReportCallback* aHandleReport,
240 nsISupports* aData,
241 nsTArray<ImageMemoryCounter>& aCounterArray,
242 const char* aPathPrefix, bool aAnonymize,
243 layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
244 MemoryTotal summaryTotal;
245 MemoryTotal nonNotableTotal;
247 // Report notable images, and compute total and non-notable aggregate sizes.
248 for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
249 ImageMemoryCounter& counter = aCounterArray[i];
251 if (aAnonymize) {
252 counter.URI().Truncate();
253 counter.URI().AppendPrintf("<anonymized-%u>", i);
254 } else {
255 // The URI could be an extremely long data: URI. Truncate if needed.
256 static const size_t max = 256;
257 if (counter.URI().Length() > max) {
258 counter.URI().Truncate(max);
259 counter.URI().AppendLiteral(" (truncated)");
261 counter.URI().ReplaceChar('/', '\\');
264 summaryTotal += counter;
266 if (counter.IsNotable() || StaticPrefs::ImageMemDebugReporting()) {
267 ReportImage(aHandleReport, aData, aPathPrefix, counter,
268 aSharedSurfaces);
269 } else {
270 ImageMemoryReporter::TrimSharedSurfaces(counter, aSharedSurfaces);
271 nonNotableTotal += counter;
275 // Report non-notable images in aggregate.
276 ReportTotal(aHandleReport, aData, /* aExplicit = */ true, aPathPrefix,
277 "<non-notable images>/", nonNotableTotal);
279 // Report a summary in aggregate, outside of the explicit tree.
280 ReportTotal(aHandleReport, aData, /* aExplicit = */ false, aPathPrefix, "",
281 summaryTotal);
284 static void ReportImage(nsIHandleReportCallback* aHandleReport,
285 nsISupports* aData, const char* aPathPrefix,
286 const ImageMemoryCounter& aCounter,
287 layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
288 nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/"));
289 pathPrefix.Append(aPathPrefix);
290 pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER
291 ? "/raster/"
292 : "/vector/");
293 pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
294 pathPrefix.AppendLiteral("image(");
295 pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
296 pathPrefix.AppendLiteral("x");
297 pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
298 pathPrefix.AppendLiteral(", ");
300 if (aCounter.URI().IsEmpty()) {
301 pathPrefix.AppendLiteral("<unknown URI>");
302 } else {
303 pathPrefix.Append(aCounter.URI());
306 pathPrefix.AppendLiteral(")/");
308 ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter, aSharedSurfaces);
310 ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
313 static void ReportSurfaces(
314 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
315 const nsACString& aPathPrefix, const ImageMemoryCounter& aCounter,
316 layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
317 for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
318 nsAutoCString surfacePathPrefix(aPathPrefix);
319 if (counter.IsLocked()) {
320 surfacePathPrefix.AppendLiteral("locked/");
321 } else {
322 surfacePathPrefix.AppendLiteral("unlocked/");
324 if (counter.IsFactor2()) {
325 surfacePathPrefix.AppendLiteral("factor2/");
327 if (counter.CannotSubstitute()) {
328 surfacePathPrefix.AppendLiteral("cannot_substitute/");
330 surfacePathPrefix.AppendLiteral("surface(");
331 surfacePathPrefix.AppendInt(counter.Key().Size().width);
332 surfacePathPrefix.AppendLiteral("x");
333 surfacePathPrefix.AppendInt(counter.Key().Size().height);
335 if (counter.Values().ExternalHandles() > 0) {
336 surfacePathPrefix.AppendLiteral(", handles:");
337 surfacePathPrefix.AppendInt(
338 uint32_t(counter.Values().ExternalHandles()));
341 ImageMemoryReporter::AppendSharedSurfacePrefix(surfacePathPrefix, counter,
342 aSharedSurfaces);
344 if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
345 PlaybackType playback = counter.Key().Playback();
346 if (playback == PlaybackType::eAnimated) {
347 if (StaticPrefs::ImageMemDebugReporting()) {
348 surfacePathPrefix.AppendPrintf(
349 " (animation %4u)", uint32_t(counter.Values().FrameIndex()));
350 } else {
351 surfacePathPrefix.AppendLiteral(" (animation)");
355 if (counter.Key().Flags() != DefaultSurfaceFlags()) {
356 surfacePathPrefix.AppendLiteral(", flags:");
357 surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
358 /* aRadix = */ 16);
361 if (counter.Key().SVGContext()) {
362 const SVGImageContext& context = counter.Key().SVGContext().ref();
363 surfacePathPrefix.AppendLiteral(", svgContext:[ ");
364 if (context.GetViewportSize()) {
365 const CSSIntSize& size = context.GetViewportSize().ref();
366 surfacePathPrefix.AppendLiteral("viewport=(");
367 surfacePathPrefix.AppendInt(size.width);
368 surfacePathPrefix.AppendLiteral("x");
369 surfacePathPrefix.AppendInt(size.height);
370 surfacePathPrefix.AppendLiteral(") ");
372 if (context.GetPreserveAspectRatio()) {
373 nsAutoString aspect;
374 context.GetPreserveAspectRatio()->ToString(aspect);
375 surfacePathPrefix.AppendLiteral("preserveAspectRatio=(");
376 LossyAppendUTF16toASCII(aspect, surfacePathPrefix);
377 surfacePathPrefix.AppendLiteral(") ");
379 if (context.GetContextPaint()) {
380 const SVGEmbeddingContextPaint* paint = context.GetContextPaint();
381 surfacePathPrefix.AppendLiteral("contextPaint=(");
382 if (paint->GetFill()) {
383 surfacePathPrefix.AppendLiteral(" fill=");
384 surfacePathPrefix.AppendInt(paint->GetFill()->ToABGR(), 16);
386 if (paint->GetFillOpacity()) {
387 surfacePathPrefix.AppendLiteral(" fillOpa=");
388 surfacePathPrefix.AppendFloat(paint->GetFillOpacity());
390 if (paint->GetStroke()) {
391 surfacePathPrefix.AppendLiteral(" stroke=");
392 surfacePathPrefix.AppendInt(paint->GetStroke()->ToABGR(), 16);
394 if (paint->GetStrokeOpacity()) {
395 surfacePathPrefix.AppendLiteral(" strokeOpa=");
396 surfacePathPrefix.AppendFloat(paint->GetStrokeOpacity());
398 surfacePathPrefix.AppendLiteral(" ) ");
400 surfacePathPrefix.AppendLiteral("]");
402 } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
403 surfacePathPrefix.AppendLiteral(", compositing frame");
404 } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) {
405 surfacePathPrefix.AppendLiteral(", compositing prev frame");
406 } else {
407 MOZ_ASSERT_UNREACHABLE("Unknown counter type");
410 surfacePathPrefix.AppendLiteral(")/");
412 ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values());
416 static void ReportTotal(nsIHandleReportCallback* aHandleReport,
417 nsISupports* aData, bool aExplicit,
418 const char* aPathPrefix, const char* aPathInfix,
419 const MemoryTotal& aTotal) {
420 nsAutoCString pathPrefix;
421 if (aExplicit) {
422 pathPrefix.AppendLiteral("explicit/");
424 pathPrefix.Append(aPathPrefix);
426 nsAutoCString rasterUsedPrefix(pathPrefix);
427 rasterUsedPrefix.AppendLiteral("/raster/used/");
428 rasterUsedPrefix.Append(aPathInfix);
429 ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster());
431 nsAutoCString rasterUnusedPrefix(pathPrefix);
432 rasterUnusedPrefix.AppendLiteral("/raster/unused/");
433 rasterUnusedPrefix.Append(aPathInfix);
434 ReportValues(aHandleReport, aData, rasterUnusedPrefix,
435 aTotal.UnusedRaster());
437 nsAutoCString vectorUsedPrefix(pathPrefix);
438 vectorUsedPrefix.AppendLiteral("/vector/used/");
439 vectorUsedPrefix.Append(aPathInfix);
440 ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector());
442 nsAutoCString vectorUnusedPrefix(pathPrefix);
443 vectorUnusedPrefix.AppendLiteral("/vector/unused/");
444 vectorUnusedPrefix.Append(aPathInfix);
445 ReportValues(aHandleReport, aData, vectorUnusedPrefix,
446 aTotal.UnusedVector());
449 static void ReportValues(nsIHandleReportCallback* aHandleReport,
450 nsISupports* aData, const nsACString& aPathPrefix,
451 const MemoryCounter& aCounter) {
452 ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);
454 ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "decoded-heap",
455 "Decoded image data which is stored on the heap.",
456 aCounter.DecodedHeap());
458 ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
459 "decoded-nonheap",
460 "Decoded image data which isn't stored on the heap.",
461 aCounter.DecodedNonHeap());
464 static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
465 nsISupports* aData,
466 const nsACString& aPathPrefix,
467 const MemoryCounter& aCounter) {
468 ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "source",
469 "Raster image source data and vector image documents.",
470 aCounter.Source());
473 static void ReportValue(nsIHandleReportCallback* aHandleReport,
474 nsISupports* aData, int32_t aKind,
475 const nsACString& aPathPrefix,
476 const char* aPathSuffix, const char* aDescription,
477 size_t aValue) {
478 if (aValue == 0) {
479 return;
482 nsAutoCString desc(aDescription);
483 nsAutoCString path(aPathPrefix);
484 path.Append(aPathSuffix);
486 aHandleReport->Callback(EmptyCString(), path, aKind, UNITS_BYTES, aValue,
487 desc, aData);
490 static void RecordCounterForRequest(imgRequest* aRequest,
491 nsTArray<ImageMemoryCounter>* aArray,
492 bool aIsUsed) {
493 RefPtr<image::Image> image = aRequest->GetImage();
494 if (!image) {
495 return;
498 SizeOfState state(ImagesMallocSizeOf);
499 ImageMemoryCounter counter(image, state, aIsUsed);
501 aArray->AppendElement(std::move(counter));
505 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
507 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, nsIProgressEventSink,
508 nsIChannelEventSink, nsIInterfaceRequestor)
510 NS_IMETHODIMP
511 nsProgressNotificationProxy::OnProgress(nsIRequest* request, nsISupports* ctxt,
512 int64_t progress, int64_t progressMax) {
513 nsCOMPtr<nsILoadGroup> loadGroup;
514 request->GetLoadGroup(getter_AddRefs(loadGroup));
516 nsCOMPtr<nsIProgressEventSink> target;
517 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
518 NS_GET_IID(nsIProgressEventSink),
519 getter_AddRefs(target));
520 if (!target) {
521 return NS_OK;
523 return target->OnProgress(mImageRequest, ctxt, progress, progressMax);
526 NS_IMETHODIMP
527 nsProgressNotificationProxy::OnStatus(nsIRequest* request, nsISupports* ctxt,
528 nsresult status,
529 const char16_t* statusArg) {
530 nsCOMPtr<nsILoadGroup> loadGroup;
531 request->GetLoadGroup(getter_AddRefs(loadGroup));
533 nsCOMPtr<nsIProgressEventSink> target;
534 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
535 NS_GET_IID(nsIProgressEventSink),
536 getter_AddRefs(target));
537 if (!target) {
538 return NS_OK;
540 return target->OnStatus(mImageRequest, ctxt, status, statusArg);
543 NS_IMETHODIMP
544 nsProgressNotificationProxy::AsyncOnChannelRedirect(
545 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
546 nsIAsyncVerifyRedirectCallback* cb) {
547 // Tell the original original callbacks about it too
548 nsCOMPtr<nsILoadGroup> loadGroup;
549 newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
550 nsCOMPtr<nsIChannelEventSink> target;
551 NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
552 NS_GET_IID(nsIChannelEventSink),
553 getter_AddRefs(target));
554 if (!target) {
555 cb->OnRedirectVerifyCallback(NS_OK);
556 return NS_OK;
559 // Delegate to |target| if set, reusing |cb|
560 return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
563 NS_IMETHODIMP
564 nsProgressNotificationProxy::GetInterface(const nsIID& iid, void** result) {
565 if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
566 *result = static_cast<nsIProgressEventSink*>(this);
567 NS_ADDREF_THIS();
568 return NS_OK;
570 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
571 *result = static_cast<nsIChannelEventSink*>(this);
572 NS_ADDREF_THIS();
573 return NS_OK;
575 if (mOriginalCallbacks) {
576 return mOriginalCallbacks->GetInterface(iid, result);
578 return NS_NOINTERFACE;
581 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,
582 imgLoader* aLoader, const ImageCacheKey& aKey,
583 imgRequest** aRequest, imgCacheEntry** aEntry) {
584 RefPtr<imgRequest> request = new imgRequest(aLoader, aKey);
585 RefPtr<imgCacheEntry> entry =
586 new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
587 aLoader->AddToUncachedImages(request);
588 request.forget(aRequest);
589 entry.forget(aEntry);
592 static bool ShouldRevalidateEntry(imgCacheEntry* aEntry, nsLoadFlags aFlags,
593 bool aHasExpired) {
594 bool bValidateEntry = false;
596 if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) {
597 return false;
600 if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
601 bValidateEntry = true;
602 } else if (aEntry->GetMustValidate()) {
603 bValidateEntry = true;
604 } else if (aHasExpired) {
605 // The cache entry has expired... Determine whether the stale cache
606 // entry can be used without validation...
607 if (aFlags &
608 (nsIRequest::VALIDATE_NEVER | nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
609 // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
610 // entries to be used unless they have been explicitly marked to
611 // indicate that revalidation is necessary.
612 bValidateEntry = false;
614 } else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
615 // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
616 // the entry must be revalidated.
617 bValidateEntry = true;
621 return bValidateEntry;
624 /* Call content policies on cached images that went through a redirect */
625 static bool ShouldLoadCachedImage(imgRequest* aImgRequest,
626 nsISupports* aLoadingContext,
627 nsIPrincipal* aTriggeringPrincipal,
628 nsContentPolicyType aPolicyType,
629 bool aSendCSPViolationReports) {
630 /* Call content policies on cached images - Bug 1082837
631 * Cached images are keyed off of the first uri in a redirect chain.
632 * Hence content policies don't get a chance to test the intermediate hops
633 * or the final desitnation. Here we test the final destination using
634 * mFinalURI off of the imgRequest and passing it into content policies.
635 * For Mixed Content Blocker, we do an additional check to determine if any
636 * of the intermediary hops went through an insecure redirect with the
637 * mHadInsecureRedirect flag
639 bool insecureRedirect = aImgRequest->HadInsecureRedirect();
640 nsCOMPtr<nsIURI> contentLocation;
641 aImgRequest->GetFinalURI(getter_AddRefs(contentLocation));
642 nsresult rv;
644 nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aLoadingContext);
645 nsCOMPtr<nsIPrincipal> loadingPrincipal =
646 requestingNode ? requestingNode->NodePrincipal() : aTriggeringPrincipal;
647 // If there is no context and also no triggeringPrincipal, then we use a fresh
648 // nullPrincipal as the loadingPrincipal because we can not create a loadinfo
649 // without a valid loadingPrincipal.
650 if (!loadingPrincipal) {
651 loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
654 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
655 loadingPrincipal, aTriggeringPrincipal, requestingNode,
656 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, aPolicyType);
658 secCheckLoadInfo->SetSendCSPViolationEvents(aSendCSPViolationReports);
660 int16_t decision = nsIContentPolicy::REJECT_REQUEST;
661 rv = NS_CheckContentLoadPolicy(contentLocation, secCheckLoadInfo,
662 EmptyCString(), // mime guess
663 &decision, nsContentUtils::GetContentPolicy());
664 if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
665 return false;
668 // We call all Content Policies above, but we also have to call mcb
669 // individually to check the intermediary redirect hops are secure.
670 if (insecureRedirect) {
671 // Bug 1314356: If the image ended up in the cache upgraded by HSTS and the
672 // page uses upgrade-inscure-requests it had an insecure redirect
673 // (http->https). We need to invalidate the image and reload it because
674 // mixed content blocker only bails if upgrade-insecure-requests is set on
675 // the doc and the resource load is http: which would result in an incorrect
676 // mixed content warning.
677 nsCOMPtr<nsIDocShell> docShell =
678 NS_CP_GetDocShellFromContext(aLoadingContext);
679 if (docShell) {
680 Document* document = docShell->GetDocument();
681 if (document && document->GetUpgradeInsecureRequests(false)) {
682 return false;
686 if (!nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
687 // Set the requestingLocation from the aTriggeringPrincipal.
688 nsCOMPtr<nsIURI> requestingLocation;
689 if (aTriggeringPrincipal) {
690 rv = aTriggeringPrincipal->GetURI(getter_AddRefs(requestingLocation));
691 NS_ENSURE_SUCCESS(rv, false);
694 // reset the decision for mixed content blocker check
695 decision = nsIContentPolicy::REJECT_REQUEST;
696 rv = nsMixedContentBlocker::ShouldLoad(
697 insecureRedirect, aPolicyType, contentLocation, requestingLocation,
698 aLoadingContext,
699 EmptyCString(), // mime guess
700 aTriggeringPrincipal, &decision);
701 if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
702 return false;
707 return true;
710 // Returns true if this request is compatible with the given CORS mode on the
711 // given loading principal, and false if the request may not be reused due
712 // to CORS. Also checks the Referrer Policy, since requests with different
713 // referrers/policies may generate different responses.
714 static bool ValidateSecurityInfo(imgRequest* request, bool forcePrincipalCheck,
715 int32_t corsmode,
716 nsIPrincipal* triggeringPrincipal,
717 nsISupports* aCX,
718 nsContentPolicyType aPolicyType,
719 ReferrerPolicy referrerPolicy) {
720 // If the entry's Referrer Policy doesn't match, we can't use this request.
721 // XXX: this will return false if an image has different referrer attributes,
722 // i.e. we currently don't use the cached image but reload the image with
723 // the new referrer policy bug 1174921
724 if (referrerPolicy != request->GetReferrerPolicy()) {
725 return false;
728 // If the entry's CORS mode doesn't match, or the CORS mode matches but the
729 // document principal isn't the same, we can't use this request.
730 if (request->GetCORSMode() != corsmode) {
731 return false;
733 if (request->GetCORSMode() != imgIRequest::CORS_NONE || forcePrincipalCheck) {
734 nsCOMPtr<nsIPrincipal> otherprincipal = request->GetTriggeringPrincipal();
736 // If we previously had a principal, but we don't now, we can't use this
737 // request.
738 if (otherprincipal && !triggeringPrincipal) {
739 return false;
742 if (otherprincipal && triggeringPrincipal) {
743 bool equals = false;
744 otherprincipal->Equals(triggeringPrincipal, &equals);
745 if (!equals) {
746 return false;
751 // Content Policy Check on Cached Images
752 return ShouldLoadCachedImage(request, aCX, triggeringPrincipal, aPolicyType,
753 /* aSendCSPViolationReports */ false);
756 static nsresult NewImageChannel(
757 nsIChannel** aResult,
758 // If aForcePrincipalCheckForCacheEntry is true, then we will
759 // force a principal check even when not using CORS before
760 // assuming we have a cache hit on a cache entry that we
761 // create for this channel. This is an out param that should
762 // be set to true if this channel ends up depending on
763 // aTriggeringPrincipal and false otherwise.
764 bool* aForcePrincipalCheckForCacheEntry, nsIURI* aURI,
765 nsIURI* aInitialDocumentURI, int32_t aCORSMode, nsIURI* aReferringURI,
766 ReferrerPolicy aReferrerPolicy, nsILoadGroup* aLoadGroup,
767 const nsCString& aAcceptHeader, nsLoadFlags aLoadFlags,
768 nsContentPolicyType aPolicyType, nsIPrincipal* aTriggeringPrincipal,
769 nsISupports* aRequestingContext, bool aRespectPrivacy) {
770 MOZ_ASSERT(aResult);
772 nsresult rv;
773 nsCOMPtr<nsIHttpChannel> newHttpChannel;
775 nsCOMPtr<nsIInterfaceRequestor> callbacks;
777 if (aLoadGroup) {
778 // Get the notification callbacks from the load group for the new channel.
780 // XXX: This is not exactly correct, because the network request could be
781 // referenced by multiple windows... However, the new channel needs
782 // something. So, using the 'first' notification callbacks is better
783 // than nothing...
785 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
788 // Pass in a nullptr loadgroup because this is the underlying network
789 // request. This request may be referenced by several proxy image requests
790 // (possibly in different documents).
791 // If all of the proxy requests are canceled then this request should be
792 // canceled too.
795 nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aRequestingContext);
797 nsSecurityFlags securityFlags =
798 aCORSMode == imgIRequest::CORS_NONE
799 ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
800 : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
801 if (aCORSMode == imgIRequest::CORS_ANONYMOUS) {
802 securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
803 } else if (aCORSMode == imgIRequest::CORS_USE_CREDENTIALS) {
804 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
806 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
808 // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a
809 // node and a principal. This is for things like background images that are
810 // specified by user stylesheets, where the document is being styled, but
811 // the principal is that of the user stylesheet.
812 if (requestingNode && aTriggeringPrincipal) {
813 rv = NS_NewChannelWithTriggeringPrincipal(aResult, aURI, requestingNode,
814 aTriggeringPrincipal,
815 securityFlags, aPolicyType,
816 nullptr, // PerformanceStorage
817 nullptr, // loadGroup
818 callbacks, aLoadFlags);
820 if (NS_FAILED(rv)) {
821 return rv;
824 if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
825 // If this is a favicon loading, we will use the originAttributes from the
826 // triggeringPrincipal as the channel's originAttributes. This allows the
827 // favicon loading from XUL will use the correct originAttributes.
829 nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
830 rv = loadInfo->SetOriginAttributes(
831 aTriggeringPrincipal->OriginAttributesRef());
833 } else {
834 // either we are loading something inside a document, in which case
835 // we should always have a requestingNode, or we are loading something
836 // outside a document, in which case the triggeringPrincipal and
837 // triggeringPrincipal should always be the systemPrincipal.
838 // However, there are exceptions: one is Notifications which create a
839 // channel in the parent process in which case we can't get a
840 // requestingNode.
841 rv = NS_NewChannel(aResult, aURI, nsContentUtils::GetSystemPrincipal(),
842 securityFlags, aPolicyType,
843 nullptr, // nsICookieSettings
844 nullptr, // PerformanceStorage
845 nullptr, // loadGroup
846 callbacks, aLoadFlags);
848 if (NS_FAILED(rv)) {
849 return rv;
852 // Use the OriginAttributes from the loading principal, if one is available,
853 // and adjust the private browsing ID based on what kind of load the caller
854 // has asked us to perform.
855 OriginAttributes attrs;
856 if (aTriggeringPrincipal) {
857 attrs = aTriggeringPrincipal->OriginAttributesRef();
859 attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0;
861 nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
862 rv = loadInfo->SetOriginAttributes(attrs);
865 if (NS_FAILED(rv)) {
866 return rv;
869 // only inherit if we have a principal
870 *aForcePrincipalCheckForCacheEntry =
871 aTriggeringPrincipal && nsContentUtils::ChannelShouldInheritPrincipal(
872 aTriggeringPrincipal, aURI,
873 /* aInheritForAboutBlank */ false,
874 /* aForceInherit */ false);
876 // Initialize HTTP-specific attributes
877 newHttpChannel = do_QueryInterface(*aResult);
878 if (newHttpChannel) {
879 rv = newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
880 aAcceptHeader, false);
881 MOZ_ASSERT(NS_SUCCEEDED(rv));
883 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
884 do_QueryInterface(newHttpChannel);
885 NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
886 rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
887 MOZ_ASSERT(NS_SUCCEEDED(rv));
888 nsCOMPtr<nsIReferrerInfo> referrerInfo =
889 new ReferrerInfo(aReferringURI, aReferrerPolicy);
890 rv = newHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
891 MOZ_ASSERT(NS_SUCCEEDED(rv));
894 // Image channels are loaded by default with reduced priority.
895 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
896 if (p) {
897 uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
899 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
900 ++priority; // further reduce priority for background loads
903 p->AdjustPriority(priority);
906 // Create a new loadgroup for this new channel, using the old group as
907 // the parent. The indirection keeps the channel insulated from cancels,
908 // but does allow a way for this revalidation to be associated with at
909 // least one base load group for scheduling/caching purposes.
911 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
912 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
913 if (childLoadGroup) {
914 childLoadGroup->SetParentLoadGroup(aLoadGroup);
916 (*aResult)->SetLoadGroup(loadGroup);
918 return NS_OK;
921 /* static */
922 uint32_t imgCacheEntry::SecondsFromPRTime(PRTime prTime) {
923 return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
926 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
927 bool forcePrincipalCheck)
928 : mLoader(loader),
929 mRequest(request),
930 mDataSize(0),
931 mTouchedTime(SecondsFromPRTime(PR_Now())),
932 mLoadTime(SecondsFromPRTime(PR_Now())),
933 mExpiryTime(0),
934 mMustValidate(false),
935 // We start off as evicted so we don't try to update the cache.
936 // PutIntoCache will set this to false.
937 mEvicted(true),
938 mHasNoProxies(true),
939 mForcePrincipalCheck(forcePrincipalCheck) {}
941 imgCacheEntry::~imgCacheEntry() {
942 LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
945 void imgCacheEntry::Touch(bool updateTime /* = true */) {
946 LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
948 if (updateTime) {
949 mTouchedTime = SecondsFromPRTime(PR_Now());
952 UpdateCache();
955 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) {
956 // Don't update the cache if we've been removed from it or it doesn't care
957 // about our size or usage.
958 if (!Evicted() && HasNoProxies()) {
959 mLoader->CacheEntriesChanged(mRequest->IsChrome(), diff);
963 void imgCacheEntry::UpdateLoadTime() {
964 mLoadTime = SecondsFromPRTime(PR_Now());
967 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) {
968 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
969 if (hasNoProxies) {
970 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true", "uri",
971 mRequest->CacheKey().URI());
972 } else {
973 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false",
974 "uri", mRequest->CacheKey().URI());
978 mHasNoProxies = hasNoProxies;
981 imgCacheQueue::imgCacheQueue() : mDirty(false), mSize(0) {}
983 void imgCacheQueue::UpdateSize(int32_t diff) { mSize += diff; }
985 uint32_t imgCacheQueue::GetSize() const { return mSize; }
987 #include <algorithm>
988 using namespace std;
990 void imgCacheQueue::Remove(imgCacheEntry* entry) {
991 uint64_t index = mQueue.IndexOf(entry);
992 if (index == queueContainer::NoIndex) {
993 return;
996 mSize -= mQueue[index]->GetDataSize();
998 // If the queue is clean and this is the first entry,
999 // then we can efficiently remove the entry without
1000 // dirtying the sort order.
1001 if (!IsDirty() && index == 0) {
1002 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1003 mQueue.RemoveLastElement();
1004 return;
1007 // Remove from the middle of the list. This potentially
1008 // breaks the binary heap sort order.
1009 mQueue.RemoveElementAt(index);
1011 // If we only have one entry or the queue is empty, though,
1012 // then the sort order is still effectively good. Simply
1013 // refresh the list to clear the dirty flag.
1014 if (mQueue.Length() <= 1) {
1015 Refresh();
1016 return;
1019 // Otherwise we must mark the queue dirty and potentially
1020 // trigger an expensive sort later.
1021 MarkDirty();
1024 void imgCacheQueue::Push(imgCacheEntry* entry) {
1025 mSize += entry->GetDataSize();
1027 RefPtr<imgCacheEntry> refptr(entry);
1028 mQueue.AppendElement(std::move(refptr));
1029 // If we're not dirty already, then we can efficiently add this to the
1030 // binary heap immediately. This is only O(log n).
1031 if (!IsDirty()) {
1032 std::push_heap(mQueue.begin(), mQueue.end(),
1033 imgLoader::CompareCacheEntries);
1037 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() {
1038 if (mQueue.IsEmpty()) {
1039 return nullptr;
1041 if (IsDirty()) {
1042 Refresh();
1045 std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1046 RefPtr<imgCacheEntry> entry = mQueue.PopLastElement();
1048 mSize -= entry->GetDataSize();
1049 return entry.forget();
1052 void imgCacheQueue::Refresh() {
1053 // Resort the list. This is an O(3 * n) operation and best avoided
1054 // if possible.
1055 std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1056 mDirty = false;
1059 void imgCacheQueue::MarkDirty() { mDirty = true; }
1061 bool imgCacheQueue::IsDirty() { return mDirty; }
1063 uint32_t imgCacheQueue::GetNumElements() const { return mQueue.Length(); }
1065 bool imgCacheQueue::Contains(imgCacheEntry* aEntry) const {
1066 return mQueue.Contains(aEntry);
1069 imgCacheQueue::iterator imgCacheQueue::begin() { return mQueue.begin(); }
1071 imgCacheQueue::const_iterator imgCacheQueue::begin() const {
1072 return mQueue.begin();
1075 imgCacheQueue::iterator imgCacheQueue::end() { return mQueue.end(); }
1077 imgCacheQueue::const_iterator imgCacheQueue::end() const {
1078 return mQueue.end();
1081 nsresult imgLoader::CreateNewProxyForRequest(
1082 imgRequest* aRequest, nsILoadGroup* aLoadGroup, Document* aLoadingDocument,
1083 imgINotificationObserver* aObserver, nsLoadFlags aLoadFlags,
1084 imgRequestProxy** _retval) {
1085 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest",
1086 "imgRequest", aRequest);
1088 /* XXX If we move decoding onto separate threads, we should save off the
1089 calling thread here and pass it off to |proxyRequest| so that it call
1090 proxy calls to |aObserver|.
1093 RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy();
1095 /* It is important to call |SetLoadFlags()| before calling |Init()| because
1096 |Init()| adds the request to the loadgroup.
1098 proxyRequest->SetLoadFlags(aLoadFlags);
1100 nsCOMPtr<nsIURI> uri;
1101 aRequest->GetURI(getter_AddRefs(uri));
1103 // init adds itself to imgRequest's list of observers
1104 nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aLoadingDocument, uri,
1105 aObserver);
1106 if (NS_WARN_IF(NS_FAILED(rv))) {
1107 return rv;
1110 proxyRequest.forget(_retval);
1111 return NS_OK;
1114 class imgCacheExpirationTracker final
1115 : public nsExpirationTracker<imgCacheEntry, 3> {
1116 enum { TIMEOUT_SECONDS = 10 };
1118 public:
1119 imgCacheExpirationTracker();
1121 protected:
1122 void NotifyExpired(imgCacheEntry* entry) override;
1125 imgCacheExpirationTracker::imgCacheExpirationTracker()
1126 : nsExpirationTracker<imgCacheEntry, 3>(
1127 TIMEOUT_SECONDS * 1000, "imgCacheExpirationTracker",
1128 SystemGroup::EventTargetFor(TaskCategory::Other)) {}
1130 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) {
1131 // Hold on to a reference to this entry, because the expiration tracker
1132 // mechanism doesn't.
1133 RefPtr<imgCacheEntry> kungFuDeathGrip(entry);
1135 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1136 RefPtr<imgRequest> req = entry->GetRequest();
1137 if (req) {
1138 LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired",
1139 "entry", req->CacheKey().URI());
1143 // We can be called multiple times on the same entry. Don't do work multiple
1144 // times.
1145 if (!entry->Evicted()) {
1146 entry->Loader()->RemoveFromCache(entry);
1149 entry->Loader()->VerifyCacheSizes();
1152 ///////////////////////////////////////////////////////////////////////////////
1153 // imgLoader
1154 ///////////////////////////////////////////////////////////////////////////////
1156 double imgLoader::sCacheTimeWeight;
1157 uint32_t imgLoader::sCacheMaxSize;
1158 imgMemoryReporter* imgLoader::sMemReporter;
1160 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
1161 nsISupportsWeakReference, nsIObserver)
1163 static imgLoader* gNormalLoader = nullptr;
1164 static imgLoader* gPrivateBrowsingLoader = nullptr;
1166 /* static */
1167 already_AddRefed<imgLoader> imgLoader::CreateImageLoader() {
1168 // In some cases, such as xpctests, XPCOM modules are not automatically
1169 // initialized. We need to make sure that our module is initialized before
1170 // we hand out imgLoader instances and code starts using them.
1171 mozilla::image::EnsureModuleInitialized();
1173 RefPtr<imgLoader> loader = new imgLoader();
1174 loader->Init();
1176 return loader.forget();
1179 imgLoader* imgLoader::NormalLoader() {
1180 if (!gNormalLoader) {
1181 gNormalLoader = CreateImageLoader().take();
1183 return gNormalLoader;
1186 imgLoader* imgLoader::PrivateBrowsingLoader() {
1187 if (!gPrivateBrowsingLoader) {
1188 gPrivateBrowsingLoader = CreateImageLoader().take();
1189 gPrivateBrowsingLoader->RespectPrivacyNotifications();
1191 return gPrivateBrowsingLoader;
1194 imgLoader::imgLoader()
1195 : mUncachedImagesMutex("imgLoader::UncachedImages"),
1196 mRespectPrivacy(false) {
1197 sMemReporter->AddRef();
1198 sMemReporter->RegisterLoader(this);
1201 imgLoader::~imgLoader() {
1202 ClearChromeImageCache();
1203 ClearImageCache();
1205 // If there are any of our imgRequest's left they are in the uncached
1206 // images set, so clear their pointer to us.
1207 MutexAutoLock lock(mUncachedImagesMutex);
1208 for (auto iter = mUncachedImages.Iter(); !iter.Done(); iter.Next()) {
1209 nsPtrHashKey<imgRequest>* entry = iter.Get();
1210 RefPtr<imgRequest> req = entry->GetKey();
1211 req->ClearLoader();
1214 sMemReporter->UnregisterLoader(this);
1215 sMemReporter->Release();
1218 void imgLoader::VerifyCacheSizes() {
1219 #ifdef DEBUG
1220 if (!mCacheTracker) {
1221 return;
1224 uint32_t cachesize = mCache.Count() + mChromeCache.Count();
1225 uint32_t queuesize =
1226 mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
1227 uint32_t trackersize = 0;
1228 for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
1229 it.Next();) {
1230 trackersize++;
1232 MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
1233 MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
1234 #endif
1237 imgLoader::imgCacheTable& imgLoader::GetCache(bool aForChrome) {
1238 return aForChrome ? mChromeCache : mCache;
1241 imgLoader::imgCacheTable& imgLoader::GetCache(const ImageCacheKey& aKey) {
1242 return GetCache(aKey.IsChrome());
1245 imgCacheQueue& imgLoader::GetCacheQueue(bool aForChrome) {
1246 return aForChrome ? mChromeCacheQueue : mCacheQueue;
1249 imgCacheQueue& imgLoader::GetCacheQueue(const ImageCacheKey& aKey) {
1250 return GetCacheQueue(aKey.IsChrome());
1253 void imgLoader::GlobalInit() {
1254 sCacheTimeWeight = StaticPrefs::ImageCacheTimeWeight() / 1000.0;
1255 int32_t cachesize = StaticPrefs::ImageCacheSize();
1256 sCacheMaxSize = cachesize > 0 ? cachesize : 0;
1258 sMemReporter = new imgMemoryReporter();
1259 RegisterStrongAsyncMemoryReporter(sMemReporter);
1260 RegisterImagesContentUsedUncompressedDistinguishedAmount(
1261 imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
1264 void imgLoader::ShutdownMemoryReporter() {
1265 UnregisterImagesContentUsedUncompressedDistinguishedAmount();
1266 UnregisterStrongMemoryReporter(sMemReporter);
1269 nsresult imgLoader::InitCache() {
1270 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1271 if (!os) {
1272 return NS_ERROR_FAILURE;
1275 os->AddObserver(this, "memory-pressure", false);
1276 os->AddObserver(this, "chrome-flush-caches", false);
1277 os->AddObserver(this, "last-pb-context-exited", false);
1278 os->AddObserver(this, "profile-before-change", false);
1279 os->AddObserver(this, "xpcom-shutdown", false);
1281 mCacheTracker = MakeUnique<imgCacheExpirationTracker>();
1283 return NS_OK;
1286 nsresult imgLoader::Init() {
1287 InitCache();
1289 ReadAcceptHeaderPref();
1291 Preferences::AddWeakObserver(this, "image.http.accept");
1293 return NS_OK;
1296 NS_IMETHODIMP
1297 imgLoader::RespectPrivacyNotifications() {
1298 mRespectPrivacy = true;
1299 return NS_OK;
1302 NS_IMETHODIMP
1303 imgLoader::Observe(nsISupports* aSubject, const char* aTopic,
1304 const char16_t* aData) {
1305 // We listen for pref change notifications...
1306 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1307 if (!NS_strcmp(aData, u"image.http.accept")) {
1308 ReadAcceptHeaderPref();
1311 } else if (strcmp(aTopic, "memory-pressure") == 0) {
1312 MinimizeCaches();
1313 } else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1314 MinimizeCaches();
1315 ClearChromeImageCache();
1316 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
1317 if (mRespectPrivacy) {
1318 ClearImageCache();
1319 ClearChromeImageCache();
1321 } else if (strcmp(aTopic, "profile-before-change") == 0) {
1322 mCacheTracker = nullptr;
1323 } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
1324 mCacheTracker = nullptr;
1325 ShutdownMemoryReporter();
1327 } else {
1328 // (Nothing else should bring us here)
1329 MOZ_ASSERT(0, "Invalid topic received");
1332 return NS_OK;
1335 void imgLoader::ReadAcceptHeaderPref() {
1336 nsAutoCString accept;
1337 nsresult rv = Preferences::GetCString("image.http.accept", accept);
1338 if (NS_SUCCEEDED(rv)) {
1339 mAcceptHeader = accept;
1340 } else {
1341 mAcceptHeader =
1342 IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5";
1346 NS_IMETHODIMP
1347 imgLoader::ClearCache(bool chrome) {
1348 if (XRE_IsParentProcess()) {
1349 bool privateLoader = this == gPrivateBrowsingLoader;
1350 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1351 Unused << cp->SendClearImageCache(privateLoader, chrome);
1355 if (chrome) {
1356 return ClearChromeImageCache();
1358 return ClearImageCache();
1361 NS_IMETHODIMP
1362 imgLoader::RemoveEntriesFromPrincipal(nsIPrincipal* aPrincipal) {
1363 nsAutoString origin;
1364 nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, origin);
1365 if (NS_WARN_IF(NS_FAILED(rv))) {
1366 return rv;
1369 AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1371 imgCacheTable& cache =
1372 GetCache(nsContentUtils::IsSystemPrincipal(aPrincipal));
1373 for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1374 auto& key = iter.Key();
1376 if (key.OriginAttributesRef() !=
1377 BasePrincipal::Cast(aPrincipal)->OriginAttributesRef()) {
1378 continue;
1381 nsAutoString imageOrigin;
1382 nsresult rv = nsContentUtils::GetUTFOrigin(key.URI(), imageOrigin);
1383 if (NS_WARN_IF(NS_FAILED(rv))) {
1384 continue;
1387 if (imageOrigin == origin) {
1388 entriesToBeRemoved.AppendElement(iter.Data());
1392 for (auto& entry : entriesToBeRemoved) {
1393 if (!RemoveFromCache(entry)) {
1394 NS_WARNING(
1395 "Couldn't remove an entry from the cache in "
1396 "RemoveEntriesFromPrincipal()\n");
1400 return NS_OK;
1403 NS_IMETHODIMP
1404 imgLoader::RemoveEntry(nsIURI* aURI, Document* aDoc) {
1405 if (aURI) {
1406 OriginAttributes attrs;
1407 if (aDoc) {
1408 nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1409 if (principal) {
1410 attrs = principal->OriginAttributesRef();
1414 ImageCacheKey key(aURI, attrs, aDoc);
1415 if (RemoveFromCache(key)) {
1416 return NS_OK;
1419 return NS_ERROR_NOT_AVAILABLE;
1422 NS_IMETHODIMP
1423 imgLoader::FindEntryProperties(nsIURI* uri, Document* aDoc,
1424 nsIProperties** _retval) {
1425 *_retval = nullptr;
1427 OriginAttributes attrs;
1428 if (aDoc) {
1429 nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1430 if (principal) {
1431 attrs = principal->OriginAttributesRef();
1435 ImageCacheKey key(uri, attrs, aDoc);
1436 imgCacheTable& cache = GetCache(key);
1438 RefPtr<imgCacheEntry> entry;
1439 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1440 if (mCacheTracker && entry->HasNoProxies()) {
1441 mCacheTracker->MarkUsed(entry);
1444 RefPtr<imgRequest> request = entry->GetRequest();
1445 if (request) {
1446 nsCOMPtr<nsIProperties> properties = request->Properties();
1447 properties.forget(_retval);
1451 return NS_OK;
1454 NS_IMETHODIMP_(void)
1455 imgLoader::ClearCacheForControlledDocument(Document* aDoc) {
1456 MOZ_ASSERT(aDoc);
1457 AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1458 imgCacheTable& cache = GetCache(false);
1459 for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1460 auto& key = iter.Key();
1461 if (key.ControlledDocument() == aDoc) {
1462 entriesToBeRemoved.AppendElement(iter.Data());
1465 for (auto& entry : entriesToBeRemoved) {
1466 if (!RemoveFromCache(entry)) {
1467 NS_WARNING(
1468 "Couldn't remove an entry from the cache in "
1469 "ClearCacheForControlledDocument()\n");
1474 void imgLoader::Shutdown() {
1475 NS_IF_RELEASE(gNormalLoader);
1476 gNormalLoader = nullptr;
1477 NS_IF_RELEASE(gPrivateBrowsingLoader);
1478 gPrivateBrowsingLoader = nullptr;
1481 nsresult imgLoader::ClearChromeImageCache() {
1482 return EvictEntries(mChromeCache);
1485 nsresult imgLoader::ClearImageCache() { return EvictEntries(mCache); }
1487 void imgLoader::MinimizeCaches() {
1488 EvictEntries(mCacheQueue);
1489 EvictEntries(mChromeCacheQueue);
1492 bool imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) {
1493 imgCacheTable& cache = GetCache(aKey);
1495 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::PutIntoCache", "uri",
1496 aKey.URI());
1498 // Check to see if this request already exists in the cache. If so, we'll
1499 // replace the old version.
1500 RefPtr<imgCacheEntry> tmpCacheEntry;
1501 if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1502 MOZ_LOG(
1503 gImgLog, LogLevel::Debug,
1504 ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
1505 nullptr));
1506 RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1508 // If it already exists, and we're putting the same key into the cache, we
1509 // should remove the old version.
1510 MOZ_LOG(gImgLog, LogLevel::Debug,
1511 ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element",
1512 nullptr));
1514 RemoveFromCache(aKey);
1515 } else {
1516 MOZ_LOG(gImgLog, LogLevel::Debug,
1517 ("[this=%p] imgLoader::PutIntoCache --"
1518 " Element NOT already in the cache",
1519 nullptr));
1522 cache.Put(aKey, entry);
1524 // We can be called to resurrect an evicted entry.
1525 if (entry->Evicted()) {
1526 entry->SetEvicted(false);
1529 // If we're resurrecting an entry with no proxies, put it back in the
1530 // tracker and queue.
1531 if (entry->HasNoProxies()) {
1532 nsresult addrv = NS_OK;
1534 if (mCacheTracker) {
1535 addrv = mCacheTracker->AddObject(entry);
1538 if (NS_SUCCEEDED(addrv)) {
1539 imgCacheQueue& queue = GetCacheQueue(aKey);
1540 queue.Push(entry);
1544 RefPtr<imgRequest> request = entry->GetRequest();
1545 request->SetIsInCache(true);
1546 RemoveFromUncachedImages(request);
1548 return true;
1551 bool imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) {
1552 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasNoProxies", "uri",
1553 aRequest->CacheKey().URI());
1555 aEntry->SetHasNoProxies(true);
1557 if (aEntry->Evicted()) {
1558 return false;
1561 imgCacheQueue& queue = GetCacheQueue(aRequest->IsChrome());
1563 nsresult addrv = NS_OK;
1565 if (mCacheTracker) {
1566 addrv = mCacheTracker->AddObject(aEntry);
1569 if (NS_SUCCEEDED(addrv)) {
1570 queue.Push(aEntry);
1573 imgCacheTable& cache = GetCache(aRequest->IsChrome());
1574 CheckCacheLimits(cache, queue);
1576 return true;
1579 bool imgLoader::SetHasProxies(imgRequest* aRequest) {
1580 VerifyCacheSizes();
1582 const ImageCacheKey& key = aRequest->CacheKey();
1583 imgCacheTable& cache = GetCache(key);
1585 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies", "uri",
1586 key.URI());
1588 RefPtr<imgCacheEntry> entry;
1589 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1590 // Make sure the cache entry is for the right request
1591 RefPtr<imgRequest> entryRequest = entry->GetRequest();
1592 if (entryRequest == aRequest && entry->HasNoProxies()) {
1593 imgCacheQueue& queue = GetCacheQueue(key);
1594 queue.Remove(entry);
1596 if (mCacheTracker) {
1597 mCacheTracker->RemoveObject(entry);
1600 entry->SetHasNoProxies(false);
1602 return true;
1606 return false;
1609 void imgLoader::CacheEntriesChanged(bool aForChrome,
1610 int32_t aSizeDiff /* = 0 */) {
1611 imgCacheQueue& queue = GetCacheQueue(aForChrome);
1612 // We only need to dirty the queue if there is any sorting
1613 // taking place. Empty or single-entry lists can't become
1614 // dirty.
1615 if (queue.GetNumElements() > 1) {
1616 queue.MarkDirty();
1618 queue.UpdateSize(aSizeDiff);
1621 void imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue) {
1622 if (queue.GetNumElements() == 0) {
1623 NS_ASSERTION(queue.GetSize() == 0,
1624 "imgLoader::CheckCacheLimits -- incorrect cache size");
1627 // Remove entries from the cache until we're back at our desired max size.
1628 while (queue.GetSize() > sCacheMaxSize) {
1629 // Remove the first entry in the queue.
1630 RefPtr<imgCacheEntry> entry(queue.Pop());
1632 NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1634 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1635 RefPtr<imgRequest> req = entry->GetRequest();
1636 if (req) {
1637 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::CheckCacheLimits",
1638 "entry", req->CacheKey().URI());
1642 if (entry) {
1643 // We just popped this entry from the queue, so pass AlreadyRemoved
1644 // to avoid searching the queue again in RemoveFromCache.
1645 RemoveFromCache(entry, QueueState::AlreadyRemoved);
1650 bool imgLoader::ValidateRequestWithNewChannel(
1651 imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1652 nsIURI* aReferrerURI, ReferrerPolicy aReferrerPolicy,
1653 nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
1654 nsISupports* aCX, Document* aLoadingDocument, nsLoadFlags aLoadFlags,
1655 nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest,
1656 nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode,
1657 bool* aNewChannelCreated) {
1658 // now we need to insert a new channel request object inbetween the real
1659 // request and the proxy that basically delays loading the image until it
1660 // gets a 304 or figures out that this needs to be a new request
1662 nsresult rv;
1664 // If we're currently in the middle of validating this request, just hand
1665 // back a proxy to it; the required work will be done for us.
1666 if (request->GetValidator()) {
1667 rv = CreateNewProxyForRequest(request, aLoadGroup, aLoadingDocument,
1668 aObserver, aLoadFlags, aProxyRequest);
1669 if (NS_FAILED(rv)) {
1670 return false;
1673 if (*aProxyRequest) {
1674 imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1676 // We will send notifications from imgCacheValidator::OnStartRequest().
1677 // In the mean time, we must defer notifications because we are added to
1678 // the imgRequest's proxy list, and we can get extra notifications
1679 // resulting from methods such as StartDecoding(). See bug 579122.
1680 proxy->MarkValidating();
1682 // Attach the proxy without notifying
1683 request->GetValidator()->AddProxy(proxy);
1686 return NS_SUCCEEDED(rv);
1688 // We will rely on Necko to cache this request when it's possible, and to
1689 // tell imgCacheValidator::OnStartRequest whether the request came from its
1690 // cache.
1691 nsCOMPtr<nsIChannel> newChannel;
1692 bool forcePrincipalCheck;
1693 rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
1694 aInitialDocumentURI, aCORSMode, aReferrerURI,
1695 aReferrerPolicy, aLoadGroup, mAcceptHeader, aLoadFlags,
1696 aLoadPolicyType, aTriggeringPrincipal, aCX,
1697 mRespectPrivacy);
1698 if (NS_FAILED(rv)) {
1699 return false;
1702 if (aNewChannelCreated) {
1703 *aNewChannelCreated = true;
1706 RefPtr<imgRequestProxy> req;
1707 rv = CreateNewProxyForRequest(request, aLoadGroup, aLoadingDocument,
1708 aObserver, aLoadFlags, getter_AddRefs(req));
1709 if (NS_FAILED(rv)) {
1710 return false;
1713 // Make sure that OnStatus/OnProgress calls have the right request set...
1714 RefPtr<nsProgressNotificationProxy> progressproxy =
1715 new nsProgressNotificationProxy(newChannel, req);
1716 if (!progressproxy) {
1717 return false;
1720 RefPtr<imgCacheValidator> hvc = new imgCacheValidator(
1721 progressproxy, this, request, aCX, forcePrincipalCheck);
1723 // Casting needed here to get past multiple inheritance.
1724 nsCOMPtr<nsIStreamListener> listener =
1725 do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
1726 NS_ENSURE_TRUE(listener, false);
1728 // We must set the notification callbacks before setting up the
1729 // CORS listener, because that's also interested inthe
1730 // notification callbacks.
1731 newChannel->SetNotificationCallbacks(hvc);
1733 request->SetValidator(hvc);
1735 // We will send notifications from imgCacheValidator::OnStartRequest().
1736 // In the mean time, we must defer notifications because we are added to
1737 // the imgRequest's proxy list, and we can get extra notifications
1738 // resulting from methods such as StartDecoding(). See bug 579122.
1739 req->MarkValidating();
1741 // Add the proxy without notifying
1742 hvc->AddProxy(req);
1744 mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
1745 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1746 aLoadGroup);
1747 rv = newChannel->AsyncOpen(listener);
1748 if (NS_WARN_IF(NS_FAILED(rv))) {
1749 req->CancelAndForgetObserver(rv);
1750 return false;
1753 req.forget(aProxyRequest);
1754 return true;
1757 bool imgLoader::ValidateEntry(
1758 imgCacheEntry* aEntry, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1759 nsIURI* aReferrerURI, ReferrerPolicy aReferrerPolicy,
1760 nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
1761 nsISupports* aCX, Document* aLoadingDocument, nsLoadFlags aLoadFlags,
1762 nsContentPolicyType aLoadPolicyType, bool aCanMakeNewChannel,
1763 bool* aNewChannelCreated, imgRequestProxy** aProxyRequest,
1764 nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode) {
1765 LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
1767 // If the expiration time is zero, then the request has not gotten far enough
1768 // to know when it will expire.
1769 uint32_t expiryTime = aEntry->GetExpiryTime();
1770 bool hasExpired = expiryTime != 0 &&
1771 expiryTime <= imgCacheEntry::SecondsFromPRTime(PR_Now());
1773 nsresult rv;
1775 // Special treatment for file URLs - aEntry has expired if file has changed
1776 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
1777 if (fileUrl) {
1778 uint32_t lastModTime = aEntry->GetLoadTime();
1780 nsCOMPtr<nsIFile> theFile;
1781 rv = fileUrl->GetFile(getter_AddRefs(theFile));
1782 if (NS_SUCCEEDED(rv)) {
1783 PRTime fileLastMod;
1784 rv = theFile->GetLastModifiedTime(&fileLastMod);
1785 if (NS_SUCCEEDED(rv)) {
1786 // nsIFile uses millisec, NSPR usec
1787 fileLastMod *= 1000;
1788 hasExpired =
1789 imgCacheEntry::SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
1794 RefPtr<imgRequest> request(aEntry->GetRequest());
1796 if (!request) {
1797 return false;
1800 if (!ValidateSecurityInfo(request, aEntry->ForcePrincipalCheck(), aCORSMode,
1801 aTriggeringPrincipal, aCX, aLoadPolicyType,
1802 aReferrerPolicy))
1803 return false;
1805 // data URIs are immutable and by their nature can't leak data, so we can
1806 // just return true in that case. Doing so would mean that shift-reload
1807 // doesn't reload data URI documents/images though (which is handy for
1808 // debugging during gecko development) so we make an exception in that case.
1809 nsAutoCString scheme;
1810 aURI->GetScheme(scheme);
1811 if (scheme.EqualsLiteral("data") &&
1812 !(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) {
1813 return true;
1816 bool validateRequest = false;
1818 // If the request's loadId is the same as the aCX, then it is ok to use
1819 // this one because it has already been validated for this context.
1821 // XXX: nullptr seems to be a 'special' key value that indicates that NO
1822 // validation is required.
1823 // XXX: we also check the window ID because the loadID() can return a reused
1824 // pointer of a document. This can still happen for non-document image
1825 // cache entries.
1826 void* key = (void*)aCX;
1827 nsCOMPtr<Document> doc = do_QueryInterface(aCX);
1828 uint64_t innerWindowID = doc ? doc->InnerWindowID() : 0;
1829 if (request->LoadId() != key || request->InnerWindowID() != innerWindowID) {
1830 // If we would need to revalidate this entry, but we're being told to
1831 // bypass the cache, we don't allow this entry to be used.
1832 if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1833 return false;
1836 if (MOZ_UNLIKELY(ChaosMode::isActive(ChaosFeature::ImageCache))) {
1837 if (ChaosMode::randomUint32LessThan(4) < 1) {
1838 return false;
1842 // Determine whether the cache aEntry must be revalidated...
1843 validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
1845 MOZ_LOG(gImgLog, LogLevel::Debug,
1846 ("imgLoader::ValidateEntry validating cache entry. "
1847 "validateRequest = %d",
1848 validateRequest));
1849 } else if (!key && MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1850 MOZ_LOG(gImgLog, LogLevel::Debug,
1851 ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
1852 "because of NULL LoadID",
1853 aURI->GetSpecOrDefault().get()));
1856 // We can't use a cached request if it comes from a different
1857 // application cache than this load is expecting.
1858 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
1859 nsCOMPtr<nsIApplicationCache> requestAppCache;
1860 nsCOMPtr<nsIApplicationCache> groupAppCache;
1861 if ((appCacheContainer = do_GetInterface(request->GetRequest()))) {
1862 appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
1864 if ((appCacheContainer = do_QueryInterface(aLoadGroup))) {
1865 appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
1868 if (requestAppCache != groupAppCache) {
1869 MOZ_LOG(gImgLog, LogLevel::Debug,
1870 ("imgLoader::ValidateEntry - Unable to use cached imgRequest "
1871 "[request=%p] because of mismatched application caches\n",
1872 address_of(request)));
1873 return false;
1876 if (validateRequest && aCanMakeNewChannel) {
1877 LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate");
1879 return ValidateRequestWithNewChannel(
1880 request, aURI, aInitialDocumentURI, aReferrerURI, aReferrerPolicy,
1881 aLoadGroup, aObserver, aCX, aLoadingDocument, aLoadFlags,
1882 aLoadPolicyType, aProxyRequest, aTriggeringPrincipal, aCORSMode,
1883 aNewChannelCreated);
1886 return !validateRequest;
1889 bool imgLoader::RemoveFromCache(const ImageCacheKey& aKey) {
1890 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache", "uri",
1891 aKey.URI());
1893 imgCacheTable& cache = GetCache(aKey);
1894 imgCacheQueue& queue = GetCacheQueue(aKey);
1896 RefPtr<imgCacheEntry> entry;
1897 cache.Remove(aKey, getter_AddRefs(entry));
1898 if (entry) {
1899 MOZ_ASSERT(!entry->Evicted(), "Evicting an already-evicted cache entry!");
1901 // Entries with no proxies are in the tracker.
1902 if (entry->HasNoProxies()) {
1903 if (mCacheTracker) {
1904 mCacheTracker->RemoveObject(entry);
1906 queue.Remove(entry);
1909 entry->SetEvicted(true);
1911 RefPtr<imgRequest> request = entry->GetRequest();
1912 request->SetIsInCache(false);
1913 AddToUncachedImages(request);
1915 return true;
1917 return false;
1920 bool imgLoader::RemoveFromCache(imgCacheEntry* entry, QueueState aQueueState) {
1921 LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry");
1923 RefPtr<imgRequest> request = entry->GetRequest();
1924 if (request) {
1925 const ImageCacheKey& key = request->CacheKey();
1926 imgCacheTable& cache = GetCache(key);
1927 imgCacheQueue& queue = GetCacheQueue(key);
1929 LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache",
1930 "entry's uri", key.URI());
1932 cache.Remove(key);
1934 if (entry->HasNoProxies()) {
1935 LOG_STATIC_FUNC(gImgLog,
1936 "imgLoader::RemoveFromCache removing from tracker");
1937 if (mCacheTracker) {
1938 mCacheTracker->RemoveObject(entry);
1940 // Only search the queue to remove the entry if its possible it might
1941 // be in the queue. If we know its not in the queue this would be
1942 // wasted work.
1943 MOZ_ASSERT_IF(aQueueState == QueueState::AlreadyRemoved,
1944 !queue.Contains(entry));
1945 if (aQueueState == QueueState::MaybeExists) {
1946 queue.Remove(entry);
1950 entry->SetEvicted(true);
1951 request->SetIsInCache(false);
1952 AddToUncachedImages(request);
1954 return true;
1957 return false;
1960 nsresult imgLoader::EvictEntries(imgCacheTable& aCacheToClear) {
1961 LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries table");
1963 // We have to make a temporary, since RemoveFromCache removes the element
1964 // from the queue, invalidating iterators.
1965 nsTArray<RefPtr<imgCacheEntry> > entries;
1966 for (auto iter = aCacheToClear.Iter(); !iter.Done(); iter.Next()) {
1967 RefPtr<imgCacheEntry>& data = iter.Data();
1968 entries.AppendElement(data);
1971 for (uint32_t i = 0; i < entries.Length(); ++i) {
1972 if (!RemoveFromCache(entries[i])) {
1973 return NS_ERROR_FAILURE;
1977 MOZ_ASSERT(aCacheToClear.Count() == 0);
1979 return NS_OK;
1982 nsresult imgLoader::EvictEntries(imgCacheQueue& aQueueToClear) {
1983 LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries queue");
1985 // We have to make a temporary, since RemoveFromCache removes the element
1986 // from the queue, invalidating iterators.
1987 nsTArray<RefPtr<imgCacheEntry> > entries(aQueueToClear.GetNumElements());
1988 for (auto i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i) {
1989 entries.AppendElement(*i);
1992 // Iterate in reverse order to minimize array copying.
1993 for (auto& entry : entries) {
1994 if (!RemoveFromCache(entry)) {
1995 return NS_ERROR_FAILURE;
1999 MOZ_ASSERT(aQueueToClear.GetNumElements() == 0);
2001 return NS_OK;
2004 void imgLoader::AddToUncachedImages(imgRequest* aRequest) {
2005 MutexAutoLock lock(mUncachedImagesMutex);
2006 mUncachedImages.PutEntry(aRequest);
2009 void imgLoader::RemoveFromUncachedImages(imgRequest* aRequest) {
2010 MutexAutoLock lock(mUncachedImagesMutex);
2011 mUncachedImages.RemoveEntry(aRequest);
2014 bool imgLoader::PreferLoadFromCache(nsIURI* aURI) const {
2015 // If we are trying to load an image from a protocol that doesn't support
2016 // caching (e.g. thumbnails via the moz-page-thumb:// protocol, or icons via
2017 // the moz-extension:// protocol), load it directly from the cache to prevent
2018 // re-decoding the image. See Bug 1373258.
2019 // TODO: Bug 1406134
2020 bool match = false;
2021 return (NS_SUCCEEDED(aURI->SchemeIs("moz-page-thumb", &match)) && match) ||
2022 (NS_SUCCEEDED(aURI->SchemeIs("moz-extension", &match)) && match);
2025 #define LOAD_FLAGS_CACHE_MASK \
2026 (nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FROM_CACHE)
2028 #define LOAD_FLAGS_VALIDATE_MASK \
2029 (nsIRequest::VALIDATE_ALWAYS | nsIRequest::VALIDATE_NEVER | \
2030 nsIRequest::VALIDATE_ONCE_PER_SESSION)
2032 NS_IMETHODIMP
2033 imgLoader::LoadImageXPCOM(
2034 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIURI* aReferrerURI,
2035 const nsAString& aReferrerPolicy, nsIPrincipal* aTriggeringPrincipal,
2036 nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
2037 nsISupports* aCX, nsLoadFlags aLoadFlags, nsISupports* aCacheKey,
2038 nsContentPolicyType aContentPolicyType, imgIRequest** _retval) {
2039 // Optional parameter, so defaults to 0 (== TYPE_INVALID)
2040 if (!aContentPolicyType) {
2041 aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2043 imgRequestProxy* proxy;
2044 ReferrerPolicy refpol = ReferrerPolicyFromString(aReferrerPolicy);
2045 nsCOMPtr<nsINode> node = do_QueryInterface(aCX);
2046 nsCOMPtr<Document> doc = do_QueryInterface(aCX);
2047 nsresult rv =
2048 LoadImage(aURI, aInitialDocumentURI, aReferrerURI, refpol,
2049 aTriggeringPrincipal, 0, aLoadGroup, aObserver, node, doc,
2050 aLoadFlags, aCacheKey, aContentPolicyType, EmptyString(),
2051 /* aUseUrgentStartForChannel */ false, &proxy);
2052 *_retval = proxy;
2053 return rv;
2056 nsresult imgLoader::LoadImage(
2057 nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIURI* aReferrerURI,
2058 ReferrerPolicy aReferrerPolicy, nsIPrincipal* aTriggeringPrincipal,
2059 uint64_t aRequestContextID, nsILoadGroup* aLoadGroup,
2060 imgINotificationObserver* aObserver, nsINode* aContext,
2061 Document* aLoadingDocument, nsLoadFlags aLoadFlags, nsISupports* aCacheKey,
2062 nsContentPolicyType aContentPolicyType, const nsAString& initiatorType,
2063 bool aUseUrgentStartForChannel, imgRequestProxy** _retval) {
2064 VerifyCacheSizes();
2066 NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
2068 if (!aURI) {
2069 return NS_ERROR_NULL_POINTER;
2072 #ifdef MOZ_GECKO_PROFILER
2073 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("imgLoader::LoadImage", NETWORK,
2074 aURI->GetSpecOrDefault());
2075 #endif
2077 LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", aURI);
2079 *_retval = nullptr;
2081 RefPtr<imgRequest> request;
2083 nsresult rv;
2084 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2086 #ifdef DEBUG
2087 bool isPrivate = false;
2089 if (aLoadingDocument) {
2090 isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadingDocument);
2091 } else if (aLoadGroup) {
2092 isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadGroup);
2094 MOZ_ASSERT(isPrivate == mRespectPrivacy);
2096 if (aLoadingDocument) {
2097 // The given load group should match that of the document if given. If
2098 // that isn't the case, then we need to add more plumbing to ensure we
2099 // block the document as well.
2100 nsCOMPtr<nsILoadGroup> docLoadGroup =
2101 aLoadingDocument->GetDocumentLoadGroup();
2102 MOZ_ASSERT(docLoadGroup == aLoadGroup);
2104 #endif
2106 // Get the default load flags from the loadgroup (if possible)...
2107 if (aLoadGroup) {
2108 aLoadGroup->GetLoadFlags(&requestFlags);
2109 if (PreferLoadFromCache(aURI)) {
2110 requestFlags |= nsIRequest::LOAD_FROM_CACHE;
2114 // Merge the default load flags with those passed in via aLoadFlags.
2115 // Currently, *only* the caching, validation and background load flags
2116 // are merged...
2118 // The flags in aLoadFlags take precedence over the default flags!
2120 if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
2121 // Override the default caching flags...
2122 requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
2123 (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
2125 if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
2126 // Override the default validation flags...
2127 requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
2128 (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
2130 if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
2131 // Propagate background loading...
2132 requestFlags |= nsIRequest::LOAD_BACKGROUND;
2135 int32_t corsmode = imgIRequest::CORS_NONE;
2136 if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) {
2137 corsmode = imgIRequest::CORS_ANONYMOUS;
2138 } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) {
2139 corsmode = imgIRequest::CORS_USE_CREDENTIALS;
2142 RefPtr<imgCacheEntry> entry;
2144 // Look in the cache for our URI, and then validate it.
2145 // XXX For now ignore aCacheKey. We will need it in the future
2146 // for correctly dealing with image load requests that are a result
2147 // of post data.
2148 OriginAttributes attrs;
2149 if (aTriggeringPrincipal) {
2150 attrs = aTriggeringPrincipal->OriginAttributesRef();
2152 ImageCacheKey key(aURI, attrs, aLoadingDocument);
2153 imgCacheTable& cache = GetCache(key);
2155 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2156 bool newChannelCreated = false;
2157 if (ValidateEntry(
2158 entry, aURI, aInitialDocumentURI, aReferrerURI, aReferrerPolicy,
2159 aLoadGroup, aObserver, ToSupports(aLoadingDocument),
2160 aLoadingDocument, requestFlags, aContentPolicyType, true,
2161 &newChannelCreated, _retval, aTriggeringPrincipal, corsmode)) {
2162 request = entry->GetRequest();
2164 // If this entry has no proxies, its request has no reference to the
2165 // entry.
2166 if (entry->HasNoProxies()) {
2167 LOG_FUNC_WITH_PARAM(gImgLog,
2168 "imgLoader::LoadImage() adding proxyless entry",
2169 "uri", key.URI());
2170 MOZ_ASSERT(!request->HasCacheEntry(),
2171 "Proxyless entry's request has cache entry!");
2172 request->SetCacheEntry(entry);
2174 if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2175 mCacheTracker->MarkUsed(entry);
2179 entry->Touch();
2181 if (!newChannelCreated) {
2182 // This is ugly but it's needed to report CSP violations. We have 3
2183 // scenarios:
2184 // - we don't have cache. We are not in this if() stmt. A new channel is
2185 // created and that triggers the CSP checks.
2186 // - We have a cache entry and this is blocked by CSP directives.
2187 DebugOnly<bool> shouldLoad =
2188 ShouldLoadCachedImage(request, ToSupports(aLoadingDocument),
2189 aTriggeringPrincipal, aContentPolicyType,
2190 /* aSendCSPViolationReports */ true);
2191 MOZ_ASSERT(shouldLoad);
2193 } else {
2194 // We can't use this entry. We'll try to load it off the network, and if
2195 // successful, overwrite the old entry in the cache with a new one.
2196 entry = nullptr;
2200 // Keep the channel in this scope, so we can adjust its notificationCallbacks
2201 // later when we create the proxy.
2202 nsCOMPtr<nsIChannel> newChannel;
2203 // If we didn't get a cache hit, we need to load from the network.
2204 if (!request) {
2205 LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|");
2207 bool forcePrincipalCheck;
2208 rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
2209 aInitialDocumentURI, corsmode, aReferrerURI,
2210 aReferrerPolicy, aLoadGroup, mAcceptHeader,
2211 requestFlags, aContentPolicyType, aTriggeringPrincipal,
2212 aContext, mRespectPrivacy);
2213 if (NS_FAILED(rv)) {
2214 return NS_ERROR_FAILURE;
2217 MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy);
2219 NewRequestAndEntry(forcePrincipalCheck, this, key, getter_AddRefs(request),
2220 getter_AddRefs(entry));
2222 MOZ_LOG(gImgLog, LogLevel::Debug,
2223 ("[this=%p] imgLoader::LoadImage -- Created new imgRequest"
2224 " [request=%p]\n",
2225 this, request.get()));
2227 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
2228 if (cos) {
2229 if (aUseUrgentStartForChannel) {
2230 cos->AddClassFlags(nsIClassOfService::UrgentStart);
2233 if (StaticPrefs::network_http_tailing_enabled() &&
2234 aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
2235 cos->AddClassFlags(nsIClassOfService::Throttleable |
2236 nsIClassOfService::Tail);
2237 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
2238 if (httpChannel) {
2239 Unused << httpChannel->SetRequestContextID(aRequestContextID);
2244 nsCOMPtr<nsILoadGroup> channelLoadGroup;
2245 newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
2246 rv = request->Init(aURI, aURI, /* aHadInsecureRedirect = */ false,
2247 channelLoadGroup, newChannel, entry,
2248 ToSupports(aLoadingDocument), aTriggeringPrincipal,
2249 corsmode, aReferrerPolicy);
2250 if (NS_FAILED(rv)) {
2251 return NS_ERROR_FAILURE;
2254 // Add the initiator type for this image load
2255 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel);
2256 if (timedChannel) {
2257 timedChannel->SetInitiatorType(initiatorType);
2260 // create the proxy listener
2261 nsCOMPtr<nsIStreamListener> listener = new ProxyListener(request.get());
2263 MOZ_LOG(gImgLog, LogLevel::Debug,
2264 ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n",
2265 this));
2267 mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
2268 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
2269 aLoadGroup);
2271 nsresult openRes = newChannel->AsyncOpen(listener);
2273 if (NS_FAILED(openRes)) {
2274 MOZ_LOG(
2275 gImgLog, LogLevel::Debug,
2276 ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%" PRIx32
2277 "\n",
2278 this, static_cast<uint32_t>(openRes)));
2279 request->CancelAndAbort(openRes);
2280 return openRes;
2283 // Try to add the new request into the cache.
2284 PutIntoCache(key, entry);
2285 } else {
2286 LOG_MSG_WITH_PARAM(gImgLog, "imgLoader::LoadImage |cache hit|", "request",
2287 request);
2290 // If we didn't get a proxy when validating the cache entry, we need to
2291 // create one.
2292 if (!*_retval) {
2293 // ValidateEntry() has three return values: "Is valid," "might be valid --
2294 // validating over network", and "not valid." If we don't have a _retval,
2295 // we know ValidateEntry is not validating over the network, so it's safe
2296 // to SetLoadId here because we know this request is valid for this context.
2298 // Note, however, that this doesn't guarantee the behaviour we want (one
2299 // URL maps to the same image on a page) if we load the same image in a
2300 // different tab (see bug 528003), because its load id will get re-set, and
2301 // that'll cause us to validate over the network.
2302 request->SetLoadId(aLoadingDocument);
2304 LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request.");
2305 rv = CreateNewProxyForRequest(request, aLoadGroup, aLoadingDocument,
2306 aObserver, requestFlags, _retval);
2307 if (NS_FAILED(rv)) {
2308 return rv;
2311 imgRequestProxy* proxy = *_retval;
2313 // Make sure that OnStatus/OnProgress calls have the right request set, if
2314 // we did create a channel here.
2315 if (newChannel) {
2316 nsCOMPtr<nsIInterfaceRequestor> requestor(
2317 new nsProgressNotificationProxy(newChannel, proxy));
2318 if (!requestor) {
2319 return NS_ERROR_OUT_OF_MEMORY;
2321 newChannel->SetNotificationCallbacks(requestor);
2324 // Note that it's OK to add here even if the request is done. If it is,
2325 // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
2326 // the proxy will be removed from the loadgroup.
2327 proxy->AddToLoadGroup();
2329 // If we're loading off the network, explicitly don't notify our proxy,
2330 // because necko (or things called from necko, such as imgCacheValidator)
2331 // are going to call our notifications asynchronously, and we can't make it
2332 // further asynchronous because observers might rely on imagelib completing
2333 // its work between the channel's OnStartRequest and OnStopRequest.
2334 if (!newChannel) {
2335 proxy->NotifyListener();
2338 return rv;
2341 NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
2343 return NS_OK;
2346 NS_IMETHODIMP
2347 imgLoader::LoadImageWithChannelXPCOM(nsIChannel* channel,
2348 imgINotificationObserver* aObserver,
2349 nsISupports* aCX,
2350 nsIStreamListener** listener,
2351 imgIRequest** _retval) {
2352 nsresult result;
2353 imgRequestProxy* proxy;
2354 result = LoadImageWithChannel(channel, aObserver, aCX, listener, &proxy);
2355 *_retval = proxy;
2356 return result;
2359 nsresult imgLoader::LoadImageWithChannel(nsIChannel* channel,
2360 imgINotificationObserver* aObserver,
2361 nsISupports* aCX,
2362 nsIStreamListener** listener,
2363 imgRequestProxy** _retval) {
2364 NS_ASSERTION(channel,
2365 "imgLoader::LoadImageWithChannel -- NULL channel pointer");
2367 MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
2369 LOG_SCOPE(gImgLog, "imgLoader::LoadImageWithChannel");
2370 RefPtr<imgRequest> request;
2372 nsCOMPtr<nsIURI> uri;
2373 channel->GetURI(getter_AddRefs(uri));
2374 nsCOMPtr<Document> doc = do_QueryInterface(aCX);
2376 NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
2377 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2379 OriginAttributes attrs = loadInfo->GetOriginAttributes();
2381 ImageCacheKey key(uri, attrs, doc);
2383 nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2384 channel->GetLoadFlags(&requestFlags);
2386 if (PreferLoadFromCache(uri)) {
2387 requestFlags |= nsIRequest::LOAD_FROM_CACHE;
2390 RefPtr<imgCacheEntry> entry;
2392 if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
2393 RemoveFromCache(key);
2394 } else {
2395 // Look in the cache for our URI, and then validate it.
2396 // XXX For now ignore aCacheKey. We will need it in the future
2397 // for correctly dealing with image load requests that are a result
2398 // of post data.
2399 imgCacheTable& cache = GetCache(key);
2400 if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2401 // We don't want to kick off another network load. So we ask
2402 // ValidateEntry to only do validation without creating a new proxy. If
2403 // it says that the entry isn't valid any more, we'll only use the entry
2404 // we're getting if the channel is loading from the cache anyways.
2406 // XXX -- should this be changed? it's pretty much verbatim from the old
2407 // code, but seems nonsensical.
2409 // Since aCanMakeNewChannel == false, we don't need to pass content policy
2410 // type/principal/etc
2412 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2413 // if there is a loadInfo, use the right contentType, otherwise
2414 // default to the internal image type
2415 nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
2417 if (ValidateEntry(entry, uri, nullptr, nullptr, RP_Unset, nullptr,
2418 aObserver, aCX, doc, requestFlags, policyType, false,
2419 nullptr, nullptr, nullptr, imgIRequest::CORS_NONE)) {
2420 request = entry->GetRequest();
2421 } else {
2422 nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(channel));
2423 bool bUseCacheCopy;
2425 if (cacheChan) {
2426 cacheChan->IsFromCache(&bUseCacheCopy);
2427 } else {
2428 bUseCacheCopy = false;
2431 if (!bUseCacheCopy) {
2432 entry = nullptr;
2433 } else {
2434 request = entry->GetRequest();
2438 if (request && entry) {
2439 // If this entry has no proxies, its request has no reference to
2440 // the entry.
2441 if (entry->HasNoProxies()) {
2442 LOG_FUNC_WITH_PARAM(
2443 gImgLog,
2444 "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri",
2445 key.URI());
2446 MOZ_ASSERT(!request->HasCacheEntry(),
2447 "Proxyless entry's request has cache entry!");
2448 request->SetCacheEntry(entry);
2450 if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2451 mCacheTracker->MarkUsed(entry);
2458 nsCOMPtr<nsILoadGroup> loadGroup;
2459 channel->GetLoadGroup(getter_AddRefs(loadGroup));
2461 #ifdef DEBUG
2462 if (doc) {
2463 // The load group of the channel should always match that of the
2464 // document if given. If that isn't the case, then we need to add more
2465 // plumbing to ensure we block the document as well.
2466 nsCOMPtr<nsILoadGroup> docLoadGroup = doc->GetDocumentLoadGroup();
2467 MOZ_ASSERT(docLoadGroup == loadGroup);
2469 #endif
2471 // Filter out any load flags not from nsIRequest
2472 requestFlags &= nsIRequest::LOAD_REQUESTMASK;
2474 nsresult rv = NS_OK;
2475 if (request) {
2476 // we have this in our cache already.. cancel the current (document) load
2478 // this should fire an OnStopRequest
2479 channel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
2481 *listener = nullptr; // give them back a null nsIStreamListener
2483 rv = CreateNewProxyForRequest(request, loadGroup, doc, aObserver,
2484 requestFlags, _retval);
2485 static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
2486 } else {
2487 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2488 nsCOMPtr<nsIURI> originalURI;
2489 channel->GetOriginalURI(getter_AddRefs(originalURI));
2491 // XXX(seth): We should be able to just use |key| here, except that |key| is
2492 // constructed above with the *current URI* and not the *original URI*. I'm
2493 // pretty sure this is a bug, and it's preventing us from ever getting a
2494 // cache hit in LoadImageWithChannel when redirects are involved.
2495 ImageCacheKey originalURIKey(originalURI, attrs, doc);
2497 // Default to doing a principal check because we don't know who
2498 // started that load and whether their principal ended up being
2499 // inherited on the channel.
2500 NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true, this,
2501 originalURIKey, getter_AddRefs(request),
2502 getter_AddRefs(entry));
2504 // No principal specified here, because we're not passed one.
2505 // In LoadImageWithChannel, the redirects that may have been
2506 // assoicated with this load would have gone through necko.
2507 // We only have the final URI in ImageLib and hence don't know
2508 // if the request went through insecure redirects. But if it did,
2509 // the necko cache should have handled that (since all necko cache hits
2510 // including the redirects will go through content policy). Hence, we
2511 // can set aHadInsecureRedirect to false here.
2512 rv = request->Init(originalURI, uri, /* aHadInsecureRedirect = */ false,
2513 channel, channel, entry, aCX, nullptr,
2514 imgIRequest::CORS_NONE, RP_Unset);
2515 NS_ENSURE_SUCCESS(rv, rv);
2517 RefPtr<ProxyListener> pl =
2518 new ProxyListener(static_cast<nsIStreamListener*>(request.get()));
2519 pl.forget(listener);
2521 // Try to add the new request into the cache.
2522 PutIntoCache(originalURIKey, entry);
2524 rv = CreateNewProxyForRequest(request, loadGroup, doc, aObserver,
2525 requestFlags, _retval);
2527 // Explicitly don't notify our proxy, because we're loading off the
2528 // network, and necko (or things called from necko, such as
2529 // imgCacheValidator) are going to call our notifications asynchronously,
2530 // and we can't make it further asynchronous because observers might rely
2531 // on imagelib completing its work between the channel's OnStartRequest and
2532 // OnStopRequest.
2535 if (NS_FAILED(rv)) {
2536 return rv;
2539 (*_retval)->AddToLoadGroup();
2540 return rv;
2543 bool imgLoader::SupportImageWithMimeType(const char* aMimeType,
2544 AcceptedMimeTypes aAccept
2545 /* = AcceptedMimeTypes::IMAGES */) {
2546 nsAutoCString mimeType(aMimeType);
2547 ToLowerCase(mimeType);
2549 if (aAccept == AcceptedMimeTypes::IMAGES_AND_DOCUMENTS &&
2550 mimeType.EqualsLiteral("image/svg+xml")) {
2551 return true;
2554 DecoderType type = DecoderFactory::GetDecoderType(mimeType.get());
2555 return type != DecoderType::UNKNOWN;
2558 NS_IMETHODIMP
2559 imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
2560 const uint8_t* aContents, uint32_t aLength,
2561 nsACString& aContentType) {
2562 return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
2565 /* static */
2566 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents,
2567 uint32_t aLength,
2568 nsACString& aContentType) {
2569 /* Is it a GIF? */
2570 if (aLength >= 6 &&
2571 (!strncmp(aContents, "GIF87a", 6) || !strncmp(aContents, "GIF89a", 6))) {
2572 aContentType.AssignLiteral(IMAGE_GIF);
2574 /* or a PNG? */
2575 } else if (aLength >= 8 && ((unsigned char)aContents[0] == 0x89 &&
2576 (unsigned char)aContents[1] == 0x50 &&
2577 (unsigned char)aContents[2] == 0x4E &&
2578 (unsigned char)aContents[3] == 0x47 &&
2579 (unsigned char)aContents[4] == 0x0D &&
2580 (unsigned char)aContents[5] == 0x0A &&
2581 (unsigned char)aContents[6] == 0x1A &&
2582 (unsigned char)aContents[7] == 0x0A)) {
2583 aContentType.AssignLiteral(IMAGE_PNG);
2585 /* maybe a JPEG (JFIF)? */
2586 /* JFIF files start with SOI APP0 but older files can start with SOI DQT
2587 * so we test for SOI followed by any marker, i.e. FF D8 FF
2588 * this will also work for SPIFF JPEG files if they appear in the future.
2590 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
2592 } else if (aLength >= 3 && ((unsigned char)aContents[0]) == 0xFF &&
2593 ((unsigned char)aContents[1]) == 0xD8 &&
2594 ((unsigned char)aContents[2]) == 0xFF) {
2595 aContentType.AssignLiteral(IMAGE_JPEG);
2597 /* or how about ART? */
2598 /* ART begins with JG (4A 47). Major version offset 2.
2599 * Minor version offset 3. Offset 4 must be nullptr.
2601 } else if (aLength >= 5 && ((unsigned char)aContents[0]) == 0x4a &&
2602 ((unsigned char)aContents[1]) == 0x47 &&
2603 ((unsigned char)aContents[4]) == 0x00) {
2604 aContentType.AssignLiteral(IMAGE_ART);
2606 } else if (aLength >= 2 && !strncmp(aContents, "BM", 2)) {
2607 aContentType.AssignLiteral(IMAGE_BMP);
2609 // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
2610 // CURs begin with 2-byte 0 followed by 2-byte 2.
2611 } else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
2612 !memcmp(aContents, "\000\000\002\000", 4))) {
2613 aContentType.AssignLiteral(IMAGE_ICO);
2615 // WebPs always begin with RIFF, a 32-bit length, and WEBP.
2616 } else if (aLength >= 12 && !memcmp(aContents, "RIFF", 4) &&
2617 !memcmp(aContents + 8, "WEBP", 4)) {
2618 aContentType.AssignLiteral(IMAGE_WEBP);
2620 } else {
2621 /* none of the above? I give up */
2622 return NS_ERROR_NOT_AVAILABLE;
2625 return NS_OK;
2629 * proxy stream listener class used to handle multipart/x-mixed-replace
2632 #include "nsIRequest.h"
2633 #include "nsIStreamConverterService.h"
2635 NS_IMPL_ISUPPORTS(ProxyListener, nsIStreamListener,
2636 nsIThreadRetargetableStreamListener, nsIRequestObserver)
2638 ProxyListener::ProxyListener(nsIStreamListener* dest) : mDestListener(dest) {
2639 /* member initializers and constructor code */
2642 ProxyListener::~ProxyListener() { /* destructor code */
2645 /** nsIRequestObserver methods **/
2647 NS_IMETHODIMP
2648 ProxyListener::OnStartRequest(nsIRequest* aRequest) {
2649 if (!mDestListener) {
2650 return NS_ERROR_FAILURE;
2653 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2654 if (channel) {
2655 // We need to set the initiator type for the image load
2656 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel);
2657 if (timedChannel) {
2658 nsAutoString type;
2659 timedChannel->GetInitiatorType(type);
2660 if (type.IsEmpty()) {
2661 timedChannel->SetInitiatorType(NS_LITERAL_STRING("img"));
2665 nsAutoCString contentType;
2666 nsresult rv = channel->GetContentType(contentType);
2668 if (!contentType.IsEmpty()) {
2669 /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
2670 in the pipeline to handle the content and pass it along to our
2671 original listener.
2673 if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) {
2674 nsCOMPtr<nsIStreamConverterService> convServ(
2675 do_GetService("@mozilla.org/streamConverters;1", &rv));
2676 if (NS_SUCCEEDED(rv)) {
2677 nsCOMPtr<nsIStreamListener> toListener(mDestListener);
2678 nsCOMPtr<nsIStreamListener> fromListener;
2680 rv = convServ->AsyncConvertData("multipart/x-mixed-replace", "*/*",
2681 toListener, nullptr,
2682 getter_AddRefs(fromListener));
2683 if (NS_SUCCEEDED(rv)) {
2684 mDestListener = fromListener;
2691 return mDestListener->OnStartRequest(aRequest);
2694 NS_IMETHODIMP
2695 ProxyListener::OnStopRequest(nsIRequest* aRequest, nsresult status) {
2696 if (!mDestListener) {
2697 return NS_ERROR_FAILURE;
2700 return mDestListener->OnStopRequest(aRequest, status);
2703 /** nsIStreamListener methods **/
2705 NS_IMETHODIMP
2706 ProxyListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
2707 uint64_t sourceOffset, uint32_t count) {
2708 if (!mDestListener) {
2709 return NS_ERROR_FAILURE;
2712 return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
2715 /** nsThreadRetargetableStreamListener methods **/
2716 NS_IMETHODIMP
2717 ProxyListener::CheckListenerChain() {
2718 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2719 nsresult rv = NS_OK;
2720 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2721 do_QueryInterface(mDestListener, &rv);
2722 if (retargetableListener) {
2723 rv = retargetableListener->CheckListenerChain();
2725 MOZ_LOG(
2726 gImgLog, LogLevel::Debug,
2727 ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%" PRIx32
2728 "]",
2729 (NS_SUCCEEDED(rv) ? "success" : "failure"), this,
2730 (nsIStreamListener*)mDestListener, static_cast<uint32_t>(rv)));
2731 return rv;
2735 * http validate class. check a channel for a 304
2738 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
2739 nsIThreadRetargetableStreamListener, nsIChannelEventSink,
2740 nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
2742 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
2743 imgLoader* loader, imgRequest* request,
2744 nsISupports* aContext,
2745 bool forcePrincipalCheckForCacheEntry)
2746 : mProgressProxy(progress),
2747 mRequest(request),
2748 mContext(aContext),
2749 mImgLoader(loader),
2750 mHadInsecureRedirect(false) {
2751 NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader,
2752 mRequest->CacheKey(), getter_AddRefs(mNewRequest),
2753 getter_AddRefs(mNewEntry));
2756 imgCacheValidator::~imgCacheValidator() {
2757 if (mRequest) {
2758 // If something went wrong, and we never unblocked the requests waiting on
2759 // validation, now is our last chance. We will cancel the new request and
2760 // switch the waiting proxies to it.
2761 UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ false);
2765 void imgCacheValidator::AddProxy(imgRequestProxy* aProxy) {
2766 // aProxy needs to be in the loadgroup since we're validating from
2767 // the network.
2768 aProxy->AddToLoadGroup();
2770 mProxies.AppendElement(aProxy);
2773 void imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) {
2774 mProxies.RemoveElement(aProxy);
2777 void imgCacheValidator::UpdateProxies(bool aCancelRequest, bool aSyncNotify) {
2778 MOZ_ASSERT(mRequest);
2780 // Clear the validator before updating the proxies. The notifications may
2781 // clone an existing request, and its state could be inconsistent.
2782 mRequest->SetValidator(nullptr);
2783 mRequest = nullptr;
2785 // If an error occurred, we will want to cancel the new request, and make the
2786 // validating proxies point to it. Any proxies still bound to the original
2787 // request which are not validating should remain untouched.
2788 if (aCancelRequest) {
2789 MOZ_ASSERT(mNewRequest);
2790 mNewRequest->CancelAndAbort(NS_BINDING_ABORTED);
2793 // We have finished validating the request, so we can safely take ownership
2794 // of the proxy list. imgRequestProxy::SyncNotifyListener can mutate the list
2795 // if imgRequestProxy::CancelAndForgetObserver is called by its owner. Note
2796 // that any potential notifications should still be suppressed in
2797 // imgRequestProxy::ChangeOwner because we haven't cleared the validating
2798 // flag yet, and thus they will remain deferred.
2799 AutoTArray<RefPtr<imgRequestProxy>, 4> proxies(std::move(mProxies));
2801 for (auto& proxy : proxies) {
2802 // First update the state of all proxies before notifying any of them
2803 // to ensure a consistent state (e.g. in case the notification causes
2804 // other proxies to be touched indirectly.)
2805 MOZ_ASSERT(proxy->IsValidating());
2806 MOZ_ASSERT(proxy->NotificationsDeferred(),
2807 "Proxies waiting on cache validation should be "
2808 "deferring notifications!");
2809 if (mNewRequest) {
2810 proxy->ChangeOwner(mNewRequest);
2812 proxy->ClearValidating();
2815 mNewRequest = nullptr;
2816 mNewEntry = nullptr;
2818 for (auto& proxy : proxies) {
2819 if (aSyncNotify) {
2820 // Notify synchronously, because the caller knows we are already in an
2821 // asynchronously-called function (e.g. OnStartRequest).
2822 proxy->SyncNotifyListener();
2823 } else {
2824 // Notify asynchronously, because the caller does not know our current
2825 // call state (e.g. ~imgCacheValidator).
2826 proxy->NotifyListener();
2831 /** nsIRequestObserver methods **/
2833 NS_IMETHODIMP
2834 imgCacheValidator::OnStartRequest(nsIRequest* aRequest) {
2835 // We may be holding on to a document, so ensure that it's released.
2836 nsCOMPtr<nsISupports> context = mContext.forget();
2838 // If for some reason we don't still have an existing request (probably
2839 // because OnStartRequest got delivered more than once), just bail.
2840 if (!mRequest) {
2841 MOZ_ASSERT_UNREACHABLE("OnStartRequest delivered more than once?");
2842 aRequest->Cancel(NS_BINDING_ABORTED);
2843 return NS_ERROR_FAILURE;
2846 // If this request is coming from cache and has the same URI as our
2847 // imgRequest, the request all our proxies are pointing at is valid, and all
2848 // we have to do is tell them to notify their listeners.
2849 nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(aRequest));
2850 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2851 if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) {
2852 bool isFromCache = false;
2853 cacheChan->IsFromCache(&isFromCache);
2855 nsCOMPtr<nsIURI> channelURI;
2856 channel->GetURI(getter_AddRefs(channelURI));
2858 nsCOMPtr<nsIURI> finalURI;
2859 mRequest->GetFinalURI(getter_AddRefs(finalURI));
2861 bool sameURI = false;
2862 if (channelURI && finalURI) {
2863 channelURI->Equals(finalURI, &sameURI);
2866 if (isFromCache && sameURI) {
2867 // We don't need to load this any more.
2868 aRequest->Cancel(NS_BINDING_ABORTED);
2869 mNewRequest = nullptr;
2871 // Clear the validator before updating the proxies. The notifications may
2872 // clone an existing request, and its state could be inconsistent.
2873 mRequest->SetLoadId(context);
2874 UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true);
2875 return NS_OK;
2879 // We can't load out of cache. We have to create a whole new request for the
2880 // data that's coming in off the channel.
2881 nsCOMPtr<nsIURI> uri;
2882 mRequest->GetURI(getter_AddRefs(uri));
2884 LOG_MSG_WITH_PARAM(gImgLog,
2885 "imgCacheValidator::OnStartRequest creating new request",
2886 "uri", uri);
2888 int32_t corsmode = mRequest->GetCORSMode();
2889 ReferrerPolicy refpol = mRequest->GetReferrerPolicy();
2890 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
2891 mRequest->GetTriggeringPrincipal();
2893 // Doom the old request's cache entry
2894 mRequest->RemoveFromCache();
2896 // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2897 nsCOMPtr<nsIURI> originalURI;
2898 channel->GetOriginalURI(getter_AddRefs(originalURI));
2899 nsresult rv = mNewRequest->Init(originalURI, uri, mHadInsecureRedirect,
2900 aRequest, channel, mNewEntry, context,
2901 triggeringPrincipal, corsmode, refpol);
2902 if (NS_FAILED(rv)) {
2903 UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ true);
2904 return rv;
2907 mDestListener = new ProxyListener(mNewRequest);
2909 // Try to add the new request into the cache. Note that the entry must be in
2910 // the cache before the proxies' ownership changes, because adding a proxy
2911 // changes the caching behaviour for imgRequests.
2912 mImgLoader->PutIntoCache(mNewRequest->CacheKey(), mNewEntry);
2913 UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true);
2914 return mDestListener->OnStartRequest(aRequest);
2917 NS_IMETHODIMP
2918 imgCacheValidator::OnStopRequest(nsIRequest* aRequest, nsresult status) {
2919 // Be sure we've released the document that we may have been holding on to.
2920 mContext = nullptr;
2922 if (!mDestListener) {
2923 return NS_OK;
2926 return mDestListener->OnStopRequest(aRequest, status);
2929 /** nsIStreamListener methods **/
2931 NS_IMETHODIMP
2932 imgCacheValidator::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
2933 uint64_t sourceOffset, uint32_t count) {
2934 if (!mDestListener) {
2935 // XXX see bug 113959
2936 uint32_t _retval;
2937 inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
2938 return NS_OK;
2941 return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
2944 /** nsIThreadRetargetableStreamListener methods **/
2946 NS_IMETHODIMP
2947 imgCacheValidator::CheckListenerChain() {
2948 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2949 nsresult rv = NS_OK;
2950 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2951 do_QueryInterface(mDestListener, &rv);
2952 if (retargetableListener) {
2953 rv = retargetableListener->CheckListenerChain();
2955 MOZ_LOG(
2956 gImgLog, LogLevel::Debug,
2957 ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %" PRId32 "=%s",
2958 this, static_cast<uint32_t>(rv),
2959 NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
2960 return rv;
2963 /** nsIInterfaceRequestor methods **/
2965 NS_IMETHODIMP
2966 imgCacheValidator::GetInterface(const nsIID& aIID, void** aResult) {
2967 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
2968 return QueryInterface(aIID, aResult);
2971 return mProgressProxy->GetInterface(aIID, aResult);
2974 // These functions are materially the same as the same functions in imgRequest.
2975 // We duplicate them because we're verifying whether cache loads are necessary,
2976 // not unconditionally loading.
2978 /** nsIChannelEventSink methods **/
2979 NS_IMETHODIMP
2980 imgCacheValidator::AsyncOnChannelRedirect(
2981 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
2982 nsIAsyncVerifyRedirectCallback* callback) {
2983 // Note all cache information we get from the old channel.
2984 mNewRequest->SetCacheValidation(mNewEntry, oldChannel);
2986 // If the previous URI is a non-HTTPS URI, record that fact for later use by
2987 // security code, which needs to know whether there is an insecure load at any
2988 // point in the redirect chain.
2989 nsCOMPtr<nsIURI> oldURI;
2990 bool isHttps = false;
2991 bool isChrome = false;
2992 bool schemeLocal = false;
2993 if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) ||
2994 NS_FAILED(oldURI->SchemeIs("https", &isHttps)) ||
2995 NS_FAILED(oldURI->SchemeIs("chrome", &isChrome)) ||
2996 NS_FAILED(NS_URIChainHasFlags(
2997 oldURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
2998 (!isHttps && !isChrome && !schemeLocal)) {
2999 mHadInsecureRedirect = true;
3002 // Prepare for callback
3003 mRedirectCallback = callback;
3004 mRedirectChannel = newChannel;
3006 return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
3007 this);
3010 NS_IMETHODIMP
3011 imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) {
3012 // If we've already been told to abort, just do so.
3013 if (NS_FAILED(aResult)) {
3014 mRedirectCallback->OnRedirectVerifyCallback(aResult);
3015 mRedirectCallback = nullptr;
3016 mRedirectChannel = nullptr;
3017 return NS_OK;
3020 // make sure we have a protocol that returns data rather than opens
3021 // an external application, e.g. mailto:
3022 nsCOMPtr<nsIURI> uri;
3023 mRedirectChannel->GetURI(getter_AddRefs(uri));
3024 bool doesNotReturnData = false;
3025 NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
3026 &doesNotReturnData);
3028 nsresult result = NS_OK;
3030 if (doesNotReturnData) {
3031 result = NS_ERROR_ABORT;
3034 mRedirectCallback->OnRedirectVerifyCallback(result);
3035 mRedirectCallback = nullptr;
3036 mRedirectChannel = nullptr;
3037 return NS_OK;