1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CanvasRenderingContext2D.h"
8 #include "mozilla/gfx/Helpers.h"
9 #include "nsXULElement.h"
11 #include "nsMathUtils.h"
13 #include "nsContentUtils.h"
15 #include "mozilla/intl/BidiEmbeddingLevel.h"
16 #include "mozilla/PresShell.h"
17 #include "mozilla/PresShellInlines.h"
18 #include "mozilla/SVGImageContext.h"
19 #include "mozilla/SVGObserverUtils.h"
20 #include "mozilla/dom/Document.h"
21 #include "mozilla/dom/FontFaceSetImpl.h"
22 #include "mozilla/dom/FontFaceSet.h"
23 #include "mozilla/dom/HTMLCanvasElement.h"
24 #include "mozilla/dom/GeneratePlaceholderCanvasData.h"
25 #include "nsPresContext.h"
27 #include "nsIInterfaceRequestorUtils.h"
31 #include "nsCSSPseudoElements.h"
32 #include "nsComputedDOMStyle.h"
34 #include "nsPrintfCString.h"
36 #include "nsReadableUtils.h"
39 #include "nsGfxCIID.h"
40 #include "nsIDocShell.h"
41 #include "nsPIDOMWindow.h"
42 #include "nsDisplayList.h"
43 #include "nsFocusManager.h"
47 #include "ImageEncoder.h"
48 #include "ImageRegion.h"
50 #include "gfxContext.h"
51 #include "gfxPlatform.h"
54 #include "gfxTextRun.h"
57 #include "nsFrameLoader.h"
58 #include "nsBidiPresUtils.h"
59 #include "LayerUserData.h"
60 #include "CanvasUtils.h"
61 #include "nsIMemoryReporter.h"
62 #include "nsStyleUtil.h"
63 #include "CanvasImageCache.h"
64 #include "DrawTargetWebgl.h"
69 #include "jsfriendapi.h"
70 #include "js/Array.h" // JS::GetArrayLength
71 #include "js/Conversions.h"
72 #include "js/experimental/TypedData.h" // JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData
73 #include "js/HeapAPI.h"
74 #include "js/PropertyAndElement.h" // JS_GetElement
75 #include "js/Warnings.h" // JS::WarnASCII
77 #include "mozilla/Alignment.h"
78 #include "mozilla/Assertions.h"
79 #include "mozilla/CheckedInt.h"
80 #include "mozilla/DebugOnly.h"
81 #include "mozilla/dom/CanvasGradient.h"
82 #include "mozilla/dom/CanvasPattern.h"
83 #include "mozilla/dom/DOMMatrix.h"
84 #include "mozilla/dom/ImageBitmap.h"
85 #include "mozilla/dom/ImageData.h"
86 #include "mozilla/dom/PBrowserParent.h"
87 #include "mozilla/dom/ToJSValue.h"
88 #include "mozilla/dom/TypedArray.h"
89 #include "mozilla/EndianUtils.h"
90 #include "mozilla/FilterInstance.h"
91 #include "mozilla/gfx/2D.h"
92 #include "mozilla/gfx/Tools.h"
93 #include "mozilla/gfx/PathHelpers.h"
94 #include "mozilla/gfx/DataSurfaceHelpers.h"
95 #include "mozilla/gfx/PatternHelpers.h"
96 #include "mozilla/gfx/Swizzle.h"
97 #include "mozilla/layers/PersistentBufferProvider.h"
98 #include "mozilla/MathAlgorithms.h"
99 #include "mozilla/Preferences.h"
100 #include "mozilla/RestyleManager.h"
101 #include "mozilla/ServoBindings.h"
102 #include "mozilla/StaticPrefs_gfx.h"
103 #include "mozilla/Telemetry.h"
104 #include "mozilla/TimeStamp.h"
105 #include "mozilla/UniquePtr.h"
106 #include "mozilla/Unused.h"
107 #include "nsCCUncollectableMarker.h"
108 #include "nsWrapperCacheInlines.h"
109 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
110 #include "mozilla/dom/CanvasPath.h"
111 #include "mozilla/dom/HTMLImageElement.h"
112 #include "mozilla/dom/HTMLVideoElement.h"
113 #include "mozilla/dom/SVGImageElement.h"
114 #include "mozilla/dom/TextMetrics.h"
115 #include "mozilla/FloatingPoint.h"
116 #include "nsGlobalWindow.h"
117 #include "nsDeviceContext.h"
118 #include "nsFontMetrics.h"
119 #include "nsLayoutUtils.h"
121 #include "mozilla/CycleCollectedJSRuntime.h"
122 #include "mozilla/ServoCSSParser.h"
123 #include "mozilla/ServoStyleSet.h"
124 #include "mozilla/SVGContentUtils.h"
125 #include "mozilla/layers/CanvasClient.h"
126 #include "mozilla/layers/WebRenderUserData.h"
127 #include "mozilla/layers/WebRenderCanvasRenderer.h"
128 #include "WindowRenderer.h"
130 #undef free // apparently defined by some windows header, clashing with a
131 // free() method in SkTypes.h
134 # include "gfxWindowsPlatform.h"
137 // windows.h (included by chromium code) defines this, in its infinite wisdom
140 using namespace mozilla
;
141 using namespace mozilla::CanvasUtils
;
142 using namespace mozilla::css
;
143 using namespace mozilla::gfx
;
144 using namespace mozilla::image
;
145 using namespace mozilla::ipc
;
146 using namespace mozilla::layers
;
148 namespace mozilla::dom
{
150 // Cap sigma to avoid overly large temp surfaces.
151 const Float SIGMA_MAX
= 100;
153 const size_t MAX_STYLE_STACK_SIZE
= 1024;
155 /* Memory reporter stuff */
156 static Atomic
<int64_t> gCanvasAzureMemoryUsed(0);
158 // Adds Save() / Restore() calls to the scope.
159 class MOZ_RAII AutoSaveRestore
{
161 explicit AutoSaveRestore(CanvasRenderingContext2D
* aCtx
) : mCtx(aCtx
) {
164 ~AutoSaveRestore() { mCtx
->Restore(); }
167 RefPtr
<CanvasRenderingContext2D
> mCtx
;
170 // This is KIND_OTHER because it's not always clear where in memory the pixels
171 // of a canvas are stored. Furthermore, this memory will be tracked by the
172 // underlying surface implementations. See bug 655638 for details.
173 class Canvas2dPixelsReporter final
: public nsIMemoryReporter
{
174 ~Canvas2dPixelsReporter() = default;
179 NS_IMETHOD
CollectReports(nsIHandleReportCallback
* aHandleReport
,
180 nsISupports
* aData
, bool aAnonymize
) override
{
181 MOZ_COLLECT_REPORT("canvas-2d-pixels", KIND_OTHER
, UNITS_BYTES
,
182 gCanvasAzureMemoryUsed
,
183 "Memory used by 2D canvases. Each canvas requires "
184 "(width * height * 4) bytes.");
190 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter
, nsIMemoryReporter
)
192 class CanvasConicGradient
: public CanvasGradient
{
194 CanvasConicGradient(CanvasRenderingContext2D
* aContext
, Float aAngle
,
195 const Point
& aCenter
)
196 : CanvasGradient(aContext
, Type::CONIC
),
204 class CanvasRadialGradient
: public CanvasGradient
{
206 CanvasRadialGradient(CanvasRenderingContext2D
* aContext
,
207 const Point
& aBeginOrigin
, Float aBeginRadius
,
208 const Point
& aEndOrigin
, Float aEndRadius
)
209 : CanvasGradient(aContext
, Type::RADIAL
),
210 mCenter1(aBeginOrigin
),
211 mCenter2(aEndOrigin
),
212 mRadius1(aBeginRadius
),
213 mRadius2(aEndRadius
) {}
221 class CanvasLinearGradient
: public CanvasGradient
{
223 CanvasLinearGradient(CanvasRenderingContext2D
* aContext
, const Point
& aBegin
,
225 : CanvasGradient(aContext
, Type::LINEAR
), mBegin(aBegin
), mEnd(aEnd
) {}
228 friend struct CanvasBidiProcessor
;
229 friend class CanvasGeneralPattern
;
231 // Beginning of linear gradient.
233 // End of linear gradient.
237 bool CanvasRenderingContext2D::PatternIsOpaque(
238 CanvasRenderingContext2D::Style aStyle
, bool* aIsColor
) const {
239 const ContextState
& state
= CurrentState();
242 if (state
.globalAlpha
>= 1.0) {
243 if (state
.patternStyles
[aStyle
] && state
.patternStyles
[aStyle
]->mSurface
) {
244 opaque
= IsOpaque(state
.patternStyles
[aStyle
]->mSurface
->GetFormat());
245 } else if (!state
.gradientStyles
[aStyle
]) {
246 // TODO: for gradient patterns we could check that all stops are opaque
248 // it's a color pattern.
249 opaque
= sRGBColor::FromABGR(state
.colorStyles
[aStyle
]).a
>= 1.0;
259 // This class is named 'GeneralCanvasPattern' instead of just
260 // 'GeneralPattern' to keep Windows PGO builds from confusing the
261 // GeneralPattern class in gfxContext.cpp with this one.
262 class CanvasGeneralPattern
{
264 using Style
= CanvasRenderingContext2D::Style
;
265 using ContextState
= CanvasRenderingContext2D::ContextState
;
267 Pattern
& ForStyle(CanvasRenderingContext2D
* aCtx
, Style aStyle
,
269 // This should only be called once or the mPattern destructor will
272 !mPattern
.GetPattern(),
273 "ForStyle() should only be called once on CanvasGeneralPattern!");
275 const ContextState
& state
= aCtx
->CurrentState();
277 if (state
.StyleIsColor(aStyle
)) {
278 mPattern
.InitColorPattern(ToDeviceColor(state
.colorStyles
[aStyle
]));
279 } else if (state
.gradientStyles
[aStyle
] &&
280 state
.gradientStyles
[aStyle
]->GetType() ==
281 CanvasGradient::Type::LINEAR
) {
282 auto gradient
= static_cast<CanvasLinearGradient
*>(
283 state
.gradientStyles
[aStyle
].get());
285 mPattern
.InitLinearGradientPattern(
286 gradient
->mBegin
, gradient
->mEnd
,
287 gradient
->GetGradientStopsForTarget(aRT
));
288 } else if (state
.gradientStyles
[aStyle
] &&
289 state
.gradientStyles
[aStyle
]->GetType() ==
290 CanvasGradient::Type::RADIAL
) {
291 auto gradient
= static_cast<CanvasRadialGradient
*>(
292 state
.gradientStyles
[aStyle
].get());
294 mPattern
.InitRadialGradientPattern(
295 gradient
->mCenter1
, gradient
->mCenter2
, gradient
->mRadius1
,
296 gradient
->mRadius2
, gradient
->GetGradientStopsForTarget(aRT
));
297 } else if (state
.gradientStyles
[aStyle
] &&
298 state
.gradientStyles
[aStyle
]->GetType() ==
299 CanvasGradient::Type::CONIC
) {
301 static_cast<CanvasConicGradient
*>(state
.gradientStyles
[aStyle
].get());
303 mPattern
.InitConicGradientPattern(
304 gradient
->mCenter
, gradient
->mAngle
, 0, 1,
305 gradient
->GetGradientStopsForTarget(aRT
));
306 } else if (state
.patternStyles
[aStyle
]) {
307 aCtx
->DoSecurityCheck(state
.patternStyles
[aStyle
]->mPrincipal
,
308 state
.patternStyles
[aStyle
]->mForceWriteOnly
,
309 state
.patternStyles
[aStyle
]->mCORSUsed
);
312 if (state
.patternStyles
[aStyle
]->mRepeat
==
313 CanvasPattern::RepeatMode::NOREPEAT
) {
314 mode
= ExtendMode::CLAMP
;
316 mode
= ExtendMode::REPEAT
;
319 SamplingFilter samplingFilter
;
320 if (state
.imageSmoothingEnabled
) {
321 samplingFilter
= SamplingFilter::GOOD
;
323 samplingFilter
= SamplingFilter::POINT
;
326 mPattern
.InitSurfacePattern(state
.patternStyles
[aStyle
]->mSurface
, mode
,
327 state
.patternStyles
[aStyle
]->mTransform
,
331 return *mPattern
.GetPattern();
334 GeneralPattern mPattern
;
337 /* This is an RAII based class that can be used as a drawtarget for
338 * operations that need to have a filter applied to their results.
339 * All coordinates passed to the constructor are in device space.
341 class AdjustedTargetForFilter
{
343 using ContextState
= CanvasRenderingContext2D::ContextState
;
345 AdjustedTargetForFilter(CanvasRenderingContext2D
* aCtx
,
346 DrawTarget
* aFinalTarget
,
347 const gfx::IntPoint
& aFilterSpaceToTargetOffset
,
348 const gfx::IntRect
& aPreFilterBounds
,
349 const gfx::IntRect
& aPostFilterBounds
,
350 gfx::CompositionOp aCompositionOp
)
351 : mFinalTarget(aFinalTarget
),
353 mPostFilterBounds(aPostFilterBounds
),
354 mOffset(aFilterSpaceToTargetOffset
),
355 mCompositionOp(aCompositionOp
) {
356 nsIntRegion sourceGraphicNeededRegion
;
357 nsIntRegion fillPaintNeededRegion
;
358 nsIntRegion strokePaintNeededRegion
;
360 FilterSupport::ComputeSourceNeededRegions(
361 aCtx
->CurrentState().filter
, mPostFilterBounds
,
362 sourceGraphicNeededRegion
, fillPaintNeededRegion
,
363 strokePaintNeededRegion
);
365 mSourceGraphicRect
= sourceGraphicNeededRegion
.GetBounds();
366 mFillPaintRect
= fillPaintNeededRegion
.GetBounds();
367 mStrokePaintRect
= strokePaintNeededRegion
.GetBounds();
369 mSourceGraphicRect
= mSourceGraphicRect
.Intersect(aPreFilterBounds
);
371 if (mSourceGraphicRect
.IsEmpty()) {
372 // The filter might not make any use of the source graphic. We need to
373 // create a DrawTarget that we can return from DT() anyway, so we'll
374 // just use a 1x1-sized one.
375 mSourceGraphicRect
.SizeTo(1, 1);
378 if (!mFinalTarget
->CanCreateSimilarDrawTarget(mSourceGraphicRect
.Size(),
379 SurfaceFormat::B8G8R8A8
)) {
380 mTarget
= mFinalTarget
;
382 mFinalTarget
= nullptr;
386 mTarget
= mFinalTarget
->CreateSimilarDrawTarget(mSourceGraphicRect
.Size(),
387 SurfaceFormat::B8G8R8A8
);
391 mTarget
->ClearRect(gfx::Rect());
394 if (!mTarget
|| !mTarget
->IsValid()) {
395 // XXX - Deal with the situation where our temp size is too big to
396 // fit in a texture (bug 1066622).
397 mTarget
= mFinalTarget
;
399 mFinalTarget
= nullptr;
403 mTarget
->SetTransform(mFinalTarget
->GetTransform().PostTranslate(
404 -mSourceGraphicRect
.TopLeft() + mOffset
));
407 // Return a SourceSurface that contains the FillPaint or StrokePaint source.
408 already_AddRefed
<SourceSurface
> DoSourcePaint(
409 gfx::IntRect
& aRect
, CanvasRenderingContext2D::Style aStyle
) {
410 if (aRect
.IsEmpty()) {
414 RefPtr
<DrawTarget
> dt
= mFinalTarget
->CreateSimilarDrawTarget(
415 aRect
.Size(), SurfaceFormat::B8G8R8A8
);
419 dt
->ClearRect(gfx::Rect());
422 if (!dt
|| !dt
->IsValid()) {
428 mFinalTarget
->GetTransform().PostTranslate(-aRect
.TopLeft() + mOffset
);
430 dt
->SetTransform(transform
);
432 if (transform
.Invert()) {
433 gfx::Rect
dtBounds(0, 0, aRect
.width
, aRect
.height
);
434 gfx::Rect fillRect
= transform
.TransformBounds(dtBounds
);
435 dt
->FillRect(fillRect
, CanvasGeneralPattern().ForStyle(mCtx
, aStyle
, dt
));
437 return dt
->Snapshot();
440 ~AdjustedTargetForFilter() {
445 RefPtr
<SourceSurface
> snapshot
= mTarget
->Snapshot();
447 RefPtr
<SourceSurface
> fillPaint
=
448 DoSourcePaint(mFillPaintRect
, CanvasRenderingContext2D::Style::FILL
);
449 RefPtr
<SourceSurface
> strokePaint
= DoSourcePaint(
450 mStrokePaintRect
, CanvasRenderingContext2D::Style::STROKE
);
452 AutoRestoreTransform
autoRestoreTransform(mFinalTarget
);
453 mFinalTarget
->SetTransform(Matrix());
455 MOZ_RELEASE_ASSERT(!mCtx
->CurrentState().filter
.mPrimitives
.IsEmpty());
456 gfx::FilterSupport::RenderFilterDescription(
457 mFinalTarget
, mCtx
->CurrentState().filter
, gfx::Rect(mPostFilterBounds
),
458 snapshot
, mSourceGraphicRect
, fillPaint
, mFillPaintRect
, strokePaint
,
459 mStrokePaintRect
, mCtx
->CurrentState().filterAdditionalImages
,
460 mPostFilterBounds
.TopLeft() - mOffset
,
461 DrawOptions(1.0f
, mCompositionOp
));
463 const gfx::FilterDescription
& filter
= mCtx
->CurrentState().filter
;
464 MOZ_RELEASE_ASSERT(!filter
.mPrimitives
.IsEmpty());
465 if (filter
.mPrimitives
.LastElement().IsTainted() && mCtx
->mCanvasElement
) {
466 mCtx
->mCanvasElement
->SetWriteOnly();
470 DrawTarget
* DT() { return mTarget
; }
473 RefPtr
<DrawTarget
> mTarget
;
474 RefPtr
<DrawTarget
> mFinalTarget
;
475 CanvasRenderingContext2D
* mCtx
;
476 gfx::IntRect mSourceGraphicRect
;
477 gfx::IntRect mFillPaintRect
;
478 gfx::IntRect mStrokePaintRect
;
479 gfx::IntRect mPostFilterBounds
;
480 gfx::IntPoint mOffset
;
481 gfx::CompositionOp mCompositionOp
;
484 /* This is an RAII based class that can be used as a drawtarget for
485 * operations that need to have a shadow applied to their results.
486 * All coordinates passed to the constructor are in device space.
488 class AdjustedTargetForShadow
{
490 using ContextState
= CanvasRenderingContext2D::ContextState
;
492 AdjustedTargetForShadow(CanvasRenderingContext2D
* aCtx
,
493 DrawTarget
* aFinalTarget
, const gfx::Rect
& aBounds
,
494 gfx::CompositionOp aCompositionOp
)
495 : mFinalTarget(aFinalTarget
), mCtx(aCtx
), mCompositionOp(aCompositionOp
) {
496 const ContextState
& state
= mCtx
->CurrentState();
497 mSigma
= state
.ShadowBlurSigma();
499 // We actually include the bounds of the shadow blur, this makes it
500 // easier to execute the actual blur on hardware, and shouldn't affect
501 // the amount of pixels that need to be touched.
502 gfx::Rect bounds
= aBounds
;
503 int32_t blurRadius
= state
.ShadowBlurRadius();
504 bounds
.Inflate(blurRadius
);
506 if (!bounds
.ToIntRect(&mTempRect
) ||
507 !mFinalTarget
->CanCreateSimilarDrawTarget(mTempRect
.Size(),
508 SurfaceFormat::B8G8R8A8
)) {
509 mTarget
= mFinalTarget
;
511 mFinalTarget
= nullptr;
515 mTarget
= mFinalTarget
->CreateShadowDrawTarget(
516 mTempRect
.Size(), SurfaceFormat::B8G8R8A8
, mSigma
);
520 mTarget
->ClearRect(gfx::Rect());
523 if (!mTarget
|| !mTarget
->IsValid()) {
524 // XXX - Deal with the situation where our temp size is too big to
525 // fit in a texture (bug 1066622).
526 mTarget
= mFinalTarget
;
528 mFinalTarget
= nullptr;
530 mTarget
->SetTransform(
531 mFinalTarget
->GetTransform().PostTranslate(-mTempRect
.TopLeft()));
535 ~AdjustedTargetForShadow() {
540 RefPtr
<SourceSurface
> snapshot
= mTarget
->Snapshot();
542 mFinalTarget
->DrawSurfaceWithShadow(
543 snapshot
, mTempRect
.TopLeft(),
544 ShadowOptions(ToDeviceColor(mCtx
->CurrentState().shadowColor
),
545 mCtx
->CurrentState().shadowOffset
, mSigma
),
549 DrawTarget
* DT() { return mTarget
; }
551 gfx::IntPoint
OffsetToFinalDT() { return mTempRect
.TopLeft(); }
554 RefPtr
<DrawTarget
> mTarget
;
555 RefPtr
<DrawTarget
> mFinalTarget
;
556 CanvasRenderingContext2D
* mCtx
;
558 gfx::IntRect mTempRect
;
559 gfx::CompositionOp mCompositionOp
;
563 * This is an RAII based class that can be used as a drawtarget for
564 * operations that need a shadow or a filter drawn. It will automatically
565 * provide a temporary target when needed, and if so blend it back with a
566 * shadow, filter, or both.
567 * If both a shadow and a filter are needed, the filter is applied first,
568 * and the shadow is applied to the filtered results.
570 * aBounds specifies the bounds of the drawing operation that will be
571 * drawn to the target, it is given in device space! If this is nullptr the
572 * drawing operation will be assumed to cover the whole canvas.
574 class AdjustedTarget
{
576 using ContextState
= CanvasRenderingContext2D::ContextState
;
578 explicit AdjustedTarget(CanvasRenderingContext2D
* aCtx
,
579 const gfx::Rect
* aBounds
= nullptr,
580 bool aAllowOptimization
= false)
582 mOptimizeShadow(false),
583 mUsedOperation(aCtx
->CurrentState().op
) {
584 // All rects in this function are in the device space of ctx->mTarget.
586 // In order to keep our temporary surfaces as small as possible, we first
587 // calculate what their maximum required bounds would need to be if we
588 // were to fill the whole canvas. Everything outside those bounds we don't
590 gfx::Rect
r(0, 0, aCtx
->mWidth
, aCtx
->mHeight
);
591 gfx::Rect maxSourceNeededBoundsForShadow
=
592 MaxSourceNeededBoundsForShadow(r
, aCtx
);
593 gfx::Rect maxSourceNeededBoundsForFilter
=
594 MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow
, aCtx
);
595 if (!aCtx
->IsTargetValid()) {
599 gfx::Rect bounds
= maxSourceNeededBoundsForFilter
;
601 bounds
= bounds
.Intersect(*aBounds
);
603 gfx::Rect boundsAfterFilter
= BoundsAfterFilter(bounds
, aCtx
);
604 if (!aCtx
->IsTargetValid() || !boundsAfterFilter
.IsFinite()) {
608 gfx::IntPoint offsetToFinalDT
;
610 // First set up the shadow draw target, because the shadow goes outside.
611 // It applies to the post-filter results, if both a filter and a shadow
613 const bool applyFilter
= aCtx
->NeedToApplyFilter();
614 if (aCtx
->NeedToDrawShadow()) {
615 if (aAllowOptimization
&& !applyFilter
) {
616 // If only drawing a shadow and no filter, then avoid buffering to an
617 // intermediate target while drawing the shadow directly to the final
618 // target. When doing so, we want to use the actual composition op
619 // instead of OP_OVER.
620 mTarget
= aCtx
->mTarget
;
621 if (mTarget
&& mTarget
->IsValid()) {
622 mOptimizeShadow
= true;
626 mShadowTarget
= MakeUnique
<AdjustedTargetForShadow
>(
627 aCtx
, aCtx
->mTarget
, boundsAfterFilter
, mUsedOperation
);
628 mTarget
= mShadowTarget
->DT();
629 offsetToFinalDT
= mShadowTarget
->OffsetToFinalDT();
631 // If we also have a filter, the filter needs to be drawn with OP_OVER
632 // because shadow drawing already applies op on the result.
633 mUsedOperation
= CompositionOp::OP_OVER
;
636 // Now set up the filter draw target.
637 if (!aCtx
->IsTargetValid()) {
644 mTarget
= aCtx
->mTarget
;
646 gfx::IntRect intBounds
;
647 if (!bounds
.ToIntRect(&intBounds
)) {
650 mFilterTarget
= MakeUnique
<AdjustedTargetForFilter
>(
651 aCtx
, mTarget
, offsetToFinalDT
, intBounds
,
652 gfx::RoundedToInt(boundsAfterFilter
), mUsedOperation
);
653 mTarget
= mFilterTarget
->DT();
654 mUsedOperation
= CompositionOp::OP_OVER
;
657 mTarget
= aCtx
->mTarget
;
662 // The order in which the targets are finalized is important.
663 // Filters are inside, any shadow applies to the post-filter results.
664 mFilterTarget
.reset();
665 mShadowTarget
.reset();
668 operator DrawTarget
*() { return mTarget
; }
670 DrawTarget
* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN
{ return mTarget
; }
672 CompositionOp
UsedOperation() const { return mUsedOperation
; }
674 ShadowOptions
ShadowParams() const {
675 const ContextState
& state
= mCtx
->CurrentState();
676 return ShadowOptions(ToDeviceColor(state
.shadowColor
), state
.shadowOffset
,
677 state
.ShadowBlurSigma());
680 void Fill(const Path
* aPath
, const Pattern
& aPattern
,
681 const DrawOptions
& aOptions
) {
682 if (mOptimizeShadow
) {
683 mTarget
->DrawShadow(aPath
, aPattern
, ShadowParams(), aOptions
);
685 mTarget
->Fill(aPath
, aPattern
, aOptions
);
688 void FillRect(const Rect
& aRect
, const Pattern
& aPattern
,
689 const DrawOptions
& aOptions
) {
690 if (mOptimizeShadow
) {
691 RefPtr
<Path
> path
= MakePathForRect(*mTarget
, aRect
);
692 mTarget
->DrawShadow(path
, aPattern
, ShadowParams(), aOptions
);
694 mTarget
->FillRect(aRect
, aPattern
, aOptions
);
697 void Stroke(const Path
* aPath
, const Pattern
& aPattern
,
698 const StrokeOptions
& aStrokeOptions
,
699 const DrawOptions
& aOptions
) {
700 if (mOptimizeShadow
) {
701 mTarget
->DrawShadow(aPath
, aPattern
, ShadowParams(), aOptions
,
704 mTarget
->Stroke(aPath
, aPattern
, aStrokeOptions
, aOptions
);
707 void StrokeRect(const Rect
& aRect
, const Pattern
& aPattern
,
708 const StrokeOptions
& aStrokeOptions
,
709 const DrawOptions
& aOptions
) {
710 if (mOptimizeShadow
) {
711 RefPtr
<Path
> path
= MakePathForRect(*mTarget
, aRect
);
712 mTarget
->DrawShadow(path
, aPattern
, ShadowParams(), aOptions
,
715 mTarget
->StrokeRect(aRect
, aPattern
, aStrokeOptions
, aOptions
);
718 void StrokeLine(const Point
& aStart
, const Point
& aEnd
,
719 const Pattern
& aPattern
, const StrokeOptions
& aStrokeOptions
,
720 const DrawOptions
& aOptions
) {
721 if (mOptimizeShadow
) {
722 RefPtr
<PathBuilder
> builder
= mTarget
->CreatePathBuilder();
723 builder
->MoveTo(aStart
);
724 builder
->LineTo(aEnd
);
725 RefPtr
<Path
> path
= builder
->Finish();
726 mTarget
->DrawShadow(path
, aPattern
, ShadowParams(), aOptions
,
729 mTarget
->StrokeLine(aStart
, aEnd
, aPattern
, aStrokeOptions
, aOptions
);
732 void DrawSurface(SourceSurface
* aSurface
, const Rect
& aDest
,
733 const Rect
& aSource
, const DrawSurfaceOptions
& aSurfOptions
,
734 const DrawOptions
& aOptions
) {
735 if (mOptimizeShadow
) {
736 RefPtr
<Path
> path
= MakePathForRect(*mTarget
, aSource
);
737 ShadowOptions
shadowParams(ShadowParams());
738 SurfacePattern
pattern(aSurface
, ExtendMode::CLAMP
, Matrix(),
739 shadowParams
.BlurRadius() > 1
740 ? SamplingFilter::POINT
741 : aSurfOptions
.mSamplingFilter
);
742 Matrix matrix
= Matrix::Scaling(aDest
.width
/ aSource
.width
,
743 aDest
.height
/ aSource
.height
);
744 matrix
.PreTranslate(-aSource
.x
, -aSource
.y
);
745 matrix
.PostTranslate(aDest
.x
, aDest
.y
);
746 AutoRestoreTransform
autoRestoreTransform(mTarget
);
747 mTarget
->ConcatTransform(matrix
);
748 mTarget
->DrawShadow(path
, pattern
, shadowParams
, aOptions
);
750 mTarget
->DrawSurface(aSurface
, aDest
, aSource
, aSurfOptions
, aOptions
);
754 gfx::Rect
MaxSourceNeededBoundsForFilter(const gfx::Rect
& aDestBounds
,
755 CanvasRenderingContext2D
* aCtx
) {
756 const bool applyFilter
= aCtx
->NeedToApplyFilter();
757 if (!aCtx
->IsTargetValid()) {
764 nsIntRegion sourceGraphicNeededRegion
;
765 nsIntRegion fillPaintNeededRegion
;
766 nsIntRegion strokePaintNeededRegion
;
768 FilterSupport::ComputeSourceNeededRegions(
769 aCtx
->CurrentState().filter
, gfx::RoundedToInt(aDestBounds
),
770 sourceGraphicNeededRegion
, fillPaintNeededRegion
,
771 strokePaintNeededRegion
);
773 return gfx::Rect(sourceGraphicNeededRegion
.GetBounds());
776 gfx::Rect
MaxSourceNeededBoundsForShadow(const gfx::Rect
& aDestBounds
,
777 CanvasRenderingContext2D
* aCtx
) {
778 if (!aCtx
->NeedToDrawShadow()) {
782 const ContextState
& state
= aCtx
->CurrentState();
783 gfx::Rect sourceBounds
= aDestBounds
- state
.shadowOffset
;
784 sourceBounds
.Inflate(state
.ShadowBlurRadius());
786 // Union the shadow source with the original rect because we're going to
788 return sourceBounds
.Union(aDestBounds
);
791 gfx::Rect
BoundsAfterFilter(const gfx::Rect
& aBounds
,
792 CanvasRenderingContext2D
* aCtx
) {
793 const bool applyFilter
= aCtx
->NeedToApplyFilter();
794 if (!aCtx
->IsTargetValid()) {
801 gfx::Rect
bounds(aBounds
);
804 gfx::IntRect intBounds
;
805 if (!bounds
.ToIntRect(&intBounds
)) {
809 nsIntRegion extents
= gfx::FilterSupport::ComputePostFilterExtents(
810 aCtx
->CurrentState().filter
, intBounds
);
811 return gfx::Rect(extents
.GetBounds());
814 CanvasRenderingContext2D
* mCtx
;
815 bool mOptimizeShadow
;
816 CompositionOp mUsedOperation
;
817 RefPtr
<DrawTarget
> mTarget
;
818 UniquePtr
<AdjustedTargetForShadow
> mShadowTarget
;
819 UniquePtr
<AdjustedTargetForFilter
> mFilterTarget
;
822 void CanvasPattern::SetTransform(const DOMMatrix2DInit
& aInit
,
823 ErrorResult
& aError
) {
824 RefPtr
<DOMMatrixReadOnly
> matrix
=
825 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit
, aError
);
826 if (aError
.Failed()) {
829 const auto* matrix2D
= matrix
->GetInternal2D();
830 if (!matrix2D
->IsFinite()) {
833 mTransform
= Matrix(*matrix2D
);
836 void CanvasGradient::AddColorStop(float aOffset
, const nsACString
& aColorstr
,
838 if (aOffset
< 0.0 || aOffset
> 1.0) {
839 return aRv
.ThrowIndexSizeError("Offset out of 0-1.0 range");
843 return aRv
.ThrowSyntaxError("No canvas context");
846 auto color
= mContext
->ParseColor(
847 aColorstr
, CanvasRenderingContext2D::ResolveCurrentColor::No
);
849 return aRv
.ThrowSyntaxError("Invalid color");
852 GradientStop newStop
;
854 newStop
.offset
= aOffset
;
855 newStop
.color
= ToDeviceColor(*color
);
857 mRawStops
.AppendElement(newStop
);
860 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient
, mContext
)
862 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern
, mContext
)
864 class CanvasShutdownObserver final
: public nsIObserver
{
866 explicit CanvasShutdownObserver(CanvasRenderingContext2D
* aCanvas
)
867 : mCanvas(aCanvas
) {}
875 nsContentUtils::UnregisterShutdownObserver(this);
881 ~CanvasShutdownObserver() = default;
883 CanvasRenderingContext2D
* mCanvas
;
886 NS_IMPL_ISUPPORTS(CanvasShutdownObserver
, nsIObserver
)
889 CanvasShutdownObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
890 const char16_t
* aData
) {
891 if (mCanvas
&& strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
892 mCanvas
->OnShutdown();
899 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D
)
900 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D
)
902 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CanvasRenderingContext2D
)
904 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D
)
905 // Make sure we remove ourselves from the list of demotable contexts (raw
906 // pointers), since we're logically destructed at this point.
907 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement
)
908 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOffscreenCanvas
)
909 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell
)
910 for (uint32_t i
= 0; i
< tmp
->mStyleStack
.Length(); i
++) {
911 ImplCycleCollectionUnlink(tmp
->mStyleStack
[i
].patternStyles
[Style::STROKE
]);
912 ImplCycleCollectionUnlink(tmp
->mStyleStack
[i
].patternStyles
[Style::FILL
]);
913 ImplCycleCollectionUnlink(
914 tmp
->mStyleStack
[i
].gradientStyles
[Style::STROKE
]);
915 ImplCycleCollectionUnlink(tmp
->mStyleStack
[i
].gradientStyles
[Style::FILL
]);
916 auto autoSVGFiltersObserver
=
917 tmp
->mStyleStack
[i
].autoSVGFiltersObserver
.get();
918 if (autoSVGFiltersObserver
) {
919 // XXXjwatt: I don't think this call achieves anything. See the comment
920 // that documents this function.
921 SVGObserverUtils::DetachFromCanvasContext(autoSVGFiltersObserver
);
923 ImplCycleCollectionUnlink(tmp
->mStyleStack
[i
].autoSVGFiltersObserver
);
925 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
926 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
927 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
929 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D
)
930 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement
)
931 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOffscreenCanvas
)
932 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell
)
933 for (uint32_t i
= 0; i
< tmp
->mStyleStack
.Length(); i
++) {
934 ImplCycleCollectionTraverse(
935 cb
, tmp
->mStyleStack
[i
].patternStyles
[Style::STROKE
],
936 "Stroke CanvasPattern");
937 ImplCycleCollectionTraverse(cb
,
938 tmp
->mStyleStack
[i
].patternStyles
[Style::FILL
],
939 "Fill CanvasPattern");
940 ImplCycleCollectionTraverse(
941 cb
, tmp
->mStyleStack
[i
].gradientStyles
[Style::STROKE
],
942 "Stroke CanvasGradient");
943 ImplCycleCollectionTraverse(cb
,
944 tmp
->mStyleStack
[i
].gradientStyles
[Style::FILL
],
945 "Fill CanvasGradient");
946 ImplCycleCollectionTraverse(cb
, tmp
->mStyleStack
[i
].autoSVGFiltersObserver
,
947 "RAII SVG Filters Observer");
949 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
951 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D
)
952 if (nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper()) {
953 dom::Element
* canvasElement
= tmp
->mCanvasElement
;
955 if (canvasElement
->IsPurple()) {
956 canvasElement
->RemovePurple();
958 dom::Element::MarkNodeChildren(canvasElement
);
962 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
964 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D
)
965 return nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper();
966 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
968 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D
)
969 return nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper();
970 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
972 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D
)
973 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
974 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal
)
975 NS_INTERFACE_MAP_ENTRY(nsISupports
)
978 CanvasRenderingContext2D::ContextState::ContextState() = default;
980 CanvasRenderingContext2D::ContextState::ContextState(const ContextState
& aOther
)
981 : fontGroup(aOther
.fontGroup
),
982 fontLanguage(aOther
.fontLanguage
),
983 fontFont(aOther
.fontFont
),
984 gradientStyles(aOther
.gradientStyles
),
985 patternStyles(aOther
.patternStyles
),
986 colorStyles(aOther
.colorStyles
),
988 textAlign(aOther
.textAlign
),
989 textBaseline(aOther
.textBaseline
),
990 textDirection(aOther
.textDirection
),
991 fontKerning(aOther
.fontKerning
),
992 shadowColor(aOther
.shadowColor
),
993 transform(aOther
.transform
),
994 shadowOffset(aOther
.shadowOffset
),
995 lineWidth(aOther
.lineWidth
),
996 miterLimit(aOther
.miterLimit
),
997 globalAlpha(aOther
.globalAlpha
),
998 shadowBlur(aOther
.shadowBlur
),
999 dash(aOther
.dash
.Clone()),
1000 dashOffset(aOther
.dashOffset
),
1002 fillRule(aOther
.fillRule
),
1003 lineCap(aOther
.lineCap
),
1004 lineJoin(aOther
.lineJoin
),
1005 filterString(aOther
.filterString
),
1006 filterChain(aOther
.filterChain
),
1007 autoSVGFiltersObserver(aOther
.autoSVGFiltersObserver
),
1008 filter(aOther
.filter
),
1009 filterAdditionalImages(aOther
.filterAdditionalImages
.Clone()),
1010 filterSourceGraphicTainted(aOther
.filterSourceGraphicTainted
),
1011 imageSmoothingEnabled(aOther
.imageSmoothingEnabled
),
1012 fontExplicitLanguage(aOther
.fontExplicitLanguage
) {}
1014 CanvasRenderingContext2D::ContextState::~ContextState() = default;
1016 void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle
,
1018 colorStyles
[aWhichStyle
] = aColor
;
1019 gradientStyles
[aWhichStyle
] = nullptr;
1020 patternStyles
[aWhichStyle
] = nullptr;
1023 void CanvasRenderingContext2D::ContextState::SetPatternStyle(
1024 Style aWhichStyle
, CanvasPattern
* aPat
) {
1025 gradientStyles
[aWhichStyle
] = nullptr;
1026 patternStyles
[aWhichStyle
] = aPat
;
1029 void CanvasRenderingContext2D::ContextState::SetGradientStyle(
1030 Style aWhichStyle
, CanvasGradient
* aGrad
) {
1031 gradientStyles
[aWhichStyle
] = aGrad
;
1032 patternStyles
[aWhichStyle
] = nullptr;
1036 ** CanvasRenderingContext2D impl
1039 // Initialize our static variables.
1040 MOZ_THREAD_LOCAL(uintptr_t) CanvasRenderingContext2D::sNumLivingContexts
;
1041 MOZ_THREAD_LOCAL(DrawTarget
*) CanvasRenderingContext2D::sErrorTarget
;
1043 CanvasRenderingContext2D::CanvasRenderingContext2D(
1044 layers::LayersBackend aCompositorBackend
)
1045 : // these are the default values from the Canvas spec
1049 mOpaqueAttrValue(false),
1050 mContextAttributesHasAlpha(true),
1054 mHasPendingStableStateCallback(false),
1055 mIsEntireFrameInvalid(false),
1056 mPredictManyRedrawCalls(false),
1057 mFrameCaptureState(FrameCaptureState::CLEAN
,
1058 "CanvasRenderingContext2D::mFrameCaptureState"),
1059 mPathTransformWillUpdate(false),
1060 mInvalidateCount(0),
1062 sNumLivingContexts
.infallibleInit();
1063 sErrorTarget
.infallibleInit();
1064 sNumLivingContexts
.set(sNumLivingContexts
.get() + 1);
1067 CanvasRenderingContext2D::~CanvasRenderingContext2D() {
1068 RemovePostRefreshObserver();
1069 RemoveShutdownObserver();
1072 sNumLivingContexts
.set(sNumLivingContexts
.get() - 1);
1073 if (sNumLivingContexts
.get() == 0 && sErrorTarget
.get()) {
1074 RefPtr
<DrawTarget
> target
= dont_AddRef(sErrorTarget
.get());
1075 sErrorTarget
.set(nullptr);
1079 void CanvasRenderingContext2D::Initialize() { AddShutdownObserver(); }
1081 JSObject
* CanvasRenderingContext2D::WrapObject(
1082 JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
1083 return CanvasRenderingContext2D_Binding::Wrap(aCx
, this, aGivenProto
);
1086 CanvasRenderingContext2D::ColorStyleCacheEntry
1087 CanvasRenderingContext2D::ParseColorSlow(const nsACString
& aString
) {
1088 ColorStyleCacheEntry result
{nsCString(aString
)};
1089 Document
* document
= mCanvasElement
? mCanvasElement
->OwnerDoc() : nullptr;
1090 css::Loader
* loader
= document
? document
->CSSLoader() : nullptr;
1092 PresShell
* presShell
= GetPresShell();
1093 ServoStyleSet
* set
= presShell
? presShell
->StyleSet() : nullptr;
1094 bool wasCurrentColor
= false;
1096 if (ServoCSSParser::ComputeColor(set
, NS_RGB(0, 0, 0), aString
, &color
,
1097 &wasCurrentColor
, loader
)) {
1098 result
.mWasCurrentColor
= wasCurrentColor
;
1099 result
.mColor
.emplace(color
);
1105 Maybe
<nscolor
> CanvasRenderingContext2D::ParseColor(
1106 const nsACString
& aString
, ResolveCurrentColor aResolveCurrentColor
) {
1107 auto entry
= mColorStyleCache
.Lookup(aString
);
1109 entry
.Set(ParseColorSlow(aString
));
1112 const auto& data
= entry
.Data();
1113 if (data
.mWasCurrentColor
&& mCanvasElement
&&
1114 aResolveCurrentColor
== ResolveCurrentColor::Yes
) {
1115 // If it was currentColor, get the value of the color property, flushing
1116 // style if necessary.
1117 RefPtr
<const ComputedStyle
> canvasStyle
=
1118 nsComputedDOMStyle::GetComputedStyle(mCanvasElement
);
1120 return Some(canvasStyle
->StyleText()->mColor
.ToColor());
1126 void CanvasRenderingContext2D::ResetBitmap(bool aFreeBuffer
) {
1127 if (mCanvasElement
) {
1128 mCanvasElement
->InvalidateCanvas();
1131 // only do this for non-docshell created contexts,
1132 // since those are the ones that we created a surface for
1133 if (mTarget
&& IsTargetValid() && !mDocShell
) {
1134 gCanvasAzureMemoryUsed
-= mWidth
* mHeight
* 4;
1137 bool forceReset
= true;
1138 ReturnTarget(forceReset
);
1141 mBufferProvider
= nullptr;
1142 } else if (mBufferProvider
) {
1143 // Try to keep the buffer around. However, we still need to clear the
1144 // contents as if it was recreated before next use.
1145 mBufferNeedsClear
= true;
1148 // Since the target changes the backing texture will change, and this will
1149 // no longer be valid.
1150 mIsEntireFrameInvalid
= false;
1151 mPredictManyRedrawCalls
= false;
1152 mFrameCaptureState
= FrameCaptureState::CLEAN
;
1155 void CanvasRenderingContext2D::OnShutdown() {
1156 mShutdownObserver
= nullptr;
1158 RefPtr
<PersistentBufferProvider
> provider
= mBufferProvider
;
1163 provider
->OnShutdown();
1167 void CanvasRenderingContext2D::AddShutdownObserver() {
1168 MOZ_ASSERT(!mShutdownObserver
);
1169 MOZ_ASSERT(NS_IsMainThread());
1171 mShutdownObserver
= new CanvasShutdownObserver(this);
1172 nsContentUtils::RegisterShutdownObserver(mShutdownObserver
);
1175 void CanvasRenderingContext2D::RemoveShutdownObserver() {
1176 if (mShutdownObserver
) {
1177 mShutdownObserver
->OnShutdown();
1178 mShutdownObserver
= nullptr;
1182 void CanvasRenderingContext2D::SetStyleFromString(const nsACString
& aStr
,
1183 Style aWhichStyle
) {
1184 MOZ_ASSERT(!aStr
.IsVoid());
1186 Maybe
<nscolor
> color
= ParseColor(aStr
);
1191 CurrentState().SetColorStyle(aWhichStyle
, *color
);
1194 void CanvasRenderingContext2D::GetStyleAsUnion(
1195 OwningUTF8StringOrCanvasGradientOrCanvasPattern
& aValue
,
1196 Style aWhichStyle
) {
1197 const ContextState
& state
= CurrentState();
1198 if (state
.patternStyles
[aWhichStyle
]) {
1199 aValue
.SetAsCanvasPattern() = state
.patternStyles
[aWhichStyle
];
1200 } else if (state
.gradientStyles
[aWhichStyle
]) {
1201 aValue
.SetAsCanvasGradient() = state
.gradientStyles
[aWhichStyle
];
1203 StyleColorToString(state
.colorStyles
[aWhichStyle
],
1204 aValue
.SetAsUTF8String());
1209 void CanvasRenderingContext2D::StyleColorToString(const nscolor
& aColor
,
1212 // We can't reuse the normal CSS color stringification code,
1213 // because the spec calls for a different algorithm for canvas.
1214 if (NS_GET_A(aColor
) == 255) {
1215 aStr
.AppendPrintf("#%02x%02x%02x", NS_GET_R(aColor
), NS_GET_G(aColor
),
1218 aStr
.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor
), NS_GET_G(aColor
),
1220 aStr
.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor
)));
1225 nsresult
CanvasRenderingContext2D::Redraw() {
1226 mFrameCaptureState
= FrameCaptureState::DIRTY
;
1228 if (mIsEntireFrameInvalid
) {
1232 mIsEntireFrameInvalid
= true;
1234 if (mCanvasElement
) {
1235 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement
);
1236 mCanvasElement
->InvalidateCanvasContent(nullptr);
1237 } else if (mOffscreenCanvas
) {
1238 mOffscreenCanvas
->QueueCommitToCompositor();
1240 NS_ASSERTION(mDocShell
, "Redraw with no canvas element or docshell!");
1246 void CanvasRenderingContext2D::Redraw(const gfx::Rect
& aR
) {
1247 mFrameCaptureState
= FrameCaptureState::DIRTY
;
1251 if (mIsEntireFrameInvalid
) {
1255 if (mPredictManyRedrawCalls
|| mInvalidateCount
> kCanvasMaxInvalidateCount
) {
1260 if (mCanvasElement
) {
1261 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement
);
1262 mCanvasElement
->InvalidateCanvasContent(&aR
);
1263 } else if (mOffscreenCanvas
) {
1264 mOffscreenCanvas
->QueueCommitToCompositor();
1266 NS_ASSERTION(mDocShell
, "Redraw with no canvas element or docshell!");
1270 void CanvasRenderingContext2D::DidRefresh() {}
1272 void CanvasRenderingContext2D::RedrawUser(const gfxRect
& aR
) {
1273 mFrameCaptureState
= FrameCaptureState::DIRTY
;
1275 if (mIsEntireFrameInvalid
) {
1280 gfx::Rect newr
= mTarget
->GetTransform().TransformBounds(ToRect(aR
));
1284 bool CanvasRenderingContext2D::CopyBufferProvider(
1285 PersistentBufferProvider
& aOld
, DrawTarget
& aTarget
, IntRect aCopyRect
) {
1286 // Borrowing the snapshot must be done after ReturnTarget.
1287 RefPtr
<SourceSurface
> snapshot
= aOld
.BorrowSnapshot();
1293 aTarget
.CopySurface(snapshot
, aCopyRect
, IntPoint());
1294 aOld
.ReturnSnapshot(snapshot
.forget());
1298 void CanvasRenderingContext2D::Demote() {}
1300 void CanvasRenderingContext2D::ScheduleStableStateCallback() {
1301 if (mHasPendingStableStateCallback
) {
1304 mHasPendingStableStateCallback
= true;
1306 nsContentUtils::RunInStableState(
1307 NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", this,
1308 &CanvasRenderingContext2D::OnStableState
));
1311 void CanvasRenderingContext2D::OnStableState() {
1312 if (!mHasPendingStableStateCallback
) {
1318 mHasPendingStableStateCallback
= false;
1321 void CanvasRenderingContext2D::RestoreClipsAndTransformToTarget() {
1322 // Restore clips and transform.
1323 mTarget
->SetTransform(Matrix());
1325 if (mTarget
->GetBackendType() == gfx::BackendType::CAIRO
) {
1326 // Cairo doesn't play well with huge clips. When given a very big clip it
1327 // will try to allocate big mask surface without taking the target
1328 // size into account which can cause OOM. See bug 1034593.
1329 // This limits the clip extents to the size of the canvas.
1330 // A fix in Cairo would probably be preferable, but requires somewhat
1331 // invasive changes.
1332 mTarget
->PushClipRect(gfx::Rect(0, 0, mWidth
, mHeight
));
1335 for (auto& style
: mStyleStack
) {
1336 for (auto clipOrTransform
= style
.clipsAndTransforms
.begin();
1337 clipOrTransform
!= style
.clipsAndTransforms
.end(); clipOrTransform
++) {
1338 if (clipOrTransform
->IsClip()) {
1339 if (mClipsNeedConverting
) {
1340 // We have possibly changed backends, so we need to convert the clips
1341 // in case they are no longer compatible with mTarget.
1342 RefPtr
<PathBuilder
> pathBuilder
= mTarget
->CreatePathBuilder();
1343 clipOrTransform
->clip
->StreamToSink(pathBuilder
);
1344 clipOrTransform
->clip
= pathBuilder
->Finish();
1346 mTarget
->PushClip(clipOrTransform
->clip
);
1348 mTarget
->SetTransform(clipOrTransform
->transform
);
1353 mClipsNeedConverting
= false;
1356 bool CanvasRenderingContext2D::BorrowTarget(const IntRect
& aPersistedRect
,
1358 // We are attempting to request a DrawTarget from the current
1359 // PersistentBufferProvider. However, if the provider needs to be refreshed,
1360 // or if it is accelerated and the application has requested that we disallow
1361 // acceleration, then we skip trying to use this provider so that it will be
1362 // recreated by EnsureTarget later.
1363 if (!mBufferProvider
|| mBufferProvider
->RequiresRefresh() ||
1364 (mBufferProvider
->IsAccelerated() && mWillReadFrequently
)) {
1367 mTarget
= mBufferProvider
->BorrowDrawTarget(aPersistedRect
);
1368 if (!mTarget
|| !mTarget
->IsValid()) {
1370 mBufferProvider
->ReturnDrawTarget(mTarget
.forget());
1374 if (mBufferNeedsClear
) {
1375 if (mBufferProvider
->PreservesDrawingState()) {
1376 // If the buffer provider preserves the clip and transform state, then
1377 // we must ensure it is cleared before reusing the target.
1378 if (!mTarget
->RemoveAllClips()) {
1379 mBufferProvider
->ReturnDrawTarget(mTarget
.forget());
1382 mTarget
->SetTransform(Matrix());
1384 // If the canvas was reset, then we need to clear the target in case its
1385 // contents was somehow preserved. We only need to clear the target if
1386 // the operation doesn't fill the entire canvas.
1388 mTarget
->ClearRect(gfx::Rect(mTarget
->GetRect()));
1391 if (!mBufferProvider
->PreservesDrawingState() || mBufferNeedsClear
) {
1392 RestoreClipsAndTransformToTarget();
1394 mBufferNeedsClear
= false;
1398 bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect
* aCoveredRect
,
1400 if (AlreadyShutDown()) {
1401 gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
1407 return mTarget
!= sErrorTarget
.get();
1410 // Check that the dimensions are sane
1411 if (mWidth
> StaticPrefs::gfx_canvas_max_size() ||
1412 mHeight
> StaticPrefs::gfx_canvas_max_size() || mWidth
< 0 ||
1418 // If the next drawing command covers the entire canvas, we can skip copying
1419 // from the previous frame and/or clearing the canvas.
1420 gfx::Rect
canvasRect(0, 0, mWidth
, mHeight
);
1421 bool canDiscardContent
=
1422 aCoveredRect
&& CurrentState()
1423 .transform
.TransformBounds(*aCoveredRect
)
1424 .Contains(canvasRect
);
1426 // If a clip is active we don't know for sure that the next drawing command
1427 // will really cover the entire canvas.
1428 for (const auto& style
: mStyleStack
) {
1429 if (!canDiscardContent
) {
1432 for (const auto& clipOrTransform
: style
.clipsAndTransforms
) {
1433 if (clipOrTransform
.IsClip()) {
1434 canDiscardContent
= false;
1440 ScheduleStableStateCallback();
1442 IntRect persistedRect
= canDiscardContent
|| mBufferNeedsClear
1444 : IntRect(0, 0, mWidth
, mHeight
);
1446 // Attempt to reuse the existing buffer provider.
1447 if (BorrowTarget(persistedRect
, !canDiscardContent
)) {
1451 RefPtr
<DrawTarget
> newTarget
;
1452 RefPtr
<PersistentBufferProvider
> newProvider
;
1454 if (!TryAcceleratedTarget(newTarget
, newProvider
) &&
1455 !TrySharedTarget(newTarget
, newProvider
) &&
1456 !TryBasicTarget(newTarget
, newProvider
)) {
1458 CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
1459 << "Failed borrow shared and basic targets.";
1465 MOZ_ASSERT(newTarget
);
1466 MOZ_ASSERT(newProvider
);
1469 !canDiscardContent
|| (mBufferProvider
&& mBufferNeedsClear
);
1470 if (newTarget
->GetBackendType() == gfx::BackendType::SKIA
&&
1471 (needsClear
|| !aWillClear
)) {
1472 // Skia expects the unused X channel to contains 0xFF even for opaque
1473 // operations so we can't skip clearing in that case, even if we are going
1474 // to cover the entire canvas in the next drawing operation.
1475 newTarget
->ClearRect(canvasRect
);
1479 // Try to copy data from the previous buffer provider if there is one.
1480 if (!canDiscardContent
&& mBufferProvider
&& !mBufferNeedsClear
&&
1481 CopyBufferProvider(*mBufferProvider
, *newTarget
, persistedRect
)) {
1486 newTarget
->ClearRect(canvasRect
);
1489 mTarget
= std::move(newTarget
);
1490 mBufferProvider
= std::move(newProvider
);
1491 mBufferNeedsClear
= false;
1493 RegisterAllocation();
1494 AddZoneWaitingForGC();
1496 RestoreClipsAndTransformToTarget();
1498 // Force a full layer transaction since we didn't have a layer before
1499 // and now we might need one.
1500 if (mCanvasElement
) {
1501 mCanvasElement
->InvalidateCanvas();
1503 // EnsureTarget hasn't drawn anything. Preserve mFrameCaptureState.
1504 FrameCaptureState captureState
= mFrameCaptureState
;
1505 // Calling Redraw() tells our invalidation machinery that the entire
1506 // canvas is already invalid, which can speed up future drawing.
1508 mFrameCaptureState
= captureState
;
1513 void CanvasRenderingContext2D::SetInitialState() {
1514 // Set up the initial canvas defaults
1515 mPathBuilder
= nullptr;
1517 mDSPathBuilder
= nullptr;
1518 mPathTransformWillUpdate
= false;
1520 mStyleStack
.Clear();
1521 ContextState
* state
= mStyleStack
.AppendElement();
1522 state
->globalAlpha
= 1.0;
1524 state
->colorStyles
[Style::FILL
] = NS_RGB(0, 0, 0);
1525 state
->colorStyles
[Style::STROKE
] = NS_RGB(0, 0, 0);
1526 state
->shadowColor
= NS_RGBA(0, 0, 0, 0);
1529 void CanvasRenderingContext2D::SetErrorState() {
1530 EnsureErrorTarget();
1532 if (mTarget
&& mTarget
!= sErrorTarget
.get()) {
1533 gCanvasAzureMemoryUsed
-= mWidth
* mHeight
* 4;
1536 mTarget
= sErrorTarget
.get();
1537 mBufferProvider
= nullptr;
1539 // clear transforms, clips, etc.
1543 void CanvasRenderingContext2D::RegisterAllocation() {
1544 // XXX - It would make more sense to track the allocation in
1545 // PeristentBufferProvider, rather than here.
1546 static bool registered
= false;
1547 // FIXME: Disable the reporter for now, see bug 1241865
1548 if (!registered
&& false) {
1550 RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
1554 void CanvasRenderingContext2D::AddZoneWaitingForGC() {
1555 JSObject
* wrapper
= GetWrapperPreserveColor();
1557 CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
1558 JS::GetObjectZone(wrapper
));
1562 static WindowRenderer
* WindowRendererFromCanvasElement(
1563 nsINode
* aCanvasElement
) {
1564 if (!aCanvasElement
) {
1568 return nsContentUtils::WindowRendererForDocument(aCanvasElement
->OwnerDoc());
1571 bool CanvasRenderingContext2D::TryAcceleratedTarget(
1572 RefPtr
<gfx::DrawTarget
>& aOutDT
,
1573 RefPtr
<layers::PersistentBufferProvider
>& aOutProvider
) {
1574 if (!XRE_IsContentProcess()) {
1575 // Only allow accelerated contexts to be created in a content process to
1576 // ensure it is remoted appropriately and run on the correct parent or
1577 // GPU process threads.
1580 if (mBufferProvider
&& mBufferProvider
->IsAccelerated() &&
1581 mBufferProvider
->RequiresRefresh()) {
1582 // If there is already a provider and we got here, then the provider needs
1583 // to be refreshed and we should avoid using acceleration in the future.
1584 mAllowAcceleration
= false;
1586 // Don't try creating an accelerate DrawTarget if either acceleration failed
1587 // previously or if the application expects acceleration to be slow.
1588 if (!mAllowAcceleration
|| mWillReadFrequently
) {
1591 aOutDT
= DrawTargetWebgl::Create(GetSize(), GetSurfaceFormat());
1596 aOutProvider
= new PersistentBufferProviderAccelerated(aOutDT
);
1600 bool CanvasRenderingContext2D::TrySharedTarget(
1601 RefPtr
<gfx::DrawTarget
>& aOutDT
,
1602 RefPtr
<layers::PersistentBufferProvider
>& aOutProvider
) {
1604 aOutProvider
= nullptr;
1606 if (!mCanvasElement
) {
1610 if (mBufferProvider
&& mBufferProvider
->IsShared()) {
1611 // we are already using a shared buffer provider, we are allocating a new
1612 // one because the current one failed so let's just fall back to the basic
1614 mClipsNeedConverting
= true;
1618 WindowRenderer
* renderer
= WindowRendererFromCanvasElement(mCanvasElement
);
1625 renderer
->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat());
1627 if (!aOutProvider
) {
1631 // We can pass an empty persisted rect since we just created the buffer
1632 // provider (nothing to restore).
1633 aOutDT
= aOutProvider
->BorrowDrawTarget(IntRect());
1639 bool CanvasRenderingContext2D::TryBasicTarget(
1640 RefPtr
<gfx::DrawTarget
>& aOutDT
,
1641 RefPtr
<layers::PersistentBufferProvider
>& aOutProvider
) {
1642 aOutDT
= gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
1643 GetSize(), GetSurfaceFormat());
1648 // See Bug 1524554 - this forces DT initialization.
1649 aOutDT
->ClearRect(gfx::Rect());
1651 if (!aOutDT
->IsValid()) {
1656 aOutProvider
= new PersistentBufferProviderBasic(aOutDT
);
1660 PersistentBufferProvider
* CanvasRenderingContext2D::GetBufferProvider() {
1661 if (mBufferProvider
&& mBufferNeedsClear
) {
1662 // Force the buffer to clear before it is used.
1665 return mBufferProvider
;
1668 Maybe
<SurfaceDescriptor
> CanvasRenderingContext2D::GetFrontBuffer(
1669 WebGLFramebufferJS
*, const bool webvr
) {
1670 if (auto* provider
= GetBufferProvider()) {
1671 return provider
->GetFrontBuffer();
1676 PresShell
* CanvasRenderingContext2D::GetPresShell() {
1677 if (mCanvasElement
) {
1678 return mCanvasElement
->OwnerDoc()->GetPresShell();
1681 return mDocShell
->GetPresShell();
1687 CanvasRenderingContext2D::SetDimensions(int32_t aWidth
, int32_t aHeight
) {
1688 // Zero sized surfaces can cause problems.
1699 ClearTarget(aWidth
, aHeight
);
1704 void CanvasRenderingContext2D::AddAssociatedMemory() {
1705 JSObject
* wrapper
= GetWrapperMaybeDead();
1707 JS::AddAssociatedMemory(wrapper
, BindingJSObjectMallocBytes(this),
1708 JS::MemoryUse::DOMBinding
);
1712 void CanvasRenderingContext2D::RemoveAssociatedMemory() {
1713 JSObject
* wrapper
= GetWrapperMaybeDead();
1715 JS::RemoveAssociatedMemory(wrapper
, BindingJSObjectMallocBytes(this),
1716 JS::MemoryUse::DOMBinding
);
1720 void CanvasRenderingContext2D::ClearTarget(int32_t aWidth
, int32_t aHeight
) {
1721 // Only free the buffer provider if the size no longer matches.
1722 bool freeBuffer
= aWidth
!= mWidth
|| aHeight
!= mHeight
;
1723 ResetBitmap(freeBuffer
);
1729 // Update dimensions only if new (strictly positive) values were passed.
1730 if (aWidth
> 0 && aHeight
> 0) {
1731 // Update the memory size associated with the wrapper object when we change
1732 // the dimensions. Note that we need to keep updating dying wrappers before
1733 // they are finalized so that the memory accounting balances out.
1734 RemoveAssociatedMemory();
1737 AddAssociatedMemory();
1740 if (mOffscreenCanvas
) {
1741 OffscreenCanvasDisplayData data
;
1742 data
.mSize
= {mWidth
, mHeight
};
1743 data
.mIsOpaque
= mOpaque
;
1744 data
.mIsAlphaPremult
= true;
1745 data
.mDoPaintCallbacks
= true;
1746 mOffscreenCanvas
->UpdateDisplayData(data
);
1749 if (!mCanvasElement
|| !mCanvasElement
->IsInComposedDoc()) {
1753 // For vertical writing-mode, unless text-orientation is sideways,
1754 // we'll modify the initial value of textBaseline to 'middle'.
1755 RefPtr
<const ComputedStyle
> canvasStyle
=
1756 nsComputedDOMStyle::GetComputedStyle(mCanvasElement
);
1758 WritingMode
wm(canvasStyle
);
1759 if (wm
.IsVertical() && !wm
.IsSideways()) {
1760 CurrentState().textBaseline
= TextBaseline::MIDDLE
;
1765 void CanvasRenderingContext2D::ReturnTarget(bool aForceReset
) {
1766 if (mTarget
&& mBufferProvider
&& mTarget
!= sErrorTarget
.get()) {
1767 CurrentState().transform
= mTarget
->GetTransform();
1768 if (aForceReset
|| !mBufferProvider
->PreservesDrawingState()) {
1769 for (const auto& style
: mStyleStack
) {
1770 for (const auto& clipOrTransform
: style
.clipsAndTransforms
) {
1771 if (clipOrTransform
.IsClip()) {
1777 if (mTarget
->GetBackendType() == gfx::BackendType::CAIRO
) {
1778 // With the cairo backend we pushed an extra clip rect which we have to
1779 // balance out here. See the comment in
1780 // RestoreClipsAndTransformToTarget.
1784 mTarget
->SetTransform(Matrix());
1787 mBufferProvider
->ReturnDrawTarget(mTarget
.forget());
1792 CanvasRenderingContext2D::InitializeWithDrawTarget(
1793 nsIDocShell
* aShell
, NotNull
<gfx::DrawTarget
*> aTarget
) {
1794 RemovePostRefreshObserver();
1796 AddPostRefreshObserverIfNecessary();
1798 IntSize size
= aTarget
->GetSize();
1799 SetDimensions(size
.width
, size
.height
);
1802 mBufferProvider
= new PersistentBufferProviderBasic(aTarget
);
1804 RestoreClipsAndTransformToTarget();
1809 void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(
1810 bool aOpaqueAttrValue
) {
1811 if (aOpaqueAttrValue
!= mOpaqueAttrValue
) {
1812 mOpaqueAttrValue
= aOpaqueAttrValue
;
1817 void CanvasRenderingContext2D::UpdateIsOpaque() {
1818 mOpaque
= !mContextAttributesHasAlpha
|| mOpaqueAttrValue
;
1823 CanvasRenderingContext2D::SetContextOptions(JSContext
* aCx
,
1824 JS::Handle
<JS::Value
> aOptions
,
1825 ErrorResult
& aRvForDictionaryInit
) {
1826 if (aOptions
.isNullOrUndefined()) {
1830 // This shouldn't be called before drawing starts, so there should be no
1832 MOZ_ASSERT(!mTarget
);
1834 ContextAttributes2D attributes
;
1835 if (!attributes
.Init(aCx
, aOptions
)) {
1836 aRvForDictionaryInit
.Throw(NS_ERROR_UNEXPECTED
);
1837 return NS_ERROR_UNEXPECTED
;
1840 mWillReadFrequently
= attributes
.mWillReadFrequently
;
1842 mContextAttributesHasAlpha
= attributes
.mAlpha
;
1848 UniquePtr
<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
1849 int32_t* out_format
, gfx::IntSize
* out_imageSize
) {
1850 UniquePtr
<uint8_t[]> ret
;
1853 *out_imageSize
= {};
1855 if (!GetBufferProvider() && !EnsureTarget()) {
1859 RefPtr
<SourceSurface
> snapshot
= mBufferProvider
->BorrowSnapshot();
1861 RefPtr
<DataSourceSurface
> data
= snapshot
->GetDataSurface();
1862 if (data
&& data
->GetSize() == GetSize()) {
1863 *out_format
= imgIEncoder::INPUT_FORMAT_HOSTARGB
;
1864 *out_imageSize
= data
->GetSize();
1865 ret
= SurfaceToPackedBGRA(data
);
1869 mBufferProvider
->ReturnSnapshot(snapshot
.forget());
1875 CanvasRenderingContext2D::GetInputStream(const char* aMimeType
,
1876 const nsAString
& aEncoderOptions
,
1877 nsIInputStream
** aStream
) {
1878 nsCString
enccid("@mozilla.org/image/encoder;2?type=");
1879 enccid
+= aMimeType
;
1880 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(enccid
.get());
1882 return NS_ERROR_FAILURE
;
1886 gfx::IntSize imageSize
= {};
1887 UniquePtr
<uint8_t[]> imageBuffer
= GetImageBuffer(&format
, &imageSize
);
1889 return NS_ERROR_FAILURE
;
1892 return ImageEncoder::GetInputStream(imageSize
.width
, imageSize
.height
,
1893 imageBuffer
.get(), format
, encoder
,
1894 aEncoderOptions
, aStream
);
1897 already_AddRefed
<mozilla::gfx::SourceSurface
>
1898 CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget
* aTarget
,
1899 gfxAlphaType
* aOutAlphaType
) {
1900 if (aOutAlphaType
) {
1901 *aOutAlphaType
= (mOpaque
? gfxAlphaType::Opaque
: gfxAlphaType::Premult
);
1904 // For GetSurfaceSnapshot we always call EnsureTarget even if mBufferProvider
1905 // already exists, otherwise we get performance issues. See bug 1567054.
1906 if (!EnsureTarget()) {
1908 mTarget
== sErrorTarget
.get(),
1909 "On EnsureTarget failure mTarget should be set to sErrorTarget.");
1910 // In rare circumstances we may have failed to create an error target.
1911 return mTarget
? mTarget
->Snapshot() : nullptr;
1914 // The concept of BorrowSnapshot seems a bit broken here, but the original
1915 // code in GetSurfaceSnapshot just returned a snapshot from mTarget, which
1916 // amounts to breaking the concept implicitly.
1917 RefPtr
<SourceSurface
> snapshot
= mBufferProvider
->BorrowSnapshot(aTarget
);
1918 RefPtr
<SourceSurface
> retSurface
= snapshot
;
1919 mBufferProvider
->ReturnSnapshot(snapshot
.forget());
1920 return retSurface
.forget();
1923 SurfaceFormat
CanvasRenderingContext2D::GetSurfaceFormat() const {
1924 return mOpaque
? SurfaceFormat::B8G8R8X8
: SurfaceFormat::B8G8R8A8
;
1931 void CanvasRenderingContext2D::Save() {
1933 if (MOZ_UNLIKELY(!mTarget
|| mStyleStack
.IsEmpty())) {
1937 mStyleStack
[mStyleStack
.Length() - 1].transform
= mTarget
->GetTransform();
1938 mStyleStack
.SetCapacity(mStyleStack
.Length() + 1);
1939 mStyleStack
.AppendElement(CurrentState());
1941 if (mStyleStack
.Length() > MAX_STYLE_STACK_SIZE
) {
1942 // This is not fast, but is better than OOMing and shouldn't be hit by
1944 mStyleStack
.RemoveElementAt(0);
1948 void CanvasRenderingContext2D::Restore() {
1949 if (MOZ_UNLIKELY(mStyleStack
.Length() < 2)) {
1953 TransformWillUpdate();
1954 if (!IsTargetValid()) {
1958 for (const auto& clipOrTransform
: CurrentState().clipsAndTransforms
) {
1959 if (clipOrTransform
.IsClip()) {
1964 mStyleStack
.RemoveLastElement();
1966 mTarget
->SetTransform(CurrentState().transform
);
1973 void CanvasRenderingContext2D::Scale(double aX
, double aY
,
1974 ErrorResult
& aError
) {
1975 TransformWillUpdate();
1976 if (!IsTargetValid()) {
1977 aError
.Throw(NS_ERROR_FAILURE
);
1981 Matrix newMatrix
= mTarget
->GetTransform();
1982 newMatrix
.PreScale(aX
, aY
);
1984 SetTransformInternal(newMatrix
);
1987 void CanvasRenderingContext2D::Rotate(double aAngle
, ErrorResult
& aError
) {
1988 TransformWillUpdate();
1989 if (!IsTargetValid()) {
1990 aError
.Throw(NS_ERROR_FAILURE
);
1994 Matrix newMatrix
= Matrix::Rotation(aAngle
) * mTarget
->GetTransform();
1996 SetTransformInternal(newMatrix
);
1999 void CanvasRenderingContext2D::Translate(double aX
, double aY
,
2000 ErrorResult
& aError
) {
2001 TransformWillUpdate();
2002 if (!IsTargetValid()) {
2003 aError
.Throw(NS_ERROR_FAILURE
);
2007 Matrix newMatrix
= mTarget
->GetTransform();
2008 newMatrix
.PreTranslate(aX
, aY
);
2010 SetTransformInternal(newMatrix
);
2013 void CanvasRenderingContext2D::Transform(double aM11
, double aM12
, double aM21
,
2014 double aM22
, double aDx
, double aDy
,
2015 ErrorResult
& aError
) {
2016 TransformWillUpdate();
2017 if (!IsTargetValid()) {
2018 aError
.Throw(NS_ERROR_FAILURE
);
2022 Matrix
newMatrix(aM11
, aM12
, aM21
, aM22
, aDx
, aDy
);
2023 newMatrix
*= mTarget
->GetTransform();
2025 SetTransformInternal(newMatrix
);
2028 already_AddRefed
<DOMMatrix
> CanvasRenderingContext2D::GetTransform(
2029 ErrorResult
& aError
) {
2031 if (!IsTargetValid()) {
2032 aError
.Throw(NS_ERROR_FAILURE
);
2035 RefPtr
<DOMMatrix
> matrix
=
2036 new DOMMatrix(GetParentObject(), mTarget
->GetTransform());
2037 return matrix
.forget();
2040 void CanvasRenderingContext2D::SetTransform(double aM11
, double aM12
,
2041 double aM21
, double aM22
,
2042 double aDx
, double aDy
,
2043 ErrorResult
& aError
) {
2044 TransformWillUpdate();
2045 if (!IsTargetValid()) {
2046 aError
.Throw(NS_ERROR_FAILURE
);
2050 SetTransformInternal(Matrix(aM11
, aM12
, aM21
, aM22
, aDx
, aDy
));
2053 void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit
& aInit
,
2054 ErrorResult
& aError
) {
2055 TransformWillUpdate();
2056 if (!IsTargetValid()) {
2057 aError
.Throw(NS_ERROR_FAILURE
);
2061 RefPtr
<DOMMatrixReadOnly
> matrix
=
2062 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit
, aError
);
2063 if (!aError
.Failed()) {
2064 SetTransformInternal(Matrix(*(matrix
->GetInternal2D())));
2068 void CanvasRenderingContext2D::SetTransformInternal(const Matrix
& aTransform
) {
2069 if (!aTransform
.IsFinite()) {
2073 // Save the transform in the clip stack to be able to replay clips properly.
2074 auto& clipsAndTransforms
= CurrentState().clipsAndTransforms
;
2075 if (clipsAndTransforms
.IsEmpty() ||
2076 clipsAndTransforms
.LastElement().IsClip()) {
2077 clipsAndTransforms
.AppendElement(ClipState(aTransform
));
2079 // If the last item is a transform we can replace it instead of appending
2081 clipsAndTransforms
.LastElement().transform
= aTransform
;
2083 mTarget
->SetTransform(aTransform
);
2086 void CanvasRenderingContext2D::ResetTransform(ErrorResult
& aError
) {
2087 SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError
);
2094 void CanvasRenderingContext2D::SetStyleFromUnion(
2095 const UTF8StringOrCanvasGradientOrCanvasPattern
& aValue
,
2096 Style aWhichStyle
) {
2097 if (aValue
.IsUTF8String()) {
2098 SetStyleFromString(aValue
.GetAsUTF8String(), aWhichStyle
);
2102 if (aValue
.IsCanvasGradient()) {
2103 SetStyleFromGradient(aValue
.GetAsCanvasGradient(), aWhichStyle
);
2107 if (aValue
.IsCanvasPattern()) {
2108 CanvasPattern
& pattern
= aValue
.GetAsCanvasPattern();
2109 SetStyleFromPattern(pattern
, aWhichStyle
);
2110 if (pattern
.mForceWriteOnly
) {
2116 MOZ_ASSERT_UNREACHABLE("Invalid union value");
2119 void CanvasRenderingContext2D::SetFillRule(const nsAString
& aString
) {
2122 if (aString
.EqualsLiteral("evenodd"))
2123 rule
= FillRule::FILL_EVEN_ODD
;
2124 else if (aString
.EqualsLiteral("nonzero"))
2125 rule
= FillRule::FILL_WINDING
;
2129 CurrentState().fillRule
= rule
;
2132 void CanvasRenderingContext2D::GetFillRule(nsAString
& aString
) {
2133 switch (CurrentState().fillRule
) {
2134 case FillRule::FILL_WINDING
:
2135 aString
.AssignLiteral("nonzero");
2137 case FillRule::FILL_EVEN_ODD
:
2138 aString
.AssignLiteral("evenodd");
2143 // gradients and patterns
2145 already_AddRefed
<CanvasGradient
> CanvasRenderingContext2D::CreateLinearGradient(
2146 double aX0
, double aY0
, double aX1
, double aY1
) {
2147 RefPtr
<CanvasGradient
> grad
=
2148 new CanvasLinearGradient(this, Point(aX0
, aY0
), Point(aX1
, aY1
));
2150 return grad
.forget();
2153 already_AddRefed
<CanvasGradient
> CanvasRenderingContext2D::CreateRadialGradient(
2154 double aX0
, double aY0
, double aR0
, double aX1
, double aY1
, double aR1
,
2155 ErrorResult
& aError
) {
2156 if (aR0
< 0.0 || aR1
< 0.0) {
2157 aError
.ThrowIndexSizeError("Negative radius");
2161 RefPtr
<CanvasGradient
> grad
= new CanvasRadialGradient(
2162 this, Point(aX0
, aY0
), aR0
, Point(aX1
, aY1
), aR1
);
2164 return grad
.forget();
2167 already_AddRefed
<CanvasGradient
> CanvasRenderingContext2D::CreateConicGradient(
2168 double aAngle
, double aCx
, double aCy
) {
2169 double adjustedStartAngle
= aAngle
+ M_PI
/ 2.0;
2170 return MakeAndAddRef
<CanvasConicGradient
>(this, adjustedStartAngle
,
2174 already_AddRefed
<CanvasPattern
> CanvasRenderingContext2D::CreatePattern(
2175 const CanvasImageSource
& aSource
, const nsAString
& aRepeat
,
2176 ErrorResult
& aError
) {
2177 CanvasPattern::RepeatMode repeatMode
= CanvasPattern::RepeatMode::NOREPEAT
;
2179 if (aRepeat
.IsEmpty() || aRepeat
.EqualsLiteral("repeat")) {
2180 repeatMode
= CanvasPattern::RepeatMode::REPEAT
;
2181 } else if (aRepeat
.EqualsLiteral("repeat-x")) {
2182 repeatMode
= CanvasPattern::RepeatMode::REPEATX
;
2183 } else if (aRepeat
.EqualsLiteral("repeat-y")) {
2184 repeatMode
= CanvasPattern::RepeatMode::REPEATY
;
2185 } else if (aRepeat
.EqualsLiteral("no-repeat")) {
2186 repeatMode
= CanvasPattern::RepeatMode::NOREPEAT
;
2188 aError
.ThrowSyntaxError("Invalid pattern keyword");
2192 Element
* element
= nullptr;
2193 OffscreenCanvas
* offscreenCanvas
= nullptr;
2195 if (aSource
.IsHTMLCanvasElement()) {
2196 HTMLCanvasElement
* canvas
= &aSource
.GetAsHTMLCanvasElement();
2199 nsIntSize size
= canvas
->GetSize();
2200 if (size
.width
== 0) {
2201 aError
.ThrowInvalidStateError("Passed-in canvas has width 0");
2205 if (size
.height
== 0) {
2206 aError
.ThrowInvalidStateError("Passed-in canvas has height 0");
2210 // Special case for Canvas, which could be an Azure canvas!
2211 nsICanvasRenderingContextInternal
* srcCanvas
= canvas
->GetCurrentContext();
2213 // This might not be an Azure canvas!
2214 RefPtr
<SourceSurface
> srcSurf
= srcCanvas
->GetSurfaceSnapshot();
2216 aError
.ThrowInvalidStateError(
2217 "CanvasRenderingContext2D.createPattern() failed to snapshot source"
2222 RefPtr
<CanvasPattern
> pat
=
2223 new CanvasPattern(this, srcSurf
, repeatMode
, element
->NodePrincipal(),
2224 canvas
->IsWriteOnly(), false);
2226 return pat
.forget();
2228 } else if (aSource
.IsHTMLImageElement()) {
2229 HTMLImageElement
* img
= &aSource
.GetAsHTMLImageElement();
2231 } else if (aSource
.IsSVGImageElement()) {
2232 SVGImageElement
* img
= &aSource
.GetAsSVGImageElement();
2234 } else if (aSource
.IsHTMLVideoElement()) {
2235 auto& video
= aSource
.GetAsHTMLVideoElement();
2236 video
.LogVisibility(
2237 mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN
);
2239 } else if (aSource
.IsOffscreenCanvas()) {
2240 offscreenCanvas
= &aSource
.GetAsOffscreenCanvas();
2242 nsIntSize size
= offscreenCanvas
->GetWidthHeight();
2243 if (size
.width
== 0) {
2244 aError
.ThrowInvalidStateError("Passed-in canvas has width 0");
2248 if (size
.height
== 0) {
2249 aError
.ThrowInvalidStateError("Passed-in canvas has height 0");
2253 nsICanvasRenderingContextInternal
* srcCanvas
=
2254 offscreenCanvas
->GetContext();
2256 RefPtr
<SourceSurface
> srcSurf
= srcCanvas
->GetSurfaceSnapshot();
2258 aError
.ThrowInvalidStateError(
2259 "Passed-in canvas failed to create snapshot");
2263 RefPtr
<CanvasPattern
> pat
= new CanvasPattern(
2264 this, srcSurf
, repeatMode
, srcCanvas
->PrincipalOrNull(),
2265 offscreenCanvas
->IsWriteOnly(), false);
2267 return pat
.forget();
2270 // Special case for ImageBitmap
2271 ImageBitmap
& imgBitmap
= aSource
.GetAsImageBitmap();
2273 if (!IsTargetValid()) {
2274 aError
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2277 RefPtr
<SourceSurface
> srcSurf
= imgBitmap
.PrepareForDrawTarget(mTarget
);
2279 aError
.ThrowInvalidStateError(
2280 "Passed-in ImageBitmap has been transferred");
2284 // An ImageBitmap never taints others so we set principalForSecurityCheck to
2285 // nullptr and set CORSUsed to true for passing the security check in
2286 // CanvasUtils::DoDrawImageSecurityCheck().
2287 RefPtr
<CanvasPattern
> pat
= new CanvasPattern(
2288 this, srcSurf
, repeatMode
, nullptr, imgBitmap
.IsWriteOnly(), true);
2290 return pat
.forget();
2294 if (!IsTargetValid()) {
2295 aError
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2299 // The canvas spec says that createPattern should use the first frame
2300 // of animated images
2301 auto flags
= nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE
|
2302 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE
;
2303 SurfaceFromElementResult res
=
2305 ? nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas
, flags
,
2307 : nsLayoutUtils::SurfaceFromElement(element
, flags
, mTarget
);
2309 // Per spec, we should throw here for the HTMLImageElement and SVGImageElement
2310 // cases if the image request state is "broken". In terms of the infromation
2311 // in "res", the "broken" state corresponds to not having a size and not being
2312 // still-loading (so there is no size forthcoming).
2313 if (aSource
.IsHTMLImageElement() || aSource
.IsSVGImageElement()) {
2314 if (!res
.mIsStillLoading
&& !res
.mHasSize
) {
2315 aError
.ThrowInvalidStateError(
2316 "Passed-in image's current request's state is \"broken\"");
2320 if (res
.mSize
.width
== 0 || res
.mSize
.height
== 0) {
2324 // Is the "fully decodable" check already done in SurfaceFromElement? It's
2325 // not clear how to do it from here, exactly.
2328 RefPtr
<SourceSurface
> surface
= res
.GetSourceSurface();
2333 RefPtr
<CanvasPattern
> pat
=
2334 new CanvasPattern(this, surface
, repeatMode
, res
.mPrincipal
,
2335 res
.mIsWriteOnly
, res
.mCORSUsed
);
2336 return pat
.forget();
2342 void CanvasRenderingContext2D::SetShadowColor(const nsACString
& aShadowColor
) {
2343 Maybe
<nscolor
> color
= ParseColor(aShadowColor
);
2348 CurrentState().shadowColor
= *color
;
2355 static already_AddRefed
<RawServoDeclarationBlock
> CreateDeclarationForServo(
2356 nsCSSPropertyID aProperty
, const nsACString
& aPropertyValue
,
2357 Document
* aDocument
) {
2358 ServoCSSParser::ParsingEnvironment env
{aDocument
->DefaultStyleAttrURLData(),
2359 aDocument
->GetCompatibilityMode(),
2360 aDocument
->CSSLoader()};
2361 RefPtr
<RawServoDeclarationBlock
> servoDeclarations
=
2362 ServoCSSParser::ParseProperty(aProperty
, aPropertyValue
, env
);
2364 if (!servoDeclarations
) {
2365 // We got a syntax error. The spec says this value must be ignored.
2369 // From canvas spec, force to set line-height property to 'normal' font
2371 if (aProperty
== eCSSProperty_font
) {
2372 const nsCString normalString
= "normal"_ns
;
2373 Servo_DeclarationBlock_SetPropertyById(
2374 servoDeclarations
, eCSSProperty_line_height
, &normalString
, false,
2375 env
.mUrlExtraData
, ParsingMode::Default
, env
.mCompatMode
, env
.mLoader
,
2379 return servoDeclarations
.forget();
2382 static already_AddRefed
<RawServoDeclarationBlock
> CreateFontDeclarationForServo(
2383 const nsACString
& aFont
, Document
* aDocument
) {
2384 return CreateDeclarationForServo(eCSSProperty_font
, aFont
, aDocument
);
2387 static already_AddRefed
<const ComputedStyle
> GetFontStyleForServo(
2388 Element
* aElement
, const nsACString
& aFont
, PresShell
* aPresShell
,
2389 nsACString
& aOutUsedFont
, ErrorResult
& aError
) {
2390 RefPtr
<RawServoDeclarationBlock
> declarations
=
2391 CreateFontDeclarationForServo(aFont
, aPresShell
->GetDocument());
2392 if (!declarations
) {
2393 // We got a syntax error. The spec says this value must be ignored.
2397 // In addition to unparseable values, the spec says we need to reject
2398 // 'inherit' and 'initial'. The easiest way to check for this is to look
2399 // at font-size-adjust, which the font shorthand resets to 'none'.
2400 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations
,
2401 eCSSProperty_font_size_adjust
)) {
2405 ServoStyleSet
* styleSet
= aPresShell
->StyleSet();
2407 RefPtr
<const ComputedStyle
> parentStyle
;
2408 // have to get a parent ComputedStyle for inherit-like relative
2409 // values (2em, bolder, etc.)
2410 if (aElement
&& aElement
->IsInComposedDoc()) {
2411 parentStyle
= nsComputedDOMStyle::GetComputedStyle(aElement
);
2413 // The flush killed the shell, so we couldn't get any meaningful style
2415 aError
.Throw(NS_ERROR_FAILURE
);
2419 RefPtr
<RawServoDeclarationBlock
> declarations
=
2420 CreateFontDeclarationForServo("10px sans-serif"_ns
,
2421 aPresShell
->GetDocument());
2422 MOZ_ASSERT(declarations
);
2425 aPresShell
->StyleSet()->ResolveForDeclarations(nullptr, declarations
);
2428 MOZ_RELEASE_ASSERT(parentStyle
, "Should have a valid parent style");
2430 MOZ_ASSERT(!aPresShell
->IsDestroying(),
2431 "We should have returned an error above if the presshell is "
2432 "being destroyed.");
2434 RefPtr
<const ComputedStyle
> sc
=
2435 styleSet
->ResolveForDeclarations(parentStyle
, declarations
);
2437 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-font
2438 // The font-size component must be converted to CSS px for reserialization,
2439 // so we update the declarations with the value from the computed style.
2440 if (!sc
->StyleFont()->mFont
.family
.is_system_font
) {
2441 nsAutoCString computedFontSize
;
2442 sc
->GetComputedPropertyValue(eCSSProperty_font_size
, computedFontSize
);
2443 Servo_DeclarationBlock_SetPropertyById(
2444 declarations
, eCSSProperty_font_size
, &computedFontSize
, false, nullptr,
2445 ParsingMode::Default
, eCompatibility_FullStandards
, nullptr,
2446 StyleCssRuleType::Style
, {});
2449 // The font getter is required to be reserialized based on what we
2450 // parsed (including having line-height removed).
2451 Servo_SerializeFontValueForCanvas(declarations
, &aOutUsedFont
);
2455 static already_AddRefed
<RawServoDeclarationBlock
>
2456 CreateFilterDeclarationForServo(const nsACString
& aFilter
,
2457 Document
* aDocument
) {
2458 return CreateDeclarationForServo(eCSSProperty_filter
, aFilter
, aDocument
);
2461 static already_AddRefed
<const ComputedStyle
> ResolveFilterStyleForServo(
2462 const nsACString
& aFilterString
, const ComputedStyle
* aParentStyle
,
2463 PresShell
* aPresShell
, ErrorResult
& aError
) {
2464 RefPtr
<RawServoDeclarationBlock
> declarations
=
2465 CreateFilterDeclarationForServo(aFilterString
, aPresShell
->GetDocument());
2466 if (!declarations
) {
2467 // Refuse to accept the filter, but do not throw an error.
2471 // In addition to unparseable values, the spec says we need to reject
2472 // 'inherit' and 'initial'.
2473 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations
,
2474 eCSSProperty_filter
)) {
2478 ServoStyleSet
* styleSet
= aPresShell
->StyleSet();
2479 RefPtr
<const ComputedStyle
> computedValues
=
2480 styleSet
->ResolveForDeclarations(aParentStyle
, declarations
);
2482 return computedValues
.forget();
2485 bool CanvasRenderingContext2D::ParseFilter(
2486 const nsACString
& aString
, StyleOwnedSlice
<StyleFilter
>& aFilterChain
,
2487 ErrorResult
& aError
) {
2488 RefPtr
<PresShell
> presShell
= GetPresShell();
2489 if (NS_WARN_IF(!presShell
)) {
2490 aError
.Throw(NS_ERROR_FAILURE
);
2494 nsAutoCString usedFont
; // unused
2496 RefPtr
<const ComputedStyle
> parentStyle
= GetFontStyleForServo(
2497 mCanvasElement
, GetFont(), presShell
, usedFont
, aError
);
2502 RefPtr
<const ComputedStyle
> style
=
2503 ResolveFilterStyleForServo(aString
, parentStyle
, presShell
, aError
);
2508 aFilterChain
= style
->StyleEffects()->mFilters
;
2512 void CanvasRenderingContext2D::SetFilter(const nsACString
& aFilter
,
2513 ErrorResult
& aError
) {
2514 StyleOwnedSlice
<StyleFilter
> filterChain
;
2515 if (ParseFilter(aFilter
, filterChain
, aError
)) {
2516 CurrentState().filterString
= aFilter
;
2517 CurrentState().filterChain
= std::move(filterChain
);
2518 if (mCanvasElement
) {
2519 CurrentState().autoSVGFiltersObserver
=
2520 SVGObserverUtils::ObserveFiltersForCanvasContext(
2521 this, mCanvasElement
, CurrentState().filterChain
.AsSpan());
2527 class CanvasUserSpaceMetrics
: public UserSpaceMetricsWithSize
{
2529 CanvasUserSpaceMetrics(const gfx::IntSize
& aSize
, const nsFont
& aFont
,
2530 nsAtom
* aFontLanguage
, bool aExplicitLanguage
,
2531 nsPresContext
* aPresContext
)
2534 mFontLanguage(aFontLanguage
),
2535 mExplicitLanguage(aExplicitLanguage
),
2536 mPresContext(aPresContext
) {}
2538 virtual float GetEmLength() const override
{
2539 return mFont
.size
.ToCSSPixels();
2542 virtual float GetExLength() const override
{
2543 nsFontMetrics::Params params
;
2544 params
.language
= mFontLanguage
;
2545 params
.explicitLanguage
= mExplicitLanguage
;
2546 params
.textPerf
= mPresContext
->GetTextPerfMetrics();
2547 params
.featureValueLookup
= mPresContext
->GetFontFeatureValuesLookup();
2548 RefPtr
<nsFontMetrics
> fontMetrics
=
2549 mPresContext
->GetMetricsFor(mFont
, params
);
2550 return NSAppUnitsToFloatPixels(fontMetrics
->XHeight(),
2551 AppUnitsPerCSSPixel());
2554 virtual gfx::Size
GetSize() const override
{ return Size(mSize
); }
2558 const nsFont
& mFont
;
2559 nsAtom
* mFontLanguage
;
2560 bool mExplicitLanguage
;
2561 nsPresContext
* mPresContext
;
2564 // The filter might reference an SVG filter that is declared inside this
2565 // document. Flush frames so that we'll have a SVGFilterFrame to work
2567 static bool FiltersNeedFrameFlush(Span
<const StyleFilter
> aFilters
) {
2568 for (const auto& filter
: aFilters
) {
2569 if (filter
.IsUrl()) {
2576 void CanvasRenderingContext2D::UpdateFilter() {
2577 RefPtr
<PresShell
> presShell
= GetPresShell();
2578 if (!presShell
|| presShell
->IsDestroying()) {
2579 // Ensure we set an empty filter and update the state to
2580 // reflect the current "taint" status of the canvas
2581 CurrentState().filter
= FilterDescription();
2582 CurrentState().filterSourceGraphicTainted
=
2583 mCanvasElement
&& mCanvasElement
->IsWriteOnly();
2587 if (FiltersNeedFrameFlush(CurrentState().filterChain
.AsSpan())) {
2588 presShell
->FlushPendingNotifications(FlushType::Frames
);
2591 MOZ_RELEASE_ASSERT(!mStyleStack
.IsEmpty());
2592 if (MOZ_UNLIKELY(presShell
->IsDestroying())) {
2596 const bool sourceGraphicIsTainted
=
2597 mCanvasElement
&& mCanvasElement
->IsWriteOnly();
2599 CurrentState().filter
= FilterInstance::GetFilterDescription(
2600 mCanvasElement
, CurrentState().filterChain
.AsSpan(),
2601 sourceGraphicIsTainted
,
2602 CanvasUserSpaceMetrics(
2603 GetSize(), CurrentState().fontFont
, CurrentState().fontLanguage
,
2604 CurrentState().fontExplicitLanguage
, presShell
->GetPresContext()),
2605 gfxRect(0, 0, mWidth
, mHeight
), CurrentState().filterAdditionalImages
);
2606 CurrentState().filterSourceGraphicTainted
= sourceGraphicIsTainted
;
2613 static bool ValidateRect(double& aX
, double& aY
, double& aWidth
,
2614 double& aHeight
, bool aIsZeroSizeValid
) {
2615 if (!aIsZeroSizeValid
&& (aWidth
== 0.0 || aHeight
== 0.0)) {
2620 // The values of canvas API input are in double precision, but Moz2D APIs are
2621 // using float precision. Bypass canvas API calls when the input is out of
2622 // float precision to avoid precision problem
2623 if (!std::isfinite((float)aX
) || !std::isfinite((float)aY
) ||
2624 !std::isfinite((float)aWidth
) || !std::isfinite((float)aHeight
)) {
2629 // The canvas spec does not forbid rects with negative w or h, so given
2630 // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate
2631 // the appropriate rect by flipping negative dimensions. This prevents
2632 // draw targets from receiving "empty" rects later on.
2644 void CanvasRenderingContext2D::ClearRect(double aX
, double aY
, double aW
,
2646 // Do not allow zeros - it's a no-op at that point per spec.
2647 if (!ValidateRect(aX
, aY
, aW
, aH
, false)) {
2651 gfx::Rect
clearRect(aX
, aY
, aW
, aH
);
2653 EnsureTarget(&clearRect
, true);
2654 if (!IsTargetValid()) {
2658 mTarget
->ClearRect(clearRect
);
2660 RedrawUser(gfxRect(aX
, aY
, aW
, aH
));
2663 void CanvasRenderingContext2D::FillRect(double aX
, double aY
, double aW
,
2665 if (!ValidateRect(aX
, aY
, aW
, aH
, true)) {
2669 const ContextState
* state
= &CurrentState();
2670 if (state
->patternStyles
[Style::FILL
]) {
2671 auto& style
= state
->patternStyles
[Style::FILL
];
2672 CanvasPattern::RepeatMode repeat
= style
->mRepeat
;
2673 // In the FillRect case repeat modes are easy to deal with.
2674 bool limitx
= repeat
== CanvasPattern::RepeatMode::NOREPEAT
||
2675 repeat
== CanvasPattern::RepeatMode::REPEATY
;
2676 bool limity
= repeat
== CanvasPattern::RepeatMode::NOREPEAT
||
2677 repeat
== CanvasPattern::RepeatMode::REPEATX
;
2678 if ((limitx
|| limity
) && style
->mTransform
.IsRectilinear()) {
2679 // For rectilinear transforms, we can just get the transformed pattern
2680 // bounds and intersect them with the fill rectangle bounds.
2681 // TODO: If the transform is not rectilinear, then we would need a fully
2682 // general clip path to represent the X and Y clip planes bounding the
2683 // pattern. For such cases, it would be more efficient to rely on Skia's
2684 // Decal tiling mode rather than trying to generate a path. Until then,
2685 // just punt to relying on the default Clamp mode.
2686 gfx::Rect
patternBounds(style
->mSurface
->GetRect());
2687 patternBounds
= style
->mTransform
.TransformBounds(patternBounds
);
2688 if (style
->mTransform
.HasNonAxisAlignedTransform()) {
2689 // If there is an rotation (90 or 270 degrees), the X axis of the
2690 // pattern projects onto the Y axis of the geometry, and vice versa.
2691 std::swap(limitx
, limity
);
2693 // We always need to execute painting for non-over operators, even if
2694 // we end up with w/h = 0. The default Rect::Intersect can cause both
2695 // dimensions to become empty if either dimension individually fails
2696 // to overlap, which is unsuitable. Instead, we need to independently
2697 // limit the supplied rectangle on each dimension as required.
2699 double x2
= aX
+ aW
;
2700 aX
= std::max(aX
, double(patternBounds
.x
));
2701 aW
= std::max(std::min(x2
, double(patternBounds
.XMost())) - aX
, 0.0);
2704 double y2
= aY
+ aH
;
2705 aY
= std::max(aY
, double(patternBounds
.y
));
2706 aH
= std::max(std::min(y2
, double(patternBounds
.YMost())) - aY
, 0.0);
2713 bool discardContent
= PatternIsOpaque(Style::FILL
, &isColor
) &&
2714 (CurrentState().op
== CompositionOp::OP_OVER
||
2715 CurrentState().op
== CompositionOp::OP_SOURCE
);
2716 const gfx::Rect
fillRect(aX
, aY
, aW
, aH
);
2717 EnsureTarget(discardContent
? &fillRect
: nullptr, discardContent
&& isColor
);
2718 if (!IsTargetValid()) {
2723 const bool needBounds
= NeedToCalculateBounds();
2724 if (!IsTargetValid()) {
2728 bounds
= mTarget
->GetTransform().TransformBounds(fillRect
);
2731 AntialiasMode antialiasMode
= CurrentState().imageSmoothingEnabled
2732 ? AntialiasMode::DEFAULT
2733 : AntialiasMode::NONE
;
2735 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2736 CompositionOp op
= target
.UsedOperation();
2740 target
.FillRect(gfx::Rect(aX
, aY
, aW
, aH
),
2741 CanvasGeneralPattern().ForStyle(this, Style::FILL
, mTarget
),
2742 DrawOptions(CurrentState().globalAlpha
, op
, antialiasMode
));
2744 RedrawUser(gfxRect(aX
, aY
, aW
, aH
));
2747 void CanvasRenderingContext2D::StrokeRect(double aX
, double aY
, double aW
,
2753 if (!ValidateRect(aX
, aY
, aW
, aH
, true)) {
2758 if (!IsTargetValid()) {
2762 const bool needBounds
= NeedToCalculateBounds();
2763 if (!IsTargetValid()) {
2769 const ContextState
& state
= CurrentState();
2770 bounds
= gfx::Rect(aX
- state
.lineWidth
/ 2.0f
, aY
- state
.lineWidth
/ 2.0f
,
2771 aW
+ state
.lineWidth
, aH
+ state
.lineWidth
);
2772 bounds
= mTarget
->GetTransform().TransformBounds(bounds
);
2775 if (!IsTargetValid()) {
2780 CapStyle cap
= CapStyle::BUTT
;
2781 if (CurrentState().lineJoin
== JoinStyle::ROUND
) {
2782 cap
= CapStyle::ROUND
;
2784 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2785 auto op
= target
.UsedOperation();
2790 const ContextState
& state
= CurrentState();
2792 Point(aX
, aY
), Point(aX
+ aW
, aY
),
2793 CanvasGeneralPattern().ForStyle(this, Style::STROKE
, mTarget
),
2794 StrokeOptions(state
.lineWidth
, state
.lineJoin
, cap
, state
.miterLimit
,
2795 state
.dash
.Length(), state
.dash
.Elements(),
2797 DrawOptions(state
.globalAlpha
, op
));
2802 CapStyle cap
= CapStyle::BUTT
;
2803 if (CurrentState().lineJoin
== JoinStyle::ROUND
) {
2804 cap
= CapStyle::ROUND
;
2806 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2807 auto op
= target
.UsedOperation();
2812 const ContextState
& state
= CurrentState();
2814 Point(aX
, aY
), Point(aX
, aY
+ aH
),
2815 CanvasGeneralPattern().ForStyle(this, Style::STROKE
, mTarget
),
2816 StrokeOptions(state
.lineWidth
, state
.lineJoin
, cap
, state
.miterLimit
,
2817 state
.dash
.Length(), state
.dash
.Elements(),
2819 DrawOptions(state
.globalAlpha
, op
));
2823 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2824 auto op
= target
.UsedOperation();
2829 const ContextState
& state
= CurrentState();
2831 gfx::Rect(aX
, aY
, aW
, aH
),
2832 CanvasGeneralPattern().ForStyle(this, Style::STROKE
, mTarget
),
2833 StrokeOptions(state
.lineWidth
, state
.lineJoin
, state
.lineCap
,
2834 state
.miterLimit
, state
.dash
.Length(),
2835 state
.dash
.Elements(), state
.dashOffset
),
2836 DrawOptions(state
.globalAlpha
, op
));
2845 void CanvasRenderingContext2D::BeginPath() {
2847 mPathBuilder
= nullptr;
2848 mDSPathBuilder
= nullptr;
2849 mPathTransformWillUpdate
= false;
2852 void CanvasRenderingContext2D::Fill(const CanvasWindingRule
& aWinding
) {
2853 EnsureUserSpacePath(aWinding
);
2859 const bool needBounds
= NeedToCalculateBounds();
2860 if (!IsTargetValid()) {
2865 bounds
= mPath
->GetBounds(mTarget
->GetTransform());
2868 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2873 auto op
= target
.UsedOperation();
2874 if (!IsTargetValid() || !target
) {
2878 CanvasGeneralPattern().ForStyle(this, Style::FILL
, mTarget
),
2879 DrawOptions(CurrentState().globalAlpha
, op
));
2883 void CanvasRenderingContext2D::Fill(const CanvasPath
& aPath
,
2884 const CanvasWindingRule
& aWinding
) {
2886 if (!IsTargetValid()) {
2890 RefPtr
<gfx::Path
> gfxpath
= aPath
.GetPath(aWinding
, mTarget
);
2895 const bool needBounds
= NeedToCalculateBounds();
2896 if (!IsTargetValid()) {
2901 bounds
= gfxpath
->GetBounds(mTarget
->GetTransform());
2904 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2909 auto op
= target
.UsedOperation();
2910 if (!IsTargetValid() || !target
) {
2913 target
.Fill(gfxpath
,
2914 CanvasGeneralPattern().ForStyle(this, Style::FILL
, mTarget
),
2915 DrawOptions(CurrentState().globalAlpha
, op
));
2919 void CanvasRenderingContext2D::Stroke() {
2920 EnsureUserSpacePath();
2926 const ContextState
* state
= &CurrentState();
2927 StrokeOptions
strokeOptions(state
->lineWidth
, state
->lineJoin
, state
->lineCap
,
2928 state
->miterLimit
, state
->dash
.Length(),
2929 state
->dash
.Elements(), state
->dashOffset
);
2932 const bool needBounds
= NeedToCalculateBounds();
2933 if (!IsTargetValid()) {
2938 bounds
= mPath
->GetStrokedBounds(strokeOptions
, mTarget
->GetTransform());
2941 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2946 auto op
= target
.UsedOperation();
2947 if (!IsTargetValid() || !target
) {
2950 target
.Stroke(mPath
,
2951 CanvasGeneralPattern().ForStyle(this, Style::STROKE
, mTarget
),
2952 strokeOptions
, DrawOptions(CurrentState().globalAlpha
, op
));
2956 void CanvasRenderingContext2D::Stroke(const CanvasPath
& aPath
) {
2958 if (!IsTargetValid()) {
2962 RefPtr
<gfx::Path
> gfxpath
=
2963 aPath
.GetPath(CanvasWindingRule::Nonzero
, mTarget
);
2969 const ContextState
* state
= &CurrentState();
2970 StrokeOptions
strokeOptions(state
->lineWidth
, state
->lineJoin
, state
->lineCap
,
2971 state
->miterLimit
, state
->dash
.Length(),
2972 state
->dash
.Elements(), state
->dashOffset
);
2975 const bool needBounds
= NeedToCalculateBounds();
2976 if (!IsTargetValid()) {
2981 bounds
= gfxpath
->GetStrokedBounds(strokeOptions
, mTarget
->GetTransform());
2984 AdjustedTarget
target(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
2989 auto op
= target
.UsedOperation();
2990 if (!IsTargetValid() || !target
) {
2993 target
.Stroke(gfxpath
,
2994 CanvasGeneralPattern().ForStyle(this, Style::STROKE
, mTarget
),
2995 strokeOptions
, DrawOptions(CurrentState().globalAlpha
, op
));
2999 void CanvasRenderingContext2D::DrawFocusIfNeeded(
3000 mozilla::dom::Element
& aElement
, ErrorResult
& aRv
) {
3001 EnsureUserSpacePath();
3006 if (DrawCustomFocusRing(aElement
)) {
3007 AutoSaveRestore
asr(this);
3009 // set state to conforming focus state
3010 ContextState
* state
= &CurrentState();
3011 state
->globalAlpha
= 1.0;
3012 state
->shadowBlur
= 0;
3013 state
->shadowOffset
.x
= 0;
3014 state
->shadowOffset
.y
= 0;
3015 state
->op
= mozilla::gfx::CompositionOp::OP_OVER
;
3017 state
->lineCap
= CapStyle::BUTT
;
3018 state
->lineJoin
= mozilla::gfx::JoinStyle::MITER_OR_BEVEL
;
3019 state
->lineWidth
= 1;
3020 state
->dash
.Clear();
3022 // color and style of the rings is the same as for image maps
3023 // set the background focus color
3024 state
->SetColorStyle(Style::STROKE
, NS_RGBA(255, 255, 255, 255));
3027 // draw the focus ring
3033 // set dashing for foreground
3034 nsTArray
<mozilla::gfx::Float
>& dash
= CurrentState().dash
;
3035 for (uint32_t i
= 0; i
< 2; ++i
) {
3036 if (!dash
.AppendElement(1, fallible
)) {
3037 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
3042 // set the foreground focus color
3043 CurrentState().SetColorStyle(Style::STROKE
, NS_RGBA(0, 0, 0, 255));
3044 // draw the focus ring
3052 bool CanvasRenderingContext2D::DrawCustomFocusRing(Element
& aElement
) {
3053 if (!aElement
.State().HasState(ElementState::FOCUSRING
)) {
3057 HTMLCanvasElement
* canvas
= GetCanvas();
3058 if (!canvas
|| !aElement
.IsInclusiveDescendantOf(canvas
)) {
3062 EnsureUserSpacePath();
3066 void CanvasRenderingContext2D::Clip(const CanvasWindingRule
& aWinding
) {
3067 EnsureUserSpacePath(aWinding
);
3073 mTarget
->PushClip(mPath
);
3074 CurrentState().clipsAndTransforms
.AppendElement(ClipState(mPath
));
3077 void CanvasRenderingContext2D::Clip(const CanvasPath
& aPath
,
3078 const CanvasWindingRule
& aWinding
) {
3080 if (!IsTargetValid()) {
3084 RefPtr
<gfx::Path
> gfxpath
= aPath
.GetPath(aWinding
, mTarget
);
3090 mTarget
->PushClip(gfxpath
);
3091 CurrentState().clipsAndTransforms
.AppendElement(ClipState(gfxpath
));
3094 void CanvasRenderingContext2D::ArcTo(double aX1
, double aY1
, double aX2
,
3095 double aY2
, double aRadius
,
3096 ErrorResult
& aError
) {
3098 return aError
.ThrowIndexSizeError("Negative radius");
3101 EnsureWritablePath();
3103 // Current point in user space!
3106 p0
= mPathBuilder
->CurrentPoint();
3108 Matrix invTransform
= mTarget
->GetTransform();
3109 if (!invTransform
.Invert()) {
3113 p0
= invTransform
.TransformPoint(mDSPathBuilder
->CurrentPoint());
3119 // Execute these calculations in double precision to avoid cumulative
3121 double dir
, a2
, b2
, c2
, cosx
, sinx
, d
, anx
, any
, bnx
, bny
, x3
, y3
, x4
, y4
, cx
,
3125 if (p0
== p1
|| p1
== p2
|| aRadius
== 0) {
3130 // Check for colinearity
3131 dir
= (p2
.x
.value
- p1
.x
.value
) * (p0
.y
.value
- p1
.y
.value
) +
3132 (p2
.y
.value
- p1
.y
.value
) * (p1
.x
.value
- p0
.x
.value
);
3138 // XXX - Math for this code was already available from the non-azure code
3139 // and would be well tested. Perhaps converting to bezier directly might
3140 // be more efficient longer run.
3141 a2
= (p0
.x
- aX1
) * (p0
.x
- aX1
) + (p0
.y
- aY1
) * (p0
.y
- aY1
);
3142 b2
= (aX1
- aX2
) * (aX1
- aX2
) + (aY1
- aY2
) * (aY1
- aY2
);
3143 c2
= (p0
.x
- aX2
) * (p0
.x
- aX2
) + (p0
.y
- aY2
) * (p0
.y
- aY2
);
3144 cosx
= (a2
+ b2
- c2
) / (2 * sqrt(a2
* b2
));
3146 sinx
= sqrt(1 - cosx
* cosx
);
3147 d
= aRadius
/ ((1 - cosx
) / sinx
);
3149 anx
= (aX1
- p0
.x
) / sqrt(a2
);
3150 any
= (aY1
- p0
.y
) / sqrt(a2
);
3151 bnx
= (aX1
- aX2
) / sqrt(b2
);
3152 bny
= (aY1
- aY2
) / sqrt(b2
);
3157 anticlockwise
= (dir
< 0);
3158 cx
= x3
+ any
* aRadius
* (anticlockwise
? 1 : -1);
3159 cy
= y3
- anx
* aRadius
* (anticlockwise
? 1 : -1);
3160 angle0
= atan2((y3
- cy
), (x3
- cx
));
3161 angle1
= atan2((y4
- cy
), (x4
- cx
));
3165 Arc(cx
, cy
, aRadius
, angle0
, angle1
, anticlockwise
, aError
);
3168 void CanvasRenderingContext2D::Arc(double aX
, double aY
, double aR
,
3169 double aStartAngle
, double aEndAngle
,
3170 bool aAnticlockwise
, ErrorResult
& aError
) {
3172 return aError
.ThrowIndexSizeError("Negative radius");
3175 EnsureWritablePath();
3177 ArcToBezier(this, Point(aX
, aY
), Size(aR
, aR
), aStartAngle
, aEndAngle
,
3181 void CanvasRenderingContext2D::Rect(double aX
, double aY
, double aW
,
3183 EnsureWritablePath();
3186 mPathBuilder
->MoveTo(Point(aX
, aY
));
3187 mPathBuilder
->LineTo(Point(aX
+ aW
, aY
));
3188 mPathBuilder
->LineTo(Point(aX
+ aW
, aY
+ aH
));
3189 mPathBuilder
->LineTo(Point(aX
, aY
+ aH
));
3190 mPathBuilder
->Close();
3192 mDSPathBuilder
->MoveTo(
3193 mTarget
->GetTransform().TransformPoint(Point(aX
, aY
)));
3194 mDSPathBuilder
->LineTo(
3195 mTarget
->GetTransform().TransformPoint(Point(aX
+ aW
, aY
)));
3196 mDSPathBuilder
->LineTo(
3197 mTarget
->GetTransform().TransformPoint(Point(aX
+ aW
, aY
+ aH
)));
3198 mDSPathBuilder
->LineTo(
3199 mTarget
->GetTransform().TransformPoint(Point(aX
, aY
+ aH
)));
3200 mDSPathBuilder
->Close();
3204 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
3205 static void RoundRectImpl(
3206 PathBuilder
* aPathBuilder
, const Maybe
<Matrix
>& aTransform
, double aX
,
3207 double aY
, double aW
, double aH
,
3208 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence
&
3210 ErrorResult
& aError
) {
3211 // Step 1. If any of x, y, w, or h are infinite or NaN, then return.
3212 if (!IsFinite(aX
) || !IsFinite(aY
) || !IsFinite(aW
) || !IsFinite(aH
)) {
3216 nsTArray
<OwningUnrestrictedDoubleOrDOMPointInit
> radii
;
3217 // Step 2. If radii is an unrestricted double or DOMPointInit, then set radii
3219 if (aRadii
.IsUnrestrictedDouble()) {
3220 radii
.AppendElement()->SetAsUnrestrictedDouble() =
3221 aRadii
.GetAsUnrestrictedDouble();
3222 } else if (aRadii
.IsDOMPointInit()) {
3223 radii
.AppendElement()->SetAsDOMPointInit() = aRadii
.GetAsDOMPointInit();
3225 radii
= aRadii
.GetAsUnrestrictedDoubleOrDOMPointInitSequence();
3226 // Step 3. If radii is not a list of size one, two, three, or
3227 // four, then throw a RangeError.
3228 if (radii
.Length() < 1 || radii
.Length() > 4) {
3229 aError
.ThrowRangeError("Can have between 1 and 4 radii");
3234 // Step 4. Let normalizedRadii be an empty list.
3235 AutoTArray
<Size
, 4> normalizedRadii
;
3237 // Step 5. For each radius of radii:
3238 for (const auto& radius
: radii
) {
3239 // Step 5.1. If radius is a DOMPointInit:
3240 if (radius
.IsDOMPointInit()) {
3241 const DOMPointInit
& point
= radius
.GetAsDOMPointInit();
3242 // Step 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then
3244 if (!IsFinite(point
.mX
) || !IsFinite(point
.mY
)) {
3248 // Step 5.1.2. If radius["x"] or radius["y"] is negative, then
3249 // throw a RangeError.
3250 if (point
.mX
< 0 || point
.mY
< 0) {
3251 aError
.ThrowRangeError("Radius can not be negative");
3255 // Step 5.1.3. Otherwise, append radius to
3257 normalizedRadii
.AppendElement(
3258 Size(gfx::Float(point
.mX
), gfx::Float(point
.mY
)));
3262 // Step 5.2. If radius is a unrestricted double:
3263 double r
= radius
.GetAsUnrestrictedDouble();
3264 // Step 5.2.1. If radius is infinite or NaN, then return.
3269 // Step 5.2.2. If radius is negative, then throw a RangeError.
3271 aError
.ThrowRangeError("Radius can not be negative");
3275 // Step 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to
3277 normalizedRadii
.AppendElement(Size(gfx::Float(r
), gfx::Float(r
)));
3280 // Step 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null.
3281 Size upperLeft
, upperRight
, lowerRight
, lowerLeft
;
3283 if (normalizedRadii
.Length() == 4) {
3284 // Step 7. If normalizedRadii's size is 4, then set upperLeft to
3285 // normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight
3286 // to normalizedRadii[2], and set lowerLeft to normalizedRadii[3].
3287 upperLeft
= normalizedRadii
[0];
3288 upperRight
= normalizedRadii
[1];
3289 lowerRight
= normalizedRadii
[2];
3290 lowerLeft
= normalizedRadii
[3];
3291 } else if (normalizedRadii
.Length() == 3) {
3292 // Step 8. If normalizedRadii's size is 3, then set upperLeft to
3293 // normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1],
3294 // and set lowerRight to normalizedRadii[2].
3295 upperLeft
= normalizedRadii
[0];
3296 upperRight
= normalizedRadii
[1];
3297 lowerRight
= normalizedRadii
[2];
3298 lowerLeft
= normalizedRadii
[1];
3299 } else if (normalizedRadii
.Length() == 2) {
3300 // Step 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight
3301 // to normalizedRadii[0] and set upperRight and lowerLeft to
3302 // normalizedRadii[1].
3303 upperLeft
= normalizedRadii
[0];
3304 upperRight
= normalizedRadii
[1];
3305 lowerRight
= normalizedRadii
[0];
3306 lowerLeft
= normalizedRadii
[1];
3308 // Step 10. If normalizedRadii's size is 1, then set upperLeft, upperRight,
3309 // lowerRight, and lowerLeft to normalizedRadii[0].
3310 MOZ_ASSERT(normalizedRadii
.Length() == 1);
3311 upperLeft
= normalizedRadii
[0];
3312 upperRight
= normalizedRadii
[0];
3313 lowerRight
= normalizedRadii
[0];
3314 lowerLeft
= normalizedRadii
[0];
3317 // This is not as specified but copied from Chrome.
3318 // XXX Maybe if we implemented Step 12 (the path algorithm) per
3319 // spec this wouldn't be needed?
3320 Float
x(aX
), y(aY
), w(aW
), h(aH
);
3321 bool clockwise
= true;
3327 std::swap(upperLeft
, upperRight
);
3328 std::swap(lowerLeft
, lowerRight
);
3333 clockwise
= !clockwise
;
3336 std::swap(upperLeft
, lowerLeft
);
3337 std::swap(upperRight
, lowerRight
);
3340 // Step 11. Corner curves must not overlap. Scale all radii to prevent this:
3341 // Step 11.1. Let top be upperLeft["x"] + upperRight["x"].
3342 Float top
= upperLeft
.width
+ upperRight
.width
;
3343 // Step 11.2. Let right be upperRight["y"] + lowerRight["y"].
3344 Float right
= upperRight
.height
+ lowerRight
.height
;
3345 // Step 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"].
3346 Float bottom
= lowerRight
.width
+ lowerLeft
.width
;
3347 // Step 11.4. Let left be upperLeft["y"] + lowerLeft["y"].
3348 Float left
= upperLeft
.height
+ lowerLeft
.height
;
3349 // Step 11.5. Let scale be the minimum value of the ratios w / top, h / right,
3350 // w / bottom, h / left.
3351 Float scale
= std::min({w
/ top
, h
/ right
, w
/ bottom
, h
/ left
});
3352 // Step 11.6. If scale is less than 1, then set the x and y members of
3353 // upperLeft, upperRight, lowerLeft, and lowerRight to their current values
3354 // multiplied by scale.
3356 upperLeft
= upperLeft
* scale
;
3357 upperRight
= upperRight
* scale
;
3358 lowerLeft
= lowerLeft
* scale
;
3359 lowerRight
= lowerRight
* scale
;
3362 // Step 12. Create a new subpath:
3363 // Step 13. Mark the subpath as closed.
3364 // Note: Implemented by AppendRoundedRectToPath, which is shared with CSS
3366 gfx::Rect rect
{x
, y
, w
, h
};
3367 RectCornerRadii
cornerRadii(upperLeft
, upperRight
, lowerRight
, lowerLeft
);
3368 AppendRoundedRectToPath(aPathBuilder
, rect
, cornerRadii
, clockwise
,
3371 // Step 14. Create a new subpath with the point (x, y) as the only point in
3373 // XXX We don't seem to be doing this for ::Rect either?
3376 void CanvasRenderingContext2D::RoundRect(
3377 double aX
, double aY
, double aW
, double aH
,
3378 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence
&
3380 ErrorResult
& aError
) {
3381 EnsureWritablePath();
3383 PathBuilder
* builder
= mPathBuilder
;
3384 Maybe
<Matrix
> transform
= Nothing();
3386 builder
= mDSPathBuilder
;
3387 transform
= Some(mTarget
->GetTransform());
3390 RoundRectImpl(builder
, transform
, aX
, aY
, aW
, aH
, aRadii
, aError
);
3393 void CanvasRenderingContext2D::Ellipse(double aX
, double aY
, double aRadiusX
,
3394 double aRadiusY
, double aRotation
,
3395 double aStartAngle
, double aEndAngle
,
3396 bool aAnticlockwise
,
3397 ErrorResult
& aError
) {
3398 if (aRadiusX
< 0.0 || aRadiusY
< 0.0) {
3399 return aError
.ThrowIndexSizeError("Negative radius");
3402 EnsureWritablePath();
3404 ArcToBezier(this, Point(aX
, aY
), Size(aRadiusX
, aRadiusY
), aStartAngle
,
3405 aEndAngle
, aAnticlockwise
, aRotation
);
3408 void CanvasRenderingContext2D::EnsureWritablePath() {
3410 // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
3411 // go ahead and create a path anyway since callers depend on that.
3413 if (mDSPathBuilder
) {
3417 FillRule fillRule
= CurrentState().fillRule
;
3420 if (mPathTransformWillUpdate
) {
3421 mPath
= mPathBuilder
->Finish();
3422 mDSPathBuilder
= mPath
->TransformedCopyToBuilder(mPathToDS
, fillRule
);
3424 mPathBuilder
= nullptr;
3425 mPathTransformWillUpdate
= false;
3432 !mPathTransformWillUpdate
,
3433 "mPathTransformWillUpdate should be false, if all paths are null");
3434 mPathBuilder
= mTarget
->CreatePathBuilder(fillRule
);
3435 } else if (!mPathTransformWillUpdate
) {
3436 mPathBuilder
= mPath
->CopyToBuilder(fillRule
);
3438 mDSPathBuilder
= mPath
->TransformedCopyToBuilder(mPathToDS
, fillRule
);
3439 mPathTransformWillUpdate
= false;
3444 void CanvasRenderingContext2D::EnsureUserSpacePath(
3445 const CanvasWindingRule
& aWinding
) {
3446 FillRule fillRule
= CurrentState().fillRule
;
3447 if (aWinding
== CanvasWindingRule::Evenodd
)
3448 fillRule
= FillRule::FILL_EVEN_ODD
;
3451 if (!IsTargetValid()) {
3455 if (!mPath
&& !mPathBuilder
&& !mDSPathBuilder
) {
3456 mPathBuilder
= mTarget
->CreatePathBuilder(fillRule
);
3460 mPath
= mPathBuilder
->Finish();
3461 mPathBuilder
= nullptr;
3464 if (mPath
&& mPathTransformWillUpdate
) {
3465 mDSPathBuilder
= mPath
->TransformedCopyToBuilder(mPathToDS
, fillRule
);
3467 mPathTransformWillUpdate
= false;
3470 if (mDSPathBuilder
) {
3471 RefPtr
<Path
> dsPath
;
3472 dsPath
= mDSPathBuilder
->Finish();
3473 mDSPathBuilder
= nullptr;
3475 Matrix inverse
= mTarget
->GetTransform();
3476 if (!inverse
.Invert()) {
3477 NS_WARNING("Could not invert transform");
3481 mPathBuilder
= dsPath
->TransformedCopyToBuilder(inverse
, fillRule
);
3482 mPath
= mPathBuilder
->Finish();
3483 mPathBuilder
= nullptr;
3486 if (mPath
&& mPath
->GetFillRule() != fillRule
) {
3487 mPathBuilder
= mPath
->CopyToBuilder(fillRule
);
3488 mPath
= mPathBuilder
->Finish();
3489 mPathBuilder
= nullptr;
3492 NS_ASSERTION(mPath
, "mPath should exist");
3495 void CanvasRenderingContext2D::TransformWillUpdate() {
3497 if (!IsTargetValid()) {
3501 // Store the matrix that would transform the current path to device
3503 if (mPath
|| mPathBuilder
) {
3504 if (!mPathTransformWillUpdate
) {
3505 // If the transform has already been updated, but a device space builder
3506 // has not been created yet mPathToDS contains the right transform to
3507 // transform the current mPath into device space.
3508 // We should leave it alone.
3509 mPathToDS
= mTarget
->GetTransform();
3511 mPathTransformWillUpdate
= true;
3519 void CanvasRenderingContext2D::SetFont(const nsACString
& aFont
,
3520 ErrorResult
& aError
) {
3521 SetFontInternal(aFont
, aError
);
3524 bool CanvasRenderingContext2D::SetFontInternal(const nsACString
& aFont
,
3525 ErrorResult
& aError
) {
3526 RefPtr
<PresShell
> presShell
= GetPresShell();
3528 return SetFontInternalDisconnected(aFont
, aError
);
3531 nsPresContext
* c
= presShell
->GetPresContext();
3532 FontStyleCacheKey key
{aFont
, c
->RestyleManager()->GetRestyleGeneration()};
3533 auto entry
= mFontStyleCache
.Lookup(key
);
3535 FontStyleData newData
;
3537 newData
.mStyle
= GetFontStyleForServo(mCanvasElement
, aFont
, presShell
,
3538 newData
.mUsedFont
, aError
);
3542 const auto& data
= entry
.Data();
3547 const nsStyleFont
* fontStyle
= data
.mStyle
->StyleFont();
3549 // Purposely ignore the font size that respects the user's minimum
3550 // font preference (fontStyle->mFont.size) in favor of the computed
3551 // size (fontStyle->mSize). See
3552 // https://bugzilla.mozilla.org/show_bug.cgi?id=698652.
3553 // FIXME: Nobody initializes mAllowZoom for servo?
3554 // MOZ_ASSERT(!fontStyle->mAllowZoom,
3555 // "expected text zoom to be disabled on this nsStyleFont");
3556 nsFont
resizedFont(fontStyle
->mFont
);
3557 // Create a font group working in units of CSS pixels instead of the usual
3558 // device pixels, to avoid being affected by page zoom. nsFontMetrics will
3559 // convert nsFont size in app units to device pixels for the font group, so
3560 // here we first apply to the size the equivalent of a conversion from device
3561 // pixels to CSS pixels, to adjust for the difference in expectations from
3562 // other nsFontMetrics clients.
3564 fontStyle
->mSize
.ScaledBy(1.0f
/ c
->CSSToDevPixelScale().scale
);
3566 // Our FontKerning constants (see the enum definition) are the same as the
3567 // NS_FONT_KERNING_* values so we can simply assign here.
3568 resizedFont
.kerning
= uint8_t(CurrentState().fontKerning
);
3570 c
->Document()->FlushUserFontSet();
3572 nsFontMetrics::Params params
;
3573 params
.language
= fontStyle
->mLanguage
;
3574 params
.explicitLanguage
= fontStyle
->mExplicitLanguage
;
3575 params
.userFontSet
= c
->GetUserFontSet();
3576 params
.textPerf
= c
->GetTextPerfMetrics();
3577 RefPtr
<nsFontMetrics
> metrics
= c
->GetMetricsFor(resizedFont
, params
);
3579 gfxFontGroup
* newFontGroup
= metrics
->GetThebesFontGroup();
3580 CurrentState().fontGroup
= newFontGroup
;
3581 NS_ASSERTION(CurrentState().fontGroup
, "Could not get font group");
3582 CurrentState().font
= data
.mUsedFont
;
3583 CurrentState().fontFont
= fontStyle
->mFont
;
3584 CurrentState().fontFont
.size
= fontStyle
->mSize
;
3585 CurrentState().fontLanguage
= fontStyle
->mLanguage
;
3586 CurrentState().fontExplicitLanguage
= fontStyle
->mExplicitLanguage
;
3591 static nsAutoCString
FamilyListToString(
3592 const StyleFontFamilyList
& aFamilyList
) {
3593 return StringJoin(","_ns
, aFamilyList
.list
.AsSpan(),
3594 [](nsACString
& dst
, const StyleSingleFontFamily
& name
) {
3595 name
.AppendToString(dst
);
3599 static void SerializeFontForCanvas(const StyleFontFamilyList
& aList
,
3600 const gfxFontStyle
& aStyle
,
3601 nsACString
& aUsedFont
) {
3602 // Re-serialize the font shorthand as required by the canvas spec.
3603 aUsedFont
.Truncate();
3605 if (!aStyle
.style
.IsNormal()) {
3606 aStyle
.style
.ToString(aUsedFont
);
3607 aUsedFont
.Append(" ");
3610 // font-weight is serialized as a number
3611 if (!aStyle
.weight
.IsNormal()) {
3612 aUsedFont
.AppendFloat(aStyle
.weight
.ToFloat());
3613 aUsedFont
.Append(" ");
3616 // font-stretch is serialized using CSS Fonts 3 keywords, not percentages.
3617 if (!aStyle
.stretch
.IsNormal() &&
3618 Servo_FontStretch_SerializeKeyword(&aStyle
.stretch
, &aUsedFont
)) {
3619 aUsedFont
.Append(" ");
3622 // Serialize the computed (not specified) size, and the family name(s).
3623 aUsedFont
.AppendFloat(aStyle
.size
);
3624 aUsedFont
.Append("px ");
3625 aUsedFont
.Append(FamilyListToString(aList
));
3628 bool CanvasRenderingContext2D::SetFontInternalDisconnected(
3629 const nsACString
& aFont
, ErrorResult
& aError
) {
3630 FontFaceSet
* fontFaceSet
= nullptr;
3631 if (mCanvasElement
) {
3632 fontFaceSet
= mCanvasElement
->OwnerDoc()->Fonts();
3634 nsIGlobalObject
* global
= GetParentObject();
3635 fontFaceSet
= global
? global
->Fonts() : nullptr;
3638 FontFaceSetImpl
* fontFaceSetImpl
=
3639 fontFaceSet
? fontFaceSet
->GetImpl() : nullptr;
3640 RefPtr
<URLExtraData
> urlExtraData
=
3641 fontFaceSetImpl
? fontFaceSetImpl
->GetURLExtraData() : nullptr;
3643 if (NS_WARN_IF(!urlExtraData
)) {
3644 // Provided we have a FontFaceSetImpl object, this should only happen on
3645 // worker threads, where we failed to initialize the worker before it was
3647 aError
.ThrowInvalidStateError("Missing URLExtraData");
3651 if (fontFaceSetImpl
) {
3652 fontFaceSetImpl
->FlushUserFontSet();
3655 // In the OffscreenCanvas case we don't have the context necessary to call
3656 // GetFontStyleForServo(), as we do in the main-thread canvas context, so
3657 // instead we borrow ParseFontShorthandForMatching to parse the attribute.
3658 StyleComputedFontStyleDescriptor
style(
3659 StyleComputedFontStyleDescriptor::Normal());
3660 StyleFontFamilyList list
;
3661 gfxFontStyle fontStyle
;
3663 if (!ServoCSSParser::ParseFontShorthandForMatching(
3664 aFont
, urlExtraData
, list
, fontStyle
.style
, fontStyle
.stretch
,
3665 fontStyle
.weight
, &size
)) {
3669 fontStyle
.size
= size
;
3671 // Set the kerning feature, if required by the fontKerning attribute.
3672 gfxFontFeature setting
{TRUETYPE_TAG('k', 'e', 'r', 'n'), 0};
3673 switch (CurrentState().fontKerning
) {
3674 case FontKerning::NONE
:
3676 fontStyle
.featureSettings
.AppendElement(setting
);
3678 case FontKerning::NORMAL
:
3680 fontStyle
.featureSettings
.AppendElement(setting
);
3683 // auto case implies use user agent default
3687 // If we have a canvas element, get its lang (if known).
3688 RefPtr
<nsAtom
> language
;
3689 bool explicitLanguage
= false;
3690 if (mCanvasElement
) {
3691 language
= mCanvasElement
->FragmentOrElement::GetLang();
3693 explicitLanguage
= true;
3695 language
= mCanvasElement
->OwnerDoc()->GetLanguageForStyle();
3698 // Pass the OS default language, to behave similarly to HTML or canvas-
3699 // element content with no language tag.
3700 language
= nsLanguageAtomService::GetService()->GetLocaleLanguage();
3703 // TODO: Cache fontGroups in the Worker (use an nsFontCache?)
3704 gfxFontGroup
* fontGroup
=
3705 new gfxFontGroup(nullptr, // aPresContext
3706 list
, // aFontFamilyList
3707 &fontStyle
, // aStyle
3708 language
, // aLanguage
3709 explicitLanguage
, // aExplicitLanguage
3710 nullptr, // aTextPerf
3711 fontFaceSetImpl
, // aUserFontSet
3712 1.0, // aDevToCssSize
3713 StyleFontVariantEmoji::Normal
);
3714 CurrentState().fontGroup
= fontGroup
;
3715 SerializeFontForCanvas(list
, fontStyle
, CurrentState().font
);
3716 CurrentState().fontFont
= nsFont(StyleFontFamily
{list
, false, false},
3717 StyleCSSPixelLength::FromPixels(size
));
3718 CurrentState().fontLanguage
= nullptr;
3719 CurrentState().fontExplicitLanguage
= false;
3723 void CanvasRenderingContext2D::SetTextAlign(const nsAString
& aTextAlign
) {
3724 if (aTextAlign
.EqualsLiteral("start"))
3725 CurrentState().textAlign
= TextAlign::START
;
3726 else if (aTextAlign
.EqualsLiteral("end"))
3727 CurrentState().textAlign
= TextAlign::END
;
3728 else if (aTextAlign
.EqualsLiteral("left"))
3729 CurrentState().textAlign
= TextAlign::LEFT
;
3730 else if (aTextAlign
.EqualsLiteral("right"))
3731 CurrentState().textAlign
= TextAlign::RIGHT
;
3732 else if (aTextAlign
.EqualsLiteral("center"))
3733 CurrentState().textAlign
= TextAlign::CENTER
;
3736 void CanvasRenderingContext2D::GetTextAlign(nsAString
& aTextAlign
) {
3737 switch (CurrentState().textAlign
) {
3738 case TextAlign::START
:
3739 aTextAlign
.AssignLiteral("start");
3741 case TextAlign::END
:
3742 aTextAlign
.AssignLiteral("end");
3744 case TextAlign::LEFT
:
3745 aTextAlign
.AssignLiteral("left");
3747 case TextAlign::RIGHT
:
3748 aTextAlign
.AssignLiteral("right");
3750 case TextAlign::CENTER
:
3751 aTextAlign
.AssignLiteral("center");
3756 void CanvasRenderingContext2D::SetTextBaseline(const nsAString
& aTextBaseline
) {
3757 if (aTextBaseline
.EqualsLiteral("top"))
3758 CurrentState().textBaseline
= TextBaseline::TOP
;
3759 else if (aTextBaseline
.EqualsLiteral("hanging"))
3760 CurrentState().textBaseline
= TextBaseline::HANGING
;
3761 else if (aTextBaseline
.EqualsLiteral("middle"))
3762 CurrentState().textBaseline
= TextBaseline::MIDDLE
;
3763 else if (aTextBaseline
.EqualsLiteral("alphabetic"))
3764 CurrentState().textBaseline
= TextBaseline::ALPHABETIC
;
3765 else if (aTextBaseline
.EqualsLiteral("ideographic"))
3766 CurrentState().textBaseline
= TextBaseline::IDEOGRAPHIC
;
3767 else if (aTextBaseline
.EqualsLiteral("bottom"))
3768 CurrentState().textBaseline
= TextBaseline::BOTTOM
;
3771 void CanvasRenderingContext2D::GetTextBaseline(nsAString
& aTextBaseline
) {
3772 switch (CurrentState().textBaseline
) {
3773 case TextBaseline::TOP
:
3774 aTextBaseline
.AssignLiteral("top");
3776 case TextBaseline::HANGING
:
3777 aTextBaseline
.AssignLiteral("hanging");
3779 case TextBaseline::MIDDLE
:
3780 aTextBaseline
.AssignLiteral("middle");
3782 case TextBaseline::ALPHABETIC
:
3783 aTextBaseline
.AssignLiteral("alphabetic");
3785 case TextBaseline::IDEOGRAPHIC
:
3786 aTextBaseline
.AssignLiteral("ideographic");
3788 case TextBaseline::BOTTOM
:
3789 aTextBaseline
.AssignLiteral("bottom");
3794 void CanvasRenderingContext2D::SetDirection(const nsAString
& aDirection
) {
3795 if (aDirection
.EqualsLiteral("ltr")) {
3796 CurrentState().textDirection
= TextDirection::LTR
;
3797 } else if (aDirection
.EqualsLiteral("rtl")) {
3798 CurrentState().textDirection
= TextDirection::RTL
;
3799 } else if (aDirection
.EqualsLiteral("inherit")) {
3800 CurrentState().textDirection
= TextDirection::INHERIT
;
3804 void CanvasRenderingContext2D::GetDirection(nsAString
& aDirection
) {
3805 switch (CurrentState().textDirection
) {
3806 case TextDirection::LTR
:
3807 aDirection
.AssignLiteral("ltr");
3809 case TextDirection::RTL
:
3810 aDirection
.AssignLiteral("rtl");
3812 case TextDirection::INHERIT
:
3813 aDirection
.AssignLiteral("inherit");
3818 void CanvasRenderingContext2D::SetFontKerning(const nsAString
& aFontKerning
) {
3819 auto oldValue
= CurrentState().fontKerning
;
3820 if (aFontKerning
.EqualsLiteral("auto")) {
3821 CurrentState().fontKerning
= FontKerning::AUTO
;
3822 } else if (aFontKerning
.EqualsLiteral("normal")) {
3823 CurrentState().fontKerning
= FontKerning::NORMAL
;
3824 } else if (aFontKerning
.EqualsLiteral("none")) {
3825 CurrentState().fontKerning
= FontKerning::NONE
;
3827 if (CurrentState().fontKerning
!= oldValue
) {
3828 CurrentState().fontGroup
= nullptr;
3832 void CanvasRenderingContext2D::GetFontKerning(nsAString
& aFontKerning
) {
3833 switch (CurrentState().fontKerning
) {
3834 case FontKerning::AUTO
:
3835 aFontKerning
.AssignLiteral("auto");
3837 case FontKerning::NORMAL
:
3838 aFontKerning
.AssignLiteral("normal");
3840 case FontKerning::NONE
:
3841 aFontKerning
.AssignLiteral("none");
3847 * Helper function that replaces the whitespace characters in a string
3848 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
3849 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
3850 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
3851 * @param str The string whose whitespace characters to replace.
3853 static inline void TextReplaceWhitespaceCharacters(nsAutoString
& aStr
) {
3854 aStr
.ReplaceChar(u
"\x09\x0A\x0B\x0C\x0D", char16_t(' '));
3857 void CanvasRenderingContext2D::FillText(const nsAString
& aText
, double aX
,
3859 const Optional
<double>& aMaxWidth
,
3860 ErrorResult
& aError
) {
3861 DebugOnly
<TextMetrics
*> metrics
= DrawOrMeasureText(
3862 aText
, aX
, aY
, aMaxWidth
, TextDrawOperation::FILL
, aError
);
3863 MOZ_ASSERT(!metrics
); // drawing operation never returns TextMetrics
3866 void CanvasRenderingContext2D::StrokeText(const nsAString
& aText
, double aX
,
3868 const Optional
<double>& aMaxWidth
,
3869 ErrorResult
& aError
) {
3870 DebugOnly
<TextMetrics
*> metrics
= DrawOrMeasureText(
3871 aText
, aX
, aY
, aMaxWidth
, TextDrawOperation::STROKE
, aError
);
3872 MOZ_ASSERT(!metrics
); // drawing operation never returns TextMetrics
3875 TextMetrics
* CanvasRenderingContext2D::MeasureText(const nsAString
& aRawText
,
3876 ErrorResult
& aError
) {
3877 Optional
<double> maxWidth
;
3878 return DrawOrMeasureText(aRawText
, 0, 0, maxWidth
, TextDrawOperation::MEASURE
,
3883 * Used for nsBidiPresUtils::ProcessText
3885 struct MOZ_STACK_CLASS CanvasBidiProcessor final
3886 : public nsBidiPresUtils::BidiProcessor
{
3887 using Style
= CanvasRenderingContext2D::Style
;
3889 CanvasBidiProcessor()
3890 : nsBidiPresUtils::BidiProcessor(),
3893 mAppUnitsPerDevPixel(0),
3894 mOp(CanvasRenderingContext2D::TextDrawOperation::FILL
),
3897 mDoMeasureBoundingBox(false),
3898 mIgnoreSetText(false) {
3899 if (StaticPrefs::gfx_missing_fonts_notify()) {
3900 mMissingFonts
= MakeUnique
<gfxMissingFontRecorder
>();
3904 ~CanvasBidiProcessor() {
3905 // notify front-end code if we encountered missing glyphs in any script
3906 if (mMissingFonts
) {
3907 mMissingFonts
->Flush();
3911 using ContextState
= CanvasRenderingContext2D::ContextState
;
3913 void SetText(const char16_t
* aText
, int32_t aLength
,
3914 intl::BidiDirection aDirection
) override
{
3915 if (mIgnoreSetText
) {
3916 // We've been told to ignore SetText because the processor is only ever
3917 // handling a single, fixed string.
3918 MOZ_ASSERT(mTextRun
&& mTextRun
->GetLength() == uint32_t(aLength
));
3922 auto* pfl
= gfxPlatformFontList::PlatformFontList();
3924 mFontgrp
->CheckForUpdatedPlatformList();
3925 mFontgrp
->UpdateUserFonts(); // ensure user font generation is current
3926 // adjust flags for current direction run
3927 gfx::ShapedTextFlags flags
= mTextRunFlags
;
3928 if (aDirection
== intl::BidiDirection::RTL
) {
3929 flags
|= gfx::ShapedTextFlags::TEXT_IS_RTL
;
3931 flags
&= ~gfx::ShapedTextFlags::TEXT_IS_RTL
;
3933 mTextRun
= mFontgrp
->MakeTextRun(
3934 aText
, aLength
, mDrawTarget
, mAppUnitsPerDevPixel
, flags
,
3935 nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts
,
3936 mMissingFonts
.get());
3940 nscoord
GetWidth() override
{
3941 gfxTextRun::Metrics textRunMetrics
= mTextRun
->MeasureText(
3942 mDoMeasureBoundingBox
? gfxFont::TIGHT_INK_EXTENTS
3943 : gfxFont::LOOSE_INK_EXTENTS
,
3946 // this only measures the height; the total width is gotten from the
3947 // the return value of ProcessText.
3948 if (mDoMeasureBoundingBox
) {
3949 textRunMetrics
.mBoundingBox
.Scale(1.0 / mAppUnitsPerDevPixel
);
3950 mBoundingBox
= mBoundingBox
.Union(textRunMetrics
.mBoundingBox
);
3953 return NSToCoordRound(textRunMetrics
.mAdvanceWidth
);
3956 already_AddRefed
<gfxPattern
> GetGradientFor(Style aStyle
) {
3957 RefPtr
<gfxPattern
> pattern
;
3958 CanvasGradient
* gradient
= mCtx
->CurrentState().gradientStyles
[aStyle
];
3959 CanvasGradient::Type type
= gradient
->GetType();
3962 case CanvasGradient::Type::CONIC
: {
3963 auto conic
= static_cast<CanvasConicGradient
*>(gradient
);
3964 pattern
= new gfxPattern(conic
->mCenter
.x
, conic
->mCenter
.y
,
3965 conic
->mAngle
, 0, 1);
3968 case CanvasGradient::Type::RADIAL
: {
3969 auto radial
= static_cast<CanvasRadialGradient
*>(gradient
);
3970 pattern
= new gfxPattern(radial
->mCenter1
.x
, radial
->mCenter1
.y
,
3971 radial
->mRadius1
, radial
->mCenter2
.x
,
3972 radial
->mCenter2
.y
, radial
->mRadius2
);
3975 case CanvasGradient::Type::LINEAR
: {
3976 auto linear
= static_cast<CanvasLinearGradient
*>(gradient
);
3977 pattern
= new gfxPattern(linear
->mBegin
.x
, linear
->mBegin
.y
,
3978 linear
->mEnd
.x
, linear
->mEnd
.y
);
3982 MOZ_ASSERT(false, "Should be linear, radial or conic gradient.");
3986 for (auto stop
: gradient
->mRawStops
) {
3987 pattern
->AddColorStop(stop
.offset
, stop
.color
);
3990 return pattern
.forget();
3993 gfx::ExtendMode
CvtCanvasRepeatToGfxRepeat(
3994 CanvasPattern::RepeatMode aRepeatMode
) {
3995 switch (aRepeatMode
) {
3996 case CanvasPattern::RepeatMode::REPEAT
:
3997 return gfx::ExtendMode::REPEAT
;
3998 case CanvasPattern::RepeatMode::REPEATX
:
3999 return gfx::ExtendMode::REPEAT_X
;
4000 case CanvasPattern::RepeatMode::REPEATY
:
4001 return gfx::ExtendMode::REPEAT_Y
;
4002 case CanvasPattern::RepeatMode::NOREPEAT
:
4003 return gfx::ExtendMode::CLAMP
;
4005 return gfx::ExtendMode::CLAMP
;
4009 already_AddRefed
<gfxPattern
> GetPatternFor(Style aStyle
) {
4010 const CanvasPattern
* pat
= mCtx
->CurrentState().patternStyles
[aStyle
];
4011 RefPtr
<gfxPattern
> pattern
= new gfxPattern(pat
->mSurface
, pat
->mTransform
);
4012 pattern
->SetExtend(CvtCanvasRepeatToGfxRepeat(pat
->mRepeat
));
4013 return pattern
.forget();
4016 void DrawText(nscoord aXOffset
, nscoord aWidth
) override
{
4017 gfx::Point point
= mPt
;
4018 bool rtl
= mTextRun
->IsRightToLeft();
4019 bool verticalRun
= mTextRun
->IsVertical();
4020 RefPtr
<gfxPattern
> pattern
;
4022 float& inlineCoord
= verticalRun
? point
.y
.value
: point
.x
.value
;
4023 inlineCoord
+= aXOffset
;
4025 // offset is given in terms of left side of string
4027 // Bug 581092 - don't use rounded pixel width to advance to
4028 // right-hand end of run, because this will cause different
4029 // glyph positioning for LTR vs RTL drawing of the same
4030 // glyph string on OS X and DWrite where textrun widths may
4031 // involve fractional pixels.
4032 gfxTextRun::Metrics textRunMetrics
= mTextRun
->MeasureText(
4033 mDoMeasureBoundingBox
? gfxFont::TIGHT_INK_EXTENTS
4034 : gfxFont::LOOSE_INK_EXTENTS
,
4036 inlineCoord
+= textRunMetrics
.mAdvanceWidth
;
4038 // point.x += width * mAppUnitsPerDevPixel;
4039 // TODO: restore this if/when we move to fractional coords
4040 // throughout the text layout process
4043 mCtx
->EnsureTarget();
4044 if (!mCtx
->IsTargetValid()) {
4048 // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
4050 StrokeOptions strokeOpts
;
4051 DrawOptions drawOpts
;
4052 Style style
= (mOp
== CanvasRenderingContext2D::TextDrawOperation::FILL
)
4055 const ContextState
& state
= mCtx
->CurrentState();
4058 if (mCtx
->NeedToCalculateBounds()) {
4059 bounds
= ToRect(mBoundingBox
);
4060 bounds
.MoveBy(mPt
/ mAppUnitsPerDevPixel
);
4061 if (style
== Style::STROKE
) {
4062 bounds
.Inflate(state
.lineWidth
/ 2.0);
4064 bounds
= mDrawTarget
->GetTransform().TransformBounds(bounds
);
4067 AdjustedTarget
target(mCtx
, bounds
.IsEmpty() ? nullptr : &bounds
, false);
4072 gfxContext
thebes(target
, /* aPreserveTransform */ true);
4073 gfxTextRun::DrawParams
params(&thebes
);
4075 params
.allowGDI
= false;
4077 if (state
.StyleIsColor(style
)) { // Color
4078 nscolor fontColor
= state
.colorStyles
[style
];
4079 if (style
== Style::FILL
) {
4080 params
.context
->SetColor(sRGBColor::FromABGR(fontColor
));
4082 params
.textStrokeColor
= fontColor
;
4085 if (state
.gradientStyles
[style
]) { // Gradient
4086 pattern
= GetGradientFor(style
);
4087 } else if (state
.patternStyles
[style
]) { // Pattern
4088 pattern
= GetPatternFor(style
);
4090 MOZ_ASSERT(false, "Should never reach here.");
4093 MOZ_ASSERT(pattern
, "No valid pattern.");
4095 if (style
== Style::FILL
) {
4096 params
.context
->SetPattern(pattern
);
4098 params
.textStrokePattern
= pattern
;
4102 drawOpts
.mAlpha
= state
.globalAlpha
;
4103 drawOpts
.mCompositionOp
= target
.UsedOperation();
4104 if (!mCtx
->IsTargetValid()) {
4108 params
.drawOpts
= &drawOpts
;
4110 if (style
== Style::STROKE
) {
4111 strokeOpts
.mLineWidth
= state
.lineWidth
;
4112 strokeOpts
.mLineJoin
= state
.lineJoin
;
4113 strokeOpts
.mLineCap
= state
.lineCap
;
4114 strokeOpts
.mMiterLimit
= state
.miterLimit
;
4115 strokeOpts
.mDashLength
= state
.dash
.Length();
4116 strokeOpts
.mDashPattern
=
4117 (strokeOpts
.mDashLength
> 0) ? state
.dash
.Elements() : 0;
4118 strokeOpts
.mDashOffset
= state
.dashOffset
;
4120 params
.drawMode
= DrawMode::GLYPH_STROKE
;
4121 params
.strokeOpts
= &strokeOpts
;
4124 mTextRun
->Draw(gfxTextRun::Range(mTextRun
.get()), point
, params
);
4128 RefPtr
<gfxTextRun
> mTextRun
;
4130 // pointer to a screen reference context used to measure text and such
4131 RefPtr
<DrawTarget
> mDrawTarget
;
4133 // Pointer to the draw target we should fill our text to
4134 CanvasRenderingContext2D
* mCtx
;
4136 // position of the left side of the string, alphabetic baseline
4140 gfxFontGroup
* mFontgrp
;
4142 // to record any unsupported characters found in the text,
4143 // and notify front-end if it is interested
4144 UniquePtr
<gfxMissingFontRecorder
> mMissingFonts
;
4146 // dev pixel conversion factor
4147 int32_t mAppUnitsPerDevPixel
;
4149 // operation (fill or stroke)
4150 CanvasRenderingContext2D::TextDrawOperation mOp
;
4152 // union of bounding boxes of all runs, needed for shadows
4153 gfxRect mBoundingBox
;
4155 // flags to use when creating textrun, based on CSS style
4156 gfx::ShapedTextFlags mTextRunFlags
;
4158 // Count of how many times SetText has been called on this processor.
4159 uint32_t mSetTextCount
;
4161 // true iff the bounding box should be measured
4162 bool mDoMeasureBoundingBox
;
4164 // true if future SetText calls should be ignored
4165 bool mIgnoreSetText
;
4168 TextMetrics
* CanvasRenderingContext2D::DrawOrMeasureText(
4169 const nsAString
& aRawText
, float aX
, float aY
,
4170 const Optional
<double>& aMaxWidth
, TextDrawOperation aOp
,
4171 ErrorResult
& aError
) {
4172 // Approximated baselines. In an ideal world, we'd read the baseline info
4173 // directly from the font (where available). Alas we currently lack
4174 // that functionality. These numbers are best guesses and should
4175 // suffice for now. Both are fractions of the em ascent/descent from the
4176 // alphabetic baseline.
4177 const double kHangingBaselineDefault
= 0.8; // fraction of ascent
4178 const double kIdeographicBaselineDefault
= 0.5; // fraction of descent
4180 gfxFontGroup
* currentFontStyle
= GetCurrentFontStyle();
4181 if (NS_WARN_IF(!currentFontStyle
)) {
4182 aError
= NS_ERROR_FAILURE
;
4186 RefPtr
<PresShell
> presShell
= GetPresShell();
4187 Document
* document
= presShell
? presShell
->GetDocument() : nullptr;
4189 // replace all the whitespace characters with U+0020 SPACE
4190 nsAutoString
textToDraw(aRawText
);
4191 TextReplaceWhitespaceCharacters(textToDraw
);
4193 // According to spec, the API should return an empty array if maxWidth was
4194 // provided but is less than or equal to zero or equal to NaN.
4195 if (aMaxWidth
.WasPassed() &&
4196 (aMaxWidth
.Value() <= 0 || IsNaN(aMaxWidth
.Value()))) {
4197 textToDraw
.Truncate();
4200 RefPtr
<const ComputedStyle
> canvasStyle
;
4201 if (mCanvasElement
&& mCanvasElement
->IsInComposedDoc()) {
4202 // try to find the closest context
4203 canvasStyle
= nsComputedDOMStyle::GetComputedStyle(mCanvasElement
);
4205 aError
= NS_ERROR_FAILURE
;
4210 // Get text direction, either from the property or inherited from context.
4211 const ContextState
& state
= CurrentState();
4213 switch (state
.textDirection
) {
4214 case TextDirection::LTR
:
4217 case TextDirection::RTL
:
4220 case TextDirection::INHERIT
:
4223 canvasStyle
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
4224 } else if (document
) {
4225 isRTL
= GET_BIDI_OPTION_DIRECTION(document
->GetBidiOptions()) ==
4226 IBMBIDI_TEXTDIRECTION_RTL
;
4233 // This is only needed to know if we can know the drawing bounding box easily.
4234 const bool doCalculateBounds
= NeedToCalculateBounds();
4235 if (presShell
&& presShell
->IsDestroying()) {
4236 aError
= NS_ERROR_FAILURE
;
4240 nsPresContext
* presContext
=
4241 presShell
? presShell
->GetPresContext() : nullptr;
4244 // ensure user font set is up to date
4245 presContext
->Document()->FlushUserFontSet();
4246 currentFontStyle
->SetUserFontSet(presContext
->GetUserFontSet());
4249 if (currentFontStyle
->GetStyle()->size
== 0.0F
) {
4251 if (aOp
== TextDrawOperation::MEASURE
) {
4252 return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
4258 if (!IsFinite(aX
) || !IsFinite(aY
)) {
4260 // This may not be correct - what should TextMetrics contain in the case of
4261 // infinite width or height?
4262 if (aOp
== TextDrawOperation::MEASURE
) {
4263 return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
4269 CanvasBidiProcessor processor
;
4271 // If we don't have a ComputedStyle, we can't set up vertical-text flags
4272 // (for now, at least; perhaps we need new Canvas API to control this).
4273 processor
.mTextRunFlags
=
4274 canvasStyle
? nsLayoutUtils::GetTextRunFlagsForStyle(
4275 canvasStyle
, presContext
, canvasStyle
->StyleFont(),
4276 canvasStyle
->StyleText(), 0)
4277 : gfx::ShapedTextFlags();
4279 GetAppUnitsValues(&processor
.mAppUnitsPerDevPixel
, nullptr);
4280 processor
.mPt
= gfx::Point(aX
, aY
);
4281 processor
.mDrawTarget
= gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
4283 // If we don't have a target then we don't have a transform. A target won't
4284 // be needed in the case where we're measuring the text size. This allows
4285 // to avoid creating a target if it's only being used to measure text sizes.
4287 processor
.mDrawTarget
->SetTransform(mTarget
->GetTransform());
4289 processor
.mCtx
= this;
4290 processor
.mOp
= aOp
;
4291 processor
.mBoundingBox
= gfxRect(0, 0, 0, 0);
4292 processor
.mDoMeasureBoundingBox
= doCalculateBounds
||
4293 !mIsEntireFrameInvalid
||
4294 aOp
== TextDrawOperation::MEASURE
;
4295 processor
.mFontgrp
= currentFontStyle
;
4297 nscoord totalWidthCoord
;
4300 ->UpdateUserFonts(); // ensure user font generation is current
4301 RefPtr
<gfxFont
> font
= processor
.mFontgrp
->GetFirstValidFont();
4302 const gfxFont::Metrics
& fontMetrics
=
4303 font
->GetMetrics(nsFontMetrics::eHorizontal
);
4305 // calls bidi algo twice since it needs the full text width and the
4306 // bounding boxes before rendering anything
4307 aError
= nsBidiPresUtils::ProcessText(
4308 textToDraw
.get(), textToDraw
.Length(),
4309 isRTL
? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(),
4310 presContext
, processor
, nsBidiPresUtils::MODE_MEASURE
, nullptr, 0,
4311 &totalWidthCoord
, &mBidiEngine
);
4312 if (aError
.Failed()) {
4316 // If ProcessText only called SetText once, we're dealing with a single run,
4317 // and so we don't need to repeat SetText and textRun construction at drawing
4318 // time below; we can just re-use the existing textRun.
4319 if (processor
.mSetTextCount
== 1) {
4320 processor
.mIgnoreSetText
= true;
4323 float totalWidth
= float(totalWidthCoord
) / processor
.mAppUnitsPerDevPixel
;
4325 // offset pt.x based on text align
4328 if (state
.textAlign
== TextAlign::CENTER
) {
4330 } else if (state
.textAlign
== TextAlign::LEFT
||
4331 (!isRTL
&& state
.textAlign
== TextAlign::START
) ||
4332 (isRTL
&& state
.textAlign
== TextAlign::END
)) {
4338 float offsetX
= anchorX
* totalWidth
;
4339 processor
.mPt
.x
-= offsetX
;
4341 // offset pt.y (or pt.x, for vertical text) based on text baseline
4342 gfxFloat baselineAnchor
;
4344 switch (state
.textBaseline
) {
4345 case TextBaseline::HANGING
:
4346 baselineAnchor
= fontMetrics
.emAscent
* kHangingBaselineDefault
;
4348 case TextBaseline::TOP
:
4349 baselineAnchor
= fontMetrics
.emAscent
;
4351 case TextBaseline::MIDDLE
:
4352 baselineAnchor
= (fontMetrics
.emAscent
- fontMetrics
.emDescent
) * .5f
;
4354 case TextBaseline::ALPHABETIC
:
4357 case TextBaseline::IDEOGRAPHIC
:
4358 baselineAnchor
= -fontMetrics
.emDescent
* kIdeographicBaselineDefault
;
4360 case TextBaseline::BOTTOM
:
4361 baselineAnchor
= -fontMetrics
.emDescent
;
4364 MOZ_CRASH("GFX: unexpected TextBaseline");
4367 // We can't query the textRun directly, as it may not have been created yet;
4368 // so instead we check the flags that will be used to initialize it.
4369 gfx::ShapedTextFlags runOrientation
=
4370 (processor
.mTextRunFlags
& gfx::ShapedTextFlags::TEXT_ORIENT_MASK
);
4371 if (runOrientation
!= gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL
) {
4372 if (runOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
||
4373 runOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
) {
4374 // Adjust to account for mTextRun being shaped using center baseline
4375 // rather than alphabetic.
4376 baselineAnchor
-= (fontMetrics
.emAscent
- fontMetrics
.emDescent
) * .5f
;
4378 processor
.mPt
.x
-= baselineAnchor
;
4380 processor
.mPt
.y
+= baselineAnchor
;
4383 // if only measuring, don't need to do any more work
4384 if (aOp
== TextDrawOperation::MEASURE
) {
4386 // Note that actualBoundingBoxLeft measures the distance in the leftward
4387 // direction, so its sign is reversed from our usual physical coordinates.
4388 double actualBoundingBoxLeft
= offsetX
- processor
.mBoundingBox
.X();
4389 double actualBoundingBoxRight
= processor
.mBoundingBox
.XMost() - offsetX
;
4390 double actualBoundingBoxAscent
=
4391 -processor
.mBoundingBox
.Y() - baselineAnchor
;
4392 double actualBoundingBoxDescent
=
4393 processor
.mBoundingBox
.YMost() + baselineAnchor
;
4394 double hangingBaseline
=
4395 fontMetrics
.emAscent
* kHangingBaselineDefault
- baselineAnchor
;
4396 double ideographicBaseline
=
4397 -fontMetrics
.emDescent
* kIdeographicBaselineDefault
- baselineAnchor
;
4398 return new TextMetrics(
4399 totalWidth
, actualBoundingBoxLeft
, actualBoundingBoxRight
,
4400 fontMetrics
.maxAscent
- baselineAnchor
, // fontBBAscent
4401 fontMetrics
.maxDescent
+ baselineAnchor
, // fontBBDescent
4402 actualBoundingBoxAscent
, actualBoundingBoxDescent
,
4403 fontMetrics
.emAscent
- baselineAnchor
, // emHeightAscent
4404 -fontMetrics
.emDescent
- baselineAnchor
, // emHeightDescent
4406 -baselineAnchor
, // alphabeticBaseline
4407 ideographicBaseline
);
4410 // If we did not actually calculate bounds, set up a simple bounding box
4411 // based on the text position and advance.
4412 if (!doCalculateBounds
) {
4413 processor
.mBoundingBox
.width
= totalWidth
;
4414 processor
.mBoundingBox
.MoveBy(gfxPoint(processor
.mPt
.x
, processor
.mPt
.y
));
4417 processor
.mPt
.x
*= processor
.mAppUnitsPerDevPixel
;
4418 processor
.mPt
.y
*= processor
.mAppUnitsPerDevPixel
;
4421 if (!IsTargetValid()) {
4422 aError
= NS_ERROR_FAILURE
;
4426 Matrix oldTransform
= mTarget
->GetTransform();
4427 // if text is over aMaxWidth, then scale the text horizontally such that its
4428 // width is precisely aMaxWidth
4429 if (aMaxWidth
.WasPassed() && aMaxWidth
.Value() > 0 &&
4430 totalWidth
> aMaxWidth
.Value()) {
4431 Matrix newTransform
= oldTransform
;
4433 // Translate so that the anchor point is at 0,0, then scale and then
4435 newTransform
.PreTranslate(aX
, 0);
4436 newTransform
.PreScale(aMaxWidth
.Value() / totalWidth
, 1);
4437 newTransform
.PreTranslate(-aX
, 0);
4438 /* we do this to avoid an ICE in the android compiler */
4439 Matrix androidCompilerBug
= newTransform
;
4440 mTarget
->SetTransform(androidCompilerBug
);
4443 // save the previous bounding box
4444 gfxRect boundingBox
= processor
.mBoundingBox
;
4446 // don't ever need to measure the bounding box twice
4447 processor
.mDoMeasureBoundingBox
= false;
4449 aError
= nsBidiPresUtils::ProcessText(
4450 textToDraw
.get(), textToDraw
.Length(),
4451 isRTL
? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(),
4452 presContext
, processor
, nsBidiPresUtils::MODE_DRAW
, nullptr, 0, nullptr,
4455 if (aError
.Failed()) {
4459 mTarget
->SetTransform(oldTransform
);
4461 if (aOp
== CanvasRenderingContext2D::TextDrawOperation::FILL
&&
4462 !doCalculateBounds
) {
4463 RedrawUser(boundingBox
);
4472 gfxFontGroup
* CanvasRenderingContext2D::GetCurrentFontStyle() {
4473 // Use lazy (re)initialization for the fontGroup since it's rather expensive.
4475 RefPtr
<PresShell
> presShell
= GetPresShell();
4476 nsPresContext
* presContext
=
4477 presShell
? presShell
->GetPresContext() : nullptr;
4479 // If we have a cached fontGroup, check that it is valid for the current
4480 // prescontext; if not, we need to discard and re-create it.
4481 RefPtr
<gfxFontGroup
>& fontGroup
= CurrentState().fontGroup
;
4483 if (fontGroup
->GetPresContext() != presContext
) {
4484 fontGroup
= nullptr;
4490 constexpr auto kDefaultFontStyle
= "10px sans-serif"_ns
;
4491 const float kDefaultFontSize
= 10.0;
4492 // If the font has already been set, we're re-creating the fontGroup
4493 // and should re-use the existing font attribute; if not, we initialize
4494 // it to the canvas default.
4495 const nsCString
& currentFont
= CurrentState().font
;
4496 bool fontUpdated
= SetFontInternal(
4497 currentFont
.IsEmpty() ? kDefaultFontStyle
: currentFont
, err
);
4498 if (err
.Failed() || !fontUpdated
) {
4499 err
.SuppressException();
4500 // XXX Should we get a default lang from the prescontext or something?
4501 nsAtom
* language
= nsGkAtoms::x_western
;
4502 bool explicitLanguage
= false;
4504 style
.size
= kDefaultFontSize
;
4505 int32_t perDevPixel
, perCSSPixel
;
4506 GetAppUnitsValues(&perDevPixel
, &perCSSPixel
);
4507 gfxFloat devToCssSize
= gfxFloat(perDevPixel
) / gfxFloat(perCSSPixel
);
4509 Servo_FontFamily_Generic(StyleGenericFontFamily::SansSerif
);
4510 fontGroup
= new gfxFontGroup(
4511 presContext
, sans
->families
, &style
, language
, explicitLanguage
,
4512 presContext
? presContext
->GetTextPerfMetrics() : nullptr, nullptr,
4513 devToCssSize
, StyleFontVariantEmoji::Normal
);
4515 CurrentState().font
= kDefaultFontStyle
;
4517 NS_ERROR("Default canvas font is invalid");
4521 // The fontgroup needs to check if its cached families/faces are valid.
4522 fontGroup
->CheckForUpdatedPlatformList();
4532 void CanvasRenderingContext2D::SetLineCap(const nsAString
& aLinecapStyle
) {
4535 if (aLinecapStyle
.EqualsLiteral("butt")) {
4536 cap
= CapStyle::BUTT
;
4537 } else if (aLinecapStyle
.EqualsLiteral("round")) {
4538 cap
= CapStyle::ROUND
;
4539 } else if (aLinecapStyle
.EqualsLiteral("square")) {
4540 cap
= CapStyle::SQUARE
;
4542 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4546 CurrentState().lineCap
= cap
;
4549 void CanvasRenderingContext2D::GetLineCap(nsAString
& aLinecapStyle
) {
4550 switch (CurrentState().lineCap
) {
4551 case CapStyle::BUTT
:
4552 aLinecapStyle
.AssignLiteral("butt");
4554 case CapStyle::ROUND
:
4555 aLinecapStyle
.AssignLiteral("round");
4557 case CapStyle::SQUARE
:
4558 aLinecapStyle
.AssignLiteral("square");
4563 void CanvasRenderingContext2D::SetLineJoin(const nsAString
& aLinejoinStyle
) {
4566 if (aLinejoinStyle
.EqualsLiteral("round")) {
4567 j
= JoinStyle::ROUND
;
4568 } else if (aLinejoinStyle
.EqualsLiteral("bevel")) {
4569 j
= JoinStyle::BEVEL
;
4570 } else if (aLinejoinStyle
.EqualsLiteral("miter")) {
4571 j
= JoinStyle::MITER_OR_BEVEL
;
4573 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4577 CurrentState().lineJoin
= j
;
4580 void CanvasRenderingContext2D::GetLineJoin(nsAString
& aLinejoinStyle
,
4581 ErrorResult
& aError
) {
4582 switch (CurrentState().lineJoin
) {
4583 case JoinStyle::ROUND
:
4584 aLinejoinStyle
.AssignLiteral("round");
4586 case JoinStyle::BEVEL
:
4587 aLinejoinStyle
.AssignLiteral("bevel");
4589 case JoinStyle::MITER_OR_BEVEL
:
4590 aLinejoinStyle
.AssignLiteral("miter");
4593 aError
.Throw(NS_ERROR_FAILURE
);
4597 void CanvasRenderingContext2D::SetLineDash(const Sequence
<double>& aSegments
,
4599 nsTArray
<mozilla::gfx::Float
> dash
;
4601 for (uint32_t x
= 0; x
< aSegments
.Length(); x
++) {
4602 if (aSegments
[x
] < 0.0) {
4603 // Pattern elements must be finite "numbers" >= 0, with "finite"
4604 // taken care of by WebIDL
4608 if (!dash
.AppendElement(aSegments
[x
], fallible
)) {
4609 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
4613 if (aSegments
.Length() %
4614 2) { // If the number of elements is odd, concatenate again
4615 for (uint32_t x
= 0; x
< aSegments
.Length(); x
++) {
4616 if (!dash
.AppendElement(aSegments
[x
], fallible
)) {
4617 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
4623 CurrentState().dash
= std::move(dash
);
4626 void CanvasRenderingContext2D::GetLineDash(nsTArray
<double>& aSegments
) const {
4627 const nsTArray
<mozilla::gfx::Float
>& dash
= CurrentState().dash
;
4630 for (uint32_t x
= 0; x
< dash
.Length(); x
++) {
4631 aSegments
.AppendElement(dash
[x
]);
4635 void CanvasRenderingContext2D::SetLineDashOffset(double aOffset
) {
4636 CurrentState().dashOffset
= aOffset
;
4639 double CanvasRenderingContext2D::LineDashOffset() const {
4640 return CurrentState().dashOffset
;
4643 bool CanvasRenderingContext2D::IsPointInPath(JSContext
* aCx
, double aX
,
4645 const CanvasWindingRule
& aWinding
,
4646 nsIPrincipal
& aSubjectPrincipal
) {
4647 return IsPointInPath(aCx
, aX
, aY
, aWinding
, Some(&aSubjectPrincipal
));
4650 bool CanvasRenderingContext2D::IsPointInPath(
4651 JSContext
* aCx
, double aX
, double aY
, const CanvasWindingRule
& aWinding
,
4652 Maybe
<nsIPrincipal
*> aSubjectPrincipal
) {
4653 if (!FloatValidate(aX
, aY
)) {
4657 // Check for site-specific permission and return false if no permission.
4658 if (mCanvasElement
) {
4659 nsCOMPtr
<Document
> ownerDoc
= mCanvasElement
->OwnerDoc();
4660 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc
, aCx
,
4661 aSubjectPrincipal
)) {
4664 } else if (mOffscreenCanvas
&&
4665 mOffscreenCanvas
->ShouldResistFingerprinting()) {
4669 EnsureUserSpacePath(aWinding
);
4674 if (mPathTransformWillUpdate
) {
4675 return mPath
->ContainsPoint(Point(aX
, aY
), mPathToDS
);
4678 return mPath
->ContainsPoint(Point(aX
, aY
), mTarget
->GetTransform());
4681 bool CanvasRenderingContext2D::IsPointInPath(JSContext
* aCx
,
4682 const CanvasPath
& aPath
, double aX
,
4684 const CanvasWindingRule
& aWinding
,
4685 nsIPrincipal
& aSubjectPrincipal
) {
4686 return IsPointInPath(aCx
, aPath
, aX
, aY
, aWinding
, Some(&aSubjectPrincipal
));
4689 bool CanvasRenderingContext2D::IsPointInPath(JSContext
* aCx
,
4690 const CanvasPath
& aPath
, double aX
,
4692 const CanvasWindingRule
& aWinding
,
4693 Maybe
<nsIPrincipal
*>) {
4694 if (!FloatValidate(aX
, aY
)) {
4699 if (!IsTargetValid()) {
4703 RefPtr
<gfx::Path
> tempPath
= aPath
.GetPath(aWinding
, mTarget
);
4705 return tempPath
->ContainsPoint(Point(aX
, aY
), mTarget
->GetTransform());
4708 bool CanvasRenderingContext2D::IsPointInStroke(
4709 JSContext
* aCx
, double aX
, double aY
, nsIPrincipal
& aSubjectPrincipal
) {
4710 return IsPointInStroke(aCx
, aX
, aY
, Some(&aSubjectPrincipal
));
4713 bool CanvasRenderingContext2D::IsPointInStroke(
4714 JSContext
* aCx
, double aX
, double aY
,
4715 Maybe
<nsIPrincipal
*> aSubjectPrincipal
) {
4716 if (!FloatValidate(aX
, aY
)) {
4720 // Check for site-specific permission and return false if no permission.
4721 if (mCanvasElement
) {
4722 nsCOMPtr
<Document
> ownerDoc
= mCanvasElement
->OwnerDoc();
4723 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc
, aCx
,
4724 aSubjectPrincipal
)) {
4727 } else if (mOffscreenCanvas
&&
4728 mOffscreenCanvas
->ShouldResistFingerprinting()) {
4732 EnsureUserSpacePath();
4737 const ContextState
& state
= CurrentState();
4739 StrokeOptions
strokeOptions(state
.lineWidth
, state
.lineJoin
, state
.lineCap
,
4740 state
.miterLimit
, state
.dash
.Length(),
4741 state
.dash
.Elements(), state
.dashOffset
);
4743 if (mPathTransformWillUpdate
) {
4744 return mPath
->StrokeContainsPoint(strokeOptions
, Point(aX
, aY
), mPathToDS
);
4746 return mPath
->StrokeContainsPoint(strokeOptions
, Point(aX
, aY
),
4747 mTarget
->GetTransform());
4750 bool CanvasRenderingContext2D::IsPointInStroke(
4751 JSContext
* aCx
, const CanvasPath
& aPath
, double aX
, double aY
,
4752 nsIPrincipal
& aSubjectPrincipal
) {
4753 return IsPointInStroke(aCx
, aPath
, aX
, aY
, Some(&aSubjectPrincipal
));
4756 bool CanvasRenderingContext2D::IsPointInStroke(JSContext
* aCx
,
4757 const CanvasPath
& aPath
,
4758 double aX
, double aY
,
4759 Maybe
<nsIPrincipal
*>) {
4760 if (!FloatValidate(aX
, aY
)) {
4765 if (!IsTargetValid()) {
4769 RefPtr
<gfx::Path
> tempPath
=
4770 aPath
.GetPath(CanvasWindingRule::Nonzero
, mTarget
);
4772 const ContextState
& state
= CurrentState();
4774 StrokeOptions
strokeOptions(state
.lineWidth
, state
.lineJoin
, state
.lineCap
,
4775 state
.miterLimit
, state
.dash
.Length(),
4776 state
.dash
.Elements(), state
.dashOffset
);
4778 return tempPath
->StrokeContainsPoint(strokeOptions
, Point(aX
, aY
),
4779 mTarget
->GetTransform());
4782 // Returns a surface that contains only the part needed to draw aSourceRect.
4783 // On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
4784 // relative to the returned surface.
4785 static already_AddRefed
<SourceSurface
> ExtractSubrect(SourceSurface
* aSurface
,
4786 gfx::Rect
* aSourceRect
,
4787 DrawTarget
* aTargetDT
) {
4788 gfx::Rect roundedOutSourceRect
= *aSourceRect
;
4789 roundedOutSourceRect
.RoundOut();
4790 gfx::IntRect roundedOutSourceRectInt
;
4791 if (!roundedOutSourceRect
.ToIntRect(&roundedOutSourceRectInt
)) {
4792 RefPtr
<SourceSurface
> surface(aSurface
);
4793 return surface
.forget();
4796 // Try to extract an optimized sub-surface.
4797 if (RefPtr
<SourceSurface
> surface
=
4798 aSurface
->ExtractSubrect(roundedOutSourceRectInt
)) {
4799 *aSourceRect
-= roundedOutSourceRect
.TopLeft();
4800 return surface
.forget();
4803 RefPtr
<DrawTarget
> subrectDT
= aTargetDT
->CreateSimilarDrawTarget(
4804 roundedOutSourceRectInt
.Size(), SurfaceFormat::B8G8R8A8
);
4808 subrectDT
->ClearRect(gfx::Rect());
4811 if (!subrectDT
|| !subrectDT
->IsValid()) {
4812 RefPtr
<SourceSurface
> surface(aSurface
);
4813 return surface
.forget();
4816 *aSourceRect
-= roundedOutSourceRect
.TopLeft();
4818 subrectDT
->CopySurface(aSurface
, roundedOutSourceRectInt
, IntPoint());
4819 return subrectDT
->Snapshot();
4826 static void ClipImageDimension(double& aSourceCoord
, double& aSourceSize
,
4827 int32_t aImageSize
, double& aDestCoord
,
4828 double& aDestSize
) {
4829 double scale
= aDestSize
/ aSourceSize
;
4830 if (aSourceCoord
< 0.0) {
4831 double destEnd
= aDestCoord
+ aDestSize
;
4832 aDestCoord
-= aSourceCoord
* scale
;
4833 aDestSize
= destEnd
- aDestCoord
;
4834 aSourceSize
+= aSourceCoord
;
4837 double delta
= aImageSize
- (aSourceCoord
+ aSourceSize
);
4839 aDestSize
+= delta
* scale
;
4840 aSourceSize
= aImageSize
- aSourceCoord
;
4844 // Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
4845 // to pull a SourceSurface from our cache. This allows us to avoid
4846 // reoptimizing surfaces if content and canvas backends are different.
4847 SurfaceFromElementResult
CanvasRenderingContext2D::CachedSurfaceFromElement(
4848 Element
* aElement
) {
4849 SurfaceFromElementResult res
;
4850 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(aElement
);
4855 nsCOMPtr
<imgIRequest
> imgRequest
;
4856 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
4857 getter_AddRefs(imgRequest
));
4862 uint32_t status
= 0;
4863 if (NS_FAILED(imgRequest
->GetImageStatus(&status
)) ||
4864 !(status
& imgIRequest::STATUS_LOAD_COMPLETE
)) {
4868 nsCOMPtr
<nsIPrincipal
> principal
;
4869 if (NS_FAILED(imgRequest
->GetImagePrincipal(getter_AddRefs(principal
))) ||
4874 if (NS_FAILED(imgRequest
->GetHadCrossOriginRedirects(
4875 &res
.mHadCrossOriginRedirects
))) {
4879 res
.mSourceSurface
= CanvasImageCache::LookupAllCanvas(aElement
, mTarget
);
4880 if (!res
.mSourceSurface
) {
4884 int32_t corsmode
= CORS_NONE
;
4885 if (NS_SUCCEEDED(imgRequest
->GetCORSMode(&corsmode
))) {
4886 res
.mCORSUsed
= corsmode
!= CORS_NONE
;
4889 res
.mSize
= res
.mIntrinsicSize
= res
.mSourceSurface
->GetSize();
4890 res
.mPrincipal
= std::move(principal
);
4891 res
.mImageRequest
= std::move(imgRequest
);
4892 res
.mIsWriteOnly
= CheckWriteOnlySecurity(res
.mCORSUsed
, res
.mPrincipal
,
4893 res
.mHadCrossOriginRedirects
);
4898 static void SwapScaleWidthHeightForRotation(gfx::Rect
& aRect
,
4899 VideoInfo::Rotation aDegrees
) {
4900 if (aDegrees
== VideoInfo::Rotation::kDegree_90
||
4901 aDegrees
== VideoInfo::Rotation::kDegree_270
) {
4902 std::swap(aRect
.width
, aRect
.height
);
4906 static Matrix
ComputeRotationMatrix(gfxFloat aRotatedWidth
,
4907 gfxFloat aRotatedHeight
,
4908 VideoInfo::Rotation aDegrees
) {
4909 Matrix shiftVideoCenterToOrigin
;
4910 if (aDegrees
== VideoInfo::Rotation::kDegree_90
||
4911 aDegrees
== VideoInfo::Rotation::kDegree_270
) {
4912 shiftVideoCenterToOrigin
=
4913 Matrix::Translation(-aRotatedHeight
/ 2.0, -aRotatedWidth
/ 2.0);
4915 shiftVideoCenterToOrigin
=
4916 Matrix::Translation(-aRotatedWidth
/ 2.0, -aRotatedHeight
/ 2.0);
4919 auto angle
= static_cast<double>(aDegrees
) / 180.0 * M_PI
;
4920 Matrix rotation
= Matrix::Rotation(static_cast<gfx::Float
>(angle
));
4921 Matrix shiftLeftTopToOrigin
=
4922 Matrix::Translation(aRotatedWidth
/ 2.0, aRotatedHeight
/ 2.0);
4923 return shiftVideoCenterToOrigin
* rotation
* shiftLeftTopToOrigin
;
4926 // drawImage(in HTMLImageElement image, in float dx, in float dy);
4927 // -- render image from 0,0 at dx,dy top-left coords
4928 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw,
4930 // -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
4931 // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw,
4932 // in float sh, in float dx, in float dy, in float dw, in float dh);
4933 // -- render the region defined by (sx,sy,sw,wh) in image-local space into the
4934 // region (dx,dy,dw,dh) on the canvas
4936 // If only dx and dy are passed in then optional_argc should be 0. If only
4937 // dx, dy, dw and dh are passed in then optional_argc should be 2. The only
4938 // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
4939 // are all passed in.
4941 void CanvasRenderingContext2D::DrawImage(const CanvasImageSource
& aImage
,
4942 double aSx
, double aSy
, double aSw
,
4943 double aSh
, double aDx
, double aDy
,
4944 double aDw
, double aDh
,
4945 uint8_t aOptional_argc
,
4946 ErrorResult
& aError
) {
4947 MOZ_ASSERT(aOptional_argc
== 0 || aOptional_argc
== 2 || aOptional_argc
== 6);
4949 if (!ValidateRect(aDx
, aDy
, aDw
, aDh
, true)) {
4952 if (aOptional_argc
== 6) {
4953 if (!ValidateRect(aSx
, aSy
, aSw
, aSh
, true)) {
4958 RefPtr
<SourceSurface
> srcSurf
;
4959 gfx::IntSize imgSize
;
4960 gfx::IntSize intrinsicImgSize
;
4961 Element
* element
= nullptr;
4962 OffscreenCanvas
* offscreenCanvas
= nullptr;
4965 if (!IsTargetValid()) {
4969 if (aImage
.IsHTMLCanvasElement()) {
4970 HTMLCanvasElement
* canvas
= &aImage
.GetAsHTMLCanvasElement();
4972 nsIntSize size
= canvas
->GetSize();
4973 if (size
.width
== 0 || size
.height
== 0) {
4974 return aError
.ThrowInvalidStateError("Passed-in canvas is empty");
4977 if (canvas
->IsWriteOnly()) {
4980 } else if (aImage
.IsOffscreenCanvas()) {
4981 offscreenCanvas
= &aImage
.GetAsOffscreenCanvas();
4982 nsIntSize size
= offscreenCanvas
->GetWidthHeight();
4983 if (size
.IsEmpty()) {
4984 return aError
.ThrowInvalidStateError("Passed-in canvas is empty");
4987 srcSurf
= offscreenCanvas
->GetSurfaceSnapshot();
4989 imgSize
= intrinsicImgSize
= srcSurf
->GetSize();
4992 if (offscreenCanvas
->IsWriteOnly()) {
4995 } else if (aImage
.IsImageBitmap()) {
4996 ImageBitmap
& imageBitmap
= aImage
.GetAsImageBitmap();
4997 srcSurf
= imageBitmap
.PrepareForDrawTarget(mTarget
);
5000 if (imageBitmap
.IsClosed()) {
5001 aError
.ThrowInvalidStateError("Passed-in ImageBitmap is closed");
5006 if (imageBitmap
.IsWriteOnly()) {
5010 imgSize
= intrinsicImgSize
=
5011 gfx::IntSize(imageBitmap
.Width(), imageBitmap
.Height());
5013 if (aImage
.IsHTMLImageElement()) {
5014 HTMLImageElement
* img
= &aImage
.GetAsHTMLImageElement();
5016 } else if (aImage
.IsSVGImageElement()) {
5017 SVGImageElement
* img
= &aImage
.GetAsSVGImageElement();
5020 HTMLVideoElement
* video
= &aImage
.GetAsHTMLVideoElement();
5021 video
->LogVisibility(
5022 mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE
);
5026 srcSurf
= CanvasImageCache::LookupCanvas(element
, mCanvasElement
, mTarget
,
5027 &imgSize
, &intrinsicImgSize
);
5030 DirectDrawInfo drawInfo
;
5033 // The canvas spec says that drawImage should draw the first frame
5034 // of animated images. We also don't want to rasterize vector images.
5035 uint32_t sfeFlags
= nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE
|
5036 nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS
|
5037 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE
;
5039 SurfaceFromElementResult res
;
5040 if (offscreenCanvas
) {
5041 res
= nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas
, sfeFlags
,
5044 res
= CanvasRenderingContext2D::CachedSurfaceFromElement(element
);
5045 if (!res
.mSourceSurface
) {
5046 res
= nsLayoutUtils::SurfaceFromElement(element
, sfeFlags
, mTarget
);
5050 if (!res
.mSourceSurface
&& !res
.mDrawInfo
.mImgContainer
) {
5051 // https://html.spec.whatwg.org/#check-the-usability-of-the-image-argument:
5053 // Only throw if the request is broken and the element is an
5054 // HTMLImageElement / SVGImageElement. Note that even for those the spec
5055 // says to silently do nothing in the following cases:
5056 // - The element is still loading.
5057 // - The image is bad, but it's not in the broken state (i.e., we could
5058 // decode the headers and get the size).
5059 if (!res
.mIsStillLoading
&& !res
.mHasSize
&&
5060 (aImage
.IsHTMLImageElement() || aImage
.IsSVGImageElement())) {
5061 aError
.ThrowInvalidStateError("Passed-in image is \"broken\"");
5066 imgSize
= res
.mSize
;
5067 intrinsicImgSize
= res
.mIntrinsicSize
;
5068 DoSecurityCheck(res
.mPrincipal
, res
.mIsWriteOnly
, res
.mCORSUsed
);
5070 if (res
.mSourceSurface
) {
5071 if (res
.mImageRequest
) {
5072 CanvasImageCache::NotifyDrawImage(element
, mCanvasElement
, mTarget
,
5073 res
.mSourceSurface
, imgSize
,
5076 srcSurf
= res
.mSourceSurface
;
5078 drawInfo
= res
.mDrawInfo
;
5082 if (aOptional_argc
== 0) {
5084 aSw
= (double)imgSize
.width
;
5085 aSh
= (double)imgSize
.height
;
5086 aDw
= (double)intrinsicImgSize
.width
;
5087 aDh
= (double)intrinsicImgSize
.height
;
5088 } else if (aOptional_argc
== 2) {
5090 aSw
= (double)imgSize
.width
;
5091 aSh
= (double)imgSize
.height
;
5094 if (aSw
== 0.0 || aSh
== 0.0) {
5098 ClipImageDimension(aSx
, aSw
, imgSize
.width
, aDx
, aDw
);
5099 ClipImageDimension(aSy
, aSh
, imgSize
.height
, aDy
, aDh
);
5101 if (aSw
<= 0.0 || aSh
<= 0.0 || aDw
<= 0.0 || aDh
<= 0.0) {
5102 // source and/or destination are fully clipped, so nothing is painted
5106 // Per spec, the smoothing setting applies only to scaling up a bitmap image.
5107 // When down-scaling the user agent is free to choose whether or not to smooth
5108 // the image. Nearest sampling when down-scaling is rarely desirable and
5109 // smoothing when down-scaling matches chromium's behavior.
5110 // If any dimension is up-scaled, we consider the image as being up-scaled.
5111 auto scale
= mTarget
->GetTransform().ScaleFactors();
5113 aDw
* Abs(scale
.xScale
) < aSw
&& aDh
* Abs(scale
.yScale
) < aSh
;
5115 SamplingFilter samplingFilter
;
5116 AntialiasMode antialiasMode
;
5118 if (CurrentState().imageSmoothingEnabled
|| isDownScale
) {
5119 samplingFilter
= gfx::SamplingFilter::LINEAR
;
5120 antialiasMode
= AntialiasMode::DEFAULT
;
5122 samplingFilter
= gfx::SamplingFilter::POINT
;
5123 antialiasMode
= AntialiasMode::NONE
;
5126 const bool needBounds
= NeedToCalculateBounds();
5127 if (!IsTargetValid()) {
5132 bounds
= gfx::Rect(aDx
, aDy
, aDw
, aDh
);
5133 bounds
= mTarget
->GetTransform().TransformBounds(bounds
);
5136 if (!IsTargetValid()) {
5137 aError
.Throw(NS_ERROR_FAILURE
);
5142 gfx::Rect
sourceRect(aSx
, aSy
, aSw
, aSh
);
5143 if (element
== mCanvasElement
) {
5144 // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
5145 // trigger a COW copy of the whole canvas into srcSurf. That's a huge
5146 // waste if sourceRect doesn't cover the whole canvas.
5147 // We avoid copying the whole canvas by manually copying just the part
5149 srcSurf
= ExtractSubrect(srcSurf
, &sourceRect
, mTarget
);
5152 AdjustedTarget
tempTarget(this, bounds
.IsEmpty() ? nullptr : &bounds
, true);
5154 gfxWarning() << "Invalid adjusted target in Canvas2D "
5155 << gfx::hexa((DrawTarget
*)mTarget
) << ", "
5156 << NeedToDrawShadow() << NeedToApplyFilter();
5160 auto op
= tempTarget
.UsedOperation();
5161 if (!IsTargetValid() || !tempTarget
) {
5165 VideoInfo::Rotation rotationDeg
= VideoInfo::Rotation::kDegree_0
;
5166 if (HTMLVideoElement
* video
= HTMLVideoElement::FromNodeOrNull(element
)) {
5167 rotationDeg
= video
->RotationDegrees();
5170 gfx::Rect
destRect(aDx
, aDy
, aDw
, aDh
);
5173 if (rotationDeg
!= VideoInfo::Rotation::kDegree_0
) {
5174 Matrix preTransform
= ComputeRotationMatrix(aDw
, aDh
, rotationDeg
);
5175 transform
= preTransform
* Matrix::Translation(aDx
, aDy
);
5177 SwapScaleWidthHeightForRotation(destRect
, rotationDeg
);
5178 // When rotation exists, aDx, aDy is handled by transform, Since aDest.x
5179 // aDest.y handling of DrawSurface() does not care about the rotation.
5183 Matrix currentTransform
= tempTarget
->GetTransform();
5184 transform
*= currentTransform
;
5186 tempTarget
->SetTransform(transform
);
5188 tempTarget
.DrawSurface(
5189 srcSurf
, destRect
, sourceRect
,
5190 DrawSurfaceOptions(samplingFilter
, SamplingBounds::UNBOUNDED
),
5191 DrawOptions(CurrentState().globalAlpha
, op
, antialiasMode
));
5193 tempTarget
->SetTransform(currentTransform
);
5196 DrawDirectlyToCanvas(drawInfo
, &bounds
, gfx::Rect(aDx
, aDy
, aDw
, aDh
),
5197 gfx::Rect(aSx
, aSy
, aSw
, aSh
), imgSize
);
5200 RedrawUser(gfxRect(aDx
, aDy
, aDw
, aDh
));
5203 void CanvasRenderingContext2D::DrawDirectlyToCanvas(
5204 const DirectDrawInfo
& aImage
, gfx::Rect
* aBounds
, gfx::Rect aDest
,
5205 gfx::Rect aSrc
, gfx::IntSize aImgSize
) {
5206 MOZ_ASSERT(aSrc
.width
> 0 && aSrc
.height
> 0,
5207 "Need positive source width and height");
5209 AdjustedTarget
tempTarget(this, aBounds
->IsEmpty() ? nullptr : aBounds
);
5210 if (!tempTarget
|| !tempTarget
->IsValid()) {
5214 // Get any existing transforms on the context, including transformations used
5215 // for context shadow.
5216 Matrix matrix
= tempTarget
->GetTransform();
5217 gfxMatrix contextMatrix
= ThebesMatrix(matrix
);
5218 MatrixScalesDouble contextScale
= contextMatrix
.ScaleFactors();
5220 // Scale the dest rect to include the context scale.
5221 aDest
.Scale((float)contextScale
.xScale
, (float)contextScale
.yScale
);
5223 // Scale the image size to the dest rect, and adjust the source rect to match.
5224 MatrixScalesDouble
scale(aDest
.width
/ aSrc
.width
,
5225 aDest
.height
/ aSrc
.height
);
5226 IntSize scaledImageSize
=
5227 IntSize::Ceil(static_cast<float>(scale
.xScale
* aImgSize
.width
),
5228 static_cast<float>(scale
.yScale
* aImgSize
.height
));
5229 aSrc
.Scale(static_cast<float>(scale
.xScale
),
5230 static_cast<float>(scale
.yScale
));
5232 // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
5233 // the matrix even though this is a temp gfxContext.
5234 AutoRestoreTransform
autoRestoreTransform(mTarget
);
5236 gfxContext
context(tempTarget
);
5237 context
.SetMatrixDouble(
5239 .PreScale(1.0 / contextScale
.xScale
, 1.0 / contextScale
.yScale
)
5240 .PreTranslate(aDest
.x
- aSrc
.x
, aDest
.y
- aSrc
.y
));
5242 context
.SetOp(tempTarget
.UsedOperation());
5244 // FLAG_CLAMP is added for increased performance, since we never tile here.
5245 uint32_t modifiedFlags
= aImage
.mDrawingFlags
| imgIContainer::FLAG_CLAMP
;
5247 // XXX hmm is scaledImageSize really in CSS pixels?
5248 CSSIntSize
sz(scaledImageSize
.width
, scaledImageSize
.height
);
5249 SVGImageContext
svgContext(Some(sz
));
5251 auto result
= aImage
.mImgContainer
->Draw(
5252 &context
, scaledImageSize
,
5253 ImageRegion::Create(gfxRect(aSrc
.x
, aSrc
.y
, aSrc
.width
, aSrc
.height
)),
5254 aImage
.mWhichFrame
, SamplingFilter::GOOD
, svgContext
, modifiedFlags
,
5255 CurrentState().globalAlpha
);
5257 if (result
!= ImgDrawResult::SUCCESS
) {
5258 NS_WARNING("imgIContainer::Draw failed");
5262 void CanvasRenderingContext2D::SetGlobalCompositeOperation(
5263 const nsAString
& aOp
, ErrorResult
& aError
) {
5264 CompositionOp comp_op
;
5266 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
5267 if (aOp.EqualsLiteral(cvsop)) comp_op = CompositionOp::OP_##op2d;
5269 CANVAS_OP_TO_GFX_OP("copy", SOURCE
)
5270 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP
)
5271 else CANVAS_OP_TO_GFX_OP("source-in", IN
)
5272 else CANVAS_OP_TO_GFX_OP("source-out", OUT
)
5273 else CANVAS_OP_TO_GFX_OP("source-over", OVER
)
5274 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN
)
5275 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT
)
5276 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER
)
5277 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP
)
5278 else CANVAS_OP_TO_GFX_OP("lighter", ADD
)
5279 else CANVAS_OP_TO_GFX_OP("xor", XOR
)
5280 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY
)
5281 else CANVAS_OP_TO_GFX_OP("screen", SCREEN
)
5282 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY
)
5283 else CANVAS_OP_TO_GFX_OP("darken", DARKEN
)
5284 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN
)
5285 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE
)
5286 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN
)
5287 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT
)
5288 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT
)
5289 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE
)
5290 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION
)
5291 else CANVAS_OP_TO_GFX_OP("hue", HUE
)
5292 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION
)
5293 else CANVAS_OP_TO_GFX_OP("color", COLOR
)
5294 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY
)
5295 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
5298 #undef CANVAS_OP_TO_GFX_OP
5299 CurrentState().op
= comp_op
;
5302 void CanvasRenderingContext2D::GetGlobalCompositeOperation(
5303 nsAString
& aOp
, ErrorResult
& aError
) {
5304 CompositionOp comp_op
= CurrentState().op
;
5306 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
5307 if (comp_op == CompositionOp::OP_##op2d) aOp.AssignLiteral(cvsop);
5309 CANVAS_OP_TO_GFX_OP("copy", SOURCE
)
5310 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP
)
5311 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN
)
5312 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT
)
5313 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER
)
5314 else CANVAS_OP_TO_GFX_OP("lighter", ADD
)
5315 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP
)
5316 else CANVAS_OP_TO_GFX_OP("source-in", IN
)
5317 else CANVAS_OP_TO_GFX_OP("source-out", OUT
)
5318 else CANVAS_OP_TO_GFX_OP("source-over", OVER
)
5319 else CANVAS_OP_TO_GFX_OP("xor", XOR
)
5320 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY
)
5321 else CANVAS_OP_TO_GFX_OP("screen", SCREEN
)
5322 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY
)
5323 else CANVAS_OP_TO_GFX_OP("darken", DARKEN
)
5324 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN
)
5325 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE
)
5326 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN
)
5327 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT
)
5328 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT
)
5329 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE
)
5330 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION
)
5331 else CANVAS_OP_TO_GFX_OP("hue", HUE
)
5332 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION
)
5333 else CANVAS_OP_TO_GFX_OP("color", COLOR
)
5334 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY
)
5336 aError
.Throw(NS_ERROR_FAILURE
);
5339 #undef CANVAS_OP_TO_GFX_OP
5342 void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner
& aWindow
,
5343 double aX
, double aY
, double aW
,
5344 double aH
, const nsACString
& aBgColor
,
5346 nsIPrincipal
& aSubjectPrincipal
,
5347 ErrorResult
& aError
) {
5348 if (int32_t(aW
) == 0 || int32_t(aH
) == 0) {
5352 // protect against too-large surfaces that will cause allocation
5353 // or overflow issues
5354 if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW
), int32_t(aH
)), 0xffff)) {
5355 aError
.Throw(NS_ERROR_FAILURE
);
5359 Document
* doc
= aWindow
.GetExtantDoc();
5360 if (doc
&& aSubjectPrincipal
.GetIsAddonOrExpandedAddonPrincipal()) {
5362 DeprecatedOperations::eDrawWindowCanvasRenderingContext2D
);
5365 // Flush layout updates
5366 if (!(aFlags
& CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH
)) {
5367 nsContentUtils::FlushLayoutForTree(aWindow
.GetOuterWindow());
5370 CompositionOp op
= CurrentState().op
;
5371 bool discardContent
=
5372 GlobalAlpha() == 1.0f
&&
5373 (op
== CompositionOp::OP_OVER
|| op
== CompositionOp::OP_SOURCE
);
5374 const gfx::Rect
drawRect(aX
, aY
, aW
, aH
);
5375 EnsureTarget(discardContent
? &drawRect
: nullptr);
5376 if (!IsTargetValid()) {
5380 RefPtr
<nsPresContext
> presContext
;
5381 nsIDocShell
* docshell
= aWindow
.GetDocShell();
5383 presContext
= docshell
->GetPresContext();
5386 aError
.Throw(NS_ERROR_FAILURE
);
5390 Maybe
<nscolor
> backgroundColor
= ParseColor(aBgColor
);
5391 if (!backgroundColor
) {
5392 aError
.Throw(NS_ERROR_FAILURE
);
5396 nsRect
r(nsPresContext::CSSPixelsToAppUnits((float)aX
),
5397 nsPresContext::CSSPixelsToAppUnits((float)aY
),
5398 nsPresContext::CSSPixelsToAppUnits((float)aW
),
5399 nsPresContext::CSSPixelsToAppUnits((float)aH
));
5400 RenderDocumentFlags renderDocFlags
=
5401 (RenderDocumentFlags::IgnoreViewportScrolling
|
5402 RenderDocumentFlags::DocumentRelative
);
5403 if (aFlags
& CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET
) {
5404 renderDocFlags
|= RenderDocumentFlags::DrawCaret
;
5406 if (aFlags
& CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW
) {
5407 renderDocFlags
&= ~(RenderDocumentFlags::IgnoreViewportScrolling
|
5408 RenderDocumentFlags::DocumentRelative
);
5410 if (aFlags
& CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS
) {
5411 renderDocFlags
|= RenderDocumentFlags::UseWidgetLayers
;
5414 CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES
) {
5415 renderDocFlags
|= RenderDocumentFlags::AsyncDecodeImages
;
5417 if (aFlags
& CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH
) {
5418 renderDocFlags
|= RenderDocumentFlags::DrawWindowNotFlushing
;
5421 // gfxContext-over-Azure may modify the DrawTarget's transform, so
5422 // save and restore it
5423 Matrix matrix
= mTarget
->GetTransform();
5424 double sw
= matrix
._11
* aW
;
5425 double sh
= matrix
._22
* aH
;
5430 Maybe
<gfxContext
> thebes
;
5431 RefPtr
<DrawTarget
> drawDT
;
5432 // Rendering directly is faster and can be done if mTarget supports Azure
5433 // and does not need alpha blending.
5434 // Since the pre-transaction callback calls ReturnTarget, we can't have a
5435 // gfxContext wrapped around it when using a shared buffer provider because
5436 // the DrawTarget's shared buffer may be unmapped in ReturnTarget.
5437 op
= CompositionOp::OP_ADD
;
5438 if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget
) &&
5439 GlobalAlpha() == 1.0f
) {
5440 op
= CurrentState().op
;
5441 if (!IsTargetValid()) {
5442 aError
.Throw(NS_ERROR_FAILURE
);
5446 if (op
== CompositionOp::OP_OVER
&&
5447 (!mBufferProvider
|| !mBufferProvider
->IsShared())) {
5448 thebes
.emplace(mTarget
);
5449 thebes
.ref().SetMatrix(matrix
);
5451 IntSize dtSize
= IntSize::Ceil(sw
, sh
);
5452 if (!Factory::AllowedSurfaceSize(dtSize
)) {
5453 // attempt to limit the DT to what will actually cover the target
5454 Size
limitSize(mTarget
->GetSize());
5455 limitSize
.Scale(matrix
._11
, matrix
._22
);
5456 dtSize
= Min(dtSize
, IntSize::Ceil(limitSize
));
5457 // if the DT is still too big, then error
5458 if (!Factory::AllowedSurfaceSize(dtSize
)) {
5459 aError
.Throw(NS_ERROR_FAILURE
);
5463 drawDT
= gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
5464 dtSize
, SurfaceFormat::B8G8R8A8
);
5465 if (!drawDT
|| !drawDT
->IsValid()) {
5466 aError
.Throw(NS_ERROR_FAILURE
);
5470 thebes
.emplace(drawDT
);
5471 thebes
.ref().SetMatrix(Matrix::Scaling(matrix
._11
, matrix
._22
));
5473 MOZ_ASSERT(thebes
.isSome());
5475 RefPtr
<PresShell
> presShell
= presContext
->PresShell();
5477 Unused
<< presShell
->RenderDocument(r
, renderDocFlags
, *backgroundColor
,
5479 // If this canvas was contained in the drawn window, the pre-transaction
5480 // callback may have returned its DT. If so, we must reacquire it here.
5481 EnsureTarget(discardContent
? &drawRect
: nullptr);
5484 RefPtr
<SourceSurface
> snapshot
= drawDT
->Snapshot();
5485 if (NS_WARN_IF(!snapshot
)) {
5486 aError
.Throw(NS_ERROR_FAILURE
);
5490 op
= CurrentState().op
;
5491 if (!IsTargetValid()) {
5492 aError
.Throw(NS_ERROR_FAILURE
);
5495 gfx::Rect
destRect(0, 0, aW
, aH
);
5496 gfx::Rect
sourceRect(0, 0, sw
, sh
);
5497 mTarget
->DrawSurface(snapshot
, destRect
, sourceRect
,
5498 DrawSurfaceOptions(gfx::SamplingFilter::POINT
),
5499 DrawOptions(GlobalAlpha(), op
, AntialiasMode::NONE
));
5501 mTarget
->SetTransform(matrix
);
5504 // note that x and y are coordinates in the document that
5505 // we're drawing; x and y are drawn to 0,0 in current user
5507 RedrawUser(gfxRect(0, 0, aW
, aH
));
5511 // device pixel getting/setting
5514 already_AddRefed
<ImageData
> CanvasRenderingContext2D::GetImageData(
5515 JSContext
* aCx
, int32_t aSx
, int32_t aSy
, int32_t aSw
, int32_t aSh
,
5516 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aError
) {
5517 return GetImageData(aCx
, aSx
, aSy
, aSw
, aSh
, Some(&aSubjectPrincipal
),
5521 already_AddRefed
<ImageData
> CanvasRenderingContext2D::GetImageData(
5522 JSContext
* aCx
, int32_t aSx
, int32_t aSy
, int32_t aSw
, int32_t aSh
,
5523 Maybe
<nsIPrincipal
*> aSubjectPrincipal
, ErrorResult
& aError
) {
5524 if (!mCanvasElement
&& !mDocShell
&& !mOffscreenCanvas
) {
5525 NS_ERROR("No canvas element and no docshell in GetImageData!!!");
5526 aError
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
5530 // Check only if we have a canvas element; if we were created with a docshell,
5531 // then it's special internal use.
5532 // FIXME(aosmond): OffscreenCanvas security check??!
5533 if (IsWriteOnly() ||
5534 (mCanvasElement
&& !mCanvasElement
->CallerCanRead(aCx
))) {
5535 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
5536 aError
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
5541 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
5545 // Handle negative width and height by flipping the rectangle over in the
5546 // relevant direction.
5568 JS::Rooted
<JSObject
*> array(aCx
);
5569 aError
= GetImageDataArray(aCx
, aSx
, aSy
, w
, h
, aSubjectPrincipal
,
5571 if (aError
.Failed()) {
5575 return MakeAndAddRef
<ImageData
>(w
, h
, *array
);
5578 static IntRect
ClipImageDataTransfer(IntRect
& aSrc
, const IntPoint
& aDestOffset
,
5579 const IntSize
& aDestBounds
) {
5580 IntRect dest
= aSrc
;
5581 dest
.SafeMoveBy(aDestOffset
);
5582 dest
= IntRect(IntPoint(0, 0), aDestBounds
).SafeIntersect(dest
);
5584 aSrc
= aSrc
.SafeIntersect(dest
- aDestOffset
);
5585 return aSrc
+ aDestOffset
;
5588 nsresult
CanvasRenderingContext2D::GetImageDataArray(
5589 JSContext
* aCx
, int32_t aX
, int32_t aY
, uint32_t aWidth
, uint32_t aHeight
,
5590 Maybe
<nsIPrincipal
*> aSubjectPrincipal
, JSObject
** aRetval
) {
5591 MOZ_ASSERT(aWidth
&& aHeight
);
5593 // Restrict the typed array length to INT32_MAX because that's all we support
5594 // in dom::TypedArray::ComputeState.
5595 CheckedInt
<uint32_t> len
= CheckedInt
<uint32_t>(aWidth
) * aHeight
* 4;
5596 if (!len
.isValid() || len
.value() > INT32_MAX
) {
5597 return NS_ERROR_DOM_INDEX_SIZE_ERR
;
5600 CheckedInt
<int32_t> rightMost
= CheckedInt
<int32_t>(aX
) + aWidth
;
5601 CheckedInt
<int32_t> bottomMost
= CheckedInt
<int32_t>(aY
) + aHeight
;
5603 if (!rightMost
.isValid() || !bottomMost
.isValid()) {
5604 return NS_ERROR_DOM_SYNTAX_ERR
;
5607 JS::Rooted
<JSObject
*> darray(aCx
, JS_NewUint8ClampedArray(aCx
, len
.value()));
5609 return NS_ERROR_OUT_OF_MEMORY
;
5617 IntRect
dstWriteRect(0, 0, aWidth
, aHeight
);
5618 IntRect srcReadRect
= ClipImageDataTransfer(dstWriteRect
, IntPoint(aX
, aY
),
5619 IntSize(mWidth
, mHeight
));
5620 if (srcReadRect
.IsEmpty()) {
5625 if (!GetBufferProvider() && !EnsureTarget()) {
5626 return NS_ERROR_FAILURE
;
5629 RefPtr
<SourceSurface
> snapshot
= mBufferProvider
->BorrowSnapshot();
5631 return NS_ERROR_OUT_OF_MEMORY
;
5634 RefPtr
<DataSourceSurface
> readback
= snapshot
->GetDataSurface();
5635 mBufferProvider
->ReturnSnapshot(snapshot
.forget());
5637 DataSourceSurface::MappedSurface rawData
;
5638 if (!readback
|| !readback
->Map(DataSourceSurface::READ
, &rawData
)) {
5639 return NS_ERROR_OUT_OF_MEMORY
;
5642 // Check for site-specific permission. This check is not needed if the
5643 // canvas was created with a docshell (that is only done for special
5645 bool usePlaceholder
= false;
5646 if (mCanvasElement
) {
5647 nsCOMPtr
<Document
> ownerDoc
= mCanvasElement
->OwnerDoc();
5648 usePlaceholder
= !CanvasUtils::IsImageExtractionAllowed(ownerDoc
, aCx
,
5650 } else if (mOffscreenCanvas
) {
5651 usePlaceholder
= mOffscreenCanvas
->ShouldResistFingerprinting();
5655 uint8_t* randomData
;
5656 if (usePlaceholder
) {
5657 // Since we cannot call any GC-able functions (like requesting the RNG
5658 // service) after we call JS_GetUint8ClampedArrayData, we will
5659 // pre-generate the randomness required for GeneratePlaceholderCanvasData.
5660 randomData
= TryToGenerateRandomDataForPlaceholderCanvasData();
5663 JS::AutoCheckCannotGC nogc
;
5665 uint8_t* data
= JS_GetUint8ClampedArrayData(darray
, &isShared
, nogc
);
5666 MOZ_ASSERT(!isShared
); // Should not happen, data was created above
5668 if (usePlaceholder
) {
5669 FillPlaceholderCanvas(randomData
, len
.value(), data
);
5673 uint32_t srcStride
= rawData
.mStride
;
5675 rawData
.mData
+ srcReadRect
.y
* srcStride
+ srcReadRect
.x
* 4;
5677 uint8_t* dst
= data
+ dstWriteRect
.y
* (aWidth
* 4) + dstWriteRect
.x
* 4;
5680 SwizzleData(src
, srcStride
, SurfaceFormat::X8R8G8B8_UINT32
, dst
,
5681 aWidth
* 4, SurfaceFormat::R8G8B8A8
, dstWriteRect
.Size());
5683 UnpremultiplyData(src
, srcStride
, SurfaceFormat::A8R8G8B8_UINT32
, dst
,
5684 aWidth
* 4, SurfaceFormat::R8G8B8A8
,
5685 dstWriteRect
.Size());
5694 void CanvasRenderingContext2D::EnsureErrorTarget() {
5695 if (sErrorTarget
.get()) {
5699 RefPtr
<DrawTarget
> errorTarget
=
5700 gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
5701 IntSize(1, 1), SurfaceFormat::B8G8R8A8
);
5702 MOZ_ASSERT(errorTarget
, "Failed to allocate the error target!");
5704 sErrorTarget
.set(errorTarget
.forget().take());
5707 void CanvasRenderingContext2D::FillRuleChanged() {
5709 mPathBuilder
= mPath
->CopyToBuilder(CurrentState().fillRule
);
5714 void CanvasRenderingContext2D::PutImageData(ImageData
& aImageData
, int32_t aDx
,
5715 int32_t aDy
, ErrorResult
& aRv
) {
5716 RootedSpiderMonkeyInterface
<Uint8ClampedArray
> arr(RootingCx());
5717 PutImageData_explicit(aDx
, aDy
, aImageData
, false, 0, 0, 0, 0, aRv
);
5720 void CanvasRenderingContext2D::PutImageData(ImageData
& aImageData
, int32_t aDx
,
5721 int32_t aDy
, int32_t aDirtyX
,
5723 int32_t aDirtyWidth
,
5724 int32_t aDirtyHeight
,
5726 PutImageData_explicit(aDx
, aDy
, aImageData
, true, aDirtyX
, aDirtyY
,
5727 aDirtyWidth
, aDirtyHeight
, aRv
);
5730 void CanvasRenderingContext2D::PutImageData_explicit(
5731 int32_t aX
, int32_t aY
, ImageData
& aImageData
, bool aHasDirtyRect
,
5732 int32_t aDirtyX
, int32_t aDirtyY
, int32_t aDirtyWidth
, int32_t aDirtyHeight
,
5734 RootedSpiderMonkeyInterface
<Uint8ClampedArray
> arr(RootingCx());
5735 if (!arr
.Init(aImageData
.GetDataObject())) {
5736 return aRv
.ThrowInvalidStateError(
5737 "Failed to extract Uint8ClampedArray from ImageData (security check "
5741 const uint32_t width
= aImageData
.Width();
5742 const uint32_t height
= aImageData
.Height();
5743 if (width
== 0 || height
== 0) {
5744 return aRv
.ThrowInvalidStateError("Passed-in image is empty");
5748 IntRect
imageDataRect(0, 0, width
, height
);
5750 if (aHasDirtyRect
) {
5751 // fix up negative dimensions
5752 if (aDirtyWidth
< 0) {
5753 if (aDirtyWidth
== INT_MIN
) {
5754 return aRv
.ThrowInvalidStateError("Dirty width is invalid");
5757 CheckedInt32 checkedDirtyX
= CheckedInt32(aDirtyX
) + aDirtyWidth
;
5759 if (!checkedDirtyX
.isValid()) {
5760 return aRv
.ThrowInvalidStateError("Dirty width is invalid");
5763 aDirtyX
= checkedDirtyX
.value();
5764 aDirtyWidth
= -aDirtyWidth
;
5767 if (aDirtyHeight
< 0) {
5768 if (aDirtyHeight
== INT_MIN
) {
5769 return aRv
.ThrowInvalidStateError("Dirty height is invalid");
5772 CheckedInt32 checkedDirtyY
= CheckedInt32(aDirtyY
) + aDirtyHeight
;
5774 if (!checkedDirtyY
.isValid()) {
5775 return aRv
.ThrowInvalidStateError("Dirty height is invalid");
5778 aDirtyY
= checkedDirtyY
.value();
5779 aDirtyHeight
= -aDirtyHeight
;
5782 // bound the dirty rect within the imageData rectangle
5783 dirtyRect
= imageDataRect
.Intersect(
5784 IntRect(aDirtyX
, aDirtyY
, aDirtyWidth
, aDirtyHeight
));
5786 if (dirtyRect
.Width() <= 0 || dirtyRect
.Height() <= 0) {
5790 dirtyRect
= imageDataRect
;
5793 IntRect srcRect
= dirtyRect
;
5794 dirtyRect
= ClipImageDataTransfer(srcRect
, IntPoint(aX
, aY
),
5795 IntSize(mWidth
, mHeight
));
5796 if (dirtyRect
.IsEmpty()) {
5802 uint32_t dataLen
= arr
.Length();
5804 uint32_t len
= width
* height
* 4;
5805 if (dataLen
!= len
) {
5806 return aRv
.ThrowInvalidStateError("Invalid width or height");
5809 // The canvas spec says that the current path, transformation matrix, shadow
5810 // attributes, global alpha, the clipping region, and global composition
5811 // operator must not affect the getImageData() and putImageData() methods.
5812 const gfx::Rect
putRect(dirtyRect
);
5813 EnsureTarget(&putRect
);
5815 if (!IsTargetValid()) {
5816 return aRv
.Throw(NS_ERROR_FAILURE
);
5819 DataSourceSurface::MappedSurface map
;
5820 RefPtr
<DataSourceSurface
> sourceSurface
;
5821 uint8_t* lockedBits
= nullptr;
5825 SurfaceFormat dstFormat
;
5826 if (mTarget
->LockBits(&lockedBits
, &dstSize
, &dstStride
, &dstFormat
)) {
5827 dstData
= lockedBits
+ dirtyRect
.y
* dstStride
+ dirtyRect
.x
* 4;
5829 sourceSurface
= Factory::CreateDataSourceSurface(
5830 dirtyRect
.Size(), SurfaceFormat::B8G8R8A8
, false);
5832 // In certain scenarios, requesting larger than 8k image fails. Bug 803568
5833 // covers the details of how to run into it, but the full detailed
5834 // investigation hasn't been done to determine the underlying cause. We
5835 // will just handle the failure to allocate the surface to avoid a crash.
5836 if (!sourceSurface
) {
5837 return aRv
.Throw(NS_ERROR_FAILURE
);
5839 if (!sourceSurface
->Map(DataSourceSurface::READ_WRITE
, &map
)) {
5840 return aRv
.Throw(NS_ERROR_FAILURE
);
5843 dstData
= map
.mData
;
5845 return aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
5847 dstStride
= map
.mStride
;
5848 dstFormat
= sourceSurface
->GetFormat();
5851 uint8_t* srcData
= arr
.Data() + srcRect
.y
* (width
* 4) + srcRect
.x
* 4;
5854 srcData
, width
* 4, SurfaceFormat::R8G8B8A8
, dstData
, dstStride
,
5855 mOpaque
? SurfaceFormat::X8R8G8B8_UINT32
: SurfaceFormat::A8R8G8B8_UINT32
,
5859 mTarget
->ReleaseBits(lockedBits
);
5860 } else if (sourceSurface
) {
5861 sourceSurface
->Unmap();
5862 mTarget
->CopySurface(sourceSurface
, dirtyRect
- dirtyRect
.TopLeft(),
5863 dirtyRect
.TopLeft());
5867 gfx::Rect(dirtyRect
.x
, dirtyRect
.y
, dirtyRect
.width
, dirtyRect
.height
));
5870 static already_AddRefed
<ImageData
> CreateImageData(
5871 JSContext
* aCx
, CanvasRenderingContext2D
* aContext
, uint32_t aW
,
5872 uint32_t aH
, ErrorResult
& aError
) {
5873 if (aW
== 0) aW
= 1;
5874 if (aH
== 0) aH
= 1;
5876 // Restrict the typed array length to INT32_MAX because that's all we support
5877 // in dom::TypedArray::ComputeState.
5878 CheckedInt
<uint32_t> len
= CheckedInt
<uint32_t>(aW
) * aH
* 4;
5879 if (!len
.isValid() || len
.value() > INT32_MAX
) {
5880 aError
.ThrowIndexSizeError("Invalid width or height");
5884 // Create the fast typed array; it's initialized to 0 by default.
5885 JSObject
* darray
= Uint8ClampedArray::Create(aCx
, aContext
, len
.value());
5887 // TODO: Should use OOMReporter.
5888 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
5892 return do_AddRef(new ImageData(aW
, aH
, *darray
));
5895 already_AddRefed
<ImageData
> CanvasRenderingContext2D::CreateImageData(
5896 JSContext
* aCx
, int32_t aSw
, int32_t aSh
, ErrorResult
& aError
) {
5898 aError
.ThrowIndexSizeError("Invalid width or height");
5902 uint32_t w
= Abs(aSw
);
5903 uint32_t h
= Abs(aSh
);
5904 return dom::CreateImageData(aCx
, this, w
, h
, aError
);
5907 already_AddRefed
<ImageData
> CanvasRenderingContext2D::CreateImageData(
5908 JSContext
* aCx
, ImageData
& aImagedata
, ErrorResult
& aError
) {
5909 return dom::CreateImageData(aCx
, this, aImagedata
.Width(),
5910 aImagedata
.Height(), aError
);
5913 void CanvasRenderingContext2D::OnMemoryPressure() {
5914 if (mBufferProvider
) {
5915 mBufferProvider
->OnMemoryPressure();
5919 void CanvasRenderingContext2D::OnBeforePaintTransaction() {
5920 if (!mTarget
) return;
5924 void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); }
5926 bool CanvasRenderingContext2D::UpdateWebRenderCanvasData(
5927 nsDisplayListBuilder
* aBuilder
, WebRenderCanvasData
* aCanvasData
) {
5929 // If we're opaque then make sure we have a surface so we paint black
5930 // instead of transparent.
5934 // Don't call EnsureTarget() ... if there isn't already a surface, then
5935 // we have nothing to paint and there is no need to create a surface just
5936 // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
5937 // layer manager which must NOT happen during a paint.
5938 if (!mBufferProvider
&& !IsTargetValid()) {
5939 // No DidTransactionCallback will be received, so mark the context clean
5940 // now so future invalidations will be dispatched.
5942 // Clear CanvasRenderer of WebRenderCanvasData
5943 aCanvasData
->ClearCanvasRenderer();
5947 auto renderer
= aCanvasData
->GetCanvasRenderer();
5949 if (!mResetLayer
&& renderer
) {
5950 CanvasRendererData data
;
5951 data
.mContext
= this;
5952 data
.mSize
= GetSize();
5954 if (renderer
->IsDataValid(data
)) {
5959 renderer
= aCanvasData
->CreateCanvasRenderer();
5960 if (!InitializeCanvasRenderer(aBuilder
, renderer
)) {
5961 // Clear CanvasRenderer of WebRenderCanvasData
5962 aCanvasData
->ClearCanvasRenderer();
5966 MOZ_ASSERT(renderer
);
5967 mResetLayer
= false;
5971 bool CanvasRenderingContext2D::InitializeCanvasRenderer(
5972 nsDisplayListBuilder
* aBuilder
, CanvasRenderer
* aRenderer
) {
5973 CanvasRendererData data
;
5974 data
.mContext
= this;
5975 data
.mSize
= GetSize();
5976 data
.mIsOpaque
= mOpaque
;
5977 data
.mDoPaintCallbacks
= true;
5979 if (!mBufferProvider
) {
5980 // Force the creation of a buffer provider.
5983 if (!mBufferProvider
) {
5989 aRenderer
->Initialize(data
);
5990 aRenderer
->SetDirty();
5994 void CanvasRenderingContext2D::MarkContextClean() {
5995 if (mInvalidateCount
> 0) {
5996 mPredictManyRedrawCalls
= mInvalidateCount
> kCanvasMaxInvalidateCount
;
5998 mIsEntireFrameInvalid
= false;
5999 mInvalidateCount
= 0;
6002 void CanvasRenderingContext2D::GetAppUnitsValues(int32_t* aPerDevPixel
,
6003 int32_t* aPerCSSPixel
) {
6004 // If we don't have a canvas element, we just return something generic.
6011 PresShell
* presShell
= GetPresShell();
6015 nsPresContext
* presContext
= presShell
->GetPresContext();
6020 *aPerDevPixel
= presContext
->AppUnitsPerDevPixel();
6023 *aPerCSSPixel
= AppUnitsPerCSSPixel();
6027 void CanvasRenderingContext2D::SetWriteOnly() {
6029 if (mCanvasElement
) {
6030 mCanvasElement
->SetWriteOnly();
6034 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath
, mParent
)
6036 CanvasPath::CanvasPath(nsISupports
* aParent
) : mParent(aParent
) {
6038 gfxPlatform::ThreadLocalScreenReferenceDrawTarget()->CreatePathBuilder();
6041 CanvasPath::CanvasPath(nsISupports
* aParent
,
6042 already_AddRefed
<PathBuilder
> aPathBuilder
)
6043 : mParent(aParent
), mPathBuilder(aPathBuilder
) {
6044 if (!mPathBuilder
) {
6045 mPathBuilder
= gfxPlatform::ThreadLocalScreenReferenceDrawTarget()
6046 ->CreatePathBuilder();
6050 JSObject
* CanvasPath::WrapObject(JSContext
* aCx
,
6051 JS::Handle
<JSObject
*> aGivenProto
) {
6052 return Path2D_Binding::Wrap(aCx
, this, aGivenProto
);
6055 already_AddRefed
<CanvasPath
> CanvasPath::Constructor(
6056 const GlobalObject
& aGlobal
) {
6057 RefPtr
<CanvasPath
> path
= new CanvasPath(aGlobal
.GetAsSupports());
6058 return path
.forget();
6061 already_AddRefed
<CanvasPath
> CanvasPath::Constructor(
6062 const GlobalObject
& aGlobal
, CanvasPath
& aCanvasPath
) {
6063 RefPtr
<gfx::DrawTarget
> drawTarget
=
6064 gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
6065 RefPtr
<gfx::Path
> tempPath
=
6066 aCanvasPath
.GetPath(CanvasWindingRule::Nonzero
, drawTarget
.get());
6068 RefPtr
<CanvasPath
> path
=
6069 new CanvasPath(aGlobal
.GetAsSupports(), tempPath
->CopyToBuilder());
6070 return path
.forget();
6073 already_AddRefed
<CanvasPath
> CanvasPath::Constructor(
6074 const GlobalObject
& aGlobal
, const nsAString
& aPathString
) {
6075 RefPtr
<gfx::Path
> tempPath
= SVGContentUtils::GetPath(aPathString
);
6077 return Constructor(aGlobal
);
6080 RefPtr
<CanvasPath
> path
=
6081 new CanvasPath(aGlobal
.GetAsSupports(), tempPath
->CopyToBuilder());
6082 return path
.forget();
6085 void CanvasPath::ClosePath() {
6086 EnsurePathBuilder();
6088 mPathBuilder
->Close();
6091 void CanvasPath::MoveTo(double aX
, double aY
) {
6092 EnsurePathBuilder();
6094 mPathBuilder
->MoveTo(Point(ToFloat(aX
), ToFloat(aY
)));
6097 void CanvasPath::LineTo(double aX
, double aY
) {
6098 EnsurePathBuilder();
6100 mPathBuilder
->LineTo(Point(ToFloat(aX
), ToFloat(aY
)));
6103 void CanvasPath::QuadraticCurveTo(double aCpx
, double aCpy
, double aX
,
6105 EnsurePathBuilder();
6107 mPathBuilder
->QuadraticBezierTo(gfx::Point(ToFloat(aCpx
), ToFloat(aCpy
)),
6108 gfx::Point(ToFloat(aX
), ToFloat(aY
)));
6111 void CanvasPath::BezierCurveTo(double aCp1x
, double aCp1y
, double aCp2x
,
6112 double aCp2y
, double aX
, double aY
) {
6113 BezierTo(gfx::Point(ToFloat(aCp1x
), ToFloat(aCp1y
)),
6114 gfx::Point(ToFloat(aCp2x
), ToFloat(aCp2y
)),
6115 gfx::Point(ToFloat(aX
), ToFloat(aY
)));
6118 void CanvasPath::ArcTo(double aX1
, double aY1
, double aX2
, double aY2
,
6119 double aRadius
, ErrorResult
& aError
) {
6121 return aError
.ThrowIndexSizeError("Negative radius");
6124 EnsurePathBuilder();
6126 // Current point in user space!
6127 Point p0
= mPathBuilder
->CurrentPoint();
6131 // Execute these calculations in double precision to avoid cumulative
6133 double dir
, a2
, b2
, c2
, cosx
, sinx
, d
, anx
, any
, bnx
, bny
, x3
, y3
, x4
, y4
, cx
,
6137 if (p0
== p1
|| p1
== p2
|| aRadius
== 0) {
6142 // Check for colinearity
6143 dir
= (p2
.x
.value
- p1
.x
.value
) * (p0
.y
.value
- p1
.y
.value
) +
6144 (p2
.y
.value
- p1
.y
.value
) * (p1
.x
.value
- p0
.x
.value
);
6150 // XXX - Math for this code was already available from the non-azure code
6151 // and would be well tested. Perhaps converting to bezier directly might
6152 // be more efficient longer run.
6153 a2
= (p0
.x
- aX1
) * (p0
.x
- aX1
) + (p0
.y
- aY1
) * (p0
.y
- aY1
);
6154 b2
= (aX1
- aX2
) * (aX1
- aX2
) + (aY1
- aY2
) * (aY1
- aY2
);
6155 c2
= (p0
.x
- aX2
) * (p0
.x
- aX2
) + (p0
.y
- aY2
) * (p0
.y
- aY2
);
6156 cosx
= (a2
+ b2
- c2
) / (2 * sqrt(a2
* b2
));
6158 sinx
= sqrt(1 - cosx
* cosx
);
6159 d
= aRadius
/ ((1 - cosx
) / sinx
);
6161 anx
= (aX1
- p0
.x
) / sqrt(a2
);
6162 any
= (aY1
- p0
.y
) / sqrt(a2
);
6163 bnx
= (aX1
- aX2
) / sqrt(b2
);
6164 bny
= (aY1
- aY2
) / sqrt(b2
);
6169 anticlockwise
= (dir
< 0);
6170 cx
= x3
+ any
* aRadius
* (anticlockwise
? 1 : -1);
6171 cy
= y3
- anx
* aRadius
* (anticlockwise
? 1 : -1);
6172 angle0
= atan2((y3
- cy
), (x3
- cx
));
6173 angle1
= atan2((y4
- cy
), (x4
- cx
));
6177 Arc(cx
, cy
, aRadius
, angle0
, angle1
, anticlockwise
, aError
);
6180 void CanvasPath::Rect(double aX
, double aY
, double aW
, double aH
) {
6182 LineTo(aX
+ aW
, aY
);
6183 LineTo(aX
+ aW
, aY
+ aH
);
6184 LineTo(aX
, aY
+ aH
);
6188 void CanvasPath::RoundRect(
6189 double aX
, double aY
, double aW
, double aH
,
6190 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence
&
6192 ErrorResult
& aError
) {
6193 EnsurePathBuilder();
6195 RoundRectImpl(mPathBuilder
, Nothing(), aX
, aY
, aW
, aH
, aRadii
, aError
);
6198 void CanvasPath::Arc(double aX
, double aY
, double aRadius
, double aStartAngle
,
6199 double aEndAngle
, bool aAnticlockwise
,
6200 ErrorResult
& aError
) {
6201 if (aRadius
< 0.0) {
6202 return aError
.ThrowIndexSizeError("Negative radius");
6205 EnsurePathBuilder();
6207 ArcToBezier(this, Point(aX
, aY
), Size(aRadius
, aRadius
), aStartAngle
,
6208 aEndAngle
, aAnticlockwise
);
6211 void CanvasPath::Ellipse(double x
, double y
, double radiusX
, double radiusY
,
6212 double rotation
, double startAngle
, double endAngle
,
6213 bool anticlockwise
, ErrorResult
& aError
) {
6214 if (radiusX
< 0.0 || radiusY
< 0.0) {
6215 return aError
.ThrowIndexSizeError("Negative radius");
6218 EnsurePathBuilder();
6220 ArcToBezier(this, Point(x
, y
), Size(radiusX
, radiusY
), startAngle
, endAngle
,
6221 anticlockwise
, rotation
);
6224 void CanvasPath::LineTo(const gfx::Point
& aPoint
) {
6225 EnsurePathBuilder();
6227 mPathBuilder
->LineTo(aPoint
);
6230 void CanvasPath::BezierTo(const gfx::Point
& aCP1
, const gfx::Point
& aCP2
,
6231 const gfx::Point
& aCP3
) {
6232 EnsurePathBuilder();
6234 mPathBuilder
->BezierTo(aCP1
, aCP2
, aCP3
);
6237 void CanvasPath::AddPath(CanvasPath
& aCanvasPath
, const DOMMatrix2DInit
& aInit
,
6238 ErrorResult
& aError
) {
6239 RefPtr
<gfx::DrawTarget
> drawTarget
=
6240 gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
6241 RefPtr
<gfx::Path
> tempPath
=
6242 aCanvasPath
.GetPath(CanvasWindingRule::Nonzero
, drawTarget
.get());
6244 RefPtr
<DOMMatrixReadOnly
> matrix
=
6245 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit
, aError
);
6246 if (aError
.Failed()) {
6250 Matrix
transform(*(matrix
->GetInternal2D()));
6252 if (!transform
.IsFinite()) {
6256 if (!transform
.IsIdentity()) {
6257 RefPtr
<PathBuilder
> tempBuilder
=
6258 tempPath
->TransformedCopyToBuilder(transform
, FillRule::FILL_WINDING
);
6259 tempPath
= tempBuilder
->Finish();
6262 EnsurePathBuilder(); // in case a path is added to itself
6263 tempPath
->StreamToSink(mPathBuilder
);
6266 already_AddRefed
<gfx::Path
> CanvasPath::GetPath(
6267 const CanvasWindingRule
& aWinding
, const DrawTarget
* aTarget
) const {
6268 FillRule fillRule
= FillRule::FILL_WINDING
;
6269 if (aWinding
== CanvasWindingRule::Evenodd
) {
6270 fillRule
= FillRule::FILL_EVEN_ODD
;
6273 if (mPath
&& (mPath
->GetBackendType() == aTarget
->GetBackendType()) &&
6274 (mPath
->GetFillRule() == fillRule
)) {
6275 RefPtr
<gfx::Path
> path(mPath
);
6276 return path
.forget();
6280 // if there is no path, there must be a pathbuilder
6281 MOZ_ASSERT(mPathBuilder
);
6282 mPath
= mPathBuilder
->Finish();
6284 RefPtr
<gfx::Path
> path(mPath
);
6285 return path
.forget();
6288 mPathBuilder
= nullptr;
6291 // retarget our backend if we're used with a different backend
6292 if (mPath
->GetBackendType() != aTarget
->GetBackendType()) {
6293 RefPtr
<PathBuilder
> tmpPathBuilder
= aTarget
->CreatePathBuilder(fillRule
);
6294 mPath
->StreamToSink(tmpPathBuilder
);
6295 mPath
= tmpPathBuilder
->Finish();
6296 } else if (mPath
->GetFillRule() != fillRule
) {
6297 RefPtr
<PathBuilder
> tmpPathBuilder
= mPath
->CopyToBuilder(fillRule
);
6298 mPath
= tmpPathBuilder
->Finish();
6301 RefPtr
<gfx::Path
> path(mPath
);
6302 return path
.forget();
6305 void CanvasPath::EnsurePathBuilder() const {
6310 // if there is not pathbuilder, there must be a path
6312 mPathBuilder
= mPath
->CopyToBuilder();
6316 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D
* aContext
) {
6317 IntSize size
= aContext
->GetSize();
6319 // TODO: Bug 1552137: No memory will be allocated if either dimension is
6320 // greater than gfxPrefs::gfx_canvas_max_size(). We should check this here
6323 CheckedInt
<uint32_t> bytes
=
6324 CheckedInt
<uint32_t>(size
.width
) * size
.height
* 4;
6325 if (!bytes
.isValid()) {
6329 return bytes
.value();
6332 } // namespace mozilla::dom