Bug 1755481: correct documentation of `nsIClipboard::getData`. r=mccr8
[gecko.git] / image / VectorImage.cpp
blob4ce9adde254de577379d97a25a17584f595c7bd6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "VectorImage.h"
8 #include "AutoRestoreSVGState.h"
9 #include "gfx2DGlue.h"
10 #include "gfxContext.h"
11 #include "gfxDrawable.h"
12 #include "gfxPlatform.h"
13 #include "gfxUtils.h"
14 #include "imgFrame.h"
15 #include "mozilla/MemoryReporting.h"
16 #include "mozilla/MediaFeatureChange.h"
17 #include "mozilla/dom/Event.h"
18 #include "mozilla/dom/SVGSVGElement.h"
19 #include "mozilla/dom/SVGDocument.h"
20 #include "mozilla/gfx/2D.h"
21 #include "mozilla/gfx/gfxVars.h"
22 #include "mozilla/PendingAnimationTracker.h"
23 #include "mozilla/PresShell.h"
24 #include "mozilla/ProfilerLabels.h"
25 #include "mozilla/RefPtr.h"
26 #include "mozilla/StaticPrefs_image.h"
27 #include "mozilla/SVGObserverUtils.h" // for SVGRenderingObserver
28 #include "mozilla/Tuple.h"
29 #include "nsIStreamListener.h"
30 #include "nsMimeTypes.h"
31 #include "nsPresContext.h"
32 #include "nsRect.h"
33 #include "nsString.h"
34 #include "nsStubDocumentObserver.h"
35 #include "nsWindowSizes.h"
36 #include "ImageRegion.h"
37 #include "ISurfaceProvider.h"
38 #include "LookupResult.h"
39 #include "Orientation.h"
40 #include "SVGDocumentWrapper.h"
41 #include "SVGDrawingCallback.h"
42 #include "SVGDrawingParameters.h"
43 #include "nsIDOMEventListener.h"
44 #include "SurfaceCache.h"
45 #include "BlobSurfaceProvider.h"
46 #include "mozilla/dom/Document.h"
47 #include "mozilla/dom/DocumentInlines.h"
48 #include "mozilla/image/Resolution.h"
49 #include "WindowRenderer.h"
51 namespace mozilla {
53 using namespace dom;
54 using namespace dom::SVGPreserveAspectRatio_Binding;
55 using namespace gfx;
56 using namespace layers;
58 namespace image {
60 // Helper-class: SVGRootRenderingObserver
61 class SVGRootRenderingObserver final : public SVGRenderingObserver {
62 public:
63 NS_DECL_ISUPPORTS
65 SVGRootRenderingObserver(SVGDocumentWrapper* aDocWrapper,
66 VectorImage* aVectorImage)
67 : SVGRenderingObserver(),
68 mDocWrapper(aDocWrapper),
69 mVectorImage(aVectorImage),
70 mHonoringInvalidations(true) {
71 MOZ_ASSERT(mDocWrapper, "Need a non-null SVG document wrapper");
72 MOZ_ASSERT(mVectorImage, "Need a non-null VectorImage");
74 StartObserving();
75 Element* elem = GetReferencedElementWithoutObserving();
76 MOZ_ASSERT(elem, "no root SVG node for us to observe");
78 SVGObserverUtils::AddRenderingObserver(elem, this);
79 mInObserverSet = true;
82 void ResumeHonoringInvalidations() { mHonoringInvalidations = true; }
84 protected:
85 virtual ~SVGRootRenderingObserver() {
86 // This needs to call our GetReferencedElementWithoutObserving override,
87 // so must be called here rather than in our base class's dtor.
88 StopObserving();
91 Element* GetReferencedElementWithoutObserving() final {
92 return mDocWrapper->GetRootSVGElem();
95 virtual void OnRenderingChange() override {
96 Element* elem = GetReferencedElementWithoutObserving();
97 MOZ_ASSERT(elem, "missing root SVG node");
99 if (mHonoringInvalidations && !mDocWrapper->ShouldIgnoreInvalidation()) {
100 nsIFrame* frame = elem->GetPrimaryFrame();
101 if (!frame || frame->PresShell()->IsDestroying()) {
102 // We're being destroyed. Bail out.
103 return;
106 // Ignore further invalidations until we draw.
107 mHonoringInvalidations = false;
109 mVectorImage->InvalidateObserversOnNextRefreshDriverTick();
112 // Our caller might've removed us from rendering-observer list.
113 // Add ourselves back!
114 if (!mInObserverSet) {
115 SVGObserverUtils::AddRenderingObserver(elem, this);
116 mInObserverSet = true;
120 // Private data
121 const RefPtr<SVGDocumentWrapper> mDocWrapper;
122 VectorImage* const mVectorImage; // Raw pointer because it owns me.
123 bool mHonoringInvalidations;
126 NS_IMPL_ISUPPORTS(SVGRootRenderingObserver, nsIMutationObserver)
128 class SVGParseCompleteListener final : public nsStubDocumentObserver {
129 public:
130 NS_DECL_ISUPPORTS
132 SVGParseCompleteListener(SVGDocument* aDocument, VectorImage* aImage)
133 : mDocument(aDocument), mImage(aImage) {
134 MOZ_ASSERT(mDocument, "Need an SVG document");
135 MOZ_ASSERT(mImage, "Need an image");
137 mDocument->AddObserver(this);
140 private:
141 ~SVGParseCompleteListener() {
142 if (mDocument) {
143 // The document must have been destroyed before we got our event.
144 // Otherwise this can't happen, since documents hold strong references to
145 // their observers.
146 Cancel();
150 public:
151 void EndLoad(Document* aDocument) override {
152 MOZ_ASSERT(aDocument == mDocument, "Got EndLoad for wrong document?");
154 // OnSVGDocumentParsed will release our owner's reference to us, so ensure
155 // we stick around long enough to complete our work.
156 RefPtr<SVGParseCompleteListener> kungFuDeathGrip(this);
158 mImage->OnSVGDocumentParsed();
161 void Cancel() {
162 MOZ_ASSERT(mDocument, "Duplicate call to Cancel");
163 if (mDocument) {
164 mDocument->RemoveObserver(this);
165 mDocument = nullptr;
169 private:
170 RefPtr<SVGDocument> mDocument;
171 VectorImage* const mImage; // Raw pointer to owner.
174 NS_IMPL_ISUPPORTS(SVGParseCompleteListener, nsIDocumentObserver)
176 class SVGLoadEventListener final : public nsIDOMEventListener {
177 public:
178 NS_DECL_ISUPPORTS
180 SVGLoadEventListener(Document* aDocument, VectorImage* aImage)
181 : mDocument(aDocument), mImage(aImage) {
182 MOZ_ASSERT(mDocument, "Need an SVG document");
183 MOZ_ASSERT(mImage, "Need an image");
185 mDocument->AddEventListener(u"MozSVGAsImageDocumentLoad"_ns, this, true,
186 false);
189 private:
190 ~SVGLoadEventListener() {
191 if (mDocument) {
192 // The document must have been destroyed before we got our event.
193 // Otherwise this can't happen, since documents hold strong references to
194 // their observers.
195 Cancel();
199 public:
200 NS_IMETHOD HandleEvent(Event* aEvent) override {
201 MOZ_ASSERT(mDocument, "Need an SVG document. Received multiple events?");
203 // OnSVGDocumentLoaded will release our owner's reference
204 // to us, so ensure we stick around long enough to complete our work.
205 RefPtr<SVGLoadEventListener> kungFuDeathGrip(this);
207 #ifdef DEBUG
208 nsAutoString eventType;
209 aEvent->GetType(eventType);
210 MOZ_ASSERT(eventType.EqualsLiteral("MozSVGAsImageDocumentLoad"),
211 "Received unexpected event");
212 #endif
214 mImage->OnSVGDocumentLoaded();
216 return NS_OK;
219 void Cancel() {
220 MOZ_ASSERT(mDocument, "Duplicate call to Cancel");
221 if (mDocument) {
222 mDocument->RemoveEventListener(u"MozSVGAsImageDocumentLoad"_ns, this,
223 true);
224 mDocument = nullptr;
228 private:
229 nsCOMPtr<Document> mDocument;
230 VectorImage* const mImage; // Raw pointer to owner.
233 NS_IMPL_ISUPPORTS(SVGLoadEventListener, nsIDOMEventListener)
235 SVGDrawingCallback::SVGDrawingCallback(SVGDocumentWrapper* aSVGDocumentWrapper,
236 const IntSize& aViewportSize,
237 const IntSize& aSize,
238 uint32_t aImageFlags)
239 : mSVGDocumentWrapper(aSVGDocumentWrapper),
240 mViewportSize(aViewportSize),
241 mSize(aSize),
242 mImageFlags(aImageFlags) {}
244 SVGDrawingCallback::~SVGDrawingCallback() = default;
246 // Based loosely on SVGIntegrationUtils' PaintFrameCallback::operator()
247 bool SVGDrawingCallback::operator()(gfxContext* aContext,
248 const gfxRect& aFillRect,
249 const SamplingFilter aSamplingFilter,
250 const gfxMatrix& aTransform) {
251 MOZ_ASSERT(mSVGDocumentWrapper, "need an SVGDocumentWrapper");
253 // Get (& sanity-check) the helper-doc's presShell
254 RefPtr<PresShell> presShell = mSVGDocumentWrapper->GetPresShell();
255 MOZ_ASSERT(presShell, "GetPresShell returned null for an SVG image?");
257 Document* doc = presShell->GetDocument();
258 [[maybe_unused]] nsIURI* uri = doc ? doc->GetDocumentURI() : nullptr;
259 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
260 "SVG Image drawing", GRAPHICS,
261 nsPrintfCString("%dx%d %s", mSize.width, mSize.height,
262 uri ? uri->GetSpecOrDefault().get() : "N/A"));
264 gfxContextAutoSaveRestore contextRestorer(aContext);
266 // Clip to aFillRect so that we don't paint outside.
267 aContext->NewPath();
268 aContext->Rectangle(aFillRect);
269 aContext->Clip();
271 gfxMatrix matrix = aTransform;
272 if (!matrix.Invert()) {
273 return false;
275 aContext->SetMatrixDouble(
276 aContext->CurrentMatrixDouble().PreMultiply(matrix).PreScale(
277 double(mSize.width) / mViewportSize.width,
278 double(mSize.height) / mViewportSize.height));
280 nsPresContext* presContext = presShell->GetPresContext();
281 MOZ_ASSERT(presContext, "pres shell w/out pres context");
283 nsRect svgRect(0, 0, presContext->DevPixelsToAppUnits(mViewportSize.width),
284 presContext->DevPixelsToAppUnits(mViewportSize.height));
286 RenderDocumentFlags renderDocFlags =
287 RenderDocumentFlags::IgnoreViewportScrolling;
288 if (!(mImageFlags & imgIContainer::FLAG_SYNC_DECODE)) {
289 renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages;
291 if (mImageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) {
292 renderDocFlags |= RenderDocumentFlags::UseHighQualityScaling;
295 presShell->RenderDocument(svgRect, renderDocFlags,
296 NS_RGBA(0, 0, 0, 0), // transparent
297 aContext);
299 return true;
302 // Implement VectorImage's nsISupports-inherited methods
303 NS_IMPL_ISUPPORTS(VectorImage, imgIContainer, nsIStreamListener,
304 nsIRequestObserver)
306 //------------------------------------------------------------------------------
307 // Constructor / Destructor
309 VectorImage::VectorImage(nsIURI* aURI /* = nullptr */)
310 : ImageResource(aURI), // invoke superclass's constructor
311 mLockCount(0),
312 mIsInitialized(false),
313 mDiscardable(false),
314 mIsFullyLoaded(false),
315 mHaveAnimations(false),
316 mHasPendingInvalidation(false) {}
318 VectorImage::~VectorImage() {
319 ReportDocumentUseCounters();
320 CancelAllListeners();
321 SurfaceCache::RemoveImage(ImageKey(this));
324 //------------------------------------------------------------------------------
325 // Methods inherited from Image.h
327 nsresult VectorImage::Init(const char* aMimeType, uint32_t aFlags) {
328 // We don't support re-initialization
329 if (mIsInitialized) {
330 return NS_ERROR_ILLEGAL_VALUE;
333 MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations && !mError,
334 "Flags unexpectedly set before initialization");
335 MOZ_ASSERT(!strcmp(aMimeType, IMAGE_SVG_XML), "Unexpected mimetype");
337 mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE);
339 // Lock this image's surfaces in the SurfaceCache if we're not discardable.
340 if (!mDiscardable) {
341 mLockCount++;
342 SurfaceCache::LockImage(ImageKey(this));
345 mIsInitialized = true;
346 return NS_OK;
349 size_t VectorImage::SizeOfSourceWithComputedFallback(
350 SizeOfState& aState) const {
351 if (!mSVGDocumentWrapper) {
352 return 0; // No document, so no memory used for the document.
355 SVGDocument* doc = mSVGDocumentWrapper->GetDocument();
356 if (!doc) {
357 return 0; // No document, so no memory used for the document.
360 nsWindowSizes windowSizes(aState);
361 doc->DocAddSizeOfIncludingThis(windowSizes);
363 if (windowSizes.getTotalSize() == 0) {
364 // MallocSizeOf fails on this platform. Because we also use this method for
365 // determining the size of cache entries, we need to return something
366 // reasonable here. Unfortunately, there's no way to estimate the document's
367 // size accurately, so we just use a constant value of 100KB, which will
368 // generally underestimate the true size.
369 return 100 * 1024;
372 return windowSizes.getTotalSize();
375 nsresult VectorImage::OnImageDataComplete(nsIRequest* aRequest,
376 nsresult aStatus, bool aLastPart) {
377 // Call our internal OnStopRequest method, which only talks to our embedded
378 // SVG document. This won't have any effect on our ProgressTracker.
379 nsresult finalStatus = OnStopRequest(aRequest, aStatus);
381 // Give precedence to Necko failure codes.
382 if (NS_FAILED(aStatus)) {
383 finalStatus = aStatus;
386 Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus);
388 if (mIsFullyLoaded || mError) {
389 // Our document is loaded, so we're ready to notify now.
390 mProgressTracker->SyncNotifyProgress(loadProgress);
391 } else {
392 // Record our progress so far; we'll actually send the notifications in
393 // OnSVGDocumentLoaded or OnSVGDocumentError.
394 mLoadProgress = Some(loadProgress);
397 return finalStatus;
400 nsresult VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
401 nsIInputStream* aInStr,
402 uint64_t aSourceOffset,
403 uint32_t aCount) {
404 return OnDataAvailable(aRequest, aInStr, aSourceOffset, aCount);
407 nsresult VectorImage::StartAnimation() {
408 if (mError) {
409 return NS_ERROR_FAILURE;
412 MOZ_ASSERT(ShouldAnimate(), "Should not animate!");
414 mSVGDocumentWrapper->StartAnimation();
415 return NS_OK;
418 nsresult VectorImage::StopAnimation() {
419 nsresult rv = NS_OK;
420 if (mError) {
421 rv = NS_ERROR_FAILURE;
422 } else {
423 MOZ_ASSERT(mIsFullyLoaded && mHaveAnimations,
424 "Should not have been animating!");
426 mSVGDocumentWrapper->StopAnimation();
429 mAnimating = false;
430 return rv;
433 bool VectorImage::ShouldAnimate() {
434 return ImageResource::ShouldAnimate() && mIsFullyLoaded && mHaveAnimations;
437 NS_IMETHODIMP_(void)
438 VectorImage::SetAnimationStartTime(const TimeStamp& aTime) {
439 // We don't care about animation start time.
442 //------------------------------------------------------------------------------
443 // imgIContainer methods
445 //******************************************************************************
446 NS_IMETHODIMP
447 VectorImage::GetWidth(int32_t* aWidth) {
448 if (mError || !mIsFullyLoaded) {
449 // XXXdholbert Technically we should leave outparam untouched when we
450 // fail. But since many callers don't check for failure, we set it to 0 on
451 // failure, for sane/predictable results.
452 *aWidth = 0;
453 return NS_ERROR_FAILURE;
456 SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem();
457 MOZ_ASSERT(rootElem,
458 "Should have a root SVG elem, since we finished "
459 "loading without errors");
460 int32_t rootElemWidth = rootElem->GetIntrinsicWidth();
461 if (rootElemWidth < 0) {
462 *aWidth = 0;
463 return NS_ERROR_FAILURE;
465 *aWidth = rootElemWidth;
466 return NS_OK;
469 //******************************************************************************
470 nsresult VectorImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const {
471 return NS_ERROR_NOT_IMPLEMENTED;
474 //******************************************************************************
475 size_t VectorImage::GetNativeSizesLength() const { return 0; }
477 //******************************************************************************
478 NS_IMETHODIMP_(void)
479 VectorImage::RequestRefresh(const TimeStamp& aTime) {
480 if (HadRecentRefresh(aTime)) {
481 return;
484 Document* doc = mSVGDocumentWrapper->GetDocument();
485 if (!doc) {
486 // We are racing between shutdown and a refresh.
487 return;
490 PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
491 if (tracker && ShouldAnimate()) {
492 tracker->TriggerPendingAnimationsOnNextTick(aTime);
495 EvaluateAnimation();
497 mSVGDocumentWrapper->TickRefreshDriver();
499 if (mHasPendingInvalidation) {
500 SendInvalidationNotifications();
504 void VectorImage::SendInvalidationNotifications() {
505 // Animated images don't send out invalidation notifications as soon as
506 // they're generated. Instead, InvalidateObserversOnNextRefreshDriverTick
507 // records that there are pending invalidations and then returns immediately.
508 // The notifications are actually sent from RequestRefresh(). We send these
509 // notifications there to ensure that there is actually a document observing
510 // us. Otherwise, the notifications are just wasted effort.
512 // Non-animated images post an event to call this method from
513 // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never
514 // called for them. Ordinarily this isn't needed, since we send out
515 // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the
516 // SVG document may not be 100% ready to render at that time. In those cases
517 // we would miss the subsequent invalidations if we didn't send out the
518 // notifications indirectly in |InvalidateObservers...|.
520 mHasPendingInvalidation = false;
522 if (SurfaceCache::InvalidateImage(ImageKey(this))) {
523 // If we still have recordings in the cache, make sure we handle future
524 // invalidations.
525 MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
526 mRenderingObserver->ResumeHonoringInvalidations();
529 if (mProgressTracker) {
530 mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
531 GetMaxSizedIntRect());
535 NS_IMETHODIMP_(IntRect)
536 VectorImage::GetImageSpaceInvalidationRect(const IntRect& aRect) {
537 return aRect;
540 //******************************************************************************
541 NS_IMETHODIMP
542 VectorImage::GetHeight(int32_t* aHeight) {
543 if (mError || !mIsFullyLoaded) {
544 // XXXdholbert Technically we should leave outparam untouched when we
545 // fail. But since many callers don't check for failure, we set it to 0 on
546 // failure, for sane/predictable results.
547 *aHeight = 0;
548 return NS_ERROR_FAILURE;
551 SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem();
552 MOZ_ASSERT(rootElem,
553 "Should have a root SVG elem, since we finished "
554 "loading without errors");
555 int32_t rootElemHeight = rootElem->GetIntrinsicHeight();
556 if (rootElemHeight < 0) {
557 *aHeight = 0;
558 return NS_ERROR_FAILURE;
560 *aHeight = rootElemHeight;
561 return NS_OK;
564 //******************************************************************************
565 NS_IMETHODIMP
566 VectorImage::GetIntrinsicSize(nsSize* aSize) {
567 if (mError || !mIsFullyLoaded) {
568 return NS_ERROR_FAILURE;
571 nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
572 if (!rootFrame) {
573 return NS_ERROR_FAILURE;
576 *aSize = nsSize(-1, -1);
577 IntrinsicSize rfSize = rootFrame->GetIntrinsicSize();
578 if (rfSize.width) {
579 aSize->width = *rfSize.width;
581 if (rfSize.height) {
582 aSize->height = *rfSize.height;
584 return NS_OK;
587 //******************************************************************************
588 Maybe<AspectRatio> VectorImage::GetIntrinsicRatio() {
589 if (mError || !mIsFullyLoaded) {
590 return Nothing();
593 nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
594 if (!rootFrame) {
595 return Nothing();
598 return Some(rootFrame->GetIntrinsicRatio());
601 NS_IMETHODIMP_(Orientation)
602 VectorImage::GetOrientation() { return Orientation(); }
604 NS_IMETHODIMP_(Resolution)
605 VectorImage::GetResolution() { return {}; }
607 //******************************************************************************
608 NS_IMETHODIMP
609 VectorImage::GetType(uint16_t* aType) {
610 NS_ENSURE_ARG_POINTER(aType);
612 *aType = imgIContainer::TYPE_VECTOR;
613 return NS_OK;
616 //******************************************************************************
617 NS_IMETHODIMP
618 VectorImage::GetProviderId(uint32_t* aId) {
619 NS_ENSURE_ARG_POINTER(aId);
621 *aId = ImageResource::GetImageProviderId();
622 return NS_OK;
625 //******************************************************************************
626 NS_IMETHODIMP
627 VectorImage::GetAnimated(bool* aAnimated) {
628 if (mError || !mIsFullyLoaded) {
629 return NS_ERROR_FAILURE;
632 *aAnimated = mSVGDocumentWrapper->IsAnimated();
633 return NS_OK;
636 //******************************************************************************
637 int32_t VectorImage::GetFirstFrameDelay() {
638 if (mError) {
639 return -1;
642 if (!mSVGDocumentWrapper->IsAnimated()) {
643 return -1;
646 // We don't really have a frame delay, so just pretend that we constantly
647 // need updates.
648 return 0;
651 NS_IMETHODIMP_(bool)
652 VectorImage::WillDrawOpaqueNow() {
653 return false; // In general, SVG content is not opaque.
656 //******************************************************************************
657 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
658 VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
659 if (mError) {
660 return nullptr;
663 // Look up height & width
664 // ----------------------
665 SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem();
666 MOZ_ASSERT(svgElem,
667 "Should have a root SVG elem, since we finished "
668 "loading without errors");
669 nsIntSize imageIntSize(svgElem->GetIntrinsicWidth(),
670 svgElem->GetIntrinsicHeight());
672 if (imageIntSize.IsEmpty()) {
673 // We'll get here if our SVG doc has a percent-valued or negative width or
674 // height.
675 return nullptr;
678 return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags);
681 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
682 VectorImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame,
683 uint32_t aFlags) {
684 MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
686 AutoProfilerImagePaintMarker PROFILER_RAII(this);
687 #ifdef DEBUG
688 NotifyDrawingObservers();
689 #endif
691 if (aSize.IsEmpty() || aWhichFrame > FRAME_MAX_VALUE || mError ||
692 !mIsFullyLoaded) {
693 return nullptr;
696 uint32_t whichFrame = mHaveAnimations ? aWhichFrame : FRAME_FIRST;
698 RefPtr<SourceSurface> sourceSurface;
699 IntSize decodeSize;
700 Tie(sourceSurface, decodeSize) =
701 LookupCachedSurface(aSize, Nothing(), aFlags);
702 if (sourceSurface) {
703 return sourceSurface.forget();
706 if (mSVGDocumentWrapper->IsDrawing()) {
707 NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
708 return nullptr;
711 float animTime = (whichFrame == FRAME_FIRST)
712 ? 0.0f
713 : mSVGDocumentWrapper->GetCurrentTimeAsFloat();
715 // By using a null gfxContext, we ensure that we will always attempt to
716 // create a surface, even if we aren't capable of caching it (e.g. due to our
717 // flags, having an animation, etc). Otherwise CreateSurface will assume that
718 // the caller is capable of drawing directly to its own draw target if we
719 // cannot cache.
720 Maybe<SVGImageContext> svgContext;
721 SVGDrawingParameters params(
722 nullptr, decodeSize, aSize, ImageRegion::Create(decodeSize),
723 SamplingFilter::POINT, svgContext, animTime, aFlags, 1.0);
725 bool didCache; // Was the surface put into the cache?
727 AutoRestoreSVGState autoRestore(params, mSVGDocumentWrapper,
728 /* aContextPaint */ false);
730 RefPtr<gfxDrawable> svgDrawable = CreateSVGDrawable(params);
731 RefPtr<SourceSurface> surface = CreateSurface(params, svgDrawable, didCache);
732 if (!surface) {
733 MOZ_ASSERT(!didCache);
734 return nullptr;
737 SendFrameComplete(didCache, params.flags);
738 return surface.forget();
741 NS_IMETHODIMP_(bool)
742 VectorImage::IsImageContainerAvailable(WindowRenderer* aRenderer,
743 uint32_t aFlags) {
744 if (mError || !mIsFullyLoaded ||
745 aRenderer->GetBackendType() != LayersBackend::LAYERS_WR) {
746 return false;
749 if (mHaveAnimations && !StaticPrefs::image_svg_blob_image()) {
750 // We don't support rasterizing animation SVGs. We can put them in a blob
751 // recording however instead of using fallback.
752 return false;
755 return true;
758 //******************************************************************************
759 NS_IMETHODIMP_(ImgDrawResult)
760 VectorImage::GetImageProvider(WindowRenderer* aRenderer,
761 const gfx::IntSize& aSize,
762 const Maybe<SVGImageContext>& aSVGContext,
763 const Maybe<ImageIntRegion>& aRegion,
764 uint32_t aFlags,
765 WebRenderImageProvider** aProvider) {
766 MOZ_ASSERT(NS_IsMainThread());
767 MOZ_ASSERT(aRenderer);
768 MOZ_ASSERT(!(aFlags & FLAG_BYPASS_SURFACE_CACHE), "Unsupported flags");
770 // We don't need to check if the size is too big since we only support
771 // WebRender backends.
772 if (aSize.IsEmpty()) {
773 return ImgDrawResult::BAD_ARGS;
776 if (mError) {
777 return ImgDrawResult::BAD_IMAGE;
780 if (!mIsFullyLoaded) {
781 return ImgDrawResult::NOT_READY;
784 if (mHaveAnimations && !(aFlags & FLAG_RECORD_BLOB)) {
785 // We don't support rasterizing animation SVGs. We can put them in a blob
786 // recording however instead of using fallback.
787 return ImgDrawResult::NOT_SUPPORTED;
790 AutoProfilerImagePaintMarker PROFILER_RAII(this);
791 #ifdef DEBUG
792 NotifyDrawingObservers();
793 #endif
795 // Only blob recordings support a region to restrict drawing.
796 const bool blobRecording = aFlags & FLAG_RECORD_BLOB;
797 MOZ_ASSERT_IF(!blobRecording, aRegion.isNothing());
799 LookupResult result(MatchType::NOT_FOUND);
800 auto playbackType =
801 mHaveAnimations ? PlaybackType::eAnimated : PlaybackType::eStatic;
802 auto surfaceFlags = ToSurfaceFlags(aFlags);
804 SurfaceKey surfaceKey =
805 VectorSurfaceKey(aSize, aRegion, aSVGContext, surfaceFlags, playbackType);
806 if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) {
807 result = SurfaceCache::Lookup(ImageKey(this), surfaceKey,
808 /* aMarkUsed = */ true);
809 } else {
810 result = SurfaceCache::LookupBestMatch(ImageKey(this), surfaceKey,
811 /* aMarkUsed = */ true);
814 // Unless we get a best match (exact or factor of 2 limited), then we want to
815 // generate a new recording/rerasterize, even if we have a substitute.
816 if (result && (result.Type() == MatchType::EXACT ||
817 result.Type() == MatchType::SUBSTITUTE_BECAUSE_BEST)) {
818 result.Surface().TakeProvider(aProvider);
819 return ImgDrawResult::SUCCESS;
822 // Ensure we store the surface with the correct key if we switched to factor
823 // of 2 sizing or we otherwise got clamped.
824 IntSize rasterSize(aSize);
825 if (!result.SuggestedSize().IsEmpty()) {
826 rasterSize = result.SuggestedSize();
827 surfaceKey = surfaceKey.CloneWithSize(rasterSize);
830 // We're about to rerasterize, which may mean that some of the previous
831 // surfaces we've rasterized aren't useful anymore. We can allow them to
832 // expire from the cache by unlocking them here, and then sending out an
833 // invalidation. If this image is locked, any surfaces that are still useful
834 // will become locked again when Draw touches them, and the remainder will
835 // eventually expire.
836 bool mayCache = SurfaceCache::CanHold(rasterSize);
837 if (mayCache) {
838 SurfaceCache::UnlockEntries(ImageKey(this));
841 // Blob recorded vector images just create a provider responsible for
842 // generating blob keys and recording bindings. The recording won't happen
843 // until the caller requests the key explicitly.
844 RefPtr<ISurfaceProvider> provider;
845 if (blobRecording) {
846 provider = MakeRefPtr<BlobSurfaceProvider>(ImageKey(this), surfaceKey,
847 mSVGDocumentWrapper, aFlags);
848 } else {
849 if (mSVGDocumentWrapper->IsDrawing()) {
850 NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
851 return ImgDrawResult::TEMPORARY_ERROR;
854 // We aren't using blobs, so we need to rasterize.
855 float animTime =
856 mHaveAnimations ? mSVGDocumentWrapper->GetCurrentTimeAsFloat() : 0.0f;
858 // By using a null gfxContext, we ensure that we will always attempt to
859 // create a surface, even if we aren't capable of caching it (e.g. due to
860 // our flags, having an animation, etc). Otherwise CreateSurface will assume
861 // that the caller is capable of drawing directly to its own draw target if
862 // we cannot cache.
863 SVGDrawingParameters params(
864 nullptr, rasterSize, aSize, ImageRegion::Create(rasterSize),
865 SamplingFilter::POINT, aSVGContext, animTime, aFlags, 1.0);
867 RefPtr<gfxDrawable> svgDrawable = CreateSVGDrawable(params);
868 bool contextPaint = aSVGContext && aSVGContext->GetContextPaint();
869 AutoRestoreSVGState autoRestore(params, mSVGDocumentWrapper, contextPaint);
871 mSVGDocumentWrapper->UpdateViewportBounds(params.viewportSize);
872 mSVGDocumentWrapper->FlushImageTransformInvalidation();
874 // Given we have no context, the default backend is fine.
875 BackendType backend =
876 gfxPlatform::GetPlatform()->GetDefaultContentBackend();
878 // Try to create an imgFrame, initializing the surface it contains by
879 // drawing our gfxDrawable into it. (We use FILTER_NEAREST since we never
880 // scale here.)
881 auto frame = MakeNotNull<RefPtr<imgFrame>>();
882 nsresult rv = frame->InitWithDrawable(
883 svgDrawable, params.size, SurfaceFormat::OS_RGBA, SamplingFilter::POINT,
884 params.flags, backend);
886 // If we couldn't create the frame, it was probably because it would end
887 // up way too big. Generally it also wouldn't fit in the cache, but the
888 // prefs could be set such that the cache isn't the limiting factor.
889 if (NS_FAILED(rv)) {
890 return ImgDrawResult::TEMPORARY_ERROR;
893 provider =
894 MakeRefPtr<SimpleSurfaceProvider>(ImageKey(this), surfaceKey, frame);
897 if (mayCache) {
898 // Attempt to cache the frame.
899 if (SurfaceCache::Insert(WrapNotNull(provider)) == InsertOutcome::SUCCESS) {
900 if (rasterSize != aSize) {
901 // We created a new surface that wasn't the size we requested, which
902 // means we entered factor-of-2 mode. We should purge any surfaces we
903 // no longer need rather than waiting for the cache to expire them.
904 SurfaceCache::PruneImage(ImageKey(this));
907 SendFrameComplete(/* aDidCache */ true, aFlags);
911 MOZ_ASSERT(provider);
912 provider.forget(aProvider);
913 return ImgDrawResult::SUCCESS;
916 bool VectorImage::MaybeRestrictSVGContext(
917 Maybe<SVGImageContext>& aNewSVGContext,
918 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags) {
919 bool overridePAR =
920 (aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) && aSVGContext;
922 bool haveContextPaint = aSVGContext && aSVGContext->GetContextPaint();
923 bool blockContextPaint = false;
924 if (haveContextPaint) {
925 blockContextPaint = !SVGContextPaint::IsAllowedForImageFromURI(mURI);
928 if (overridePAR || blockContextPaint) {
929 // The key that we create for the image surface cache must match the way
930 // that the image will be painted, so we need to initialize a new matching
931 // SVGImageContext here in order to generate the correct key.
933 aNewSVGContext = aSVGContext; // copy
935 if (overridePAR) {
936 // The SVGImageContext must take account of the preserveAspectRatio
937 // override:
938 MOZ_ASSERT(!aSVGContext->GetPreserveAspectRatio(),
939 "FLAG_FORCE_PRESERVEASPECTRATIO_NONE is not expected if a "
940 "preserveAspectRatio override is supplied");
941 Maybe<SVGPreserveAspectRatio> aspectRatio = Some(SVGPreserveAspectRatio(
942 SVG_PRESERVEASPECTRATIO_NONE, SVG_MEETORSLICE_UNKNOWN));
943 aNewSVGContext->SetPreserveAspectRatio(aspectRatio);
946 if (blockContextPaint) {
947 // The SVGImageContext must not include context paint if the image is
948 // not allowed to use it:
949 aNewSVGContext->ClearContextPaint();
953 return haveContextPaint && !blockContextPaint;
956 //******************************************************************************
957 NS_IMETHODIMP_(ImgDrawResult)
958 VectorImage::Draw(gfxContext* aContext, const nsIntSize& aSize,
959 const ImageRegion& aRegion, uint32_t aWhichFrame,
960 SamplingFilter aSamplingFilter,
961 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
962 float aOpacity) {
963 if (aWhichFrame > FRAME_MAX_VALUE) {
964 return ImgDrawResult::BAD_ARGS;
967 if (!aContext) {
968 return ImgDrawResult::BAD_ARGS;
971 if (mError) {
972 return ImgDrawResult::BAD_IMAGE;
975 if (!mIsFullyLoaded) {
976 return ImgDrawResult::NOT_READY;
979 if (mAnimationConsumers == 0 && mHaveAnimations) {
980 SendOnUnlockedDraw(aFlags);
983 // We should bypass the cache when:
984 // - We are using a DrawTargetRecording because we prefer the drawing commands
985 // in general to the rasterized surface. This allows blob images to avoid
986 // rasterized SVGs with WebRender.
987 // - The size exceeds what we are willing to cache as a rasterized surface.
988 // We don't do this for WebRender because the performance of the fallback
989 // path is quite bad and upscaling the SVG from the clamped size is better
990 // than bringing the browser to a crawl.
991 if (aContext->GetDrawTarget()->GetBackendType() == BackendType::RECORDING ||
992 (!gfxVars::UseWebRender() &&
993 aSize != SurfaceCache::ClampVectorSize(aSize))) {
994 aFlags |= FLAG_BYPASS_SURFACE_CACHE;
997 MOZ_ASSERT(!(aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) ||
998 (aSVGContext && aSVGContext->GetViewportSize()),
999 "Viewport size is required when using "
1000 "FLAG_FORCE_PRESERVEASPECTRATIO_NONE");
1002 uint32_t whichFrame = mHaveAnimations ? aWhichFrame : FRAME_FIRST;
1004 float animTime = (whichFrame == FRAME_FIRST)
1005 ? 0.0f
1006 : mSVGDocumentWrapper->GetCurrentTimeAsFloat();
1008 Maybe<SVGImageContext> newSVGContext;
1009 bool contextPaint =
1010 MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags);
1012 SVGDrawingParameters params(aContext, aSize, aSize, aRegion, aSamplingFilter,
1013 newSVGContext ? newSVGContext : aSVGContext,
1014 animTime, aFlags, aOpacity);
1016 // If we have an prerasterized version of this image that matches the
1017 // drawing parameters, use that.
1018 RefPtr<SourceSurface> sourceSurface;
1019 Tie(sourceSurface, params.size) =
1020 LookupCachedSurface(aSize, params.svgContext, aFlags);
1021 if (sourceSurface) {
1022 RefPtr<gfxDrawable> drawable =
1023 new gfxSurfaceDrawable(sourceSurface, params.size);
1024 Show(drawable, params);
1025 return ImgDrawResult::SUCCESS;
1028 // else, we need to paint the image:
1030 if (mSVGDocumentWrapper->IsDrawing()) {
1031 NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
1032 return ImgDrawResult::TEMPORARY_ERROR;
1035 AutoRestoreSVGState autoRestore(params, mSVGDocumentWrapper, contextPaint);
1037 bool didCache; // Was the surface put into the cache?
1038 RefPtr<gfxDrawable> svgDrawable = CreateSVGDrawable(params);
1039 sourceSurface = CreateSurface(params, svgDrawable, didCache);
1040 if (!sourceSurface) {
1041 MOZ_ASSERT(!didCache);
1042 Show(svgDrawable, params);
1043 return ImgDrawResult::SUCCESS;
1046 RefPtr<gfxDrawable> drawable =
1047 new gfxSurfaceDrawable(sourceSurface, params.size);
1048 Show(drawable, params);
1049 SendFrameComplete(didCache, params.flags);
1050 return ImgDrawResult::SUCCESS;
1053 already_AddRefed<gfxDrawable> VectorImage::CreateSVGDrawable(
1054 const SVGDrawingParameters& aParams) {
1055 RefPtr<gfxDrawingCallback> cb = new SVGDrawingCallback(
1056 mSVGDocumentWrapper, aParams.viewportSize, aParams.size, aParams.flags);
1058 RefPtr<gfxDrawable> svgDrawable = new gfxCallbackDrawable(cb, aParams.size);
1059 return svgDrawable.forget();
1062 Tuple<RefPtr<SourceSurface>, IntSize> VectorImage::LookupCachedSurface(
1063 const IntSize& aSize, const Maybe<SVGImageContext>& aSVGContext,
1064 uint32_t aFlags) {
1065 // We can't use cached surfaces if we:
1066 // - Explicitly disallow it via FLAG_BYPASS_SURFACE_CACHE
1067 // - Want a blob recording which aren't supported by the cache.
1068 // - Have animations which aren't supported by the cache.
1069 if (aFlags & (FLAG_BYPASS_SURFACE_CACHE | FLAG_RECORD_BLOB) ||
1070 mHaveAnimations) {
1071 return MakeTuple(RefPtr<SourceSurface>(), aSize);
1074 LookupResult result(MatchType::NOT_FOUND);
1075 SurfaceKey surfaceKey = VectorSurfaceKey(aSize, aSVGContext);
1076 if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) {
1077 result = SurfaceCache::Lookup(ImageKey(this), surfaceKey,
1078 /* aMarkUsed = */ true);
1079 } else {
1080 result = SurfaceCache::LookupBestMatch(ImageKey(this), surfaceKey,
1081 /* aMarkUsed = */ true);
1084 IntSize rasterSize =
1085 result.SuggestedSize().IsEmpty() ? aSize : result.SuggestedSize();
1086 MOZ_ASSERT(result.Type() != MatchType::SUBSTITUTE_BECAUSE_PENDING);
1087 if (!result || result.Type() == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND) {
1088 // No matching surface, or the OS freed the volatile buffer.
1089 return MakeTuple(RefPtr<SourceSurface>(), rasterSize);
1092 RefPtr<SourceSurface> sourceSurface = result.Surface()->GetSourceSurface();
1093 if (!sourceSurface) {
1094 // Something went wrong. (Probably a GPU driver crash or device reset.)
1095 // Attempt to recover.
1096 RecoverFromLossOfSurfaces();
1097 return MakeTuple(RefPtr<SourceSurface>(), rasterSize);
1100 return MakeTuple(std::move(sourceSurface), rasterSize);
1103 already_AddRefed<SourceSurface> VectorImage::CreateSurface(
1104 const SVGDrawingParameters& aParams, gfxDrawable* aSVGDrawable,
1105 bool& aWillCache) {
1106 MOZ_ASSERT(mSVGDocumentWrapper->IsDrawing());
1107 MOZ_ASSERT(!(aParams.flags & FLAG_RECORD_BLOB));
1109 mSVGDocumentWrapper->UpdateViewportBounds(aParams.viewportSize);
1110 mSVGDocumentWrapper->FlushImageTransformInvalidation();
1112 // Determine whether or not we should put the surface to be created into
1113 // the cache. If we fail, we need to reset this to false to let the caller
1114 // know nothing was put in the cache.
1115 aWillCache = !(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) &&
1116 // Refuse to cache animated images:
1117 // XXX(seth): We may remove this restriction in bug 922893.
1118 !mHaveAnimations &&
1119 // The image is too big to fit in the cache:
1120 SurfaceCache::CanHold(aParams.size);
1122 // If we weren't given a context, then we know we just want the rasterized
1123 // surface. We will create the frame below but only insert it into the cache
1124 // if we actually need to.
1125 if (!aWillCache && aParams.context) {
1126 return nullptr;
1129 // We're about to rerasterize, which may mean that some of the previous
1130 // surfaces we've rasterized aren't useful anymore. We can allow them to
1131 // expire from the cache by unlocking them here, and then sending out an
1132 // invalidation. If this image is locked, any surfaces that are still useful
1133 // will become locked again when Draw touches them, and the remainder will
1134 // eventually expire.
1135 if (aWillCache) {
1136 SurfaceCache::UnlockEntries(ImageKey(this));
1139 // If there is no context, the default backend is fine.
1140 BackendType backend =
1141 aParams.context ? aParams.context->GetDrawTarget()->GetBackendType()
1142 : gfxPlatform::GetPlatform()->GetDefaultContentBackend();
1144 if (backend == BackendType::DIRECT2D1_1) {
1145 // We don't want to draw arbitrary content with D2D anymore
1146 // because it doesn't support PushLayerWithBlend so switch to skia
1147 backend = BackendType::SKIA;
1150 // Try to create an imgFrame, initializing the surface it contains by drawing
1151 // our gfxDrawable into it. (We use FILTER_NEAREST since we never scale here.)
1152 auto frame = MakeNotNull<RefPtr<imgFrame>>();
1153 nsresult rv = frame->InitWithDrawable(
1154 aSVGDrawable, aParams.size, SurfaceFormat::OS_RGBA, SamplingFilter::POINT,
1155 aParams.flags, backend);
1157 // If we couldn't create the frame, it was probably because it would end
1158 // up way too big. Generally it also wouldn't fit in the cache, but the prefs
1159 // could be set such that the cache isn't the limiting factor.
1160 if (NS_FAILED(rv)) {
1161 aWillCache = false;
1162 return nullptr;
1165 // Take a strong reference to the frame's surface and make sure it hasn't
1166 // already been purged by the operating system.
1167 RefPtr<SourceSurface> surface = frame->GetSourceSurface();
1168 if (!surface) {
1169 aWillCache = false;
1170 return nullptr;
1173 // We created the frame, but only because we had no context to draw to
1174 // directly. All the caller wants is the surface in this case.
1175 if (!aWillCache) {
1176 return surface.forget();
1179 // Attempt to cache the frame.
1180 SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext);
1181 NotNull<RefPtr<ISurfaceProvider>> provider =
1182 MakeNotNull<SimpleSurfaceProvider*>(ImageKey(this), surfaceKey, frame);
1184 if (SurfaceCache::Insert(provider) == InsertOutcome::SUCCESS) {
1185 if (aParams.size != aParams.drawSize) {
1186 // We created a new surface that wasn't the size we requested, which means
1187 // we entered factor-of-2 mode. We should purge any surfaces we no longer
1188 // need rather than waiting for the cache to expire them.
1189 SurfaceCache::PruneImage(ImageKey(this));
1191 } else {
1192 aWillCache = false;
1195 return surface.forget();
1198 void VectorImage::SendFrameComplete(bool aDidCache, uint32_t aFlags) {
1199 // If the cache was not updated, we have nothing to do.
1200 if (!aDidCache) {
1201 return;
1204 // Send out an invalidation so that surfaces that are still in use get
1205 // re-locked. See the discussion of the UnlockSurfaces call above.
1206 if (!(aFlags & FLAG_ASYNC_NOTIFY)) {
1207 mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
1208 GetMaxSizedIntRect());
1209 } else {
1210 NotNull<RefPtr<VectorImage>> image = WrapNotNull(this);
1211 NS_DispatchToMainThread(CreateRenderBlockingRunnable(NS_NewRunnableFunction(
1212 "ProgressTracker::SyncNotifyProgress", [=]() -> void {
1213 RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
1214 if (tracker) {
1215 tracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
1216 GetMaxSizedIntRect());
1218 })));
1222 void VectorImage::Show(gfxDrawable* aDrawable,
1223 const SVGDrawingParameters& aParams) {
1224 // The surface size may differ from the size at which we wish to draw. As
1225 // such, we may need to adjust the context/region to take this into account.
1226 gfxContextMatrixAutoSaveRestore saveMatrix(aParams.context);
1227 ImageRegion region(aParams.region);
1228 if (aParams.drawSize != aParams.size) {
1229 gfx::Size scale(double(aParams.drawSize.width) / aParams.size.width,
1230 double(aParams.drawSize.height) / aParams.size.height);
1231 aParams.context->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
1232 region.Scale(1.0 / scale.width, 1.0 / scale.height);
1235 MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now");
1236 gfxUtils::DrawPixelSnapped(aParams.context, aDrawable,
1237 SizeDouble(aParams.size), region,
1238 SurfaceFormat::OS_RGBA, aParams.samplingFilter,
1239 aParams.flags, aParams.opacity, false);
1241 AutoProfilerImagePaintMarker PROFILER_RAII(this);
1242 #ifdef DEBUG
1243 NotifyDrawingObservers();
1244 #endif
1246 MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
1247 mRenderingObserver->ResumeHonoringInvalidations();
1250 void VectorImage::RecoverFromLossOfSurfaces() {
1251 NS_WARNING("An imgFrame became invalid. Attempting to recover...");
1253 // Discard all existing frames, since they're probably all now invalid.
1254 SurfaceCache::RemoveImage(ImageKey(this));
1257 NS_IMETHODIMP
1258 VectorImage::StartDecoding(uint32_t aFlags, uint32_t aWhichFrame) {
1259 // Nothing to do for SVG images
1260 return NS_OK;
1263 bool VectorImage::StartDecodingWithResult(uint32_t aFlags,
1264 uint32_t aWhichFrame) {
1265 // SVG images are ready to draw when they are loaded
1266 return mIsFullyLoaded;
1269 imgIContainer::DecodeResult VectorImage::RequestDecodeWithResult(
1270 uint32_t aFlags, uint32_t aWhichFrame) {
1271 // SVG images are ready to draw when they are loaded and don't have an error.
1273 if (mError) {
1274 return imgIContainer::DECODE_REQUEST_FAILED;
1277 if (!mIsFullyLoaded) {
1278 return imgIContainer::DECODE_REQUESTED;
1281 return imgIContainer::DECODE_SURFACE_AVAILABLE;
1284 NS_IMETHODIMP
1285 VectorImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags,
1286 uint32_t aWhichFrame) {
1287 // Nothing to do for SVG images, though in theory we could rasterize to the
1288 // provided size ahead of time if we supported off-main-thread SVG
1289 // rasterization...
1290 return NS_OK;
1293 //******************************************************************************
1295 NS_IMETHODIMP
1296 VectorImage::LockImage() {
1297 MOZ_ASSERT(NS_IsMainThread());
1299 if (mError) {
1300 return NS_ERROR_FAILURE;
1303 mLockCount++;
1305 if (mLockCount == 1) {
1306 // Lock this image's surfaces in the SurfaceCache.
1307 SurfaceCache::LockImage(ImageKey(this));
1310 return NS_OK;
1313 //******************************************************************************
1315 NS_IMETHODIMP
1316 VectorImage::UnlockImage() {
1317 MOZ_ASSERT(NS_IsMainThread());
1319 if (mError) {
1320 return NS_ERROR_FAILURE;
1323 if (mLockCount == 0) {
1324 MOZ_ASSERT_UNREACHABLE("Calling UnlockImage with a zero lock count");
1325 return NS_ERROR_ABORT;
1328 mLockCount--;
1330 if (mLockCount == 0) {
1331 // Unlock this image's surfaces in the SurfaceCache.
1332 SurfaceCache::UnlockImage(ImageKey(this));
1335 return NS_OK;
1338 //******************************************************************************
1340 NS_IMETHODIMP
1341 VectorImage::RequestDiscard() {
1342 MOZ_ASSERT(NS_IsMainThread());
1344 if (mDiscardable && mLockCount == 0) {
1345 SurfaceCache::RemoveImage(ImageKey(this));
1346 mProgressTracker->OnDiscard();
1349 return NS_OK;
1352 void VectorImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) {
1353 MOZ_ASSERT(mProgressTracker);
1355 NS_DispatchToMainThread(NewRunnableMethod("ProgressTracker::OnDiscard",
1356 mProgressTracker,
1357 &ProgressTracker::OnDiscard));
1360 //******************************************************************************
1361 NS_IMETHODIMP
1362 VectorImage::ResetAnimation() {
1363 if (mError) {
1364 return NS_ERROR_FAILURE;
1367 if (!mIsFullyLoaded || !mHaveAnimations) {
1368 return NS_OK; // There are no animations to be reset.
1371 mSVGDocumentWrapper->ResetAnimation();
1373 return NS_OK;
1376 NS_IMETHODIMP_(float)
1377 VectorImage::GetFrameIndex(uint32_t aWhichFrame) {
1378 MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
1379 return aWhichFrame == FRAME_FIRST
1380 ? 0.0f
1381 : mSVGDocumentWrapper->GetCurrentTimeAsFloat();
1384 //------------------------------------------------------------------------------
1385 // nsIRequestObserver methods
1387 //******************************************************************************
1388 NS_IMETHODIMP
1389 VectorImage::OnStartRequest(nsIRequest* aRequest) {
1390 MOZ_ASSERT(!mSVGDocumentWrapper,
1391 "Repeated call to OnStartRequest -- can this happen?");
1393 mSVGDocumentWrapper = new SVGDocumentWrapper();
1394 nsresult rv = mSVGDocumentWrapper->OnStartRequest(aRequest);
1395 if (NS_FAILED(rv)) {
1396 mSVGDocumentWrapper = nullptr;
1397 mError = true;
1398 return rv;
1401 // Create a listener to wait until the SVG document is fully loaded, which
1402 // will signal that this image is ready to render. Certain error conditions
1403 // will prevent us from ever getting this notification, so we also create a
1404 // listener that waits for parsing to complete and cancels the
1405 // SVGLoadEventListener if needed. The listeners are automatically attached
1406 // to the document by their constructors.
1407 SVGDocument* document = mSVGDocumentWrapper->GetDocument();
1408 mLoadEventListener = new SVGLoadEventListener(document, this);
1409 mParseCompleteListener = new SVGParseCompleteListener(document, this);
1411 // Displayed documents will call InitUseCounters under SetScriptGlobalObject,
1412 // but SVG image documents never get a script global object, so we initialize
1413 // use counters here, right after the document has been created.
1414 document->InitUseCounters();
1416 return NS_OK;
1419 //******************************************************************************
1420 NS_IMETHODIMP
1421 VectorImage::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
1422 if (mError) {
1423 return NS_ERROR_FAILURE;
1426 return mSVGDocumentWrapper->OnStopRequest(aRequest, aStatus);
1429 void VectorImage::OnSVGDocumentParsed() {
1430 MOZ_ASSERT(mParseCompleteListener, "Should have the parse complete listener");
1431 MOZ_ASSERT(mLoadEventListener, "Should have the load event listener");
1433 if (!mSVGDocumentWrapper->GetRootSVGElem()) {
1434 // This is an invalid SVG document. It may have failed to parse, or it may
1435 // be missing the <svg> root element, or the <svg> root element may not
1436 // declare the correct namespace. In any of these cases, we'll never be
1437 // notified that the SVG finished loading, so we need to treat this as an
1438 // error.
1439 OnSVGDocumentError();
1443 void VectorImage::CancelAllListeners() {
1444 if (mParseCompleteListener) {
1445 mParseCompleteListener->Cancel();
1446 mParseCompleteListener = nullptr;
1448 if (mLoadEventListener) {
1449 mLoadEventListener->Cancel();
1450 mLoadEventListener = nullptr;
1454 void VectorImage::OnSVGDocumentLoaded() {
1455 MOZ_ASSERT(mSVGDocumentWrapper->GetRootSVGElem(),
1456 "Should have parsed successfully");
1457 MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations,
1458 "These flags shouldn't get set until OnSVGDocumentLoaded. "
1459 "Duplicate calls to OnSVGDocumentLoaded?");
1461 CancelAllListeners();
1463 // XXX Flushing is wasteful if embedding frame hasn't had initial reflow.
1464 mSVGDocumentWrapper->FlushLayout();
1466 // This is the earliest point that we can get accurate use counter data
1467 // for a valid SVG document. Without the FlushLayout call, we would miss
1468 // any CSS property usage that comes from SVG presentation attributes.
1469 mSVGDocumentWrapper->GetDocument()->ReportDocumentUseCounters();
1471 mIsFullyLoaded = true;
1472 mHaveAnimations = mSVGDocumentWrapper->IsAnimated();
1474 // Start listening to our image for rendering updates.
1475 mRenderingObserver = new SVGRootRenderingObserver(mSVGDocumentWrapper, this);
1477 // ProgressTracker::SyncNotifyProgress may release us, so ensure we
1478 // stick around long enough to complete our work.
1479 RefPtr<VectorImage> kungFuDeathGrip(this);
1481 // Tell *our* observers that we're done loading.
1482 if (mProgressTracker) {
1483 Progress progress = FLAG_SIZE_AVAILABLE | FLAG_HAS_TRANSPARENCY |
1484 FLAG_FRAME_COMPLETE | FLAG_DECODE_COMPLETE;
1486 if (mHaveAnimations) {
1487 progress |= FLAG_IS_ANIMATED;
1490 // Merge in any saved progress from OnImageDataComplete.
1491 if (mLoadProgress) {
1492 progress |= *mLoadProgress;
1493 mLoadProgress = Nothing();
1496 mProgressTracker->SyncNotifyProgress(progress, GetMaxSizedIntRect());
1499 EvaluateAnimation();
1502 void VectorImage::OnSVGDocumentError() {
1503 CancelAllListeners();
1505 mError = true;
1507 // We won't enter OnSVGDocumentLoaded, so report use counters now for this
1508 // invalid document.
1509 ReportDocumentUseCounters();
1511 if (mProgressTracker) {
1512 // Notify observers about the error and unblock page load.
1513 Progress progress = FLAG_HAS_ERROR;
1515 // Merge in any saved progress from OnImageDataComplete.
1516 if (mLoadProgress) {
1517 progress |= *mLoadProgress;
1518 mLoadProgress = Nothing();
1521 mProgressTracker->SyncNotifyProgress(progress);
1525 //------------------------------------------------------------------------------
1526 // nsIStreamListener method
1528 //******************************************************************************
1529 NS_IMETHODIMP
1530 VectorImage::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
1531 uint64_t aSourceOffset, uint32_t aCount) {
1532 if (mError) {
1533 return NS_ERROR_FAILURE;
1536 return mSVGDocumentWrapper->OnDataAvailable(aRequest, aInStr, aSourceOffset,
1537 aCount);
1540 // --------------------------
1541 // Invalidation helper method
1543 void VectorImage::InvalidateObserversOnNextRefreshDriverTick() {
1544 if (mHasPendingInvalidation) {
1545 return;
1548 mHasPendingInvalidation = true;
1550 // Animated images can wait for the refresh tick.
1551 if (mHaveAnimations) {
1552 return;
1555 // Non-animated images won't get the refresh tick, so we should just send an
1556 // invalidation outside the current execution context. We need to defer
1557 // because the layout tree is in the middle of invalidation, and the tree
1558 // state needs to be consistent. Specifically only some of the frames have
1559 // had the NS_FRAME_DESCENDANT_NEEDS_PAINT and/or NS_FRAME_NEEDS_PAINT bits
1560 // set by InvalidateFrameInternal in layout/generic/nsFrame.cpp. These bits
1561 // get cleared when we repaint the SVG into a surface by
1562 // nsIFrame::ClearInvalidationStateBits in nsDisplayList::PaintRoot.
1563 nsCOMPtr<nsIEventTarget> eventTarget;
1564 if (mProgressTracker) {
1565 eventTarget = mProgressTracker->GetEventTarget();
1566 } else {
1567 eventTarget = do_GetMainThread();
1570 RefPtr<VectorImage> self(this);
1571 nsCOMPtr<nsIRunnable> ev(NS_NewRunnableFunction(
1572 "VectorImage::SendInvalidationNotifications",
1573 [=]() -> void { self->SendInvalidationNotifications(); }));
1574 eventTarget->Dispatch(CreateRenderBlockingRunnable(ev.forget()),
1575 NS_DISPATCH_NORMAL);
1578 void VectorImage::PropagateUseCounters(Document* aReferencingDocument) {
1579 if (Document* doc = mSVGDocumentWrapper->GetDocument()) {
1580 doc->PropagateImageUseCounters(aReferencingDocument);
1584 nsIntSize VectorImage::OptimalImageSizeForDest(const gfxSize& aDest,
1585 uint32_t aWhichFrame,
1586 SamplingFilter aSamplingFilter,
1587 uint32_t aFlags) {
1588 MOZ_ASSERT(aDest.width >= 0 || ceil(aDest.width) <= INT32_MAX ||
1589 aDest.height >= 0 || ceil(aDest.height) <= INT32_MAX,
1590 "Unexpected destination size");
1592 // We can rescale SVGs freely, so just return the provided destination size.
1593 return nsIntSize::Ceil(aDest.width, aDest.height);
1596 already_AddRefed<imgIContainer> VectorImage::Unwrap() {
1597 nsCOMPtr<imgIContainer> self(this);
1598 return self.forget();
1601 void VectorImage::MediaFeatureValuesChangedAllDocuments(
1602 const MediaFeatureChange& aChange) {
1603 if (!mSVGDocumentWrapper) {
1604 return;
1607 // Don't bother if the document hasn't loaded yet.
1608 if (!mIsFullyLoaded) {
1609 return;
1612 if (Document* doc = mSVGDocumentWrapper->GetDocument()) {
1613 if (RefPtr<nsPresContext> presContext = doc->GetPresContext()) {
1614 presContext->MediaFeatureValuesChanged(
1615 aChange, MediaFeatureChangePropagation::All);
1616 // Media feature value changes don't happen in the middle of layout,
1617 // so we don't need to call InvalidateObserversOnNextRefreshDriverTick
1618 // to invalidate asynchronously.
1619 if (presContext->FlushPendingMediaFeatureValuesChanged()) {
1620 // NOTE(emilio): SendInvalidationNotifications flushes layout via
1621 // VectorImage::CreateSurface -> FlushImageTransformInvalidation.
1622 SendInvalidationNotifications();
1628 nsresult VectorImage::GetHotspotX(int32_t* aX) {
1629 return Image::GetHotspotX(aX);
1632 nsresult VectorImage::GetHotspotY(int32_t* aY) {
1633 return Image::GetHotspotY(aY);
1636 void VectorImage::ReportDocumentUseCounters() {
1637 if (!mSVGDocumentWrapper) {
1638 return;
1641 if (Document* doc = mSVGDocumentWrapper->GetDocument()) {
1642 doc->ReportDocumentUseCounters();
1646 } // namespace image
1647 } // namespace mozilla