tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

imgLoader.cpp (122911B)


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