tor-browser

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

ImageLoader.cpp (27365B)


      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* A class that handles style system image loads (other image loads are handled
      8 * by the nodes in the content tree).
      9 */
     10 
     11 #include "mozilla/css/ImageLoader.h"
     12 
     13 #include "Image.h"
     14 #include "imgIContainer.h"
     15 #include "imgINotificationObserver.h"
     16 #include "mozilla/PresShell.h"
     17 #include "mozilla/ProfilerLabels.h"
     18 #include "mozilla/SVGObserverUtils.h"
     19 #include "mozilla/StaticPtr.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/DocumentInlines.h"
     22 #include "mozilla/dom/LargestContentfulPaint.h"
     23 #include "mozilla/layers/WebRenderUserData.h"
     24 #include "nsCanvasFrame.h"
     25 #include "nsContentUtils.h"
     26 #include "nsDisplayList.h"
     27 #include "nsError.h"
     28 #include "nsIFrameInlines.h"
     29 #include "nsIReflowCallback.h"
     30 #include "nsLayoutUtils.h"
     31 #include "nsTHashSet.h"
     32 
     33 using namespace mozilla::dom;
     34 
     35 namespace mozilla::css {
     36 
     37 // This is a singleton observer which looks in the `GlobalRequestTable` to look
     38 // at which loaders to notify.
     39 struct GlobalImageObserver final : public imgINotificationObserver {
     40  NS_DECL_ISUPPORTS
     41  NS_DECL_IMGINOTIFICATIONOBSERVER
     42 
     43  GlobalImageObserver() = default;
     44 
     45 private:
     46  virtual ~GlobalImageObserver() = default;
     47 };
     48 
     49 NS_IMPL_ADDREF(GlobalImageObserver)
     50 NS_IMPL_RELEASE(GlobalImageObserver)
     51 
     52 NS_INTERFACE_MAP_BEGIN(GlobalImageObserver)
     53  NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
     54 NS_INTERFACE_MAP_END
     55 
     56 // Data associated with every started load.
     57 struct ImageTableEntry {
     58  // Set of all ImageLoaders that have registered this URL and care for updates
     59  // for it.
     60  nsTHashSet<ImageLoader*> mImageLoaders;
     61 
     62  // The amount of style values that are sharing this image.
     63  uint32_t mSharedCount = 1;
     64 };
     65 
     66 using GlobalRequestTable =
     67    nsClassHashtable<nsRefPtrHashKey<imgIRequest>, ImageTableEntry>;
     68 
     69 // A table of all loads, keyed by their id mapping them to the set of
     70 // ImageLoaders they have been registered in, and recording their "canonical"
     71 // image request.
     72 //
     73 // We use the load id as the key since we can only access sImages on the
     74 // main thread, but LoadData objects might be destroyed from other threads,
     75 // and we don't want to leave dangling pointers around.
     76 static StaticAutoPtr<GlobalRequestTable> sImages;
     77 static StaticRefPtr<GlobalImageObserver> sImageObserver;
     78 
     79 /* static */
     80 void ImageLoader::Init() {
     81  sImages = new GlobalRequestTable();
     82  sImageObserver = new GlobalImageObserver();
     83 }
     84 
     85 /* static */
     86 void ImageLoader::Shutdown() {
     87  for (const auto& entry : *sImages) {
     88    imgIRequest* imgRequest = entry.GetKey();
     89    // All the images we put in sImages are imgRequestProxy, see LoadImage, but
     90    // it's non-trivial to make the hash table to use that without changing a
     91    // lot of other code.
     92    auto* req = static_cast<imgRequestProxy*>(imgRequest);
     93    req->SetCancelable(true);
     94    req->CancelAndForgetObserver(NS_BINDING_ABORTED);
     95  }
     96 
     97  sImages = nullptr;
     98  sImageObserver = nullptr;
     99 }
    100 
    101 void ImageLoader::DropDocumentReference() {
    102  MOZ_ASSERT(NS_IsMainThread());
    103 
    104  // It's okay if GetPresContext returns null here (due to the presshell pointer
    105  // on the document being null) as that means the presshell has already
    106  // been destroyed, and it also calls ClearFrames when it is destroyed.
    107  ClearFrames(GetPresContext());
    108 
    109  mDocument = nullptr;
    110 }
    111 
    112 // Arrays of requests and frames are sorted by their pointer address,
    113 // for faster lookup.
    114 template <typename Elem, typename Item,
    115          typename Comparator = nsDefaultComparator<Elem, Item>>
    116 static size_t GetMaybeSortedIndex(const nsTArray<Elem>& aArray,
    117                                  const Item& aItem, bool* aFound,
    118                                  Comparator aComparator = Comparator()) {
    119  size_t index = aArray.IndexOfFirstElementGt(aItem, aComparator);
    120  *aFound = index > 0 && aComparator.Equals(aItem, aArray.ElementAt(index - 1));
    121  return index;
    122 }
    123 
    124 // Returns true if an async decode is triggered for aRequest, and thus we will
    125 // get an OnFrameComplete callback for this request eventually.
    126 static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest* aRequest) {
    127  uint32_t status = 0;
    128  // Don't block onload if we've already got a frame complete status
    129  // (since in that case the image is already loaded), or if we get an
    130  // error status (since then we know the image won't ever load).
    131  if (NS_SUCCEEDED(aRequest->GetImageStatus(&status))) {
    132    if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
    133      // Already decoded, no need to do it again.
    134      return false;
    135    }
    136    if (status & imgIRequest::STATUS_ERROR) {
    137      // Already errored, this would be useless.
    138      return false;
    139    }
    140  }
    141 
    142  // We want to request decode in such a way that avoids triggering sync decode.
    143  // First, we attempt to convert the aRequest into a imgIContainer. If that
    144  // succeeds, then aRequest has an image and we can request decoding for size
    145  // at zero size, the size will be ignored because we don't pass the
    146  // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass
    147  // any sync decoding flags) at the intrinsic size will be requested. If the
    148  // conversion to imgIContainer is unsuccessful, then that means aRequest
    149  // doesn't have an image yet, which means we can safely call StartDecoding()
    150  // on it without triggering any synchronous work.
    151  nsCOMPtr<imgIContainer> imgContainer;
    152  aRequest->GetImage(getter_AddRefs(imgContainer));
    153  if (imgContainer) {
    154    imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
    155                                       imgIContainer::DECODE_FLAGS_DEFAULT);
    156  } else {
    157    // It's safe to call StartDecoding directly, since it can't
    158    // trigger synchronous decode without an image. Flags are ignored.
    159    aRequest->StartDecoding(imgIContainer::FLAG_NONE);
    160  }
    161  return true;
    162 }
    163 
    164 void ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
    165                                          nsIFrame* aFrame, Flags aFlags) {
    166  MOZ_ASSERT(NS_IsMainThread());
    167  MOZ_ASSERT(!(aFlags & Flags::IsBlockingLoadEvent),
    168             "Shouldn't be used in the public API");
    169 
    170  {
    171    nsCOMPtr<imgINotificationObserver> observer;
    172    aRequest->GetNotificationObserver(getter_AddRefs(observer));
    173    if (!observer) {
    174      // The request has already been canceled, so ignore it. This is ok because
    175      // we're not going to get any more notifications from a canceled request.
    176      return;
    177    }
    178    MOZ_ASSERT(observer == sImageObserver);
    179  }
    180 
    181  auto* const frameSet =
    182      mRequestToFrameMap
    183          .LookupOrInsertWith(
    184              aRequest,
    185              [&] {
    186                mDocument->TrackImage(aRequest);
    187 
    188                if (auto entry = sImages->Lookup(aRequest)) {
    189                  DebugOnly<bool> inserted =
    190                      entry.Data()->mImageLoaders.EnsureInserted(this);
    191                  MOZ_ASSERT(inserted);
    192                } else {
    193                  MOZ_ASSERT_UNREACHABLE(
    194                      "Shouldn't be associating images not in sImages");
    195                }
    196 
    197                if (nsPresContext* presContext = GetPresContext()) {
    198                  nsLayoutUtils::RegisterImageRequestIfAnimated(
    199                      presContext, aRequest, nullptr);
    200                }
    201                return MakeUnique<FrameSet>();
    202              })
    203          .get();
    204 
    205  auto* const requestSet =
    206      mFrameToRequestMap
    207          .LookupOrInsertWith(aFrame,
    208                              [=]() {
    209                                aFrame->SetHasImageRequest(true);
    210                                return MakeUnique<RequestSet>();
    211                              })
    212          .get();
    213 
    214  // Add frame to the frameSet, and handle any special processing the
    215  // frame might require.
    216  FrameWithFlags fwf(aFrame);
    217  FrameWithFlags* fwfToModify = &fwf;
    218 
    219  // See if the frameSet already has this frame.
    220  bool found;
    221  uint32_t i =
    222      GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator());
    223  if (found) {
    224    // We're already tracking this frame, so prepare to modify the
    225    // existing FrameWithFlags object.
    226    fwfToModify = &frameSet->ElementAt(i - 1);
    227  }
    228 
    229  // Check if the frame requires special processing.
    230  if (aFlags & Flags::RequiresReflowOnSizeAvailable) {
    231    MOZ_ASSERT(!(aFlags &
    232                 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking),
    233               "These two are exclusive");
    234    fwfToModify->mFlags |= Flags::RequiresReflowOnSizeAvailable;
    235  }
    236 
    237  if (aFlags & Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
    238    fwfToModify->mFlags |=
    239        Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking;
    240 
    241    // If we weren't already blocking onload, do that now.
    242    if (!(fwfToModify->mFlags & Flags::IsBlockingLoadEvent)) {
    243      if (TriggerAsyncDecodeAtIntrinsicSize(aRequest)) {
    244        // If there's no error, and the image has not loaded yet, so we can
    245        // block onload.
    246        fwfToModify->mFlags |= Flags::IsBlockingLoadEvent;
    247 
    248        // Block document onload until we either remove the frame in
    249        // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
    250        mDocument->BlockOnload();
    251      }
    252    }
    253  }
    254 
    255  // Do some sanity checking to ensure that we only add to one mapping
    256  // iff we also add to the other mapping.
    257  DebugOnly<bool> didAddToFrameSet(false);
    258  DebugOnly<bool> didAddToRequestSet(false);
    259 
    260  // If we weren't already tracking this frame, add it to the frameSet.
    261  if (!found) {
    262    frameSet->InsertElementAt(i, fwf);
    263    didAddToFrameSet = true;
    264  }
    265 
    266  // Add request to the request set if it wasn't already there.
    267  i = GetMaybeSortedIndex(*requestSet, aRequest, &found);
    268  if (!found) {
    269    requestSet->InsertElementAt(i, aRequest);
    270    didAddToRequestSet = true;
    271  }
    272 
    273  MOZ_ASSERT(didAddToFrameSet == didAddToRequestSet,
    274             "We should only add to one map iff we also add to the other map.");
    275 }
    276 
    277 void ImageLoader::RemoveRequestToFrameMapping(imgIRequest* aRequest,
    278                                              nsIFrame* aFrame) {
    279 #ifdef DEBUG
    280  {
    281    nsCOMPtr<imgINotificationObserver> observer;
    282    aRequest->GetNotificationObserver(getter_AddRefs(observer));
    283    MOZ_ASSERT(!observer || observer == sImageObserver);
    284  }
    285 #endif
    286 
    287  if (auto entry = mRequestToFrameMap.Lookup(aRequest)) {
    288    const auto& frameSet = entry.Data();
    289    MOZ_ASSERT(frameSet, "This should never be null");
    290 
    291    // Before we remove aFrame from the frameSet, unblock onload if needed.
    292    bool found;
    293    uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found,
    294                                     FrameOnlyComparator());
    295    if (found) {
    296      UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1));
    297      frameSet->RemoveElementAtUnsafe(i - 1);
    298    }
    299 
    300    if (frameSet->IsEmpty()) {
    301      DeregisterImageRequest(aRequest, GetPresContext());
    302      entry.Remove();
    303    }
    304  }
    305 }
    306 
    307 void ImageLoader::DeregisterImageRequest(imgIRequest* aRequest,
    308                                         nsPresContext* aPresContext) {
    309  mDocument->UntrackImage(aRequest);
    310 
    311  if (auto entry = sImages->Lookup(aRequest)) {
    312    entry.Data()->mImageLoaders.EnsureRemoved(this);
    313  }
    314 
    315  if (aPresContext) {
    316    nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr);
    317  }
    318 }
    319 
    320 void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest,
    321                                              nsIFrame* aFrame) {
    322  if (auto entry = mFrameToRequestMap.Lookup(aFrame)) {
    323    const auto& requestSet = entry.Data();
    324    MOZ_ASSERT(requestSet, "This should never be null");
    325    requestSet->RemoveElementSorted(aRequest);
    326    if (requestSet->IsEmpty()) {
    327      aFrame->SetHasImageRequest(false);
    328      entry.Remove();
    329    }
    330  }
    331 }
    332 
    333 void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
    334                                               nsIFrame* aFrame) {
    335  MOZ_ASSERT(NS_IsMainThread());
    336  MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
    337 
    338  RemoveRequestToFrameMapping(aRequest, aFrame);
    339  RemoveFrameToRequestMapping(aRequest, aFrame);
    340 }
    341 
    342 void ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) {
    343  MOZ_ASSERT(NS_IsMainThread());
    344  MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
    345 
    346  UniquePtr<RequestSet> requestSet;
    347  mFrameToRequestMap.Remove(aFrame, &requestSet);
    348  aFrame->SetHasImageRequest(false);
    349  if (MOZ_UNLIKELY(!requestSet)) {
    350    MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying");
    351    return;
    352  }
    353  for (imgIRequest* request : *requestSet) {
    354    RemoveRequestToFrameMapping(request, aFrame);
    355  }
    356 }
    357 
    358 void ImageLoader::SetAnimationMode(uint16_t aMode) {
    359  MOZ_ASSERT(NS_IsMainThread());
    360  NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
    361                   aMode == imgIContainer::kDontAnimMode ||
    362                   aMode == imgIContainer::kLoopOnceAnimMode,
    363               "Wrong Animation Mode is being set!");
    364 
    365  for (nsISupports* key : mRequestToFrameMap.Keys()) {
    366    auto* request = static_cast<imgIRequest*>(key);
    367 
    368 #ifdef DEBUG
    369    {
    370      nsCOMPtr<imgIRequest> debugRequest = request;
    371      NS_ASSERTION(debugRequest == request, "This is bad");
    372    }
    373 #endif
    374 
    375    nsCOMPtr<imgIContainer> container;
    376    request->GetImage(getter_AddRefs(container));
    377    if (!container) {
    378      continue;
    379    }
    380 
    381    // This can fail if the image is in error, and we don't care.
    382    container->SetAnimationMode(aMode);
    383  }
    384 }
    385 
    386 void ImageLoader::ClearFrames(nsPresContext* aPresContext) {
    387  MOZ_ASSERT(NS_IsMainThread());
    388 
    389  for (const auto& key : mRequestToFrameMap.Keys()) {
    390    auto* request = static_cast<imgIRequest*>(key);
    391 
    392 #ifdef DEBUG
    393    {
    394      nsCOMPtr<imgIRequest> debugRequest = request;
    395      NS_ASSERTION(debugRequest == request, "This is bad");
    396    }
    397 #endif
    398 
    399    DeregisterImageRequest(request, aPresContext);
    400  }
    401 
    402  mRequestToFrameMap.Clear();
    403  mFrameToRequestMap.Clear();
    404 }
    405 
    406 static CORSMode EffectiveCorsMode(nsIURI* aURI,
    407                                  const StyleComputedUrl& aImage) {
    408  MOZ_ASSERT(aURI);
    409  StyleCorsMode mode = aImage.CorsMode();
    410  if (mode == StyleCorsMode::None) {
    411    return CORSMode::CORS_NONE;
    412  }
    413  MOZ_ASSERT(mode == StyleCorsMode::Anonymous);
    414  if (aURI->SchemeIs("resource")) {
    415    return CORSMode::CORS_NONE;
    416  }
    417  return CORSMode::CORS_ANONYMOUS;
    418 }
    419 
    420 /* static */
    421 already_AddRefed<imgRequestProxy> ImageLoader::LoadImage(
    422    const StyleComputedUrl& aImage, Document& aDocument) {
    423  MOZ_ASSERT(NS_IsMainThread());
    424  nsIURI* uri = aImage.GetURI();
    425  if (!uri) {
    426    return nullptr;
    427  }
    428 
    429  if (aImage.HasRef()) {
    430    bool isEqualExceptRef = false;
    431    nsIURI* docURI = aDocument.GetDocumentURI();
    432    if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) &&
    433        isEqualExceptRef) {
    434      // Prevent loading an internal resource.
    435      return nullptr;
    436    }
    437  }
    438 
    439  int32_t loadFlags =
    440      nsIRequest::LOAD_NORMAL |
    441      nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri, aImage));
    442 
    443  const URLExtraData& data = aImage.ExtraData();
    444 
    445  RefPtr<imgRequestProxy> request;
    446  nsresult rv = nsContentUtils::LoadImage(
    447      uri, &aDocument, &aDocument, data.Principal(), 0, data.ReferrerInfo(),
    448      sImageObserver, loadFlags, u"css"_ns, getter_AddRefs(request));
    449 
    450  if (NS_FAILED(rv) || !request) {
    451    return nullptr;
    452  }
    453 
    454  // This image could be shared across documents, so its load cannot be
    455  // canceled, see bug 1800979.
    456  request->SetCancelable(false);
    457  sImages->GetOrInsertNew(request);
    458  return request.forget();
    459 }
    460 
    461 void ImageLoader::UnloadImage(imgRequestProxy* aImage) {
    462  MOZ_ASSERT(NS_IsMainThread());
    463  MOZ_ASSERT(aImage);
    464 
    465  if (MOZ_UNLIKELY(!sImages)) {
    466    return;  // Shutdown() takes care of it.
    467  }
    468 
    469  auto lookup = sImages->Lookup(aImage);
    470  MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
    471  if (MOZ_UNLIKELY(!lookup)) {
    472    return;
    473  }
    474 
    475  if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) {
    476    // Someone else still cares about this image.
    477    return;
    478  }
    479 
    480  // Now we want to really cancel the request.
    481  aImage->SetCancelable(true);
    482  aImage->CancelAndForgetObserver(NS_BINDING_ABORTED);
    483  MOZ_DIAGNOSTIC_ASSERT(lookup.Data()->mImageLoaders.IsEmpty(),
    484                        "Shouldn't be keeping references to any loader "
    485                        "by now");
    486  lookup.Remove();
    487 }
    488 
    489 void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) {
    490  MOZ_ASSERT(NS_IsMainThread());
    491  MOZ_ASSERT(aImage);
    492 
    493  auto lookup = sImages->Lookup(aImage);
    494  MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
    495  if (MOZ_UNLIKELY(!lookup)) {
    496    return;
    497  }
    498 
    499  lookup.Data()->mSharedCount++;
    500 }
    501 
    502 nsPresContext* ImageLoader::GetPresContext() {
    503  if (!mDocument) {
    504    return nullptr;
    505  }
    506 
    507  return mDocument->GetPresContext();
    508 }
    509 
    510 static bool IsRenderNoImages(uint32_t aDisplayItemKey) {
    511  DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey);
    512  uint8_t flags = GetDisplayItemFlagsForType(type);
    513  return flags & TYPE_RENDERS_NO_IMAGES;
    514 }
    515 
    516 static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest,
    517                             bool aForcePaint) {
    518  if (!aFrame->StyleVisibility()->IsVisible()) {
    519    return;
    520  }
    521 
    522  if (aFrame->IsTablePart()) {
    523    // Tables don't necessarily build border/background display items
    524    // for the individual table part frames, so IterateRetainedDataFor
    525    // might not find the right display item.
    526    return aFrame->InvalidateFrame();
    527  }
    528 
    529  if (aFrame->ShouldPropagateRepaintsToRoot()) {
    530    if (auto* canvas = aFrame->PresShell()->GetCanvasFrame()) {
    531      // Try to invalidate the canvas too, in the probable case the background
    532      // was propagated to it.
    533      InvalidateImages(canvas, aRequest, aForcePaint);
    534    }
    535  }
    536 
    537  bool invalidateFrame = aForcePaint;
    538 
    539  if (auto userDataTable =
    540          aFrame->GetProperty(layers::WebRenderUserDataProperty::Key())) {
    541    for (RefPtr<layers::WebRenderUserData> data : userDataTable->Values()) {
    542      switch (data->GetType()) {
    543        case layers::WebRenderUserData::UserDataType::eFallback:
    544          if (!IsRenderNoImages(data->GetDisplayItemKey())) {
    545            static_cast<layers::WebRenderFallbackData*>(data.get())
    546                ->SetInvalid(true);
    547          }
    548          // XXX: handle Blob data
    549          invalidateFrame = true;
    550          break;
    551        case layers::WebRenderUserData::UserDataType::eMask:
    552          static_cast<layers::WebRenderMaskData*>(data.get())->Invalidate();
    553          invalidateFrame = true;
    554          break;
    555        case layers::WebRenderUserData::UserDataType::eImageProvider:
    556          if (static_cast<layers::WebRenderImageProviderData*>(data.get())
    557                  ->Invalidate(aRequest->GetProviderId())) {
    558            break;
    559          }
    560          [[fallthrough]];
    561        default:
    562          invalidateFrame = true;
    563          break;
    564      }
    565    }
    566  }
    567 #ifdef XP_MACOSX
    568  else if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
    569    // On macOS popups are painted with fallback rendering so they don't have
    570    // webrender user data to tell us if the frame was painted last time, so we
    571    // just have to invalidate always. Bug 1754796 tracks making popups on macOS
    572    // use webrender. (On other platforms tooltips type popups are still
    573    // rendered with fallback, but we don't expect them to have images.)
    574    invalidateFrame = true;
    575  }
    576 #endif
    577 
    578  // Update ancestor rendering observers (-moz-element etc)
    579  //
    580  // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526.
    581  {
    582    nsIFrame* f = aFrame;
    583    while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
    584      SVGObserverUtils::InvalidateDirectRenderingObservers(f);
    585      f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
    586    }
    587  }
    588 
    589  if (invalidateFrame) {
    590    aFrame->SchedulePaint();
    591  }
    592 }
    593 
    594 void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags& aFwf) {
    595  if (aFwf.mFlags & Flags::IsBlockingLoadEvent) {
    596    mDocument->UnblockOnload(false);
    597    aFwf.mFlags &= ~Flags::IsBlockingLoadEvent;
    598  }
    599 }
    600 
    601 void ImageLoader::UnblockOnloadIfNeeded(nsIFrame* aFrame,
    602                                        imgIRequest* aRequest) {
    603  MOZ_ASSERT(aFrame);
    604  MOZ_ASSERT(aRequest);
    605 
    606  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
    607  if (!frameSet) {
    608    return;
    609  }
    610 
    611  size_t i =
    612      frameSet->BinaryIndexOf(FrameWithFlags(aFrame), FrameOnlyComparator());
    613  if (i != FrameSet::NoIndex) {
    614    UnblockOnloadIfNeeded(frameSet->ElementAt(i));
    615  }
    616 }
    617 
    618 // This callback is used to unblock document onload after a reflow
    619 // triggered from an image load.
    620 struct ImageLoader::ImageReflowCallback final : public nsIReflowCallback {
    621  RefPtr<ImageLoader> mLoader;
    622  WeakFrame mFrame;
    623  nsCOMPtr<imgIRequest> const mRequest;
    624 
    625  ImageReflowCallback(ImageLoader* aLoader, nsIFrame* aFrame,
    626                      imgIRequest* aRequest)
    627      : mLoader(aLoader), mFrame(aFrame), mRequest(aRequest) {}
    628 
    629  bool ReflowFinished() override;
    630  void ReflowCallbackCanceled() override;
    631 };
    632 
    633 bool ImageLoader::ImageReflowCallback::ReflowFinished() {
    634  // Check that the frame is still valid. If it isn't, then onload was
    635  // unblocked when the frame was removed from the FrameSet in
    636  // RemoveRequestToFrameMapping.
    637  if (mFrame.IsAlive()) {
    638    mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
    639  }
    640 
    641  // Get rid of this callback object.
    642  delete this;
    643 
    644  // We don't need to trigger layout.
    645  return false;
    646 }
    647 
    648 void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() {
    649  // Check that the frame is still valid. If it isn't, then onload was
    650  // unblocked when the frame was removed from the FrameSet in
    651  // RemoveRequestToFrameMapping.
    652  if (mFrame.IsAlive()) {
    653    mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
    654  }
    655 
    656  // Get rid of this callback object.
    657  delete this;
    658 }
    659 
    660 void GlobalImageObserver::Notify(imgIRequest* aRequest, int32_t aType,
    661                                 const nsIntRect* aData) {
    662  auto entry = sImages->Lookup(aRequest);
    663  MOZ_DIAGNOSTIC_ASSERT(entry);
    664  if (MOZ_UNLIKELY(!entry)) {
    665    return;
    666  }
    667 
    668  const auto loadersToNotify =
    669      ToTArray<nsTArray<RefPtr<ImageLoader>>>(entry.Data()->mImageLoaders);
    670  for (const auto& loader : loadersToNotify) {
    671    loader->Notify(aRequest, aType, aData);
    672  }
    673 }
    674 
    675 void ImageLoader::Notify(imgIRequest* aRequest, int32_t aType,
    676                         const nsIntRect* aData) {
    677  nsCString uriString;
    678  if (profiler_is_active()) {
    679    nsCOMPtr<nsIURI> uri;
    680    aRequest->GetFinalURI(getter_AddRefs(uri));
    681    if (uri) {
    682      uri->GetSpec(uriString);
    683    }
    684  }
    685 
    686  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("ImageLoader::Notify", OTHER,
    687                                   uriString.get());
    688 
    689  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
    690    nsCOMPtr<imgIContainer> image;
    691    aRequest->GetImage(getter_AddRefs(image));
    692    return OnSizeAvailable(aRequest, image);
    693  }
    694 
    695  if (aType == imgINotificationObserver::IS_ANIMATED) {
    696    return OnImageIsAnimated(aRequest);
    697  }
    698 
    699  if (aType == imgINotificationObserver::FRAME_COMPLETE) {
    700    return OnFrameComplete(aRequest);
    701  }
    702 
    703  if (aType == imgINotificationObserver::FRAME_UPDATE) {
    704    return OnFrameUpdate(aRequest);
    705  }
    706 
    707  if (aType == imgINotificationObserver::DECODE_COMPLETE) {
    708    nsCOMPtr<imgIContainer> image;
    709    aRequest->GetImage(getter_AddRefs(image));
    710    if (image && mDocument) {
    711      image->PropagateUseCounters(mDocument);
    712    }
    713  }
    714 
    715  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
    716    return OnLoadComplete(aRequest);
    717  }
    718 }
    719 
    720 void ImageLoader::OnSizeAvailable(imgIRequest* aRequest,
    721                                  imgIContainer* aImage) {
    722  nsPresContext* presContext = GetPresContext();
    723  if (!presContext) {
    724    return;
    725  }
    726 
    727  aImage->SetAnimationMode(presContext->ImageAnimationMode());
    728 
    729  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
    730  if (!frameSet) {
    731    return;
    732  }
    733 
    734  for (const FrameWithFlags& fwf : *frameSet) {
    735    if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) {
    736      fwf.mFrame->PresShell()->FrameNeedsReflow(
    737          fwf.mFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
    738          NS_FRAME_IS_DIRTY);
    739    }
    740  }
    741 }
    742 
    743 void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) {
    744  if (!mDocument) {
    745    return;
    746  }
    747 
    748  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
    749  if (!frameSet) {
    750    return;
    751  }
    752 
    753  // Register with the refresh driver now that we are aware that
    754  // we are animated.
    755  nsPresContext* presContext = GetPresContext();
    756  if (presContext) {
    757    nsLayoutUtils::RegisterImageRequest(presContext, aRequest, nullptr);
    758  }
    759 }
    760 
    761 void ImageLoader::OnFrameComplete(imgIRequest* aRequest) {
    762  ImageFrameChanged(aRequest, /* aFirstFrame = */ true);
    763 }
    764 
    765 void ImageLoader::OnFrameUpdate(imgIRequest* aRequest) {
    766  ImageFrameChanged(aRequest, /* aFirstFrame = */ false);
    767 }
    768 
    769 void ImageLoader::ImageFrameChanged(imgIRequest* aRequest, bool aFirstFrame) {
    770  if (!mDocument) {
    771    return;
    772  }
    773 
    774  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
    775  if (!frameSet) {
    776    return;
    777  }
    778 
    779  for (FrameWithFlags& fwf : *frameSet) {
    780    // Since we just finished decoding a frame, we always want to paint, in
    781    // case we're now able to paint an image that we couldn't paint before
    782    // (and hence that we don't have retained data for).
    783    const bool forceRepaint = aFirstFrame;
    784    InvalidateImages(fwf.mFrame, aRequest, forceRepaint);
    785    if (!aFirstFrame) {
    786      // We don't reflow / try to unblock onload for subsequent frame updates.
    787      continue;
    788    }
    789    if (fwf.mFlags &
    790        Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
    791      // Tell the container of the frame to reflow because the image request
    792      // has finished decoding its first frame.
    793      // FIXME(emilio): Why requesting reflow on the _parent_?
    794      nsIFrame* parent = fwf.mFrame->GetInFlowParent();
    795      parent->PresShell()->FrameNeedsReflow(
    796          parent, IntrinsicDirty::FrameAncestorsAndDescendants,
    797          NS_FRAME_IS_DIRTY);
    798      // If we need to also potentially unblock onload, do it once reflow is
    799      // done, with a reflow callback.
    800      if (fwf.mFlags & Flags::IsBlockingLoadEvent) {
    801        auto* unblocker = new ImageReflowCallback(this, fwf.mFrame, aRequest);
    802        parent->PresShell()->PostReflowCallback(unblocker);
    803      }
    804    }
    805  }
    806 }
    807 
    808 void ImageLoader::OnLoadComplete(imgIRequest* aRequest) {
    809  if (!mDocument) {
    810    return;
    811  }
    812 
    813  uint32_t status = 0;
    814  if (NS_FAILED(aRequest->GetImageStatus(&status))) {
    815    return;
    816  }
    817 
    818  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
    819  if (!frameSet) {
    820    return;
    821  }
    822 
    823  for (FrameWithFlags& fwf : *frameSet) {
    824    if (status & imgIRequest::STATUS_ERROR) {
    825      // Check if aRequest has an error state. If it does, we need to unblock
    826      // Document onload for all the frames associated with this request that
    827      // have blocked onload. This is what happens in a CORS mode violation, and
    828      // may happen during other network events.
    829      UnblockOnloadIfNeeded(fwf);
    830    }
    831    nsIFrame* frame = fwf.mFrame;
    832    if (frame->StyleVisibility()->IsVisible()) {
    833      frame->SchedulePaint();
    834    }
    835 
    836    if (StaticPrefs::dom_enable_largest_contentful_paint()) {
    837      LargestContentfulPaint::MaybeProcessImageForElementTiming(
    838          static_cast<imgRequestProxy*>(aRequest),
    839          frame->GetContent()->AsElement());
    840    }
    841  }
    842 }
    843 }  // namespace mozilla::css