summaryrefslogtreecommitdiffstats
path: root/dom/canvas/CanvasRenderingContext2D.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/canvas/CanvasRenderingContext2D.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/CanvasRenderingContext2D.cpp')
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp6560
1 files changed, 6560 insertions, 0 deletions
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
new file mode 100644
index 0000000000..83fe205e35
--- /dev/null
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -0,0 +1,6560 @@
+/* -*- 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/intl/BidiEmbeddingLevel.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/FontFaceSetImpl.h"
+#include "mozilla/dom/FontFaceSet.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 "nsRFPService.h"
+#include "nsReadableUtils.h"
+
+#include "nsColor.h"
+#include "nsGfxCIID.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "nsDisplayList.h"
+#include "nsFocusManager.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 "LayerUserData.h"
+#include "CanvasUtils.h"
+#include "nsIMemoryReporter.h"
+#include "nsStyleUtil.h"
+#include "CanvasImageCache.h"
+#include "DrawTargetWebgl.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/PropertyAndElement.h" // JS_GetElement
+#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/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/RestyleManager.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 "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"
+#include "WindowRenderer.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 Atomic<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:
+ using Style = CanvasRenderingContext2D::Style;
+ using ContextState = CanvasRenderingContext2D::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]) {
+ aCtx->DoSecurityCheck(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:
+ using ContextState = CanvasRenderingContext2D::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()) {
+ if (mCtx->mCanvasElement) {
+ mCtx->mCanvasElement->SetWriteOnly();
+ } else if (mCtx->mOffscreenCanvas) {
+ mCtx->mOffscreenCanvas->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:
+ using ContextState = CanvasRenderingContext2D::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();
+ if (!bounds.ToIntRect(&mTempRect) ||
+ !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(),
+ ShadowOptions(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:
+ using ContextState = CanvasRenderingContext2D::ContextState;
+
+ explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
+ const gfx::Rect* aBounds = nullptr,
+ bool aAllowOptimization = false)
+ : mCtx(aCtx),
+ mOptimizeShadow(false),
+ mUsedOperation(aCtx->CurrentState().op) {
+ // 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() || !boundsAfterFilter.IsFinite()) {
+ return;
+ }
+
+ 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.
+ const bool applyFilter = aCtx->NeedToApplyFilter();
+ if (aCtx->NeedToDrawShadow()) {
+ if (aAllowOptimization && !applyFilter) {
+ // If only drawing a shadow and no filter, then avoid buffering to an
+ // intermediate target while drawing the shadow directly to the final
+ // target. When doing so, we want to use the actual composition op
+ // instead of OP_OVER.
+ mTarget = aCtx->mTarget;
+ if (mTarget && mTarget->IsValid()) {
+ mOptimizeShadow = true;
+ return;
+ }
+ }
+ mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
+ aCtx, aCtx->mTarget, boundsAfterFilter, mUsedOperation);
+ 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.
+ mUsedOperation = CompositionOp::OP_OVER;
+ }
+
+ // Now set up the filter draw target.
+ 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), mUsedOperation);
+ mTarget = mFilterTarget->DT();
+ mUsedOperation = CompositionOp::OP_OVER;
+ }
+ 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; }
+
+ CompositionOp UsedOperation() const { return mUsedOperation; }
+
+ ShadowOptions ShadowParams() const {
+ const ContextState& state = mCtx->CurrentState();
+ return ShadowOptions(ToDeviceColor(state.shadowColor), state.shadowOffset,
+ state.ShadowBlurSigma());
+ }
+
+ void Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (mOptimizeShadow) {
+ mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions);
+ }
+ mTarget->Fill(aPath, aPattern, aOptions);
+ }
+
+ void FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ if (mOptimizeShadow) {
+ RefPtr<Path> path = MakePathForRect(*mTarget, aRect);
+ mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions);
+ }
+ mTarget->FillRect(aRect, aPattern, aOptions);
+ }
+
+ void Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (mOptimizeShadow) {
+ mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions,
+ &aStrokeOptions);
+ }
+ mTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
+ }
+
+ void StrokeRect(const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (mOptimizeShadow) {
+ RefPtr<Path> path = MakePathForRect(*mTarget, aRect);
+ mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions,
+ &aStrokeOptions);
+ }
+ mTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
+ }
+
+ void StrokeLine(const Point& aStart, const Point& aEnd,
+ const Pattern& aPattern, const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (mOptimizeShadow) {
+ RefPtr<PathBuilder> builder = mTarget->CreatePathBuilder();
+ builder->MoveTo(aStart);
+ builder->LineTo(aEnd);
+ RefPtr<Path> path = builder->Finish();
+ mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions,
+ &aStrokeOptions);
+ }
+ mTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
+ }
+
+ void DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource, const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) {
+ if (mOptimizeShadow) {
+ RefPtr<Path> path = MakePathForRect(*mTarget, aSource);
+ ShadowOptions shadowParams(ShadowParams());
+ SurfacePattern pattern(aSurface, ExtendMode::CLAMP, Matrix(),
+ shadowParams.BlurRadius() > 1
+ ? SamplingFilter::POINT
+ : aSurfOptions.mSamplingFilter);
+ Matrix matrix = Matrix::Scaling(aDest.width / aSource.width,
+ aDest.height / aSource.height);
+ matrix.PreTranslate(-aSource.x, -aSource.y);
+ matrix.PostTranslate(aDest.x, aDest.y);
+ AutoRestoreTransform autoRestoreTransform(mTarget);
+ mTarget->ConcatTransform(matrix);
+ mTarget->DrawShadow(path, pattern, shadowParams, aOptions);
+ }
+ mTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions);
+ }
+
+ 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());
+ }
+
+ CanvasRenderingContext2D* mCtx;
+ bool mOptimizeShadow;
+ CompositionOp mUsedOperation;
+ 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");
+ }
+
+ if (!mContext) {
+ return aRv.ThrowSyntaxError("No canvas context");
+ }
+
+ auto color = mContext->ParseColor(
+ aColorstr, CanvasRenderingContext2D::ResolveCurrentColor::No);
+ if (!color) {
+ return aRv.ThrowSyntaxError("Invalid color");
+ }
+
+ GradientStop newStop;
+
+ newStop.offset = aOffset;
+ newStop.color = ToDeviceColor(*color);
+
+ mRawStops.AppendElement(newStop);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
+
+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_WRAPPERCACHE_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(mOffscreenCanvas)
+ 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);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOffscreenCanvas)
+ 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");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+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),
+ textDirection(aOther.textDirection),
+ fontKerning(aOther.fontKerning),
+ letterSpacing(aOther.letterSpacing),
+ wordSpacing(aOther.wordSpacing),
+ letterSpacingStr(aOther.letterSpacingStr),
+ wordSpacingStr(aOther.wordSpacingStr),
+ 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.
+MOZ_THREAD_LOCAL(uintptr_t) CanvasRenderingContext2D::sNumLivingContexts;
+MOZ_THREAD_LOCAL(DrawTarget*) CanvasRenderingContext2D::sErrorTarget;
+
+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),
+ mFrameCaptureState(FrameCaptureState::CLEAN,
+ "CanvasRenderingContext2D::mFrameCaptureState"),
+ mPathTransformWillUpdate(false),
+ mInvalidateCount(0),
+ mWriteOnly(false) {
+ sNumLivingContexts.infallibleInit();
+ sErrorTarget.infallibleInit();
+ sNumLivingContexts.set(sNumLivingContexts.get() + 1);
+}
+
+CanvasRenderingContext2D::~CanvasRenderingContext2D() {
+ RemovePostRefreshObserver();
+ RemoveShutdownObserver();
+ ResetBitmap();
+
+ sNumLivingContexts.set(sNumLivingContexts.get() - 1);
+ if (sNumLivingContexts.get() == 0 && sErrorTarget.get()) {
+ RefPtr<DrawTarget> target = dont_AddRef(sErrorTarget.get());
+ sErrorTarget.set(nullptr);
+ }
+}
+
+void CanvasRenderingContext2D::Initialize() { AddShutdownObserver(); }
+
+JSObject* CanvasRenderingContext2D::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+CanvasRenderingContext2D::ColorStyleCacheEntry
+CanvasRenderingContext2D::ParseColorSlow(const nsACString& aString) {
+ ColorStyleCacheEntry result{nsCString(aString)};
+ Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
+ css::Loader* loader = document ? document->CSSLoader() : nullptr;
+
+ PresShell* presShell = GetPresShell();
+ ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
+ bool wasCurrentColor = false;
+ nscolor color;
+ if (ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, &color,
+ &wasCurrentColor, loader)) {
+ result.mWasCurrentColor = wasCurrentColor;
+ result.mColor.emplace(color);
+ }
+
+ return result;
+}
+
+Maybe<nscolor> CanvasRenderingContext2D::ParseColor(
+ const nsACString& aString, ResolveCurrentColor aResolveCurrentColor) {
+ auto entry = mColorStyleCache.Lookup(aString);
+ if (!entry) {
+ entry.Set(ParseColorSlow(aString));
+ }
+
+ const auto& data = entry.Data();
+ if (data.mWasCurrentColor && mCanvasElement &&
+ aResolveCurrentColor == ResolveCurrentColor::Yes) {
+ // If it was currentColor, get the value of the color property, flushing
+ // style if necessary.
+ RefPtr<const ComputedStyle> canvasStyle =
+ nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
+ if (canvasStyle) {
+ return Some(canvasStyle->StyleText()->mColor.ToColor());
+ }
+ }
+ return data.mColor;
+}
+
+void CanvasRenderingContext2D::ResetBitmap(bool aFreeBuffer) {
+ 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;
+ if (aFreeBuffer) {
+ mBufferProvider = nullptr;
+ } else if (mBufferProvider) {
+ // Try to keep the buffer around. However, we still need to clear the
+ // contents as if it was recreated before next use.
+ mBufferNeedsClear = true;
+ }
+
+ // Since the target changes the backing texture will change, and this will
+ // no longer be valid.
+ mIsEntireFrameInvalid = false;
+ mPredictManyRedrawCalls = false;
+ mFrameCaptureState = FrameCaptureState::CLEAN;
+}
+
+void CanvasRenderingContext2D::OnShutdown() {
+ mShutdownObserver = nullptr;
+
+ RefPtr<PersistentBufferProvider> provider = mBufferProvider;
+
+ ResetBitmap();
+
+ if (provider) {
+ provider->OnShutdown();
+ }
+}
+
+void CanvasRenderingContext2D::AddShutdownObserver() {
+ MOZ_ASSERT(!mShutdownObserver);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mShutdownObserver = new CanvasShutdownObserver(this);
+ nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
+}
+
+void CanvasRenderingContext2D::RemoveShutdownObserver() {
+ if (mShutdownObserver) {
+ mShutdownObserver->OnShutdown();
+ mShutdownObserver = nullptr;
+ }
+}
+
+void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
+ Style aWhichStyle) {
+ MOZ_ASSERT(!aStr.IsVoid());
+
+ Maybe<nscolor> color = ParseColor(aStr);
+ if (!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() {
+ mFrameCaptureState = FrameCaptureState::DIRTY;
+
+ if (mIsEntireFrameInvalid) {
+ return NS_OK;
+ }
+
+ mIsEntireFrameInvalid = true;
+
+ if (mCanvasElement) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
+ mCanvasElement->InvalidateCanvasContent(nullptr);
+ } else if (mOffscreenCanvas) {
+ mOffscreenCanvas->QueueCommitToCompositor();
+ } else {
+ NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
+ }
+
+ return NS_OK;
+}
+
+void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
+ mFrameCaptureState = FrameCaptureState::DIRTY;
+
+ ++mInvalidateCount;
+
+ if (mIsEntireFrameInvalid) {
+ return;
+ }
+
+ if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) {
+ Redraw();
+ return;
+ }
+
+ if (mCanvasElement) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
+ mCanvasElement->InvalidateCanvasContent(&aR);
+ } else if (mOffscreenCanvas) {
+ mOffscreenCanvas->QueueCommitToCompositor();
+ } else {
+ NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
+ }
+}
+
+void CanvasRenderingContext2D::DidRefresh() {}
+
+void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) {
+ mFrameCaptureState = FrameCaptureState::DIRTY;
+
+ 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) {
+ 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::BorrowTarget(const IntRect& aPersistedRect,
+ bool aNeedsClear) {
+ // We are attempting to request a DrawTarget from the current
+ // PersistentBufferProvider. However, if the provider needs to be refreshed,
+ // or if it is accelerated and the application has requested that we disallow
+ // acceleration, then we skip trying to use this provider so that it will be
+ // recreated by EnsureTarget later.
+ if (!mBufferProvider || mBufferProvider->RequiresRefresh() ||
+ (mBufferProvider->IsAccelerated() && mWillReadFrequently)) {
+ return false;
+ }
+ mTarget = mBufferProvider->BorrowDrawTarget(aPersistedRect);
+ if (!mTarget || !mTarget->IsValid()) {
+ if (mTarget) {
+ mBufferProvider->ReturnDrawTarget(mTarget.forget());
+ }
+ return false;
+ }
+ if (mBufferNeedsClear) {
+ if (mBufferProvider->PreservesDrawingState()) {
+ // If the buffer provider preserves the clip and transform state, then
+ // we must ensure it is cleared before reusing the target.
+ if (!mTarget->RemoveAllClips()) {
+ mBufferProvider->ReturnDrawTarget(mTarget.forget());
+ return false;
+ }
+ mTarget->SetTransform(Matrix());
+ }
+ // If the canvas was reset, then we need to clear the target in case its
+ // contents was somehow preserved. We only need to clear the target if
+ // the operation doesn't fill the entire canvas.
+ if (aNeedsClear) {
+ mTarget->ClearRect(gfx::Rect(mTarget->GetRect()));
+ }
+ }
+ if (!mBufferProvider->PreservesDrawingState() || mBufferNeedsClear) {
+ RestoreClipsAndTransformToTarget();
+ }
+ mBufferNeedsClear = false;
+ return true;
+}
+
+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.get();
+ }
+
+ // 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 || mBufferNeedsClear
+ ? IntRect()
+ : IntRect(0, 0, mWidth, mHeight);
+
+ // Attempt to reuse the existing buffer provider.
+ if (BorrowTarget(persistedRect, !canDiscardContent)) {
+ return true;
+ }
+
+ RefPtr<DrawTarget> newTarget;
+ RefPtr<PersistentBufferProvider> newProvider;
+
+ if (!TryAcceleratedTarget(newTarget, newProvider) &&
+ !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 || (mBufferProvider && mBufferNeedsClear);
+ 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 && !mBufferNeedsClear &&
+ CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
+ needsClear = false;
+ }
+
+ if (needsClear) {
+ newTarget->ClearRect(canvasRect);
+ }
+
+ mTarget = std::move(newTarget);
+ mBufferProvider = std::move(newProvider);
+ mBufferNeedsClear = false;
+
+ RegisterAllocation();
+ AddZoneWaitingForGC();
+
+ 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 mFrameCaptureState.
+ FrameCaptureState captureState = mFrameCaptureState;
+ // Calling Redraw() tells our invalidation machinery that the entire
+ // canvas is already invalid, which can speed up future drawing.
+ Redraw();
+ mFrameCaptureState = captureState;
+
+ 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.get()) {
+ gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
+ }
+
+ mTarget = sErrorTarget.get();
+ 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());
+ }
+}
+
+void CanvasRenderingContext2D::AddZoneWaitingForGC() {
+ JSObject* wrapper = GetWrapperPreserveColor();
+ if (wrapper) {
+ CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
+ JS::GetObjectZone(wrapper));
+ }
+}
+
+static WindowRenderer* WindowRendererFromCanvasElement(
+ nsINode* aCanvasElement) {
+ if (!aCanvasElement) {
+ return nullptr;
+ }
+
+ return nsContentUtils::WindowRendererForDocument(aCanvasElement->OwnerDoc());
+}
+
+bool CanvasRenderingContext2D::TryAcceleratedTarget(
+ RefPtr<gfx::DrawTarget>& aOutDT,
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
+ if (!XRE_IsContentProcess()) {
+ // Only allow accelerated contexts to be created in a content process to
+ // ensure it is remoted appropriately and run on the correct parent or
+ // GPU process threads.
+ return false;
+ }
+ if (mBufferProvider && mBufferProvider->IsAccelerated() &&
+ mBufferProvider->RequiresRefresh()) {
+ // If there is already a provider and we got here, then the provider needs
+ // to be refreshed and we should avoid using acceleration in the future.
+ mAllowAcceleration = false;
+ }
+ // Don't try creating an accelerate DrawTarget if either acceleration failed
+ // previously or if the application expects acceleration to be slow.
+ if (!mAllowAcceleration || mWillReadFrequently) {
+ return false;
+ }
+ aOutDT = DrawTargetWebgl::Create(GetSize(), GetSurfaceFormat());
+ if (!aOutDT) {
+ return false;
+ }
+
+ aOutProvider = new PersistentBufferProviderAccelerated(aOutDT);
+ return true;
+}
+
+bool CanvasRenderingContext2D::TrySharedTarget(
+ RefPtr<gfx::DrawTarget>& aOutDT,
+ RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
+ aOutDT = nullptr;
+ aOutProvider = nullptr;
+
+ if (!mCanvasElement) {
+ return false;
+ }
+
+ if (mBufferProvider && mBufferProvider->IsShared()) {
+ // 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;
+ }
+
+ WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
+
+ if (!renderer) {
+ return false;
+ }
+
+ aOutProvider =
+ renderer->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;
+}
+
+PersistentBufferProvider* CanvasRenderingContext2D::GetBufferProvider() {
+ if (mBufferProvider && mBufferNeedsClear) {
+ // Force the buffer to clear before it is used.
+ EnsureTarget();
+ }
+ return mBufferProvider;
+}
+
+Maybe<SurfaceDescriptor> CanvasRenderingContext2D::GetFrontBuffer(
+ WebGLFramebufferJS*, const bool webvr) {
+ if (auto* provider = GetBufferProvider()) {
+ return provider->GetFrontBuffer();
+ }
+ return Nothing();
+}
+
+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::AddAssociatedMemory() {
+ JSObject* wrapper = GetWrapperMaybeDead();
+ if (wrapper) {
+ JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
+ JS::MemoryUse::DOMBinding);
+ }
+}
+
+void CanvasRenderingContext2D::RemoveAssociatedMemory() {
+ JSObject* wrapper = GetWrapperMaybeDead();
+ if (wrapper) {
+ JS::RemoveAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
+ JS::MemoryUse::DOMBinding);
+ }
+}
+
+void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) {
+ // Only free the buffer provider if the size no longer matches.
+ bool freeBuffer = aWidth != mWidth || aHeight != mHeight;
+ ResetBitmap(freeBuffer);
+
+ 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.
+ RemoveAssociatedMemory();
+ mWidth = aWidth;
+ mHeight = aHeight;
+ AddAssociatedMemory();
+ }
+
+ if (mOffscreenCanvas) {
+ OffscreenCanvasDisplayData data;
+ data.mSize = {mWidth, mHeight};
+ data.mIsOpaque = mOpaque;
+ data.mIsAlphaPremult = true;
+ data.mDoPaintCallbacks = true;
+ mOffscreenCanvas->UpdateDisplayData(data);
+ }
+
+ 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<const ComputedStyle> canvasStyle =
+ nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
+ if (canvasStyle) {
+ WritingMode wm(canvasStyle);
+ if (wm.IsVertical() && !wm.IsSideways()) {
+ CurrentState().textBaseline = TextBaseline::MIDDLE;
+ }
+ }
+}
+
+void CanvasRenderingContext2D::ReturnTarget(bool aForceReset) {
+ if (mTarget && mBufferProvider && mTarget != sErrorTarget.get()) {
+ 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);
+
+ RestoreClipsAndTransformToTarget();
+
+ return NS_OK;
+}
+
+void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(
+ bool aOpaqueAttrValue) {
+ if (aOpaqueAttrValue != mOpaqueAttrValue) {
+ mOpaqueAttrValue = aOpaqueAttrValue;
+ UpdateIsOpaque();
+ }
+}
+
+void CanvasRenderingContext2D::UpdateIsOpaque() {
+ mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue;
+ ClearTarget();
+}
+
+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;
+ }
+
+ mWillReadFrequently = attributes.mWillReadFrequently;
+
+ mContextAttributesHasAlpha = attributes.mAlpha;
+ UpdateIsOpaque();
+
+ return NS_OK;
+}
+
+UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
+ int32_t* out_format, gfx::IntSize* out_imageSize) {
+ UniquePtr<uint8_t[]> ret;
+
+ *out_format = 0;
+ *out_imageSize = {};
+
+ if (!GetBufferProvider() && !EnsureTarget()) {
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
+ if (snapshot) {
+ RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
+ if (data && data->GetSize() == GetSize()) {
+ *out_format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+ *out_imageSize = data->GetSize();
+ ret = SurfaceToPackedBGRA(data);
+ }
+ }
+
+ mBufferProvider->ReturnSnapshot(snapshot.forget());
+
+ if (ret && ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
+ nsRFPService::RandomizePixels(
+ GetCookieJarSettings(), ret.get(),
+ out_imageSize->width * out_imageSize->height * 4,
+ SurfaceFormat::A8R8G8B8_UINT32);
+ }
+
+ return ret;
+}
+
+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;
+ gfx::IntSize imageSize = {};
+ UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format, &imageSize);
+ if (!imageBuffer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return ImageEncoder::GetInputStream(imageSize.width, imageSize.height,
+ imageBuffer.get(), format, encoder,
+ aEncoderOptions, aStream);
+}
+
+already_AddRefed<mozilla::gfx::SourceSurface>
+CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget* aTarget,
+ 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.get(),
+ "On EnsureTarget failure mTarget should be set to sErrorTarget.");
+ // In rare circumstances we may have failed to create an error target.
+ return mTarget ? mTarget->Snapshot() : nullptr;
+ }
+
+ // 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(aTarget);
+ 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);
+}
+
+//
+// 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) {
+ double adjustedStartAngle = aAngle + M_PI / 2.0;
+ return MakeAndAddRef<CanvasConicGradient>(this, adjustedStartAngle,
+ 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 = nullptr;
+ OffscreenCanvas* offscreenCanvas = nullptr;
+
+ 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.LogVisibility(
+ mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
+ element = &video;
+ } else if (aSource.IsOffscreenCanvas()) {
+ offscreenCanvas = &aSource.GetAsOffscreenCanvas();
+
+ nsIntSize size = offscreenCanvas->GetWidthHeight();
+ 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;
+ }
+
+ nsICanvasRenderingContextInternal* srcCanvas =
+ offscreenCanvas->GetContext();
+ if (srcCanvas) {
+ RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
+ if (!srcSurf) {
+ aError.ThrowInvalidStateError(
+ "Passed-in canvas failed to create snapshot");
+ return nullptr;
+ }
+
+ RefPtr<CanvasPattern> pat = new CanvasPattern(
+ this, srcSurf, repeatMode, srcCanvas->PrincipalOrNull(),
+ offscreenCanvas->IsWriteOnly(), false);
+
+ return pat.forget();
+ }
+ } 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
+ auto flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
+ nsLayoutUtils::SFE_EXACT_SIZE_SURFACE;
+ SurfaceFromElementResult res =
+ offscreenCanvas
+ ? nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, flags,
+ mTarget)
+ : nsLayoutUtils::SurfaceFromElement(element, flags, 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) {
+ Maybe<nscolor> color = ParseColor(aShadowColor);
+ if (!color) {
+ return;
+ }
+
+ CurrentState().shadowColor = *color;
+}
+
+//
+// filters
+//
+
+static already_AddRefed<StyleLockedDeclarationBlock> CreateDeclarationForServo(
+ nsCSSPropertyID aProperty, const nsACString& aPropertyValue,
+ Document* aDocument) {
+ ServoCSSParser::ParsingEnvironment env{aDocument->DefaultStyleAttrURLData(),
+ aDocument->GetCompatibilityMode(),
+ aDocument->CSSLoader()};
+ RefPtr<StyleLockedDeclarationBlock> 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<StyleLockedDeclarationBlock>
+CreateFontDeclarationForServo(const nsACString& aFont, Document* aDocument) {
+ return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument);
+}
+
+static already_AddRefed<const ComputedStyle> GetFontStyleForServo(
+ Element* aElement, const nsACString& aFont, PresShell* aPresShell,
+ nsACString& aOutUsedFont, ErrorResult& aError) {
+ RefPtr<StyleLockedDeclarationBlock> 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();
+
+ // Have to get a parent ComputedStyle for inherit-like relative values (2em,
+ // bolder, etc.)
+ RefPtr<const ComputedStyle> parentStyle;
+ if (aElement) {
+ parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement);
+ if (NS_WARN_IF(aPresShell->IsDestroying())) {
+ // The flush might've killed the shell.
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+ if (!parentStyle) {
+ RefPtr<StyleLockedDeclarationBlock> 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<const ComputedStyle> sc =
+ styleSet->ResolveForDeclarations(parentStyle, declarations);
+
+ // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-font
+ // The font-size component must be converted to CSS px for reserialization,
+ // so we update the declarations with the value from the computed style.
+ if (!sc->StyleFont()->mFont.family.is_system_font) {
+ nsAutoCString computedFontSize;
+ sc->GetComputedPropertyValue(eCSSProperty_font_size, computedFontSize);
+ Servo_DeclarationBlock_SetPropertyById(
+ declarations, eCSSProperty_font_size, &computedFontSize, false, nullptr,
+ ParsingMode::Default, eCompatibility_FullStandards, nullptr,
+ StyleCssRuleType::Style, {});
+ }
+
+ // The font getter is required to be reserialized based on what we
+ // parsed (including having line-height removed).
+ Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont);
+ return sc.forget();
+}
+
+static already_AddRefed<StyleLockedDeclarationBlock>
+CreateFilterDeclarationForServo(const nsACString& aFilter,
+ Document* aDocument) {
+ return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument);
+}
+
+static already_AddRefed<const ComputedStyle> ResolveFilterStyleForServo(
+ const nsACString& aFilterString, const ComputedStyle* aParentStyle,
+ PresShell* aPresShell, ErrorResult& aError) {
+ RefPtr<StyleLockedDeclarationBlock> 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<const ComputedStyle> computedValues =
+ styleSet->ResolveForDeclarations(aParentStyle, declarations);
+
+ return computedValues.forget();
+}
+
+bool CanvasRenderingContext2D::ParseFilter(
+ const nsACString& aString, StyleOwnedSlice<StyleFilter>& aFilterChain,
+ ErrorResult& aError) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsAutoCString usedFont; // unused
+
+ RefPtr<const ComputedStyle> parentStyle = GetFontStyleForServo(
+ mCanvasElement, GetFont(), presShell, usedFont, aError);
+ if (!parentStyle) {
+ return false;
+ }
+
+ RefPtr<const 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();
+ }
+ }
+}
+
+static already_AddRefed<const ComputedStyle> ResolveStyleForServo(
+ nsCSSPropertyID aProperty, const nsACString& aString,
+ const ComputedStyle* aParentStyle, PresShell* aPresShell,
+ ErrorResult& aError) {
+ RefPtr<StyleLockedDeclarationBlock> declarations =
+ CreateDeclarationForServo(aProperty, aString, aPresShell->GetDocument());
+ if (!declarations) {
+ return nullptr;
+ }
+
+ // In addition to unparseable values, reject 'inherit' and 'initial'.
+ if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, aProperty)) {
+ return nullptr;
+ }
+
+ ServoStyleSet* styleSet = aPresShell->StyleSet();
+ return styleSet->ResolveForDeclarations(aParentStyle, declarations);
+}
+
+already_AddRefed<const ComputedStyle>
+CanvasRenderingContext2D::ResolveStyleForProperty(nsCSSPropertyID aProperty,
+ const nsACString& aValue) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (NS_WARN_IF(!presShell)) {
+ return nullptr;
+ }
+
+ nsAutoCString usedFont;
+ IgnoredErrorResult err;
+ RefPtr<const ComputedStyle> parentStyle =
+ GetFontStyleForServo(mCanvasElement, GetFont(), presShell, usedFont, err);
+ if (!parentStyle) {
+ return nullptr;
+ }
+
+ return ResolveStyleForServo(aProperty, aValue, parentStyle, presShell, err);
+}
+
+void CanvasRenderingContext2D::GetLetterSpacing(nsACString& aLetterSpacing) {
+ if (CurrentState().letterSpacingStr.IsEmpty()) {
+ aLetterSpacing.AssignLiteral("0px");
+ } else {
+ aLetterSpacing = CurrentState().letterSpacingStr;
+ }
+}
+
+void CanvasRenderingContext2D::SetLetterSpacing(
+ const nsACString& aLetterSpacing) {
+ ParseSpacing(aLetterSpacing, &CurrentState().letterSpacing,
+ CurrentState().letterSpacingStr);
+}
+
+void CanvasRenderingContext2D::GetWordSpacing(nsACString& aWordSpacing) {
+ if (CurrentState().wordSpacingStr.IsEmpty()) {
+ aWordSpacing.AssignLiteral("0px");
+ } else {
+ aWordSpacing = CurrentState().wordSpacingStr;
+ }
+}
+
+void CanvasRenderingContext2D::SetWordSpacing(const nsACString& aWordSpacing) {
+ ParseSpacing(aWordSpacing, &CurrentState().wordSpacing,
+ CurrentState().wordSpacingStr);
+}
+
+void CanvasRenderingContext2D::ParseSpacing(const nsACString& aSpacing,
+ float* aValue,
+ nsACString& aNormalized) {
+ // Normalize whitespace in the string before trying to parse it, as we want
+ // to store it in normalized form, and this allows a simple check against the
+ // 'normal' keyword, which is not accepted.
+ nsAutoCString normalized(aSpacing);
+ normalized.CompressWhitespace(true, true);
+ if (normalized.Equals("normal", nsCaseInsensitiveCStringComparator)) {
+ return;
+ }
+ float value;
+ if (!Servo_ParseAbsoluteLength(&normalized, &value)) {
+ if (!GetPresShell()) {
+ return;
+ }
+ RefPtr<const ComputedStyle> style =
+ ResolveStyleForProperty(eCSSProperty_letter_spacing, aSpacing);
+ if (!style) {
+ return;
+ }
+ value = style->StyleText()->mLetterSpacing.ToCSSPixels();
+ }
+ aNormalized = normalized;
+ *aValue = value;
+}
+
+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 {
+ nsFontMetrics::Params params;
+ params.language = mFontLanguage;
+ params.explicitLanguage = mExplicitLanguage;
+ params.textPerf = mPresContext->GetTextPerfMetrics();
+ params.featureValueLookup = mPresContext->GetFontFeatureValuesLookup();
+ RefPtr<nsFontMetrics> fontMetrics =
+ mPresContext->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]) {
+ auto& style = state->patternStyles[Style::FILL];
+ CanvasPattern::RepeatMode repeat = style->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;
+ if ((limitx || limity) && style->mTransform.IsRectilinear()) {
+ // For rectilinear transforms, we can just get the transformed pattern
+ // bounds and intersect them with the fill rectangle bounds.
+ // TODO: If the transform is not rectilinear, then we would need a fully
+ // general clip path to represent the X and Y clip planes bounding the
+ // pattern. For such cases, it would be more efficient to rely on Skia's
+ // Decal tiling mode rather than trying to generate a path. Until then,
+ // just punt to relying on the default Clamp mode.
+ gfx::Rect patternBounds(style->mSurface->GetRect());
+ patternBounds = style->mTransform.TransformBounds(patternBounds);
+ if (style->mTransform.HasNonAxisAlignedTransform()) {
+ // If there is an rotation (90 or 270 degrees), the X axis of the
+ // pattern projects onto the Y axis of the geometry, and vice versa.
+ std::swap(limitx, limity);
+ }
+ // We always need to execute painting for non-over operators, even if
+ // we end up with w/h = 0. The default Rect::Intersect can cause both
+ // dimensions to become empty if either dimension individually fails
+ // to overlap, which is unsuitable. Instead, we need to independently
+ // limit the supplied rectangle on each dimension as required.
+ if (limitx) {
+ double x2 = aX + aW;
+ aX = std::max(aX, double(patternBounds.x));
+ aW = std::max(std::min(x2, double(patternBounds.XMost())) - aX, 0.0);
+ }
+ if (limity) {
+ double y2 = aY + aH;
+ aY = std::max(aY, double(patternBounds.y));
+ aH = std::max(std::min(y2, double(patternBounds.YMost())) - aY, 0.0);
+ }
+ }
+ }
+ state = nullptr;
+
+ bool isColor;
+ bool discardContent = PatternIsOpaque(Style::FILL, &isColor) &&
+ (CurrentState().op == CompositionOp::OP_OVER ||
+ CurrentState().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, true);
+ CompositionOp op = target.UsedOperation();
+ 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);
+ }
+
+ if (!IsTargetValid()) {
+ return;
+ }
+
+ if (!aH) {
+ CapStyle cap = CapStyle::BUTT;
+ if (CurrentState().lineJoin == JoinStyle::ROUND) {
+ cap = CapStyle::ROUND;
+ }
+ AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
+ auto op = target.UsedOperation();
+ 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, true);
+ auto op = target.UsedOperation();
+ 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, true);
+ auto op = target.UsedOperation();
+ 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, true);
+ if (!target) {
+ return;
+ }
+
+ auto op = target.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, true);
+ if (!target) {
+ return;
+ }
+
+ auto op = target.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, true);
+ if (!target) {
+ return;
+ }
+
+ auto op = target.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, true);
+ if (!target) {
+ return;
+ }
+
+ auto op = target.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(ElementState::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.value - p1.x.value) * (p0.y.value - p1.y.value) +
+ (p2.y.value - p1.y.value) * (p1.x.value - p0.x.value);
+ 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();
+ }
+}
+
+// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
+static void RoundRectImpl(
+ PathBuilder* aPathBuilder, const Maybe<Matrix>& aTransform, double aX,
+ double aY, double aW, double aH,
+ const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
+ aRadii,
+ ErrorResult& aError) {
+ // Step 1. If any of x, y, w, or h are infinite or NaN, then return.
+ if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) ||
+ !std::isfinite(aH)) {
+ return;
+ }
+
+ nsTArray<OwningUnrestrictedDoubleOrDOMPointInit> radii;
+ // Step 2. If radii is an unrestricted double or DOMPointInit, then set radii
+ // to « radii ».
+ if (aRadii.IsUnrestrictedDouble()) {
+ radii.AppendElement()->SetAsUnrestrictedDouble() =
+ aRadii.GetAsUnrestrictedDouble();
+ } else if (aRadii.IsDOMPointInit()) {
+ radii.AppendElement()->SetAsDOMPointInit() = aRadii.GetAsDOMPointInit();
+ } else {
+ radii = aRadii.GetAsUnrestrictedDoubleOrDOMPointInitSequence();
+ // Step 3. If radii is not a list of size one, two, three, or
+ // four, then throw a RangeError.
+ if (radii.Length() < 1 || radii.Length() > 4) {
+ aError.ThrowRangeError("Can have between 1 and 4 radii");
+ return;
+ }
+ }
+
+ // Step 4. Let normalizedRadii be an empty list.
+ AutoTArray<Size, 4> normalizedRadii;
+
+ // Step 5. For each radius of radii:
+ for (const auto& radius : radii) {
+ // Step 5.1. If radius is a DOMPointInit:
+ if (radius.IsDOMPointInit()) {
+ const DOMPointInit& point = radius.GetAsDOMPointInit();
+ // Step 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then
+ // return.
+ if (!std::isfinite(point.mX) || !std::isfinite(point.mY)) {
+ return;
+ }
+
+ // Step 5.1.2. If radius["x"] or radius["y"] is negative, then
+ // throw a RangeError.
+ if (point.mX < 0 || point.mY < 0) {
+ aError.ThrowRangeError("Radius can not be negative");
+ return;
+ }
+
+ // Step 5.1.3. Otherwise, append radius to
+ // normalizedRadii.
+ normalizedRadii.AppendElement(
+ Size(gfx::Float(point.mX), gfx::Float(point.mY)));
+ continue;
+ }
+
+ // Step 5.2. If radius is a unrestricted double:
+ double r = radius.GetAsUnrestrictedDouble();
+ // Step 5.2.1. If radius is infinite or NaN, then return.
+ if (!std::isfinite(r)) {
+ return;
+ }
+
+ // Step 5.2.2. If radius is negative, then throw a RangeError.
+ if (r < 0) {
+ aError.ThrowRangeError("Radius can not be negative");
+ return;
+ }
+
+ // Step 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to
+ // normalizedRadii.
+ normalizedRadii.AppendElement(Size(gfx::Float(r), gfx::Float(r)));
+ }
+
+ // Step 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null.
+ Size upperLeft, upperRight, lowerRight, lowerLeft;
+
+ if (normalizedRadii.Length() == 4) {
+ // Step 7. If normalizedRadii's size is 4, then set upperLeft to
+ // normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight
+ // to normalizedRadii[2], and set lowerLeft to normalizedRadii[3].
+ upperLeft = normalizedRadii[0];
+ upperRight = normalizedRadii[1];
+ lowerRight = normalizedRadii[2];
+ lowerLeft = normalizedRadii[3];
+ } else if (normalizedRadii.Length() == 3) {
+ // Step 8. If normalizedRadii's size is 3, then set upperLeft to
+ // normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1],
+ // and set lowerRight to normalizedRadii[2].
+ upperLeft = normalizedRadii[0];
+ upperRight = normalizedRadii[1];
+ lowerRight = normalizedRadii[2];
+ lowerLeft = normalizedRadii[1];
+ } else if (normalizedRadii.Length() == 2) {
+ // Step 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight
+ // to normalizedRadii[0] and set upperRight and lowerLeft to
+ // normalizedRadii[1].
+ upperLeft = normalizedRadii[0];
+ upperRight = normalizedRadii[1];
+ lowerRight = normalizedRadii[0];
+ lowerLeft = normalizedRadii[1];
+ } else {
+ // Step 10. If normalizedRadii's size is 1, then set upperLeft, upperRight,
+ // lowerRight, and lowerLeft to normalizedRadii[0].
+ MOZ_ASSERT(normalizedRadii.Length() == 1);
+ upperLeft = normalizedRadii[0];
+ upperRight = normalizedRadii[0];
+ lowerRight = normalizedRadii[0];
+ lowerLeft = normalizedRadii[0];
+ }
+
+ // This is not as specified but copied from Chrome.
+ // XXX Maybe if we implemented Step 12 (the path algorithm) per
+ // spec this wouldn't be needed?
+ Float x(aX), y(aY), w(aW), h(aH);
+ bool clockwise = true;
+ if (w < 0) {
+ // Horizontal flip
+ clockwise = false;
+ x += w;
+ w = -w;
+ std::swap(upperLeft, upperRight);
+ std::swap(lowerLeft, lowerRight);
+ }
+
+ if (h < 0) {
+ // Vertical flip
+ clockwise = !clockwise;
+ y += h;
+ h = -h;
+ std::swap(upperLeft, lowerLeft);
+ std::swap(upperRight, lowerRight);
+ }
+
+ // Step 11. Corner curves must not overlap. Scale all radii to prevent this:
+ // Step 11.1. Let top be upperLeft["x"] + upperRight["x"].
+ Float top = upperLeft.width + upperRight.width;
+ // Step 11.2. Let right be upperRight["y"] + lowerRight["y"].
+ Float right = upperRight.height + lowerRight.height;
+ // Step 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"].
+ Float bottom = lowerRight.width + lowerLeft.width;
+ // Step 11.4. Let left be upperLeft["y"] + lowerLeft["y"].
+ Float left = upperLeft.height + lowerLeft.height;
+ // Step 11.5. Let scale be the minimum value of the ratios w / top, h / right,
+ // w / bottom, h / left.
+ Float scale = std::min({w / top, h / right, w / bottom, h / left});
+ // Step 11.6. If scale is less than 1, then set the x and y members of
+ // upperLeft, upperRight, lowerLeft, and lowerRight to their current values
+ // multiplied by scale.
+ if (scale < 1.0f) {
+ upperLeft = upperLeft * scale;
+ upperRight = upperRight * scale;
+ lowerLeft = lowerLeft * scale;
+ lowerRight = lowerRight * scale;
+ }
+
+ // Step 12. Create a new subpath:
+ // Step 13. Mark the subpath as closed.
+ // Note: Implemented by AppendRoundedRectToPath, which is shared with CSS
+ // borders etc.
+ gfx::Rect rect{x, y, w, h};
+ RectCornerRadii cornerRadii(upperLeft, upperRight, lowerRight, lowerLeft);
+ AppendRoundedRectToPath(aPathBuilder, rect, cornerRadii, clockwise,
+ aTransform);
+
+ // Step 14. Create a new subpath with the point (x, y) as the only point in
+ // the subpath.
+ // XXX We don't seem to be doing this for ::Rect either?
+}
+
+void CanvasRenderingContext2D::RoundRect(
+ double aX, double aY, double aW, double aH,
+ const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
+ aRadii,
+ ErrorResult& aError) {
+ EnsureWritablePath();
+
+ PathBuilder* builder = mPathBuilder;
+ Maybe<Matrix> transform = Nothing();
+ if (!builder) {
+ builder = mDSPathBuilder;
+ transform = Some(mTarget->GetTransform());
+ }
+
+ RoundRectImpl(builder, transform, aX, aY, aW, aH, aRadii, aError);
+}
+
+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);
+ if (aError.Failed()) {
+ return;
+ }
+
+ // If letterSpacing or wordSpacing is present, recompute to account for
+ // changes to font-relative dimensions.
+ UpdateSpacing();
+}
+
+bool CanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
+ ErrorResult& aError) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ return SetFontInternalDisconnected(aFont, aError);
+ }
+
+ nsPresContext* c = presShell->GetPresContext();
+ FontStyleCacheKey key{aFont, c->RestyleManager()->GetRestyleGeneration()};
+ auto entry = mFontStyleCache.Lookup(key);
+ if (!entry) {
+ FontStyleData newData;
+ newData.mKey = key;
+ newData.mStyle = GetFontStyleForServo(mCanvasElement, aFont, presShell,
+ newData.mUsedFont, aError);
+ entry.Set(newData);
+ }
+
+ const auto& data = entry.Data();
+ if (!data.mStyle) {
+ return false;
+ }
+
+ const nsStyleFont* fontStyle = data.mStyle->StyleFont();
+
+ // 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);
+
+ // Our FontKerning constants (see the enum definition) are the same as the
+ // NS_FONT_KERNING_* values so we can simply assign here.
+ resizedFont.kerning = uint8_t(CurrentState().fontKerning);
+
+ c->Document()->FlushUserFontSet();
+
+ nsFontMetrics::Params params;
+ params.language = fontStyle->mLanguage;
+ params.explicitLanguage = fontStyle->mExplicitLanguage;
+ params.userFontSet = c->GetUserFontSet();
+ params.textPerf = c->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> metrics = c->GetMetricsFor(resizedFont, params);
+
+ gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup();
+ CurrentState().fontGroup = newFontGroup;
+ NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
+ CurrentState().font = data.mUsedFont;
+ CurrentState().fontFont = fontStyle->mFont;
+ CurrentState().fontFont.size = fontStyle->mSize;
+ CurrentState().fontLanguage = fontStyle->mLanguage;
+ CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
+
+ return true;
+}
+
+static nsAutoCString FamilyListToString(
+ const StyleFontFamilyList& aFamilyList) {
+ return StringJoin(","_ns, aFamilyList.list.AsSpan(),
+ [](nsACString& dst, const StyleSingleFontFamily& name) {
+ name.AppendToString(dst);
+ });
+}
+
+static void SerializeFontForCanvas(const StyleFontFamilyList& aList,
+ const gfxFontStyle& aStyle,
+ nsACString& aUsedFont) {
+ // Re-serialize the font shorthand as required by the canvas spec.
+ aUsedFont.Truncate();
+
+ if (!aStyle.style.IsNormal()) {
+ aStyle.style.ToString(aUsedFont);
+ aUsedFont.Append(" ");
+ }
+
+ // font-weight is serialized as a number
+ if (!aStyle.weight.IsNormal()) {
+ aUsedFont.AppendFloat(aStyle.weight.ToFloat());
+ aUsedFont.Append(" ");
+ }
+
+ // font-stretch is serialized using CSS Fonts 3 keywords, not percentages.
+ if (!aStyle.stretch.IsNormal() &&
+ Servo_FontStretch_SerializeKeyword(&aStyle.stretch, &aUsedFont)) {
+ aUsedFont.Append(" ");
+ }
+
+ // Serialize the computed (not specified) size, and the family name(s).
+ aUsedFont.AppendFloat(aStyle.size);
+ aUsedFont.Append("px ");
+ aUsedFont.Append(FamilyListToString(aList));
+}
+
+bool CanvasRenderingContext2D::SetFontInternalDisconnected(
+ const nsACString& aFont, ErrorResult& aError) {
+ FontFaceSet* fontFaceSet = nullptr;
+ if (mCanvasElement) {
+ fontFaceSet = mCanvasElement->OwnerDoc()->Fonts();
+ } else {
+ nsIGlobalObject* global = GetParentObject();
+ fontFaceSet = global ? global->GetFonts() : nullptr;
+ }
+
+ FontFaceSetImpl* fontFaceSetImpl =
+ fontFaceSet ? fontFaceSet->GetImpl() : nullptr;
+ RefPtr<URLExtraData> urlExtraData =
+ fontFaceSetImpl ? fontFaceSetImpl->GetURLExtraData() : nullptr;
+
+ if (NS_WARN_IF(!urlExtraData)) {
+ // Provided we have a FontFaceSetImpl object, this should only happen on
+ // worker threads, where we failed to initialize the worker before it was
+ // shutdown.
+ aError.ThrowInvalidStateError("Missing URLExtraData");
+ return false;
+ }
+
+ if (fontFaceSetImpl) {
+ fontFaceSetImpl->FlushUserFontSet();
+ }
+
+ // In the OffscreenCanvas case we don't have the context necessary to call
+ // GetFontStyleForServo(), as we do in the main-thread canvas context, so
+ // instead we borrow ParseFontShorthandForMatching to parse the attribute.
+ StyleComputedFontStyleDescriptor style(
+ StyleComputedFontStyleDescriptor::Normal());
+ StyleFontFamilyList list;
+ gfxFontStyle fontStyle;
+ float size = 0.0f;
+ if (!ServoCSSParser::ParseFontShorthandForMatching(
+ aFont, urlExtraData, list, fontStyle.style, fontStyle.stretch,
+ fontStyle.weight, &size)) {
+ return false;
+ }
+
+ fontStyle.size = size;
+
+ // Set the kerning feature, if required by the fontKerning attribute.
+ gfxFontFeature setting{TRUETYPE_TAG('k', 'e', 'r', 'n'), 0};
+ switch (CurrentState().fontKerning) {
+ case FontKerning::NONE:
+ setting.mValue = 0;
+ fontStyle.featureSettings.AppendElement(setting);
+ break;
+ case FontKerning::NORMAL:
+ setting.mValue = 1;
+ fontStyle.featureSettings.AppendElement(setting);
+ break;
+ default:
+ // auto case implies use user agent default
+ break;
+ }
+
+ // If we have a canvas element, get its lang (if known).
+ RefPtr<nsAtom> language;
+ bool explicitLanguage = false;
+ if (mCanvasElement) {
+ language = mCanvasElement->FragmentOrElement::GetLang();
+ if (language) {
+ explicitLanguage = true;
+ } else {
+ language = mCanvasElement->OwnerDoc()->GetLanguageForStyle();
+ }
+ } else {
+ // Pass the OS default language, to behave similarly to HTML or canvas-
+ // element content with no language tag.
+ language = nsLanguageAtomService::GetService()->GetLocaleLanguage();
+ }
+
+ // TODO: Cache fontGroups in the Worker (use an nsFontCache?)
+ gfxFontGroup* fontGroup =
+ new gfxFontGroup(nullptr, // aPresContext
+ list, // aFontFamilyList
+ &fontStyle, // aStyle
+ language, // aLanguage
+ explicitLanguage, // aExplicitLanguage
+ nullptr, // aTextPerf
+ fontFaceSetImpl, // aUserFontSet
+ 1.0, // aDevToCssSize
+ StyleFontVariantEmoji::Normal);
+ CurrentState().fontGroup = fontGroup;
+ SerializeFontForCanvas(list, fontStyle, CurrentState().font);
+ CurrentState().fontFont = nsFont(StyleFontFamily{list, false, false},
+ StyleCSSPixelLength::FromPixels(size));
+ CurrentState().fontLanguage = nullptr;
+ CurrentState().fontExplicitLanguage = false;
+ return true;
+}
+
+void CanvasRenderingContext2D::UpdateSpacing() {
+ auto state = CurrentState();
+ if (!state.letterSpacingStr.IsEmpty()) {
+ SetLetterSpacing(state.letterSpacingStr);
+ }
+ if (!state.wordSpacingStr.IsEmpty()) {
+ SetWordSpacing(state.wordSpacingStr);
+ }
+}
+
+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;
+ }
+}
+
+void CanvasRenderingContext2D::SetDirection(const nsAString& aDirection) {
+ if (aDirection.EqualsLiteral("ltr")) {
+ CurrentState().textDirection = TextDirection::LTR;
+ } else if (aDirection.EqualsLiteral("rtl")) {
+ CurrentState().textDirection = TextDirection::RTL;
+ } else if (aDirection.EqualsLiteral("inherit")) {
+ CurrentState().textDirection = TextDirection::INHERIT;
+ }
+}
+
+void CanvasRenderingContext2D::GetDirection(nsAString& aDirection) {
+ switch (CurrentState().textDirection) {
+ case TextDirection::LTR:
+ aDirection.AssignLiteral("ltr");
+ break;
+ case TextDirection::RTL:
+ aDirection.AssignLiteral("rtl");
+ break;
+ case TextDirection::INHERIT:
+ aDirection.AssignLiteral("inherit");
+ break;
+ }
+}
+
+void CanvasRenderingContext2D::SetFontKerning(const nsAString& aFontKerning) {
+ auto oldValue = CurrentState().fontKerning;
+ if (aFontKerning.EqualsLiteral("auto")) {
+ CurrentState().fontKerning = FontKerning::AUTO;
+ } else if (aFontKerning.EqualsLiteral("normal")) {
+ CurrentState().fontKerning = FontKerning::NORMAL;
+ } else if (aFontKerning.EqualsLiteral("none")) {
+ CurrentState().fontKerning = FontKerning::NONE;
+ }
+ if (CurrentState().fontKerning != oldValue) {
+ CurrentState().fontGroup = nullptr;
+ }
+}
+
+void CanvasRenderingContext2D::GetFontKerning(nsAString& aFontKerning) {
+ switch (CurrentState().fontKerning) {
+ case FontKerning::AUTO:
+ aFontKerning.AssignLiteral("auto");
+ break;
+ case FontKerning::NORMAL:
+ aFontKerning.AssignLiteral("normal");
+ break;
+ case FontKerning::NONE:
+ aFontKerning.AssignLiteral("none");
+ 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).
+ * We also replace characters with Bidi type Segment Separator or Block
+ * Separator.
+ * @param str The string whose whitespace characters to replace.
+ */
+static inline void TextReplaceWhitespaceCharacters(nsAutoString& aStr) {
+ aStr.ReplaceChar(u"\x09\x0A\x0B\x0C\x0D\x1C\x1D\x1E\x1F\x85\x2029",
+ 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);
+}
+
+/**
+ * Used for nsBidiPresUtils::ProcessText
+ */
+struct MOZ_STACK_CLASS CanvasBidiProcessor final
+ : public nsBidiPresUtils::BidiProcessor {
+ using Style = CanvasRenderingContext2D::Style;
+
+ CanvasBidiProcessor() : nsBidiPresUtils::BidiProcessor() {
+ if (StaticPrefs::gfx_missing_fonts_notify()) {
+ mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
+ }
+ }
+
+ ~CanvasBidiProcessor() {
+ // notify front-end code if we encountered missing glyphs in any script
+ if (mMissingFonts) {
+ mMissingFonts->Flush();
+ }
+ }
+
+ class PropertyProvider : public gfxTextRun::PropertyProvider {
+ public:
+ explicit PropertyProvider(const CanvasBidiProcessor& aProcessor)
+ : mProcessor(aProcessor) {}
+
+ void GetSpacing(gfxTextRun::Range aRange,
+ gfxFont::Spacing* aSpacing) const {
+ for (auto i = aRange.start; i < aRange.end; ++i) {
+ auto* charGlyphs = mProcessor.mTextRun->GetCharacterGlyphs();
+ if (i == mProcessor.mTextRun->GetLength() - 1 ||
+ (charGlyphs[i + 1].IsClusterStart() &&
+ charGlyphs[i + 1].IsLigatureGroupStart())) {
+ // Currently we add all the letterspacing to the right of the glyph,
+ // which is similar to Chrome's behavior, though the LTR vs RTL
+ // asymmetry seems unfortunate.
+ if (mProcessor.mTextRun->IsRightToLeft()) {
+ aSpacing->mAfter = 0;
+ aSpacing->mBefore = mProcessor.mLetterSpacing;
+ } else {
+ aSpacing->mBefore = 0;
+ aSpacing->mAfter = mProcessor.mLetterSpacing;
+ }
+ } else {
+ aSpacing->mBefore = 0;
+ aSpacing->mAfter = 0;
+ }
+ if (charGlyphs[i].CharIsSpace()) {
+ if (mProcessor.mTextRun->IsRightToLeft()) {
+ aSpacing->mBefore += mProcessor.mWordSpacing;
+ } else {
+ aSpacing->mAfter += mProcessor.mWordSpacing;
+ }
+ }
+ aSpacing++;
+ }
+ }
+
+ mozilla::StyleHyphens GetHyphensOption() const {
+ return mozilla::StyleHyphens::None;
+ }
+
+ // Methods only used when hyphenation is active, not relevant to canvas2d:
+ void GetHyphenationBreaks(gfxTextRun::Range aRange,
+ gfxTextRun::HyphenType* aBreakBefore) const {
+ MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
+ }
+ gfxFloat GetHyphenWidth() const {
+ MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
+ return 0.0;
+ }
+ already_AddRefed<DrawTarget> GetDrawTarget() const {
+ MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
+ return nullptr;
+ }
+ uint32_t GetAppUnitsPerDevUnit() const {
+ MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
+ return 60;
+ }
+ gfx::ShapedTextFlags GetShapedTextFlags() const {
+ MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
+ return gfx::ShapedTextFlags();
+ }
+
+ private:
+ const CanvasBidiProcessor& mProcessor;
+ };
+
+ using ContextState = CanvasRenderingContext2D::ContextState;
+
+ void SetText(const char16_t* aText, int32_t aLength,
+ intl::BidiDirection aDirection) override {
+ if (mIgnoreSetText) {
+ // We've been told to ignore SetText because the processor is only ever
+ // handling a single, fixed string.
+ MOZ_ASSERT(mTextRun && mTextRun->GetLength() == uint32_t(aLength));
+ return;
+ }
+ mSetTextCount++;
+ auto* pfl = gfxPlatformFontList::PlatformFontList();
+ pfl->Lock();
+ mFontgrp->CheckForUpdatedPlatformList();
+ mFontgrp->UpdateUserFonts(); // ensure user font generation is current
+ // adjust flags for current direction run
+ gfx::ShapedTextFlags flags = mTextRunFlags;
+ if (aDirection == intl::BidiDirection::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());
+ pfl->Unlock();
+ }
+
+ nscoord GetWidth() override {
+ PropertyProvider provider(*this);
+ gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
+ mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
+ : gfxFont::LOOSE_INK_EXTENTS,
+ mDrawTarget, &provider);
+
+ // 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();
+ }
+
+ void DrawText(nscoord aXOffset) override {
+ gfx::Point point = mPt;
+ bool rtl = mTextRun->IsRightToLeft();
+ bool verticalRun = mTextRun->IsVertical();
+ RefPtr<gfxPattern> pattern;
+
+ float& inlineCoord = verticalRun ? point.y.value : point.x.value;
+ inlineCoord += aXOffset;
+
+ PropertyProvider provider(*this);
+
+ // 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, &provider);
+ 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;
+ const ContextState& state = mCtx->CurrentState();
+
+ gfx::Rect bounds;
+ if (mCtx->NeedToCalculateBounds()) {
+ bounds = ToRect(mBoundingBox);
+ bounds.MoveBy(mPt / mAppUnitsPerDevPixel);
+ if (style == Style::STROKE) {
+ bounds.Inflate(state.lineWidth / 2.0);
+ }
+ bounds = mDrawTarget->GetTransform().TransformBounds(bounds);
+ }
+
+ AdjustedTarget target(mCtx, bounds.IsEmpty() ? nullptr : &bounds, false);
+ if (!target) {
+ return;
+ }
+
+ gfxContext thebes(target, /* aPreserveTransform */ true);
+ gfxTextRun::DrawParams params(&thebes);
+
+ params.allowGDI = false;
+
+ 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 = target.UsedOperation();
+ if (!mCtx->IsTargetValid()) {
+ return;
+ }
+
+ params.drawOpts = &drawOpts;
+ params.provider = &provider;
+
+ 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 = nullptr;
+
+ // position of the left side of the string, alphabetic baseline
+ gfx::Point mPt;
+
+ // current font
+ gfxFontGroup* mFontgrp = nullptr;
+
+ // spacing adjustments to be applied
+ gfx::Float mLetterSpacing = 0.0f;
+ gfx::Float mWordSpacing = 0.0f;
+
+ // 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 = 0;
+
+ // operation (fill or stroke)
+ CanvasRenderingContext2D::TextDrawOperation mOp =
+ CanvasRenderingContext2D::TextDrawOperation::FILL;
+
+ // 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 = gfx::ShapedTextFlags();
+
+ // Count of how many times SetText has been called on this processor.
+ uint32_t mSetTextCount = 0;
+
+ // true iff the bounding box should be measured
+ bool mDoMeasureBoundingBox = false;
+
+ // true if future SetText calls should be ignored
+ bool mIgnoreSetText = false;
+};
+
+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
+
+ RefPtr<gfxFontGroup> currentFontStyle = GetCurrentFontStyle();
+ if (NS_WARN_IF(!currentFontStyle)) {
+ aError = NS_ERROR_FAILURE;
+ return nullptr;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ RefPtr<Document> document = presShell ? presShell->GetDocument() : nullptr;
+
+ // 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 || std::isnan(aMaxWidth.Value()))) {
+ textToDraw.Truncate();
+ }
+
+ RefPtr<const ComputedStyle> canvasStyle;
+ if (mCanvasElement) {
+ canvasStyle = nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
+ }
+
+ // Get text direction, either from the property or inherited from context.
+ const ContextState& state = CurrentState();
+ bool isRTL;
+ switch (state.textDirection) {
+ case TextDirection::LTR:
+ isRTL = false;
+ break;
+ case TextDirection::RTL:
+ isRTL = true;
+ break;
+ case TextDirection::INHERIT:
+ if (canvasStyle) {
+ isRTL =
+ canvasStyle->StyleVisibility()->mDirection == StyleDirection::Rtl;
+ } else if (document) {
+ isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) ==
+ IBMBIDI_TEXTDIRECTION_RTL;
+ } else {
+ isRTL = false;
+ }
+ break;
+ }
+
+ // This is only needed to know if we can know the drawing bounding box easily.
+ const bool doCalculateBounds = NeedToCalculateBounds();
+ if (presShell && presShell->IsDestroying()) {
+ aError = NS_ERROR_FAILURE;
+ return nullptr;
+ }
+
+ nsPresContext* presContext =
+ presShell ? presShell->GetPresContext() : nullptr;
+
+ if (presContext) {
+ // 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 (!std::isfinite(aX) || !std::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::ThreadLocalScreenReferenceDrawTarget();
+
+ // 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;
+
+ if (state.letterSpacing != 0.0 || state.wordSpacing != 0.0) {
+ processor.mLetterSpacing =
+ state.letterSpacing * processor.mAppUnitsPerDevPixel;
+ processor.mWordSpacing = state.wordSpacing * processor.mAppUnitsPerDevPixel;
+ processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
+ if (state.letterSpacing != 0.0) {
+ processor.mTextRunFlags |=
+ gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
+ }
+ }
+
+ nscoord totalWidthCoord;
+
+ processor.mFontgrp
+ ->UpdateUserFonts(); // ensure user font generation is current
+ RefPtr<gfxFont> font = processor.mFontgrp->GetFirstValidFont();
+ const gfxFont::Metrics& fontMetrics =
+ font->GetMetrics(nsFontMetrics::eHorizontal);
+
+ // 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 ? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(),
+ presContext, processor, nsBidiPresUtils::MODE_MEASURE, nullptr, 0,
+ &totalWidthCoord, mBidiEngine);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // If ProcessText only called SetText once, we're dealing with a single run,
+ // and so we don't need to repeat SetText and textRun construction at drawing
+ // time below; we can just re-use the existing textRun.
+ if (processor.mSetTextCount == 1) {
+ processor.mIgnoreSetText = true;
+ }
+
+ float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
+
+ // offset pt.x based on text align
+ gfxFloat anchorX;
+
+ 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
+ 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 ? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(),
+ presContext, 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 (re)initialization for the fontGroup since it's rather expensive.
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ nsPresContext* presContext =
+ presShell ? presShell->GetPresContext() : nullptr;
+
+ // If we have a cached fontGroup, check that it is valid for the current
+ // prescontext; if not, we need to discard and re-create it.
+ RefPtr<gfxFontGroup>& fontGroup = CurrentState().fontGroup;
+ if (fontGroup) {
+ if (fontGroup->GetPresContext() != presContext) {
+ fontGroup = nullptr;
+ }
+ }
+
+ if (!fontGroup) {
+ ErrorResult err;
+ constexpr auto kDefaultFontStyle = "10px sans-serif"_ns;
+ const float kDefaultFontSize = 10.0;
+ // If the font has already been set, we're re-creating the fontGroup
+ // and should re-use the existing font attribute; if not, we initialize
+ // it to the canvas default.
+ const nsCString& currentFont = CurrentState().font;
+ bool fontUpdated = SetFontInternal(
+ currentFont.IsEmpty() ? kDefaultFontStyle : currentFont, 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;
+ int32_t perDevPixel, perCSSPixel;
+ GetAppUnitsValues(&perDevPixel, &perCSSPixel);
+ gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
+ const auto* sans =
+ Servo_FontFamily_Generic(StyleGenericFontFamily::SansSerif);
+ fontGroup = new gfxFontGroup(
+ presContext, sans->families, &style, language, explicitLanguage,
+ presContext ? presContext->GetTextPerfMetrics() : nullptr, nullptr,
+ devToCssSize, StyleFontVariantEmoji::Normal);
+ if (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.
+ fontGroup->CheckForUpdatedPlatformList();
+ }
+
+ return 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) {
+ return IsPointInPath(aCx, aX, aY, aWinding, Some(&aSubjectPrincipal));
+}
+
+bool CanvasRenderingContext2D::IsPointInPath(
+ JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding,
+ Maybe<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;
+ }
+ } else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting(
+ RFPTarget::CanvasImageExtractionPrompt)) {
+ 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& aSubjectPrincipal) {
+ return IsPointInPath(aCx, aPath, aX, aY, aWinding, Some(&aSubjectPrincipal));
+}
+
+bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx,
+ const CanvasPath& aPath, double aX,
+ double aY,
+ const CanvasWindingRule& aWinding,
+ Maybe<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) {
+ return IsPointInStroke(aCx, aX, aY, Some(&aSubjectPrincipal));
+}
+
+bool CanvasRenderingContext2D::IsPointInStroke(
+ JSContext* aCx, double aX, double aY,
+ Maybe<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;
+ }
+ } else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting(
+ RFPTarget::CanvasImageExtractionPrompt)) {
+ 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& aSubjectPrincipal) {
+ return IsPointInStroke(aCx, aPath, aX, aY, Some(&aSubjectPrincipal));
+}
+
+bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx,
+ const CanvasPath& aPath,
+ double aX, double aY,
+ Maybe<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();
+ }
+
+ // Try to extract an optimized sub-surface.
+ if (RefPtr<SourceSurface> surface =
+ aSurface->ExtractSubrect(roundedOutSourceRectInt)) {
+ *aSourceRect -= roundedOutSourceRect.TopLeft();
+ 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, mTarget);
+ if (!res.mSourceSurface) {
+ return res;
+ }
+
+ res.mCORSUsed = nsLayoutUtils::ImageRequestUsesCORS(imgRequest);
+ 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;
+}
+
+static void SwapScaleWidthHeightForRotation(gfx::Rect& aRect,
+ VideoInfo::Rotation aDegrees) {
+ if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
+ aDegrees == VideoInfo::Rotation::kDegree_270) {
+ std::swap(aRect.width, aRect.height);
+ }
+}
+
+static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
+ gfxFloat aRotatedHeight,
+ VideoInfo::Rotation aDegrees) {
+ Matrix shiftVideoCenterToOrigin;
+ if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
+ aDegrees == VideoInfo::Rotation::kDegree_270) {
+ shiftVideoCenterToOrigin =
+ Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
+ } else {
+ shiftVideoCenterToOrigin =
+ Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
+ }
+
+ auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI;
+ Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle));
+ Matrix shiftLeftTopToOrigin =
+ Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
+ return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
+}
+
+// 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;
+ OffscreenCanvas* offscreenCanvas = 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.IsOffscreenCanvas()) {
+ offscreenCanvas = &aImage.GetAsOffscreenCanvas();
+ nsIntSize size = offscreenCanvas->GetWidthHeight();
+ if (size.IsEmpty()) {
+ return aError.ThrowInvalidStateError("Passed-in canvas is empty");
+ }
+
+ srcSurf = offscreenCanvas->GetSurfaceSnapshot();
+ if (srcSurf) {
+ imgSize = intrinsicImgSize = srcSurf->GetSize();
+ }
+
+ if (offscreenCanvas->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->LogVisibility(
+ mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
+ element = video;
+ }
+
+ srcSurf = CanvasImageCache::LookupCanvas(element, mCanvasElement, mTarget,
+ &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 |
+ nsLayoutUtils::SFE_EXACT_SIZE_SURFACE;
+
+ SurfaceFromElementResult res;
+ if (offscreenCanvas) {
+ res = nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, sfeFlags,
+ mTarget);
+ } else {
+ res = CanvasRenderingContext2D::CachedSurfaceFromElement(element);
+ if (!res.mSourceSurface) {
+ res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
+ }
+ }
+
+ if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
+ // https://html.spec.whatwg.org/#check-the-usability-of-the-image-argument:
+ //
+ // Only throw if the request is broken and the element is an
+ // HTMLImageElement / SVGImageElement. Note that even for those 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 &&
+ (aImage.IsHTMLImageElement() || aImage.IsSVGImageElement())) {
+ aError.ThrowInvalidStateError("Passed-in image is \"broken\"");
+ }
+ return;
+ }
+
+ imgSize = res.mSize;
+ intrinsicImgSize = res.mIntrinsicSize;
+ DoSecurityCheck(res.mPrincipal, res.mIsWriteOnly, res.mCORSUsed);
+
+ if (res.mSourceSurface) {
+ if (res.mImageRequest) {
+ CanvasImageCache::NotifyDrawImage(element, mCanvasElement, mTarget,
+ 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.xScale) < aSw && aDh * Abs(scale.yScale) < 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, true);
+ if (!tempTarget) {
+ gfxWarning() << "Invalid adjusted target in Canvas2D "
+ << gfx::hexa((DrawTarget*)mTarget) << ", "
+ << NeedToDrawShadow() << NeedToApplyFilter();
+ return;
+ }
+
+ auto op = tempTarget.UsedOperation();
+ if (!IsTargetValid() || !tempTarget) {
+ return;
+ }
+
+ VideoInfo::Rotation rotationDeg = VideoInfo::Rotation::kDegree_0;
+ if (HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(element)) {
+ rotationDeg = video->RotationDegrees();
+ }
+
+ gfx::Rect destRect(aDx, aDy, aDw, aDh);
+
+ Matrix transform;
+ if (rotationDeg != VideoInfo::Rotation::kDegree_0) {
+ Matrix preTransform = ComputeRotationMatrix(aDw, aDh, rotationDeg);
+ transform = preTransform * Matrix::Translation(aDx, aDy);
+
+ SwapScaleWidthHeightForRotation(destRect, rotationDeg);
+ // When rotation exists, aDx, aDy is handled by transform, Since aDest.x
+ // aDest.y handling of DrawSurface() does not care about the rotation.
+ destRect.x = 0;
+ destRect.y = 0;
+ }
+ Matrix currentTransform = tempTarget->GetTransform();
+ transform *= currentTransform;
+
+ tempTarget->SetTransform(transform);
+
+ tempTarget.DrawSurface(
+ srcSurf, destRect, sourceRect,
+ DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
+ DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
+
+ tempTarget->SetTransform(currentTransform);
+
+ } 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 || !tempTarget->IsValid()) {
+ return;
+ }
+
+ // Get any existing transforms on the context, including transformations used
+ // for context shadow.
+ Matrix matrix = tempTarget->GetTransform();
+ gfxMatrix contextMatrix = ThebesMatrix(matrix);
+ MatrixScalesDouble contextScale = contextMatrix.ScaleFactors();
+
+ // Scale the dest rect to include the context scale.
+ aDest.Scale((float)contextScale.xScale, (float)contextScale.yScale);
+
+ // Scale the image size to the dest rect, and adjust the source rect to match.
+ MatrixScalesDouble scale(aDest.width / aSrc.width,
+ aDest.height / aSrc.height);
+ IntSize scaledImageSize =
+ IntSize::Ceil(static_cast<float>(scale.xScale * aImgSize.width),
+ static_cast<float>(scale.yScale * aImgSize.height));
+ aSrc.Scale(static_cast<float>(scale.xScale),
+ static_cast<float>(scale.yScale));
+
+ // 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);
+
+ gfxContext context(tempTarget);
+ context.SetMatrixDouble(
+ contextMatrix
+ .PreScale(1.0 / contextScale.xScale, 1.0 / contextScale.yScale)
+ .PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y));
+
+ context.SetOp(tempTarget.UsedOperation());
+
+ // FLAG_CLAMP is added for increased performance, since we never tile here.
+ uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
+
+ // XXX hmm is scaledImageSize really in CSS pixels?
+ CSSIntSize sz(scaledImageSize.width, scaledImageSize.height);
+ 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, 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("clear", CLEAR)
+ else 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("clear", CLEAR)
+ else 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,
+ nsIPrincipal& aSubjectPrincipal,
+ 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;
+ }
+
+ Document* doc = aWindow.GetExtantDoc();
+ if (doc && aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
+ doc->WarnOnceAbout(
+ DeprecatedOperations::eDrawWindowCanvasRenderingContext2D);
+ }
+
+ // Flush layout updates
+ if (!(aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) {
+ nsContentUtils::FlushLayoutForTree(aWindow.GetOuterWindow());
+ }
+
+ CompositionOp op = CurrentState().op;
+ 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;
+ }
+
+ Maybe<nscolor> backgroundColor = ParseColor(aBgColor);
+ if (!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;
+ }
+
+ Maybe<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 = CurrentState().op;
+ if (!IsTargetValid()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+ if (op == CompositionOp::OP_OVER &&
+ (!mBufferProvider || !mBufferProvider->IsShared())) {
+ thebes.emplace(mTarget);
+ thebes.ref().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.emplace(drawDT);
+ thebes.ref().SetMatrix(Matrix::Scaling(matrix._11, matrix._22));
+ }
+ MOZ_ASSERT(thebes.isSome());
+
+ RefPtr<PresShell> presShell = presContext->PresShell();
+
+ Unused << presShell->RenderDocument(r, renderDocFlags, *backgroundColor,
+ &thebes.ref());
+ // 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 = CurrentState().op;
+ 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) {
+ return GetImageData(aCx, aSx, aSy, aSw, aSh, Some(&aSubjectPrincipal),
+ aError);
+}
+
+already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData(
+ JSContext* aCx, int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
+ Maybe<nsIPrincipal*> aSubjectPrincipal, ErrorResult& aError) {
+ if (!mCanvasElement && !mDocShell && !mOffscreenCanvas) {
+ 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.
+ nsIPrincipal* subjectPrincipal =
+ aSubjectPrincipal ? *aSubjectPrincipal : nullptr;
+ if (IsWriteOnly() ||
+ (mCanvasElement && !mCanvasElement->CallerCanRead(subjectPrincipal)) ||
+ (mOffscreenCanvas &&
+ !mOffscreenCanvas->CallerCanRead(subjectPrincipal))) {
+ // 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);
+}
+
+static IntRect ClipImageDataTransfer(IntRect& aSrc, const IntPoint& aDestOffset,
+ const IntSize& aDestBounds) {
+ IntRect dest = aSrc;
+ dest.SafeMoveBy(aDestOffset);
+ dest = IntRect(IntPoint(0, 0), aDestBounds).SafeIntersect(dest);
+
+ aSrc = aSrc.SafeIntersect(dest - aDestOffset);
+ return aSrc + aDestOffset;
+}
+
+nsresult CanvasRenderingContext2D::GetImageDataArray(
+ JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight,
+ Maybe<nsIPrincipal*> aSubjectPrincipal, JSObject** aRetval) {
+ MOZ_ASSERT(aWidth && aHeight);
+
+ // Restrict the typed array length to INT32_MAX because that's all we support
+ // in dom::TypedArray::ComputeState.
+ CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
+ if (!len.isValid() || len.value() > INT32_MAX) {
+ 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 dstWriteRect(0, 0, aWidth, aHeight);
+ IntRect srcReadRect = ClipImageDataTransfer(dstWriteRect, IntPoint(aX, aY),
+ IntSize(mWidth, mHeight));
+ if (srcReadRect.IsEmpty()) {
+ *aRetval = darray;
+ return NS_OK;
+ }
+
+ if (!GetBufferProvider() && !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());
+
+ // 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);
+ } else if (mOffscreenCanvas) {
+ usePlaceholder = mOffscreenCanvas->ShouldResistFingerprinting(
+ RFPTarget::CanvasImageExtractionPrompt);
+ }
+
+ // Clone the data source surface if canvas randomization is enabled. We need
+ // to do this because we don't want to alter the actual image buffer.
+ // Otherwise, we will provide inconsistent image data with multiple calls.
+ //
+ // Note that we don't need to clone if we will use the place holder because
+ // the place holder doesn't use actual image data.
+ bool needRandomizePixels = false;
+ if (!usePlaceholder &&
+ ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
+ needRandomizePixels = true;
+ readback = CreateDataSourceSurfaceByCloning(readback);
+ }
+
+ DataSourceSurface::MappedSurface rawData;
+ if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ 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();
+ } else if (needRandomizePixels) {
+ // Apply the random noises if canvan randomization is enabled. We don't
+ // need to calculate random noises if we are going to use the place
+ // holder.
+
+ const IntSize size = readback->GetSize();
+ nsRFPService::RandomizePixels(GetCookieJarSettings(), rawData.mData,
+ size.height * size.width * 4,
+ SurfaceFormat::A8R8G8B8_UINT32);
+ }
+
+ 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.get()) {
+ return;
+ }
+
+ RefPtr<DrawTarget> errorTarget =
+ gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
+ IntSize(1, 1), SurfaceFormat::B8G8R8A8);
+ MOZ_ASSERT(errorTarget, "Failed to allocate the error target!");
+
+ sErrorTarget.set(errorTarget.forget().take());
+}
+
+void CanvasRenderingContext2D::FillRuleChanged() {
+ if (mPath) {
+ mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
+ mPath = nullptr;
+ }
+}
+
+void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx,
+ int32_t aDy, ErrorResult& aRv) {
+ RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
+ PutImageData_explicit(aDx, aDy, aImageData, false, 0, 0, 0, 0, aRv);
+}
+
+void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx,
+ int32_t aDy, int32_t aDirtyX,
+ int32_t aDirtyY,
+ int32_t aDirtyWidth,
+ int32_t aDirtyHeight,
+ ErrorResult& aRv) {
+ PutImageData_explicit(aDx, aDy, aImageData, true, aDirtyX, aDirtyY,
+ aDirtyWidth, aDirtyHeight, aRv);
+}
+
+void CanvasRenderingContext2D::PutImageData_explicit(
+ int32_t aX, int32_t aY, ImageData& aImageData, bool aHasDirtyRect,
+ int32_t aDirtyX, int32_t aDirtyY, int32_t aDirtyWidth, int32_t aDirtyHeight,
+ ErrorResult& aRv) {
+ RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
+ if (!arr.Init(aImageData.GetDataObject())) {
+ return aRv.ThrowInvalidStateError(
+ "Failed to extract Uint8ClampedArray from ImageData (security check "
+ "failed?)");
+ }
+
+ const uint32_t width = aImageData.Width();
+ const uint32_t height = aImageData.Height();
+ if (width == 0 || height == 0) {
+ return aRv.ThrowInvalidStateError("Passed-in image is empty");
+ }
+
+ IntRect dirtyRect;
+ IntRect imageDataRect(0, 0, width, height);
+
+ 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;
+ }
+
+ IntRect srcRect = dirtyRect;
+ dirtyRect = ClipImageDataTransfer(srcRect, IntPoint(aX, aY),
+ IntSize(mWidth, mHeight));
+ if (dirtyRect.IsEmpty()) {
+ return;
+ }
+
+ arr.ComputeState();
+
+ uint32_t dataLen = arr.Length();
+
+ uint32_t len = width * height * 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();
+ }
+
+ uint8_t* srcData = arr.Data() + srcRect.y * (width * 4) + srcRect.x * 4;
+
+ PremultiplyData(
+ srcData, width * 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;
+
+ // Restrict the typed array length to INT32_MAX because that's all we support
+ // in dom::TypedArray::ComputeState.
+ CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4;
+ if (!len.isValid() || len.value() > INT32_MAX) {
+ 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::OnMemoryPressure() {
+ if (mBufferProvider) {
+ mBufferProvider->OnMemoryPressure();
+ }
+}
+
+void CanvasRenderingContext2D::OnBeforePaintTransaction() {
+ if (!mTarget) return;
+ OnStableState();
+}
+
+void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); }
+
+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 = this;
+ 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 = this;
+ 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::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();
+ } else if (mOffscreenCanvas) {
+ mOffscreenCanvas->SetWriteOnly();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
+
+CanvasPath::CanvasPath(nsISupports* aParent) : mParent(aParent) {
+ mPathBuilder =
+ gfxPlatform::ThreadLocalScreenReferenceDrawTarget()->CreatePathBuilder();
+}
+
+CanvasPath::CanvasPath(nsISupports* aParent,
+ already_AddRefed<PathBuilder> aPathBuilder)
+ : mParent(aParent), mPathBuilder(aPathBuilder) {
+ if (!mPathBuilder) {
+ mPathBuilder = gfxPlatform::ThreadLocalScreenReferenceDrawTarget()
+ ->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::DrawTarget> drawTarget =
+ gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
+ RefPtr<gfx::Path> tempPath =
+ aCanvasPath.GetPath(CanvasWindingRule::Nonzero, drawTarget.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.value - p1.x.value) * (p0.y.value - p1.y.value) +
+ (p2.y.value - p1.y.value) * (p1.x.value - p0.x.value);
+ 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::RoundRect(
+ double aX, double aY, double aW, double aH,
+ const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
+ aRadii,
+ ErrorResult& aError) {
+ EnsurePathBuilder();
+
+ RoundRectImpl(mPathBuilder, Nothing(), aX, aY, aW, aH, aRadii, aError);
+}
+
+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::DrawTarget> drawTarget =
+ gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
+ RefPtr<gfx::Path> tempPath =
+ aCanvasPath.GetPath(CanvasWindingRule::Nonzero, drawTarget.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) {
+ IntSize size = aContext->GetSize();
+
+ // 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>(size.width) * size.height * 4;
+ if (!bytes.isValid()) {
+ return 0;
+ }
+
+ return bytes.value();
+}
+
+} // namespace mozilla::dom