michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* A class that handles style system image loads (other image loads are handled michael@0: * by the nodes in the content tree). michael@0: */ michael@0: michael@0: #include "mozilla/css/ImageLoader.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsDisplayList.h" michael@0: #include "FrameLayerBuilder.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "imgIContainer.h" michael@0: michael@0: namespace mozilla { michael@0: namespace css { michael@0: michael@0: /* static */ PLDHashOperator michael@0: ImageLoader::SetAnimationModeEnumerator(nsISupports* aKey, FrameSet* aValue, michael@0: void* aClosure) michael@0: { michael@0: imgIRequest* request = static_cast(aKey); michael@0: michael@0: uint16_t* mode = static_cast(aClosure); michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: nsCOMPtr debugRequest = do_QueryInterface(aKey); michael@0: NS_ASSERTION(debugRequest == request, "This is bad"); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr container; michael@0: request->GetImage(getter_AddRefs(container)); michael@0: if (!container) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // This can fail if the image is in error, and we don't care. michael@0: container->SetAnimationMode(*mode); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: ClearImageHashSet(nsPtrHashKey* aKey, void* aClosure) michael@0: { michael@0: nsIDocument* doc = static_cast(aClosure); michael@0: ImageLoader::Image* image = aKey->GetKey(); michael@0: michael@0: imgIRequest* request = image->mRequests.GetWeak(doc); michael@0: if (request) { michael@0: request->CancelAndForgetObserver(NS_BINDING_ABORTED); michael@0: } michael@0: michael@0: image->mRequests.Remove(doc); michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void michael@0: ImageLoader::DropDocumentReference() michael@0: { michael@0: ClearFrames(); michael@0: mImages.EnumerateEntries(&ClearImageHashSet, mDocument); michael@0: mDocument = nullptr; michael@0: } michael@0: michael@0: void michael@0: ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest, michael@0: nsIFrame* aFrame) michael@0: { michael@0: nsCOMPtr observer; michael@0: aRequest->GetNotificationObserver(getter_AddRefs(observer)); michael@0: if (!observer) { michael@0: // The request has already been canceled, so ignore it. This is ok because michael@0: // we're not going to get any more notifications from a canceled request. michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(observer == this); michael@0: michael@0: FrameSet* frameSet = nullptr; michael@0: if (mRequestToFrameMap.Get(aRequest, &frameSet)) { michael@0: NS_ASSERTION(frameSet, "This should never be null!"); michael@0: } michael@0: michael@0: if (!frameSet) { michael@0: nsAutoPtr newFrameSet(new FrameSet()); michael@0: michael@0: mRequestToFrameMap.Put(aRequest, newFrameSet); michael@0: frameSet = newFrameSet.forget(); michael@0: michael@0: nsPresContext* presContext = GetPresContext(); michael@0: if (presContext) { michael@0: nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, michael@0: aRequest, michael@0: nullptr); michael@0: } michael@0: } michael@0: michael@0: RequestSet* requestSet = nullptr; michael@0: if (mFrameToRequestMap.Get(aFrame, &requestSet)) { michael@0: NS_ASSERTION(requestSet, "This should never be null"); michael@0: } michael@0: michael@0: if (!requestSet) { michael@0: nsAutoPtr newRequestSet(new RequestSet()); michael@0: michael@0: mFrameToRequestMap.Put(aFrame, newRequestSet); michael@0: requestSet = newRequestSet.forget(); michael@0: } michael@0: michael@0: // Add these to the sets, but only if they're not already there. michael@0: uint32_t i = frameSet->IndexOfFirstElementGt(aFrame); michael@0: if (i == 0 || aFrame != frameSet->ElementAt(i-1)) { michael@0: frameSet->InsertElementAt(i, aFrame); michael@0: } michael@0: i = requestSet->IndexOfFirstElementGt(aRequest); michael@0: if (i == 0 || aRequest != requestSet->ElementAt(i-1)) { michael@0: requestSet->InsertElementAt(i, aRequest); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ImageLoader::MaybeRegisterCSSImage(ImageLoader::Image* aImage) michael@0: { michael@0: NS_ASSERTION(aImage, "This should never be null!"); michael@0: michael@0: bool found = false; michael@0: aImage->mRequests.GetWeak(mDocument, &found); michael@0: if (found) { michael@0: // This document already has a request. michael@0: return; michael@0: } michael@0: michael@0: imgRequestProxy* canonicalRequest = aImage->mRequests.GetWeak(nullptr); michael@0: if (!canonicalRequest) { michael@0: // The image was blocked or something. michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr request; michael@0: michael@0: // Ignore errors here. If cloning fails for some reason we'll put a null michael@0: // entry in the hash and we won't keep trying to clone. michael@0: mInClone = true; michael@0: canonicalRequest->Clone(this, getter_AddRefs(request)); michael@0: mInClone = false; michael@0: michael@0: aImage->mRequests.Put(mDocument, request); michael@0: michael@0: AddImage(aImage); michael@0: } michael@0: michael@0: void michael@0: ImageLoader::DeregisterCSSImage(ImageLoader::Image* aImage) michael@0: { michael@0: RemoveImage(aImage); michael@0: } michael@0: michael@0: void michael@0: ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest, michael@0: nsIFrame* aFrame) michael@0: { michael@0: FrameSet* frameSet = nullptr; michael@0: RequestSet* requestSet = nullptr; michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: nsCOMPtr observer; michael@0: aRequest->GetNotificationObserver(getter_AddRefs(observer)); michael@0: MOZ_ASSERT(!observer || observer == this); michael@0: } michael@0: #endif michael@0: michael@0: mRequestToFrameMap.Get(aRequest, &frameSet); michael@0: mFrameToRequestMap.Get(aFrame, &requestSet); michael@0: michael@0: if (frameSet) { michael@0: frameSet->RemoveElementSorted(aFrame); michael@0: } michael@0: if (requestSet) { michael@0: requestSet->RemoveElementSorted(aRequest); michael@0: } michael@0: michael@0: if (frameSet && !frameSet->Length()) { michael@0: mRequestToFrameMap.Remove(aRequest); michael@0: michael@0: nsPresContext* presContext = GetPresContext(); michael@0: if (presContext) { michael@0: nsLayoutUtils::DeregisterImageRequest(presContext, michael@0: aRequest, michael@0: nullptr); michael@0: } michael@0: } michael@0: michael@0: if (requestSet && !requestSet->Length()) { michael@0: mFrameToRequestMap.Remove(aFrame); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) michael@0: { michael@0: RequestSet* requestSet = nullptr; michael@0: if (!mFrameToRequestMap.Get(aFrame, &requestSet)) { michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(requestSet, "This should never be null"); michael@0: michael@0: RequestSet frozenRequestSet(*requestSet); michael@0: for (RequestSet::size_type i = frozenRequestSet.Length(); i != 0; --i) { michael@0: imgIRequest* request = frozenRequestSet.ElementAt(i - 1); michael@0: michael@0: DisassociateRequestFromFrame(request, aFrame); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ImageLoader::SetAnimationMode(uint16_t aMode) michael@0: { michael@0: NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode || michael@0: aMode == imgIContainer::kDontAnimMode || michael@0: aMode == imgIContainer::kLoopOnceAnimMode, michael@0: "Wrong Animation Mode is being set!"); michael@0: michael@0: mRequestToFrameMap.EnumerateRead(SetAnimationModeEnumerator, &aMode); michael@0: } michael@0: michael@0: void michael@0: ImageLoader::ClearFrames() michael@0: { michael@0: mRequestToFrameMap.Clear(); michael@0: mFrameToRequestMap.Clear(); michael@0: } michael@0: michael@0: void michael@0: ImageLoader::LoadImage(nsIURI* aURI, nsIPrincipal* aOriginPrincipal, michael@0: nsIURI* aReferrer, ImageLoader::Image* aImage) michael@0: { michael@0: NS_ASSERTION(aImage->mRequests.Count() == 0, "Huh?"); michael@0: michael@0: aImage->mRequests.Put(nullptr, nullptr); michael@0: michael@0: if (!aURI) { michael@0: return; michael@0: } michael@0: michael@0: if (!nsContentUtils::CanLoadImage(aURI, mDocument, mDocument, michael@0: aOriginPrincipal)) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr request; michael@0: nsContentUtils::LoadImage(aURI, mDocument, aOriginPrincipal, aReferrer, michael@0: nullptr, nsIRequest::LOAD_NORMAL, michael@0: NS_LITERAL_STRING("css"), michael@0: getter_AddRefs(request)); michael@0: michael@0: if (!request) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr clonedRequest; michael@0: mInClone = true; michael@0: nsresult rv = request->Clone(this, getter_AddRefs(clonedRequest)); michael@0: mInClone = false; michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: aImage->mRequests.Put(nullptr, request); michael@0: aImage->mRequests.Put(mDocument, clonedRequest); michael@0: michael@0: AddImage(aImage); michael@0: } michael@0: michael@0: void michael@0: ImageLoader::AddImage(ImageLoader::Image* aImage) michael@0: { michael@0: NS_ASSERTION(!mImages.Contains(aImage), "Huh?"); michael@0: if (!mImages.PutEntry(aImage)) { michael@0: NS_RUNTIMEABORT("OOM"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ImageLoader::RemoveImage(ImageLoader::Image* aImage) michael@0: { michael@0: NS_ASSERTION(mImages.Contains(aImage), "Huh?"); michael@0: mImages.RemoveEntry(aImage); michael@0: } michael@0: michael@0: nsPresContext* michael@0: ImageLoader::GetPresContext() michael@0: { michael@0: if (!mDocument) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIPresShell* shell = mDocument->GetShell(); michael@0: if (!shell) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return shell->GetPresContext(); michael@0: } michael@0: michael@0: void InvalidateImagesCallback(nsIFrame* aFrame, michael@0: FrameLayerBuilder::DisplayItemData* aItem) michael@0: { michael@0: nsDisplayItem::Type type = nsDisplayItem::GetDisplayItemTypeFromKey(aItem->GetDisplayItemKey()); michael@0: uint8_t flags = nsDisplayItem::GetDisplayItemFlagsForType(type); michael@0: michael@0: if (flags & nsDisplayItem::TYPE_RENDERS_NO_IMAGES) { michael@0: return; michael@0: } michael@0: michael@0: aItem->Invalidate(); michael@0: michael@0: // Update ancestor rendering observers (-moz-element etc) michael@0: nsIFrame *f = aFrame; michael@0: while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { michael@0: nsSVGEffects::InvalidateDirectRenderingObservers(f); michael@0: f = nsLayoutUtils::GetCrossDocParentFrame(f); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ImageLoader::DoRedraw(FrameSet* aFrameSet) michael@0: { michael@0: NS_ASSERTION(aFrameSet, "Must have a frame set"); michael@0: NS_ASSERTION(mDocument, "Should have returned earlier!"); michael@0: michael@0: FrameSet::size_type length = aFrameSet->Length(); michael@0: for (FrameSet::size_type i = 0; i < length; i++) { michael@0: nsIFrame* frame = aFrameSet->ElementAt(i); michael@0: michael@0: if (frame->StyleVisibility()->IsVisible()) { michael@0: if (frame->IsFrameOfType(nsIFrame::eTablePart)) { michael@0: // Tables don't necessarily build border/background display items michael@0: // for the individual table part frames, so IterateRetainedDataFor michael@0: // might not find the right display item. michael@0: frame->InvalidateFrame(); michael@0: } else { michael@0: FrameLayerBuilder::IterateRetainedDataFor(frame, InvalidateImagesCallback); michael@0: frame->SchedulePaint(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(ImageLoader) michael@0: NS_IMPL_RELEASE(ImageLoader) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(ImageLoader) michael@0: NS_INTERFACE_MAP_ENTRY(imgINotificationObserver) michael@0: NS_INTERFACE_MAP_ENTRY(imgIOnloadBlocker) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMETHODIMP michael@0: ImageLoader::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) michael@0: { michael@0: if (aType == imgINotificationObserver::SIZE_AVAILABLE) { michael@0: nsCOMPtr image; michael@0: aRequest->GetImage(getter_AddRefs(image)); michael@0: return OnStartContainer(aRequest, image); michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::IS_ANIMATED) { michael@0: return OnImageIsAnimated(aRequest); michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::LOAD_COMPLETE) { michael@0: return OnStopFrame(aRequest); michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::FRAME_UPDATE) { michael@0: return FrameChanged(aRequest); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ImageLoader::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage) michael@0: { michael@0: nsPresContext* presContext = GetPresContext(); michael@0: if (!presContext) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: aImage->SetAnimationMode(presContext->ImageAnimationMode()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) michael@0: { michael@0: if (!mDocument) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: FrameSet* frameSet = nullptr; michael@0: if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Register with the refresh driver now that we are aware that michael@0: // we are animated. michael@0: nsPresContext* presContext = GetPresContext(); michael@0: if (presContext) { michael@0: nsLayoutUtils::RegisterImageRequest(presContext, michael@0: aRequest, michael@0: nullptr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ImageLoader::OnStopFrame(imgIRequest *aRequest) michael@0: { michael@0: if (!mDocument || mInClone) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: FrameSet* frameSet = nullptr; michael@0: if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(frameSet, "This should never be null!"); michael@0: michael@0: DoRedraw(frameSet); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ImageLoader::FrameChanged(imgIRequest *aRequest) michael@0: { michael@0: if (!mDocument || mInClone) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: FrameSet* frameSet = nullptr; michael@0: if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(frameSet, "This should never be null!"); michael@0: michael@0: DoRedraw(frameSet); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageLoader::BlockOnload(imgIRequest* aRequest) michael@0: { michael@0: if (!mDocument) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mDocument->BlockOnload(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ImageLoader::UnblockOnload(imgIRequest* aRequest) michael@0: { michael@0: if (!mDocument) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mDocument->UnblockOnload(false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace css michael@0: } // namespace mozilla