diff options
Diffstat (limited to 'dom/canvas/CanvasRenderingContext2D.cpp')
-rw-r--r-- | dom/canvas/CanvasRenderingContext2D.cpp | 5801 |
1 files changed, 5801 insertions, 0 deletions
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp new file mode 100644 index 0000000000..fd6471e6a8 --- /dev/null +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -0,0 +1,5801 @@ +/* -*- 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 "nsMathUtils.h" + +#include "nsContentUtils.h" + +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/SVGImageContext.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/GeneratePlaceholderCanvasData.h" +#include "nsPresContext.h" + +#include "nsIInterfaceRequestorUtils.h" +#include "nsIFrame.h" +#include "nsError.h" + +#include "nsCSSPseudoElements.h" +#include "nsComputedDOMStyle.h" + +#include "nsPrintfCString.h" + +#include "nsReadableUtils.h" + +#include "nsColor.h" +#include "nsGfxCIID.h" +#include "nsIDocShell.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 "gfxPlatform.h" +#include "gfxFont.h" +#include "gfxBlur.h" +#include "gfxTextRun.h" +#include "gfxUtils.h" + +#include "nsFrameLoader.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/Array.h" // JS::GetArrayLength +#include "js/Conversions.h" +#include "js/experimental/TypedData.h" // JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData +#include "js/HeapAPI.h" +#include "js/Warnings.h" // JS::WarnASCII + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/CanvasGradient.h" +#include "mozilla/dom/CanvasPattern.h" +#include "mozilla/dom/DOMMatrix.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/FilterInstance.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/gfx/Swizzle.h" +#include "mozilla/layers/PersistentBufferProvider.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Preferences.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/StaticPrefs_gfx.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/SVGImageElement.h" +#include "mozilla/dom/TextMetrics.h" +#include "mozilla/FloatingPoint.h" +#include "nsGlobalWindow.h" +#include "nsDeviceContext.h" +#include "nsFontMetrics.h" +#include "nsLayoutUtils.h" +#include "Units.h" +#include "CanvasUtils.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/ServoCSSParser.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/layers/CanvasClient.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "mozilla/layers/WebRenderCanvasRenderer.h" + +#undef free // apparently defined by some windows header, clashing with a + // free() method in SkTypes.h + +#ifdef XP_WIN +# include "gfxWindowsPlatform.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::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) : mCtx(aCtx) { + mCtx->Save(); + } + ~AutoSaveRestore() { mCtx->Restore(); } + + private: + RefPtr<CanvasRenderingContext2D> mCtx; +}; + +// 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() = default; + + 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 CanvasConicGradient : public CanvasGradient { + public: + CanvasConicGradient(CanvasRenderingContext2D* aContext, Float aAngle, + const Point& aCenter) + : CanvasGradient(aContext, Type::CONIC), + mAngle(aAngle), + mCenter(aCenter) {} + + const Float mAngle; + const Point mCenter; +}; + +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, bool* aIsColor) const { + const ContextState& state = CurrentState(); + bool opaque = false; + bool color = false; + if (state.globalAlpha >= 1.0) { + if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) { + opaque = IsOpaque(state.patternStyles[aStyle]->mSurface->GetFormat()); + } else if (!state.gradientStyles[aStyle]) { + // TODO: for gradient patterns we could check that all stops are opaque + // colors. + // it's a color pattern. + opaque = sRGBColor::FromABGR(state.colorStyles[aStyle]).a >= 1.0; + color = true; + } + } + if (aIsColor) { + *aIsColor = color; + } + return opaque; +} + +// 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.gradientStyles[aStyle] && + state.gradientStyles[aStyle]->GetType() == + CanvasGradient::Type::CONIC) { + auto gradient = + static_cast<CanvasConicGradient*>(state.gradientStyles[aStyle].get()); + + mPattern.InitConicGradientPattern( + gradient->mCenter, gradient->mAngle, 0, 1, + 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); + } + + if (!mFinalTarget->CanCreateSimilarDrawTarget(mSourceGraphicRect.Size(), + SurfaceFormat::B8G8R8A8)) { + mTarget = mFinalTarget; + mCtx = nullptr; + mFinalTarget = nullptr; + return; + } + + mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(), + SurfaceFormat::B8G8R8A8); + + if (mTarget) { + // See bug 1524554. + mTarget->ClearRect(gfx::Rect()); + } + + if (!mTarget || !mTarget->IsValid()) { + // 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) { + // See bug 1524554. + dt->ClearRect(gfx::Rect()); + } + + if (!dt || !dt->IsValid()) { + 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); + + if (!mFinalTarget->CanCreateSimilarDrawTarget(mTempRect.Size(), + SurfaceFormat::B8G8R8A8)) { + mTarget = mFinalTarget; + mCtx = nullptr; + mFinalTarget = nullptr; + return; + } + + mTarget = mFinalTarget->CreateShadowDrawTarget( + mTempRect.Size(), SurfaceFormat::B8G8R8A8, mSigma); + + if (mTarget) { + // See bug 1524554. + mTarget->ClearRect(gfx::Rect()); + } + + if (!mTarget || !mTarget->IsValid()) { + // 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(), + ToDeviceColor(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(const DOMMatrix2DInit& aInit, + ErrorResult& aError) { + RefPtr<DOMMatrixReadOnly> matrix = + DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError); + if (aError.Failed()) { + return; + } + const auto* matrix2D = matrix->GetInternal2D(); + if (!matrix2D->IsFinite()) { + return; + } + mTransform = Matrix(*matrix2D); +} + +void CanvasGradient::AddColorStop(float aOffset, const nsACString& aColorstr, + ErrorResult& aRv) { + if (aOffset < 0.0 || aOffset > 1.0) { + return aRv.ThrowIndexSizeError("Offset out of 0-1.0 range"); + } + + PresShell* presShell = mContext ? mContext->GetPresShell() : nullptr; + ServoStyleSet* styleSet = presShell ? presShell->StyleSet() : nullptr; + + nscolor color; + bool ok = ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), aColorstr, + &color); + if (!ok) { + return aRv.ThrowSyntaxError("Invalid color"); + } + + mStops = nullptr; + + GradientStop newStop; + + newStop.offset = aOffset; + newStop.color = ToDeviceColor(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) {} + + void OnShutdown() { + if (!mCanvas) { + return; + } + + mCanvas = nullptr; + nsContentUtils::UnregisterShutdownObserver(this); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + ~CanvasShutdownObserver() = default; + + 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(); + OnShutdown(); + } + + return NS_OK; +} + +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. + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) + 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 autoSVGFiltersObserver = + tmp->mStyleStack[i].autoSVGFiltersObserver.get(); + if (autoSVGFiltersObserver) { + // XXXjwatt: I don't think this call achieves anything. See the comment + // that documents this function. + SVGObserverUtils::DetachFromCanvasContext(autoSVGFiltersObserver); + } + ImplCycleCollectionUnlink(tmp->mStyleStack[i].autoSVGFiltersObserver); + } + 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) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) + 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].autoSVGFiltersObserver, + "RAII SVG Filters 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_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) + if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) { + 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->HasKnownLiveWrapper(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) + return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); +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::ContextState::ContextState() = default; + +CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther) + : fontGroup(aOther.fontGroup), + fontLanguage(aOther.fontLanguage), + fontFont(aOther.fontFont), + gradientStyles(aOther.gradientStyles), + patternStyles(aOther.patternStyles), + colorStyles(aOther.colorStyles), + font(aOther.font), + textAlign(aOther.textAlign), + textBaseline(aOther.textBaseline), + shadowColor(aOther.shadowColor), + transform(aOther.transform), + shadowOffset(aOther.shadowOffset), + lineWidth(aOther.lineWidth), + miterLimit(aOther.miterLimit), + globalAlpha(aOther.globalAlpha), + shadowBlur(aOther.shadowBlur), + dash(aOther.dash.Clone()), + dashOffset(aOther.dashOffset), + op(aOther.op), + fillRule(aOther.fillRule), + lineCap(aOther.lineCap), + lineJoin(aOther.lineJoin), + filterString(aOther.filterString), + filterChain(aOther.filterChain), + autoSVGFiltersObserver(aOther.autoSVGFiltersObserver), + filter(aOther.filter), + filterAdditionalImages(aOther.filterAdditionalImages.Clone()), + filterSourceGraphicTainted(aOther.filterSourceGraphicTainted), + imageSmoothingEnabled(aOther.imageSmoothingEnabled), + fontExplicitLanguage(aOther.fontExplicitLanguage) {} + +CanvasRenderingContext2D::ContextState::~ContextState() = default; + +void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle, + nscolor aColor) { + colorStyles[aWhichStyle] = aColor; + gradientStyles[aWhichStyle] = nullptr; + patternStyles[aWhichStyle] = nullptr; +} + +void CanvasRenderingContext2D::ContextState::SetPatternStyle( + Style aWhichStyle, CanvasPattern* aPat) { + gradientStyles[aWhichStyle] = nullptr; + patternStyles[aWhichStyle] = aPat; +} + +void CanvasRenderingContext2D::ContextState::SetGradientStyle( + Style aWhichStyle, CanvasGradient* aGrad) { + gradientStyles[aWhichStyle] = aGrad; + patternStyles[aWhichStyle] = nullptr; +} + +/** + ** CanvasRenderingContext2D impl + **/ + +// Initialize our static variables. +uintptr_t CanvasRenderingContext2D::sNumLivingContexts = 0; +DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; + +CanvasRenderingContext2D::CanvasRenderingContext2D( + layers::LayersBackend aCompositorBackend) + : // these are the default values from the Canvas spec + mWidth(0), + mHeight(0), + mZero(false), + mOpaqueAttrValue(false), + mContextAttributesHasAlpha(true), + mOpaque(false), + mResetLayer(true), + mIPC(false), + mHasPendingStableStateCallback(false), + mIsEntireFrameInvalid(false), + mPredictManyRedrawCalls(false), + mIsCapturedFrameInvalid(false), + mPathTransformWillUpdate(false), + mInvalidateCount(0), + mWriteOnly(false) { + sNumLivingContexts++; + + mShutdownObserver = new CanvasShutdownObserver(this); + nsContentUtils::RegisterShutdownObserver(mShutdownObserver); +} + +CanvasRenderingContext2D::~CanvasRenderingContext2D() { + RemovePostRefreshObserver(); + RemoveShutdownObserver(); + Reset(); + + sNumLivingContexts--; + if (!sNumLivingContexts) { + NS_IF_RELEASE(sErrorTarget); + } +} + +JSObject* CanvasRenderingContext2D::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto); +} + +bool CanvasRenderingContext2D::ParseColor(const nsACString& aString, + nscolor* aColor) { + Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; + css::Loader* loader = document ? document->CSSLoader() : nullptr; + + PresShell* presShell = GetPresShell(); + ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr; + + // First, try computing the color without handling currentcolor. + bool wasCurrentColor = false; + if (!ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, aColor, + &wasCurrentColor, loader)) { + return false; + } + + if (wasCurrentColor && mCanvasElement) { + // Otherwise, get the value of the color property, flushing style + // if necessary. + RefPtr<ComputedStyle> canvasStyle = + nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr); + if (canvasStyle) { + *aColor = canvasStyle->StyleText()->mColor.ToColor(); + } + // Beware that the presShell could be gone here. + } + 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) { + mShutdownObserver->OnShutdown(); + mShutdownObserver = nullptr; + } +} + +void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr, + Style aWhichStyle) { + MOZ_ASSERT(!aStr.IsVoid()); + + nscolor color; + if (!ParseColor(aStr, &color)) { + return; + } + + CurrentState().SetColorStyle(aWhichStyle, color); +} + +void CanvasRenderingContext2D::GetStyleAsUnion( + OwningUTF8StringOrCanvasGradientOrCanvasPattern& 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.SetAsUTF8String()); + } +} + +// static +void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, + nsACString& aStr) { + aStr.Truncate(); + // 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) { + aStr.AppendPrintf("#%02x%02x%02x", NS_GET_R(aColor), NS_GET_G(aColor), + NS_GET_B(aColor)); + } else { + aStr.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor), NS_GET_G(aColor), + NS_GET_B(aColor)); + 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; + } + + SVGObserverUtils::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; + } + + SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); + + mCanvasElement->InvalidateCanvasContent(&aR); +} + +void CanvasRenderingContext2D::DidRefresh() {} + +void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) { + mIsCapturedFrameInvalid = true; + + if (mIsEntireFrameInvalid) { + ++mInvalidateCount; + return; + } + + gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR)); + Redraw(newr); +} + +bool CanvasRenderingContext2D::CopyBufferProvider( + PersistentBufferProvider& aOld, DrawTarget& aTarget, IntRect aCopyRect) { + // Borrowing the snapshot must be done after ReturnTarget. + RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot(); + + if (!snapshot) { + return false; + } + + aTarget.CopySurface(snapshot, aCopyRect, IntPoint()); + aOld.ReturnSnapshot(snapshot.forget()); + return true; +} + +void CanvasRenderingContext2D::Demote() {} + +void CanvasRenderingContext2D::ScheduleStableStateCallback() { + if (mHasPendingStableStateCallback) { + return; + } + mHasPendingStableStateCallback = true; + + nsContentUtils::RunInStableState( + NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", 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 (auto& style : mStyleStack) { + for (auto clipOrTransform = style.clipsAndTransforms.begin(); + clipOrTransform != style.clipsAndTransforms.end(); clipOrTransform++) { + if (clipOrTransform->IsClip()) { + if (mClipsNeedConverting) { + // We have possibly changed backends, so we need to convert the clips + // in case they are no longer compatible with mTarget. + RefPtr<PathBuilder> pathBuilder = mTarget->CreatePathBuilder(); + clipOrTransform->clip->StreamToSink(pathBuilder); + clipOrTransform->clip = pathBuilder->Finish(); + } + mTarget->PushClip(clipOrTransform->clip); + } else { + mTarget->SetTransform(clipOrTransform->transform); + } + } + } + + mClipsNeedConverting = false; +} + +bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, + bool aWillClear) { + if (AlreadyShutDown()) { + gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown."; + SetErrorState(); + return false; + } + + if (mTarget) { + return mTarget != sErrorTarget; + } + + // Check that the dimensions are sane + if (mWidth > StaticPrefs::gfx_canvas_max_size() || + mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 || + mHeight < 0) { + SetErrorState(); + return false; + } + + // 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) { + mTarget = mBufferProvider->BorrowDrawTarget(persistedRect); + + if (mTarget && !mBufferProvider->PreservesDrawingState()) { + RestoreClipsAndTransformToTarget(); + } + + if (mTarget && mTarget->IsValid()) { + return true; + } + } + + RefPtr<DrawTarget> newTarget; + RefPtr<PersistentBufferProvider> newProvider; + + if (!TrySharedTarget(newTarget, newProvider) && + !TryBasicTarget(newTarget, newProvider)) { + gfxCriticalError( + CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize()))) + << "Failed borrow shared and basic targets."; + + SetErrorState(); + return false; + } + + MOZ_ASSERT(newTarget); + MOZ_ASSERT(newProvider); + + bool needsClear = !canDiscardContent; + if (newTarget->GetBackendType() == gfx::BackendType::SKIA && + (needsClear || !aWillClear)) { + // Skia expects the unused X channel to contains 0xFF 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. + newTarget->ClearRect(canvasRect); + needsClear = false; + } + + // Try to copy data from the previous buffer provider if there is one. + if (!canDiscardContent && mBufferProvider && + CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) { + needsClear = false; + } + + if (needsClear) { + newTarget->ClearRect(canvasRect); + } + + mTarget = std::move(newTarget); + mBufferProvider = std::move(newProvider); + + RegisterAllocation(); + + RestoreClipsAndTransformToTarget(); + + // Force a full layer transaction since we didn't have a layer before + // and now we might need one. + if (mCanvasElement) { + mCanvasElement->InvalidateCanvas(); + } + // EnsureTarget hasn't drawn anything. Preserve mIsCapturedFrameInvalid. + bool capturedFrameInvalid = mIsCapturedFrameInvalid; + // Calling Redraw() tells our invalidation machinery that the entire + // canvas is already invalid, which can speed up future drawing. + Redraw(); + mIsCapturedFrameInvalid = capturedFrameInvalid; + + return true; +} + +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()); + } + + JSObject* wrapper = GetWrapperPreserveColor(); + if (wrapper) { + CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC( + JS::GetObjectZone(wrapper)); + } +} + +static already_AddRefed<LayerManager> LayerManagerFromCanvasElement( + nsINode* aCanvasElement) { + if (!aCanvasElement) { + return nullptr; + } + + return nsContentUtils::PersistentLayerManagerForDocument( + aCanvasElement->OwnerDoc()); +} + +bool CanvasRenderingContext2D::TrySharedTarget( + RefPtr<gfx::DrawTarget>& aOutDT, + RefPtr<layers::PersistentBufferProvider>& aOutProvider) { + aOutDT = nullptr; + aOutProvider = nullptr; + + if (!mCanvasElement) { + return false; + } + + if (mBufferProvider && + (mBufferProvider->GetType() == LayersBackend::LAYERS_CLIENT || + mBufferProvider->GetType() == LayersBackend::LAYERS_WR)) { + // we are already using a shared buffer provider, we are allocating a new + // one because the current one failed so let's just fall back to the basic + // provider. + mClipsNeedConverting = true; + 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; + } + + // See Bug 1524554 - this forces DT initialization. + aOutDT->ClearRect(gfx::Rect()); + + if (!aOutDT->IsValid()) { + aOutDT = nullptr; + return false; + } + + aOutProvider = new PersistentBufferProviderBasic(aOutDT); + return true; +} + +PresShell* CanvasRenderingContext2D::GetPresShell() { + if (mCanvasElement) { + return mCanvasElement->OwnerDoc()->GetPresShell(); + } + if (mDocShell) { + return mDocShell->GetPresShell(); + } + return nullptr; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) { + // Zero sized surfaces can cause problems. + mZero = false; + if (aHeight == 0) { + aHeight = 1; + mZero = true; + } + if (aWidth == 0) { + aWidth = 1; + mZero = true; + } + + ClearTarget(aWidth, aHeight); + + return NS_OK; +} + +void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) { + Reset(); + + mResetLayer = true; + + SetInitialState(); + + // Update dimensions only if new (strictly positive) values were passed. + if (aWidth > 0 && aHeight > 0) { + // Update the memory size associated with the wrapper object when we change + // the dimensions. Note that we need to keep updating dying wrappers before + // they are finalized so that the memory accounting balances out. + JSObject* wrapper = GetWrapperMaybeDead(); + if (wrapper) { + JS::RemoveAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this), + JS::MemoryUse::DOMBinding); + } + + mWidth = aWidth; + mHeight = aHeight; + + if (wrapper) { + JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this), + JS::MemoryUse::DOMBinding); + } + } + + if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) { + return; + } + + // For vertical writing-mode, unless text-orientation is sideways, + // we'll modify the initial value of textBaseline to 'middle'. + RefPtr<ComputedStyle> canvasStyle = + nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr); + 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; +} + +void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr( + bool aOpaqueAttrValue) { + if (aOpaqueAttrValue != mOpaqueAttrValue) { + mOpaqueAttrValue = aOpaqueAttrValue; + UpdateIsOpaque(); + } +} + +void CanvasRenderingContext2D::UpdateIsOpaque() { + mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue; + ClearTarget(); +} + +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; + } + + mContextAttributesHasAlpha = attributes.mAlpha; + UpdateIsOpaque(); + + return NS_OK; +} + +UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer( + int32_t* aFormat) { + UniquePtr<uint8_t[]> ret; + + *aFormat = 0; + + if (!mBufferProvider) { + if (!EnsureTarget()) { + return nullptr; + } + } + + RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(); + if (snapshot) { + RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); + if (data && data->GetSize() == GetSize()) { + *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB; + ret = SurfaceToPackedBGRA(data); + } + } + + 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 nsAString& 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); +} + +already_AddRefed<mozilla::gfx::SourceSurface> +CanvasRenderingContext2D::GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) { + if (aOutAlphaType) { + *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult); + } + + // For GetSurfaceSnapshot we always call EnsureTarget even if mBufferProvider + // already exists, otherwise we get performance issues. See bug 1567054. + if (!EnsureTarget()) { + MOZ_ASSERT( + mTarget == sErrorTarget, + "On EnsureTarget failure mTarget should be set to sErrorTarget."); + return mTarget->Snapshot(); + } + + // The concept of BorrowSnapshot seems a bit broken here, but the original + // code in GetSurfaceSnapshot just returned a snapshot from mTarget, which + // amounts to breaking the concept implicitly. + RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(); + RefPtr<SourceSurface> retSurface = snapshot; + mBufferProvider->ReturnSnapshot(snapshot.forget()); + return retSurface.forget(); +} + +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.RemoveLastElement(); + + 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); +} + +already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform( + ErrorResult& aError) { + EnsureTarget(); + if (!IsTargetValid()) { + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + RefPtr<DOMMatrix> matrix = + new DOMMatrix(GetParentObject(), mTarget->GetTransform()); + return matrix.forget(); +} + +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::SetTransform(const DOMMatrix2DInit& aInit, + ErrorResult& aError) { + TransformWillUpdate(); + if (!IsTargetValid()) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + RefPtr<DOMMatrixReadOnly> matrix = + DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError); + if (!aError.Failed()) { + SetTransformInternal(Matrix(*(matrix->GetInternal2D()))); + } +} + +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::GenericNaN(); + ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); + } + + MatrixToJSObject(aCx, ctm, aResult, aError); +} + +// +// colors +// + +void CanvasRenderingContext2D::SetStyleFromUnion( + const UTF8StringOrCanvasGradientOrCanvasPattern& aValue, + Style aWhichStyle) { + if (aValue.IsUTF8String()) { + SetStyleFromString(aValue.GetAsUTF8String(), aWhichStyle); + return; + } + + if (aValue.IsCanvasGradient()) { + SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle); + return; + } + + if (aValue.IsCanvasPattern()) { + CanvasPattern& pattern = aValue.GetAsCanvasPattern(); + SetStyleFromPattern(pattern, aWhichStyle); + if (pattern.mForceWriteOnly) { + SetWriteOnly(); + } + 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.ThrowIndexSizeError("Negative radius"); + return nullptr; + } + + RefPtr<CanvasGradient> grad = new CanvasRadialGradient( + this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1); + + return grad.forget(); +} + +already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateConicGradient( + double aAngle, double aCx, double aCy) { + return MakeAndAddRef<CanvasConicGradient>(this, aAngle, Point(aCx, aCy)); +} + +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.ThrowSyntaxError("Invalid pattern keyword"); + return nullptr; + } + + Element* element; + if (aSource.IsHTMLCanvasElement()) { + HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement(); + element = canvas; + + nsIntSize size = canvas->GetSize(); + if (size.width == 0) { + aError.ThrowInvalidStateError("Passed-in canvas has width 0"); + return nullptr; + } + + if (size.height == 0) { + aError.ThrowInvalidStateError("Passed-in canvas has height 0"); + return nullptr; + } + + // Special case for Canvas, which could be an Azure canvas! + nsICanvasRenderingContextInternal* srcCanvas = canvas->GetCurrentContext(); + if (srcCanvas) { + // This might not be an Azure canvas! + RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); + if (!srcSurf) { + aError.ThrowInvalidStateError( + "CanvasRenderingContext2D.createPattern() failed to snapshot source" + "canvas."); + return nullptr; + } + + RefPtr<CanvasPattern> pat = + new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(), + canvas->IsWriteOnly(), false); + + return pat.forget(); + } + } else if (aSource.IsHTMLImageElement()) { + HTMLImageElement* img = &aSource.GetAsHTMLImageElement(); + element = img; + } else if (aSource.IsSVGImageElement()) { + SVGImageElement* img = &aSource.GetAsSVGImageElement(); + element = img; + } else if (aSource.IsHTMLVideoElement()) { + auto& video = aSource.GetAsHTMLVideoElement(); + video.MarkAsContentSource( + mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN); + element = &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) { + aError.ThrowInvalidStateError( + "Passed-in ImageBitmap has been transferred"); + 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, imgBitmap.IsWriteOnly(), 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 + SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromElement( + element, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE, mTarget); + + // Per spec, we should throw here for the HTMLImageElement and SVGImageElement + // cases if the image request state is "broken". In terms of the infromation + // in "res", the "broken" state corresponds to not having a size and not being + // still-loading (so there is no size forthcoming). + if (aSource.IsHTMLImageElement() || aSource.IsSVGImageElement()) { + if (!res.mIsStillLoading && !res.mHasSize) { + aError.ThrowInvalidStateError( + "Passed-in image's current request's state is \"broken\""); + return nullptr; + } + + if (res.mSize.width == 0 || res.mSize.height == 0) { + return nullptr; + } + + // Is the "fully decodable" check already done in SurfaceFromElement? It's + // not clear how to do it from here, exactly. + } + + RefPtr<SourceSurface> surface = res.GetSourceSurface(); + if (!surface) { + return nullptr; + } + + RefPtr<CanvasPattern> pat = + new CanvasPattern(this, surface, repeatMode, res.mPrincipal, + res.mIsWriteOnly, res.mCORSUsed); + return pat.forget(); +} + +// +// shadows +// +void CanvasRenderingContext2D::SetShadowColor(const nsACString& aShadowColor) { + nscolor color; + if (!ParseColor(aShadowColor, &color)) { + return; + } + + CurrentState().shadowColor = color; +} + +// +// filters +// + +static already_AddRefed<RawServoDeclarationBlock> CreateDeclarationForServo( + nsCSSPropertyID aProperty, const nsACString& aPropertyValue, + Document* aDocument) { + ServoCSSParser::ParsingEnvironment env{aDocument->DefaultStyleAttrURLData(), + aDocument->GetCompatibilityMode(), + aDocument->CSSLoader()}; + RefPtr<RawServoDeclarationBlock> servoDeclarations = + ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env); + + if (!servoDeclarations) { + // We got a syntax error. The spec says this value must be ignored. + return nullptr; + } + + // From canvas spec, force to set line-height property to 'normal' font + // property. + if (aProperty == eCSSProperty_font) { + const nsCString normalString = "normal"_ns; + Servo_DeclarationBlock_SetPropertyById( + servoDeclarations, eCSSProperty_line_height, &normalString, false, + env.mUrlExtraData, ParsingMode::Default, env.mCompatMode, env.mLoader, + env.mRuleType, {}); + } + + return servoDeclarations.forget(); +} + +static already_AddRefed<RawServoDeclarationBlock> CreateFontDeclarationForServo( + const nsACString& aFont, Document* aDocument) { + return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument); +} + +static already_AddRefed<ComputedStyle> GetFontStyleForServo( + Element* aElement, const nsACString& aFont, PresShell* aPresShell, + nsACString& aOutUsedFont, ErrorResult& aError) { + RefPtr<RawServoDeclarationBlock> declarations = + CreateFontDeclarationForServo(aFont, aPresShell->GetDocument()); + if (!declarations) { + // 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 'none'. + if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, + eCSSProperty_font_size_adjust)) { + return nullptr; + } + + ServoStyleSet* styleSet = aPresShell->StyleSet(); + + RefPtr<ComputedStyle> parentStyle; + // have to get a parent ComputedStyle for inherit-like relative + // values (2em, bolder, etc.) + if (aElement && aElement->IsInComposedDoc()) { + parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement, nullptr); + if (!parentStyle) { + // The flush killed the shell, so we couldn't get any meaningful style + // back. + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } else { + RefPtr<RawServoDeclarationBlock> declarations = + CreateFontDeclarationForServo("10px sans-serif"_ns, + aPresShell->GetDocument()); + MOZ_ASSERT(declarations); + + parentStyle = + aPresShell->StyleSet()->ResolveForDeclarations(nullptr, declarations); + } + + MOZ_RELEASE_ASSERT(parentStyle, "Should have a valid parent style"); + + MOZ_ASSERT(!aPresShell->IsDestroying(), + "We should have returned an error above if the presshell is " + "being destroyed."); + + RefPtr<ComputedStyle> sc = + styleSet->ResolveForDeclarations(parentStyle, declarations); + + // 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.) + Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont); + return sc.forget(); +} + +static already_AddRefed<RawServoDeclarationBlock> +CreateFilterDeclarationForServo(const nsACString& aFilter, + Document* aDocument) { + return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument); +} + +static already_AddRefed<ComputedStyle> ResolveFilterStyleForServo( + const nsACString& aFilterString, const ComputedStyle* aParentStyle, + PresShell* aPresShell, ErrorResult& aError) { + RefPtr<RawServoDeclarationBlock> declarations = + CreateFilterDeclarationForServo(aFilterString, aPresShell->GetDocument()); + if (!declarations) { + // 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 (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, + eCSSProperty_filter)) { + return nullptr; + } + + ServoStyleSet* styleSet = aPresShell->StyleSet(); + RefPtr<ComputedStyle> computedValues = + styleSet->ResolveForDeclarations(aParentStyle, declarations); + + return computedValues.forget(); +} + +bool CanvasRenderingContext2D::ParseFilter( + const nsACString& aString, StyleOwnedSlice<StyleFilter>& 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; + } + + RefPtr<PresShell> presShell = GetPresShell(); + if (NS_WARN_IF(!presShell)) { + aError.Throw(NS_ERROR_FAILURE); + return false; + } + + nsAutoCString usedFont; // unused + + RefPtr<ComputedStyle> parentStyle = GetFontStyleForServo( + mCanvasElement, GetFont(), presShell, usedFont, aError); + if (!parentStyle) { + return false; + } + + RefPtr<ComputedStyle> style = + ResolveFilterStyleForServo(aString, parentStyle, presShell, aError); + if (!style) { + return false; + } + + aFilterChain = style->StyleEffects()->mFilters; + return true; +} + +void CanvasRenderingContext2D::SetFilter(const nsACString& aFilter, + ErrorResult& aError) { + StyleOwnedSlice<StyleFilter> filterChain; + if (ParseFilter(aFilter, filterChain, aError)) { + CurrentState().filterString = aFilter; + CurrentState().filterChain = std::move(filterChain); + if (mCanvasElement) { + CurrentState().autoSVGFiltersObserver = + SVGObserverUtils::ObserveFiltersForCanvasContext( + this, mCanvasElement, CurrentState().filterChain.AsSpan()); + UpdateFilter(); + } + } +} + +class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize { + public: + CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont, + nsAtom* aFontLanguage, bool aExplicitLanguage, + nsPresContext* aPresContext) + : mSize(aSize), + mFont(aFont), + mFontLanguage(aFontLanguage), + mExplicitLanguage(aExplicitLanguage), + mPresContext(aPresContext) {} + + virtual float GetEmLength() const override { + return mFont.size.ToCSSPixels(); + } + + virtual float GetExLength() const override { + nsDeviceContext* dc = mPresContext->DeviceContext(); + nsFontMetrics::Params params; + params.language = mFontLanguage; + params.explicitLanguage = mExplicitLanguage; + params.textPerf = mPresContext->GetTextPerfMetrics(); + params.fontStats = mPresContext->GetFontMatchingStats(); + params.featureValueLookup = mPresContext->GetFontFeatureValuesLookup(); + RefPtr<nsFontMetrics> fontMetrics = dc->GetMetricsFor(mFont, params); + return NSAppUnitsToFloatPixels(fontMetrics->XHeight(), + AppUnitsPerCSSPixel()); + } + + virtual gfx::Size GetSize() const override { return Size(mSize); } + + private: + gfx::IntSize mSize; + const nsFont& mFont; + nsAtom* mFontLanguage; + bool mExplicitLanguage; + nsPresContext* mPresContext; +}; + +// The filter might reference an SVG filter that is declared inside this +// document. Flush frames so that we'll have a SVGFilterFrame to work +// with. +static bool FiltersNeedFrameFlush(Span<const StyleFilter> aFilters) { + for (const auto& filter : aFilters) { + if (filter.IsUrl()) { + return true; + } + } + return false; +} + +void CanvasRenderingContext2D::UpdateFilter() { + RefPtr<PresShell> 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; + } + + if (FiltersNeedFrameFlush(CurrentState().filterChain.AsSpan())) { + presShell->FlushPendingNotifications(FlushType::Frames); + } + + MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty()); + if (MOZ_UNLIKELY(presShell->IsDestroying())) { + return; + } + + const bool sourceGraphicIsTainted = + mCanvasElement && mCanvasElement->IsWriteOnly(); + + CurrentState().filter = FilterInstance::GetFilterDescription( + mCanvasElement, CurrentState().filterChain.AsSpan(), + 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, true); + 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 isColor; + bool discardContent = + PatternIsOpaque(Style::FILL, &isColor) && + (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE); + const gfx::Rect fillRect(aX, aY, aW, aH); + EnsureTarget(discardContent ? &fillRect : nullptr, discardContent && isColor); + 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(Element& aElement) { + if (!aElement.State().HasState(NS_EVENT_STATE_FOCUSRING)) { + return false; + } + + HTMLCanvasElement* canvas = GetCanvas(); + if (!canvas || !aElement.IsInclusiveDescendantOf(canvas)) { + return false; + } + + EnsureUserSpacePath(); + return true; +} + +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) { + return aError.ThrowIndexSizeError("Negative radius"); + } + + 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) { + return aError.ThrowIndexSizeError("Negative radius"); + } + + 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) { + return aError.ThrowIndexSizeError("Negative radius"); + } + + 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 nsACString& aFont, + ErrorResult& aError) { + SetFontInternal(aFont, aError); +} + +bool CanvasRenderingContext2D::SetFontInternal(const nsACString& aFont, + ErrorResult& aError) { + /* + * If font is defined with relative units (e.g. ems) and the parent + * ComputedStyle 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; + } + + RefPtr<PresShell> presShell = GetPresShell(); + if (NS_WARN_IF(!presShell)) { + aError.Throw(NS_ERROR_FAILURE); + return false; + } + + nsCString usedFont; + RefPtr<ComputedStyle> sc = + GetFontStyleForServo(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. + // FIXME: Nobody initializes mAllowZoom for servo? + // 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.ScaledBy(1.0f / c->CSSToDevPixelScale().scale); + + c->Document()->FlushUserFontSet(); + + nsFontMetrics::Params params; + params.language = fontStyle->mLanguage; + params.explicitLanguage = fontStyle->mExplicitLanguage; + params.userFontSet = c->GetUserFontSet(); + params.textPerf = c->GetTextPerfMetrics(); + params.fontStats = c->GetFontMatchingStats(); + 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) { + DebugOnly<TextMetrics*> metrics = DrawOrMeasureText( + aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, aError); + MOZ_ASSERT(!metrics); // drawing operation never returns TextMetrics +} + +void CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX, + double aY, + const Optional<double>& aMaxWidth, + ErrorResult& aError) { + DebugOnly<TextMetrics*> metrics = DrawOrMeasureText( + aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, aError); + MOZ_ASSERT(!metrics); // drawing operation never returns TextMetrics +} + +TextMetrics* CanvasRenderingContext2D::MeasureText(const nsAString& aRawText, + ErrorResult& aError) { + Optional<double> maxWidth; + return DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, + aError); +} + +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) { + return aError.ThrowNotSupportedError("Invalid path"); + } + + // 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()) { + return aError.ThrowNotSupportedError("The specified region has no pixels"); + } + + // 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, + reinterpret_cast<void*>(true)); +#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(), + mCtx(nullptr), + mFontgrp(nullptr), + mAppUnitsPerDevPixel(0), + mOp(CanvasRenderingContext2D::TextDrawOperation::FILL), + mTextRunFlags(), + mDoMeasureBoundingBox(false) { + if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) { + mMissingFonts = MakeUnique<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) override { + mFontgrp->UpdateUserFonts(); // ensure user font generation is current + // adjust flags for current direction run + gfx::ShapedTextFlags flags = mTextRunFlags; + if (aDirection == NSBIDI_RTL) { + flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; + } else { + flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL; + } + mTextRun = mFontgrp->MakeTextRun( + aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags, + nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts, + mMissingFonts.get()); + } + + virtual nscoord GetWidth() override { + 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::CONIC: { + auto conic = static_cast<CanvasConicGradient*>(gradient); + pattern = new gfxPattern(conic->mCenter.x, conic->mCenter.y, + conic->mAngle, 0, 1); + break; + } + 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, radial or conic 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, pat->mTransform); + pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat)); + return pattern.forget(); + } + + virtual void DrawText(nscoord aXOffset, nscoord aWidth) override { + gfx::Point point = mPt; + bool rtl = mTextRun->IsRightToLeft(); + bool verticalRun = mTextRun->IsVertical(); + RefPtr<gfxPattern> pattern; + + float& 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(sRGBColor::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 + gfx::Point mPt; + + // current font + gfxFontGroup* mFontgrp; + + // to record any unsupported characters found in the text, + // and notify front-end if it is interested + UniquePtr<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 + gfx::ShapedTextFlags mTextRunFlags; + + // true iff the bounding box should be measured + bool mDoMeasureBoundingBox; +}; + +TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText( + const nsAString& aRawText, float aX, float aY, + const Optional<double>& aMaxWidth, TextDrawOperation aOp, + ErrorResult& aError) { + // Approximated baselines. In an ideal world, we'd read the baseline info + // directly from the font (where available). Alas we currently lack + // that functionality. These numbers are best guesses and should + // suffice for now. Both are fractions of the em ascent/descent from the + // alphabetic baseline. + const double kHangingBaselineDefault = 0.8; // fraction of ascent + const double kIdeographicBaselineDefault = 0.5; // fraction of descent + + if (!mCanvasElement && !mDocShell) { + NS_WARNING( + "Canvas element must be non-null or a docshell must be provided"); + aError = NS_ERROR_FAILURE; + return nullptr; + } + + RefPtr<PresShell> presShell = GetPresShell(); + if (NS_WARN_IF(!presShell)) { + aError = NS_ERROR_FAILURE; + return nullptr; + } + + Document* 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<ComputedStyle> canvasStyle; + if (mCanvasElement && mCanvasElement->IsInComposedDoc()) { + // try to find the closest context + canvasStyle = nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr); + if (!canvasStyle) { + aError = NS_ERROR_FAILURE; + return nullptr; + } + + isRTL = canvasStyle->StyleVisibility()->mDirection == StyleDirection::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()) { + aError = NS_ERROR_FAILURE; + return nullptr; + } + + gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); + if (!currentFontStyle) { + aError = NS_ERROR_FAILURE; + return nullptr; + } + + MOZ_ASSERT(!presShell->IsDestroying(), + "GetCurrentFontStyle() should have returned null if the presshell " + "is being destroyed"); + + nsPresContext* presContext = presShell->GetPresContext(); + + // ensure user font set is up to date + presContext->Document()->FlushUserFontSet(); + currentFontStyle->SetUserFontSet(presContext->GetUserFontSet()); + + if (currentFontStyle->GetStyle()->size == 0.0F) { + aError = NS_OK; + if (aOp == TextDrawOperation::MEASURE) { + return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0); + } + return nullptr; + } + + if (!IsFinite(aX) || !IsFinite(aY)) { + aError = NS_OK; + // This may not be correct - what should TextMetrics contain in the case of + // infinite width or height? + if (aOp == TextDrawOperation::MEASURE) { + return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0); + } + return nullptr; + } + + CanvasBidiProcessor processor; + + // If we don't have a ComputedStyle, 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, presContext, canvasStyle->StyleFont(), + canvasStyle->StyleText(), 0) + : gfx::ShapedTextFlags(); + + GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); + processor.mPt = gfx::Point(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 || + aOp == TextDrawOperation::MEASURE; + processor.mFontgrp = currentFontStyle; + + nscoord totalWidthCoord; + + // calls bidi algo twice since it needs the full text width and the + // bounding boxes before rendering anything + aError = nsBidiPresUtils::ProcessText( + textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR, + presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_MEASURE, + nullptr, 0, &totalWidthCoord, &mBidiEngine); + if (aError.Failed()) { + return nullptr; + } + + float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; + + // 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; + } + + float offsetX = anchorX * totalWidth; + processor.mPt.x -= offsetX; + + // 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( + nsFontMetrics::eHorizontal); + + gfxFloat baselineAnchor; + + switch (state.textBaseline) { + case TextBaseline::HANGING: + baselineAnchor = fontMetrics.emAscent * kHangingBaselineDefault; + break; + case TextBaseline::TOP: + baselineAnchor = fontMetrics.emAscent; + break; + case TextBaseline::MIDDLE: + baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; + break; + case TextBaseline::ALPHABETIC: + baselineAnchor = 0; + break; + case TextBaseline::IDEOGRAPHIC: + baselineAnchor = -fontMetrics.emDescent * kIdeographicBaselineDefault; + 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. + gfx::ShapedTextFlags runOrientation = + (processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK); + if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) { + if (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED || + runOrientation == gfx::ShapedTextFlags::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; + } + + // if only measuring, don't need to do any more work + if (aOp == TextDrawOperation::MEASURE) { + aError = NS_OK; + // Note that actualBoundingBoxLeft measures the distance in the leftward + // direction, so its sign is reversed from our usual physical coordinates. + double actualBoundingBoxLeft = offsetX - processor.mBoundingBox.X(); + double actualBoundingBoxRight = processor.mBoundingBox.XMost() - offsetX; + double actualBoundingBoxAscent = + -processor.mBoundingBox.Y() - baselineAnchor; + double actualBoundingBoxDescent = + processor.mBoundingBox.YMost() + baselineAnchor; + double hangingBaseline = + fontMetrics.emAscent * kHangingBaselineDefault - baselineAnchor; + double ideographicBaseline = + -fontMetrics.emDescent * kIdeographicBaselineDefault - baselineAnchor; + return new TextMetrics( + totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight, + fontMetrics.maxAscent - baselineAnchor, // fontBBAscent + fontMetrics.maxDescent + baselineAnchor, // fontBBDescent + actualBoundingBoxAscent, actualBoundingBoxDescent, + fontMetrics.emAscent - baselineAnchor, // emHeightAscent + -fontMetrics.emDescent - baselineAnchor, // emHeightDescent + hangingBaseline, + -baselineAnchor, // alphabeticBaseline + ideographicBaseline); + } + + // If we did not actually calculate bounds, set up a simple bounding box + // based on the text position and advance. + if (!doCalculateBounds) { + processor.mBoundingBox.width = totalWidth; + processor.mBoundingBox.MoveBy(gfxPoint(processor.mPt.x, processor.mPt.y)); + } + + processor.mPt.x *= processor.mAppUnitsPerDevPixel; + processor.mPt.y *= processor.mAppUnitsPerDevPixel; + + EnsureTarget(); + if (!IsTargetValid()) { + aError = NS_ERROR_FAILURE; + return nullptr; + } + + 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; + + aError = nsBidiPresUtils::ProcessText( + textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR, + presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_DRAW, + nullptr, 0, nullptr, &mBidiEngine); + + if (aError.Failed()) { + return nullptr; + } + + mTarget->SetTransform(oldTransform); + + if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL && + !doCalculateBounds) { + RedrawUser(boundingBox); + } else { + Redraw(); + } + + aError = NS_OK; + return nullptr; +} + +gfxFontGroup* CanvasRenderingContext2D::GetCurrentFontStyle() { + // use lazy initilization for the font group since it's rather expensive + if (!CurrentState().fontGroup) { + ErrorResult err; + constexpr auto kDefaultFontStyle = "10px sans-serif"_ns; + static float kDefaultFontSize = 10.0; + RefPtr<PresShell> presShell = GetPresShell(); + bool fontUpdated = SetFontInternal(kDefaultFontStyle, err); + if (err.Failed() || !fontUpdated) { + err.SuppressException(); + // XXX Should we get a default lang from the prescontext or something? + nsAtom* language = nsGkAtoms::x_western; + bool explicitLanguage = false; + gfxFontStyle style; + style.size = kDefaultFontSize; + gfxTextPerfMetrics* tp = nullptr; + FontMatchingStats* fontStats = nullptr; + if (presShell && !presShell->IsDestroying()) { + tp = presShell->GetPresContext()->GetTextPerfMetrics(); + fontStats = presShell->GetPresContext()->GetFontMatchingStats(); + } + int32_t perDevPixel, perCSSPixel; + GetAppUnitsValues(&perDevPixel, &perCSSPixel); + gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel); + CurrentState().fontGroup = gfxPlatform::GetPlatform()->CreateFontGroup( + FontFamilyList(StyleGenericFontFamily::SansSerif), &style, language, + explicitLanguage, tp, fontStats, nullptr, devToCssSize); + if (CurrentState().fontGroup) { + CurrentState().font = kDefaultFontStyle; + } else { + NS_ERROR("Default canvas font is invalid"); + } + } + } else { + // The fontgroup needs to check if its cached families/faces are valid. + CurrentState().fontGroup->CheckForUpdatedPlatformList(); + } + + 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 = std::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(JSContext* aCx, double aX, + double aY, + const CanvasWindingRule& aWinding, + nsIPrincipal& aSubjectPrincipal) { + if (!FloatValidate(aX, aY)) { + return false; + } + + // Check for site-specific permission and return false if no permission. + if (mCanvasElement) { + nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc(); + if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, + aSubjectPrincipal)) { + 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(JSContext* aCx, + const CanvasPath& aPath, double aX, + double aY, + const CanvasWindingRule& aWinding, + nsIPrincipal&) { + 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( + JSContext* aCx, double aX, double aY, nsIPrincipal& aSubjectPrincipal) { + if (!FloatValidate(aX, aY)) { + return false; + } + + // Check for site-specific permission and return false if no permission. + if (mCanvasElement) { + nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc(); + if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, + aSubjectPrincipal)) { + 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(JSContext* aCx, + const CanvasPath& aPath, + double aX, double aY, + nsIPrincipal&) { + 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) { + // See bug 1524554. + subrectDT->ClearRect(gfx::Rect()); + } + + if (!subrectDT || !subrectDT->IsValid()) { + 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. +SurfaceFromElementResult CanvasRenderingContext2D::CachedSurfaceFromElement( + Element* aElement) { + 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; + } + + if (NS_FAILED(imgRequest->GetHadCrossOriginRedirects( + &res.mHadCrossOriginRedirects))) { + return res; + } + + res.mSourceSurface = CanvasImageCache::LookupAllCanvas(aElement); + 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.mIntrinsicSize = res.mSourceSurface->GetSize(); + res.mPrincipal = std::move(principal); + res.mImageRequest = std::move(imgRequest); + res.mIsWriteOnly = CheckWriteOnlySecurity(res.mCORSUsed, res.mPrincipal, + res.mHadCrossOriginRedirects); + + 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) { + 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; + gfx::IntSize intrinsicImgSize; + + 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) { + return aError.ThrowInvalidStateError("Passed-in canvas is empty"); + } + + if (canvas->IsWriteOnly()) { + SetWriteOnly(); + } + } else if (aImage.IsImageBitmap()) { + ImageBitmap& imageBitmap = aImage.GetAsImageBitmap(); + srcSurf = imageBitmap.PrepareForDrawTarget(mTarget); + + if (!srcSurf) { + if (imageBitmap.IsClosed()) { + aError.ThrowInvalidStateError("Passed-in ImageBitmap is closed"); + } + return; + } + + if (imageBitmap.IsWriteOnly()) { + SetWriteOnly(); + } + + imgSize = intrinsicImgSize = + gfx::IntSize(imageBitmap.Width(), imageBitmap.Height()); + } else { + if (aImage.IsHTMLImageElement()) { + HTMLImageElement* img = &aImage.GetAsHTMLImageElement(); + element = img; + } else if (aImage.IsSVGImageElement()) { + SVGImageElement* img = &aImage.GetAsSVGImageElement(); + element = img; + } else { + HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement(); + video->MarkAsContentSource( + mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE); + element = video; + } + + srcSurf = CanvasImageCache::LookupCanvas(element, mCanvasElement, &imgSize, + &intrinsicImgSize); + } + + DirectDrawInfo drawInfo; + + 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_IF_IMAGE | + nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS; + + 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.ThrowInvalidStateError("Passed-in image is \"broken\""); + } + return; + } + + imgSize = res.mSize; + intrinsicImgSize = res.mIntrinsicSize; + + if (mCanvasElement) { + CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, res.mPrincipal, + res.mIsWriteOnly, res.mCORSUsed); + } + + if (res.mSourceSurface) { + if (res.mImageRequest) { + CanvasImageCache::NotifyDrawImage(element, mCanvasElement, + res.mSourceSurface, imgSize, + intrinsicImgSize); + } + srcSurf = res.mSourceSurface; + } else { + drawInfo = res.mDrawInfo; + } + } + + if (aOptional_argc == 0) { + aSx = aSy = 0.0; + aSw = (double)imgSize.width; + aSh = (double)imgSize.height; + aDw = (double)intrinsicImgSize.width; + aDh = (double)intrinsicImgSize.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) { + 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; + } + + // Per spec, the smoothing setting applies only to scaling up a bitmap image. + // When down-scaling the user agent is free to choose whether or not to smooth + // the image. Nearest sampling when down-scaling is rarely desirable and + // smoothing when down-scaling matches chromium's behavior. + // If any dimension is up-scaled, we consider the image as being up-scaled. + auto scale = mTarget->GetTransform().ScaleFactors(); + bool isDownScale = + aDw * Abs(scale.width) < aSw && aDh * Abs(scale.height) < aSh; + + SamplingFilter samplingFilter; + AntialiasMode antialiasMode; + + if (CurrentState().imageSmoothingEnabled || isDownScale) { + 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) { + gfxDevCrash(LogReason::InvalidDrawTarget) + << "Invalid adjusted target in Canvas2D " + << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow() + << NeedToApplyFilter(); + 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 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 = ThebesMatrix(matrix); + gfxSize contextScale(contextMatrix.ScaleFactors()); + + // 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->SetMatrixDouble( + contextMatrix + .PreScale(1.0 / contextScale.width, 1.0 / contextScale.height) + .PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y)); + + context->SetOp(UsedOperation()); + + // 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(Some(sz)); + + 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, + CurrentState().globalAlpha); + + if (result != ImgDrawResult::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(nsGlobalWindowInner& aWindow, + double aX, double aY, double aW, + double aH, const nsACString& aBgColor, + uint32_t aFlags, + ErrorResult& aError) { + 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 & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) { + nsContentUtils::FlushLayoutForTree(aWindow.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; + } + + RefPtr<nsPresContext> presContext; + nsIDocShell* docshell = aWindow.GetDocShell(); + if (docshell) { + presContext = docshell->GetPresContext(); + } + 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)); + RenderDocumentFlags renderDocFlags = + (RenderDocumentFlags::IgnoreViewportScrolling | + RenderDocumentFlags::DocumentRelative); + if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET) { + renderDocFlags |= RenderDocumentFlags::DrawCaret; + } + if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW) { + renderDocFlags &= ~(RenderDocumentFlags::IgnoreViewportScrolling | + RenderDocumentFlags::DocumentRelative); + } + if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS) { + renderDocFlags |= RenderDocumentFlags::UseWidgetLayers; + } + if (aFlags & + CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES) { + renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages; + } + if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH) { + renderDocFlags |= RenderDocumentFlags::DrawWindowNotFlushing; + } + + // 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 && + mBufferProvider->GetType() != LayersBackend::LAYERS_WR))) { + thebes = gfxContext::CreateOrNull(mTarget); + MOZ_ASSERT(thebes); // already checked the draw target above + // (in SupportsAzureContentForDrawTarget) + thebes->SetMatrix(matrix); + } else { + IntSize dtSize = IntSize::Ceil(sw, sh); + if (!Factory::AllowedSurfaceSize(dtSize)) { + // attempt to limit the DT to what will actually cover the target + Size limitSize(mTarget->GetSize()); + limitSize.Scale(matrix._11, matrix._22); + dtSize = Min(dtSize, IntSize::Ceil(limitSize)); + // if the DT is still too big, then error + 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(Matrix::Scaling(matrix._11, matrix._22)); + } + + RefPtr<PresShell> presShell = presContext->PresShell(); + + Unused << presShell->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; + } + + 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(snapshot, 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)); +} + +// +// device pixel getting/setting +// + +already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData( + JSContext* aCx, int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) { + 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 (IsWriteOnly() || + (mCanvasElement && !mCanvasElement->CallerCanRead(aCx))) { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + aError.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + if (!aSw || !aSh) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + // Handle negative width and height by flipping the rectangle over in the + // relevant direction. + uint32_t w, h; + if (aSw < 0) { + w = -aSw; + aSx -= w; + } else { + w = aSw; + } + if (aSh < 0) { + h = -aSh; + aSy -= h; + } else { + h = aSh; + } + + if (w == 0) { + w = 1; + } + if (h == 0) { + h = 1; + } + + JS::Rooted<JSObject*> array(aCx); + aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal, + array.address()); + if (aError.Failed()) { + return nullptr; + } + MOZ_ASSERT(array); + return MakeAndAddRef<ImageData>(w, h, *array); +} + +nsresult CanvasRenderingContext2D::GetImageDataArray( + JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight, + nsIPrincipal& aSubjectPrincipal, JSObject** aRetval) { + 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); + if (srcReadRect.IsEmpty()) { + *aRetval = darray; + return NS_OK; + } + + if (!mBufferProvider) { + if (!EnsureTarget()) { + return NS_ERROR_FAILURE; + } + } + + RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(); + if (!snapshot) { + return NS_ERROR_OUT_OF_MEMORY; + } + + RefPtr<DataSourceSurface> readback = snapshot->GetDataSurface(); + mBufferProvider->ReturnSnapshot(snapshot.forget()); + + DataSourceSurface::MappedSurface rawData; + if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + IntRect dstWriteRect = srcReadRect; + dstWriteRect.MoveBy(-aX, -aY); + + // Check for site-specific permission. This check is not needed if the + // canvas was created with a docshell (that is only done for special + // internal uses). + bool usePlaceholder = false; + if (mCanvasElement) { + nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc(); + usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, + aSubjectPrincipal); + } + + do { + uint8_t* randomData; + if (usePlaceholder) { + // Since we cannot call any GC-able functions (like requesting the RNG + // service) after we call JS_GetUint8ClampedArrayData, we will + // pre-generate the randomness required for GeneratePlaceholderCanvasData. + randomData = TryToGenerateRandomDataForPlaceholderCanvasData(); + } + + JS::AutoCheckCannotGC nogc; + bool isShared; + uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc); + MOZ_ASSERT(!isShared); // Should not happen, data was created above + + if (usePlaceholder) { + FillPlaceholderCanvas(randomData, len.value(), data); + break; + } + + uint32_t srcStride = rawData.mStride; + uint8_t* src = + rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4; + + uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; + + if (mOpaque) { + SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32, dst, + aWidth * 4, SurfaceFormat::R8G8B8A8, dstWriteRect.Size()); + } else { + UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32, dst, + aWidth * 4, SurfaceFormat::R8G8B8A8, + dstWriteRect.Size()); + } + } while (false); + + 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, int32_t aDx, + int32_t aDy, ErrorResult& aError) { + RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx()); + DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject()); + MOZ_ASSERT(inited); + + PutImageData_explicit(aDx, aDy, aImageData.Width(), aImageData.Height(), &arr, + false, 0, 0, 0, 0, aError); +} + +void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx, + int32_t aDy, int32_t aDirtyX, + int32_t aDirtyY, + int32_t aDirtyWidth, + int32_t aDirtyHeight, + ErrorResult& aError) { + RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx()); + DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject()); + MOZ_ASSERT(inited); + + PutImageData_explicit(aDx, aDy, aImageData.Width(), aImageData.Height(), &arr, + true, aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight, + aError); +} + +void 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, + ErrorResult& aRv) { + if (aW == 0 || aH == 0) { + return aRv.ThrowInvalidStateError("Passed-in image is empty"); + } + + IntRect dirtyRect; + IntRect imageDataRect(0, 0, aW, aH); + + if (aHasDirtyRect) { + // fix up negative dimensions + if (aDirtyWidth < 0) { + if (aDirtyWidth == INT_MIN) { + return aRv.ThrowInvalidStateError("Dirty width is invalid"); + } + + CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth; + + if (!checkedDirtyX.isValid()) { + return aRv.ThrowInvalidStateError("Dirty width is invalid"); + } + + aDirtyX = checkedDirtyX.value(); + aDirtyWidth = -aDirtyWidth; + } + + if (aDirtyHeight < 0) { + if (aDirtyHeight == INT_MIN) { + return aRv.ThrowInvalidStateError("Dirty height is invalid"); + } + + CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight; + + if (!checkedDirtyY.isValid()) { + return aRv.ThrowInvalidStateError("Dirty height is invalid"); + } + + 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; + } + } else { + dirtyRect = imageDataRect; + } + + dirtyRect.MoveBy(IntPoint(aX, aY)); + dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); + + if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { + return; + } + + aArray->ComputeState(); + + uint32_t dataLen = aArray->Length(); + + uint32_t len = aW * aH * 4; + if (dataLen != len) { + return aRv.ThrowInvalidStateError("Invalid width or height"); + } + + // 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 aRv.Throw(NS_ERROR_FAILURE); + } + + DataSourceSurface::MappedSurface map; + RefPtr<DataSourceSurface> sourceSurface; + uint8_t* lockedBits = nullptr; + uint8_t* dstData; + IntSize dstSize; + int32_t dstStride; + SurfaceFormat dstFormat; + if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) { + dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4; + } else { + sourceSurface = Factory::CreateDataSourceSurface( + dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false); + + // 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 aRv.Throw(NS_ERROR_FAILURE); + } + if (!sourceSurface->Map(DataSourceSurface::READ_WRITE, &map)) { + return aRv.Throw(NS_ERROR_FAILURE); + } + + dstData = map.mData; + if (!dstData) { + return aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + } + dstStride = map.mStride; + dstFormat = sourceSurface->GetFormat(); + } + + IntRect srcRect = dirtyRect - IntPoint(aX, aY); + uint8_t* srcData = aArray->Data() + srcRect.y * (aW * 4) + srcRect.x * 4; + + PremultiplyData( + srcData, aW * 4, SurfaceFormat::R8G8B8A8, dstData, dstStride, + mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32, + dirtyRect.Size()); + + if (lockedBits) { + mTarget->ReleaseBits(lockedBits); + } else if (sourceSurface) { + sourceSurface->Unmap(); + mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(), + dirtyRect.TopLeft()); + } + + Redraw( + gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); +} + +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.ThrowIndexSizeError("Invalid width or height"); + return nullptr; + } + + // Create the fast typed array; it's initialized to 0 by default. + JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value()); + if (!darray) { + // TODO: Should use OOMReporter. + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + return do_AddRef(new ImageData(aW, aH, *darray)); +} + +already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData( + JSContext* aCx, int32_t aSw, int32_t aSh, ErrorResult& aError) { + if (!aSw || !aSh) { + aError.ThrowIndexSizeError("Invalid width or height"); + return nullptr; + } + + uint32_t w = Abs(aSw); + uint32_t h = Abs(aSh); + return dom::CreateImageData(aCx, this, w, h, aError); +} + +already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData( + JSContext* aCx, ImageData& aImagedata, ErrorResult& aError) { + return dom::CreateImageData(aCx, this, aImagedata.Width(), + aImagedata.Height(), aError); +} + +void CanvasRenderingContext2D::OnBeforePaintTransaction() { + if (!mTarget) return; + OnStableState(); +} + +void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); } + +already_AddRefed<Layer> CanvasRenderingContext2D::GetCanvasLayer( + nsDisplayListBuilder* aBuilder, Layer* aOldLayer, LayerManager* aManager) { + if (mOpaque) { + // If we're opaque then make sure we have a surface so we paint black + // instead of transparent. + 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 && !IsTargetValid()) { + // No DidTransactionCallback will be received, so mark the context clean + // now so future invalidations will be dispatched. + MarkContextClean(); + return nullptr; + } + + if (!mResetLayer && aOldLayer) { + 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; + } + + const auto canvasRenderer = canvasLayer->CreateOrGetCanvasRenderer(); + InitializeCanvasRenderer(aBuilder, canvasRenderer); + uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; + canvasLayer->SetContentFlags(flags); + + mResetLayer = false; + + return canvasLayer.forget(); +} + +bool CanvasRenderingContext2D::UpdateWebRenderCanvasData( + nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) { + if (mOpaque) { + // If we're opaque then make sure we have a surface so we paint black + // instead of transparent. + 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 && !IsTargetValid()) { + // No DidTransactionCallback will be received, so mark the context clean + // now so future invalidations will be dispatched. + MarkContextClean(); + // Clear CanvasRenderer of WebRenderCanvasData + aCanvasData->ClearCanvasRenderer(); + return false; + } + + auto renderer = aCanvasData->GetCanvasRenderer(); + + if (!mResetLayer && renderer) { + CanvasRendererData data; + data.mContext = mSharedPtrPtr; + data.mSize = GetSize(); + + if (renderer->IsDataValid(data)) { + return true; + } + } + + renderer = aCanvasData->CreateCanvasRenderer(); + if (!InitializeCanvasRenderer(aBuilder, renderer)) { + // Clear CanvasRenderer of WebRenderCanvasData + aCanvasData->ClearCanvasRenderer(); + return false; + } + + MOZ_ASSERT(renderer); + mResetLayer = false; + return true; +} + +bool CanvasRenderingContext2D::InitializeCanvasRenderer( + nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) { + CanvasRendererData data; + data.mContext = mSharedPtrPtr; + data.mSize = GetSize(); + data.mIsOpaque = mOpaque; + data.mDoPaintCallbacks = true; + + if (!mBufferProvider) { + // Force the creation of a buffer provider. + EnsureTarget(); + ReturnTarget(); + if (!mBufferProvider) { + MarkContextClean(); + return false; + } + } + + aRenderer->Initialize(data); + aRenderer->SetDirty(); + return true; +} + +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()); +} + +void CanvasRenderingContext2D::GetAppUnitsValues(int32_t* aPerDevPixel, + int32_t* aPerCSSPixel) { + // If we don't have a canvas element, we just return something generic. + if (aPerDevPixel) { + *aPerDevPixel = 60; + } + if (aPerCSSPixel) { + *aPerCSSPixel = 60; + } + PresShell* presShell = GetPresShell(); + if (!presShell) { + return; + } + nsPresContext* presContext = presShell->GetPresContext(); + if (!presContext) { + return; + } + if (aPerDevPixel) { + *aPerDevPixel = presContext->AppUnitsPerDevPixel(); + } + if (aPerCSSPixel) { + *aPerCSSPixel = AppUnitsPerCSSPixel(); + } +} + +void CanvasRenderingContext2D::SetWriteOnly() { + mWriteOnly = true; + if (mCanvasElement) { + mCanvasElement->SetWriteOnly(); + } +} + +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 Path2D_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<CanvasPath> CanvasPath::Constructor( + const GlobalObject& aGlobal) { + RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports()); + return path.forget(); +} + +already_AddRefed<CanvasPath> CanvasPath::Constructor( + const GlobalObject& aGlobal, CanvasPath& aCanvasPath) { + RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath( + CanvasWindingRule::Nonzero, + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); + + RefPtr<CanvasPath> path = + new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); + return path.forget(); +} + +already_AddRefed<CanvasPath> CanvasPath::Constructor( + const GlobalObject& aGlobal, const nsAString& aPathString) { + RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString); + if (!tempPath) { + return Constructor(aGlobal); + } + + 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) { + return aError.ThrowIndexSizeError("Negative radius"); + } + + 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) { + return aError.ThrowIndexSizeError("Negative radius"); + } + + 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& aError) { + if (radiusX < 0.0 || radiusY < 0.0) { + return aError.ThrowIndexSizeError("Negative radius"); + } + + 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 DOMMatrix2DInit& aInit, + ErrorResult& aError) { + RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath( + CanvasWindingRule::Nonzero, + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); + + RefPtr<DOMMatrixReadOnly> matrix = + DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError); + if (aError.Failed()) { + return; + } + + Matrix transform(*(matrix->GetInternal2D())); + + if (!transform.IsFinite()) { + return; + } + + 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; +} + +size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) { + int32_t width = aContext->GetWidth(); + int32_t height = aContext->GetHeight(); + + // TODO: Bug 1552137: No memory will be allocated if either dimension is + // greater than gfxPrefs::gfx_canvas_max_size(). We should check this here + // too. + + CheckedInt<uint32_t> bytes = CheckedInt<uint32_t>(width) * height * 4; + if (!bytes.isValid()) { + return 0; + } + + return bytes.value(); +} + +} // namespace mozilla::dom |