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/. */
6 
7 // Undefine windows version of LoadImage because our code uses that name.
8 #include "mozilla/ScopeExit.h"
9 #undef LoadImage
10 
11 #include "imgLoader.h"
12 
13 #include <algorithm>
14 #include <utility>
15 
16 #include "DecoderFactory.h"
17 #include "Image.h"
18 #include "ImageLogging.h"
19 #include "ReferrerInfo.h"
20 #include "imgRequestProxy.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/BasePrincipal.h"
23 #include "mozilla/ChaosMode.h"
24 #include "mozilla/ClearOnShutdown.h"
25 #include "mozilla/LoadInfo.h"
26 #include "mozilla/NullPrincipal.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/ProfilerLabels.h"
29 #include "mozilla/StaticPrefs_image.h"
30 #include "mozilla/StaticPrefs_network.h"
31 #include "mozilla/StoragePrincipalHelper.h"
32 #include "mozilla/dom/ContentParent.h"
33 #include "mozilla/dom/nsMixedContentBlocker.h"
34 #include "mozilla/image/ImageMemoryReporter.h"
35 #include "mozilla/layers/CompositorManagerChild.h"
36 #include "nsCOMPtr.h"
37 #include "nsCRT.h"
38 #include "nsComponentManagerUtils.h"
39 #include "nsContentPolicyUtils.h"
40 #include "nsContentUtils.h"
41 #include "nsHttpChannel.h"
42 #include "nsIAsyncVerifyRedirectCallback.h"
43 #include "nsICacheInfoChannel.h"
44 #include "nsIChannelEventSink.h"
45 #include "nsIClassOfService.h"
46 #include "nsIEffectiveTLDService.h"
47 #include "nsIFile.h"
48 #include "nsIFileURL.h"
49 #include "nsIHttpChannel.h"
50 #include "nsIInterfaceRequestor.h"
51 #include "nsIInterfaceRequestorUtils.h"
52 #include "nsIMemoryReporter.h"
53 #include "nsINetworkPredictor.h"
54 #include "nsIProgressEventSink.h"
55 #include "nsIProtocolHandler.h"
56 #include "nsImageModule.h"
57 #include "nsMediaSniffer.h"
58 #include "nsMimeTypes.h"
59 #include "nsNetCID.h"
60 #include "nsNetUtil.h"
61 #include "nsProxyRelease.h"
62 #include "nsQueryObject.h"
63 #include "nsReadableUtils.h"
64 #include "nsStreamUtils.h"
65 #include "prtime.h"
66 
67 // we want to explore making the document own the load group
68 // so we can associate the document URI with the load group.
69 // until this point, we have an evil hack:
70 #include "nsIHttpChannelInternal.h"
71 #include "nsILoadGroupChild.h"
72 #include "nsIDocShell.h"
73 
74 using namespace mozilla;
75 using namespace mozilla::dom;
76 using namespace mozilla::image;
77 using namespace mozilla::net;
78 
79 MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)
80 
81 class imgMemoryReporter final : public nsIMemoryReporter {
82   ~imgMemoryReporter() = default;
83 
84  public:
85   NS_DECL_ISUPPORTS
86 
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)87   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
88                             nsISupports* aData, bool aAnonymize) override {
89     MOZ_ASSERT(NS_IsMainThread());
90 
91     layers::CompositorManagerChild* manager =
92         mozilla::layers::CompositorManagerChild::GetInstance();
93     if (!manager || !StaticPrefs::image_mem_debug_reporting()) {
94       layers::SharedSurfacesMemoryReport sharedSurfaces;
95       FinishCollectReports(aHandleReport, aData, aAnonymize, sharedSurfaces);
96       return NS_OK;
97     }
98 
99     RefPtr<imgMemoryReporter> self(this);
100     nsCOMPtr<nsIHandleReportCallback> handleReport(aHandleReport);
101     nsCOMPtr<nsISupports> data(aData);
102     manager->SendReportSharedSurfacesMemory(
103         [=](layers::SharedSurfacesMemoryReport aReport) {
104           self->FinishCollectReports(handleReport, data, aAnonymize, aReport);
105         },
106         [=](mozilla::ipc::ResponseRejectReason&& aReason) {
107           layers::SharedSurfacesMemoryReport sharedSurfaces;
108           self->FinishCollectReports(handleReport, data, aAnonymize,
109                                      sharedSurfaces);
110         });
111     return NS_OK;
112   }
113 
FinishCollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize,layers::SharedSurfacesMemoryReport & aSharedSurfaces)114   void FinishCollectReports(
115       nsIHandleReportCallback* aHandleReport, nsISupports* aData,
116       bool aAnonymize, layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
117     nsTArray<ImageMemoryCounter> chrome;
118     nsTArray<ImageMemoryCounter> content;
119     nsTArray<ImageMemoryCounter> uncached;
120 
121     for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
122       for (imgCacheEntry* entry : mKnownLoaders[i]->mChromeCache.Values()) {
123         RefPtr<imgRequest> req = entry->GetRequest();
124         RecordCounterForRequest(req, &chrome, !entry->HasNoProxies());
125       }
126       for (imgCacheEntry* entry : mKnownLoaders[i]->mCache.Values()) {
127         RefPtr<imgRequest> req = entry->GetRequest();
128         RecordCounterForRequest(req, &content, !entry->HasNoProxies());
129       }
130       MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex);
131       for (RefPtr<imgRequest> req : mKnownLoaders[i]->mUncachedImages) {
132         RecordCounterForRequest(req, &uncached, req->HasConsumers());
133       }
134     }
135 
136     // Note that we only need to anonymize content image URIs.
137 
138     ReportCounterArray(aHandleReport, aData, chrome, "images/chrome",
139                        /* aAnonymize */ false, aSharedSurfaces);
140 
141     ReportCounterArray(aHandleReport, aData, content, "images/content",
142                        aAnonymize, aSharedSurfaces);
143 
144     // Uncached images may be content or chrome, so anonymize them.
145     ReportCounterArray(aHandleReport, aData, uncached, "images/uncached",
146                        aAnonymize, aSharedSurfaces);
147 
148     // Report any shared surfaces that were not merged with the surface cache.
149     ImageMemoryReporter::ReportSharedSurfaces(aHandleReport, aData,
150                                               aSharedSurfaces);
151 
152     nsCOMPtr<nsIMemoryReporterManager> imgr =
153         do_GetService("@mozilla.org/memory-reporter-manager;1");
154     if (imgr) {
155       imgr->EndReport();
156     }
157   }
158 
ImagesContentUsedUncompressedDistinguishedAmount()159   static int64_t ImagesContentUsedUncompressedDistinguishedAmount() {
160     size_t n = 0;
161     for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length();
162          i++) {
163       for (imgCacheEntry* entry :
164            imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Values()) {
165         if (entry->HasNoProxies()) {
166           continue;
167         }
168 
169         RefPtr<imgRequest> req = entry->GetRequest();
170         RefPtr<image::Image> image = req->GetImage();
171         if (!image) {
172           continue;
173         }
174 
175         // Both this and EntryImageSizes measure
176         // images/content/raster/used/decoded memory.  This function's
177         // measurement is secondary -- the result doesn't go in the "explicit"
178         // tree -- so we use moz_malloc_size_of instead of ImagesMallocSizeOf to
179         // prevent DMD from seeing it reported twice.
180         SizeOfState state(moz_malloc_size_of);
181         ImageMemoryCounter counter(req, image, state, /* aIsUsed = */ true);
182 
183         n += counter.Values().DecodedHeap();
184         n += counter.Values().DecodedNonHeap();
185         n += counter.Values().DecodedUnknown();
186       }
187     }
188     return n;
189   }
190 
RegisterLoader(imgLoader * aLoader)191   void RegisterLoader(imgLoader* aLoader) {
192     mKnownLoaders.AppendElement(aLoader);
193   }
194 
UnregisterLoader(imgLoader * aLoader)195   void UnregisterLoader(imgLoader* aLoader) {
196     mKnownLoaders.RemoveElement(aLoader);
197   }
198 
199  private:
200   nsTArray<imgLoader*> mKnownLoaders;
201 
202   struct MemoryTotal {
operator +=imgMemoryReporter::MemoryTotal203     MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) {
204       if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
205         if (aImageCounter.IsUsed()) {
206           mUsedRasterCounter += aImageCounter.Values();
207         } else {
208           mUnusedRasterCounter += aImageCounter.Values();
209         }
210       } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) {
211         if (aImageCounter.IsUsed()) {
212           mUsedVectorCounter += aImageCounter.Values();
213         } else {
214           mUnusedVectorCounter += aImageCounter.Values();
215         }
216       } else if (aImageCounter.Type() == imgIContainer::TYPE_REQUEST) {
217         // Nothing to do, we did not get to the point of having an image.
218       } else {
219         MOZ_CRASH("Unexpected image type");
220       }
221 
222       return *this;
223     }
224 
UsedRasterimgMemoryReporter::MemoryTotal225     const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; }
UnusedRasterimgMemoryReporter::MemoryTotal226     const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; }
UsedVectorimgMemoryReporter::MemoryTotal227     const MemoryCounter& UsedVector() const { return mUsedVectorCounter; }
UnusedVectorimgMemoryReporter::MemoryTotal228     const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; }
229 
230    private:
231     MemoryCounter mUsedRasterCounter;
232     MemoryCounter mUnusedRasterCounter;
233     MemoryCounter mUsedVectorCounter;
234     MemoryCounter mUnusedVectorCounter;
235   };
236 
237   // Reports all images of a single kind, e.g. all used chrome images.
ReportCounterArray(nsIHandleReportCallback * aHandleReport,nsISupports * aData,nsTArray<ImageMemoryCounter> & aCounterArray,const char * aPathPrefix,bool aAnonymize,layers::SharedSurfacesMemoryReport & aSharedSurfaces)238   void ReportCounterArray(nsIHandleReportCallback* aHandleReport,
239                           nsISupports* aData,
240                           nsTArray<ImageMemoryCounter>& aCounterArray,
241                           const char* aPathPrefix, bool aAnonymize,
242                           layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
243     MemoryTotal summaryTotal;
244     MemoryTotal nonNotableTotal;
245 
246     // Report notable images, and compute total and non-notable aggregate sizes.
247     for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
248       ImageMemoryCounter& counter = aCounterArray[i];
249 
250       if (aAnonymize) {
251         counter.URI().Truncate();
252         counter.URI().AppendPrintf("<anonymized-%u>", i);
253       } else {
254         // The URI could be an extremely long data: URI. Truncate if needed.
255         static const size_t max = 256;
256         if (counter.URI().Length() > max) {
257           counter.URI().Truncate(max);
258           counter.URI().AppendLiteral(" (truncated)");
259         }
260         counter.URI().ReplaceChar('/', '\\');
261       }
262 
263       summaryTotal += counter;
264 
265       if (counter.IsNotable() || StaticPrefs::image_mem_debug_reporting()) {
266         ReportImage(aHandleReport, aData, aPathPrefix, counter,
267                     aSharedSurfaces);
268       } else {
269         ImageMemoryReporter::TrimSharedSurfaces(counter, aSharedSurfaces);
270         nonNotableTotal += counter;
271       }
272     }
273 
274     // Report non-notable images in aggregate.
275     ReportTotal(aHandleReport, aData, /* aExplicit = */ true, aPathPrefix,
276                 "<non-notable images>/", nonNotableTotal);
277 
278     // Report a summary in aggregate, outside of the explicit tree.
279     ReportTotal(aHandleReport, aData, /* aExplicit = */ false, aPathPrefix, "",
280                 summaryTotal);
281   }
282 
ReportImage(nsIHandleReportCallback * aHandleReport,nsISupports * aData,const char * aPathPrefix,const ImageMemoryCounter & aCounter,layers::SharedSurfacesMemoryReport & aSharedSurfaces)283   static void ReportImage(nsIHandleReportCallback* aHandleReport,
284                           nsISupports* aData, const char* aPathPrefix,
285                           const ImageMemoryCounter& aCounter,
286                           layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
287     nsAutoCString pathPrefix("explicit/"_ns);
288     pathPrefix.Append(aPathPrefix);
289 
290     switch (aCounter.Type()) {
291       case imgIContainer::TYPE_RASTER:
292         pathPrefix.AppendLiteral("/raster/");
293         break;
294       case imgIContainer::TYPE_VECTOR:
295         pathPrefix.AppendLiteral("/vector/");
296         break;
297       case imgIContainer::TYPE_REQUEST:
298         pathPrefix.AppendLiteral("/request/");
299         break;
300       default:
301         pathPrefix.AppendLiteral("/unknown=");
302         pathPrefix.AppendInt(aCounter.Type());
303         pathPrefix.AppendLiteral("/");
304         break;
305     }
306 
307     pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
308     if (aCounter.IsValidating()) {
309       pathPrefix.AppendLiteral("validating/");
310     }
311     if (aCounter.HasError()) {
312       pathPrefix.AppendLiteral("err/");
313     }
314 
315     pathPrefix.AppendLiteral("progress=");
316     pathPrefix.AppendInt(aCounter.Progress(), 16);
317     pathPrefix.AppendLiteral("/");
318 
319     pathPrefix.AppendLiteral("image(");
320     pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
321     pathPrefix.AppendLiteral("x");
322     pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
323     pathPrefix.AppendLiteral(", ");
324 
325     if (aCounter.URI().IsEmpty()) {
326       pathPrefix.AppendLiteral("<unknown URI>");
327     } else {
328       pathPrefix.Append(aCounter.URI());
329     }
330 
331     pathPrefix.AppendLiteral(")/");
332 
333     ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter, aSharedSurfaces);
334 
335     ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
336   }
337 
ReportSurfaces(nsIHandleReportCallback * aHandleReport,nsISupports * aData,const nsACString & aPathPrefix,const ImageMemoryCounter & aCounter,layers::SharedSurfacesMemoryReport & aSharedSurfaces)338   static void ReportSurfaces(
339       nsIHandleReportCallback* aHandleReport, nsISupports* aData,
340       const nsACString& aPathPrefix, const ImageMemoryCounter& aCounter,
341       layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
342     for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
343       nsAutoCString surfacePathPrefix(aPathPrefix);
344       switch (counter.Type()) {
345         case SurfaceMemoryCounterType::NORMAL:
346           if (counter.IsLocked()) {
347             surfacePathPrefix.AppendLiteral("locked/");
348           } else {
349             surfacePathPrefix.AppendLiteral("unlocked/");
350           }
351           if (counter.IsFactor2()) {
352             surfacePathPrefix.AppendLiteral("factor2/");
353           }
354           if (counter.CannotSubstitute()) {
355             surfacePathPrefix.AppendLiteral("cannot_substitute/");
356           }
357           break;
358         case SurfaceMemoryCounterType::CONTAINER:
359           surfacePathPrefix.AppendLiteral("container/");
360           break;
361         default:
362           MOZ_ASSERT_UNREACHABLE("Unknown counter type");
363           break;
364       }
365 
366       surfacePathPrefix.AppendLiteral("types=");
367       surfacePathPrefix.AppendInt(counter.Values().SurfaceTypes(), 16);
368       surfacePathPrefix.AppendLiteral("/surface(");
369       surfacePathPrefix.AppendInt(counter.Key().Size().width);
370       surfacePathPrefix.AppendLiteral("x");
371       surfacePathPrefix.AppendInt(counter.Key().Size().height);
372 
373       if (!counter.IsFinished()) {
374         surfacePathPrefix.AppendLiteral(", incomplete");
375       }
376 
377       if (counter.Values().ExternalHandles() > 0) {
378         surfacePathPrefix.AppendLiteral(", handles:");
379         surfacePathPrefix.AppendInt(
380             uint32_t(counter.Values().ExternalHandles()));
381       }
382 
383       ImageMemoryReporter::AppendSharedSurfacePrefix(surfacePathPrefix, counter,
384                                                      aSharedSurfaces);
385 
386       PlaybackType playback = counter.Key().Playback();
387       if (playback == PlaybackType::eAnimated) {
388         if (StaticPrefs::image_mem_debug_reporting()) {
389           surfacePathPrefix.AppendPrintf(
390               " (animation %4u)", uint32_t(counter.Values().FrameIndex()));
391         } else {
392           surfacePathPrefix.AppendLiteral(" (animation)");
393         }
394       }
395 
396       if (counter.Key().Flags() != DefaultSurfaceFlags()) {
397         surfacePathPrefix.AppendLiteral(", flags:");
398         surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
399                                     /* aRadix = */ 16);
400       }
401 
402       if (counter.Key().Region()) {
403         const ImageIntRegion& region = counter.Key().Region().ref();
404         const gfx::IntRect& rect = region.Rect();
405         surfacePathPrefix.AppendLiteral(", region:[ rect=(");
406         surfacePathPrefix.AppendInt(rect.x);
407         surfacePathPrefix.AppendLiteral(",");
408         surfacePathPrefix.AppendInt(rect.y);
409         surfacePathPrefix.AppendLiteral(") ");
410         surfacePathPrefix.AppendInt(rect.width);
411         surfacePathPrefix.AppendLiteral("x");
412         surfacePathPrefix.AppendInt(rect.height);
413         if (region.IsRestricted()) {
414           const gfx::IntRect& restrict = region.Restriction();
415           if (restrict == rect) {
416             surfacePathPrefix.AppendLiteral(", restrict=rect");
417           } else {
418             surfacePathPrefix.AppendLiteral(", restrict=(");
419             surfacePathPrefix.AppendInt(restrict.x);
420             surfacePathPrefix.AppendLiteral(",");
421             surfacePathPrefix.AppendInt(restrict.y);
422             surfacePathPrefix.AppendLiteral(") ");
423             surfacePathPrefix.AppendInt(restrict.width);
424             surfacePathPrefix.AppendLiteral("x");
425             surfacePathPrefix.AppendInt(restrict.height);
426           }
427         }
428         if (region.GetExtendMode() != gfx::ExtendMode::CLAMP) {
429           surfacePathPrefix.AppendLiteral(", extendMode=");
430           surfacePathPrefix.AppendInt(int32_t(region.GetExtendMode()));
431         }
432         surfacePathPrefix.AppendLiteral("]");
433       }
434 
435       if (counter.Key().SVGContext()) {
436         const SVGImageContext& context = counter.Key().SVGContext().ref();
437         surfacePathPrefix.AppendLiteral(", svgContext:[ ");
438         if (context.GetViewportSize()) {
439           const CSSIntSize& size = context.GetViewportSize().ref();
440           surfacePathPrefix.AppendLiteral("viewport=(");
441           surfacePathPrefix.AppendInt(size.width);
442           surfacePathPrefix.AppendLiteral("x");
443           surfacePathPrefix.AppendInt(size.height);
444           surfacePathPrefix.AppendLiteral(") ");
445         }
446         if (context.GetPreserveAspectRatio()) {
447           nsAutoString aspect;
448           context.GetPreserveAspectRatio()->ToString(aspect);
449           surfacePathPrefix.AppendLiteral("preserveAspectRatio=(");
450           LossyAppendUTF16toASCII(aspect, surfacePathPrefix);
451           surfacePathPrefix.AppendLiteral(") ");
452         }
453         if (context.GetContextPaint()) {
454           const SVGEmbeddingContextPaint* paint = context.GetContextPaint();
455           surfacePathPrefix.AppendLiteral("contextPaint=(");
456           if (paint->GetFill()) {
457             surfacePathPrefix.AppendLiteral(" fill=");
458             surfacePathPrefix.AppendInt(paint->GetFill()->ToABGR(), 16);
459           }
460           if (paint->GetFillOpacity() != 1.0) {
461             surfacePathPrefix.AppendLiteral(" fillOpa=");
462             surfacePathPrefix.AppendFloat(paint->GetFillOpacity());
463           }
464           if (paint->GetStroke()) {
465             surfacePathPrefix.AppendLiteral(" stroke=");
466             surfacePathPrefix.AppendInt(paint->GetStroke()->ToABGR(), 16);
467           }
468           if (paint->GetStrokeOpacity() != 1.0) {
469             surfacePathPrefix.AppendLiteral(" strokeOpa=");
470             surfacePathPrefix.AppendFloat(paint->GetStrokeOpacity());
471           }
472           surfacePathPrefix.AppendLiteral(" ) ");
473         }
474         surfacePathPrefix.AppendLiteral("]");
475       }
476 
477       surfacePathPrefix.AppendLiteral(")/");
478 
479       ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values());
480     }
481   }
482 
ReportTotal(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aExplicit,const char * aPathPrefix,const char * aPathInfix,const MemoryTotal & aTotal)483   static void ReportTotal(nsIHandleReportCallback* aHandleReport,
484                           nsISupports* aData, bool aExplicit,
485                           const char* aPathPrefix, const char* aPathInfix,
486                           const MemoryTotal& aTotal) {
487     nsAutoCString pathPrefix;
488     if (aExplicit) {
489       pathPrefix.AppendLiteral("explicit/");
490     }
491     pathPrefix.Append(aPathPrefix);
492 
493     nsAutoCString rasterUsedPrefix(pathPrefix);
494     rasterUsedPrefix.AppendLiteral("/raster/used/");
495     rasterUsedPrefix.Append(aPathInfix);
496     ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster());
497 
498     nsAutoCString rasterUnusedPrefix(pathPrefix);
499     rasterUnusedPrefix.AppendLiteral("/raster/unused/");
500     rasterUnusedPrefix.Append(aPathInfix);
501     ReportValues(aHandleReport, aData, rasterUnusedPrefix,
502                  aTotal.UnusedRaster());
503 
504     nsAutoCString vectorUsedPrefix(pathPrefix);
505     vectorUsedPrefix.AppendLiteral("/vector/used/");
506     vectorUsedPrefix.Append(aPathInfix);
507     ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector());
508 
509     nsAutoCString vectorUnusedPrefix(pathPrefix);
510     vectorUnusedPrefix.AppendLiteral("/vector/unused/");
511     vectorUnusedPrefix.Append(aPathInfix);
512     ReportValues(aHandleReport, aData, vectorUnusedPrefix,
513                  aTotal.UnusedVector());
514   }
515 
ReportValues(nsIHandleReportCallback * aHandleReport,nsISupports * aData,const nsACString & aPathPrefix,const MemoryCounter & aCounter)516   static void ReportValues(nsIHandleReportCallback* aHandleReport,
517                            nsISupports* aData, const nsACString& aPathPrefix,
518                            const MemoryCounter& aCounter) {
519     ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);
520 
521     ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "decoded-heap",
522                 "Decoded image data which is stored on the heap.",
523                 aCounter.DecodedHeap());
524 
525     ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
526                 "decoded-nonheap",
527                 "Decoded image data which isn't stored on the heap.",
528                 aCounter.DecodedNonHeap());
529 
530     // We don't know for certain whether or not it is on the heap, so let's
531     // just report it as non-heap for reporting purposes.
532     ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
533                 "decoded-unknown",
534                 "Decoded image data which is unknown to be on the heap or not.",
535                 aCounter.DecodedUnknown());
536   }
537 
ReportSourceValue(nsIHandleReportCallback * aHandleReport,nsISupports * aData,const nsACString & aPathPrefix,const MemoryCounter & aCounter)538   static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
539                                 nsISupports* aData,
540                                 const nsACString& aPathPrefix,
541                                 const MemoryCounter& aCounter) {
542     ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "source",
543                 "Raster image source data and vector image documents.",
544                 aCounter.Source());
545   }
546 
ReportValue(nsIHandleReportCallback * aHandleReport,nsISupports * aData,int32_t aKind,const nsACString & aPathPrefix,const char * aPathSuffix,const char * aDescription,size_t aValue)547   static void ReportValue(nsIHandleReportCallback* aHandleReport,
548                           nsISupports* aData, int32_t aKind,
549                           const nsACString& aPathPrefix,
550                           const char* aPathSuffix, const char* aDescription,
551                           size_t aValue) {
552     if (aValue == 0) {
553       return;
554     }
555 
556     nsAutoCString desc(aDescription);
557     nsAutoCString path(aPathPrefix);
558     path.Append(aPathSuffix);
559 
560     aHandleReport->Callback(""_ns, path, aKind, UNITS_BYTES, aValue, desc,
561                             aData);
562   }
563 
RecordCounterForRequest(imgRequest * aRequest,nsTArray<ImageMemoryCounter> * aArray,bool aIsUsed)564   static void RecordCounterForRequest(imgRequest* aRequest,
565                                       nsTArray<ImageMemoryCounter>* aArray,
566                                       bool aIsUsed) {
567     SizeOfState state(ImagesMallocSizeOf);
568     RefPtr<image::Image> image = aRequest->GetImage();
569     if (image) {
570       ImageMemoryCounter counter(aRequest, image, state, aIsUsed);
571       aArray->AppendElement(std::move(counter));
572     } else {
573       // We can at least record some information about the image from the
574       // request, and mark it as not knowing the image type yet.
575       ImageMemoryCounter counter(aRequest, state, aIsUsed);
576       aArray->AppendElement(std::move(counter));
577     }
578   }
579 };
580 
NS_IMPL_ISUPPORTS(imgMemoryReporter,nsIMemoryReporter)581 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
582 
583 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, nsIProgressEventSink,
584                   nsIChannelEventSink, nsIInterfaceRequestor)
585 
586 NS_IMETHODIMP
587 nsProgressNotificationProxy::OnProgress(nsIRequest* request, int64_t progress,
588                                         int64_t progressMax) {
589   nsCOMPtr<nsILoadGroup> loadGroup;
590   request->GetLoadGroup(getter_AddRefs(loadGroup));
591 
592   nsCOMPtr<nsIProgressEventSink> target;
593   NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
594                                 NS_GET_IID(nsIProgressEventSink),
595                                 getter_AddRefs(target));
596   if (!target) {
597     return NS_OK;
598   }
599   return target->OnProgress(mImageRequest, progress, progressMax);
600 }
601 
602 NS_IMETHODIMP
OnStatus(nsIRequest * request,nsresult status,const char16_t * statusArg)603 nsProgressNotificationProxy::OnStatus(nsIRequest* request, nsresult status,
604                                       const char16_t* statusArg) {
605   nsCOMPtr<nsILoadGroup> loadGroup;
606   request->GetLoadGroup(getter_AddRefs(loadGroup));
607 
608   nsCOMPtr<nsIProgressEventSink> target;
609   NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
610                                 NS_GET_IID(nsIProgressEventSink),
611                                 getter_AddRefs(target));
612   if (!target) {
613     return NS_OK;
614   }
615   return target->OnStatus(mImageRequest, status, statusArg);
616 }
617 
618 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * oldChannel,nsIChannel * newChannel,uint32_t flags,nsIAsyncVerifyRedirectCallback * cb)619 nsProgressNotificationProxy::AsyncOnChannelRedirect(
620     nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
621     nsIAsyncVerifyRedirectCallback* cb) {
622   // Tell the original original callbacks about it too
623   nsCOMPtr<nsILoadGroup> loadGroup;
624   newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
625   nsCOMPtr<nsIChannelEventSink> target;
626   NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
627                                 NS_GET_IID(nsIChannelEventSink),
628                                 getter_AddRefs(target));
629   if (!target) {
630     cb->OnRedirectVerifyCallback(NS_OK);
631     return NS_OK;
632   }
633 
634   // Delegate to |target| if set, reusing |cb|
635   return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
636 }
637 
638 NS_IMETHODIMP
GetInterface(const nsIID & iid,void ** result)639 nsProgressNotificationProxy::GetInterface(const nsIID& iid, void** result) {
640   if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
641     *result = static_cast<nsIProgressEventSink*>(this);
642     NS_ADDREF_THIS();
643     return NS_OK;
644   }
645   if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
646     *result = static_cast<nsIChannelEventSink*>(this);
647     NS_ADDREF_THIS();
648     return NS_OK;
649   }
650   if (mOriginalCallbacks) {
651     return mOriginalCallbacks->GetInterface(iid, result);
652   }
653   return NS_NOINTERFACE;
654 }
655 
NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,imgLoader * aLoader,const ImageCacheKey & aKey,imgRequest ** aRequest,imgCacheEntry ** aEntry)656 static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,
657                                imgLoader* aLoader, const ImageCacheKey& aKey,
658                                imgRequest** aRequest, imgCacheEntry** aEntry) {
659   RefPtr<imgRequest> request = new imgRequest(aLoader, aKey);
660   RefPtr<imgCacheEntry> entry =
661       new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
662   aLoader->AddToUncachedImages(request);
663   request.forget(aRequest);
664   entry.forget(aEntry);
665 }
666 
ShouldRevalidateEntry(imgCacheEntry * aEntry,nsLoadFlags aFlags,bool aHasExpired)667 static bool ShouldRevalidateEntry(imgCacheEntry* aEntry, nsLoadFlags aFlags,
668                                   bool aHasExpired) {
669   if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) {
670     return false;
671   }
672   if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
673     return true;
674   }
675   if (aEntry->GetMustValidate()) {
676     return true;
677   }
678   if (aHasExpired) {
679     // The cache entry has expired...  Determine whether the stale cache
680     // entry can be used without validation...
681     if (aFlags & (nsIRequest::LOAD_FROM_CACHE | nsIRequest::VALIDATE_NEVER |
682                   nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
683       // LOAD_FROM_CACHE, VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow
684       // stale cache entries to be used unless they have been explicitly marked
685       // to indicate that revalidation is necessary.
686       return false;
687     }
688     // Entry is expired, revalidate.
689     return true;
690   }
691   return false;
692 }
693 
694 /* Call content policies on cached images that went through a redirect */
ShouldLoadCachedImage(imgRequest * aImgRequest,Document * aLoadingDocument,nsIPrincipal * aTriggeringPrincipal,nsContentPolicyType aPolicyType,bool aSendCSPViolationReports)695 static bool ShouldLoadCachedImage(imgRequest* aImgRequest,
696                                   Document* aLoadingDocument,
697                                   nsIPrincipal* aTriggeringPrincipal,
698                                   nsContentPolicyType aPolicyType,
699                                   bool aSendCSPViolationReports) {
700   /* Call content policies on cached images - Bug 1082837
701    * Cached images are keyed off of the first uri in a redirect chain.
702    * Hence content policies don't get a chance to test the intermediate hops
703    * or the final destination.  Here we test the final destination using
704    * mFinalURI off of the imgRequest and passing it into content policies.
705    * For Mixed Content Blocker, we do an additional check to determine if any
706    * of the intermediary hops went through an insecure redirect with the
707    * mHadInsecureRedirect flag
708    */
709   bool insecureRedirect = aImgRequest->HadInsecureRedirect();
710   nsCOMPtr<nsIURI> contentLocation;
711   aImgRequest->GetFinalURI(getter_AddRefs(contentLocation));
712   nsresult rv;
713 
714   nsCOMPtr<nsIPrincipal> loadingPrincipal =
715       aLoadingDocument ? aLoadingDocument->NodePrincipal()
716                        : aTriggeringPrincipal;
717   // If there is no context and also no triggeringPrincipal, then we use a fresh
718   // nullPrincipal as the loadingPrincipal because we can not create a loadinfo
719   // without a valid loadingPrincipal.
720   if (!loadingPrincipal) {
721     loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
722   }
723 
724   nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
725       loadingPrincipal, aTriggeringPrincipal, aLoadingDocument,
726       nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, aPolicyType);
727 
728   secCheckLoadInfo->SetSendCSPViolationEvents(aSendCSPViolationReports);
729 
730   int16_t decision = nsIContentPolicy::REJECT_REQUEST;
731   rv = NS_CheckContentLoadPolicy(contentLocation, secCheckLoadInfo,
732                                  ""_ns,  // mime guess
733                                  &decision, nsContentUtils::GetContentPolicy());
734   if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
735     return false;
736   }
737 
738   // We call all Content Policies above, but we also have to call mcb
739   // individually to check the intermediary redirect hops are secure.
740   if (insecureRedirect) {
741     // Bug 1314356: If the image ended up in the cache upgraded by HSTS and the
742     // page uses upgrade-inscure-requests it had an insecure redirect
743     // (http->https). We need to invalidate the image and reload it because
744     // mixed content blocker only bails if upgrade-insecure-requests is set on
745     // the doc and the resource load is http: which would result in an incorrect
746     // mixed content warning.
747     nsCOMPtr<nsIDocShell> docShell =
748         NS_CP_GetDocShellFromContext(ToSupports(aLoadingDocument));
749     if (docShell) {
750       Document* document = docShell->GetDocument();
751       if (document && document->GetUpgradeInsecureRequests(false)) {
752         return false;
753       }
754     }
755 
756     if (!aTriggeringPrincipal || !aTriggeringPrincipal->IsSystemPrincipal()) {
757       // reset the decision for mixed content blocker check
758       decision = nsIContentPolicy::REJECT_REQUEST;
759       rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect, contentLocation,
760                                              secCheckLoadInfo,
761                                              ""_ns,  // mime guess
762                                              true,   // aReportError
763                                              &decision);
764       if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
765         return false;
766       }
767     }
768   }
769 
770   return true;
771 }
772 
773 // Returns true if this request is compatible with the given CORS mode on the
774 // given loading principal, and false if the request may not be reused due
775 // to CORS.
ValidateCORSMode(imgRequest * aRequest,bool aForcePrincipalCheck,CORSMode aCORSMode,nsIPrincipal * aTriggeringPrincipal)776 static bool ValidateCORSMode(imgRequest* aRequest, bool aForcePrincipalCheck,
777                              CORSMode aCORSMode,
778                              nsIPrincipal* aTriggeringPrincipal) {
779   // If the entry's CORS mode doesn't match, or the CORS mode matches but the
780   // document principal isn't the same, we can't use this request.
781   if (aRequest->GetCORSMode() != aCORSMode) {
782     return false;
783   }
784 
785   if (aRequest->GetCORSMode() != CORS_NONE || aForcePrincipalCheck) {
786     nsCOMPtr<nsIPrincipal> otherprincipal = aRequest->GetTriggeringPrincipal();
787 
788     // If we previously had a principal, but we don't now, we can't use this
789     // request.
790     if (otherprincipal && !aTriggeringPrincipal) {
791       return false;
792     }
793 
794     if (otherprincipal && aTriggeringPrincipal &&
795         !otherprincipal->Equals(aTriggeringPrincipal)) {
796       return false;
797     }
798   }
799 
800   return true;
801 }
802 
ValidateSecurityInfo(imgRequest * aRequest,bool aForcePrincipalCheck,CORSMode aCORSMode,nsIPrincipal * aTriggeringPrincipal,Document * aLoadingDocument,nsContentPolicyType aPolicyType)803 static bool ValidateSecurityInfo(imgRequest* aRequest,
804                                  bool aForcePrincipalCheck, CORSMode aCORSMode,
805                                  nsIPrincipal* aTriggeringPrincipal,
806                                  Document* aLoadingDocument,
807                                  nsContentPolicyType aPolicyType) {
808   if (!ValidateCORSMode(aRequest, aForcePrincipalCheck, aCORSMode,
809                         aTriggeringPrincipal)) {
810     return false;
811   }
812   // Content Policy Check on Cached Images
813   return ShouldLoadCachedImage(aRequest, aLoadingDocument, aTriggeringPrincipal,
814                                aPolicyType,
815                                /* aSendCSPViolationReports */ false);
816 }
817 
NewImageChannel(nsIChannel ** aResult,bool * aForcePrincipalCheckForCacheEntry,nsIURI * aURI,nsIURI * aInitialDocumentURI,CORSMode aCORSMode,nsIReferrerInfo * aReferrerInfo,nsILoadGroup * aLoadGroup,nsLoadFlags aLoadFlags,nsContentPolicyType aPolicyType,nsIPrincipal * aTriggeringPrincipal,nsINode * aRequestingNode,bool aRespectPrivacy)818 static nsresult NewImageChannel(
819     nsIChannel** aResult,
820     // If aForcePrincipalCheckForCacheEntry is true, then we will
821     // force a principal check even when not using CORS before
822     // assuming we have a cache hit on a cache entry that we
823     // create for this channel.  This is an out param that should
824     // be set to true if this channel ends up depending on
825     // aTriggeringPrincipal and false otherwise.
826     bool* aForcePrincipalCheckForCacheEntry, nsIURI* aURI,
827     nsIURI* aInitialDocumentURI, CORSMode aCORSMode,
828     nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
829     nsLoadFlags aLoadFlags, nsContentPolicyType aPolicyType,
830     nsIPrincipal* aTriggeringPrincipal, nsINode* aRequestingNode,
831     bool aRespectPrivacy) {
832   MOZ_ASSERT(aResult);
833 
834   nsresult rv;
835   nsCOMPtr<nsIHttpChannel> newHttpChannel;
836 
837   nsCOMPtr<nsIInterfaceRequestor> callbacks;
838 
839   if (aLoadGroup) {
840     // Get the notification callbacks from the load group for the new channel.
841     //
842     // XXX: This is not exactly correct, because the network request could be
843     //      referenced by multiple windows...  However, the new channel needs
844     //      something.  So, using the 'first' notification callbacks is better
845     //      than nothing...
846     //
847     aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
848   }
849 
850   // Pass in a nullptr loadgroup because this is the underlying network
851   // request. This request may be referenced by several proxy image requests
852   // (possibly in different documents).
853   // If all of the proxy requests are canceled then this request should be
854   // canceled too.
855   //
856 
857   nsSecurityFlags securityFlags =
858       aCORSMode == CORS_NONE
859           ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
860           : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
861   if (aCORSMode == CORS_ANONYMOUS) {
862     securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
863   } else if (aCORSMode == CORS_USE_CREDENTIALS) {
864     securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
865   }
866   securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
867 
868   // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a
869   // node and a principal. This is for things like background images that are
870   // specified by user stylesheets, where the document is being styled, but
871   // the principal is that of the user stylesheet.
872   if (aRequestingNode && aTriggeringPrincipal) {
873     rv = NS_NewChannelWithTriggeringPrincipal(aResult, aURI, aRequestingNode,
874                                               aTriggeringPrincipal,
875                                               securityFlags, aPolicyType,
876                                               nullptr,  // PerformanceStorage
877                                               nullptr,  // loadGroup
878                                               callbacks, aLoadFlags);
879 
880     if (NS_FAILED(rv)) {
881       return rv;
882     }
883 
884     if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
885       // If this is a favicon loading, we will use the originAttributes from the
886       // triggeringPrincipal as the channel's originAttributes. This allows the
887       // favicon loading from XUL will use the correct originAttributes.
888 
889       nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
890       rv = loadInfo->SetOriginAttributes(
891           aTriggeringPrincipal->OriginAttributesRef());
892     }
893   } else {
894     // either we are loading something inside a document, in which case
895     // we should always have a requestingNode, or we are loading something
896     // outside a document, in which case the triggeringPrincipal and
897     // triggeringPrincipal should always be the systemPrincipal.
898     // However, there are exceptions: one is Notifications which create a
899     // channel in the parent process in which case we can't get a
900     // requestingNode.
901     rv = NS_NewChannel(aResult, aURI, nsContentUtils::GetSystemPrincipal(),
902                        securityFlags, aPolicyType,
903                        nullptr,  // nsICookieJarSettings
904                        nullptr,  // PerformanceStorage
905                        nullptr,  // loadGroup
906                        callbacks, aLoadFlags);
907 
908     if (NS_FAILED(rv)) {
909       return rv;
910     }
911 
912     // Use the OriginAttributes from the loading principal, if one is available,
913     // and adjust the private browsing ID based on what kind of load the caller
914     // has asked us to perform.
915     OriginAttributes attrs;
916     if (aTriggeringPrincipal) {
917       attrs = aTriggeringPrincipal->OriginAttributesRef();
918     }
919     attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0;
920 
921     nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
922     rv = loadInfo->SetOriginAttributes(attrs);
923   }
924 
925   if (NS_FAILED(rv)) {
926     return rv;
927   }
928 
929   // only inherit if we have a principal
930   *aForcePrincipalCheckForCacheEntry =
931       aTriggeringPrincipal && nsContentUtils::ChannelShouldInheritPrincipal(
932                                   aTriggeringPrincipal, aURI,
933                                   /* aInheritForAboutBlank */ false,
934                                   /* aForceInherit */ false);
935 
936   // Initialize HTTP-specific attributes
937   newHttpChannel = do_QueryInterface(*aResult);
938   if (newHttpChannel) {
939     nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
940         do_QueryInterface(newHttpChannel);
941     NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
942     rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
943     MOZ_ASSERT(NS_SUCCEEDED(rv));
944     if (aReferrerInfo) {
945       DebugOnly<nsresult> rv = newHttpChannel->SetReferrerInfo(aReferrerInfo);
946       MOZ_ASSERT(NS_SUCCEEDED(rv));
947     }
948   }
949 
950   // Image channels are loaded by default with reduced priority.
951   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
952   if (p) {
953     uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
954 
955     if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
956       ++priority;  // further reduce priority for background loads
957     }
958 
959     p->AdjustPriority(priority);
960   }
961 
962   // Create a new loadgroup for this new channel, using the old group as
963   // the parent. The indirection keeps the channel insulated from cancels,
964   // but does allow a way for this revalidation to be associated with at
965   // least one base load group for scheduling/caching purposes.
966 
967   nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
968   nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
969   if (childLoadGroup) {
970     childLoadGroup->SetParentLoadGroup(aLoadGroup);
971   }
972   (*aResult)->SetLoadGroup(loadGroup);
973 
974   return NS_OK;
975 }
976 
SecondsFromPRTime(PRTime aTime)977 static uint32_t SecondsFromPRTime(PRTime aTime) {
978   return nsContentUtils::SecondsFromPRTime(aTime);
979 }
980 
981 /* static */
imgCacheEntry(imgLoader * loader,imgRequest * request,bool forcePrincipalCheck)982 imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
983                              bool forcePrincipalCheck)
984     : mLoader(loader),
985       mRequest(request),
986       mDataSize(0),
987       mTouchedTime(SecondsFromPRTime(PR_Now())),
988       mLoadTime(SecondsFromPRTime(PR_Now())),
989       mExpiryTime(0),
990       mMustValidate(false),
991       // We start off as evicted so we don't try to update the cache.
992       // PutIntoCache will set this to false.
993       mEvicted(true),
994       mHasNoProxies(true),
995       mForcePrincipalCheck(forcePrincipalCheck),
996       mHasNotified(false) {}
997 
~imgCacheEntry()998 imgCacheEntry::~imgCacheEntry() {
999   LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
1000 }
1001 
Touch(bool updateTime)1002 void imgCacheEntry::Touch(bool updateTime /* = true */) {
1003   LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
1004 
1005   if (updateTime) {
1006     mTouchedTime = SecondsFromPRTime(PR_Now());
1007   }
1008 
1009   UpdateCache();
1010 }
1011 
UpdateCache(int32_t diff)1012 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) {
1013   // Don't update the cache if we've been removed from it or it doesn't care
1014   // about our size or usage.
1015   if (!Evicted() && HasNoProxies()) {
1016     mLoader->CacheEntriesChanged(mRequest->IsChrome(), diff);
1017   }
1018 }
1019 
UpdateLoadTime()1020 void imgCacheEntry::UpdateLoadTime() {
1021   mLoadTime = SecondsFromPRTime(PR_Now());
1022 }
1023 
SetHasNoProxies(bool hasNoProxies)1024 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) {
1025   if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1026     if (hasNoProxies) {
1027       LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true", "uri",
1028                           mRequest->CacheKey().URI());
1029     } else {
1030       LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false",
1031                           "uri", mRequest->CacheKey().URI());
1032     }
1033   }
1034 
1035   mHasNoProxies = hasNoProxies;
1036 }
1037 
imgCacheQueue()1038 imgCacheQueue::imgCacheQueue() : mDirty(false), mSize(0) {}
1039 
UpdateSize(int32_t diff)1040 void imgCacheQueue::UpdateSize(int32_t diff) { mSize += diff; }
1041 
GetSize() const1042 uint32_t imgCacheQueue::GetSize() const { return mSize; }
1043 
Remove(imgCacheEntry * entry)1044 void imgCacheQueue::Remove(imgCacheEntry* entry) {
1045   uint64_t index = mQueue.IndexOf(entry);
1046   if (index == queueContainer::NoIndex) {
1047     return;
1048   }
1049 
1050   mSize -= mQueue[index]->GetDataSize();
1051 
1052   // If the queue is clean and this is the first entry,
1053   // then we can efficiently remove the entry without
1054   // dirtying the sort order.
1055   if (!IsDirty() && index == 0) {
1056     std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1057     mQueue.RemoveLastElement();
1058     return;
1059   }
1060 
1061   // Remove from the middle of the list.  This potentially
1062   // breaks the binary heap sort order.
1063   mQueue.RemoveElementAt(index);
1064 
1065   // If we only have one entry or the queue is empty, though,
1066   // then the sort order is still effectively good.  Simply
1067   // refresh the list to clear the dirty flag.
1068   if (mQueue.Length() <= 1) {
1069     Refresh();
1070     return;
1071   }
1072 
1073   // Otherwise we must mark the queue dirty and potentially
1074   // trigger an expensive sort later.
1075   MarkDirty();
1076 }
1077 
Push(imgCacheEntry * entry)1078 void imgCacheQueue::Push(imgCacheEntry* entry) {
1079   mSize += entry->GetDataSize();
1080 
1081   RefPtr<imgCacheEntry> refptr(entry);
1082   mQueue.AppendElement(std::move(refptr));
1083   // If we're not dirty already, then we can efficiently add this to the
1084   // binary heap immediately.  This is only O(log n).
1085   if (!IsDirty()) {
1086     std::push_heap(mQueue.begin(), mQueue.end(),
1087                    imgLoader::CompareCacheEntries);
1088   }
1089 }
1090 
Pop()1091 already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() {
1092   if (mQueue.IsEmpty()) {
1093     return nullptr;
1094   }
1095   if (IsDirty()) {
1096     Refresh();
1097   }
1098 
1099   std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1100   RefPtr<imgCacheEntry> entry = mQueue.PopLastElement();
1101 
1102   mSize -= entry->GetDataSize();
1103   return entry.forget();
1104 }
1105 
Refresh()1106 void imgCacheQueue::Refresh() {
1107   // Resort the list.  This is an O(3 * n) operation and best avoided
1108   // if possible.
1109   std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1110   mDirty = false;
1111 }
1112 
MarkDirty()1113 void imgCacheQueue::MarkDirty() { mDirty = true; }
1114 
IsDirty()1115 bool imgCacheQueue::IsDirty() { return mDirty; }
1116 
GetNumElements() const1117 uint32_t imgCacheQueue::GetNumElements() const { return mQueue.Length(); }
1118 
Contains(imgCacheEntry * aEntry) const1119 bool imgCacheQueue::Contains(imgCacheEntry* aEntry) const {
1120   return mQueue.Contains(aEntry);
1121 }
1122 
begin()1123 imgCacheQueue::iterator imgCacheQueue::begin() { return mQueue.begin(); }
1124 
begin() const1125 imgCacheQueue::const_iterator imgCacheQueue::begin() const {
1126   return mQueue.begin();
1127 }
1128 
end()1129 imgCacheQueue::iterator imgCacheQueue::end() { return mQueue.end(); }
1130 
end() const1131 imgCacheQueue::const_iterator imgCacheQueue::end() const {
1132   return mQueue.end();
1133 }
1134 
CreateNewProxyForRequest(imgRequest * aRequest,nsIURI * aURI,nsILoadGroup * aLoadGroup,Document * aLoadingDocument,imgINotificationObserver * aObserver,nsLoadFlags aLoadFlags,imgRequestProxy ** _retval)1135 nsresult imgLoader::CreateNewProxyForRequest(
1136     imgRequest* aRequest, nsIURI* aURI, nsILoadGroup* aLoadGroup,
1137     Document* aLoadingDocument, imgINotificationObserver* aObserver,
1138     nsLoadFlags aLoadFlags, imgRequestProxy** _retval) {
1139   LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest",
1140                        "imgRequest", aRequest);
1141 
1142   /* XXX If we move decoding onto separate threads, we should save off the
1143      calling thread here and pass it off to |proxyRequest| so that it call
1144      proxy calls to |aObserver|.
1145    */
1146 
1147   RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy();
1148 
1149   /* It is important to call |SetLoadFlags()| before calling |Init()| because
1150      |Init()| adds the request to the loadgroup.
1151    */
1152   proxyRequest->SetLoadFlags(aLoadFlags);
1153 
1154   // init adds itself to imgRequest's list of observers
1155   nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aLoadingDocument, aURI,
1156                                    aObserver);
1157   if (NS_WARN_IF(NS_FAILED(rv))) {
1158     return rv;
1159   }
1160 
1161   proxyRequest.forget(_retval);
1162   return NS_OK;
1163 }
1164 
1165 class imgCacheExpirationTracker final
1166     : public nsExpirationTracker<imgCacheEntry, 3> {
1167   enum { TIMEOUT_SECONDS = 10 };
1168 
1169  public:
1170   imgCacheExpirationTracker();
1171 
1172  protected:
1173   void NotifyExpired(imgCacheEntry* entry) override;
1174 };
1175 
imgCacheExpirationTracker()1176 imgCacheExpirationTracker::imgCacheExpirationTracker()
1177     : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000,
1178                                             "imgCacheExpirationTracker") {}
1179 
NotifyExpired(imgCacheEntry * entry)1180 void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) {
1181   // Hold on to a reference to this entry, because the expiration tracker
1182   // mechanism doesn't.
1183   RefPtr<imgCacheEntry> kungFuDeathGrip(entry);
1184 
1185   if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1186     RefPtr<imgRequest> req = entry->GetRequest();
1187     if (req) {
1188       LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired",
1189                           "entry", req->CacheKey().URI());
1190     }
1191   }
1192 
1193   // We can be called multiple times on the same entry. Don't do work multiple
1194   // times.
1195   if (!entry->Evicted()) {
1196     entry->Loader()->RemoveFromCache(entry);
1197   }
1198 
1199   entry->Loader()->VerifyCacheSizes();
1200 }
1201 
1202 ///////////////////////////////////////////////////////////////////////////////
1203 // imgLoader
1204 ///////////////////////////////////////////////////////////////////////////////
1205 
1206 double imgLoader::sCacheTimeWeight;
1207 uint32_t imgLoader::sCacheMaxSize;
1208 imgMemoryReporter* imgLoader::sMemReporter;
1209 
1210 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
1211                   nsISupportsWeakReference, nsIObserver)
1212 
1213 static imgLoader* gNormalLoader = nullptr;
1214 static imgLoader* gPrivateBrowsingLoader = nullptr;
1215 
1216 /* static */
CreateImageLoader()1217 already_AddRefed<imgLoader> imgLoader::CreateImageLoader() {
1218   // In some cases, such as xpctests, XPCOM modules are not automatically
1219   // initialized.  We need to make sure that our module is initialized before
1220   // we hand out imgLoader instances and code starts using them.
1221   mozilla::image::EnsureModuleInitialized();
1222 
1223   RefPtr<imgLoader> loader = new imgLoader();
1224   loader->Init();
1225 
1226   return loader.forget();
1227 }
1228 
NormalLoader()1229 imgLoader* imgLoader::NormalLoader() {
1230   if (!gNormalLoader) {
1231     gNormalLoader = CreateImageLoader().take();
1232   }
1233   return gNormalLoader;
1234 }
1235 
PrivateBrowsingLoader()1236 imgLoader* imgLoader::PrivateBrowsingLoader() {
1237   if (!gPrivateBrowsingLoader) {
1238     gPrivateBrowsingLoader = CreateImageLoader().take();
1239     gPrivateBrowsingLoader->RespectPrivacyNotifications();
1240   }
1241   return gPrivateBrowsingLoader;
1242 }
1243 
imgLoader()1244 imgLoader::imgLoader()
1245     : mUncachedImagesMutex("imgLoader::UncachedImages"),
1246       mRespectPrivacy(false) {
1247   sMemReporter->AddRef();
1248   sMemReporter->RegisterLoader(this);
1249 }
1250 
~imgLoader()1251 imgLoader::~imgLoader() {
1252   ClearChromeImageCache();
1253   ClearImageCache();
1254   {
1255     // If there are any of our imgRequest's left they are in the uncached
1256     // images set, so clear their pointer to us.
1257     MutexAutoLock lock(mUncachedImagesMutex);
1258     for (RefPtr<imgRequest> req : mUncachedImages) {
1259       req->ClearLoader();
1260     }
1261   }
1262   sMemReporter->UnregisterLoader(this);
1263   sMemReporter->Release();
1264 }
1265 
VerifyCacheSizes()1266 void imgLoader::VerifyCacheSizes() {
1267 #ifdef DEBUG
1268   if (!mCacheTracker) {
1269     return;
1270   }
1271 
1272   uint32_t cachesize = mCache.Count() + mChromeCache.Count();
1273   uint32_t queuesize =
1274       mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
1275   uint32_t trackersize = 0;
1276   for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
1277        it.Next();) {
1278     trackersize++;
1279   }
1280   MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
1281   MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
1282 #endif
1283 }
1284 
GetCache(bool aForChrome)1285 imgLoader::imgCacheTable& imgLoader::GetCache(bool aForChrome) {
1286   return aForChrome ? mChromeCache : mCache;
1287 }
1288 
GetCache(const ImageCacheKey & aKey)1289 imgLoader::imgCacheTable& imgLoader::GetCache(const ImageCacheKey& aKey) {
1290   return GetCache(aKey.IsChrome());
1291 }
1292 
GetCacheQueue(bool aForChrome)1293 imgCacheQueue& imgLoader::GetCacheQueue(bool aForChrome) {
1294   return aForChrome ? mChromeCacheQueue : mCacheQueue;
1295 }
1296 
GetCacheQueue(const ImageCacheKey & aKey)1297 imgCacheQueue& imgLoader::GetCacheQueue(const ImageCacheKey& aKey) {
1298   return GetCacheQueue(aKey.IsChrome());
1299 }
1300 
GlobalInit()1301 void imgLoader::GlobalInit() {
1302   sCacheTimeWeight = StaticPrefs::image_cache_timeweight_AtStartup() / 1000.0;
1303   int32_t cachesize = StaticPrefs::image_cache_size_AtStartup();
1304   sCacheMaxSize = cachesize > 0 ? cachesize : 0;
1305 
1306   sMemReporter = new imgMemoryReporter();
1307   RegisterStrongAsyncMemoryReporter(sMemReporter);
1308   RegisterImagesContentUsedUncompressedDistinguishedAmount(
1309       imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
1310 }
1311 
ShutdownMemoryReporter()1312 void imgLoader::ShutdownMemoryReporter() {
1313   UnregisterImagesContentUsedUncompressedDistinguishedAmount();
1314   UnregisterStrongMemoryReporter(sMemReporter);
1315 }
1316 
InitCache()1317 nsresult imgLoader::InitCache() {
1318   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1319   if (!os) {
1320     return NS_ERROR_FAILURE;
1321   }
1322 
1323   os->AddObserver(this, "memory-pressure", false);
1324   os->AddObserver(this, "chrome-flush-caches", false);
1325   os->AddObserver(this, "last-pb-context-exited", false);
1326   os->AddObserver(this, "profile-before-change", false);
1327   os->AddObserver(this, "xpcom-shutdown", false);
1328 
1329   mCacheTracker = MakeUnique<imgCacheExpirationTracker>();
1330 
1331   return NS_OK;
1332 }
1333 
Init()1334 nsresult imgLoader::Init() {
1335   InitCache();
1336 
1337   return NS_OK;
1338 }
1339 
1340 NS_IMETHODIMP
RespectPrivacyNotifications()1341 imgLoader::RespectPrivacyNotifications() {
1342   mRespectPrivacy = true;
1343   return NS_OK;
1344 }
1345 
1346 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1347 imgLoader::Observe(nsISupports* aSubject, const char* aTopic,
1348                    const char16_t* aData) {
1349   if (strcmp(aTopic, "memory-pressure") == 0) {
1350     MinimizeCaches();
1351   } else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1352     MinimizeCaches();
1353     ClearChromeImageCache();
1354   } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
1355     if (mRespectPrivacy) {
1356       ClearImageCache();
1357       ClearChromeImageCache();
1358     }
1359   } else if (strcmp(aTopic, "profile-before-change") == 0) {
1360     mCacheTracker = nullptr;
1361   } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
1362     mCacheTracker = nullptr;
1363     ShutdownMemoryReporter();
1364 
1365   } else {
1366     // (Nothing else should bring us here)
1367     MOZ_ASSERT(0, "Invalid topic received");
1368   }
1369 
1370   return NS_OK;
1371 }
1372 
1373 NS_IMETHODIMP
ClearCache(bool chrome)1374 imgLoader::ClearCache(bool chrome) {
1375   if (XRE_IsParentProcess()) {
1376     bool privateLoader = this == gPrivateBrowsingLoader;
1377     for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1378       Unused << cp->SendClearImageCache(privateLoader, chrome);
1379     }
1380   }
1381 
1382   if (chrome) {
1383     return ClearChromeImageCache();
1384   }
1385   return ClearImageCache();
1386 }
1387 
1388 NS_IMETHODIMP
RemoveEntriesFromPrincipalInAllProcesses(nsIPrincipal * aPrincipal)1389 imgLoader::RemoveEntriesFromPrincipalInAllProcesses(nsIPrincipal* aPrincipal) {
1390   if (!XRE_IsParentProcess()) {
1391     return NS_ERROR_NOT_AVAILABLE;
1392   }
1393 
1394   for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1395     Unused << cp->SendClearImageCacheFromPrincipal(aPrincipal);
1396   }
1397 
1398   imgLoader* loader;
1399   if (aPrincipal->OriginAttributesRef().mPrivateBrowsingId ==
1400       nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) {
1401     loader = imgLoader::NormalLoader();
1402   } else {
1403     loader = imgLoader::PrivateBrowsingLoader();
1404   }
1405 
1406   return loader->RemoveEntriesInternal(aPrincipal, nullptr);
1407 }
1408 
1409 NS_IMETHODIMP
RemoveEntriesFromBaseDomainInAllProcesses(const nsACString & aBaseDomain)1410 imgLoader::RemoveEntriesFromBaseDomainInAllProcesses(
1411     const nsACString& aBaseDomain) {
1412   if (!XRE_IsParentProcess()) {
1413     return NS_ERROR_NOT_AVAILABLE;
1414   }
1415 
1416   for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1417     Unused << cp->SendClearImageCacheFromBaseDomain(nsCString(aBaseDomain));
1418   }
1419 
1420   return RemoveEntriesInternal(nullptr, &aBaseDomain);
1421 }
1422 
RemoveEntriesInternal(nsIPrincipal * aPrincipal,const nsACString * aBaseDomain)1423 nsresult imgLoader::RemoveEntriesInternal(nsIPrincipal* aPrincipal,
1424                                           const nsACString* aBaseDomain) {
1425   // Can only clear by either principal or base domain.
1426   if ((!aPrincipal && !aBaseDomain) || (aPrincipal && aBaseDomain)) {
1427     return NS_ERROR_INVALID_ARG;
1428   }
1429 
1430   nsAutoString origin;
1431   if (aPrincipal) {
1432     nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, origin);
1433     if (NS_WARN_IF(NS_FAILED(rv))) {
1434       return rv;
1435     }
1436   }
1437 
1438   nsCOMPtr<nsIEffectiveTLDService> tldService;
1439   AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1440 
1441   // For base domain we only clear the non-chrome cache.
1442   imgCacheTable& cache =
1443       GetCache(aPrincipal && aPrincipal->IsSystemPrincipal());
1444   for (const auto& entry : cache) {
1445     const auto& key = entry.GetKey();
1446 
1447     const bool shouldRemove = [&] {
1448       if (aPrincipal) {
1449         if (key.OriginAttributesRef() !=
1450             BasePrincipal::Cast(aPrincipal)->OriginAttributesRef()) {
1451           return false;
1452         }
1453 
1454         nsAutoString imageOrigin;
1455         nsresult rv = nsContentUtils::GetUTFOrigin(key.URI(), imageOrigin);
1456         if (NS_WARN_IF(NS_FAILED(rv))) {
1457           return false;
1458         }
1459 
1460         return imageOrigin == origin;
1461       }
1462 
1463       if (!aBaseDomain) {
1464         return false;
1465       }
1466       // Clear by baseDomain.
1467       nsAutoCString host;
1468       nsresult rv = key.URI()->GetHost(host);
1469       if (NS_FAILED(rv) || host.IsEmpty()) {
1470         return false;
1471       }
1472 
1473       if (!tldService) {
1474         tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
1475       }
1476       if (NS_WARN_IF(!tldService)) {
1477         return false;
1478       }
1479 
1480       bool hasRootDomain = false;
1481       rv = tldService->HasRootDomain(host, *aBaseDomain, &hasRootDomain);
1482       if (NS_SUCCEEDED(rv) && hasRootDomain) {
1483         return true;
1484       }
1485 
1486       // If we don't get a direct base domain match, also check for cache of
1487       // third parties partitioned under aBaseDomain.
1488 
1489       // The isolation key is either just the base domain, or an origin suffix
1490       // which contains the partitionKey holding the baseDomain.
1491 
1492       if (key.IsolationKeyRef().Equals(*aBaseDomain)) {
1493         return true;
1494       }
1495 
1496       // The isolation key does not match the given base domain. It may be an
1497       // origin suffix. Parse it into origin attributes.
1498       OriginAttributes attrs;
1499       if (!attrs.PopulateFromSuffix(key.IsolationKeyRef())) {
1500         // Key is not an origin suffix.
1501         return false;
1502       }
1503 
1504       return StoragePrincipalHelper::PartitionKeyHasBaseDomain(
1505           attrs.mPartitionKey, *aBaseDomain);
1506     }();
1507 
1508     if (shouldRemove) {
1509       entriesToBeRemoved.AppendElement(entry.GetData());
1510     }
1511   }
1512 
1513   for (auto& entry : entriesToBeRemoved) {
1514     if (!RemoveFromCache(entry)) {
1515       NS_WARNING(
1516           "Couldn't remove an entry from the cache in "
1517           "RemoveEntriesInternal()\n");
1518     }
1519   }
1520 
1521   return NS_OK;
1522 }
1523 
1524 NS_IMETHODIMP
RemoveEntry(nsIURI * aURI,Document * aDoc)1525 imgLoader::RemoveEntry(nsIURI* aURI, Document* aDoc) {
1526   if (aURI) {
1527     OriginAttributes attrs;
1528     if (aDoc) {
1529       nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1530       if (principal) {
1531         attrs = principal->OriginAttributesRef();
1532       }
1533     }
1534 
1535     ImageCacheKey key(aURI, attrs, aDoc);
1536     if (RemoveFromCache(key)) {
1537       return NS_OK;
1538     }
1539   }
1540   return NS_ERROR_NOT_AVAILABLE;
1541 }
1542 
1543 NS_IMETHODIMP
FindEntryProperties(nsIURI * uri,Document * aDoc,nsIProperties ** _retval)1544 imgLoader::FindEntryProperties(nsIURI* uri, Document* aDoc,
1545                                nsIProperties** _retval) {
1546   *_retval = nullptr;
1547 
1548   OriginAttributes attrs;
1549   if (aDoc) {
1550     nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1551     if (principal) {
1552       attrs = principal->OriginAttributesRef();
1553     }
1554   }
1555 
1556   ImageCacheKey key(uri, attrs, aDoc);
1557   imgCacheTable& cache = GetCache(key);
1558 
1559   RefPtr<imgCacheEntry> entry;
1560   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1561     if (mCacheTracker && entry->HasNoProxies()) {
1562       mCacheTracker->MarkUsed(entry);
1563     }
1564 
1565     RefPtr<imgRequest> request = entry->GetRequest();
1566     if (request) {
1567       nsCOMPtr<nsIProperties> properties = request->Properties();
1568       properties.forget(_retval);
1569     }
1570   }
1571 
1572   return NS_OK;
1573 }
1574 
NS_IMETHODIMP_(void)1575 NS_IMETHODIMP_(void)
1576 imgLoader::ClearCacheForControlledDocument(Document* aDoc) {
1577   MOZ_ASSERT(aDoc);
1578   AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1579   imgCacheTable& cache = GetCache(false);
1580   for (const auto& entry : cache) {
1581     const auto& key = entry.GetKey();
1582     if (key.ControlledDocument() == aDoc) {
1583       entriesToBeRemoved.AppendElement(entry.GetData());
1584     }
1585   }
1586   for (auto& entry : entriesToBeRemoved) {
1587     if (!RemoveFromCache(entry)) {
1588       NS_WARNING(
1589           "Couldn't remove an entry from the cache in "
1590           "ClearCacheForControlledDocument()\n");
1591     }
1592   }
1593 }
1594 
Shutdown()1595 void imgLoader::Shutdown() {
1596   NS_IF_RELEASE(gNormalLoader);
1597   gNormalLoader = nullptr;
1598   NS_IF_RELEASE(gPrivateBrowsingLoader);
1599   gPrivateBrowsingLoader = nullptr;
1600 }
1601 
ClearChromeImageCache()1602 nsresult imgLoader::ClearChromeImageCache() {
1603   return EvictEntries(mChromeCache);
1604 }
1605 
ClearImageCache()1606 nsresult imgLoader::ClearImageCache() { return EvictEntries(mCache); }
1607 
MinimizeCaches()1608 void imgLoader::MinimizeCaches() {
1609   EvictEntries(mCacheQueue);
1610   EvictEntries(mChromeCacheQueue);
1611 }
1612 
PutIntoCache(const ImageCacheKey & aKey,imgCacheEntry * entry)1613 bool imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) {
1614   imgCacheTable& cache = GetCache(aKey);
1615 
1616   LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::PutIntoCache", "uri",
1617                              aKey.URI());
1618 
1619   // Check to see if this request already exists in the cache. If so, we'll
1620   // replace the old version.
1621   RefPtr<imgCacheEntry> tmpCacheEntry;
1622   if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1623     MOZ_LOG(
1624         gImgLog, LogLevel::Debug,
1625         ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
1626          nullptr));
1627     RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1628 
1629     // If it already exists, and we're putting the same key into the cache, we
1630     // should remove the old version.
1631     MOZ_LOG(gImgLog, LogLevel::Debug,
1632             ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element",
1633              nullptr));
1634 
1635     RemoveFromCache(aKey);
1636   } else {
1637     MOZ_LOG(gImgLog, LogLevel::Debug,
1638             ("[this=%p] imgLoader::PutIntoCache --"
1639              " Element NOT already in the cache",
1640              nullptr));
1641   }
1642 
1643   cache.InsertOrUpdate(aKey, RefPtr{entry});
1644 
1645   // We can be called to resurrect an evicted entry.
1646   if (entry->Evicted()) {
1647     entry->SetEvicted(false);
1648   }
1649 
1650   // If we're resurrecting an entry with no proxies, put it back in the
1651   // tracker and queue.
1652   if (entry->HasNoProxies()) {
1653     nsresult addrv = NS_OK;
1654 
1655     if (mCacheTracker) {
1656       addrv = mCacheTracker->AddObject(entry);
1657     }
1658 
1659     if (NS_SUCCEEDED(addrv)) {
1660       imgCacheQueue& queue = GetCacheQueue(aKey);
1661       queue.Push(entry);
1662     }
1663   }
1664 
1665   RefPtr<imgRequest> request = entry->GetRequest();
1666   request->SetIsInCache(true);
1667   RemoveFromUncachedImages(request);
1668 
1669   return true;
1670 }
1671 
SetHasNoProxies(imgRequest * aRequest,imgCacheEntry * aEntry)1672 bool imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) {
1673   LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasNoProxies", "uri",
1674                              aRequest->CacheKey().URI());
1675 
1676   aEntry->SetHasNoProxies(true);
1677 
1678   if (aEntry->Evicted()) {
1679     return false;
1680   }
1681 
1682   imgCacheQueue& queue = GetCacheQueue(aRequest->IsChrome());
1683 
1684   nsresult addrv = NS_OK;
1685 
1686   if (mCacheTracker) {
1687     addrv = mCacheTracker->AddObject(aEntry);
1688   }
1689 
1690   if (NS_SUCCEEDED(addrv)) {
1691     queue.Push(aEntry);
1692   }
1693 
1694   imgCacheTable& cache = GetCache(aRequest->IsChrome());
1695   CheckCacheLimits(cache, queue);
1696 
1697   return true;
1698 }
1699 
SetHasProxies(imgRequest * aRequest)1700 bool imgLoader::SetHasProxies(imgRequest* aRequest) {
1701   VerifyCacheSizes();
1702 
1703   const ImageCacheKey& key = aRequest->CacheKey();
1704   imgCacheTable& cache = GetCache(key);
1705 
1706   LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies", "uri",
1707                              key.URI());
1708 
1709   RefPtr<imgCacheEntry> entry;
1710   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1711     // Make sure the cache entry is for the right request
1712     RefPtr<imgRequest> entryRequest = entry->GetRequest();
1713     if (entryRequest == aRequest && entry->HasNoProxies()) {
1714       imgCacheQueue& queue = GetCacheQueue(key);
1715       queue.Remove(entry);
1716 
1717       if (mCacheTracker) {
1718         mCacheTracker->RemoveObject(entry);
1719       }
1720 
1721       entry->SetHasNoProxies(false);
1722 
1723       return true;
1724     }
1725   }
1726 
1727   return false;
1728 }
1729 
CacheEntriesChanged(bool aForChrome,int32_t aSizeDiff)1730 void imgLoader::CacheEntriesChanged(bool aForChrome,
1731                                     int32_t aSizeDiff /* = 0 */) {
1732   imgCacheQueue& queue = GetCacheQueue(aForChrome);
1733   // We only need to dirty the queue if there is any sorting
1734   // taking place.  Empty or single-entry lists can't become
1735   // dirty.
1736   if (queue.GetNumElements() > 1) {
1737     queue.MarkDirty();
1738   }
1739   queue.UpdateSize(aSizeDiff);
1740 }
1741 
CheckCacheLimits(imgCacheTable & cache,imgCacheQueue & queue)1742 void imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue) {
1743   if (queue.GetNumElements() == 0) {
1744     NS_ASSERTION(queue.GetSize() == 0,
1745                  "imgLoader::CheckCacheLimits -- incorrect cache size");
1746   }
1747 
1748   // Remove entries from the cache until we're back at our desired max size.
1749   while (queue.GetSize() > sCacheMaxSize) {
1750     // Remove the first entry in the queue.
1751     RefPtr<imgCacheEntry> entry(queue.Pop());
1752 
1753     NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1754 
1755     if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1756       RefPtr<imgRequest> req = entry->GetRequest();
1757       if (req) {
1758         LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::CheckCacheLimits",
1759                                    "entry", req->CacheKey().URI());
1760       }
1761     }
1762 
1763     if (entry) {
1764       // We just popped this entry from the queue, so pass AlreadyRemoved
1765       // to avoid searching the queue again in RemoveFromCache.
1766       RemoveFromCache(entry, QueueState::AlreadyRemoved);
1767     }
1768   }
1769 }
1770 
ValidateRequestWithNewChannel(imgRequest * request,nsIURI * aURI,nsIURI * aInitialDocumentURI,nsIReferrerInfo * aReferrerInfo,nsILoadGroup * aLoadGroup,imgINotificationObserver * aObserver,Document * aLoadingDocument,uint64_t aInnerWindowId,nsLoadFlags aLoadFlags,nsContentPolicyType aLoadPolicyType,imgRequestProxy ** aProxyRequest,nsIPrincipal * aTriggeringPrincipal,CORSMode aCORSMode,bool aLinkPreload,bool * aNewChannelCreated)1771 bool imgLoader::ValidateRequestWithNewChannel(
1772     imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1773     nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
1774     imgINotificationObserver* aObserver, Document* aLoadingDocument,
1775     uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
1776     nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest,
1777     nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode, bool aLinkPreload,
1778     bool* aNewChannelCreated) {
1779   // now we need to insert a new channel request object in between the real
1780   // request and the proxy that basically delays loading the image until it
1781   // gets a 304 or figures out that this needs to be a new request
1782 
1783   nsresult rv;
1784 
1785   // If we're currently in the middle of validating this request, just hand
1786   // back a proxy to it; the required work will be done for us.
1787   if (imgCacheValidator* validator = request->GetValidator()) {
1788     rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
1789                                   aObserver, aLoadFlags, aProxyRequest);
1790     if (NS_FAILED(rv)) {
1791       return false;
1792     }
1793 
1794     if (*aProxyRequest) {
1795       imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1796 
1797       // We will send notifications from imgCacheValidator::OnStartRequest().
1798       // In the mean time, we must defer notifications because we are added to
1799       // the imgRequest's proxy list, and we can get extra notifications
1800       // resulting from methods such as StartDecoding(). See bug 579122.
1801       proxy->MarkValidating();
1802 
1803       if (aLinkPreload) {
1804         MOZ_ASSERT(aLoadingDocument);
1805         proxy->PrioritizeAsPreload();
1806         auto preloadKey = PreloadHashKey::CreateAsImage(
1807             aURI, aTriggeringPrincipal, aCORSMode);
1808         proxy->NotifyOpen(preloadKey, aLoadingDocument, true);
1809       }
1810 
1811       // Attach the proxy without notifying
1812       validator->AddProxy(proxy);
1813     }
1814 
1815     return true;
1816   }
1817   // We will rely on Necko to cache this request when it's possible, and to
1818   // tell imgCacheValidator::OnStartRequest whether the request came from its
1819   // cache.
1820   nsCOMPtr<nsIChannel> newChannel;
1821   bool forcePrincipalCheck;
1822   rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
1823                        aInitialDocumentURI, aCORSMode, aReferrerInfo,
1824                        aLoadGroup, aLoadFlags, aLoadPolicyType,
1825                        aTriggeringPrincipal, aLoadingDocument, mRespectPrivacy);
1826   if (NS_FAILED(rv)) {
1827     return false;
1828   }
1829 
1830   if (aNewChannelCreated) {
1831     *aNewChannelCreated = true;
1832   }
1833 
1834   RefPtr<imgRequestProxy> req;
1835   rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
1836                                 aObserver, aLoadFlags, getter_AddRefs(req));
1837   if (NS_FAILED(rv)) {
1838     return false;
1839   }
1840 
1841   // Make sure that OnStatus/OnProgress calls have the right request set...
1842   RefPtr<nsProgressNotificationProxy> progressproxy =
1843       new nsProgressNotificationProxy(newChannel, req);
1844   if (!progressproxy) {
1845     return false;
1846   }
1847 
1848   RefPtr<imgCacheValidator> hvc =
1849       new imgCacheValidator(progressproxy, this, request, aLoadingDocument,
1850                             aInnerWindowId, forcePrincipalCheck);
1851 
1852   // Casting needed here to get past multiple inheritance.
1853   nsCOMPtr<nsIStreamListener> listener =
1854       do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
1855   NS_ENSURE_TRUE(listener, false);
1856 
1857   // We must set the notification callbacks before setting up the
1858   // CORS listener, because that's also interested inthe
1859   // notification callbacks.
1860   newChannel->SetNotificationCallbacks(hvc);
1861 
1862   request->SetValidator(hvc);
1863 
1864   // We will send notifications from imgCacheValidator::OnStartRequest().
1865   // In the mean time, we must defer notifications because we are added to
1866   // the imgRequest's proxy list, and we can get extra notifications
1867   // resulting from methods such as StartDecoding(). See bug 579122.
1868   req->MarkValidating();
1869 
1870   if (aLinkPreload) {
1871     MOZ_ASSERT(aLoadingDocument);
1872     req->PrioritizeAsPreload();
1873     auto preloadKey =
1874         PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal, aCORSMode);
1875     req->NotifyOpen(preloadKey, aLoadingDocument, true);
1876   }
1877 
1878   // Add the proxy without notifying
1879   hvc->AddProxy(req);
1880 
1881   mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
1882                                nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1883                                aLoadGroup);
1884   rv = newChannel->AsyncOpen(listener);
1885   if (NS_WARN_IF(NS_FAILED(rv))) {
1886     req->CancelAndForgetObserver(rv);
1887     // This will notify any current or future <link preload> tags.  Pass the
1888     // non-open channel so that we can read loadinfo and referrer info of that
1889     // channel.
1890     req->NotifyStart(newChannel);
1891     // Use the non-channel overload of this method to force the notification to
1892     // happen.  The preload request has not been assigned a channel.
1893     req->NotifyStop(rv);
1894     return false;
1895   }
1896 
1897   req.forget(aProxyRequest);
1898   return true;
1899 }
1900 
NotifyObserversForCachedImage(imgCacheEntry * aEntry,imgRequest * request,nsIURI * aURI,nsIReferrerInfo * aReferrerInfo,Document * aLoadingDocument,nsIPrincipal * aTriggeringPrincipal,CORSMode aCORSMode)1901 void imgLoader::NotifyObserversForCachedImage(
1902     imgCacheEntry* aEntry, imgRequest* request, nsIURI* aURI,
1903     nsIReferrerInfo* aReferrerInfo, Document* aLoadingDocument,
1904     nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode) {
1905   if (aEntry->HasNotified()) {
1906     return;
1907   }
1908 
1909   nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
1910 
1911   if (!obsService->HasObservers("http-on-image-cache-response")) {
1912     return;
1913   }
1914 
1915   aEntry->SetHasNotified();
1916 
1917   nsCOMPtr<nsIChannel> newChannel;
1918   bool forcePrincipalCheck;
1919   nsresult rv =
1920       NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
1921                       nullptr, aCORSMode, aReferrerInfo, nullptr, 0,
1922                       nsIContentPolicy::TYPE_INTERNAL_IMAGE,
1923                       aTriggeringPrincipal, aLoadingDocument, mRespectPrivacy);
1924   if (NS_FAILED(rv)) {
1925     return;
1926   }
1927 
1928   RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(newChannel);
1929   if (httpBaseChannel) {
1930     httpBaseChannel->SetDummyChannelForImageCache();
1931     newChannel->SetContentType(nsDependentCString(request->GetMimeType()));
1932     RefPtr<mozilla::image::Image> image = request->GetImage();
1933     if (image) {
1934       newChannel->SetContentLength(aEntry->GetDataSize());
1935     }
1936     obsService->NotifyObservers(newChannel, "http-on-image-cache-response",
1937                                 nullptr);
1938   }
1939 }
1940 
ValidateEntry(imgCacheEntry * aEntry,nsIURI * aURI,nsIURI * aInitialDocumentURI,nsIReferrerInfo * aReferrerInfo,nsILoadGroup * aLoadGroup,imgINotificationObserver * aObserver,Document * aLoadingDocument,nsLoadFlags aLoadFlags,nsContentPolicyType aLoadPolicyType,bool aCanMakeNewChannel,bool * aNewChannelCreated,imgRequestProxy ** aProxyRequest,nsIPrincipal * aTriggeringPrincipal,CORSMode aCORSMode,bool aLinkPreload)1941 bool imgLoader::ValidateEntry(
1942     imgCacheEntry* aEntry, nsIURI* aURI, nsIURI* aInitialDocumentURI,
1943     nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
1944     imgINotificationObserver* aObserver, Document* aLoadingDocument,
1945     nsLoadFlags aLoadFlags, nsContentPolicyType aLoadPolicyType,
1946     bool aCanMakeNewChannel, bool* aNewChannelCreated,
1947     imgRequestProxy** aProxyRequest, nsIPrincipal* aTriggeringPrincipal,
1948     CORSMode aCORSMode, bool aLinkPreload) {
1949   LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
1950 
1951   // If the expiration time is zero, then the request has not gotten far enough
1952   // to know when it will expire, or we know it will never expire (see
1953   // nsContentUtils::GetSubresourceCacheValidationInfo).
1954   uint32_t expiryTime = aEntry->GetExpiryTime();
1955   bool hasExpired = expiryTime && expiryTime <= SecondsFromPRTime(PR_Now());
1956 
1957   nsresult rv;
1958 
1959   // Special treatment for file URLs - aEntry has expired if file has changed
1960   nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
1961   if (fileUrl) {
1962     uint32_t lastModTime = aEntry->GetLoadTime();
1963 
1964     nsCOMPtr<nsIFile> theFile;
1965     rv = fileUrl->GetFile(getter_AddRefs(theFile));
1966     if (NS_SUCCEEDED(rv)) {
1967       PRTime fileLastMod;
1968       rv = theFile->GetLastModifiedTime(&fileLastMod);
1969       if (NS_SUCCEEDED(rv)) {
1970         // nsIFile uses millisec, NSPR usec
1971         fileLastMod *= 1000;
1972         hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
1973       }
1974     }
1975   }
1976 
1977   RefPtr<imgRequest> request(aEntry->GetRequest());
1978 
1979   if (!request) {
1980     return false;
1981   }
1982 
1983   if (!ValidateSecurityInfo(request, aEntry->ForcePrincipalCheck(), aCORSMode,
1984                             aTriggeringPrincipal, aLoadingDocument,
1985                             aLoadPolicyType)) {
1986     return false;
1987   }
1988 
1989   // data URIs are immutable and by their nature can't leak data, so we can
1990   // just return true in that case.  Doing so would mean that shift-reload
1991   // doesn't reload data URI documents/images though (which is handy for
1992   // debugging during gecko development) so we make an exception in that case.
1993   nsAutoCString scheme;
1994   aURI->GetScheme(scheme);
1995   if (scheme.EqualsLiteral("data") &&
1996       !(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) {
1997     return true;
1998   }
1999 
2000   bool validateRequest = false;
2001 
2002   if (!request->CanReuseWithoutValidation(aLoadingDocument)) {
2003     // If we would need to revalidate this entry, but we're being told to
2004     // bypass the cache, we don't allow this entry to be used.
2005     if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
2006       return false;
2007     }
2008 
2009     if (MOZ_UNLIKELY(ChaosMode::isActive(ChaosFeature::ImageCache))) {
2010       if (ChaosMode::randomUint32LessThan(4) < 1) {
2011         return false;
2012       }
2013     }
2014 
2015     // Determine whether the cache aEntry must be revalidated...
2016     validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
2017 
2018     MOZ_LOG(gImgLog, LogLevel::Debug,
2019             ("imgLoader::ValidateEntry validating cache entry. "
2020              "validateRequest = %d",
2021              validateRequest));
2022   } else if (!aLoadingDocument && MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
2023     MOZ_LOG(gImgLog, LogLevel::Debug,
2024             ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
2025              "because of NULL loading document",
2026              aURI->GetSpecOrDefault().get()));
2027   }
2028 
2029   // If the original request is still transferring don't kick off a validation
2030   // network request because it is a bit silly to issue a validation request if
2031   // the original request hasn't even finished yet. So just return true
2032   // indicating the caller can create a new proxy for the request and use it as
2033   // is.
2034   // This is an optimization but it's also required for correctness. If we don't
2035   // do this then when firing the load complete notification for the original
2036   // request that can unblock load for the document and then spin the event loop
2037   // (see the stack in bug 1641682) which then the OnStartRequest for the
2038   // validation request can fire which can call UpdateProxies and can sync
2039   // notify on the progress tracker about all existing state, which includes
2040   // load complete, so we fire a second load complete notification for the
2041   // image.
2042   // In addition, we want to validate if the original request encountered
2043   // an error for two reasons. The first being if the error was a network error
2044   // then trying to re-fetch the image might succeed. The second is more
2045   // complicated. We decide if we should fire the load or error event for img
2046   // elements depending on if the image has error in its status at the time when
2047   // the load complete notification is received, and we set error status on an
2048   // image if it encounters a network error or a decode error with no real way
2049   // to tell them apart. So if we load an image that will produce a decode error
2050   // the first time we will usually fire the load event, and then decode enough
2051   // to encounter the decode error and set the error status on the image. The
2052   // next time we reference the image in the same document the load complete
2053   // notification is replayed and this time the error status from the decode is
2054   // already present so we fire the error event instead of the load event. This
2055   // is a bug (bug 1645576) that we should fix. In order to avoid that bug in
2056   // some cases (specifically the cases when we hit this code and try to
2057   // validate the request) we make sure to validate. This avoids the bug because
2058   // when the load complete notification arrives the proxy is marked as
2059   // validating so it lies about its status and returns nothing.
2060   bool requestComplete = false;
2061   RefPtr<ProgressTracker> tracker;
2062   RefPtr<mozilla::image::Image> image = request->GetImage();
2063   if (image) {
2064     tracker = image->GetProgressTracker();
2065   } else {
2066     tracker = request->GetProgressTracker();
2067   }
2068   if (tracker) {
2069     if (tracker->GetProgress() & (FLAG_LOAD_COMPLETE | FLAG_HAS_ERROR)) {
2070       requestComplete = true;
2071     }
2072   }
2073   if (!requestComplete) {
2074     return true;
2075   }
2076 
2077   if (validateRequest && aCanMakeNewChannel) {
2078     LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate");
2079 
2080     uint64_t innerWindowID =
2081         aLoadingDocument ? aLoadingDocument->InnerWindowID() : 0;
2082     return ValidateRequestWithNewChannel(
2083         request, aURI, aInitialDocumentURI, aReferrerInfo, aLoadGroup,
2084         aObserver, aLoadingDocument, innerWindowID, aLoadFlags, aLoadPolicyType,
2085         aProxyRequest, aTriggeringPrincipal, aCORSMode, aLinkPreload,
2086         aNewChannelCreated);
2087   }
2088 
2089   if (!validateRequest) {
2090     NotifyObserversForCachedImage(aEntry, request, aURI, aReferrerInfo,
2091                                   aLoadingDocument, aTriggeringPrincipal,
2092                                   aCORSMode);
2093   }
2094 
2095   return !validateRequest;
2096 }
2097 
RemoveFromCache(const ImageCacheKey & aKey)2098 bool imgLoader::RemoveFromCache(const ImageCacheKey& aKey) {
2099   LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache", "uri",
2100                              aKey.URI());
2101 
2102   imgCacheTable& cache = GetCache(aKey);
2103   imgCacheQueue& queue = GetCacheQueue(aKey);
2104 
2105   RefPtr<imgCacheEntry> entry;
2106   cache.Remove(aKey, getter_AddRefs(entry));
2107   if (entry) {
2108     MOZ_ASSERT(!entry->Evicted(), "Evicting an already-evicted cache entry!");
2109 
2110     // Entries with no proxies are in the tracker.
2111     if (entry->HasNoProxies()) {
2112       if (mCacheTracker) {
2113         mCacheTracker->RemoveObject(entry);
2114       }
2115       queue.Remove(entry);
2116     }
2117 
2118     entry->SetEvicted(true);
2119 
2120     RefPtr<imgRequest> request = entry->GetRequest();
2121     request->SetIsInCache(false);
2122     AddToUncachedImages(request);
2123 
2124     return true;
2125   }
2126   return false;
2127 }
2128 
RemoveFromCache(imgCacheEntry * entry,QueueState aQueueState)2129 bool imgLoader::RemoveFromCache(imgCacheEntry* entry, QueueState aQueueState) {
2130   LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry");
2131 
2132   RefPtr<imgRequest> request = entry->GetRequest();
2133   if (request) {
2134     const ImageCacheKey& key = request->CacheKey();
2135     imgCacheTable& cache = GetCache(key);
2136     imgCacheQueue& queue = GetCacheQueue(key);
2137 
2138     LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache",
2139                                "entry's uri", key.URI());
2140 
2141     cache.Remove(key);
2142 
2143     if (entry->HasNoProxies()) {
2144       LOG_STATIC_FUNC(gImgLog,
2145                       "imgLoader::RemoveFromCache removing from tracker");
2146       if (mCacheTracker) {
2147         mCacheTracker->RemoveObject(entry);
2148       }
2149       // Only search the queue to remove the entry if its possible it might
2150       // be in the queue.  If we know its not in the queue this would be
2151       // wasted work.
2152       MOZ_ASSERT_IF(aQueueState == QueueState::AlreadyRemoved,
2153                     !queue.Contains(entry));
2154       if (aQueueState == QueueState::MaybeExists) {
2155         queue.Remove(entry);
2156       }
2157     }
2158 
2159     entry->SetEvicted(true);
2160     request->SetIsInCache(false);
2161     AddToUncachedImages(request);
2162 
2163     return true;
2164   }
2165 
2166   return false;
2167 }
2168 
EvictEntries(imgCacheTable & aCacheToClear)2169 nsresult imgLoader::EvictEntries(imgCacheTable& aCacheToClear) {
2170   LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries table");
2171 
2172   // We have to make a temporary, since RemoveFromCache removes the element
2173   // from the queue, invalidating iterators.
2174   const auto entries =
2175       ToTArray<nsTArray<RefPtr<imgCacheEntry>>>(aCacheToClear.Values());
2176   for (const auto& entry : entries) {
2177     if (!RemoveFromCache(entry)) {
2178       return NS_ERROR_FAILURE;
2179     }
2180   }
2181 
2182   MOZ_ASSERT(aCacheToClear.Count() == 0);
2183 
2184   return NS_OK;
2185 }
2186 
EvictEntries(imgCacheQueue & aQueueToClear)2187 nsresult imgLoader::EvictEntries(imgCacheQueue& aQueueToClear) {
2188   LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries queue");
2189 
2190   // We have to make a temporary, since RemoveFromCache removes the element
2191   // from the queue, invalidating iterators.
2192   nsTArray<RefPtr<imgCacheEntry>> entries(aQueueToClear.GetNumElements());
2193   for (auto i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i) {
2194     entries.AppendElement(*i);
2195   }
2196 
2197   // Iterate in reverse order to minimize array copying.
2198   for (auto& entry : entries) {
2199     if (!RemoveFromCache(entry)) {
2200       return NS_ERROR_FAILURE;
2201     }
2202   }
2203 
2204   MOZ_ASSERT(aQueueToClear.GetNumElements() == 0);
2205 
2206   return NS_OK;
2207 }
2208 
AddToUncachedImages(imgRequest * aRequest)2209 void imgLoader::AddToUncachedImages(imgRequest* aRequest) {
2210   MutexAutoLock lock(mUncachedImagesMutex);
2211   mUncachedImages.Insert(aRequest);
2212 }
2213 
RemoveFromUncachedImages(imgRequest * aRequest)2214 void imgLoader::RemoveFromUncachedImages(imgRequest* aRequest) {
2215   MutexAutoLock lock(mUncachedImagesMutex);
2216   mUncachedImages.Remove(aRequest);
2217 }
2218 
2219 #define LOAD_FLAGS_CACHE_MASK \
2220   (nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FROM_CACHE)
2221 
2222 #define LOAD_FLAGS_VALIDATE_MASK                              \
2223   (nsIRequest::VALIDATE_ALWAYS | nsIRequest::VALIDATE_NEVER | \
2224    nsIRequest::VALIDATE_ONCE_PER_SESSION)
2225 
2226 NS_IMETHODIMP
LoadImageXPCOM(nsIURI * aURI,nsIURI * aInitialDocumentURI,nsIReferrerInfo * aReferrerInfo,nsIPrincipal * aTriggeringPrincipal,nsILoadGroup * aLoadGroup,imgINotificationObserver * aObserver,Document * aLoadingDocument,nsLoadFlags aLoadFlags,nsISupports * aCacheKey,nsContentPolicyType aContentPolicyType,imgIRequest ** _retval)2227 imgLoader::LoadImageXPCOM(
2228     nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo,
2229     nsIPrincipal* aTriggeringPrincipal, nsILoadGroup* aLoadGroup,
2230     imgINotificationObserver* aObserver, Document* aLoadingDocument,
2231     nsLoadFlags aLoadFlags, nsISupports* aCacheKey,
2232     nsContentPolicyType aContentPolicyType, imgIRequest** _retval) {
2233   // Optional parameter, so defaults to 0 (== TYPE_INVALID)
2234   if (!aContentPolicyType) {
2235     aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2236   }
2237   imgRequestProxy* proxy;
2238   nsresult rv = LoadImage(
2239       aURI, aInitialDocumentURI, aReferrerInfo, aTriggeringPrincipal, 0,
2240       aLoadGroup, aObserver, aLoadingDocument, aLoadingDocument, aLoadFlags,
2241       aCacheKey, aContentPolicyType, u""_ns,
2242       /* aUseUrgentStartForChannel */ false, /* aListPreload */ false, &proxy);
2243   *_retval = proxy;
2244   return rv;
2245 }
2246 
MakeRequestStaticIfNeeded(Document * aLoadingDocument,imgRequestProxy ** aProxyAboutToGetReturned)2247 static void MakeRequestStaticIfNeeded(
2248     Document* aLoadingDocument, imgRequestProxy** aProxyAboutToGetReturned) {
2249   if (!aLoadingDocument || !aLoadingDocument->IsStaticDocument()) {
2250     return;
2251   }
2252 
2253   if (!*aProxyAboutToGetReturned) {
2254     return;
2255   }
2256 
2257   RefPtr<imgRequestProxy> proxy = dont_AddRef(*aProxyAboutToGetReturned);
2258   *aProxyAboutToGetReturned = nullptr;
2259 
2260   RefPtr<imgRequestProxy> staticProxy =
2261       proxy->GetStaticRequest(aLoadingDocument);
2262   if (staticProxy != proxy) {
2263     proxy->CancelAndForgetObserver(NS_BINDING_ABORTED);
2264     proxy = std::move(staticProxy);
2265   }
2266   proxy.forget(aProxyAboutToGetReturned);
2267 }
2268 
IsImageAvailable(nsIURI * aURI,nsIPrincipal * aTriggeringPrincipal,CORSMode aCORSMode,Document * aDocument)2269 bool imgLoader::IsImageAvailable(nsIURI* aURI,
2270                                  nsIPrincipal* aTriggeringPrincipal,
2271                                  CORSMode aCORSMode, Document* aDocument) {
2272   ImageCacheKey key(aURI, aTriggeringPrincipal->OriginAttributesRef(),
2273                     aDocument);
2274   RefPtr<imgCacheEntry> entry;
2275   imgCacheTable& cache = GetCache(key);
2276   if (!cache.Get(key, getter_AddRefs(entry)) || !entry) {
2277     return false;
2278   }
2279   RefPtr<imgRequest> request = entry->GetRequest();
2280   if (!request) {
2281     return false;
2282   }
2283   return ValidateCORSMode(request, false, aCORSMode, aTriggeringPrincipal);
2284 }
2285 
LoadImage(nsIURI * aURI,nsIURI * aInitialDocumentURI,nsIReferrerInfo * aReferrerInfo,nsIPrincipal * aTriggeringPrincipal,uint64_t aRequestContextID,nsILoadGroup * aLoadGroup,imgINotificationObserver * aObserver,nsINode * aContext,Document * aLoadingDocument,nsLoadFlags aLoadFlags,nsISupports * aCacheKey,nsContentPolicyType aContentPolicyType,const nsAString & initiatorType,bool aUseUrgentStartForChannel,bool aLinkPreload,imgRequestProxy ** _retval)2286 nsresult imgLoader::LoadImage(
2287     nsIURI* aURI, nsIURI* aInitialDocumentURI, nsIReferrerInfo* aReferrerInfo,
2288     nsIPrincipal* aTriggeringPrincipal, uint64_t aRequestContextID,
2289     nsILoadGroup* aLoadGroup, imgINotificationObserver* aObserver,
2290     nsINode* aContext, Document* aLoadingDocument, nsLoadFlags aLoadFlags,
2291     nsISupports* aCacheKey, nsContentPolicyType aContentPolicyType,
2292     const nsAString& initiatorType, bool aUseUrgentStartForChannel,
2293     bool aLinkPreload, imgRequestProxy** _retval) {
2294   VerifyCacheSizes();
2295 
2296   NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
2297 
2298   if (!aURI) {
2299     return NS_ERROR_NULL_POINTER;
2300   }
2301 
2302   auto makeStaticIfNeeded = mozilla::MakeScopeExit(
2303       [&] { MakeRequestStaticIfNeeded(aLoadingDocument, _retval); });
2304 
2305   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("imgLoader::LoadImage", NETWORK,
2306                                         aURI->GetSpecOrDefault());
2307 
2308   LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", aURI);
2309 
2310   *_retval = nullptr;
2311 
2312   RefPtr<imgRequest> request;
2313 
2314   nsresult rv;
2315   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2316 
2317 #ifdef DEBUG
2318   bool isPrivate = false;
2319 
2320   if (aLoadingDocument) {
2321     isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadingDocument);
2322   } else if (aLoadGroup) {
2323     isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadGroup);
2324   }
2325   MOZ_ASSERT(isPrivate == mRespectPrivacy);
2326 
2327   if (aLoadingDocument) {
2328     // The given load group should match that of the document if given. If
2329     // that isn't the case, then we need to add more plumbing to ensure we
2330     // block the document as well.
2331     nsCOMPtr<nsILoadGroup> docLoadGroup =
2332         aLoadingDocument->GetDocumentLoadGroup();
2333     MOZ_ASSERT(docLoadGroup == aLoadGroup);
2334   }
2335 #endif
2336 
2337   // Get the default load flags from the loadgroup (if possible)...
2338   if (aLoadGroup) {
2339     aLoadGroup->GetLoadFlags(&requestFlags);
2340   }
2341   //
2342   // Merge the default load flags with those passed in via aLoadFlags.
2343   // Currently, *only* the caching, validation and background load flags
2344   // are merged...
2345   //
2346   // The flags in aLoadFlags take precedence over the default flags!
2347   //
2348   if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
2349     // Override the default caching flags...
2350     requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
2351                    (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
2352   }
2353   if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
2354     // Override the default validation flags...
2355     requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
2356                    (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
2357   }
2358   if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
2359     // Propagate background loading...
2360     requestFlags |= nsIRequest::LOAD_BACKGROUND;
2361   }
2362   if (aLoadFlags & nsIRequest::LOAD_RECORD_START_REQUEST_DELAY) {
2363     requestFlags |= nsIRequest::LOAD_RECORD_START_REQUEST_DELAY;
2364   }
2365 
2366   if (aLinkPreload) {
2367     // Set background loading if it is <link rel=preload>
2368     requestFlags |= nsIRequest::LOAD_BACKGROUND;
2369   }
2370 
2371   CORSMode corsmode = CORS_NONE;
2372   if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) {
2373     corsmode = CORS_ANONYMOUS;
2374   } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) {
2375     corsmode = CORS_USE_CREDENTIALS;
2376   }
2377 
2378   // Look in the preloaded images of loading document first.
2379   if (StaticPrefs::network_preload() && !aLinkPreload && aLoadingDocument) {
2380     auto key =
2381         PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal, corsmode);
2382     if (RefPtr<PreloaderBase> preload =
2383             aLoadingDocument->Preloads().LookupPreload(key)) {
2384       RefPtr<imgRequestProxy> proxy = do_QueryObject(preload);
2385       MOZ_ASSERT(proxy);
2386 
2387       MOZ_LOG(gImgLog, LogLevel::Debug,
2388               ("[this=%p] imgLoader::LoadImage -- preloaded [proxy=%p]"
2389                " [document=%p]\n",
2390                this, proxy.get(), aLoadingDocument));
2391 
2392       // Removing the preload for this image to be in parity with Chromium.  Any
2393       // following regular image request will be reloaded using the regular
2394       // path: image cache, http cache, network.  Any following `<link
2395       // rel=preload as=image>` will start a new image preload that can be
2396       // satisfied from http cache or network.
2397       //
2398       // There is a spec discussion for "preload cache", see
2399       // https://github.com/w3c/preload/issues/97. And it is also not clear how
2400       // preload image interacts with list of available images, see
2401       // https://github.com/whatwg/html/issues/4474.
2402       proxy->RemoveSelf(aLoadingDocument);
2403       proxy->NotifyUsage();
2404 
2405       imgRequest* request = proxy->GetOwner();
2406       nsresult rv =
2407           CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
2408                                    aObserver, requestFlags, _retval);
2409       NS_ENSURE_SUCCESS(rv, rv);
2410 
2411       imgRequestProxy* newProxy = *_retval;
2412       if (imgCacheValidator* validator = request->GetValidator()) {
2413         newProxy->MarkValidating();
2414         // Attach the proxy without notifying and this will add us to the load
2415         // group.
2416         validator->AddProxy(newProxy);
2417       } else {
2418         // It's OK to add here even if the request is done. If it is, it'll send
2419         // a OnStopRequest()and the proxy will be removed from the loadgroup in
2420         // imgRequestProxy::OnLoadComplete.
2421         newProxy->AddToLoadGroup();
2422         newProxy->NotifyListener();
2423       }
2424 
2425       return NS_OK;
2426     }
2427   }
2428 
2429   RefPtr<imgCacheEntry> entry;
2430 
2431   // Look in the cache for our URI, and then validate it.
2432   // XXX For now ignore aCacheKey. We will need it in the future
2433   // for correctly dealing with image load requests that are a result
2434   // of post data.
2435   OriginAttributes attrs;
2436   if (aTriggeringPrincipal) {
2437     attrs = aTriggeringPrincipal->OriginAttributesRef();
2438   }
2439   ImageCacheKey key(aURI, attrs, aLoadingDocument);
2440   imgCacheTable& cache = GetCache(key);
2441 
2442   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2443     bool newChannelCreated = false;
2444     if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerInfo,
2445                       aLoadGroup, aObserver, aLoadingDocument, requestFlags,
2446                       aContentPolicyType, true, &newChannelCreated, _retval,
2447                       aTriggeringPrincipal, corsmode, aLinkPreload)) {
2448       request = entry->GetRequest();
2449 
2450       // If this entry has no proxies, its request has no reference to the
2451       // entry.
2452       if (entry->HasNoProxies()) {
2453         LOG_FUNC_WITH_PARAM(gImgLog,
2454                             "imgLoader::LoadImage() adding proxyless entry",
2455                             "uri", key.URI());
2456         MOZ_ASSERT(!request->HasCacheEntry(),
2457                    "Proxyless entry's request has cache entry!");
2458         request->SetCacheEntry(entry);
2459 
2460         if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2461           mCacheTracker->MarkUsed(entry);
2462         }
2463       }
2464 
2465       entry->Touch();
2466 
2467       if (!newChannelCreated) {
2468         // This is ugly but it's needed to report CSP violations. We have 3
2469         // scenarios:
2470         // - we don't have cache. We are not in this if() stmt. A new channel is
2471         //   created and that triggers the CSP checks.
2472         // - We have a cache entry and this is blocked by CSP directives.
2473         DebugOnly<bool> shouldLoad = ShouldLoadCachedImage(
2474             request, aLoadingDocument, aTriggeringPrincipal, aContentPolicyType,
2475             /* aSendCSPViolationReports */ true);
2476         MOZ_ASSERT(shouldLoad);
2477       }
2478     } else {
2479       // We can't use this entry. We'll try to load it off the network, and if
2480       // successful, overwrite the old entry in the cache with a new one.
2481       entry = nullptr;
2482     }
2483   }
2484 
2485   // Keep the channel in this scope, so we can adjust its notificationCallbacks
2486   // later when we create the proxy.
2487   nsCOMPtr<nsIChannel> newChannel;
2488   // If we didn't get a cache hit, we need to load from the network.
2489   if (!request) {
2490     LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|");
2491 
2492     bool forcePrincipalCheck;
2493     rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
2494                          aInitialDocumentURI, corsmode, aReferrerInfo,
2495                          aLoadGroup, requestFlags, aContentPolicyType,
2496                          aTriggeringPrincipal, aContext, mRespectPrivacy);
2497     if (NS_FAILED(rv)) {
2498       return NS_ERROR_FAILURE;
2499     }
2500 
2501     MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy);
2502 
2503     NewRequestAndEntry(forcePrincipalCheck, this, key, getter_AddRefs(request),
2504                        getter_AddRefs(entry));
2505 
2506     MOZ_LOG(gImgLog, LogLevel::Debug,
2507             ("[this=%p] imgLoader::LoadImage -- Created new imgRequest"
2508              " [request=%p]\n",
2509              this, request.get()));
2510 
2511     nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
2512     if (cos) {
2513       if (aUseUrgentStartForChannel && !aLinkPreload) {
2514         cos->AddClassFlags(nsIClassOfService::UrgentStart);
2515       }
2516 
2517       if (StaticPrefs::network_http_tailing_enabled() &&
2518           aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
2519         cos->AddClassFlags(nsIClassOfService::Throttleable |
2520                            nsIClassOfService::Tail);
2521         nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
2522         if (httpChannel) {
2523           Unused << httpChannel->SetRequestContextID(aRequestContextID);
2524         }
2525       }
2526     }
2527 
2528     nsCOMPtr<nsILoadGroup> channelLoadGroup;
2529     newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
2530     rv = request->Init(aURI, aURI, /* aHadInsecureRedirect = */ false,
2531                        channelLoadGroup, newChannel, entry, aLoadingDocument,
2532                        aTriggeringPrincipal, corsmode, aReferrerInfo);
2533     if (NS_FAILED(rv)) {
2534       return NS_ERROR_FAILURE;
2535     }
2536 
2537     // Add the initiator type for this image load
2538     nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel);
2539     if (timedChannel) {
2540       timedChannel->SetInitiatorType(initiatorType);
2541     }
2542 
2543     // create the proxy listener
2544     nsCOMPtr<nsIStreamListener> listener = new ProxyListener(request.get());
2545 
2546     MOZ_LOG(gImgLog, LogLevel::Debug,
2547             ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n",
2548              this));
2549 
2550     mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
2551                                  nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
2552                                  aLoadGroup);
2553 
2554     nsresult openRes = newChannel->AsyncOpen(listener);
2555 
2556     if (NS_FAILED(openRes)) {
2557       MOZ_LOG(
2558           gImgLog, LogLevel::Debug,
2559           ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%" PRIx32
2560            "\n",
2561            this, static_cast<uint32_t>(openRes)));
2562       request->CancelAndAbort(openRes);
2563       return openRes;
2564     }
2565 
2566     // Try to add the new request into the cache.
2567     PutIntoCache(key, entry);
2568   } else {
2569     LOG_MSG_WITH_PARAM(gImgLog, "imgLoader::LoadImage |cache hit|", "request",
2570                        request);
2571   }
2572 
2573   // If we didn't get a proxy when validating the cache entry, we need to
2574   // create one.
2575   if (!*_retval) {
2576     // ValidateEntry() has three return values: "Is valid," "might be valid --
2577     // validating over network", and "not valid." If we don't have a _retval,
2578     // we know ValidateEntry is not validating over the network, so it's safe
2579     // to SetLoadId here because we know this request is valid for this context.
2580     //
2581     // Note, however, that this doesn't guarantee the behaviour we want (one
2582     // URL maps to the same image on a page) if we load the same image in a
2583     // different tab (see bug 528003), because its load id will get re-set, and
2584     // that'll cause us to validate over the network.
2585     request->SetLoadId(aLoadingDocument);
2586 
2587     LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request.");
2588     rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
2589                                   aObserver, requestFlags, _retval);
2590     if (NS_FAILED(rv)) {
2591       return rv;
2592     }
2593 
2594     imgRequestProxy* proxy = *_retval;
2595 
2596     // Make sure that OnStatus/OnProgress calls have the right request set, if
2597     // we did create a channel here.
2598     if (newChannel) {
2599       nsCOMPtr<nsIInterfaceRequestor> requestor(
2600           new nsProgressNotificationProxy(newChannel, proxy));
2601       if (!requestor) {
2602         return NS_ERROR_OUT_OF_MEMORY;
2603       }
2604       newChannel->SetNotificationCallbacks(requestor);
2605     }
2606 
2607     if (aLinkPreload) {
2608       MOZ_ASSERT(aLoadingDocument);
2609       proxy->PrioritizeAsPreload();
2610       auto preloadKey =
2611           PreloadHashKey::CreateAsImage(aURI, aTriggeringPrincipal, corsmode);
2612       proxy->NotifyOpen(preloadKey, aLoadingDocument, true);
2613     }
2614 
2615     // Note that it's OK to add here even if the request is done.  If it is,
2616     // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
2617     // the proxy will be removed from the loadgroup.
2618     proxy->AddToLoadGroup();
2619 
2620     // If we're loading off the network, explicitly don't notify our proxy,
2621     // because necko (or things called from necko, such as imgCacheValidator)
2622     // are going to call our notifications asynchronously, and we can't make it
2623     // further asynchronous because observers might rely on imagelib completing
2624     // its work between the channel's OnStartRequest and OnStopRequest.
2625     if (!newChannel) {
2626       proxy->NotifyListener();
2627     }
2628 
2629     return rv;
2630   }
2631 
2632   NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
2633 
2634   return NS_OK;
2635 }
2636 
2637 NS_IMETHODIMP
LoadImageWithChannelXPCOM(nsIChannel * channel,imgINotificationObserver * aObserver,Document * aLoadingDocument,nsIStreamListener ** listener,imgIRequest ** _retval)2638 imgLoader::LoadImageWithChannelXPCOM(nsIChannel* channel,
2639                                      imgINotificationObserver* aObserver,
2640                                      Document* aLoadingDocument,
2641                                      nsIStreamListener** listener,
2642                                      imgIRequest** _retval) {
2643   nsresult result;
2644   imgRequestProxy* proxy;
2645   result = LoadImageWithChannel(channel, aObserver, aLoadingDocument, listener,
2646                                 &proxy);
2647   *_retval = proxy;
2648   return result;
2649 }
2650 
LoadImageWithChannel(nsIChannel * channel,imgINotificationObserver * aObserver,Document * aLoadingDocument,nsIStreamListener ** listener,imgRequestProxy ** _retval)2651 nsresult imgLoader::LoadImageWithChannel(nsIChannel* channel,
2652                                          imgINotificationObserver* aObserver,
2653                                          Document* aLoadingDocument,
2654                                          nsIStreamListener** listener,
2655                                          imgRequestProxy** _retval) {
2656   NS_ASSERTION(channel,
2657                "imgLoader::LoadImageWithChannel -- NULL channel pointer");
2658 
2659   MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
2660 
2661   auto makeStaticIfNeeded = mozilla::MakeScopeExit(
2662       [&] { MakeRequestStaticIfNeeded(aLoadingDocument, _retval); });
2663 
2664   LOG_SCOPE(gImgLog, "imgLoader::LoadImageWithChannel");
2665   RefPtr<imgRequest> request;
2666 
2667   nsCOMPtr<nsIURI> uri;
2668   channel->GetURI(getter_AddRefs(uri));
2669 
2670   NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
2671   nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2672 
2673   OriginAttributes attrs = loadInfo->GetOriginAttributes();
2674 
2675   ImageCacheKey key(uri, attrs, aLoadingDocument);
2676 
2677   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2678   channel->GetLoadFlags(&requestFlags);
2679 
2680   RefPtr<imgCacheEntry> entry;
2681 
2682   if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
2683     RemoveFromCache(key);
2684   } else {
2685     // Look in the cache for our URI, and then validate it.
2686     // XXX For now ignore aCacheKey. We will need it in the future
2687     // for correctly dealing with image load requests that are a result
2688     // of post data.
2689     imgCacheTable& cache = GetCache(key);
2690     if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2691       // We don't want to kick off another network load. So we ask
2692       // ValidateEntry to only do validation without creating a new proxy. If
2693       // it says that the entry isn't valid any more, we'll only use the entry
2694       // we're getting if the channel is loading from the cache anyways.
2695       //
2696       // XXX -- should this be changed? it's pretty much verbatim from the old
2697       // code, but seems nonsensical.
2698       //
2699       // Since aCanMakeNewChannel == false, we don't need to pass content policy
2700       // type/principal/etc
2701 
2702       nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2703       // if there is a loadInfo, use the right contentType, otherwise
2704       // default to the internal image type
2705       nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
2706 
2707       if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver,
2708                         aLoadingDocument, requestFlags, policyType, false,
2709                         nullptr, nullptr, nullptr, CORS_NONE, false)) {
2710         request = entry->GetRequest();
2711       } else {
2712         nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(channel));
2713         bool bUseCacheCopy;
2714 
2715         if (cacheChan) {
2716           cacheChan->IsFromCache(&bUseCacheCopy);
2717         } else {
2718           bUseCacheCopy = false;
2719         }
2720 
2721         if (!bUseCacheCopy) {
2722           entry = nullptr;
2723         } else {
2724           request = entry->GetRequest();
2725         }
2726       }
2727 
2728       if (request && entry) {
2729         // If this entry has no proxies, its request has no reference to
2730         // the entry.
2731         if (entry->HasNoProxies()) {
2732           LOG_FUNC_WITH_PARAM(
2733               gImgLog,
2734               "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri",
2735               key.URI());
2736           MOZ_ASSERT(!request->HasCacheEntry(),
2737                      "Proxyless entry's request has cache entry!");
2738           request->SetCacheEntry(entry);
2739 
2740           if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2741             mCacheTracker->MarkUsed(entry);
2742           }
2743         }
2744       }
2745     }
2746   }
2747 
2748   nsCOMPtr<nsILoadGroup> loadGroup;
2749   channel->GetLoadGroup(getter_AddRefs(loadGroup));
2750 
2751 #ifdef DEBUG
2752   if (aLoadingDocument) {
2753     // The load group of the channel should always match that of the
2754     // document if given. If that isn't the case, then we need to add more
2755     // plumbing to ensure we block the document as well.
2756     nsCOMPtr<nsILoadGroup> docLoadGroup =
2757         aLoadingDocument->GetDocumentLoadGroup();
2758     MOZ_ASSERT(docLoadGroup == loadGroup);
2759   }
2760 #endif
2761 
2762   // Filter out any load flags not from nsIRequest
2763   requestFlags &= nsIRequest::LOAD_REQUESTMASK;
2764 
2765   nsresult rv = NS_OK;
2766   if (request) {
2767     // we have this in our cache already.. cancel the current (document) load
2768 
2769     // this should fire an OnStopRequest
2770     channel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
2771 
2772     *listener = nullptr;  // give them back a null nsIStreamListener
2773 
2774     rv = CreateNewProxyForRequest(request, uri, loadGroup, aLoadingDocument,
2775                                   aObserver, requestFlags, _retval);
2776     static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
2777   } else {
2778     // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2779     nsCOMPtr<nsIURI> originalURI;
2780     channel->GetOriginalURI(getter_AddRefs(originalURI));
2781 
2782     // XXX(seth): We should be able to just use |key| here, except that |key| is
2783     // constructed above with the *current URI* and not the *original URI*. I'm
2784     // pretty sure this is a bug, and it's preventing us from ever getting a
2785     // cache hit in LoadImageWithChannel when redirects are involved.
2786     ImageCacheKey originalURIKey(originalURI, attrs, aLoadingDocument);
2787 
2788     // Default to doing a principal check because we don't know who
2789     // started that load and whether their principal ended up being
2790     // inherited on the channel.
2791     NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true, this,
2792                        originalURIKey, getter_AddRefs(request),
2793                        getter_AddRefs(entry));
2794 
2795     // No principal specified here, because we're not passed one.
2796     // In LoadImageWithChannel, the redirects that may have been
2797     // associated with this load would have gone through necko.
2798     // We only have the final URI in ImageLib and hence don't know
2799     // if the request went through insecure redirects.  But if it did,
2800     // the necko cache should have handled that (since all necko cache hits
2801     // including the redirects will go through content policy).  Hence, we
2802     // can set aHadInsecureRedirect to false here.
2803     rv = request->Init(originalURI, uri, /* aHadInsecureRedirect = */ false,
2804                        channel, channel, entry, aLoadingDocument, nullptr,
2805                        CORS_NONE, nullptr);
2806     NS_ENSURE_SUCCESS(rv, rv);
2807 
2808     RefPtr<ProxyListener> pl =
2809         new ProxyListener(static_cast<nsIStreamListener*>(request.get()));
2810     pl.forget(listener);
2811 
2812     // Try to add the new request into the cache.
2813     PutIntoCache(originalURIKey, entry);
2814 
2815     rv = CreateNewProxyForRequest(request, originalURI, loadGroup,
2816                                   aLoadingDocument, aObserver, requestFlags,
2817                                   _retval);
2818 
2819     // Explicitly don't notify our proxy, because we're loading off the
2820     // network, and necko (or things called from necko, such as
2821     // imgCacheValidator) are going to call our notifications asynchronously,
2822     // and we can't make it further asynchronous because observers might rely
2823     // on imagelib completing its work between the channel's OnStartRequest and
2824     // OnStopRequest.
2825   }
2826 
2827   if (NS_FAILED(rv)) {
2828     return rv;
2829   }
2830 
2831   (*_retval)->AddToLoadGroup();
2832   return rv;
2833 }
2834 
SupportImageWithMimeType(const nsACString & aMimeType,AcceptedMimeTypes aAccept)2835 bool imgLoader::SupportImageWithMimeType(const nsACString& aMimeType,
2836                                          AcceptedMimeTypes aAccept
2837                                          /* = AcceptedMimeTypes::IMAGES */) {
2838   nsAutoCString mimeType(aMimeType);
2839   ToLowerCase(mimeType);
2840 
2841   if (aAccept == AcceptedMimeTypes::IMAGES_AND_DOCUMENTS &&
2842       mimeType.EqualsLiteral("image/svg+xml")) {
2843     return true;
2844   }
2845 
2846   DecoderType type = DecoderFactory::GetDecoderType(mimeType.get());
2847   return type != DecoderType::UNKNOWN;
2848 }
2849 
2850 NS_IMETHODIMP
GetMIMETypeFromContent(nsIRequest * aRequest,const uint8_t * aContents,uint32_t aLength,nsACString & aContentType)2851 imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
2852                                   const uint8_t* aContents, uint32_t aLength,
2853                                   nsACString& aContentType) {
2854   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2855   if (channel) {
2856     nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2857     if (loadInfo->GetSkipContentSniffing()) {
2858       return NS_ERROR_NOT_AVAILABLE;
2859     }
2860   }
2861 
2862   nsresult rv =
2863       GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
2864   if (NS_SUCCEEDED(rv) && channel && XRE_IsParentProcess()) {
2865     if (RefPtr<mozilla::net::nsHttpChannel> httpChannel =
2866             do_QueryObject(channel)) {
2867       // If the image type pattern matching algorithm given bytes does not
2868       // return undefined, then disable the further check and allow the
2869       // response.
2870       httpChannel->DisableIsOpaqueResponseAllowedAfterSniffCheck(
2871           mozilla::net::nsHttpChannel::SnifferType::Image);
2872     }
2873   }
2874 
2875   return rv;
2876 }
2877 
2878 /* static */
GetMimeTypeFromContent(const char * aContents,uint32_t aLength,nsACString & aContentType)2879 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents,
2880                                            uint32_t aLength,
2881                                            nsACString& aContentType) {
2882   nsAutoCString detected;
2883 
2884   /* Is it a GIF? */
2885   if (aLength >= 6 &&
2886       (!strncmp(aContents, "GIF87a", 6) || !strncmp(aContents, "GIF89a", 6))) {
2887     aContentType.AssignLiteral(IMAGE_GIF);
2888 
2889     /* or a PNG? */
2890   } else if (aLength >= 8 && ((unsigned char)aContents[0] == 0x89 &&
2891                               (unsigned char)aContents[1] == 0x50 &&
2892                               (unsigned char)aContents[2] == 0x4E &&
2893                               (unsigned char)aContents[3] == 0x47 &&
2894                               (unsigned char)aContents[4] == 0x0D &&
2895                               (unsigned char)aContents[5] == 0x0A &&
2896                               (unsigned char)aContents[6] == 0x1A &&
2897                               (unsigned char)aContents[7] == 0x0A)) {
2898     aContentType.AssignLiteral(IMAGE_PNG);
2899 
2900     /* maybe a JPEG (JFIF)? */
2901     /* JFIF files start with SOI APP0 but older files can start with SOI DQT
2902      * so we test for SOI followed by any marker, i.e. FF D8 FF
2903      * this will also work for SPIFF JPEG files if they appear in the future.
2904      *
2905      * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
2906      */
2907   } else if (aLength >= 3 && ((unsigned char)aContents[0]) == 0xFF &&
2908              ((unsigned char)aContents[1]) == 0xD8 &&
2909              ((unsigned char)aContents[2]) == 0xFF) {
2910     aContentType.AssignLiteral(IMAGE_JPEG);
2911 
2912     /* or how about ART? */
2913     /* ART begins with JG (4A 47). Major version offset 2.
2914      * Minor version offset 3. Offset 4 must be nullptr.
2915      */
2916   } else if (aLength >= 5 && ((unsigned char)aContents[0]) == 0x4a &&
2917              ((unsigned char)aContents[1]) == 0x47 &&
2918              ((unsigned char)aContents[4]) == 0x00) {
2919     aContentType.AssignLiteral(IMAGE_ART);
2920 
2921   } else if (aLength >= 2 && !strncmp(aContents, "BM", 2)) {
2922     aContentType.AssignLiteral(IMAGE_BMP);
2923 
2924     // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
2925     // CURs begin with 2-byte 0 followed by 2-byte 2.
2926   } else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
2927                               !memcmp(aContents, "\000\000\002\000", 4))) {
2928     aContentType.AssignLiteral(IMAGE_ICO);
2929 
2930     // WebPs always begin with RIFF, a 32-bit length, and WEBP.
2931   } else if (aLength >= 12 && !memcmp(aContents, "RIFF", 4) &&
2932              !memcmp(aContents + 8, "WEBP", 4)) {
2933     aContentType.AssignLiteral(IMAGE_WEBP);
2934 
2935   } else if (MatchesMP4(reinterpret_cast<const uint8_t*>(aContents), aLength,
2936                         detected) &&
2937              detected.Equals(IMAGE_AVIF)) {
2938     aContentType.AssignLiteral(IMAGE_AVIF);
2939   } else if ((aLength >= 2 && !memcmp(aContents, "\xFF\x0A", 2)) ||
2940              (aLength >= 12 &&
2941               !memcmp(aContents, "\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A", 12))) {
2942     // Each version is for containerless and containerful files respectively.
2943     aContentType.AssignLiteral(IMAGE_JXL);
2944   } else {
2945     /* none of the above?  I give up */
2946     return NS_ERROR_NOT_AVAILABLE;
2947   }
2948 
2949   return NS_OK;
2950 }
2951 
2952 /**
2953  * proxy stream listener class used to handle multipart/x-mixed-replace
2954  */
2955 
2956 #include "nsIRequest.h"
2957 #include "nsIStreamConverterService.h"
2958 
NS_IMPL_ISUPPORTS(ProxyListener,nsIStreamListener,nsIThreadRetargetableStreamListener,nsIRequestObserver)2959 NS_IMPL_ISUPPORTS(ProxyListener, nsIStreamListener,
2960                   nsIThreadRetargetableStreamListener, nsIRequestObserver)
2961 
2962 ProxyListener::ProxyListener(nsIStreamListener* dest) : mDestListener(dest) {
2963   /* member initializers and constructor code */
2964 }
2965 
~ProxyListener()2966 ProxyListener::~ProxyListener() { /* destructor code */
2967 }
2968 
2969 /** nsIRequestObserver methods **/
2970 
2971 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)2972 ProxyListener::OnStartRequest(nsIRequest* aRequest) {
2973   if (!mDestListener) {
2974     return NS_ERROR_FAILURE;
2975   }
2976 
2977   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2978   if (channel) {
2979     // We need to set the initiator type for the image load
2980     nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel);
2981     if (timedChannel) {
2982       nsAutoString type;
2983       timedChannel->GetInitiatorType(type);
2984       if (type.IsEmpty()) {
2985         timedChannel->SetInitiatorType(u"img"_ns);
2986       }
2987     }
2988 
2989     nsAutoCString contentType;
2990     nsresult rv = channel->GetContentType(contentType);
2991 
2992     if (!contentType.IsEmpty()) {
2993       /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
2994          in the pipeline to handle the content and pass it along to our
2995          original listener.
2996        */
2997       if ("multipart/x-mixed-replace"_ns.Equals(contentType)) {
2998         nsCOMPtr<nsIStreamConverterService> convServ(
2999             do_GetService("@mozilla.org/streamConverters;1", &rv));
3000         if (NS_SUCCEEDED(rv)) {
3001           nsCOMPtr<nsIStreamListener> toListener(mDestListener);
3002           nsCOMPtr<nsIStreamListener> fromListener;
3003 
3004           rv = convServ->AsyncConvertData("multipart/x-mixed-replace", "*/*",
3005                                           toListener, nullptr,
3006                                           getter_AddRefs(fromListener));
3007           if (NS_SUCCEEDED(rv)) {
3008             mDestListener = fromListener;
3009           }
3010         }
3011       }
3012     }
3013   }
3014 
3015   return mDestListener->OnStartRequest(aRequest);
3016 }
3017 
3018 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult status)3019 ProxyListener::OnStopRequest(nsIRequest* aRequest, nsresult status) {
3020   if (!mDestListener) {
3021     return NS_ERROR_FAILURE;
3022   }
3023 
3024   return mDestListener->OnStopRequest(aRequest, status);
3025 }
3026 
3027 /** nsIStreamListener methods **/
3028 
3029 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)3030 ProxyListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
3031                                uint64_t sourceOffset, uint32_t count) {
3032   if (!mDestListener) {
3033     return NS_ERROR_FAILURE;
3034   }
3035 
3036   return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
3037 }
3038 
3039 /** nsThreadRetargetableStreamListener methods **/
3040 NS_IMETHODIMP
CheckListenerChain()3041 ProxyListener::CheckListenerChain() {
3042   NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
3043   nsresult rv = NS_OK;
3044   nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
3045       do_QueryInterface(mDestListener, &rv);
3046   if (retargetableListener) {
3047     rv = retargetableListener->CheckListenerChain();
3048   }
3049   MOZ_LOG(
3050       gImgLog, LogLevel::Debug,
3051       ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%" PRIx32
3052        "]",
3053        (NS_SUCCEEDED(rv) ? "success" : "failure"), this,
3054        (nsIStreamListener*)mDestListener, static_cast<uint32_t>(rv)));
3055   return rv;
3056 }
3057 
3058 /**
3059  * http validate class.  check a channel for a 304
3060  */
3061 
NS_IMPL_ISUPPORTS(imgCacheValidator,nsIStreamListener,nsIRequestObserver,nsIThreadRetargetableStreamListener,nsIChannelEventSink,nsIInterfaceRequestor,nsIAsyncVerifyRedirectCallback)3062 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
3063                   nsIThreadRetargetableStreamListener, nsIChannelEventSink,
3064                   nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
3065 
3066 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
3067                                      imgLoader* loader, imgRequest* request,
3068                                      Document* aDocument,
3069                                      uint64_t aInnerWindowId,
3070                                      bool forcePrincipalCheckForCacheEntry)
3071     : mProgressProxy(progress),
3072       mRequest(request),
3073       mDocument(aDocument),
3074       mInnerWindowId(aInnerWindowId),
3075       mImgLoader(loader),
3076       mHadInsecureRedirect(false) {
3077   NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader,
3078                      mRequest->CacheKey(), getter_AddRefs(mNewRequest),
3079                      getter_AddRefs(mNewEntry));
3080 }
3081 
~imgCacheValidator()3082 imgCacheValidator::~imgCacheValidator() {
3083   if (mRequest) {
3084     // If something went wrong, and we never unblocked the requests waiting on
3085     // validation, now is our last chance. We will cancel the new request and
3086     // switch the waiting proxies to it.
3087     UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ false);
3088   }
3089 }
3090 
AddProxy(imgRequestProxy * aProxy)3091 void imgCacheValidator::AddProxy(imgRequestProxy* aProxy) {
3092   // aProxy needs to be in the loadgroup since we're validating from
3093   // the network.
3094   aProxy->AddToLoadGroup();
3095 
3096   mProxies.AppendElement(aProxy);
3097 }
3098 
RemoveProxy(imgRequestProxy * aProxy)3099 void imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) {
3100   mProxies.RemoveElement(aProxy);
3101 }
3102 
UpdateProxies(bool aCancelRequest,bool aSyncNotify)3103 void imgCacheValidator::UpdateProxies(bool aCancelRequest, bool aSyncNotify) {
3104   MOZ_ASSERT(mRequest);
3105 
3106   // Clear the validator before updating the proxies. The notifications may
3107   // clone an existing request, and its state could be inconsistent.
3108   mRequest->SetValidator(nullptr);
3109   mRequest = nullptr;
3110 
3111   // If an error occurred, we will want to cancel the new request, and make the
3112   // validating proxies point to it. Any proxies still bound to the original
3113   // request which are not validating should remain untouched.
3114   if (aCancelRequest) {
3115     MOZ_ASSERT(mNewRequest);
3116     mNewRequest->CancelAndAbort(NS_BINDING_ABORTED);
3117   }
3118 
3119   // We have finished validating the request, so we can safely take ownership
3120   // of the proxy list. imgRequestProxy::SyncNotifyListener can mutate the list
3121   // if imgRequestProxy::CancelAndForgetObserver is called by its owner. Note
3122   // that any potential notifications should still be suppressed in
3123   // imgRequestProxy::ChangeOwner because we haven't cleared the validating
3124   // flag yet, and thus they will remain deferred.
3125   AutoTArray<RefPtr<imgRequestProxy>, 4> proxies(std::move(mProxies));
3126 
3127   for (auto& proxy : proxies) {
3128     // First update the state of all proxies before notifying any of them
3129     // to ensure a consistent state (e.g. in case the notification causes
3130     // other proxies to be touched indirectly.)
3131     MOZ_ASSERT(proxy->IsValidating());
3132     MOZ_ASSERT(proxy->NotificationsDeferred(),
3133                "Proxies waiting on cache validation should be "
3134                "deferring notifications!");
3135     if (mNewRequest) {
3136       proxy->ChangeOwner(mNewRequest);
3137     }
3138     proxy->ClearValidating();
3139   }
3140 
3141   mNewRequest = nullptr;
3142   mNewEntry = nullptr;
3143 
3144   for (auto& proxy : proxies) {
3145     if (aSyncNotify) {
3146       // Notify synchronously, because the caller knows we are already in an
3147       // asynchronously-called function (e.g. OnStartRequest).
3148       proxy->SyncNotifyListener();
3149     } else {
3150       // Notify asynchronously, because the caller does not know our current
3151       // call state (e.g. ~imgCacheValidator).
3152       proxy->NotifyListener();
3153     }
3154   }
3155 }
3156 
3157 /** nsIRequestObserver methods **/
3158 
3159 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)3160 imgCacheValidator::OnStartRequest(nsIRequest* aRequest) {
3161   // We may be holding on to a document, so ensure that it's released.
3162   RefPtr<Document> document = mDocument.forget();
3163 
3164   // If for some reason we don't still have an existing request (probably
3165   // because OnStartRequest got delivered more than once), just bail.
3166   if (!mRequest) {
3167     MOZ_ASSERT_UNREACHABLE("OnStartRequest delivered more than once?");
3168     aRequest->Cancel(NS_BINDING_ABORTED);
3169     return NS_ERROR_FAILURE;
3170   }
3171 
3172   // If this request is coming from cache and has the same URI as our
3173   // imgRequest, the request all our proxies are pointing at is valid, and all
3174   // we have to do is tell them to notify their listeners.
3175   nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(aRequest));
3176   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
3177   if (cacheChan && channel) {
3178     bool isFromCache = false;
3179     cacheChan->IsFromCache(&isFromCache);
3180 
3181     nsCOMPtr<nsIURI> channelURI;
3182     channel->GetURI(getter_AddRefs(channelURI));
3183 
3184     nsCOMPtr<nsIURI> finalURI;
3185     mRequest->GetFinalURI(getter_AddRefs(finalURI));
3186 
3187     bool sameURI = false;
3188     if (channelURI && finalURI) {
3189       channelURI->Equals(finalURI, &sameURI);
3190     }
3191 
3192     if (isFromCache && sameURI) {
3193       // We don't need to load this any more.
3194       aRequest->Cancel(NS_BINDING_ABORTED);
3195       mNewRequest = nullptr;
3196 
3197       // Clear the validator before updating the proxies. The notifications may
3198       // clone an existing request, and its state could be inconsistent.
3199       mRequest->SetLoadId(document);
3200       mRequest->SetInnerWindowID(mInnerWindowId);
3201       UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true);
3202       return NS_OK;
3203     }
3204   }
3205 
3206   // We can't load out of cache. We have to create a whole new request for the
3207   // data that's coming in off the channel.
3208   nsCOMPtr<nsIURI> uri;
3209   mRequest->GetURI(getter_AddRefs(uri));
3210 
3211   LOG_MSG_WITH_PARAM(gImgLog,
3212                      "imgCacheValidator::OnStartRequest creating new request",
3213                      "uri", uri);
3214 
3215   CORSMode corsmode = mRequest->GetCORSMode();
3216   nsCOMPtr<nsIReferrerInfo> referrerInfo = mRequest->GetReferrerInfo();
3217   nsCOMPtr<nsIPrincipal> triggeringPrincipal =
3218       mRequest->GetTriggeringPrincipal();
3219 
3220   // Doom the old request's cache entry
3221   mRequest->RemoveFromCache();
3222 
3223   // We use originalURI here to fulfil the imgIRequest contract on GetURI.
3224   nsCOMPtr<nsIURI> originalURI;
3225   channel->GetOriginalURI(getter_AddRefs(originalURI));
3226   nsresult rv = mNewRequest->Init(originalURI, uri, mHadInsecureRedirect,
3227                                   aRequest, channel, mNewEntry, document,
3228                                   triggeringPrincipal, corsmode, referrerInfo);
3229   if (NS_FAILED(rv)) {
3230     UpdateProxies(/* aCancelRequest */ true, /* aSyncNotify */ true);
3231     return rv;
3232   }
3233 
3234   mDestListener = new ProxyListener(mNewRequest);
3235 
3236   // Try to add the new request into the cache. Note that the entry must be in
3237   // the cache before the proxies' ownership changes, because adding a proxy
3238   // changes the caching behaviour for imgRequests.
3239   mImgLoader->PutIntoCache(mNewRequest->CacheKey(), mNewEntry);
3240   UpdateProxies(/* aCancelRequest */ false, /* aSyncNotify */ true);
3241   return mDestListener->OnStartRequest(aRequest);
3242 }
3243 
3244 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult status)3245 imgCacheValidator::OnStopRequest(nsIRequest* aRequest, nsresult status) {
3246   // Be sure we've released the document that we may have been holding on to.
3247   mDocument = nullptr;
3248 
3249   if (!mDestListener) {
3250     return NS_OK;
3251   }
3252 
3253   return mDestListener->OnStopRequest(aRequest, status);
3254 }
3255 
3256 /** nsIStreamListener methods **/
3257 
3258 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)3259 imgCacheValidator::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
3260                                    uint64_t sourceOffset, uint32_t count) {
3261   if (!mDestListener) {
3262     // XXX see bug 113959
3263     uint32_t _retval;
3264     inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
3265     return NS_OK;
3266   }
3267 
3268   return mDestListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
3269 }
3270 
3271 /** nsIThreadRetargetableStreamListener methods **/
3272 
3273 NS_IMETHODIMP
CheckListenerChain()3274 imgCacheValidator::CheckListenerChain() {
3275   NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
3276   nsresult rv = NS_OK;
3277   nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
3278       do_QueryInterface(mDestListener, &rv);
3279   if (retargetableListener) {
3280     rv = retargetableListener->CheckListenerChain();
3281   }
3282   MOZ_LOG(
3283       gImgLog, LogLevel::Debug,
3284       ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %" PRId32 "=%s",
3285        this, static_cast<uint32_t>(rv),
3286        NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
3287   return rv;
3288 }
3289 
3290 /** nsIInterfaceRequestor methods **/
3291 
3292 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)3293 imgCacheValidator::GetInterface(const nsIID& aIID, void** aResult) {
3294   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3295     return QueryInterface(aIID, aResult);
3296   }
3297 
3298   return mProgressProxy->GetInterface(aIID, aResult);
3299 }
3300 
3301 // These functions are materially the same as the same functions in imgRequest.
3302 // We duplicate them because we're verifying whether cache loads are necessary,
3303 // not unconditionally loading.
3304 
3305 /** nsIChannelEventSink methods **/
3306 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * oldChannel,nsIChannel * newChannel,uint32_t flags,nsIAsyncVerifyRedirectCallback * callback)3307 imgCacheValidator::AsyncOnChannelRedirect(
3308     nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
3309     nsIAsyncVerifyRedirectCallback* callback) {
3310   // Note all cache information we get from the old channel.
3311   mNewRequest->SetCacheValidation(mNewEntry, oldChannel);
3312 
3313   // If the previous URI is a non-HTTPS URI, record that fact for later use by
3314   // security code, which needs to know whether there is an insecure load at any
3315   // point in the redirect chain.
3316   nsCOMPtr<nsIURI> oldURI;
3317   bool schemeLocal = false;
3318   if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) ||
3319       NS_FAILED(NS_URIChainHasFlags(
3320           oldURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
3321       (!oldURI->SchemeIs("https") && !oldURI->SchemeIs("chrome") &&
3322        !schemeLocal)) {
3323     mHadInsecureRedirect = true;
3324   }
3325 
3326   // Prepare for callback
3327   mRedirectCallback = callback;
3328   mRedirectChannel = newChannel;
3329 
3330   return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
3331                                                 this);
3332 }
3333 
3334 NS_IMETHODIMP
OnRedirectVerifyCallback(nsresult aResult)3335 imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) {
3336   // If we've already been told to abort, just do so.
3337   if (NS_FAILED(aResult)) {
3338     mRedirectCallback->OnRedirectVerifyCallback(aResult);
3339     mRedirectCallback = nullptr;
3340     mRedirectChannel = nullptr;
3341     return NS_OK;
3342   }
3343 
3344   // make sure we have a protocol that returns data rather than opens
3345   // an external application, e.g. mailto:
3346   nsCOMPtr<nsIURI> uri;
3347   mRedirectChannel->GetURI(getter_AddRefs(uri));
3348   bool doesNotReturnData = false;
3349   NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
3350                       &doesNotReturnData);
3351 
3352   nsresult result = NS_OK;
3353 
3354   if (doesNotReturnData) {
3355     result = NS_ERROR_ABORT;
3356   }
3357 
3358   mRedirectCallback->OnRedirectVerifyCallback(result);
3359   mRedirectCallback = nullptr;
3360   mRedirectChannel = nullptr;
3361   return NS_OK;
3362 }
3363