tor-browser

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

nsImageLoadingContent.cpp (66705B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * A base class which implements nsIImageLoadingContent and can be
      9 * subclassed by various content nodes that want to provide image
     10 * loading functionality (eg <img>, <object>, etc).
     11 */
     12 
     13 #include "nsImageLoadingContent.h"
     14 
     15 #include "Orientation.h"
     16 #include "imgIContainer.h"
     17 #include "imgLoader.h"
     18 #include "imgRequestProxy.h"
     19 #include "mozAutoDocUpdate.h"
     20 #include "mozilla/AsyncEventDispatcher.h"
     21 #include "mozilla/AutoRestore.h"
     22 #include "mozilla/CycleCollectedJSContext.h"
     23 #include "mozilla/EventStateManager.h"
     24 #include "mozilla/PageloadEvent.h"
     25 #include "mozilla/Preferences.h"
     26 #include "mozilla/PresShell.h"
     27 #include "mozilla/SVGImageFrame.h"
     28 #include "mozilla/SVGObserverUtils.h"
     29 #include "mozilla/StaticPrefs_image.h"
     30 #include "mozilla/StaticPrefs_svg.h"
     31 #include "mozilla/dom/BindContext.h"
     32 #include "mozilla/dom/Document.h"
     33 #include "mozilla/dom/Element.h"
     34 #include "mozilla/dom/FetchPriority.h"
     35 #include "mozilla/dom/HTMLImageElement.h"
     36 #include "mozilla/dom/ImageTextBinding.h"
     37 #include "mozilla/dom/LargestContentfulPaint.h"
     38 #include "mozilla/dom/PContent.h"  // For TextRecognitionResult
     39 #include "mozilla/dom/PageLoadEventUtils.h"
     40 #include "mozilla/dom/ReferrerInfo.h"
     41 #include "mozilla/dom/ResponsiveImageSelector.h"
     42 #include "mozilla/dom/ScriptSettings.h"
     43 #include "mozilla/intl/Locale.h"
     44 #include "mozilla/intl/LocaleService.h"
     45 #include "mozilla/net/UrlClassifierFeatureFactory.h"
     46 #include "mozilla/widget/TextRecognition.h"
     47 #include "nsContentList.h"
     48 #include "nsContentPolicyUtils.h"
     49 #include "nsContentUtils.h"
     50 #include "nsError.h"
     51 #include "nsIChannel.h"
     52 #include "nsIContent.h"
     53 #include "nsIContentPolicy.h"
     54 #include "nsIFrame.h"
     55 #include "nsIScriptGlobalObject.h"
     56 #include "nsIStreamListener.h"
     57 #include "nsIURI.h"
     58 #include "nsImageFrame.h"
     59 #include "nsLayoutUtils.h"
     60 #include "nsNetUtil.h"
     61 #include "nsServiceManagerUtils.h"
     62 #include "nsThreadUtils.h"
     63 
     64 #ifdef LoadImage
     65 // Undefine LoadImage to prevent naming conflict with Windows.
     66 #  undef LoadImage
     67 #endif
     68 
     69 using namespace mozilla;
     70 using namespace mozilla::dom;
     71 
     72 #ifdef DEBUG_chb
     73 static void PrintReqURL(imgIRequest* req) {
     74  if (!req) {
     75    printf("(null req)\n");
     76    return;
     77  }
     78 
     79  nsCOMPtr<nsIURI> uri;
     80  req->GetURI(getter_AddRefs(uri));
     81  if (!uri) {
     82    printf("(null uri)\n");
     83    return;
     84  }
     85 
     86  nsAutoCString spec;
     87  uri->GetSpec(spec);
     88  printf("spec='%s'\n", spec.get());
     89 }
     90 #endif /* DEBUG_chb */
     91 
     92 class ImageLoadTask : public MicroTaskRunnable {
     93 public:
     94  ImageLoadTask(nsImageLoadingContent* aElement, bool aAlwaysLoad,
     95                bool aUseUrgentStartForChannel)
     96      : mElement(aElement),
     97        mDocument(aElement->AsContent()->OwnerDoc()),
     98        mAlwaysLoad(aAlwaysLoad),
     99        mUseUrgentStartForChannel(aUseUrgentStartForChannel) {
    100    mDocument->BlockOnload();
    101  }
    102 
    103  void Run(AutoSlowOperation& aAso) override {
    104    if (mElement->mPendingImageLoadTask == this) {
    105      JSCallingLocation::AutoFallback fallback(&mCallingLocation);
    106      mElement->mUseUrgentStartForChannel = mUseUrgentStartForChannel;
    107      mElement->ClearImageLoadTask();
    108      mElement->LoadSelectedImage(mAlwaysLoad, /* aStopLazyLoading = */ false);
    109    }
    110    mDocument->UnblockOnload(false);
    111  }
    112 
    113  bool Suppressed() override {
    114    nsIGlobalObject* global = mElement->AsContent()->GetOwnerGlobal();
    115    return global && global->IsInSyncOperation();
    116  }
    117 
    118  bool AlwaysLoad() const { return mAlwaysLoad; }
    119 
    120 private:
    121  ~ImageLoadTask() = default;
    122  const RefPtr<nsImageLoadingContent> mElement;
    123  const RefPtr<dom::Document> mDocument;
    124  const JSCallingLocation mCallingLocation{JSCallingLocation::Get()};
    125  const bool mAlwaysLoad;
    126  // True if we want to set nsIClassOfService::UrgentStart to the channel to get
    127  // the response ASAP for better user responsiveness.
    128  const bool mUseUrgentStartForChannel;
    129 };
    130 
    131 nsImageLoadingContent::nsImageLoadingContent() : mObserverList(nullptr) {
    132  if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
    133    mLoadingEnabled = false;
    134  }
    135 
    136  mMostRecentRequestChange = TimeStamp::ProcessCreation();
    137 }
    138 
    139 void nsImageLoadingContent::Destroy() {
    140  // Cancel our requests so they won't hold stale refs to us
    141  // NB: Don't ask to discard the images here.
    142  RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
    143  ClearCurrentRequest(NS_BINDING_ABORTED);
    144  ClearPendingRequest(NS_BINDING_ABORTED);
    145 }
    146 
    147 nsImageLoadingContent::~nsImageLoadingContent() {
    148  MOZ_ASSERT(!mCurrentRequest && !mPendingRequest, "Destroy not called");
    149  MOZ_ASSERT(!mObserverList.mObserver && !mObserverList.mNext,
    150             "Observers still registered?");
    151  MOZ_ASSERT(mScriptedObservers.IsEmpty(),
    152             "Scripted observers still registered?");
    153  MOZ_ASSERT(mOutstandingDecodePromises == 0,
    154             "Decode promises still unfulfilled?");
    155  MOZ_ASSERT(mDecodePromises.IsEmpty(), "Decode promises still unfulfilled?");
    156 }
    157 
    158 void nsImageLoadingContent::QueueImageTask(
    159    nsIURI* aSrcURI, nsIPrincipal* aSrcTriggeringPrincipal, bool aForceAsync,
    160    bool aAlwaysLoad, bool aNotify) {
    161  // If loading is temporarily disabled, we don't want to queue tasks that may
    162  // then run when loading is re-enabled.
    163  // Roughly step 1 and 2.
    164  // FIXME(emilio): Would be great to do this more per-spec. We don't cancel
    165  // existing loads etc.
    166  if (!LoadingEnabled() || !GetOurOwnerDoc()->ShouldLoadImages()) {
    167    return;
    168  }
    169 
    170  // Ensure that we don't overwrite a previous load request that requires
    171  // a complete load to occur.
    172  const bool alwaysLoad = aAlwaysLoad || (mPendingImageLoadTask &&
    173                                          mPendingImageLoadTask->AlwaysLoad());
    174 
    175  // Steps 5 and 7 (sync cache check for src).
    176  const bool shouldLoadSync = [&] {
    177    if (aForceAsync) {
    178      return false;
    179    }
    180    if (!aSrcURI) {
    181      // NOTE(emilio): we need to also do a sync check for empty / invalid src,
    182      // see https://github.com/whatwg/html/issues/2429
    183      // But do it sync only when there's a current request.
    184      return !!mCurrentRequest;
    185    }
    186    if (AsContent()->IsSVGElement()) {
    187      if (GetOurOwnerDoc()->IsBeingUsedAsImage()) {
    188        return true;
    189      }
    190      if (StaticPrefs::svg_image_element_force_sync_load()) {
    191        return true;
    192      }
    193    }
    194    return nsContentUtils::IsImageAvailable(
    195        AsContent(), aSrcURI, aSrcTriggeringPrincipal, GetCORSMode());
    196  }();
    197 
    198  if (shouldLoadSync) {
    199    if (!nsContentUtils::IsSafeToRunScript()) {
    200      // If not safe to run script, we should do the sync load task as soon as
    201      // possible instead. This prevents unsound state changes from frame
    202      // construction and such.
    203      void (nsImageLoadingContent::*fp)(nsIURI*, nsIPrincipal*, bool, bool,
    204                                        bool) =
    205          &nsImageLoadingContent::QueueImageTask;
    206      nsContentUtils::AddScriptRunner(
    207          NewRunnableMethod<nsIURI*, nsIPrincipal*, bool, bool, bool>(
    208              "nsImageLoadingContent::QueueImageTask", this, fp, aSrcURI,
    209              aSrcTriggeringPrincipal, aForceAsync, aAlwaysLoad,
    210              /* aNotify = */ true));
    211      return;
    212    }
    213 
    214    ClearImageLoadTask();
    215    LoadSelectedImage(alwaysLoad, mLazyLoading && aSrcURI);
    216    return;
    217  }
    218 
    219  if (mLazyLoading) {
    220    // This check is not in the spec, but it is just a performance optimization.
    221    // The reasoning for why it is sound is that we early-return from the image
    222    // task when lazy loading, and that StopLazyLoading makes us queue a new
    223    // task (which will implicitly cancel all the pre-existing tasks).
    224    return;
    225  }
    226 
    227  RefPtr task = new ImageLoadTask(this, alwaysLoad, mUseUrgentStartForChannel);
    228  mPendingImageLoadTask = task;
    229  // We might have just become non-broken.
    230  UpdateImageState(aNotify);
    231  // The task checks this to determine if it was the last queued event, and so
    232  // earlier tasks are implicitly canceled.
    233  CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
    234 }
    235 
    236 void nsImageLoadingContent::ClearImageLoadTask() {
    237  mPendingImageLoadTask = nullptr;
    238 }
    239 
    240 /*
    241 * imgINotificationObserver impl
    242 */
    243 void nsImageLoadingContent::Notify(imgIRequest* aRequest, int32_t aType,
    244                                   const nsIntRect* aData) {
    245  MOZ_ASSERT(aRequest, "no request?");
    246  MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest,
    247             "Forgot to cancel a previous request?");
    248 
    249  if (aType == imgINotificationObserver::IS_ANIMATED) {
    250    return OnImageIsAnimated(aRequest);
    251  }
    252 
    253  if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
    254    return OnUnlockedDraw();
    255  }
    256 
    257  {
    258    // Calling Notify on observers can modify the list of observers so make
    259    // a local copy.
    260    AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers;
    261    for (ImageObserver *observer = &mObserverList, *next; observer;
    262         observer = next) {
    263      next = observer->mNext;
    264      if (observer->mObserver) {
    265        observers.AppendElement(observer->mObserver);
    266      }
    267    }
    268 
    269    nsAutoScriptBlocker scriptBlocker;
    270 
    271    for (auto& observer : observers) {
    272      observer->Notify(aRequest, aType, aData);
    273    }
    274  }
    275 
    276  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
    277    uint32_t reqStatus;
    278    aRequest->GetImageStatus(&reqStatus);
    279    /* triage STATUS_ERROR */
    280    if (reqStatus & imgIRequest::STATUS_ERROR) {
    281      nsresult errorCode = NS_OK;
    282      aRequest->GetImageErrorCode(&errorCode);
    283 
    284      /* Handle image not loading error because source was a tracking URL (or
    285       * fingerprinting, cryptomining, etc).
    286       * We make a note of this image node by including it in a dedicated
    287       * array of blocked tracking nodes under its parent document.
    288       */
    289      if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
    290              errorCode)) {
    291        Document* doc = GetOurOwnerDoc();
    292        doc->AddBlockedNodeByClassifier(AsContent());
    293      }
    294    }
    295    return OnLoadComplete(aRequest, reqStatus);
    296  }
    297 
    298  if ((aType == imgINotificationObserver::FRAME_COMPLETE ||
    299       aType == imgINotificationObserver::FRAME_UPDATE) &&
    300      mCurrentRequest == aRequest) {
    301    MaybeResolveDecodePromises();
    302  }
    303 
    304  if (aType == imgINotificationObserver::DECODE_COMPLETE) {
    305    nsCOMPtr<imgIContainer> container;
    306    aRequest->GetImage(getter_AddRefs(container));
    307    if (container) {
    308      container->PropagateUseCounters(GetOurOwnerDoc());
    309    }
    310    UpdateImageState(true);
    311  }
    312 }
    313 
    314 void nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest,
    315                                           uint32_t aImageStatus) {
    316  // XXXjdm This occurs when we have a pending request created, then another
    317  //       pending request replaces it before the first one is finished.
    318  //       This begs the question of what the correct behaviour is; we used
    319  //       to not have to care because we ran this code in OnStopDecode which
    320  //       wasn't called when the first request was cancelled. For now, I choose
    321  //       to punt when the given request doesn't appear to have terminated in
    322  //       an expected state.
    323  if (!(aImageStatus &
    324        (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE))) {
    325    return;
    326  }
    327 
    328  // If the pending request is loaded, switch to it.
    329  if (aRequest == mPendingRequest) {
    330    MakePendingRequestCurrent();
    331  }
    332  MOZ_ASSERT(aRequest == mCurrentRequest,
    333             "One way or another, we should be current by now");
    334 
    335  // Fire the appropriate DOM event.
    336  if (!(aImageStatus & imgIRequest::STATUS_ERROR)) {
    337    FireEvent(u"load"_ns);
    338  } else {
    339    FireEvent(u"error"_ns);
    340  }
    341 
    342  Element* element = AsContent()->AsElement();
    343  SVGObserverUtils::InvalidateDirectRenderingObservers(element);
    344  MaybeResolveDecodePromises();
    345  LargestContentfulPaint::MaybeProcessImageForElementTiming(mCurrentRequest,
    346                                                            element);
    347  UpdateImageState(true);
    348 }
    349 
    350 void nsImageLoadingContent::OnUnlockedDraw() {
    351  // This notification is only sent for animated images. It's OK for
    352  // non-animated images to wait until the next frame visibility update to
    353  // become locked. (And that's preferable, since in the case of scrolling it
    354  // keeps memory usage minimal.)
    355  //
    356  // For animated images, though, we want to mark them visible right away so we
    357  // can call IncrementAnimationConsumers() on them and they'll start animating.
    358 
    359  nsIFrame* frame = GetOurPrimaryImageFrame();
    360  if (!frame) {
    361    return;
    362  }
    363 
    364  if (frame->GetVisibility() == Visibility::ApproximatelyVisible) {
    365    // This frame is already marked visible; there's nothing to do.
    366    return;
    367  }
    368 
    369  nsPresContext* presContext = frame->PresContext();
    370  if (!presContext) {
    371    return;
    372  }
    373 
    374  PresShell* presShell = presContext->GetPresShell();
    375  if (!presShell) {
    376    return;
    377  }
    378 
    379  presShell->EnsureFrameInApproximatelyVisibleList(frame);
    380 }
    381 
    382 void nsImageLoadingContent::OnImageIsAnimated(imgIRequest* aRequest) {
    383  bool* requestFlag = nullptr;
    384  if (aRequest == mCurrentRequest) {
    385    requestFlag = &mCurrentRequestRegistered;
    386  } else if (aRequest == mPendingRequest) {
    387    requestFlag = &mPendingRequestRegistered;
    388  } else {
    389    MOZ_ASSERT_UNREACHABLE("Which image is this?");
    390    return;
    391  }
    392  nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), aRequest,
    393                                      requestFlag);
    394 }
    395 
    396 static bool IsOurImageFrame(nsIFrame* aFrame) {
    397  if (nsImageFrame* f = do_QueryFrame(aFrame)) {
    398    return f->IsForImageLoadingContent();
    399  }
    400  return aFrame->IsSVGImageFrame() || aFrame->IsSVGFEImageFrame();
    401 }
    402 
    403 nsIFrame* nsImageLoadingContent::GetOurPrimaryImageFrame() {
    404  nsIFrame* frame = AsContent()->GetPrimaryFrame();
    405  if (!frame || !IsOurImageFrame(frame)) {
    406    return nullptr;
    407  }
    408  return frame;
    409 }
    410 
    411 /*
    412 * nsIImageLoadingContent impl
    413 */
    414 
    415 void nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled) {
    416  if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
    417    mLoadingEnabled = aLoadingEnabled;
    418  }
    419 }
    420 
    421 nsresult nsImageLoadingContent::GetSyncDecodingHint(bool* aHint) {
    422  *aHint = mSyncDecodingHint;
    423  return NS_OK;
    424 }
    425 
    426 already_AddRefed<Promise> nsImageLoadingContent::QueueDecodeAsync(
    427    ErrorResult& aRv) {
    428  Document* doc = GetOurOwnerDoc();
    429  RefPtr<Promise> promise = Promise::Create(doc->GetScopeObject(), aRv);
    430  if (aRv.Failed()) {
    431    return nullptr;
    432  }
    433 
    434  class QueueDecodeTask final : public MicroTaskRunnable {
    435   public:
    436    QueueDecodeTask(nsImageLoadingContent* aOwner, Promise* aPromise,
    437                    uint32_t aRequestGeneration)
    438        : mOwner(aOwner),
    439          mPromise(aPromise),
    440          mRequestGeneration(aRequestGeneration) {}
    441 
    442    virtual void Run(AutoSlowOperation& aAso) override {
    443      mOwner->DecodeAsync(std::move(mPromise), mRequestGeneration);
    444    }
    445 
    446    virtual bool Suppressed() override {
    447      nsIGlobalObject* global = mOwner->GetOurOwnerDoc()->GetScopeObject();
    448      return global && global->IsInSyncOperation();
    449    }
    450 
    451   private:
    452    RefPtr<nsImageLoadingContent> mOwner;
    453    RefPtr<Promise> mPromise;
    454    uint32_t mRequestGeneration;
    455  };
    456 
    457  if (++mOutstandingDecodePromises == 1) {
    458    MOZ_ASSERT(mDecodePromises.IsEmpty());
    459    doc->RegisterActivityObserver(AsContent()->AsElement());
    460  }
    461 
    462  auto task = MakeRefPtr<QueueDecodeTask>(this, promise, mRequestGeneration);
    463  CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
    464  return promise.forget();
    465 }
    466 
    467 void nsImageLoadingContent::DecodeAsync(RefPtr<Promise>&& aPromise,
    468                                        uint32_t aRequestGeneration) {
    469  MOZ_ASSERT(aPromise);
    470  MOZ_ASSERT(mOutstandingDecodePromises > mDecodePromises.Length());
    471 
    472  // The request may have gotten updated since the decode call was issued.
    473  if (aRequestGeneration != mRequestGeneration) {
    474    aPromise->MaybeReject(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
    475    // We never got placed in mDecodePromises, so we must ensure we decrement
    476    // the counter explicitly.
    477    --mOutstandingDecodePromises;
    478    MaybeDeregisterActivityObserver();
    479    return;
    480  }
    481 
    482  bool wasEmpty = mDecodePromises.IsEmpty();
    483  mDecodePromises.AppendElement(std::move(aPromise));
    484  if (wasEmpty) {
    485    MaybeResolveDecodePromises();
    486  }
    487 }
    488 
    489 void nsImageLoadingContent::MaybeResolveDecodePromises() {
    490  if (mDecodePromises.IsEmpty()) {
    491    return;
    492  }
    493 
    494  if (!mCurrentRequest) {
    495    RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
    496    return;
    497  }
    498 
    499  // Only can resolve if our document is the active document. If not we are
    500  // supposed to reject the promise, even if it was fulfilled successfully.
    501  if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
    502    RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
    503    return;
    504  }
    505 
    506  // If any error occurred while decoding, we need to reject first.
    507  uint32_t status = imgIRequest::STATUS_NONE;
    508  mCurrentRequest->GetImageStatus(&status);
    509  if (status & imgIRequest::STATUS_ERROR) {
    510    RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
    511    return;
    512  }
    513 
    514  // We need the size to bother with requesting a decode, as we are either
    515  // blocked on validation or metadata decoding.
    516  if (!(status & imgIRequest::STATUS_SIZE_AVAILABLE)) {
    517    return;
    518  }
    519 
    520  // Check the surface cache status and/or request decoding begin. We do this
    521  // before LOAD_COMPLETE because we want to start as soon as possible.
    522  uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
    523                   imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE;
    524  imgIContainer::DecodeResult decodeResult =
    525      mCurrentRequest->RequestDecodeWithResult(flags);
    526  if (decodeResult == imgIContainer::DECODE_REQUESTED) {
    527    return;
    528  }
    529  if (decodeResult == imgIContainer::DECODE_REQUEST_FAILED) {
    530    RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
    531    return;
    532  }
    533  MOZ_ASSERT(decodeResult == imgIContainer::DECODE_SURFACE_AVAILABLE);
    534 
    535  // We can only fulfill the promises once we have all the data.
    536  if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
    537    return;
    538  }
    539 
    540  for (auto& promise : mDecodePromises) {
    541    promise->MaybeResolveWithUndefined();
    542  }
    543 
    544  MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length());
    545  mOutstandingDecodePromises -= mDecodePromises.Length();
    546  mDecodePromises.Clear();
    547  MaybeDeregisterActivityObserver();
    548 }
    549 
    550 void nsImageLoadingContent::RejectDecodePromises(nsresult aStatus) {
    551  if (mDecodePromises.IsEmpty()) {
    552    return;
    553  }
    554 
    555  for (auto& promise : mDecodePromises) {
    556    promise->MaybeReject(aStatus);
    557  }
    558 
    559  MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length());
    560  mOutstandingDecodePromises -= mDecodePromises.Length();
    561  mDecodePromises.Clear();
    562  MaybeDeregisterActivityObserver();
    563 }
    564 
    565 void nsImageLoadingContent::MaybeAgeRequestGeneration(nsIURI* aNewURI) {
    566  MOZ_ASSERT(mCurrentRequest);
    567 
    568  // If the current request is about to change, we need to verify if the new
    569  // URI matches the existing current request's URI. If it doesn't, we need to
    570  // reject any outstanding promises due to the current request mutating as per
    571  // step 2.2 of the decode API requirements.
    572  //
    573  // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode
    574  if (aNewURI) {
    575    nsCOMPtr<nsIURI> currentURI;
    576    mCurrentRequest->GetURI(getter_AddRefs(currentURI));
    577 
    578    bool equal = false;
    579    if (NS_SUCCEEDED(aNewURI->Equals(currentURI, &equal)) && equal) {
    580      return;
    581    }
    582  }
    583 
    584  ++mRequestGeneration;
    585  RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
    586 }
    587 
    588 void nsImageLoadingContent::MaybeDeregisterActivityObserver() {
    589  if (mOutstandingDecodePromises == 0) {
    590    MOZ_ASSERT(mDecodePromises.IsEmpty());
    591    GetOurOwnerDoc()->UnregisterActivityObserver(AsContent()->AsElement());
    592  }
    593 }
    594 
    595 void nsImageLoadingContent::SetSyncDecodingHint(bool aHint) {
    596  if (mSyncDecodingHint == aHint) {
    597    return;
    598  }
    599 
    600  mSyncDecodingHint = aHint;
    601  MaybeForceSyncDecoding(/* aPrepareNextRequest */ false);
    602 }
    603 
    604 void nsImageLoadingContent::MaybeForceSyncDecoding(
    605    bool aPrepareNextRequest, nsIFrame* aFrame /* = nullptr */) {
    606  // GetOurPrimaryImageFrame() might not return the frame during frame init.
    607  nsIFrame* frame = aFrame ? aFrame : GetOurPrimaryImageFrame();
    608  if (!frame) {
    609    return;
    610  }
    611 
    612  bool forceSync = mSyncDecodingHint;
    613  if (!forceSync && aPrepareNextRequest) {
    614    // Detect JavaScript-based animations created by changing the |src|
    615    // attribute on a timer.
    616    TimeStamp now = TimeStamp::Now();
    617    TimeDuration threshold = TimeDuration::FromMilliseconds(
    618        StaticPrefs::image_infer_src_animation_threshold_ms());
    619 
    620    // If the length of time between request changes is less than the threshold,
    621    // then force sync decoding to eliminate flicker from the animation.
    622    forceSync = (now - mMostRecentRequestChange < threshold);
    623    mMostRecentRequestChange = now;
    624  }
    625 
    626  if (nsImageFrame* imageFrame = do_QueryFrame(frame)) {
    627    imageFrame->SetForceSyncDecoding(forceSync);
    628  } else if (SVGImageFrame* svgImageFrame = do_QueryFrame(frame)) {
    629    svgImageFrame->SetForceSyncDecoding(forceSync);
    630  }
    631 }
    632 
    633 static void ReplayImageStatus(imgIRequest* aRequest,
    634                              imgINotificationObserver* aObserver) {
    635  if (!aRequest) {
    636    return;
    637  }
    638 
    639  uint32_t status = 0;
    640  nsresult rv = aRequest->GetImageStatus(&status);
    641  if (NS_FAILED(rv)) {
    642    return;
    643  }
    644 
    645  if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
    646    aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE,
    647                      nullptr);
    648  }
    649  if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
    650    aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE,
    651                      nullptr);
    652  }
    653  if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) {
    654    aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY,
    655                      nullptr);
    656  }
    657  if (status & imgIRequest::STATUS_IS_ANIMATED) {
    658    aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr);
    659  }
    660  if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
    661    aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE,
    662                      nullptr);
    663  }
    664  if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
    665    aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE,
    666                      nullptr);
    667  }
    668 }
    669 
    670 void nsImageLoadingContent::AddNativeObserver(
    671    imgINotificationObserver* aObserver) {
    672  if (NS_WARN_IF(!aObserver)) {
    673    return;
    674  }
    675 
    676  if (!mObserverList.mObserver) {
    677    // Don't touch the linking of the list!
    678    mObserverList.mObserver = aObserver;
    679 
    680    ReplayImageStatus(mCurrentRequest, aObserver);
    681    ReplayImageStatus(mPendingRequest, aObserver);
    682 
    683    return;
    684  }
    685 
    686  // otherwise we have to create a new entry
    687 
    688  ImageObserver* observer = &mObserverList;
    689  while (observer->mNext) {
    690    observer = observer->mNext;
    691  }
    692 
    693  observer->mNext = new ImageObserver(aObserver);
    694  ReplayImageStatus(mCurrentRequest, aObserver);
    695  ReplayImageStatus(mPendingRequest, aObserver);
    696 }
    697 
    698 void nsImageLoadingContent::RemoveNativeObserver(
    699    imgINotificationObserver* aObserver) {
    700  if (NS_WARN_IF(!aObserver)) {
    701    return;
    702  }
    703 
    704  if (mObserverList.mObserver == aObserver) {
    705    mObserverList.mObserver = nullptr;
    706    // Don't touch the linking of the list!
    707    return;
    708  }
    709 
    710  // otherwise have to find it and splice it out
    711  ImageObserver* observer = &mObserverList;
    712  while (observer->mNext && observer->mNext->mObserver != aObserver) {
    713    observer = observer->mNext;
    714  }
    715 
    716  // At this point, we are pointing to the list element whose mNext is
    717  // the right observer (assuming of course that mNext is not null)
    718  if (observer->mNext) {
    719    // splice it out
    720    ImageObserver* oldObserver = observer->mNext;
    721    observer->mNext = oldObserver->mNext;
    722    oldObserver->mNext = nullptr;  // so we don't destroy them all
    723    delete oldObserver;
    724  }
    725 #ifdef DEBUG
    726  else {
    727    NS_WARNING("Asked to remove nonexistent observer");
    728  }
    729 #endif
    730 }
    731 
    732 void nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver) {
    733  if (NS_WARN_IF(!aObserver)) {
    734    return;
    735  }
    736 
    737  RefPtr<imgRequestProxy> currentReq;
    738  if (mCurrentRequest) {
    739    // Scripted observers may not belong to the same document as us, so when we
    740    // create the imgRequestProxy, we shouldn't use any. This allows the request
    741    // to dispatch notifications from the correct scheduler group.
    742    nsresult rv =
    743        mCurrentRequest->Clone(aObserver, nullptr, getter_AddRefs(currentReq));
    744    if (NS_FAILED(rv)) {
    745      return;
    746    }
    747  }
    748 
    749  RefPtr<imgRequestProxy> pendingReq;
    750  if (mPendingRequest) {
    751    // See above for why we don't use the loading document.
    752    nsresult rv =
    753        mPendingRequest->Clone(aObserver, nullptr, getter_AddRefs(pendingReq));
    754    if (NS_FAILED(rv)) {
    755      mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
    756      return;
    757    }
    758  }
    759 
    760  mScriptedObservers.AppendElement(new ScriptedImageObserver(
    761      aObserver, std::move(currentReq), std::move(pendingReq)));
    762 }
    763 
    764 void nsImageLoadingContent::RemoveObserver(
    765    imgINotificationObserver* aObserver) {
    766  if (NS_WARN_IF(!aObserver)) {
    767    return;
    768  }
    769 
    770  if (NS_WARN_IF(mScriptedObservers.IsEmpty())) {
    771    return;
    772  }
    773 
    774  RefPtr<ScriptedImageObserver> observer;
    775  auto i = mScriptedObservers.Length();
    776  do {
    777    --i;
    778    if (mScriptedObservers[i]->mObserver == aObserver) {
    779      observer = std::move(mScriptedObservers[i]);
    780      mScriptedObservers.RemoveElementAt(i);
    781      break;
    782    }
    783  } while (i > 0);
    784 
    785  if (NS_WARN_IF(!observer)) {
    786    return;
    787  }
    788 
    789  // If the cancel causes a mutation, it will be harmless, because we have
    790  // already removed the observer from the list.
    791  observer->CancelRequests();
    792 }
    793 
    794 void nsImageLoadingContent::ClearScriptedRequests(int32_t aRequestType,
    795                                                  nsresult aReason) {
    796  if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
    797    return;
    798  }
    799 
    800  nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
    801  auto i = observers.Length();
    802  do {
    803    --i;
    804 
    805    RefPtr<imgRequestProxy> req;
    806    switch (aRequestType) {
    807      case CURRENT_REQUEST:
    808        req = std::move(observers[i]->mCurrentRequest);
    809        break;
    810      case PENDING_REQUEST:
    811        req = std::move(observers[i]->mPendingRequest);
    812        break;
    813      default:
    814        NS_ERROR("Unknown request type");
    815        return;
    816    }
    817 
    818    if (req) {
    819      req->CancelAndForgetObserver(aReason);
    820    }
    821  } while (i > 0);
    822 }
    823 
    824 void nsImageLoadingContent::CloneScriptedRequests(imgRequestProxy* aRequest) {
    825  MOZ_ASSERT(aRequest);
    826 
    827  if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
    828    return;
    829  }
    830 
    831  bool current;
    832  if (aRequest == mCurrentRequest) {
    833    current = true;
    834  } else if (aRequest == mPendingRequest) {
    835    current = false;
    836  } else {
    837    MOZ_ASSERT_UNREACHABLE("Unknown request type");
    838    return;
    839  }
    840 
    841  nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
    842  auto i = observers.Length();
    843  do {
    844    --i;
    845 
    846    ScriptedImageObserver* observer = observers[i];
    847    RefPtr<imgRequestProxy>& req =
    848        current ? observer->mCurrentRequest : observer->mPendingRequest;
    849    if (NS_WARN_IF(req)) {
    850      MOZ_ASSERT_UNREACHABLE("Should have cancelled original request");
    851      req->CancelAndForgetObserver(NS_BINDING_ABORTED);
    852      req = nullptr;
    853    }
    854 
    855    nsresult rv =
    856        aRequest->Clone(observer->mObserver, nullptr, getter_AddRefs(req));
    857    (void)NS_WARN_IF(NS_FAILED(rv));
    858  } while (i > 0);
    859 }
    860 
    861 void nsImageLoadingContent::MakePendingScriptedRequestsCurrent() {
    862  if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
    863    return;
    864  }
    865 
    866  nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
    867  auto i = observers.Length();
    868  do {
    869    --i;
    870 
    871    ScriptedImageObserver* observer = observers[i];
    872    if (observer->mCurrentRequest) {
    873      observer->mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
    874    }
    875    observer->mCurrentRequest = std::move(observer->mPendingRequest);
    876  } while (i > 0);
    877 }
    878 
    879 already_AddRefed<imgIRequest> nsImageLoadingContent::GetRequest(
    880    int32_t aRequestType, ErrorResult& aError) {
    881  nsCOMPtr<imgIRequest> request;
    882  switch (aRequestType) {
    883    case CURRENT_REQUEST:
    884      request = mCurrentRequest;
    885      break;
    886    case PENDING_REQUEST:
    887      request = mPendingRequest;
    888      break;
    889    default:
    890      NS_ERROR("Unknown request type");
    891      aError.Throw(NS_ERROR_UNEXPECTED);
    892  }
    893 
    894  return request.forget();
    895 }
    896 
    897 NS_IMETHODIMP
    898 nsImageLoadingContent::GetRequest(int32_t aRequestType,
    899                                  imgIRequest** aRequest) {
    900  NS_ENSURE_ARG_POINTER(aRequest);
    901 
    902  ErrorResult result;
    903  *aRequest = GetRequest(aRequestType, result).take();
    904 
    905  return result.StealNSResult();
    906 }
    907 
    908 NS_IMETHODIMP_(void)
    909 nsImageLoadingContent::FrameCreated(nsIFrame* aFrame) {
    910  MOZ_ASSERT(aFrame, "aFrame is null");
    911  MOZ_ASSERT(IsOurImageFrame(aFrame));
    912 
    913  MaybeForceSyncDecoding(/* aPrepareNextRequest */ false, aFrame);
    914  TrackImage(mCurrentRequest, aFrame);
    915  TrackImage(mPendingRequest, aFrame);
    916 
    917  // We need to make sure that our image request is registered, if it should
    918  // be registered.
    919  nsPresContext* presContext = aFrame->PresContext();
    920  if (mCurrentRequest) {
    921    nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest,
    922                                                  &mCurrentRequestRegistered);
    923  }
    924 
    925  if (mPendingRequest) {
    926    nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest,
    927                                                  &mPendingRequestRegistered);
    928  }
    929 }
    930 
    931 NS_IMETHODIMP_(void)
    932 nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame) {
    933  NS_ASSERTION(aFrame, "aFrame is null");
    934 
    935  // We need to make sure that our image request is deregistered.
    936  nsPresContext* presContext = GetFramePresContext();
    937  if (mCurrentRequest) {
    938    nsLayoutUtils::DeregisterImageRequest(presContext, mCurrentRequest,
    939                                          &mCurrentRequestRegistered);
    940  }
    941 
    942  if (mPendingRequest) {
    943    nsLayoutUtils::DeregisterImageRequest(presContext, mPendingRequest,
    944                                          &mPendingRequestRegistered);
    945  }
    946 
    947  UntrackImage(mCurrentRequest);
    948  UntrackImage(mPendingRequest);
    949 
    950  PresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
    951  if (presShell) {
    952    presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
    953  }
    954 }
    955 
    956 /* static */
    957 nsContentPolicyType nsImageLoadingContent::PolicyTypeForLoad(
    958    ImageLoadType aImageLoadType) {
    959  if (aImageLoadType == eImageLoadType_Imageset) {
    960    return nsIContentPolicy::TYPE_IMAGESET;
    961  }
    962 
    963  MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal,
    964             "Unknown ImageLoadType type in PolicyTypeForLoad");
    965  return nsIContentPolicy::TYPE_INTERNAL_IMAGE;
    966 }
    967 
    968 int32_t nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
    969                                              ErrorResult& aError) {
    970  if (aRequest == mCurrentRequest) {
    971    return CURRENT_REQUEST;
    972  }
    973 
    974  if (aRequest == mPendingRequest) {
    975    return PENDING_REQUEST;
    976  }
    977 
    978  NS_ERROR("Unknown request");
    979  aError.Throw(NS_ERROR_UNEXPECTED);
    980  return UNKNOWN_REQUEST;
    981 }
    982 
    983 NS_IMETHODIMP
    984 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
    985                                      int32_t* aRequestType) {
    986  MOZ_ASSERT(aRequestType, "Null out param");
    987 
    988  ErrorResult result;
    989  *aRequestType = GetRequestType(aRequest, result);
    990  return result.StealNSResult();
    991 }
    992 
    993 already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentURI() {
    994  nsCOMPtr<nsIURI> uri;
    995  if (mCurrentRequest) {
    996    mCurrentRequest->GetURI(getter_AddRefs(uri));
    997  } else {
    998    uri = mCurrentURI;
    999  }
   1000 
   1001  return uri.forget();
   1002 }
   1003 
   1004 NS_IMETHODIMP
   1005 nsImageLoadingContent::GetCurrentURI(nsIURI** aURI) {
   1006  NS_ENSURE_ARG_POINTER(aURI);
   1007  *aURI = GetCurrentURI().take();
   1008  return NS_OK;
   1009 }
   1010 
   1011 already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentRequestFinalURI() {
   1012  nsCOMPtr<nsIURI> uri;
   1013  if (mCurrentRequest) {
   1014    mCurrentRequest->GetFinalURI(getter_AddRefs(uri));
   1015  }
   1016  return uri.forget();
   1017 }
   1018 
   1019 NS_IMETHODIMP
   1020 nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
   1021                                            nsIStreamListener** aListener) {
   1022  imgLoader* loader =
   1023      nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc());
   1024  if (!loader) {
   1025    return NS_ERROR_NULL_POINTER;
   1026  }
   1027 
   1028  nsCOMPtr<Document> doc = GetOurOwnerDoc();
   1029  if (!doc) {
   1030    // Don't bother
   1031    *aListener = nullptr;
   1032    return NS_OK;
   1033  }
   1034 
   1035  // XXX what should we do with content policies here, if anything?
   1036  // Shouldn't that be done before the start of the load?
   1037  // XXX what about shouldProcess?
   1038 
   1039  // Our state might change. Watch it.
   1040  auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(true); });
   1041  // Do the load.
   1042  nsCOMPtr<nsIURI> uri;
   1043  aChannel->GetOriginalURI(getter_AddRefs(uri));
   1044  RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal, uri);
   1045  nsresult rv = loader->LoadImageWithChannel(aChannel, this, doc, aListener,
   1046                                             getter_AddRefs(req));
   1047  if (NS_SUCCEEDED(rv)) {
   1048    CloneScriptedRequests(req);
   1049    TrackImage(req);
   1050    return NS_OK;
   1051  }
   1052 
   1053  MOZ_ASSERT(!req, "Shouldn't have non-null request here");
   1054  // If we don't have a current URI, we might as well store this URI so people
   1055  // know what we tried (and failed) to load.
   1056  if (!mCurrentRequest) aChannel->GetURI(getter_AddRefs(mCurrentURI));
   1057 
   1058  FireEvent(u"error"_ns);
   1059  return rv;
   1060 }
   1061 
   1062 void nsImageLoadingContent::ForceReload(bool aNotify, ErrorResult& aError) {
   1063  nsCOMPtr<nsIURI> currentURI;
   1064  GetCurrentURI(getter_AddRefs(currentURI));
   1065  if (!currentURI) {
   1066    aError.Throw(NS_ERROR_NOT_AVAILABLE);
   1067    return;
   1068  }
   1069 
   1070  // We keep this flag around along with the old URI even for failed requests
   1071  // without a live request object
   1072  ImageLoadType loadType = (mCurrentRequestFlags & REQUEST_IS_IMAGESET)
   1073                               ? eImageLoadType_Imageset
   1074                               : eImageLoadType_Normal;
   1075  nsresult rv = LoadImage(currentURI, true, aNotify, loadType,
   1076                          nsIRequest::VALIDATE_ALWAYS | LoadFlags());
   1077  if (NS_FAILED(rv)) {
   1078    aError.Throw(rv);
   1079  }
   1080 }
   1081 
   1082 /*
   1083 * Non-interface methods
   1084 */
   1085 
   1086 nsresult nsImageLoadingContent::LoadImage(const nsAString& aNewURI, bool aForce,
   1087                                          bool aNotify,
   1088                                          ImageLoadType aImageLoadType,
   1089                                          nsIPrincipal* aTriggeringPrincipal) {
   1090  // First, get a document (needed for security checks and the like)
   1091  Document* doc = GetOurOwnerDoc();
   1092  if (!doc) {
   1093    // No reason to bother, I think...
   1094    return NS_OK;
   1095  }
   1096 
   1097  // Parse the URI string to get image URI
   1098  nsCOMPtr<nsIURI> imageURI;
   1099  if (!aNewURI.IsEmpty()) {
   1100    (void)StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
   1101  }
   1102 
   1103  return LoadImage(imageURI, aForce, aNotify, aImageLoadType, LoadFlags(), doc,
   1104                   aTriggeringPrincipal);
   1105 }
   1106 
   1107 nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce,
   1108                                          bool aNotify,
   1109                                          ImageLoadType aImageLoadType,
   1110                                          nsLoadFlags aLoadFlags,
   1111                                          Document* aDocument,
   1112                                          nsIPrincipal* aTriggeringPrincipal) {
   1113  // Pending load/error events need to be canceled in some situations. This
   1114  // is not documented in the spec, but can cause site compat problems if not
   1115  // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
   1116  CancelPendingEvent();
   1117 
   1118  if (!aNewURI) {
   1119    // Cancel image requests and then fire only error event per spec.
   1120    CancelImageRequests(aNotify);
   1121    if (aImageLoadType == eImageLoadType_Normal) {
   1122      // Mark error event as cancelable only for src="" case, since only this
   1123      // error causes site compat problem (bug 1308069) for now.
   1124      FireEvent(u"error"_ns, true);
   1125    }
   1126    return NS_OK;
   1127  }
   1128 
   1129  if (!mLoadingEnabled) {
   1130    // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
   1131    // don't want/need it.
   1132    FireEvent(u"error"_ns);
   1133    return NS_OK;
   1134  }
   1135 
   1136  NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(),
   1137               "Bogus document passed in");
   1138  // First, get a document (needed for security checks and the like)
   1139  if (!aDocument) {
   1140    aDocument = GetOurOwnerDoc();
   1141    if (!aDocument) {
   1142      // No reason to bother, I think...
   1143      return NS_OK;
   1144    }
   1145  }
   1146 
   1147  // Data documents, or documents from DOMParser shouldn't perform image
   1148  // loading.
   1149  //
   1150  // FIXME(emilio): Shouldn't this check be part of
   1151  // Document::ShouldLoadImages()? Or alternatively check ShouldLoadImages here
   1152  // instead? (It seems we only check ShouldLoadImages in HTMLImageElement,
   1153  // which seems wrong...)
   1154  if (aDocument->IsLoadedAsData() && !aDocument->IsStaticDocument()) {
   1155    // Clear our pending request if we do have one.
   1156    ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
   1157 
   1158    FireEvent(u"error"_ns);
   1159    return NS_OK;
   1160  }
   1161 
   1162  // URI equality check.
   1163  //
   1164  // We skip the equality check if we don't have a current image, since in that
   1165  // case we really do want to try loading again.
   1166  if (!aForce && mCurrentRequest) {
   1167    nsCOMPtr<nsIURI> currentURI;
   1168    GetCurrentURI(getter_AddRefs(currentURI));
   1169    bool equal;
   1170    if (currentURI && NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) &&
   1171        equal) {
   1172      // Nothing to do here.
   1173      return NS_OK;
   1174    }
   1175  }
   1176 
   1177  // From this point on, our image state could change. Watch it.
   1178  auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(aNotify); });
   1179 
   1180  // Sanity check.
   1181  //
   1182  // We use the principal of aDocument to avoid having to QI |this| an extra
   1183  // time. It should always be the same as the principal of this node.
   1184  Element* element = AsContent()->AsElement();
   1185  MOZ_ASSERT(element->NodePrincipal() == aDocument->NodePrincipal(),
   1186             "Principal mismatch?");
   1187 
   1188  nsLoadFlags loadFlags =
   1189      aLoadFlags | nsContentUtils::CORSModeToLoadImageFlags(GetCORSMode());
   1190 
   1191  RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType, aNewURI);
   1192 
   1193  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
   1194  bool result = nsContentUtils::QueryTriggeringPrincipal(
   1195      element, aTriggeringPrincipal, getter_AddRefs(triggeringPrincipal));
   1196 
   1197  // If result is true, which means this node has specified
   1198  // 'triggeringprincipal' attribute on it, so we use favicon as the policy
   1199  // type.
   1200  nsContentPolicyType policyType =
   1201      result ? nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON
   1202             : PolicyTypeForLoad(aImageLoadType);
   1203 
   1204  auto referrerInfo = MakeRefPtr<ReferrerInfo>(*element);
   1205  auto fetchPriority = GetFetchPriorityForImage();
   1206  nsresult rv = nsContentUtils::LoadImage(
   1207      aNewURI, element, aDocument, triggeringPrincipal, 0, referrerInfo, this,
   1208      loadFlags, element->LocalName(), getter_AddRefs(req), policyType,
   1209      mUseUrgentStartForChannel, /* aLinkPreload */ false,
   1210      /* aEarlyHintPreloaderId */ 0, fetchPriority);
   1211 
   1212  if (fetchPriority != FetchPriority::Auto) {
   1213    aDocument->SetPageloadEventFeature(
   1214        performance::pageload_event::DocumentFeature::FETCH_PRIORITY_IMAGES);
   1215  }
   1216 
   1217  // Reset the flag to avoid loading from XPCOM or somewhere again else without
   1218  // initiated by user interaction.
   1219  mUseUrgentStartForChannel = false;
   1220 
   1221  // Tell the document to forget about the image preload, if any, for
   1222  // this URI, now that we might have another imgRequestProxy for it.
   1223  // That way if we get canceled later the image load won't continue.
   1224  aDocument->ForgetImagePreload(aNewURI);
   1225 
   1226  if (NS_SUCCEEDED(rv)) {
   1227    // Based on performance testing unsuppressing painting soon after the page
   1228    // has gotten an image may improve visual metrics.
   1229    if (Document* doc = element->GetComposedDoc()) {
   1230      if (PresShell* shell = doc->GetPresShell()) {
   1231        shell->TryUnsuppressPaintingSoon();
   1232      }
   1233    }
   1234 
   1235    CloneScriptedRequests(req);
   1236    TrackImage(req);
   1237 
   1238    // Handle cases when we just ended up with a request but it's already done.
   1239    // In that situation we have to synchronously switch that request to being
   1240    // the current request, because websites depend on that behavior.
   1241    {
   1242      uint32_t loadStatus;
   1243      if (NS_SUCCEEDED(req->GetImageStatus(&loadStatus)) &&
   1244          (loadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) {
   1245        if (req == mPendingRequest) {
   1246          MakePendingRequestCurrent();
   1247        }
   1248        MOZ_ASSERT(mCurrentRequest,
   1249                   "How could we not have a current request here?");
   1250 
   1251        if (nsImageFrame* f = do_QueryFrame(GetOurPrimaryImageFrame())) {
   1252          f->NotifyNewCurrentRequest(mCurrentRequest);
   1253        }
   1254      }
   1255    }
   1256  } else {
   1257    MOZ_ASSERT(!req, "Shouldn't have non-null request here");
   1258    // If we don't have a current URI, we might as well store this URI so people
   1259    // know what we tried (and failed) to load.
   1260    if (!mCurrentRequest) {
   1261      mCurrentURI = aNewURI;
   1262    }
   1263 
   1264    FireEvent(u"error"_ns);
   1265  }
   1266 
   1267  return NS_OK;
   1268 }
   1269 
   1270 already_AddRefed<Promise> nsImageLoadingContent::RecognizeCurrentImageText(
   1271    ErrorResult& aRv) {
   1272  using widget::TextRecognition;
   1273 
   1274  if (!mCurrentRequest) {
   1275    aRv.ThrowInvalidStateError("No current request");
   1276    return nullptr;
   1277  }
   1278  nsCOMPtr<imgIContainer> image;
   1279  mCurrentRequest->GetImage(getter_AddRefs(image));
   1280  if (!image) {
   1281    aRv.ThrowInvalidStateError("No image");
   1282    return nullptr;
   1283  }
   1284 
   1285  RefPtr<Promise> domPromise =
   1286      Promise::Create(GetOurOwnerDoc()->GetScopeObject(), aRv);
   1287  if (aRv.Failed()) {
   1288    return nullptr;
   1289  }
   1290 
   1291  // The list of ISO 639-1 language tags to pass to the text recognition API.
   1292  AutoTArray<nsCString, 4> languages;
   1293  {
   1294    // The document's locale should be the top language to use. Parse the BCP 47
   1295    // locale and extract the ISO 639-1 language tag. e.g. "en-US" -> "en".
   1296    nsAutoCString elementLanguage;
   1297    nsAtom* imgLanguage = AsContent()->GetLang();
   1298    intl::Locale locale;
   1299    if (imgLanguage) {
   1300      imgLanguage->ToUTF8String(elementLanguage);
   1301      auto result = intl::LocaleParser::TryParse(elementLanguage, locale);
   1302      if (result.isOk()) {
   1303        languages.AppendElement(locale.Language().Span());
   1304      }
   1305    }
   1306  }
   1307 
   1308  {
   1309    // The app locales should also be included after the document's locales.
   1310    // Extract the language tag like above.
   1311    nsTArray<nsCString> appLocales;
   1312    intl::LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
   1313 
   1314    for (const auto& localeString : appLocales) {
   1315      intl::Locale locale;
   1316      auto result = intl::LocaleParser::TryParse(localeString, locale);
   1317      if (result.isErr()) {
   1318        NS_WARNING("Could not parse an app locale string, ignoring it.");
   1319        continue;
   1320      }
   1321      languages.AppendElement(locale.Language().Span());
   1322    }
   1323  }
   1324 
   1325  TextRecognition::FindText(*image, languages)
   1326      ->Then(
   1327          GetCurrentSerialEventTarget(), __func__,
   1328          [weak = RefPtr{do_GetWeakReference(this)},
   1329           request = RefPtr{mCurrentRequest}, domPromise](
   1330              TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) {
   1331            if (aValue.IsReject()) {
   1332              domPromise->MaybeRejectWithNotSupportedError(
   1333                  aValue.RejectValue());
   1334              return;
   1335            }
   1336            RefPtr<nsIImageLoadingContent> iilc = do_QueryReferent(weak.get());
   1337            if (!iilc) {
   1338              domPromise->MaybeRejectWithInvalidStateError(
   1339                  "Element was dead when we got the results");
   1340              return;
   1341            }
   1342            auto* ilc = static_cast<nsImageLoadingContent*>(iilc.get());
   1343            if (ilc->mCurrentRequest != request) {
   1344              domPromise->MaybeRejectWithInvalidStateError(
   1345                  "Request not current");
   1346              return;
   1347            }
   1348            auto& textRecognitionResult = aValue.ResolveValue();
   1349            Element* el = ilc->AsContent()->AsElement();
   1350 
   1351            // When enabled, this feature will place the recognized text as
   1352            // spans inside of the shadow dom of the img element. These are then
   1353            // positioned so that the user can select the text.
   1354            if (Preferences::GetBool("dom.text-recognition.shadow-dom-enabled",
   1355                                     false)) {
   1356              el->AttachAndSetUAShadowRoot(Element::NotifyUAWidgetSetup::Yes);
   1357              TextRecognition::FillShadow(*el->GetShadowRoot(),
   1358                                          textRecognitionResult);
   1359              el->NotifyUAWidgetSetupOrChange();
   1360            }
   1361 
   1362            nsTArray<ImageText> imageTexts(
   1363                textRecognitionResult.quads().Length());
   1364            nsIGlobalObject* global = el->OwnerDoc()->GetOwnerGlobal();
   1365 
   1366            for (const auto& quad : textRecognitionResult.quads()) {
   1367              NotNull<ImageText*> imageText = imageTexts.AppendElement();
   1368 
   1369              // Note: These points are not actually CSSPixels, but a DOMQuad is
   1370              // a conveniently similar structure that can store these values.
   1371              CSSPoint points[4];
   1372              points[0] = CSSPoint(quad.points()[0].x, quad.points()[0].y);
   1373              points[1] = CSSPoint(quad.points()[1].x, quad.points()[1].y);
   1374              points[2] = CSSPoint(quad.points()[2].x, quad.points()[2].y);
   1375              points[3] = CSSPoint(quad.points()[3].x, quad.points()[3].y);
   1376 
   1377              imageText->mQuad = new DOMQuad(global, points);
   1378              imageText->mConfidence = quad.confidence();
   1379              imageText->mString = quad.string();
   1380            }
   1381            domPromise->MaybeResolve(std::move(imageTexts));
   1382          });
   1383  return domPromise.forget();
   1384 }
   1385 
   1386 CSSIntSize nsImageLoadingContent::NaturalSize(
   1387    DoDensityCorrection aDensityCorrection) {
   1388  if (!mCurrentRequest) {
   1389    return {};
   1390  }
   1391 
   1392  nsCOMPtr<imgIContainer> image;
   1393  mCurrentRequest->GetImage(getter_AddRefs(image));
   1394  if (!image) {
   1395    return {};
   1396  }
   1397 
   1398  mozilla::image::ImageIntrinsicSize intrinsicSize;
   1399  nsresult rv = image->GetIntrinsicSize(&intrinsicSize);
   1400  if (NS_FAILED(rv)) {
   1401    return {};
   1402  }
   1403 
   1404  CSSIntSize size;  // defaults to 0,0
   1405  if (!StaticPrefs::image_natural_size_fallback_enabled()) {
   1406    size.width = intrinsicSize.mWidth.valueOr(0);
   1407    size.height = intrinsicSize.mHeight.valueOr(0);
   1408  } else {
   1409    // Fallback case, for web-compatibility!
   1410    // See https://github.com/whatwg/html/issues/11287 and bug 1935269.
   1411    // If we lack an intrinsic size in either axis, then use the fallback size,
   1412    // unless we can transfer the size through the aspect ratio.
   1413    // (And if we *only* have an intrinsic aspect ratio, use the fallback width
   1414    // and transfer that through the aspect ratio to produce a height.)
   1415    size.width = intrinsicSize.mWidth.valueOr(kFallbackIntrinsicWidthInPixels);
   1416    size.height =
   1417        intrinsicSize.mHeight.valueOr(kFallbackIntrinsicHeightInPixels);
   1418    AspectRatio ratio = image->GetIntrinsicRatio();
   1419    if (ratio) {
   1420      if (!intrinsicSize.mHeight) {
   1421        // Compute the height from the width & ratio.  (Note that the width we
   1422        // use here might be kFallbackIntrinsicWidthInPixels, and that's fine.)
   1423        size.height = ratio.Inverted().ApplyTo(size.width);
   1424      } else if (!intrinsicSize.mWidth) {
   1425        // Compute the width from the height & ratio.
   1426        size.width = ratio.ApplyTo(size.height);
   1427      }
   1428    }
   1429  }
   1430 
   1431  ImageResolution resolution = image->GetResolution();
   1432  if (aDensityCorrection == DoDensityCorrection::Yes) {
   1433    // NOTE(emilio): What we implement here matches the image-set() spec, but
   1434    // it's unclear whether this is the right thing to do, see
   1435    // https://github.com/whatwg/html/pull/5574#issuecomment-826335244.
   1436    if (auto* image = HTMLImageElement::FromNode(AsContent())) {
   1437      if (auto* sel = image->GetResponsiveImageSelector()) {
   1438        float density = sel->GetSelectedImageDensity();
   1439        MOZ_ASSERT(density >= 0.0);
   1440        resolution.ScaleBy(density);
   1441      }
   1442    }
   1443  }
   1444 
   1445  resolution.ApplyTo(size.width, size.height);
   1446  return size;
   1447 }
   1448 
   1449 CSSIntSize nsImageLoadingContent::GetWidthHeightForImage() {
   1450  Element* element = AsContent()->AsElement();
   1451  if (nsIFrame* frame = element->GetPrimaryFrame(FlushType::Layout)) {
   1452    return CSSIntSize::FromAppUnitsRounded(frame->GetContentRect().Size());
   1453  }
   1454 
   1455  CSSIntSize size;
   1456  nsCOMPtr<imgIContainer> image;
   1457  if (StaticPrefs::image_natural_size_fallback_enabled()) {
   1458    // Our image is not rendered (we don't have any frame); so we should should
   1459    // return the natural size, per:
   1460    // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width
   1461    //
   1462    // Note that the spec says to use the "density-corrected natural width and
   1463    // height of the image", but we don't do that -- we specifically request
   1464    // the NaturalSize *without* density-correction here.  This handles a case
   1465    // where browsers deviate from the spec in an interoperable way, which
   1466    // hopefully we'll address in the spec soon. See case (2) in this comment
   1467    // for more:
   1468    // https://github.com/whatwg/html/issues/11287#issuecomment-2923467541
   1469    size = NaturalSize(DoDensityCorrection::No);
   1470  } else if (mCurrentRequest) {
   1471    mCurrentRequest->GetImage(getter_AddRefs(image));
   1472  }
   1473 
   1474  // If we have width or height attrs, we'll let those stomp on whatever
   1475  // NaturalSize we may have gotten above. This handles a case where browsers
   1476  // deviate from the spec in an interoperable way, which hopefully we'll
   1477  // address in the spec soon. See case (1) in this comment for more:
   1478  // https://github.com/whatwg/html/issues/11287#issuecomment-2923467541
   1479  const nsAttrValue* value;
   1480  if ((value = element->GetParsedAttr(nsGkAtoms::width)) &&
   1481      value->Type() == nsAttrValue::eInteger) {
   1482    size.width = value->GetIntegerValue();
   1483  } else if (image) {
   1484    image->GetWidth(&size.width);
   1485  }
   1486 
   1487  if ((value = element->GetParsedAttr(nsGkAtoms::height)) &&
   1488      value->Type() == nsAttrValue::eInteger) {
   1489    size.height = value->GetIntegerValue();
   1490  } else if (image) {
   1491    image->GetHeight(&size.height);
   1492  }
   1493 
   1494  NS_ASSERTION(size.width >= 0, "negative width");
   1495  NS_ASSERTION(size.height >= 0, "negative height");
   1496  return size;
   1497 }
   1498 
   1499 void nsImageLoadingContent::UpdateImageState(bool aNotify) {
   1500  Element* thisElement = AsContent()->AsElement();
   1501  const bool isBroken = [&] {
   1502    if (mLazyLoading || mPendingImageLoadTask) {
   1503      return false;
   1504    }
   1505    if (!mCurrentRequest) {
   1506      return true;
   1507    }
   1508    uint32_t currentLoadStatus;
   1509    nsresult rv = mCurrentRequest->GetImageStatus(&currentLoadStatus);
   1510    return NS_FAILED(rv) || currentLoadStatus & imgIRequest::STATUS_ERROR;
   1511  }();
   1512  thisElement->SetStates(ElementState::BROKEN, isBroken, aNotify);
   1513  if (isBroken) {
   1514    RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
   1515  }
   1516 }
   1517 
   1518 void nsImageLoadingContent::CancelImageRequests(bool aNotify) {
   1519  RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
   1520  ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
   1521  ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
   1522  UpdateImageState(aNotify);
   1523 }
   1524 
   1525 Document* nsImageLoadingContent::GetOurOwnerDoc() {
   1526  return AsContent()->OwnerDoc();
   1527 }
   1528 
   1529 Document* nsImageLoadingContent::GetOurCurrentDoc() {
   1530  return AsContent()->GetComposedDoc();
   1531 }
   1532 
   1533 nsPresContext* nsImageLoadingContent::GetFramePresContext() {
   1534  nsIFrame* frame = GetOurPrimaryImageFrame();
   1535  if (!frame) {
   1536    return nullptr;
   1537  }
   1538  return frame->PresContext();
   1539 }
   1540 
   1541 nsresult nsImageLoadingContent::StringToURI(const nsAString& aSpec,
   1542                                            Document* aDocument,
   1543                                            nsIURI** aURI) {
   1544  MOZ_ASSERT(aDocument, "Must have a document");
   1545  MOZ_ASSERT(aURI, "Null out param");
   1546 
   1547  // (1) Get the base URI
   1548  nsIContent* thisContent = AsContent();
   1549  nsIURI* baseURL = thisContent->GetBaseURI();
   1550 
   1551  // (2) Get the charset
   1552  auto encoding = aDocument->GetDocumentCharacterSet();
   1553 
   1554  // (3) Construct the silly thing
   1555  return NS_NewURI(aURI, aSpec, encoding, baseURL);
   1556 }
   1557 
   1558 nsresult nsImageLoadingContent::FireEvent(const nsAString& aEventType,
   1559                                          bool aIsCancelable) {
   1560  if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) {
   1561    // Don't bother to fire any events, especially error events.
   1562    RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
   1563    return NS_OK;
   1564  }
   1565 
   1566  // We have to fire the event asynchronously so that we won't go into infinite
   1567  // loops in cases when onLoad handlers reset the src and the new src is in
   1568  // cache.
   1569 
   1570  nsCOMPtr<nsINode> thisNode = AsContent();
   1571 
   1572  RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
   1573      new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, CanBubble::eNo,
   1574                                           ChromeOnlyDispatch::eNo);
   1575  loadBlockingAsyncDispatcher->PostDOMEvent();
   1576 
   1577  if (aIsCancelable) {
   1578    mPendingEvent = loadBlockingAsyncDispatcher;
   1579  }
   1580 
   1581  return NS_OK;
   1582 }
   1583 
   1584 void nsImageLoadingContent::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
   1585  if (mPendingEvent == aEvent) {
   1586    mPendingEvent = nullptr;
   1587  }
   1588 }
   1589 
   1590 void nsImageLoadingContent::CancelPendingEvent() {
   1591  if (mPendingEvent) {
   1592    mPendingEvent->Cancel();
   1593    mPendingEvent = nullptr;
   1594  }
   1595 }
   1596 
   1597 RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareNextRequest(
   1598    ImageLoadType aImageLoadType, nsIURI* aNewURI) {
   1599  MaybeForceSyncDecoding(/* aPrepareNextRequest */ true);
   1600 
   1601  // We only want to cancel the existing current request if size is not
   1602  // available. bz says the web depends on this behavior.
   1603  // Otherwise, we get rid of any half-baked request that might be sitting there
   1604  // and make this one current.
   1605  return HaveSize(mCurrentRequest)
   1606             ? PreparePendingRequest(aImageLoadType)
   1607             : PrepareCurrentRequest(aImageLoadType, aNewURI);
   1608 }
   1609 
   1610 RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareCurrentRequest(
   1611    ImageLoadType aImageLoadType, nsIURI* aNewURI) {
   1612  if (mCurrentRequest) {
   1613    MaybeAgeRequestGeneration(aNewURI);
   1614  }
   1615  // Get rid of anything that was there previously.
   1616  ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
   1617 
   1618  if (aImageLoadType == eImageLoadType_Imageset) {
   1619    mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
   1620  }
   1621 
   1622  // Return a reference.
   1623  return mCurrentRequest;
   1624 }
   1625 
   1626 RefPtr<imgRequestProxy>& nsImageLoadingContent::PreparePendingRequest(
   1627    ImageLoadType aImageLoadType) {
   1628  // Get rid of anything that was there previously.
   1629  ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
   1630 
   1631  if (aImageLoadType == eImageLoadType_Imageset) {
   1632    mPendingRequestFlags |= REQUEST_IS_IMAGESET;
   1633  }
   1634 
   1635  // Return a reference.
   1636  return mPendingRequest;
   1637 }
   1638 
   1639 namespace {
   1640 
   1641 class ImageRequestAutoLock {
   1642 public:
   1643  explicit ImageRequestAutoLock(imgIRequest* aRequest) : mRequest(aRequest) {
   1644    if (mRequest) {
   1645      mRequest->LockImage();
   1646    }
   1647  }
   1648 
   1649  ~ImageRequestAutoLock() {
   1650    if (mRequest) {
   1651      mRequest->UnlockImage();
   1652    }
   1653  }
   1654 
   1655 private:
   1656  nsCOMPtr<imgIRequest> mRequest;
   1657 };
   1658 
   1659 }  // namespace
   1660 
   1661 void nsImageLoadingContent::MakePendingRequestCurrent() {
   1662  MOZ_ASSERT(mPendingRequest);
   1663 
   1664  // If we have a pending request, we know that there is an existing current
   1665  // request with size information. If the pending request is for a different
   1666  // URI, then we need to reject any outstanding promises.
   1667  nsCOMPtr<nsIURI> uri;
   1668  mPendingRequest->GetURI(getter_AddRefs(uri));
   1669 
   1670  // Lock mCurrentRequest for the duration of this method.  We do this because
   1671  // PrepareCurrentRequest() might unlock mCurrentRequest.  If mCurrentRequest
   1672  // and mPendingRequest are both requests for the same image, unlocking
   1673  // mCurrentRequest before we lock mPendingRequest can cause the lock count
   1674  // to go to 0 and the image to be discarded!
   1675  ImageRequestAutoLock autoLock(mCurrentRequest);
   1676 
   1677  ImageLoadType loadType = (mPendingRequestFlags & REQUEST_IS_IMAGESET)
   1678                               ? eImageLoadType_Imageset
   1679                               : eImageLoadType_Normal;
   1680 
   1681  PrepareCurrentRequest(loadType, uri) = mPendingRequest;
   1682  MakePendingScriptedRequestsCurrent();
   1683  mPendingRequest = nullptr;
   1684  mCurrentRequestFlags = mPendingRequestFlags;
   1685  mPendingRequestFlags = 0;
   1686  mCurrentRequestRegistered = mPendingRequestRegistered;
   1687  mPendingRequestRegistered = false;
   1688 }
   1689 
   1690 void nsImageLoadingContent::ClearCurrentRequest(
   1691    nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) {
   1692  if (!mCurrentRequest) {
   1693    // Even if we didn't have a current request, we might have been keeping
   1694    // a URI and flags as a placeholder for a failed load. Clear that now.
   1695    mCurrentURI = nullptr;
   1696    mCurrentRequestFlags = 0;
   1697    return;
   1698  }
   1699  MOZ_ASSERT(!mCurrentURI,
   1700             "Shouldn't have both mCurrentRequest and mCurrentURI!");
   1701 
   1702  // Deregister this image from the refresh driver so it no longer receives
   1703  // notifications.
   1704  nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
   1705                                        &mCurrentRequestRegistered);
   1706 
   1707  // Clean up the request.
   1708  UntrackImage(mCurrentRequest, aNonvisibleAction);
   1709  ClearScriptedRequests(CURRENT_REQUEST, aReason);
   1710  mCurrentRequest->CancelAndForgetObserver(aReason);
   1711  mCurrentRequest = nullptr;
   1712  mCurrentRequestFlags = 0;
   1713 }
   1714 
   1715 void nsImageLoadingContent::ClearPendingRequest(
   1716    nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) {
   1717  if (!mPendingRequest) return;
   1718 
   1719  // Deregister this image from the refresh driver so it no longer receives
   1720  // notifications.
   1721  nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
   1722                                        &mPendingRequestRegistered);
   1723 
   1724  UntrackImage(mPendingRequest, aNonvisibleAction);
   1725  ClearScriptedRequests(PENDING_REQUEST, aReason);
   1726  mPendingRequest->CancelAndForgetObserver(aReason);
   1727  mPendingRequest = nullptr;
   1728  mPendingRequestFlags = 0;
   1729 }
   1730 
   1731 bool nsImageLoadingContent::HaveSize(imgIRequest* aImage) {
   1732  // Handle the null case
   1733  if (!aImage) return false;
   1734 
   1735  // Query the image
   1736  uint32_t status;
   1737  nsresult rv = aImage->GetImageStatus(&status);
   1738  return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE));
   1739 }
   1740 
   1741 void nsImageLoadingContent::NotifyOwnerDocumentActivityChanged() {
   1742  if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
   1743    RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
   1744  }
   1745 }
   1746 
   1747 void nsImageLoadingContent::BindToTree(BindContext& aContext,
   1748                                       nsINode& aParent) {
   1749  // We may be getting connected, if so our image should be tracked,
   1750  if (aContext.InComposedDoc()) {
   1751    TrackImage(mCurrentRequest);
   1752    TrackImage(mPendingRequest);
   1753  }
   1754 }
   1755 
   1756 void nsImageLoadingContent::UnbindFromTree() {
   1757  // We may be leaving the document, so if our image is tracked, untrack it.
   1758  nsCOMPtr<Document> doc = GetOurCurrentDoc();
   1759  if (!doc) {
   1760    return;
   1761  }
   1762 
   1763  UntrackImage(mCurrentRequest);
   1764  UntrackImage(mPendingRequest);
   1765 }
   1766 
   1767 void nsImageLoadingContent::OnVisibilityChange(
   1768    Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
   1769  switch (aNewVisibility) {
   1770    case Visibility::ApproximatelyVisible:
   1771      TrackImage(mCurrentRequest);
   1772      TrackImage(mPendingRequest);
   1773      break;
   1774 
   1775    case Visibility::ApproximatelyNonVisible:
   1776      UntrackImage(mCurrentRequest, aNonvisibleAction);
   1777      UntrackImage(mPendingRequest, aNonvisibleAction);
   1778      break;
   1779 
   1780    case Visibility::Untracked:
   1781      MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
   1782      break;
   1783  }
   1784 }
   1785 
   1786 void nsImageLoadingContent::TrackImage(imgIRequest* aImage,
   1787                                       nsIFrame* aFrame /*= nullptr */) {
   1788  if (!aImage) return;
   1789 
   1790  MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
   1791             "Why haven't we heard of this request?");
   1792 
   1793  Document* doc = GetOurCurrentDoc();
   1794  if (!doc) {
   1795    return;
   1796  }
   1797 
   1798  if (!aFrame) {
   1799    aFrame = GetOurPrimaryImageFrame();
   1800  }
   1801 
   1802  /* This line is deceptively simple. It hides a lot of subtlety. Before we
   1803   * create an nsImageFrame we call nsImageFrame::ShouldCreateImageFrameFor
   1804   * to determine if we should create an nsImageFrame or create a frame based
   1805   * on the display of the element (ie inline, block, etc). Inline, block, etc
   1806   * frames don't register for visibility tracking so they will return UNTRACKED
   1807   * from GetVisibility(). So this line is choosing to mark such images as
   1808   * visible. Once the image loads we will get an nsImageFrame and the proper
   1809   * visibility. This is a pitfall of tracking the visibility on the frames
   1810   * instead of the content node.
   1811   */
   1812  if (!aFrame ||
   1813      aFrame->GetVisibility() == Visibility::ApproximatelyNonVisible) {
   1814    return;
   1815  }
   1816 
   1817  if (aImage == mCurrentRequest &&
   1818      !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
   1819    mCurrentRequestFlags |= REQUEST_IS_TRACKED;
   1820    doc->TrackImage(mCurrentRequest);
   1821  }
   1822  if (aImage == mPendingRequest &&
   1823      !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
   1824    mPendingRequestFlags |= REQUEST_IS_TRACKED;
   1825    doc->TrackImage(mPendingRequest);
   1826  }
   1827 }
   1828 
   1829 void nsImageLoadingContent::UntrackImage(
   1830    imgIRequest* aImage, const Maybe<OnNonvisible>& aNonvisibleAction
   1831    /* = Nothing() */) {
   1832  if (!aImage) return;
   1833 
   1834  MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
   1835             "Why haven't we heard of this request?");
   1836 
   1837  // We may not be in the document.  If we outlived our document that's fine,
   1838  // because the document empties out the tracker and unlocks all locked images
   1839  // on destruction.  But if we were never in the document we may need to force
   1840  // discarding the image here, since this is the only chance we have.
   1841  Document* doc = GetOurCurrentDoc();
   1842  if (aImage == mCurrentRequest) {
   1843    if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
   1844      mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
   1845      doc->UntrackImage(mCurrentRequest,
   1846                        aNonvisibleAction == Some(OnNonvisible::DiscardImages)
   1847                            ? Document::RequestDiscard::Yes
   1848                            : Document::RequestDiscard::No);
   1849    } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) {
   1850      // If we're not in the document we may still need to be discarded.
   1851      aImage->RequestDiscard();
   1852    }
   1853  }
   1854  if (aImage == mPendingRequest) {
   1855    if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
   1856      mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
   1857      doc->UntrackImage(mPendingRequest,
   1858                        aNonvisibleAction == Some(OnNonvisible::DiscardImages)
   1859                            ? Document::RequestDiscard::Yes
   1860                            : Document::RequestDiscard::No);
   1861    } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) {
   1862      // If we're not in the document we may still need to be discarded.
   1863      aImage->RequestDiscard();
   1864    }
   1865  }
   1866 }
   1867 
   1868 CORSMode nsImageLoadingContent::GetCORSMode() { return CORS_NONE; }
   1869 
   1870 nsImageLoadingContent::ImageObserver::ImageObserver(
   1871    imgINotificationObserver* aObserver)
   1872    : mObserver(aObserver), mNext(nullptr) {
   1873  MOZ_COUNT_CTOR(ImageObserver);
   1874 }
   1875 
   1876 nsImageLoadingContent::ImageObserver::~ImageObserver() {
   1877  MOZ_COUNT_DTOR(ImageObserver);
   1878  NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
   1879 }
   1880 
   1881 nsImageLoadingContent::ScriptedImageObserver::ScriptedImageObserver(
   1882    imgINotificationObserver* aObserver,
   1883    RefPtr<imgRequestProxy>&& aCurrentRequest,
   1884    RefPtr<imgRequestProxy>&& aPendingRequest)
   1885    : mObserver(aObserver),
   1886      mCurrentRequest(aCurrentRequest),
   1887      mPendingRequest(aPendingRequest) {}
   1888 
   1889 nsImageLoadingContent::ScriptedImageObserver::~ScriptedImageObserver() {
   1890  // We should have cancelled any requests before getting released.
   1891  DebugOnly<bool> cancel = CancelRequests();
   1892  MOZ_ASSERT(!cancel, "Still have requests in ~ScriptedImageObserver!");
   1893 }
   1894 
   1895 bool nsImageLoadingContent::ScriptedImageObserver::CancelRequests() {
   1896  bool cancelled = false;
   1897  if (mCurrentRequest) {
   1898    mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
   1899    mCurrentRequest = nullptr;
   1900    cancelled = true;
   1901  }
   1902  if (mPendingRequest) {
   1903    mPendingRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
   1904    mPendingRequest = nullptr;
   1905    cancelled = true;
   1906  }
   1907  return cancelled;
   1908 }
   1909 
   1910 Element* nsImageLoadingContent::FindImageMap() {
   1911  return FindImageMap(AsContent()->AsElement());
   1912 }
   1913 
   1914 /* static */ Element* nsImageLoadingContent::FindImageMap(Element* aElement) {
   1915  nsAutoString useMap;
   1916  aElement->GetAttr(nsGkAtoms::usemap, useMap);
   1917  if (useMap.IsEmpty()) {
   1918    return nullptr;
   1919  }
   1920 
   1921  nsAString::const_iterator start, end;
   1922  useMap.BeginReading(start);
   1923  useMap.EndReading(end);
   1924 
   1925  int32_t hash = useMap.FindChar('#');
   1926  if (hash < 0) {
   1927    return nullptr;
   1928  }
   1929  // useMap contains a '#', set start to point right after the '#'
   1930  start.advance(hash + 1);
   1931 
   1932  if (start == end) {
   1933    return nullptr;  // useMap == "#"
   1934  }
   1935 
   1936  RefPtr<nsContentList> imageMapList;
   1937  if (aElement->IsInUncomposedDoc()) {
   1938    // Optimize the common case and use document level image map.
   1939    imageMapList = aElement->OwnerDoc()->ImageMapList();
   1940  } else {
   1941    // Per HTML spec image map should be searched in the element's scope,
   1942    // so using SubtreeRoot() here.
   1943    // Because this is a temporary list, we don't need to make it live.
   1944    imageMapList =
   1945        new nsContentList(aElement->SubtreeRoot(), kNameSpaceID_XHTML,
   1946                          nsGkAtoms::map, nsGkAtoms::map, true, /* deep */
   1947                          false /* live */);
   1948  }
   1949 
   1950  nsAutoString mapName(Substring(start, end));
   1951 
   1952  uint32_t i, n = imageMapList->Length(true);
   1953  for (i = 0; i < n; ++i) {
   1954    nsIContent* map = imageMapList->Item(i);
   1955    if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName,
   1956                                      eCaseMatters) ||
   1957        map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
   1958                                      mapName, eCaseMatters)) {
   1959      return map->AsElement();
   1960    }
   1961  }
   1962 
   1963  return nullptr;
   1964 }
   1965 
   1966 nsLoadFlags nsImageLoadingContent::LoadFlags() {
   1967  auto* image = HTMLImageElement::FromNode(AsContent());
   1968  if (image && image->OwnerDoc()->IsScriptEnabled() &&
   1969      !image->OwnerDoc()->IsStaticDocument() &&
   1970      image->LoadingState() == Element::Loading::Lazy) {
   1971    // Note that LOAD_BACKGROUND is not about priority of the load, but about
   1972    // whether it blocks the load event (by bypassing the loadgroup).
   1973    return nsIRequest::LOAD_BACKGROUND;
   1974  }
   1975  return nsIRequest::LOAD_NORMAL;
   1976 }
   1977 
   1978 FetchPriority nsImageLoadingContent::GetFetchPriorityForImage() const {
   1979  return FetchPriority::Auto;
   1980 }