forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			827 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			827 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /* A class that handles style system image loads (other image loads are handled
 | |
|  * by the nodes in the content tree).
 | |
|  */
 | |
| 
 | |
| #include "mozilla/css/ImageLoader.h"
 | |
| 
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "mozilla/dom/DocumentInlines.h"
 | |
| #include "mozilla/dom/ImageTracker.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsIReflowCallback.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsError.h"
 | |
| #include "nsCanvasFrame.h"
 | |
| #include "nsDisplayList.h"
 | |
| #include "nsIFrameInlines.h"
 | |
| #include "imgIContainer.h"
 | |
| #include "imgINotificationObserver.h"
 | |
| #include "Image.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/ProfilerLabels.h"
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "mozilla/SVGObserverUtils.h"
 | |
| #include "mozilla/layers/WebRenderUserData.h"
 | |
| #include "nsTHashSet.h"
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| 
 | |
| namespace mozilla::css {
 | |
| 
 | |
| // This is a singleton observer which looks in the `GlobalRequestTable` to look
 | |
| // at which loaders to notify.
 | |
| struct GlobalImageObserver final : public imgINotificationObserver {
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_IMGINOTIFICATIONOBSERVER
 | |
| 
 | |
|   GlobalImageObserver() = default;
 | |
| 
 | |
|  private:
 | |
|   virtual ~GlobalImageObserver() = default;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ADDREF(GlobalImageObserver)
 | |
| NS_IMPL_RELEASE(GlobalImageObserver)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN(GlobalImageObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| // Data associated with every started load.
 | |
| struct ImageTableEntry {
 | |
|   // Set of all ImageLoaders that have registered this URL and care for updates
 | |
|   // for it.
 | |
|   nsTHashSet<ImageLoader*> mImageLoaders;
 | |
| 
 | |
|   // The amount of style values that are sharing this image.
 | |
|   uint32_t mSharedCount = 1;
 | |
| };
 | |
| 
 | |
| using GlobalRequestTable =
 | |
|     nsClassHashtable<nsRefPtrHashKey<imgIRequest>, ImageTableEntry>;
 | |
| 
 | |
| // A table of all loads, keyed by their id mapping them to the set of
 | |
| // ImageLoaders they have been registered in, and recording their "canonical"
 | |
| // image request.
 | |
| //
 | |
| // We use the load id as the key since we can only access sImages on the
 | |
| // main thread, but LoadData objects might be destroyed from other threads,
 | |
| // and we don't want to leave dangling pointers around.
 | |
| static StaticAutoPtr<GlobalRequestTable> sImages;
 | |
| static StaticRefPtr<GlobalImageObserver> sImageObserver;
 | |
| 
 | |
| /* static */
 | |
| void ImageLoader::Init() {
 | |
|   sImages = new GlobalRequestTable();
 | |
|   sImageObserver = new GlobalImageObserver();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void ImageLoader::Shutdown() {
 | |
|   for (const auto& entry : *sImages) {
 | |
|     imgIRequest* imgRequest = entry.GetKey();
 | |
|     // All the images we put in sImages are imgRequestProxy, see LoadImage, but
 | |
|     // it's non-trivial to make the hash table to use that without changing a
 | |
|     // lot of other code.
 | |
|     auto* req = static_cast<imgRequestProxy*>(imgRequest);
 | |
|     req->SetCancelable(true);
 | |
|     req->CancelAndForgetObserver(NS_BINDING_ABORTED);
 | |
|   }
 | |
| 
 | |
|   sImages = nullptr;
 | |
|   sImageObserver = nullptr;
 | |
| }
 | |
| 
 | |
| void ImageLoader::DropDocumentReference() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   // It's okay if GetPresContext returns null here (due to the presshell pointer
 | |
|   // on the document being null) as that means the presshell has already
 | |
|   // been destroyed, and it also calls ClearFrames when it is destroyed.
 | |
|   ClearFrames(GetPresContext());
 | |
| 
 | |
|   mDocument = nullptr;
 | |
| }
 | |
| 
 | |
| // Arrays of requests and frames are sorted by their pointer address,
 | |
| // for faster lookup.
 | |
| template <typename Elem, typename Item,
 | |
|           typename Comparator = nsDefaultComparator<Elem, Item>>
 | |
| static size_t GetMaybeSortedIndex(const nsTArray<Elem>& aArray,
 | |
|                                   const Item& aItem, bool* aFound,
 | |
|                                   Comparator aComparator = Comparator()) {
 | |
|   size_t index = aArray.IndexOfFirstElementGt(aItem, aComparator);
 | |
|   *aFound = index > 0 && aComparator.Equals(aItem, aArray.ElementAt(index - 1));
 | |
|   return index;
 | |
| }
 | |
| 
 | |
| // Returns true if an async decode is triggered for aRequest, and thus we will
 | |
| // get an OnFrameComplete callback for this request eventually.
 | |
| static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest* aRequest) {
 | |
|   uint32_t status = 0;
 | |
|   // Don't block onload if we've already got a frame complete status
 | |
|   // (since in that case the image is already loaded), or if we get an
 | |
|   // error status (since then we know the image won't ever load).
 | |
|   if (NS_SUCCEEDED(aRequest->GetImageStatus(&status))) {
 | |
|     if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
 | |
|       // Already decoded, no need to do it again.
 | |
|       return false;
 | |
|     }
 | |
|     if (status & imgIRequest::STATUS_ERROR) {
 | |
|       // Already errored, this would be useless.
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // We want to request decode in such a way that avoids triggering sync decode.
 | |
|   // First, we attempt to convert the aRequest into a imgIContainer. If that
 | |
|   // succeeds, then aRequest has an image and we can request decoding for size
 | |
|   // at zero size, the size will be ignored because we don't pass the
 | |
|   // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass
 | |
|   // any sync decoding flags) at the intrinsic size will be requested. If the
 | |
|   // conversion to imgIContainer is unsuccessful, then that means aRequest
 | |
|   // doesn't have an image yet, which means we can safely call StartDecoding()
 | |
|   // on it without triggering any synchronous work.
 | |
|   nsCOMPtr<imgIContainer> imgContainer;
 | |
|   aRequest->GetImage(getter_AddRefs(imgContainer));
 | |
|   if (imgContainer) {
 | |
|     imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
 | |
|                                        imgIContainer::DECODE_FLAGS_DEFAULT);
 | |
|   } else {
 | |
|     // It's safe to call StartDecoding directly, since it can't
 | |
|     // trigger synchronous decode without an image. Flags are ignored.
 | |
|     aRequest->StartDecoding(imgIContainer::FLAG_NONE);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
 | |
|                                           nsIFrame* aFrame, Flags aFlags) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(!(aFlags & Flags::IsBlockingLoadEvent),
 | |
|              "Shouldn't be used in the public API");
 | |
| 
 | |
|   {
 | |
|     nsCOMPtr<imgINotificationObserver> observer;
 | |
|     aRequest->GetNotificationObserver(getter_AddRefs(observer));
 | |
|     if (!observer) {
 | |
|       // The request has already been canceled, so ignore it. This is ok because
 | |
|       // we're not going to get any more notifications from a canceled request.
 | |
|       return;
 | |
|     }
 | |
|     MOZ_ASSERT(observer == sImageObserver);
 | |
|   }
 | |
| 
 | |
|   auto* const frameSet =
 | |
|       mRequestToFrameMap
 | |
|           .LookupOrInsertWith(
 | |
|               aRequest,
 | |
|               [&] {
 | |
|                 mDocument->ImageTracker()->Add(aRequest);
 | |
| 
 | |
|                 if (auto entry = sImages->Lookup(aRequest)) {
 | |
|                   DebugOnly<bool> inserted =
 | |
|                       entry.Data()->mImageLoaders.EnsureInserted(this);
 | |
|                   MOZ_ASSERT(inserted);
 | |
|                 } else {
 | |
|                   MOZ_ASSERT_UNREACHABLE(
 | |
|                       "Shouldn't be associating images not in sImages");
 | |
|                 }
 | |
| 
 | |
|                 if (nsPresContext* presContext = GetPresContext()) {
 | |
|                   nsLayoutUtils::RegisterImageRequestIfAnimated(
 | |
|                       presContext, aRequest, nullptr);
 | |
|                 }
 | |
|                 return MakeUnique<FrameSet>();
 | |
|               })
 | |
|           .get();
 | |
| 
 | |
|   auto* const requestSet =
 | |
|       mFrameToRequestMap
 | |
|           .LookupOrInsertWith(aFrame,
 | |
|                               [=]() {
 | |
|                                 aFrame->SetHasImageRequest(true);
 | |
|                                 return MakeUnique<RequestSet>();
 | |
|                               })
 | |
|           .get();
 | |
| 
 | |
|   // Add frame to the frameSet, and handle any special processing the
 | |
|   // frame might require.
 | |
|   FrameWithFlags fwf(aFrame);
 | |
|   FrameWithFlags* fwfToModify = &fwf;
 | |
| 
 | |
|   // See if the frameSet already has this frame.
 | |
|   bool found;
 | |
|   uint32_t i =
 | |
|       GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator());
 | |
|   if (found) {
 | |
|     // We're already tracking this frame, so prepare to modify the
 | |
|     // existing FrameWithFlags object.
 | |
|     fwfToModify = &frameSet->ElementAt(i - 1);
 | |
|   }
 | |
| 
 | |
|   // Check if the frame requires special processing.
 | |
|   if (aFlags & Flags::RequiresReflowOnSizeAvailable) {
 | |
|     MOZ_ASSERT(!(aFlags &
 | |
|                  Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking),
 | |
|                "These two are exclusive");
 | |
|     fwfToModify->mFlags |= Flags::RequiresReflowOnSizeAvailable;
 | |
|   }
 | |
| 
 | |
|   if (aFlags & Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
 | |
|     fwfToModify->mFlags |=
 | |
|         Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking;
 | |
| 
 | |
|     // If we weren't already blocking onload, do that now.
 | |
|     if (!(fwfToModify->mFlags & Flags::IsBlockingLoadEvent)) {
 | |
|       if (TriggerAsyncDecodeAtIntrinsicSize(aRequest)) {
 | |
|         // If there's no error, and the image has not loaded yet, so we can
 | |
|         // block onload.
 | |
|         fwfToModify->mFlags |= Flags::IsBlockingLoadEvent;
 | |
| 
 | |
|         // Block document onload until we either remove the frame in
 | |
|         // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
 | |
|         mDocument->BlockOnload();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Do some sanity checking to ensure that we only add to one mapping
 | |
|   // iff we also add to the other mapping.
 | |
|   DebugOnly<bool> didAddToFrameSet(false);
 | |
|   DebugOnly<bool> didAddToRequestSet(false);
 | |
| 
 | |
|   // If we weren't already tracking this frame, add it to the frameSet.
 | |
|   if (!found) {
 | |
|     frameSet->InsertElementAt(i, fwf);
 | |
|     didAddToFrameSet = true;
 | |
|   }
 | |
| 
 | |
|   // Add request to the request set if it wasn't already there.
 | |
|   i = GetMaybeSortedIndex(*requestSet, aRequest, &found);
 | |
|   if (!found) {
 | |
|     requestSet->InsertElementAt(i, aRequest);
 | |
|     didAddToRequestSet = true;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(didAddToFrameSet == didAddToRequestSet,
 | |
|              "We should only add to one map iff we also add to the other map.");
 | |
| }
 | |
| 
 | |
| void ImageLoader::RemoveRequestToFrameMapping(imgIRequest* aRequest,
 | |
|                                               nsIFrame* aFrame) {
 | |
| #ifdef DEBUG
 | |
|   {
 | |
|     nsCOMPtr<imgINotificationObserver> observer;
 | |
|     aRequest->GetNotificationObserver(getter_AddRefs(observer));
 | |
|     MOZ_ASSERT(!observer || observer == sImageObserver);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (auto entry = mRequestToFrameMap.Lookup(aRequest)) {
 | |
|     const auto& frameSet = entry.Data();
 | |
|     MOZ_ASSERT(frameSet, "This should never be null");
 | |
| 
 | |
|     // Before we remove aFrame from the frameSet, unblock onload if needed.
 | |
|     bool found;
 | |
|     uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found,
 | |
|                                      FrameOnlyComparator());
 | |
|     if (found) {
 | |
|       UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1));
 | |
|       frameSet->RemoveElementAt(i - 1);
 | |
|     }
 | |
| 
 | |
|     if (frameSet->IsEmpty()) {
 | |
|       DeregisterImageRequest(aRequest, GetPresContext());
 | |
|       entry.Remove();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::DeregisterImageRequest(imgIRequest* aRequest,
 | |
|                                          nsPresContext* aPresContext) {
 | |
|   mDocument->ImageTracker()->Remove(aRequest);
 | |
| 
 | |
|   if (auto entry = sImages->Lookup(aRequest)) {
 | |
|     entry.Data()->mImageLoaders.EnsureRemoved(this);
 | |
|   }
 | |
| 
 | |
|   if (aPresContext) {
 | |
|     nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest,
 | |
|                                               nsIFrame* aFrame) {
 | |
|   if (auto entry = mFrameToRequestMap.Lookup(aFrame)) {
 | |
|     const auto& requestSet = entry.Data();
 | |
|     MOZ_ASSERT(requestSet, "This should never be null");
 | |
|     requestSet->RemoveElementSorted(aRequest);
 | |
|     if (requestSet->IsEmpty()) {
 | |
|       aFrame->SetHasImageRequest(false);
 | |
|       entry.Remove();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
 | |
|                                                nsIFrame* aFrame) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
 | |
| 
 | |
|   RemoveRequestToFrameMapping(aRequest, aFrame);
 | |
|   RemoveFrameToRequestMapping(aRequest, aFrame);
 | |
| }
 | |
| 
 | |
| void ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
 | |
| 
 | |
|   UniquePtr<RequestSet> requestSet;
 | |
|   mFrameToRequestMap.Remove(aFrame, &requestSet);
 | |
|   aFrame->SetHasImageRequest(false);
 | |
|   if (MOZ_UNLIKELY(!requestSet)) {
 | |
|     MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying");
 | |
|     return;
 | |
|   }
 | |
|   for (imgIRequest* request : *requestSet) {
 | |
|     RemoveRequestToFrameMapping(request, aFrame);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::SetAnimationMode(uint16_t aMode) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
 | |
|                    aMode == imgIContainer::kDontAnimMode ||
 | |
|                    aMode == imgIContainer::kLoopOnceAnimMode,
 | |
|                "Wrong Animation Mode is being set!");
 | |
| 
 | |
|   for (nsISupports* key : mRequestToFrameMap.Keys()) {
 | |
|     auto* request = static_cast<imgIRequest*>(key);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|     {
 | |
|       nsCOMPtr<imgIRequest> debugRequest = request;
 | |
|       NS_ASSERTION(debugRequest == request, "This is bad");
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     nsCOMPtr<imgIContainer> container;
 | |
|     request->GetImage(getter_AddRefs(container));
 | |
|     if (!container) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // This can fail if the image is in error, and we don't care.
 | |
|     container->SetAnimationMode(aMode);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::ClearFrames(nsPresContext* aPresContext) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   for (const auto& key : mRequestToFrameMap.Keys()) {
 | |
|     auto* request = static_cast<imgIRequest*>(key);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|     {
 | |
|       nsCOMPtr<imgIRequest> debugRequest = request;
 | |
|       NS_ASSERTION(debugRequest == request, "This is bad");
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     DeregisterImageRequest(request, aPresContext);
 | |
|   }
 | |
| 
 | |
|   mRequestToFrameMap.Clear();
 | |
|   mFrameToRequestMap.Clear();
 | |
| }
 | |
| 
 | |
| static CORSMode EffectiveCorsMode(nsIURI* aURI,
 | |
|                                   const StyleComputedImageUrl& aImage) {
 | |
|   MOZ_ASSERT(aURI);
 | |
|   StyleCorsMode mode = aImage.CorsMode();
 | |
|   if (mode == StyleCorsMode::None) {
 | |
|     return CORSMode::CORS_NONE;
 | |
|   }
 | |
|   MOZ_ASSERT(mode == StyleCorsMode::Anonymous);
 | |
|   if (aURI->SchemeIs("resource")) {
 | |
|     return CORSMode::CORS_NONE;
 | |
|   }
 | |
|   return CORSMode::CORS_ANONYMOUS;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<imgRequestProxy> ImageLoader::LoadImage(
 | |
|     const StyleComputedImageUrl& aImage, Document& aDocument) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   nsIURI* uri = aImage.GetURI();
 | |
|   if (!uri) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (aImage.HasRef()) {
 | |
|     bool isEqualExceptRef = false;
 | |
|     nsIURI* docURI = aDocument.GetDocumentURI();
 | |
|     if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) &&
 | |
|         isEqualExceptRef) {
 | |
|       // Prevent loading an internal resource.
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   int32_t loadFlags =
 | |
|       nsIRequest::LOAD_NORMAL |
 | |
|       nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri, aImage));
 | |
| 
 | |
|   const URLExtraData& data = aImage.ExtraData();
 | |
| 
 | |
|   RefPtr<imgRequestProxy> request;
 | |
|   nsresult rv = nsContentUtils::LoadImage(
 | |
|       uri, &aDocument, &aDocument, data.Principal(), 0, data.ReferrerInfo(),
 | |
|       sImageObserver, loadFlags, u"css"_ns, getter_AddRefs(request));
 | |
| 
 | |
|   if (NS_FAILED(rv) || !request) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // This image could be shared across documents, so its load cannot be
 | |
|   // canceled, see bug 1800979.
 | |
|   request->SetCancelable(false);
 | |
|   sImages->GetOrInsertNew(request);
 | |
|   return request.forget();
 | |
| }
 | |
| 
 | |
| void ImageLoader::UnloadImage(imgRequestProxy* aImage) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aImage);
 | |
| 
 | |
|   if (MOZ_UNLIKELY(!sImages)) {
 | |
|     return;  // Shutdown() takes care of it.
 | |
|   }
 | |
| 
 | |
|   auto lookup = sImages->Lookup(aImage);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
 | |
|   if (MOZ_UNLIKELY(!lookup)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) {
 | |
|     // Someone else still cares about this image.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Now we want to really cancel the request.
 | |
|   aImage->SetCancelable(true);
 | |
|   aImage->CancelAndForgetObserver(NS_BINDING_ABORTED);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(lookup.Data()->mImageLoaders.IsEmpty(),
 | |
|                         "Shouldn't be keeping references to any loader "
 | |
|                         "by now");
 | |
|   lookup.Remove();
 | |
| }
 | |
| 
 | |
| void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aImage);
 | |
| 
 | |
|   auto lookup = sImages->Lookup(aImage);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
 | |
|   if (MOZ_UNLIKELY(!lookup)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   lookup.Data()->mSharedCount++;
 | |
| }
 | |
| 
 | |
| nsPresContext* ImageLoader::GetPresContext() {
 | |
|   if (!mDocument) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return mDocument->GetPresContext();
 | |
| }
 | |
| 
 | |
| static bool IsRenderNoImages(uint32_t aDisplayItemKey) {
 | |
|   DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey);
 | |
|   uint8_t flags = GetDisplayItemFlagsForType(type);
 | |
|   return flags & TYPE_RENDERS_NO_IMAGES;
 | |
| }
 | |
| 
 | |
| static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest,
 | |
|                              bool aForcePaint) {
 | |
|   if (!aFrame->StyleVisibility()->IsVisible()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsFrameOfType(nsIFrame::eTablePart)) {
 | |
|     // Tables don't necessarily build border/background display items
 | |
|     // for the individual table part frames, so IterateRetainedDataFor
 | |
|     // might not find the right display item.
 | |
|     return aFrame->InvalidateFrame();
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
 | |
|     if (auto* canvas = aFrame->PresShell()->GetCanvasFrame()) {
 | |
|       // Try to invalidate the canvas too, in the probable case the background
 | |
|       // was propagated to it.
 | |
|       InvalidateImages(canvas, aRequest, aForcePaint);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool invalidateFrame = aForcePaint;
 | |
| 
 | |
|   if (auto userDataTable =
 | |
|           aFrame->GetProperty(layers::WebRenderUserDataProperty::Key())) {
 | |
|     for (RefPtr<layers::WebRenderUserData> data : userDataTable->Values()) {
 | |
|       switch (data->GetType()) {
 | |
|         case layers::WebRenderUserData::UserDataType::eFallback:
 | |
|           if (!IsRenderNoImages(data->GetDisplayItemKey())) {
 | |
|             static_cast<layers::WebRenderFallbackData*>(data.get())
 | |
|                 ->SetInvalid(true);
 | |
|           }
 | |
|           // XXX: handle Blob data
 | |
|           invalidateFrame = true;
 | |
|           break;
 | |
|         case layers::WebRenderUserData::UserDataType::eMask:
 | |
|           static_cast<layers::WebRenderMaskData*>(data.get())->Invalidate();
 | |
|           invalidateFrame = true;
 | |
|           break;
 | |
|         case layers::WebRenderUserData::UserDataType::eImageProvider:
 | |
|           if (static_cast<layers::WebRenderImageProviderData*>(data.get())
 | |
|                   ->Invalidate(aRequest->GetProviderId())) {
 | |
|             break;
 | |
|           }
 | |
|           [[fallthrough]];
 | |
|         default:
 | |
|           invalidateFrame = true;
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Update ancestor rendering observers (-moz-element etc)
 | |
|   //
 | |
|   // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526.
 | |
|   {
 | |
|     nsIFrame* f = aFrame;
 | |
|     while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
 | |
|       SVGObserverUtils::InvalidateDirectRenderingObservers(f);
 | |
|       f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (invalidateFrame) {
 | |
|     aFrame->SchedulePaint();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags& aFwf) {
 | |
|   if (aFwf.mFlags & Flags::IsBlockingLoadEvent) {
 | |
|     mDocument->UnblockOnload(false);
 | |
|     aFwf.mFlags &= ~Flags::IsBlockingLoadEvent;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::UnblockOnloadIfNeeded(nsIFrame* aFrame,
 | |
|                                         imgIRequest* aRequest) {
 | |
|   MOZ_ASSERT(aFrame);
 | |
|   MOZ_ASSERT(aRequest);
 | |
| 
 | |
|   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
 | |
|   if (!frameSet) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   size_t i =
 | |
|       frameSet->BinaryIndexOf(FrameWithFlags(aFrame), FrameOnlyComparator());
 | |
|   if (i != FrameSet::NoIndex) {
 | |
|     UnblockOnloadIfNeeded(frameSet->ElementAt(i));
 | |
|   }
 | |
| }
 | |
| 
 | |
| // This callback is used to unblock document onload after a reflow
 | |
| // triggered from an image load.
 | |
| struct ImageLoader::ImageReflowCallback final : public nsIReflowCallback {
 | |
|   RefPtr<ImageLoader> mLoader;
 | |
|   WeakFrame mFrame;
 | |
|   nsCOMPtr<imgIRequest> const mRequest;
 | |
| 
 | |
|   ImageReflowCallback(ImageLoader* aLoader, nsIFrame* aFrame,
 | |
|                       imgIRequest* aRequest)
 | |
|       : mLoader(aLoader), mFrame(aFrame), mRequest(aRequest) {}
 | |
| 
 | |
|   bool ReflowFinished() override;
 | |
|   void ReflowCallbackCanceled() override;
 | |
| };
 | |
| 
 | |
| bool ImageLoader::ImageReflowCallback::ReflowFinished() {
 | |
|   // Check that the frame is still valid. If it isn't, then onload was
 | |
|   // unblocked when the frame was removed from the FrameSet in
 | |
|   // RemoveRequestToFrameMapping.
 | |
|   if (mFrame.IsAlive()) {
 | |
|     mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
 | |
|   }
 | |
| 
 | |
|   // Get rid of this callback object.
 | |
|   delete this;
 | |
| 
 | |
|   // We don't need to trigger layout.
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() {
 | |
|   // Check that the frame is still valid. If it isn't, then onload was
 | |
|   // unblocked when the frame was removed from the FrameSet in
 | |
|   // RemoveRequestToFrameMapping.
 | |
|   if (mFrame.IsAlive()) {
 | |
|     mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
 | |
|   }
 | |
| 
 | |
|   // Get rid of this callback object.
 | |
|   delete this;
 | |
| }
 | |
| 
 | |
| void GlobalImageObserver::Notify(imgIRequest* aRequest, int32_t aType,
 | |
|                                  const nsIntRect* aData) {
 | |
|   auto entry = sImages->Lookup(aRequest);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(entry);
 | |
|   if (MOZ_UNLIKELY(!entry)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto loadersToNotify =
 | |
|       ToTArray<nsTArray<RefPtr<ImageLoader>>>(entry.Data()->mImageLoaders);
 | |
|   for (const auto& loader : loadersToNotify) {
 | |
|     loader->Notify(aRequest, aType, aData);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::Notify(imgIRequest* aRequest, int32_t aType,
 | |
|                          const nsIntRect* aData) {
 | |
|   nsCString uriString;
 | |
|   if (profiler_is_active()) {
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     aRequest->GetFinalURI(getter_AddRefs(uri));
 | |
|     if (uri) {
 | |
|       uri->GetSpec(uriString);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   AUTO_PROFILER_LABEL_DYNAMIC_CSTR("ImageLoader::Notify", OTHER,
 | |
|                                    uriString.get());
 | |
| 
 | |
|   if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
 | |
|     nsCOMPtr<imgIContainer> image;
 | |
|     aRequest->GetImage(getter_AddRefs(image));
 | |
|     return OnSizeAvailable(aRequest, image);
 | |
|   }
 | |
| 
 | |
|   if (aType == imgINotificationObserver::IS_ANIMATED) {
 | |
|     return OnImageIsAnimated(aRequest);
 | |
|   }
 | |
| 
 | |
|   if (aType == imgINotificationObserver::FRAME_COMPLETE) {
 | |
|     return OnFrameComplete(aRequest);
 | |
|   }
 | |
| 
 | |
|   if (aType == imgINotificationObserver::FRAME_UPDATE) {
 | |
|     return OnFrameUpdate(aRequest);
 | |
|   }
 | |
| 
 | |
|   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
 | |
|     nsCOMPtr<imgIContainer> image;
 | |
|     aRequest->GetImage(getter_AddRefs(image));
 | |
|     if (image && mDocument) {
 | |
|       image->PropagateUseCounters(mDocument);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
 | |
|     return OnLoadComplete(aRequest);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::OnSizeAvailable(imgIRequest* aRequest,
 | |
|                                   imgIContainer* aImage) {
 | |
|   nsPresContext* presContext = GetPresContext();
 | |
|   if (!presContext) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   aImage->SetAnimationMode(presContext->ImageAnimationMode());
 | |
| 
 | |
|   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
 | |
|   if (!frameSet) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (const FrameWithFlags& fwf : *frameSet) {
 | |
|     if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) {
 | |
|       fwf.mFrame->PresShell()->FrameNeedsReflow(
 | |
|           fwf.mFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
 | |
|           NS_FRAME_IS_DIRTY);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) {
 | |
|   if (!mDocument) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
 | |
|   if (!frameSet) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Register with the refresh driver now that we are aware that
 | |
|   // we are animated.
 | |
|   nsPresContext* presContext = GetPresContext();
 | |
|   if (presContext) {
 | |
|     nsLayoutUtils::RegisterImageRequest(presContext, aRequest, nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::OnFrameComplete(imgIRequest* aRequest) {
 | |
|   ImageFrameChanged(aRequest, /* aFirstFrame = */ true);
 | |
| }
 | |
| 
 | |
| void ImageLoader::OnFrameUpdate(imgIRequest* aRequest) {
 | |
|   ImageFrameChanged(aRequest, /* aFirstFrame = */ false);
 | |
| }
 | |
| 
 | |
| void ImageLoader::ImageFrameChanged(imgIRequest* aRequest, bool aFirstFrame) {
 | |
|   if (!mDocument) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
 | |
|   if (!frameSet) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (FrameWithFlags& fwf : *frameSet) {
 | |
|     // Since we just finished decoding a frame, we always want to paint, in
 | |
|     // case we're now able to paint an image that we couldn't paint before
 | |
|     // (and hence that we don't have retained data for).
 | |
|     const bool forceRepaint = aFirstFrame;
 | |
|     InvalidateImages(fwf.mFrame, aRequest, forceRepaint);
 | |
|     if (!aFirstFrame) {
 | |
|       // We don't reflow / try to unblock onload for subsequent frame updates.
 | |
|       continue;
 | |
|     }
 | |
|     if (fwf.mFlags &
 | |
|         Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
 | |
|       // Tell the container of the frame to reflow because the image request
 | |
|       // has finished decoding its first frame.
 | |
|       // FIXME(emilio): Why requesting reflow on the _parent_?
 | |
|       nsIFrame* parent = fwf.mFrame->GetInFlowParent();
 | |
|       parent->PresShell()->FrameNeedsReflow(
 | |
|           parent, IntrinsicDirty::FrameAncestorsAndDescendants,
 | |
|           NS_FRAME_IS_DIRTY);
 | |
|       // If we need to also potentially unblock onload, do it once reflow is
 | |
|       // done, with a reflow callback.
 | |
|       if (fwf.mFlags & Flags::IsBlockingLoadEvent) {
 | |
|         auto* unblocker = new ImageReflowCallback(this, fwf.mFrame, aRequest);
 | |
|         parent->PresShell()->PostReflowCallback(unblocker);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageLoader::OnLoadComplete(imgIRequest* aRequest) {
 | |
|   if (!mDocument) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint32_t status = 0;
 | |
|   if (NS_FAILED(aRequest->GetImageStatus(&status))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
 | |
|   if (!frameSet) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (FrameWithFlags& fwf : *frameSet) {
 | |
|     if (status & imgIRequest::STATUS_ERROR) {
 | |
|       // Check if aRequest has an error state. If it does, we need to unblock
 | |
|       // Document onload for all the frames associated with this request that
 | |
|       // have blocked onload. This is what happens in a CORS mode violation, and
 | |
|       // may happen during other network events.
 | |
|       UnblockOnloadIfNeeded(fwf);
 | |
|     }
 | |
|     if (fwf.mFrame->StyleVisibility()->IsVisible()) {
 | |
|       fwf.mFrame->SchedulePaint();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::css
 | 
