Bug 1767595 [wpt PR 33926] - Added a WPT-test for connecting a TMST to a video elemen...
[gecko.git] / image / VectorImage.cpp
blob042237243f30aaa53c2c845c638a829d0a8c347b
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->Clip(aFillRect);
269 gfxMatrix matrix = aTransform;
270 if (!matrix.Invert()) {
271 return false;
273 aContext->SetMatrixDouble(
274 aContext->CurrentMatrixDouble().PreMultiply(matrix).PreScale(
275 double(mSize.width) / mViewportSize.width,
276 double(mSize.height) / mViewportSize.height));
278 nsPresContext* presContext = presShell->GetPresContext();
279 MOZ_ASSERT(presContext, "pres shell w/out pres context");
281 nsRect svgRect(0, 0, presContext->DevPixelsToAppUnits(mViewportSize.width),
282 presContext->DevPixelsToAppUnits(mViewportSize.height));
284 RenderDocumentFlags renderDocFlags =
285 RenderDocumentFlags::IgnoreViewportScrolling;
286 if (!(mImageFlags & imgIContainer::FLAG_SYNC_DECODE)) {
287 renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages;
289 if (mImageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) {
290 renderDocFlags |= RenderDocumentFlags::UseHighQualityScaling;
293 presShell->RenderDocument(svgRect, renderDocFlags,
294 NS_RGBA(0, 0, 0, 0), // transparent
295 aContext);
297 return true;
300 // Implement VectorImage's nsISupports-inherited methods
301 NS_IMPL_ISUPPORTS(VectorImage, imgIContainer, nsIStreamListener,
302 nsIRequestObserver)
304 //------------------------------------------------------------------------------
305 // Constructor / Destructor
307 VectorImage::VectorImage(nsIURI* aURI /* = nullptr */)
308 : ImageResource(aURI), // invoke superclass's constructor
309 mLockCount(0),
310 mIsInitialized(false),
311 mDiscardable(false),
312 mIsFullyLoaded(false),
313 mHaveAnimations(false),
314 mHasPendingInvalidation(false) {}
316 VectorImage::~VectorImage() {
317 ReportDocumentUseCounters();
318 CancelAllListeners();
319 SurfaceCache::RemoveImage(ImageKey(this));
322 //------------------------------------------------------------------------------
323 // Methods inherited from Image.h
325 nsresult VectorImage::Init(const char* aMimeType, uint32_t aFlags) {
326 // We don't support re-initialization
327 if (mIsInitialized) {
328 return NS_ERROR_ILLEGAL_VALUE;
331 MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations && !mError,
332 "Flags unexpectedly set before initialization");
333 MOZ_ASSERT(!strcmp(aMimeType, IMAGE_SVG_XML), "Unexpected mimetype");
335 mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE);
337 // Lock this image's surfaces in the SurfaceCache if we're not discardable.
338 if (!mDiscardable) {
339 mLockCount++;
340 SurfaceCache::LockImage(ImageKey(this));
343 mIsInitialized = true;
344 return NS_OK;
347 size_t VectorImage::SizeOfSourceWithComputedFallback(
348 SizeOfState& aState) const {
349 if (!mSVGDocumentWrapper) {
350 return 0; // No document, so no memory used for the document.
353 SVGDocument* doc = mSVGDocumentWrapper->GetDocument();
354 if (!doc) {
355 return 0; // No document, so no memory used for the document.
358 nsWindowSizes windowSizes(aState);
359 doc->DocAddSizeOfIncludingThis(windowSizes);
361 if (windowSizes.getTotalSize() == 0) {
362 // MallocSizeOf fails on this platform. Because we also use this method for
363 // determining the size of cache entries, we need to return something
364 // reasonable here. Unfortunately, there's no way to estimate the document's
365 // size accurately, so we just use a constant value of 100KB, which will
366 // generally underestimate the true size.
367 return 100 * 1024;
370 return windowSizes.getTotalSize();
373 nsresult VectorImage::OnImageDataComplete(nsIRequest* aRequest,
374 nsresult aStatus, bool aLastPart) {
375 // Call our internal OnStopRequest method, which only talks to our embedded
376 // SVG document. This won't have any effect on our ProgressTracker.
377 nsresult finalStatus = OnStopRequest(aRequest, aStatus);
379 // Give precedence to Necko failure codes.
380 if (NS_FAILED(aStatus)) {
381 finalStatus = aStatus;
384 Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus);
386 if (mIsFullyLoaded || mError) {
387 // Our document is loaded, so we're ready to notify now.
388 mProgressTracker->SyncNotifyProgress(loadProgress);
389 } else {
390 // Record our progress so far; we'll actually send the notifications in
391 // OnSVGDocumentLoaded or OnSVGDocumentError.
392 mLoadProgress = Some(loadProgress);
395 return finalStatus;
398 nsresult VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
399 nsIInputStream* aInStr,
400 uint64_t aSourceOffset,
401 uint32_t aCount) {
402 return OnDataAvailable(aRequest, aInStr, aSourceOffset, aCount);
405 nsresult VectorImage::StartAnimation() {
406 if (mError) {
407 return NS_ERROR_FAILURE;
410 MOZ_ASSERT(ShouldAnimate(), "Should not animate!");
412 mSVGDocumentWrapper->StartAnimation();
413 return NS_OK;
416 nsresult VectorImage::StopAnimation() {
417 nsresult rv = NS_OK;
418 if (mError) {
419 rv = NS_ERROR_FAILURE;
420 } else {
421 MOZ_ASSERT(mIsFullyLoaded && mHaveAnimations,
422 "Should not have been animating!");
424 mSVGDocumentWrapper->StopAnimation();
427 mAnimating = false;
428 return rv;
431 bool VectorImage::ShouldAnimate() {
432 return ImageResource::ShouldAnimate() && mIsFullyLoaded && mHaveAnimations;
435 NS_IMETHODIMP_(void)
436 VectorImage::SetAnimationStartTime(const TimeStamp& aTime) {
437 // We don't care about animation start time.
440 //------------------------------------------------------------------------------
441 // imgIContainer methods
443 //******************************************************************************
444 NS_IMETHODIMP
445 VectorImage::GetWidth(int32_t* aWidth) {
446 if (mError || !mIsFullyLoaded) {
447 // XXXdholbert Technically we should leave outparam untouched when we
448 // fail. But since many callers don't check for failure, we set it to 0 on
449 // failure, for sane/predictable results.
450 *aWidth = 0;
451 return NS_ERROR_FAILURE;
454 SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem();
455 MOZ_ASSERT(rootElem,
456 "Should have a root SVG elem, since we finished "
457 "loading without errors");
458 int32_t rootElemWidth = rootElem->GetIntrinsicWidth();
459 if (rootElemWidth < 0) {
460 *aWidth = 0;
461 return NS_ERROR_FAILURE;
463 *aWidth = rootElemWidth;
464 return NS_OK;
467 //******************************************************************************
468 nsresult VectorImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const {
469 return NS_ERROR_NOT_IMPLEMENTED;
472 //******************************************************************************
473 size_t VectorImage::GetNativeSizesLength() const { return 0; }
475 //******************************************************************************
476 NS_IMETHODIMP_(void)
477 VectorImage::RequestRefresh(const TimeStamp& aTime) {
478 if (HadRecentRefresh(aTime)) {
479 return;
482 Document* doc = mSVGDocumentWrapper->GetDocument();
483 if (!doc) {
484 // We are racing between shutdown and a refresh.
485 return;
488 PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
489 if (tracker && ShouldAnimate()) {
490 tracker->TriggerPendingAnimationsOnNextTick(aTime);
493 EvaluateAnimation();
495 mSVGDocumentWrapper->TickRefreshDriver();
497 if (mHasPendingInvalidation) {
498 SendInvalidationNotifications();
502 void VectorImage::SendInvalidationNotifications() {
503 // Animated images don't send out invalidation notifications as soon as
504 // they're generated. Instead, InvalidateObserversOnNextRefreshDriverTick
505 // records that there are pending invalidations and then returns immediately.
506 // The notifications are actually sent from RequestRefresh(). We send these
507 // notifications there to ensure that there is actually a document observing
508 // us. Otherwise, the notifications are just wasted effort.
510 // Non-animated images post an event to call this method from
511 // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never
512 // called for them. Ordinarily this isn't needed, since we send out
513 // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the
514 // SVG document may not be 100% ready to render at that time. In those cases
515 // we would miss the subsequent invalidations if we didn't send out the
516 // notifications indirectly in |InvalidateObservers...|.
518 mHasPendingInvalidation = false;
520 if (SurfaceCache::InvalidateImage(ImageKey(this))) {
521 // If we still have recordings in the cache, make sure we handle future
522 // invalidations.
523 MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
524 mRenderingObserver->ResumeHonoringInvalidations();
527 if (mProgressTracker) {
528 mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
529 GetMaxSizedIntRect());
533 NS_IMETHODIMP_(IntRect)
534 VectorImage::GetImageSpaceInvalidationRect(const IntRect& aRect) {
535 return aRect;
538 //******************************************************************************
539 NS_IMETHODIMP
540 VectorImage::GetHeight(int32_t* aHeight) {
541 if (mError || !mIsFullyLoaded) {
542 // XXXdholbert Technically we should leave outparam untouched when we
543 // fail. But since many callers don't check for failure, we set it to 0 on
544 // failure, for sane/predictable results.
545 *aHeight = 0;
546 return NS_ERROR_FAILURE;
549 SVGSVGElement* rootElem = mSVGDocumentWrapper->GetRootSVGElem();
550 MOZ_ASSERT(rootElem,
551 "Should have a root SVG elem, since we finished "
552 "loading without errors");
553 int32_t rootElemHeight = rootElem->GetIntrinsicHeight();
554 if (rootElemHeight < 0) {
555 *aHeight = 0;
556 return NS_ERROR_FAILURE;
558 *aHeight = rootElemHeight;
559 return NS_OK;
562 //******************************************************************************
563 NS_IMETHODIMP
564 VectorImage::GetIntrinsicSize(nsSize* aSize) {
565 if (mError || !mIsFullyLoaded) {
566 return NS_ERROR_FAILURE;
569 nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
570 if (!rootFrame) {
571 return NS_ERROR_FAILURE;
574 *aSize = nsSize(-1, -1);
575 IntrinsicSize rfSize = rootFrame->GetIntrinsicSize();
576 if (rfSize.width) {
577 aSize->width = *rfSize.width;
579 if (rfSize.height) {
580 aSize->height = *rfSize.height;
582 return NS_OK;
585 //******************************************************************************
586 Maybe<AspectRatio> VectorImage::GetIntrinsicRatio() {
587 if (mError || !mIsFullyLoaded) {
588 return Nothing();
591 nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
592 if (!rootFrame) {
593 return Nothing();
596 return Some(rootFrame->GetIntrinsicRatio());
599 NS_IMETHODIMP_(Orientation)
600 VectorImage::GetOrientation() { return Orientation(); }
602 NS_IMETHODIMP_(Resolution)
603 VectorImage::GetResolution() { return {}; }
605 //******************************************************************************
606 NS_IMETHODIMP
607 VectorImage::GetType(uint16_t* aType) {
608 NS_ENSURE_ARG_POINTER(aType);
610 *aType = imgIContainer::TYPE_VECTOR;
611 return NS_OK;
614 //******************************************************************************
615 NS_IMETHODIMP
616 VectorImage::GetProviderId(uint32_t* aId) {
617 NS_ENSURE_ARG_POINTER(aId);
619 *aId = ImageResource::GetImageProviderId();
620 return NS_OK;
623 //******************************************************************************
624 NS_IMETHODIMP
625 VectorImage::GetAnimated(bool* aAnimated) {
626 if (mError || !mIsFullyLoaded) {
627 return NS_ERROR_FAILURE;
630 *aAnimated = mSVGDocumentWrapper->IsAnimated();
631 return NS_OK;
634 //******************************************************************************
635 int32_t VectorImage::GetFirstFrameDelay() {
636 if (mError) {
637 return -1;
640 if (!mSVGDocumentWrapper->IsAnimated()) {
641 return -1;
644 // We don't really have a frame delay, so just pretend that we constantly
645 // need updates.
646 return 0;
649 NS_IMETHODIMP_(bool)
650 VectorImage::WillDrawOpaqueNow() {
651 return false; // In general, SVG content is not opaque.
654 //******************************************************************************
655 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
656 VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
657 if (mError) {
658 return nullptr;
661 // Look up height & width
662 // ----------------------
663 SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem();
664 MOZ_ASSERT(svgElem,
665 "Should have a root SVG elem, since we finished "
666 "loading without errors");
667 nsIntSize imageIntSize(svgElem->GetIntrinsicWidth(),
668 svgElem->GetIntrinsicHeight());
670 if (imageIntSize.IsEmpty()) {
671 // We'll get here if our SVG doc has a percent-valued or negative width or
672 // height.
673 return nullptr;
676 return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags);
679 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
680 VectorImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame,
681 uint32_t aFlags) {
682 MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
684 AutoProfilerImagePaintMarker PROFILER_RAII(this);
685 #ifdef DEBUG
686 NotifyDrawingObservers();
687 #endif
689 if (aSize.IsEmpty() || aWhichFrame > FRAME_MAX_VALUE || mError ||
690 !mIsFullyLoaded) {
691 return nullptr;
694 uint32_t whichFrame = mHaveAnimations ? aWhichFrame : FRAME_FIRST;
696 RefPtr<SourceSurface> sourceSurface;
697 IntSize decodeSize;
698 Tie(sourceSurface, decodeSize) =
699 LookupCachedSurface(aSize, Nothing(), aFlags);
700 if (sourceSurface) {
701 return sourceSurface.forget();
704 if (mSVGDocumentWrapper->IsDrawing()) {
705 NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
706 return nullptr;
709 float animTime = (whichFrame == FRAME_FIRST)
710 ? 0.0f
711 : mSVGDocumentWrapper->GetCurrentTimeAsFloat();
713 // By using a null gfxContext, we ensure that we will always attempt to
714 // create a surface, even if we aren't capable of caching it (e.g. due to our
715 // flags, having an animation, etc). Otherwise CreateSurface will assume that
716 // the caller is capable of drawing directly to its own draw target if we
717 // cannot cache.
718 Maybe<SVGImageContext> svgContext;
719 SVGDrawingParameters params(
720 nullptr, decodeSize, aSize, ImageRegion::Create(decodeSize),
721 SamplingFilter::POINT, svgContext, animTime, aFlags, 1.0);
723 bool didCache; // Was the surface put into the cache?
725 AutoRestoreSVGState autoRestore(params, mSVGDocumentWrapper,
726 /* aContextPaint */ false);
728 RefPtr<gfxDrawable> svgDrawable = CreateSVGDrawable(params);
729 RefPtr<SourceSurface> surface = CreateSurface(params, svgDrawable, didCache);
730 if (!surface) {
731 MOZ_ASSERT(!didCache);
732 return nullptr;
735 SendFrameComplete(didCache, params.flags);
736 return surface.forget();
739 NS_IMETHODIMP_(bool)
740 VectorImage::IsImageContainerAvailable(WindowRenderer* aRenderer,
741 uint32_t aFlags) {
742 if (mError || !mIsFullyLoaded ||
743 aRenderer->GetBackendType() != LayersBackend::LAYERS_WR) {
744 return false;
747 if (mHaveAnimations && !StaticPrefs::image_svg_blob_image()) {
748 // We don't support rasterizing animation SVGs. We can put them in a blob
749 // recording however instead of using fallback.
750 return false;
753 return true;
756 //******************************************************************************
757 NS_IMETHODIMP_(ImgDrawResult)
758 VectorImage::GetImageProvider(WindowRenderer* aRenderer,
759 const gfx::IntSize& aSize,
760 const Maybe<SVGImageContext>& aSVGContext,
761 const Maybe<ImageIntRegion>& aRegion,
762 uint32_t aFlags,
763 WebRenderImageProvider** aProvider) {
764 MOZ_ASSERT(NS_IsMainThread());
765 MOZ_ASSERT(aRenderer);
766 MOZ_ASSERT(!(aFlags & FLAG_BYPASS_SURFACE_CACHE), "Unsupported flags");
768 // We don't need to check if the size is too big since we only support
769 // WebRender backends.
770 if (aSize.IsEmpty()) {
771 return ImgDrawResult::BAD_ARGS;
774 if (mError) {
775 return ImgDrawResult::BAD_IMAGE;
778 if (!mIsFullyLoaded) {
779 return ImgDrawResult::NOT_READY;
782 if (mHaveAnimations && !(aFlags & FLAG_RECORD_BLOB)) {
783 // We don't support rasterizing animation SVGs. We can put them in a blob
784 // recording however instead of using fallback.
785 return ImgDrawResult::NOT_SUPPORTED;
788 AutoProfilerImagePaintMarker PROFILER_RAII(this);
789 #ifdef DEBUG
790 NotifyDrawingObservers();
791 #endif
793 // Only blob recordings support a region to restrict drawing.
794 const bool blobRecording = aFlags & FLAG_RECORD_BLOB;
795 MOZ_ASSERT_IF(!blobRecording, aRegion.isNothing());
797 LookupResult result(MatchType::NOT_FOUND);
798 auto playbackType =
799 mHaveAnimations ? PlaybackType::eAnimated : PlaybackType::eStatic;
800 auto surfaceFlags = ToSurfaceFlags(aFlags);
802 SurfaceKey surfaceKey =
803 VectorSurfaceKey(aSize, aRegion, aSVGContext, surfaceFlags, playbackType);
804 if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) {
805 result = SurfaceCache::Lookup(ImageKey(this), surfaceKey,
806 /* aMarkUsed = */ true);
807 } else {
808 result = SurfaceCache::LookupBestMatch(ImageKey(this), surfaceKey,
809 /* aMarkUsed = */ true);
812 // Unless we get a best match (exact or factor of 2 limited), then we want to
813 // generate a new recording/rerasterize, even if we have a substitute.
814 if (result && (result.Type() == MatchType::EXACT ||
815 result.Type() == MatchType::SUBSTITUTE_BECAUSE_BEST)) {
816 result.Surface().TakeProvider(aProvider);
817 return ImgDrawResult::SUCCESS;
820 // Ensure we store the surface with the correct key if we switched to factor
821 // of 2 sizing or we otherwise got clamped.
822 IntSize rasterSize(aSize);
823 if (!result.SuggestedSize().IsEmpty()) {
824 rasterSize = result.SuggestedSize();
825 surfaceKey = surfaceKey.CloneWithSize(rasterSize);
828 // We're about to rerasterize, which may mean that some of the previous
829 // surfaces we've rasterized aren't useful anymore. We can allow them to
830 // expire from the cache by unlocking them here, and then sending out an
831 // invalidation. If this image is locked, any surfaces that are still useful
832 // will become locked again when Draw touches them, and the remainder will
833 // eventually expire.
834 bool mayCache = SurfaceCache::CanHold(rasterSize);
835 if (mayCache) {
836 SurfaceCache::UnlockEntries(ImageKey(this));
839 // Blob recorded vector images just create a provider responsible for
840 // generating blob keys and recording bindings. The recording won't happen
841 // until the caller requests the key explicitly.
842 RefPtr<ISurfaceProvider> provider;
843 if (blobRecording) {
844 provider = MakeRefPtr<BlobSurfaceProvider>(ImageKey(this), surfaceKey,
845 mSVGDocumentWrapper, aFlags);
846 } else {
847 if (mSVGDocumentWrapper->IsDrawing()) {
848 NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
849 return ImgDrawResult::TEMPORARY_ERROR;
852 // We aren't using blobs, so we need to rasterize.
853 float animTime =
854 mHaveAnimations ? mSVGDocumentWrapper->GetCurrentTimeAsFloat() : 0.0f;
856 // By using a null gfxContext, we ensure that we will always attempt to
857 // create a surface, even if we aren't capable of caching it (e.g. due to
858 // our flags, having an animation, etc). Otherwise CreateSurface will assume
859 // that the caller is capable of drawing directly to its own draw target if
860 // we cannot cache.
861 SVGDrawingParameters params(
862 nullptr, rasterSize, aSize, ImageRegion::Create(rasterSize),
863 SamplingFilter::POINT, aSVGContext, animTime, aFlags, 1.0);
865 RefPtr<gfxDrawable> svgDrawable = CreateSVGDrawable(params);
866 bool contextPaint = aSVGContext && aSVGContext->GetContextPaint();
867 AutoRestoreSVGState autoRestore(params, mSVGDocumentWrapper, contextPaint);
869 mSVGDocumentWrapper->UpdateViewportBounds(params.viewportSize);
870 mSVGDocumentWrapper->FlushImageTransformInvalidation();
872 // Given we have no context, the default backend is fine.
873 BackendType backend =
874 gfxPlatform::GetPlatform()->GetDefaultContentBackend();
876 // Try to create an imgFrame, initializing the surface it contains by
877 // drawing our gfxDrawable into it. (We use FILTER_NEAREST since we never
878 // scale here.)
879 auto frame = MakeNotNull<RefPtr<imgFrame>>();
880 nsresult rv = frame->InitWithDrawable(
881 svgDrawable, params.size, SurfaceFormat::OS_RGBA, SamplingFilter::POINT,
882 params.flags, backend);
884 // If we couldn't create the frame, it was probably because it would end
885 // up way too big. Generally it also wouldn't fit in the cache, but the
886 // prefs could be set such that the cache isn't the limiting factor.
887 if (NS_FAILED(rv)) {
888 return ImgDrawResult::TEMPORARY_ERROR;
891 provider =
892 MakeRefPtr<SimpleSurfaceProvider>(ImageKey(this), surfaceKey, frame);
895 if (mayCache) {
896 // Attempt to cache the frame.
897 if (SurfaceCache::Insert(WrapNotNull(provider)) == InsertOutcome::SUCCESS) {
898 if (rasterSize != aSize) {
899 // We created a new surface that wasn't the size we requested, which
900 // means we entered factor-of-2 mode. We should purge any surfaces we
901 // no longer need rather than waiting for the cache to expire them.
902 SurfaceCache::PruneImage(ImageKey(this));
905 SendFrameComplete(/* aDidCache */ true, aFlags);
909 MOZ_ASSERT(provider);
910 provider.forget(aProvider);
911 return ImgDrawResult::SUCCESS;
914 bool VectorImage::MaybeRestrictSVGContext(
915 Maybe<SVGImageContext>& aNewSVGContext,
916 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags) {
917 bool overridePAR =
918 (aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) && aSVGContext;
920 bool haveContextPaint = aSVGContext && aSVGContext->GetContextPaint();
921 bool blockContextPaint = false;
922 if (haveContextPaint) {
923 blockContextPaint = !SVGContextPaint::IsAllowedForImageFromURI(mURI);
926 if (overridePAR || blockContextPaint) {
927 // The key that we create for the image surface cache must match the way
928 // that the image will be painted, so we need to initialize a new matching
929 // SVGImageContext here in order to generate the correct key.
931 aNewSVGContext = aSVGContext; // copy
933 if (overridePAR) {
934 // The SVGImageContext must take account of the preserveAspectRatio
935 // override:
936 MOZ_ASSERT(!aSVGContext->GetPreserveAspectRatio(),
937 "FLAG_FORCE_PRESERVEASPECTRATIO_NONE is not expected if a "
938 "preserveAspectRatio override is supplied");
939 Maybe<SVGPreserveAspectRatio> aspectRatio = Some(SVGPreserveAspectRatio(
940 SVG_PRESERVEASPECTRATIO_NONE, SVG_MEETORSLICE_UNKNOWN));
941 aNewSVGContext->SetPreserveAspectRatio(aspectRatio);
944 if (blockContextPaint) {
945 // The SVGImageContext must not include context paint if the image is
946 // not allowed to use it:
947 aNewSVGContext->ClearContextPaint();
951 return haveContextPaint && !blockContextPaint;
954 //******************************************************************************
955 NS_IMETHODIMP_(ImgDrawResult)
956 VectorImage::Draw(gfxContext* aContext, const nsIntSize& aSize,
957 const ImageRegion& aRegion, uint32_t aWhichFrame,
958 SamplingFilter aSamplingFilter,
959 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
960 float aOpacity) {
961 if (aWhichFrame > FRAME_MAX_VALUE) {
962 return ImgDrawResult::BAD_ARGS;
965 if (!aContext) {
966 return ImgDrawResult::BAD_ARGS;
969 if (mError) {
970 return ImgDrawResult::BAD_IMAGE;
973 if (!mIsFullyLoaded) {
974 return ImgDrawResult::NOT_READY;
977 if (mAnimationConsumers == 0 && mHaveAnimations) {
978 SendOnUnlockedDraw(aFlags);
981 // We should bypass the cache when:
982 // - We are using a DrawTargetRecording because we prefer the drawing commands
983 // in general to the rasterized surface. This allows blob images to avoid
984 // rasterized SVGs with WebRender.
985 // - The size exceeds what we are willing to cache as a rasterized surface.
986 // We don't do this for WebRender because the performance of the fallback
987 // path is quite bad and upscaling the SVG from the clamped size is better
988 // than bringing the browser to a crawl.
989 if (aContext->GetDrawTarget()->GetBackendType() == BackendType::RECORDING ||
990 (!gfxVars::UseWebRender() &&
991 aSize != SurfaceCache::ClampVectorSize(aSize))) {
992 aFlags |= FLAG_BYPASS_SURFACE_CACHE;
995 MOZ_ASSERT(!(aFlags & FLAG_FORCE_PRESERVEASPECTRATIO_NONE) ||
996 (aSVGContext && aSVGContext->GetViewportSize()),
997 "Viewport size is required when using "
998 "FLAG_FORCE_PRESERVEASPECTRATIO_NONE");
1000 uint32_t whichFrame = mHaveAnimations ? aWhichFrame : FRAME_FIRST;
1002 float animTime = (whichFrame == FRAME_FIRST)
1003 ? 0.0f
1004 : mSVGDocumentWrapper->GetCurrentTimeAsFloat();
1006 Maybe<SVGImageContext> newSVGContext;
1007 bool contextPaint =
1008 MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags);
1010 SVGDrawingParameters params(aContext, aSize, aSize, aRegion, aSamplingFilter,
1011 newSVGContext ? newSVGContext : aSVGContext,
1012 animTime, aFlags, aOpacity);
1014 // If we have an prerasterized version of this image that matches the
1015 // drawing parameters, use that.
1016 RefPtr<SourceSurface> sourceSurface;
1017 Tie(sourceSurface, params.size) =
1018 LookupCachedSurface(aSize, params.svgContext, aFlags);
1019 if (sourceSurface) {
1020 RefPtr<gfxDrawable> drawable =
1021 new gfxSurfaceDrawable(sourceSurface, params.size);
1022 Show(drawable, params);
1023 return ImgDrawResult::SUCCESS;
1026 // else, we need to paint the image:
1028 if (mSVGDocumentWrapper->IsDrawing()) {
1029 NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
1030 return ImgDrawResult::TEMPORARY_ERROR;
1033 AutoRestoreSVGState autoRestore(params, mSVGDocumentWrapper, contextPaint);
1035 bool didCache; // Was the surface put into the cache?
1036 RefPtr<gfxDrawable> svgDrawable = CreateSVGDrawable(params);
1037 sourceSurface = CreateSurface(params, svgDrawable, didCache);
1038 if (!sourceSurface) {
1039 MOZ_ASSERT(!didCache);
1040 Show(svgDrawable, params);
1041 return ImgDrawResult::SUCCESS;
1044 RefPtr<gfxDrawable> drawable =
1045 new gfxSurfaceDrawable(sourceSurface, params.size);
1046 Show(drawable, params);
1047 SendFrameComplete(didCache, params.flags);
1048 return ImgDrawResult::SUCCESS;
1051 already_AddRefed<gfxDrawable> VectorImage::CreateSVGDrawable(
1052 const SVGDrawingParameters& aParams) {
1053 RefPtr<gfxDrawingCallback> cb = new SVGDrawingCallback(
1054 mSVGDocumentWrapper, aParams.viewportSize, aParams.size, aParams.flags);
1056 RefPtr<gfxDrawable> svgDrawable = new gfxCallbackDrawable(cb, aParams.size);
1057 return svgDrawable.forget();
1060 Tuple<RefPtr<SourceSurface>, IntSize> VectorImage::LookupCachedSurface(
1061 const IntSize& aSize, const Maybe<SVGImageContext>& aSVGContext,
1062 uint32_t aFlags) {
1063 // We can't use cached surfaces if we:
1064 // - Explicitly disallow it via FLAG_BYPASS_SURFACE_CACHE
1065 // - Want a blob recording which aren't supported by the cache.
1066 // - Have animations which aren't supported by the cache.
1067 if (aFlags & (FLAG_BYPASS_SURFACE_CACHE | FLAG_RECORD_BLOB) ||
1068 mHaveAnimations) {
1069 return MakeTuple(RefPtr<SourceSurface>(), aSize);
1072 LookupResult result(MatchType::NOT_FOUND);
1073 SurfaceKey surfaceKey = VectorSurfaceKey(aSize, aSVGContext);
1074 if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) {
1075 result = SurfaceCache::Lookup(ImageKey(this), surfaceKey,
1076 /* aMarkUsed = */ true);
1077 } else {
1078 result = SurfaceCache::LookupBestMatch(ImageKey(this), surfaceKey,
1079 /* aMarkUsed = */ true);
1082 IntSize rasterSize =
1083 result.SuggestedSize().IsEmpty() ? aSize : result.SuggestedSize();
1084 MOZ_ASSERT(result.Type() != MatchType::SUBSTITUTE_BECAUSE_PENDING);
1085 if (!result || result.Type() == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND) {
1086 // No matching surface, or the OS freed the volatile buffer.
1087 return MakeTuple(RefPtr<SourceSurface>(), rasterSize);
1090 RefPtr<SourceSurface> sourceSurface = result.Surface()->GetSourceSurface();
1091 if (!sourceSurface) {
1092 // Something went wrong. (Probably a GPU driver crash or device reset.)
1093 // Attempt to recover.
1094 RecoverFromLossOfSurfaces();
1095 return MakeTuple(RefPtr<SourceSurface>(), rasterSize);
1098 return MakeTuple(std::move(sourceSurface), rasterSize);
1101 already_AddRefed<SourceSurface> VectorImage::CreateSurface(
1102 const SVGDrawingParameters& aParams, gfxDrawable* aSVGDrawable,
1103 bool& aWillCache) {
1104 MOZ_ASSERT(mSVGDocumentWrapper->IsDrawing());
1105 MOZ_ASSERT(!(aParams.flags & FLAG_RECORD_BLOB));
1107 mSVGDocumentWrapper->UpdateViewportBounds(aParams.viewportSize);
1108 mSVGDocumentWrapper->FlushImageTransformInvalidation();
1110 // Determine whether or not we should put the surface to be created into
1111 // the cache. If we fail, we need to reset this to false to let the caller
1112 // know nothing was put in the cache.
1113 aWillCache = !(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) &&
1114 // Refuse to cache animated images:
1115 // XXX(seth): We may remove this restriction in bug 922893.
1116 !mHaveAnimations &&
1117 // The image is too big to fit in the cache:
1118 SurfaceCache::CanHold(aParams.size);
1120 // If we weren't given a context, then we know we just want the rasterized
1121 // surface. We will create the frame below but only insert it into the cache
1122 // if we actually need to.
1123 if (!aWillCache && aParams.context) {
1124 return nullptr;
1127 // We're about to rerasterize, which may mean that some of the previous
1128 // surfaces we've rasterized aren't useful anymore. We can allow them to
1129 // expire from the cache by unlocking them here, and then sending out an
1130 // invalidation. If this image is locked, any surfaces that are still useful
1131 // will become locked again when Draw touches them, and the remainder will
1132 // eventually expire.
1133 if (aWillCache) {
1134 SurfaceCache::UnlockEntries(ImageKey(this));
1137 // If there is no context, the default backend is fine.
1138 BackendType backend =
1139 aParams.context ? aParams.context->GetDrawTarget()->GetBackendType()
1140 : gfxPlatform::GetPlatform()->GetDefaultContentBackend();
1142 if (backend == BackendType::DIRECT2D1_1) {
1143 // We don't want to draw arbitrary content with D2D anymore
1144 // because it doesn't support PushLayerWithBlend so switch to skia
1145 backend = BackendType::SKIA;
1148 // Try to create an imgFrame, initializing the surface it contains by drawing
1149 // our gfxDrawable into it. (We use FILTER_NEAREST since we never scale here.)
1150 auto frame = MakeNotNull<RefPtr<imgFrame>>();
1151 nsresult rv = frame->InitWithDrawable(
1152 aSVGDrawable, aParams.size, SurfaceFormat::OS_RGBA, SamplingFilter::POINT,
1153 aParams.flags, backend);
1155 // If we couldn't create the frame, it was probably because it would end
1156 // up way too big. Generally it also wouldn't fit in the cache, but the prefs
1157 // could be set such that the cache isn't the limiting factor.
1158 if (NS_FAILED(rv)) {
1159 aWillCache = false;
1160 return nullptr;
1163 // Take a strong reference to the frame's surface and make sure it hasn't
1164 // already been purged by the operating system.
1165 RefPtr<SourceSurface> surface = frame->GetSourceSurface();
1166 if (!surface) {
1167 aWillCache = false;
1168 return nullptr;
1171 // We created the frame, but only because we had no context to draw to
1172 // directly. All the caller wants is the surface in this case.
1173 if (!aWillCache) {
1174 return surface.forget();
1177 // Attempt to cache the frame.
1178 SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext);
1179 NotNull<RefPtr<ISurfaceProvider>> provider =
1180 MakeNotNull<SimpleSurfaceProvider*>(ImageKey(this), surfaceKey, frame);
1182 if (SurfaceCache::Insert(provider) == InsertOutcome::SUCCESS) {
1183 if (aParams.size != aParams.drawSize) {
1184 // We created a new surface that wasn't the size we requested, which means
1185 // we entered factor-of-2 mode. We should purge any surfaces we no longer
1186 // need rather than waiting for the cache to expire them.
1187 SurfaceCache::PruneImage(ImageKey(this));
1189 } else {
1190 aWillCache = false;
1193 return surface.forget();
1196 void VectorImage::SendFrameComplete(bool aDidCache, uint32_t aFlags) {
1197 // If the cache was not updated, we have nothing to do.
1198 if (!aDidCache) {
1199 return;
1202 // Send out an invalidation so that surfaces that are still in use get
1203 // re-locked. See the discussion of the UnlockSurfaces call above.
1204 if (!(aFlags & FLAG_ASYNC_NOTIFY)) {
1205 mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
1206 GetMaxSizedIntRect());
1207 } else {
1208 NotNull<RefPtr<VectorImage>> image = WrapNotNull(this);
1209 NS_DispatchToMainThread(CreateRenderBlockingRunnable(NS_NewRunnableFunction(
1210 "ProgressTracker::SyncNotifyProgress", [=]() -> void {
1211 RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
1212 if (tracker) {
1213 tracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
1214 GetMaxSizedIntRect());
1216 })));
1220 void VectorImage::Show(gfxDrawable* aDrawable,
1221 const SVGDrawingParameters& aParams) {
1222 // The surface size may differ from the size at which we wish to draw. As
1223 // such, we may need to adjust the context/region to take this into account.
1224 gfxContextMatrixAutoSaveRestore saveMatrix(aParams.context);
1225 ImageRegion region(aParams.region);
1226 if (aParams.drawSize != aParams.size) {
1227 gfx::Size scale(double(aParams.drawSize.width) / aParams.size.width,
1228 double(aParams.drawSize.height) / aParams.size.height);
1229 aParams.context->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
1230 region.Scale(1.0 / scale.width, 1.0 / scale.height);
1233 MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now");
1234 gfxUtils::DrawPixelSnapped(aParams.context, aDrawable,
1235 SizeDouble(aParams.size), region,
1236 SurfaceFormat::OS_RGBA, aParams.samplingFilter,
1237 aParams.flags, aParams.opacity, false);
1239 AutoProfilerImagePaintMarker PROFILER_RAII(this);
1240 #ifdef DEBUG
1241 NotifyDrawingObservers();
1242 #endif
1244 MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
1245 mRenderingObserver->ResumeHonoringInvalidations();
1248 void VectorImage::RecoverFromLossOfSurfaces() {
1249 NS_WARNING("An imgFrame became invalid. Attempting to recover...");
1251 // Discard all existing frames, since they're probably all now invalid.
1252 SurfaceCache::RemoveImage(ImageKey(this));
1255 NS_IMETHODIMP
1256 VectorImage::StartDecoding(uint32_t aFlags, uint32_t aWhichFrame) {
1257 // Nothing to do for SVG images
1258 return NS_OK;
1261 bool VectorImage::StartDecodingWithResult(uint32_t aFlags,
1262 uint32_t aWhichFrame) {
1263 // SVG images are ready to draw when they are loaded
1264 return mIsFullyLoaded;
1267 imgIContainer::DecodeResult VectorImage::RequestDecodeWithResult(
1268 uint32_t aFlags, uint32_t aWhichFrame) {
1269 // SVG images are ready to draw when they are loaded and don't have an error.
1271 if (mError) {
1272 return imgIContainer::DECODE_REQUEST_FAILED;
1275 if (!mIsFullyLoaded) {
1276 return imgIContainer::DECODE_REQUESTED;
1279 return imgIContainer::DECODE_SURFACE_AVAILABLE;
1282 NS_IMETHODIMP
1283 VectorImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags,
1284 uint32_t aWhichFrame) {
1285 // Nothing to do for SVG images, though in theory we could rasterize to the
1286 // provided size ahead of time if we supported off-main-thread SVG
1287 // rasterization...
1288 return NS_OK;
1291 //******************************************************************************
1293 NS_IMETHODIMP
1294 VectorImage::LockImage() {
1295 MOZ_ASSERT(NS_IsMainThread());
1297 if (mError) {
1298 return NS_ERROR_FAILURE;
1301 mLockCount++;
1303 if (mLockCount == 1) {
1304 // Lock this image's surfaces in the SurfaceCache.
1305 SurfaceCache::LockImage(ImageKey(this));
1308 return NS_OK;
1311 //******************************************************************************
1313 NS_IMETHODIMP
1314 VectorImage::UnlockImage() {
1315 MOZ_ASSERT(NS_IsMainThread());
1317 if (mError) {
1318 return NS_ERROR_FAILURE;
1321 if (mLockCount == 0) {
1322 MOZ_ASSERT_UNREACHABLE("Calling UnlockImage with a zero lock count");
1323 return NS_ERROR_ABORT;
1326 mLockCount--;
1328 if (mLockCount == 0) {
1329 // Unlock this image's surfaces in the SurfaceCache.
1330 SurfaceCache::UnlockImage(ImageKey(this));
1333 return NS_OK;
1336 //******************************************************************************
1338 NS_IMETHODIMP
1339 VectorImage::RequestDiscard() {
1340 MOZ_ASSERT(NS_IsMainThread());
1342 if (mDiscardable && mLockCount == 0) {
1343 SurfaceCache::RemoveImage(ImageKey(this));
1344 mProgressTracker->OnDiscard();
1347 return NS_OK;
1350 void VectorImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) {
1351 MOZ_ASSERT(mProgressTracker);
1353 NS_DispatchToMainThread(NewRunnableMethod("ProgressTracker::OnDiscard",
1354 mProgressTracker,
1355 &ProgressTracker::OnDiscard));
1358 //******************************************************************************
1359 NS_IMETHODIMP
1360 VectorImage::ResetAnimation() {
1361 if (mError) {
1362 return NS_ERROR_FAILURE;
1365 if (!mIsFullyLoaded || !mHaveAnimations) {
1366 return NS_OK; // There are no animations to be reset.
1369 mSVGDocumentWrapper->ResetAnimation();
1371 return NS_OK;
1374 NS_IMETHODIMP_(float)
1375 VectorImage::GetFrameIndex(uint32_t aWhichFrame) {
1376 MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
1377 return aWhichFrame == FRAME_FIRST
1378 ? 0.0f
1379 : mSVGDocumentWrapper->GetCurrentTimeAsFloat();
1382 //------------------------------------------------------------------------------
1383 // nsIRequestObserver methods
1385 //******************************************************************************
1386 NS_IMETHODIMP
1387 VectorImage::OnStartRequest(nsIRequest* aRequest) {
1388 MOZ_ASSERT(!mSVGDocumentWrapper,
1389 "Repeated call to OnStartRequest -- can this happen?");
1391 mSVGDocumentWrapper = new SVGDocumentWrapper();
1392 nsresult rv = mSVGDocumentWrapper->OnStartRequest(aRequest);
1393 if (NS_FAILED(rv)) {
1394 mSVGDocumentWrapper = nullptr;
1395 mError = true;
1396 return rv;
1399 // Create a listener to wait until the SVG document is fully loaded, which
1400 // will signal that this image is ready to render. Certain error conditions
1401 // will prevent us from ever getting this notification, so we also create a
1402 // listener that waits for parsing to complete and cancels the
1403 // SVGLoadEventListener if needed. The listeners are automatically attached
1404 // to the document by their constructors.
1405 SVGDocument* document = mSVGDocumentWrapper->GetDocument();
1406 mLoadEventListener = new SVGLoadEventListener(document, this);
1407 mParseCompleteListener = new SVGParseCompleteListener(document, this);
1409 // Displayed documents will call InitUseCounters under SetScriptGlobalObject,
1410 // but SVG image documents never get a script global object, so we initialize
1411 // use counters here, right after the document has been created.
1412 document->InitUseCounters();
1414 return NS_OK;
1417 //******************************************************************************
1418 NS_IMETHODIMP
1419 VectorImage::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
1420 if (mError) {
1421 return NS_ERROR_FAILURE;
1424 return mSVGDocumentWrapper->OnStopRequest(aRequest, aStatus);
1427 void VectorImage::OnSVGDocumentParsed() {
1428 MOZ_ASSERT(mParseCompleteListener, "Should have the parse complete listener");
1429 MOZ_ASSERT(mLoadEventListener, "Should have the load event listener");
1431 if (!mSVGDocumentWrapper->GetRootSVGElem()) {
1432 // This is an invalid SVG document. It may have failed to parse, or it may
1433 // be missing the <svg> root element, or the <svg> root element may not
1434 // declare the correct namespace. In any of these cases, we'll never be
1435 // notified that the SVG finished loading, so we need to treat this as an
1436 // error.
1437 OnSVGDocumentError();
1441 void VectorImage::CancelAllListeners() {
1442 if (mParseCompleteListener) {
1443 mParseCompleteListener->Cancel();
1444 mParseCompleteListener = nullptr;
1446 if (mLoadEventListener) {
1447 mLoadEventListener->Cancel();
1448 mLoadEventListener = nullptr;
1452 void VectorImage::OnSVGDocumentLoaded() {
1453 MOZ_ASSERT(mSVGDocumentWrapper->GetRootSVGElem(),
1454 "Should have parsed successfully");
1455 MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations,
1456 "These flags shouldn't get set until OnSVGDocumentLoaded. "
1457 "Duplicate calls to OnSVGDocumentLoaded?");
1459 CancelAllListeners();
1461 // XXX Flushing is wasteful if embedding frame hasn't had initial reflow.
1462 mSVGDocumentWrapper->FlushLayout();
1464 // This is the earliest point that we can get accurate use counter data
1465 // for a valid SVG document. Without the FlushLayout call, we would miss
1466 // any CSS property usage that comes from SVG presentation attributes.
1467 mSVGDocumentWrapper->GetDocument()->ReportDocumentUseCounters();
1469 mIsFullyLoaded = true;
1470 mHaveAnimations = mSVGDocumentWrapper->IsAnimated();
1472 // Start listening to our image for rendering updates.
1473 mRenderingObserver = new SVGRootRenderingObserver(mSVGDocumentWrapper, this);
1475 // ProgressTracker::SyncNotifyProgress may release us, so ensure we
1476 // stick around long enough to complete our work.
1477 RefPtr<VectorImage> kungFuDeathGrip(this);
1479 // Tell *our* observers that we're done loading.
1480 if (mProgressTracker) {
1481 Progress progress = FLAG_SIZE_AVAILABLE | FLAG_HAS_TRANSPARENCY |
1482 FLAG_FRAME_COMPLETE | FLAG_DECODE_COMPLETE;
1484 if (mHaveAnimations) {
1485 progress |= FLAG_IS_ANIMATED;
1488 // Merge in any saved progress from OnImageDataComplete.
1489 if (mLoadProgress) {
1490 progress |= *mLoadProgress;
1491 mLoadProgress = Nothing();
1494 mProgressTracker->SyncNotifyProgress(progress, GetMaxSizedIntRect());
1497 EvaluateAnimation();
1500 void VectorImage::OnSVGDocumentError() {
1501 CancelAllListeners();
1503 mError = true;
1505 // We won't enter OnSVGDocumentLoaded, so report use counters now for this
1506 // invalid document.
1507 ReportDocumentUseCounters();
1509 if (mProgressTracker) {
1510 // Notify observers about the error and unblock page load.
1511 Progress progress = FLAG_HAS_ERROR;
1513 // Merge in any saved progress from OnImageDataComplete.
1514 if (mLoadProgress) {
1515 progress |= *mLoadProgress;
1516 mLoadProgress = Nothing();
1519 mProgressTracker->SyncNotifyProgress(progress);
1523 //------------------------------------------------------------------------------
1524 // nsIStreamListener method
1526 //******************************************************************************
1527 NS_IMETHODIMP
1528 VectorImage::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
1529 uint64_t aSourceOffset, uint32_t aCount) {
1530 if (mError) {
1531 return NS_ERROR_FAILURE;
1534 return mSVGDocumentWrapper->OnDataAvailable(aRequest, aInStr, aSourceOffset,
1535 aCount);
1538 // --------------------------
1539 // Invalidation helper method
1541 void VectorImage::InvalidateObserversOnNextRefreshDriverTick() {
1542 if (mHasPendingInvalidation) {
1543 return;
1546 mHasPendingInvalidation = true;
1548 // Animated images can wait for the refresh tick.
1549 if (mHaveAnimations) {
1550 return;
1553 // Non-animated images won't get the refresh tick, so we should just send an
1554 // invalidation outside the current execution context. We need to defer
1555 // because the layout tree is in the middle of invalidation, and the tree
1556 // state needs to be consistent. Specifically only some of the frames have
1557 // had the NS_FRAME_DESCENDANT_NEEDS_PAINT and/or NS_FRAME_NEEDS_PAINT bits
1558 // set by InvalidateFrameInternal in layout/generic/nsFrame.cpp. These bits
1559 // get cleared when we repaint the SVG into a surface by
1560 // nsIFrame::ClearInvalidationStateBits in nsDisplayList::PaintRoot.
1561 nsCOMPtr<nsIEventTarget> eventTarget;
1562 if (mProgressTracker) {
1563 eventTarget = mProgressTracker->GetEventTarget();
1564 } else {
1565 eventTarget = do_GetMainThread();
1568 RefPtr<VectorImage> self(this);
1569 nsCOMPtr<nsIRunnable> ev(NS_NewRunnableFunction(
1570 "VectorImage::SendInvalidationNotifications",
1571 [=]() -> void { self->SendInvalidationNotifications(); }));
1572 eventTarget->Dispatch(CreateRenderBlockingRunnable(ev.forget()),
1573 NS_DISPATCH_NORMAL);
1576 void VectorImage::PropagateUseCounters(Document* aReferencingDocument) {
1577 if (Document* doc = mSVGDocumentWrapper->GetDocument()) {
1578 doc->PropagateImageUseCounters(aReferencingDocument);
1582 nsIntSize VectorImage::OptimalImageSizeForDest(const gfxSize& aDest,
1583 uint32_t aWhichFrame,
1584 SamplingFilter aSamplingFilter,
1585 uint32_t aFlags) {
1586 MOZ_ASSERT(aDest.width >= 0 || ceil(aDest.width) <= INT32_MAX ||
1587 aDest.height >= 0 || ceil(aDest.height) <= INT32_MAX,
1588 "Unexpected destination size");
1590 // We can rescale SVGs freely, so just return the provided destination size.
1591 return nsIntSize::Ceil(aDest.width, aDest.height);
1594 already_AddRefed<imgIContainer> VectorImage::Unwrap() {
1595 nsCOMPtr<imgIContainer> self(this);
1596 return self.forget();
1599 void VectorImage::MediaFeatureValuesChangedAllDocuments(
1600 const MediaFeatureChange& aChange) {
1601 if (!mSVGDocumentWrapper) {
1602 return;
1605 // Don't bother if the document hasn't loaded yet.
1606 if (!mIsFullyLoaded) {
1607 return;
1610 if (Document* doc = mSVGDocumentWrapper->GetDocument()) {
1611 if (RefPtr<nsPresContext> presContext = doc->GetPresContext()) {
1612 presContext->MediaFeatureValuesChanged(
1613 aChange, MediaFeatureChangePropagation::All);
1614 // Media feature value changes don't happen in the middle of layout,
1615 // so we don't need to call InvalidateObserversOnNextRefreshDriverTick
1616 // to invalidate asynchronously.
1617 if (presContext->FlushPendingMediaFeatureValuesChanged()) {
1618 // NOTE(emilio): SendInvalidationNotifications flushes layout via
1619 // VectorImage::CreateSurface -> FlushImageTransformInvalidation.
1620 SendInvalidationNotifications();
1626 nsresult VectorImage::GetHotspotX(int32_t* aX) {
1627 return Image::GetHotspotX(aX);
1630 nsresult VectorImage::GetHotspotY(int32_t* aY) {
1631 return Image::GetHotspotY(aY);
1634 void VectorImage::ReportDocumentUseCounters() {
1635 if (!mSVGDocumentWrapper) {
1636 return;
1639 if (Document* doc = mSVGDocumentWrapper->GetDocument()) {
1640 doc->ReportDocumentUseCounters();
1644 } // namespace image
1645 } // namespace mozilla