/* -*- 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/. */ /* utility code for drawing images as CSS borders, backgrounds, and shapes. */ #include "nsImageRenderer.h" #include "mozilla/webrender/WebRenderAPI.h" #ifdef MOZ_WIDGET_GTK # include "nsIconChannel.h" #endif #include "gfxContext.h" #include "gfxDrawable.h" #include "ImageOps.h" #include "ImageRegion.h" #include "mozilla/image/WebRenderImageProvider.h" #include "mozilla/layers/RenderRootStateManager.h" #include "mozilla/layers/StackingContextHelper.h" #include "mozilla/layers/WebRenderLayerManager.h" #include "nsContentUtils.h" #include "nsCSSRendering.h" #include "nsCSSRenderingGradients.h" #include "nsDeviceContext.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" #include "nsStyleStructInlines.h" #include "mozilla/StaticPrefs_image.h" #include "mozilla/ISVGDisplayableFrame.h" #include "mozilla/SVGIntegrationUtils.h" #include "mozilla/SVGPaintServerFrame.h" #include "mozilla/SVGObserverUtils.h" using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::image; using namespace mozilla::layers; nsSize CSSSizeOrRatio::ComputeConcreteSize() const { NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute"); if (mHasWidth && mHasHeight) { return nsSize(mWidth, mHeight); } if (mHasWidth) { return nsSize(mWidth, mRatio.Inverted().ApplyTo(mWidth)); } MOZ_ASSERT(mHasHeight); return nsSize(mRatio.ApplyTo(mHeight), mHeight); } nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, const StyleImage* aImage, uint32_t aFlags) : mForFrame(aForFrame), mImage(&aImage->FinalImage()), mImageResolution(aImage->GetResolution(*aForFrame->Style())), mType(mImage->tag), mImageContainer(nullptr), mGradientData(nullptr), mPaintServerFrame(nullptr), mPrepareResult(ImgDrawResult::NOT_READY), mSize(0, 0), mFlags(aFlags), mExtendMode(ExtendMode::CLAMP), mMaskOp(StyleMaskMode::MatchSource) {} using SymbolicImageKey = std::tuple, int, nscolor>; struct SymbolicImageEntry { SymbolicImageKey mKey; nsCOMPtr mImage; }; struct SymbolicImageCache final : public mozilla::MruCache { static HashNumber Hash(const KeyType& aKey) { return AddToHash(std::get<0>(aKey)->hash(), HashGeneric(std::get<1>(aKey), std::get<2>(aKey))); } static bool Match(const KeyType& aKey, const ValueType& aVal) { return aVal.mKey == aKey; } }; NS_DECLARE_FRAME_PROPERTY_DELETABLE(SymbolicImageCacheProp, SymbolicImageCache); static already_AddRefed GetSymbolicIconImage(nsAtom* aName, int aScale, nsIFrame* aFrame) { if (NS_WARN_IF(!XRE_IsParentProcess())) { return nullptr; } const auto fg = aFrame->StyleText()->mColor.ToColor(); auto key = std::make_tuple(aName, aScale, fg); auto* cache = aFrame->GetProperty(SymbolicImageCacheProp()); if (!cache) { cache = new SymbolicImageCache(); aFrame->SetProperty(SymbolicImageCacheProp(), cache); } auto lookup = cache->Lookup(key); if (lookup) { return do_AddRef(lookup.Data().mImage); } RefPtr surface; #ifdef MOZ_WIDGET_GTK surface = nsIconChannel::GetSymbolicIcon(nsAtomCString(aName), 16, aScale, fg); #endif if (NS_WARN_IF(!surface)) { return nullptr; } RefPtr drawable = new gfxSurfaceDrawable(surface, surface->GetSize()); nsCOMPtr container = ImageOps::CreateFromDrawable(drawable); MOZ_ASSERT(container); lookup.Set(SymbolicImageEntry{std::move(key), std::move(container)}); return do_AddRef(lookup.Data().mImage); } bool nsImageRenderer::PrepareImage() { if (mImage->IsNone()) { mPrepareResult = ImgDrawResult::BAD_IMAGE; return false; } const bool isImageRequest = mImage->IsImageRequestType(); MOZ_ASSERT_IF(!isImageRequest, !mImage->GetImageRequest()); imgRequestProxy* request = nullptr; if (isImageRequest) { request = mImage->GetImageRequest(); if (!request) { // request could be null here if the StyleImage refused // to load a same-document URL, or the url was invalid, for example. mPrepareResult = ImgDrawResult::BAD_IMAGE; return false; } } if (!mImage->IsComplete()) { MOZ_DIAGNOSTIC_ASSERT(isImageRequest); // Make sure the image is actually decoding. bool frameComplete = request->StartDecodingWithResult( imgIContainer::FLAG_ASYNC_NOTIFY | imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE); // Boost the loading priority since we know we want to draw the image. if (mFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) { request->BoostPriority(imgIRequest::CATEGORY_DISPLAY); } // Check again to see if we finished. // We cannot prepare the image for rendering if it is not fully loaded. if (!frameComplete && !mImage->IsComplete()) { uint32_t imageStatus = 0; request->GetImageStatus(&imageStatus); if (imageStatus & imgIRequest::STATUS_ERROR) { mPrepareResult = ImgDrawResult::BAD_IMAGE; return false; } // If not errored, and we requested a sync decode, and the image has // loaded, push on through because the Draw() will do a sync decode then. const bool syncDecodeWillComplete = (mFlags & FLAG_SYNC_DECODE_IMAGES) && (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE); bool canDrawPartial = (mFlags & nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES) && isImageRequest && mImage->IsSizeAvailable(); // If we are drawing a partial frame then we want to make sure there are // some pixels to draw, otherwise we waste effort pushing through to draw // nothing. if (!syncDecodeWillComplete && canDrawPartial) { nsCOMPtr image; canDrawPartial = canDrawPartial && NS_SUCCEEDED(request->GetImage(getter_AddRefs(image))) && image && image->GetType() == imgIContainer::TYPE_RASTER && image->HasDecodedPixels(); } // If we can draw partial then proceed if we at least have the size // available. if (!(syncDecodeWillComplete || canDrawPartial)) { mPrepareResult = ImgDrawResult::NOT_READY; return false; } } } if (isImageRequest) { nsCOMPtr srcImage; nsresult rv = request->GetImage(getter_AddRefs(srcImage)); MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage, "If GetImage() is failing, mImage->IsComplete() " "should have returned false"); if (!NS_SUCCEEDED(rv)) { srcImage = nullptr; } if (srcImage) { StyleImageOrientation orientation = mForFrame->StyleVisibility()->UsedImageOrientation(request); srcImage = nsLayoutUtils::OrientImage(srcImage, orientation); } mImageContainer.swap(srcImage); mPrepareResult = ImgDrawResult::SUCCESS; } else if (mImage->IsGradient()) { mGradientData = &*mImage->AsGradient(); mPrepareResult = ImgDrawResult::SUCCESS; } else if (mImage->IsElement()) { dom::Element* paintElement = // may be null SVGObserverUtils::GetAndObserveBackgroundImage( mForFrame->FirstContinuation(), mImage->AsElement().AsAtom()); // If the referenced element is an , , or