michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "VectorImage.h" michael@0: michael@0: #include "gfx2DGlue.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxDrawable.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxUtils.h" michael@0: #include "imgDecoderObserver.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/dom/SVGSVGElement.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRect.h" michael@0: #include "nsStubDocumentObserver.h" michael@0: #include "nsSVGEffects.h" // for nsSVGRenderingObserver michael@0: #include "Orientation.h" michael@0: #include "SVGDocumentWrapper.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "SurfaceCache.h" michael@0: michael@0: // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK michael@0: #undef GetCurrentTime michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace dom; michael@0: using namespace gfx; michael@0: using namespace layers; michael@0: michael@0: namespace image { michael@0: michael@0: // Helper-class: SVGRootRenderingObserver michael@0: class SVGRootRenderingObserver MOZ_FINAL : public nsSVGRenderingObserver { michael@0: public: michael@0: SVGRootRenderingObserver(SVGDocumentWrapper* aDocWrapper, michael@0: VectorImage* aVectorImage) michael@0: : nsSVGRenderingObserver() michael@0: , mDocWrapper(aDocWrapper) michael@0: , mVectorImage(aVectorImage) michael@0: , mHonoringInvalidations(true) michael@0: { michael@0: MOZ_ASSERT(mDocWrapper, "Need a non-null SVG document wrapper"); michael@0: MOZ_ASSERT(mVectorImage, "Need a non-null VectorImage"); michael@0: michael@0: StartListening(); michael@0: Element* elem = GetTarget(); michael@0: MOZ_ASSERT(elem, "no root SVG node for us to observe"); michael@0: michael@0: nsSVGEffects::AddRenderingObserver(elem, this); michael@0: mInObserverList = true; michael@0: } michael@0: michael@0: virtual ~SVGRootRenderingObserver() michael@0: { michael@0: StopListening(); michael@0: } michael@0: michael@0: void ResumeHonoringInvalidations() michael@0: { michael@0: mHonoringInvalidations = true; michael@0: } michael@0: michael@0: protected: michael@0: virtual Element* GetTarget() MOZ_OVERRIDE michael@0: { michael@0: return mDocWrapper->GetRootSVGElem(); michael@0: } michael@0: michael@0: virtual void DoUpdate() MOZ_OVERRIDE michael@0: { michael@0: Element* elem = GetTarget(); michael@0: MOZ_ASSERT(elem, "missing root SVG node"); michael@0: michael@0: if (mHonoringInvalidations && !mDocWrapper->ShouldIgnoreInvalidation()) { michael@0: nsIFrame* frame = elem->GetPrimaryFrame(); michael@0: if (!frame || frame->PresContext()->PresShell()->IsDestroying()) { michael@0: // We're being destroyed. Bail out. michael@0: return; michael@0: } michael@0: michael@0: // Ignore further invalidations until we draw. michael@0: mHonoringInvalidations = false; michael@0: michael@0: mVectorImage->InvalidateObserversOnNextRefreshDriverTick(); michael@0: } michael@0: michael@0: // Our caller might've removed us from rendering-observer list. michael@0: // Add ourselves back! michael@0: if (!mInObserverList) { michael@0: nsSVGEffects::AddRenderingObserver(elem, this); michael@0: mInObserverList = true; michael@0: } michael@0: } michael@0: michael@0: // Private data michael@0: const nsRefPtr mDocWrapper; michael@0: VectorImage* const mVectorImage; // Raw pointer because it owns me. michael@0: bool mHonoringInvalidations; michael@0: }; michael@0: michael@0: class SVGParseCompleteListener MOZ_FINAL : public nsStubDocumentObserver { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: SVGParseCompleteListener(nsIDocument* aDocument, michael@0: VectorImage* aImage) michael@0: : mDocument(aDocument) michael@0: , mImage(aImage) michael@0: { michael@0: MOZ_ASSERT(mDocument, "Need an SVG document"); michael@0: MOZ_ASSERT(mImage, "Need an image"); michael@0: michael@0: mDocument->AddObserver(this); michael@0: } michael@0: michael@0: ~SVGParseCompleteListener() michael@0: { michael@0: if (mDocument) { michael@0: // The document must have been destroyed before we got our event. michael@0: // Otherwise this can't happen, since documents hold strong references to michael@0: // their observers. michael@0: Cancel(); michael@0: } michael@0: } michael@0: michael@0: void EndLoad(nsIDocument* aDocument) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(aDocument == mDocument, "Got EndLoad for wrong document?"); michael@0: michael@0: // OnSVGDocumentParsed will release our owner's reference to us, so ensure michael@0: // we stick around long enough to complete our work. michael@0: nsRefPtr kungFuDeathGroup(this); michael@0: michael@0: mImage->OnSVGDocumentParsed(); michael@0: } michael@0: michael@0: void Cancel() michael@0: { michael@0: MOZ_ASSERT(mDocument, "Duplicate call to Cancel"); michael@0: if (mDocument) { michael@0: mDocument->RemoveObserver(this); michael@0: mDocument = nullptr; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mDocument; michael@0: VectorImage* const mImage; // Raw pointer to owner. michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(SVGParseCompleteListener, nsIDocumentObserver) michael@0: michael@0: class SVGLoadEventListener MOZ_FINAL : public nsIDOMEventListener { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: SVGLoadEventListener(nsIDocument* aDocument, michael@0: VectorImage* aImage) michael@0: : mDocument(aDocument) michael@0: , mImage(aImage) michael@0: { michael@0: MOZ_ASSERT(mDocument, "Need an SVG document"); michael@0: MOZ_ASSERT(mImage, "Need an image"); michael@0: michael@0: mDocument->AddEventListener(NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), this, true, false); michael@0: mDocument->AddEventListener(NS_LITERAL_STRING("SVGAbort"), this, true, false); michael@0: mDocument->AddEventListener(NS_LITERAL_STRING("SVGError"), this, true, false); michael@0: } michael@0: michael@0: ~SVGLoadEventListener() michael@0: { michael@0: if (mDocument) { michael@0: // The document must have been destroyed before we got our event. michael@0: // Otherwise this can't happen, since documents hold strong references to michael@0: // their observers. michael@0: Cancel(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(mDocument, "Need an SVG document. Received multiple events?"); michael@0: michael@0: // OnSVGDocumentLoaded/OnSVGDocumentError will release our owner's reference michael@0: // to us, so ensure we stick around long enough to complete our work. michael@0: nsRefPtr kungFuDeathGroup(this); michael@0: michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: MOZ_ASSERT(eventType.EqualsLiteral("MozSVGAsImageDocumentLoad") || michael@0: eventType.EqualsLiteral("SVGAbort") || michael@0: eventType.EqualsLiteral("SVGError"), michael@0: "Received unexpected event"); michael@0: michael@0: if (eventType.EqualsLiteral("MozSVGAsImageDocumentLoad")) { michael@0: mImage->OnSVGDocumentLoaded(); michael@0: } else { michael@0: mImage->OnSVGDocumentError(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void Cancel() michael@0: { michael@0: MOZ_ASSERT(mDocument, "Duplicate call to Cancel"); michael@0: if (mDocument) { michael@0: mDocument->RemoveEventListener(NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), this, true); michael@0: mDocument->RemoveEventListener(NS_LITERAL_STRING("SVGAbort"), this, true); michael@0: mDocument->RemoveEventListener(NS_LITERAL_STRING("SVGError"), this, true); michael@0: mDocument = nullptr; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mDocument; michael@0: VectorImage* const mImage; // Raw pointer to owner. michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(SVGLoadEventListener, nsIDOMEventListener) michael@0: michael@0: // Helper-class: SVGDrawingCallback michael@0: class SVGDrawingCallback : public gfxDrawingCallback { michael@0: public: michael@0: SVGDrawingCallback(SVGDocumentWrapper* aSVGDocumentWrapper, michael@0: const nsIntRect& aViewport, michael@0: const gfxSize& aScale, michael@0: uint32_t aImageFlags) : michael@0: mSVGDocumentWrapper(aSVGDocumentWrapper), michael@0: mViewport(aViewport), michael@0: mScale(aScale), michael@0: mImageFlags(aImageFlags) michael@0: {} michael@0: virtual bool operator()(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform); michael@0: private: michael@0: nsRefPtr mSVGDocumentWrapper; michael@0: const nsIntRect mViewport; michael@0: const gfxSize mScale; michael@0: uint32_t mImageFlags; michael@0: }; michael@0: michael@0: // Based loosely on nsSVGIntegrationUtils' PaintFrameCallback::operator() michael@0: bool michael@0: SVGDrawingCallback::operator()(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform) michael@0: { michael@0: MOZ_ASSERT(mSVGDocumentWrapper, "need an SVGDocumentWrapper"); michael@0: michael@0: // Get (& sanity-check) the helper-doc's presShell michael@0: nsCOMPtr presShell; michael@0: if (NS_FAILED(mSVGDocumentWrapper->GetPresShell(getter_AddRefs(presShell)))) { michael@0: NS_WARNING("Unable to draw -- presShell lookup failed"); michael@0: return false; michael@0: } michael@0: MOZ_ASSERT(presShell, "GetPresShell succeeded but returned null"); michael@0: michael@0: gfxContextAutoSaveRestore contextRestorer(aContext); michael@0: michael@0: // Clip to aFillRect so that we don't paint outside. michael@0: aContext->NewPath(); michael@0: aContext->Rectangle(aFillRect); michael@0: aContext->Clip(); michael@0: michael@0: gfxContextMatrixAutoSaveRestore contextMatrixRestorer(aContext); michael@0: aContext->Multiply(gfxMatrix(aTransform).Invert()); michael@0: aContext->Scale(1.0 / mScale.width, 1.0 / mScale.height); michael@0: michael@0: nsPresContext* presContext = presShell->GetPresContext(); michael@0: MOZ_ASSERT(presContext, "pres shell w/out pres context"); michael@0: michael@0: nsRect svgRect(presContext->DevPixelsToAppUnits(mViewport.x), michael@0: presContext->DevPixelsToAppUnits(mViewport.y), michael@0: presContext->DevPixelsToAppUnits(mViewport.width), michael@0: presContext->DevPixelsToAppUnits(mViewport.height)); michael@0: michael@0: uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; michael@0: if (!(mImageFlags & imgIContainer::FLAG_SYNC_DECODE)) { michael@0: renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; michael@0: } michael@0: michael@0: presShell->RenderDocument(svgRect, renderDocFlags, michael@0: NS_RGBA(0, 0, 0, 0), // transparent michael@0: aContext); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Implement VectorImage's nsISupports-inherited methods michael@0: NS_IMPL_ISUPPORTS(VectorImage, michael@0: imgIContainer, michael@0: nsIStreamListener, michael@0: nsIRequestObserver) michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // Constructor / Destructor michael@0: michael@0: VectorImage::VectorImage(imgStatusTracker* aStatusTracker, michael@0: ImageURL* aURI /* = nullptr */) : michael@0: ImageResource(aURI), // invoke superclass's constructor michael@0: mIsInitialized(false), michael@0: mIsFullyLoaded(false), michael@0: mIsDrawing(false), michael@0: mHaveAnimations(false), michael@0: mHasPendingInvalidation(false) michael@0: { michael@0: mStatusTrackerInit = new imgStatusTrackerInit(this, aStatusTracker); michael@0: } michael@0: michael@0: VectorImage::~VectorImage() michael@0: { michael@0: CancelAllListeners(); michael@0: SurfaceCache::Discard(this); michael@0: } michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // Methods inherited from Image.h michael@0: michael@0: nsresult michael@0: VectorImage::Init(const char* aMimeType, michael@0: uint32_t aFlags) michael@0: { michael@0: // We don't support re-initialization michael@0: if (mIsInitialized) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations && !mError, michael@0: "Flags unexpectedly set before initialization"); michael@0: MOZ_ASSERT(!strcmp(aMimeType, IMAGE_SVG_XML), "Unexpected mimetype"); michael@0: michael@0: mIsInitialized = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIntRect michael@0: VectorImage::FrameRect(uint32_t aWhichFrame) michael@0: { michael@0: return nsIntRect::GetMaxSizedIntRect(); michael@0: } michael@0: michael@0: size_t michael@0: VectorImage::HeapSizeOfSourceWithComputedFallback(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: // We're not storing the source data -- we just feed that directly to michael@0: // our helper SVG document as we receive it, for it to parse. michael@0: // So 0 is an appropriate return value here. michael@0: return 0; michael@0: } michael@0: michael@0: size_t michael@0: VectorImage::HeapSizeOfDecodedWithComputedFallback(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: // XXXdholbert TODO: return num bytes used by helper SVG doc. (bug 590790) michael@0: return 0; michael@0: } michael@0: michael@0: size_t michael@0: VectorImage::NonHeapSizeOfDecoded() const michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: size_t michael@0: VectorImage::OutOfProcessSizeOfDecoded() const michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: VectorImage::OnImageDataComplete(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsresult aStatus, michael@0: bool aLastPart) michael@0: { michael@0: // Call our internal OnStopRequest method, which only talks to our embedded michael@0: // SVG document. This won't have any effect on our imgStatusTracker. michael@0: nsresult finalStatus = OnStopRequest(aRequest, aContext, aStatus); michael@0: michael@0: // Give precedence to Necko failure codes. michael@0: if (NS_FAILED(aStatus)) michael@0: finalStatus = aStatus; michael@0: michael@0: // Actually fire OnStopRequest. michael@0: if (mStatusTracker) { michael@0: // XXX(seth): Is this seriously the least insane way to do this? michael@0: nsRefPtr clone = mStatusTracker->CloneForRecording(); michael@0: imgDecoderObserver* observer = clone->GetDecoderObserver(); michael@0: observer->OnStopRequest(aLastPart, finalStatus); michael@0: ImageStatusDiff diff = mStatusTracker->Difference(clone); michael@0: mStatusTracker->ApplyDifference(diff); michael@0: mStatusTracker->SyncNotifyDifference(diff); michael@0: } michael@0: return finalStatus; michael@0: } michael@0: michael@0: nsresult michael@0: VectorImage::OnImageDataAvailable(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsIInputStream* aInStr, michael@0: uint64_t aSourceOffset, michael@0: uint32_t aCount) michael@0: { michael@0: return OnDataAvailable(aRequest, aContext, aInStr, aSourceOffset, aCount); michael@0: } michael@0: michael@0: nsresult michael@0: VectorImage::OnNewSourceData() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: VectorImage::StartAnimation() michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: MOZ_ASSERT(ShouldAnimate(), "Should not animate!"); michael@0: michael@0: mSVGDocumentWrapper->StartAnimation(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: VectorImage::StopAnimation() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (mError) { michael@0: rv = NS_ERROR_FAILURE; michael@0: } else { michael@0: MOZ_ASSERT(mIsFullyLoaded && mHaveAnimations, michael@0: "Should not have been animating!"); michael@0: michael@0: mSVGDocumentWrapper->StopAnimation(); michael@0: } michael@0: michael@0: mAnimating = false; michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: VectorImage::ShouldAnimate() michael@0: { michael@0: return ImageResource::ShouldAnimate() && mIsFullyLoaded && mHaveAnimations; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: VectorImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime) michael@0: { michael@0: // We don't care about animation start time. michael@0: } michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // imgIContainer methods michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute int32_t width; */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetWidth(int32_t* aWidth) michael@0: { michael@0: if (mError || !mIsFullyLoaded) { michael@0: *aWidth = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mSVGDocumentWrapper->GetWidthOrHeight(SVGDocumentWrapper::eWidth, michael@0: *aWidth)) { michael@0: *aWidth = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [notxpcom] void requestRefresh ([const] in TimeStamp aTime); */ michael@0: NS_IMETHODIMP_(void) michael@0: VectorImage::RequestRefresh(const mozilla::TimeStamp& aTime) michael@0: { michael@0: // TODO: Implement for b666446. michael@0: EvaluateAnimation(); michael@0: michael@0: if (mHasPendingInvalidation) { michael@0: SendInvalidationNotifications(); michael@0: mHasPendingInvalidation = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: VectorImage::SendInvalidationNotifications() michael@0: { michael@0: // Animated images don't send out invalidation notifications as soon as michael@0: // they're generated. Instead, InvalidateObserversOnNextRefreshDriverTick michael@0: // records that there are pending invalidations and then returns immediately. michael@0: // The notifications are actually sent from RequestRefresh(). We send these michael@0: // notifications there to ensure that there is actually a document observing michael@0: // us. Otherwise, the notifications are just wasted effort. michael@0: // michael@0: // Non-animated images call this method directly from michael@0: // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never michael@0: // called for them. Ordinarily this isn't needed, since we send out michael@0: // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the michael@0: // SVG document may not be 100% ready to render at that time. In those cases michael@0: // we would miss the subsequent invalidations if we didn't send out the michael@0: // notifications directly in |InvalidateObservers...|. michael@0: michael@0: if (mStatusTracker) { michael@0: SurfaceCache::Discard(this); michael@0: mStatusTracker->FrameChanged(&nsIntRect::GetMaxSizedIntRect()); michael@0: mStatusTracker->OnStopFrame(); michael@0: } michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute int32_t height; */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetHeight(int32_t* aHeight) michael@0: { michael@0: if (mError || !mIsFullyLoaded) { michael@0: *aHeight = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mSVGDocumentWrapper->GetWidthOrHeight(SVGDocumentWrapper::eHeight, michael@0: *aHeight)) { michael@0: *aHeight = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] readonly attribute nsSize intrinsicSize; */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetIntrinsicSize(nsSize* aSize) michael@0: { michael@0: if (mError || !mIsFullyLoaded) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame(); michael@0: if (!rootFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aSize = nsSize(-1, -1); michael@0: IntrinsicSize rfSize = rootFrame->GetIntrinsicSize(); michael@0: if (rfSize.width.GetUnit() == eStyleUnit_Coord) michael@0: aSize->width = rfSize.width.GetCoordValue(); michael@0: if (rfSize.height.GetUnit() == eStyleUnit_Coord) michael@0: aSize->height = rfSize.height.GetCoordValue(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] readonly attribute nsSize intrinsicRatio; */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetIntrinsicRatio(nsSize* aRatio) michael@0: { michael@0: if (mError || !mIsFullyLoaded) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame(); michael@0: if (!rootFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aRatio = rootFrame->GetIntrinsicRatio(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(Orientation) michael@0: VectorImage::GetOrientation() michael@0: { michael@0: return Orientation(); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute unsigned short type; */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetType(uint16_t* aType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aType); michael@0: michael@0: *aType = GetType(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript, notxpcom] uint16_t GetType(); */ michael@0: NS_IMETHODIMP_(uint16_t) michael@0: VectorImage::GetType() michael@0: { michael@0: return imgIContainer::TYPE_VECTOR; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute boolean animated; */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetAnimated(bool* aAnimated) michael@0: { michael@0: if (mError || !mIsFullyLoaded) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aAnimated = mSVGDocumentWrapper->IsAnimated(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [notxpcom] int32_t getFirstFrameDelay (); */ michael@0: int32_t michael@0: VectorImage::GetFirstFrameDelay() michael@0: { michael@0: if (mError) michael@0: return -1; michael@0: michael@0: if (!mSVGDocumentWrapper->IsAnimated()) michael@0: return -1; michael@0: michael@0: // We don't really have a frame delay, so just pretend that we constantly michael@0: // need updates. michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: //****************************************************************************** michael@0: /* [notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame); */ michael@0: NS_IMETHODIMP_(bool) michael@0: VectorImage::FrameIsOpaque(uint32_t aWhichFrame) michael@0: { michael@0: if (aWhichFrame > FRAME_MAX_VALUE) michael@0: NS_WARNING("aWhichFrame outside valid range!"); michael@0: michael@0: return false; // In general, SVG content is not opaque. michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame, michael@0: * in uint32_t aFlags; */ michael@0: NS_IMETHODIMP_(TemporaryRef) michael@0: VectorImage::GetFrame(uint32_t aWhichFrame, michael@0: uint32_t aFlags) michael@0: { michael@0: MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); michael@0: michael@0: if (aWhichFrame > FRAME_MAX_VALUE) michael@0: return nullptr; michael@0: michael@0: if (mError) michael@0: return nullptr; michael@0: michael@0: // Look up height & width michael@0: // ---------------------- michael@0: nsIntSize imageIntSize; michael@0: if (!mSVGDocumentWrapper->GetWidthOrHeight(SVGDocumentWrapper::eWidth, michael@0: imageIntSize.width) || michael@0: !mSVGDocumentWrapper->GetWidthOrHeight(SVGDocumentWrapper::eHeight, michael@0: imageIntSize.height)) { michael@0: // We'll get here if our SVG doc has a percent-valued width or height. michael@0: return nullptr; michael@0: } michael@0: michael@0: // Make our surface the size of what will ultimately be drawn to it. michael@0: // (either the full image size, or the restricted region) michael@0: RefPtr dt = gfxPlatform::GetPlatform()-> michael@0: CreateOffscreenContentDrawTarget(IntSize(imageIntSize.width, michael@0: imageIntSize.height), michael@0: SurfaceFormat::B8G8R8A8); michael@0: nsRefPtr context = new gfxContext(dt); michael@0: michael@0: nsresult rv = Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), michael@0: gfxRect(gfxPoint(0,0), gfxIntSize(imageIntSize.width, michael@0: imageIntSize.height)), michael@0: nsIntRect(nsIntPoint(0,0), imageIntSize), michael@0: imageIntSize, nullptr, aWhichFrame, aFlags); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: return dt->Snapshot(); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] ImageContainer getImageContainer(); */ michael@0: NS_IMETHODIMP michael@0: VectorImage::GetImageContainer(LayerManager* aManager, michael@0: mozilla::layers::ImageContainer** _retval) michael@0: { michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct SVGDrawingParameters michael@0: { michael@0: SVGDrawingParameters(gfxContext* aContext, michael@0: GraphicsFilter aFilter, michael@0: const gfxMatrix& aUserSpaceToImageSpace, michael@0: const gfxRect& aFill, michael@0: const nsIntRect& aSubimage, michael@0: const nsIntSize& aViewportSize, michael@0: const SVGImageContext* aSVGContext, michael@0: float aAnimationTime, michael@0: uint32_t aFlags) michael@0: : context(aContext) michael@0: , filter(aFilter) michael@0: , fill(aFill) michael@0: , viewportSize(aViewportSize) michael@0: , animationTime(aAnimationTime) michael@0: , svgContext(aSVGContext) michael@0: , flags(aFlags) michael@0: { michael@0: // gfxUtils::DrawPixelSnapped may rasterize this image to a temporary surface michael@0: // if we hit the tiling path. Unfortunately, the temporary surface isn't michael@0: // created at the size at which we'll ultimately draw, causing fuzzy output. michael@0: // To fix this we pre-apply the transform's scaling to the drawing parameters michael@0: // and remove the scaling from the transform, so the fact that temporary michael@0: // surfaces won't take the scaling into account doesn't matter. (Bug 600207.) michael@0: scale = aUserSpaceToImageSpace.ScaleFactors(true); michael@0: gfxPoint translation(aUserSpaceToImageSpace.GetTranslation()); michael@0: michael@0: // Remove the scaling from the transform. michael@0: gfxMatrix unscale; michael@0: unscale.Translate(gfxPoint(translation.x / scale.width, michael@0: translation.y / scale.height)); michael@0: unscale.Scale(1.0 / scale.width, 1.0 / scale.height); michael@0: unscale.Translate(-translation); michael@0: userSpaceToImageSpace = aUserSpaceToImageSpace * unscale; michael@0: michael@0: // Rescale drawing parameters. michael@0: IntSize drawableSize(aViewportSize.width / scale.width, michael@0: aViewportSize.height / scale.height); michael@0: sourceRect = userSpaceToImageSpace.Transform(aFill); michael@0: imageRect = IntRect(IntPoint(0, 0), drawableSize); michael@0: subimage = gfxRect(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); michael@0: subimage.ScaleRoundOut(1.0 / scale.width, 1.0 / scale.height); michael@0: } michael@0: michael@0: gfxContext* context; michael@0: GraphicsFilter filter; michael@0: gfxMatrix userSpaceToImageSpace; michael@0: gfxRect fill; michael@0: gfxRect subimage; michael@0: gfxRect sourceRect; michael@0: IntRect imageRect; michael@0: nsIntSize viewportSize; michael@0: gfxSize scale; michael@0: float animationTime; michael@0: const SVGImageContext* svgContext; michael@0: uint32_t flags; michael@0: }; michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] void draw(in gfxContext aContext, michael@0: * in gfxGraphicsFilter aFilter, michael@0: * [const] in gfxMatrix aUserSpaceToImageSpace, michael@0: * [const] in gfxRect aFill, michael@0: * [const] in nsIntRect aSubimage, michael@0: * [const] in nsIntSize aViewportSize, michael@0: * [const] in SVGImageContext aSVGContext, michael@0: * in uint32_t aWhichFrame, michael@0: * in uint32_t aFlags); */ michael@0: NS_IMETHODIMP michael@0: VectorImage::Draw(gfxContext* aContext, michael@0: GraphicsFilter aFilter, michael@0: const gfxMatrix& aUserSpaceToImageSpace, michael@0: const gfxRect& aFill, michael@0: const nsIntRect& aSubimage, michael@0: const nsIntSize& aViewportSize, michael@0: const SVGImageContext* aSVGContext, michael@0: uint32_t aWhichFrame, michael@0: uint32_t aFlags) michael@0: { michael@0: if (aWhichFrame > FRAME_MAX_VALUE) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aContext); michael@0: if (mError || !mIsFullyLoaded) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (mIsDrawing) { michael@0: NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (mAnimationConsumers == 0 && mStatusTracker) { michael@0: mStatusTracker->OnUnlockedDraw(); michael@0: } michael@0: michael@0: AutoRestore autoRestoreIsDrawing(mIsDrawing); michael@0: mIsDrawing = true; michael@0: michael@0: float animTime = (aWhichFrame == FRAME_FIRST) ? 0.0f michael@0: : mSVGDocumentWrapper->GetCurrentTime(); michael@0: AutoSVGRenderingState autoSVGState(aSVGContext, animTime, michael@0: mSVGDocumentWrapper->GetRootSVGElem()); michael@0: michael@0: // Pack up the drawing parameters. michael@0: SVGDrawingParameters params(aContext, aFilter, aUserSpaceToImageSpace, aFill, michael@0: aSubimage, aViewportSize, aSVGContext, animTime, aFlags); michael@0: michael@0: // Check the cache. michael@0: nsRefPtr drawable = michael@0: SurfaceCache::Lookup(ImageKey(this), michael@0: SurfaceKey(params.imageRect.Size(), params.scale, michael@0: aSVGContext, animTime, aFlags)); michael@0: michael@0: // Draw. michael@0: if (drawable) { michael@0: Show(drawable, params); michael@0: } else { michael@0: CreateDrawableAndShow(params); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: VectorImage::CreateDrawableAndShow(const SVGDrawingParameters& aParams) michael@0: { michael@0: mSVGDocumentWrapper->UpdateViewportBounds(aParams.viewportSize); michael@0: mSVGDocumentWrapper->FlushImageTransformInvalidation(); michael@0: michael@0: nsRefPtr cb = michael@0: new SVGDrawingCallback(mSVGDocumentWrapper, michael@0: nsIntRect(nsIntPoint(0, 0), aParams.viewportSize), michael@0: aParams.scale, michael@0: aParams.flags); michael@0: michael@0: nsRefPtr svgDrawable = michael@0: new gfxCallbackDrawable(cb, ThebesIntSize(aParams.imageRect.Size())); michael@0: michael@0: // Refuse to cache animated images. michael@0: // XXX(seth): We may remove this restriction in bug 922893. michael@0: if (mHaveAnimations) michael@0: return Show(svgDrawable, aParams); michael@0: michael@0: // If the image is too big to fit in the cache, don't go any further. michael@0: if (!SurfaceCache::CanHold(aParams.imageRect.Size())) michael@0: return Show(svgDrawable, aParams); michael@0: michael@0: // Try to create an offscreen surface. michael@0: mozilla::RefPtr target = michael@0: gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(aParams.imageRect.Size(), gfx::SurfaceFormat::B8G8R8A8); michael@0: michael@0: // If we couldn't create the draw target, it was probably because it would end michael@0: // up way too big. Generally it also wouldn't fit in the cache, but the prefs michael@0: // could be set such that the cache isn't the limiting factor. michael@0: if (!target) michael@0: return Show(svgDrawable, aParams); michael@0: michael@0: nsRefPtr ctx = new gfxContext(target); michael@0: michael@0: // Actually draw. (We use FILTER_NEAREST since we never scale here.) michael@0: gfxUtils::DrawPixelSnapped(ctx, svgDrawable, gfxMatrix(), michael@0: ThebesIntRect(aParams.imageRect), michael@0: ThebesIntRect(aParams.imageRect), michael@0: ThebesIntRect(aParams.imageRect), michael@0: ThebesIntRect(aParams.imageRect), michael@0: gfxImageFormat::ARGB32, michael@0: GraphicsFilter::FILTER_NEAREST, aParams.flags); michael@0: michael@0: // Attempt to cache the resulting surface. michael@0: SurfaceCache::Insert(target, michael@0: ImageKey(this), michael@0: SurfaceKey(aParams.imageRect.Size(), aParams.scale, michael@0: aParams.svgContext, aParams.animationTime, michael@0: aParams.flags)); michael@0: michael@0: // Draw. Note that if SurfaceCache::Insert failed for whatever reason, michael@0: // then |target| is all that is keeping the pixel data alive, so we have michael@0: // to draw before returning from this function. michael@0: nsRefPtr drawable = michael@0: new gfxSurfaceDrawable(target, ThebesIntSize(aParams.imageRect.Size())); michael@0: Show(drawable, aParams); michael@0: } michael@0: michael@0: michael@0: void michael@0: VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams) michael@0: { michael@0: MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now"); michael@0: gfxUtils::DrawPixelSnapped(aParams.context, aDrawable, michael@0: aParams.userSpaceToImageSpace, michael@0: aParams.subimage, aParams.sourceRect, michael@0: ThebesIntRect(aParams.imageRect), aParams.fill, michael@0: gfxImageFormat::ARGB32, michael@0: aParams.filter, aParams.flags); michael@0: michael@0: MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now"); michael@0: mRenderingObserver->ResumeHonoringInvalidations(); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void requestDecode() */ michael@0: NS_IMETHODIMP michael@0: VectorImage::RequestDecode() michael@0: { michael@0: // Nothing to do for SVG images michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: VectorImage::StartDecoding() michael@0: { michael@0: // Nothing to do for SVG images michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: VectorImage::IsDecoded() michael@0: { michael@0: return mIsFullyLoaded || mError; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void lockImage() */ michael@0: NS_IMETHODIMP michael@0: VectorImage::LockImage() michael@0: { michael@0: // This method is for image-discarding, which only applies to RasterImages. michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void unlockImage() */ michael@0: NS_IMETHODIMP michael@0: VectorImage::UnlockImage() michael@0: { michael@0: // This method is for image-discarding, which only applies to RasterImages. michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void requestDiscard() */ michael@0: NS_IMETHODIMP michael@0: VectorImage::RequestDiscard() michael@0: { michael@0: SurfaceCache::Discard(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void resetAnimation (); */ michael@0: NS_IMETHODIMP michael@0: VectorImage::ResetAnimation() michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!mIsFullyLoaded || !mHaveAnimations) { michael@0: return NS_OK; // There are no animations to be reset. michael@0: } michael@0: michael@0: mSVGDocumentWrapper->ResetAnimation(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(float) michael@0: VectorImage::GetFrameIndex(uint32_t aWhichFrame) michael@0: { michael@0: MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument"); michael@0: return aWhichFrame == FRAME_FIRST michael@0: ? 0.0f michael@0: : mSVGDocumentWrapper->GetCurrentTime(); michael@0: } michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // nsIRequestObserver methods michael@0: michael@0: //****************************************************************************** michael@0: /* void onStartRequest(in nsIRequest request, in nsISupports ctxt); */ michael@0: NS_IMETHODIMP michael@0: VectorImage::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) michael@0: { michael@0: MOZ_ASSERT(!mSVGDocumentWrapper, michael@0: "Repeated call to OnStartRequest -- can this happen?"); michael@0: michael@0: mSVGDocumentWrapper = new SVGDocumentWrapper(); michael@0: nsresult rv = mSVGDocumentWrapper->OnStartRequest(aRequest, aCtxt); michael@0: if (NS_FAILED(rv)) { michael@0: mSVGDocumentWrapper = nullptr; michael@0: mError = true; michael@0: return rv; michael@0: } michael@0: michael@0: // Sending StartDecode will block page load until the document's ready. (We michael@0: // unblock it by sending StopDecode in OnSVGDocumentLoaded or michael@0: // OnSVGDocumentError.) michael@0: if (mStatusTracker) { michael@0: nsRefPtr clone = mStatusTracker->CloneForRecording(); michael@0: imgDecoderObserver* observer = clone->GetDecoderObserver(); michael@0: observer->OnStartDecode(); michael@0: ImageStatusDiff diff = mStatusTracker->Difference(clone); michael@0: mStatusTracker->ApplyDifference(diff); michael@0: mStatusTracker->SyncNotifyDifference(diff); michael@0: } michael@0: michael@0: // Create a listener to wait until the SVG document is fully loaded, which michael@0: // will signal that this image is ready to render. Certain error conditions michael@0: // will prevent us from ever getting this notification, so we also create a michael@0: // listener that waits for parsing to complete and cancels the michael@0: // SVGLoadEventListener if needed. The listeners are automatically attached michael@0: // to the document by their constructors. michael@0: nsIDocument* document = mSVGDocumentWrapper->GetDocument(); michael@0: mLoadEventListener = new SVGLoadEventListener(document, this); michael@0: mParseCompleteListener = new SVGParseCompleteListener(document, this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void onStopRequest(in nsIRequest request, in nsISupports ctxt, michael@0: in nsresult status); */ michael@0: NS_IMETHODIMP michael@0: VectorImage::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt, michael@0: nsresult aStatus) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mSVGDocumentWrapper->OnStopRequest(aRequest, aCtxt, aStatus); michael@0: } michael@0: michael@0: void michael@0: VectorImage::OnSVGDocumentParsed() michael@0: { michael@0: MOZ_ASSERT(mParseCompleteListener, "Should have the parse complete listener"); michael@0: MOZ_ASSERT(mLoadEventListener, "Should have the load event listener"); michael@0: michael@0: if (!mSVGDocumentWrapper->GetRootSVGElem()) { michael@0: // This is an invalid SVG document. It may have failed to parse, or it may michael@0: // be missing the root element, or the root element may not michael@0: // declare the correct namespace. In any of these cases, we'll never be michael@0: // notified that the SVG finished loading, so we need to treat this as an error. michael@0: OnSVGDocumentError(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: VectorImage::CancelAllListeners() michael@0: { michael@0: if (mParseCompleteListener) { michael@0: mParseCompleteListener->Cancel(); michael@0: mParseCompleteListener = nullptr; michael@0: } michael@0: if (mLoadEventListener) { michael@0: mLoadEventListener->Cancel(); michael@0: mLoadEventListener = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: VectorImage::OnSVGDocumentLoaded() michael@0: { michael@0: MOZ_ASSERT(mSVGDocumentWrapper->GetRootSVGElem(), michael@0: "Should have parsed successfully"); michael@0: MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations, michael@0: "These flags shouldn't get set until OnSVGDocumentLoaded. " michael@0: "Duplicate calls to OnSVGDocumentLoaded?"); michael@0: michael@0: CancelAllListeners(); michael@0: michael@0: // XXX Flushing is wasteful if embedding frame hasn't had initial reflow. michael@0: mSVGDocumentWrapper->FlushLayout(); michael@0: michael@0: mIsFullyLoaded = true; michael@0: mHaveAnimations = mSVGDocumentWrapper->IsAnimated(); michael@0: michael@0: // Start listening to our image for rendering updates. michael@0: mRenderingObserver = new SVGRootRenderingObserver(mSVGDocumentWrapper, this); michael@0: michael@0: // Tell *our* observers that we're done loading. michael@0: if (mStatusTracker) { michael@0: nsRefPtr clone = mStatusTracker->CloneForRecording(); michael@0: imgDecoderObserver* observer = clone->GetDecoderObserver(); michael@0: michael@0: observer->OnStartContainer(); // Signal that width/height are available. michael@0: observer->FrameChanged(&nsIntRect::GetMaxSizedIntRect()); michael@0: observer->OnStopFrame(); michael@0: observer->OnStopDecode(NS_OK); // Unblock page load. michael@0: michael@0: ImageStatusDiff diff = mStatusTracker->Difference(clone); michael@0: mStatusTracker->ApplyDifference(diff); michael@0: mStatusTracker->SyncNotifyDifference(diff); michael@0: } michael@0: michael@0: EvaluateAnimation(); michael@0: } michael@0: michael@0: void michael@0: VectorImage::OnSVGDocumentError() michael@0: { michael@0: CancelAllListeners(); michael@0: michael@0: // XXXdholbert Need to do something more for the parsing failed case -- right michael@0: // now, this just makes us draw the "object" icon, rather than the (jagged) michael@0: // "broken image" icon. See bug 594505. michael@0: mError = true; michael@0: michael@0: if (mStatusTracker) { michael@0: nsRefPtr clone = mStatusTracker->CloneForRecording(); michael@0: imgDecoderObserver* observer = clone->GetDecoderObserver(); michael@0: michael@0: // Unblock page load. michael@0: observer->OnStopDecode(NS_ERROR_FAILURE); michael@0: ImageStatusDiff diff = mStatusTracker->Difference(clone); michael@0: mStatusTracker->ApplyDifference(diff); michael@0: mStatusTracker->SyncNotifyDifference(diff); michael@0: } michael@0: } michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // nsIStreamListener method michael@0: michael@0: //****************************************************************************** michael@0: /* void onDataAvailable(in nsIRequest request, in nsISupports ctxt, michael@0: in nsIInputStream inStr, in unsigned long sourceOffset, michael@0: in unsigned long count); */ michael@0: NS_IMETHODIMP michael@0: VectorImage::OnDataAvailable(nsIRequest* aRequest, nsISupports* aCtxt, michael@0: nsIInputStream* aInStr, uint64_t aSourceOffset, michael@0: uint32_t aCount) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mSVGDocumentWrapper->OnDataAvailable(aRequest, aCtxt, aInStr, michael@0: aSourceOffset, aCount); michael@0: } michael@0: michael@0: // -------------------------- michael@0: // Invalidation helper method michael@0: michael@0: void michael@0: VectorImage::InvalidateObserversOnNextRefreshDriverTick() michael@0: { michael@0: if (mHaveAnimations) { michael@0: mHasPendingInvalidation = true; michael@0: } else { michael@0: SendInvalidationNotifications(); michael@0: } michael@0: } michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla