summaryrefslogtreecommitdiff
path: root/image/VectorImage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/VectorImage.cpp')
-rw-r--r--image/VectorImage.cpp1350
1 files changed, 1350 insertions, 0 deletions
diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp
new file mode 100644
index 0000000000..6e39283622
--- /dev/null
+++ b/image/VectorImage.cpp
@@ -0,0 +1,1350 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "VectorImage.h"
+
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxDrawable.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "imgFrame.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsIDOMEvent.h"
+#include "nsIPresShell.h"
+#include "nsIStreamListener.h"
+#include "nsMimeTypes.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsStubDocumentObserver.h"
+#include "nsSVGEffects.h" // for nsSVGRenderingObserver
+#include "nsWindowMemoryReporter.h"
+#include "ImageRegion.h"
+#include "ISurfaceProvider.h"
+#include "LookupResult.h"
+#include "Orientation.h"
+#include "SVGDocumentWrapper.h"
+#include "nsIDOMEventListener.h"
+#include "SurfaceCache.h"
+#include "nsDocument.h"
+
+// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
+#undef GetCurrentTime
+
+namespace mozilla {
+
+using namespace dom;
+using namespace gfx;
+using namespace layers;
+
+namespace image {
+
+// Helper-class: SVGRootRenderingObserver
+class SVGRootRenderingObserver final : public nsSVGRenderingObserver {
+public:
+ NS_DECL_ISUPPORTS
+
+ SVGRootRenderingObserver(SVGDocumentWrapper* aDocWrapper,
+ VectorImage* aVectorImage)
+ : nsSVGRenderingObserver()
+ , mDocWrapper(aDocWrapper)
+ , mVectorImage(aVectorImage)
+ , mHonoringInvalidations(true)
+ {
+ MOZ_ASSERT(mDocWrapper, "Need a non-null SVG document wrapper");
+ MOZ_ASSERT(mVectorImage, "Need a non-null VectorImage");
+
+ StartListening();
+ Element* elem = GetTarget();
+ MOZ_ASSERT(elem, "no root SVG node for us to observe");
+
+ nsSVGEffects::AddRenderingObserver(elem, this);
+ mInObserverList = true;
+ }
+
+
+ void ResumeHonoringInvalidations()
+ {
+ mHonoringInvalidations = true;
+ }
+
+protected:
+ virtual ~SVGRootRenderingObserver()
+ {
+ StopListening();
+ }
+
+ virtual Element* GetTarget() override
+ {
+ return mDocWrapper->GetRootSVGElem();
+ }
+
+ virtual void DoUpdate() override
+ {
+ Element* elem = GetTarget();
+ MOZ_ASSERT(elem, "missing root SVG node");
+
+ if (mHonoringInvalidations && !mDocWrapper->ShouldIgnoreInvalidation()) {
+ nsIFrame* frame = elem->GetPrimaryFrame();
+ if (!frame || frame->PresContext()->PresShell()->IsDestroying()) {
+ // We're being destroyed. Bail out.
+ return;
+ }
+
+ // Ignore further invalidations until we draw.
+ mHonoringInvalidations = false;
+
+ mVectorImage->InvalidateObserversOnNextRefreshDriverTick();
+ }
+
+ // Our caller might've removed us from rendering-observer list.
+ // Add ourselves back!
+ if (!mInObserverList) {
+ nsSVGEffects::AddRenderingObserver(elem, this);
+ mInObserverList = true;
+ }
+ }
+
+ // Private data
+ const RefPtr<SVGDocumentWrapper> mDocWrapper;
+ VectorImage* const mVectorImage; // Raw pointer because it owns me.
+ bool mHonoringInvalidations;
+};
+
+NS_IMPL_ISUPPORTS(SVGRootRenderingObserver, nsIMutationObserver)
+
+class SVGParseCompleteListener final : public nsStubDocumentObserver {
+public:
+ NS_DECL_ISUPPORTS
+
+ SVGParseCompleteListener(nsIDocument* aDocument,
+ VectorImage* aImage)
+ : mDocument(aDocument)
+ , mImage(aImage)
+ {
+ MOZ_ASSERT(mDocument, "Need an SVG document");
+ MOZ_ASSERT(mImage, "Need an image");
+
+ mDocument->AddObserver(this);
+ }
+
+private:
+ ~SVGParseCompleteListener()
+ {
+ if (mDocument) {
+ // The document must have been destroyed before we got our event.
+ // Otherwise this can't happen, since documents hold strong references to
+ // their observers.
+ Cancel();
+ }
+ }
+
+public:
+ void EndLoad(nsIDocument* aDocument) override
+ {
+ MOZ_ASSERT(aDocument == mDocument, "Got EndLoad for wrong document?");
+
+ // OnSVGDocumentParsed will release our owner's reference to us, so ensure
+ // we stick around long enough to complete our work.
+ RefPtr<SVGParseCompleteListener> kungFuDeathGrip(this);
+
+ mImage->OnSVGDocumentParsed();
+ }
+
+ void Cancel()
+ {
+ MOZ_ASSERT(mDocument, "Duplicate call to Cancel");
+ if (mDocument) {
+ mDocument->RemoveObserver(this);
+ mDocument = nullptr;
+ }
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+ VectorImage* const mImage; // Raw pointer to owner.
+};
+
+NS_IMPL_ISUPPORTS(SVGParseCompleteListener, nsIDocumentObserver)
+
+class SVGLoadEventListener final : public nsIDOMEventListener {
+public:
+ NS_DECL_ISUPPORTS
+
+ SVGLoadEventListener(nsIDocument* aDocument,
+ VectorImage* aImage)
+ : mDocument(aDocument)
+ , mImage(aImage)
+ {
+ MOZ_ASSERT(mDocument, "Need an SVG document");
+ MOZ_ASSERT(mImage, "Need an image");
+
+ mDocument->AddEventListener(NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"),
+ this, true, false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("SVGAbort"), this, true,
+ false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("SVGError"), this, true,
+ false);
+ }
+
+private:
+ ~SVGLoadEventListener()
+ {
+ if (mDocument) {
+ // The document must have been destroyed before we got our event.
+ // Otherwise this can't happen, since documents hold strong references to
+ // their observers.
+ Cancel();
+ }
+ }
+
+public:
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override
+ {
+ MOZ_ASSERT(mDocument, "Need an SVG document. Received multiple events?");
+
+ // OnSVGDocumentLoaded/OnSVGDocumentError will release our owner's reference
+ // to us, so ensure we stick around long enough to complete our work.
+ RefPtr<SVGLoadEventListener> kungFuDeathGrip(this);
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ MOZ_ASSERT(eventType.EqualsLiteral("MozSVGAsImageDocumentLoad") ||
+ eventType.EqualsLiteral("SVGAbort") ||
+ eventType.EqualsLiteral("SVGError"),
+ "Received unexpected event");
+
+ if (eventType.EqualsLiteral("MozSVGAsImageDocumentLoad")) {
+ mImage->OnSVGDocumentLoaded();
+ } else {
+ mImage->OnSVGDocumentError();
+ }
+
+ return NS_OK;
+ }
+
+ void Cancel()
+ {
+ MOZ_ASSERT(mDocument, "Duplicate call to Cancel");
+ if (mDocument) {
+ mDocument
+ ->RemoveEventListener(NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"),
+ this, true);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("SVGAbort"), this, true);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("SVGError"), this, true);
+ mDocument = nullptr;
+ }
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+ VectorImage* const mImage; // Raw pointer to owner.
+};
+
+NS_IMPL_ISUPPORTS(SVGLoadEventListener, nsIDOMEventListener)
+
+// Helper-class: SVGDrawingCallback
+class SVGDrawingCallback : public gfxDrawingCallback {
+public:
+ SVGDrawingCallback(SVGDocumentWrapper* aSVGDocumentWrapper,
+ const IntRect& aViewport,
+ const IntSize& aSize,
+ uint32_t aImageFlags)
+ : mSVGDocumentWrapper(aSVGDocumentWrapper)
+ , mViewport(aViewport)
+ , mSize(aSize)
+ , mImageFlags(aImageFlags)
+ { }
+ virtual bool operator()(gfxContext* aContext,
+ const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform);
+private:
+ RefPtr<SVGDocumentWrapper> mSVGDocumentWrapper;
+ const IntRect mViewport;
+ const IntSize mSize;
+ uint32_t mImageFlags;
+};
+
+// Based loosely on nsSVGIntegrationUtils' PaintFrameCallback::operator()
+bool
+SVGDrawingCallback::operator()(gfxContext* aContext,
+ const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform)
+{
+ MOZ_ASSERT(mSVGDocumentWrapper, "need an SVGDocumentWrapper");
+
+ // Get (& sanity-check) the helper-doc's presShell
+ nsCOMPtr<nsIPresShell> presShell;
+ if (NS_FAILED(mSVGDocumentWrapper->GetPresShell(getter_AddRefs(presShell)))) {
+ NS_WARNING("Unable to draw -- presShell lookup failed");
+ return false;
+ }
+ MOZ_ASSERT(presShell, "GetPresShell succeeded but returned null");
+
+ gfxContextAutoSaveRestore contextRestorer(aContext);
+
+ // Clip to aFillRect so that we don't paint outside.
+ aContext->NewPath();
+ aContext->Rectangle(aFillRect);
+ aContext->Clip();
+
+ gfxMatrix matrix = aTransform;
+ if (!matrix.Invert()) {
+ return false;
+ }
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().PreMultiply(matrix).
+ Scale(double(mSize.width) / mViewport.width,
+ double(mSize.height) / mViewport.height));
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ MOZ_ASSERT(presContext, "pres shell w/out pres context");
+
+ nsRect svgRect(presContext->DevPixelsToAppUnits(mViewport.x),
+ presContext->DevPixelsToAppUnits(mViewport.y),
+ presContext->DevPixelsToAppUnits(mViewport.width),
+ presContext->DevPixelsToAppUnits(mViewport.height));
+
+ uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
+ if (!(mImageFlags & imgIContainer::FLAG_SYNC_DECODE)) {
+ renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES;
+ }
+
+ presShell->RenderDocument(svgRect, renderDocFlags,
+ NS_RGBA(0, 0, 0, 0), // transparent
+ aContext);
+
+ return true;
+}
+
+// Implement VectorImage's nsISupports-inherited methods
+NS_IMPL_ISUPPORTS(VectorImage,
+ imgIContainer,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+//------------------------------------------------------------------------------
+// Constructor / Destructor
+
+VectorImage::VectorImage(ImageURL* aURI /* = nullptr */) :
+ ImageResource(aURI), // invoke superclass's constructor
+ mLockCount(0),
+ mIsInitialized(false),
+ mIsFullyLoaded(false),
+ mIsDrawing(false),
+ mHaveAnimations(false),
+ mHasPendingInvalidation(false)
+{ }
+
+VectorImage::~VectorImage()
+{
+ CancelAllListeners();
+ SurfaceCache::RemoveImage(ImageKey(this));
+}
+
+//------------------------------------------------------------------------------
+// Methods inherited from Image.h
+
+nsresult
+VectorImage::Init(const char* aMimeType,
+ uint32_t aFlags)
+{
+ // We don't support re-initialization
+ if (mIsInitialized) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations && !mError,
+ "Flags unexpectedly set before initialization");
+ MOZ_ASSERT(!strcmp(aMimeType, IMAGE_SVG_XML), "Unexpected mimetype");
+
+ mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE);
+
+ // Lock this image's surfaces in the SurfaceCache if we're not discardable.
+ if (!mDiscardable) {
+ mLockCount++;
+ SurfaceCache::LockImage(ImageKey(this));
+ }
+
+ mIsInitialized = true;
+ return NS_OK;
+}
+
+size_t
+VectorImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
+{
+ if (!mSVGDocumentWrapper) {
+ return 0; // No document, so no memory used for the document.
+ }
+
+ nsIDocument* doc = mSVGDocumentWrapper->GetDocument();
+ if (!doc) {
+ return 0; // No document, so no memory used for the document.
+ }
+
+ nsWindowSizes windowSizes(aMallocSizeOf);
+ doc->DocAddSizeOfIncludingThis(&windowSizes);
+
+ if (windowSizes.getTotalSize() == 0) {
+ // MallocSizeOf fails on this platform. Because we also use this method for
+ // determining the size of cache entries, we need to return something
+ // reasonable here. Unfortunately, there's no way to estimate the document's
+ // size accurately, so we just use a constant value of 100KB, which will
+ // generally underestimate the true size.
+ return 100 * 1024;
+ }
+
+ return windowSizes.getTotalSize();
+}
+
+void
+VectorImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+ MallocSizeOf aMallocSizeOf) const
+{
+ SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf);
+}
+
+nsresult
+VectorImage::OnImageDataComplete(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus,
+ bool aLastPart)
+{
+ // Call our internal OnStopRequest method, which only talks to our embedded
+ // SVG document. This won't have any effect on our ProgressTracker.
+ nsresult finalStatus = OnStopRequest(aRequest, aContext, aStatus);
+
+ // Give precedence to Necko failure codes.
+ if (NS_FAILED(aStatus)) {
+ finalStatus = aStatus;
+ }
+
+ Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus);
+
+ if (mIsFullyLoaded || mError) {
+ // Our document is loaded, so we're ready to notify now.
+ mProgressTracker->SyncNotifyProgress(loadProgress);
+ } else {
+ // Record our progress so far; we'll actually send the notifications in
+ // OnSVGDocumentLoaded or OnSVGDocumentError.
+ mLoadProgress = Some(loadProgress);
+ }
+
+ return finalStatus;
+}
+
+nsresult
+VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ return OnDataAvailable(aRequest, aContext, aInStr, aSourceOffset, aCount);
+}
+
+nsresult
+VectorImage::StartAnimation()
+{
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(ShouldAnimate(), "Should not animate!");
+
+ mSVGDocumentWrapper->StartAnimation();
+ return NS_OK;
+}
+
+nsresult
+VectorImage::StopAnimation()
+{
+ nsresult rv = NS_OK;
+ if (mError) {
+ rv = NS_ERROR_FAILURE;
+ } else {
+ MOZ_ASSERT(mIsFullyLoaded && mHaveAnimations,
+ "Should not have been animating!");
+
+ mSVGDocumentWrapper->StopAnimation();
+ }
+
+ mAnimating = false;
+ return rv;
+}
+
+bool
+VectorImage::ShouldAnimate()
+{
+ return ImageResource::ShouldAnimate() && mIsFullyLoaded && mHaveAnimations;
+}
+
+NS_IMETHODIMP_(void)
+VectorImage::SetAnimationStartTime(const TimeStamp& aTime)
+{
+ // We don't care about animation start time.
+}
+
+//------------------------------------------------------------------------------
+// imgIContainer methods
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::GetWidth(int32_t* aWidth)
+{
+ if (mError || !mIsFullyLoaded) {
+ // XXXdholbert Technically we should leave outparam untouched when we
+ // fail. But since many callers don't check for failure, we set it to 0 on
+ // failure, for sane/predictable results.
+ *aWidth = 0;
+ return NS_ERROR_FAILURE;
+ }
+
+ SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem();
+ MOZ_ASSERT(rootElem, "Should have a root SVG elem, since we finished "
+ "loading without errors");
+ int32_t rootElemWidth = rootElem->GetIntrinsicWidth();
+ if (rootElemWidth < 0) {
+ *aWidth = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aWidth = rootElemWidth;
+ return NS_OK;
+}
+
+//******************************************************************************
+NS_IMETHODIMP_(void)
+VectorImage::RequestRefresh(const TimeStamp& aTime)
+{
+ if (HadRecentRefresh(aTime)) {
+ return;
+ }
+
+ PendingAnimationTracker* tracker =
+ mSVGDocumentWrapper->GetDocument()->GetPendingAnimationTracker();
+ if (tracker && ShouldAnimate()) {
+ tracker->TriggerPendingAnimationsOnNextTick(aTime);
+ }
+
+ EvaluateAnimation();
+
+ mSVGDocumentWrapper->TickRefreshDriver();
+
+ if (mHasPendingInvalidation) {
+ mHasPendingInvalidation = false;
+ SendInvalidationNotifications();
+ }
+}
+
+void
+VectorImage::SendInvalidationNotifications()
+{
+ // Animated images don't send out invalidation notifications as soon as
+ // they're generated. Instead, InvalidateObserversOnNextRefreshDriverTick
+ // records that there are pending invalidations and then returns immediately.
+ // The notifications are actually sent from RequestRefresh(). We send these
+ // notifications there to ensure that there is actually a document observing
+ // us. Otherwise, the notifications are just wasted effort.
+ //
+ // Non-animated images call this method directly from
+ // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never
+ // called for them. Ordinarily this isn't needed, since we send out
+ // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the
+ // SVG document may not be 100% ready to render at that time. In those cases
+ // we would miss the subsequent invalidations if we didn't send out the
+ // notifications directly in |InvalidateObservers...|.
+
+ if (mProgressTracker) {
+ SurfaceCache::RemoveImage(ImageKey(this));
+ mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
+ GetMaxSizedIntRect());
+ }
+}
+
+NS_IMETHODIMP_(IntRect)
+VectorImage::GetImageSpaceInvalidationRect(const IntRect& aRect)
+{
+ return aRect;
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::GetHeight(int32_t* aHeight)
+{
+ if (mError || !mIsFullyLoaded) {
+ // XXXdholbert Technically we should leave outparam untouched when we
+ // fail. But since many callers don't check for failure, we set it to 0 on
+ // failure, for sane/predictable results.
+ *aHeight = 0;
+ return NS_ERROR_FAILURE;
+ }
+
+ SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem();
+ MOZ_ASSERT(rootElem, "Should have a root SVG elem, since we finished "
+ "loading without errors");
+ int32_t rootElemHeight = rootElem->GetIntrinsicHeight();
+ if (rootElemHeight < 0) {
+ *aHeight = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aHeight = rootElemHeight;
+ return NS_OK;
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::GetIntrinsicSize(nsSize* aSize)
+{
+ if (mError || !mIsFullyLoaded) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
+ if (!rootFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aSize = nsSize(-1, -1);
+ IntrinsicSize rfSize = rootFrame->GetIntrinsicSize();
+ if (rfSize.width.GetUnit() == eStyleUnit_Coord) {
+ aSize->width = rfSize.width.GetCoordValue();
+ }
+ if (rfSize.height.GetUnit() == eStyleUnit_Coord) {
+ aSize->height = rfSize.height.GetCoordValue();
+ }
+
+ return NS_OK;
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::GetIntrinsicRatio(nsSize* aRatio)
+{
+ if (mError || !mIsFullyLoaded) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
+ if (!rootFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aRatio = rootFrame->GetIntrinsicRatio();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(Orientation)
+VectorImage::GetOrientation()
+{
+ return Orientation();
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::GetType(uint16_t* aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = imgIContainer::TYPE_VECTOR;
+ return NS_OK;
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::GetAnimated(bool* aAnimated)
+{
+ if (mError || !mIsFullyLoaded) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aAnimated = mSVGDocumentWrapper->IsAnimated();
+ return NS_OK;
+}
+
+//******************************************************************************
+int32_t
+VectorImage::GetFirstFrameDelay()
+{
+ if (mError) {
+ return -1;
+ }
+
+ if (!mSVGDocumentWrapper->IsAnimated()) {
+ return -1;
+ }
+
+ // We don't really have a frame delay, so just pretend that we constantly
+ // need updates.
+ return 0;
+}
+
+NS_IMETHODIMP_(bool)
+VectorImage::WillDrawOpaqueNow()
+{
+ return false; // In general, SVG content is not opaque.
+}
+
+//******************************************************************************
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags)
+{
+ if (mError) {
+ return nullptr;
+ }
+
+ // Look up height & width
+ // ----------------------
+ SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem();
+ MOZ_ASSERT(svgElem, "Should have a root SVG elem, since we finished "
+ "loading without errors");
+ nsIntSize imageIntSize(svgElem->GetIntrinsicWidth(),
+ svgElem->GetIntrinsicHeight());
+
+ if (imageIntSize.IsEmpty()) {
+ // We'll get here if our SVG doc has a percent-valued or negative width or
+ // height.
+ return nullptr;
+ }
+
+ return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags);
+}
+
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+VectorImage::GetFrameAtSize(const IntSize& aSize,
+ uint32_t aWhichFrame,
+ uint32_t aFlags)
+{
+ MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
+
+ if (aSize.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (aWhichFrame > FRAME_MAX_VALUE) {
+ return nullptr;
+ }
+
+ if (mError || !mIsFullyLoaded) {
+ return nullptr;
+ }
+
+ // Make our surface the size of what will ultimately be drawn to it.
+ // (either the full image size, or the restricted region)
+ RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
+ CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) {
+ NS_ERROR("Could not create a DrawTarget");
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(context); // already checked the draw target above
+
+ auto result = Draw(context, aSize, ImageRegion::Create(aSize),
+ aWhichFrame, SamplingFilter::POINT, Nothing(), aFlags);
+
+ return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr;
+}
+
+NS_IMETHODIMP_(bool)
+VectorImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
+{
+ return false;
+}
+
+//******************************************************************************
+NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
+VectorImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
+{
+ return nullptr;
+}
+
+struct SVGDrawingParameters
+{
+ SVGDrawingParameters(gfxContext* aContext,
+ const nsIntSize& aSize,
+ const ImageRegion& aRegion,
+ SamplingFilter aSamplingFilter,
+ const Maybe<SVGImageContext>& aSVGContext,
+ float aAnimationTime,
+ uint32_t aFlags)
+ : context(aContext)
+ , size(aSize.width, aSize.height)
+ , region(aRegion)
+ , samplingFilter(aSamplingFilter)
+ , svgContext(aSVGContext)
+ , viewportSize(aSize)
+ , animationTime(aAnimationTime)
+ , flags(aFlags)
+ , opacity(aSVGContext ? aSVGContext->GetGlobalOpacity() : 1.0)
+ {
+ if (aSVGContext) {
+ CSSIntSize sz = aSVGContext->GetViewportSize();
+ viewportSize = nsIntSize(sz.width, sz.height); // XXX losing unit
+ }
+ }
+
+ gfxContext* context;
+ IntSize size;
+ ImageRegion region;
+ SamplingFilter samplingFilter;
+ const Maybe<SVGImageContext>& svgContext;
+ nsIntSize viewportSize;
+ float animationTime;
+ uint32_t flags;
+ gfxFloat opacity;
+};
+
+//******************************************************************************
+NS_IMETHODIMP_(DrawResult)
+VectorImage::Draw(gfxContext* aContext,
+ const nsIntSize& aSize,
+ const ImageRegion& aRegion,
+ uint32_t aWhichFrame,
+ SamplingFilter aSamplingFilter,
+ const Maybe<SVGImageContext>& aSVGContext,
+ uint32_t aFlags)
+{
+ if (aWhichFrame > FRAME_MAX_VALUE) {
+ return DrawResult::BAD_ARGS;
+ }
+
+ if (!aContext) {
+ return DrawResult::BAD_ARGS;
+ }
+
+ if (mError) {
+ return DrawResult::BAD_IMAGE;
+ }
+
+ if (!mIsFullyLoaded) {
+ return DrawResult::NOT_READY;
+ }
+
+ if (mIsDrawing) {
+ NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ if (mAnimationConsumers == 0 && mProgressTracker) {
+ mProgressTracker->OnUnlockedDraw();
+ }
+
+ AutoRestore<bool> autoRestoreIsDrawing(mIsDrawing);
+ mIsDrawing = true;
+
+ Maybe<SVGImageContext> svgContext;
+ // If FLAG_FORCE_PRESERVEASPECTRATIO_NONE bit is set, that mean we should
+ // overwrite SVG preserveAspectRatio attibute of this image with none, and
+ // always stretch this image to viewport non-uniformly.
+ // And we can do this only if the caller pass in the the SVG viewport, via
+ // aSVGContext.
+ if ((aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) && aSVGContext.isSome()) {
+ Maybe<SVGPreserveAspectRatio> aspectRatio =
+ Some(SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE,
+ SVG_MEETORSLICE_UNKNOWN));
+ svgContext =
+ Some(SVGImageContext(aSVGContext->GetViewportSize(),
+ aspectRatio));
+ } else {
+ svgContext = aSVGContext;
+ }
+
+ float animTime =
+ (aWhichFrame == FRAME_FIRST) ? 0.0f
+ : mSVGDocumentWrapper->GetCurrentTime();
+ AutoSVGRenderingState autoSVGState(svgContext, animTime,
+ mSVGDocumentWrapper->GetRootSVGElem());
+
+
+ SVGDrawingParameters params(aContext, aSize, aRegion, aSamplingFilter,
+ svgContext, animTime, aFlags);
+
+ // If we have an prerasterized version of this image that matches the
+ // drawing parameters, use that.
+ RefPtr<gfxDrawable> svgDrawable = LookupCachedSurface(params);
+ if (svgDrawable) {
+ Show(svgDrawable, params);
+ return DrawResult::SUCCESS;
+ }
+
+ // We didn't get a hit in the surface cache, so we'll need to rerasterize.
+ CreateSurfaceAndShow(params, aContext->GetDrawTarget()->GetBackendType());
+ return DrawResult::SUCCESS;
+}
+
+already_AddRefed<gfxDrawable>
+VectorImage::LookupCachedSurface(const SVGDrawingParameters& aParams)
+{
+ // If we're not allowed to use a cached surface, don't attempt a lookup.
+ if (aParams.flags & FLAG_BYPASS_SURFACE_CACHE) {
+ return nullptr;
+ }
+
+ // We don't do any caching if we have animation, so don't bother with a lookup
+ // in this case either.
+ if (mHaveAnimations) {
+ return nullptr;
+ }
+
+ LookupResult result =
+ SurfaceCache::Lookup(ImageKey(this),
+ VectorSurfaceKey(aParams.size, aParams.svgContext));
+ if (!result) {
+ return nullptr; // No matching surface, or the OS freed the volatile buffer.
+ }
+
+ RefPtr<SourceSurface> sourceSurface = result.Surface()->GetSourceSurface();
+ if (!sourceSurface) {
+ // Something went wrong. (Probably a GPU driver crash or device reset.)
+ // Attempt to recover.
+ RecoverFromLossOfSurfaces();
+ return nullptr;
+ }
+
+ RefPtr<gfxDrawable> svgDrawable =
+ new gfxSurfaceDrawable(sourceSurface, result.Surface()->GetSize());
+ return svgDrawable.forget();
+}
+
+void
+VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams, BackendType aBackend)
+{
+ mSVGDocumentWrapper->UpdateViewportBounds(aParams.viewportSize);
+ mSVGDocumentWrapper->FlushImageTransformInvalidation();
+
+ RefPtr<gfxDrawingCallback> cb =
+ new SVGDrawingCallback(mSVGDocumentWrapper,
+ IntRect(IntPoint(0, 0), aParams.viewportSize),
+ aParams.size,
+ aParams.flags);
+
+ RefPtr<gfxDrawable> svgDrawable =
+ new gfxCallbackDrawable(cb, aParams.size);
+
+ bool bypassCache = bool(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) ||
+ // Refuse to cache animated images:
+ // XXX(seth): We may remove this restriction in bug 922893.
+ mHaveAnimations ||
+ // The image is too big to fit in the cache:
+ !SurfaceCache::CanHold(aParams.size);
+ if (bypassCache) {
+ return Show(svgDrawable, aParams);
+ }
+
+ // We're about to rerasterize, which may mean that some of the previous
+ // surfaces we've rasterized aren't useful anymore. We can allow them to
+ // expire from the cache by unlocking them here, and then sending out an
+ // invalidation. If this image is locked, any surfaces that are still useful
+ // will become locked again when Draw touches them, and the remainder will
+ // eventually expire.
+ SurfaceCache::UnlockEntries(ImageKey(this));
+
+ // Try to create an imgFrame, initializing the surface it contains by drawing
+ // our gfxDrawable into it. (We use FILTER_NEAREST since we never scale here.)
+ NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame);
+ nsresult rv =
+ frame->InitWithDrawable(svgDrawable, aParams.size,
+ SurfaceFormat::B8G8R8A8,
+ SamplingFilter::POINT, aParams.flags,
+ aBackend);
+
+ // If we couldn't create the frame, it was probably because it would end
+ // up way too big. Generally it also wouldn't fit in the cache, but the prefs
+ // could be set such that the cache isn't the limiting factor.
+ if (NS_FAILED(rv)) {
+ return Show(svgDrawable, aParams);
+ }
+
+ // Take a strong reference to the frame's surface and make sure it hasn't
+ // already been purged by the operating system.
+ RefPtr<SourceSurface> surface = frame->GetSourceSurface();
+ if (!surface) {
+ return Show(svgDrawable, aParams);
+ }
+
+ // Attempt to cache the frame.
+ SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext);
+ NotNull<RefPtr<ISurfaceProvider>> provider =
+ WrapNotNull(new SimpleSurfaceProvider(ImageKey(this), surfaceKey, frame));
+ SurfaceCache::Insert(provider);
+
+ // Draw.
+ RefPtr<gfxDrawable> drawable =
+ new gfxSurfaceDrawable(surface, aParams.size);
+ Show(drawable, aParams);
+
+ // Send out an invalidation so that surfaces that are still in use get
+ // re-locked. See the discussion of the UnlockSurfaces call above.
+ mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
+ GetMaxSizedIntRect());
+}
+
+
+void
+VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams)
+{
+ MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now");
+ gfxUtils::DrawPixelSnapped(aParams.context, aDrawable,
+ aParams.size,
+ aParams.region,
+ SurfaceFormat::B8G8R8A8,
+ aParams.samplingFilter,
+ aParams.flags, aParams.opacity);
+
+ MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
+ mRenderingObserver->ResumeHonoringInvalidations();
+}
+
+void
+VectorImage::RecoverFromLossOfSurfaces()
+{
+ NS_WARNING("An imgFrame became invalid. Attempting to recover...");
+
+ // Discard all existing frames, since they're probably all now invalid.
+ SurfaceCache::RemoveImage(ImageKey(this));
+}
+
+NS_IMETHODIMP
+VectorImage::StartDecoding()
+{
+ // Nothing to do for SVG images
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VectorImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
+{
+ // Nothing to do for SVG images, though in theory we could rasterize to the
+ // provided size ahead of time if we supported off-main-thread SVG
+ // rasterization...
+ return NS_OK;
+}
+
+//******************************************************************************
+
+NS_IMETHODIMP
+VectorImage::LockImage()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLockCount++;
+
+ if (mLockCount == 1) {
+ // Lock this image's surfaces in the SurfaceCache.
+ SurfaceCache::LockImage(ImageKey(this));
+ }
+
+ return NS_OK;
+}
+
+//******************************************************************************
+
+NS_IMETHODIMP
+VectorImage::UnlockImage()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mLockCount == 0) {
+ MOZ_ASSERT_UNREACHABLE("Calling UnlockImage with a zero lock count");
+ return NS_ERROR_ABORT;
+ }
+
+ mLockCount--;
+
+ if (mLockCount == 0) {
+ // Unlock this image's surfaces in the SurfaceCache.
+ SurfaceCache::UnlockImage(ImageKey(this));
+ }
+
+ return NS_OK;
+}
+
+//******************************************************************************
+
+NS_IMETHODIMP
+VectorImage::RequestDiscard()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mDiscardable && mLockCount == 0) {
+ SurfaceCache::RemoveImage(ImageKey(this));
+ mProgressTracker->OnDiscard();
+ }
+
+ return NS_OK;
+}
+
+void
+VectorImage::OnSurfaceDiscarded()
+{
+ MOZ_ASSERT(mProgressTracker);
+
+ NS_DispatchToMainThread(NewRunnableMethod(mProgressTracker, &ProgressTracker::OnDiscard));
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::ResetAnimation()
+{
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mIsFullyLoaded || !mHaveAnimations) {
+ return NS_OK; // There are no animations to be reset.
+ }
+
+ mSVGDocumentWrapper->ResetAnimation();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(float)
+VectorImage::GetFrameIndex(uint32_t aWhichFrame)
+{
+ MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
+ return aWhichFrame == FRAME_FIRST
+ ? 0.0f
+ : mSVGDocumentWrapper->GetCurrentTime();
+}
+
+//------------------------------------------------------------------------------
+// nsIRequestObserver methods
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt)
+{
+ MOZ_ASSERT(!mSVGDocumentWrapper,
+ "Repeated call to OnStartRequest -- can this happen?");
+
+ mSVGDocumentWrapper = new SVGDocumentWrapper();
+ nsresult rv = mSVGDocumentWrapper->OnStartRequest(aRequest, aCtxt);
+ if (NS_FAILED(rv)) {
+ mSVGDocumentWrapper = nullptr;
+ mError = true;
+ return rv;
+ }
+
+ // ProgressTracker::SyncNotifyProgress may release us, so ensure we
+ // stick around long enough to complete our work.
+ RefPtr<VectorImage> kungFuDeathGrip(this);
+
+ // Block page load until the document's ready. (We unblock it in
+ // OnSVGDocumentLoaded or OnSVGDocumentError.)
+ if (mProgressTracker) {
+ mProgressTracker->SyncNotifyProgress(FLAG_ONLOAD_BLOCKED);
+ }
+
+ // Create a listener to wait until the SVG document is fully loaded, which
+ // will signal that this image is ready to render. Certain error conditions
+ // will prevent us from ever getting this notification, so we also create a
+ // listener that waits for parsing to complete and cancels the
+ // SVGLoadEventListener if needed. The listeners are automatically attached
+ // to the document by their constructors.
+ nsIDocument* document = mSVGDocumentWrapper->GetDocument();
+ mLoadEventListener = new SVGLoadEventListener(document, this);
+ mParseCompleteListener = new SVGParseCompleteListener(document, this);
+
+ return NS_OK;
+}
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt,
+ nsresult aStatus)
+{
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mSVGDocumentWrapper->OnStopRequest(aRequest, aCtxt, aStatus);
+}
+
+void
+VectorImage::OnSVGDocumentParsed()
+{
+ MOZ_ASSERT(mParseCompleteListener, "Should have the parse complete listener");
+ MOZ_ASSERT(mLoadEventListener, "Should have the load event listener");
+
+ if (!mSVGDocumentWrapper->GetRootSVGElem()) {
+ // This is an invalid SVG document. It may have failed to parse, or it may
+ // be missing the <svg> root element, or the <svg> root element may not
+ // declare the correct namespace. In any of these cases, we'll never be
+ // notified that the SVG finished loading, so we need to treat this as an
+ // error.
+ OnSVGDocumentError();
+ }
+}
+
+void
+VectorImage::CancelAllListeners()
+{
+ if (mParseCompleteListener) {
+ mParseCompleteListener->Cancel();
+ mParseCompleteListener = nullptr;
+ }
+ if (mLoadEventListener) {
+ mLoadEventListener->Cancel();
+ mLoadEventListener = nullptr;
+ }
+}
+
+void
+VectorImage::OnSVGDocumentLoaded()
+{
+ MOZ_ASSERT(mSVGDocumentWrapper->GetRootSVGElem(),
+ "Should have parsed successfully");
+ MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations,
+ "These flags shouldn't get set until OnSVGDocumentLoaded. "
+ "Duplicate calls to OnSVGDocumentLoaded?");
+
+ CancelAllListeners();
+
+ // XXX Flushing is wasteful if embedding frame hasn't had initial reflow.
+ mSVGDocumentWrapper->FlushLayout();
+
+ mIsFullyLoaded = true;
+ mHaveAnimations = mSVGDocumentWrapper->IsAnimated();
+
+ // Start listening to our image for rendering updates.
+ mRenderingObserver = new SVGRootRenderingObserver(mSVGDocumentWrapper, this);
+
+ // ProgressTracker::SyncNotifyProgress may release us, so ensure we
+ // stick around long enough to complete our work.
+ RefPtr<VectorImage> kungFuDeathGrip(this);
+
+ // Tell *our* observers that we're done loading.
+ if (mProgressTracker) {
+ Progress progress = FLAG_SIZE_AVAILABLE |
+ FLAG_HAS_TRANSPARENCY |
+ FLAG_FRAME_COMPLETE |
+ FLAG_DECODE_COMPLETE |
+ FLAG_ONLOAD_UNBLOCKED;
+
+ if (mHaveAnimations) {
+ progress |= FLAG_IS_ANIMATED;
+ }
+
+ // Merge in any saved progress from OnImageDataComplete.
+ if (mLoadProgress) {
+ progress |= *mLoadProgress;
+ mLoadProgress = Nothing();
+ }
+
+ mProgressTracker->SyncNotifyProgress(progress, GetMaxSizedIntRect());
+ }
+
+ EvaluateAnimation();
+}
+
+void
+VectorImage::OnSVGDocumentError()
+{
+ CancelAllListeners();
+
+ mError = true;
+
+ if (mProgressTracker) {
+ // Notify observers about the error and unblock page load.
+ Progress progress = FLAG_ONLOAD_UNBLOCKED | FLAG_HAS_ERROR;
+
+ // Merge in any saved progress from OnImageDataComplete.
+ if (mLoadProgress) {
+ progress |= *mLoadProgress;
+ mLoadProgress = Nothing();
+ }
+
+ mProgressTracker->SyncNotifyProgress(progress);
+ }
+}
+
+//------------------------------------------------------------------------------
+// nsIStreamListener method
+
+//******************************************************************************
+NS_IMETHODIMP
+VectorImage::OnDataAvailable(nsIRequest* aRequest, nsISupports* aCtxt,
+ nsIInputStream* aInStr, uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mSVGDocumentWrapper->OnDataAvailable(aRequest, aCtxt, aInStr,
+ aSourceOffset, aCount);
+}
+
+// --------------------------
+// Invalidation helper method
+
+void
+VectorImage::InvalidateObserversOnNextRefreshDriverTick()
+{
+ if (mHaveAnimations) {
+ mHasPendingInvalidation = true;
+ } else {
+ SendInvalidationNotifications();
+ }
+}
+
+void
+VectorImage::PropagateUseCounters(nsIDocument* aParentDocument)
+{
+ nsIDocument* doc = mSVGDocumentWrapper->GetDocument();
+ if (doc) {
+ doc->PropagateUseCounters(aParentDocument);
+ }
+}
+
+void
+VectorImage::ReportUseCounters()
+{
+ nsIDocument* doc = mSVGDocumentWrapper->GetDocument();
+ if (doc) {
+ static_cast<nsDocument*>(doc)->ReportUseCounters();
+ }
+}
+
+nsIntSize
+VectorImage::OptimalImageSizeForDest(const gfxSize& aDest,
+ uint32_t aWhichFrame,
+ SamplingFilter aSamplingFilter,
+ uint32_t aFlags)
+{
+ MOZ_ASSERT(aDest.width >= 0 || ceil(aDest.width) <= INT32_MAX ||
+ aDest.height >= 0 || ceil(aDest.height) <= INT32_MAX,
+ "Unexpected destination size");
+
+ // We can rescale SVGs freely, so just return the provided destination size.
+ return nsIntSize::Ceil(aDest.width, aDest.height);
+}
+
+already_AddRefed<imgIContainer>
+VectorImage::Unwrap()
+{
+ nsCOMPtr<imgIContainer> self(this);
+ return self.forget();
+}
+
+} // namespace image
+} // namespace mozilla