/* 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 ImageBitmap;
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;

class DOMMatrix;
class DOMMatrixReadOnly;
struct DOMMatrix2DInit;

/**
 ** 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);
  already_AddRefed<DOMMatrix> GetTransform(mozilla::ErrorResult& aError);
  void SetTransform(double aM11, double aM12, double aM21, double aM22, double aDx,
                    double aDy, mozilla::ErrorResult& aError);
  void SetTransform(const DOMMatrix2DInit& aInit, 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.
   *
   * Parameters are the new dimensions to be used, or if either is negative,
   * existing dimensions will be left unchanged.
   */
  void ClearTarget(int32_t aWidth = -1, int32_t aHeight = -1);

  /*
   * 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 = IsWriteOnly() ||
                       (mCanvasElement && mCanvasElement->IsWriteOnly()) ||
                       (mOffscreenCanvas && mOffscreenCanvas->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;
  friend class ImageBitmap;

  // For the origin-clean algorithm (mWriteOnly == !origin-clean)
  // See https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html
  void SetWriteOnly();

  bool IsWriteOnly() const {
    return mWriteOnly;
  }

  bool mWriteOnly;
};

} // namespace dom
} // namespace mozilla

#endif /* CanvasRenderingContext2D_h */
