/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef CanvasRenderingContext2D_h #define CanvasRenderingContext2D_h #include "mozilla/Attributes.h" #include <vector> #include "nsIDOMCanvasRenderingContext2D.h" #include "nsICanvasRenderingContextInternal.h" #include "mozilla/RefPtr.h" #include "nsColor.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/HTMLVideoElement.h" #include "gfxTextRun.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/CanvasGradient.h" #include "mozilla/dom/CanvasRenderingContext2DBinding.h" #include "mozilla/dom/CanvasPattern.h" #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/2D.h" #include "mozilla/UniquePtr.h" #include "gfx2DGlue.h" #include "imgIEncoder.h" #include "nsLayoutUtils.h" #include "mozilla/EnumeratedArray.h" #include "FilterSupport.h" #include "nsSVGEffects.h" #include "Layers.h" class nsGlobalWindow; class nsXULElement; namespace mozilla { namespace gl { class SourceSurface; } // namespace gl namespace dom { class HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap; typedef HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap CanvasImageSource; class ImageData; class StringOrCanvasGradientOrCanvasPattern; class OwningStringOrCanvasGradientOrCanvasPattern; class TextMetrics; class CanvasFilterChainObserver; class CanvasPath; extern const mozilla::gfx::Float SIGMA_MAX; template<typename T> class Optional; struct CanvasBidiProcessor; class CanvasRenderingContext2DUserData; class CanvasDrawObserver; class CanvasShutdownObserver; /** ** CanvasRenderingContext2D **/ class CanvasRenderingContext2D final : public nsICanvasRenderingContextInternal, public nsWrapperCache { virtual ~CanvasRenderingContext2D(); public: explicit CanvasRenderingContext2D(layers::LayersBackend aCompositorBackend); virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override; HTMLCanvasElement* GetCanvas() const { if (!mCanvasElement || mCanvasElement->IsInNativeAnonymousSubtree()) { return nullptr; } // corresponds to changes to the old bindings made in bug 745025 return mCanvasElement->GetOriginalCanvas(); } void Save(); void Restore(); void Scale(double aX, double aY, mozilla::ErrorResult& aError); void Rotate(double aAngle, mozilla::ErrorResult& aError); void Translate(double aX, double aY, mozilla::ErrorResult& aError); void Transform(double aM11, double aM12, double aM21, double aM22, double aDx, double aDy, mozilla::ErrorResult& aError); void SetTransform(double aM11, double aM12, double aM21, double aM22, double aDx, double aDy, mozilla::ErrorResult& aError); void ResetTransform(mozilla::ErrorResult& aError); double GlobalAlpha() { return CurrentState().globalAlpha; } // Useful for silencing cast warnings static mozilla::gfx::Float ToFloat(double aValue) { return mozilla::gfx::Float(aValue); } void SetGlobalAlpha(double aGlobalAlpha) { if (aGlobalAlpha >= 0.0 && aGlobalAlpha <= 1.0) { CurrentState().globalAlpha = ToFloat(aGlobalAlpha); } } void GetGlobalCompositeOperation(nsAString& aOp, mozilla::ErrorResult& aError); void SetGlobalCompositeOperation(const nsAString& aOp, mozilla::ErrorResult& aError); void GetStrokeStyle(OwningStringOrCanvasGradientOrCanvasPattern& aValue) { GetStyleAsUnion(aValue, Style::STROKE); } void SetStrokeStyle(const StringOrCanvasGradientOrCanvasPattern& aValue) { SetStyleFromUnion(aValue, Style::STROKE); } void GetFillStyle(OwningStringOrCanvasGradientOrCanvasPattern& aValue) { GetStyleAsUnion(aValue, Style::FILL); } void SetFillStyle(const StringOrCanvasGradientOrCanvasPattern& aValue) { SetStyleFromUnion(aValue, Style::FILL); } already_AddRefed<CanvasGradient> CreateLinearGradient(double aX0, double aY0, double aX1, double aY1); already_AddRefed<CanvasGradient> CreateRadialGradient(double aX0, double aY0, double aR0, double aX1, double aY1, double aR1, ErrorResult& aError); already_AddRefed<CanvasPattern> CreatePattern(const CanvasImageSource& aElement, const nsAString& aRepeat, ErrorResult& aError); double ShadowOffsetX() { return CurrentState().shadowOffset.x; } void SetShadowOffsetX(double aShadowOffsetX) { CurrentState().shadowOffset.x = ToFloat(aShadowOffsetX); } double ShadowOffsetY() { return CurrentState().shadowOffset.y; } void SetShadowOffsetY(double aShadowOffsetY) { CurrentState().shadowOffset.y = ToFloat(aShadowOffsetY); } double ShadowBlur() { return CurrentState().shadowBlur; } void SetShadowBlur(double aShadowBlur) { if (aShadowBlur >= 0.0) { CurrentState().shadowBlur = ToFloat(aShadowBlur); } } void GetShadowColor(nsAString& aShadowColor) { StyleColorToString(CurrentState().shadowColor, aShadowColor); } void GetFilter(nsAString& aFilter) { aFilter = CurrentState().filterString; } void SetShadowColor(const nsAString& aShadowColor); void SetFilter(const nsAString& aFilter, mozilla::ErrorResult& aError); void ClearRect(double aX, double aY, double aW, double aH); void FillRect(double aX, double aY, double aW, double aH); void StrokeRect(double aX, double aY, double aW, double aH); void BeginPath(); void Fill(const CanvasWindingRule& aWinding); void Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding); void Stroke(); void Stroke(const CanvasPath& aPath); void DrawFocusIfNeeded(mozilla::dom::Element& aElement, ErrorResult& aRv); bool DrawCustomFocusRing(mozilla::dom::Element& aElement); void Clip(const CanvasWindingRule& aWinding); void Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding); bool IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding); bool IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding); bool IsPointInStroke(double aX, double aY); bool IsPointInStroke(const CanvasPath& aPath, double aX, double aY); void FillText(const nsAString& aText, double aX, double aY, const Optional<double>& aMaxWidth, mozilla::ErrorResult& aError); void StrokeText(const nsAString& aText, double aX, double aY, const Optional<double>& aMaxWidth, mozilla::ErrorResult& aError); TextMetrics* MeasureText(const nsAString& aRawText, mozilla::ErrorResult& aError); void AddHitRegion(const HitRegionOptions& aOptions, mozilla::ErrorResult& aError); void RemoveHitRegion(const nsAString& aId); void ClearHitRegions(); void DrawImage(const CanvasImageSource& aImage, double aDx, double aDy, mozilla::ErrorResult& aError) { DrawImage(aImage, 0.0, 0.0, 0.0, 0.0, aDx, aDy, 0.0, 0.0, 0, aError); } void DrawImage(const CanvasImageSource& aImage, double aDx, double aDy, double aDw, double aDh, mozilla::ErrorResult& aError) { DrawImage(aImage, 0.0, 0.0, 0.0, 0.0, aDx, aDy, aDw, aDh, 2, aError); } void DrawImage(const CanvasImageSource& aImage, double aSx, double aSy, double aSw, double aSh, double aDx, double aDy, double aDw, double aDh, mozilla::ErrorResult& aError) { DrawImage(aImage, aSx, aSy, aSw, aSh, aDx, aDy, aDw, aDh, 6, aError); } already_AddRefed<ImageData> CreateImageData(JSContext* aCx, double aSw, double aSh, mozilla::ErrorResult& aError); already_AddRefed<ImageData> CreateImageData(JSContext* aCx, ImageData& aImagedata, mozilla::ErrorResult& aError); already_AddRefed<ImageData> GetImageData(JSContext* aCx, double aSx, double aSy, double aSw, double aSh, mozilla::ErrorResult& aError); void PutImageData(ImageData& aImageData, double aDx, double aDy, mozilla::ErrorResult& aError); void PutImageData(ImageData& aImageData, double aDx, double aDy, double aDirtyX, double aDirtyY, double aDirtyWidth, double aDirtyHeight, mozilla::ErrorResult& aError); double LineWidth() { return CurrentState().lineWidth; } void SetLineWidth(double aWidth) { if (aWidth > 0.0) { CurrentState().lineWidth = ToFloat(aWidth); } } void GetLineCap(nsAString& aLinecapStyle); void SetLineCap(const nsAString& aLinecapStyle); void GetLineJoin(nsAString& aLinejoinStyle, mozilla::ErrorResult& aError); void SetLineJoin(const nsAString& aLinejoinStyle); double MiterLimit() { return CurrentState().miterLimit; } void SetMiterLimit(double aMiter) { if (aMiter > 0.0) { CurrentState().miterLimit = ToFloat(aMiter); } } void GetFont(nsAString& aFont) { aFont = GetFont(); } void SetFont(const nsAString& aFont, mozilla::ErrorResult& aError); void GetTextAlign(nsAString& aTextAlign); void SetTextAlign(const nsAString& aTextAlign); void GetTextBaseline(nsAString& aTextBaseline); void SetTextBaseline(const nsAString& aTextBaseline); void ClosePath() { EnsureWritablePath(); if (mPathBuilder) { mPathBuilder->Close(); } else { mDSPathBuilder->Close(); } } void MoveTo(double aX, double aY) { EnsureWritablePath(); if (mPathBuilder) { mPathBuilder->MoveTo(mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))); } else { mDSPathBuilder->MoveTo(mTarget->GetTransform().TransformPoint( mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)))); } } void LineTo(double aX, double aY) { EnsureWritablePath(); LineTo(mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))); } void QuadraticCurveTo(double aCpx, double aCpy, double aX, double aY) { EnsureWritablePath(); if (mPathBuilder) { mPathBuilder->QuadraticBezierTo(mozilla::gfx::Point(ToFloat(aCpx), ToFloat(aCpy)), mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))); } else { mozilla::gfx::Matrix transform = mTarget->GetTransform(); mDSPathBuilder->QuadraticBezierTo(transform.TransformPoint( mozilla::gfx::Point(ToFloat(aCpx), ToFloat(aCpy))), transform.TransformPoint( mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)))); } } void BezierCurveTo(double aCp1x, double aCp1y, double aCp2x, double aCp2y, double aX, double aY) { EnsureWritablePath(); BezierTo(mozilla::gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)), mozilla::gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)), mozilla::gfx::Point(ToFloat(aX), ToFloat(aY))); } void ArcTo(double aX1, double aY1, double aX2, double aY2, double aRadius, mozilla::ErrorResult& aError); void Rect(double aX, double aY, double aW, double aH); void Arc(double aX, double aY, double aRadius, double aStartAngle, double aEndAngle, bool aAnticlockwise, mozilla::ErrorResult& aError); void Ellipse(double aX, double aY, double aRadiusX, double aRadiusY, double aRotation, double aStartAngle, double aEndAngle, bool aAnticlockwise, ErrorResult& aError); void GetMozCurrentTransform(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, mozilla::ErrorResult& aError); void SetMozCurrentTransform(JSContext* aCx, JS::Handle<JSObject*> aCurrentTransform, mozilla::ErrorResult& aError); void GetMozCurrentTransformInverse(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, mozilla::ErrorResult& aError); void SetMozCurrentTransformInverse(JSContext* aCx, JS::Handle<JSObject*> aCurrentTransform, mozilla::ErrorResult& aError); void GetFillRule(nsAString& aFillRule); void SetFillRule(const nsAString& aFillRule); void SetLineDash(const Sequence<double>& aSegments, mozilla::ErrorResult& aRv); void GetLineDash(nsTArray<double>& aSegments) const; void SetLineDashOffset(double aOffset); double LineDashOffset() const; void GetMozTextStyle(nsAString& aMozTextStyle) { GetFont(aMozTextStyle); } void SetMozTextStyle(const nsAString& aMozTextStyle, mozilla::ErrorResult& aError) { SetFont(aMozTextStyle, aError); } bool ImageSmoothingEnabled() { return CurrentState().imageSmoothingEnabled; } void SetImageSmoothingEnabled(bool aImageSmoothingEnabled) { if (aImageSmoothingEnabled != CurrentState().imageSmoothingEnabled) { CurrentState().imageSmoothingEnabled = aImageSmoothingEnabled; } } void DrawWindow(nsGlobalWindow& aWindow, double aX, double aY, double aW, double aH, const nsAString& aBgColor, uint32_t aFlags, mozilla::ErrorResult& aError); void AsyncDrawXULElement(nsXULElement& aElem, double aX, double aY, double aW, double aH, const nsAString& aBgColor, uint32_t aFlags, mozilla::ErrorResult& aError); enum RenderingMode { SoftwareBackendMode, OpenGLBackendMode, DefaultBackendMode }; bool SwitchRenderingMode(RenderingMode aRenderingMode); // Eventually this should be deprecated. Keeping for now to keep the binding functional. void Demote(); nsresult Redraw(); virtual int32_t GetWidth() const override; virtual int32_t GetHeight() const override; gfx::IntSize GetSize() const { return gfx::IntSize(mWidth, mHeight); } // nsICanvasRenderingContextInternal /** * Gets the pres shell from either the canvas element or the doc shell */ virtual nsIPresShell *GetPresShell() override { if (mCanvasElement) { return mCanvasElement->OwnerDoc()->GetShell(); } if (mDocShell) { return mDocShell->GetPresShell(); } return nullptr; } NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override; NS_IMETHOD InitializeWithDrawTarget(nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) override; NS_IMETHOD GetInputStream(const char* aMimeType, const char16_t* aEncoderOptions, nsIInputStream** aStream) override; already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override { EnsureTarget(); if (aPremultAlpha) { *aPremultAlpha = true; } return mTarget->Snapshot(); } NS_IMETHOD SetIsOpaque(bool aIsOpaque) override; bool GetIsOpaque() override { return mOpaque; } NS_IMETHOD Reset() override; already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder, Layer* aOldLayer, LayerManager* aManager, bool aMirror = false) override; virtual bool ShouldForceInactiveLayer(LayerManager* aManager) override; void MarkContextClean() override; void MarkContextCleanForFrameCapture() override; bool IsContextCleanForFrameCapture() override; NS_IMETHOD SetIsIPC(bool aIsIPC) override; // this rect is in canvas device space void Redraw(const mozilla::gfx::Rect& aR); NS_IMETHOD Redraw(const gfxRect& aR) override { Redraw(ToRect(aR)); return NS_OK; } NS_IMETHOD SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions, ErrorResult& aRvForDictionaryInit) override; /** * An abstract base class to be implemented by callers wanting to be notified * that a refresh has occurred. Callers must ensure an observer is removed * before it is destroyed. */ virtual void DidRefresh() override; // this rect is in mTarget's current user space void RedrawUser(const gfxRect& aR); // nsISupports interface + CC NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(CanvasRenderingContext2D) enum class CanvasMultiGetterType : uint8_t { STRING = 0, PATTERN = 1, GRADIENT = 2 }; enum class Style : uint8_t { STROKE = 0, FILL, MAX }; nsINode* GetParentObject() { return mCanvasElement; } void LineTo(const mozilla::gfx::Point& aPoint) { if (mPathBuilder) { mPathBuilder->LineTo(aPoint); } else { mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(aPoint)); } } void BezierTo(const mozilla::gfx::Point& aCP1, const mozilla::gfx::Point& aCP2, const mozilla::gfx::Point& aCP3) { if (mPathBuilder) { mPathBuilder->BezierTo(aCP1, aCP2, aCP3); } else { mozilla::gfx::Matrix transform = mTarget->GetTransform(); mDSPathBuilder->BezierTo(transform.TransformPoint(aCP1), transform.TransformPoint(aCP2), transform.TransformPoint(aCP3)); } } friend class CanvasRenderingContext2DUserData; virtual UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override; // Given a point, return hit region ID if it exists nsString GetHitRegion(const mozilla::gfx::Point& aPoint) override; // return true and fills in the bound rect if element has a hit region. bool GetHitRegionRect(Element* aElement, nsRect& aRect) override; void OnShutdown(); // Check the global setup, as well as the compositor type: bool AllowOpenGLCanvas() const; protected: nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight, JSObject** aRetval); nsresult PutImageData_explicit(int32_t aX, int32_t aY, uint32_t aW, uint32_t aH, dom::Uint8ClampedArray* aArray, bool aHasDirtyRect, int32_t aDirtyX, int32_t aDirtyY, int32_t aDirtyWidth, int32_t aDirtyHeight); /** * Internal method to complete initialisation, expects mTarget to have been set */ nsresult Initialize(int32_t aWidth, int32_t aHeight); nsresult InitializeWithTarget(mozilla::gfx::DrawTarget* aSurface, int32_t aWidth, int32_t aHeight); /** * The number of living nsCanvasRenderingContexts. When this goes down to * 0, we free the premultiply and unpremultiply tables, if they exist. */ static uintptr_t sNumLivingContexts; static mozilla::gfx::DrawTarget* sErrorTarget; void SetTransformInternal(const mozilla::gfx::Matrix& aTransform); // Some helpers. Doesn't modify a color on failure. void SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& aValue, Style aWhichStyle); void SetStyleFromString(const nsAString& aStr, Style aWhichStyle); void SetStyleFromGradient(CanvasGradient& aGradient, Style aWhichStyle) { CurrentState().SetGradientStyle(aWhichStyle, &aGradient); } void SetStyleFromPattern(CanvasPattern& aPattern, Style aWhichStyle) { CurrentState().SetPatternStyle(aWhichStyle, &aPattern); } void GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue, Style aWhichStyle); // Returns whether a color was successfully parsed. bool ParseColor(const nsAString& aString, nscolor* aColor); static void StyleColorToString(const nscolor& aColor, nsAString& aStr); // Returns whether a filter was successfully parsed. bool ParseFilter(const nsAString& aString, nsTArray<nsStyleFilter>& aFilterChain, ErrorResult& aError); // Returns whether the font was successfully updated. bool SetFontInternal(const nsAString& aFont, mozilla::ErrorResult& aError); /** * Creates the error target, if it doesn't exist */ static void EnsureErrorTarget(); /* This function ensures there is a writable pathbuilder available, this * pathbuilder may be working in user space or in device space or * device space. * After calling this function mPathTransformWillUpdate will be false */ void EnsureWritablePath(); // Ensures a path in UserSpace is available. void EnsureUserSpacePath(const CanvasWindingRule& aWinding = CanvasWindingRule::Nonzero); /** * Needs to be called before updating the transform. This makes a call to * EnsureTarget() so you don't have to. */ void TransformWillUpdate(); // Report the fillRule has changed. void FillRuleChanged(); /** * Create the backing surfacing, if it doesn't exist. If there is an error * in creating the target then it will put sErrorTarget in place. If there * is in turn an error in creating the sErrorTarget then they would both * be null so IsTargetValid() would still return null. * * Returns the actual rendering mode being used by the created target. */ RenderingMode EnsureTarget(const gfx::Rect* aCoveredRect = nullptr, RenderingMode aRenderMode = RenderingMode::DefaultBackendMode); void RestoreClipsAndTransformToTarget(); bool TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT, RefPtr<layers::PersistentBufferProvider>& aOutProvider); bool TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT, RefPtr<layers::PersistentBufferProvider>& aOutProvider); bool TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT, RefPtr<layers::PersistentBufferProvider>& aOutProvider); void RegisterAllocation(); void SetInitialState(); void SetErrorState(); /** * This method is run at the end of the event-loop spin where * ScheduleStableStateCallback was called. * * We use it to unlock resources that need to be locked while drawing. */ void OnStableState(); /** * Cf. OnStableState. */ void ScheduleStableStateCallback(); /** * Disposes an old target and prepares to lazily create a new target. */ void ClearTarget(); /* * Returns the target to the buffer provider. i.e. this will queue a frame for * rendering. */ void ReturnTarget(bool aForceReset = false); /** * Check if the target is valid after calling EnsureTarget. */ bool IsTargetValid() const { return (sErrorTarget == nullptr || mTarget != sErrorTarget) && (mBufferProvider != nullptr || mTarget); } /** * Returns the surface format this canvas should be allocated using. Takes * into account mOpaque, platform requirements, etc. */ mozilla::gfx::SurfaceFormat GetSurfaceFormat() const; /** * Returns true if we know for sure that the pattern for a given style is opaque. * Usefull to know if we can discard the content below in certain situations. */ bool PatternIsOpaque(Style aStyle) const; /** * Update CurrentState().filter with the filter description for * CurrentState().filterChain. * Flushes the PresShell, so the world can change if you call this function. */ void UpdateFilter(); nsLayoutUtils::SurfaceFromElementResult CachedSurfaceFromElement(Element* aElement); void DrawImage(const CanvasImageSource& aImgElt, double aSx, double aSy, double aSw, double aSh, double aDx, double aDy, double aDw, double aDh, uint8_t aOptional_argc, mozilla::ErrorResult& aError); void DrawDirectlyToCanvas(const nsLayoutUtils::DirectDrawInfo& aImage, mozilla::gfx::Rect* aBounds, mozilla::gfx::Rect aDest, mozilla::gfx::Rect aSrc, gfx::IntSize aImgSize); nsString& GetFont() { /* will initilize the value if not set, else does nothing */ GetCurrentFontStyle(); return CurrentState().font; } // This function maintains a list of raw pointers to cycle-collected // objects. We need to ensure that no entries persist beyond unlink, // since the objects are logically destructed at that point. static std::vector<CanvasRenderingContext2D*>& DemotableContexts(); static void DemoteOldestContextIfNecessary(); static void AddDemotableContext(CanvasRenderingContext2D* aContext); static void RemoveDemotableContext(CanvasRenderingContext2D* aContext); RenderingMode mRenderingMode; layers::LayersBackend mCompositorBackend; // Member vars int32_t mWidth, mHeight; // This is true when the canvas is valid, but of zero size, this requires // specific behavior on some operations. bool mZero; bool mOpaque; // This is true when the next time our layer is retrieved we need to // recreate it (i.e. our backing surface changed) bool mResetLayer; // This is needed for drawing in drawAsyncXULElement bool mIPC; // True if the current DrawTarget is using skia-gl, used so we can avoid // requesting the DT from mBufferProvider to check. bool mIsSkiaGL; bool mHasPendingStableStateCallback; nsTArray<CanvasRenderingContext2DUserData*> mUserDatas; // If mCanvasElement is not provided, then a docshell is nsCOMPtr<nsIDocShell> mDocShell; // This is created lazily so it is necessary to call EnsureTarget before // accessing it. In the event of an error it will be equal to // sErrorTarget. RefPtr<mozilla::gfx::DrawTarget> mTarget; RefPtr<mozilla::layers::PersistentBufferProvider> mBufferProvider; uint32_t SkiaGLTex() const; // This observes our draw calls at the beginning of the canvas // lifetime and switches to software or GPU mode depending on // what it thinks is best CanvasDrawObserver* mDrawObserver; void RemoveDrawObserver(); RefPtr<CanvasShutdownObserver> mShutdownObserver; void RemoveShutdownObserver(); bool AlreadyShutDown() const { return !mShutdownObserver; } /** * Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever * Redraw is called, reset to false when Render is called. */ bool mIsEntireFrameInvalid; /** * When this is set, the first call to Redraw(gfxRect) should set * mIsEntireFrameInvalid since we expect it will be followed by * many more Redraw calls. */ bool mPredictManyRedrawCalls; /** * Flag to avoid unnecessary surface copies to FrameCaptureListeners in the * case when the canvas is not currently being drawn into and not rendered * but canvas capturing is still ongoing. */ bool mIsCapturedFrameInvalid; /** * We also have a device space pathbuilder. The reason for this is as * follows, when a path is being built, but the transform changes, we * can no longer keep a single path in userspace, considering there's * several 'user spaces' now. We therefore transform the current path * into device space, and add all operations to this path in device * space. * * When then finally executing a render, the Azure drawing API expects * the path to be in userspace. We could then set an identity transform * on the DrawTarget and do all drawing in device space. This is * undesirable because it requires transforming patterns, gradients, * clips, etc. into device space and it would not work for stroking. * What we do instead is convert the path back to user space when it is * drawn, and draw it with the current transform. This makes all drawing * occur correctly. * * There's never both a device space path builder and a user space path * builder present at the same time. There is also never a path and a * path builder present at the same time. When writing proceeds on an * existing path the Path is cleared and a new builder is created. * * mPath is always in user-space. */ RefPtr<mozilla::gfx::Path> mPath; RefPtr<mozilla::gfx::PathBuilder> mDSPathBuilder; RefPtr<mozilla::gfx::PathBuilder> mPathBuilder; bool mPathTransformWillUpdate; mozilla::gfx::Matrix mPathToDS; /** * Number of times we've invalidated before calling redraw */ uint32_t mInvalidateCount; static const uint32_t kCanvasMaxInvalidateCount = 100; /** * State information for hit regions */ struct RegionInfo { nsString mId; // fallback element for a11y RefPtr<Element> mElement; // Path of the hit region in the 2d context coordinate space (not user space) RefPtr<gfx::Path> mPath; }; nsTArray<RegionInfo> mHitRegionsOptions; /** * Returns true if a shadow should be drawn along with a * drawing operation. */ bool NeedToDrawShadow() { const ContextState& state = CurrentState(); // The spec says we should not draw shadows if the operator is OVER. // If it's over and the alpha value is zero, nothing needs to be drawn. return NS_GET_A(state.shadowColor) != 0 && (state.shadowBlur != 0.f || state.shadowOffset.x != 0.f || state.shadowOffset.y != 0.f); } /** * Returns true if the result of a drawing operation should be * drawn with a filter. */ bool NeedToApplyFilter() { return EnsureUpdatedFilter().mPrimitives.Length() > 0; } /** * Calls UpdateFilter if the canvas's WriteOnly state has changed between the * last call to UpdateFilter and now. */ const gfx::FilterDescription& EnsureUpdatedFilter() { bool isWriteOnly = mCanvasElement && mCanvasElement->IsWriteOnly(); if (CurrentState().filterSourceGraphicTainted != isWriteOnly) { UpdateFilter(); EnsureTarget(); } MOZ_ASSERT(CurrentState().filterSourceGraphicTainted == isWriteOnly); return CurrentState().filter; } bool NeedToCalculateBounds() { return NeedToDrawShadow() || NeedToApplyFilter(); } mozilla::gfx::CompositionOp UsedOperation() { if (NeedToDrawShadow() || NeedToApplyFilter()) { // In this case the shadow or filter rendering will use the operator. return mozilla::gfx::CompositionOp::OP_OVER; } return CurrentState().op; } // text protected: enum class TextAlign : uint8_t { START, END, LEFT, RIGHT, CENTER }; enum class TextBaseline : uint8_t { TOP, HANGING, MIDDLE, ALPHABETIC, IDEOGRAPHIC, BOTTOM }; enum class TextDrawOperation : uint8_t { FILL, STROKE, MEASURE }; protected: gfxFontGroup *GetCurrentFontStyle(); /** * Implementation of the fillText, strokeText, and measure functions with * the operation abstracted to a flag. */ nsresult DrawOrMeasureText(const nsAString& aText, float aX, float aY, const Optional<double>& aMaxWidth, TextDrawOperation aOp, float* aWidth); bool CheckSizeForSkiaGL(mozilla::gfx::IntSize aSize); // A clip or a transform, recorded and restored in order. struct ClipState { explicit ClipState(mozilla::gfx::Path* aClip) : clip(aClip) {} explicit ClipState(const mozilla::gfx::Matrix& aTransform) : transform(aTransform) {} bool IsClip() const { return !!clip; } RefPtr<mozilla::gfx::Path> clip; mozilla::gfx::Matrix transform; }; // state stack handling class ContextState { public: ContextState() : textAlign(TextAlign::START), textBaseline(TextBaseline::ALPHABETIC), shadowColor(0), lineWidth(1.0f), miterLimit(10.0f), globalAlpha(1.0f), shadowBlur(0.0), dashOffset(0.0f), op(mozilla::gfx::CompositionOp::OP_OVER), fillRule(mozilla::gfx::FillRule::FILL_WINDING), lineCap(mozilla::gfx::CapStyle::BUTT), lineJoin(mozilla::gfx::JoinStyle::MITER_OR_BEVEL), filterString(u"none"), filterSourceGraphicTainted(false), imageSmoothingEnabled(true), fontExplicitLanguage(false) { } ContextState(const ContextState& aOther) : fontGroup(aOther.fontGroup), fontLanguage(aOther.fontLanguage), fontFont(aOther.fontFont), gradientStyles(aOther.gradientStyles), patternStyles(aOther.patternStyles), colorStyles(aOther.colorStyles), font(aOther.font), textAlign(aOther.textAlign), textBaseline(aOther.textBaseline), shadowColor(aOther.shadowColor), transform(aOther.transform), shadowOffset(aOther.shadowOffset), lineWidth(aOther.lineWidth), miterLimit(aOther.miterLimit), globalAlpha(aOther.globalAlpha), shadowBlur(aOther.shadowBlur), dash(aOther.dash), dashOffset(aOther.dashOffset), op(aOther.op), fillRule(aOther.fillRule), lineCap(aOther.lineCap), lineJoin(aOther.lineJoin), filterString(aOther.filterString), filterChain(aOther.filterChain), filterChainObserver(aOther.filterChainObserver), filter(aOther.filter), filterAdditionalImages(aOther.filterAdditionalImages), filterSourceGraphicTainted(aOther.filterSourceGraphicTainted), imageSmoothingEnabled(aOther.imageSmoothingEnabled), fontExplicitLanguage(aOther.fontExplicitLanguage) { } void SetColorStyle(Style aWhichStyle, nscolor aColor) { colorStyles[aWhichStyle] = aColor; gradientStyles[aWhichStyle] = nullptr; patternStyles[aWhichStyle] = nullptr; } void SetPatternStyle(Style aWhichStyle, CanvasPattern* aPat) { gradientStyles[aWhichStyle] = nullptr; patternStyles[aWhichStyle] = aPat; } void SetGradientStyle(Style aWhichStyle, CanvasGradient* aGrad) { gradientStyles[aWhichStyle] = aGrad; patternStyles[aWhichStyle] = nullptr; } /** * returns true iff the given style is a solid color. */ bool StyleIsColor(Style aWhichStyle) const { return !(patternStyles[aWhichStyle] || gradientStyles[aWhichStyle]); } int32_t ShadowBlurRadius() const { static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; return (int32_t)floor(ShadowBlurSigma() * GAUSSIAN_SCALE_FACTOR + 0.5); } mozilla::gfx::Float ShadowBlurSigma() const { return std::min(SIGMA_MAX, shadowBlur / 2.0f); } nsTArray<ClipState> clipsAndTransforms; RefPtr<gfxFontGroup> fontGroup; nsCOMPtr<nsIAtom> fontLanguage; nsFont fontFont; EnumeratedArray<Style, Style::MAX, RefPtr<CanvasGradient>> gradientStyles; EnumeratedArray<Style, Style::MAX, RefPtr<CanvasPattern>> patternStyles; EnumeratedArray<Style, Style::MAX, nscolor> colorStyles; nsString font; TextAlign textAlign; TextBaseline textBaseline; nscolor shadowColor; mozilla::gfx::Matrix transform; mozilla::gfx::Point shadowOffset; mozilla::gfx::Float lineWidth; mozilla::gfx::Float miterLimit; mozilla::gfx::Float globalAlpha; mozilla::gfx::Float shadowBlur; nsTArray<mozilla::gfx::Float> dash; mozilla::gfx::Float dashOffset; mozilla::gfx::CompositionOp op; mozilla::gfx::FillRule fillRule; mozilla::gfx::CapStyle lineCap; mozilla::gfx::JoinStyle lineJoin; nsString filterString; nsTArray<nsStyleFilter> filterChain; RefPtr<nsSVGFilterChainObserver> filterChainObserver; mozilla::gfx::FilterDescription filter; nsTArray<RefPtr<mozilla::gfx::SourceSurface>> filterAdditionalImages; // This keeps track of whether the canvas was "tainted" or not when // we last used a filter. This is a security measure, whereby the // canvas is flipped to write-only if a cross-origin image is drawn to it. // This is to stop bad actors from reading back data they shouldn't have // access to. // // This also limits what filters we can apply to the context; in particular // feDisplacementMap is restricted. // // We keep track of this to ensure that if this gets out of sync with the // tainted state of the canvas itself, we update our filters accordingly. bool filterSourceGraphicTainted; bool imageSmoothingEnabled; bool fontExplicitLanguage; }; AutoTArray<ContextState, 3> mStyleStack; inline ContextState& CurrentState() { return mStyleStack[mStyleStack.Length() - 1]; } inline const ContextState& CurrentState() const { return mStyleStack[mStyleStack.Length() - 1]; } friend class CanvasGeneralPattern; friend class CanvasFilterChainObserver; friend class AdjustedTarget; friend class AdjustedTargetForShadow; friend class AdjustedTargetForFilter; // other helpers void GetAppUnitsValues(int32_t* aPerDevPixel, int32_t* aPerCSSPixel) { // If we don't have a canvas element, we just return something generic. int32_t devPixel = 60; int32_t cssPixel = 60; nsIPresShell *ps = GetPresShell(); nsPresContext *pc; if (!ps) goto FINISH; pc = ps->GetPresContext(); if (!pc) goto FINISH; devPixel = pc->AppUnitsPerDevPixel(); cssPixel = pc->AppUnitsPerCSSPixel(); FINISH: if (aPerDevPixel) *aPerDevPixel = devPixel; if (aPerCSSPixel) *aPerCSSPixel = cssPixel; } friend struct CanvasBidiProcessor; friend class CanvasDrawObserver; }; } // namespace dom } // namespace mozilla #endif /* CanvasRenderingContext2D_h */