Bug 1812932 - Add size outvar to GetImageBuffer. r=gfx-reviewers,bradwerth
[gecko.git] / dom / canvas / CanvasRenderingContext2D.cpp
blob50e64c217d36143fc3b672194cc3e8e04daa6e75
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"
28 #include "nsIFrame.h"
29 #include "nsError.h"
31 #include "nsCSSPseudoElements.h"
32 #include "nsComputedDOMStyle.h"
34 #include "nsPrintfCString.h"
36 #include "nsReadableUtils.h"
38 #include "nsColor.h"
39 #include "nsGfxCIID.h"
40 #include "nsIDocShell.h"
41 #include "nsPIDOMWindow.h"
42 #include "nsDisplayList.h"
43 #include "nsFocusManager.h"
45 #include "nsTArray.h"
47 #include "ImageEncoder.h"
48 #include "ImageRegion.h"
50 #include "gfxContext.h"
51 #include "gfxPlatform.h"
52 #include "gfxFont.h"
53 #include "gfxBlur.h"
54 #include "gfxTextRun.h"
55 #include "gfxUtils.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"
66 #include <algorithm>
68 #include "jsapi.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"
120 #include "Units.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
133 #ifdef XP_WIN
134 # include "gfxWindowsPlatform.h"
135 #endif
137 // windows.h (included by chromium code) defines this, in its infinite wisdom
138 #undef DrawText
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 {
160 public:
161 explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx) : mCtx(aCtx) {
162 mCtx->Save();
164 ~AutoSaveRestore() { mCtx->Restore(); }
166 private:
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;
176 public:
177 NS_DECL_ISUPPORTS
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.");
186 return NS_OK;
190 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
192 class CanvasConicGradient : public CanvasGradient {
193 public:
194 CanvasConicGradient(CanvasRenderingContext2D* aContext, Float aAngle,
195 const Point& aCenter)
196 : CanvasGradient(aContext, Type::CONIC),
197 mAngle(aAngle),
198 mCenter(aCenter) {}
200 const Float mAngle;
201 const Point mCenter;
204 class CanvasRadialGradient : public CanvasGradient {
205 public:
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) {}
215 Point mCenter1;
216 Point mCenter2;
217 Float mRadius1;
218 Float mRadius2;
221 class CanvasLinearGradient : public CanvasGradient {
222 public:
223 CanvasLinearGradient(CanvasRenderingContext2D* aContext, const Point& aBegin,
224 const Point& aEnd)
225 : CanvasGradient(aContext, Type::LINEAR), mBegin(aBegin), mEnd(aEnd) {}
227 protected:
228 friend struct CanvasBidiProcessor;
229 friend class CanvasGeneralPattern;
231 // Beginning of linear gradient.
232 Point mBegin;
233 // End of linear gradient.
234 Point mEnd;
237 bool CanvasRenderingContext2D::PatternIsOpaque(
238 CanvasRenderingContext2D::Style aStyle, bool* aIsColor) const {
239 const ContextState& state = CurrentState();
240 bool opaque = false;
241 bool color = false;
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
247 // colors.
248 // it's a color pattern.
249 opaque = sRGBColor::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
250 color = true;
253 if (aIsColor) {
254 *aIsColor = color;
256 return opaque;
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 {
263 public:
264 using Style = CanvasRenderingContext2D::Style;
265 using ContextState = CanvasRenderingContext2D::ContextState;
267 Pattern& ForStyle(CanvasRenderingContext2D* aCtx, Style aStyle,
268 DrawTarget* aRT) {
269 // This should only be called once or the mPattern destructor will
270 // not be executed.
271 NS_ASSERTION(
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) {
300 auto gradient =
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);
311 ExtendMode mode;
312 if (state.patternStyles[aStyle]->mRepeat ==
313 CanvasPattern::RepeatMode::NOREPEAT) {
314 mode = ExtendMode::CLAMP;
315 } else {
316 mode = ExtendMode::REPEAT;
319 SamplingFilter samplingFilter;
320 if (state.imageSmoothingEnabled) {
321 samplingFilter = SamplingFilter::GOOD;
322 } else {
323 samplingFilter = SamplingFilter::POINT;
326 mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
327 state.patternStyles[aStyle]->mTransform,
328 samplingFilter);
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 {
342 public:
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),
352 mCtx(aCtx),
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;
381 mCtx = nullptr;
382 mFinalTarget = nullptr;
383 return;
386 mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(),
387 SurfaceFormat::B8G8R8A8);
389 if (mTarget) {
390 // See bug 1524554.
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;
398 mCtx = nullptr;
399 mFinalTarget = nullptr;
400 return;
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()) {
411 return nullptr;
414 RefPtr<DrawTarget> dt = mFinalTarget->CreateSimilarDrawTarget(
415 aRect.Size(), SurfaceFormat::B8G8R8A8);
417 if (dt) {
418 // See bug 1524554.
419 dt->ClearRect(gfx::Rect());
422 if (!dt || !dt->IsValid()) {
423 aRect.SetEmpty();
424 return nullptr;
427 Matrix transform =
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() {
441 if (!mCtx) {
442 return;
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; }
472 private:
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 {
489 public:
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);
505 bounds.RoundOut();
506 if (!bounds.ToIntRect(&mTempRect) ||
507 !mFinalTarget->CanCreateSimilarDrawTarget(mTempRect.Size(),
508 SurfaceFormat::B8G8R8A8)) {
509 mTarget = mFinalTarget;
510 mCtx = nullptr;
511 mFinalTarget = nullptr;
512 return;
515 mTarget = mFinalTarget->CreateShadowDrawTarget(
516 mTempRect.Size(), SurfaceFormat::B8G8R8A8, mSigma);
518 if (mTarget) {
519 // See bug 1524554.
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;
527 mCtx = nullptr;
528 mFinalTarget = nullptr;
529 } else {
530 mTarget->SetTransform(
531 mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft()));
535 ~AdjustedTargetForShadow() {
536 if (!mCtx) {
537 return;
540 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
542 mFinalTarget->DrawSurfaceWithShadow(
543 snapshot, mTempRect.TopLeft(),
544 ShadowOptions(ToDeviceColor(mCtx->CurrentState().shadowColor),
545 mCtx->CurrentState().shadowOffset, mSigma),
546 mCompositionOp);
549 DrawTarget* DT() { return mTarget; }
551 gfx::IntPoint OffsetToFinalDT() { return mTempRect.TopLeft(); }
553 private:
554 RefPtr<DrawTarget> mTarget;
555 RefPtr<DrawTarget> mFinalTarget;
556 CanvasRenderingContext2D* mCtx;
557 Float mSigma;
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 {
575 public:
576 using ContextState = CanvasRenderingContext2D::ContextState;
578 explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
579 const gfx::Rect* aBounds = nullptr,
580 bool aAllowOptimization = false)
581 : mCtx(aCtx),
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
589 // need to render.
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()) {
596 return;
599 gfx::Rect bounds = maxSourceNeededBoundsForFilter;
600 if (aBounds) {
601 bounds = bounds.Intersect(*aBounds);
603 gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
604 if (!aCtx->IsTargetValid() || !boundsAfterFilter.IsFinite()) {
605 return;
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
612 // are used.
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;
623 return;
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()) {
638 return;
640 if (applyFilter) {
641 bounds.RoundOut();
643 if (!mTarget) {
644 mTarget = aCtx->mTarget;
646 gfx::IntRect intBounds;
647 if (!bounds.ToIntRect(&intBounds)) {
648 return;
650 mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
651 aCtx, mTarget, offsetToFinalDT, intBounds,
652 gfx::RoundedToInt(boundsAfterFilter), mUsedOperation);
653 mTarget = mFilterTarget->DT();
654 mUsedOperation = CompositionOp::OP_OVER;
656 if (!mTarget) {
657 mTarget = aCtx->mTarget;
661 ~AdjustedTarget() {
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,
702 &aStrokeOptions);
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,
713 &aStrokeOptions);
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,
727 &aStrokeOptions);
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);
753 private:
754 gfx::Rect MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds,
755 CanvasRenderingContext2D* aCtx) {
756 const bool applyFilter = aCtx->NeedToApplyFilter();
757 if (!aCtx->IsTargetValid()) {
758 return aDestBounds;
760 if (!applyFilter) {
761 return aDestBounds;
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()) {
779 return aDestBounds;
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
787 // draw both.
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()) {
795 return aBounds;
797 if (!applyFilter) {
798 return aBounds;
801 gfx::Rect bounds(aBounds);
802 bounds.RoundOut();
804 gfx::IntRect intBounds;
805 if (!bounds.ToIntRect(&intBounds)) {
806 return gfx::Rect();
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()) {
827 return;
829 const auto* matrix2D = matrix->GetInternal2D();
830 if (!matrix2D->IsFinite()) {
831 return;
833 mTransform = Matrix(*matrix2D);
836 void CanvasGradient::AddColorStop(float aOffset, const nsACString& aColorstr,
837 ErrorResult& aRv) {
838 if (aOffset < 0.0 || aOffset > 1.0) {
839 return aRv.ThrowIndexSizeError("Offset out of 0-1.0 range");
842 if (!mContext) {
843 return aRv.ThrowSyntaxError("No canvas context");
846 auto color = mContext->ParseColor(
847 aColorstr, CanvasRenderingContext2D::ResolveCurrentColor::No);
848 if (!color) {
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 {
865 public:
866 explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
867 : mCanvas(aCanvas) {}
869 void OnShutdown() {
870 if (!mCanvas) {
871 return;
874 mCanvas = nullptr;
875 nsContentUtils::UnregisterShutdownObserver(this);
878 NS_DECL_ISUPPORTS
879 NS_DECL_NSIOBSERVER
880 private:
881 ~CanvasShutdownObserver() = default;
883 CanvasRenderingContext2D* mCanvas;
886 NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
888 NS_IMETHODIMP
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();
893 OnShutdown();
896 return NS_OK;
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;
954 if (canvasElement) {
955 if (canvasElement->IsPurple()) {
956 canvasElement->RemovePurple();
958 dom::Element::MarkNodeChildren(canvasElement);
960 return true;
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)
976 NS_INTERFACE_MAP_END
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),
987 font(aOther.font),
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),
1001 op(aOther.op),
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,
1017 nscolor aColor) {
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
1046 mWidth(0),
1047 mHeight(0),
1048 mZero(false),
1049 mOpaqueAttrValue(false),
1050 mContextAttributesHasAlpha(true),
1051 mOpaque(false),
1052 mResetLayer(true),
1053 mIPC(false),
1054 mHasPendingStableStateCallback(false),
1055 mIsEntireFrameInvalid(false),
1056 mPredictManyRedrawCalls(false),
1057 mFrameCaptureState(FrameCaptureState::CLEAN,
1058 "CanvasRenderingContext2D::mFrameCaptureState"),
1059 mPathTransformWillUpdate(false),
1060 mInvalidateCount(0),
1061 mWriteOnly(false) {
1062 sNumLivingContexts.infallibleInit();
1063 sErrorTarget.infallibleInit();
1064 sNumLivingContexts.set(sNumLivingContexts.get() + 1);
1067 CanvasRenderingContext2D::~CanvasRenderingContext2D() {
1068 RemovePostRefreshObserver();
1069 RemoveShutdownObserver();
1070 ResetBitmap();
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;
1095 nscolor color;
1096 if (ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, &color,
1097 &wasCurrentColor, loader)) {
1098 result.mWasCurrentColor = wasCurrentColor;
1099 result.mColor.emplace(color);
1102 return result;
1105 Maybe<nscolor> CanvasRenderingContext2D::ParseColor(
1106 const nsACString& aString, ResolveCurrentColor aResolveCurrentColor) {
1107 auto entry = mColorStyleCache.Lookup(aString);
1108 if (!entry) {
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);
1119 if (canvasStyle) {
1120 return Some(canvasStyle->StyleText()->mColor.ToColor());
1123 return data.mColor;
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);
1139 mTarget = nullptr;
1140 if (aFreeBuffer) {
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;
1160 ResetBitmap();
1162 if (provider) {
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);
1187 if (!color) {
1188 return;
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];
1202 } else {
1203 StyleColorToString(state.colorStyles[aWhichStyle],
1204 aValue.SetAsUTF8String());
1208 // static
1209 void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor,
1210 nsACString& aStr) {
1211 aStr.Truncate();
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),
1216 NS_GET_B(aColor));
1217 } else {
1218 aStr.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor), NS_GET_G(aColor),
1219 NS_GET_B(aColor));
1220 aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
1221 aStr.Append(')');
1225 nsresult CanvasRenderingContext2D::Redraw() {
1226 mFrameCaptureState = FrameCaptureState::DIRTY;
1228 if (mIsEntireFrameInvalid) {
1229 return NS_OK;
1232 mIsEntireFrameInvalid = true;
1234 if (mCanvasElement) {
1235 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1236 mCanvasElement->InvalidateCanvasContent(nullptr);
1237 } else if (mOffscreenCanvas) {
1238 mOffscreenCanvas->QueueCommitToCompositor();
1239 } else {
1240 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1243 return NS_OK;
1246 void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
1247 mFrameCaptureState = FrameCaptureState::DIRTY;
1249 ++mInvalidateCount;
1251 if (mIsEntireFrameInvalid) {
1252 return;
1255 if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) {
1256 Redraw();
1257 return;
1260 if (mCanvasElement) {
1261 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1262 mCanvasElement->InvalidateCanvasContent(&aR);
1263 } else if (mOffscreenCanvas) {
1264 mOffscreenCanvas->QueueCommitToCompositor();
1265 } else {
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) {
1276 ++mInvalidateCount;
1277 return;
1280 gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
1281 Redraw(newr);
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();
1289 if (!snapshot) {
1290 return false;
1293 aTarget.CopySurface(snapshot, aCopyRect, IntPoint());
1294 aOld.ReturnSnapshot(snapshot.forget());
1295 return true;
1298 void CanvasRenderingContext2D::Demote() {}
1300 void CanvasRenderingContext2D::ScheduleStableStateCallback() {
1301 if (mHasPendingStableStateCallback) {
1302 return;
1304 mHasPendingStableStateCallback = true;
1306 nsContentUtils::RunInStableState(
1307 NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", this,
1308 &CanvasRenderingContext2D::OnStableState));
1311 void CanvasRenderingContext2D::OnStableState() {
1312 if (!mHasPendingStableStateCallback) {
1313 return;
1316 ReturnTarget();
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);
1347 } else {
1348 mTarget->SetTransform(clipOrTransform->transform);
1353 mClipsNeedConverting = false;
1356 bool CanvasRenderingContext2D::BorrowTarget(const IntRect& aPersistedRect,
1357 bool aNeedsClear) {
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)) {
1365 return false;
1367 mTarget = mBufferProvider->BorrowDrawTarget(aPersistedRect);
1368 if (!mTarget || !mTarget->IsValid()) {
1369 if (mTarget) {
1370 mBufferProvider->ReturnDrawTarget(mTarget.forget());
1372 return false;
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());
1380 return false;
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.
1387 if (aNeedsClear) {
1388 mTarget->ClearRect(gfx::Rect(mTarget->GetRect()));
1391 if (!mBufferProvider->PreservesDrawingState() || mBufferNeedsClear) {
1392 RestoreClipsAndTransformToTarget();
1394 mBufferNeedsClear = false;
1395 return true;
1398 bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
1399 bool aWillClear) {
1400 if (AlreadyShutDown()) {
1401 gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
1402 SetErrorState();
1403 return false;
1406 if (mTarget) {
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 ||
1413 mHeight < 0) {
1414 SetErrorState();
1415 return false;
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) {
1430 break;
1432 for (const auto& clipOrTransform : style.clipsAndTransforms) {
1433 if (clipOrTransform.IsClip()) {
1434 canDiscardContent = false;
1435 break;
1440 ScheduleStableStateCallback();
1442 IntRect persistedRect = canDiscardContent || mBufferNeedsClear
1443 ? IntRect()
1444 : IntRect(0, 0, mWidth, mHeight);
1446 // Attempt to reuse the existing buffer provider.
1447 if (BorrowTarget(persistedRect, !canDiscardContent)) {
1448 return true;
1451 RefPtr<DrawTarget> newTarget;
1452 RefPtr<PersistentBufferProvider> newProvider;
1454 if (!TryAcceleratedTarget(newTarget, newProvider) &&
1455 !TrySharedTarget(newTarget, newProvider) &&
1456 !TryBasicTarget(newTarget, newProvider)) {
1457 gfxCriticalError(
1458 CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
1459 << "Failed borrow shared and basic targets.";
1461 SetErrorState();
1462 return false;
1465 MOZ_ASSERT(newTarget);
1466 MOZ_ASSERT(newProvider);
1468 bool needsClear =
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);
1476 needsClear = false;
1479 // Try to copy data from the previous buffer provider if there is one.
1480 if (!canDiscardContent && mBufferProvider && !mBufferNeedsClear &&
1481 CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
1482 needsClear = false;
1485 if (needsClear) {
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.
1507 Redraw();
1508 mFrameCaptureState = captureState;
1510 return true;
1513 void CanvasRenderingContext2D::SetInitialState() {
1514 // Set up the initial canvas defaults
1515 mPathBuilder = nullptr;
1516 mPath = 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.
1540 SetInitialState();
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) {
1549 registered = true;
1550 RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
1554 void CanvasRenderingContext2D::AddZoneWaitingForGC() {
1555 JSObject* wrapper = GetWrapperPreserveColor();
1556 if (wrapper) {
1557 CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
1558 JS::GetObjectZone(wrapper));
1562 static WindowRenderer* WindowRendererFromCanvasElement(
1563 nsINode* aCanvasElement) {
1564 if (!aCanvasElement) {
1565 return nullptr;
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.
1578 return false;
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) {
1589 return false;
1591 aOutDT = DrawTargetWebgl::Create(GetSize(), GetSurfaceFormat());
1592 if (!aOutDT) {
1593 return false;
1596 aOutProvider = new PersistentBufferProviderAccelerated(aOutDT);
1597 return true;
1600 bool CanvasRenderingContext2D::TrySharedTarget(
1601 RefPtr<gfx::DrawTarget>& aOutDT,
1602 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1603 aOutDT = nullptr;
1604 aOutProvider = nullptr;
1606 if (!mCanvasElement) {
1607 return false;
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
1613 // provider.
1614 mClipsNeedConverting = true;
1615 return false;
1618 WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
1620 if (!renderer) {
1621 return false;
1624 aOutProvider =
1625 renderer->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat());
1627 if (!aOutProvider) {
1628 return false;
1631 // We can pass an empty persisted rect since we just created the buffer
1632 // provider (nothing to restore).
1633 aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
1634 MOZ_ASSERT(aOutDT);
1636 return !!aOutDT;
1639 bool CanvasRenderingContext2D::TryBasicTarget(
1640 RefPtr<gfx::DrawTarget>& aOutDT,
1641 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1642 aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
1643 GetSize(), GetSurfaceFormat());
1644 if (!aOutDT) {
1645 return false;
1648 // See Bug 1524554 - this forces DT initialization.
1649 aOutDT->ClearRect(gfx::Rect());
1651 if (!aOutDT->IsValid()) {
1652 aOutDT = nullptr;
1653 return false;
1656 aOutProvider = new PersistentBufferProviderBasic(aOutDT);
1657 return true;
1660 PersistentBufferProvider* CanvasRenderingContext2D::GetBufferProvider() {
1661 if (mBufferProvider && mBufferNeedsClear) {
1662 // Force the buffer to clear before it is used.
1663 EnsureTarget();
1665 return mBufferProvider;
1668 Maybe<SurfaceDescriptor> CanvasRenderingContext2D::GetFrontBuffer(
1669 WebGLFramebufferJS*, const bool webvr) {
1670 if (auto* provider = GetBufferProvider()) {
1671 return provider->GetFrontBuffer();
1673 return Nothing();
1676 PresShell* CanvasRenderingContext2D::GetPresShell() {
1677 if (mCanvasElement) {
1678 return mCanvasElement->OwnerDoc()->GetPresShell();
1680 if (mDocShell) {
1681 return mDocShell->GetPresShell();
1683 return nullptr;
1686 NS_IMETHODIMP
1687 CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) {
1688 // Zero sized surfaces can cause problems.
1689 mZero = false;
1690 if (aHeight == 0) {
1691 aHeight = 1;
1692 mZero = true;
1694 if (aWidth == 0) {
1695 aWidth = 1;
1696 mZero = true;
1699 ClearTarget(aWidth, aHeight);
1701 return NS_OK;
1704 void CanvasRenderingContext2D::AddAssociatedMemory() {
1705 JSObject* wrapper = GetWrapperMaybeDead();
1706 if (wrapper) {
1707 JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
1708 JS::MemoryUse::DOMBinding);
1712 void CanvasRenderingContext2D::RemoveAssociatedMemory() {
1713 JSObject* wrapper = GetWrapperMaybeDead();
1714 if (wrapper) {
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);
1725 mResetLayer = true;
1727 SetInitialState();
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();
1735 mWidth = aWidth;
1736 mHeight = aHeight;
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()) {
1750 return;
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);
1757 if (canvasStyle) {
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()) {
1772 mTarget->PopClip();
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.
1781 mTarget->PopClip();
1784 mTarget->SetTransform(Matrix());
1787 mBufferProvider->ReturnDrawTarget(mTarget.forget());
1791 NS_IMETHODIMP
1792 CanvasRenderingContext2D::InitializeWithDrawTarget(
1793 nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) {
1794 RemovePostRefreshObserver();
1795 mDocShell = aShell;
1796 AddPostRefreshObserverIfNecessary();
1798 IntSize size = aTarget->GetSize();
1799 SetDimensions(size.width, size.height);
1801 mTarget = aTarget;
1802 mBufferProvider = new PersistentBufferProviderBasic(aTarget);
1804 RestoreClipsAndTransformToTarget();
1806 return NS_OK;
1809 void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(
1810 bool aOpaqueAttrValue) {
1811 if (aOpaqueAttrValue != mOpaqueAttrValue) {
1812 mOpaqueAttrValue = aOpaqueAttrValue;
1813 UpdateIsOpaque();
1817 void CanvasRenderingContext2D::UpdateIsOpaque() {
1818 mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue;
1819 ClearTarget();
1822 NS_IMETHODIMP
1823 CanvasRenderingContext2D::SetContextOptions(JSContext* aCx,
1824 JS::Handle<JS::Value> aOptions,
1825 ErrorResult& aRvForDictionaryInit) {
1826 if (aOptions.isNullOrUndefined()) {
1827 return NS_OK;
1830 // This shouldn't be called before drawing starts, so there should be no
1831 // drawtarget yet
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;
1843 UpdateIsOpaque();
1845 return NS_OK;
1848 UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
1849 int32_t* out_format, gfx::IntSize* out_imageSize) {
1850 UniquePtr<uint8_t[]> ret;
1852 *out_format = 0;
1853 *out_imageSize = {};
1855 if (!GetBufferProvider() && !EnsureTarget()) {
1856 return nullptr;
1859 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
1860 if (snapshot) {
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());
1871 return ret;
1874 NS_IMETHODIMP
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());
1881 if (!encoder) {
1882 return NS_ERROR_FAILURE;
1885 int32_t format = 0;
1886 gfx::IntSize imageSize = {};
1887 UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format, &imageSize);
1888 if (!imageBuffer) {
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()) {
1907 MOZ_ASSERT(
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;
1928 // state
1931 void CanvasRenderingContext2D::Save() {
1932 EnsureTarget();
1933 if (MOZ_UNLIKELY(!mTarget || mStyleStack.IsEmpty())) {
1934 SetErrorState();
1935 return;
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
1943 // reasonable code.
1944 mStyleStack.RemoveElementAt(0);
1948 void CanvasRenderingContext2D::Restore() {
1949 if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) {
1950 return;
1953 TransformWillUpdate();
1954 if (!IsTargetValid()) {
1955 return;
1958 for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
1959 if (clipOrTransform.IsClip()) {
1960 mTarget->PopClip();
1964 mStyleStack.RemoveLastElement();
1966 mTarget->SetTransform(CurrentState().transform);
1970 // transformations
1973 void CanvasRenderingContext2D::Scale(double aX, double aY,
1974 ErrorResult& aError) {
1975 TransformWillUpdate();
1976 if (!IsTargetValid()) {
1977 aError.Throw(NS_ERROR_FAILURE);
1978 return;
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);
1991 return;
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);
2004 return;
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);
2019 return;
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) {
2030 EnsureTarget();
2031 if (!IsTargetValid()) {
2032 aError.Throw(NS_ERROR_FAILURE);
2033 return nullptr;
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);
2047 return;
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);
2058 return;
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()) {
2070 return;
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));
2078 } else {
2079 // If the last item is a transform we can replace it instead of appending
2080 // a new item.
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);
2091 // colors
2094 void CanvasRenderingContext2D::SetStyleFromUnion(
2095 const UTF8StringOrCanvasGradientOrCanvasPattern& aValue,
2096 Style aWhichStyle) {
2097 if (aValue.IsUTF8String()) {
2098 SetStyleFromString(aValue.GetAsUTF8String(), aWhichStyle);
2099 return;
2102 if (aValue.IsCanvasGradient()) {
2103 SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle);
2104 return;
2107 if (aValue.IsCanvasPattern()) {
2108 CanvasPattern& pattern = aValue.GetAsCanvasPattern();
2109 SetStyleFromPattern(pattern, aWhichStyle);
2110 if (pattern.mForceWriteOnly) {
2111 SetWriteOnly();
2113 return;
2116 MOZ_ASSERT_UNREACHABLE("Invalid union value");
2119 void CanvasRenderingContext2D::SetFillRule(const nsAString& aString) {
2120 FillRule rule;
2122 if (aString.EqualsLiteral("evenodd"))
2123 rule = FillRule::FILL_EVEN_ODD;
2124 else if (aString.EqualsLiteral("nonzero"))
2125 rule = FillRule::FILL_WINDING;
2126 else
2127 return;
2129 CurrentState().fillRule = rule;
2132 void CanvasRenderingContext2D::GetFillRule(nsAString& aString) {
2133 switch (CurrentState().fillRule) {
2134 case FillRule::FILL_WINDING:
2135 aString.AssignLiteral("nonzero");
2136 break;
2137 case FillRule::FILL_EVEN_ODD:
2138 aString.AssignLiteral("evenodd");
2139 break;
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");
2158 return nullptr;
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,
2171 Point(aCx, aCy));
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;
2187 } else {
2188 aError.ThrowSyntaxError("Invalid pattern keyword");
2189 return nullptr;
2192 Element* element = nullptr;
2193 OffscreenCanvas* offscreenCanvas = nullptr;
2195 if (aSource.IsHTMLCanvasElement()) {
2196 HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement();
2197 element = canvas;
2199 nsIntSize size = canvas->GetSize();
2200 if (size.width == 0) {
2201 aError.ThrowInvalidStateError("Passed-in canvas has width 0");
2202 return nullptr;
2205 if (size.height == 0) {
2206 aError.ThrowInvalidStateError("Passed-in canvas has height 0");
2207 return nullptr;
2210 // Special case for Canvas, which could be an Azure canvas!
2211 nsICanvasRenderingContextInternal* srcCanvas = canvas->GetCurrentContext();
2212 if (srcCanvas) {
2213 // This might not be an Azure canvas!
2214 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
2215 if (!srcSurf) {
2216 aError.ThrowInvalidStateError(
2217 "CanvasRenderingContext2D.createPattern() failed to snapshot source"
2218 "canvas.");
2219 return nullptr;
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();
2230 element = img;
2231 } else if (aSource.IsSVGImageElement()) {
2232 SVGImageElement* img = &aSource.GetAsSVGImageElement();
2233 element = img;
2234 } else if (aSource.IsHTMLVideoElement()) {
2235 auto& video = aSource.GetAsHTMLVideoElement();
2236 video.LogVisibility(
2237 mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
2238 element = &video;
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");
2245 return nullptr;
2248 if (size.height == 0) {
2249 aError.ThrowInvalidStateError("Passed-in canvas has height 0");
2250 return nullptr;
2253 nsICanvasRenderingContextInternal* srcCanvas =
2254 offscreenCanvas->GetContext();
2255 if (srcCanvas) {
2256 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
2257 if (!srcSurf) {
2258 aError.ThrowInvalidStateError(
2259 "Passed-in canvas failed to create snapshot");
2260 return nullptr;
2263 RefPtr<CanvasPattern> pat = new CanvasPattern(
2264 this, srcSurf, repeatMode, srcCanvas->PrincipalOrNull(),
2265 offscreenCanvas->IsWriteOnly(), false);
2267 return pat.forget();
2269 } else {
2270 // Special case for ImageBitmap
2271 ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
2272 EnsureTarget();
2273 if (!IsTargetValid()) {
2274 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2275 return nullptr;
2277 RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
2278 if (!srcSurf) {
2279 aError.ThrowInvalidStateError(
2280 "Passed-in ImageBitmap has been transferred");
2281 return nullptr;
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();
2293 EnsureTarget();
2294 if (!IsTargetValid()) {
2295 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2296 return nullptr;
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 =
2304 offscreenCanvas
2305 ? nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, flags,
2306 mTarget)
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\"");
2317 return nullptr;
2320 if (res.mSize.width == 0 || res.mSize.height == 0) {
2321 return nullptr;
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();
2329 if (!surface) {
2330 return nullptr;
2333 RefPtr<CanvasPattern> pat =
2334 new CanvasPattern(this, surface, repeatMode, res.mPrincipal,
2335 res.mIsWriteOnly, res.mCORSUsed);
2336 return pat.forget();
2340 // shadows
2342 void CanvasRenderingContext2D::SetShadowColor(const nsACString& aShadowColor) {
2343 Maybe<nscolor> color = ParseColor(aShadowColor);
2344 if (!color) {
2345 return;
2348 CurrentState().shadowColor = *color;
2352 // filters
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.
2366 return nullptr;
2369 // From canvas spec, force to set line-height property to 'normal' font
2370 // property.
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,
2376 env.mRuleType, {});
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.
2394 return nullptr;
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)) {
2402 return nullptr;
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);
2412 if (!parentStyle) {
2413 // The flush killed the shell, so we couldn't get any meaningful style
2414 // back.
2415 aError.Throw(NS_ERROR_FAILURE);
2416 return nullptr;
2418 } else {
2419 RefPtr<RawServoDeclarationBlock> declarations =
2420 CreateFontDeclarationForServo("10px sans-serif"_ns,
2421 aPresShell->GetDocument());
2422 MOZ_ASSERT(declarations);
2424 parentStyle =
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);
2452 return sc.forget();
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.
2468 return nullptr;
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)) {
2475 return nullptr;
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);
2491 return false;
2494 nsAutoCString usedFont; // unused
2496 RefPtr<const ComputedStyle> parentStyle = GetFontStyleForServo(
2497 mCanvasElement, GetFont(), presShell, usedFont, aError);
2498 if (!parentStyle) {
2499 return false;
2502 RefPtr<const ComputedStyle> style =
2503 ResolveFilterStyleForServo(aString, parentStyle, presShell, aError);
2504 if (!style) {
2505 return false;
2508 aFilterChain = style->StyleEffects()->mFilters;
2509 return true;
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());
2522 UpdateFilter();
2527 class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize {
2528 public:
2529 CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont,
2530 nsAtom* aFontLanguage, bool aExplicitLanguage,
2531 nsPresContext* aPresContext)
2532 : mSize(aSize),
2533 mFont(aFont),
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); }
2556 private:
2557 gfx::IntSize 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
2566 // with.
2567 static bool FiltersNeedFrameFlush(Span<const StyleFilter> aFilters) {
2568 for (const auto& filter : aFilters) {
2569 if (filter.IsUrl()) {
2570 return true;
2573 return false;
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();
2584 return;
2587 if (FiltersNeedFrameFlush(CurrentState().filterChain.AsSpan())) {
2588 presShell->FlushPendingNotifications(FlushType::Frames);
2591 MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty());
2592 if (MOZ_UNLIKELY(presShell->IsDestroying())) {
2593 return;
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;
2610 // rects
2613 static bool ValidateRect(double& aX, double& aY, double& aWidth,
2614 double& aHeight, bool aIsZeroSizeValid) {
2615 if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) {
2616 return false;
2619 // bug 1018527
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)) {
2625 return false;
2628 // bug 1074733
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.
2633 if (aWidth < 0) {
2634 aWidth = -aWidth;
2635 aX -= aWidth;
2637 if (aHeight < 0) {
2638 aHeight = -aHeight;
2639 aY -= aHeight;
2641 return true;
2644 void CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW,
2645 double aH) {
2646 // Do not allow zeros - it's a no-op at that point per spec.
2647 if (!ValidateRect(aX, aY, aW, aH, false)) {
2648 return;
2651 gfx::Rect clearRect(aX, aY, aW, aH);
2653 EnsureTarget(&clearRect, true);
2654 if (!IsTargetValid()) {
2655 return;
2658 mTarget->ClearRect(clearRect);
2660 RedrawUser(gfxRect(aX, aY, aW, aH));
2663 void CanvasRenderingContext2D::FillRect(double aX, double aY, double aW,
2664 double aH) {
2665 if (!ValidateRect(aX, aY, aW, aH, true)) {
2666 return;
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.
2698 if (limitx) {
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);
2703 if (limity) {
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);
2710 state = nullptr;
2712 bool isColor;
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()) {
2719 return;
2722 gfx::Rect bounds;
2723 const bool needBounds = NeedToCalculateBounds();
2724 if (!IsTargetValid()) {
2725 return;
2727 if (needBounds) {
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();
2737 if (!target) {
2738 return;
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,
2748 double aH) {
2749 if (!aW && !aH) {
2750 return;
2753 if (!ValidateRect(aX, aY, aW, aH, true)) {
2754 return;
2757 EnsureTarget();
2758 if (!IsTargetValid()) {
2759 return;
2762 const bool needBounds = NeedToCalculateBounds();
2763 if (!IsTargetValid()) {
2764 return;
2767 gfx::Rect bounds;
2768 if (needBounds) {
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()) {
2776 return;
2779 if (!aH) {
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();
2786 if (!target) {
2787 return;
2790 const ContextState& state = CurrentState();
2791 target.StrokeLine(
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(),
2796 state.dashOffset),
2797 DrawOptions(state.globalAlpha, op));
2798 return;
2801 if (!aW) {
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();
2808 if (!target) {
2809 return;
2812 const ContextState& state = CurrentState();
2813 target.StrokeLine(
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(),
2818 state.dashOffset),
2819 DrawOptions(state.globalAlpha, op));
2820 return;
2823 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
2824 auto op = target.UsedOperation();
2825 if (!target) {
2826 return;
2829 const ContextState& state = CurrentState();
2830 target.StrokeRect(
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));
2838 Redraw();
2842 // path bits
2845 void CanvasRenderingContext2D::BeginPath() {
2846 mPath = nullptr;
2847 mPathBuilder = nullptr;
2848 mDSPathBuilder = nullptr;
2849 mPathTransformWillUpdate = false;
2852 void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) {
2853 EnsureUserSpacePath(aWinding);
2855 if (!mPath) {
2856 return;
2859 const bool needBounds = NeedToCalculateBounds();
2860 if (!IsTargetValid()) {
2861 return;
2863 gfx::Rect bounds;
2864 if (needBounds) {
2865 bounds = mPath->GetBounds(mTarget->GetTransform());
2868 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
2869 if (!target) {
2870 return;
2873 auto op = target.UsedOperation();
2874 if (!IsTargetValid() || !target) {
2875 return;
2877 target.Fill(mPath,
2878 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
2879 DrawOptions(CurrentState().globalAlpha, op));
2880 Redraw();
2883 void CanvasRenderingContext2D::Fill(const CanvasPath& aPath,
2884 const CanvasWindingRule& aWinding) {
2885 EnsureTarget();
2886 if (!IsTargetValid()) {
2887 return;
2890 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
2891 if (!gfxpath) {
2892 return;
2895 const bool needBounds = NeedToCalculateBounds();
2896 if (!IsTargetValid()) {
2897 return;
2899 gfx::Rect bounds;
2900 if (needBounds) {
2901 bounds = gfxpath->GetBounds(mTarget->GetTransform());
2904 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
2905 if (!target) {
2906 return;
2909 auto op = target.UsedOperation();
2910 if (!IsTargetValid() || !target) {
2911 return;
2913 target.Fill(gfxpath,
2914 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
2915 DrawOptions(CurrentState().globalAlpha, op));
2916 Redraw();
2919 void CanvasRenderingContext2D::Stroke() {
2920 EnsureUserSpacePath();
2922 if (!mPath) {
2923 return;
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);
2930 state = nullptr;
2932 const bool needBounds = NeedToCalculateBounds();
2933 if (!IsTargetValid()) {
2934 return;
2936 gfx::Rect bounds;
2937 if (needBounds) {
2938 bounds = mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
2941 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
2942 if (!target) {
2943 return;
2946 auto op = target.UsedOperation();
2947 if (!IsTargetValid() || !target) {
2948 return;
2950 target.Stroke(mPath,
2951 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2952 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
2953 Redraw();
2956 void CanvasRenderingContext2D::Stroke(const CanvasPath& aPath) {
2957 EnsureTarget();
2958 if (!IsTargetValid()) {
2959 return;
2962 RefPtr<gfx::Path> gfxpath =
2963 aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
2965 if (!gfxpath) {
2966 return;
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);
2973 state = nullptr;
2975 const bool needBounds = NeedToCalculateBounds();
2976 if (!IsTargetValid()) {
2977 return;
2979 gfx::Rect bounds;
2980 if (needBounds) {
2981 bounds = gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
2984 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
2985 if (!target) {
2986 return;
2989 auto op = target.UsedOperation();
2990 if (!IsTargetValid() || !target) {
2991 return;
2993 target.Stroke(gfxpath,
2994 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2995 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
2996 Redraw();
2999 void CanvasRenderingContext2D::DrawFocusIfNeeded(
3000 mozilla::dom::Element& aElement, ErrorResult& aRv) {
3001 EnsureUserSpacePath();
3002 if (!mPath) {
3003 return;
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));
3025 state = nullptr;
3027 // draw the focus ring
3028 Stroke();
3029 if (!mPath) {
3030 return;
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);
3038 return;
3042 // set the foreground focus color
3043 CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0, 0, 0, 255));
3044 // draw the focus ring
3045 Stroke();
3046 if (!mPath) {
3047 return;
3052 bool CanvasRenderingContext2D::DrawCustomFocusRing(Element& aElement) {
3053 if (!aElement.State().HasState(ElementState::FOCUSRING)) {
3054 return false;
3057 HTMLCanvasElement* canvas = GetCanvas();
3058 if (!canvas || !aElement.IsInclusiveDescendantOf(canvas)) {
3059 return false;
3062 EnsureUserSpacePath();
3063 return true;
3066 void CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding) {
3067 EnsureUserSpacePath(aWinding);
3069 if (!mPath) {
3070 return;
3073 mTarget->PushClip(mPath);
3074 CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath));
3077 void CanvasRenderingContext2D::Clip(const CanvasPath& aPath,
3078 const CanvasWindingRule& aWinding) {
3079 EnsureTarget();
3080 if (!IsTargetValid()) {
3081 return;
3084 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
3086 if (!gfxpath) {
3087 return;
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) {
3097 if (aRadius < 0) {
3098 return aError.ThrowIndexSizeError("Negative radius");
3101 EnsureWritablePath();
3103 // Current point in user space!
3104 Point p0;
3105 if (mPathBuilder) {
3106 p0 = mPathBuilder->CurrentPoint();
3107 } else {
3108 Matrix invTransform = mTarget->GetTransform();
3109 if (!invTransform.Invert()) {
3110 return;
3113 p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint());
3116 Point p1(aX1, aY1);
3117 Point p2(aX2, aY2);
3119 // Execute these calculations in double precision to avoid cumulative
3120 // rounding errors.
3121 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
3122 cy, angle0, angle1;
3123 bool anticlockwise;
3125 if (p0 == p1 || p1 == p2 || aRadius == 0) {
3126 LineTo(p1.x, p1.y);
3127 return;
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);
3133 if (dir == 0) {
3134 LineTo(p1.x, p1.y);
3135 return;
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);
3153 x3 = aX1 - anx * d;
3154 y3 = aY1 - any * d;
3155 x4 = aX1 - bnx * d;
3156 y4 = aY1 - bny * d;
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));
3163 LineTo(x3, y3);
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) {
3171 if (aR < 0.0) {
3172 return aError.ThrowIndexSizeError("Negative radius");
3175 EnsureWritablePath();
3177 ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle,
3178 aAnticlockwise);
3181 void CanvasRenderingContext2D::Rect(double aX, double aY, double aW,
3182 double aH) {
3183 EnsureWritablePath();
3185 if (mPathBuilder) {
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();
3191 } else {
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&
3209 aRadii,
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)) {
3213 return;
3216 nsTArray<OwningUnrestrictedDoubleOrDOMPointInit> radii;
3217 // Step 2. If radii is an unrestricted double or DOMPointInit, then set radii
3218 // to « radii ».
3219 if (aRadii.IsUnrestrictedDouble()) {
3220 radii.AppendElement()->SetAsUnrestrictedDouble() =
3221 aRadii.GetAsUnrestrictedDouble();
3222 } else if (aRadii.IsDOMPointInit()) {
3223 radii.AppendElement()->SetAsDOMPointInit() = aRadii.GetAsDOMPointInit();
3224 } else {
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");
3230 return;
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
3243 // return.
3244 if (!IsFinite(point.mX) || !IsFinite(point.mY)) {
3245 return;
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");
3252 return;
3255 // Step 5.1.3. Otherwise, append radius to
3256 // normalizedRadii.
3257 normalizedRadii.AppendElement(
3258 Size(gfx::Float(point.mX), gfx::Float(point.mY)));
3259 continue;
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.
3265 if (!IsFinite(r)) {
3266 return;
3269 // Step 5.2.2. If radius is negative, then throw a RangeError.
3270 if (r < 0) {
3271 aError.ThrowRangeError("Radius can not be negative");
3272 return;
3275 // Step 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to
3276 // normalizedRadii.
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];
3307 } else {
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;
3322 if (w < 0) {
3323 // Horizontal flip
3324 clockwise = false;
3325 x += w;
3326 w = -w;
3327 std::swap(upperLeft, upperRight);
3328 std::swap(lowerLeft, lowerRight);
3331 if (h < 0) {
3332 // Vertical flip
3333 clockwise = !clockwise;
3334 y += h;
3335 h = -h;
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.
3355 if (scale < 1.0f) {
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
3365 // borders etc.
3366 gfx::Rect rect{x, y, w, h};
3367 RectCornerRadii cornerRadii(upperLeft, upperRight, lowerRight, lowerLeft);
3368 AppendRoundedRectToPath(aPathBuilder, rect, cornerRadii, clockwise,
3369 aTransform);
3371 // Step 14. Create a new subpath with the point (x, y) as the only point in
3372 // the subpath.
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&
3379 aRadii,
3380 ErrorResult& aError) {
3381 EnsureWritablePath();
3383 PathBuilder* builder = mPathBuilder;
3384 Maybe<Matrix> transform = Nothing();
3385 if (!builder) {
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() {
3409 EnsureTarget();
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) {
3414 return;
3417 FillRule fillRule = CurrentState().fillRule;
3419 if (mPathBuilder) {
3420 if (mPathTransformWillUpdate) {
3421 mPath = mPathBuilder->Finish();
3422 mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3423 mPath = nullptr;
3424 mPathBuilder = nullptr;
3425 mPathTransformWillUpdate = false;
3427 return;
3430 if (!mPath) {
3431 NS_ASSERTION(
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);
3437 } else {
3438 mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3439 mPathTransformWillUpdate = false;
3440 mPath = nullptr;
3444 void CanvasRenderingContext2D::EnsureUserSpacePath(
3445 const CanvasWindingRule& aWinding) {
3446 FillRule fillRule = CurrentState().fillRule;
3447 if (aWinding == CanvasWindingRule::Evenodd)
3448 fillRule = FillRule::FILL_EVEN_ODD;
3450 EnsureTarget();
3451 if (!IsTargetValid()) {
3452 return;
3455 if (!mPath && !mPathBuilder && !mDSPathBuilder) {
3456 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3459 if (mPathBuilder) {
3460 mPath = mPathBuilder->Finish();
3461 mPathBuilder = nullptr;
3464 if (mPath && mPathTransformWillUpdate) {
3465 mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3466 mPath = nullptr;
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");
3478 return;
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() {
3496 EnsureTarget();
3497 if (!IsTargetValid()) {
3498 return;
3501 // Store the matrix that would transform the current path to device
3502 // space.
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;
3516 // text
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();
3527 if (!presShell) {
3528 return SetFontInternalDisconnected(aFont, aError);
3531 nsPresContext* c = presShell->GetPresContext();
3532 FontStyleCacheKey key{aFont, c->RestyleManager()->GetRestyleGeneration()};
3533 auto entry = mFontStyleCache.Lookup(key);
3534 if (!entry) {
3535 FontStyleData newData;
3536 newData.mKey = key;
3537 newData.mStyle = GetFontStyleForServo(mCanvasElement, aFont, presShell,
3538 newData.mUsedFont, aError);
3539 entry.Set(newData);
3542 const auto& data = entry.Data();
3543 if (!data.mStyle) {
3544 return false;
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.
3563 resizedFont.size =
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;
3588 return true;
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();
3633 } else {
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
3646 // shutdown.
3647 aError.ThrowInvalidStateError("Missing URLExtraData");
3648 return false;
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;
3662 float size = 0.0f;
3663 if (!ServoCSSParser::ParseFontShorthandForMatching(
3664 aFont, urlExtraData, list, fontStyle.style, fontStyle.stretch,
3665 fontStyle.weight, &size)) {
3666 return false;
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:
3675 setting.mValue = 0;
3676 fontStyle.featureSettings.AppendElement(setting);
3677 break;
3678 case FontKerning::NORMAL:
3679 setting.mValue = 1;
3680 fontStyle.featureSettings.AppendElement(setting);
3681 break;
3682 default:
3683 // auto case implies use user agent default
3684 break;
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();
3692 if (language) {
3693 explicitLanguage = true;
3694 } else {
3695 language = mCanvasElement->OwnerDoc()->GetLanguageForStyle();
3697 } else {
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;
3720 return true;
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");
3740 break;
3741 case TextAlign::END:
3742 aTextAlign.AssignLiteral("end");
3743 break;
3744 case TextAlign::LEFT:
3745 aTextAlign.AssignLiteral("left");
3746 break;
3747 case TextAlign::RIGHT:
3748 aTextAlign.AssignLiteral("right");
3749 break;
3750 case TextAlign::CENTER:
3751 aTextAlign.AssignLiteral("center");
3752 break;
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");
3775 break;
3776 case TextBaseline::HANGING:
3777 aTextBaseline.AssignLiteral("hanging");
3778 break;
3779 case TextBaseline::MIDDLE:
3780 aTextBaseline.AssignLiteral("middle");
3781 break;
3782 case TextBaseline::ALPHABETIC:
3783 aTextBaseline.AssignLiteral("alphabetic");
3784 break;
3785 case TextBaseline::IDEOGRAPHIC:
3786 aTextBaseline.AssignLiteral("ideographic");
3787 break;
3788 case TextBaseline::BOTTOM:
3789 aTextBaseline.AssignLiteral("bottom");
3790 break;
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");
3808 break;
3809 case TextDirection::RTL:
3810 aDirection.AssignLiteral("rtl");
3811 break;
3812 case TextDirection::INHERIT:
3813 aDirection.AssignLiteral("inherit");
3814 break;
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");
3836 break;
3837 case FontKerning::NORMAL:
3838 aFontKerning.AssignLiteral("normal");
3839 break;
3840 case FontKerning::NONE:
3841 aFontKerning.AssignLiteral("none");
3842 break;
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,
3858 double aY,
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,
3867 double aY,
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,
3879 aError);
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(),
3891 mCtx(nullptr),
3892 mFontgrp(nullptr),
3893 mAppUnitsPerDevPixel(0),
3894 mOp(CanvasRenderingContext2D::TextDrawOperation::FILL),
3895 mTextRunFlags(),
3896 mSetTextCount(0),
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));
3919 return;
3921 mSetTextCount++;
3922 auto* pfl = gfxPlatformFontList::PlatformFontList();
3923 pfl->Lock();
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;
3930 } else {
3931 flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL;
3933 mTextRun = mFontgrp->MakeTextRun(
3934 aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags,
3935 nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts,
3936 mMissingFonts.get());
3937 pfl->Unlock();
3940 nscoord GetWidth() override {
3941 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
3942 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
3943 : gfxFont::LOOSE_INK_EXTENTS,
3944 mDrawTarget);
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();
3961 switch (type) {
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);
3966 break;
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);
3973 break;
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);
3979 break;
3981 default:
3982 MOZ_ASSERT(false, "Should be linear, radial or conic gradient.");
3983 return nullptr;
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;
4004 default:
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
4026 if (rtl) {
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,
4035 mDrawTarget);
4036 inlineCoord += textRunMetrics.mAdvanceWidth;
4037 // old code was:
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()) {
4045 return;
4048 // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
4049 // appropriately.
4050 StrokeOptions strokeOpts;
4051 DrawOptions drawOpts;
4052 Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
4053 ? Style::FILL
4054 : Style::STROKE;
4055 const ContextState& state = mCtx->CurrentState();
4057 gfx::Rect bounds;
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);
4068 if (!target) {
4069 return;
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));
4081 } else {
4082 params.textStrokeColor = fontColor;
4084 } else {
4085 if (state.gradientStyles[style]) { // Gradient
4086 pattern = GetGradientFor(style);
4087 } else if (state.patternStyles[style]) { // Pattern
4088 pattern = GetPatternFor(style);
4089 } else {
4090 MOZ_ASSERT(false, "Should never reach here.");
4091 return;
4093 MOZ_ASSERT(pattern, "No valid pattern.");
4095 if (style == Style::FILL) {
4096 params.context->SetPattern(pattern);
4097 } else {
4098 params.textStrokePattern = pattern;
4102 drawOpts.mAlpha = state.globalAlpha;
4103 drawOpts.mCompositionOp = target.UsedOperation();
4104 if (!mCtx->IsTargetValid()) {
4105 return;
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);
4127 // current text run
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
4137 gfx::Point mPt;
4139 // current font
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;
4183 return nullptr;
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);
4204 if (!canvasStyle) {
4205 aError = NS_ERROR_FAILURE;
4206 return nullptr;
4210 // Get text direction, either from the property or inherited from context.
4211 const ContextState& state = CurrentState();
4212 bool isRTL;
4213 switch (state.textDirection) {
4214 case TextDirection::LTR:
4215 isRTL = false;
4216 break;
4217 case TextDirection::RTL:
4218 isRTL = true;
4219 break;
4220 case TextDirection::INHERIT:
4221 if (canvasStyle) {
4222 isRTL =
4223 canvasStyle->StyleVisibility()->mDirection == StyleDirection::Rtl;
4224 } else if (document) {
4225 isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) ==
4226 IBMBIDI_TEXTDIRECTION_RTL;
4227 } else {
4228 isRTL = false;
4230 break;
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;
4237 return nullptr;
4240 nsPresContext* presContext =
4241 presShell ? presShell->GetPresContext() : nullptr;
4243 if (presContext) {
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) {
4250 aError = NS_OK;
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,
4253 0.0, 0.0);
4255 return nullptr;
4258 if (!IsFinite(aX) || !IsFinite(aY)) {
4259 aError = NS_OK;
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,
4264 0.0, 0.0);
4266 return nullptr;
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.
4286 if (mTarget) {
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;
4299 processor.mFontgrp
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()) {
4313 return nullptr;
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
4326 gfxFloat anchorX;
4328 if (state.textAlign == TextAlign::CENTER) {
4329 anchorX = .5;
4330 } else if (state.textAlign == TextAlign::LEFT ||
4331 (!isRTL && state.textAlign == TextAlign::START) ||
4332 (isRTL && state.textAlign == TextAlign::END)) {
4333 anchorX = 0;
4334 } else {
4335 anchorX = 1;
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;
4347 break;
4348 case TextBaseline::TOP:
4349 baselineAnchor = fontMetrics.emAscent;
4350 break;
4351 case TextBaseline::MIDDLE:
4352 baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
4353 break;
4354 case TextBaseline::ALPHABETIC:
4355 baselineAnchor = 0;
4356 break;
4357 case TextBaseline::IDEOGRAPHIC:
4358 baselineAnchor = -fontMetrics.emDescent * kIdeographicBaselineDefault;
4359 break;
4360 case TextBaseline::BOTTOM:
4361 baselineAnchor = -fontMetrics.emDescent;
4362 break;
4363 default:
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;
4379 } else {
4380 processor.mPt.y += baselineAnchor;
4383 // if only measuring, don't need to do any more work
4384 if (aOp == TextDrawOperation::MEASURE) {
4385 aError = NS_OK;
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
4405 hangingBaseline,
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;
4420 EnsureTarget();
4421 if (!IsTargetValid()) {
4422 aError = NS_ERROR_FAILURE;
4423 return nullptr;
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
4434 // translate back.
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,
4453 &mBidiEngine);
4455 if (aError.Failed()) {
4456 return nullptr;
4459 mTarget->SetTransform(oldTransform);
4461 if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
4462 !doCalculateBounds) {
4463 RedrawUser(boundingBox);
4464 } else {
4465 Redraw();
4468 aError = NS_OK;
4469 return nullptr;
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;
4482 if (fontGroup) {
4483 if (fontGroup->GetPresContext() != presContext) {
4484 fontGroup = nullptr;
4488 if (!fontGroup) {
4489 ErrorResult err;
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;
4503 gfxFontStyle style;
4504 style.size = kDefaultFontSize;
4505 int32_t perDevPixel, perCSSPixel;
4506 GetAppUnitsValues(&perDevPixel, &perCSSPixel);
4507 gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
4508 const auto* sans =
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);
4514 if (fontGroup) {
4515 CurrentState().font = kDefaultFontStyle;
4516 } else {
4517 NS_ERROR("Default canvas font is invalid");
4520 } else {
4521 // The fontgroup needs to check if its cached families/faces are valid.
4522 fontGroup->CheckForUpdatedPlatformList();
4525 return fontGroup;
4529 // line caps/joins
4532 void CanvasRenderingContext2D::SetLineCap(const nsAString& aLinecapStyle) {
4533 CapStyle cap;
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;
4541 } else {
4542 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4543 return;
4546 CurrentState().lineCap = cap;
4549 void CanvasRenderingContext2D::GetLineCap(nsAString& aLinecapStyle) {
4550 switch (CurrentState().lineCap) {
4551 case CapStyle::BUTT:
4552 aLinecapStyle.AssignLiteral("butt");
4553 break;
4554 case CapStyle::ROUND:
4555 aLinecapStyle.AssignLiteral("round");
4556 break;
4557 case CapStyle::SQUARE:
4558 aLinecapStyle.AssignLiteral("square");
4559 break;
4563 void CanvasRenderingContext2D::SetLineJoin(const nsAString& aLinejoinStyle) {
4564 JoinStyle j;
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;
4572 } else {
4573 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4574 return;
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");
4585 break;
4586 case JoinStyle::BEVEL:
4587 aLinejoinStyle.AssignLiteral("bevel");
4588 break;
4589 case JoinStyle::MITER_OR_BEVEL:
4590 aLinejoinStyle.AssignLiteral("miter");
4591 break;
4592 default:
4593 aError.Throw(NS_ERROR_FAILURE);
4597 void CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments,
4598 ErrorResult& aRv) {
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
4605 return;
4608 if (!dash.AppendElement(aSegments[x], fallible)) {
4609 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4610 return;
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);
4618 return;
4623 CurrentState().dash = std::move(dash);
4626 void CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
4627 const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
4628 aSegments.Clear();
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,
4644 double aY,
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)) {
4654 return false;
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)) {
4662 return false;
4664 } else if (mOffscreenCanvas &&
4665 mOffscreenCanvas->ShouldResistFingerprinting()) {
4666 return false;
4669 EnsureUserSpacePath(aWinding);
4670 if (!mPath) {
4671 return false;
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,
4683 double aY,
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,
4691 double aY,
4692 const CanvasWindingRule& aWinding,
4693 Maybe<nsIPrincipal*>) {
4694 if (!FloatValidate(aX, aY)) {
4695 return false;
4698 EnsureTarget();
4699 if (!IsTargetValid()) {
4700 return false;
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)) {
4717 return false;
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)) {
4725 return false;
4727 } else if (mOffscreenCanvas &&
4728 mOffscreenCanvas->ShouldResistFingerprinting()) {
4729 return false;
4732 EnsureUserSpacePath();
4733 if (!mPath) {
4734 return false;
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)) {
4761 return false;
4764 EnsureTarget();
4765 if (!IsTargetValid()) {
4766 return false;
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);
4806 if (subrectDT) {
4807 // See bug 1524554.
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();
4823 // image
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;
4835 aSourceCoord = 0.0;
4837 double delta = aImageSize - (aSourceCoord + aSourceSize);
4838 if (delta < 0.0) {
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);
4851 if (!imageLoader) {
4852 return res;
4855 nsCOMPtr<imgIRequest> imgRequest;
4856 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
4857 getter_AddRefs(imgRequest));
4858 if (!imgRequest) {
4859 return res;
4862 uint32_t status = 0;
4863 if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
4864 !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
4865 return res;
4868 nsCOMPtr<nsIPrincipal> principal;
4869 if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
4870 !principal) {
4871 return res;
4874 if (NS_FAILED(imgRequest->GetHadCrossOriginRedirects(
4875 &res.mHadCrossOriginRedirects))) {
4876 return res;
4879 res.mSourceSurface = CanvasImageCache::LookupAllCanvas(aElement, mTarget);
4880 if (!res.mSourceSurface) {
4881 return res;
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);
4895 return res;
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);
4914 } else {
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,
4929 // in float dh);
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)) {
4950 return;
4952 if (aOptional_argc == 6) {
4953 if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
4954 return;
4958 RefPtr<SourceSurface> srcSurf;
4959 gfx::IntSize imgSize;
4960 gfx::IntSize intrinsicImgSize;
4961 Element* element = nullptr;
4962 OffscreenCanvas* offscreenCanvas = nullptr;
4964 EnsureTarget();
4965 if (!IsTargetValid()) {
4966 return;
4969 if (aImage.IsHTMLCanvasElement()) {
4970 HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
4971 element = canvas;
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()) {
4978 SetWriteOnly();
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();
4988 if (srcSurf) {
4989 imgSize = intrinsicImgSize = srcSurf->GetSize();
4992 if (offscreenCanvas->IsWriteOnly()) {
4993 SetWriteOnly();
4995 } else if (aImage.IsImageBitmap()) {
4996 ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
4997 srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
4999 if (!srcSurf) {
5000 if (imageBitmap.IsClosed()) {
5001 aError.ThrowInvalidStateError("Passed-in ImageBitmap is closed");
5003 return;
5006 if (imageBitmap.IsWriteOnly()) {
5007 SetWriteOnly();
5010 imgSize = intrinsicImgSize =
5011 gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
5012 } else {
5013 if (aImage.IsHTMLImageElement()) {
5014 HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
5015 element = img;
5016 } else if (aImage.IsSVGImageElement()) {
5017 SVGImageElement* img = &aImage.GetAsSVGImageElement();
5018 element = img;
5019 } else {
5020 HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
5021 video->LogVisibility(
5022 mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
5023 element = video;
5026 srcSurf = CanvasImageCache::LookupCanvas(element, mCanvasElement, mTarget,
5027 &imgSize, &intrinsicImgSize);
5030 DirectDrawInfo drawInfo;
5032 if (!srcSurf) {
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,
5042 mTarget);
5043 } else {
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\"");
5063 return;
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,
5074 intrinsicImgSize);
5076 srcSurf = res.mSourceSurface;
5077 } else {
5078 drawInfo = res.mDrawInfo;
5082 if (aOptional_argc == 0) {
5083 aSx = aSy = 0.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) {
5089 aSx = aSy = 0.0;
5090 aSw = (double)imgSize.width;
5091 aSh = (double)imgSize.height;
5094 if (aSw == 0.0 || aSh == 0.0) {
5095 return;
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
5103 return;
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();
5112 bool isDownScale =
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;
5121 } else {
5122 samplingFilter = gfx::SamplingFilter::POINT;
5123 antialiasMode = AntialiasMode::NONE;
5126 const bool needBounds = NeedToCalculateBounds();
5127 if (!IsTargetValid()) {
5128 return;
5130 gfx::Rect bounds;
5131 if (needBounds) {
5132 bounds = gfx::Rect(aDx, aDy, aDw, aDh);
5133 bounds = mTarget->GetTransform().TransformBounds(bounds);
5136 if (!IsTargetValid()) {
5137 aError.Throw(NS_ERROR_FAILURE);
5138 return;
5141 if (srcSurf) {
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
5148 // that we need.
5149 srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
5152 AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds, true);
5153 if (!tempTarget) {
5154 gfxWarning() << "Invalid adjusted target in Canvas2D "
5155 << gfx::hexa((DrawTarget*)mTarget) << ", "
5156 << NeedToDrawShadow() << NeedToApplyFilter();
5157 return;
5160 auto op = tempTarget.UsedOperation();
5161 if (!IsTargetValid() || !tempTarget) {
5162 return;
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);
5172 Matrix transform;
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.
5180 destRect.x = 0;
5181 destRect.y = 0;
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);
5195 } else {
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()) {
5211 return;
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(
5238 contextMatrix
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)
5296 else return;
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)
5335 else {
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,
5345 uint32_t aFlags,
5346 nsIPrincipal& aSubjectPrincipal,
5347 ErrorResult& aError) {
5348 if (int32_t(aW) == 0 || int32_t(aH) == 0) {
5349 return;
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);
5356 return;
5359 Document* doc = aWindow.GetExtantDoc();
5360 if (doc && aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
5361 doc->WarnOnceAbout(
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()) {
5377 return;
5380 RefPtr<nsPresContext> presContext;
5381 nsIDocShell* docshell = aWindow.GetDocShell();
5382 if (docshell) {
5383 presContext = docshell->GetPresContext();
5385 if (!presContext) {
5386 aError.Throw(NS_ERROR_FAILURE);
5387 return;
5390 Maybe<nscolor> backgroundColor = ParseColor(aBgColor);
5391 if (!backgroundColor) {
5392 aError.Throw(NS_ERROR_FAILURE);
5393 return;
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;
5413 if (aFlags &
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;
5426 if (!sw || !sh) {
5427 return;
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);
5443 return;
5446 if (op == CompositionOp::OP_OVER &&
5447 (!mBufferProvider || !mBufferProvider->IsShared())) {
5448 thebes.emplace(mTarget);
5449 thebes.ref().SetMatrix(matrix);
5450 } else {
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);
5460 return;
5463 drawDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
5464 dtSize, SurfaceFormat::B8G8R8A8);
5465 if (!drawDT || !drawDT->IsValid()) {
5466 aError.Throw(NS_ERROR_FAILURE);
5467 return;
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,
5478 &thebes.ref());
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);
5483 if (drawDT) {
5484 RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
5485 if (NS_WARN_IF(!snapshot)) {
5486 aError.Throw(NS_ERROR_FAILURE);
5487 return;
5490 op = CurrentState().op;
5491 if (!IsTargetValid()) {
5492 aError.Throw(NS_ERROR_FAILURE);
5493 return;
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));
5500 } else {
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
5506 // space.
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),
5518 aError);
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);
5527 return nullptr;
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);
5537 return nullptr;
5540 if (!aSw || !aSh) {
5541 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5542 return nullptr;
5545 // Handle negative width and height by flipping the rectangle over in the
5546 // relevant direction.
5547 uint32_t w, h;
5548 if (aSw < 0) {
5549 w = -aSw;
5550 aSx -= w;
5551 } else {
5552 w = aSw;
5554 if (aSh < 0) {
5555 h = -aSh;
5556 aSy -= h;
5557 } else {
5558 h = aSh;
5561 if (w == 0) {
5562 w = 1;
5564 if (h == 0) {
5565 h = 1;
5568 JS::Rooted<JSObject*> array(aCx);
5569 aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal,
5570 array.address());
5571 if (aError.Failed()) {
5572 return nullptr;
5574 MOZ_ASSERT(array);
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()));
5608 if (!darray) {
5609 return NS_ERROR_OUT_OF_MEMORY;
5612 if (mZero) {
5613 *aRetval = darray;
5614 return NS_OK;
5617 IntRect dstWriteRect(0, 0, aWidth, aHeight);
5618 IntRect srcReadRect = ClipImageDataTransfer(dstWriteRect, IntPoint(aX, aY),
5619 IntSize(mWidth, mHeight));
5620 if (srcReadRect.IsEmpty()) {
5621 *aRetval = darray;
5622 return NS_OK;
5625 if (!GetBufferProvider() && !EnsureTarget()) {
5626 return NS_ERROR_FAILURE;
5629 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
5630 if (!snapshot) {
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
5644 // internal uses).
5645 bool usePlaceholder = false;
5646 if (mCanvasElement) {
5647 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
5648 usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
5649 aSubjectPrincipal);
5650 } else if (mOffscreenCanvas) {
5651 usePlaceholder = mOffscreenCanvas->ShouldResistFingerprinting();
5654 do {
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;
5664 bool isShared;
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);
5670 break;
5673 uint32_t srcStride = rawData.mStride;
5674 uint8_t* src =
5675 rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
5677 uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
5679 if (mOpaque) {
5680 SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32, dst,
5681 aWidth * 4, SurfaceFormat::R8G8B8A8, dstWriteRect.Size());
5682 } else {
5683 UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32, dst,
5684 aWidth * 4, SurfaceFormat::R8G8B8A8,
5685 dstWriteRect.Size());
5687 } while (false);
5689 readback->Unmap();
5690 *aRetval = darray;
5691 return NS_OK;
5694 void CanvasRenderingContext2D::EnsureErrorTarget() {
5695 if (sErrorTarget.get()) {
5696 return;
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() {
5708 if (mPath) {
5709 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
5710 mPath = nullptr;
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,
5722 int32_t aDirtyY,
5723 int32_t aDirtyWidth,
5724 int32_t aDirtyHeight,
5725 ErrorResult& aRv) {
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,
5733 ErrorResult& aRv) {
5734 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
5735 if (!arr.Init(aImageData.GetDataObject())) {
5736 return aRv.ThrowInvalidStateError(
5737 "Failed to extract Uint8ClampedArray from ImageData (security check "
5738 "failed?)");
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");
5747 IntRect dirtyRect;
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) {
5787 return;
5789 } else {
5790 dirtyRect = imageDataRect;
5793 IntRect srcRect = dirtyRect;
5794 dirtyRect = ClipImageDataTransfer(srcRect, IntPoint(aX, aY),
5795 IntSize(mWidth, mHeight));
5796 if (dirtyRect.IsEmpty()) {
5797 return;
5800 arr.ComputeState();
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;
5822 uint8_t* dstData;
5823 IntSize dstSize;
5824 int32_t dstStride;
5825 SurfaceFormat dstFormat;
5826 if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) {
5827 dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4;
5828 } else {
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;
5844 if (!dstData) {
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;
5853 PremultiplyData(
5854 srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData, dstStride,
5855 mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32,
5856 dirtyRect.Size());
5858 if (lockedBits) {
5859 mTarget->ReleaseBits(lockedBits);
5860 } else if (sourceSurface) {
5861 sourceSurface->Unmap();
5862 mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(),
5863 dirtyRect.TopLeft());
5866 Redraw(
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");
5881 return nullptr;
5884 // Create the fast typed array; it's initialized to 0 by default.
5885 JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value());
5886 if (!darray) {
5887 // TODO: Should use OOMReporter.
5888 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
5889 return nullptr;
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) {
5897 if (!aSw || !aSh) {
5898 aError.ThrowIndexSizeError("Invalid width or height");
5899 return nullptr;
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;
5921 OnStableState();
5924 void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); }
5926 bool CanvasRenderingContext2D::UpdateWebRenderCanvasData(
5927 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
5928 if (mOpaque) {
5929 // If we're opaque then make sure we have a surface so we paint black
5930 // instead of transparent.
5931 EnsureTarget();
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.
5941 MarkContextClean();
5942 // Clear CanvasRenderer of WebRenderCanvasData
5943 aCanvasData->ClearCanvasRenderer();
5944 return false;
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)) {
5955 return true;
5959 renderer = aCanvasData->CreateCanvasRenderer();
5960 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
5961 // Clear CanvasRenderer of WebRenderCanvasData
5962 aCanvasData->ClearCanvasRenderer();
5963 return false;
5966 MOZ_ASSERT(renderer);
5967 mResetLayer = false;
5968 return true;
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.
5981 EnsureTarget();
5982 ReturnTarget();
5983 if (!mBufferProvider) {
5984 MarkContextClean();
5985 return false;
5989 aRenderer->Initialize(data);
5990 aRenderer->SetDirty();
5991 return true;
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.
6005 if (aPerDevPixel) {
6006 *aPerDevPixel = 60;
6008 if (aPerCSSPixel) {
6009 *aPerCSSPixel = 60;
6011 PresShell* presShell = GetPresShell();
6012 if (!presShell) {
6013 return;
6015 nsPresContext* presContext = presShell->GetPresContext();
6016 if (!presContext) {
6017 return;
6019 if (aPerDevPixel) {
6020 *aPerDevPixel = presContext->AppUnitsPerDevPixel();
6022 if (aPerCSSPixel) {
6023 *aPerCSSPixel = AppUnitsPerCSSPixel();
6027 void CanvasRenderingContext2D::SetWriteOnly() {
6028 mWriteOnly = true;
6029 if (mCanvasElement) {
6030 mCanvasElement->SetWriteOnly();
6034 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
6036 CanvasPath::CanvasPath(nsISupports* aParent) : mParent(aParent) {
6037 mPathBuilder =
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);
6076 if (!tempPath) {
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,
6104 double aY) {
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) {
6120 if (aRadius < 0) {
6121 return aError.ThrowIndexSizeError("Negative radius");
6124 EnsurePathBuilder();
6126 // Current point in user space!
6127 Point p0 = mPathBuilder->CurrentPoint();
6128 Point p1(aX1, aY1);
6129 Point p2(aX2, aY2);
6131 // Execute these calculations in double precision to avoid cumulative
6132 // rounding errors.
6133 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
6134 cy, angle0, angle1;
6135 bool anticlockwise;
6137 if (p0 == p1 || p1 == p2 || aRadius == 0) {
6138 LineTo(p1.x, p1.y);
6139 return;
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);
6145 if (dir == 0) {
6146 LineTo(p1.x, p1.y);
6147 return;
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);
6165 x3 = aX1 - anx * d;
6166 y3 = aY1 - any * d;
6167 x4 = aX1 - bnx * d;
6168 y4 = aY1 - bny * d;
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));
6175 LineTo(x3, y3);
6177 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
6180 void CanvasPath::Rect(double aX, double aY, double aW, double aH) {
6181 MoveTo(aX, aY);
6182 LineTo(aX + aW, aY);
6183 LineTo(aX + aW, aY + aH);
6184 LineTo(aX, aY + aH);
6185 ClosePath();
6188 void CanvasPath::RoundRect(
6189 double aX, double aY, double aW, double aH,
6190 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
6191 aRadii,
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()) {
6247 return;
6250 Matrix transform(*(matrix->GetInternal2D()));
6252 if (!transform.IsFinite()) {
6253 return;
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();
6279 if (!mPath) {
6280 // if there is no path, there must be a pathbuilder
6281 MOZ_ASSERT(mPathBuilder);
6282 mPath = mPathBuilder->Finish();
6283 if (!mPath) {
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 {
6306 if (mPathBuilder) {
6307 return;
6310 // if there is not pathbuilder, there must be a path
6311 MOZ_ASSERT(mPath);
6312 mPathBuilder = mPath->CopyToBuilder();
6313 mPath = nullptr;
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
6321 // too.
6323 CheckedInt<uint32_t> bytes =
6324 CheckedInt<uint32_t>(size.width) * size.height * 4;
6325 if (!bytes.isValid()) {
6326 return 0;
6329 return bytes.value();
6332 } // namespace mozilla::dom