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