Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/image/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 119 kB image not shown  

Quelle  imgLoader.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


// Undefine windows version of LoadImage because our code uses that name.
#include "mozilla/ScopeExit.h"
#include "nsIChildChannel.h"
#include "nsIThreadRetargetableStreamListener.h"
#undef LoadImage

#include "imgLoader.h"

#include <algorithm>
#include <utility>

#include "DecoderFactory.h"
#include "Image.h"
#include "ImageLogging.h"
#include "ReferrerInfo.h"
#include "imgRequestProxy.h"
#include "mozilla/Attributes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/StaticPrefs_image.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/CacheExpirationTime.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FetchPriority.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/image/ImageMemoryReporter.h"
#include "mozilla/layers/CompositorManagerChild.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsComponentManagerUtils.h"
#include "nsContentPolicyUtils.h"
#include "nsContentSecurityManager.h"
#include "nsContentUtils.h"
#include "nsHttpChannel.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsICacheInfoChannel.h"
#include "nsIChannelEventSink.h"
#include "nsIClassOfService.h"
#include "nsIEffectiveTLDService.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIHttpChannel.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIMemoryReporter.h"
#include "nsINetworkPredictor.h"
#include "nsIProgressEventSink.h"
#include "nsIProtocolHandler.h"
#include "nsImageModule.h"
#include "nsMediaSniffer.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsQueryObject.h"
#include "nsReadableUtils.h"
#include "nsStreamUtils.h"
#include "prtime.h"

// we want to explore making the document own the load group
// so we can associate the document URI with the load group.
// until this point, we have an evil hack:
#include "nsIHttpChannelInternal.h"
#include "nsILoadGroupChild.h"
#include "nsIDocShell.h"

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::image;
using namespace mozilla::net;

MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)

class imgMemoryReporter final : public nsIMemoryReporter {
  ~imgMemoryReporter() = default;

 public:
  NS_DECL_ISUPPORTS

  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                            nsISupports* aData, bool aAnonymize) override {
    MOZ_ASSERT(NS_IsMainThread());

    layers::CompositorManagerChild* manager =
        mozilla::layers::CompositorManagerChild::GetInstance();
    if (!manager || !StaticPrefs::image_mem_debug_reporting()) {
      layers::SharedSurfacesMemoryReport sharedSurfaces;
      FinishCollectReports(aHandleReport, aData, aAnonymize, sharedSurfaces);
      return NS_OK;
    }

    RefPtr<imgMemoryReporter> self(this);
    nsCOMPtr<nsIHandleReportCallback> handleReport(aHandleReport);
    nsCOMPtr<nsISupports> data(aData);
    manager->SendReportSharedSurfacesMemory(
        [=](layers::SharedSurfacesMemoryReport aReport) {
          self->FinishCollectReports(handleReport, data, aAnonymize, aReport);
        },
        [=](mozilla::ipc::ResponseRejectReason&& aReason) {
          layers::SharedSurfacesMemoryReport sharedSurfaces;
          self->FinishCollectReports(handleReport, data, aAnonymize,
                                     sharedSurfaces);
        });
    return NS_OK;
  }

  void FinishCollectReports(
      nsIHandleReportCallback* aHandleReport, nsISupports* aData,
      bool aAnonymize, layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
    nsTArray<ImageMemoryCounter> chrome;
    nsTArray<ImageMemoryCounter> content;
    nsTArray<ImageMemoryCounter> uncached;

    for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
      for (imgCacheEntry* entry : mKnownLoaders[i]->mCache.Values()) {
        RefPtr<imgRequest> req = entry->GetRequest();
        RecordCounterForRequest(req, &content, !entry->HasNoProxies());
      }
      MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex);
      for (RefPtr<imgRequest> req : mKnownLoaders[i]->mUncachedImages) {
        RecordCounterForRequest(req, &uncached, req->HasConsumers());
      }
    }

    // Note that we only need to anonymize content image URIs.

    ReportCounterArray(aHandleReport, aData, chrome, "images/chrome",
                       /* aAnonymize */ false, aSharedSurfaces);

    ReportCounterArray(aHandleReport, aData, content, "images/content",
                       aAnonymize, aSharedSurfaces);

    // Uncached images may be content or chrome, so anonymize them.
    ReportCounterArray(aHandleReport, aData, uncached, "images/uncached",
                       aAnonymize, aSharedSurfaces);

    // Report any shared surfaces that were not merged with the surface cache.
    ImageMemoryReporter::ReportSharedSurfaces(aHandleReport, aData,
                                              aSharedSurfaces);

    nsCOMPtr<nsIMemoryReporterManager> imgr =
        do_GetService("@mozilla.org/memory-reporter-manager;1");
    if (imgr) {
      imgr->EndReport();
    }
  }

  static int64_t ImagesContentUsedUncompressedDistinguishedAmount() {
    size_t n = 0;
    for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length();
         i++) {
      for (imgCacheEntry* entry :
           imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Values()) {
        if (entry->HasNoProxies()) {
          continue;
        }

        RefPtr<imgRequest> req = entry->GetRequest();
        RefPtr<image::Image> image = req->GetImage();
        if (!image) {
          continue;
        }

        // Both this and EntryImageSizes measure
        // images/content/raster/used/decoded memory.  This function's
        // measurement is secondary -- the result doesn't go in the "explicit"
        // tree -- so we use moz_malloc_size_of instead of ImagesMallocSizeOf to
        // prevent DMD from seeing it reported twice.
        SizeOfState state(moz_malloc_size_of);
        ImageMemoryCounter counter(req, image, state, /* aIsUsed = */ true);

        n += counter.Values().DecodedHeap();
        n += counter.Values().DecodedNonHeap();
        n += counter.Values().DecodedUnknown();
      }
    }
    return n;
  }

  void RegisterLoader(imgLoader* aLoader) {
    mKnownLoaders.AppendElement(aLoader);
  }

  void UnregisterLoader(imgLoader* aLoader) {
    mKnownLoaders.RemoveElement(aLoader);
  }

 private:
  nsTArray<imgLoader*> mKnownLoaders;

  struct MemoryTotal {
    MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter) {
      if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
        if (aImageCounter.IsUsed()) {
          mUsedRasterCounter += aImageCounter.Values();
        } else {
          mUnusedRasterCounter += aImageCounter.Values();
        }
      } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) {
        if (aImageCounter.IsUsed()) {
          mUsedVectorCounter += aImageCounter.Values();
        } else {
          mUnusedVectorCounter += aImageCounter.Values();
        }
      } else if (aImageCounter.Type() == imgIContainer::TYPE_REQUEST) {
        // Nothing to do, we did not get to the point of having an image.
      } else {
        MOZ_CRASH("Unexpected image type");
      }

      return *this;
    }

    const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; }
    const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; }
    const MemoryCounter& UsedVector() const { return mUsedVectorCounter; }
    const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; }

   private:
    MemoryCounter mUsedRasterCounter;
    MemoryCounter mUnusedRasterCounter;
    MemoryCounter mUsedVectorCounter;
    MemoryCounter mUnusedVectorCounter;
  };

  // Reports all images of a single kind, e.g. all used chrome images.
  void ReportCounterArray(nsIHandleReportCallback* aHandleReport,
                          nsISupports* aData,
                          nsTArray<ImageMemoryCounter>& aCounterArray,
                          const char* aPathPrefix, bool aAnonymize,
                          layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
    MemoryTotal summaryTotal;
    MemoryTotal nonNotableTotal;

    // Report notable images, and compute total and non-notable aggregate sizes.
    for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
      ImageMemoryCounter& counter = aCounterArray[i];

      if (aAnonymize) {
        counter.URI().Truncate();
        counter.URI().AppendPrintf("", i);
      } else {
        // The URI could be an extremely long data: URI. Truncate if needed.
        static const size_t max = 256;
        if (counter.URI().Length() > max) {
          counter.URI().Truncate(max);
          counter.URI().AppendLiteral(" (truncated)");
        }
        counter.URI().ReplaceChar('/''\\');
      }

      summaryTotal += counter;

      if (counter.IsNotable() || StaticPrefs::image_mem_debug_reporting()) {
        ReportImage(aHandleReport, aData, aPathPrefix, counter,
                    aSharedSurfaces);
      } else {
        ImageMemoryReporter::TrimSharedSurfaces(counter, aSharedSurfaces);
        nonNotableTotal += counter;
      }
    }

    // Report non-notable images in aggregate.
    ReportTotal(aHandleReport, aData, /* aExplicit = */ true, aPathPrefix,
                "/", nonNotableTotal);

    // Report a summary in aggregate, outside of the explicit tree.
    ReportTotal(aHandleReport, aData, /* aExplicit = */ false, aPathPrefix, "",
                summaryTotal);
  }

  static void ReportImage(nsIHandleReportCallback* aHandleReport,
                          nsISupports* aData, const char* aPathPrefix,
                          const ImageMemoryCounter& aCounter,
                          layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
    nsAutoCString pathPrefix("explicit/"_ns);
    pathPrefix.Append(aPathPrefix);

    switch (aCounter.Type()) {
      case imgIContainer::TYPE_RASTER:
        pathPrefix.AppendLiteral("/raster/");
        break;
      case imgIContainer::TYPE_VECTOR:
        pathPrefix.AppendLiteral("/vector/");
        break;
      case imgIContainer::TYPE_REQUEST:
        pathPrefix.AppendLiteral("/request/");
        break;
      default:
        pathPrefix.AppendLiteral("/unknown=");
        pathPrefix.AppendInt(aCounter.Type());
        pathPrefix.AppendLiteral("/");
        break;
    }

    pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
    if (aCounter.IsValidating()) {
      pathPrefix.AppendLiteral("validating/");
    }
    if (aCounter.HasError()) {
      pathPrefix.AppendLiteral("err/");
    }

    pathPrefix.AppendLiteral("progress=");
    pathPrefix.AppendInt(aCounter.Progress(), 16);
    pathPrefix.AppendLiteral("/");

    pathPrefix.AppendLiteral("image(");
    pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
    pathPrefix.AppendLiteral("x");
    pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
    pathPrefix.AppendLiteral(", ");

    if (aCounter.URI().IsEmpty()) {
      pathPrefix.AppendLiteral("");
    } else {
      pathPrefix.Append(aCounter.URI());
    }

    pathPrefix.AppendLiteral(")/");

    ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter, aSharedSurfaces);

    ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
  }

  static void ReportSurfaces(
      nsIHandleReportCallback* aHandleReport, nsISupports* aData,
      const nsACString& aPathPrefix, const ImageMemoryCounter& aCounter,
      layers::SharedSurfacesMemoryReport& aSharedSurfaces) {
    for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
      nsAutoCString surfacePathPrefix(aPathPrefix);
      switch (counter.Type()) {
        case SurfaceMemoryCounterType::NORMAL:
          if (counter.IsLocked()) {
            surfacePathPrefix.AppendLiteral("locked/");
          } else {
            surfacePathPrefix.AppendLiteral("unlocked/");
          }
          if (counter.IsFactor2()) {
            surfacePathPrefix.AppendLiteral("factor2/");
          }
          if (counter.CannotSubstitute()) {
            surfacePathPrefix.AppendLiteral("cannot_substitute/");
          }
          break;
        case SurfaceMemoryCounterType::CONTAINER:
          surfacePathPrefix.AppendLiteral("container/");
          break;
        default:
          MOZ_ASSERT_UNREACHABLE("Unknown counter type");
          break;
      }

      surfacePathPrefix.AppendLiteral("types=");
      surfacePathPrefix.AppendInt(counter.Values().SurfaceTypes(), 16);
      surfacePathPrefix.AppendLiteral("/surface(");
      surfacePathPrefix.AppendInt(counter.Key().Size().width);
      surfacePathPrefix.AppendLiteral("x");
      surfacePathPrefix.AppendInt(counter.Key().Size().height);

      if (!counter.IsFinished()) {
        surfacePathPrefix.AppendLiteral(", incomplete");
      }

      if (counter.Values().ExternalHandles() > 0) {
        surfacePathPrefix.AppendLiteral(", handles:");
        surfacePathPrefix.AppendInt(
            uint32_t(counter.Values().ExternalHandles()));
      }

      ImageMemoryReporter::AppendSharedSurfacePrefix(surfacePathPrefix, counter,
                                                     aSharedSurfaces);

      PlaybackType playback = counter.Key().Playback();
      if (playback == PlaybackType::eAnimated) {
        if (StaticPrefs::image_mem_debug_reporting()) {
          surfacePathPrefix.AppendPrintf(
              " (animation %4u)", uint32_t(counter.Values().FrameIndex()));
        } else {
          surfacePathPrefix.AppendLiteral(" (animation)");
        }
      }

      if (counter.Key().Flags() != DefaultSurfaceFlags()) {
        surfacePathPrefix.AppendLiteral(", flags:");
        surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
                                    /* aRadix = */ 16);
      }

      if (counter.Key().Region()) {
        const ImageIntRegion& region = counter.Key().Region().ref();
        const gfx::IntRect& rect = region.Rect();
        surfacePathPrefix.AppendLiteral(", region:[ rect=(");
        surfacePathPrefix.AppendInt(rect.x);
        surfacePathPrefix.AppendLiteral(",");
        surfacePathPrefix.AppendInt(rect.y);
        surfacePathPrefix.AppendLiteral(") ");
        surfacePathPrefix.AppendInt(rect.width);
        surfacePathPrefix.AppendLiteral("x");
        surfacePathPrefix.AppendInt(rect.height);
        if (region.IsRestricted()) {
          const gfx::IntRect& restrict = region.Restriction();
          if (restrict == rect) {
            surfacePathPrefix.AppendLiteral(", restrict=rect");
          } else {
            surfacePathPrefix.AppendLiteral(", restrict=(");
            surfacePathPrefix.AppendInt(restrict.x);
            surfacePathPrefix.AppendLiteral(",");
            surfacePathPrefix.AppendInt(restrict.y);
            surfacePathPrefix.AppendLiteral(") ");
            surfacePathPrefix.AppendInt(restrict.width);
            surfacePathPrefix.AppendLiteral("x");
            surfacePathPrefix.AppendInt(restrict.height);
          }
        }
        if (region.GetExtendMode() != gfx::ExtendMode::CLAMP) {
          surfacePathPrefix.AppendLiteral(", extendMode=");
          surfacePathPrefix.AppendInt(int32_t(region.GetExtendMode()));
        }
        surfacePathPrefix.AppendLiteral("]");
      }

      const SVGImageContext& context = counter.Key().SVGContext();
      surfacePathPrefix.AppendLiteral(", svgContext:[ ");
      if (context.GetViewportSize()) {
        const CSSIntSize& size = context.GetViewportSize().ref();
        surfacePathPrefix.AppendLiteral("viewport=(");
        surfacePathPrefix.AppendInt(size.width);
        surfacePathPrefix.AppendLiteral("x");
        surfacePathPrefix.AppendInt(size.height);
        surfacePathPrefix.AppendLiteral(") ");
      }
      if (context.GetPreserveAspectRatio()) {
        nsAutoString aspect;
        context.GetPreserveAspectRatio()->ToString(aspect);
        surfacePathPrefix.AppendLiteral("preserveAspectRatio=(");
        LossyAppendUTF16toASCII(aspect, surfacePathPrefix);
        surfacePathPrefix.AppendLiteral(") ");
      }
      if (auto scheme = context.GetColorScheme()) {
        surfacePathPrefix.AppendLiteral("colorScheme=");
        surfacePathPrefix.AppendInt(int32_t(*scheme));
        surfacePathPrefix.AppendLiteral(" ");
      }
      if (context.GetContextPaint()) {
        const SVGEmbeddingContextPaint* paint = context.GetContextPaint();
        surfacePathPrefix.AppendLiteral("contextPaint=(");
        if (paint->GetFill()) {
          surfacePathPrefix.AppendLiteral(" fill=");
          surfacePathPrefix.AppendInt(paint->GetFill()->ToABGR(), 16);
        }
        if (paint->GetFillOpacity() != 1.0) {
          surfacePathPrefix.AppendLiteral(" fillOpa=");
          surfacePathPrefix.AppendFloat(paint->GetFillOpacity());
        }
        if (paint->GetStroke()) {
          surfacePathPrefix.AppendLiteral(" stroke=");
          surfacePathPrefix.AppendInt(paint->GetStroke()->ToABGR(), 16);
        }
        if (paint->GetStrokeOpacity() != 1.0) {
          surfacePathPrefix.AppendLiteral(" strokeOpa=");
          surfacePathPrefix.AppendFloat(paint->GetStrokeOpacity());
        }
        surfacePathPrefix.AppendLiteral(" ) ");
      }
      surfacePathPrefix.AppendLiteral("]");

      surfacePathPrefix.AppendLiteral(")/");

      ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values());
    }
  }

  static void ReportTotal(nsIHandleReportCallback* aHandleReport,
                          nsISupports* aData, bool aExplicit,
                          const char* aPathPrefix, const char* aPathInfix,
                          const MemoryTotal& aTotal) {
    nsAutoCString pathPrefix;
    if (aExplicit) {
      pathPrefix.AppendLiteral("explicit/");
    }
    pathPrefix.Append(aPathPrefix);

    nsAutoCString rasterUsedPrefix(pathPrefix);
    rasterUsedPrefix.AppendLiteral("/raster/used/");
    rasterUsedPrefix.Append(aPathInfix);
    ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster());

    nsAutoCString rasterUnusedPrefix(pathPrefix);
    rasterUnusedPrefix.AppendLiteral("/raster/unused/");
    rasterUnusedPrefix.Append(aPathInfix);
    ReportValues(aHandleReport, aData, rasterUnusedPrefix,
                 aTotal.UnusedRaster());

    nsAutoCString vectorUsedPrefix(pathPrefix);
    vectorUsedPrefix.AppendLiteral("/vector/used/");
    vectorUsedPrefix.Append(aPathInfix);
    ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector());

    nsAutoCString vectorUnusedPrefix(pathPrefix);
    vectorUnusedPrefix.AppendLiteral("/vector/unused/");
    vectorUnusedPrefix.Append(aPathInfix);
    ReportValues(aHandleReport, aData, vectorUnusedPrefix,
                 aTotal.UnusedVector());
  }

  static void ReportValues(nsIHandleReportCallback* aHandleReport,
                           nsISupports* aData, const nsACString& aPathPrefix,
                           const MemoryCounter& aCounter) {
    ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);

    ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "decoded-heap",
                "Decoded image data which is stored on the heap.",
                aCounter.DecodedHeap());

    ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
                "decoded-nonheap",
                "Decoded image data which isn't stored on the heap.",
                aCounter.DecodedNonHeap());

    // We don't know for certain whether or not it is on the heap, so let's
    // just report it as non-heap for reporting purposes.
    ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
                "decoded-unknown",
                "Decoded image data which is unknown to be on the heap or not.",
                aCounter.DecodedUnknown());
  }

  static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
                                nsISupports* aData,
                                const nsACString& aPathPrefix,
                                const MemoryCounter& aCounter) {
    ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix, "source",
                "Raster image source data and vector image documents.",
                aCounter.Source());
  }

  static void ReportValue(nsIHandleReportCallback* aHandleReport,
                          nsISupports* aData, int32_t aKind,
                          const nsACString& aPathPrefix,
                          const char* aPathSuffix, const char* aDescription,
                          size_t aValue) {
    if (aValue == 0) {
      return;
    }

    nsAutoCString desc(aDescription);
    nsAutoCString path(aPathPrefix);
    path.Append(aPathSuffix);

    aHandleReport->Callback(""_ns, path, aKind, UNITS_BYTES, aValue, desc,
                            aData);
  }

  static void RecordCounterForRequest(imgRequest* aRequest,
                                      nsTArray<ImageMemoryCounter>* aArray,
                                      bool aIsUsed) {
    SizeOfState state(ImagesMallocSizeOf);
    RefPtr<image::Image> image = aRequest->GetImage();
    if (image) {
      ImageMemoryCounter counter(aRequest, image, state, aIsUsed);
      aArray->AppendElement(std::move(counter));
    } else {
      // We can at least record some information about the image from the
      // request, and mark it as not knowing the image type yet.
      ImageMemoryCounter counter(aRequest, state, aIsUsed);
      aArray->AppendElement(std::move(counter));
    }
  }
};

NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)

NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, nsIProgressEventSink,
                  nsIChannelEventSink, nsIInterfaceRequestor)

NS_IMETHODIMP
nsProgressNotificationProxy::OnProgress(nsIRequest* request, int64_t progress,
                                        int64_t progressMax) {
  nsCOMPtr<nsILoadGroup> loadGroup;
  request->GetLoadGroup(getter_AddRefs(loadGroup));

  nsCOMPtr<nsIProgressEventSink> target;
  NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
                                NS_GET_IID(nsIProgressEventSink),
                                getter_AddRefs(target));
  if (!target) {
    return NS_OK;
  }
  return target->OnProgress(mImageRequest, progress, progressMax);
}

NS_IMETHODIMP
nsProgressNotificationProxy::OnStatus(nsIRequest* request, nsresult status,
                                      const char16_t* statusArg) {
  nsCOMPtr<nsILoadGroup> loadGroup;
  request->GetLoadGroup(getter_AddRefs(loadGroup));

  nsCOMPtr<nsIProgressEventSink> target;
  NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
                                NS_GET_IID(nsIProgressEventSink),
                                getter_AddRefs(target));
  if (!target) {
    return NS_OK;
  }
  return target->OnStatus(mImageRequest, status, statusArg);
}

NS_IMETHODIMP
nsProgressNotificationProxy::AsyncOnChannelRedirect(
    nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
    nsIAsyncVerifyRedirectCallback* cb) {
  // Tell the original original callbacks about it too
  nsCOMPtr<nsILoadGroup> loadGroup;
  newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
  nsCOMPtr<nsIChannelEventSink> target;
  NS_QueryNotificationCallbacks(mOriginalCallbacks, loadGroup,
                                NS_GET_IID(nsIChannelEventSink),
                                getter_AddRefs(target));
  if (!target) {
    cb->OnRedirectVerifyCallback(NS_OK);
    return NS_OK;
  }

  // Delegate to |target| if set, reusing |cb|
  return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
}

NS_IMETHODIMP
nsProgressNotificationProxy::GetInterface(const nsIID& iid, void** result) {
  if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
    *result = static_cast<nsIProgressEventSink*>(this);
    NS_ADDREF_THIS();
    return NS_OK;
  }
  if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
    *result = static_cast<nsIChannelEventSink*>(this);
    NS_ADDREF_THIS();
    return NS_OK;
  }
  if (mOriginalCallbacks) {
    return mOriginalCallbacks->GetInterface(iid, result);
  }
  return NS_NOINTERFACE;
}

static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,
                               imgLoader* aLoader, const ImageCacheKey& aKey,
                               imgRequest** aRequest, imgCacheEntry** aEntry) {
  RefPtr<imgRequest> request = new imgRequest(aLoader, aKey);
  RefPtr<imgCacheEntry> entry =
      new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
  aLoader->AddToUncachedImages(request);
  request.forget(aRequest);
  entry.forget(aEntry);
}

static bool ShouldRevalidateEntry(imgCacheEntry* aEntry, nsLoadFlags aFlags,
                                  bool aHasExpired) {
  if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) {
    return false;
  }
  if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
    return true;
  }
  if (aEntry->GetMustValidate()) {
    return true;
  }
  if (aHasExpired) {
    // The cache entry has expired...  Determine whether the stale cache
    // entry can be used without validation...
    if (aFlags & (nsIRequest::LOAD_FROM_CACHE | nsIRequest::VALIDATE_NEVER |
                  nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
      // LOAD_FROM_CACHE, VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow
      // stale cache entries to be used unless they have been explicitly marked
      // to indicate that revalidation is necessary.
      return false;
    }
    // Entry is expired, revalidate.
    return true;
  }
  return false;
}

/* Call content policies on cached images that went through a redirect */
static bool ShouldLoadCachedImage(imgRequest* aImgRequest,
                                  Document* aLoadingDocument,
                                  nsIPrincipal* aTriggeringPrincipal,
                                  nsContentPolicyType aPolicyType,
                                  bool aSendCSPViolationReports) {
  /* Call content policies on cached images - Bug 1082837
   * Cached images are keyed off of the first uri in a redirect chain.
   * Hence content policies don't get a chance to test the intermediate hops
   * or the final destination.  Here we test the final destination using
   * mFinalURI off of the imgRequest and passing it into content policies.
   * For Mixed Content Blocker, we do an additional check to determine if any
   * of the intermediary hops went through an insecure redirect with the
   * mHadInsecureRedirect flag
   */

  bool insecureRedirect = aImgRequest->HadInsecureRedirect();
  nsCOMPtr<nsIURI> contentLocation;
  aImgRequest->GetFinalURI(getter_AddRefs(contentLocation));
  nsresult rv;

  nsCOMPtr<nsIPrincipal> loadingPrincipal =
      aLoadingDocument ? aLoadingDocument->NodePrincipal()
                       : aTriggeringPrincipal;
  // If there is no context and also no triggeringPrincipal, then we use a fresh
  // nullPrincipal as the loadingPrincipal because we can not create a loadinfo
  // without a valid loadingPrincipal.
  if (!loadingPrincipal) {
    loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
  }

  nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
      loadingPrincipal, aTriggeringPrincipal, aLoadingDocument,
      nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, aPolicyType);

  secCheckLoadInfo->SetSendCSPViolationEvents(aSendCSPViolationReports);

  int16_t decision = nsIContentPolicy::REJECT_REQUEST;
  rv = NS_CheckContentLoadPolicy(contentLocation, secCheckLoadInfo, &decision,
                                 nsContentUtils::GetContentPolicy());
  if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
    return false;
  }

  // We call all Content Policies above, but we also have to call mcb
  // individually to check the intermediary redirect hops are secure.
  if (insecureRedirect) {
    // Bug 1314356: If the image ended up in the cache upgraded by HSTS and the
    // page uses upgrade-inscure-requests it had an insecure redirect
    // (http->https). We need to invalidate the image and reload it because
    // mixed content blocker only bails if upgrade-insecure-requests is set on
    // the doc and the resource load is http: which would result in an incorrect
    // mixed content warning.
    nsCOMPtr<nsIDocShell> docShell =
        NS_CP_GetDocShellFromContext(ToSupports(aLoadingDocument));
    if (docShell) {
      Document* document = docShell->GetDocument();
      if (document && document->GetUpgradeInsecureRequests(false)) {
        return false;
      }
    }

    if (!aTriggeringPrincipal || !aTriggeringPrincipal->IsSystemPrincipal()) {
      // reset the decision for mixed content blocker check
      decision = nsIContentPolicy::REJECT_REQUEST;
      rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect, contentLocation,
                                             secCheckLoadInfo,
                                             true,  // aReportError
                                             &decision);
      if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
        return false;
      }
    }
  }

  return true;
}

// Returns true if this request is compatible with the given CORS mode on the
// given loading principal, and false if the request may not be reused due
// to CORS.
static bool ValidateCORSMode(imgRequest* aRequest, bool aForcePrincipalCheck,
                             CORSMode aCORSMode,
                             nsIPrincipal* aTriggeringPrincipal) {
  // If the entry's CORS mode doesn't match, or the CORS mode matches but the
  // document principal isn't the same, we can't use this request.
  if (aRequest->GetCORSMode() != aCORSMode) {
    return false;
  }

  if (aRequest->GetCORSMode() != CORS_NONE || aForcePrincipalCheck) {
    nsCOMPtr<nsIPrincipal> otherprincipal = aRequest->GetTriggeringPrincipal();

    // If we previously had a principal, but we don't now, we can't use this
    // request.
    if (otherprincipal && !aTriggeringPrincipal) {
      return false;
    }

    if (otherprincipal && aTriggeringPrincipal &&
        !otherprincipal->Equals(aTriggeringPrincipal)) {
      return false;
    }
  }

  return true;
}

static bool ValidateSecurityInfo(imgRequest* aRequest,
                                 bool aForcePrincipalCheck, CORSMode aCORSMode,
                                 nsIPrincipal* aTriggeringPrincipal,
                                 Document* aLoadingDocument,
                                 nsContentPolicyType aPolicyType) {
  if (!ValidateCORSMode(aRequest, aForcePrincipalCheck, aCORSMode,
                        aTriggeringPrincipal)) {
    return false;
  }
  // Content Policy Check on Cached Images
  return ShouldLoadCachedImage(aRequest, aLoadingDocument, aTriggeringPrincipal,
                               aPolicyType,
                               /* aSendCSPViolationReports */ false);
}

static void AdjustPriorityForImages(nsIChannel* aChannel,
                                    nsLoadFlags aLoadFlags,
                                    FetchPriority aFetchPriority) {
  // Image channels are loaded by default with reduced priority.
  if (nsCOMPtr<nsISupportsPriority> supportsPriority =
          do_QueryInterface(aChannel)) {
    int32_t priority = nsISupportsPriority::PRIORITY_LOW;

    // Adjust priority according to fetchpriorty attribute.
    if (StaticPrefs::network_fetchpriority_enabled()) {
      priority += FETCH_PRIORITY_ADJUSTMENT_FOR(images, aFetchPriority);
    }

    // Further reduce priority for background loads
    if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
      ++priority;
    }

    supportsPriority->AdjustPriority(priority);
  }

  if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) {
    cos->SetFetchPriorityDOM(aFetchPriority);
  }
}

static nsresult NewImageChannel(
    nsIChannel** aResult,
    // If aForcePrincipalCheckForCacheEntry is true, then we will
    // force a principal check even when not using CORS before
    // assuming we have a cache hit on a cache entry that we
    // create for this channel.  This is an out param that should
    // be set to true if this channel ends up depending on
    // aTriggeringPrincipal and false otherwise.
    bool* aForcePrincipalCheckForCacheEntry, nsIURI* aURI,
    nsIURI* aInitialDocumentURI, CORSMode aCORSMode,
    nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
    nsLoadFlags aLoadFlags, nsContentPolicyType aPolicyType,
    nsIPrincipal* aTriggeringPrincipal, nsINode* aRequestingNode,
    bool aRespectPrivacy, uint64_t aEarlyHintPreloaderId,
    FetchPriority aFetchPriority) {
  MOZ_ASSERT(aResult);

  nsresult rv;
  nsCOMPtr<nsIHttpChannel> newHttpChannel;

  nsCOMPtr<nsIInterfaceRequestor> callbacks;

  if (aLoadGroup) {
    // Get the notification callbacks from the load group for the new channel.
    //
    // XXX: This is not exactly correct, because the network request could be
    //      referenced by multiple windows...  However, the new channel needs
    //      something.  So, using the 'first' notification callbacks is better
    //      than nothing...
    //
    aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
  }

  // Pass in a nullptr loadgroup because this is the underlying network
  // request. This request may be referenced by several proxy image requests
  // (possibly in different documents).
  // If all of the proxy requests are canceled then this request should be
  // canceled too.
  //

  nsSecurityFlags securityFlags =
      nsContentSecurityManager::ComputeSecurityFlags(
          aCORSMode, nsContentSecurityManager::CORSSecurityMapping::
                         CORS_NONE_MAPS_TO_INHERITED_CONTEXT);

  securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;

  // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a
  // node and a principal. This is for things like background images that are
  // specified by user stylesheets, where the document is being styled, but
  // the principal is that of the user stylesheet.
  if (aRequestingNode && aTriggeringPrincipal) {
    rv = NS_NewChannelWithTriggeringPrincipal(aResult, aURI, aRequestingNode,
                                              aTriggeringPrincipal,
                                              securityFlags, aPolicyType,
                                              nullptr,  // PerformanceStorage
                                              nullptr,  // loadGroup
                                              callbacks, aLoadFlags);

    if (NS_FAILED(rv)) {
      return rv;
    }

    if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
      // If this is a favicon loading, we will use the originAttributes from the
      // triggeringPrincipal as the channel's originAttributes. This allows the
      // favicon loading from XUL will use the correct originAttributes.

      nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
      rv = loadInfo->SetOriginAttributes(
          aTriggeringPrincipal->OriginAttributesRef());
    }
  } else {
    // either we are loading something inside a document, in which case
    // we should always have a requestingNode, or we are loading something
    // outside a document, in which case the triggeringPrincipal and
    // triggeringPrincipal should always be the systemPrincipal.
    // However, there are exceptions: one is Notifications which create a
    // channel in the parent process in which case we can't get a
    // requestingNode.
    rv = NS_NewChannel(aResult, aURI, nsContentUtils::GetSystemPrincipal(),
                       securityFlags, aPolicyType,
                       nullptr,  // nsICookieJarSettings
                       nullptr,  // PerformanceStorage
                       nullptr,  // loadGroup
                       callbacks, aLoadFlags);

    if (NS_FAILED(rv)) {
      return rv;
    }

    // Use the OriginAttributes from the loading principal, if one is available,
    // and adjust the private browsing ID based on what kind of load the caller
    // has asked us to perform.
    OriginAttributes attrs;
    if (aTriggeringPrincipal) {
      attrs = aTriggeringPrincipal->OriginAttributesRef();
    }
    attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0;

    nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->LoadInfo();
    rv = loadInfo->SetOriginAttributes(attrs);
  }

  if (NS_FAILED(rv)) {
    return rv;
  }

  // only inherit if we have a principal
  *aForcePrincipalCheckForCacheEntry =
      aTriggeringPrincipal && nsContentUtils::ChannelShouldInheritPrincipal(
                                  aTriggeringPrincipal, aURI,
                                  /* aInheritForAboutBlank */ false,
                                  /* aForceInherit */ false);

  // Initialize HTTP-specific attributes
  newHttpChannel = do_QueryInterface(*aResult);
  if (newHttpChannel) {
    nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
        do_QueryInterface(newHttpChannel);
    NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
    rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    if (aReferrerInfo) {
      DebugOnly<nsresult> rv = newHttpChannel->SetReferrerInfo(aReferrerInfo);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }

    if (aEarlyHintPreloaderId) {
      rv = httpChannelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  AdjustPriorityForImages(*aResult, aLoadFlags, aFetchPriority);

  // Create a new loadgroup for this new channel, using the old group as
  // the parent. The indirection keeps the channel insulated from cancels,
  // but does allow a way for this revalidation to be associated with at
  // least one base load group for scheduling/caching purposes.

  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
  nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
  if (childLoadGroup) {
    childLoadGroup->SetParentLoadGroup(aLoadGroup);
  }
  (*aResult)->SetLoadGroup(loadGroup);

  return NS_OK;
}

static uint32_t SecondsFromPRTime(PRTime aTime) {
  return nsContentUtils::SecondsFromPRTime(aTime);
}

/* static */
imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
                             bool forcePrincipalCheck)
    : mLoader(loader),
      mRequest(request),
      mDataSize(0),
      mTouchedTime(SecondsFromPRTime(PR_Now())),
      mLoadTime(SecondsFromPRTime(PR_Now())),
      mExpiryTime(CacheExpirationTime::Never()),
      mMustValidate(false),
      // We start off as evicted so we don't try to update the cache.
      // PutIntoCache will set this to false.
      mEvicted(true),
      mHasNoProxies(true),
      mForcePrincipalCheck(forcePrincipalCheck),
      mHasNotified(false) {}

imgCacheEntry::~imgCacheEntry() {
  LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
}

void imgCacheEntry::Touch(bool updateTime /* = true */) {
  LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");

  if (updateTime) {
    mTouchedTime = SecondsFromPRTime(PR_Now());
  }

  UpdateCache();
}

void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) {
  // Don't update the cache if we've been removed from it or it doesn't care
  // about our size or usage.
  if (!Evicted() && HasNoProxies()) {
    mLoader->CacheEntriesChanged(diff);
  }
}

void imgCacheEntry::UpdateLoadTime() {
  mLoadTime = SecondsFromPRTime(PR_Now());
}

void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) {
  if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
    if (hasNoProxies) {
      LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true""uri",
                          mRequest->CacheKey().URI());
    } else {
      LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false",
                          "uri", mRequest->CacheKey().URI());
    }
  }

  mHasNoProxies = hasNoProxies;
}

imgCacheQueue::imgCacheQueue() : mDirty(false), mSize(0) {}

void imgCacheQueue::UpdateSize(int32_t diff) { mSize += diff; }

uint32_t imgCacheQueue::GetSize() const { return mSize; }

void imgCacheQueue::Remove(imgCacheEntry* entry) {
  uint64_t index = mQueue.IndexOf(entry);
  if (index == queueContainer::NoIndex) {
    return;
  }

  mSize -= mQueue[index]->GetDataSize();

  // If the queue is clean and this is the first entry,
  // then we can efficiently remove the entry without
  // dirtying the sort order.
  if (!IsDirty() && index == 0) {
    std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
    mQueue.RemoveLastElement();
    return;
  }

  // Remove from the middle of the list.  This potentially
  // breaks the binary heap sort order.
  mQueue.RemoveElementAt(index);

  // If we only have one entry or the queue is empty, though,
  // then the sort order is still effectively good.  Simply
  // refresh the list to clear the dirty flag.
  if (mQueue.Length() <= 1) {
    Refresh();
    return;
  }

  // Otherwise we must mark the queue dirty and potentially
  // trigger an expensive sort later.
  MarkDirty();
}

void imgCacheQueue::Push(imgCacheEntry* entry) {
  mSize += entry->GetDataSize();

  RefPtr<imgCacheEntry> refptr(entry);
  mQueue.AppendElement(std::move(refptr));
  // If we're not dirty already, then we can efficiently add this to the
  // binary heap immediately.  This is only O(log n).
  if (!IsDirty()) {
    std::push_heap(mQueue.begin(), mQueue.end(),
                   imgLoader::CompareCacheEntries);
  }
}

already_AddRefed<imgCacheEntry> imgCacheQueue::Pop() {
  if (mQueue.IsEmpty()) {
    return nullptr;
  }
  if (IsDirty()) {
    Refresh();
  }

  std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
  RefPtr<imgCacheEntry> entry = mQueue.PopLastElement();

  mSize -= entry->GetDataSize();
  return entry.forget();
}

void imgCacheQueue::Refresh() {
  // Resort the list.  This is an O(3 * n) operation and best avoided
  // if possible.
  std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
  mDirty = false;
}

void imgCacheQueue::MarkDirty() { mDirty = true; }

bool imgCacheQueue::IsDirty() { return mDirty; }

uint32_t imgCacheQueue::GetNumElements() const { return mQueue.Length(); }

bool imgCacheQueue::Contains(imgCacheEntry* aEntry) const {
  return mQueue.Contains(aEntry);
}

imgCacheQueue::iterator imgCacheQueue::begin() { return mQueue.begin(); }

imgCacheQueue::const_iterator imgCacheQueue::begin() const {
  return mQueue.begin();
}

imgCacheQueue::iterator imgCacheQueue::end() { return mQueue.end(); }

imgCacheQueue::const_iterator imgCacheQueue::end() const {
  return mQueue.end();
}

nsresult imgLoader::CreateNewProxyForRequest(
    imgRequest* aRequest, nsIURI* aURI, nsILoadGroup* aLoadGroup,
    Document* aLoadingDocument, imgINotificationObserver* aObserver,
    nsLoadFlags aLoadFlags, imgRequestProxy** _retval) {
  LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest",
                       "imgRequest", aRequest);

  /* XXX If we move decoding onto separate threads, we should save off the
     calling thread here and pass it off to |proxyRequest| so that it call
     proxy calls to |aObserver|.
   */


  RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy();

  /* It is important to call |SetLoadFlags()| before calling |Init()| because
     |Init()| adds the request to the loadgroup.
   */

  proxyRequest->SetLoadFlags(aLoadFlags);

  // init adds itself to imgRequest's list of observers
  nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aURI, aObserver);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  proxyRequest.forget(_retval);
  return NS_OK;
}

class imgCacheExpirationTracker final
    : public nsExpirationTracker<imgCacheEntry, 3> {
  enum { TIMEOUT_SECONDS = 10 };

 public:
  imgCacheExpirationTracker();

 protected:
  void NotifyExpired(imgCacheEntry* entry) override;
};

imgCacheExpirationTracker::imgCacheExpirationTracker()
    : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000,
                                            "imgCacheExpirationTracker") {}

void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry) {
  // Hold on to a reference to this entry, because the expiration tracker
  // mechanism doesn't.
  RefPtr<imgCacheEntry> kungFuDeathGrip(entry);

  if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
    RefPtr<imgRequest> req = entry->GetRequest();
    if (req) {
      LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired",
                          "entry", req->CacheKey().URI());
    }
  }

  // We can be called multiple times on the same entry. Don't do work multiple
  // times.
  if (!entry->Evicted()) {
    entry->Loader()->RemoveFromCache(entry);
  }

  entry->Loader()->VerifyCacheSizes();
}

///////////////////////////////////////////////////////////////////////////////
// imgLoader
///////////////////////////////////////////////////////////////////////////////

double imgLoader::sCacheTimeWeight;
uint32_t imgLoader::sCacheMaxSize;
imgMemoryReporter* imgLoader::sMemReporter;

NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
                  nsISupportsWeakReference, nsIObserver)

static imgLoader* gNormalLoader = nullptr;
static imgLoader* gPrivateBrowsingLoader = nullptr;

/* static */
already_AddRefed<imgLoader> imgLoader::CreateImageLoader() {
  // In some cases, such as xpctests, XPCOM modules are not automatically
  // initialized.  We need to make sure that our module is initialized before
  // we hand out imgLoader instances and code starts using them.
  mozilla::image::EnsureModuleInitialized();

  RefPtr<imgLoader> loader = new imgLoader();
  loader->Init();

  return loader.forget();
}

imgLoader* imgLoader::NormalLoader() {
  if (!gNormalLoader) {
    gNormalLoader = CreateImageLoader().take();
  }
  return gNormalLoader;
}

imgLoader* imgLoader::PrivateBrowsingLoader() {
  if (!gPrivateBrowsingLoader) {
    gPrivateBrowsingLoader = CreateImageLoader().take();
    gPrivateBrowsingLoader->RespectPrivacyNotifications();
  }
  return gPrivateBrowsingLoader;
}

imgLoader::imgLoader()
    : mUncachedImagesMutex("imgLoader::UncachedImages"),
      mRespectPrivacy(false) {
  sMemReporter->AddRef();
  sMemReporter->RegisterLoader(this);
}

imgLoader::~imgLoader() {
  ClearImageCache();
  {
    // If there are any of our imgRequest's left they are in the uncached
    // images set, so clear their pointer to us.
    MutexAutoLock lock(mUncachedImagesMutex);
    for (RefPtr<imgRequest> req : mUncachedImages) {
      req->ClearLoader();
    }
  }
  sMemReporter->UnregisterLoader(this);
  sMemReporter->Release();
}

void imgLoader::VerifyCacheSizes() {
#ifdef DEBUG
  if (!mCacheTracker) {
    return;
  }

  uint32_t cachesize = mCache.Count();
  uint32_t queuesize = mCacheQueue.GetNumElements();
  uint32_t trackersize = 0;
  for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
       it.Next();) {
    trackersize++;
  }
  MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
  MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
#endif
}

void imgLoader::GlobalInit() {
  sCacheTimeWeight = StaticPrefs::image_cache_timeweight_AtStartup() / 1000.0;
  int32_t cachesize = StaticPrefs::image_cache_size_AtStartup();
  sCacheMaxSize = cachesize > 0 ? cachesize : 0;

  sMemReporter = new imgMemoryReporter();
  RegisterStrongAsyncMemoryReporter(sMemReporter);
  RegisterImagesContentUsedUncompressedDistinguishedAmount(
      imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
}

void imgLoader::ShutdownMemoryReporter() {
  UnregisterImagesContentUsedUncompressedDistinguishedAmount();
  UnregisterStrongMemoryReporter(sMemReporter);
}

nsresult imgLoader::InitCache() {
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  if (!os) {
    return NS_ERROR_FAILURE;
  }

  os->AddObserver(this"memory-pressure"false);
  os->AddObserver(this"chrome-flush-caches"false);
  os->AddObserver(this"last-pb-context-exited"false);
  os->AddObserver(this"profile-before-change"false);
  os->AddObserver(this"xpcom-shutdown"false);

  mCacheTracker = MakeUnique<imgCacheExpirationTracker>();

  return NS_OK;
}

nsresult imgLoader::Init() {
  InitCache();

  return NS_OK;
}

NS_IMETHODIMP
imgLoader::RespectPrivacyNotifications() {
  mRespectPrivacy = true;
  return NS_OK;
}

NS_IMETHODIMP
imgLoader::Observe(nsISupports* aSubject, const char* aTopic,
                   const char16_t* aData) {
  if (strcmp(aTopic, "memory-pressure") == 0) {
    MinimizeCache();
  } else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
    MinimizeCache();
    ClearImageCache({ClearOption::ChromeOnly});
  } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
    if (mRespectPrivacy) {
      ClearImageCache();
    }
  } else if (strcmp(aTopic, "profile-before-change") == 0) {
    mCacheTracker = nullptr;
  } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
    mCacheTracker = nullptr;
    ShutdownMemoryReporter();

  } else {
    // (Nothing else should bring us here)
    MOZ_ASSERT(0, "Invalid topic received");
  }

  return NS_OK;
}

NS_IMETHODIMP
imgLoader::ClearCache(bool chrome) {
  if (XRE_IsParentProcess()) {
    bool privateLoader = this == gPrivateBrowsingLoader;
    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
      Unused << cp->SendClearImageCache(privateLoader, chrome);
    }
  }
  ClearOptions options;
  if (chrome) {
    options += ClearOption::ChromeOnly;
  }
  return ClearImageCache(options);
}

NS_IMETHODIMP
imgLoader::RemoveEntriesFromPrincipalInAllProcesses(nsIPrincipal* aPrincipal) {
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
    Unused << cp->SendClearImageCacheFromPrincipal(aPrincipal);
  }

  imgLoader* loader;
  if (aPrincipal->OriginAttributesRef().IsPrivateBrowsing()) {
    loader = imgLoader::PrivateBrowsingLoader();
  } else {
    loader = imgLoader::NormalLoader();
  }

  return loader->RemoveEntriesInternal(Some(aPrincipal), Nothing(), Nothing());
}

NS_IMETHODIMP
imgLoader::RemoveEntriesFromSiteInAllProcesses(
    const nsACString& aSchemelessSite,
    JS::Handle<JS::Value> aOriginAttributesPattern, JSContext* aCx) {
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  OriginAttributesPattern pattern;
  if (!aOriginAttributesPattern.isObject() ||
      !pattern.Init(aCx, aOriginAttributesPattern)) {
    return NS_ERROR_INVALID_ARG;
  }

  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
    Unused << cp->SendClearImageCacheFromSite(aSchemelessSite, pattern);
  }

  return RemoveEntriesInternal(Nothing(), Some(nsCString(aSchemelessSite)),
                               Some(pattern));
}

nsresult imgLoader::RemoveEntriesInternal(
    const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    const Maybe<nsCString>& aSchemelessSite,
    const Maybe<OriginAttributesPattern>& aPattern) {
  // Can only clear by either principal or site + pattern.
  if ((!aPrincipal && !aSchemelessSite) || (aPrincipal && aSchemelessSite) ||
      aSchemelessSite.isSome() != aPattern.isSome()) {
    return NS_ERROR_INVALID_ARG;
  }

  nsCOMPtr<nsIEffectiveTLDService> tldService;
  AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;

  Maybe<OriginAttributesPattern> patternWithPartitionKey = Nothing();
  if (aPattern) {
    // Used for checking for cache entries partitioned under aSchemelessSite.
    OriginAttributesPattern pattern(aPattern.ref());
    pattern.mPartitionKeyPattern.Construct();
    pattern.mPartitionKeyPattern.Value().mBaseDomain.Construct(
        NS_ConvertUTF8toUTF16(aSchemelessSite.ref()));

    patternWithPartitionKey.emplace(std::move(pattern));
  }

  // For base domain we only clear the non-chrome cache.
  for (const auto& entry : mCache) {
    const auto& key = entry.GetKey();

    const bool shouldRemove = [&] {
      // The isolation key is either just the site, or an origin suffix
      // which contains the partitionKey holding the baseDomain.

      if (aPrincipal) {
        nsCOMPtr<nsIPrincipal> keyPrincipal =
            BasePrincipal::CreateContentPrincipal(key.URI(),
                                                  key.OriginAttributesRef());
        return keyPrincipal->Equals(aPrincipal.ref());
      }

      if (!aSchemelessSite) {
        return false;
      }
      // Clear by site and pattern.
      nsAutoCString host;
      nsresult rv = key.URI()->GetHost(host);
      if (NS_FAILED(rv) || host.IsEmpty()) {
        return false;
      }

      if (!tldService) {
        tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
      }
      if (NS_WARN_IF(!tldService)) {
        return false;
      }

      bool hasRootDomain = false;
      rv = tldService->HasRootDomain(host, aSchemelessSite.ref(),
                                     &hasRootDomain);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return false;
      }

      if (hasRootDomain && aPattern->Matches(key.OriginAttributesRef())) {
        return true;
      }

      // Attempt to parse isolation key into origin attributes.
      Maybe<OriginAttributes> originAttributesWithPartitionKey;
      {
        OriginAttributes attrs;
        if (attrs.PopulateFromSuffix(key.IsolationKeyRef())) {
          OriginAttributes attrsWithPartitionKey(key.OriginAttributesRef());
          attrsWithPartitionKey.mPartitionKey = attrs.mPartitionKey;
          originAttributesWithPartitionKey.emplace(
              std::move(attrsWithPartitionKey));
        }
      }

      // Match it against the pattern that contains the partition key and any
      // fields set by the caller pattern.
      if (originAttributesWithPartitionKey.isSome()) {
        nsAutoCString oaSuffixForPrinting;
        originAttributesWithPartitionKey->CreateSuffix(oaSuffixForPrinting);

        nsAutoString patternForPrinting;
        patternWithPartitionKey->ToJSON(patternForPrinting);

        return patternWithPartitionKey.ref().Matches(
            originAttributesWithPartitionKey.ref());
      }

      // The isolation key is the site.
      return aSchemelessSite->Equals(key.IsolationKeyRef());
    }();

    if (shouldRemove) {
      entriesToBeRemoved.AppendElement(entry.GetData());
    }
  }

  for (auto& entry : entriesToBeRemoved) {
    if (!RemoveFromCache(entry)) {
      NS_WARNING(
          "Couldn't remove an entry from the cache in "
          "RemoveEntriesInternal()\n");
    }
  }

  return NS_OK;
}

constexpr auto AllCORSModes() {
  return MakeInclusiveEnumeratedRange(kFirstCORSMode, kLastCORSMode);
}

NS_IMETHODIMP
imgLoader::RemoveEntry(nsIURI* aURI, Document* aDoc) {
  if (!aURI) {
    return NS_OK;
  }
  OriginAttributes attrs;
  if (aDoc) {
    attrs = aDoc->NodePrincipal()->OriginAttributesRef();
  }
  for (auto corsMode : AllCORSModes()) {
    ImageCacheKey key(aURI, corsMode, attrs, aDoc);
    RemoveFromCache(key);
  }
  return NS_OK;
}

NS_IMETHODIMP
imgLoader::FindEntryProperties(nsIURI* uri, Document* aDoc,
                               nsIProperties** _retval) {
  *_retval = nullptr;

  OriginAttributes attrs;
  if (aDoc) {
    nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
    if (principal) {
      attrs = principal->OriginAttributesRef();
    }
  }

  for (auto corsMode : AllCORSModes()) {
    ImageCacheKey key(uri, corsMode, attrs, aDoc);
    RefPtr<imgCacheEntry> entry;
    if (!mCache.Get(key, getter_AddRefs(entry)) || !entry) {
      continue;
    }
    if (mCacheTracker && entry->HasNoProxies()) {
      mCacheTracker->MarkUsed(entry);
    }
    RefPtr<imgRequest> request = entry->GetRequest();
    if (request) {
      nsCOMPtr<nsIProperties> properties = request->Properties();
      properties.forget(_retval);
      return NS_OK;
    }
  }
  return NS_OK;
}

NS_IMETHODIMP_(void)
imgLoader::ClearCacheForControlledDocument(Document* aDoc) {
  MOZ_ASSERT(aDoc);
  AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
  for (const auto& entry : mCache) {
    const auto& key = entry.GetKey();
    if (key.ControlledDocument() == aDoc) {
      entriesToBeRemoved.AppendElement(entry.GetData());
    }
  }
  for (auto& entry : entriesToBeRemoved) {
    if (!RemoveFromCache(entry)) {
      NS_WARNING(
          "Couldn't remove an entry from the cache in "
          "ClearCacheForControlledDocument()\n");
    }
  }
}

void imgLoader::Shutdown() {
  NS_IF_RELEASE(gNormalLoader);
  gNormalLoader = nullptr;
  NS_IF_RELEASE(gPrivateBrowsingLoader);
  gPrivateBrowsingLoader = nullptr;
}

bool imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry) {
  LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::PutIntoCache""uri",
                             aKey.URI());

  // Check to see if this request already exists in the cache. If so, we'll
  // replace the old version.
  RefPtr<imgCacheEntry> tmpCacheEntry;
  if (mCache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
    MOZ_LOG(
        gImgLog, LogLevel::Debug,
        ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
         nullptr));
    RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();

    // If it already exists, and we're putting the same key into the cache, we
    // should remove the old version.
    MOZ_LOG(gImgLog, LogLevel::Debug,
            ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element",
             nullptr));

    RemoveFromCache(aKey);
  } else {
    MOZ_LOG(gImgLog, LogLevel::Debug,
            ("[this=%p] imgLoader::PutIntoCache --"
             " Element NOT already in the cache",
             nullptr));
  }

  mCache.InsertOrUpdate(aKey, RefPtr{entry});

  // We can be called to resurrect an evicted entry.
  if (entry->Evicted()) {
    entry->SetEvicted(false);
  }

  // If we're resurrecting an entry with no proxies, put it back in the
  // tracker and queue.
  if (entry->HasNoProxies()) {
    nsresult addrv = NS_OK;

    if (mCacheTracker) {
      addrv = mCacheTracker->AddObject(entry);
    }

    if (NS_SUCCEEDED(addrv)) {
      mCacheQueue.Push(entry);
    }
  }

  RefPtr<imgRequest> request = entry->GetRequest();
  request->SetIsInCache(true);
  RemoveFromUncachedImages(request);

  return true;
}

bool imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry) {
  LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasNoProxies""uri",
                             aRequest->CacheKey().URI());

  aEntry->SetHasNoProxies(true);

  if (aEntry->Evicted()) {
    return false;
  }

  nsresult addrv = NS_OK;

  if (mCacheTracker) {
    addrv = mCacheTracker->AddObject(aEntry);
  }

  if (NS_SUCCEEDED(addrv)) {
    mCacheQueue.Push(aEntry);
  }

  return true;
}

bool imgLoader::SetHasProxies(imgRequest* aRequest) {
  VerifyCacheSizes();

  const ImageCacheKey& key = aRequest->CacheKey();

  LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies""uri",
                             key.URI());

  RefPtr<imgCacheEntry> entry;
  if (mCache.Get(key, getter_AddRefs(entry)) && entry) {
    // Make sure the cache entry is for the right request
    RefPtr<imgRequest> entryRequest = entry->GetRequest();
    if (entryRequest == aRequest && entry->HasNoProxies()) {
      mCacheQueue.Remove(entry);

      if (mCacheTracker) {
        mCacheTracker->RemoveObject(entry);
      }

      entry->SetHasNoProxies(false);

      return true;
    }
  }

  return false;
}

void imgLoader::CacheEntriesChanged(int32_t aSizeDiff /* = 0 */) {
  // We only need to dirty the queue if there is any sorting
  // taking place.  Empty or single-entry lists can't become
  // dirty.
  if (mCacheQueue.GetNumElements() > 1) {
    mCacheQueue.MarkDirty();
  }
  mCacheQueue.UpdateSize(aSizeDiff);
}

void imgLoader::CheckCacheLimits() {
  if (mCacheQueue.GetNumElements() == 0) {
    NS_ASSERTION(mCacheQueue.GetSize() == 0,
                 "imgLoader::CheckCacheLimits -- incorrect cache size");
  }

  // Remove entries from the cache until we're back at our desired max size.
  while (mCacheQueue.GetSize() > sCacheMaxSize) {
    // Remove the first entry in the queue.
    RefPtr<imgCacheEntry> entry(mCacheQueue.Pop());

    NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");

    if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
      RefPtr<imgRequest> req = entry->GetRequest();
      if (req) {
        LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::CheckCacheLimits",
                                   "entry", req->CacheKey().URI());
      }
    }

    if (entry) {
      // We just popped this entry from the queue, so pass AlreadyRemoved
      // to avoid searching the queue again in RemoveFromCache.
      RemoveFromCache(entry, QueueState::AlreadyRemoved);
    }
  }
}

bool imgLoader::ValidateRequestWithNewChannel(
    imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI,
    nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
    imgINotificationObserver* aObserver, Document* aLoadingDocument,
    uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
    nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest,
    nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode, bool aLinkPreload,
    uint64_t aEarlyHintPreloaderId, FetchPriority aFetchPriority,
    bool* aNewChannelCreated) {
  // now we need to insert a new channel request object in between the real
  // request and the proxy that basically delays loading the image until it
  // gets a 304 or figures out that this needs to be a new request

  nsresult rv;

  // If we're currently in the middle of validating this request, just hand
  // back a proxy to it; the required work will be done for us.
  if (imgCacheValidator* validator = request->GetValidator()) {
    rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
                                  aObserver, aLoadFlags, aProxyRequest);
    if (NS_FAILED(rv)) {
      return false;
    }

    if (*aProxyRequest) {
      imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);

      // We will send notifications from imgCacheValidator::OnStartRequest().
      // In the mean time, we must defer notifications because we are added to
      // the imgRequest's proxy list, and we can get extra notifications
      // resulting from methods such as StartDecoding(). See bug 579122.
      proxy->MarkValidating();

      if (aLinkPreload) {
        MOZ_ASSERT(aLoadingDocument);
        auto preloadKey = PreloadHashKey::CreateAsImage(
            aURI, aTriggeringPrincipal, aCORSMode);
        proxy->NotifyOpen(preloadKey, aLoadingDocument, true);
      }

      // Attach the proxy without notifying
      validator->AddProxy(proxy);
    }

    return true;
  }
  // We will rely on Necko to cache this request when it's possible, and to
  // tell imgCacheValidator::OnStartRequest whether the request came from its
  // cache.
  nsCOMPtr<nsIChannel> newChannel;
  bool forcePrincipalCheck;
  rv = NewImageChannel(getter_AddRefs(newChannel), &forcePrincipalCheck, aURI,
                       aInitialDocumentURI, aCORSMode, aReferrerInfo,
                       aLoadGroup, aLoadFlags, aLoadPolicyType,
                       aTriggeringPrincipal, aLoadingDocument, mRespectPrivacy,
                       aEarlyHintPreloaderId, aFetchPriority);
  if (NS_FAILED(rv)) {
    return false;
  }

  if (aNewChannelCreated) {
    *aNewChannelCreated = true;
  }

  RefPtr<imgRequestProxy> req;
  rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
                                aObserver, aLoadFlags, getter_AddRefs(req));
  if (NS_FAILED(rv)) {
    return false;
  }

  // Make sure that OnStatus/OnProgress calls have the right request set...
  RefPtr<nsProgressNotificationProxy> progressproxy =
      new nsProgressNotificationProxy(newChannel, req);
  if (!progressproxy) {
    return false;
  }

  RefPtr<imgCacheValidator> hvc =
      new imgCacheValidator(progressproxy, this, request, aLoadingDocument,
                            aInnerWindowId, forcePrincipalCheck);

  // Casting needed here to get past multiple inheritance.
  nsCOMPtr<nsIStreamListener> listener =
      static_cast<nsIThreadRetargetableStreamListener*>(hvc);
  NS_ENSURE_TRUE(listener, false);

  // We must set the notification callbacks before setting up the
  // CORS listener, because that's also interested inthe
  // notification callbacks.
  newChannel->SetNotificationCallbacks(hvc);

  request->SetValidator(hvc);

  // We will send notifications from imgCacheValidator::OnStartRequest().
  // In the mean time, we must defer notifications because we are added to
--> --------------------

--> maximum size reached

--> --------------------

94%


¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.