Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / canvas / CanvasRenderingContext2D.cpp
blob36c99fe8328611b0e58027dbfbc63c239cf81b88
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/PresShell.h"
16 #include "mozilla/PresShellInlines.h"
17 #include "mozilla/SVGImageContext.h"
18 #include "mozilla/SVGObserverUtils.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/HTMLCanvasElement.h"
21 #include "mozilla/dom/GeneratePlaceholderCanvasData.h"
22 #include "nsPresContext.h"
24 #include "nsIInterfaceRequestorUtils.h"
25 #include "nsIFrame.h"
26 #include "nsError.h"
28 #include "nsCSSPseudoElements.h"
29 #include "nsComputedDOMStyle.h"
31 #include "nsPrintfCString.h"
33 #include "nsReadableUtils.h"
35 #include "nsColor.h"
36 #include "nsGfxCIID.h"
37 #include "nsIDocShell.h"
38 #include "nsPIDOMWindow.h"
39 #include "nsDisplayList.h"
40 #include "nsFocusManager.h"
41 #include "nsContentUtils.h"
43 #include "nsTArray.h"
45 #include "ImageEncoder.h"
46 #include "ImageRegion.h"
48 #include "gfxContext.h"
49 #include "gfxPlatform.h"
50 #include "gfxFont.h"
51 #include "gfxBlur.h"
52 #include "gfxTextRun.h"
53 #include "gfxUtils.h"
55 #include "nsFrameLoader.h"
56 #include "nsBidiPresUtils.h"
57 #include "Layers.h"
58 #include "LayerUserData.h"
59 #include "CanvasUtils.h"
60 #include "nsIMemoryReporter.h"
61 #include "nsStyleUtil.h"
62 #include "CanvasImageCache.h"
64 #include <algorithm>
66 #include "jsapi.h"
67 #include "jsfriendapi.h"
68 #include "js/Array.h" // JS::GetArrayLength
69 #include "js/Conversions.h"
70 #include "js/experimental/TypedData.h" // JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData
71 #include "js/HeapAPI.h"
72 #include "js/Warnings.h" // JS::WarnASCII
74 #include "mozilla/Alignment.h"
75 #include "mozilla/Assertions.h"
76 #include "mozilla/CheckedInt.h"
77 #include "mozilla/DebugOnly.h"
78 #include "mozilla/dom/CanvasGradient.h"
79 #include "mozilla/dom/CanvasPattern.h"
80 #include "mozilla/dom/DOMMatrix.h"
81 #include "mozilla/dom/ImageBitmap.h"
82 #include "mozilla/dom/ImageData.h"
83 #include "mozilla/dom/PBrowserParent.h"
84 #include "mozilla/dom/ToJSValue.h"
85 #include "mozilla/dom/TypedArray.h"
86 #include "mozilla/EndianUtils.h"
87 #include "mozilla/FilterInstance.h"
88 #include "mozilla/gfx/2D.h"
89 #include "mozilla/gfx/Helpers.h"
90 #include "mozilla/gfx/Tools.h"
91 #include "mozilla/gfx/PathHelpers.h"
92 #include "mozilla/gfx/DataSurfaceHelpers.h"
93 #include "mozilla/gfx/PatternHelpers.h"
94 #include "mozilla/gfx/Swizzle.h"
95 #include "mozilla/layers/PersistentBufferProvider.h"
96 #include "mozilla/MathAlgorithms.h"
97 #include "mozilla/Preferences.h"
98 #include "mozilla/ServoBindings.h"
99 #include "mozilla/StaticPrefs_gfx.h"
100 #include "mozilla/Telemetry.h"
101 #include "mozilla/TimeStamp.h"
102 #include "mozilla/UniquePtr.h"
103 #include "mozilla/Unused.h"
104 #include "nsCCUncollectableMarker.h"
105 #include "nsWrapperCacheInlines.h"
106 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
107 #include "mozilla/dom/CanvasPath.h"
108 #include "mozilla/dom/HTMLImageElement.h"
109 #include "mozilla/dom/HTMLVideoElement.h"
110 #include "mozilla/dom/SVGImageElement.h"
111 #include "mozilla/dom/TextMetrics.h"
112 #include "mozilla/FloatingPoint.h"
113 #include "nsGlobalWindow.h"
114 #include "nsDeviceContext.h"
115 #include "nsFontMetrics.h"
116 #include "nsLayoutUtils.h"
117 #include "Units.h"
118 #include "CanvasUtils.h"
119 #include "mozilla/CycleCollectedJSRuntime.h"
120 #include "mozilla/ServoCSSParser.h"
121 #include "mozilla/ServoStyleSet.h"
122 #include "mozilla/SVGContentUtils.h"
123 #include "mozilla/layers/CanvasClient.h"
124 #include "mozilla/layers/WebRenderUserData.h"
125 #include "mozilla/layers/WebRenderCanvasRenderer.h"
127 #undef free // apparently defined by some windows header, clashing with a
128 // free() method in SkTypes.h
130 #ifdef XP_WIN
131 # include "gfxWindowsPlatform.h"
132 #endif
134 // windows.h (included by chromium code) defines this, in its infinite wisdom
135 #undef DrawText
137 using namespace mozilla;
138 using namespace mozilla::CanvasUtils;
139 using namespace mozilla::css;
140 using namespace mozilla::gfx;
141 using namespace mozilla::image;
142 using namespace mozilla::ipc;
143 using namespace mozilla::layers;
145 namespace mozilla::dom {
147 // Cap sigma to avoid overly large temp surfaces.
148 const Float SIGMA_MAX = 100;
150 const size_t MAX_STYLE_STACK_SIZE = 1024;
152 /* Memory reporter stuff */
153 static int64_t gCanvasAzureMemoryUsed = 0;
155 // Adds Save() / Restore() calls to the scope.
156 class MOZ_RAII AutoSaveRestore {
157 public:
158 explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx) : mCtx(aCtx) {
159 mCtx->Save();
161 ~AutoSaveRestore() { mCtx->Restore(); }
163 private:
164 RefPtr<CanvasRenderingContext2D> mCtx;
167 // This is KIND_OTHER because it's not always clear where in memory the pixels
168 // of a canvas are stored. Furthermore, this memory will be tracked by the
169 // underlying surface implementations. See bug 655638 for details.
170 class Canvas2dPixelsReporter final : public nsIMemoryReporter {
171 ~Canvas2dPixelsReporter() = default;
173 public:
174 NS_DECL_ISUPPORTS
176 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
177 nsISupports* aData, bool aAnonymize) override {
178 MOZ_COLLECT_REPORT("canvas-2d-pixels", KIND_OTHER, UNITS_BYTES,
179 gCanvasAzureMemoryUsed,
180 "Memory used by 2D canvases. Each canvas requires "
181 "(width * height * 4) bytes.");
183 return NS_OK;
187 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
189 class CanvasConicGradient : public CanvasGradient {
190 public:
191 CanvasConicGradient(CanvasRenderingContext2D* aContext, Float aAngle,
192 const Point& aCenter)
193 : CanvasGradient(aContext, Type::CONIC),
194 mAngle(aAngle),
195 mCenter(aCenter) {}
197 const Float mAngle;
198 const Point mCenter;
201 class CanvasRadialGradient : public CanvasGradient {
202 public:
203 CanvasRadialGradient(CanvasRenderingContext2D* aContext,
204 const Point& aBeginOrigin, Float aBeginRadius,
205 const Point& aEndOrigin, Float aEndRadius)
206 : CanvasGradient(aContext, Type::RADIAL),
207 mCenter1(aBeginOrigin),
208 mCenter2(aEndOrigin),
209 mRadius1(aBeginRadius),
210 mRadius2(aEndRadius) {}
212 Point mCenter1;
213 Point mCenter2;
214 Float mRadius1;
215 Float mRadius2;
218 class CanvasLinearGradient : public CanvasGradient {
219 public:
220 CanvasLinearGradient(CanvasRenderingContext2D* aContext, const Point& aBegin,
221 const Point& aEnd)
222 : CanvasGradient(aContext, Type::LINEAR), mBegin(aBegin), mEnd(aEnd) {}
224 protected:
225 friend struct CanvasBidiProcessor;
226 friend class CanvasGeneralPattern;
228 // Beginning of linear gradient.
229 Point mBegin;
230 // End of linear gradient.
231 Point mEnd;
234 bool CanvasRenderingContext2D::PatternIsOpaque(
235 CanvasRenderingContext2D::Style aStyle, bool* aIsColor) const {
236 const ContextState& state = CurrentState();
237 bool opaque = false;
238 bool color = false;
239 if (state.globalAlpha >= 1.0) {
240 if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) {
241 opaque = IsOpaque(state.patternStyles[aStyle]->mSurface->GetFormat());
242 } else if (!state.gradientStyles[aStyle]) {
243 // TODO: for gradient patterns we could check that all stops are opaque
244 // colors.
245 // it's a color pattern.
246 opaque = sRGBColor::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
247 color = true;
250 if (aIsColor) {
251 *aIsColor = color;
253 return opaque;
256 // This class is named 'GeneralCanvasPattern' instead of just
257 // 'GeneralPattern' to keep Windows PGO builds from confusing the
258 // GeneralPattern class in gfxContext.cpp with this one.
259 class CanvasGeneralPattern {
260 public:
261 typedef CanvasRenderingContext2D::Style Style;
262 typedef CanvasRenderingContext2D::ContextState ContextState;
264 Pattern& ForStyle(CanvasRenderingContext2D* aCtx, Style aStyle,
265 DrawTarget* aRT) {
266 // This should only be called once or the mPattern destructor will
267 // not be executed.
268 NS_ASSERTION(
269 !mPattern.GetPattern(),
270 "ForStyle() should only be called once on CanvasGeneralPattern!");
272 const ContextState& state = aCtx->CurrentState();
274 if (state.StyleIsColor(aStyle)) {
275 mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle]));
276 } else if (state.gradientStyles[aStyle] &&
277 state.gradientStyles[aStyle]->GetType() ==
278 CanvasGradient::Type::LINEAR) {
279 auto gradient = static_cast<CanvasLinearGradient*>(
280 state.gradientStyles[aStyle].get());
282 mPattern.InitLinearGradientPattern(
283 gradient->mBegin, gradient->mEnd,
284 gradient->GetGradientStopsForTarget(aRT));
285 } else if (state.gradientStyles[aStyle] &&
286 state.gradientStyles[aStyle]->GetType() ==
287 CanvasGradient::Type::RADIAL) {
288 auto gradient = static_cast<CanvasRadialGradient*>(
289 state.gradientStyles[aStyle].get());
291 mPattern.InitRadialGradientPattern(
292 gradient->mCenter1, gradient->mCenter2, gradient->mRadius1,
293 gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT));
294 } else if (state.gradientStyles[aStyle] &&
295 state.gradientStyles[aStyle]->GetType() ==
296 CanvasGradient::Type::CONIC) {
297 auto gradient =
298 static_cast<CanvasConicGradient*>(state.gradientStyles[aStyle].get());
300 mPattern.InitConicGradientPattern(
301 gradient->mCenter, gradient->mAngle, 0, 1,
302 gradient->GetGradientStopsForTarget(aRT));
303 } else if (state.patternStyles[aStyle]) {
304 if (aCtx->mCanvasElement) {
305 CanvasUtils::DoDrawImageSecurityCheck(
306 aCtx->mCanvasElement, state.patternStyles[aStyle]->mPrincipal,
307 state.patternStyles[aStyle]->mForceWriteOnly,
308 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 typedef CanvasRenderingContext2D::ContextState 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 typedef CanvasRenderingContext2D::ContextState 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 bounds.ToIntRect(&mTempRect);
508 if (!mFinalTarget->CanCreateSimilarDrawTarget(mTempRect.Size(),
509 SurfaceFormat::B8G8R8A8)) {
510 mTarget = mFinalTarget;
511 mCtx = nullptr;
512 mFinalTarget = nullptr;
513 return;
516 mTarget = mFinalTarget->CreateShadowDrawTarget(
517 mTempRect.Size(), SurfaceFormat::B8G8R8A8, mSigma);
519 if (mTarget) {
520 // See bug 1524554.
521 mTarget->ClearRect(gfx::Rect());
524 if (!mTarget || !mTarget->IsValid()) {
525 // XXX - Deal with the situation where our temp size is too big to
526 // fit in a texture (bug 1066622).
527 mTarget = mFinalTarget;
528 mCtx = nullptr;
529 mFinalTarget = nullptr;
530 } else {
531 mTarget->SetTransform(
532 mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft()));
536 ~AdjustedTargetForShadow() {
537 if (!mCtx) {
538 return;
541 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
543 mFinalTarget->DrawSurfaceWithShadow(
544 snapshot, mTempRect.TopLeft(),
545 ToDeviceColor(mCtx->CurrentState().shadowColor),
546 mCtx->CurrentState().shadowOffset, mSigma, 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 typedef CanvasRenderingContext2D::ContextState ContextState;
578 explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
579 const gfx::Rect* aBounds = nullptr) {
580 // All rects in this function are in the device space of ctx->mTarget.
582 // In order to keep our temporary surfaces as small as possible, we first
583 // calculate what their maximum required bounds would need to be if we
584 // were to fill the whole canvas. Everything outside those bounds we don't
585 // need to render.
586 gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight);
587 gfx::Rect maxSourceNeededBoundsForShadow =
588 MaxSourceNeededBoundsForShadow(r, aCtx);
589 gfx::Rect maxSourceNeededBoundsForFilter =
590 MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx);
591 if (!aCtx->IsTargetValid()) {
592 return;
595 gfx::Rect bounds = maxSourceNeededBoundsForFilter;
596 if (aBounds) {
597 bounds = bounds.Intersect(*aBounds);
599 gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
600 if (!aCtx->IsTargetValid()) {
601 return;
604 mozilla::gfx::CompositionOp op = aCtx->CurrentState().op;
606 gfx::IntPoint offsetToFinalDT;
608 // First set up the shadow draw target, because the shadow goes outside.
609 // It applies to the post-filter results, if both a filter and a shadow
610 // are used.
611 if (aCtx->NeedToDrawShadow()) {
612 mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
613 aCtx, aCtx->mTarget, boundsAfterFilter, op);
614 mTarget = mShadowTarget->DT();
615 offsetToFinalDT = mShadowTarget->OffsetToFinalDT();
617 // If we also have a filter, the filter needs to be drawn with OP_OVER
618 // because shadow drawing already applies op on the result.
619 op = gfx::CompositionOp::OP_OVER;
622 // Now set up the filter draw target.
623 const bool applyFilter = aCtx->NeedToApplyFilter();
624 if (!aCtx->IsTargetValid()) {
625 return;
627 if (applyFilter) {
628 bounds.RoundOut();
630 if (!mTarget) {
631 mTarget = aCtx->mTarget;
633 gfx::IntRect intBounds;
634 if (!bounds.ToIntRect(&intBounds)) {
635 return;
637 mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
638 aCtx, mTarget, offsetToFinalDT, intBounds,
639 gfx::RoundedToInt(boundsAfterFilter), op);
640 mTarget = mFilterTarget->DT();
642 if (!mTarget) {
643 mTarget = aCtx->mTarget;
647 ~AdjustedTarget() {
648 // The order in which the targets are finalized is important.
649 // Filters are inside, any shadow applies to the post-filter results.
650 mFilterTarget.reset();
651 mShadowTarget.reset();
654 operator DrawTarget*() { return mTarget; }
656 DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mTarget; }
658 private:
659 gfx::Rect MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds,
660 CanvasRenderingContext2D* aCtx) {
661 const bool applyFilter = aCtx->NeedToApplyFilter();
662 if (!aCtx->IsTargetValid()) {
663 return aDestBounds;
665 if (!applyFilter) {
666 return aDestBounds;
669 nsIntRegion sourceGraphicNeededRegion;
670 nsIntRegion fillPaintNeededRegion;
671 nsIntRegion strokePaintNeededRegion;
673 FilterSupport::ComputeSourceNeededRegions(
674 aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds),
675 sourceGraphicNeededRegion, fillPaintNeededRegion,
676 strokePaintNeededRegion);
678 return gfx::Rect(sourceGraphicNeededRegion.GetBounds());
681 gfx::Rect MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds,
682 CanvasRenderingContext2D* aCtx) {
683 if (!aCtx->NeedToDrawShadow()) {
684 return aDestBounds;
687 const ContextState& state = aCtx->CurrentState();
688 gfx::Rect sourceBounds = aDestBounds - state.shadowOffset;
689 sourceBounds.Inflate(state.ShadowBlurRadius());
691 // Union the shadow source with the original rect because we're going to
692 // draw both.
693 return sourceBounds.Union(aDestBounds);
696 gfx::Rect BoundsAfterFilter(const gfx::Rect& aBounds,
697 CanvasRenderingContext2D* aCtx) {
698 const bool applyFilter = aCtx->NeedToApplyFilter();
699 if (!aCtx->IsTargetValid()) {
700 return aBounds;
702 if (!applyFilter) {
703 return aBounds;
706 gfx::Rect bounds(aBounds);
707 bounds.RoundOut();
709 gfx::IntRect intBounds;
710 if (!bounds.ToIntRect(&intBounds)) {
711 return gfx::Rect();
714 nsIntRegion extents = gfx::FilterSupport::ComputePostFilterExtents(
715 aCtx->CurrentState().filter, intBounds);
716 return gfx::Rect(extents.GetBounds());
719 RefPtr<DrawTarget> mTarget;
720 UniquePtr<AdjustedTargetForShadow> mShadowTarget;
721 UniquePtr<AdjustedTargetForFilter> mFilterTarget;
724 void CanvasPattern::SetTransform(const DOMMatrix2DInit& aInit,
725 ErrorResult& aError) {
726 RefPtr<DOMMatrixReadOnly> matrix =
727 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
728 if (aError.Failed()) {
729 return;
731 const auto* matrix2D = matrix->GetInternal2D();
732 if (!matrix2D->IsFinite()) {
733 return;
735 mTransform = Matrix(*matrix2D);
738 void CanvasGradient::AddColorStop(float aOffset, const nsACString& aColorstr,
739 ErrorResult& aRv) {
740 if (aOffset < 0.0 || aOffset > 1.0) {
741 return aRv.ThrowIndexSizeError("Offset out of 0-1.0 range");
744 PresShell* presShell = mContext ? mContext->GetPresShell() : nullptr;
745 ServoStyleSet* styleSet = presShell ? presShell->StyleSet() : nullptr;
747 nscolor color;
748 bool ok = ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), aColorstr,
749 &color);
750 if (!ok) {
751 return aRv.ThrowSyntaxError("Invalid color");
754 GradientStop newStop;
756 newStop.offset = aOffset;
757 newStop.color = ToDeviceColor(color);
759 mRawStops.AppendElement(newStop);
762 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef)
763 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release)
765 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
767 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef)
768 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
770 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
772 class CanvasShutdownObserver final : public nsIObserver {
773 public:
774 explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
775 : mCanvas(aCanvas) {}
777 void OnShutdown() {
778 if (!mCanvas) {
779 return;
782 mCanvas = nullptr;
783 nsContentUtils::UnregisterShutdownObserver(this);
786 NS_DECL_ISUPPORTS
787 NS_DECL_NSIOBSERVER
788 private:
789 ~CanvasShutdownObserver() = default;
791 CanvasRenderingContext2D* mCanvas;
794 NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
796 NS_IMETHODIMP
797 CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
798 const char16_t* aData) {
799 if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
800 mCanvas->OnShutdown();
801 OnShutdown();
804 return NS_OK;
807 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
808 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
810 NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
812 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
813 // Make sure we remove ourselves from the list of demotable contexts (raw
814 // pointers), since we're logically destructed at this point.
815 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
816 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
817 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
818 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
819 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
820 ImplCycleCollectionUnlink(
821 tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
822 ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
823 auto autoSVGFiltersObserver =
824 tmp->mStyleStack[i].autoSVGFiltersObserver.get();
825 if (autoSVGFiltersObserver) {
826 // XXXjwatt: I don't think this call achieves anything. See the comment
827 // that documents this function.
828 SVGObserverUtils::DetachFromCanvasContext(autoSVGFiltersObserver);
830 ImplCycleCollectionUnlink(tmp->mStyleStack[i].autoSVGFiltersObserver);
832 for (size_t x = 0; x < tmp->mHitRegionsOptions.Length(); x++) {
833 RegionInfo& info = tmp->mHitRegionsOptions[x];
834 if (info.mElement) {
835 ImplCycleCollectionUnlink(info.mElement);
838 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
839 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
841 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
842 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
843 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
844 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
845 ImplCycleCollectionTraverse(
846 cb, tmp->mStyleStack[i].patternStyles[Style::STROKE],
847 "Stroke CanvasPattern");
848 ImplCycleCollectionTraverse(cb,
849 tmp->mStyleStack[i].patternStyles[Style::FILL],
850 "Fill CanvasPattern");
851 ImplCycleCollectionTraverse(
852 cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE],
853 "Stroke CanvasGradient");
854 ImplCycleCollectionTraverse(cb,
855 tmp->mStyleStack[i].gradientStyles[Style::FILL],
856 "Fill CanvasGradient");
857 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].autoSVGFiltersObserver,
858 "RAII SVG Filters Observer");
860 for (size_t x = 0; x < tmp->mHitRegionsOptions.Length(); x++) {
861 RegionInfo& info = tmp->mHitRegionsOptions[x];
862 if (info.mElement) {
863 ImplCycleCollectionTraverse(cb, info.mElement,
864 "Hit region fallback element");
867 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
869 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D)
871 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D)
872 if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) {
873 dom::Element* canvasElement = tmp->mCanvasElement;
874 if (canvasElement) {
875 if (canvasElement->IsPurple()) {
876 canvasElement->RemovePurple();
878 dom::Element::MarkNodeChildren(canvasElement);
880 return true;
882 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
884 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D)
885 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
886 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
888 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D)
889 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
890 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
892 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D)
893 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
894 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
895 NS_INTERFACE_MAP_ENTRY(nsISupports)
896 NS_INTERFACE_MAP_END
898 CanvasRenderingContext2D::ContextState::ContextState() = default;
900 CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther)
901 : fontGroup(aOther.fontGroup),
902 fontLanguage(aOther.fontLanguage),
903 fontFont(aOther.fontFont),
904 gradientStyles(aOther.gradientStyles),
905 patternStyles(aOther.patternStyles),
906 colorStyles(aOther.colorStyles),
907 font(aOther.font),
908 textAlign(aOther.textAlign),
909 textBaseline(aOther.textBaseline),
910 shadowColor(aOther.shadowColor),
911 transform(aOther.transform),
912 shadowOffset(aOther.shadowOffset),
913 lineWidth(aOther.lineWidth),
914 miterLimit(aOther.miterLimit),
915 globalAlpha(aOther.globalAlpha),
916 shadowBlur(aOther.shadowBlur),
917 dash(aOther.dash.Clone()),
918 dashOffset(aOther.dashOffset),
919 op(aOther.op),
920 fillRule(aOther.fillRule),
921 lineCap(aOther.lineCap),
922 lineJoin(aOther.lineJoin),
923 filterString(aOther.filterString),
924 filterChain(aOther.filterChain),
925 autoSVGFiltersObserver(aOther.autoSVGFiltersObserver),
926 filter(aOther.filter),
927 filterAdditionalImages(aOther.filterAdditionalImages.Clone()),
928 filterSourceGraphicTainted(aOther.filterSourceGraphicTainted),
929 imageSmoothingEnabled(aOther.imageSmoothingEnabled),
930 fontExplicitLanguage(aOther.fontExplicitLanguage) {}
932 CanvasRenderingContext2D::ContextState::~ContextState() = default;
934 void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle,
935 nscolor aColor) {
936 colorStyles[aWhichStyle] = aColor;
937 gradientStyles[aWhichStyle] = nullptr;
938 patternStyles[aWhichStyle] = nullptr;
941 void CanvasRenderingContext2D::ContextState::SetPatternStyle(
942 Style aWhichStyle, CanvasPattern* aPat) {
943 gradientStyles[aWhichStyle] = nullptr;
944 patternStyles[aWhichStyle] = aPat;
947 void CanvasRenderingContext2D::ContextState::SetGradientStyle(
948 Style aWhichStyle, CanvasGradient* aGrad) {
949 gradientStyles[aWhichStyle] = aGrad;
950 patternStyles[aWhichStyle] = nullptr;
954 ** CanvasRenderingContext2D impl
957 // Initialize our static variables.
958 uintptr_t CanvasRenderingContext2D::sNumLivingContexts = 0;
959 DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
961 CanvasRenderingContext2D::CanvasRenderingContext2D(
962 layers::LayersBackend aCompositorBackend)
963 : // these are the default values from the Canvas spec
964 mWidth(0),
965 mHeight(0),
966 mZero(false),
967 mOpaqueAttrValue(false),
968 mContextAttributesHasAlpha(true),
969 mOpaque(false),
970 mResetLayer(true),
971 mIPC(false),
972 mHasPendingStableStateCallback(false),
973 mIsEntireFrameInvalid(false),
974 mPredictManyRedrawCalls(false),
975 mIsCapturedFrameInvalid(false),
976 mPathTransformWillUpdate(false),
977 mInvalidateCount(0),
978 mWriteOnly(false) {
979 sNumLivingContexts++;
981 mShutdownObserver = new CanvasShutdownObserver(this);
982 nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
985 CanvasRenderingContext2D::~CanvasRenderingContext2D() {
986 RemovePostRefreshObserver();
987 RemoveShutdownObserver();
988 Reset();
990 sNumLivingContexts--;
991 if (!sNumLivingContexts) {
992 NS_IF_RELEASE(sErrorTarget);
996 JSObject* CanvasRenderingContext2D::WrapObject(
997 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
998 return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto);
1001 bool CanvasRenderingContext2D::ParseColor(const nsACString& aString,
1002 nscolor* aColor) {
1003 Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
1004 css::Loader* loader = document ? document->CSSLoader() : nullptr;
1006 PresShell* presShell = GetPresShell();
1007 ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
1009 // First, try computing the color without handling currentcolor.
1010 bool wasCurrentColor = false;
1011 if (!ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, aColor,
1012 &wasCurrentColor, loader)) {
1013 return false;
1016 if (wasCurrentColor && mCanvasElement) {
1017 // Otherwise, get the value of the color property, flushing style
1018 // if necessary.
1019 RefPtr<ComputedStyle> canvasStyle =
1020 nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
1021 if (canvasStyle) {
1022 *aColor = canvasStyle->StyleText()->mColor.ToColor();
1024 // Beware that the presShell could be gone here.
1026 return true;
1029 nsresult CanvasRenderingContext2D::Reset() {
1030 if (mCanvasElement) {
1031 mCanvasElement->InvalidateCanvas();
1034 // only do this for non-docshell created contexts,
1035 // since those are the ones that we created a surface for
1036 if (mTarget && IsTargetValid() && !mDocShell) {
1037 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
1040 bool forceReset = true;
1041 ReturnTarget(forceReset);
1042 mTarget = nullptr;
1043 mBufferProvider = nullptr;
1045 // reset hit regions
1046 mHitRegionsOptions.ClearAndRetainStorage();
1048 // Since the target changes the backing texture will change, and this will
1049 // no longer be valid.
1050 mIsEntireFrameInvalid = false;
1051 mPredictManyRedrawCalls = false;
1052 mIsCapturedFrameInvalid = false;
1054 return NS_OK;
1057 void CanvasRenderingContext2D::OnShutdown() {
1058 mShutdownObserver = nullptr;
1060 RefPtr<PersistentBufferProvider> provider = mBufferProvider;
1062 Reset();
1064 if (provider) {
1065 provider->OnShutdown();
1069 void CanvasRenderingContext2D::RemoveShutdownObserver() {
1070 if (mShutdownObserver) {
1071 mShutdownObserver->OnShutdown();
1072 mShutdownObserver = nullptr;
1076 void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
1077 Style aWhichStyle) {
1078 MOZ_ASSERT(!aStr.IsVoid());
1080 nscolor color;
1081 if (!ParseColor(aStr, &color)) {
1082 return;
1085 CurrentState().SetColorStyle(aWhichStyle, color);
1088 void CanvasRenderingContext2D::GetStyleAsUnion(
1089 OwningUTF8StringOrCanvasGradientOrCanvasPattern& aValue,
1090 Style aWhichStyle) {
1091 const ContextState& state = CurrentState();
1092 if (state.patternStyles[aWhichStyle]) {
1093 aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
1094 } else if (state.gradientStyles[aWhichStyle]) {
1095 aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
1096 } else {
1097 StyleColorToString(state.colorStyles[aWhichStyle],
1098 aValue.SetAsUTF8String());
1102 // static
1103 void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor,
1104 nsACString& aStr) {
1105 aStr.Truncate();
1106 // We can't reuse the normal CSS color stringification code,
1107 // because the spec calls for a different algorithm for canvas.
1108 if (NS_GET_A(aColor) == 255) {
1109 aStr.AppendPrintf("#%02x%02x%02x", NS_GET_R(aColor), NS_GET_G(aColor),
1110 NS_GET_B(aColor));
1111 } else {
1112 aStr.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor), NS_GET_G(aColor),
1113 NS_GET_B(aColor));
1114 aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
1115 aStr.Append(')');
1119 nsresult CanvasRenderingContext2D::Redraw() {
1120 mIsCapturedFrameInvalid = true;
1122 if (mIsEntireFrameInvalid) {
1123 return NS_OK;
1126 mIsEntireFrameInvalid = true;
1128 if (!mCanvasElement) {
1129 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1130 return NS_OK;
1133 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1135 mCanvasElement->InvalidateCanvasContent(nullptr);
1137 return NS_OK;
1140 void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
1141 mIsCapturedFrameInvalid = true;
1143 ++mInvalidateCount;
1145 if (mIsEntireFrameInvalid) {
1146 return;
1149 if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) {
1150 Redraw();
1151 return;
1154 if (!mCanvasElement) {
1155 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1156 return;
1159 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1161 mCanvasElement->InvalidateCanvasContent(&aR);
1164 void CanvasRenderingContext2D::DidRefresh() {}
1166 void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) {
1167 mIsCapturedFrameInvalid = true;
1169 if (mIsEntireFrameInvalid) {
1170 ++mInvalidateCount;
1171 return;
1174 gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
1175 Redraw(newr);
1178 bool CanvasRenderingContext2D::CopyBufferProvider(
1179 PersistentBufferProvider& aOld, DrawTarget& aTarget, IntRect aCopyRect) {
1180 // Borrowing the snapshot must be done after ReturnTarget.
1181 RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot();
1183 if (!snapshot) {
1184 return false;
1187 aTarget.CopySurface(snapshot, aCopyRect, IntPoint());
1188 aOld.ReturnSnapshot(snapshot.forget());
1189 return true;
1192 void CanvasRenderingContext2D::Demote() {}
1194 void CanvasRenderingContext2D::ScheduleStableStateCallback() {
1195 if (mHasPendingStableStateCallback) {
1196 return;
1198 mHasPendingStableStateCallback = true;
1200 nsContentUtils::RunInStableState(
1201 NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", this,
1202 &CanvasRenderingContext2D::OnStableState));
1205 void CanvasRenderingContext2D::OnStableState() {
1206 if (!mHasPendingStableStateCallback) {
1207 return;
1210 ReturnTarget();
1212 mHasPendingStableStateCallback = false;
1215 void CanvasRenderingContext2D::RestoreClipsAndTransformToTarget() {
1216 // Restore clips and transform.
1217 mTarget->SetTransform(Matrix());
1219 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1220 // Cairo doesn't play well with huge clips. When given a very big clip it
1221 // will try to allocate big mask surface without taking the target
1222 // size into account which can cause OOM. See bug 1034593.
1223 // This limits the clip extents to the size of the canvas.
1224 // A fix in Cairo would probably be preferable, but requires somewhat
1225 // invasive changes.
1226 mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight));
1229 for (auto& style : mStyleStack) {
1230 for (auto clipOrTransform = style.clipsAndTransforms.begin();
1231 clipOrTransform != style.clipsAndTransforms.end(); clipOrTransform++) {
1232 if (clipOrTransform->IsClip()) {
1233 if (mClipsNeedConverting) {
1234 // We have possibly changed backends, so we need to convert the clips
1235 // in case they are no longer compatible with mTarget.
1236 RefPtr<PathBuilder> pathBuilder = mTarget->CreatePathBuilder();
1237 clipOrTransform->clip->StreamToSink(pathBuilder);
1238 clipOrTransform->clip = pathBuilder->Finish();
1240 mTarget->PushClip(clipOrTransform->clip);
1241 } else {
1242 mTarget->SetTransform(clipOrTransform->transform);
1247 mClipsNeedConverting = false;
1250 bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
1251 bool aWillClear) {
1252 if (AlreadyShutDown()) {
1253 gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
1254 SetErrorState();
1255 return false;
1258 if (mTarget) {
1259 return mTarget != sErrorTarget;
1262 // Check that the dimensions are sane
1263 if (mWidth > StaticPrefs::gfx_canvas_max_size() ||
1264 mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 ||
1265 mHeight < 0) {
1266 SetErrorState();
1267 return false;
1270 // If the next drawing command covers the entire canvas, we can skip copying
1271 // from the previous frame and/or clearing the canvas.
1272 gfx::Rect canvasRect(0, 0, mWidth, mHeight);
1273 bool canDiscardContent =
1274 aCoveredRect && CurrentState()
1275 .transform.TransformBounds(*aCoveredRect)
1276 .Contains(canvasRect);
1278 // If a clip is active we don't know for sure that the next drawing command
1279 // will really cover the entire canvas.
1280 for (const auto& style : mStyleStack) {
1281 if (!canDiscardContent) {
1282 break;
1284 for (const auto& clipOrTransform : style.clipsAndTransforms) {
1285 if (clipOrTransform.IsClip()) {
1286 canDiscardContent = false;
1287 break;
1292 ScheduleStableStateCallback();
1294 IntRect persistedRect =
1295 canDiscardContent ? IntRect() : IntRect(0, 0, mWidth, mHeight);
1297 if (mBufferProvider) {
1298 mTarget = mBufferProvider->BorrowDrawTarget(persistedRect);
1300 if (mTarget && !mBufferProvider->PreservesDrawingState()) {
1301 RestoreClipsAndTransformToTarget();
1304 if (mTarget && mTarget->IsValid()) {
1305 return true;
1309 RefPtr<DrawTarget> newTarget;
1310 RefPtr<PersistentBufferProvider> newProvider;
1312 if (!TrySharedTarget(newTarget, newProvider) &&
1313 !TryBasicTarget(newTarget, newProvider)) {
1314 gfxCriticalError(
1315 CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
1316 << "Failed borrow shared and basic targets.";
1318 SetErrorState();
1319 return false;
1322 MOZ_ASSERT(newTarget);
1323 MOZ_ASSERT(newProvider);
1325 bool needsClear = !canDiscardContent;
1326 if (newTarget->GetBackendType() == gfx::BackendType::SKIA &&
1327 (needsClear || !aWillClear)) {
1328 // Skia expects the unused X channel to contains 0xFF even for opaque
1329 // operations so we can't skip clearing in that case, even if we are going
1330 // to cover the entire canvas in the next drawing operation.
1331 newTarget->ClearRect(canvasRect);
1332 needsClear = false;
1335 // Try to copy data from the previous buffer provider if there is one.
1336 if (!canDiscardContent && mBufferProvider &&
1337 CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
1338 needsClear = false;
1341 if (needsClear) {
1342 newTarget->ClearRect(canvasRect);
1345 mTarget = std::move(newTarget);
1346 mBufferProvider = std::move(newProvider);
1348 RegisterAllocation();
1350 RestoreClipsAndTransformToTarget();
1352 // Force a full layer transaction since we didn't have a layer before
1353 // and now we might need one.
1354 if (mCanvasElement) {
1355 mCanvasElement->InvalidateCanvas();
1357 // EnsureTarget hasn't drawn anything. Preserve mIsCapturedFrameInvalid.
1358 bool capturedFrameInvalid = mIsCapturedFrameInvalid;
1359 // Calling Redraw() tells our invalidation machinery that the entire
1360 // canvas is already invalid, which can speed up future drawing.
1361 Redraw();
1362 mIsCapturedFrameInvalid = capturedFrameInvalid;
1364 return true;
1367 void CanvasRenderingContext2D::SetInitialState() {
1368 // Set up the initial canvas defaults
1369 mPathBuilder = nullptr;
1370 mPath = nullptr;
1371 mDSPathBuilder = nullptr;
1372 mPathTransformWillUpdate = false;
1374 mStyleStack.Clear();
1375 ContextState* state = mStyleStack.AppendElement();
1376 state->globalAlpha = 1.0;
1378 state->colorStyles[Style::FILL] = NS_RGB(0, 0, 0);
1379 state->colorStyles[Style::STROKE] = NS_RGB(0, 0, 0);
1380 state->shadowColor = NS_RGBA(0, 0, 0, 0);
1383 void CanvasRenderingContext2D::SetErrorState() {
1384 EnsureErrorTarget();
1386 if (mTarget && mTarget != sErrorTarget) {
1387 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
1390 mTarget = sErrorTarget;
1391 mBufferProvider = nullptr;
1393 // clear transforms, clips, etc.
1394 SetInitialState();
1397 void CanvasRenderingContext2D::RegisterAllocation() {
1398 // XXX - It would make more sense to track the allocation in
1399 // PeristentBufferProvider, rather than here.
1400 static bool registered = false;
1401 // FIXME: Disable the reporter for now, see bug 1241865
1402 if (!registered && false) {
1403 registered = true;
1404 RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
1407 JSObject* wrapper = GetWrapperPreserveColor();
1408 if (wrapper) {
1409 CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
1410 JS::GetObjectZone(wrapper));
1414 static already_AddRefed<LayerManager> LayerManagerFromCanvasElement(
1415 nsINode* aCanvasElement) {
1416 if (!aCanvasElement) {
1417 return nullptr;
1420 return nsContentUtils::PersistentLayerManagerForDocument(
1421 aCanvasElement->OwnerDoc());
1424 bool CanvasRenderingContext2D::TrySharedTarget(
1425 RefPtr<gfx::DrawTarget>& aOutDT,
1426 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1427 aOutDT = nullptr;
1428 aOutProvider = nullptr;
1430 if (!mCanvasElement) {
1431 return false;
1434 if (mBufferProvider &&
1435 (mBufferProvider->GetType() == LayersBackend::LAYERS_CLIENT ||
1436 mBufferProvider->GetType() == LayersBackend::LAYERS_WR)) {
1437 // we are already using a shared buffer provider, we are allocating a new
1438 // one because the current one failed so let's just fall back to the basic
1439 // provider.
1440 mClipsNeedConverting = true;
1441 return false;
1444 RefPtr<LayerManager> layerManager =
1445 LayerManagerFromCanvasElement(mCanvasElement);
1447 if (!layerManager) {
1448 return false;
1451 aOutProvider = layerManager->CreatePersistentBufferProvider(
1452 GetSize(), GetSurfaceFormat());
1454 if (!aOutProvider) {
1455 return false;
1458 // We can pass an empty persisted rect since we just created the buffer
1459 // provider (nothing to restore).
1460 aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
1461 MOZ_ASSERT(aOutDT);
1463 return !!aOutDT;
1466 bool CanvasRenderingContext2D::TryBasicTarget(
1467 RefPtr<gfx::DrawTarget>& aOutDT,
1468 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1469 aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
1470 GetSize(), GetSurfaceFormat());
1471 if (!aOutDT) {
1472 return false;
1475 // See Bug 1524554 - this forces DT initialization.
1476 aOutDT->ClearRect(gfx::Rect());
1478 if (!aOutDT->IsValid()) {
1479 aOutDT = nullptr;
1480 return false;
1483 aOutProvider = new PersistentBufferProviderBasic(aOutDT);
1484 return true;
1487 PresShell* CanvasRenderingContext2D::GetPresShell() {
1488 if (mCanvasElement) {
1489 return mCanvasElement->OwnerDoc()->GetPresShell();
1491 if (mDocShell) {
1492 return mDocShell->GetPresShell();
1494 return nullptr;
1497 NS_IMETHODIMP
1498 CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) {
1499 // Zero sized surfaces can cause problems.
1500 mZero = false;
1501 if (aHeight == 0) {
1502 aHeight = 1;
1503 mZero = true;
1505 if (aWidth == 0) {
1506 aWidth = 1;
1507 mZero = true;
1510 ClearTarget(aWidth, aHeight);
1512 return NS_OK;
1515 void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) {
1516 Reset();
1518 mResetLayer = true;
1520 SetInitialState();
1522 // Update dimensions only if new (strictly positive) values were passed.
1523 if (aWidth > 0 && aHeight > 0) {
1524 // Update the memory size associated with the wrapper object when we change
1525 // the dimensions. Note that we need to keep updating dying wrappers before
1526 // they are finalized so that the memory accounting balances out.
1527 JSObject* wrapper = GetWrapperMaybeDead();
1528 if (wrapper) {
1529 JS::RemoveAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
1530 JS::MemoryUse::DOMBinding);
1533 mWidth = aWidth;
1534 mHeight = aHeight;
1536 if (wrapper) {
1537 JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
1538 JS::MemoryUse::DOMBinding);
1542 if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) {
1543 return;
1546 // For vertical writing-mode, unless text-orientation is sideways,
1547 // we'll modify the initial value of textBaseline to 'middle'.
1548 RefPtr<ComputedStyle> canvasStyle =
1549 nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
1550 if (canvasStyle) {
1551 WritingMode wm(canvasStyle);
1552 if (wm.IsVertical() && !wm.IsSideways()) {
1553 CurrentState().textBaseline = TextBaseline::MIDDLE;
1558 void CanvasRenderingContext2D::ReturnTarget(bool aForceReset) {
1559 if (mTarget && mBufferProvider && mTarget != sErrorTarget) {
1560 CurrentState().transform = mTarget->GetTransform();
1561 if (aForceReset || !mBufferProvider->PreservesDrawingState()) {
1562 for (const auto& style : mStyleStack) {
1563 for (const auto& clipOrTransform : style.clipsAndTransforms) {
1564 if (clipOrTransform.IsClip()) {
1565 mTarget->PopClip();
1570 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1571 // With the cairo backend we pushed an extra clip rect which we have to
1572 // balance out here. See the comment in
1573 // RestoreClipsAndTransformToTarget.
1574 mTarget->PopClip();
1577 mTarget->SetTransform(Matrix());
1580 mBufferProvider->ReturnDrawTarget(mTarget.forget());
1584 NS_IMETHODIMP
1585 CanvasRenderingContext2D::InitializeWithDrawTarget(
1586 nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) {
1587 RemovePostRefreshObserver();
1588 mDocShell = aShell;
1589 AddPostRefreshObserverIfNecessary();
1591 IntSize size = aTarget->GetSize();
1592 SetDimensions(size.width, size.height);
1594 mTarget = aTarget;
1595 mBufferProvider = new PersistentBufferProviderBasic(aTarget);
1597 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1598 // Cf comment in EnsureTarget
1599 mTarget->PushClipRect(gfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
1602 return NS_OK;
1605 void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(
1606 bool aOpaqueAttrValue) {
1607 if (aOpaqueAttrValue != mOpaqueAttrValue) {
1608 mOpaqueAttrValue = aOpaqueAttrValue;
1609 UpdateIsOpaque();
1613 void CanvasRenderingContext2D::UpdateIsOpaque() {
1614 mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue;
1615 ClearTarget();
1618 NS_IMETHODIMP
1619 CanvasRenderingContext2D::SetIsIPC(bool aIsIPC) {
1620 if (aIsIPC != mIPC) {
1621 mIPC = aIsIPC;
1622 ClearTarget();
1625 return NS_OK;
1628 NS_IMETHODIMP
1629 CanvasRenderingContext2D::SetContextOptions(JSContext* aCx,
1630 JS::Handle<JS::Value> aOptions,
1631 ErrorResult& aRvForDictionaryInit) {
1632 if (aOptions.isNullOrUndefined()) {
1633 return NS_OK;
1636 // This shouldn't be called before drawing starts, so there should be no
1637 // drawtarget yet
1638 MOZ_ASSERT(!mTarget);
1640 ContextAttributes2D attributes;
1641 if (!attributes.Init(aCx, aOptions)) {
1642 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
1643 return NS_ERROR_UNEXPECTED;
1646 mContextAttributesHasAlpha = attributes.mAlpha;
1647 UpdateIsOpaque();
1649 return NS_OK;
1652 UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
1653 int32_t* aFormat) {
1654 UniquePtr<uint8_t[]> ret;
1656 *aFormat = 0;
1658 if (!mBufferProvider) {
1659 if (!EnsureTarget()) {
1660 return nullptr;
1664 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
1665 if (snapshot) {
1666 RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
1667 if (data && data->GetSize() == GetSize()) {
1668 *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1669 ret = SurfaceToPackedBGRA(data);
1673 mBufferProvider->ReturnSnapshot(snapshot.forget());
1675 return ret;
1678 nsString CanvasRenderingContext2D::GetHitRegion(
1679 const mozilla::gfx::Point& aPoint) {
1680 for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
1681 RegionInfo& info = mHitRegionsOptions[x];
1682 if (info.mPath->ContainsPoint(aPoint, Matrix())) {
1683 return info.mId;
1686 return nsString();
1689 NS_IMETHODIMP
1690 CanvasRenderingContext2D::GetInputStream(const char* aMimeType,
1691 const nsAString& aEncoderOptions,
1692 nsIInputStream** aStream) {
1693 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1694 enccid += aMimeType;
1695 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1696 if (!encoder) {
1697 return NS_ERROR_FAILURE;
1700 int32_t format = 0;
1701 UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format);
1702 if (!imageBuffer) {
1703 return NS_ERROR_FAILURE;
1706 return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(),
1707 format, encoder, aEncoderOptions,
1708 aStream);
1711 already_AddRefed<mozilla::gfx::SourceSurface>
1712 CanvasRenderingContext2D::GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) {
1713 if (aOutAlphaType) {
1714 *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
1717 // For GetSurfaceSnapshot we always call EnsureTarget even if mBufferProvider
1718 // already exists, otherwise we get performance issues. See bug 1567054.
1719 if (!EnsureTarget()) {
1720 MOZ_ASSERT(
1721 mTarget == sErrorTarget,
1722 "On EnsureTarget failure mTarget should be set to sErrorTarget.");
1723 return mTarget->Snapshot();
1726 // The concept of BorrowSnapshot seems a bit broken here, but the original
1727 // code in GetSurfaceSnapshot just returned a snapshot from mTarget, which
1728 // amounts to breaking the concept implicitly.
1729 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
1730 RefPtr<SourceSurface> retSurface = snapshot;
1731 mBufferProvider->ReturnSnapshot(snapshot.forget());
1732 return retSurface.forget();
1735 SurfaceFormat CanvasRenderingContext2D::GetSurfaceFormat() const {
1736 return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
1740 // state
1743 void CanvasRenderingContext2D::Save() {
1744 EnsureTarget();
1745 if (MOZ_UNLIKELY(!mTarget || mStyleStack.IsEmpty())) {
1746 SetErrorState();
1747 return;
1749 mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
1750 mStyleStack.SetCapacity(mStyleStack.Length() + 1);
1751 mStyleStack.AppendElement(CurrentState());
1753 if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) {
1754 // This is not fast, but is better than OOMing and shouldn't be hit by
1755 // reasonable code.
1756 mStyleStack.RemoveElementAt(0);
1760 void CanvasRenderingContext2D::Restore() {
1761 if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) {
1762 return;
1765 TransformWillUpdate();
1766 if (!IsTargetValid()) {
1767 return;
1770 for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
1771 if (clipOrTransform.IsClip()) {
1772 mTarget->PopClip();
1776 mStyleStack.RemoveLastElement();
1778 mTarget->SetTransform(CurrentState().transform);
1782 // transformations
1785 void CanvasRenderingContext2D::Scale(double aX, double aY,
1786 ErrorResult& aError) {
1787 TransformWillUpdate();
1788 if (!IsTargetValid()) {
1789 aError.Throw(NS_ERROR_FAILURE);
1790 return;
1793 Matrix newMatrix = mTarget->GetTransform();
1794 newMatrix.PreScale(aX, aY);
1796 SetTransformInternal(newMatrix);
1799 void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) {
1800 TransformWillUpdate();
1801 if (!IsTargetValid()) {
1802 aError.Throw(NS_ERROR_FAILURE);
1803 return;
1806 Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform();
1808 SetTransformInternal(newMatrix);
1811 void CanvasRenderingContext2D::Translate(double aX, double aY,
1812 ErrorResult& aError) {
1813 TransformWillUpdate();
1814 if (!IsTargetValid()) {
1815 aError.Throw(NS_ERROR_FAILURE);
1816 return;
1819 Matrix newMatrix = mTarget->GetTransform();
1820 newMatrix.PreTranslate(aX, aY);
1822 SetTransformInternal(newMatrix);
1825 void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
1826 double aM22, double aDx, double aDy,
1827 ErrorResult& aError) {
1828 TransformWillUpdate();
1829 if (!IsTargetValid()) {
1830 aError.Throw(NS_ERROR_FAILURE);
1831 return;
1834 Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
1835 newMatrix *= mTarget->GetTransform();
1837 SetTransformInternal(newMatrix);
1840 already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform(
1841 ErrorResult& aError) {
1842 EnsureTarget();
1843 if (!IsTargetValid()) {
1844 aError.Throw(NS_ERROR_FAILURE);
1845 return nullptr;
1847 RefPtr<DOMMatrix> matrix =
1848 new DOMMatrix(GetParentObject(), mTarget->GetTransform());
1849 return matrix.forget();
1852 void CanvasRenderingContext2D::SetTransform(double aM11, double aM12,
1853 double aM21, double aM22,
1854 double aDx, double aDy,
1855 ErrorResult& aError) {
1856 TransformWillUpdate();
1857 if (!IsTargetValid()) {
1858 aError.Throw(NS_ERROR_FAILURE);
1859 return;
1862 SetTransformInternal(Matrix(aM11, aM12, aM21, aM22, aDx, aDy));
1865 void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit,
1866 ErrorResult& aError) {
1867 TransformWillUpdate();
1868 if (!IsTargetValid()) {
1869 aError.Throw(NS_ERROR_FAILURE);
1870 return;
1873 RefPtr<DOMMatrixReadOnly> matrix =
1874 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
1875 if (!aError.Failed()) {
1876 SetTransformInternal(Matrix(*(matrix->GetInternal2D())));
1880 void CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform) {
1881 if (!aTransform.IsFinite()) {
1882 return;
1885 // Save the transform in the clip stack to be able to replay clips properly.
1886 auto& clipsAndTransforms = CurrentState().clipsAndTransforms;
1887 if (clipsAndTransforms.IsEmpty() ||
1888 clipsAndTransforms.LastElement().IsClip()) {
1889 clipsAndTransforms.AppendElement(ClipState(aTransform));
1890 } else {
1891 // If the last item is a transform we can replace it instead of appending
1892 // a new item.
1893 clipsAndTransforms.LastElement().transform = aTransform;
1895 mTarget->SetTransform(aTransform);
1898 void CanvasRenderingContext2D::ResetTransform(ErrorResult& aError) {
1899 SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError);
1902 static void MatrixToJSObject(JSContext* aCx, const Matrix& aMatrix,
1903 JS::MutableHandle<JSObject*> aResult,
1904 ErrorResult& aError) {
1905 double elts[6] = {aMatrix._11, aMatrix._12, aMatrix._21,
1906 aMatrix._22, aMatrix._31, aMatrix._32};
1908 // XXX Should we enter GetWrapper()'s compartment?
1909 JS::Rooted<JS::Value> val(aCx);
1910 if (!ToJSValue(aCx, elts, &val)) {
1911 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
1912 } else {
1913 aResult.set(&val.toObject());
1917 static bool ObjectToMatrix(JSContext* aCx, JS::Handle<JSObject*> aObj,
1918 Matrix& aMatrix, ErrorResult& aError) {
1919 uint32_t length;
1920 if (!JS::GetArrayLength(aCx, aObj, &length) || length != 6) {
1921 // Not an array-like thing or wrong size
1922 aError.Throw(NS_ERROR_INVALID_ARG);
1923 return false;
1926 Float* elts[] = {&aMatrix._11, &aMatrix._12, &aMatrix._21,
1927 &aMatrix._22, &aMatrix._31, &aMatrix._32};
1928 for (uint32_t i = 0; i < 6; ++i) {
1929 JS::Rooted<JS::Value> elt(aCx);
1930 double d;
1931 if (!JS_GetElement(aCx, aObj, i, &elt)) {
1932 aError.Throw(NS_ERROR_FAILURE);
1933 return false;
1935 if (!CoerceDouble(elt, &d)) {
1936 aError.Throw(NS_ERROR_INVALID_ARG);
1937 return false;
1939 if (!FloatValidate(d)) {
1940 // This is weird, but it's the behavior of SetTransform()
1941 return false;
1943 *elts[i] = Float(d);
1945 return true;
1948 void CanvasRenderingContext2D::SetMozCurrentTransform(
1949 JSContext* aCx, JS::Handle<JSObject*> aCurrentTransform,
1950 ErrorResult& aError) {
1951 EnsureTarget();
1952 if (!IsTargetValid()) {
1953 aError.Throw(NS_ERROR_FAILURE);
1954 return;
1957 Matrix newCTM;
1958 if (ObjectToMatrix(aCx, aCurrentTransform, newCTM, aError) &&
1959 newCTM.IsFinite()) {
1960 mTarget->SetTransform(newCTM);
1964 void CanvasRenderingContext2D::GetMozCurrentTransform(
1965 JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aError) {
1966 EnsureTarget();
1968 MatrixToJSObject(aCx, mTarget ? mTarget->GetTransform() : Matrix(), aResult,
1969 aError);
1972 void CanvasRenderingContext2D::SetMozCurrentTransformInverse(
1973 JSContext* aCx, JS::Handle<JSObject*> aCurrentTransform,
1974 ErrorResult& aError) {
1975 EnsureTarget();
1976 if (!IsTargetValid()) {
1977 aError.Throw(NS_ERROR_FAILURE);
1978 return;
1981 Matrix newCTMInverse;
1982 if (ObjectToMatrix(aCx, aCurrentTransform, newCTMInverse, aError)) {
1983 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
1984 if (newCTMInverse.Invert() && newCTMInverse.IsFinite()) {
1985 mTarget->SetTransform(newCTMInverse);
1990 void CanvasRenderingContext2D::GetMozCurrentTransformInverse(
1991 JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aError) {
1992 EnsureTarget();
1994 if (!mTarget) {
1995 MatrixToJSObject(aCx, Matrix(), aResult, aError);
1996 return;
1999 Matrix ctm = mTarget->GetTransform();
2001 if (!ctm.Invert()) {
2002 double NaN = JS::GenericNaN();
2003 ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN);
2006 MatrixToJSObject(aCx, ctm, aResult, aError);
2010 // colors
2013 void CanvasRenderingContext2D::SetStyleFromUnion(
2014 const UTF8StringOrCanvasGradientOrCanvasPattern& aValue,
2015 Style aWhichStyle) {
2016 if (aValue.IsUTF8String()) {
2017 SetStyleFromString(aValue.GetAsUTF8String(), aWhichStyle);
2018 return;
2021 if (aValue.IsCanvasGradient()) {
2022 SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle);
2023 return;
2026 if (aValue.IsCanvasPattern()) {
2027 CanvasPattern& pattern = aValue.GetAsCanvasPattern();
2028 SetStyleFromPattern(pattern, aWhichStyle);
2029 if (pattern.mForceWriteOnly) {
2030 SetWriteOnly();
2032 return;
2035 MOZ_ASSERT_UNREACHABLE("Invalid union value");
2038 void CanvasRenderingContext2D::SetFillRule(const nsAString& aString) {
2039 FillRule rule;
2041 if (aString.EqualsLiteral("evenodd"))
2042 rule = FillRule::FILL_EVEN_ODD;
2043 else if (aString.EqualsLiteral("nonzero"))
2044 rule = FillRule::FILL_WINDING;
2045 else
2046 return;
2048 CurrentState().fillRule = rule;
2051 void CanvasRenderingContext2D::GetFillRule(nsAString& aString) {
2052 switch (CurrentState().fillRule) {
2053 case FillRule::FILL_WINDING:
2054 aString.AssignLiteral("nonzero");
2055 break;
2056 case FillRule::FILL_EVEN_ODD:
2057 aString.AssignLiteral("evenodd");
2058 break;
2062 // gradients and patterns
2064 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateLinearGradient(
2065 double aX0, double aY0, double aX1, double aY1) {
2066 RefPtr<CanvasGradient> grad =
2067 new CanvasLinearGradient(this, Point(aX0, aY0), Point(aX1, aY1));
2069 return grad.forget();
2072 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateRadialGradient(
2073 double aX0, double aY0, double aR0, double aX1, double aY1, double aR1,
2074 ErrorResult& aError) {
2075 if (aR0 < 0.0 || aR1 < 0.0) {
2076 aError.ThrowIndexSizeError("Negative radius");
2077 return nullptr;
2080 RefPtr<CanvasGradient> grad = new CanvasRadialGradient(
2081 this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1);
2083 return grad.forget();
2086 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateConicGradient(
2087 double aAngle, double aCx, double aCy) {
2088 return MakeAndAddRef<CanvasConicGradient>(this, aAngle, Point(aCx, aCy));
2091 already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern(
2092 const CanvasImageSource& aSource, const nsAString& aRepeat,
2093 ErrorResult& aError) {
2094 CanvasPattern::RepeatMode repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
2096 if (aRepeat.IsEmpty() || aRepeat.EqualsLiteral("repeat")) {
2097 repeatMode = CanvasPattern::RepeatMode::REPEAT;
2098 } else if (aRepeat.EqualsLiteral("repeat-x")) {
2099 repeatMode = CanvasPattern::RepeatMode::REPEATX;
2100 } else if (aRepeat.EqualsLiteral("repeat-y")) {
2101 repeatMode = CanvasPattern::RepeatMode::REPEATY;
2102 } else if (aRepeat.EqualsLiteral("no-repeat")) {
2103 repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
2104 } else {
2105 aError.ThrowSyntaxError("Invalid pattern keyword");
2106 return nullptr;
2109 Element* element;
2110 if (aSource.IsHTMLCanvasElement()) {
2111 HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement();
2112 element = canvas;
2114 nsIntSize size = canvas->GetSize();
2115 if (size.width == 0) {
2116 aError.ThrowInvalidStateError("Passed-in canvas has width 0");
2117 return nullptr;
2120 if (size.height == 0) {
2121 aError.ThrowInvalidStateError("Passed-in canvas has height 0");
2122 return nullptr;
2125 // Special case for Canvas, which could be an Azure canvas!
2126 nsICanvasRenderingContextInternal* srcCanvas = canvas->GetCurrentContext();
2127 if (srcCanvas) {
2128 // This might not be an Azure canvas!
2129 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
2130 if (!srcSurf) {
2131 aError.ThrowInvalidStateError(
2132 "CanvasRenderingContext2D.createPattern() failed to snapshot source"
2133 "canvas.");
2134 return nullptr;
2137 RefPtr<CanvasPattern> pat =
2138 new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(),
2139 canvas->IsWriteOnly(), false);
2141 return pat.forget();
2143 } else if (aSource.IsHTMLImageElement()) {
2144 HTMLImageElement* img = &aSource.GetAsHTMLImageElement();
2145 element = img;
2146 } else if (aSource.IsSVGImageElement()) {
2147 SVGImageElement* img = &aSource.GetAsSVGImageElement();
2148 element = img;
2149 } else if (aSource.IsHTMLVideoElement()) {
2150 auto& video = aSource.GetAsHTMLVideoElement();
2151 video.MarkAsContentSource(
2152 mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
2153 element = &video;
2154 } else {
2155 // Special case for ImageBitmap
2156 ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
2157 EnsureTarget();
2158 if (!IsTargetValid()) {
2159 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2160 return nullptr;
2162 RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
2163 if (!srcSurf) {
2164 aError.ThrowInvalidStateError(
2165 "Passed-in ImageBitmap has been transferred");
2166 return nullptr;
2169 // An ImageBitmap never taints others so we set principalForSecurityCheck to
2170 // nullptr and set CORSUsed to true for passing the security check in
2171 // CanvasUtils::DoDrawImageSecurityCheck().
2172 RefPtr<CanvasPattern> pat = new CanvasPattern(
2173 this, srcSurf, repeatMode, nullptr, imgBitmap.IsWriteOnly(), true);
2175 return pat.forget();
2178 EnsureTarget();
2179 if (!IsTargetValid()) {
2180 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2181 return nullptr;
2184 // The canvas spec says that createPattern should use the first frame
2185 // of animated images
2186 SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromElement(
2187 element, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE, mTarget);
2189 // Per spec, we should throw here for the HTMLImageElement and SVGImageElement
2190 // cases if the image request state is "broken". In terms of the infromation
2191 // in "res", the "broken" state corresponds to not having a size and not being
2192 // still-loading (so there is no size forthcoming).
2193 if (aSource.IsHTMLImageElement() || aSource.IsSVGImageElement()) {
2194 if (!res.mIsStillLoading && !res.mHasSize) {
2195 aError.ThrowInvalidStateError(
2196 "Passed-in image's current request's state is \"broken\"");
2197 return nullptr;
2200 if (res.mSize.width == 0 || res.mSize.height == 0) {
2201 return nullptr;
2204 // Is the "fully decodable" check already done in SurfaceFromElement? It's
2205 // not clear how to do it from here, exactly.
2208 RefPtr<SourceSurface> surface = res.GetSourceSurface();
2209 if (!surface) {
2210 return nullptr;
2213 RefPtr<CanvasPattern> pat =
2214 new CanvasPattern(this, surface, repeatMode, res.mPrincipal,
2215 res.mIsWriteOnly, res.mCORSUsed);
2216 return pat.forget();
2220 // shadows
2222 void CanvasRenderingContext2D::SetShadowColor(const nsACString& aShadowColor) {
2223 nscolor color;
2224 if (!ParseColor(aShadowColor, &color)) {
2225 return;
2228 CurrentState().shadowColor = color;
2232 // filters
2235 static already_AddRefed<RawServoDeclarationBlock> CreateDeclarationForServo(
2236 nsCSSPropertyID aProperty, const nsACString& aPropertyValue,
2237 Document* aDocument) {
2238 ServoCSSParser::ParsingEnvironment env{aDocument->DefaultStyleAttrURLData(),
2239 aDocument->GetCompatibilityMode(),
2240 aDocument->CSSLoader()};
2241 RefPtr<RawServoDeclarationBlock> servoDeclarations =
2242 ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env);
2244 if (!servoDeclarations) {
2245 // We got a syntax error. The spec says this value must be ignored.
2246 return nullptr;
2249 // From canvas spec, force to set line-height property to 'normal' font
2250 // property.
2251 if (aProperty == eCSSProperty_font) {
2252 const nsCString normalString = "normal"_ns;
2253 Servo_DeclarationBlock_SetPropertyById(
2254 servoDeclarations, eCSSProperty_line_height, &normalString, false,
2255 env.mUrlExtraData, ParsingMode::Default, env.mCompatMode, env.mLoader,
2256 env.mRuleType, {});
2259 return servoDeclarations.forget();
2262 static already_AddRefed<RawServoDeclarationBlock> CreateFontDeclarationForServo(
2263 const nsACString& aFont, Document* aDocument) {
2264 return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument);
2267 static already_AddRefed<ComputedStyle> GetFontStyleForServo(
2268 Element* aElement, const nsACString& aFont, PresShell* aPresShell,
2269 nsACString& aOutUsedFont, ErrorResult& aError) {
2270 RefPtr<RawServoDeclarationBlock> declarations =
2271 CreateFontDeclarationForServo(aFont, aPresShell->GetDocument());
2272 if (!declarations) {
2273 // We got a syntax error. The spec says this value must be ignored.
2274 return nullptr;
2277 // In addition to unparseable values, the spec says we need to reject
2278 // 'inherit' and 'initial'. The easiest way to check for this is to look
2279 // at font-size-adjust, which the font shorthand resets to 'none'.
2280 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations,
2281 eCSSProperty_font_size_adjust)) {
2282 return nullptr;
2285 ServoStyleSet* styleSet = aPresShell->StyleSet();
2287 RefPtr<ComputedStyle> parentStyle;
2288 // have to get a parent ComputedStyle for inherit-like relative
2289 // values (2em, bolder, etc.)
2290 if (aElement && aElement->IsInComposedDoc()) {
2291 parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement, nullptr);
2292 if (!parentStyle) {
2293 // The flush killed the shell, so we couldn't get any meaningful style
2294 // back.
2295 aError.Throw(NS_ERROR_FAILURE);
2296 return nullptr;
2298 } else {
2299 RefPtr<RawServoDeclarationBlock> declarations =
2300 CreateFontDeclarationForServo("10px sans-serif"_ns,
2301 aPresShell->GetDocument());
2302 MOZ_ASSERT(declarations);
2304 parentStyle =
2305 aPresShell->StyleSet()->ResolveForDeclarations(nullptr, declarations);
2308 MOZ_RELEASE_ASSERT(parentStyle, "Should have a valid parent style");
2310 MOZ_ASSERT(!aPresShell->IsDestroying(),
2311 "We should have returned an error above if the presshell is "
2312 "being destroyed.");
2314 RefPtr<ComputedStyle> sc =
2315 styleSet->ResolveForDeclarations(parentStyle, declarations);
2317 // The font getter is required to be reserialized based on what we
2318 // parsed (including having line-height removed). (Older drafts of
2319 // the spec required font sizes be converted to pixels, but that no
2320 // longer seems to be required.)
2321 Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont);
2322 return sc.forget();
2325 static already_AddRefed<RawServoDeclarationBlock>
2326 CreateFilterDeclarationForServo(const nsACString& aFilter,
2327 Document* aDocument) {
2328 return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument);
2331 static already_AddRefed<ComputedStyle> ResolveFilterStyleForServo(
2332 const nsACString& aFilterString, const ComputedStyle* aParentStyle,
2333 PresShell* aPresShell, ErrorResult& aError) {
2334 RefPtr<RawServoDeclarationBlock> declarations =
2335 CreateFilterDeclarationForServo(aFilterString, aPresShell->GetDocument());
2336 if (!declarations) {
2337 // Refuse to accept the filter, but do not throw an error.
2338 return nullptr;
2341 // In addition to unparseable values, the spec says we need to reject
2342 // 'inherit' and 'initial'.
2343 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations,
2344 eCSSProperty_filter)) {
2345 return nullptr;
2348 ServoStyleSet* styleSet = aPresShell->StyleSet();
2349 RefPtr<ComputedStyle> computedValues =
2350 styleSet->ResolveForDeclarations(aParentStyle, declarations);
2352 return computedValues.forget();
2355 bool CanvasRenderingContext2D::ParseFilter(
2356 const nsACString& aString, StyleOwnedSlice<StyleFilter>& aFilterChain,
2357 ErrorResult& aError) {
2358 if (!mCanvasElement && !mDocShell) {
2359 NS_WARNING(
2360 "Canvas element must be non-null or a docshell must be provided");
2361 aError.Throw(NS_ERROR_FAILURE);
2362 return false;
2365 RefPtr<PresShell> presShell = GetPresShell();
2366 if (NS_WARN_IF(!presShell)) {
2367 aError.Throw(NS_ERROR_FAILURE);
2368 return false;
2371 nsAutoCString usedFont; // unused
2373 RefPtr<ComputedStyle> parentStyle = GetFontStyleForServo(
2374 mCanvasElement, GetFont(), presShell, usedFont, aError);
2375 if (!parentStyle) {
2376 return false;
2379 RefPtr<ComputedStyle> style =
2380 ResolveFilterStyleForServo(aString, parentStyle, presShell, aError);
2381 if (!style) {
2382 return false;
2385 aFilterChain = style->StyleEffects()->mFilters;
2386 return true;
2389 void CanvasRenderingContext2D::SetFilter(const nsACString& aFilter,
2390 ErrorResult& aError) {
2391 StyleOwnedSlice<StyleFilter> filterChain;
2392 if (ParseFilter(aFilter, filterChain, aError)) {
2393 CurrentState().filterString = aFilter;
2394 CurrentState().filterChain = std::move(filterChain);
2395 if (mCanvasElement) {
2396 CurrentState().autoSVGFiltersObserver =
2397 SVGObserverUtils::ObserveFiltersForCanvasContext(
2398 this, mCanvasElement, CurrentState().filterChain.AsSpan());
2399 UpdateFilter();
2404 class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize {
2405 public:
2406 CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont,
2407 nsAtom* aFontLanguage, bool aExplicitLanguage,
2408 nsPresContext* aPresContext)
2409 : mSize(aSize),
2410 mFont(aFont),
2411 mFontLanguage(aFontLanguage),
2412 mExplicitLanguage(aExplicitLanguage),
2413 mPresContext(aPresContext) {}
2415 virtual float GetEmLength() const override {
2416 return mFont.size.ToCSSPixels();
2419 virtual float GetExLength() const override {
2420 nsDeviceContext* dc = mPresContext->DeviceContext();
2421 nsFontMetrics::Params params;
2422 params.language = mFontLanguage;
2423 params.explicitLanguage = mExplicitLanguage;
2424 params.textPerf = mPresContext->GetTextPerfMetrics();
2425 params.fontStats = mPresContext->GetFontMatchingStats();
2426 params.featureValueLookup = mPresContext->GetFontFeatureValuesLookup();
2427 RefPtr<nsFontMetrics> fontMetrics = dc->GetMetricsFor(mFont, params);
2428 return NSAppUnitsToFloatPixels(fontMetrics->XHeight(),
2429 AppUnitsPerCSSPixel());
2432 virtual gfx::Size GetSize() const override { return Size(mSize); }
2434 private:
2435 gfx::IntSize mSize;
2436 const nsFont& mFont;
2437 nsAtom* mFontLanguage;
2438 bool mExplicitLanguage;
2439 nsPresContext* mPresContext;
2442 // The filter might reference an SVG filter that is declared inside this
2443 // document. Flush frames so that we'll have a SVGFilterFrame to work
2444 // with.
2445 static bool FiltersNeedFrameFlush(Span<const StyleFilter> aFilters) {
2446 for (const auto& filter : aFilters) {
2447 if (filter.IsUrl()) {
2448 return true;
2451 return false;
2454 void CanvasRenderingContext2D::UpdateFilter() {
2455 RefPtr<PresShell> presShell = GetPresShell();
2456 if (!presShell || presShell->IsDestroying()) {
2457 // Ensure we set an empty filter and update the state to
2458 // reflect the current "taint" status of the canvas
2459 CurrentState().filter = FilterDescription();
2460 CurrentState().filterSourceGraphicTainted =
2461 mCanvasElement && mCanvasElement->IsWriteOnly();
2462 return;
2465 if (FiltersNeedFrameFlush(CurrentState().filterChain.AsSpan())) {
2466 presShell->FlushPendingNotifications(FlushType::Frames);
2469 MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty());
2470 if (MOZ_UNLIKELY(presShell->IsDestroying())) {
2471 return;
2474 const bool sourceGraphicIsTainted =
2475 mCanvasElement && mCanvasElement->IsWriteOnly();
2477 CurrentState().filter = FilterInstance::GetFilterDescription(
2478 mCanvasElement, CurrentState().filterChain.AsSpan(),
2479 sourceGraphicIsTainted,
2480 CanvasUserSpaceMetrics(
2481 GetSize(), CurrentState().fontFont, CurrentState().fontLanguage,
2482 CurrentState().fontExplicitLanguage, presShell->GetPresContext()),
2483 gfxRect(0, 0, mWidth, mHeight), CurrentState().filterAdditionalImages);
2484 CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted;
2488 // rects
2491 static bool ValidateRect(double& aX, double& aY, double& aWidth,
2492 double& aHeight, bool aIsZeroSizeValid) {
2493 if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) {
2494 return false;
2497 // bug 1018527
2498 // The values of canvas API input are in double precision, but Moz2D APIs are
2499 // using float precision. Bypass canvas API calls when the input is out of
2500 // float precision to avoid precision problem
2501 if (!std::isfinite((float)aX) | !std::isfinite((float)aY) |
2502 !std::isfinite((float)aWidth) | !std::isfinite((float)aHeight)) {
2503 return false;
2506 // bug 1074733
2507 // The canvas spec does not forbid rects with negative w or h, so given
2508 // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate
2509 // the appropriate rect by flipping negative dimensions. This prevents
2510 // draw targets from receiving "empty" rects later on.
2511 if (aWidth < 0) {
2512 aWidth = -aWidth;
2513 aX -= aWidth;
2515 if (aHeight < 0) {
2516 aHeight = -aHeight;
2517 aY -= aHeight;
2519 return true;
2522 void CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW,
2523 double aH) {
2524 // Do not allow zeros - it's a no-op at that point per spec.
2525 if (!ValidateRect(aX, aY, aW, aH, false)) {
2526 return;
2529 gfx::Rect clearRect(aX, aY, aW, aH);
2531 EnsureTarget(&clearRect, true);
2532 if (!IsTargetValid()) {
2533 return;
2536 mTarget->ClearRect(clearRect);
2538 RedrawUser(gfxRect(aX, aY, aW, aH));
2541 void CanvasRenderingContext2D::FillRect(double aX, double aY, double aW,
2542 double aH) {
2543 if (!ValidateRect(aX, aY, aW, aH, true)) {
2544 return;
2547 const ContextState* state = &CurrentState();
2548 if (state->patternStyles[Style::FILL]) {
2549 CanvasPattern::RepeatMode repeat =
2550 state->patternStyles[Style::FILL]->mRepeat;
2551 // In the FillRect case repeat modes are easy to deal with.
2552 bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
2553 repeat == CanvasPattern::RepeatMode::REPEATY;
2554 bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
2555 repeat == CanvasPattern::RepeatMode::REPEATX;
2557 IntSize patternSize =
2558 state->patternStyles[Style::FILL]->mSurface->GetSize();
2560 // We always need to execute painting for non-over operators, even if
2561 // we end up with w/h = 0.
2562 if (limitx) {
2563 if (aX < 0) {
2564 aW += aX;
2565 if (aW < 0) {
2566 aW = 0;
2569 aX = 0;
2571 if (aX + aW > patternSize.width) {
2572 aW = patternSize.width - aX;
2573 if (aW < 0) {
2574 aW = 0;
2578 if (limity) {
2579 if (aY < 0) {
2580 aH += aY;
2581 if (aH < 0) {
2582 aH = 0;
2585 aY = 0;
2587 if (aY + aH > patternSize.height) {
2588 aH = patternSize.height - aY;
2589 if (aH < 0) {
2590 aH = 0;
2595 state = nullptr;
2597 CompositionOp op = UsedOperation();
2598 bool isColor;
2599 bool discardContent =
2600 PatternIsOpaque(Style::FILL, &isColor) &&
2601 (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
2602 const gfx::Rect fillRect(aX, aY, aW, aH);
2603 EnsureTarget(discardContent ? &fillRect : nullptr, discardContent && isColor);
2604 if (!IsTargetValid()) {
2605 return;
2608 gfx::Rect bounds;
2609 const bool needBounds = NeedToCalculateBounds();
2610 if (!IsTargetValid()) {
2611 return;
2613 if (needBounds) {
2614 bounds = mTarget->GetTransform().TransformBounds(fillRect);
2617 AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled
2618 ? AntialiasMode::DEFAULT
2619 : AntialiasMode::NONE;
2621 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2622 if (!target) {
2623 return;
2625 target->FillRect(gfx::Rect(aX, aY, aW, aH),
2626 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
2627 DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
2629 RedrawUser(gfxRect(aX, aY, aW, aH));
2632 void CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW,
2633 double aH) {
2634 if (!aW && !aH) {
2635 return;
2638 if (!ValidateRect(aX, aY, aW, aH, true)) {
2639 return;
2642 EnsureTarget();
2643 if (!IsTargetValid()) {
2644 return;
2647 const bool needBounds = NeedToCalculateBounds();
2648 if (!IsTargetValid()) {
2649 return;
2652 gfx::Rect bounds;
2653 if (needBounds) {
2654 const ContextState& state = CurrentState();
2655 bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f,
2656 aW + state.lineWidth, aH + state.lineWidth);
2657 bounds = mTarget->GetTransform().TransformBounds(bounds);
2660 auto op = UsedOperation();
2661 if (!IsTargetValid()) {
2662 return;
2665 if (!aH) {
2666 CapStyle cap = CapStyle::BUTT;
2667 if (CurrentState().lineJoin == JoinStyle::ROUND) {
2668 cap = CapStyle::ROUND;
2670 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2671 if (!target) {
2672 return;
2675 const ContextState& state = CurrentState();
2676 target->StrokeLine(
2677 Point(aX, aY), Point(aX + aW, aY),
2678 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2679 StrokeOptions(state.lineWidth, state.lineJoin, cap, state.miterLimit,
2680 state.dash.Length(), state.dash.Elements(),
2681 state.dashOffset),
2682 DrawOptions(state.globalAlpha, op));
2683 return;
2686 if (!aW) {
2687 CapStyle cap = CapStyle::BUTT;
2688 if (CurrentState().lineJoin == JoinStyle::ROUND) {
2689 cap = CapStyle::ROUND;
2691 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2692 if (!target) {
2693 return;
2696 const ContextState& state = CurrentState();
2697 target->StrokeLine(
2698 Point(aX, aY), Point(aX, aY + aH),
2699 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2700 StrokeOptions(state.lineWidth, state.lineJoin, cap, state.miterLimit,
2701 state.dash.Length(), state.dash.Elements(),
2702 state.dashOffset),
2703 DrawOptions(state.globalAlpha, op));
2704 return;
2707 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2708 if (!target) {
2709 return;
2712 const ContextState& state = CurrentState();
2713 target->StrokeRect(
2714 gfx::Rect(aX, aY, aW, aH),
2715 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2716 StrokeOptions(state.lineWidth, state.lineJoin, state.lineCap,
2717 state.miterLimit, state.dash.Length(),
2718 state.dash.Elements(), state.dashOffset),
2719 DrawOptions(state.globalAlpha, op));
2721 Redraw();
2725 // path bits
2728 void CanvasRenderingContext2D::BeginPath() {
2729 mPath = nullptr;
2730 mPathBuilder = nullptr;
2731 mDSPathBuilder = nullptr;
2732 mPathTransformWillUpdate = false;
2735 void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) {
2736 EnsureUserSpacePath(aWinding);
2738 if (!mPath) {
2739 return;
2742 const bool needBounds = NeedToCalculateBounds();
2743 if (!IsTargetValid()) {
2744 return;
2746 gfx::Rect bounds;
2747 if (needBounds) {
2748 bounds = mPath->GetBounds(mTarget->GetTransform());
2751 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2752 if (!target) {
2753 return;
2756 auto op = UsedOperation();
2757 if (!IsTargetValid() || !target) {
2758 return;
2760 target->Fill(mPath,
2761 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
2762 DrawOptions(CurrentState().globalAlpha, op));
2763 Redraw();
2766 void CanvasRenderingContext2D::Fill(const CanvasPath& aPath,
2767 const CanvasWindingRule& aWinding) {
2768 EnsureTarget();
2769 if (!IsTargetValid()) {
2770 return;
2773 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
2774 if (!gfxpath) {
2775 return;
2778 const bool needBounds = NeedToCalculateBounds();
2779 if (!IsTargetValid()) {
2780 return;
2782 gfx::Rect bounds;
2783 if (needBounds) {
2784 bounds = gfxpath->GetBounds(mTarget->GetTransform());
2787 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2788 if (!target) {
2789 return;
2792 auto op = UsedOperation();
2793 if (!IsTargetValid() || !target) {
2794 return;
2796 target->Fill(gfxpath,
2797 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
2798 DrawOptions(CurrentState().globalAlpha, op));
2799 Redraw();
2802 void CanvasRenderingContext2D::Stroke() {
2803 EnsureUserSpacePath();
2805 if (!mPath) {
2806 return;
2809 const ContextState* state = &CurrentState();
2810 StrokeOptions strokeOptions(state->lineWidth, state->lineJoin, state->lineCap,
2811 state->miterLimit, state->dash.Length(),
2812 state->dash.Elements(), state->dashOffset);
2813 state = nullptr;
2815 const bool needBounds = NeedToCalculateBounds();
2816 if (!IsTargetValid()) {
2817 return;
2819 gfx::Rect bounds;
2820 if (needBounds) {
2821 bounds = mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
2824 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2825 if (!target) {
2826 return;
2829 auto op = UsedOperation();
2830 if (!IsTargetValid() || !target) {
2831 return;
2833 target->Stroke(mPath,
2834 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2835 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
2836 Redraw();
2839 void CanvasRenderingContext2D::Stroke(const CanvasPath& aPath) {
2840 EnsureTarget();
2841 if (!IsTargetValid()) {
2842 return;
2845 RefPtr<gfx::Path> gfxpath =
2846 aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
2848 if (!gfxpath) {
2849 return;
2852 const ContextState* state = &CurrentState();
2853 StrokeOptions strokeOptions(state->lineWidth, state->lineJoin, state->lineCap,
2854 state->miterLimit, state->dash.Length(),
2855 state->dash.Elements(), state->dashOffset);
2856 state = nullptr;
2858 const bool needBounds = NeedToCalculateBounds();
2859 if (!IsTargetValid()) {
2860 return;
2862 gfx::Rect bounds;
2863 if (needBounds) {
2864 bounds = gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
2867 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds);
2868 if (!target) {
2869 return;
2872 auto op = UsedOperation();
2873 if (!IsTargetValid() || !target) {
2874 return;
2876 target->Stroke(gfxpath,
2877 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
2878 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
2879 Redraw();
2882 void CanvasRenderingContext2D::DrawFocusIfNeeded(
2883 mozilla::dom::Element& aElement, ErrorResult& aRv) {
2884 EnsureUserSpacePath();
2885 if (!mPath) {
2886 return;
2889 if (DrawCustomFocusRing(aElement)) {
2890 AutoSaveRestore asr(this);
2892 // set state to conforming focus state
2893 ContextState* state = &CurrentState();
2894 state->globalAlpha = 1.0;
2895 state->shadowBlur = 0;
2896 state->shadowOffset.x = 0;
2897 state->shadowOffset.y = 0;
2898 state->op = mozilla::gfx::CompositionOp::OP_OVER;
2900 state->lineCap = CapStyle::BUTT;
2901 state->lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL;
2902 state->lineWidth = 1;
2903 state->dash.Clear();
2905 // color and style of the rings is the same as for image maps
2906 // set the background focus color
2907 state->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
2908 state = nullptr;
2910 // draw the focus ring
2911 Stroke();
2912 if (!mPath) {
2913 return;
2916 // set dashing for foreground
2917 nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
2918 for (uint32_t i = 0; i < 2; ++i) {
2919 if (!dash.AppendElement(1, fallible)) {
2920 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2921 return;
2925 // set the foreground focus color
2926 CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0, 0, 0, 255));
2927 // draw the focus ring
2928 Stroke();
2929 if (!mPath) {
2930 return;
2935 bool CanvasRenderingContext2D::DrawCustomFocusRing(Element& aElement) {
2936 if (!aElement.State().HasState(NS_EVENT_STATE_FOCUSRING)) {
2937 return false;
2940 HTMLCanvasElement* canvas = GetCanvas();
2941 if (!canvas || !aElement.IsInclusiveDescendantOf(canvas)) {
2942 return false;
2945 EnsureUserSpacePath();
2946 return true;
2949 void CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding) {
2950 EnsureUserSpacePath(aWinding);
2952 if (!mPath) {
2953 return;
2956 mTarget->PushClip(mPath);
2957 CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath));
2960 void CanvasRenderingContext2D::Clip(const CanvasPath& aPath,
2961 const CanvasWindingRule& aWinding) {
2962 EnsureTarget();
2963 if (!IsTargetValid()) {
2964 return;
2967 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
2969 if (!gfxpath) {
2970 return;
2973 mTarget->PushClip(gfxpath);
2974 CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath));
2977 void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
2978 double aY2, double aRadius,
2979 ErrorResult& aError) {
2980 if (aRadius < 0) {
2981 return aError.ThrowIndexSizeError("Negative radius");
2984 EnsureWritablePath();
2986 // Current point in user space!
2987 Point p0;
2988 if (mPathBuilder) {
2989 p0 = mPathBuilder->CurrentPoint();
2990 } else {
2991 Matrix invTransform = mTarget->GetTransform();
2992 if (!invTransform.Invert()) {
2993 return;
2996 p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint());
2999 Point p1(aX1, aY1);
3000 Point p2(aX2, aY2);
3002 // Execute these calculations in double precision to avoid cumulative
3003 // rounding errors.
3004 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
3005 cy, angle0, angle1;
3006 bool anticlockwise;
3008 if (p0 == p1 || p1 == p2 || aRadius == 0) {
3009 LineTo(p1.x, p1.y);
3010 return;
3013 // Check for colinearity
3014 dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
3015 if (dir == 0) {
3016 LineTo(p1.x, p1.y);
3017 return;
3020 // XXX - Math for this code was already available from the non-azure code
3021 // and would be well tested. Perhaps converting to bezier directly might
3022 // be more efficient longer run.
3023 a2 = (p0.x - aX1) * (p0.x - aX1) + (p0.y - aY1) * (p0.y - aY1);
3024 b2 = (aX1 - aX2) * (aX1 - aX2) + (aY1 - aY2) * (aY1 - aY2);
3025 c2 = (p0.x - aX2) * (p0.x - aX2) + (p0.y - aY2) * (p0.y - aY2);
3026 cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2));
3028 sinx = sqrt(1 - cosx * cosx);
3029 d = aRadius / ((1 - cosx) / sinx);
3031 anx = (aX1 - p0.x) / sqrt(a2);
3032 any = (aY1 - p0.y) / sqrt(a2);
3033 bnx = (aX1 - aX2) / sqrt(b2);
3034 bny = (aY1 - aY2) / sqrt(b2);
3035 x3 = aX1 - anx * d;
3036 y3 = aY1 - any * d;
3037 x4 = aX1 - bnx * d;
3038 y4 = aY1 - bny * d;
3039 anticlockwise = (dir < 0);
3040 cx = x3 + any * aRadius * (anticlockwise ? 1 : -1);
3041 cy = y3 - anx * aRadius * (anticlockwise ? 1 : -1);
3042 angle0 = atan2((y3 - cy), (x3 - cx));
3043 angle1 = atan2((y4 - cy), (x4 - cx));
3045 LineTo(x3, y3);
3047 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
3050 void CanvasRenderingContext2D::Arc(double aX, double aY, double aR,
3051 double aStartAngle, double aEndAngle,
3052 bool aAnticlockwise, ErrorResult& aError) {
3053 if (aR < 0.0) {
3054 return aError.ThrowIndexSizeError("Negative radius");
3057 EnsureWritablePath();
3059 ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle,
3060 aAnticlockwise);
3063 void CanvasRenderingContext2D::Rect(double aX, double aY, double aW,
3064 double aH) {
3065 EnsureWritablePath();
3067 if (mPathBuilder) {
3068 mPathBuilder->MoveTo(Point(aX, aY));
3069 mPathBuilder->LineTo(Point(aX + aW, aY));
3070 mPathBuilder->LineTo(Point(aX + aW, aY + aH));
3071 mPathBuilder->LineTo(Point(aX, aY + aH));
3072 mPathBuilder->Close();
3073 } else {
3074 mDSPathBuilder->MoveTo(
3075 mTarget->GetTransform().TransformPoint(Point(aX, aY)));
3076 mDSPathBuilder->LineTo(
3077 mTarget->GetTransform().TransformPoint(Point(aX + aW, aY)));
3078 mDSPathBuilder->LineTo(
3079 mTarget->GetTransform().TransformPoint(Point(aX + aW, aY + aH)));
3080 mDSPathBuilder->LineTo(
3081 mTarget->GetTransform().TransformPoint(Point(aX, aY + aH)));
3082 mDSPathBuilder->Close();
3086 void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX,
3087 double aRadiusY, double aRotation,
3088 double aStartAngle, double aEndAngle,
3089 bool aAnticlockwise,
3090 ErrorResult& aError) {
3091 if (aRadiusX < 0.0 || aRadiusY < 0.0) {
3092 return aError.ThrowIndexSizeError("Negative radius");
3095 EnsureWritablePath();
3097 ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle,
3098 aEndAngle, aAnticlockwise, aRotation);
3101 void CanvasRenderingContext2D::EnsureWritablePath() {
3102 EnsureTarget();
3103 // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
3104 // go ahead and create a path anyway since callers depend on that.
3106 if (mDSPathBuilder) {
3107 return;
3110 FillRule fillRule = CurrentState().fillRule;
3112 if (mPathBuilder) {
3113 if (mPathTransformWillUpdate) {
3114 mPath = mPathBuilder->Finish();
3115 mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3116 mPath = nullptr;
3117 mPathBuilder = nullptr;
3118 mPathTransformWillUpdate = false;
3120 return;
3123 if (!mPath) {
3124 NS_ASSERTION(
3125 !mPathTransformWillUpdate,
3126 "mPathTransformWillUpdate should be false, if all paths are null");
3127 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3128 } else if (!mPathTransformWillUpdate) {
3129 mPathBuilder = mPath->CopyToBuilder(fillRule);
3130 } else {
3131 mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3132 mPathTransformWillUpdate = false;
3133 mPath = nullptr;
3137 void CanvasRenderingContext2D::EnsureUserSpacePath(
3138 const CanvasWindingRule& aWinding) {
3139 FillRule fillRule = CurrentState().fillRule;
3140 if (aWinding == CanvasWindingRule::Evenodd)
3141 fillRule = FillRule::FILL_EVEN_ODD;
3143 EnsureTarget();
3144 if (!IsTargetValid()) {
3145 return;
3148 if (!mPath && !mPathBuilder && !mDSPathBuilder) {
3149 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3152 if (mPathBuilder) {
3153 mPath = mPathBuilder->Finish();
3154 mPathBuilder = nullptr;
3157 if (mPath && mPathTransformWillUpdate) {
3158 mDSPathBuilder = mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
3159 mPath = nullptr;
3160 mPathTransformWillUpdate = false;
3163 if (mDSPathBuilder) {
3164 RefPtr<Path> dsPath;
3165 dsPath = mDSPathBuilder->Finish();
3166 mDSPathBuilder = nullptr;
3168 Matrix inverse = mTarget->GetTransform();
3169 if (!inverse.Invert()) {
3170 NS_WARNING("Could not invert transform");
3171 return;
3174 mPathBuilder = dsPath->TransformedCopyToBuilder(inverse, fillRule);
3175 mPath = mPathBuilder->Finish();
3176 mPathBuilder = nullptr;
3179 if (mPath && mPath->GetFillRule() != fillRule) {
3180 mPathBuilder = mPath->CopyToBuilder(fillRule);
3181 mPath = mPathBuilder->Finish();
3182 mPathBuilder = nullptr;
3185 NS_ASSERTION(mPath, "mPath should exist");
3188 void CanvasRenderingContext2D::TransformWillUpdate() {
3189 EnsureTarget();
3190 if (!IsTargetValid()) {
3191 return;
3194 // Store the matrix that would transform the current path to device
3195 // space.
3196 if (mPath || mPathBuilder) {
3197 if (!mPathTransformWillUpdate) {
3198 // If the transform has already been updated, but a device space builder
3199 // has not been created yet mPathToDS contains the right transform to
3200 // transform the current mPath into device space.
3201 // We should leave it alone.
3202 mPathToDS = mTarget->GetTransform();
3204 mPathTransformWillUpdate = true;
3209 // text
3212 void CanvasRenderingContext2D::SetFont(const nsACString& aFont,
3213 ErrorResult& aError) {
3214 SetFontInternal(aFont, aError);
3217 bool CanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
3218 ErrorResult& aError) {
3220 * If font is defined with relative units (e.g. ems) and the parent
3221 * ComputedStyle changes in between calls, setting the font to the
3222 * same value as previous could result in a different computed value,
3223 * so we cannot have the optimization where we check if the new font
3224 * string is equal to the old one.
3227 if (!mCanvasElement && !mDocShell) {
3228 NS_WARNING(
3229 "Canvas element must be non-null or a docshell must be provided");
3230 aError.Throw(NS_ERROR_FAILURE);
3231 return false;
3234 RefPtr<PresShell> presShell = GetPresShell();
3235 if (NS_WARN_IF(!presShell)) {
3236 aError.Throw(NS_ERROR_FAILURE);
3237 return false;
3240 nsCString usedFont;
3241 RefPtr<ComputedStyle> sc =
3242 GetFontStyleForServo(mCanvasElement, aFont, presShell, usedFont, aError);
3243 if (!sc) {
3244 return false;
3247 const nsStyleFont* fontStyle = sc->StyleFont();
3248 nsPresContext* c = presShell->GetPresContext();
3250 // Purposely ignore the font size that respects the user's minimum
3251 // font preference (fontStyle->mFont.size) in favor of the computed
3252 // size (fontStyle->mSize). See
3253 // https://bugzilla.mozilla.org/show_bug.cgi?id=698652.
3254 // FIXME: Nobody initializes mAllowZoom for servo?
3255 // MOZ_ASSERT(!fontStyle->mAllowZoom,
3256 // "expected text zoom to be disabled on this nsStyleFont");
3257 nsFont resizedFont(fontStyle->mFont);
3258 // Create a font group working in units of CSS pixels instead of the usual
3259 // device pixels, to avoid being affected by page zoom. nsFontMetrics will
3260 // convert nsFont size in app units to device pixels for the font group, so
3261 // here we first apply to the size the equivalent of a conversion from device
3262 // pixels to CSS pixels, to adjust for the difference in expectations from
3263 // other nsFontMetrics clients.
3264 resizedFont.size =
3265 fontStyle->mSize.ScaledBy(1.0f / c->CSSToDevPixelScale().scale);
3267 c->Document()->FlushUserFontSet();
3269 nsFontMetrics::Params params;
3270 params.language = fontStyle->mLanguage;
3271 params.explicitLanguage = fontStyle->mExplicitLanguage;
3272 params.userFontSet = c->GetUserFontSet();
3273 params.textPerf = c->GetTextPerfMetrics();
3274 params.fontStats = c->GetFontMatchingStats();
3275 RefPtr<nsFontMetrics> metrics =
3276 c->DeviceContext()->GetMetricsFor(resizedFont, params);
3278 gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup();
3279 CurrentState().fontGroup = newFontGroup;
3280 NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
3281 CurrentState().font = usedFont;
3282 CurrentState().fontFont = fontStyle->mFont;
3283 CurrentState().fontFont.size = fontStyle->mSize;
3284 CurrentState().fontLanguage = fontStyle->mLanguage;
3285 CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
3287 return true;
3290 void CanvasRenderingContext2D::SetTextAlign(const nsAString& aTextAlign) {
3291 if (aTextAlign.EqualsLiteral("start"))
3292 CurrentState().textAlign = TextAlign::START;
3293 else if (aTextAlign.EqualsLiteral("end"))
3294 CurrentState().textAlign = TextAlign::END;
3295 else if (aTextAlign.EqualsLiteral("left"))
3296 CurrentState().textAlign = TextAlign::LEFT;
3297 else if (aTextAlign.EqualsLiteral("right"))
3298 CurrentState().textAlign = TextAlign::RIGHT;
3299 else if (aTextAlign.EqualsLiteral("center"))
3300 CurrentState().textAlign = TextAlign::CENTER;
3303 void CanvasRenderingContext2D::GetTextAlign(nsAString& aTextAlign) {
3304 switch (CurrentState().textAlign) {
3305 case TextAlign::START:
3306 aTextAlign.AssignLiteral("start");
3307 break;
3308 case TextAlign::END:
3309 aTextAlign.AssignLiteral("end");
3310 break;
3311 case TextAlign::LEFT:
3312 aTextAlign.AssignLiteral("left");
3313 break;
3314 case TextAlign::RIGHT:
3315 aTextAlign.AssignLiteral("right");
3316 break;
3317 case TextAlign::CENTER:
3318 aTextAlign.AssignLiteral("center");
3319 break;
3323 void CanvasRenderingContext2D::SetTextBaseline(const nsAString& aTextBaseline) {
3324 if (aTextBaseline.EqualsLiteral("top"))
3325 CurrentState().textBaseline = TextBaseline::TOP;
3326 else if (aTextBaseline.EqualsLiteral("hanging"))
3327 CurrentState().textBaseline = TextBaseline::HANGING;
3328 else if (aTextBaseline.EqualsLiteral("middle"))
3329 CurrentState().textBaseline = TextBaseline::MIDDLE;
3330 else if (aTextBaseline.EqualsLiteral("alphabetic"))
3331 CurrentState().textBaseline = TextBaseline::ALPHABETIC;
3332 else if (aTextBaseline.EqualsLiteral("ideographic"))
3333 CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC;
3334 else if (aTextBaseline.EqualsLiteral("bottom"))
3335 CurrentState().textBaseline = TextBaseline::BOTTOM;
3338 void CanvasRenderingContext2D::GetTextBaseline(nsAString& aTextBaseline) {
3339 switch (CurrentState().textBaseline) {
3340 case TextBaseline::TOP:
3341 aTextBaseline.AssignLiteral("top");
3342 break;
3343 case TextBaseline::HANGING:
3344 aTextBaseline.AssignLiteral("hanging");
3345 break;
3346 case TextBaseline::MIDDLE:
3347 aTextBaseline.AssignLiteral("middle");
3348 break;
3349 case TextBaseline::ALPHABETIC:
3350 aTextBaseline.AssignLiteral("alphabetic");
3351 break;
3352 case TextBaseline::IDEOGRAPHIC:
3353 aTextBaseline.AssignLiteral("ideographic");
3354 break;
3355 case TextBaseline::BOTTOM:
3356 aTextBaseline.AssignLiteral("bottom");
3357 break;
3362 * Helper function that replaces the whitespace characters in a string
3363 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
3364 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
3365 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
3366 * @param str The string whose whitespace characters to replace.
3368 static inline void TextReplaceWhitespaceCharacters(nsAutoString& aStr) {
3369 aStr.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' '));
3372 void CanvasRenderingContext2D::FillText(const nsAString& aText, double aX,
3373 double aY,
3374 const Optional<double>& aMaxWidth,
3375 ErrorResult& aError) {
3376 DebugOnly<TextMetrics*> metrics = DrawOrMeasureText(
3377 aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, aError);
3378 MOZ_ASSERT(!metrics); // drawing operation never returns TextMetrics
3381 void CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX,
3382 double aY,
3383 const Optional<double>& aMaxWidth,
3384 ErrorResult& aError) {
3385 DebugOnly<TextMetrics*> metrics = DrawOrMeasureText(
3386 aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, aError);
3387 MOZ_ASSERT(!metrics); // drawing operation never returns TextMetrics
3390 TextMetrics* CanvasRenderingContext2D::MeasureText(const nsAString& aRawText,
3391 ErrorResult& aError) {
3392 Optional<double> maxWidth;
3393 return DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE,
3394 aError);
3397 void CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& aOptions,
3398 ErrorResult& aError) {
3399 RefPtr<gfx::Path> path;
3400 if (aOptions.mPath) {
3401 EnsureTarget();
3402 if (!IsTargetValid()) {
3403 return;
3405 path = aOptions.mPath->GetPath(CanvasWindingRule::Nonzero, mTarget);
3408 if (!path) {
3409 // check if the path is valid
3410 EnsureUserSpacePath(CanvasWindingRule::Nonzero);
3411 path = mPath;
3414 if (!path) {
3415 return aError.ThrowNotSupportedError("Invalid path");
3418 // get the bounds of the current path. They are relative to the canvas
3419 gfx::Rect bounds(path->GetBounds(mTarget->GetTransform()));
3420 if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) {
3421 return aError.ThrowNotSupportedError("The specified region has no pixels");
3424 // remove old hit region first
3425 RemoveHitRegion(aOptions.mId);
3427 if (aOptions.mControl) {
3428 // also remove regions with this control
3429 for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
3430 RegionInfo& info = mHitRegionsOptions[x];
3431 if (info.mElement == aOptions.mControl) {
3432 mHitRegionsOptions.RemoveElementAt(x);
3433 break;
3436 #ifdef ACCESSIBILITY
3437 aOptions.mControl->SetProperty(nsGkAtoms::hitregion,
3438 reinterpret_cast<void*>(true));
3439 #endif
3442 // finally, add the region to the list
3443 RegionInfo info;
3444 info.mId = aOptions.mId;
3445 info.mElement = aOptions.mControl;
3446 RefPtr<PathBuilder> pathBuilder =
3447 path->TransformedCopyToBuilder(mTarget->GetTransform());
3448 info.mPath = pathBuilder->Finish();
3450 mHitRegionsOptions.InsertElementAt(0, info);
3453 void CanvasRenderingContext2D::RemoveHitRegion(const nsAString& aId) {
3454 if (aId.Length() == 0) {
3455 return;
3458 for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
3459 RegionInfo& info = mHitRegionsOptions[x];
3460 if (info.mId == aId) {
3461 mHitRegionsOptions.RemoveElementAt(x);
3463 return;
3468 void CanvasRenderingContext2D::ClearHitRegions() { mHitRegionsOptions.Clear(); }
3470 bool CanvasRenderingContext2D::GetHitRegionRect(Element* aElement,
3471 nsRect& aRect) {
3472 for (unsigned int x = 0; x < mHitRegionsOptions.Length(); x++) {
3473 RegionInfo& info = mHitRegionsOptions[x];
3474 if (info.mElement == aElement) {
3475 gfx::Rect bounds(info.mPath->GetBounds());
3476 gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height);
3477 aRect = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel());
3479 return true;
3483 return false;
3487 * Used for nsBidiPresUtils::ProcessText
3489 struct MOZ_STACK_CLASS CanvasBidiProcessor
3490 : public nsBidiPresUtils::BidiProcessor {
3491 typedef CanvasRenderingContext2D::Style Style;
3493 CanvasBidiProcessor()
3494 : nsBidiPresUtils::BidiProcessor(),
3495 mCtx(nullptr),
3496 mFontgrp(nullptr),
3497 mAppUnitsPerDevPixel(0),
3498 mOp(CanvasRenderingContext2D::TextDrawOperation::FILL),
3499 mTextRunFlags(),
3500 mDoMeasureBoundingBox(false) {
3501 if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
3502 mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
3506 ~CanvasBidiProcessor() {
3507 // notify front-end code if we encountered missing glyphs in any script
3508 if (mMissingFonts) {
3509 mMissingFonts->Flush();
3513 typedef CanvasRenderingContext2D::ContextState ContextState;
3515 virtual void SetText(const char16_t* aText, int32_t aLength,
3516 nsBidiDirection aDirection) override {
3517 mFontgrp->UpdateUserFonts(); // ensure user font generation is current
3518 // adjust flags for current direction run
3519 gfx::ShapedTextFlags flags = mTextRunFlags;
3520 if (aDirection == NSBIDI_RTL) {
3521 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
3522 } else {
3523 flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL;
3525 mTextRun = mFontgrp->MakeTextRun(
3526 aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags,
3527 nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts,
3528 mMissingFonts.get());
3531 virtual nscoord GetWidth() override {
3532 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
3533 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
3534 : gfxFont::LOOSE_INK_EXTENTS,
3535 mDrawTarget);
3537 // this only measures the height; the total width is gotten from the
3538 // the return value of ProcessText.
3539 if (mDoMeasureBoundingBox) {
3540 textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
3541 mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
3544 return NSToCoordRound(textRunMetrics.mAdvanceWidth);
3547 already_AddRefed<gfxPattern> GetGradientFor(Style aStyle) {
3548 RefPtr<gfxPattern> pattern;
3549 CanvasGradient* gradient = mCtx->CurrentState().gradientStyles[aStyle];
3550 CanvasGradient::Type type = gradient->GetType();
3552 switch (type) {
3553 case CanvasGradient::Type::CONIC: {
3554 auto conic = static_cast<CanvasConicGradient*>(gradient);
3555 pattern = new gfxPattern(conic->mCenter.x, conic->mCenter.y,
3556 conic->mAngle, 0, 1);
3557 break;
3559 case CanvasGradient::Type::RADIAL: {
3560 auto radial = static_cast<CanvasRadialGradient*>(gradient);
3561 pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y,
3562 radial->mRadius1, radial->mCenter2.x,
3563 radial->mCenter2.y, radial->mRadius2);
3564 break;
3566 case CanvasGradient::Type::LINEAR: {
3567 auto linear = static_cast<CanvasLinearGradient*>(gradient);
3568 pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y,
3569 linear->mEnd.x, linear->mEnd.y);
3570 break;
3572 default:
3573 MOZ_ASSERT(false, "Should be linear, radial or conic gradient.");
3574 return nullptr;
3577 for (auto stop : gradient->mRawStops) {
3578 pattern->AddColorStop(stop.offset, stop.color);
3581 return pattern.forget();
3584 gfx::ExtendMode CvtCanvasRepeatToGfxRepeat(
3585 CanvasPattern::RepeatMode aRepeatMode) {
3586 switch (aRepeatMode) {
3587 case CanvasPattern::RepeatMode::REPEAT:
3588 return gfx::ExtendMode::REPEAT;
3589 case CanvasPattern::RepeatMode::REPEATX:
3590 return gfx::ExtendMode::REPEAT_X;
3591 case CanvasPattern::RepeatMode::REPEATY:
3592 return gfx::ExtendMode::REPEAT_Y;
3593 case CanvasPattern::RepeatMode::NOREPEAT:
3594 return gfx::ExtendMode::CLAMP;
3595 default:
3596 return gfx::ExtendMode::CLAMP;
3600 already_AddRefed<gfxPattern> GetPatternFor(Style aStyle) {
3601 const CanvasPattern* pat = mCtx->CurrentState().patternStyles[aStyle];
3602 RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, pat->mTransform);
3603 pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat));
3604 return pattern.forget();
3607 virtual void DrawText(nscoord aXOffset, nscoord aWidth) override {
3608 gfx::Point point = mPt;
3609 bool rtl = mTextRun->IsRightToLeft();
3610 bool verticalRun = mTextRun->IsVertical();
3611 RefPtr<gfxPattern> pattern;
3613 float& inlineCoord = verticalRun ? point.y : point.x;
3614 inlineCoord += aXOffset;
3616 // offset is given in terms of left side of string
3617 if (rtl) {
3618 // Bug 581092 - don't use rounded pixel width to advance to
3619 // right-hand end of run, because this will cause different
3620 // glyph positioning for LTR vs RTL drawing of the same
3621 // glyph string on OS X and DWrite where textrun widths may
3622 // involve fractional pixels.
3623 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
3624 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
3625 : gfxFont::LOOSE_INK_EXTENTS,
3626 mDrawTarget);
3627 inlineCoord += textRunMetrics.mAdvanceWidth;
3628 // old code was:
3629 // point.x += width * mAppUnitsPerDevPixel;
3630 // TODO: restore this if/when we move to fractional coords
3631 // throughout the text layout process
3634 mCtx->EnsureTarget();
3635 if (!mCtx->IsTargetValid()) {
3636 return;
3639 // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
3640 // appropriately.
3641 StrokeOptions strokeOpts;
3642 DrawOptions drawOpts;
3643 Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
3644 ? Style::FILL
3645 : Style::STROKE;
3647 AdjustedTarget target(mCtx);
3648 if (!target) {
3649 return;
3652 RefPtr<gfxContext> thebes =
3653 gfxContext::CreatePreservingTransformOrNull(target);
3654 if (!thebes) {
3655 // If CreatePreservingTransformOrNull returns null, it will also have
3656 // issued a gfxCriticalNote already, so here we'll just bail out.
3657 return;
3659 gfxTextRun::DrawParams params(thebes);
3661 const ContextState* state = &mCtx->CurrentState();
3662 if (state->StyleIsColor(style)) { // Color
3663 nscolor fontColor = state->colorStyles[style];
3664 if (style == Style::FILL) {
3665 params.context->SetColor(sRGBColor::FromABGR(fontColor));
3666 } else {
3667 params.textStrokeColor = fontColor;
3669 } else {
3670 if (state->gradientStyles[style]) { // Gradient
3671 pattern = GetGradientFor(style);
3672 } else if (state->patternStyles[style]) { // Pattern
3673 pattern = GetPatternFor(style);
3674 } else {
3675 MOZ_ASSERT(false, "Should never reach here.");
3676 return;
3678 MOZ_ASSERT(pattern, "No valid pattern.");
3680 if (style == Style::FILL) {
3681 params.context->SetPattern(pattern);
3682 } else {
3683 params.textStrokePattern = pattern;
3687 drawOpts.mAlpha = state->globalAlpha;
3688 drawOpts.mCompositionOp = mCtx->UsedOperation();
3689 if (!mCtx->IsTargetValid()) {
3690 return;
3692 state = &mCtx->CurrentState();
3693 params.drawOpts = &drawOpts;
3695 if (style == Style::STROKE) {
3696 strokeOpts.mLineWidth = state->lineWidth;
3697 strokeOpts.mLineJoin = state->lineJoin;
3698 strokeOpts.mLineCap = state->lineCap;
3699 strokeOpts.mMiterLimit = state->miterLimit;
3700 strokeOpts.mDashLength = state->dash.Length();
3701 strokeOpts.mDashPattern =
3702 (strokeOpts.mDashLength > 0) ? state->dash.Elements() : 0;
3703 strokeOpts.mDashOffset = state->dashOffset;
3705 params.drawMode = DrawMode::GLYPH_STROKE;
3706 params.strokeOpts = &strokeOpts;
3709 mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
3712 // current text run
3713 RefPtr<gfxTextRun> mTextRun;
3715 // pointer to a screen reference context used to measure text and such
3716 RefPtr<DrawTarget> mDrawTarget;
3718 // Pointer to the draw target we should fill our text to
3719 CanvasRenderingContext2D* mCtx;
3721 // position of the left side of the string, alphabetic baseline
3722 gfx::Point mPt;
3724 // current font
3725 gfxFontGroup* mFontgrp;
3727 // to record any unsupported characters found in the text,
3728 // and notify front-end if it is interested
3729 UniquePtr<gfxMissingFontRecorder> mMissingFonts;
3731 // dev pixel conversion factor
3732 int32_t mAppUnitsPerDevPixel;
3734 // operation (fill or stroke)
3735 CanvasRenderingContext2D::TextDrawOperation mOp;
3737 // union of bounding boxes of all runs, needed for shadows
3738 gfxRect mBoundingBox;
3740 // flags to use when creating textrun, based on CSS style
3741 gfx::ShapedTextFlags mTextRunFlags;
3743 // true iff the bounding box should be measured
3744 bool mDoMeasureBoundingBox;
3747 TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
3748 const nsAString& aRawText, float aX, float aY,
3749 const Optional<double>& aMaxWidth, TextDrawOperation aOp,
3750 ErrorResult& aError) {
3751 // Approximated baselines. In an ideal world, we'd read the baseline info
3752 // directly from the font (where available). Alas we currently lack
3753 // that functionality. These numbers are best guesses and should
3754 // suffice for now. Both are fractions of the em ascent/descent from the
3755 // alphabetic baseline.
3756 const double kHangingBaselineDefault = 0.8; // fraction of ascent
3757 const double kIdeographicBaselineDefault = 0.5; // fraction of descent
3759 if (!mCanvasElement && !mDocShell) {
3760 NS_WARNING(
3761 "Canvas element must be non-null or a docshell must be provided");
3762 aError = NS_ERROR_FAILURE;
3763 return nullptr;
3766 RefPtr<PresShell> presShell = GetPresShell();
3767 if (NS_WARN_IF(!presShell)) {
3768 aError = NS_ERROR_FAILURE;
3769 return nullptr;
3772 Document* document = presShell->GetDocument();
3774 // replace all the whitespace characters with U+0020 SPACE
3775 nsAutoString textToDraw(aRawText);
3776 TextReplaceWhitespaceCharacters(textToDraw);
3778 // According to spec, the API should return an empty array if maxWidth was
3779 // provided but is less than or equal to zero or equal to NaN.
3780 if (aMaxWidth.WasPassed() &&
3781 (aMaxWidth.Value() <= 0 || IsNaN(aMaxWidth.Value()))) {
3782 textToDraw.Truncate();
3785 // for now, default to ltr if not in doc
3786 bool isRTL = false;
3788 RefPtr<ComputedStyle> canvasStyle;
3789 if (mCanvasElement && mCanvasElement->IsInComposedDoc()) {
3790 // try to find the closest context
3791 canvasStyle = nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
3792 if (!canvasStyle) {
3793 aError = NS_ERROR_FAILURE;
3794 return nullptr;
3797 isRTL = canvasStyle->StyleVisibility()->mDirection == StyleDirection::Rtl;
3798 } else {
3799 isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) ==
3800 IBMBIDI_TEXTDIRECTION_RTL;
3803 // This is only needed to know if we can know the drawing bounding box easily.
3804 const bool doCalculateBounds = NeedToCalculateBounds();
3805 if (presShell->IsDestroying()) {
3806 aError = NS_ERROR_FAILURE;
3807 return nullptr;
3810 gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
3811 if (!currentFontStyle) {
3812 aError = NS_ERROR_FAILURE;
3813 return nullptr;
3816 MOZ_ASSERT(!presShell->IsDestroying(),
3817 "GetCurrentFontStyle() should have returned null if the presshell "
3818 "is being destroyed");
3820 nsPresContext* presContext = presShell->GetPresContext();
3822 // ensure user font set is up to date
3823 presContext->Document()->FlushUserFontSet();
3824 currentFontStyle->SetUserFontSet(presContext->GetUserFontSet());
3826 if (currentFontStyle->GetStyle()->size == 0.0F) {
3827 aError = NS_OK;
3828 if (aOp == TextDrawOperation::MEASURE) {
3829 return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
3830 0.0, 0.0);
3832 return nullptr;
3835 if (!IsFinite(aX) || !IsFinite(aY)) {
3836 aError = NS_OK;
3837 // This may not be correct - what should TextMetrics contain in the case of
3838 // infinite width or height?
3839 if (aOp == TextDrawOperation::MEASURE) {
3840 return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
3841 0.0, 0.0);
3843 return nullptr;
3846 CanvasBidiProcessor processor;
3848 // If we don't have a ComputedStyle, we can't set up vertical-text flags
3849 // (for now, at least; perhaps we need new Canvas API to control this).
3850 processor.mTextRunFlags =
3851 canvasStyle ? nsLayoutUtils::GetTextRunFlagsForStyle(
3852 canvasStyle, presContext, canvasStyle->StyleFont(),
3853 canvasStyle->StyleText(), 0)
3854 : gfx::ShapedTextFlags();
3856 GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
3857 processor.mPt = gfx::Point(aX, aY);
3858 processor.mDrawTarget =
3859 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
3861 // If we don't have a target then we don't have a transform. A target won't
3862 // be needed in the case where we're measuring the text size. This allows
3863 // to avoid creating a target if it's only being used to measure text sizes.
3864 if (mTarget) {
3865 processor.mDrawTarget->SetTransform(mTarget->GetTransform());
3867 processor.mCtx = this;
3868 processor.mOp = aOp;
3869 processor.mBoundingBox = gfxRect(0, 0, 0, 0);
3870 processor.mDoMeasureBoundingBox = doCalculateBounds ||
3871 !mIsEntireFrameInvalid ||
3872 aOp == TextDrawOperation::MEASURE;
3873 processor.mFontgrp = currentFontStyle;
3875 nscoord totalWidthCoord;
3877 // calls bidi algo twice since it needs the full text width and the
3878 // bounding boxes before rendering anything
3879 aError = nsBidiPresUtils::ProcessText(
3880 textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR,
3881 presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_MEASURE,
3882 nullptr, 0, &totalWidthCoord, &mBidiEngine);
3883 if (aError.Failed()) {
3884 return nullptr;
3887 float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
3889 // offset pt.x based on text align
3890 gfxFloat anchorX;
3892 const ContextState& state = CurrentState();
3893 if (state.textAlign == TextAlign::CENTER) {
3894 anchorX = .5;
3895 } else if (state.textAlign == TextAlign::LEFT ||
3896 (!isRTL && state.textAlign == TextAlign::START) ||
3897 (isRTL && state.textAlign == TextAlign::END)) {
3898 anchorX = 0;
3899 } else {
3900 anchorX = 1;
3903 float offsetX = anchorX * totalWidth;
3904 processor.mPt.x -= offsetX;
3906 // offset pt.y (or pt.x, for vertical text) based on text baseline
3907 processor.mFontgrp
3908 ->UpdateUserFonts(); // ensure user font generation is current
3909 const gfxFont::Metrics& fontMetrics =
3910 processor.mFontgrp->GetFirstValidFont()->GetMetrics(
3911 nsFontMetrics::eHorizontal);
3913 gfxFloat baselineAnchor;
3915 switch (state.textBaseline) {
3916 case TextBaseline::HANGING:
3917 baselineAnchor = fontMetrics.emAscent * kHangingBaselineDefault;
3918 break;
3919 case TextBaseline::TOP:
3920 baselineAnchor = fontMetrics.emAscent;
3921 break;
3922 case TextBaseline::MIDDLE:
3923 baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
3924 break;
3925 case TextBaseline::ALPHABETIC:
3926 baselineAnchor = 0;
3927 break;
3928 case TextBaseline::IDEOGRAPHIC:
3929 baselineAnchor = -fontMetrics.emDescent * kIdeographicBaselineDefault;
3930 break;
3931 case TextBaseline::BOTTOM:
3932 baselineAnchor = -fontMetrics.emDescent;
3933 break;
3934 default:
3935 MOZ_CRASH("GFX: unexpected TextBaseline");
3938 // We can't query the textRun directly, as it may not have been created yet;
3939 // so instead we check the flags that will be used to initialize it.
3940 gfx::ShapedTextFlags runOrientation =
3941 (processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
3942 if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) {
3943 if (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
3944 runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
3945 // Adjust to account for mTextRun being shaped using center baseline
3946 // rather than alphabetic.
3947 baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
3949 processor.mPt.x -= baselineAnchor;
3950 } else {
3951 processor.mPt.y += baselineAnchor;
3954 // if only measuring, don't need to do any more work
3955 if (aOp == TextDrawOperation::MEASURE) {
3956 aError = NS_OK;
3957 // Note that actualBoundingBoxLeft measures the distance in the leftward
3958 // direction, so its sign is reversed from our usual physical coordinates.
3959 double actualBoundingBoxLeft = offsetX - processor.mBoundingBox.X();
3960 double actualBoundingBoxRight = processor.mBoundingBox.XMost() - offsetX;
3961 double actualBoundingBoxAscent =
3962 -processor.mBoundingBox.Y() - baselineAnchor;
3963 double actualBoundingBoxDescent =
3964 processor.mBoundingBox.YMost() + baselineAnchor;
3965 double hangingBaseline =
3966 fontMetrics.emAscent * kHangingBaselineDefault - baselineAnchor;
3967 double ideographicBaseline =
3968 -fontMetrics.emDescent * kIdeographicBaselineDefault - baselineAnchor;
3969 return new TextMetrics(
3970 totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight,
3971 fontMetrics.maxAscent - baselineAnchor, // fontBBAscent
3972 fontMetrics.maxDescent + baselineAnchor, // fontBBDescent
3973 actualBoundingBoxAscent, actualBoundingBoxDescent,
3974 fontMetrics.emAscent - baselineAnchor, // emHeightAscent
3975 -fontMetrics.emDescent - baselineAnchor, // emHeightDescent
3976 hangingBaseline,
3977 -baselineAnchor, // alphabeticBaseline
3978 ideographicBaseline);
3981 // If we did not actually calculate bounds, set up a simple bounding box
3982 // based on the text position and advance.
3983 if (!doCalculateBounds) {
3984 processor.mBoundingBox.width = totalWidth;
3985 processor.mBoundingBox.MoveBy(gfxPoint(processor.mPt.x, processor.mPt.y));
3988 processor.mPt.x *= processor.mAppUnitsPerDevPixel;
3989 processor.mPt.y *= processor.mAppUnitsPerDevPixel;
3991 EnsureTarget();
3992 if (!IsTargetValid()) {
3993 aError = NS_ERROR_FAILURE;
3994 return nullptr;
3997 Matrix oldTransform = mTarget->GetTransform();
3998 // if text is over aMaxWidth, then scale the text horizontally such that its
3999 // width is precisely aMaxWidth
4000 if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 &&
4001 totalWidth > aMaxWidth.Value()) {
4002 Matrix newTransform = oldTransform;
4004 // Translate so that the anchor point is at 0,0, then scale and then
4005 // translate back.
4006 newTransform.PreTranslate(aX, 0);
4007 newTransform.PreScale(aMaxWidth.Value() / totalWidth, 1);
4008 newTransform.PreTranslate(-aX, 0);
4009 /* we do this to avoid an ICE in the android compiler */
4010 Matrix androidCompilerBug = newTransform;
4011 mTarget->SetTransform(androidCompilerBug);
4014 // save the previous bounding box
4015 gfxRect boundingBox = processor.mBoundingBox;
4017 // don't ever need to measure the bounding box twice
4018 processor.mDoMeasureBoundingBox = false;
4020 aError = nsBidiPresUtils::ProcessText(
4021 textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR,
4022 presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_DRAW,
4023 nullptr, 0, nullptr, &mBidiEngine);
4025 if (aError.Failed()) {
4026 return nullptr;
4029 mTarget->SetTransform(oldTransform);
4031 if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
4032 !doCalculateBounds) {
4033 RedrawUser(boundingBox);
4034 } else {
4035 Redraw();
4038 aError = NS_OK;
4039 return nullptr;
4042 gfxFontGroup* CanvasRenderingContext2D::GetCurrentFontStyle() {
4043 // Use lazy (re)initialization for the fontGroup since it's rather expensive.
4045 RefPtr<PresShell> presShell = GetPresShell();
4046 gfxTextPerfMetrics* tp = nullptr;
4047 FontMatchingStats* fontStats = nullptr;
4048 if (presShell && !presShell->IsDestroying()) {
4049 nsPresContext* pc = presShell->GetPresContext();
4050 tp = pc->GetTextPerfMetrics();
4051 fontStats = pc->GetFontMatchingStats();
4054 // If we have a cached fontGroup, check that it is valid for the current
4055 // prescontext; if not, we need to discard and re-create it.
4056 RefPtr<gfxFontGroup>& fontGroup = CurrentState().fontGroup;
4057 if (fontGroup) {
4058 if (fontGroup->GetFontMatchingStats() != fontStats ||
4059 fontGroup->GetTextPerfMetrics() != tp) {
4060 fontGroup = nullptr;
4064 if (!fontGroup) {
4065 ErrorResult err;
4066 constexpr auto kDefaultFontStyle = "10px sans-serif"_ns;
4067 const float kDefaultFontSize = 10.0;
4068 // If the font has already been set, we're re-creating the fontGroup
4069 // and should re-use the existing font attribute; if not, we initialize
4070 // it to the canvas default.
4071 const nsCString& currentFont = CurrentState().font;
4072 bool fontUpdated = SetFontInternal(
4073 currentFont.IsEmpty() ? kDefaultFontStyle : currentFont, err);
4074 if (err.Failed() || !fontUpdated) {
4075 err.SuppressException();
4076 // XXX Should we get a default lang from the prescontext or something?
4077 nsAtom* language = nsGkAtoms::x_western;
4078 bool explicitLanguage = false;
4079 gfxFontStyle style;
4080 style.size = kDefaultFontSize;
4081 int32_t perDevPixel, perCSSPixel;
4082 GetAppUnitsValues(&perDevPixel, &perCSSPixel);
4083 gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
4084 fontGroup = gfxPlatform::GetPlatform()->CreateFontGroup(
4085 FontFamilyList(StyleGenericFontFamily::SansSerif), &style, language,
4086 explicitLanguage, tp, fontStats, nullptr, devToCssSize);
4087 if (fontGroup) {
4088 CurrentState().font = kDefaultFontStyle;
4089 } else {
4090 NS_ERROR("Default canvas font is invalid");
4093 } else {
4094 // The fontgroup needs to check if its cached families/faces are valid.
4095 fontGroup->CheckForUpdatedPlatformList();
4098 return fontGroup;
4102 // line caps/joins
4105 void CanvasRenderingContext2D::SetLineCap(const nsAString& aLinecapStyle) {
4106 CapStyle cap;
4108 if (aLinecapStyle.EqualsLiteral("butt")) {
4109 cap = CapStyle::BUTT;
4110 } else if (aLinecapStyle.EqualsLiteral("round")) {
4111 cap = CapStyle::ROUND;
4112 } else if (aLinecapStyle.EqualsLiteral("square")) {
4113 cap = CapStyle::SQUARE;
4114 } else {
4115 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4116 return;
4119 CurrentState().lineCap = cap;
4122 void CanvasRenderingContext2D::GetLineCap(nsAString& aLinecapStyle) {
4123 switch (CurrentState().lineCap) {
4124 case CapStyle::BUTT:
4125 aLinecapStyle.AssignLiteral("butt");
4126 break;
4127 case CapStyle::ROUND:
4128 aLinecapStyle.AssignLiteral("round");
4129 break;
4130 case CapStyle::SQUARE:
4131 aLinecapStyle.AssignLiteral("square");
4132 break;
4136 void CanvasRenderingContext2D::SetLineJoin(const nsAString& aLinejoinStyle) {
4137 JoinStyle j;
4139 if (aLinejoinStyle.EqualsLiteral("round")) {
4140 j = JoinStyle::ROUND;
4141 } else if (aLinejoinStyle.EqualsLiteral("bevel")) {
4142 j = JoinStyle::BEVEL;
4143 } else if (aLinejoinStyle.EqualsLiteral("miter")) {
4144 j = JoinStyle::MITER_OR_BEVEL;
4145 } else {
4146 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4147 return;
4150 CurrentState().lineJoin = j;
4153 void CanvasRenderingContext2D::GetLineJoin(nsAString& aLinejoinStyle,
4154 ErrorResult& aError) {
4155 switch (CurrentState().lineJoin) {
4156 case JoinStyle::ROUND:
4157 aLinejoinStyle.AssignLiteral("round");
4158 break;
4159 case JoinStyle::BEVEL:
4160 aLinejoinStyle.AssignLiteral("bevel");
4161 break;
4162 case JoinStyle::MITER_OR_BEVEL:
4163 aLinejoinStyle.AssignLiteral("miter");
4164 break;
4165 default:
4166 aError.Throw(NS_ERROR_FAILURE);
4170 void CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments,
4171 ErrorResult& aRv) {
4172 nsTArray<mozilla::gfx::Float> dash;
4174 for (uint32_t x = 0; x < aSegments.Length(); x++) {
4175 if (aSegments[x] < 0.0) {
4176 // Pattern elements must be finite "numbers" >= 0, with "finite"
4177 // taken care of by WebIDL
4178 return;
4181 if (!dash.AppendElement(aSegments[x], fallible)) {
4182 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4183 return;
4186 if (aSegments.Length() %
4187 2) { // If the number of elements is odd, concatenate again
4188 for (uint32_t x = 0; x < aSegments.Length(); x++) {
4189 if (!dash.AppendElement(aSegments[x], fallible)) {
4190 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4191 return;
4196 CurrentState().dash = std::move(dash);
4199 void CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
4200 const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
4201 aSegments.Clear();
4203 for (uint32_t x = 0; x < dash.Length(); x++) {
4204 aSegments.AppendElement(dash[x]);
4208 void CanvasRenderingContext2D::SetLineDashOffset(double aOffset) {
4209 CurrentState().dashOffset = aOffset;
4212 double CanvasRenderingContext2D::LineDashOffset() const {
4213 return CurrentState().dashOffset;
4216 bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX,
4217 double aY,
4218 const CanvasWindingRule& aWinding,
4219 nsIPrincipal& aSubjectPrincipal) {
4220 if (!FloatValidate(aX, aY)) {
4221 return false;
4224 // Check for site-specific permission and return false if no permission.
4225 if (mCanvasElement) {
4226 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
4227 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
4228 aSubjectPrincipal)) {
4229 return false;
4233 EnsureUserSpacePath(aWinding);
4234 if (!mPath) {
4235 return false;
4238 if (mPathTransformWillUpdate) {
4239 return mPath->ContainsPoint(Point(aX, aY), mPathToDS);
4242 return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
4245 bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx,
4246 const CanvasPath& aPath, double aX,
4247 double aY,
4248 const CanvasWindingRule& aWinding,
4249 nsIPrincipal&) {
4250 if (!FloatValidate(aX, aY)) {
4251 return false;
4254 EnsureTarget();
4255 if (!IsTargetValid()) {
4256 return false;
4259 RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget);
4261 return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
4264 bool CanvasRenderingContext2D::IsPointInStroke(
4265 JSContext* aCx, double aX, double aY, nsIPrincipal& aSubjectPrincipal) {
4266 if (!FloatValidate(aX, aY)) {
4267 return false;
4270 // Check for site-specific permission and return false if no permission.
4271 if (mCanvasElement) {
4272 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
4273 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
4274 aSubjectPrincipal)) {
4275 return false;
4279 EnsureUserSpacePath();
4280 if (!mPath) {
4281 return false;
4284 const ContextState& state = CurrentState();
4286 StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, state.lineCap,
4287 state.miterLimit, state.dash.Length(),
4288 state.dash.Elements(), state.dashOffset);
4290 if (mPathTransformWillUpdate) {
4291 return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
4293 return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY),
4294 mTarget->GetTransform());
4297 bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx,
4298 const CanvasPath& aPath,
4299 double aX, double aY,
4300 nsIPrincipal&) {
4301 if (!FloatValidate(aX, aY)) {
4302 return false;
4305 EnsureTarget();
4306 if (!IsTargetValid()) {
4307 return false;
4310 RefPtr<gfx::Path> tempPath =
4311 aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
4313 const ContextState& state = CurrentState();
4315 StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, state.lineCap,
4316 state.miterLimit, state.dash.Length(),
4317 state.dash.Elements(), state.dashOffset);
4319 return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY),
4320 mTarget->GetTransform());
4323 // Returns a surface that contains only the part needed to draw aSourceRect.
4324 // On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
4325 // relative to the returned surface.
4326 static already_AddRefed<SourceSurface> ExtractSubrect(SourceSurface* aSurface,
4327 gfx::Rect* aSourceRect,
4328 DrawTarget* aTargetDT) {
4329 gfx::Rect roundedOutSourceRect = *aSourceRect;
4330 roundedOutSourceRect.RoundOut();
4331 gfx::IntRect roundedOutSourceRectInt;
4332 if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
4333 RefPtr<SourceSurface> surface(aSurface);
4334 return surface.forget();
4337 RefPtr<DrawTarget> subrectDT = aTargetDT->CreateSimilarDrawTarget(
4338 roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
4340 if (subrectDT) {
4341 // See bug 1524554.
4342 subrectDT->ClearRect(gfx::Rect());
4345 if (!subrectDT || !subrectDT->IsValid()) {
4346 RefPtr<SourceSurface> surface(aSurface);
4347 return surface.forget();
4350 *aSourceRect -= roundedOutSourceRect.TopLeft();
4352 subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
4353 return subrectDT->Snapshot();
4357 // image
4360 static void ClipImageDimension(double& aSourceCoord, double& aSourceSize,
4361 int32_t aImageSize, double& aDestCoord,
4362 double& aDestSize) {
4363 double scale = aDestSize / aSourceSize;
4364 if (aSourceCoord < 0.0) {
4365 double destEnd = aDestCoord + aDestSize;
4366 aDestCoord -= aSourceCoord * scale;
4367 aDestSize = destEnd - aDestCoord;
4368 aSourceSize += aSourceCoord;
4369 aSourceCoord = 0.0;
4371 double delta = aImageSize - (aSourceCoord + aSourceSize);
4372 if (delta < 0.0) {
4373 aDestSize += delta * scale;
4374 aSourceSize = aImageSize - aSourceCoord;
4378 // Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
4379 // to pull a SourceSurface from our cache. This allows us to avoid
4380 // reoptimizing surfaces if content and canvas backends are different.
4381 SurfaceFromElementResult CanvasRenderingContext2D::CachedSurfaceFromElement(
4382 Element* aElement) {
4383 SurfaceFromElementResult res;
4384 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
4385 if (!imageLoader) {
4386 return res;
4389 nsCOMPtr<imgIRequest> imgRequest;
4390 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
4391 getter_AddRefs(imgRequest));
4392 if (!imgRequest) {
4393 return res;
4396 uint32_t status = 0;
4397 if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
4398 !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
4399 return res;
4402 nsCOMPtr<nsIPrincipal> principal;
4403 if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
4404 !principal) {
4405 return res;
4408 if (NS_FAILED(imgRequest->GetHadCrossOriginRedirects(
4409 &res.mHadCrossOriginRedirects))) {
4410 return res;
4413 res.mSourceSurface = CanvasImageCache::LookupAllCanvas(aElement);
4414 if (!res.mSourceSurface) {
4415 return res;
4418 int32_t corsmode = imgIRequest::CORS_NONE;
4419 if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
4420 res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
4423 res.mSize = res.mIntrinsicSize = res.mSourceSurface->GetSize();
4424 res.mPrincipal = std::move(principal);
4425 res.mImageRequest = std::move(imgRequest);
4426 res.mIsWriteOnly = CheckWriteOnlySecurity(res.mCORSUsed, res.mPrincipal,
4427 res.mHadCrossOriginRedirects);
4429 return res;
4432 // drawImage(in HTMLImageElement image, in float dx, in float dy);
4433 // -- render image from 0,0 at dx,dy top-left coords
4434 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw,
4435 // in float dh);
4436 // -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
4437 // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw,
4438 // in float sh, in float dx, in float dy, in float dw, in float dh);
4439 // -- render the region defined by (sx,sy,sw,wh) in image-local space into the
4440 // region (dx,dy,dw,dh) on the canvas
4442 // If only dx and dy are passed in then optional_argc should be 0. If only
4443 // dx, dy, dw and dh are passed in then optional_argc should be 2. The only
4444 // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
4445 // are all passed in.
4447 void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
4448 double aSx, double aSy, double aSw,
4449 double aSh, double aDx, double aDy,
4450 double aDw, double aDh,
4451 uint8_t aOptional_argc,
4452 ErrorResult& aError) {
4453 MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6);
4455 if (!ValidateRect(aDx, aDy, aDw, aDh, true)) {
4456 return;
4458 if (aOptional_argc == 6) {
4459 if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
4460 return;
4464 RefPtr<SourceSurface> srcSurf;
4465 gfx::IntSize imgSize;
4466 gfx::IntSize intrinsicImgSize;
4468 Element* element = nullptr;
4470 EnsureTarget();
4471 if (!IsTargetValid()) {
4472 return;
4475 if (aImage.IsHTMLCanvasElement()) {
4476 HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
4477 element = canvas;
4478 nsIntSize size = canvas->GetSize();
4479 if (size.width == 0 || size.height == 0) {
4480 return aError.ThrowInvalidStateError("Passed-in canvas is empty");
4483 if (canvas->IsWriteOnly()) {
4484 SetWriteOnly();
4486 } else if (aImage.IsImageBitmap()) {
4487 ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
4488 srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
4490 if (!srcSurf) {
4491 if (imageBitmap.IsClosed()) {
4492 aError.ThrowInvalidStateError("Passed-in ImageBitmap is closed");
4494 return;
4497 if (imageBitmap.IsWriteOnly()) {
4498 SetWriteOnly();
4501 imgSize = intrinsicImgSize =
4502 gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
4503 } else {
4504 if (aImage.IsHTMLImageElement()) {
4505 HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
4506 element = img;
4507 } else if (aImage.IsSVGImageElement()) {
4508 SVGImageElement* img = &aImage.GetAsSVGImageElement();
4509 element = img;
4510 } else {
4511 HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
4512 video->MarkAsContentSource(
4513 mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
4514 element = video;
4517 srcSurf = CanvasImageCache::LookupCanvas(element, mCanvasElement, &imgSize,
4518 &intrinsicImgSize);
4521 DirectDrawInfo drawInfo;
4523 if (!srcSurf) {
4524 // The canvas spec says that drawImage should draw the first frame
4525 // of animated images. We also don't want to rasterize vector images.
4526 uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
4527 nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
4529 SurfaceFromElementResult res =
4530 CanvasRenderingContext2D::CachedSurfaceFromElement(element);
4532 if (!res.mSourceSurface) {
4533 res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
4536 if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
4537 // The spec says to silently do nothing in the following cases:
4538 // - The element is still loading.
4539 // - The image is bad, but it's not in the broken state (i.e., we could
4540 // decode the headers and get the size).
4541 if (!res.mIsStillLoading && !res.mHasSize) {
4542 aError.ThrowInvalidStateError("Passed-in image is \"broken\"");
4544 return;
4547 imgSize = res.mSize;
4548 intrinsicImgSize = res.mIntrinsicSize;
4550 if (mCanvasElement) {
4551 CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, res.mPrincipal,
4552 res.mIsWriteOnly, res.mCORSUsed);
4555 if (res.mSourceSurface) {
4556 if (res.mImageRequest) {
4557 CanvasImageCache::NotifyDrawImage(element, mCanvasElement,
4558 res.mSourceSurface, imgSize,
4559 intrinsicImgSize);
4561 srcSurf = res.mSourceSurface;
4562 } else {
4563 drawInfo = res.mDrawInfo;
4567 if (aOptional_argc == 0) {
4568 aSx = aSy = 0.0;
4569 aSw = (double)imgSize.width;
4570 aSh = (double)imgSize.height;
4571 aDw = (double)intrinsicImgSize.width;
4572 aDh = (double)intrinsicImgSize.height;
4573 } else if (aOptional_argc == 2) {
4574 aSx = aSy = 0.0;
4575 aSw = (double)imgSize.width;
4576 aSh = (double)imgSize.height;
4579 if (aSw == 0.0 || aSh == 0.0) {
4580 return;
4583 ClipImageDimension(aSx, aSw, imgSize.width, aDx, aDw);
4584 ClipImageDimension(aSy, aSh, imgSize.height, aDy, aDh);
4586 if (aSw <= 0.0 || aSh <= 0.0 || aDw <= 0.0 || aDh <= 0.0) {
4587 // source and/or destination are fully clipped, so nothing is painted
4588 return;
4591 // Per spec, the smoothing setting applies only to scaling up a bitmap image.
4592 // When down-scaling the user agent is free to choose whether or not to smooth
4593 // the image. Nearest sampling when down-scaling is rarely desirable and
4594 // smoothing when down-scaling matches chromium's behavior.
4595 // If any dimension is up-scaled, we consider the image as being up-scaled.
4596 auto scale = mTarget->GetTransform().ScaleFactors();
4597 bool isDownScale =
4598 aDw * Abs(scale.width) < aSw && aDh * Abs(scale.height) < aSh;
4600 SamplingFilter samplingFilter;
4601 AntialiasMode antialiasMode;
4603 if (CurrentState().imageSmoothingEnabled || isDownScale) {
4604 samplingFilter = gfx::SamplingFilter::LINEAR;
4605 antialiasMode = AntialiasMode::DEFAULT;
4606 } else {
4607 samplingFilter = gfx::SamplingFilter::POINT;
4608 antialiasMode = AntialiasMode::NONE;
4611 const bool needBounds = NeedToCalculateBounds();
4612 if (!IsTargetValid()) {
4613 return;
4615 gfx::Rect bounds;
4616 if (needBounds) {
4617 bounds = gfx::Rect(aDx, aDy, aDw, aDh);
4618 bounds = mTarget->GetTransform().TransformBounds(bounds);
4621 if (!IsTargetValid()) {
4622 aError.Throw(NS_ERROR_FAILURE);
4623 return;
4626 if (srcSurf) {
4627 gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
4628 if (element == mCanvasElement) {
4629 // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
4630 // trigger a COW copy of the whole canvas into srcSurf. That's a huge
4631 // waste if sourceRect doesn't cover the whole canvas.
4632 // We avoid copying the whole canvas by manually copying just the part
4633 // that we need.
4634 srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
4637 AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds);
4638 if (!tempTarget) {
4639 gfxDevCrash(LogReason::InvalidDrawTarget)
4640 << "Invalid adjusted target in Canvas2D "
4641 << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow()
4642 << NeedToApplyFilter();
4643 return;
4646 auto op = UsedOperation();
4647 if (!IsTargetValid() || !tempTarget) {
4648 return;
4650 tempTarget->DrawSurface(
4651 srcSurf, gfx::Rect(aDx, aDy, aDw, aDh), sourceRect,
4652 DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
4653 DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
4654 } else {
4655 DrawDirectlyToCanvas(drawInfo, &bounds, gfx::Rect(aDx, aDy, aDw, aDh),
4656 gfx::Rect(aSx, aSy, aSw, aSh), imgSize);
4659 RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
4662 void CanvasRenderingContext2D::DrawDirectlyToCanvas(
4663 const DirectDrawInfo& aImage, gfx::Rect* aBounds, gfx::Rect aDest,
4664 gfx::Rect aSrc, gfx::IntSize aImgSize) {
4665 MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0,
4666 "Need positive source width and height");
4668 AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr : aBounds);
4669 if (!tempTarget) {
4670 return;
4673 // Get any existing transforms on the context, including transformations used
4674 // for context shadow.
4675 Matrix matrix = tempTarget->GetTransform();
4676 gfxMatrix contextMatrix = ThebesMatrix(matrix);
4677 gfxSize contextScale(contextMatrix.ScaleFactors());
4679 // Scale the dest rect to include the context scale.
4680 aDest.Scale(contextScale.width, contextScale.height);
4682 // Scale the image size to the dest rect, and adjust the source rect to match.
4683 gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height);
4684 IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width,
4685 aImgSize.height * scale.height);
4686 aSrc.Scale(scale.width, scale.height);
4688 // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
4689 // the matrix even though this is a temp gfxContext.
4690 AutoRestoreTransform autoRestoreTransform(mTarget);
4692 RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
4693 if (!context) {
4694 gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
4695 return;
4697 context->SetMatrixDouble(
4698 contextMatrix
4699 .PreScale(1.0 / contextScale.width, 1.0 / contextScale.height)
4700 .PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y));
4702 context->SetOp(UsedOperation());
4704 // FLAG_CLAMP is added for increased performance, since we never tile here.
4705 uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
4707 CSSIntSize sz(
4708 scaledImageSize.width,
4709 scaledImageSize
4710 .height); // XXX hmm is scaledImageSize really in CSS pixels?
4711 SVGImageContext svgContext(Some(sz));
4713 auto result = aImage.mImgContainer->Draw(
4714 context, scaledImageSize,
4715 ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
4716 aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags,
4717 CurrentState().globalAlpha);
4719 if (result != ImgDrawResult::SUCCESS) {
4720 NS_WARNING("imgIContainer::Draw failed");
4724 void CanvasRenderingContext2D::SetGlobalCompositeOperation(
4725 const nsAString& aOp, ErrorResult& aError) {
4726 CompositionOp comp_op;
4728 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
4729 if (aOp.EqualsLiteral(cvsop)) comp_op = CompositionOp::OP_##op2d;
4731 CANVAS_OP_TO_GFX_OP("copy", SOURCE)
4732 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
4733 else CANVAS_OP_TO_GFX_OP("source-in", IN)
4734 else CANVAS_OP_TO_GFX_OP("source-out", OUT)
4735 else CANVAS_OP_TO_GFX_OP("source-over", OVER)
4736 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
4737 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
4738 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
4739 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
4740 else CANVAS_OP_TO_GFX_OP("lighter", ADD)
4741 else CANVAS_OP_TO_GFX_OP("xor", XOR)
4742 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
4743 else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
4744 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
4745 else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
4746 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
4747 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
4748 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
4749 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
4750 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
4751 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
4752 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
4753 else CANVAS_OP_TO_GFX_OP("hue", HUE)
4754 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
4755 else CANVAS_OP_TO_GFX_OP("color", COLOR)
4756 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
4757 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4758 else return;
4760 #undef CANVAS_OP_TO_GFX_OP
4761 CurrentState().op = comp_op;
4764 void CanvasRenderingContext2D::GetGlobalCompositeOperation(
4765 nsAString& aOp, ErrorResult& aError) {
4766 CompositionOp comp_op = CurrentState().op;
4768 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
4769 if (comp_op == CompositionOp::OP_##op2d) aOp.AssignLiteral(cvsop);
4771 CANVAS_OP_TO_GFX_OP("copy", SOURCE)
4772 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
4773 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
4774 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
4775 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
4776 else CANVAS_OP_TO_GFX_OP("lighter", ADD)
4777 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
4778 else CANVAS_OP_TO_GFX_OP("source-in", IN)
4779 else CANVAS_OP_TO_GFX_OP("source-out", OUT)
4780 else CANVAS_OP_TO_GFX_OP("source-over", OVER)
4781 else CANVAS_OP_TO_GFX_OP("xor", XOR)
4782 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
4783 else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
4784 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
4785 else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
4786 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
4787 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
4788 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
4789 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
4790 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
4791 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
4792 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
4793 else CANVAS_OP_TO_GFX_OP("hue", HUE)
4794 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
4795 else CANVAS_OP_TO_GFX_OP("color", COLOR)
4796 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
4797 else {
4798 aError.Throw(NS_ERROR_FAILURE);
4801 #undef CANVAS_OP_TO_GFX_OP
4804 void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow,
4805 double aX, double aY, double aW,
4806 double aH, const nsACString& aBgColor,
4807 uint32_t aFlags,
4808 nsIPrincipal& aSubjectPrincipal,
4809 ErrorResult& aError) {
4810 if (int32_t(aW) == 0 || int32_t(aH) == 0) {
4811 return;
4814 // protect against too-large surfaces that will cause allocation
4815 // or overflow issues
4816 if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) {
4817 aError.Throw(NS_ERROR_FAILURE);
4818 return;
4821 Document* doc = aWindow.GetExtantDoc();
4822 if (doc && aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
4823 doc->WarnOnceAbout(
4824 DeprecatedOperations::eDrawWindowCanvasRenderingContext2D);
4827 // Flush layout updates
4828 if (!(aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) {
4829 nsContentUtils::FlushLayoutForTree(aWindow.GetOuterWindow());
4832 CompositionOp op = UsedOperation();
4833 bool discardContent =
4834 GlobalAlpha() == 1.0f &&
4835 (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
4836 const gfx::Rect drawRect(aX, aY, aW, aH);
4837 EnsureTarget(discardContent ? &drawRect : nullptr);
4838 if (!IsTargetValid()) {
4839 return;
4842 RefPtr<nsPresContext> presContext;
4843 nsIDocShell* docshell = aWindow.GetDocShell();
4844 if (docshell) {
4845 presContext = docshell->GetPresContext();
4847 if (!presContext) {
4848 aError.Throw(NS_ERROR_FAILURE);
4849 return;
4852 nscolor backgroundColor;
4853 if (!ParseColor(aBgColor, &backgroundColor)) {
4854 aError.Throw(NS_ERROR_FAILURE);
4855 return;
4858 nsRect r(nsPresContext::CSSPixelsToAppUnits((float)aX),
4859 nsPresContext::CSSPixelsToAppUnits((float)aY),
4860 nsPresContext::CSSPixelsToAppUnits((float)aW),
4861 nsPresContext::CSSPixelsToAppUnits((float)aH));
4862 RenderDocumentFlags renderDocFlags =
4863 (RenderDocumentFlags::IgnoreViewportScrolling |
4864 RenderDocumentFlags::DocumentRelative);
4865 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET) {
4866 renderDocFlags |= RenderDocumentFlags::DrawCaret;
4868 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW) {
4869 renderDocFlags &= ~(RenderDocumentFlags::IgnoreViewportScrolling |
4870 RenderDocumentFlags::DocumentRelative);
4872 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS) {
4873 renderDocFlags |= RenderDocumentFlags::UseWidgetLayers;
4875 if (aFlags &
4876 CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES) {
4877 renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages;
4879 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH) {
4880 renderDocFlags |= RenderDocumentFlags::DrawWindowNotFlushing;
4883 // gfxContext-over-Azure may modify the DrawTarget's transform, so
4884 // save and restore it
4885 Matrix matrix = mTarget->GetTransform();
4886 double sw = matrix._11 * aW;
4887 double sh = matrix._22 * aH;
4888 if (!sw || !sh) {
4889 return;
4892 RefPtr<gfxContext> thebes;
4893 RefPtr<DrawTarget> drawDT;
4894 // Rendering directly is faster and can be done if mTarget supports Azure
4895 // and does not need alpha blending.
4896 // Since the pre-transaction callback calls ReturnTarget, we can't have a
4897 // gfxContext wrapped around it when using a shared buffer provider because
4898 // the DrawTarget's shared buffer may be unmapped in ReturnTarget.
4899 op = CompositionOp::OP_ADD;
4900 if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
4901 GlobalAlpha() == 1.0f) {
4902 op = UsedOperation();
4903 if (!IsTargetValid()) {
4904 aError.Throw(NS_ERROR_FAILURE);
4905 return;
4908 if (op == CompositionOp::OP_OVER &&
4909 (!mBufferProvider ||
4910 (mBufferProvider->GetType() != LayersBackend::LAYERS_CLIENT &&
4911 mBufferProvider->GetType() != LayersBackend::LAYERS_WR))) {
4912 thebes = gfxContext::CreateOrNull(mTarget);
4913 MOZ_ASSERT(thebes); // already checked the draw target above
4914 // (in SupportsAzureContentForDrawTarget)
4915 thebes->SetMatrix(matrix);
4916 } else {
4917 IntSize dtSize = IntSize::Ceil(sw, sh);
4918 if (!Factory::AllowedSurfaceSize(dtSize)) {
4919 // attempt to limit the DT to what will actually cover the target
4920 Size limitSize(mTarget->GetSize());
4921 limitSize.Scale(matrix._11, matrix._22);
4922 dtSize = Min(dtSize, IntSize::Ceil(limitSize));
4923 // if the DT is still too big, then error
4924 if (!Factory::AllowedSurfaceSize(dtSize)) {
4925 aError.Throw(NS_ERROR_FAILURE);
4926 return;
4929 drawDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
4930 dtSize, SurfaceFormat::B8G8R8A8);
4931 if (!drawDT || !drawDT->IsValid()) {
4932 aError.Throw(NS_ERROR_FAILURE);
4933 return;
4936 thebes = gfxContext::CreateOrNull(drawDT);
4937 MOZ_ASSERT(thebes); // alrady checked the draw target above
4938 thebes->SetMatrix(Matrix::Scaling(matrix._11, matrix._22));
4941 RefPtr<PresShell> presShell = presContext->PresShell();
4943 Unused << presShell->RenderDocument(r, renderDocFlags, backgroundColor,
4944 thebes);
4945 // If this canvas was contained in the drawn window, the pre-transaction
4946 // callback may have returned its DT. If so, we must reacquire it here.
4947 EnsureTarget(discardContent ? &drawRect : nullptr);
4949 if (drawDT) {
4950 RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
4951 if (NS_WARN_IF(!snapshot)) {
4952 aError.Throw(NS_ERROR_FAILURE);
4953 return;
4956 op = UsedOperation();
4957 if (!IsTargetValid()) {
4958 aError.Throw(NS_ERROR_FAILURE);
4959 return;
4961 gfx::Rect destRect(0, 0, aW, aH);
4962 gfx::Rect sourceRect(0, 0, sw, sh);
4963 mTarget->DrawSurface(snapshot, destRect, sourceRect,
4964 DrawSurfaceOptions(gfx::SamplingFilter::POINT),
4965 DrawOptions(GlobalAlpha(), op, AntialiasMode::NONE));
4966 } else {
4967 mTarget->SetTransform(matrix);
4970 // note that x and y are coordinates in the document that
4971 // we're drawing; x and y are drawn to 0,0 in current user
4972 // space.
4973 RedrawUser(gfxRect(0, 0, aW, aH));
4977 // device pixel getting/setting
4980 already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData(
4981 JSContext* aCx, int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
4982 nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
4983 if (!mCanvasElement && !mDocShell) {
4984 NS_ERROR("No canvas element and no docshell in GetImageData!!!");
4985 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
4986 return nullptr;
4989 // Check only if we have a canvas element; if we were created with a docshell,
4990 // then it's special internal use.
4991 if (IsWriteOnly() ||
4992 (mCanvasElement && !mCanvasElement->CallerCanRead(aCx))) {
4993 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
4994 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
4995 return nullptr;
4998 if (!aSw || !aSh) {
4999 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5000 return nullptr;
5003 // Handle negative width and height by flipping the rectangle over in the
5004 // relevant direction.
5005 uint32_t w, h;
5006 if (aSw < 0) {
5007 w = -aSw;
5008 aSx -= w;
5009 } else {
5010 w = aSw;
5012 if (aSh < 0) {
5013 h = -aSh;
5014 aSy -= h;
5015 } else {
5016 h = aSh;
5019 if (w == 0) {
5020 w = 1;
5022 if (h == 0) {
5023 h = 1;
5026 JS::Rooted<JSObject*> array(aCx);
5027 aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal,
5028 array.address());
5029 if (aError.Failed()) {
5030 return nullptr;
5032 MOZ_ASSERT(array);
5033 return MakeAndAddRef<ImageData>(w, h, *array);
5036 nsresult CanvasRenderingContext2D::GetImageDataArray(
5037 JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight,
5038 nsIPrincipal& aSubjectPrincipal, JSObject** aRetval) {
5039 MOZ_ASSERT(aWidth && aHeight);
5041 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
5042 if (!len.isValid()) {
5043 return NS_ERROR_DOM_INDEX_SIZE_ERR;
5046 CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth;
5047 CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight;
5049 if (!rightMost.isValid() || !bottomMost.isValid()) {
5050 return NS_ERROR_DOM_SYNTAX_ERR;
5053 JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value()));
5054 if (!darray) {
5055 return NS_ERROR_OUT_OF_MEMORY;
5058 if (mZero) {
5059 *aRetval = darray;
5060 return NS_OK;
5063 IntRect srcRect(0, 0, mWidth, mHeight);
5064 IntRect destRect(aX, aY, aWidth, aHeight);
5065 IntRect srcReadRect = srcRect.Intersect(destRect);
5066 if (srcReadRect.IsEmpty()) {
5067 *aRetval = darray;
5068 return NS_OK;
5071 if (!mBufferProvider) {
5072 if (!EnsureTarget()) {
5073 return NS_ERROR_FAILURE;
5077 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
5078 if (!snapshot) {
5079 return NS_ERROR_OUT_OF_MEMORY;
5082 RefPtr<DataSourceSurface> readback = snapshot->GetDataSurface();
5083 mBufferProvider->ReturnSnapshot(snapshot.forget());
5085 DataSourceSurface::MappedSurface rawData;
5086 if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
5087 return NS_ERROR_OUT_OF_MEMORY;
5090 IntRect dstWriteRect = srcReadRect;
5091 dstWriteRect.MoveBy(-aX, -aY);
5093 // Check for site-specific permission. This check is not needed if the
5094 // canvas was created with a docshell (that is only done for special
5095 // internal uses).
5096 bool usePlaceholder = false;
5097 if (mCanvasElement) {
5098 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
5099 usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
5100 aSubjectPrincipal);
5103 do {
5104 uint8_t* randomData;
5105 if (usePlaceholder) {
5106 // Since we cannot call any GC-able functions (like requesting the RNG
5107 // service) after we call JS_GetUint8ClampedArrayData, we will
5108 // pre-generate the randomness required for GeneratePlaceholderCanvasData.
5109 randomData = TryToGenerateRandomDataForPlaceholderCanvasData();
5112 JS::AutoCheckCannotGC nogc;
5113 bool isShared;
5114 uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
5115 MOZ_ASSERT(!isShared); // Should not happen, data was created above
5117 if (usePlaceholder) {
5118 FillPlaceholderCanvas(randomData, len.value(), data);
5119 break;
5122 uint32_t srcStride = rawData.mStride;
5123 uint8_t* src =
5124 rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
5126 uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
5128 if (mOpaque) {
5129 SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32, dst,
5130 aWidth * 4, SurfaceFormat::R8G8B8A8, dstWriteRect.Size());
5131 } else {
5132 UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32, dst,
5133 aWidth * 4, SurfaceFormat::R8G8B8A8,
5134 dstWriteRect.Size());
5136 } while (false);
5138 readback->Unmap();
5139 *aRetval = darray;
5140 return NS_OK;
5143 void CanvasRenderingContext2D::EnsureErrorTarget() {
5144 if (sErrorTarget) {
5145 return;
5148 RefPtr<DrawTarget> errorTarget =
5149 gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
5150 IntSize(1, 1), SurfaceFormat::B8G8R8A8);
5151 MOZ_ASSERT(errorTarget, "Failed to allocate the error target!");
5153 sErrorTarget = errorTarget;
5154 NS_ADDREF(sErrorTarget);
5157 void CanvasRenderingContext2D::FillRuleChanged() {
5158 if (mPath) {
5159 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
5160 mPath = nullptr;
5164 void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx,
5165 int32_t aDy, ErrorResult& aError) {
5166 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
5167 DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
5168 MOZ_ASSERT(inited);
5170 PutImageData_explicit(aDx, aDy, aImageData.Width(), aImageData.Height(), &arr,
5171 false, 0, 0, 0, 0, aError);
5174 void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx,
5175 int32_t aDy, int32_t aDirtyX,
5176 int32_t aDirtyY,
5177 int32_t aDirtyWidth,
5178 int32_t aDirtyHeight,
5179 ErrorResult& aError) {
5180 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
5181 DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
5182 MOZ_ASSERT(inited);
5184 PutImageData_explicit(aDx, aDy, aImageData.Width(), aImageData.Height(), &arr,
5185 true, aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight,
5186 aError);
5189 void CanvasRenderingContext2D::PutImageData_explicit(
5190 int32_t aX, int32_t aY, uint32_t aW, uint32_t aH,
5191 dom::Uint8ClampedArray* aArray, bool aHasDirtyRect, int32_t aDirtyX,
5192 int32_t aDirtyY, int32_t aDirtyWidth, int32_t aDirtyHeight,
5193 ErrorResult& aRv) {
5194 if (aW == 0 || aH == 0) {
5195 return aRv.ThrowInvalidStateError("Passed-in image is empty");
5198 IntRect dirtyRect;
5199 IntRect imageDataRect(0, 0, aW, aH);
5201 if (aHasDirtyRect) {
5202 // fix up negative dimensions
5203 if (aDirtyWidth < 0) {
5204 if (aDirtyWidth == INT_MIN) {
5205 return aRv.ThrowInvalidStateError("Dirty width is invalid");
5208 CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth;
5210 if (!checkedDirtyX.isValid()) {
5211 return aRv.ThrowInvalidStateError("Dirty width is invalid");
5214 aDirtyX = checkedDirtyX.value();
5215 aDirtyWidth = -aDirtyWidth;
5218 if (aDirtyHeight < 0) {
5219 if (aDirtyHeight == INT_MIN) {
5220 return aRv.ThrowInvalidStateError("Dirty height is invalid");
5223 CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight;
5225 if (!checkedDirtyY.isValid()) {
5226 return aRv.ThrowInvalidStateError("Dirty height is invalid");
5229 aDirtyY = checkedDirtyY.value();
5230 aDirtyHeight = -aDirtyHeight;
5233 // bound the dirty rect within the imageData rectangle
5234 dirtyRect = imageDataRect.Intersect(
5235 IntRect(aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight));
5237 if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) {
5238 return;
5240 } else {
5241 dirtyRect = imageDataRect;
5244 dirtyRect.MoveBy(IntPoint(aX, aY));
5245 dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect);
5247 if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) {
5248 return;
5251 aArray->ComputeState();
5253 uint32_t dataLen = aArray->Length();
5255 uint32_t len = aW * aH * 4;
5256 if (dataLen != len) {
5257 return aRv.ThrowInvalidStateError("Invalid width or height");
5260 // The canvas spec says that the current path, transformation matrix, shadow
5261 // attributes, global alpha, the clipping region, and global composition
5262 // operator must not affect the getImageData() and putImageData() methods.
5263 const gfx::Rect putRect(dirtyRect);
5264 EnsureTarget(&putRect);
5266 if (!IsTargetValid()) {
5267 return aRv.Throw(NS_ERROR_FAILURE);
5270 DataSourceSurface::MappedSurface map;
5271 RefPtr<DataSourceSurface> sourceSurface;
5272 uint8_t* lockedBits = nullptr;
5273 uint8_t* dstData;
5274 IntSize dstSize;
5275 int32_t dstStride;
5276 SurfaceFormat dstFormat;
5277 if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) {
5278 dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4;
5279 } else {
5280 sourceSurface = Factory::CreateDataSourceSurface(
5281 dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false);
5283 // In certain scenarios, requesting larger than 8k image fails. Bug 803568
5284 // covers the details of how to run into it, but the full detailed
5285 // investigation hasn't been done to determine the underlying cause. We
5286 // will just handle the failure to allocate the surface to avoid a crash.
5287 if (!sourceSurface) {
5288 return aRv.Throw(NS_ERROR_FAILURE);
5290 if (!sourceSurface->Map(DataSourceSurface::READ_WRITE, &map)) {
5291 return aRv.Throw(NS_ERROR_FAILURE);
5294 dstData = map.mData;
5295 if (!dstData) {
5296 return aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
5298 dstStride = map.mStride;
5299 dstFormat = sourceSurface->GetFormat();
5302 IntRect srcRect = dirtyRect - IntPoint(aX, aY);
5303 uint8_t* srcData = aArray->Data() + srcRect.y * (aW * 4) + srcRect.x * 4;
5305 PremultiplyData(
5306 srcData, aW * 4, SurfaceFormat::R8G8B8A8, dstData, dstStride,
5307 mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32,
5308 dirtyRect.Size());
5310 if (lockedBits) {
5311 mTarget->ReleaseBits(lockedBits);
5312 } else if (sourceSurface) {
5313 sourceSurface->Unmap();
5314 mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(),
5315 dirtyRect.TopLeft());
5318 Redraw(
5319 gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height));
5322 static already_AddRefed<ImageData> CreateImageData(
5323 JSContext* aCx, CanvasRenderingContext2D* aContext, uint32_t aW,
5324 uint32_t aH, ErrorResult& aError) {
5325 if (aW == 0) aW = 1;
5326 if (aH == 0) aH = 1;
5328 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4;
5329 if (!len.isValid()) {
5330 aError.ThrowIndexSizeError("Invalid width or height");
5331 return nullptr;
5334 // Create the fast typed array; it's initialized to 0 by default.
5335 JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value());
5336 if (!darray) {
5337 // TODO: Should use OOMReporter.
5338 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
5339 return nullptr;
5342 return do_AddRef(new ImageData(aW, aH, *darray));
5345 already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData(
5346 JSContext* aCx, int32_t aSw, int32_t aSh, ErrorResult& aError) {
5347 if (!aSw || !aSh) {
5348 aError.ThrowIndexSizeError("Invalid width or height");
5349 return nullptr;
5352 uint32_t w = Abs(aSw);
5353 uint32_t h = Abs(aSh);
5354 return dom::CreateImageData(aCx, this, w, h, aError);
5357 already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData(
5358 JSContext* aCx, ImageData& aImagedata, ErrorResult& aError) {
5359 return dom::CreateImageData(aCx, this, aImagedata.Width(),
5360 aImagedata.Height(), aError);
5363 void CanvasRenderingContext2D::OnBeforePaintTransaction() {
5364 if (!mTarget) return;
5365 OnStableState();
5368 void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); }
5370 already_AddRefed<Layer> CanvasRenderingContext2D::GetCanvasLayer(
5371 nsDisplayListBuilder* aBuilder, Layer* aOldLayer, LayerManager* aManager) {
5372 if (mOpaque) {
5373 // If we're opaque then make sure we have a surface so we paint black
5374 // instead of transparent.
5375 EnsureTarget();
5378 // Don't call EnsureTarget() ... if there isn't already a surface, then
5379 // we have nothing to paint and there is no need to create a surface just
5380 // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
5381 // layer manager which must NOT happen during a paint.
5382 if (!mBufferProvider && !IsTargetValid()) {
5383 // No DidTransactionCallback will be received, so mark the context clean
5384 // now so future invalidations will be dispatched.
5385 MarkContextClean();
5386 return nullptr;
5389 if (!mResetLayer && aOldLayer) {
5390 RefPtr<Layer> ret = aOldLayer;
5391 return ret.forget();
5394 RefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer();
5395 if (!canvasLayer) {
5396 NS_WARNING("CreateCanvasLayer returned null!");
5397 // No DidTransactionCallback will be received, so mark the context clean
5398 // now so future invalidations will be dispatched.
5399 MarkContextClean();
5400 return nullptr;
5403 const auto canvasRenderer = canvasLayer->CreateOrGetCanvasRenderer();
5404 InitializeCanvasRenderer(aBuilder, canvasRenderer);
5405 uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
5406 canvasLayer->SetContentFlags(flags);
5408 mResetLayer = false;
5410 return canvasLayer.forget();
5413 bool CanvasRenderingContext2D::UpdateWebRenderCanvasData(
5414 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
5415 if (mOpaque) {
5416 // If we're opaque then make sure we have a surface so we paint black
5417 // instead of transparent.
5418 EnsureTarget();
5421 // Don't call EnsureTarget() ... if there isn't already a surface, then
5422 // we have nothing to paint and there is no need to create a surface just
5423 // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
5424 // layer manager which must NOT happen during a paint.
5425 if (!mBufferProvider && !IsTargetValid()) {
5426 // No DidTransactionCallback will be received, so mark the context clean
5427 // now so future invalidations will be dispatched.
5428 MarkContextClean();
5429 // Clear CanvasRenderer of WebRenderCanvasData
5430 aCanvasData->ClearCanvasRenderer();
5431 return false;
5434 auto renderer = aCanvasData->GetCanvasRenderer();
5436 if (!mResetLayer && renderer) {
5437 CanvasRendererData data;
5438 data.mContext = mSharedPtrPtr;
5439 data.mSize = GetSize();
5441 if (renderer->IsDataValid(data)) {
5442 return true;
5446 renderer = aCanvasData->CreateCanvasRenderer();
5447 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
5448 // Clear CanvasRenderer of WebRenderCanvasData
5449 aCanvasData->ClearCanvasRenderer();
5450 return false;
5453 MOZ_ASSERT(renderer);
5454 mResetLayer = false;
5455 return true;
5458 bool CanvasRenderingContext2D::InitializeCanvasRenderer(
5459 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
5460 CanvasRendererData data;
5461 data.mContext = mSharedPtrPtr;
5462 data.mSize = GetSize();
5463 data.mIsOpaque = mOpaque;
5464 data.mDoPaintCallbacks = true;
5466 if (!mBufferProvider) {
5467 // Force the creation of a buffer provider.
5468 EnsureTarget();
5469 ReturnTarget();
5470 if (!mBufferProvider) {
5471 MarkContextClean();
5472 return false;
5476 aRenderer->Initialize(data);
5477 aRenderer->SetDirty();
5478 return true;
5481 void CanvasRenderingContext2D::MarkContextClean() {
5482 if (mInvalidateCount > 0) {
5483 mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
5485 mIsEntireFrameInvalid = false;
5486 mInvalidateCount = 0;
5489 void CanvasRenderingContext2D::MarkContextCleanForFrameCapture() {
5490 mIsCapturedFrameInvalid = false;
5493 bool CanvasRenderingContext2D::IsContextCleanForFrameCapture() {
5494 return !mIsCapturedFrameInvalid;
5497 bool CanvasRenderingContext2D::ShouldForceInactiveLayer(
5498 LayerManager* aManager) {
5499 return !aManager->CanUseCanvasLayerForSize(GetSize());
5502 void CanvasRenderingContext2D::GetAppUnitsValues(int32_t* aPerDevPixel,
5503 int32_t* aPerCSSPixel) {
5504 // If we don't have a canvas element, we just return something generic.
5505 if (aPerDevPixel) {
5506 *aPerDevPixel = 60;
5508 if (aPerCSSPixel) {
5509 *aPerCSSPixel = 60;
5511 PresShell* presShell = GetPresShell();
5512 if (!presShell) {
5513 return;
5515 nsPresContext* presContext = presShell->GetPresContext();
5516 if (!presContext) {
5517 return;
5519 if (aPerDevPixel) {
5520 *aPerDevPixel = presContext->AppUnitsPerDevPixel();
5522 if (aPerCSSPixel) {
5523 *aPerCSSPixel = AppUnitsPerCSSPixel();
5527 void CanvasRenderingContext2D::SetWriteOnly() {
5528 mWriteOnly = true;
5529 if (mCanvasElement) {
5530 mCanvasElement->SetWriteOnly();
5534 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
5535 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
5537 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
5539 CanvasPath::CanvasPath(nsISupports* aParent) : mParent(aParent) {
5540 mPathBuilder = gfxPlatform::GetPlatform()
5541 ->ScreenReferenceDrawTarget()
5542 ->CreatePathBuilder();
5545 CanvasPath::CanvasPath(nsISupports* aParent,
5546 already_AddRefed<PathBuilder> aPathBuilder)
5547 : mParent(aParent), mPathBuilder(aPathBuilder) {
5548 if (!mPathBuilder) {
5549 mPathBuilder = gfxPlatform::GetPlatform()
5550 ->ScreenReferenceDrawTarget()
5551 ->CreatePathBuilder();
5555 JSObject* CanvasPath::WrapObject(JSContext* aCx,
5556 JS::Handle<JSObject*> aGivenProto) {
5557 return Path2D_Binding::Wrap(aCx, this, aGivenProto);
5560 already_AddRefed<CanvasPath> CanvasPath::Constructor(
5561 const GlobalObject& aGlobal) {
5562 RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports());
5563 return path.forget();
5566 already_AddRefed<CanvasPath> CanvasPath::Constructor(
5567 const GlobalObject& aGlobal, CanvasPath& aCanvasPath) {
5568 RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(
5569 CanvasWindingRule::Nonzero,
5570 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
5572 RefPtr<CanvasPath> path =
5573 new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
5574 return path.forget();
5577 already_AddRefed<CanvasPath> CanvasPath::Constructor(
5578 const GlobalObject& aGlobal, const nsAString& aPathString) {
5579 RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString);
5580 if (!tempPath) {
5581 return Constructor(aGlobal);
5584 RefPtr<CanvasPath> path =
5585 new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
5586 return path.forget();
5589 void CanvasPath::ClosePath() {
5590 EnsurePathBuilder();
5592 mPathBuilder->Close();
5595 void CanvasPath::MoveTo(double aX, double aY) {
5596 EnsurePathBuilder();
5598 mPathBuilder->MoveTo(Point(ToFloat(aX), ToFloat(aY)));
5601 void CanvasPath::LineTo(double aX, double aY) {
5602 EnsurePathBuilder();
5604 mPathBuilder->LineTo(Point(ToFloat(aX), ToFloat(aY)));
5607 void CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX,
5608 double aY) {
5609 EnsurePathBuilder();
5611 mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(aCpx), ToFloat(aCpy)),
5612 gfx::Point(ToFloat(aX), ToFloat(aY)));
5615 void CanvasPath::BezierCurveTo(double aCp1x, double aCp1y, double aCp2x,
5616 double aCp2y, double aX, double aY) {
5617 BezierTo(gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)),
5618 gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)),
5619 gfx::Point(ToFloat(aX), ToFloat(aY)));
5622 void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2,
5623 double aRadius, ErrorResult& aError) {
5624 if (aRadius < 0) {
5625 return aError.ThrowIndexSizeError("Negative radius");
5628 EnsurePathBuilder();
5630 // Current point in user space!
5631 Point p0 = mPathBuilder->CurrentPoint();
5632 Point p1(aX1, aY1);
5633 Point p2(aX2, aY2);
5635 // Execute these calculations in double precision to avoid cumulative
5636 // rounding errors.
5637 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
5638 cy, angle0, angle1;
5639 bool anticlockwise;
5641 if (p0 == p1 || p1 == p2 || aRadius == 0) {
5642 LineTo(p1.x, p1.y);
5643 return;
5646 // Check for colinearity
5647 dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
5648 if (dir == 0) {
5649 LineTo(p1.x, p1.y);
5650 return;
5653 // XXX - Math for this code was already available from the non-azure code
5654 // and would be well tested. Perhaps converting to bezier directly might
5655 // be more efficient longer run.
5656 a2 = (p0.x - aX1) * (p0.x - aX1) + (p0.y - aY1) * (p0.y - aY1);
5657 b2 = (aX1 - aX2) * (aX1 - aX2) + (aY1 - aY2) * (aY1 - aY2);
5658 c2 = (p0.x - aX2) * (p0.x - aX2) + (p0.y - aY2) * (p0.y - aY2);
5659 cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2));
5661 sinx = sqrt(1 - cosx * cosx);
5662 d = aRadius / ((1 - cosx) / sinx);
5664 anx = (aX1 - p0.x) / sqrt(a2);
5665 any = (aY1 - p0.y) / sqrt(a2);
5666 bnx = (aX1 - aX2) / sqrt(b2);
5667 bny = (aY1 - aY2) / sqrt(b2);
5668 x3 = aX1 - anx * d;
5669 y3 = aY1 - any * d;
5670 x4 = aX1 - bnx * d;
5671 y4 = aY1 - bny * d;
5672 anticlockwise = (dir < 0);
5673 cx = x3 + any * aRadius * (anticlockwise ? 1 : -1);
5674 cy = y3 - anx * aRadius * (anticlockwise ? 1 : -1);
5675 angle0 = atan2((y3 - cy), (x3 - cx));
5676 angle1 = atan2((y4 - cy), (x4 - cx));
5678 LineTo(x3, y3);
5680 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
5683 void CanvasPath::Rect(double aX, double aY, double aW, double aH) {
5684 MoveTo(aX, aY);
5685 LineTo(aX + aW, aY);
5686 LineTo(aX + aW, aY + aH);
5687 LineTo(aX, aY + aH);
5688 ClosePath();
5691 void CanvasPath::Arc(double aX, double aY, double aRadius, double aStartAngle,
5692 double aEndAngle, bool aAnticlockwise,
5693 ErrorResult& aError) {
5694 if (aRadius < 0.0) {
5695 return aError.ThrowIndexSizeError("Negative radius");
5698 EnsurePathBuilder();
5700 ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle,
5701 aEndAngle, aAnticlockwise);
5704 void CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
5705 double rotation, double startAngle, double endAngle,
5706 bool anticlockwise, ErrorResult& aError) {
5707 if (radiusX < 0.0 || radiusY < 0.0) {
5708 return aError.ThrowIndexSizeError("Negative radius");
5711 EnsurePathBuilder();
5713 ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle,
5714 anticlockwise, rotation);
5717 void CanvasPath::LineTo(const gfx::Point& aPoint) {
5718 EnsurePathBuilder();
5720 mPathBuilder->LineTo(aPoint);
5723 void CanvasPath::BezierTo(const gfx::Point& aCP1, const gfx::Point& aCP2,
5724 const gfx::Point& aCP3) {
5725 EnsurePathBuilder();
5727 mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
5730 void CanvasPath::AddPath(CanvasPath& aCanvasPath, const DOMMatrix2DInit& aInit,
5731 ErrorResult& aError) {
5732 RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(
5733 CanvasWindingRule::Nonzero,
5734 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
5736 RefPtr<DOMMatrixReadOnly> matrix =
5737 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
5738 if (aError.Failed()) {
5739 return;
5742 Matrix transform(*(matrix->GetInternal2D()));
5744 if (!transform.IsFinite()) {
5745 return;
5748 if (!transform.IsIdentity()) {
5749 RefPtr<PathBuilder> tempBuilder =
5750 tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING);
5751 tempPath = tempBuilder->Finish();
5754 EnsurePathBuilder(); // in case a path is added to itself
5755 tempPath->StreamToSink(mPathBuilder);
5758 already_AddRefed<gfx::Path> CanvasPath::GetPath(
5759 const CanvasWindingRule& aWinding, const DrawTarget* aTarget) const {
5760 FillRule fillRule = FillRule::FILL_WINDING;
5761 if (aWinding == CanvasWindingRule::Evenodd) {
5762 fillRule = FillRule::FILL_EVEN_ODD;
5765 if (mPath && (mPath->GetBackendType() == aTarget->GetBackendType()) &&
5766 (mPath->GetFillRule() == fillRule)) {
5767 RefPtr<gfx::Path> path(mPath);
5768 return path.forget();
5771 if (!mPath) {
5772 // if there is no path, there must be a pathbuilder
5773 MOZ_ASSERT(mPathBuilder);
5774 mPath = mPathBuilder->Finish();
5775 if (!mPath) {
5776 RefPtr<gfx::Path> path(mPath);
5777 return path.forget();
5780 mPathBuilder = nullptr;
5783 // retarget our backend if we're used with a different backend
5784 if (mPath->GetBackendType() != aTarget->GetBackendType()) {
5785 RefPtr<PathBuilder> tmpPathBuilder = aTarget->CreatePathBuilder(fillRule);
5786 mPath->StreamToSink(tmpPathBuilder);
5787 mPath = tmpPathBuilder->Finish();
5788 } else if (mPath->GetFillRule() != fillRule) {
5789 RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule);
5790 mPath = tmpPathBuilder->Finish();
5793 RefPtr<gfx::Path> path(mPath);
5794 return path.forget();
5797 void CanvasPath::EnsurePathBuilder() const {
5798 if (mPathBuilder) {
5799 return;
5802 // if there is not pathbuilder, there must be a path
5803 MOZ_ASSERT(mPath);
5804 mPathBuilder = mPath->CopyToBuilder();
5805 mPath = nullptr;
5808 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) {
5809 int32_t width = aContext->GetWidth();
5810 int32_t height = aContext->GetHeight();
5812 // TODO: Bug 1552137: No memory will be allocated if either dimension is
5813 // greater than gfxPrefs::gfx_canvas_max_size(). We should check this here
5814 // too.
5816 CheckedInt<uint32_t> bytes = CheckedInt<uint32_t>(width) * height * 4;
5817 if (!bytes.isValid()) {
5818 return 0;
5821 return bytes.value();
5824 } // namespace mozilla::dom