tor-browser

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

imgRequest.cpp (39819B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 *
      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 #include "imgRequest.h"
      8 #include "ImageLogging.h"
      9 
     10 #include "imgLoader.h"
     11 #include "imgRequestProxy.h"
     12 #include "DecodePool.h"
     13 #include "ProgressTracker.h"
     14 #include "ImageFactory.h"
     15 #include "Image.h"
     16 #include "MultipartImage.h"
     17 #include "RasterImage.h"
     18 
     19 #include "nsIChannel.h"
     20 #include "nsICacheInfoChannel.h"
     21 #include "nsIClassOfService.h"
     22 #include "mozilla/dom/Document.h"
     23 #include "nsIThreadRetargetableRequest.h"
     24 #include "nsIInputStream.h"
     25 #include "nsIMultiPartChannel.h"
     26 #include "nsIHttpChannel.h"
     27 #include "nsMimeTypes.h"
     28 
     29 #include "nsIInterfaceRequestorUtils.h"
     30 #include "nsISupportsPrimitives.h"
     31 #include "nsIScriptSecurityManager.h"
     32 #include "nsComponentManagerUtils.h"
     33 #include "nsContentUtils.h"
     34 #include "nsEscape.h"
     35 
     36 #include "prtime.h"  // for PR_Now
     37 #include "nsNetUtil.h"
     38 #include "nsIProtocolHandler.h"
     39 #include "imgIRequest.h"
     40 #include "nsProperties.h"
     41 #include "nsIURL.h"
     42 
     43 #include "mozilla/IntegerPrintfMacros.h"
     44 #include "mozilla/SizeOfState.h"
     45 
     46 using namespace mozilla;
     47 using namespace mozilla::image;
     48 
     49 #define LOG_TEST(level) (MOZ_LOG_TEST(gImgLog, (level)))
     50 
     51 NS_IMPL_ISUPPORTS(imgRequest, nsIStreamListener, nsIRequestObserver,
     52                  nsIThreadRetargetableStreamListener, nsIChannelEventSink,
     53                  nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
     54 
     55 imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey)
     56    : mLoader(aLoader),
     57      mCacheKey(aCacheKey),
     58      mLoadId(nullptr),
     59      mFirstProxy(nullptr),
     60      mValidator(nullptr),
     61      mCORSMode(CORS_NONE),
     62      mImageErrorCode(NS_OK),
     63      mImageAvailable(false),
     64      mIsDeniedCrossSiteCORSRequest(false),
     65      mIsCrossSiteNoCORSRequest(false),
     66      mShouldReportRenderTimeForLCP(false),
     67      mMutex("imgRequest"),
     68      mProgressTracker(new ProgressTracker()),
     69      mIsMultiPartChannel(false),
     70      mIsInCache(false),
     71      mDecodeRequested(false),
     72      mNewPartPending(false),
     73      mHadInsecureRedirect(false),
     74      mInnerWindowId(0) {
     75  LOG_FUNC(gImgLog, "imgRequest::imgRequest()");
     76 }
     77 
     78 imgRequest::~imgRequest() {
     79  if (mLoader) {
     80    mLoader->RemoveFromUncachedImages(this);
     81  }
     82  if (mURI) {
     83    LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", "keyuri", mURI);
     84  } else
     85    LOG_FUNC(gImgLog, "imgRequest::~imgRequest()");
     86 }
     87 
     88 nsresult imgRequest::Init(
     89    nsIURI* aURI, nsIURI* aFinalURI, bool aHadInsecureRedirect,
     90    nsIRequest* aRequest, nsIChannel* aChannel, imgCacheEntry* aCacheEntry,
     91    mozilla::dom::Document* aLoadingDocument,
     92    nsIPrincipal* aTriggeringPrincipal, mozilla::CORSMode aCORSMode,
     93    nsIReferrerInfo* aReferrerInfo) MOZ_NO_THREAD_SAFETY_ANALYSIS {
     94  MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
     95  // Init() can only be called once, and that's before it can be used off
     96  // mainthread
     97 
     98  LOG_FUNC(gImgLog, "imgRequest::Init");
     99 
    100  MOZ_ASSERT(!mImage, "Multiple calls to init");
    101  MOZ_ASSERT(aURI, "No uri");
    102  MOZ_ASSERT(aFinalURI, "No final uri");
    103  MOZ_ASSERT(aRequest, "No request");
    104  MOZ_ASSERT(aChannel, "No channel");
    105 
    106  mProperties = new nsProperties();
    107  mURI = aURI;
    108  mFinalURI = aFinalURI;
    109  mRequest = aRequest;
    110  mChannel = aChannel;
    111  mTimedChannel = do_QueryInterface(mChannel);
    112  mTriggeringPrincipal = aTriggeringPrincipal;
    113  mCORSMode = aCORSMode;
    114  mReferrerInfo = aReferrerInfo;
    115 
    116  // If the original URI and the final URI are different, check whether the
    117  // original URI is secure. We deliberately don't take the final URI into
    118  // account, as it needs to be handled using more complicated rules than
    119  // earlier elements of the redirect chain.
    120  if (aURI != aFinalURI) {
    121    bool schemeLocal = false;
    122    if (NS_FAILED(NS_URIChainHasFlags(
    123            aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
    124        (!aURI->SchemeIs("https") && !aURI->SchemeIs("chrome") &&
    125         !schemeLocal)) {
    126      mHadInsecureRedirect = true;
    127    }
    128  }
    129 
    130  // imgCacheValidator may have handled redirects before we were created, so we
    131  // allow the caller to let us know if any redirects were insecure.
    132  mHadInsecureRedirect = mHadInsecureRedirect || aHadInsecureRedirect;
    133 
    134  mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink));
    135 
    136  NS_ASSERTION(mPrevChannelSink != this,
    137               "Initializing with a channel that already calls back to us!");
    138 
    139  mChannel->SetNotificationCallbacks(this);
    140 
    141  mCacheEntry = aCacheEntry;
    142  mCacheEntry->UpdateLoadTime();
    143 
    144  SetLoadId(aLoadingDocument);
    145 
    146  // Grab the inner window ID of the loading document, if possible.
    147  if (aLoadingDocument) {
    148    mInnerWindowId = aLoadingDocument->InnerWindowID();
    149  }
    150 
    151  return NS_OK;
    152 }
    153 
    154 bool imgRequest::CanReuseWithoutValidation(dom::Document* aDoc) const {
    155  // If the request's loadId is the same as the aLoadingDocument, then it is ok
    156  // to use this one because it has already been validated for this context.
    157  // XXX: nullptr seems to be a 'special' key value that indicates that NO
    158  //      validation is required.
    159  // XXX: we also check the window ID because the loadID() can return a reused
    160  //      pointer of a document. This can still happen for non-document image
    161  //      cache entries.
    162  void* key = (void*)aDoc;
    163  uint64_t innerWindowID = aDoc ? aDoc->InnerWindowID() : 0;
    164  if (LoadId() == key && InnerWindowID() == innerWindowID) {
    165    return true;
    166  }
    167 
    168  // As a special-case, if this is a print preview document, also validate on
    169  // the original document. This allows to print uncacheable images.
    170  if (dom::Document* original = aDoc ? aDoc->GetOriginalDocument() : nullptr) {
    171    return CanReuseWithoutValidation(original);
    172  }
    173 
    174  return false;
    175 }
    176 
    177 void imgRequest::ClearLoader() { mLoader = nullptr; }
    178 
    179 already_AddRefed<nsIPrincipal> imgRequest::GetTriggeringPrincipal() const {
    180  nsCOMPtr<nsIPrincipal> principal = mTriggeringPrincipal;
    181  return principal.forget();
    182 }
    183 
    184 already_AddRefed<ProgressTracker> imgRequest::GetProgressTracker() const {
    185  MutexAutoLock lock(mMutex);
    186 
    187  if (mImage) {
    188    MOZ_ASSERT(!mProgressTracker,
    189               "Should have given mProgressTracker to mImage");
    190    return mImage->GetProgressTracker();
    191  }
    192  MOZ_ASSERT(mProgressTracker,
    193             "Should have mProgressTracker until we create mImage");
    194  RefPtr<ProgressTracker> progressTracker = mProgressTracker;
    195  MOZ_ASSERT(progressTracker);
    196  return progressTracker.forget();
    197 }
    198 
    199 void imgRequest::SetCacheEntry(imgCacheEntry* entry) { mCacheEntry = entry; }
    200 
    201 bool imgRequest::HasCacheEntry() const { return mCacheEntry != nullptr; }
    202 
    203 void imgRequest::ResetCacheEntry() {
    204  if (HasCacheEntry()) {
    205    mCacheEntry->SetDataSize(0);
    206  }
    207 }
    208 
    209 void imgRequest::AddProxy(imgRequestProxy* proxy) {
    210  MOZ_ASSERT(proxy, "null imgRequestProxy passed in");
    211  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
    212 
    213  if (!mFirstProxy) {
    214    // Save a raw pointer to the first proxy we see, for use in the network
    215    // priority logic.
    216    mFirstProxy = proxy;
    217  }
    218 
    219  // If we're empty before adding, we have to tell the loader we now have
    220  // proxies.
    221  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    222  if (progressTracker->ObserverCount() == 0) {
    223    MOZ_ASSERT(mURI, "Trying to SetHasProxies without key uri.");
    224    if (mLoader) {
    225      mLoader->SetHasProxies(this);
    226    }
    227  }
    228 
    229  progressTracker->AddObserver(proxy);
    230 }
    231 
    232 nsresult imgRequest::RemoveProxy(imgRequestProxy* proxy, nsresult aStatus) {
    233  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy);
    234 
    235  // This will remove our animation consumers, so after removing
    236  // this proxy, we don't end up without proxies with observers, but still
    237  // have animation consumers.
    238  proxy->ClearAnimationConsumers();
    239 
    240  // Let the status tracker do its thing before we potentially call Cancel()
    241  // below, because Cancel() may result in OnStopRequest being called back
    242  // before Cancel() returns, leaving the image in a different state then the
    243  // one it was in at this point.
    244  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    245  if (!progressTracker->RemoveObserver(proxy)) {
    246    return NS_OK;
    247  }
    248 
    249  if (progressTracker->ObserverCount() == 0) {
    250    // If we have no observers, there's nothing holding us alive. If we haven't
    251    // been cancelled and thus removed from the cache, tell the image loader so
    252    // we can be evicted from the cache.
    253    if (mCacheEntry) {
    254      MOZ_ASSERT(mURI, "Removing last observer without key uri.");
    255 
    256      if (mLoader) {
    257        mLoader->SetHasNoProxies(this, mCacheEntry);
    258      }
    259    } else {
    260      LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry",
    261                         "uri", mURI);
    262    }
    263 
    264    /* If |aStatus| is a failure code, then cancel the load if it is still in
    265       progress.  Otherwise, let the load continue, keeping 'this' in the cache
    266       with no observers.  This way, if a proxy is destroyed without calling
    267       cancel on it, it won't leak and won't leave a bad pointer in the observer
    268       list.
    269     */
    270    if (!(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE) &&
    271        NS_FAILED(aStatus)) {
    272      LOG_MSG(gImgLog, "imgRequest::RemoveProxy",
    273              "load in progress.  canceling");
    274 
    275      this->Cancel(NS_BINDING_ABORTED);
    276    }
    277 
    278    /* break the cycle from the cache entry. */
    279    mCacheEntry = nullptr;
    280  }
    281 
    282  return NS_OK;
    283 }
    284 
    285 uint64_t imgRequest::InnerWindowID() const {
    286  MutexAutoLock lock(mMutex);
    287  return mInnerWindowId;
    288 }
    289 
    290 void imgRequest::SetInnerWindowID(uint64_t aInnerWindowId) {
    291  MutexAutoLock lock(mMutex);
    292  mInnerWindowId = aInnerWindowId;
    293 }
    294 
    295 void imgRequest::CancelAndAbort(nsresult aStatus) {
    296  LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort");
    297 
    298  Cancel(aStatus);
    299 
    300  // It's possible for the channel to fail to open after we've set our
    301  // notification callbacks. In that case, make sure to break the cycle between
    302  // the channel and us, because it won't.
    303  if (mChannel) {
    304    mChannel->SetNotificationCallbacks(mPrevChannelSink);
    305    mPrevChannelSink = nullptr;
    306  }
    307 }
    308 
    309 class imgRequestMainThreadCancel : public Runnable {
    310 public:
    311  imgRequestMainThreadCancel(imgRequest* aImgRequest, nsresult aStatus)
    312      : Runnable("imgRequestMainThreadCancel"),
    313        mImgRequest(aImgRequest),
    314        mStatus(aStatus) {
    315    MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
    316    MOZ_ASSERT(aImgRequest);
    317  }
    318 
    319  NS_IMETHOD Run() override {
    320    MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
    321    mImgRequest->ContinueCancel(mStatus);
    322    return NS_OK;
    323  }
    324 
    325 private:
    326  RefPtr<imgRequest> mImgRequest;
    327  nsresult mStatus;
    328 };
    329 
    330 void imgRequest::Cancel(nsresult aStatus) {
    331  /* The Cancel() method here should only be called by this class. */
    332  LOG_SCOPE(gImgLog, "imgRequest::Cancel");
    333 
    334  if (NS_IsMainThread()) {
    335    ContinueCancel(aStatus);
    336  } else {
    337    nsCOMPtr<nsIEventTarget> eventTarget = GetMainThreadSerialEventTarget();
    338    nsCOMPtr<nsIRunnable> ev = new imgRequestMainThreadCancel(this, aStatus);
    339    eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
    340  }
    341 }
    342 
    343 void imgRequest::ContinueCancel(nsresult aStatus) {
    344  MOZ_ASSERT(NS_IsMainThread());
    345 
    346  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    347  progressTracker->SyncNotifyProgress(FLAG_HAS_ERROR);
    348 
    349  RemoveFromCache();
    350 
    351  if (mRequest && !(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE)) {
    352    mRequest->CancelWithReason(aStatus, "imgRequest::ContinueCancel"_ns);
    353  }
    354 }
    355 
    356 class imgRequestMainThreadEvict : public Runnable {
    357 public:
    358  explicit imgRequestMainThreadEvict(imgRequest* aImgRequest)
    359      : Runnable("imgRequestMainThreadEvict"), mImgRequest(aImgRequest) {
    360    MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
    361    MOZ_ASSERT(aImgRequest);
    362  }
    363 
    364  NS_IMETHOD Run() override {
    365    MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
    366    mImgRequest->ContinueEvict();
    367    return NS_OK;
    368  }
    369 
    370 private:
    371  RefPtr<imgRequest> mImgRequest;
    372 };
    373 
    374 // EvictFromCache() is written to allowed to get called from any thread
    375 void imgRequest::EvictFromCache() {
    376  /* The EvictFromCache() method here should only be called by this class. */
    377  LOG_SCOPE(gImgLog, "imgRequest::EvictFromCache");
    378 
    379  if (NS_IsMainThread()) {
    380    ContinueEvict();
    381  } else {
    382    NS_DispatchToMainThread(new imgRequestMainThreadEvict(this));
    383  }
    384 }
    385 
    386 // Helper-method used by EvictFromCache()
    387 void imgRequest::ContinueEvict() {
    388  MOZ_ASSERT(NS_IsMainThread());
    389 
    390  RemoveFromCache();
    391 }
    392 
    393 void imgRequest::StartDecoding() {
    394  MutexAutoLock lock(mMutex);
    395  mDecodeRequested = true;
    396 }
    397 
    398 bool imgRequest::IsDecodeRequested() const {
    399  MutexAutoLock lock(mMutex);
    400  return mDecodeRequested;
    401 }
    402 
    403 nsresult imgRequest::GetURI(nsIURI** aURI) {
    404  MOZ_ASSERT(aURI);
    405 
    406  LOG_FUNC(gImgLog, "imgRequest::GetURI");
    407 
    408  if (mURI) {
    409    *aURI = mURI;
    410    NS_ADDREF(*aURI);
    411    return NS_OK;
    412  }
    413 
    414  return NS_ERROR_FAILURE;
    415 }
    416 
    417 nsresult imgRequest::GetFinalURI(nsIURI** aURI) {
    418  MOZ_ASSERT(aURI);
    419 
    420  LOG_FUNC(gImgLog, "imgRequest::GetFinalURI");
    421 
    422  if (mFinalURI) {
    423    *aURI = mFinalURI;
    424    NS_ADDREF(*aURI);
    425    return NS_OK;
    426  }
    427 
    428  return NS_ERROR_FAILURE;
    429 }
    430 
    431 bool imgRequest::IsChrome() const { return mURI->SchemeIs("chrome"); }
    432 
    433 bool imgRequest::IsData() const { return mURI->SchemeIs("data"); }
    434 
    435 nsresult imgRequest::GetImageErrorCode() { return mImageErrorCode; }
    436 
    437 void imgRequest::RemoveFromCache() {
    438  LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache");
    439 
    440  bool isInCache = false;
    441 
    442  {
    443    MutexAutoLock lock(mMutex);
    444    isInCache = mIsInCache;
    445  }
    446 
    447  if (isInCache && mLoader) {
    448    // mCacheEntry is nulled out when we have no more observers.
    449    if (mCacheEntry) {
    450      mLoader->RemoveFromCache(mCacheEntry);
    451    } else {
    452      mLoader->RemoveFromCache(mCacheKey);
    453    }
    454  }
    455 
    456  mCacheEntry = nullptr;
    457 }
    458 
    459 bool imgRequest::HasConsumers() const {
    460  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    461  return progressTracker && progressTracker->ObserverCount() > 0;
    462 }
    463 
    464 already_AddRefed<image::Image> imgRequest::GetImage() const {
    465  MutexAutoLock lock(mMutex);
    466  RefPtr<image::Image> image = mImage;
    467  return image.forget();
    468 }
    469 
    470 void imgRequest::GetFileName(nsACString& aFileName) {
    471  nsAutoString fileName;
    472 
    473  nsCOMPtr<nsISupportsCString> supportscstr;
    474  if (NS_SUCCEEDED(mProperties->Get("content-disposition",
    475                                    NS_GET_IID(nsISupportsCString),
    476                                    getter_AddRefs(supportscstr))) &&
    477      supportscstr) {
    478    nsAutoCString cdHeader;
    479    supportscstr->GetData(cdHeader);
    480    NS_GetFilenameFromDisposition(fileName, cdHeader);
    481  }
    482 
    483  if (fileName.IsEmpty()) {
    484    nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(mURI));
    485    if (imgUrl) {
    486      imgUrl->GetFileName(aFileName);
    487      NS_UnescapeURL(aFileName);
    488    }
    489  } else {
    490    aFileName = NS_ConvertUTF16toUTF8(fileName);
    491  }
    492 }
    493 
    494 int32_t imgRequest::Priority() const {
    495  int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
    496  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
    497  if (p) {
    498    p->GetPriority(&priority);
    499  }
    500  return priority;
    501 }
    502 
    503 void imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta) {
    504  // only the first proxy is allowed to modify the priority of this image load.
    505  //
    506  // XXX(darin): this is probably not the most optimal algorithm as we may want
    507  // to increase the priority of requests that have a lot of proxies.  the key
    508  // concern though is that image loads remain lower priority than other pieces
    509  // of content such as link clicks, CSS, and JS.
    510  //
    511  if (!mFirstProxy || proxy != mFirstProxy) {
    512    return;
    513  }
    514 
    515  AdjustPriorityInternal(delta);
    516 }
    517 
    518 void imgRequest::AdjustPriorityInternal(int32_t aDelta) {
    519  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
    520  if (p) {
    521    p->AdjustPriority(aDelta);
    522  }
    523 }
    524 
    525 void imgRequest::BoostPriority(uint32_t aCategory) {
    526  if (!StaticPrefs::image_layout_network_priority()) {
    527    return;
    528  }
    529 
    530  uint32_t newRequestedCategory =
    531      (mBoostCategoriesRequested & aCategory) ^ aCategory;
    532  if (!newRequestedCategory) {
    533    // priority boost for each category can only apply once.
    534    return;
    535  }
    536 
    537  MOZ_LOG(gImgLog, LogLevel::Debug,
    538          ("[this=%p] imgRequest::BoostPriority for category %x", this,
    539           newRequestedCategory));
    540 
    541  int32_t delta = 0;
    542 
    543  if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_INIT) {
    544    --delta;
    545  }
    546 
    547  if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_STYLE) {
    548    --delta;
    549  }
    550 
    551  if (newRequestedCategory & imgIRequest::CATEGORY_SIZE_QUERY) {
    552    --delta;
    553  }
    554 
    555  if (newRequestedCategory & imgIRequest::CATEGORY_DISPLAY) {
    556    delta += nsISupportsPriority::PRIORITY_HIGH;
    557  }
    558 
    559  AdjustPriorityInternal(delta);
    560  mBoostCategoriesRequested |= newRequestedCategory;
    561 }
    562 
    563 void imgRequest::SetIsInCache(bool aInCache) {
    564  LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::SetIsCacheable", "aInCache",
    565                      aInCache);
    566  MutexAutoLock lock(mMutex);
    567  mIsInCache = aInCache;
    568 }
    569 
    570 void imgRequest::UpdateCacheEntrySize() {
    571  if (!mCacheEntry) {
    572    return;
    573  }
    574 
    575  RefPtr<Image> image = GetImage();
    576  SizeOfState state(moz_malloc_size_of);
    577  size_t size = image->SizeOfSourceWithComputedFallback(state);
    578  mCacheEntry->SetDataSize(size);
    579 }
    580 
    581 void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry,
    582                                    nsIRequest* aRequest,
    583                                    bool aForceTouch /* = false */) {
    584  if (!aCacheEntry) {
    585    return;
    586  }
    587 
    588  RefPtr<imgRequest> req = aCacheEntry->GetRequest();
    589  MOZ_ASSERT(req);
    590  RefPtr<nsIURI> uri;
    591  req->GetURI(getter_AddRefs(uri));
    592  // TODO(emilio): Seems we should be able to assert `uri` is not null, but we
    593  // get here in such cases sometimes (like for some redirects, see
    594  // docshell/test/chrome/test_bug89419.xhtml).
    595  //
    596  // We have the original URI in the cache key though, probably we should be
    597  // using that instead of relying on Init() getting called.
    598  auto info = nsContentUtils::GetSubresourceCacheValidationInfo(aRequest, uri);
    599 
    600  // Expiration time defaults to 0. We set the expiration time on our entry if
    601  // it hasn't been set yet.
    602  if (!info.mExpirationTime) {
    603    // If the channel doesn't support caching, then ensure this expires the
    604    // next time it is used.
    605    info.mExpirationTime.emplace(CacheExpirationTime::AlreadyExpired());
    606  }
    607  aCacheEntry->AccumulateExpiryTime(*info.mExpirationTime, aForceTouch);
    608  // Cache entries default to not needing to validate. We ensure that
    609  // multiple calls to this function don't override an earlier decision to
    610  // validate by making validation a one-way decision.
    611  if (info.mMustRevalidate) {
    612    aCacheEntry->SetMustValidate(info.mMustRevalidate);
    613  }
    614 }
    615 
    616 bool imgRequest::GetMultipart() const {
    617  MutexAutoLock lock(mMutex);
    618  return mIsMultiPartChannel;
    619 }
    620 
    621 bool imgRequest::HadInsecureRedirect() const {
    622  MutexAutoLock lock(mMutex);
    623  return mHadInsecureRedirect;
    624 }
    625 
    626 /** nsIRequestObserver methods **/
    627 
    628 NS_IMETHODIMP
    629 imgRequest::OnStartRequest(nsIRequest* aRequest) {
    630  LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
    631 
    632  RefPtr<Image> image;
    633 
    634  if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) {
    635    nsresult rv;
    636    nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
    637    mIsDeniedCrossSiteCORSRequest =
    638        loadInfo->GetTainting() == LoadTainting::CORS &&
    639        (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv));
    640    mIsCrossSiteNoCORSRequest = loadInfo->GetTainting() == LoadTainting::Opaque;
    641  }
    642 
    643  UpdateShouldReportRenderTimeForLCP();
    644  // Figure out if we're multipart.
    645  nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
    646  {
    647    MutexAutoLock lock(mMutex);
    648 
    649    MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel,
    650               "Stopped being multipart?");
    651 
    652    mNewPartPending = true;
    653    image = mImage;
    654    mIsMultiPartChannel = bool(multiPartChannel);
    655  }
    656 
    657  // If we're not multipart, we shouldn't have an image yet.
    658  if (image && !multiPartChannel) {
    659    MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request");
    660    Cancel(NS_IMAGELIB_ERROR_FAILURE);
    661    return NS_ERROR_FAILURE;
    662  }
    663 
    664  /*
    665   * If mRequest is null here, then we need to set it so that we'll be able to
    666   * cancel it if our Cancel() method is called.  Note that this can only
    667   * happen for multipart channels.  We could simply not null out mRequest for
    668   * non-last parts, if GetIsLastPart() were reliable, but it's not.  See
    669   * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
    670   */
    671  if (!mRequest) {
    672    MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart");
    673    nsCOMPtr<nsIChannel> baseChannel;
    674    multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
    675    mRequest = baseChannel;
    676  }
    677 
    678  nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
    679  if (channel) {
    680    /* Get our principal */
    681    nsCOMPtr<nsIScriptSecurityManager> secMan =
    682        nsContentUtils::GetSecurityManager();
    683    if (secMan) {
    684      nsresult rv = secMan->GetChannelResultPrincipal(
    685          channel, getter_AddRefs(mPrincipal));
    686      if (NS_FAILED(rv)) {
    687        return rv;
    688      }
    689    }
    690  }
    691 
    692  SetCacheValidation(mCacheEntry, aRequest, /* aForceTouch = */ true);
    693 
    694  // Shouldn't we be dead already if this gets hit?
    695  // Probably multipart/x-mixed-replace...
    696  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    697  if (progressTracker->ObserverCount() == 0) {
    698    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
    699  }
    700 
    701  // Try to retarget OnDataAvailable to a decode thread. We must process data
    702  // URIs synchronously as per the spec however.
    703  if (!channel || IsData()) {
    704    return NS_OK;
    705  }
    706 
    707  nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
    708      do_QueryInterface(aRequest);
    709  if (retargetable) {
    710    nsAutoCString mimeType;
    711    nsresult rv = channel->GetContentType(mimeType);
    712    if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) {
    713      mOffMainThreadData = true;
    714      // Retarget OnDataAvailable to the DecodePool's IO thread.
    715      nsCOMPtr<nsISerialEventTarget> target =
    716          DecodePool::Singleton()->GetIOEventTarget();
    717      rv = retargetable->RetargetDeliveryTo(target);
    718      MOZ_LOG(gImgLog, LogLevel::Warning,
    719              ("[this=%p] imgRequest::OnStartRequest -- "
    720               "RetargetDeliveryTo rv %" PRIu32 "=%s\n",
    721               this, static_cast<uint32_t>(rv),
    722               NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
    723    }
    724  }
    725 
    726  return NS_OK;
    727 }
    728 
    729 NS_IMETHODIMP
    730 imgRequest::OnStopRequest(nsIRequest* aRequest, nsresult status) {
    731  LOG_FUNC(gImgLog, "imgRequest::OnStopRequest");
    732  MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread");
    733 
    734  RefPtr<imgRequest> strongThis = this;
    735 
    736  bool isMultipart = false;
    737  bool newPartPending = false;
    738  {
    739    MutexAutoLock lock(mMutex);
    740    isMultipart = mIsMultiPartChannel;
    741    newPartPending = mNewPartPending;
    742  }
    743  if (isMultipart && newPartPending) {
    744    OnDataAvailable(aRequest, nullptr, 0, 0);
    745  }
    746 
    747  // Get this after OnDataAvailable because that might have created the image.
    748  RefPtr<Image> image = GetImage();
    749 
    750  // XXXldb What if this is a non-last part of a multipart request?
    751  // xxx before we release our reference to mRequest, lets
    752  // save the last status that we saw so that the
    753  // imgRequestProxy will have access to it.
    754  if (mRequest) {
    755    mRequest = nullptr;  // we no longer need the request
    756  }
    757 
    758  // stop holding a ref to the channel, since we don't need it anymore
    759  if (mChannel) {
    760    mChannel->SetNotificationCallbacks(mPrevChannelSink);
    761    mPrevChannelSink = nullptr;
    762    mChannel = nullptr;
    763  }
    764 
    765  bool lastPart = true;
    766  nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
    767  if (mpchan) {
    768    mpchan->GetIsLastPart(&lastPart);
    769  }
    770 
    771  bool isPartial = false;
    772  if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) {
    773    isPartial = true;
    774    status = NS_OK;  // fake happy face
    775  }
    776 
    777  // Tell the image that it has all of the source data. Note that this can
    778  // trigger a failure, since the image might be waiting for more non-optional
    779  // data and this is the point where we break the news that it's not coming.
    780  if (image) {
    781    nsresult rv = image->OnImageDataComplete(aRequest, status, lastPart);
    782 
    783    // If we got an error in the OnImageDataComplete() call, we don't want to
    784    // proceed as if nothing bad happened. However, we also want to give
    785    // precedence to failure status codes from necko, since presumably they're
    786    // more meaningful.
    787    if (NS_FAILED(rv) && NS_SUCCEEDED(status)) {
    788      status = rv;
    789    }
    790  }
    791 
    792  // If the request went through, update the cache entry size. Otherwise,
    793  // cancel the request, which removes us from the cache.
    794  if (image && NS_SUCCEEDED(status) && !isPartial) {
    795    // We update the cache entry size here because this is where we finish
    796    // loading compressed source data, which is part of our size calculus.
    797    UpdateCacheEntrySize();
    798 
    799  } else if (isPartial) {
    800    // Remove the partial image from the cache.
    801    this->EvictFromCache();
    802 
    803  } else {
    804    mImageErrorCode = status;
    805 
    806    // if the error isn't "just" a partial transfer
    807    // stops animations, removes from cache
    808    this->Cancel(status);
    809  }
    810 
    811  if (!image) {
    812    // We have to fire the OnStopRequest notifications ourselves because there's
    813    // no image capable of doing so.
    814    Progress progress =
    815        LoadCompleteProgress(lastPart, /* aError = */ false, status);
    816 
    817    RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    818    progressTracker->SyncNotifyProgress(progress);
    819  }
    820 
    821  mTimedChannel = nullptr;
    822  return NS_OK;
    823 }
    824 
    825 struct mimetype_closure {
    826  nsACString* newType;
    827 };
    828 
    829 /* prototype for these defined below */
    830 static nsresult sniff_mimetype_callback(nsIInputStream* in, void* closure,
    831                                        const char* fromRawSegment,
    832                                        uint32_t toOffset, uint32_t count,
    833                                        uint32_t* writeCount);
    834 
    835 /** nsThreadRetargetableStreamListener methods **/
    836 NS_IMETHODIMP
    837 imgRequest::CheckListenerChain() {
    838  return mOffMainThreadData ? NS_OK : NS_ERROR_NO_INTERFACE;
    839 }
    840 
    841 NS_IMETHODIMP
    842 imgRequest::OnDataFinished(nsresult) { return NS_OK; }
    843 
    844 /** nsIStreamListener methods **/
    845 
    846 struct NewPartResult final {
    847  explicit NewPartResult(image::Image* aExistingImage)
    848      : mImage(aExistingImage),
    849        mIsFirstPart(!aExistingImage),
    850        mSucceeded(false),
    851        mShouldResetCacheEntry(false) {}
    852 
    853  nsAutoCString mContentType;
    854  nsAutoCString mContentDisposition;
    855  RefPtr<image::Image> mImage;
    856  const bool mIsFirstPart;
    857  bool mSucceeded;
    858  bool mShouldResetCacheEntry;
    859 };
    860 
    861 static NewPartResult PrepareForNewPart(nsIRequest* aRequest,
    862                                       nsIInputStream* aInStr, uint32_t aCount,
    863                                       nsIURI* aURI, bool aIsMultipart,
    864                                       image::Image* aExistingImage,
    865                                       ProgressTracker* aProgressTracker,
    866                                       uint64_t aInnerWindowId) {
    867  NewPartResult result(aExistingImage);
    868 
    869  if (aInStr) {
    870    mimetype_closure closure;
    871    closure.newType = &result.mContentType;
    872 
    873    // Look at the first few bytes and see if we can tell what the data is from
    874    // that since servers tend to lie. :(
    875    uint32_t out;
    876    aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out);
    877  }
    878 
    879  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
    880  if (result.mContentType.IsEmpty()) {
    881    nsresult rv =
    882        chan ? chan->GetContentType(result.mContentType) : NS_ERROR_FAILURE;
    883    if (NS_FAILED(rv)) {
    884      MOZ_LOG(gImgLog, LogLevel::Error,
    885              ("imgRequest::PrepareForNewPart -- "
    886               "Content type unavailable from the channel\n"));
    887      if (!aIsMultipart) {
    888        return result;
    889      }
    890    }
    891  }
    892 
    893  if (chan) {
    894    chan->GetContentDispositionHeader(result.mContentDisposition);
    895  }
    896 
    897  MOZ_LOG(gImgLog, LogLevel::Debug,
    898          ("imgRequest::PrepareForNewPart -- Got content type %s\n",
    899           result.mContentType.get()));
    900 
    901  // XXX If server lied about mimetype and it's SVG, we may need to copy
    902  // the data and dispatch back to the main thread, AND tell the channel to
    903  // dispatch there in the future.
    904 
    905  // Create the new image and give it ownership of our ProgressTracker.
    906  if (aIsMultipart) {
    907    // Create the ProgressTracker and image for this part.
    908    RefPtr<ProgressTracker> progressTracker = new ProgressTracker();
    909    RefPtr<image::Image> partImage = image::ImageFactory::CreateImage(
    910        aRequest, progressTracker, result.mContentType, aURI,
    911        /* aIsMultipart = */ true, aInnerWindowId);
    912 
    913    if (result.mIsFirstPart) {
    914      // First part for a multipart channel. Create the MultipartImage wrapper.
    915      MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
    916      aProgressTracker->SetIsMultipart();
    917      result.mImage = image::ImageFactory::CreateMultipartImage(
    918          partImage, aProgressTracker);
    919    } else {
    920      // Transition to the new part.
    921      auto multipartImage = static_cast<MultipartImage*>(aExistingImage);
    922      multipartImage->BeginTransitionToPart(partImage);
    923 
    924      // Reset our cache entry size so it doesn't keep growing without bound.
    925      result.mShouldResetCacheEntry = true;
    926    }
    927  } else {
    928    MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?");
    929    MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
    930 
    931    // Create an image using our progress tracker.
    932    result.mImage = image::ImageFactory::CreateImage(
    933        aRequest, aProgressTracker, result.mContentType, aURI,
    934        /* aIsMultipart = */ false, aInnerWindowId);
    935  }
    936 
    937  MOZ_ASSERT(result.mImage);
    938  if (!result.mImage->HasError() || aIsMultipart) {
    939    // We allow multipart images to fail to initialize (which generally
    940    // indicates a bad content type) without cancelling the load, because
    941    // subsequent parts might be fine.
    942    result.mSucceeded = true;
    943  }
    944 
    945  return result;
    946 }
    947 
    948 class FinishPreparingForNewPartRunnable final : public Runnable {
    949 public:
    950  FinishPreparingForNewPartRunnable(imgRequest* aImgRequest,
    951                                    NewPartResult&& aResult)
    952      : Runnable("FinishPreparingForNewPartRunnable"),
    953        mImgRequest(aImgRequest),
    954        mResult(aResult) {
    955    MOZ_ASSERT(aImgRequest);
    956  }
    957 
    958  NS_IMETHOD Run() override {
    959    mImgRequest->FinishPreparingForNewPart(mResult);
    960    return NS_OK;
    961  }
    962 
    963 private:
    964  RefPtr<imgRequest> mImgRequest;
    965  NewPartResult mResult;
    966 };
    967 
    968 void imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult) {
    969  MOZ_ASSERT(NS_IsMainThread());
    970 
    971  mContentType = aResult.mContentType;
    972 
    973  SetProperties(aResult.mContentType, aResult.mContentDisposition);
    974 
    975  if (aResult.mIsFirstPart) {
    976    // Notify listeners that we have an image.
    977    mImageAvailable = true;
    978    RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
    979    progressTracker->OnImageAvailable();
    980    MOZ_ASSERT(progressTracker->HasImage());
    981  }
    982 
    983  if (aResult.mShouldResetCacheEntry) {
    984    ResetCacheEntry();
    985  }
    986 
    987  if (IsDecodeRequested()) {
    988    aResult.mImage->StartDecoding(imgIContainer::FLAG_NONE);
    989  }
    990 }
    991 
    992 bool imgRequest::ImageAvailable() const { return mImageAvailable; }
    993 
    994 NS_IMETHODIMP
    995 imgRequest::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
    996                            uint64_t aOffset, uint32_t aCount) {
    997  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "count", aCount);
    998 
    999  NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
   1000 
   1001  RefPtr<Image> image;
   1002  RefPtr<ProgressTracker> progressTracker;
   1003  bool isMultipart = false;
   1004  bool newPartPending = false;
   1005  uint64_t innerWindowId = 0;
   1006 
   1007  // Retrieve and update our state.
   1008  {
   1009    MutexAutoLock lock(mMutex);
   1010    image = mImage;
   1011    progressTracker = mProgressTracker;
   1012    isMultipart = mIsMultiPartChannel;
   1013    newPartPending = mNewPartPending;
   1014    mNewPartPending = false;
   1015    innerWindowId = mInnerWindowId;
   1016  }
   1017 
   1018  // If this is a new part, we need to sniff its content type and create an
   1019  // appropriate image.
   1020  if (newPartPending) {
   1021    NewPartResult result =
   1022        PrepareForNewPart(aRequest, aInStr, aCount, mURI, isMultipart, image,
   1023                          progressTracker, innerWindowId);
   1024    bool succeeded = result.mSucceeded;
   1025 
   1026    if (result.mImage) {
   1027      image = result.mImage;
   1028 
   1029      // Update our state to reflect this new part.
   1030      {
   1031        MutexAutoLock lock(mMutex);
   1032        mImage = image;
   1033 
   1034        mProgressTracker = nullptr;
   1035      }
   1036 
   1037      // We only get an event target if we are not on the main thread, because
   1038      // we have to dispatch in that case.
   1039      nsCOMPtr<nsIEventTarget> eventTarget;
   1040      if (!NS_IsMainThread()) {
   1041        eventTarget = GetMainThreadSerialEventTarget();
   1042        MOZ_ASSERT(eventTarget);
   1043      }
   1044 
   1045      // Some property objects are not threadsafe, and we need to send
   1046      // OnImageAvailable on the main thread, so finish on the main thread.
   1047      if (!eventTarget) {
   1048        MOZ_ASSERT(NS_IsMainThread());
   1049        FinishPreparingForNewPart(result);
   1050      } else {
   1051        nsCOMPtr<nsIRunnable> runnable =
   1052            new FinishPreparingForNewPartRunnable(this, std::move(result));
   1053        eventTarget->Dispatch(CreateRenderBlockingRunnable(runnable.forget()),
   1054                              NS_DISPATCH_NORMAL);
   1055      }
   1056    }
   1057 
   1058    if (!succeeded) {
   1059      // Something went wrong; probably a content type issue.
   1060      Cancel(NS_IMAGELIB_ERROR_FAILURE);
   1061      return NS_BINDING_ABORTED;
   1062    }
   1063  }
   1064 
   1065  // Notify the image that it has new data.
   1066  if (aInStr) {
   1067    nsresult rv =
   1068        image->OnImageDataAvailable(aRequest, aInStr, aOffset, aCount);
   1069 
   1070    if (NS_FAILED(rv)) {
   1071      MOZ_LOG(gImgLog, LogLevel::Warning,
   1072              ("[this=%p] imgRequest::OnDataAvailable -- "
   1073               "copy to RasterImage failed\n",
   1074               this));
   1075      Cancel(NS_IMAGELIB_ERROR_FAILURE);
   1076      return NS_BINDING_ABORTED;
   1077    }
   1078  }
   1079 
   1080  return NS_OK;
   1081 }
   1082 
   1083 void imgRequest::SetProperties(const nsACString& aContentType,
   1084                               const nsACString& aContentDisposition) {
   1085  /* set our mimetype as a property */
   1086  nsCOMPtr<nsISupportsCString> contentType =
   1087      do_CreateInstance("@mozilla.org/supports-cstring;1");
   1088  if (contentType) {
   1089    contentType->SetData(aContentType);
   1090    mProperties->Set("type", contentType);
   1091  }
   1092 
   1093  /* set our content disposition as a property */
   1094  if (!aContentDisposition.IsEmpty()) {
   1095    nsCOMPtr<nsISupportsCString> contentDisposition =
   1096        do_CreateInstance("@mozilla.org/supports-cstring;1");
   1097    if (contentDisposition) {
   1098      contentDisposition->SetData(aContentDisposition);
   1099      mProperties->Set("content-disposition", contentDisposition);
   1100    }
   1101  }
   1102 }
   1103 
   1104 static nsresult sniff_mimetype_callback(nsIInputStream* in, void* data,
   1105                                        const char* fromRawSegment,
   1106                                        uint32_t toOffset, uint32_t count,
   1107                                        uint32_t* writeCount) {
   1108  mimetype_closure* closure = static_cast<mimetype_closure*>(data);
   1109 
   1110  NS_ASSERTION(closure, "closure is null!");
   1111 
   1112  if (count > 0) {
   1113    imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType);
   1114  }
   1115 
   1116  *writeCount = 0;
   1117  return NS_ERROR_FAILURE;
   1118 }
   1119 
   1120 /** nsIInterfaceRequestor methods **/
   1121 
   1122 NS_IMETHODIMP
   1123 imgRequest::GetInterface(const nsIID& aIID, void** aResult) {
   1124  if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
   1125    return QueryInterface(aIID, aResult);
   1126  }
   1127 
   1128  NS_ASSERTION(
   1129      mPrevChannelSink != this,
   1130      "Infinite recursion - don't keep track of channel sinks that are us!");
   1131  return mPrevChannelSink->GetInterface(aIID, aResult);
   1132 }
   1133 
   1134 /** nsIChannelEventSink methods **/
   1135 NS_IMETHODIMP
   1136 imgRequest::AsyncOnChannelRedirect(nsIChannel* oldChannel,
   1137                                   nsIChannel* newChannel, uint32_t flags,
   1138                                   nsIAsyncVerifyRedirectCallback* callback) {
   1139  NS_ASSERTION(mRequest && mChannel,
   1140               "Got a channel redirect after we nulled out mRequest!");
   1141  NS_ASSERTION(mChannel == oldChannel,
   1142               "Got a channel redirect for an unknown channel!");
   1143  NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!");
   1144 
   1145  SetCacheValidation(mCacheEntry, oldChannel);
   1146 
   1147  // Prepare for callback
   1148  mRedirectCallback = callback;
   1149  mNewRedirectChannel = newChannel;
   1150 
   1151  nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink));
   1152  if (sink) {
   1153    nsresult rv =
   1154        sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
   1155    if (NS_FAILED(rv)) {
   1156      mRedirectCallback = nullptr;
   1157      mNewRedirectChannel = nullptr;
   1158    }
   1159    return rv;
   1160  }
   1161 
   1162  (void)OnRedirectVerifyCallback(NS_OK);
   1163  return NS_OK;
   1164 }
   1165 
   1166 NS_IMETHODIMP
   1167 imgRequest::OnRedirectVerifyCallback(nsresult result) {
   1168  NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
   1169  NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
   1170 
   1171  if (NS_FAILED(result)) {
   1172    mRedirectCallback->OnRedirectVerifyCallback(result);
   1173    mRedirectCallback = nullptr;
   1174    mNewRedirectChannel = nullptr;
   1175    return NS_OK;
   1176  }
   1177 
   1178  mChannel = mNewRedirectChannel;
   1179  mTimedChannel = do_QueryInterface(mChannel);
   1180  mNewRedirectChannel = nullptr;
   1181 
   1182  if (LOG_TEST(LogLevel::Debug)) {
   1183    LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "old",
   1184                       mFinalURI ? mFinalURI->GetSpecOrDefault().get() : "");
   1185  }
   1186 
   1187  // If the previous URI is a non-HTTPS URI, record that fact for later use by
   1188  // security code, which needs to know whether there is an insecure load at any
   1189  // point in the redirect chain.
   1190  bool schemeLocal = false;
   1191  if (NS_FAILED(NS_URIChainHasFlags(mFinalURI,
   1192                                    nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
   1193                                    &schemeLocal)) ||
   1194      (!mFinalURI->SchemeIs("https") && !mFinalURI->SchemeIs("chrome") &&
   1195       !schemeLocal)) {
   1196    MutexAutoLock lock(mMutex);
   1197 
   1198    // The csp directive upgrade-insecure-requests performs an internal redirect
   1199    // to upgrade all requests from http to https before any data is fetched
   1200    // from the network. Do not pollute mHadInsecureRedirect in case of such an
   1201    // internal redirect.
   1202    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
   1203    bool upgradeInsecureRequests =
   1204        loadInfo ? loadInfo->GetUpgradeInsecureRequests() ||
   1205                       loadInfo->GetBrowserUpgradeInsecureRequests()
   1206                 : false;
   1207    if (!upgradeInsecureRequests) {
   1208      mHadInsecureRedirect = true;
   1209    }
   1210  }
   1211 
   1212  // Update the final URI.
   1213  mChannel->GetURI(getter_AddRefs(mFinalURI));
   1214 
   1215  if (LOG_TEST(LogLevel::Debug)) {
   1216    LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "new",
   1217                       mFinalURI ? mFinalURI->GetSpecOrDefault().get() : "");
   1218  }
   1219 
   1220  // Make sure we have a protocol that returns data rather than opens an
   1221  // external application, e.g. 'mailto:'.
   1222  if (nsContentUtils::IsExternalProtocol(mFinalURI)) {
   1223    mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
   1224    mRedirectCallback = nullptr;
   1225    return NS_OK;
   1226  }
   1227 
   1228  mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
   1229  mRedirectCallback = nullptr;
   1230  return NS_OK;
   1231 }
   1232 
   1233 void imgRequest::UpdateShouldReportRenderTimeForLCP() {
   1234  if (mTimedChannel) {
   1235    bool allRedirectPassTAO = false;
   1236    mTimedChannel->GetAllRedirectsPassTimingAllowCheck(&allRedirectPassTAO);
   1237    mShouldReportRenderTimeForLCP =
   1238        mTimedChannel->TimingAllowCheck(mTriggeringPrincipal) &&
   1239        allRedirectPassTAO;
   1240  }
   1241 }