summaryrefslogtreecommitdiffstats
path: root/dom/canvas/CanvasRenderingContext2D.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/CanvasRenderingContext2D.cpp')
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp6555
1 files changed, 6555 insertions, 0 deletions
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
new file mode 100644
index 000000000..2ed39627e
--- /dev/null
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -0,0 +1,6555 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#include "CanvasRenderingContext2D.h"
+
+#include "mozilla/gfx/Helpers.h"
+#include "nsXULElement.h"
+
+#include "nsAutoPtr.h"
+#include "nsIServiceManager.h"
+#include "nsMathUtils.h"
+#include "SVGImageContext.h"
+
+#include "nsContentUtils.h"
+
+#include "nsIDocument.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "nsSVGEffects.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFrame.h"
+#include "nsError.h"
+
+#include "nsCSSParser.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/css/Declaration.h"
+#include "nsComputedDOMStyle.h"
+#include "nsStyleSet.h"
+
+#include "nsPrintfCString.h"
+
+#include "nsReadableUtils.h"
+
+#include "nsColor.h"
+#include "nsGfxCIID.h"
+#include "nsIDocShell.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsDisplayList.h"
+#include "nsFocusManager.h"
+#include "nsContentUtils.h"
+
+#include "nsTArray.h"
+
+#include "ImageEncoder.h"
+#include "ImageRegion.h"
+
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxPlatform.h"
+#include "gfxFont.h"
+#include "gfxBlur.h"
+#include "gfxPrefs.h"
+#include "gfxUtils.h"
+
+#include "nsFrameLoader.h"
+#include "nsBidi.h"
+#include "nsBidiPresUtils.h"
+#include "Layers.h"
+#include "LayerUserData.h"
+#include "CanvasUtils.h"
+#include "nsIMemoryReporter.h"
+#include "nsStyleUtil.h"
+#include "CanvasImageCache.h"
+
+#include <algorithm>
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Conversions.h"
+
+#include "mozilla/Alignment.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "mozilla/ipc/DocumentRendererParent.h"
+#include "mozilla/ipc/PDocumentRendererParent.h"
+#include "mozilla/layers/PersistentBufferProvider.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
+#include "mozilla/dom/CanvasPath.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/SVGMatrix.h"
+#include "mozilla/dom/TextMetrics.h"
+#include "mozilla/dom/SVGMatrix.h"
+#include "mozilla/FloatingPoint.h"
+#include "nsGlobalWindow.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "SVGContentUtils.h"
+#include "nsIScreenManager.h"
+#include "nsFilterInstance.h"
+#include "nsSVGLength2.h"
+#include "nsDeviceContext.h"
+#include "nsFontMetrics.h"
+#include "Units.h"
+#include "CanvasUtils.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "mozilla/layers/CanvasClient.h"
+
+#undef free // apparently defined by some windows header, clashing with a free()
+ // method in SkTypes.h
+#include "SkiaGLGlue.h"
+#ifdef USE_SKIA
+#include "SurfaceTypes.h"
+#include "GLBlitHelper.h"
+#endif
+
+using mozilla::gl::GLContext;
+using mozilla::gl::SkiaGLGlue;
+using mozilla::gl::GLContextProvider;
+
+#ifdef XP_WIN
+#include "gfxWindowsPlatform.h"
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include "mozilla/layers/ShadowLayers.h"
+#endif
+
+// windows.h (included by chromium code) defines this, in its infinite wisdom
+#undef DrawText
+
+using namespace mozilla;
+using namespace mozilla::CanvasUtils;
+using namespace mozilla::css;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::ipc;
+using namespace mozilla::layers;
+
+namespace mozilla {
+namespace dom {
+
+// Cap sigma to avoid overly large temp surfaces.
+const Float SIGMA_MAX = 100;
+
+const size_t MAX_STYLE_STACK_SIZE = 1024;
+
+/* Memory reporter stuff */
+static int64_t gCanvasAzureMemoryUsed = 0;
+
+// Adds Save() / Restore() calls to the scope.
+class MOZ_RAII AutoSaveRestore
+{
+public:
+ explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mCtx(aCtx)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ mCtx->Save();
+ }
+ ~AutoSaveRestore() { mCtx->Restore(); }
+private:
+ RefPtr<CanvasRenderingContext2D> mCtx;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+// This is KIND_OTHER because it's not always clear where in memory the pixels
+// of a canvas are stored. Furthermore, this memory will be tracked by the
+// underlying surface implementations. See bug 655638 for details.
+class Canvas2dPixelsReporter final : public nsIMemoryReporter
+{
+ ~Canvas2dPixelsReporter() {}
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override
+ {
+ MOZ_COLLECT_REPORT(
+ "canvas-2d-pixels", KIND_OTHER, UNITS_BYTES, gCanvasAzureMemoryUsed,
+ "Memory used by 2D canvases. Each canvas requires "
+ "(width * height * 4) bytes.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
+
+class CanvasRadialGradient : public CanvasGradient
+{
+public:
+ CanvasRadialGradient(CanvasRenderingContext2D* aContext,
+ const Point& aBeginOrigin, Float aBeginRadius,
+ const Point& aEndOrigin, Float aEndRadius)
+ : CanvasGradient(aContext, Type::RADIAL)
+ , mCenter1(aBeginOrigin)
+ , mCenter2(aEndOrigin)
+ , mRadius1(aBeginRadius)
+ , mRadius2(aEndRadius)
+ {
+ }
+
+ Point mCenter1;
+ Point mCenter2;
+ Float mRadius1;
+ Float mRadius2;
+};
+
+class CanvasLinearGradient : public CanvasGradient
+{
+public:
+ CanvasLinearGradient(CanvasRenderingContext2D* aContext,
+ const Point& aBegin, const Point& aEnd)
+ : CanvasGradient(aContext, Type::LINEAR)
+ , mBegin(aBegin)
+ , mEnd(aEnd)
+ {
+ }
+
+protected:
+ friend struct CanvasBidiProcessor;
+ friend class CanvasGeneralPattern;
+
+ // Beginning of linear gradient.
+ Point mBegin;
+ // End of linear gradient.
+ Point mEnd;
+};
+
+bool
+CanvasRenderingContext2D::PatternIsOpaque(CanvasRenderingContext2D::Style aStyle) const
+{
+ const ContextState& state = CurrentState();
+ if (state.globalAlpha < 1.0) {
+ return false;
+ }
+
+ if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) {
+ return IsOpaqueFormat(state.patternStyles[aStyle]->mSurface->GetFormat());
+ }
+
+ // TODO: for gradient patterns we could check that all stops are opaque
+ // colors.
+
+ if (!state.gradientStyles[aStyle]) {
+ // it's a color pattern.
+ return Color::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
+ }
+
+ return false;
+}
+
+// This class is named 'GeneralCanvasPattern' instead of just
+// 'GeneralPattern' to keep Windows PGO builds from confusing the
+// GeneralPattern class in gfxContext.cpp with this one.
+class CanvasGeneralPattern
+{
+public:
+ typedef CanvasRenderingContext2D::Style Style;
+ typedef CanvasRenderingContext2D::ContextState ContextState;
+
+ Pattern& ForStyle(CanvasRenderingContext2D* aCtx,
+ Style aStyle,
+ DrawTarget* aRT)
+ {
+ // This should only be called once or the mPattern destructor will
+ // not be executed.
+ NS_ASSERTION(!mPattern.GetPattern(), "ForStyle() should only be called once on CanvasGeneralPattern!");
+
+ const ContextState& state = aCtx->CurrentState();
+
+ if (state.StyleIsColor(aStyle)) {
+ mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle]));
+ } else if (state.gradientStyles[aStyle] &&
+ state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) {
+ auto gradient =
+ static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get());
+
+ mPattern.InitLinearGradientPattern(gradient->mBegin, gradient->mEnd,
+ gradient->GetGradientStopsForTarget(aRT));
+ } else if (state.gradientStyles[aStyle] &&
+ state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) {
+ auto gradient =
+ static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get());
+
+ mPattern.InitRadialGradientPattern(gradient->mCenter1, gradient->mCenter2,
+ gradient->mRadius1, gradient->mRadius2,
+ gradient->GetGradientStopsForTarget(aRT));
+ } else if (state.patternStyles[aStyle]) {
+ if (aCtx->mCanvasElement) {
+ CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement,
+ state.patternStyles[aStyle]->mPrincipal,
+ state.patternStyles[aStyle]->mForceWriteOnly,
+ state.patternStyles[aStyle]->mCORSUsed);
+ }
+
+ ExtendMode mode;
+ if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) {
+ mode = ExtendMode::CLAMP;
+ } else {
+ mode = ExtendMode::REPEAT;
+ }
+
+ SamplingFilter samplingFilter;
+ if (state.imageSmoothingEnabled) {
+ samplingFilter = SamplingFilter::GOOD;
+ } else {
+ samplingFilter = SamplingFilter::POINT;
+ }
+
+ mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
+ state.patternStyles[aStyle]->mTransform,
+ samplingFilter);
+ }
+
+ return *mPattern.GetPattern();
+ }
+
+ GeneralPattern mPattern;
+};
+
+/* This is an RAII based class that can be used as a drawtarget for
+ * operations that need to have a filter applied to their results.
+ * All coordinates passed to the constructor are in device space.
+ */
+class AdjustedTargetForFilter
+{
+public:
+ typedef CanvasRenderingContext2D::ContextState ContextState;
+
+ AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx,
+ DrawTarget* aFinalTarget,
+ const gfx::IntPoint& aFilterSpaceToTargetOffset,
+ const gfx::IntRect& aPreFilterBounds,
+ const gfx::IntRect& aPostFilterBounds,
+ gfx::CompositionOp aCompositionOp)
+ : mFinalTarget(aFinalTarget)
+ , mCtx(aCtx)
+ , mPostFilterBounds(aPostFilterBounds)
+ , mOffset(aFilterSpaceToTargetOffset)
+ , mCompositionOp(aCompositionOp)
+ {
+ nsIntRegion sourceGraphicNeededRegion;
+ nsIntRegion fillPaintNeededRegion;
+ nsIntRegion strokePaintNeededRegion;
+
+ FilterSupport::ComputeSourceNeededRegions(
+ aCtx->CurrentState().filter, mPostFilterBounds,
+ sourceGraphicNeededRegion, fillPaintNeededRegion,
+ strokePaintNeededRegion);
+
+ mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds();
+ mFillPaintRect = fillPaintNeededRegion.GetBounds();
+ mStrokePaintRect = strokePaintNeededRegion.GetBounds();
+
+ mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds);
+
+ if (mSourceGraphicRect.IsEmpty()) {
+ // The filter might not make any use of the source graphic. We need to
+ // create a DrawTarget that we can return from DT() anyway, so we'll
+ // just use a 1x1-sized one.
+ mSourceGraphicRect.SizeTo(1, 1);
+ }
+
+ mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+
+ if (!mTarget) {
+ // XXX - Deal with the situation where our temp size is too big to
+ // fit in a texture (bug 1066622).
+ mTarget = mFinalTarget;
+ mCtx = nullptr;
+ mFinalTarget = nullptr;
+ return;
+ }
+
+ mTarget->SetTransform(
+ mFinalTarget->GetTransform().PostTranslate(-mSourceGraphicRect.TopLeft() + mOffset));
+ }
+
+ // Return a SourceSurface that contains the FillPaint or StrokePaint source.
+ already_AddRefed<SourceSurface>
+ DoSourcePaint(gfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle)
+ {
+ if (aRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt =
+ mFinalTarget->CreateSimilarDrawTarget(aRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ aRect.SetEmpty();
+ return nullptr;
+ }
+
+ Matrix transform =
+ mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset);
+
+ dt->SetTransform(transform);
+
+ if (transform.Invert()) {
+ gfx::Rect dtBounds(0, 0, aRect.width, aRect.height);
+ gfx::Rect fillRect = transform.TransformBounds(dtBounds);
+ dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt));
+ }
+ return dt->Snapshot();
+ }
+
+ ~AdjustedTargetForFilter()
+ {
+ if (!mCtx) {
+ return;
+ }
+
+ RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
+
+ RefPtr<SourceSurface> fillPaint =
+ DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL);
+ RefPtr<SourceSurface> strokePaint =
+ DoSourcePaint(mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE);
+
+ AutoRestoreTransform autoRestoreTransform(mFinalTarget);
+ mFinalTarget->SetTransform(Matrix());
+
+ MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty());
+ gfx::FilterSupport::RenderFilterDescription(
+ mFinalTarget, mCtx->CurrentState().filter,
+ gfx::Rect(mPostFilterBounds),
+ snapshot, mSourceGraphicRect,
+ fillPaint, mFillPaintRect,
+ strokePaint, mStrokePaintRect,
+ mCtx->CurrentState().filterAdditionalImages,
+ mPostFilterBounds.TopLeft() - mOffset,
+ DrawOptions(1.0f, mCompositionOp));
+
+ const gfx::FilterDescription& filter = mCtx->CurrentState().filter;
+ MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty());
+ if (filter.mPrimitives.LastElement().IsTainted() && mCtx->mCanvasElement) {
+ mCtx->mCanvasElement->SetWriteOnly();
+ }
+ }
+
+ DrawTarget* DT()
+ {
+ return mTarget;
+ }
+
+private:
+ RefPtr<DrawTarget> mTarget;
+ RefPtr<DrawTarget> mFinalTarget;
+ CanvasRenderingContext2D* mCtx;
+ gfx::IntRect mSourceGraphicRect;
+ gfx::IntRect mFillPaintRect;
+ gfx::IntRect mStrokePaintRect;
+ gfx::IntRect mPostFilterBounds;
+ gfx::IntPoint mOffset;
+ gfx::CompositionOp mCompositionOp;
+};
+
+/* This is an RAII based class that can be used as a drawtarget for
+ * operations that need to have a shadow applied to their results.
+ * All coordinates passed to the constructor are in device space.
+ */
+class AdjustedTargetForShadow
+{
+public:
+ typedef CanvasRenderingContext2D::ContextState ContextState;
+
+ AdjustedTargetForShadow(CanvasRenderingContext2D* aCtx,
+ DrawTarget* aFinalTarget,
+ const gfx::Rect& aBounds,
+ gfx::CompositionOp aCompositionOp)
+ : mFinalTarget(aFinalTarget)
+ , mCtx(aCtx)
+ , mCompositionOp(aCompositionOp)
+ {
+ const ContextState& state = mCtx->CurrentState();
+ mSigma = state.ShadowBlurSigma();
+
+ // We actually include the bounds of the shadow blur, this makes it
+ // easier to execute the actual blur on hardware, and shouldn't affect
+ // the amount of pixels that need to be touched.
+ gfx::Rect bounds = aBounds;
+ int32_t blurRadius = state.ShadowBlurRadius();
+ bounds.Inflate(blurRadius);
+ bounds.RoundOut();
+ bounds.ToIntRect(&mTempRect);
+
+ mTarget =
+ mFinalTarget->CreateShadowDrawTarget(mTempRect.Size(),
+ SurfaceFormat::B8G8R8A8, mSigma);
+
+ if (!mTarget) {
+ // XXX - Deal with the situation where our temp size is too big to
+ // fit in a texture (bug 1066622).
+ mTarget = mFinalTarget;
+ mCtx = nullptr;
+ mFinalTarget = nullptr;
+ } else {
+ mTarget->SetTransform(
+ mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft()));
+ }
+ }
+
+ ~AdjustedTargetForShadow()
+ {
+ if (!mCtx) {
+ return;
+ }
+
+ RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
+
+ mFinalTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(),
+ Color::FromABGR(mCtx->CurrentState().shadowColor),
+ mCtx->CurrentState().shadowOffset, mSigma,
+ mCompositionOp);
+ }
+
+ DrawTarget* DT()
+ {
+ return mTarget;
+ }
+
+ gfx::IntPoint OffsetToFinalDT()
+ {
+ return mTempRect.TopLeft();
+ }
+
+private:
+ RefPtr<DrawTarget> mTarget;
+ RefPtr<DrawTarget> mFinalTarget;
+ CanvasRenderingContext2D* mCtx;
+ Float mSigma;
+ gfx::IntRect mTempRect;
+ gfx::CompositionOp mCompositionOp;
+};
+
+/*
+ * This is an RAII based class that can be used as a drawtarget for
+ * operations that need a shadow or a filter drawn. It will automatically
+ * provide a temporary target when needed, and if so blend it back with a
+ * shadow, filter, or both.
+ * If both a shadow and a filter are needed, the filter is applied first,
+ * and the shadow is applied to the filtered results.
+ *
+ * aBounds specifies the bounds of the drawing operation that will be
+ * drawn to the target, it is given in device space! If this is nullptr the
+ * drawing operation will be assumed to cover the whole canvas.
+ */
+class AdjustedTarget
+{
+public:
+ typedef CanvasRenderingContext2D::ContextState ContextState;
+
+ explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
+ const gfx::Rect* aBounds = nullptr)
+ {
+ // All rects in this function are in the device space of ctx->mTarget.
+
+ // In order to keep our temporary surfaces as small as possible, we first
+ // calculate what their maximum required bounds would need to be if we
+ // were to fill the whole canvas. Everything outside those bounds we don't
+ // need to render.
+ gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight);
+ gfx::Rect maxSourceNeededBoundsForShadow =
+ MaxSourceNeededBoundsForShadow(r, aCtx);
+ gfx::Rect maxSourceNeededBoundsForFilter =
+ MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx);
+ if (!aCtx->IsTargetValid()) {
+ return;
+ }
+
+ gfx::Rect bounds = maxSourceNeededBoundsForFilter;
+ if (aBounds) {
+ bounds = bounds.Intersect(*aBounds);
+ }
+ gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
+ if (!aCtx->IsTargetValid()) {
+ return;
+ }
+
+ mozilla::gfx::CompositionOp op = aCtx->CurrentState().op;
+
+ gfx::IntPoint offsetToFinalDT;
+
+ // First set up the shadow draw target, because the shadow goes outside.
+ // It applies to the post-filter results, if both a filter and a shadow
+ // are used.
+ if (aCtx->NeedToDrawShadow()) {
+ mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
+ aCtx, aCtx->mTarget, boundsAfterFilter, op);
+ mTarget = mShadowTarget->DT();
+ offsetToFinalDT = mShadowTarget->OffsetToFinalDT();
+
+ // If we also have a filter, the filter needs to be drawn with OP_OVER
+ // because shadow drawing already applies op on the result.
+ op = gfx::CompositionOp::OP_OVER;
+ }
+
+ // Now set up the filter draw target.
+ const bool applyFilter = aCtx->NeedToApplyFilter();
+ if (!aCtx->IsTargetValid()) {
+ return;
+ }
+ if (applyFilter) {
+ bounds.RoundOut();
+
+ if (!mTarget) {
+ mTarget = aCtx->mTarget;
+ }
+ gfx::IntRect intBounds;
+ if (!bounds.ToIntRect(&intBounds)) {
+ return;
+ }
+ mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
+ aCtx, mTarget, offsetToFinalDT, intBounds,
+ gfx::RoundedToInt(boundsAfterFilter), op);
+ mTarget = mFilterTarget->DT();
+ }
+ if (!mTarget) {
+ mTarget = aCtx->mTarget;
+ }
+ }
+
+ ~AdjustedTarget()
+ {
+ // The order in which the targets are finalized is important.
+ // Filters are inside, any shadow applies to the post-filter results.
+ mFilterTarget.reset();
+ mShadowTarget.reset();
+ }
+
+ operator DrawTarget*()
+ {
+ return mTarget;
+ }
+
+ DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN
+ {
+ return mTarget;
+ }
+
+private:
+
+ gfx::Rect
+ MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds, CanvasRenderingContext2D* aCtx)
+ {
+ const bool applyFilter = aCtx->NeedToApplyFilter();
+ if (!aCtx->IsTargetValid()) {
+ return aDestBounds;
+ }
+ if (!applyFilter) {
+ return aDestBounds;
+ }
+
+ nsIntRegion sourceGraphicNeededRegion;
+ nsIntRegion fillPaintNeededRegion;
+ nsIntRegion strokePaintNeededRegion;
+
+ FilterSupport::ComputeSourceNeededRegions(
+ aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds),
+ sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion);
+
+ return gfx::Rect(sourceGraphicNeededRegion.GetBounds());
+ }
+
+ gfx::Rect
+ MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds, CanvasRenderingContext2D* aCtx)
+ {
+ if (!aCtx->NeedToDrawShadow()) {
+ return aDestBounds;
+ }
+
+ const ContextState& state = aCtx->CurrentState();
+ gfx::Rect sourceBounds = aDestBounds - state.shadowOffset;
+ sourceBounds.Inflate(state.ShadowBlurRadius());
+
+ // Union the shadow source with the original rect because we're going to
+ // draw both.
+ return sourceBounds.Union(aDestBounds);
+ }
+
+ gfx::Rect
+ BoundsAfterFilter(const gfx::Rect& aBounds, CanvasRenderingContext2D* aCtx)
+ {
+ const bool applyFilter = aCtx->NeedToApplyFilter();
+ if (!aCtx->IsTargetValid()) {
+ return aBounds;
+ }
+ if (!applyFilter) {
+ return aBounds;
+ }
+
+ gfx::Rect bounds(aBounds);
+ bounds.RoundOut();
+
+ gfx::IntRect intBounds;
+ if (!bounds.ToIntRect(&intBounds)) {
+ return gfx::Rect();
+ }
+
+ nsIntRegion extents =
+ gfx::FilterSupport::ComputePostFilterExtents(aCtx->CurrentState().filter,
+ intBounds);
+ return gfx::Rect(extents.GetBounds());
+ }
+
+ RefPtr<DrawTarget> mTarget;
+ UniquePtr<AdjustedTargetForShadow> mShadowTarget;
+ UniquePtr<AdjustedTargetForFilter> mFilterTarget;
+};
+
+void
+CanvasPattern::SetTransform(SVGMatrix& aMatrix)
+{
+ mTransform = ToMatrix(aMatrix.GetMatrix());
+}
+
+void
+CanvasGradient::AddColorStop(float aOffset, const nsAString& aColorstr, ErrorResult& aRv)
+{
+ if (aOffset < 0.0 || aOffset > 1.0) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsCSSValue value;
+ nsCSSParser parser;
+ if (!parser.ParseColorString(aColorstr, nullptr, 0, value)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nscolor color;
+ nsCOMPtr<nsIPresShell> presShell = mContext ? mContext->GetPresShell() : nullptr;
+ if (!nsRuleNode::ComputeColor(value, presShell ? presShell->GetPresContext() : nullptr,
+ nullptr, color)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ mStops = nullptr;
+
+ GradientStop newStop;
+
+ newStop.offset = aOffset;
+ newStop.color = Color::FromABGR(color);
+
+ mRawStops.AppendElement(newStop);
+}
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
+
+class CanvasShutdownObserver final : public nsIObserver
+{
+public:
+ explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
+ : mCanvas(aCanvas)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+private:
+ ~CanvasShutdownObserver() {}
+
+ CanvasRenderingContext2D* mCanvas;
+};
+
+NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+CanvasShutdownObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ mCanvas->OnShutdown();
+ nsContentUtils::UnregisterShutdownObserver(this);
+ }
+
+ return NS_OK;
+}
+
+class CanvasDrawObserver
+{
+public:
+ explicit CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext);
+
+ // Only enumerate draw calls that could affect the heuristic
+ enum DrawCallType {
+ PutImageData,
+ GetImageData,
+ DrawImage
+ };
+
+ // This is the one that we call on relevant draw calls and count
+ // GPU vs. CPU preferrable calls...
+ void DidDrawCall(DrawCallType aType);
+
+ // When this returns true, the observer is done making the decisions.
+ // Right now, we expect to get rid of the observer after the FrameEnd
+ // returns true, though the decision could eventually change if the
+ // function calls shift. If we change to monitor the functions called
+ // and make decisions to change more than once, we would probably want
+ // FrameEnd to reset the timer and counters as it returns true.
+ bool FrameEnd();
+
+private:
+ // These values will be picked up from preferences:
+ int32_t mMinFramesBeforeDecision;
+ float mMinSecondsBeforeDecision;
+ int32_t mMinCallsBeforeDecision;
+
+ CanvasRenderingContext2D* mCanvasContext;
+ int32_t mSoftwarePreferredCalls;
+ int32_t mGPUPreferredCalls;
+ int32_t mFramesRendered;
+ TimeStamp mCreationTime;
+};
+
+// We are not checking for the validity of the preference values. For example,
+// negative values will have an effect of a quick exit, so no harm done.
+CanvasDrawObserver::CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext)
+ : mMinFramesBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinFrames())
+ , mMinSecondsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinSeconds())
+ , mMinCallsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinCalls())
+ , mCanvasContext(aCanvasContext)
+ , mSoftwarePreferredCalls(0)
+ , mGPUPreferredCalls(0)
+ , mFramesRendered(0)
+ , mCreationTime(TimeStamp::NowLoRes())
+{}
+
+void
+CanvasDrawObserver::DidDrawCall(DrawCallType aType)
+{
+ switch (aType) {
+ case PutImageData:
+ case GetImageData:
+ if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) {
+ mCreationTime = TimeStamp::NowLoRes();
+ }
+ mSoftwarePreferredCalls++;
+ break;
+ case DrawImage:
+ if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) {
+ mCreationTime = TimeStamp::NowLoRes();
+ }
+ mGPUPreferredCalls++;
+ break;
+ }
+}
+
+// If we return true, the observer is done making the decisions...
+bool
+CanvasDrawObserver::FrameEnd()
+{
+ mFramesRendered++;
+
+ // We log the first mMinFramesBeforeDecision frames of any
+ // canvas object then make a call to determine whether it should
+ // be GPU or CPU backed
+ if ((mFramesRendered >= mMinFramesBeforeDecision) ||
+ ((TimeStamp::NowLoRes() - mCreationTime).ToSeconds()) > mMinSecondsBeforeDecision) {
+
+ // If we don't have enough data, don't bother changing...
+ if (mGPUPreferredCalls > mMinCallsBeforeDecision ||
+ mSoftwarePreferredCalls > mMinCallsBeforeDecision) {
+ CanvasRenderingContext2D::RenderingMode switchToMode;
+ if (mGPUPreferredCalls >= mSoftwarePreferredCalls) {
+ switchToMode = CanvasRenderingContext2D::RenderingMode::OpenGLBackendMode;
+ } else {
+ switchToMode = CanvasRenderingContext2D::RenderingMode::SoftwareBackendMode;
+ }
+ if (switchToMode != mCanvasContext->mRenderingMode) {
+ if (!mCanvasContext->SwitchRenderingMode(switchToMode)) {
+ gfxDebug() << "Canvas acceleration failed mode switch to " << switchToMode;
+ }
+ }
+ }
+
+ // If we ever redesign this class to constantly monitor the functions
+ // and keep making decisions, we would probably want to reset the counters
+ // and the timers here...
+ return true;
+ }
+ return false;
+}
+
+class CanvasRenderingContext2DUserData : public LayerUserData {
+public:
+ explicit CanvasRenderingContext2DUserData(CanvasRenderingContext2D* aContext)
+ : mContext(aContext)
+ {
+ aContext->mUserDatas.AppendElement(this);
+ }
+ ~CanvasRenderingContext2DUserData()
+ {
+ if (mContext) {
+ mContext->mUserDatas.RemoveElement(this);
+ }
+ }
+
+ static void PreTransactionCallback(void* aData)
+ {
+ auto self = static_cast<CanvasRenderingContext2DUserData*>(aData);
+ CanvasRenderingContext2D* context = self->mContext;
+ if (!context || !context->mTarget)
+ return;
+
+ context->OnStableState();
+ }
+
+ static void DidTransactionCallback(void* aData)
+ {
+ auto self = static_cast<CanvasRenderingContext2DUserData*>(aData);
+ if (self->mContext) {
+ self->mContext->MarkContextClean();
+ if (self->mContext->mDrawObserver) {
+ if (self->mContext->mDrawObserver->FrameEnd()) {
+ // Note that this call deletes and nulls out mDrawObserver:
+ self->mContext->RemoveDrawObserver();
+ }
+ }
+ }
+ }
+ bool IsForContext(CanvasRenderingContext2D* aContext)
+ {
+ return mContext == aContext;
+ }
+ void Forget()
+ {
+ mContext = nullptr;
+ }
+
+private:
+ CanvasRenderingContext2D* mContext;
+};
+
+class CanvasFilterChainObserver : public nsSVGFilterChainObserver
+{
+public:
+ CanvasFilterChainObserver(nsTArray<nsStyleFilter>& aFilters,
+ Element* aCanvasElement,
+ CanvasRenderingContext2D* aContext)
+ : nsSVGFilterChainObserver(aFilters, aCanvasElement)
+ , mContext(aContext)
+ {
+ }
+
+ virtual void DoUpdate() override
+ {
+ if (!mContext) {
+ MOZ_CRASH("GFX: This should never be called without a context");
+ }
+ // Refresh the cached FilterDescription in mContext->CurrentState().filter.
+ // If this filter is not at the top of the state stack, we'll refresh the
+ // wrong filter, but that's ok, because we'll refresh the right filter
+ // when we pop the state stack in CanvasRenderingContext2D::Restore().
+ RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext);
+ kungFuDeathGrip->UpdateFilter();
+ }
+
+ void DetachFromContext() { mContext = nullptr; }
+
+private:
+ CanvasRenderingContext2D* mContext;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
+ // Make sure we remove ourselves from the list of demotable contexts (raw pointers),
+ // since we're logically destructed at this point.
+ CanvasRenderingContext2D::RemoveDemotableContext(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
+ for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
+ ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
+ ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
+ ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
+ ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
+ auto filterChainObserver =
+ static_cast<CanvasFilterChainObserver*>(tmp->mStyleStack[i].filterChainObserver.get());
+ if (filterChainObserver) {
+ filterChainObserver->DetachFromContext();
+ }
+ ImplCycleCollectionUnlink(tmp->mStyleStack[i].filterChainObserver);
+ }
+ for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
+ RegionInfo& info = tmp->mHitRegionsOptions[x];
+ if (info.mElement) {
+ ImplCycleCollectionUnlink(info.mElement);
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
+ for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
+ ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern");
+ ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern");
+ ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient");
+ ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient");
+ ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].filterChainObserver, "Filter Chain Observer");
+ }
+ for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
+ RegionInfo& info = tmp->mHitRegionsOptions[x];
+ if (info.mElement) {
+ ImplCycleCollectionTraverse(cb, info.mElement, "Hit region fallback element");
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D)
+ if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) {
+ dom::Element* canvasElement = tmp->mCanvasElement;
+ if (canvasElement) {
+ if (canvasElement->IsPurple()) {
+ canvasElement->RemovePurple();
+ }
+ dom::Element::MarkNodeChildren(canvasElement);
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D)
+ return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D)
+ return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/**
+ ** CanvasRenderingContext2D impl
+ **/
+
+
+// Initialize our static variables.
+uintptr_t CanvasRenderingContext2D::sNumLivingContexts = 0;
+DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
+
+
+
+CanvasRenderingContext2D::CanvasRenderingContext2D(layers::LayersBackend aCompositorBackend)
+ : mRenderingMode(RenderingMode::OpenGLBackendMode)
+ , mCompositorBackend(aCompositorBackend)
+ // these are the default values from the Canvas spec
+ , mWidth(0), mHeight(0)
+ , mZero(false), mOpaque(false)
+ , mResetLayer(true)
+ , mIPC(false)
+ , mIsSkiaGL(false)
+ , mHasPendingStableStateCallback(false)
+ , mDrawObserver(nullptr)
+ , mIsEntireFrameInvalid(false)
+ , mPredictManyRedrawCalls(false)
+ , mIsCapturedFrameInvalid(false)
+ , mPathTransformWillUpdate(false)
+ , mInvalidateCount(0)
+{
+ sNumLivingContexts++;
+
+ mShutdownObserver = new CanvasShutdownObserver(this);
+ nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
+
+ // The default is to use OpenGL mode
+ if (AllowOpenGLCanvas()) {
+ mDrawObserver = new CanvasDrawObserver(this);
+ } else {
+ mRenderingMode = RenderingMode::SoftwareBackendMode;
+ }
+}
+
+CanvasRenderingContext2D::~CanvasRenderingContext2D()
+{
+ RemoveDrawObserver();
+ RemovePostRefreshObserver();
+ RemoveShutdownObserver();
+ Reset();
+ // Drop references from all CanvasRenderingContext2DUserData to this context
+ for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
+ mUserDatas[i]->Forget();
+ }
+ sNumLivingContexts--;
+ if (!sNumLivingContexts) {
+ NS_IF_RELEASE(sErrorTarget);
+ }
+ RemoveDemotableContext(this);
+}
+
+JSObject*
+CanvasRenderingContext2D::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return CanvasRenderingContext2DBinding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+CanvasRenderingContext2D::ParseColor(const nsAString& aString,
+ nscolor* aColor)
+{
+ nsIDocument* document = mCanvasElement
+ ? mCanvasElement->OwnerDoc()
+ : nullptr;
+
+ // Pass the CSS Loader object to the parser, to allow parser error
+ // reports to include the outer window ID.
+ nsCSSParser parser(document ? document->CSSLoader() : nullptr);
+ nsCSSValue value;
+ if (!parser.ParseColorString(aString, nullptr, 0, value)) {
+ return false;
+ }
+
+ if (value.IsNumericColorUnit()) {
+ // if we already have a color we can just use it directly
+ *aColor = value.GetColorValue();
+ } else {
+ // otherwise resolve it
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ RefPtr<nsStyleContext> parentContext;
+ if (mCanvasElement && mCanvasElement->IsInUncomposedDoc()) {
+ // Inherit from the canvas element.
+ parentContext = nsComputedDOMStyle::GetStyleContextForElement(
+ mCanvasElement, nullptr, presShell);
+ }
+
+ Unused << nsRuleNode::ComputeColor(
+ value, presShell ? presShell->GetPresContext() : nullptr, parentContext,
+ *aColor);
+ }
+ return true;
+}
+
+nsresult
+CanvasRenderingContext2D::Reset()
+{
+ if (mCanvasElement) {
+ mCanvasElement->InvalidateCanvas();
+ }
+
+ // only do this for non-docshell created contexts,
+ // since those are the ones that we created a surface for
+ if (mTarget && IsTargetValid() && !mDocShell) {
+ gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
+ }
+
+ bool forceReset = true;
+ ReturnTarget(forceReset);
+ mTarget = nullptr;
+ mBufferProvider = nullptr;
+
+ // reset hit regions
+ mHitRegionsOptions.ClearAndRetainStorage();
+
+ // Since the target changes the backing texture will change, and this will
+ // no longer be valid.
+ mIsEntireFrameInvalid = false;
+ mPredictManyRedrawCalls = false;
+ mIsCapturedFrameInvalid = false;
+
+ return NS_OK;
+}
+
+void
+CanvasRenderingContext2D::OnShutdown()
+{
+ mShutdownObserver = nullptr;
+
+ RefPtr<PersistentBufferProvider> provider = mBufferProvider;
+
+ Reset();
+
+ if (provider) {
+ provider->OnShutdown();
+ }
+}
+
+void
+CanvasRenderingContext2D::RemoveShutdownObserver()
+{
+ if (mShutdownObserver) {
+ nsContentUtils::UnregisterShutdownObserver(mShutdownObserver);
+ mShutdownObserver = nullptr;
+ }
+}
+
+void
+CanvasRenderingContext2D::SetStyleFromString(const nsAString& aStr,
+ Style aWhichStyle)
+{
+ MOZ_ASSERT(!aStr.IsVoid());
+
+ nscolor color;
+ if (!ParseColor(aStr, &color)) {
+ return;
+ }
+
+ CurrentState().SetColorStyle(aWhichStyle, color);
+}
+
+void
+CanvasRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue,
+ Style aWhichStyle)
+{
+ const ContextState& state = CurrentState();
+ if (state.patternStyles[aWhichStyle]) {
+ aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
+ } else if (state.gradientStyles[aWhichStyle]) {
+ aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
+ } else {
+ StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString());
+ }
+}
+
+// static
+void
+CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr)
+{
+ // We can't reuse the normal CSS color stringification code,
+ // because the spec calls for a different algorithm for canvas.
+ if (NS_GET_A(aColor) == 255) {
+ CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x",
+ NS_GET_R(aColor),
+ NS_GET_G(aColor),
+ NS_GET_B(aColor)),
+ aStr);
+ } else {
+ CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ",
+ NS_GET_R(aColor),
+ NS_GET_G(aColor),
+ NS_GET_B(aColor)),
+ aStr);
+ aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
+ aStr.Append(')');
+ }
+}
+
+nsresult
+CanvasRenderingContext2D::Redraw()
+{
+ mIsCapturedFrameInvalid = true;
+
+ if (mIsEntireFrameInvalid) {
+ return NS_OK;
+ }
+
+ mIsEntireFrameInvalid = true;
+
+ if (!mCanvasElement) {
+ NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
+ return NS_OK;
+ }
+
+ nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
+
+ mCanvasElement->InvalidateCanvasContent(nullptr);
+
+ return NS_OK;
+}
+
+void
+CanvasRenderingContext2D::Redraw(const gfx::Rect& aR)
+{
+ mIsCapturedFrameInvalid = true;
+
+ ++mInvalidateCount;
+
+ if (mIsEntireFrameInvalid) {
+ return;
+ }
+
+ if (mPredictManyRedrawCalls ||
+ mInvalidateCount > kCanvasMaxInvalidateCount) {
+ Redraw();
+ return;
+ }
+
+ if (!mCanvasElement) {
+ NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
+ return;
+ }
+
+ nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
+
+ mCanvasElement->InvalidateCanvasContent(&aR);
+}
+
+void
+CanvasRenderingContext2D::DidRefresh()
+{
+ if (IsTargetValid() && mIsSkiaGL) {
+ SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
+ MOZ_ASSERT(glue);
+
+ auto gl = glue->GetGLContext();
+ gl->FlushIfHeavyGLCallsSinceLastFlush();
+ }
+}
+
+void
+CanvasRenderingContext2D::RedrawUser(const gfxRect& aR)
+{
+ mIsCapturedFrameInvalid = true;
+
+ if (mIsEntireFrameInvalid) {
+ ++mInvalidateCount;
+ return;
+ }
+
+ gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
+ Redraw(newr);
+}
+
+bool
+CanvasRenderingContext2D::AllowOpenGLCanvas() const
+{
+ // If we somehow didn't have the correct compositor in the constructor,
+ // we could do something like this to get it:
+ //
+ // HTMLCanvasElement* el = GetCanvas();
+ // if (el) {
+ // mCompositorBackend = el->GetCompositorBackendType();
+ // }
+ //
+ // We could have LAYERS_NONE if there was no widget at the time of
+ // canvas creation, but in that case the
+ // HTMLCanvasElement::GetCompositorBackendType would return LAYERS_NONE
+ // as well, so it wouldn't help much.
+
+ return (mCompositorBackend == LayersBackend::LAYERS_OPENGL) &&
+ gfxPlatform::GetPlatform()->AllowOpenGLCanvas();
+}
+
+bool CanvasRenderingContext2D::SwitchRenderingMode(RenderingMode aRenderingMode)
+{
+ if (!IsTargetValid() || mRenderingMode == aRenderingMode) {
+ return false;
+ }
+
+ MOZ_ASSERT(mBufferProvider);
+
+#ifdef USE_SKIA_GPU
+ // Do not attempt to switch into GL mode if the platform doesn't allow it.
+ if ((aRenderingMode == RenderingMode::OpenGLBackendMode) &&
+ !AllowOpenGLCanvas()) {
+ return false;
+ }
+#endif
+
+ RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
+
+ // Return the old target to the buffer provider.
+ // We need to do this before calling EnsureTarget.
+ ReturnTarget();
+ mTarget = nullptr;
+ mBufferProvider = nullptr;
+ mResetLayer = true;
+
+ // Borrowing the snapshot must be done after ReturnTarget.
+ RefPtr<SourceSurface> snapshot = oldBufferProvider->BorrowSnapshot();
+
+ // Recreate mTarget using the new rendering mode
+ RenderingMode attemptedMode = EnsureTarget(nullptr, aRenderingMode);
+ if (!IsTargetValid()) {
+ oldBufferProvider->ReturnSnapshot(snapshot.forget());
+ return false;
+ }
+
+ // We succeeded, so update mRenderingMode to reflect reality
+ mRenderingMode = attemptedMode;
+
+ // Restore the content from the old DrawTarget
+ // Clips and transform were already restored in EnsureTarget.
+ mTarget->CopySurface(snapshot, IntRect(0, 0, mWidth, mHeight), IntPoint());
+ oldBufferProvider->ReturnSnapshot(snapshot.forget());
+ return true;
+}
+
+void CanvasRenderingContext2D::Demote()
+{
+ if (SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
+ RemoveDemotableContext(this);
+ }
+}
+
+std::vector<CanvasRenderingContext2D*>&
+CanvasRenderingContext2D::DemotableContexts()
+{
+ // This is a list of raw pointers to cycle-collected objects. We need to ensure
+ // that we remove elements from it during UNLINK (which can happen considerably before
+ // the actual destructor) since the object is logically destroyed at that point
+ // and will be in an inconsistant state.
+ static std::vector<CanvasRenderingContext2D*> contexts;
+ return contexts;
+}
+
+void
+CanvasRenderingContext2D::DemoteOldestContextIfNecessary()
+{
+ const size_t kMaxContexts = 64;
+
+ std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts();
+ if (contexts.size() < kMaxContexts)
+ return;
+
+ CanvasRenderingContext2D* oldest = contexts.front();
+ if (oldest->SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
+ RemoveDemotableContext(oldest);
+ }
+}
+
+void
+CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* aContext)
+{
+ std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), aContext);
+ if (iter != DemotableContexts().end())
+ return;
+
+ DemotableContexts().push_back(aContext);
+}
+
+void
+CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* aContext)
+{
+ std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), aContext);
+ if (iter != DemotableContexts().end())
+ DemotableContexts().erase(iter);
+}
+
+bool
+CanvasRenderingContext2D::CheckSizeForSkiaGL(IntSize aSize) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int minsize = Preferences::GetInt("gfx.canvas.min-size-for-skia-gl", 128);
+ if (aSize.width < minsize || aSize.height < minsize) {
+ return false;
+ }
+
+ // Maximum pref allows 3 different options:
+ // 0 means unlimited size
+ // > 0 means use value as an absolute threshold
+ // < 0 means use the number of screen pixels as a threshold
+ int maxsize = Preferences::GetInt("gfx.canvas.max-size-for-skia-gl", 0);
+
+ // unlimited max size
+ if (!maxsize) {
+ return true;
+ }
+
+ // absolute max size threshold
+ if (maxsize > 0) {
+ return aSize.width <= maxsize && aSize.height <= maxsize;
+ }
+
+ // Cache the number of pixels on the primary screen
+ static int32_t gScreenPixels = -1;
+ if (gScreenPixels < 0) {
+ // Default to historical mobile screen size of 980x480, like FishIEtank.
+ // In addition, allow skia use up to this size even if the screen is smaller.
+ // A lot content expects this size to work well.
+ // See Bug 999841
+ if (gfxPlatform::GetPlatform()->HasEnoughTotalSystemMemoryForSkiaGL()) {
+ gScreenPixels = 980 * 480;
+ }
+
+ nsCOMPtr<nsIScreenManager> screenManager =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenManager) {
+ nsCOMPtr<nsIScreen> primaryScreen;
+ screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
+ if (primaryScreen) {
+ int32_t x, y, width, height;
+ primaryScreen->GetRect(&x, &y, &width, &height);
+
+ gScreenPixels = std::max(gScreenPixels, width * height);
+ }
+ }
+ }
+
+ // Just always use a scale of 1.0. It can be changed if a lot of contents need it.
+ static double gDefaultScale = 1.0;
+
+ double scale = gDefaultScale > 0 ? gDefaultScale : 1.0;
+ int32_t threshold = ceil(scale * scale * gScreenPixels);
+
+ // screen size acts as max threshold
+ return threshold < 0 || (aSize.width * aSize.height) <= threshold;
+}
+
+void
+CanvasRenderingContext2D::ScheduleStableStateCallback()
+{
+ if (mHasPendingStableStateCallback) {
+ return;
+ }
+ mHasPendingStableStateCallback = true;
+
+ nsContentUtils::RunInStableState(
+ NewRunnableMethod(this, &CanvasRenderingContext2D::OnStableState)
+ );
+}
+
+void
+CanvasRenderingContext2D::OnStableState()
+{
+ if (!mHasPendingStableStateCallback) {
+ return;
+ }
+
+ ReturnTarget();
+
+ mHasPendingStableStateCallback = false;
+}
+
+void
+CanvasRenderingContext2D::RestoreClipsAndTransformToTarget()
+{
+ // Restore clips and transform.
+ mTarget->SetTransform(Matrix());
+
+ if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
+ // Cairo doesn't play well with huge clips. When given a very big clip it
+ // will try to allocate big mask surface without taking the target
+ // size into account which can cause OOM. See bug 1034593.
+ // This limits the clip extents to the size of the canvas.
+ // A fix in Cairo would probably be preferable, but requires somewhat
+ // invasive changes.
+ mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight));
+ }
+
+ for (const auto& style : mStyleStack) {
+ for (const auto& clipOrTransform : style.clipsAndTransforms) {
+ if (clipOrTransform.IsClip()) {
+ mTarget->PushClip(clipOrTransform.clip);
+ } else {
+ mTarget->SetTransform(clipOrTransform.transform);
+ }
+ }
+ }
+}
+
+CanvasRenderingContext2D::RenderingMode
+CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
+ RenderingMode aRenderingMode)
+{
+ if (AlreadyShutDown()) {
+ gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
+ SetErrorState();
+ return aRenderingMode;
+ }
+
+ // This would make no sense, so make sure we don't get ourselves in a mess
+ MOZ_ASSERT(mRenderingMode != RenderingMode::DefaultBackendMode);
+
+ RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode;
+
+ if (mTarget && mode == mRenderingMode) {
+ return mRenderingMode;
+ }
+
+ // Check that the dimensions are sane
+ if (mWidth > gfxPrefs::MaxCanvasSize() ||
+ mHeight > gfxPrefs::MaxCanvasSize() ||
+ mWidth < 0 || mHeight < 0) {
+
+ SetErrorState();
+ return aRenderingMode;
+ }
+
+ // If the next drawing command covers the entire canvas, we can skip copying
+ // from the previous frame and/or clearing the canvas.
+ gfx::Rect canvasRect(0, 0, mWidth, mHeight);
+ bool canDiscardContent = aCoveredRect &&
+ CurrentState().transform.TransformBounds(*aCoveredRect).Contains(canvasRect);
+
+ // If a clip is active we don't know for sure that the next drawing command
+ // will really cover the entire canvas.
+ for (const auto& style : mStyleStack) {
+ if (!canDiscardContent) {
+ break;
+ }
+ for (const auto& clipOrTransform : style.clipsAndTransforms) {
+ if (clipOrTransform.IsClip()) {
+ canDiscardContent = false;
+ break;
+ }
+ }
+ }
+
+ ScheduleStableStateCallback();
+
+ IntRect persistedRect = canDiscardContent ? IntRect()
+ : IntRect(0, 0, mWidth, mHeight);
+
+ if (mBufferProvider && mode == mRenderingMode) {
+ mTarget = mBufferProvider->BorrowDrawTarget(persistedRect);
+
+ if (mTarget && !mBufferProvider->PreservesDrawingState()) {
+ RestoreClipsAndTransformToTarget();
+ }
+
+ if (mTarget) {
+ return mode;
+ }
+ }
+
+ RefPtr<DrawTarget> newTarget;
+ RefPtr<PersistentBufferProvider> newProvider;
+
+ if (mode == RenderingMode::OpenGLBackendMode &&
+ !TrySkiaGLTarget(newTarget, newProvider)) {
+ // Fall back to software.
+ mode = RenderingMode::SoftwareBackendMode;
+ }
+
+ if (mode == RenderingMode::SoftwareBackendMode &&
+ !TrySharedTarget(newTarget, newProvider) &&
+ !TryBasicTarget(newTarget, newProvider)) {
+ SetErrorState();
+ return mode;
+ }
+
+ MOZ_ASSERT(newTarget);
+ MOZ_ASSERT(newProvider);
+
+ mTarget = newTarget.forget();
+ mBufferProvider = newProvider.forget();
+
+ RegisterAllocation();
+
+ // Skia expects the unused X channel to contains 0 even for opaque operations
+ // so we can't skip clearing in that case, even if we are going to cover the
+ // entire canvas in the next drawing operation.
+ if (!canDiscardContent || mTarget->GetBackendType() == gfx::BackendType::SKIA) {
+ mTarget->ClearRect(canvasRect);
+ }
+
+ RestoreClipsAndTransformToTarget();
+
+ // Force a full layer transaction since we didn't have a layer before
+ // and now we might need one.
+ if (mCanvasElement) {
+ mCanvasElement->InvalidateCanvas();
+ }
+ // Calling Redraw() tells our invalidation machinery that the entire
+ // canvas is already invalid, which can speed up future drawing.
+ Redraw();
+
+ return mode;
+}
+
+void
+CanvasRenderingContext2D::SetInitialState()
+{
+ // Set up the initial canvas defaults
+ mPathBuilder = nullptr;
+ mPath = nullptr;
+ mDSPathBuilder = nullptr;
+ mPathTransformWillUpdate = false;
+
+ mStyleStack.Clear();
+ ContextState* state = mStyleStack.AppendElement();
+ state->globalAlpha = 1.0;
+
+ state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
+ state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
+ state->shadowColor = NS_RGBA(0,0,0,0);
+}
+
+void
+CanvasRenderingContext2D::SetErrorState()
+{
+ EnsureErrorTarget();
+
+ if (mTarget && mTarget != sErrorTarget) {
+ gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
+ }
+
+ mTarget = sErrorTarget;
+ mBufferProvider = nullptr;
+
+ // clear transforms, clips, etc.
+ SetInitialState();
+}
+
+void
+CanvasRenderingContext2D::RegisterAllocation()
+{
+ // XXX - It would make more sense to track the allocation in
+ // PeristentBufferProvider, rather than here.
+ static bool registered = false;
+ // FIXME: Disable the reporter for now, see bug 1241865
+ if (!registered && false) {
+ registered = true;
+ RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
+ }
+
+ gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
+ JSContext* context = nsContentUtils::GetCurrentJSContext();
+ if (context) {
+ JS_updateMallocCounter(context, mWidth * mHeight * 4);
+ }
+}
+
+static already_AddRefed<LayerManager>
+LayerManagerFromCanvasElement(nsINode* aCanvasElement)
+{
+ if (!aCanvasElement || !aCanvasElement->OwnerDoc()) {
+ return nullptr;
+ }
+
+ return nsContentUtils::PersistentLayerManagerForDocument(aCanvasElement->OwnerDoc());
+}
+
+bool
+CanvasRenderingContext2D::TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+ aOutDT = nullptr;
+ aOutProvider = nullptr;
+
+ mIsSkiaGL = false;
+
+ IntSize size(mWidth, mHeight);
+ if (!AllowOpenGLCanvas() || !CheckSizeForSkiaGL(size)) {
+ return false;
+ }
+
+
+ RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
+
+ if (!layerManager) {
+ return false;
+ }
+
+ DemoteOldestContextIfNecessary();
+ mBufferProvider = nullptr;
+
+#ifdef USE_SKIA_GPU
+ SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
+ if (!glue || !glue->GetGrContext() || !glue->GetGLContext()) {
+ return false;
+ }
+
+ SurfaceFormat format = GetSurfaceFormat();
+ aOutDT = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(),
+ size, format);
+ if (!aOutDT) {
+ gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software\n";
+ return false;
+ }
+
+ MOZ_ASSERT(aOutDT->GetType() == DrawTargetType::HARDWARE_RASTER);
+
+ AddDemotableContext(this);
+ aOutProvider = new PersistentBufferProviderBasic(aOutDT);
+ mIsSkiaGL = true;
+ // Drop a note in the debug builds if we ever use accelerated Skia canvas.
+ gfxWarningOnce() << "Using SkiaGL canvas.";
+#endif
+
+ // could still be null if USE_SKIA_GPU is not #defined.
+ return !!aOutDT;
+}
+
+bool
+CanvasRenderingContext2D::TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+ aOutDT = nullptr;
+ aOutProvider = nullptr;
+
+ if (!mCanvasElement || !mCanvasElement->OwnerDoc()) {
+ return false;
+ }
+
+ RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
+
+ if (!layerManager) {
+ return false;
+ }
+
+ aOutProvider = layerManager->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat());
+
+ if (!aOutProvider) {
+ return false;
+ }
+
+ // We can pass an empty persisted rect since we just created the buffer
+ // provider (nothing to restore).
+ aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
+ MOZ_ASSERT(aOutDT);
+
+ return !!aOutDT;
+}
+
+bool
+CanvasRenderingContext2D::TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+ aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(GetSize(),
+ GetSurfaceFormat());
+ if (!aOutDT) {
+ return false;
+ }
+
+ aOutProvider = new PersistentBufferProviderBasic(aOutDT);
+ return true;
+}
+
+int32_t
+CanvasRenderingContext2D::GetWidth() const
+{
+ return mWidth;
+}
+
+int32_t
+CanvasRenderingContext2D::GetHeight() const
+{
+ return mHeight;
+}
+
+NS_IMETHODIMP
+CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight)
+{
+ ClearTarget();
+
+ // Zero sized surfaces can cause problems.
+ mZero = false;
+ if (aHeight == 0) {
+ aHeight = 1;
+ mZero = true;
+ }
+ if (aWidth == 0) {
+ aWidth = 1;
+ mZero = true;
+ }
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ return NS_OK;
+}
+
+void
+CanvasRenderingContext2D::ClearTarget()
+{
+ Reset();
+
+ mResetLayer = true;
+
+ SetInitialState();
+
+ // For vertical writing-mode, unless text-orientation is sideways,
+ // we'll modify the initial value of textBaseline to 'middle'.
+ RefPtr<nsStyleContext> canvasStyle;
+ if (mCanvasElement && mCanvasElement->IsInUncomposedDoc()) {
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ if (presShell) {
+ canvasStyle =
+ nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
+ nullptr,
+ presShell);
+ if (canvasStyle) {
+ WritingMode wm(canvasStyle);
+ if (wm.IsVertical() && !wm.IsSideways()) {
+ CurrentState().textBaseline = TextBaseline::MIDDLE;
+ }
+ }
+ }
+ }
+}
+
+void
+CanvasRenderingContext2D::ReturnTarget(bool aForceReset)
+{
+ if (mTarget && mBufferProvider && mTarget != sErrorTarget) {
+ CurrentState().transform = mTarget->GetTransform();
+ if (aForceReset || !mBufferProvider->PreservesDrawingState()) {
+ for (const auto& style : mStyleStack) {
+ for (const auto& clipOrTransform : style.clipsAndTransforms) {
+ if (clipOrTransform.IsClip()) {
+ mTarget->PopClip();
+ }
+ }
+ }
+
+ if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
+ // With the cairo backend we pushed an extra clip rect which we have to
+ // balance out here. See the comment in RestoreClipsAndTransformToTarget.
+ mTarget->PopClip();
+ }
+
+ mTarget->SetTransform(Matrix());
+ }
+
+ mBufferProvider->ReturnDrawTarget(mTarget.forget());
+ }
+}
+
+NS_IMETHODIMP
+CanvasRenderingContext2D::InitializeWithDrawTarget(nsIDocShell* aShell,
+ NotNull<gfx::DrawTarget*> aTarget)
+{
+ RemovePostRefreshObserver();
+ mDocShell = aShell;
+ AddPostRefreshObserverIfNecessary();
+
+ IntSize size = aTarget->GetSize();
+ SetDimensions(size.width, size.height);
+
+ mTarget = aTarget;
+ mBufferProvider = new PersistentBufferProviderBasic(aTarget);
+
+ if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
+ // Cf comment in EnsureTarget
+ mTarget->PushClipRect(gfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CanvasRenderingContext2D::SetIsOpaque(bool aIsOpaque)
+{
+ if (aIsOpaque != mOpaque) {
+ mOpaque = aIsOpaque;
+ ClearTarget();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CanvasRenderingContext2D::SetIsIPC(bool aIsIPC)
+{
+ if (aIsIPC != mIPC) {
+ mIPC = aIsIPC;
+ ClearTarget();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CanvasRenderingContext2D::SetContextOptions(JSContext* aCx,
+ JS::Handle<JS::Value> aOptions,
+ ErrorResult& aRvForDictionaryInit)
+{
+ if (aOptions.isNullOrUndefined()) {
+ return NS_OK;
+ }
+
+ // This shouldn't be called before drawing starts, so there should be no drawtarget yet
+ MOZ_ASSERT(!mTarget);
+
+ ContextAttributes2D attributes;
+ if (!attributes.Init(aCx, aOptions)) {
+ aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) {
+ // Use software when there is going to be a lot of readback
+ if (attributes.mWillReadFrequently) {
+
+ // We want to lock into software, so remove the observer that
+ // may potentially change that...
+ RemoveDrawObserver();
+ mRenderingMode = RenderingMode::SoftwareBackendMode;
+ }
+ }
+
+ if (!attributes.mAlpha) {
+ SetIsOpaque(true);
+ }
+
+ return NS_OK;
+}
+
+UniquePtr<uint8_t[]>
+CanvasRenderingContext2D::GetImageBuffer(int32_t* aFormat)
+{
+ UniquePtr<uint8_t[]> ret;
+
+ *aFormat = 0;
+
+ RefPtr<SourceSurface> snapshot;
+ if (mTarget) {
+ snapshot = mTarget->Snapshot();
+ } else if (mBufferProvider) {
+ snapshot = mBufferProvider->BorrowSnapshot();
+ } else {
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return nullptr;
+ }
+ snapshot = mTarget->Snapshot();
+ }
+
+ if (snapshot) {
+ RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
+ if (data && data->GetSize() == GetSize()) {
+ *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+ ret = SurfaceToPackedBGRA(data);
+ }
+ }
+
+ if (!mTarget && mBufferProvider) {
+ mBufferProvider->ReturnSnapshot(snapshot.forget());
+ }
+
+ return ret;
+}
+
+nsString CanvasRenderingContext2D::GetHitRegion(const mozilla::gfx::Point& aPoint)
+{
+ for (size_t x = 0 ; x < mHitRegionsOptions.Length(); x++) {
+ RegionInfo& info = mHitRegionsOptions[x];
+ if (info.mPath->ContainsPoint(aPoint, Matrix())) {
+ return info.mId;
+ }
+ }
+ return nsString();
+}
+
+NS_IMETHODIMP
+CanvasRenderingContext2D::GetInputStream(const char* aMimeType,
+ const char16_t* aEncoderOptions,
+ nsIInputStream** aStream)
+{
+ nsCString enccid("@mozilla.org/image/encoder;2?type=");
+ enccid += aMimeType;
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
+ if (!encoder) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t format = 0;
+ UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format);
+ if (!imageBuffer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(),
+ format, encoder, aEncoderOptions,
+ aStream);
+}
+
+SurfaceFormat
+CanvasRenderingContext2D::GetSurfaceFormat() const
+{
+ return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
+}
+
+//
+// state
+//
+
+void
+CanvasRenderingContext2D::Save()
+{
+ EnsureTarget();
+ if (MOZ_UNLIKELY(!mTarget || mStyleStack.IsEmpty())) {
+ SetErrorState();
+ return;
+ }
+ mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
+ mStyleStack.SetCapacity(mStyleStack.Length() + 1);
+ mStyleStack.AppendElement(CurrentState());
+
+ if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) {
+ // This is not fast, but is better than OOMing and shouldn't be hit by
+ // reasonable code.
+ mStyleStack.RemoveElementAt(0);
+ }
+}
+
+void
+CanvasRenderingContext2D::Restore()
+{
+ if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) {
+ return;
+ }
+
+ TransformWillUpdate();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
+ if (clipOrTransform.IsClip()) {
+ mTarget->PopClip();
+ }
+ }
+
+ mStyleStack.RemoveElementAt(mStyleStack.Length() - 1);
+
+ mTarget->SetTransform(CurrentState().transform);
+}
+
+//
+// transformations
+//
+
+void
+CanvasRenderingContext2D::Scale(double aX, double aY, ErrorResult& aError)
+{
+ TransformWillUpdate();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Matrix newMatrix = mTarget->GetTransform();
+ newMatrix.PreScale(aX, aY);
+
+ SetTransformInternal(newMatrix);
+}
+
+void
+CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError)
+{
+ TransformWillUpdate();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform();
+
+ SetTransformInternal(newMatrix);
+}
+
+void
+CanvasRenderingContext2D::Translate(double aX, double aY, ErrorResult& aError)
+{
+ TransformWillUpdate();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Matrix newMatrix = mTarget->GetTransform();
+ newMatrix.PreTranslate(aX, aY);
+
+ SetTransformInternal(newMatrix);
+}
+
+void
+CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
+ double aM22, double aDx, double aDy,
+ ErrorResult& aError)
+{
+ TransformWillUpdate();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
+ newMatrix *= mTarget->GetTransform();
+
+ SetTransformInternal(newMatrix);
+}
+
+void
+CanvasRenderingContext2D::SetTransform(double aM11, double aM12,
+ double aM21, double aM22,
+ double aDx, double aDy,
+ ErrorResult& aError)
+{
+ TransformWillUpdate();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ SetTransformInternal(Matrix(aM11, aM12, aM21, aM22, aDx, aDy));
+}
+
+void
+CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform)
+{
+ if (!aTransform.IsFinite()) {
+ return;
+ }
+
+ // Save the transform in the clip stack to be able to replay clips properly.
+ auto& clipsAndTransforms = CurrentState().clipsAndTransforms;
+ if (clipsAndTransforms.IsEmpty() || clipsAndTransforms.LastElement().IsClip()) {
+ clipsAndTransforms.AppendElement(ClipState(aTransform));
+ } else {
+ // If the last item is a transform we can replace it instead of appending
+ // a new item.
+ clipsAndTransforms.LastElement().transform = aTransform;
+ }
+ mTarget->SetTransform(aTransform);
+}
+
+void
+CanvasRenderingContext2D::ResetTransform(ErrorResult& aError)
+{
+ SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError);
+}
+
+static void
+MatrixToJSObject(JSContext* aCx, const Matrix& aMatrix,
+ JS::MutableHandle<JSObject*> aResult, ErrorResult& aError)
+{
+ double elts[6] = { aMatrix._11, aMatrix._12,
+ aMatrix._21, aMatrix._22,
+ aMatrix._31, aMatrix._32 };
+
+ // XXX Should we enter GetWrapper()'s compartment?
+ JS::Rooted<JS::Value> val(aCx);
+ if (!ToJSValue(aCx, elts, &val)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ aResult.set(&val.toObject());
+ }
+}
+
+static bool
+ObjectToMatrix(JSContext* aCx, JS::Handle<JSObject*> aObj, Matrix& aMatrix,
+ ErrorResult& aError)
+{
+ uint32_t length;
+ if (!JS_GetArrayLength(aCx, aObj, &length) || length != 6) {
+ // Not an array-like thing or wrong size
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ Float* elts[] = { &aMatrix._11, &aMatrix._12, &aMatrix._21, &aMatrix._22,
+ &aMatrix._31, &aMatrix._32 };
+ for (uint32_t i = 0; i < 6; ++i) {
+ JS::Rooted<JS::Value> elt(aCx);
+ double d;
+ if (!JS_GetElement(aCx, aObj, i, &elt)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+ if (!CoerceDouble(elt, &d)) {
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+ if (!FloatValidate(d)) {
+ // This is weird, but it's the behavior of SetTransform()
+ return false;
+ }
+ *elts[i] = Float(d);
+ }
+ return true;
+}
+
+void
+CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* aCx,
+ JS::Handle<JSObject*> aCurrentTransform,
+ ErrorResult& aError)
+{
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Matrix newCTM;
+ if (ObjectToMatrix(aCx, aCurrentTransform, newCTM, aError) && newCTM.IsFinite()) {
+ mTarget->SetTransform(newCTM);
+ }
+}
+
+void
+CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aError)
+{
+ EnsureTarget();
+
+ MatrixToJSObject(aCx, mTarget ? mTarget->GetTransform() : Matrix(),
+ aResult, aError);
+}
+
+void
+CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* aCx,
+ JS::Handle<JSObject*> aCurrentTransform,
+ ErrorResult& aError)
+{
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Matrix newCTMInverse;
+ if (ObjectToMatrix(aCx, aCurrentTransform, newCTMInverse, aError)) {
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ if (newCTMInverse.Invert() && newCTMInverse.IsFinite()) {
+ mTarget->SetTransform(newCTMInverse);
+ }
+ }
+}
+
+void
+CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aError)
+{
+ EnsureTarget();
+
+ if (!mTarget) {
+ MatrixToJSObject(aCx, Matrix(), aResult, aError);
+ return;
+ }
+
+ Matrix ctm = mTarget->GetTransform();
+
+ if (!ctm.Invert()) {
+ double NaN = JS_GetNaNValue(aCx).toDouble();
+ ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN);
+ }
+
+ MatrixToJSObject(aCx, ctm, aResult, aError);
+}
+
+//
+// colors
+//
+
+void
+CanvasRenderingContext2D::SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& aValue,
+ Style aWhichStyle)
+{
+ if (aValue.IsString()) {
+ SetStyleFromString(aValue.GetAsString(), aWhichStyle);
+ return;
+ }
+
+ if (aValue.IsCanvasGradient()) {
+ SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle);
+ return;
+ }
+
+ if (aValue.IsCanvasPattern()) {
+ SetStyleFromPattern(aValue.GetAsCanvasPattern(), aWhichStyle);
+ return;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Invalid union value");
+}
+
+void
+CanvasRenderingContext2D::SetFillRule(const nsAString& aString)
+{
+ FillRule rule;
+
+ if (aString.EqualsLiteral("evenodd"))
+ rule = FillRule::FILL_EVEN_ODD;
+ else if (aString.EqualsLiteral("nonzero"))
+ rule = FillRule::FILL_WINDING;
+ else
+ return;
+
+ CurrentState().fillRule = rule;
+}
+
+void
+CanvasRenderingContext2D::GetFillRule(nsAString& aString)
+{
+ switch (CurrentState().fillRule) {
+ case FillRule::FILL_WINDING:
+ aString.AssignLiteral("nonzero"); break;
+ case FillRule::FILL_EVEN_ODD:
+ aString.AssignLiteral("evenodd"); break;
+ }
+}
+//
+// gradients and patterns
+//
+already_AddRefed<CanvasGradient>
+CanvasRenderingContext2D::CreateLinearGradient(double aX0, double aY0, double aX1, double aY1)
+{
+ RefPtr<CanvasGradient> grad =
+ new CanvasLinearGradient(this, Point(aX0, aY0), Point(aX1, aY1));
+
+ return grad.forget();
+}
+
+already_AddRefed<CanvasGradient>
+CanvasRenderingContext2D::CreateRadialGradient(double aX0, double aY0, double aR0,
+ double aX1, double aY1, double aR1,
+ ErrorResult& aError)
+{
+ if (aR0 < 0.0 || aR1 < 0.0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<CanvasGradient> grad =
+ new CanvasRadialGradient(this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1);
+
+ return grad.forget();
+}
+
+already_AddRefed<CanvasPattern>
+CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource,
+ const nsAString& aRepeat,
+ ErrorResult& aError)
+{
+ CanvasPattern::RepeatMode repeatMode =
+ CanvasPattern::RepeatMode::NOREPEAT;
+
+ if (aRepeat.IsEmpty() || aRepeat.EqualsLiteral("repeat")) {
+ repeatMode = CanvasPattern::RepeatMode::REPEAT;
+ } else if (aRepeat.EqualsLiteral("repeat-x")) {
+ repeatMode = CanvasPattern::RepeatMode::REPEATX;
+ } else if (aRepeat.EqualsLiteral("repeat-y")) {
+ repeatMode = CanvasPattern::RepeatMode::REPEATY;
+ } else if (aRepeat.EqualsLiteral("no-repeat")) {
+ repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
+ } else {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ Element* htmlElement;
+ if (aSource.IsHTMLCanvasElement()) {
+ HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement();
+ htmlElement = canvas;
+
+ nsIntSize size = canvas->GetSize();
+ if (size.width == 0 || size.height == 0) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Special case for Canvas, which could be an Azure canvas!
+ nsICanvasRenderingContextInternal* srcCanvas = canvas->GetContextAtIndex(0);
+ if (srcCanvas) {
+ // This might not be an Azure canvas!
+ RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
+ if (!srcSurf) {
+ JSContext* context = nsContentUtils::GetCurrentJSContext();
+ if (context) {
+ JS_ReportWarningASCII(context,
+ "CanvasRenderingContext2D.createPattern()"
+ " failed to snapshot source canvas.");
+ }
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<CanvasPattern> pat =
+ new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false);
+
+ return pat.forget();
+ }
+ } else if (aSource.IsHTMLImageElement()) {
+ HTMLImageElement* img = &aSource.GetAsHTMLImageElement();
+ if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ htmlElement = img;
+ } else if (aSource.IsHTMLVideoElement()) {
+ auto& video = aSource.GetAsHTMLVideoElement();
+ video.MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
+ htmlElement = &video;
+ } else {
+ // Special case for ImageBitmap
+ ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
+ if (!srcSurf) {
+ JSContext* context = nsContentUtils::GetCurrentJSContext();
+ if (context) {
+ JS_ReportWarningASCII(context,
+ "CanvasRenderingContext2D.createPattern()"
+ " failed to prepare source ImageBitmap.");
+ }
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // An ImageBitmap never taints others so we set principalForSecurityCheck to
+ // nullptr and set CORSUsed to true for passing the security check in
+ // CanvasUtils::DoDrawImageSecurityCheck().
+ RefPtr<CanvasPattern> pat =
+ new CanvasPattern(this, srcSurf, repeatMode, nullptr, false, true);
+
+ return pat.forget();
+ }
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // The canvas spec says that createPattern should use the first frame
+ // of animated images
+ nsLayoutUtils::SurfaceFromElementResult res =
+ nsLayoutUtils::SurfaceFromElement(htmlElement,
+ nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
+
+ if (!res.GetSourceSurface()) {
+ return nullptr;
+ }
+
+ RefPtr<CanvasPattern> pat = new CanvasPattern(this, res.GetSourceSurface(), repeatMode,
+ res.mPrincipal, res.mIsWriteOnly,
+ res.mCORSUsed);
+ return pat.forget();
+}
+
+//
+// shadows
+//
+void
+CanvasRenderingContext2D::SetShadowColor(const nsAString& aShadowColor)
+{
+ nscolor color;
+ if (!ParseColor(aShadowColor, &color)) {
+ return;
+ }
+
+ CurrentState().shadowColor = color;
+}
+
+//
+// filters
+//
+
+static already_AddRefed<Declaration>
+CreateDeclaration(nsINode* aNode,
+ const nsCSSPropertyID aProp1, const nsAString& aValue1, bool* aChanged1,
+ const nsCSSPropertyID aProp2, const nsAString& aValue2, bool* aChanged2)
+{
+ nsIPrincipal* principal = aNode->NodePrincipal();
+ nsIDocument* document = aNode->OwnerDoc();
+
+ nsIURI* docURL = document->GetDocumentURI();
+ nsIURI* baseURL = document->GetDocBaseURI();
+
+ // Pass the CSS Loader object to the parser, to allow parser error reports
+ // to include the outer window ID.
+ nsCSSParser parser(document->CSSLoader());
+
+ RefPtr<Declaration> declaration =
+ parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, principal);
+
+ if (aProp1 != eCSSProperty_UNKNOWN) {
+ parser.ParseProperty(aProp1, aValue1, docURL, baseURL, principal,
+ declaration, aChanged1, false);
+ }
+
+ if (aProp2 != eCSSProperty_UNKNOWN) {
+ parser.ParseProperty(aProp2, aValue2, docURL, baseURL, principal,
+ declaration, aChanged2, false);
+ }
+
+ declaration->SetImmutable();
+ return declaration.forget();
+}
+
+static already_AddRefed<Declaration>
+CreateFontDeclaration(const nsAString& aFont,
+ nsINode* aNode,
+ bool* aOutFontChanged)
+{
+ bool lineHeightChanged;
+ return CreateDeclaration(aNode,
+ eCSSProperty_font, aFont, aOutFontChanged,
+ eCSSProperty_line_height, NS_LITERAL_STRING("normal"), &lineHeightChanged);
+}
+
+static already_AddRefed<nsStyleContext>
+GetFontParentStyleContext(Element* aElement, nsIPresShell* aPresShell,
+ ErrorResult& aError)
+{
+ if (aElement && aElement->IsInUncomposedDoc()) {
+ // Inherit from the canvas element.
+ RefPtr<nsStyleContext> result =
+ nsComputedDOMStyle::GetStyleContextForElement(aElement, nullptr,
+ aPresShell);
+ if (!result) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ return result.forget();
+ }
+
+ // otherwise inherit from default (10px sans-serif)
+
+ nsStyleSet* styleSet = aPresShell->StyleSet()->GetAsGecko();
+ if (!styleSet) {
+ // XXXheycam ServoStyleSets do not support resolving style from a list of
+ // rules yet.
+ NS_ERROR("stylo: cannot resolve style for canvas from a ServoStyleSet yet");
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ bool changed;
+ RefPtr<css::Declaration> parentRule =
+ CreateFontDeclaration(NS_LITERAL_STRING("10px sans-serif"),
+ aPresShell->GetDocument(), &changed);
+
+ nsTArray<nsCOMPtr<nsIStyleRule>> parentRules;
+ parentRules.AppendElement(parentRule);
+ RefPtr<nsStyleContext> result =
+ styleSet->ResolveStyleForRules(nullptr, parentRules);
+
+ if (!result) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ return result.forget();
+}
+
+static bool
+PropertyIsInheritOrInitial(Declaration* aDeclaration, const nsCSSPropertyID aProperty)
+{
+ // We know the declaration is not !important, so we can use
+ // GetNormalBlock().
+ const nsCSSValue* filterVal =
+ aDeclaration->GetNormalBlock()->ValueFor(aProperty);
+ return (!filterVal || (filterVal->GetUnit() == eCSSUnit_Unset ||
+ filterVal->GetUnit() == eCSSUnit_Inherit ||
+ filterVal->GetUnit() == eCSSUnit_Initial));
+}
+
+static already_AddRefed<nsStyleContext>
+GetFontStyleContext(Element* aElement, const nsAString& aFont,
+ nsIPresShell* aPresShell,
+ nsAString& aOutUsedFont,
+ ErrorResult& aError)
+{
+ nsStyleSet* styleSet = aPresShell->StyleSet()->GetAsGecko();
+ if (!styleSet) {
+ // XXXheycam ServoStyleSets do not support resolving style from a list of
+ // rules yet.
+ NS_ERROR("stylo: cannot resolve style for canvas from a ServoStyleSet yet");
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ bool fontParsedSuccessfully = false;
+ RefPtr<css::Declaration> decl =
+ CreateFontDeclaration(aFont, aPresShell->GetDocument(),
+ &fontParsedSuccessfully);
+
+ if (!fontParsedSuccessfully) {
+ // We got a syntax error. The spec says this value must be ignored.
+ return nullptr;
+ }
+
+ // In addition to unparseable values, the spec says we need to reject
+ // 'inherit' and 'initial'. The easiest way to check for this is to look
+ // at font-size-adjust, which the font shorthand resets to either 'none' or
+ // '-moz-system-font'.
+ if (PropertyIsInheritOrInitial(decl, eCSSProperty_font_size_adjust)) {
+ return nullptr;
+ }
+
+ // have to get a parent style context for inherit-like relative
+ // values (2em, bolder, etc.)
+ RefPtr<nsStyleContext> parentContext =
+ GetFontParentStyleContext(aElement, aPresShell, aError);
+
+ if (aError.Failed()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(parentContext,
+ "GFX: GetFontParentStyleContext should have returned an error if it couldn't get a parent context.");
+
+ MOZ_ASSERT(!aPresShell->IsDestroying(),
+ "GetFontParentStyleContext should have returned an error if the presshell is being destroyed.");
+
+ nsTArray<nsCOMPtr<nsIStyleRule>> rules;
+ rules.AppendElement(decl);
+ // add a rule to prevent text zoom from affecting the style
+ rules.AppendElement(new nsDisableTextZoomStyleRule);
+
+ RefPtr<nsStyleContext> sc =
+ styleSet->ResolveStyleForRules(parentContext, rules);
+
+ // The font getter is required to be reserialized based on what we
+ // parsed (including having line-height removed). (Older drafts of
+ // the spec required font sizes be converted to pixels, but that no
+ // longer seems to be required.)
+ decl->GetPropertyValueByID(eCSSProperty_font, aOutUsedFont);
+
+ return sc.forget();
+}
+
+static already_AddRefed<Declaration>
+CreateFilterDeclaration(const nsAString& aFilter,
+ nsINode* aNode,
+ bool* aOutFilterChanged)
+{
+ bool dummy;
+ return CreateDeclaration(aNode,
+ eCSSProperty_filter, aFilter, aOutFilterChanged,
+ eCSSProperty_UNKNOWN, EmptyString(), &dummy);
+}
+
+static already_AddRefed<nsStyleContext>
+ResolveStyleForFilter(const nsAString& aFilterString,
+ nsIPresShell* aPresShell,
+ nsStyleContext* aParentContext,
+ ErrorResult& aError)
+{
+ nsStyleSet* styleSet = aPresShell->StyleSet()->GetAsGecko();
+ if (!styleSet) {
+ // XXXheycam ServoStyleSets do not support resolving style from a list of
+ // rules yet.
+ NS_ERROR("stylo: cannot resolve style for canvas from a ServoStyleSet yet");
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsIDocument* document = aPresShell->GetDocument();
+ bool filterChanged = false;
+ RefPtr<css::Declaration> decl =
+ CreateFilterDeclaration(aFilterString, document, &filterChanged);
+
+ if (!filterChanged) {
+ // Refuse to accept the filter, but do not throw an error.
+ return nullptr;
+ }
+
+ // In addition to unparseable values, the spec says we need to reject
+ // 'inherit' and 'initial'.
+ if (PropertyIsInheritOrInitial(decl, eCSSProperty_filter)) {
+ return nullptr;
+ }
+
+ nsTArray<nsCOMPtr<nsIStyleRule>> rules;
+ rules.AppendElement(decl);
+
+ RefPtr<nsStyleContext> sc =
+ styleSet->ResolveStyleForRules(aParentContext, rules);
+
+ return sc.forget();
+}
+
+bool
+CanvasRenderingContext2D::ParseFilter(const nsAString& aString,
+ nsTArray<nsStyleFilter>& aFilterChain,
+ ErrorResult& aError)
+{
+ if (!mCanvasElement && !mDocShell) {
+ NS_WARNING("Canvas element must be non-null or a docshell must be provided");
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ if (!presShell) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsString usedFont;
+ RefPtr<nsStyleContext> parentContext =
+ GetFontStyleContext(mCanvasElement, GetFont(),
+ presShell, usedFont, aError);
+ if (!parentContext) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ RefPtr<nsStyleContext> sc =
+ ResolveStyleForFilter(aString, presShell, parentContext, aError);
+
+ if (!sc) {
+ return false;
+ }
+
+ aFilterChain = sc->StyleEffects()->mFilters;
+ return true;
+}
+
+void
+CanvasRenderingContext2D::SetFilter(const nsAString& aFilter, ErrorResult& aError)
+{
+ nsTArray<nsStyleFilter> filterChain;
+ if (ParseFilter(aFilter, filterChain, aError)) {
+ CurrentState().filterString = aFilter;
+ filterChain.SwapElements(CurrentState().filterChain);
+ if (mCanvasElement) {
+ CurrentState().filterChainObserver =
+ new CanvasFilterChainObserver(CurrentState().filterChain,
+ mCanvasElement, this);
+ UpdateFilter();
+ }
+ }
+}
+
+class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize
+{
+public:
+ CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont,
+ nsIAtom* aFontLanguage, bool aExplicitLanguage,
+ nsPresContext* aPresContext)
+ : mSize(aSize)
+ , mFont(aFont)
+ , mFontLanguage(aFontLanguage)
+ , mExplicitLanguage(aExplicitLanguage)
+ , mPresContext(aPresContext)
+ {
+ }
+
+ virtual float GetEmLength() const override
+ {
+ return NSAppUnitsToFloatPixels(mFont.size,
+ nsPresContext::AppUnitsPerCSSPixel());
+ }
+
+ virtual float GetExLength() const override
+ {
+ nsDeviceContext* dc = mPresContext->DeviceContext();
+ nsFontMetrics::Params params;
+ params.language = mFontLanguage;
+ params.explicitLanguage = mExplicitLanguage;
+ params.textPerf = mPresContext->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> fontMetrics = dc->GetMetricsFor(mFont, params);
+ return NSAppUnitsToFloatPixels(fontMetrics->XHeight(),
+ nsPresContext::AppUnitsPerCSSPixel());
+ }
+
+ virtual gfx::Size GetSize() const override
+ { return Size(mSize); }
+
+private:
+ gfx::IntSize mSize;
+ const nsFont& mFont;
+ nsIAtom* mFontLanguage;
+ bool mExplicitLanguage;
+ nsPresContext* mPresContext;
+};
+
+void
+CanvasRenderingContext2D::UpdateFilter()
+{
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ if (!presShell || presShell->IsDestroying()) {
+ // Ensure we set an empty filter and update the state to
+ // reflect the current "taint" status of the canvas
+ CurrentState().filter = FilterDescription();
+ CurrentState().filterSourceGraphicTainted =
+ (mCanvasElement && mCanvasElement->IsWriteOnly());
+ return;
+ }
+
+ // The filter might reference an SVG filter that is declared inside this
+ // document. Flush frames so that we'll have an nsSVGFilterFrame to work
+ // with.
+ presShell->FlushPendingNotifications(Flush_Frames);
+ MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty());
+ if (MOZ_UNLIKELY(presShell->IsDestroying())) {
+ return;
+ }
+
+ bool sourceGraphicIsTainted =
+ (mCanvasElement && mCanvasElement->IsWriteOnly());
+
+ CurrentState().filter =
+ nsFilterInstance::GetFilterDescription(mCanvasElement,
+ CurrentState().filterChain,
+ sourceGraphicIsTainted,
+ CanvasUserSpaceMetrics(GetSize(),
+ CurrentState().fontFont,
+ CurrentState().fontLanguage,
+ CurrentState().fontExplicitLanguage,
+ presShell->GetPresContext()),
+ gfxRect(0, 0, mWidth, mHeight),
+ CurrentState().filterAdditionalImages);
+ CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted;
+}
+
+//
+// rects
+//
+
+static bool
+ValidateRect(double& aX, double& aY, double& aWidth, double& aHeight, bool aIsZeroSizeValid)
+{
+ if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) {
+ return false;
+ }
+
+ // bug 1018527
+ // The values of canvas API input are in double precision, but Moz2D APIs are
+ // using float precision. Bypass canvas API calls when the input is out of
+ // float precision to avoid precision problem
+ if (!std::isfinite((float)aX) | !std::isfinite((float)aY) |
+ !std::isfinite((float)aWidth) | !std::isfinite((float)aHeight)) {
+ return false;
+ }
+
+ // bug 1074733
+ // The canvas spec does not forbid rects with negative w or h, so given
+ // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate
+ // the appropriate rect by flipping negative dimensions. This prevents
+ // draw targets from receiving "empty" rects later on.
+ if (aWidth < 0) {
+ aWidth = -aWidth;
+ aX -= aWidth;
+ }
+ if (aHeight < 0) {
+ aHeight = -aHeight;
+ aY -= aHeight;
+ }
+ return true;
+}
+
+void
+CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW,
+ double aH)
+{
+ // Do not allow zeros - it's a no-op at that point per spec.
+ if (!ValidateRect(aX, aY, aW, aH, false)) {
+ return;
+ }
+
+ gfx::Rect clearRect(aX, aY, aW, aH);
+
+ EnsureTarget(&clearRect);
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ mTarget->ClearRect(clearRect);
+
+ RedrawUser(gfxRect(aX, aY, aW, aH));
+}
+
+void
+CanvasRenderingContext2D::FillRect(double aX, double aY, double aW, double aH)
+{
+ if (!ValidateRect(aX, aY, aW, aH, true)) {
+ return;
+ }
+
+ const ContextState* state = &CurrentState();
+ if (state->patternStyles[Style::FILL]) {
+ CanvasPattern::RepeatMode repeat =
+ state->patternStyles[Style::FILL]->mRepeat;
+ // In the FillRect case repeat modes are easy to deal with.
+ bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
+ repeat == CanvasPattern::RepeatMode::REPEATY;
+ bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
+ repeat == CanvasPattern::RepeatMode::REPEATX;
+
+ IntSize patternSize =
+ state->patternStyles[Style::FILL]->mSurface->GetSize();
+
+ // We always need to execute painting for non-over operators, even if
+ // we end up with w/h = 0.
+ if (limitx) {
+ if (aX < 0) {
+ aW += aX;
+ if (aW < 0) {
+ aW = 0;
+ }
+
+ aX = 0;
+ }
+ if (aX + aW > patternSize.width) {
+ aW = patternSize.width - aX;
+ if (aW < 0) {
+ aW = 0;
+ }
+ }
+ }
+ if (limity) {
+ if (aY < 0) {
+ aH += aY;
+ if (aH < 0) {
+ aH = 0;
+ }
+
+ aY = 0;
+ }
+ if (aY + aH > patternSize.height) {
+ aH = patternSize.height - aY;
+ if (aH < 0) {
+ aH = 0;
+ }
+ }
+ }
+ }
+ state = nullptr;
+
+ CompositionOp op = UsedOperation();
+ bool discardContent = PatternIsOpaque(Style::FILL)
+ && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
+ const gfx::Rect fillRect(aX, aY, aW, aH);
+ EnsureTarget(discardContent ? &fillRect : nullptr);
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ gfx::Rect bounds;
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+ if (needBounds) {
+ bounds = mTarget->GetTransform().TransformBounds(fillRect);
+ }
+
+ AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled ?
+ AntialiasMode::DEFAULT : AntialiasMode::NONE;
+
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+ target->FillRect(gfx::Rect(aX, aY, aW, aH),
+ CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
+ DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
+
+ RedrawUser(gfxRect(aX, aY, aW, aH));
+}
+
+void
+CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW, double aH)
+{
+ if (!aW && !aH) {
+ return;
+ }
+
+ if (!ValidateRect(aX, aY, aW, aH, true)) {
+ return;
+ }
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ gfx::Rect bounds;
+ if (needBounds) {
+ const ContextState& state = CurrentState();
+ bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f,
+ aW + state.lineWidth, aH + state.lineWidth);
+ bounds = mTarget->GetTransform().TransformBounds(bounds);
+ }
+
+ auto op = UsedOperation();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ if (!aH) {
+ CapStyle cap = CapStyle::BUTT;
+ if (CurrentState().lineJoin == JoinStyle::ROUND) {
+ cap = CapStyle::ROUND;
+ }
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ const ContextState& state = CurrentState();
+ target->
+ StrokeLine(Point(aX, aY), Point(aX + aW, aY),
+ CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
+ StrokeOptions(state.lineWidth, state.lineJoin,
+ cap, state.miterLimit,
+ state.dash.Length(),
+ state.dash.Elements(),
+ state.dashOffset),
+ DrawOptions(state.globalAlpha, op));
+ return;
+ }
+
+ if (!aW) {
+ CapStyle cap = CapStyle::BUTT;
+ if (CurrentState().lineJoin == JoinStyle::ROUND) {
+ cap = CapStyle::ROUND;
+ }
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ const ContextState& state = CurrentState();
+ target->
+ StrokeLine(Point(aX, aY), Point(aX, aY + aH),
+ CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
+ StrokeOptions(state.lineWidth, state.lineJoin,
+ cap, state.miterLimit,
+ state.dash.Length(),
+ state.dash.Elements(),
+ state.dashOffset),
+ DrawOptions(state.globalAlpha, op));
+ return;
+ }
+
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ const ContextState& state = CurrentState();
+ target->
+ StrokeRect(gfx::Rect(aX, aY, aW, aH),
+ CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
+ StrokeOptions(state.lineWidth, state.lineJoin,
+ state.lineCap, state.miterLimit,
+ state.dash.Length(),
+ state.dash.Elements(),
+ state.dashOffset),
+ DrawOptions(state.globalAlpha, op));
+
+ Redraw();
+}
+
+//
+// path bits
+//
+
+void
+CanvasRenderingContext2D::BeginPath()
+{
+ mPath = nullptr;
+ mPathBuilder = nullptr;
+ mDSPathBuilder = nullptr;
+ mPathTransformWillUpdate = false;
+}
+
+void
+CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding)
+{
+ EnsureUserSpacePath(aWinding);
+
+ if (!mPath) {
+ return;
+ }
+
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+ gfx::Rect bounds;
+ if (needBounds) {
+ bounds = mPath->GetBounds(mTarget->GetTransform());
+ }
+
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ auto op = UsedOperation();
+ if (!IsTargetValid() || !target) {
+ return;
+ }
+ target->Fill(mPath,
+ CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
+ DrawOptions(CurrentState().globalAlpha, op));
+ Redraw();
+}
+
+void CanvasRenderingContext2D::Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding)
+{
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
+ if (!gfxpath) {
+ return;
+ }
+
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+ gfx::Rect bounds;
+ if (needBounds) {
+ bounds = gfxpath->GetBounds(mTarget->GetTransform());
+ }
+
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ auto op = UsedOperation();
+ if (!IsTargetValid() || !target) {
+ return;
+ }
+ target->Fill(gfxpath,
+ CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
+ DrawOptions(CurrentState().globalAlpha, op));
+ Redraw();
+}
+
+void
+CanvasRenderingContext2D::Stroke()
+{
+ EnsureUserSpacePath();
+
+ if (!mPath) {
+ return;
+ }
+
+ const ContextState* state = &CurrentState();
+ StrokeOptions strokeOptions(state->lineWidth, state->lineJoin,
+ state->lineCap, state->miterLimit,
+ state->dash.Length(), state->dash.Elements(),
+ state->dashOffset);
+ state = nullptr;
+
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+ gfx::Rect bounds;
+ if (needBounds) {
+ bounds =
+ mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
+ }
+
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ auto op = UsedOperation();
+ if (!IsTargetValid() || !target) {
+ return;
+ }
+ target->Stroke(mPath,
+ CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
+ strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
+ Redraw();
+}
+
+void
+CanvasRenderingContext2D::Stroke(const CanvasPath& aPath)
+{
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ RefPtr<gfx::Path> gfxpath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
+
+ if (!gfxpath) {
+ return;
+ }
+
+ const ContextState* state = &CurrentState();
+ StrokeOptions strokeOptions(state->lineWidth, state->lineJoin,
+ state->lineCap, state->miterLimit,
+ state->dash.Length(), state->dash.Elements(),
+ state->dashOffset);
+ state = nullptr;
+
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+ gfx::Rect bounds;
+ if (needBounds) {
+ bounds =
+ gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
+ }
+
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!target) {
+ return;
+ }
+
+ auto op = UsedOperation();
+ if (!IsTargetValid() || !target) {
+ return;
+ }
+ target->Stroke(gfxpath,
+ CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
+ strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
+ Redraw();
+}
+
+void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement,
+ ErrorResult& aRv)
+{
+ EnsureUserSpacePath();
+ if (!mPath) {
+ return;
+ }
+
+ if (DrawCustomFocusRing(aElement)) {
+ AutoSaveRestore asr(this);
+
+ // set state to conforming focus state
+ ContextState* state = &CurrentState();
+ state->globalAlpha = 1.0;
+ state->shadowBlur = 0;
+ state->shadowOffset.x = 0;
+ state->shadowOffset.y = 0;
+ state->op = mozilla::gfx::CompositionOp::OP_OVER;
+
+ state->lineCap = CapStyle::BUTT;
+ state->lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL;
+ state->lineWidth = 1;
+ state->dash.Clear();
+
+ // color and style of the rings is the same as for image maps
+ // set the background focus color
+ state->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
+ state = nullptr;
+
+ // draw the focus ring
+ Stroke();
+ if (!mPath) {
+ return;
+ }
+
+ // set dashing for foreground
+ nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
+ for (uint32_t i = 0; i < 2; ++i) {
+ if (!dash.AppendElement(1, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ // set the foreground focus color
+ CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255));
+ // draw the focus ring
+ Stroke();
+ if (!mPath) {
+ return;
+ }
+ }
+}
+
+bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement)
+{
+ EnsureUserSpacePath();
+
+ HTMLCanvasElement* canvas = GetCanvas();
+
+ if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) {
+ return false;
+ }
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ // check that the element i focused
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElement(getter_AddRefs(focusedElement));
+ if (SameCOMIdentity(aElement.AsDOMNode(), focusedElement)) {
+ if (nsPIDOMWindowOuter* window = aElement.OwnerDoc()->GetWindow()) {
+ return window->ShouldShowFocusRing();
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding)
+{
+ EnsureUserSpacePath(aWinding);
+
+ if (!mPath) {
+ return;
+ }
+
+ mTarget->PushClip(mPath);
+ CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath));
+}
+
+void
+CanvasRenderingContext2D::Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding)
+{
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
+
+ if (!gfxpath) {
+ return;
+ }
+
+ mTarget->PushClip(gfxpath);
+ CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath));
+}
+
+void
+CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
+ double aY2, double aRadius,
+ ErrorResult& aError)
+{
+ if (aRadius < 0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ EnsureWritablePath();
+
+ // Current point in user space!
+ Point p0;
+ if (mPathBuilder) {
+ p0 = mPathBuilder->CurrentPoint();
+ } else {
+ Matrix invTransform = mTarget->GetTransform();
+ if (!invTransform.Invert()) {
+ return;
+ }
+
+ p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint());
+ }
+
+ Point p1(aX1, aY1);
+ Point p2(aX2, aY2);
+
+ // Execute these calculations in double precision to avoid cumulative
+ // rounding errors.
+ double dir, a2, b2, c2, cosx, sinx, d, anx, any,
+ bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1;
+ bool anticlockwise;
+
+ if (p0 == p1 || p1 == p2 || aRadius == 0) {
+ LineTo(p1.x, p1.y);
+ return;
+ }
+
+ // Check for colinearity
+ dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
+ if (dir == 0) {
+ LineTo(p1.x, p1.y);
+ return;
+ }
+
+
+ // XXX - Math for this code was already available from the non-azure code
+ // and would be well tested. Perhaps converting to bezier directly might
+ // be more efficient longer run.
+ a2 = (p0.x-aX1)*(p0.x-aX1) + (p0.y-aY1)*(p0.y-aY1);
+ b2 = (aX1-aX2)*(aX1-aX2) + (aY1-aY2)*(aY1-aY2);
+ c2 = (p0.x-aX2)*(p0.x-aX2) + (p0.y-aY2)*(p0.y-aY2);
+ cosx = (a2+b2-c2)/(2*sqrt(a2*b2));
+
+ sinx = sqrt(1 - cosx*cosx);
+ d = aRadius / ((1 - cosx) / sinx);
+
+ anx = (aX1-p0.x) / sqrt(a2);
+ any = (aY1-p0.y) / sqrt(a2);
+ bnx = (aX1-aX2) / sqrt(b2);
+ bny = (aY1-aY2) / sqrt(b2);
+ x3 = aX1 - anx*d;
+ y3 = aY1 - any*d;
+ x4 = aX1 - bnx*d;
+ y4 = aY1 - bny*d;
+ anticlockwise = (dir < 0);
+ cx = x3 + any*aRadius*(anticlockwise ? 1 : -1);
+ cy = y3 - anx*aRadius*(anticlockwise ? 1 : -1);
+ angle0 = atan2((y3-cy), (x3-cx));
+ angle1 = atan2((y4-cy), (x4-cx));
+
+
+ LineTo(x3, y3);
+
+ Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
+}
+
+void
+CanvasRenderingContext2D::Arc(double aX, double aY, double aR,
+ double aStartAngle, double aEndAngle,
+ bool aAnticlockwise, ErrorResult& aError)
+{
+ if (aR < 0.0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ EnsureWritablePath();
+
+ ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle, aAnticlockwise);
+}
+
+void
+CanvasRenderingContext2D::Rect(double aX, double aY, double aW, double aH)
+{
+ EnsureWritablePath();
+
+ if (mPathBuilder) {
+ mPathBuilder->MoveTo(Point(aX, aY));
+ mPathBuilder->LineTo(Point(aX + aW, aY));
+ mPathBuilder->LineTo(Point(aX + aW, aY + aH));
+ mPathBuilder->LineTo(Point(aX, aY + aH));
+ mPathBuilder->Close();
+ } else {
+ mDSPathBuilder->MoveTo(mTarget->GetTransform().TransformPoint(Point(aX, aY)));
+ mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX + aW, aY)));
+ mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX + aW, aY + aH)));
+ mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX, aY + aH)));
+ mDSPathBuilder->Close();
+ }
+}
+
+void
+CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, double aRadiusY,
+ double aRotation, double aStartAngle, double aEndAngle,
+ bool aAnticlockwise, ErrorResult& aError)
+{
+ if (aRadiusX < 0.0 || aRadiusY < 0.0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ EnsureWritablePath();
+
+ ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, aEndAngle,
+ aAnticlockwise, aRotation);
+}
+
+void
+CanvasRenderingContext2D::EnsureWritablePath()
+{
+ EnsureTarget();
+ // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
+ // go ahead and create a path anyway since callers depend on that.
+
+ if (mDSPathBuilder) {
+ return;
+ }
+
+ FillRule fillRule = CurrentState().fillRule;
+
+ if (mPathBuilder) {
+ if (mPathTransformWillUpdate) {
+ mPath = mPathBuilder->Finish();
+ mDSPathBuilder =
+ mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
+ mPath = nullptr;
+ mPathBuilder = nullptr;
+ mPathTransformWillUpdate = false;
+ }
+ return;
+ }
+
+ if (!mPath) {
+ NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null");
+ mPathBuilder = mTarget->CreatePathBuilder(fillRule);
+ } else if (!mPathTransformWillUpdate) {
+ mPathBuilder = mPath->CopyToBuilder(fillRule);
+ } else {
+ mDSPathBuilder =
+ mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
+ mPathTransformWillUpdate = false;
+ mPath = nullptr;
+ }
+}
+
+void
+CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& aWinding)
+{
+ FillRule fillRule = CurrentState().fillRule;
+ if (aWinding == CanvasWindingRule::Evenodd)
+ fillRule = FillRule::FILL_EVEN_ODD;
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ if (!mPath && !mPathBuilder && !mDSPathBuilder) {
+ mPathBuilder = mTarget->CreatePathBuilder(fillRule);
+ }
+
+ if (mPathBuilder) {
+ mPath = mPathBuilder->Finish();
+ mPathBuilder = nullptr;
+ }
+
+ if (mPath &&
+ mPathTransformWillUpdate) {
+ mDSPathBuilder =
+ mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
+ mPath = nullptr;
+ mPathTransformWillUpdate = false;
+ }
+
+ if (mDSPathBuilder) {
+ RefPtr<Path> dsPath;
+ dsPath = mDSPathBuilder->Finish();
+ mDSPathBuilder = nullptr;
+
+ Matrix inverse = mTarget->GetTransform();
+ if (!inverse.Invert()) {
+ NS_WARNING("Could not invert transform");
+ return;
+ }
+
+ mPathBuilder =
+ dsPath->TransformedCopyToBuilder(inverse, fillRule);
+ mPath = mPathBuilder->Finish();
+ mPathBuilder = nullptr;
+ }
+
+ if (mPath && mPath->GetFillRule() != fillRule) {
+ mPathBuilder = mPath->CopyToBuilder(fillRule);
+ mPath = mPathBuilder->Finish();
+ mPathBuilder = nullptr;
+ }
+
+ NS_ASSERTION(mPath, "mPath should exist");
+}
+
+void
+CanvasRenderingContext2D::TransformWillUpdate()
+{
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ // Store the matrix that would transform the current path to device
+ // space.
+ if (mPath || mPathBuilder) {
+ if (!mPathTransformWillUpdate) {
+ // If the transform has already been updated, but a device space builder
+ // has not been created yet mPathToDS contains the right transform to
+ // transform the current mPath into device space.
+ // We should leave it alone.
+ mPathToDS = mTarget->GetTransform();
+ }
+ mPathTransformWillUpdate = true;
+ }
+}
+
+//
+// text
+//
+
+void
+CanvasRenderingContext2D::SetFont(const nsAString& aFont,
+ ErrorResult& aError)
+{
+ SetFontInternal(aFont, aError);
+}
+
+bool
+CanvasRenderingContext2D::SetFontInternal(const nsAString& aFont,
+ ErrorResult& aError)
+{
+ /*
+ * If font is defined with relative units (e.g. ems) and the parent
+ * style context changes in between calls, setting the font to the
+ * same value as previous could result in a different computed value,
+ * so we cannot have the optimization where we check if the new font
+ * string is equal to the old one.
+ */
+
+ if (!mCanvasElement && !mDocShell) {
+ NS_WARNING("Canvas element must be non-null or a docshell must be provided");
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ if (!presShell) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsString usedFont;
+ RefPtr<nsStyleContext> sc =
+ GetFontStyleContext(mCanvasElement, aFont, presShell, usedFont, aError);
+ if (!sc) {
+ return false;
+ }
+
+ const nsStyleFont* fontStyle = sc->StyleFont();
+
+ nsPresContext* c = presShell->GetPresContext();
+
+ // Purposely ignore the font size that respects the user's minimum
+ // font preference (fontStyle->mFont.size) in favor of the computed
+ // size (fontStyle->mSize). See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=698652.
+ MOZ_ASSERT(!fontStyle->mAllowZoom,
+ "expected text zoom to be disabled on this nsStyleFont");
+ nsFont resizedFont(fontStyle->mFont);
+ // Create a font group working in units of CSS pixels instead of the usual
+ // device pixels, to avoid being affected by page zoom. nsFontMetrics will
+ // convert nsFont size in app units to device pixels for the font group, so
+ // here we first apply to the size the equivalent of a conversion from device
+ // pixels to CSS pixels, to adjust for the difference in expectations from
+ // other nsFontMetrics clients.
+ resizedFont.size =
+ (fontStyle->mSize * c->AppUnitsPerDevPixel()) / c->AppUnitsPerCSSPixel();
+
+ nsFontMetrics::Params params;
+ params.language = fontStyle->mLanguage;
+ params.explicitLanguage = fontStyle->mExplicitLanguage;
+ params.userFontSet = c->GetUserFontSet();
+ params.textPerf = c->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> metrics =
+ c->DeviceContext()->GetMetricsFor(resizedFont, params);
+
+ gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup();
+ CurrentState().fontGroup = newFontGroup;
+ NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
+ CurrentState().font = usedFont;
+ CurrentState().fontFont = fontStyle->mFont;
+ CurrentState().fontFont.size = fontStyle->mSize;
+ CurrentState().fontLanguage = fontStyle->mLanguage;
+ CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
+
+ return true;
+}
+
+void
+CanvasRenderingContext2D::SetTextAlign(const nsAString& aTextAlign)
+{
+ if (aTextAlign.EqualsLiteral("start"))
+ CurrentState().textAlign = TextAlign::START;
+ else if (aTextAlign.EqualsLiteral("end"))
+ CurrentState().textAlign = TextAlign::END;
+ else if (aTextAlign.EqualsLiteral("left"))
+ CurrentState().textAlign = TextAlign::LEFT;
+ else if (aTextAlign.EqualsLiteral("right"))
+ CurrentState().textAlign = TextAlign::RIGHT;
+ else if (aTextAlign.EqualsLiteral("center"))
+ CurrentState().textAlign = TextAlign::CENTER;
+}
+
+void
+CanvasRenderingContext2D::GetTextAlign(nsAString& aTextAlign)
+{
+ switch (CurrentState().textAlign)
+ {
+ case TextAlign::START:
+ aTextAlign.AssignLiteral("start");
+ break;
+ case TextAlign::END:
+ aTextAlign.AssignLiteral("end");
+ break;
+ case TextAlign::LEFT:
+ aTextAlign.AssignLiteral("left");
+ break;
+ case TextAlign::RIGHT:
+ aTextAlign.AssignLiteral("right");
+ break;
+ case TextAlign::CENTER:
+ aTextAlign.AssignLiteral("center");
+ break;
+ }
+}
+
+void
+CanvasRenderingContext2D::SetTextBaseline(const nsAString& aTextBaseline)
+{
+ if (aTextBaseline.EqualsLiteral("top"))
+ CurrentState().textBaseline = TextBaseline::TOP;
+ else if (aTextBaseline.EqualsLiteral("hanging"))
+ CurrentState().textBaseline = TextBaseline::HANGING;
+ else if (aTextBaseline.EqualsLiteral("middle"))
+ CurrentState().textBaseline = TextBaseline::MIDDLE;
+ else if (aTextBaseline.EqualsLiteral("alphabetic"))
+ CurrentState().textBaseline = TextBaseline::ALPHABETIC;
+ else if (aTextBaseline.EqualsLiteral("ideographic"))
+ CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC;
+ else if (aTextBaseline.EqualsLiteral("bottom"))
+ CurrentState().textBaseline = TextBaseline::BOTTOM;
+}
+
+void
+CanvasRenderingContext2D::GetTextBaseline(nsAString& aTextBaseline)
+{
+ switch (CurrentState().textBaseline)
+ {
+ case TextBaseline::TOP:
+ aTextBaseline.AssignLiteral("top");
+ break;
+ case TextBaseline::HANGING:
+ aTextBaseline.AssignLiteral("hanging");
+ break;
+ case TextBaseline::MIDDLE:
+ aTextBaseline.AssignLiteral("middle");
+ break;
+ case TextBaseline::ALPHABETIC:
+ aTextBaseline.AssignLiteral("alphabetic");
+ break;
+ case TextBaseline::IDEOGRAPHIC:
+ aTextBaseline.AssignLiteral("ideographic");
+ break;
+ case TextBaseline::BOTTOM:
+ aTextBaseline.AssignLiteral("bottom");
+ break;
+ }
+}
+
+/*
+ * Helper function that replaces the whitespace characters in a string
+ * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
+ * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
+ * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
+ * @param str The string whose whitespace characters to replace.
+ */
+static inline void
+TextReplaceWhitespaceCharacters(nsAutoString& aStr)
+{
+ aStr.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' '));
+}
+
+void
+CanvasRenderingContext2D::FillText(const nsAString& aText, double aX,
+ double aY,
+ const Optional<double>& aMaxWidth,
+ ErrorResult& aError)
+{
+ aError = DrawOrMeasureText(aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, nullptr);
+}
+
+void
+CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX,
+ double aY,
+ const Optional<double>& aMaxWidth,
+ ErrorResult& aError)
+{
+ aError = DrawOrMeasureText(aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, nullptr);
+}
+
+TextMetrics*
+CanvasRenderingContext2D::MeasureText(const nsAString& aRawText,
+ ErrorResult& aError)
+{
+ float width;
+ Optional<double> maxWidth;
+ aError = DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, &width);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return new TextMetrics(width);
+}
+
+void
+CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& aOptions, ErrorResult& aError)
+{
+ RefPtr<gfx::Path> path;
+ if (aOptions.mPath) {
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+ path = aOptions.mPath->GetPath(CanvasWindingRule::Nonzero, mTarget);
+ }
+
+ if (!path) {
+ // check if the path is valid
+ EnsureUserSpacePath(CanvasWindingRule::Nonzero);
+ path = mPath;
+ }
+
+ if (!path) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ // get the bounds of the current path. They are relative to the canvas
+ gfx::Rect bounds(path->GetBounds(mTarget->GetTransform()));
+ if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) {
+ // The specified region has no pixels.
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ // remove old hit region first
+ RemoveHitRegion(aOptions.mId);
+
+ if (aOptions.mControl) {
+ // also remove regions with this control
+ for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
+ RegionInfo& info = mHitRegionsOptions[x];
+ if (info.mElement == aOptions.mControl) {
+ mHitRegionsOptions.RemoveElementAt(x);
+ break;
+ }
+ }
+#ifdef ACCESSIBILITY
+ aOptions.mControl->SetProperty(nsGkAtoms::hitregion, new bool(true),
+ nsINode::DeleteProperty<bool>);
+#endif
+ }
+
+ // finally, add the region to the list
+ RegionInfo info;
+ info.mId = aOptions.mId;
+ info.mElement = aOptions.mControl;
+ RefPtr<PathBuilder> pathBuilder = path->TransformedCopyToBuilder(mTarget->GetTransform());
+ info.mPath = pathBuilder->Finish();
+
+ mHitRegionsOptions.InsertElementAt(0, info);
+}
+
+void
+CanvasRenderingContext2D::RemoveHitRegion(const nsAString& aId)
+{
+ if (aId.Length() == 0) {
+ return;
+ }
+
+ for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
+ RegionInfo& info = mHitRegionsOptions[x];
+ if (info.mId == aId) {
+ mHitRegionsOptions.RemoveElementAt(x);
+
+ return;
+ }
+ }
+}
+
+void
+CanvasRenderingContext2D::ClearHitRegions()
+{
+ mHitRegionsOptions.Clear();
+}
+
+bool
+CanvasRenderingContext2D::GetHitRegionRect(Element* aElement, nsRect& aRect)
+{
+ for (unsigned int x = 0; x < mHitRegionsOptions.Length(); x++) {
+ RegionInfo& info = mHitRegionsOptions[x];
+ if (info.mElement == aElement) {
+ gfx::Rect bounds(info.mPath->GetBounds());
+ gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height);
+ aRect = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel());
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Used for nsBidiPresUtils::ProcessText
+ */
+struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
+{
+ typedef CanvasRenderingContext2D::Style Style;
+
+ CanvasBidiProcessor()
+ : nsBidiPresUtils::BidiProcessor()
+ {
+ if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+ mMissingFonts = new gfxMissingFontRecorder();
+ }
+ }
+
+ ~CanvasBidiProcessor()
+ {
+ // notify front-end code if we encountered missing glyphs in any script
+ if (mMissingFonts) {
+ mMissingFonts->Flush();
+ }
+ }
+
+ typedef CanvasRenderingContext2D::ContextState ContextState;
+
+ virtual void SetText(const char16_t* aText, int32_t aLength, nsBidiDirection aDirection)
+ {
+ mFontgrp->UpdateUserFonts(); // ensure user font generation is current
+ // adjust flags for current direction run
+ uint32_t flags = mTextRunFlags;
+ if (aDirection == NSBIDI_RTL) {
+ flags |= gfxTextRunFactory::TEXT_IS_RTL;
+ } else {
+ flags &= ~gfxTextRunFactory::TEXT_IS_RTL;
+ }
+ mTextRun = mFontgrp->MakeTextRun(aText,
+ aLength,
+ mDrawTarget,
+ mAppUnitsPerDevPixel,
+ flags,
+ mMissingFonts);
+ }
+
+ virtual nscoord GetWidth()
+ {
+ gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
+ mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
+ : gfxFont::LOOSE_INK_EXTENTS, mDrawTarget);
+
+ // this only measures the height; the total width is gotten from the
+ // the return value of ProcessText.
+ if (mDoMeasureBoundingBox) {
+ textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
+ mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
+ }
+
+ return NSToCoordRound(textRunMetrics.mAdvanceWidth);
+ }
+
+ already_AddRefed<gfxPattern> GetGradientFor(Style aStyle)
+ {
+ RefPtr<gfxPattern> pattern;
+ CanvasGradient* gradient = mCtx->CurrentState().gradientStyles[aStyle];
+ CanvasGradient::Type type = gradient->GetType();
+
+ switch (type) {
+ case CanvasGradient::Type::RADIAL: {
+ auto radial = static_cast<CanvasRadialGradient*>(gradient);
+ pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y,
+ radial->mRadius1, radial->mCenter2.x,
+ radial->mCenter2.y, radial->mRadius2);
+ break;
+ }
+ case CanvasGradient::Type::LINEAR: {
+ auto linear = static_cast<CanvasLinearGradient*>(gradient);
+ pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y,
+ linear->mEnd.x, linear->mEnd.y);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Should be linear or radial gradient.");
+ return nullptr;
+ }
+
+ for (auto stop : gradient->mRawStops) {
+ pattern->AddColorStop(stop.offset, stop.color);
+ }
+
+ return pattern.forget();
+ }
+
+ gfx::ExtendMode CvtCanvasRepeatToGfxRepeat(
+ CanvasPattern::RepeatMode aRepeatMode)
+ {
+ switch (aRepeatMode) {
+ case CanvasPattern::RepeatMode::REPEAT:
+ return gfx::ExtendMode::REPEAT;
+ case CanvasPattern::RepeatMode::REPEATX:
+ return gfx::ExtendMode::REPEAT_X;
+ case CanvasPattern::RepeatMode::REPEATY:
+ return gfx::ExtendMode::REPEAT_Y;
+ case CanvasPattern::RepeatMode::NOREPEAT:
+ return gfx::ExtendMode::CLAMP;
+ default:
+ return gfx::ExtendMode::CLAMP;
+ }
+ }
+
+ already_AddRefed<gfxPattern> GetPatternFor(Style aStyle)
+ {
+ const CanvasPattern* pat = mCtx->CurrentState().patternStyles[aStyle];
+ RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, Matrix());
+ pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat));
+ return pattern.forget();
+ }
+
+ virtual void DrawText(nscoord aXOffset, nscoord aWidth)
+ {
+ gfxPoint point = mPt;
+ bool rtl = mTextRun->IsRightToLeft();
+ bool verticalRun = mTextRun->IsVertical();
+ RefPtr<gfxPattern> pattern;
+
+ gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
+ inlineCoord += aXOffset;
+
+ // offset is given in terms of left side of string
+ if (rtl) {
+ // Bug 581092 - don't use rounded pixel width to advance to
+ // right-hand end of run, because this will cause different
+ // glyph positioning for LTR vs RTL drawing of the same
+ // glyph string on OS X and DWrite where textrun widths may
+ // involve fractional pixels.
+ gfxTextRun::Metrics textRunMetrics =
+ mTextRun->MeasureText(mDoMeasureBoundingBox ?
+ gfxFont::TIGHT_INK_EXTENTS :
+ gfxFont::LOOSE_INK_EXTENTS,
+ mDrawTarget);
+ inlineCoord += textRunMetrics.mAdvanceWidth;
+ // old code was:
+ // point.x += width * mAppUnitsPerDevPixel;
+ // TODO: restore this if/when we move to fractional coords
+ // throughout the text layout process
+ }
+
+ mCtx->EnsureTarget();
+ if (!mCtx->IsTargetValid()) {
+ return;
+ }
+
+ // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
+ // appropriately.
+ StrokeOptions strokeOpts;
+ DrawOptions drawOpts;
+ Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
+ ? Style::FILL
+ : Style::STROKE;
+
+ AdjustedTarget target(mCtx);
+ if (!target) {
+ return;
+ }
+
+ RefPtr<gfxContext> thebes =
+ gfxContext::CreatePreservingTransformOrNull(target);
+ if (!thebes) {
+ // If CreatePreservingTransformOrNull returns null, it will also have
+ // issued a gfxCriticalNote already, so here we'll just bail out.
+ return;
+ }
+ gfxTextRun::DrawParams params(thebes);
+
+ const ContextState* state = &mCtx->CurrentState();
+ if (state->StyleIsColor(style)) { // Color
+ nscolor fontColor = state->colorStyles[style];
+ if (style == Style::FILL) {
+ params.context->SetColor(Color::FromABGR(fontColor));
+ } else {
+ params.textStrokeColor = fontColor;
+ }
+ } else {
+ if (state->gradientStyles[style]) { // Gradient
+ pattern = GetGradientFor(style);
+ } else if (state->patternStyles[style]) { // Pattern
+ pattern = GetPatternFor(style);
+ } else {
+ MOZ_ASSERT(false, "Should never reach here.");
+ return;
+ }
+ MOZ_ASSERT(pattern, "No valid pattern.");
+
+ if (style == Style::FILL) {
+ params.context->SetPattern(pattern);
+ } else {
+ params.textStrokePattern = pattern;
+ }
+ }
+
+ drawOpts.mAlpha = state->globalAlpha;
+ drawOpts.mCompositionOp = mCtx->UsedOperation();
+ if (!mCtx->IsTargetValid()) {
+ return;
+ }
+ state = &mCtx->CurrentState();
+ params.drawOpts = &drawOpts;
+
+ if (style == Style::STROKE) {
+ strokeOpts.mLineWidth = state->lineWidth;
+ strokeOpts.mLineJoin = state->lineJoin;
+ strokeOpts.mLineCap = state->lineCap;
+ strokeOpts.mMiterLimit = state->miterLimit;
+ strokeOpts.mDashLength = state->dash.Length();
+ strokeOpts.mDashPattern =
+ (strokeOpts.mDashLength > 0) ? state->dash.Elements() : 0;
+ strokeOpts.mDashOffset = state->dashOffset;
+
+ params.drawMode = DrawMode::GLYPH_STROKE;
+ params.strokeOpts = &strokeOpts;
+ }
+
+ mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
+ }
+
+ // current text run
+ RefPtr<gfxTextRun> mTextRun;
+
+ // pointer to a screen reference context used to measure text and such
+ RefPtr<DrawTarget> mDrawTarget;
+
+ // Pointer to the draw target we should fill our text to
+ CanvasRenderingContext2D* mCtx;
+
+ // position of the left side of the string, alphabetic baseline
+ gfxPoint mPt;
+
+ // current font
+ gfxFontGroup* mFontgrp;
+
+ // to record any unsupported characters found in the text,
+ // and notify front-end if it is interested
+ nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
+
+ // dev pixel conversion factor
+ int32_t mAppUnitsPerDevPixel;
+
+ // operation (fill or stroke)
+ CanvasRenderingContext2D::TextDrawOperation mOp;
+
+ // union of bounding boxes of all runs, needed for shadows
+ gfxRect mBoundingBox;
+
+ // flags to use when creating textrun, based on CSS style
+ uint32_t mTextRunFlags;
+
+ // true iff the bounding box should be measured
+ bool mDoMeasureBoundingBox;
+};
+
+nsresult
+CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
+ float aX,
+ float aY,
+ const Optional<double>& aMaxWidth,
+ TextDrawOperation aOp,
+ float* aWidth)
+{
+ nsresult rv;
+
+ if (!mCanvasElement && !mDocShell) {
+ NS_WARNING("Canvas element must be non-null or a docshell must be provided");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ if (!presShell)
+ return NS_ERROR_FAILURE;
+
+ nsIDocument* document = presShell->GetDocument();
+
+ // replace all the whitespace characters with U+0020 SPACE
+ nsAutoString textToDraw(aRawText);
+ TextReplaceWhitespaceCharacters(textToDraw);
+
+ // According to spec, the API should return an empty array if maxWidth was provided
+ // but is less than or equal to zero or equal to NaN.
+ if (aMaxWidth.WasPassed() && (aMaxWidth.Value() <= 0 || IsNaN(aMaxWidth.Value()))) {
+ textToDraw.Truncate();
+ }
+
+ // for now, default to ltr if not in doc
+ bool isRTL = false;
+
+ RefPtr<nsStyleContext> canvasStyle;
+ if (mCanvasElement && mCanvasElement->IsInUncomposedDoc()) {
+ // try to find the closest context
+ canvasStyle =
+ nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
+ nullptr,
+ presShell);
+ if (!canvasStyle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ isRTL = canvasStyle->StyleVisibility()->mDirection ==
+ NS_STYLE_DIRECTION_RTL;
+ } else {
+ isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL;
+ }
+
+ // This is only needed to know if we can know the drawing bounding box easily.
+ const bool doCalculateBounds = NeedToCalculateBounds();
+ if (presShell->IsDestroying()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
+ if (!currentFontStyle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(!presShell->IsDestroying(),
+ "GetCurrentFontStyle() should have returned null if the presshell is being destroyed");
+
+ // ensure user font set is up to date
+ currentFontStyle->
+ SetUserFontSet(presShell->GetPresContext()->GetUserFontSet());
+
+ if (currentFontStyle->GetStyle()->size == 0.0F) {
+ if (aWidth) {
+ *aWidth = 0;
+ }
+ return NS_OK;
+ }
+
+ if (!IsFinite(aX) || !IsFinite(aY)) {
+ return NS_OK;
+ }
+
+ CanvasBidiProcessor processor;
+
+ // If we don't have a style context, we can't set up vertical-text flags
+ // (for now, at least; perhaps we need new Canvas API to control this).
+ processor.mTextRunFlags = canvasStyle ?
+ nsLayoutUtils::GetTextRunFlagsForStyle(canvasStyle,
+ canvasStyle->StyleFont(),
+ canvasStyle->StyleText(),
+ 0) : 0;
+
+ GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
+ processor.mPt = gfxPoint(aX, aY);
+ processor.mDrawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+
+ // If we don't have a target then we don't have a transform. A target won't
+ // be needed in the case where we're measuring the text size. This allows
+ // to avoid creating a target if it's only being used to measure text sizes.
+ if (mTarget) {
+ processor.mDrawTarget->SetTransform(mTarget->GetTransform());
+ }
+ processor.mCtx = this;
+ processor.mOp = aOp;
+ processor.mBoundingBox = gfxRect(0, 0, 0, 0);
+ processor.mDoMeasureBoundingBox = doCalculateBounds || !mIsEntireFrameInvalid;
+ processor.mFontgrp = currentFontStyle;
+
+ nscoord totalWidthCoord;
+
+ // calls bidi algo twice since it needs the full text width and the
+ // bounding boxes before rendering anything
+ nsBidi bidiEngine;
+ rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
+ textToDraw.Length(),
+ isRTL ? NSBIDI_RTL : NSBIDI_LTR,
+ presShell->GetPresContext(),
+ processor,
+ nsBidiPresUtils::MODE_MEASURE,
+ nullptr,
+ 0,
+ &totalWidthCoord,
+ &bidiEngine);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
+ if (aWidth) {
+ *aWidth = totalWidth;
+ }
+
+ // if only measuring, don't need to do any more work
+ if (aOp==TextDrawOperation::MEASURE) {
+ return NS_OK;
+ }
+
+ // offset pt.x based on text align
+ gfxFloat anchorX;
+
+ const ContextState& state = CurrentState();
+ if (state.textAlign == TextAlign::CENTER) {
+ anchorX = .5;
+ } else if (state.textAlign == TextAlign::LEFT ||
+ (!isRTL && state.textAlign == TextAlign::START) ||
+ (isRTL && state.textAlign == TextAlign::END)) {
+ anchorX = 0;
+ } else {
+ anchorX = 1;
+ }
+
+ processor.mPt.x -= anchorX * totalWidth;
+
+ // offset pt.y (or pt.x, for vertical text) based on text baseline
+ processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
+ const gfxFont::Metrics& fontMetrics =
+ processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
+
+ gfxFloat baselineAnchor;
+
+ switch (state.textBaseline)
+ {
+ case TextBaseline::HANGING:
+ // fall through; best we can do with the information available
+ case TextBaseline::TOP:
+ baselineAnchor = fontMetrics.emAscent;
+ break;
+ case TextBaseline::MIDDLE:
+ baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
+ break;
+ case TextBaseline::IDEOGRAPHIC:
+ // fall through; best we can do with the information available
+ case TextBaseline::ALPHABETIC:
+ baselineAnchor = 0;
+ break;
+ case TextBaseline::BOTTOM:
+ baselineAnchor = -fontMetrics.emDescent;
+ break;
+ default:
+ MOZ_CRASH("GFX: unexpected TextBaseline");
+ }
+
+ // We can't query the textRun directly, as it may not have been created yet;
+ // so instead we check the flags that will be used to initialize it.
+ uint16_t runOrientation =
+ (processor.mTextRunFlags & gfxTextRunFactory::TEXT_ORIENT_MASK);
+ if (runOrientation != gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL) {
+ if (runOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED ||
+ runOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT) {
+ // Adjust to account for mTextRun being shaped using center baseline
+ // rather than alphabetic.
+ baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
+ }
+ processor.mPt.x -= baselineAnchor;
+ } else {
+ processor.mPt.y += baselineAnchor;
+ }
+
+ // correct bounding box to get it to be the correct size/position
+ processor.mBoundingBox.width = totalWidth;
+ processor.mBoundingBox.MoveBy(processor.mPt);
+
+ processor.mPt.x *= processor.mAppUnitsPerDevPixel;
+ processor.mPt.y *= processor.mAppUnitsPerDevPixel;
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Matrix oldTransform = mTarget->GetTransform();
+ // if text is over aMaxWidth, then scale the text horizontally such that its
+ // width is precisely aMaxWidth
+ if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 &&
+ totalWidth > aMaxWidth.Value()) {
+ Matrix newTransform = oldTransform;
+
+ // Translate so that the anchor point is at 0,0, then scale and then
+ // translate back.
+ newTransform.PreTranslate(aX, 0);
+ newTransform.PreScale(aMaxWidth.Value() / totalWidth, 1);
+ newTransform.PreTranslate(-aX, 0);
+ /* we do this to avoid an ICE in the android compiler */
+ Matrix androidCompilerBug = newTransform;
+ mTarget->SetTransform(androidCompilerBug);
+ }
+
+ // save the previous bounding box
+ gfxRect boundingBox = processor.mBoundingBox;
+
+ // don't ever need to measure the bounding box twice
+ processor.mDoMeasureBoundingBox = false;
+
+ rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
+ textToDraw.Length(),
+ isRTL ? NSBIDI_RTL : NSBIDI_LTR,
+ presShell->GetPresContext(),
+ processor,
+ nsBidiPresUtils::MODE_DRAW,
+ nullptr,
+ 0,
+ nullptr,
+ &bidiEngine);
+
+
+ mTarget->SetTransform(oldTransform);
+
+ if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
+ !doCalculateBounds) {
+ RedrawUser(boundingBox);
+ return NS_OK;
+ }
+
+ Redraw();
+ return NS_OK;
+}
+
+gfxFontGroup*
+CanvasRenderingContext2D::GetCurrentFontStyle()
+{
+ // use lazy initilization for the font group since it's rather expensive
+ if (!CurrentState().fontGroup) {
+ ErrorResult err;
+ NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif");
+ static float kDefaultFontSize = 10.0;
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ bool fontUpdated = SetFontInternal(kDefaultFontStyle, err);
+ if (err.Failed() || !fontUpdated) {
+ err.SuppressException();
+ gfxFontStyle style;
+ style.size = kDefaultFontSize;
+ gfxTextPerfMetrics* tp = nullptr;
+ if (presShell && !presShell->IsDestroying()) {
+ tp = presShell->GetPresContext()->GetTextPerfMetrics();
+ }
+ int32_t perDevPixel, perCSSPixel;
+ GetAppUnitsValues(&perDevPixel, &perCSSPixel);
+ gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
+ CurrentState().fontGroup =
+ gfxPlatform::GetPlatform()->CreateFontGroup(FontFamilyList(eFamily_sans_serif),
+ &style, tp,
+ nullptr, devToCssSize);
+ if (CurrentState().fontGroup) {
+ CurrentState().font = kDefaultFontStyle;
+ } else {
+ NS_ERROR("Default canvas font is invalid");
+ }
+ }
+ }
+
+ return CurrentState().fontGroup;
+}
+
+//
+// line caps/joins
+//
+
+void
+CanvasRenderingContext2D::SetLineCap(const nsAString& aLinecapStyle)
+{
+ CapStyle cap;
+
+ if (aLinecapStyle.EqualsLiteral("butt")) {
+ cap = CapStyle::BUTT;
+ } else if (aLinecapStyle.EqualsLiteral("round")) {
+ cap = CapStyle::ROUND;
+ } else if (aLinecapStyle.EqualsLiteral("square")) {
+ cap = CapStyle::SQUARE;
+ } else {
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ return;
+ }
+
+ CurrentState().lineCap = cap;
+}
+
+void
+CanvasRenderingContext2D::GetLineCap(nsAString& aLinecapStyle)
+{
+ switch (CurrentState().lineCap) {
+ case CapStyle::BUTT:
+ aLinecapStyle.AssignLiteral("butt");
+ break;
+ case CapStyle::ROUND:
+ aLinecapStyle.AssignLiteral("round");
+ break;
+ case CapStyle::SQUARE:
+ aLinecapStyle.AssignLiteral("square");
+ break;
+ }
+}
+
+void
+CanvasRenderingContext2D::SetLineJoin(const nsAString& aLinejoinStyle)
+{
+ JoinStyle j;
+
+ if (aLinejoinStyle.EqualsLiteral("round")) {
+ j = JoinStyle::ROUND;
+ } else if (aLinejoinStyle.EqualsLiteral("bevel")) {
+ j = JoinStyle::BEVEL;
+ } else if (aLinejoinStyle.EqualsLiteral("miter")) {
+ j = JoinStyle::MITER_OR_BEVEL;
+ } else {
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ return;
+ }
+
+ CurrentState().lineJoin = j;
+}
+
+void
+CanvasRenderingContext2D::GetLineJoin(nsAString& aLinejoinStyle, ErrorResult& aError)
+{
+ switch (CurrentState().lineJoin) {
+ case JoinStyle::ROUND:
+ aLinejoinStyle.AssignLiteral("round");
+ break;
+ case JoinStyle::BEVEL:
+ aLinejoinStyle.AssignLiteral("bevel");
+ break;
+ case JoinStyle::MITER_OR_BEVEL:
+ aLinejoinStyle.AssignLiteral("miter");
+ break;
+ default:
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void
+CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments,
+ ErrorResult& aRv)
+{
+ nsTArray<mozilla::gfx::Float> dash;
+
+ for (uint32_t x = 0; x < aSegments.Length(); x++) {
+ if (aSegments[x] < 0.0) {
+ // Pattern elements must be finite "numbers" >= 0, with "finite"
+ // taken care of by WebIDL
+ return;
+ }
+
+ if (!dash.AppendElement(aSegments[x], fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+ if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again
+ for (uint32_t x = 0; x < aSegments.Length(); x++) {
+ if (!dash.AppendElement(aSegments[x], fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+ }
+
+ CurrentState().dash = Move(dash);
+}
+
+void
+CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
+ const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
+ aSegments.Clear();
+
+ for (uint32_t x = 0; x < dash.Length(); x++) {
+ aSegments.AppendElement(dash[x]);
+ }
+}
+
+void
+CanvasRenderingContext2D::SetLineDashOffset(double aOffset) {
+ CurrentState().dashOffset = aOffset;
+}
+
+double
+CanvasRenderingContext2D::LineDashOffset() const {
+ return CurrentState().dashOffset;
+}
+
+bool
+CanvasRenderingContext2D::IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding)
+{
+ if (!FloatValidate(aX, aY)) {
+ return false;
+ }
+
+ EnsureUserSpacePath(aWinding);
+ if (!mPath) {
+ return false;
+ }
+
+ if (mPathTransformWillUpdate) {
+ return mPath->ContainsPoint(Point(aX, aY), mPathToDS);
+ }
+
+ return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
+}
+
+bool CanvasRenderingContext2D::IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding)
+{
+ if (!FloatValidate(aX, aY)) {
+ return false;
+ }
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return false;
+ }
+
+ RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget);
+
+ return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
+}
+
+bool
+CanvasRenderingContext2D::IsPointInStroke(double aX, double aY)
+{
+ if (!FloatValidate(aX, aY)) {
+ return false;
+ }
+
+ EnsureUserSpacePath();
+ if (!mPath) {
+ return false;
+ }
+
+ const ContextState &state = CurrentState();
+
+ StrokeOptions strokeOptions(state.lineWidth,
+ state.lineJoin,
+ state.lineCap,
+ state.miterLimit,
+ state.dash.Length(),
+ state.dash.Elements(),
+ state.dashOffset);
+
+ if (mPathTransformWillUpdate) {
+ return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
+ }
+ return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
+}
+
+bool CanvasRenderingContext2D::IsPointInStroke(const CanvasPath& aPath, double aX, double aY)
+{
+ if (!FloatValidate(aX, aY)) {
+ return false;
+ }
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return false;
+ }
+
+ RefPtr<gfx::Path> tempPath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
+
+ const ContextState &state = CurrentState();
+
+ StrokeOptions strokeOptions(state.lineWidth,
+ state.lineJoin,
+ state.lineCap,
+ state.miterLimit,
+ state.dash.Length(),
+ state.dash.Elements(),
+ state.dashOffset);
+
+ return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
+}
+
+// Returns a surface that contains only the part needed to draw aSourceRect.
+// On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
+// relative to the returned surface.
+static already_AddRefed<SourceSurface>
+ExtractSubrect(SourceSurface* aSurface, gfx::Rect* aSourceRect, DrawTarget* aTargetDT)
+{
+ gfx::Rect roundedOutSourceRect = *aSourceRect;
+ roundedOutSourceRect.RoundOut();
+ gfx::IntRect roundedOutSourceRectInt;
+ if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ RefPtr<DrawTarget> subrectDT =
+ aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
+
+ if (!subrectDT) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ *aSourceRect -= roundedOutSourceRect.TopLeft();
+
+ subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
+ return subrectDT->Snapshot();
+}
+
+//
+// image
+//
+
+static void
+ClipImageDimension(double& aSourceCoord, double& aSourceSize, int32_t aImageSize,
+ double& aDestCoord, double& aDestSize)
+{
+ double scale = aDestSize / aSourceSize;
+ if (aSourceCoord < 0.0) {
+ double destEnd = aDestCoord + aDestSize;
+ aDestCoord -= aSourceCoord * scale;
+ aDestSize = destEnd - aDestCoord;
+ aSourceSize += aSourceCoord;
+ aSourceCoord = 0.0;
+ }
+ double delta = aImageSize - (aSourceCoord + aSourceSize);
+ if (delta < 0.0) {
+ aDestSize += delta * scale;
+ aSourceSize = aImageSize - aSourceCoord;
+ }
+}
+
+// Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
+// to pull a SourceSurface from our cache. This allows us to avoid
+// reoptimizing surfaces if content and canvas backends are different.
+nsLayoutUtils::SurfaceFromElementResult
+CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement)
+{
+ nsLayoutUtils::SurfaceFromElementResult res;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
+ if (!imageLoader) {
+ return res;
+ }
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+ if (!imgRequest) {
+ return res;
+ }
+
+ uint32_t status = 0;
+ if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
+ !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ return res;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
+ !principal) {
+ return res;
+ }
+
+ res.mSourceSurface =
+ CanvasImageCache::LookupAllCanvas(aElement, mIsSkiaGL);
+ if (!res.mSourceSurface) {
+ return res;
+ }
+
+ int32_t corsmode = imgIRequest::CORS_NONE;
+ if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
+ res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
+ }
+
+ res.mSize = res.mSourceSurface->GetSize();
+ res.mPrincipal = principal.forget();
+ res.mIsWriteOnly = false;
+ res.mImageRequest = imgRequest.forget();
+
+ return res;
+}
+
+// drawImage(in HTMLImageElement image, in float dx, in float dy);
+// -- render image from 0,0 at dx,dy top-left coords
+// drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw, in float dh);
+// -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
+// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
+// -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
+
+// If only dx and dy are passed in then optional_argc should be 0. If only
+// dx, dy, dw and dh are passed in then optional_argc should be 2. The only
+// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
+// are all passed in.
+
+void
+CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
+ double aSx, double aSy, double aSw,
+ double aSh, double aDx, double aDy,
+ double aDw, double aDh,
+ uint8_t aOptional_argc,
+ ErrorResult& aError)
+{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
+ }
+
+ MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6);
+
+ if (!ValidateRect(aDx, aDy, aDw, aDh, true)) {
+ return;
+ }
+ if (aOptional_argc == 6) {
+ if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
+ return;
+ }
+ }
+
+ RefPtr<SourceSurface> srcSurf;
+ gfx::IntSize imgSize;
+
+ Element* element = nullptr;
+
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ if (aImage.IsHTMLCanvasElement()) {
+ HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
+ element = canvas;
+ nsIntSize size = canvas->GetSize();
+ if (size.width == 0 || size.height == 0) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ } else if (aImage.IsImageBitmap()) {
+ ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
+ srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
+
+ if (!srcSurf) {
+ return;
+ }
+
+ imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
+ }
+ else {
+ if (aImage.IsHTMLImageElement()) {
+ HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
+ element = img;
+ } else {
+ HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
+ video->MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
+ element = video;
+ }
+
+ srcSurf =
+ CanvasImageCache::LookupCanvas(element, mCanvasElement, &imgSize, mIsSkiaGL);
+ }
+
+ nsLayoutUtils::DirectDrawInfo drawInfo;
+
+#ifdef USE_SKIA_GPU
+ if (mRenderingMode == RenderingMode::OpenGLBackendMode &&
+ mIsSkiaGL &&
+ !srcSurf &&
+ aImage.IsHTMLVideoElement() &&
+ AllowOpenGLCanvas()) {
+ mozilla::gl::GLContext* gl = gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext();
+ MOZ_ASSERT(gl);
+
+ HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
+ if (!video) {
+ return;
+ }
+
+ if (video->ContainsRestrictedContent()) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ uint16_t readyState;
+ if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
+ readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
+ // still loading, just return
+ return;
+ }
+
+ // If it doesn't have a principal, just bail
+ nsCOMPtr<nsIPrincipal> principal = video->GetCurrentVideoPrincipal();
+ if (!principal) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ mozilla::layers::ImageContainer* container = video->GetImageContainer();
+ if (!container) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ AutoLockImage lockImage(container);
+ layers::Image* srcImage = lockImage.GetImage();
+ if (!srcImage) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ gl->MakeCurrent();
+ GLuint videoTexture = 0;
+ gl->fGenTextures(1, &videoTexture);
+ // skiaGL expect upload on drawing, and uses texture 0 for texturing,
+ // so we must active texture 0 and bind the texture for it.
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl->fBindTexture(LOCAL_GL_TEXTURE_2D, videoTexture);
+
+ gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+
+ const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
+ bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
+ videoTexture, LOCAL_GL_TEXTURE_2D,
+ destOrigin);
+ if (ok) {
+ NativeSurface texSurf;
+ texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
+ texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
+ texSurf.mSize.width = srcImage->GetSize().width;
+ texSurf.mSize.height = srcImage->GetSize().height;
+ texSurf.mSurface = (void*)((uintptr_t)videoTexture);
+
+ srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
+ if (!srcSurf) {
+ gl->fDeleteTextures(1, &videoTexture);
+ }
+ imgSize.width = srcImage->GetSize().width;
+ imgSize.height = srcImage->GetSize().height;
+
+ int32_t displayWidth = video->VideoWidth();
+ int32_t displayHeight = video->VideoHeight();
+ aSw *= (double)imgSize.width / (double)displayWidth;
+ aSh *= (double)imgSize.height / (double)displayHeight;
+ } else {
+ gl->fDeleteTextures(1, &videoTexture);
+ }
+ srcImage = nullptr;
+
+ if (mCanvasElement) {
+ CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
+ principal, false,
+ video->GetCORSMode() != CORS_NONE);
+ }
+ }
+#endif
+ if (!srcSurf) {
+ // The canvas spec says that drawImage should draw the first frame
+ // of animated images. We also don't want to rasterize vector images.
+ uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME |
+ nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
+
+ nsLayoutUtils::SurfaceFromElementResult res =
+ CanvasRenderingContext2D::CachedSurfaceFromElement(element);
+
+ if (!res.mSourceSurface) {
+ res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
+ }
+
+ if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
+ // The spec says to silently do nothing in the following cases:
+ // - The element is still loading.
+ // - The image is bad, but it's not in the broken state (i.e., we could
+ // decode the headers and get the size).
+ if (!res.mIsStillLoading && !res.mHasSize) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ }
+ return;
+ }
+
+ imgSize = res.mSize;
+
+ // Scale sw/sh based on aspect ratio
+ if (aImage.IsHTMLVideoElement()) {
+ HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
+ int32_t displayWidth = video->VideoWidth();
+ int32_t displayHeight = video->VideoHeight();
+ aSw *= (double)imgSize.width / (double)displayWidth;
+ aSh *= (double)imgSize.height / (double)displayHeight;
+ }
+
+ if (mCanvasElement) {
+ CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
+ res.mPrincipal, res.mIsWriteOnly,
+ res.mCORSUsed);
+ }
+
+ if (res.mSourceSurface) {
+ if (res.mImageRequest) {
+ CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mSourceSurface, imgSize, mIsSkiaGL);
+ }
+ srcSurf = res.mSourceSurface;
+ } else {
+ drawInfo = res.mDrawInfo;
+ }
+ }
+
+ if (aOptional_argc == 0) {
+ aSx = aSy = 0.0;
+ aDw = aSw = (double) imgSize.width;
+ aDh = aSh = (double) imgSize.height;
+ } else if (aOptional_argc == 2) {
+ aSx = aSy = 0.0;
+ aSw = (double) imgSize.width;
+ aSh = (double) imgSize.height;
+ }
+
+ if (aSw == 0.0 || aSh == 0.0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ ClipImageDimension(aSx, aSw, imgSize.width, aDx, aDw);
+ ClipImageDimension(aSy, aSh, imgSize.height, aDy, aDh);
+
+ if (aSw <= 0.0 || aSh <= 0.0 ||
+ aDw <= 0.0 || aDh <= 0.0) {
+ // source and/or destination are fully clipped, so nothing is painted
+ return;
+ }
+
+ SamplingFilter samplingFilter;
+ AntialiasMode antialiasMode;
+
+ if (CurrentState().imageSmoothingEnabled) {
+ samplingFilter = gfx::SamplingFilter::LINEAR;
+ antialiasMode = AntialiasMode::DEFAULT;
+ } else {
+ samplingFilter = gfx::SamplingFilter::POINT;
+ antialiasMode = AntialiasMode::NONE;
+ }
+
+ const bool needBounds = NeedToCalculateBounds();
+ if (!IsTargetValid()) {
+ return;
+ }
+ gfx::Rect bounds;
+ if (needBounds) {
+ bounds = gfx::Rect(aDx, aDy, aDw, aDh);
+ bounds = mTarget->GetTransform().TransformBounds(bounds);
+ }
+
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (srcSurf) {
+ gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
+ if (element == mCanvasElement) {
+ // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
+ // trigger a COW copy of the whole canvas into srcSurf. That's a huge
+ // waste if sourceRect doesn't cover the whole canvas.
+ // We avoid copying the whole canvas by manually copying just the part
+ // that we need.
+ srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
+ }
+ AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds);
+ if (!tempTarget) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ auto op = UsedOperation();
+ if (!IsTargetValid() || !tempTarget) {
+ return;
+ }
+ tempTarget->
+ DrawSurface(srcSurf,
+ gfx::Rect(aDx, aDy, aDw, aDh),
+ sourceRect,
+ DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
+ DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
+ } else {
+ DrawDirectlyToCanvas(drawInfo, &bounds,
+ gfx::Rect(aDx, aDy, aDw, aDh),
+ gfx::Rect(aSx, aSy, aSw, aSh),
+ imgSize);
+ }
+
+ RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
+}
+
+void
+CanvasRenderingContext2D::DrawDirectlyToCanvas(
+ const nsLayoutUtils::DirectDrawInfo& aImage,
+ gfx::Rect* aBounds,
+ gfx::Rect aDest,
+ gfx::Rect aSrc,
+ gfx::IntSize aImgSize)
+{
+ MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0,
+ "Need positive source width and height");
+
+ AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr: aBounds);
+ if (!tempTarget) {
+ return;
+ }
+
+ // Get any existing transforms on the context, including transformations used
+ // for context shadow.
+ Matrix matrix = tempTarget->GetTransform();
+ gfxMatrix contextMatrix;
+ contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21,
+ matrix._22, matrix._31, matrix._32);
+ gfxSize contextScale(contextMatrix.ScaleFactors(true));
+
+ // Scale the dest rect to include the context scale.
+ aDest.Scale(contextScale.width, contextScale.height);
+
+ // Scale the image size to the dest rect, and adjust the source rect to match.
+ gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height);
+ IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width,
+ aImgSize.height * scale.height);
+ aSrc.Scale(scale.width, scale.height);
+
+ // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
+ // the matrix even though this is a temp gfxContext.
+ AutoRestoreTransform autoRestoreTransform(mTarget);
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
+ if (!context) {
+ gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
+ return;
+ }
+ context->SetMatrix(contextMatrix.
+ Scale(1.0 / contextScale.width,
+ 1.0 / contextScale.height).
+ Translate(aDest.x - aSrc.x, aDest.y - aSrc.y));
+
+ // FLAG_CLAMP is added for increased performance, since we never tile here.
+ uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
+
+ CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
+ SVGImageContext svgContext(sz, Nothing(), CurrentState().globalAlpha);
+
+ auto result = aImage.mImgContainer->
+ Draw(context, scaledImageSize,
+ ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
+ aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags);
+
+ if (result != DrawResult::SUCCESS) {
+ NS_WARNING("imgIContainer::Draw failed");
+ }
+}
+
+void
+CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& aOp,
+ ErrorResult& aError)
+{
+ CompositionOp comp_op;
+
+#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
+ if (aOp.EqualsLiteral(cvsop)) \
+ comp_op = CompositionOp::OP_##op2d;
+
+ CANVAS_OP_TO_GFX_OP("copy", SOURCE)
+ else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
+ else CANVAS_OP_TO_GFX_OP("source-in", IN)
+ else CANVAS_OP_TO_GFX_OP("source-out", OUT)
+ else CANVAS_OP_TO_GFX_OP("source-over", OVER)
+ else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
+ else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
+ else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
+ else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
+ else CANVAS_OP_TO_GFX_OP("lighter", ADD)
+ else CANVAS_OP_TO_GFX_OP("xor", XOR)
+ else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
+ else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
+ else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
+ else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
+ else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
+ else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
+ else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
+ else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
+ else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
+ else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
+ else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
+ else CANVAS_OP_TO_GFX_OP("hue", HUE)
+ else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
+ else CANVAS_OP_TO_GFX_OP("color", COLOR)
+ else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ else return;
+
+#undef CANVAS_OP_TO_GFX_OP
+ CurrentState().op = comp_op;
+}
+
+void
+CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& aOp,
+ ErrorResult& aError)
+{
+ CompositionOp comp_op = CurrentState().op;
+
+#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
+ if (comp_op == CompositionOp::OP_##op2d) \
+ aOp.AssignLiteral(cvsop);
+
+ CANVAS_OP_TO_GFX_OP("copy", SOURCE)
+ else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
+ else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
+ else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
+ else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
+ else CANVAS_OP_TO_GFX_OP("lighter", ADD)
+ else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
+ else CANVAS_OP_TO_GFX_OP("source-in", IN)
+ else CANVAS_OP_TO_GFX_OP("source-out", OUT)
+ else CANVAS_OP_TO_GFX_OP("source-over", OVER)
+ else CANVAS_OP_TO_GFX_OP("xor", XOR)
+ else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
+ else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
+ else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
+ else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
+ else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
+ else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
+ else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
+ else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
+ else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
+ else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
+ else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
+ else CANVAS_OP_TO_GFX_OP("hue", HUE)
+ else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
+ else CANVAS_OP_TO_GFX_OP("color", COLOR)
+ else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
+ else {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+
+#undef CANVAS_OP_TO_GFX_OP
+}
+
+void
+CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& aWindow, double aX,
+ double aY, double aW, double aH,
+ const nsAString& aBgColor,
+ uint32_t aFlags, ErrorResult& aError)
+{
+ MOZ_ASSERT(aWindow.IsInnerWindow());
+
+ if (int32_t(aW) == 0 || int32_t(aH) == 0) {
+ return;
+ }
+
+ // protect against too-large surfaces that will cause allocation
+ // or overflow issues
+ if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Flush layout updates
+ if (!(aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) {
+ nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow());
+ }
+
+ CompositionOp op = UsedOperation();
+ bool discardContent = GlobalAlpha() == 1.0f
+ && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
+ const gfx::Rect drawRect(aX, aY, aW, aH);
+ EnsureTarget(discardContent ? &drawRect : nullptr);
+
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ // We can't allow web apps to call this until we fix at least the
+ // following potential security issues:
+ // -- rendering cross-domain IFRAMEs and then extracting the results
+ // -- rendering the user's theme and then extracting the results
+ // -- rendering native anonymous content (e.g., file input paths;
+ // scrollbars should be allowed)
+ if (!nsContentUtils::IsCallerChrome()) {
+ // not permitted to use DrawWindow
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ RefPtr<nsPresContext> presContext;
+ nsIDocShell* docshell = aWindow.GetDocShell();
+ if (docshell) {
+ docshell->GetPresContext(getter_AddRefs(presContext));
+ }
+ if (!presContext) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nscolor backgroundColor;
+ if (!ParseColor(aBgColor, &backgroundColor)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsRect r(nsPresContext::CSSPixelsToAppUnits((float)aX),
+ nsPresContext::CSSPixelsToAppUnits((float)aY),
+ nsPresContext::CSSPixelsToAppUnits((float)aW),
+ nsPresContext::CSSPixelsToAppUnits((float)aH));
+ uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+ nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) {
+ renderDocFlags |= nsIPresShell::RENDER_CARET;
+ }
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) {
+ renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+ nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+ }
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) {
+ renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS;
+ }
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) {
+ renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES;
+ }
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) {
+ renderDocFlags |= nsIPresShell::RENDER_DRAWWINDOW_NOT_FLUSHING;
+ }
+
+ // gfxContext-over-Azure may modify the DrawTarget's transform, so
+ // save and restore it
+ Matrix matrix = mTarget->GetTransform();
+ double sw = matrix._11 * aW;
+ double sh = matrix._22 * aH;
+ if (!sw || !sh) {
+ return;
+ }
+
+ RefPtr<gfxContext> thebes;
+ RefPtr<DrawTarget> drawDT;
+ // Rendering directly is faster and can be done if mTarget supports Azure
+ // and does not need alpha blending.
+ // Since the pre-transaction callback calls ReturnTarget, we can't have a
+ // gfxContext wrapped around it when using a shared buffer provider because
+ // the DrawTarget's shared buffer may be unmapped in ReturnTarget.
+ op = CompositionOp::OP_ADD;
+ if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
+ GlobalAlpha() == 1.0f) {
+ op = UsedOperation();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+ if (op == CompositionOp::OP_OVER &&
+ (!mBufferProvider || mBufferProvider->GetType() != LayersBackend::LAYERS_CLIENT))
+ {
+ thebes = gfxContext::CreateOrNull(mTarget);
+ MOZ_ASSERT(thebes); // already checked the draw target above
+ // (in SupportsAzureContentForDrawTarget)
+ thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
+ matrix._22, matrix._31, matrix._32));
+ } else {
+ IntSize dtSize = IntSize::Ceil(sw, sh);
+ if (!Factory::AllowedSurfaceSize(dtSize)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ drawDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(dtSize,
+ SurfaceFormat::B8G8R8A8);
+ if (!drawDT || !drawDT->IsValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ thebes = gfxContext::CreateOrNull(drawDT);
+ MOZ_ASSERT(thebes); // alrady checked the draw target above
+ thebes->SetMatrix(gfxMatrix::Scaling(matrix._11, matrix._22));
+ }
+
+ nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
+
+ Unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes);
+ // If this canvas was contained in the drawn window, the pre-transaction callback
+ // may have returned its DT. If so, we must reacquire it here.
+ EnsureTarget(discardContent ? &drawRect : nullptr);
+
+ if (drawDT) {
+ RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
+ if (NS_WARN_IF(!snapshot)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
+ if (!data || !Factory::AllowedSurfaceSize(data->GetSize())) {
+ gfxCriticalError() << "Unexpected invalid data source surface " <<
+ (data ? data->GetSize() : IntSize(0,0));
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ DataSourceSurface::MappedSurface rawData;
+ if (NS_WARN_IF(!data->Map(DataSourceSurface::READ, &rawData))) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ RefPtr<SourceSurface> source =
+ mTarget->CreateSourceSurfaceFromData(rawData.mData,
+ data->GetSize(),
+ rawData.mStride,
+ data->GetFormat());
+ data->Unmap();
+
+ if (!source) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ op = UsedOperation();
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ gfx::Rect destRect(0, 0, aW, aH);
+ gfx::Rect sourceRect(0, 0, sw, sh);
+ mTarget->DrawSurface(source, destRect, sourceRect,
+ DrawSurfaceOptions(gfx::SamplingFilter::POINT),
+ DrawOptions(GlobalAlpha(), op,
+ AntialiasMode::NONE));
+ } else {
+ mTarget->SetTransform(matrix);
+ }
+
+ // note that x and y are coordinates in the document that
+ // we're drawing; x and y are drawn to 0,0 in current user
+ // space.
+ RedrawUser(gfxRect(0, 0, aW, aH));
+}
+
+void
+CanvasRenderingContext2D::AsyncDrawXULElement(nsXULElement& aElem,
+ double aX, double aY,
+ double aW, double aH,
+ const nsAString& aBgColor,
+ uint32_t aFlags,
+ ErrorResult& aError)
+{
+ // We can't allow web apps to call this until we fix at least the
+ // following potential security issues:
+ // -- rendering cross-domain IFRAMEs and then extracting the results
+ // -- rendering the user's theme and then extracting the results
+ // -- rendering native anonymous content (e.g., file input paths;
+ // scrollbars should be allowed)
+ if (!nsContentUtils::IsCallerChrome()) {
+ // not permitted to use DrawWindow
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+#if 0
+ nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(&elem);
+ if (!loaderOwner) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader();
+ if (!frameloader) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ PBrowserParent *child = frameloader->GetRemoteBrowser();
+ if (!child) {
+ nsIDocShell* docShell = frameLoader->GetExistingDocShell();
+ if (!docShell) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
+ if (!window) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ return DrawWindow(window->GetCurrentInnerWindow(), aX, aY, aW, aH,
+ aBgColor, aFlags);
+ }
+
+ // protect against too-large surfaces that will cause allocation
+ // or overflow issues
+ if (!Factory::CheckSurfaceSize(IntSize(aW, aH), 0xffff)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ bool flush =
+ (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0;
+
+ uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) {
+ renderDocFlags |= nsIPresShell::RENDER_CARET;
+ }
+ if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) {
+ renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
+ }
+
+ nsRect rect(nsPresContext::CSSPixelsToAppUnits(aX),
+ nsPresContext::CSSPixelsToAppUnits(aY),
+ nsPresContext::CSSPixelsToAppUnits(aW),
+ nsPresContext::CSSPixelsToAppUnits(aH));
+ if (mIPC) {
+ PDocumentRendererParent *pdocrender =
+ child->SendPDocumentRendererConstructor(rect,
+ mThebes->CurrentMatrix(),
+ nsString(aBGColor),
+ renderDocFlags, flush,
+ nsIntSize(mWidth, mHeight));
+ if (!pdocrender)
+ return NS_ERROR_FAILURE;
+
+ DocumentRendererParent *docrender =
+ static_cast<DocumentRendererParent *>(pdocrender);
+
+ docrender->SetCanvasContext(this, mThebes);
+ }
+#endif
+}
+
+//
+// device pixel getting/setting
+//
+
+already_AddRefed<ImageData>
+CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
+ double aSy, double aSw,
+ double aSh, ErrorResult& aError)
+{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
+ }
+
+ if (!mCanvasElement && !mDocShell) {
+ NS_ERROR("No canvas element and no docshell in GetImageData!!!");
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Check only if we have a canvas element; if we were created with a docshell,
+ // then it's special internal use.
+ if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
+ !nsContentUtils::IsCallerChrome())
+ {
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ if (!IsFinite(aSx) || !IsFinite(aSy) ||
+ !IsFinite(aSw) || !IsFinite(aSh)) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (!aSw || !aSh) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ int32_t x = JS::ToInt32(aSx);
+ int32_t y = JS::ToInt32(aSy);
+ int32_t wi = JS::ToInt32(aSw);
+ int32_t hi = JS::ToInt32(aSh);
+
+ // Handle negative width and height by flipping the rectangle over in the
+ // relevant direction.
+ uint32_t w, h;
+ if (aSw < 0) {
+ w = -wi;
+ x -= w;
+ } else {
+ w = wi;
+ }
+ if (aSh < 0) {
+ h = -hi;
+ y -= h;
+ } else {
+ h = hi;
+ }
+
+ if (w == 0) {
+ w = 1;
+ }
+ if (h == 0) {
+ h = 1;
+ }
+
+ JS::Rooted<JSObject*> array(aCx);
+ aError = GetImageDataArray(aCx, x, y, w, h, array.address());
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(array);
+
+ RefPtr<ImageData> imageData = new ImageData(w, h, *array);
+ return imageData.forget();
+}
+
+nsresult
+CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
+ int32_t aX,
+ int32_t aY,
+ uint32_t aWidth,
+ uint32_t aHeight,
+ JSObject** aRetval)
+{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
+ }
+
+ MOZ_ASSERT(aWidth && aHeight);
+
+ CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
+ if (!len.isValid()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth;
+ CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight;
+
+ if (!rightMost.isValid() || !bottomMost.isValid()) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value()));
+ if (!darray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mZero) {
+ *aRetval = darray;
+ return NS_OK;
+ }
+
+ IntRect srcRect(0, 0, mWidth, mHeight);
+ IntRect destRect(aX, aY, aWidth, aHeight);
+ IntRect srcReadRect = srcRect.Intersect(destRect);
+ RefPtr<DataSourceSurface> readback;
+ DataSourceSurface::MappedSurface rawData;
+ if (!srcReadRect.IsEmpty()) {
+ RefPtr<SourceSurface> snapshot;
+ if (!mTarget && mBufferProvider) {
+ snapshot = mBufferProvider->BorrowSnapshot();
+ } else {
+ EnsureTarget();
+ if (!IsTargetValid()) {
+ return NS_ERROR_FAILURE;
+ }
+ snapshot = mTarget->Snapshot();
+ }
+
+ if (snapshot) {
+ readback = snapshot->GetDataSurface();
+ }
+
+ if (!mTarget && mBufferProvider) {
+ mBufferProvider->ReturnSnapshot(snapshot.forget());
+ }
+
+ if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ IntRect dstWriteRect = srcReadRect;
+ dstWriteRect.MoveBy(-aX, -aY);
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
+ MOZ_ASSERT(!isShared); // Should not happen, data was created above
+
+ uint8_t* src;
+ uint32_t srcStride;
+ if (readback) {
+ srcStride = rawData.mStride;
+ src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
+ } else {
+ src = data;
+ srcStride = aWidth * 4;
+ }
+
+ uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
+
+ if (mOpaque) {
+ for (int32_t j = 0; j < dstWriteRect.height; ++j) {
+ for (int32_t i = 0; i < dstWriteRect.width; ++i) {
+ // XXX Is there some useful swizzle MMX we can use here?
+#if MOZ_LITTLE_ENDIAN
+ uint8_t b = *src++;
+ uint8_t g = *src++;
+ uint8_t r = *src++;
+ src++;
+#else
+ src++;
+ uint8_t r = *src++;
+ uint8_t g = *src++;
+ uint8_t b = *src++;
+#endif
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+ *dst++ = 255;
+ }
+ src += srcStride - (dstWriteRect.width * 4);
+ dst += (aWidth * 4) - (dstWriteRect.width * 4);
+ }
+ } else
+ for (int32_t j = 0; j < dstWriteRect.height; ++j) {
+ for (int32_t i = 0; i < dstWriteRect.width; ++i) {
+ // XXX Is there some useful swizzle MMX we can use here?
+#if MOZ_LITTLE_ENDIAN
+ uint8_t b = *src++;
+ uint8_t g = *src++;
+ uint8_t r = *src++;
+ uint8_t a = *src++;
+#else
+ uint8_t a = *src++;
+ uint8_t r = *src++;
+ uint8_t g = *src++;
+ uint8_t b = *src++;
+#endif
+ // Convert to non-premultiplied color
+ *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + r];
+ *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + g];
+ *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + b];
+ *dst++ = a;
+ }
+ src += srcStride - (dstWriteRect.width * 4);
+ dst += (aWidth * 4) - (dstWriteRect.width * 4);
+ }
+
+ if (readback) {
+ readback->Unmap();
+ }
+
+ *aRetval = darray;
+ return NS_OK;
+}
+
+void
+CanvasRenderingContext2D::EnsureErrorTarget()
+{
+ if (sErrorTarget) {
+ return;
+ }
+
+ RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8);
+ MOZ_ASSERT(errorTarget, "Failed to allocate the error target!");
+
+ sErrorTarget = errorTarget;
+ NS_ADDREF(sErrorTarget);
+}
+
+void
+CanvasRenderingContext2D::FillRuleChanged()
+{
+ if (mPath) {
+ mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
+ mPath = nullptr;
+ }
+}
+
+void
+CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
+ double aDy, ErrorResult& aError)
+{
+ RootedTypedArray<Uint8ClampedArray> arr(RootingCx());
+ DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
+ MOZ_ASSERT(inited);
+
+ aError = PutImageData_explicit(JS::ToInt32(aDx), JS::ToInt32(aDy),
+ aImageData.Width(), aImageData.Height(),
+ &arr, false, 0, 0, 0, 0);
+}
+
+void
+CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
+ double aDy, double aDirtyX,
+ double aDirtyY, double aDirtyWidth,
+ double aDirtyHeight,
+ ErrorResult& aError)
+{
+ RootedTypedArray<Uint8ClampedArray> arr(RootingCx());
+ DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
+ MOZ_ASSERT(inited);
+
+ aError = PutImageData_explicit(JS::ToInt32(aDx), JS::ToInt32(aDy),
+ aImageData.Width(), aImageData.Height(),
+ &arr, true,
+ JS::ToInt32(aDirtyX),
+ JS::ToInt32(aDirtyY),
+ JS::ToInt32(aDirtyWidth),
+ JS::ToInt32(aDirtyHeight));
+}
+
+nsresult
+CanvasRenderingContext2D::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)
+{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::PutImageData);
+ }
+
+ if (aW == 0 || aH == 0) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ IntRect dirtyRect;
+ IntRect imageDataRect(0, 0, aW, aH);
+
+ if (aHasDirtyRect) {
+ // fix up negative dimensions
+ if (aDirtyWidth < 0) {
+ NS_ENSURE_TRUE(aDirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR);
+
+ CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth;
+
+ if (!checkedDirtyX.isValid())
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+
+ aDirtyX = checkedDirtyX.value();
+ aDirtyWidth = -aDirtyWidth;
+ }
+
+ if (aDirtyHeight < 0) {
+ NS_ENSURE_TRUE(aDirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR);
+
+ CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight;
+
+ if (!checkedDirtyY.isValid())
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+
+ aDirtyY = checkedDirtyY.value();
+ aDirtyHeight = -aDirtyHeight;
+ }
+
+ // bound the dirty rect within the imageData rectangle
+ dirtyRect = imageDataRect.Intersect(IntRect(aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight));
+
+ if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0)
+ return NS_OK;
+ } else {
+ dirtyRect = imageDataRect;
+ }
+
+ dirtyRect.MoveBy(IntPoint(aX, aY));
+ dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect);
+
+ if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) {
+ return NS_OK;
+ }
+
+ aArray->ComputeLengthAndData();
+
+ uint32_t dataLen = aArray->Length();
+
+ uint32_t len = aW * aH * 4;
+ if (dataLen != len) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ uint32_t copyWidth = dirtyRect.Width();
+ uint32_t copyHeight = dirtyRect.Height();
+ RefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(gfx::IntSize(copyWidth, copyHeight),
+ SurfaceFormat::A8R8G8B8_UINT32,
+ false);
+ if (!imgsurf || imgsurf->CairoStatus()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t copyX = dirtyRect.x - aX;
+ uint32_t copyY = dirtyRect.y - aY;
+ //uint8_t *src = aArray->Data();
+ uint8_t *dst = imgsurf->Data();
+ uint8_t* srcLine = aArray->Data() + copyY * (aW * 4) + copyX * 4;
+ // For opaque canvases, we must still premultiply the RGB components, but write the alpha as opaque.
+ uint8_t alphaMask = mOpaque ? 255 : 0;
+#if 0
+ printf("PutImageData_explicit: dirty x=%d y=%d w=%d h=%d copy x=%d y=%d w=%d h=%d ext x=%d y=%d w=%d h=%d\n",
+ dirtyRect.x, dirtyRect.y, copyWidth, copyHeight,
+ copyX, copyY, copyWidth, copyHeight,
+ x, y, w, h);
+#endif
+ for (uint32_t j = 0; j < copyHeight; j++) {
+ uint8_t *src = srcLine;
+ for (uint32_t i = 0; i < copyWidth; i++) {
+ uint8_t r = *src++;
+ uint8_t g = *src++;
+ uint8_t b = *src++;
+ uint8_t a = *src++;
+
+ // Convert to premultiplied color (losslessly if the input came from getImageData)
+#if MOZ_LITTLE_ENDIAN
+ *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b];
+ *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g];
+ *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r];
+ *dst++ = a | alphaMask;
+#else
+ *dst++ = a | alphaMask;
+ *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r];
+ *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g];
+ *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b];
+#endif
+ }
+ srcLine += aW * 4;
+ }
+
+ // The canvas spec says that the current path, transformation matrix, shadow attributes,
+ // global alpha, the clipping region, and global composition operator must not affect the
+ // getImageData() and putImageData() methods.
+ const gfx::Rect putRect(dirtyRect);
+ EnsureTarget(&putRect);
+
+ if (!IsTargetValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<SourceSurface> sourceSurface =
+ mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(copyWidth, copyHeight), imgsurf->Stride(), SurfaceFormat::B8G8R8A8);
+
+ // In certain scenarios, requesting larger than 8k image fails. Bug 803568
+ // covers the details of how to run into it, but the full detailed
+ // investigation hasn't been done to determine the underlying cause. We
+ // will just handle the failure to allocate the surface to avoid a crash.
+ if (!sourceSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTarget->CopySurface(sourceSurface,
+ IntRect(0, 0,
+ dirtyRect.width, dirtyRect.height),
+ IntPoint(dirtyRect.x, dirtyRect.y));
+
+ Redraw(gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height));
+
+ return NS_OK;
+}
+
+static already_AddRefed<ImageData>
+CreateImageData(JSContext* aCx, CanvasRenderingContext2D* aContext,
+ uint32_t aW, uint32_t aH, ErrorResult& aError)
+{
+ if (aW == 0)
+ aW = 1;
+ if (aH == 0)
+ aH = 1;
+
+ CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4;
+ if (!len.isValid()) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ // Create the fast typed array; it's initialized to 0 by default.
+ JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value());
+ if (!darray) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ RefPtr<mozilla::dom::ImageData> imageData =
+ new mozilla::dom::ImageData(aW, aH, *darray);
+ return imageData.forget();
+}
+
+already_AddRefed<ImageData>
+CanvasRenderingContext2D::CreateImageData(JSContext* aCx, double aSw,
+ double aSh, ErrorResult& aError)
+{
+ if (!aSw || !aSh) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ int32_t wi = JS::ToInt32(aSw);
+ int32_t hi = JS::ToInt32(aSh);
+
+ uint32_t w = Abs(wi);
+ uint32_t h = Abs(hi);
+ return mozilla::dom::CreateImageData(aCx, this, w, h, aError);
+}
+
+already_AddRefed<ImageData>
+CanvasRenderingContext2D::CreateImageData(JSContext* aCx,
+ ImageData& aImagedata,
+ ErrorResult& aError)
+{
+ return mozilla::dom::CreateImageData(aCx, this, aImagedata.Width(),
+ aImagedata.Height(), aError);
+}
+
+static uint8_t g2DContextLayerUserData;
+
+
+uint32_t
+CanvasRenderingContext2D::SkiaGLTex() const
+{
+ if (!mTarget) {
+ return 0;
+ }
+ MOZ_ASSERT(IsTargetValid());
+ return (uint32_t)(uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE);
+}
+
+void CanvasRenderingContext2D::RemoveDrawObserver()
+{
+ if (mDrawObserver) {
+ delete mDrawObserver;
+ mDrawObserver = nullptr;
+ }
+}
+
+already_AddRefed<Layer>
+CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+ Layer* aOldLayer,
+ LayerManager* aManager,
+ bool aMirror /* = false */)
+{
+ if (aMirror) {
+ // Not supported for CanvasRenderingContext2D
+ return nullptr;
+ }
+
+ if (mOpaque || mIsSkiaGL) {
+ // If we're opaque then make sure we have a surface so we paint black
+ // instead of transparent.
+ // If we're using SkiaGL, then SkiaGLTex() below needs the target to
+ // be accessible.
+ EnsureTarget();
+ }
+
+ // Don't call EnsureTarget() ... if there isn't already a surface, then
+ // we have nothing to paint and there is no need to create a surface just
+ // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
+ // layer manager which must NOT happen during a paint.
+ if ((!mBufferProvider && !mTarget) || !IsTargetValid()) {
+ // No DidTransactionCallback will be received, so mark the context clean
+ // now so future invalidations will be dispatched.
+ MarkContextClean();
+ return nullptr;
+ }
+
+ if (!mResetLayer && aOldLayer) {
+ auto userData =
+ static_cast<CanvasRenderingContext2DUserData*>(
+ aOldLayer->GetUserData(&g2DContextLayerUserData));
+
+ CanvasLayer::Data data;
+
+ if (mIsSkiaGL) {
+ GLuint skiaGLTex = SkiaGLTex();
+ if (skiaGLTex) {
+ SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
+ MOZ_ASSERT(glue);
+ data.mGLContext = glue->GetGLContext();
+ data.mFrontbufferGLTex = skiaGLTex;
+ }
+ }
+
+ data.mBufferProvider = mBufferProvider;
+
+ if (userData &&
+ userData->IsForContext(this) &&
+ static_cast<CanvasLayer*>(aOldLayer)->IsDataValid(data)) {
+ RefPtr<Layer> ret = aOldLayer;
+ return ret.forget();
+ }
+ }
+
+ RefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer();
+ if (!canvasLayer) {
+ NS_WARNING("CreateCanvasLayer returned null!");
+ // No DidTransactionCallback will be received, so mark the context clean
+ // now so future invalidations will be dispatched.
+ MarkContextClean();
+ return nullptr;
+ }
+ CanvasRenderingContext2DUserData* userData = nullptr;
+ // Make the layer tell us whenever a transaction finishes (including
+ // the current transaction), so we can clear our invalidation state and
+ // start invalidating again. We need to do this for all layers since
+ // callers of DrawWindow may be expecting to receive normal invalidation
+ // notifications after this paint.
+
+ // The layer will be destroyed when we tear down the presentation
+ // (at the latest), at which time this userData will be destroyed,
+ // releasing the reference to the element.
+ // The userData will receive DidTransactionCallbacks, which flush the
+ // the invalidation state to indicate that the canvas is up to date.
+ userData = new CanvasRenderingContext2DUserData(this);
+ canvasLayer->SetDidTransactionCallback(
+ CanvasRenderingContext2DUserData::DidTransactionCallback, userData);
+ canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
+
+ CanvasLayer::Data data;
+ data.mSize = GetSize();
+ data.mHasAlpha = !mOpaque;
+
+ canvasLayer->SetPreTransactionCallback(
+ CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
+
+
+ if (mIsSkiaGL) {
+ GLuint skiaGLTex = SkiaGLTex();
+ if (skiaGLTex) {
+ SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
+ MOZ_ASSERT(glue);
+ data.mGLContext = glue->GetGLContext();
+ data.mFrontbufferGLTex = skiaGLTex;
+ }
+ }
+
+ data.mBufferProvider = mBufferProvider;
+
+ canvasLayer->Initialize(data);
+ uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
+ canvasLayer->SetContentFlags(flags);
+ canvasLayer->Updated();
+
+ mResetLayer = false;
+
+ return canvasLayer.forget();
+}
+
+void
+CanvasRenderingContext2D::MarkContextClean()
+{
+ if (mInvalidateCount > 0) {
+ mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
+ }
+ mIsEntireFrameInvalid = false;
+ mInvalidateCount = 0;
+}
+
+void
+CanvasRenderingContext2D::MarkContextCleanForFrameCapture()
+{
+ mIsCapturedFrameInvalid = false;
+}
+
+bool
+CanvasRenderingContext2D::IsContextCleanForFrameCapture()
+{
+ return !mIsCapturedFrameInvalid;
+}
+
+bool
+CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager* aManager)
+{
+ return !aManager->CanUseCanvasLayerForSize(GetSize());
+}
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
+
+CanvasPath::CanvasPath(nsISupports* aParent)
+ : mParent(aParent)
+{
+ mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder();
+}
+
+CanvasPath::CanvasPath(nsISupports* aParent, already_AddRefed<PathBuilder> aPathBuilder)
+ : mParent(aParent), mPathBuilder(aPathBuilder)
+{
+ if (!mPathBuilder) {
+ mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder();
+ }
+}
+
+JSObject*
+CanvasPath::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return Path2DBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<CanvasPath>
+CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports());
+ return path.forget();
+}
+
+already_AddRefed<CanvasPath>
+CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv)
+{
+ RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero,
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
+
+ RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
+ return path.forget();
+}
+
+already_AddRefed<CanvasPath>
+CanvasPath::Constructor(const GlobalObject& aGlobal, const nsAString& aPathString, ErrorResult& aRv)
+{
+ RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString);
+ if (!tempPath) {
+ return Constructor(aGlobal, aRv);
+ }
+
+ RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
+ return path.forget();
+}
+
+void
+CanvasPath::ClosePath()
+{
+ EnsurePathBuilder();
+
+ mPathBuilder->Close();
+}
+
+void
+CanvasPath::MoveTo(double aX, double aY)
+{
+ EnsurePathBuilder();
+
+ mPathBuilder->MoveTo(Point(ToFloat(aX), ToFloat(aY)));
+}
+
+void
+CanvasPath::LineTo(double aX, double aY)
+{
+ EnsurePathBuilder();
+
+ mPathBuilder->LineTo(Point(ToFloat(aX), ToFloat(aY)));
+}
+
+void
+CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX, double aY)
+{
+ EnsurePathBuilder();
+
+ mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(aCpx), ToFloat(aCpy)),
+ gfx::Point(ToFloat(aX), ToFloat(aY)));
+}
+
+void
+CanvasPath::BezierCurveTo(double aCp1x, double aCp1y,
+ double aCp2x, double aCp2y,
+ double aX, double aY)
+{
+ BezierTo(gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)),
+ gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)),
+ gfx::Point(ToFloat(aX), ToFloat(aY)));
+}
+
+void
+CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2, double aRadius,
+ ErrorResult& aError)
+{
+ if (aRadius < 0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ EnsurePathBuilder();
+
+ // Current point in user space!
+ Point p0 = mPathBuilder->CurrentPoint();
+ Point p1(aX1, aY1);
+ Point p2(aX2, aY2);
+
+ // Execute these calculations in double precision to avoid cumulative
+ // rounding errors.
+ double dir, a2, b2, c2, cosx, sinx, d, anx, any,
+ bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1;
+ bool anticlockwise;
+
+ if (p0 == p1 || p1 == p2 || aRadius == 0) {
+ LineTo(p1.x, p1.y);
+ return;
+ }
+
+ // Check for colinearity
+ dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
+ if (dir == 0) {
+ LineTo(p1.x, p1.y);
+ return;
+ }
+
+
+ // XXX - Math for this code was already available from the non-azure code
+ // and would be well tested. Perhaps converting to bezier directly might
+ // be more efficient longer run.
+ a2 = (p0.x-aX1)*(p0.x-aX1) + (p0.y-aY1)*(p0.y-aY1);
+ b2 = (aX1-aX2)*(aX1-aX2) + (aY1-aY2)*(aY1-aY2);
+ c2 = (p0.x-aX2)*(p0.x-aX2) + (p0.y-aY2)*(p0.y-aY2);
+ cosx = (a2+b2-c2)/(2*sqrt(a2*b2));
+
+ sinx = sqrt(1 - cosx*cosx);
+ d = aRadius / ((1 - cosx) / sinx);
+
+ anx = (aX1-p0.x) / sqrt(a2);
+ any = (aY1-p0.y) / sqrt(a2);
+ bnx = (aX1-aX2) / sqrt(b2);
+ bny = (aY1-aY2) / sqrt(b2);
+ x3 = aX1 - anx*d;
+ y3 = aY1 - any*d;
+ x4 = aX1 - bnx*d;
+ y4 = aY1 - bny*d;
+ anticlockwise = (dir < 0);
+ cx = x3 + any*aRadius*(anticlockwise ? 1 : -1);
+ cy = y3 - anx*aRadius*(anticlockwise ? 1 : -1);
+ angle0 = atan2((y3-cy), (x3-cx));
+ angle1 = atan2((y4-cy), (x4-cx));
+
+
+ LineTo(x3, y3);
+
+ Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
+}
+
+void
+CanvasPath::Rect(double aX, double aY, double aW, double aH)
+{
+ MoveTo(aX, aY);
+ LineTo(aX + aW, aY);
+ LineTo(aX + aW, aY + aH);
+ LineTo(aX, aY + aH);
+ ClosePath();
+}
+
+void
+CanvasPath::Arc(double aX, double aY, double aRadius,
+ double aStartAngle, double aEndAngle, bool aAnticlockwise,
+ ErrorResult& aError)
+{
+ if (aRadius < 0.0) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ EnsurePathBuilder();
+
+ ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle, aEndAngle, aAnticlockwise);
+}
+
+void
+CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
+ double rotation, double startAngle, double endAngle,
+ bool anticlockwise, ErrorResult& error)
+{
+ if (radiusX < 0.0 || radiusY < 0.0) {
+ error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ EnsurePathBuilder();
+
+ ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle,
+ anticlockwise, rotation);
+}
+
+void
+CanvasPath::LineTo(const gfx::Point& aPoint)
+{
+ EnsurePathBuilder();
+
+ mPathBuilder->LineTo(aPoint);
+}
+
+void
+CanvasPath::BezierTo(const gfx::Point& aCP1,
+ const gfx::Point& aCP2,
+ const gfx::Point& aCP3)
+{
+ EnsurePathBuilder();
+
+ mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
+}
+
+void
+CanvasPath::AddPath(CanvasPath& aCanvasPath, const Optional<NonNull<SVGMatrix>>& aMatrix)
+{
+ RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero,
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
+
+ if (aMatrix.WasPassed()) {
+ const SVGMatrix& m = aMatrix.Value();
+ Matrix transform(m.A(), m.B(), m.C(), m.D(), m.E(), m.F());
+
+ if (!transform.IsIdentity()) {
+ RefPtr<PathBuilder> tempBuilder = tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING);
+ tempPath = tempBuilder->Finish();
+ }
+ }
+
+ EnsurePathBuilder(); // in case a path is added to itself
+ tempPath->StreamToSink(mPathBuilder);
+}
+
+already_AddRefed<gfx::Path>
+CanvasPath::GetPath(const CanvasWindingRule& aWinding, const DrawTarget* aTarget) const
+{
+ FillRule fillRule = FillRule::FILL_WINDING;
+ if (aWinding == CanvasWindingRule::Evenodd) {
+ fillRule = FillRule::FILL_EVEN_ODD;
+ }
+
+ if (mPath &&
+ (mPath->GetBackendType() == aTarget->GetBackendType()) &&
+ (mPath->GetFillRule() == fillRule)) {
+ RefPtr<gfx::Path> path(mPath);
+ return path.forget();
+ }
+
+ if (!mPath) {
+ // if there is no path, there must be a pathbuilder
+ MOZ_ASSERT(mPathBuilder);
+ mPath = mPathBuilder->Finish();
+ if (!mPath) {
+ RefPtr<gfx::Path> path(mPath);
+ return path.forget();
+ }
+
+ mPathBuilder = nullptr;
+ }
+
+ // retarget our backend if we're used with a different backend
+ if (mPath->GetBackendType() != aTarget->GetBackendType()) {
+ RefPtr<PathBuilder> tmpPathBuilder = aTarget->CreatePathBuilder(fillRule);
+ mPath->StreamToSink(tmpPathBuilder);
+ mPath = tmpPathBuilder->Finish();
+ } else if (mPath->GetFillRule() != fillRule) {
+ RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule);
+ mPath = tmpPathBuilder->Finish();
+ }
+
+ RefPtr<gfx::Path> path(mPath);
+ return path.forget();
+}
+
+void
+CanvasPath::EnsurePathBuilder() const
+{
+ if (mPathBuilder) {
+ return;
+ }
+
+ // if there is not pathbuilder, there must be a path
+ MOZ_ASSERT(mPath);
+ mPathBuilder = mPath->CopyToBuilder();
+ mPath = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla