Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / canvas / CanvasRenderingContext2D.cpp
blobc26cd969ba78a301f9a8d2b1cd4a695997b5fb39
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CanvasRenderingContext2D.h"
8 #include "mozilla/gfx/Helpers.h"
9 #include "nsXULElement.h"
11 #include "nsMathUtils.h"
13 #include "nsContentUtils.h"
15 #include "mozilla/intl/BidiEmbeddingLevel.h"
16 #include "mozilla/PresShell.h"
17 #include "mozilla/PresShellInlines.h"
18 #include "mozilla/SVGImageContext.h"
19 #include "mozilla/SVGObserverUtils.h"
20 #include "mozilla/dom/Document.h"
21 #include "mozilla/dom/FontFaceSetImpl.h"
22 #include "mozilla/dom/FontFaceSet.h"
23 #include "mozilla/dom/HTMLCanvasElement.h"
24 #include "mozilla/dom/GeneratePlaceholderCanvasData.h"
25 #include "mozilla/dom/VideoFrame.h"
26 #include "nsPresContext.h"
28 #include "nsIInterfaceRequestorUtils.h"
29 #include "nsIFrame.h"
30 #include "nsError.h"
32 #include "nsCSSPseudoElements.h"
33 #include "nsComputedDOMStyle.h"
35 #include "nsPrintfCString.h"
37 #include "nsRFPService.h"
38 #include "nsReadableUtils.h"
40 #include "nsColor.h"
41 #include "nsGfxCIID.h"
42 #include "nsIDocShell.h"
43 #include "nsPIDOMWindow.h"
44 #include "nsDisplayList.h"
45 #include "nsFocusManager.h"
47 #include "nsTArray.h"
49 #include "ImageEncoder.h"
50 #include "ImageRegion.h"
52 #include "gfxContext.h"
53 #include "gfxPlatform.h"
54 #include "gfxFont.h"
55 #include "gfxBlur.h"
56 #include "gfxTextRun.h"
57 #include "gfxUtils.h"
59 #include "nsFrameLoader.h"
60 #include "nsBidiPresUtils.h"
61 #include "LayerUserData.h"
62 #include "CanvasUtils.h"
63 #include "nsIMemoryReporter.h"
64 #include "nsStyleUtil.h"
65 #include "CanvasImageCache.h"
66 #include "DrawTargetWebgl.h"
68 #include <algorithm>
70 #include "jsapi.h"
71 #include "jsfriendapi.h"
72 #include "js/Array.h" // JS::GetArrayLength
73 #include "js/Conversions.h"
74 #include "js/experimental/TypedData.h" // JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData
75 #include "js/HeapAPI.h"
76 #include "js/PropertyAndElement.h" // JS_GetElement
77 #include "js/Warnings.h" // JS::WarnASCII
79 #include "mozilla/Alignment.h"
80 #include "mozilla/Assertions.h"
81 #include "mozilla/CheckedInt.h"
82 #include "mozilla/DebugOnly.h"
83 #include "mozilla/dom/CanvasGradient.h"
84 #include "mozilla/dom/CanvasPattern.h"
85 #include "mozilla/dom/DOMMatrix.h"
86 #include "mozilla/dom/ImageBitmap.h"
87 #include "mozilla/dom/ImageData.h"
88 #include "mozilla/dom/PBrowserParent.h"
89 #include "mozilla/dom/ToJSValue.h"
90 #include "mozilla/dom/TypedArray.h"
91 #include "mozilla/EndianUtils.h"
92 #include "mozilla/FilterInstance.h"
93 #include "mozilla/gfx/2D.h"
94 #include "mozilla/gfx/Tools.h"
95 #include "mozilla/gfx/PathHelpers.h"
96 #include "mozilla/gfx/DataSurfaceHelpers.h"
97 #include "mozilla/gfx/PatternHelpers.h"
98 #include "mozilla/gfx/Swizzle.h"
99 #include "mozilla/layers/PersistentBufferProvider.h"
100 #include "mozilla/MathAlgorithms.h"
101 #include "mozilla/Preferences.h"
102 #include "mozilla/RestyleManager.h"
103 #include "mozilla/ServoBindings.h"
104 #include "mozilla/StaticPrefs_browser.h"
105 #include "mozilla/StaticPrefs_gfx.h"
106 #include "mozilla/Telemetry.h"
107 #include "mozilla/TimeStamp.h"
108 #include "mozilla/UniquePtr.h"
109 #include "mozilla/Unused.h"
110 #include "nsCCUncollectableMarker.h"
111 #include "nsWrapperCacheInlines.h"
112 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
113 #include "mozilla/dom/CanvasPath.h"
114 #include "mozilla/dom/HTMLImageElement.h"
115 #include "mozilla/dom/HTMLVideoElement.h"
116 #include "mozilla/dom/SVGImageElement.h"
117 #include "mozilla/dom/TextMetrics.h"
118 #include "mozilla/FloatingPoint.h"
119 #include "nsGlobalWindowInner.h"
120 #include "nsDeviceContext.h"
121 #include "nsFontMetrics.h"
122 #include "nsLayoutUtils.h"
123 #include "Units.h"
124 #include "mozilla/CycleCollectedJSRuntime.h"
125 #include "mozilla/ServoCSSParser.h"
126 #include "mozilla/ServoStyleSet.h"
127 #include "mozilla/SVGContentUtils.h"
128 #include "mozilla/layers/CanvasClient.h"
129 #include "mozilla/layers/WebRenderUserData.h"
130 #include "mozilla/layers/WebRenderCanvasRenderer.h"
131 #include "WindowRenderer.h"
132 #include "GeckoBindings.h"
134 #undef free // apparently defined by some windows header, clashing with a
135 // free() method in SkTypes.h
137 #ifdef XP_WIN
138 # include "gfxWindowsPlatform.h"
139 #endif
141 // windows.h (included by chromium code) defines this, in its infinite wisdom
142 #undef DrawText
144 using namespace mozilla;
145 using namespace mozilla::CanvasUtils;
146 using namespace mozilla::css;
147 using namespace mozilla::gfx;
148 using namespace mozilla::image;
149 using namespace mozilla::ipc;
150 using namespace mozilla::layers;
152 namespace mozilla::dom {
154 // Cap sigma to avoid overly large temp surfaces.
155 const Float SIGMA_MAX = 100;
157 const size_t MAX_STYLE_STACK_SIZE = 1024;
159 /* Memory reporter stuff */
160 static Atomic<int64_t> gCanvasAzureMemoryUsed(0);
162 // Adds Save() / Restore() calls to the scope.
163 class MOZ_RAII AutoSaveRestore {
164 public:
165 explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx) : mCtx(aCtx) {
166 mCtx->Save();
168 ~AutoSaveRestore() { mCtx->Restore(); }
170 private:
171 RefPtr<CanvasRenderingContext2D> mCtx;
174 // This is KIND_OTHER because it's not always clear where in memory the pixels
175 // of a canvas are stored. Furthermore, this memory will be tracked by the
176 // underlying surface implementations. See bug 655638 for details.
177 class Canvas2dPixelsReporter final : public nsIMemoryReporter {
178 ~Canvas2dPixelsReporter() = default;
180 public:
181 NS_DECL_ISUPPORTS
183 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
184 nsISupports* aData, bool aAnonymize) override {
185 MOZ_COLLECT_REPORT("canvas-2d-pixels", KIND_OTHER, UNITS_BYTES,
186 gCanvasAzureMemoryUsed,
187 "Memory used by 2D canvases. Each canvas requires "
188 "(width * height * 4) bytes.");
190 return NS_OK;
194 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
196 class CanvasConicGradient : public CanvasGradient {
197 public:
198 CanvasConicGradient(CanvasRenderingContext2D* aContext, Float aAngle,
199 const Point& aCenter)
200 : CanvasGradient(aContext, Type::CONIC),
201 mAngle(aAngle),
202 mCenter(aCenter) {}
204 const Float mAngle;
205 const Point mCenter;
208 class CanvasRadialGradient : public CanvasGradient {
209 public:
210 CanvasRadialGradient(CanvasRenderingContext2D* aContext,
211 const Point& aBeginOrigin, Float aBeginRadius,
212 const Point& aEndOrigin, Float aEndRadius)
213 : CanvasGradient(aContext, Type::RADIAL),
214 mCenter1(aBeginOrigin),
215 mCenter2(aEndOrigin),
216 mRadius1(aBeginRadius),
217 mRadius2(aEndRadius) {}
219 Point mCenter1;
220 Point mCenter2;
221 Float mRadius1;
222 Float mRadius2;
225 class CanvasLinearGradient : public CanvasGradient {
226 public:
227 CanvasLinearGradient(CanvasRenderingContext2D* aContext, const Point& aBegin,
228 const Point& aEnd)
229 : CanvasGradient(aContext, Type::LINEAR), mBegin(aBegin), mEnd(aEnd) {}
231 protected:
232 friend struct CanvasBidiProcessor;
233 friend class CanvasGeneralPattern;
235 // Beginning of linear gradient.
236 Point mBegin;
237 // End of linear gradient.
238 Point mEnd;
241 bool CanvasRenderingContext2D::PatternIsOpaque(
242 CanvasRenderingContext2D::Style aStyle, bool* aIsColor) const {
243 const ContextState& state = CurrentState();
244 bool opaque = false;
245 bool color = false;
246 if (state.globalAlpha >= 1.0) {
247 if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) {
248 opaque = IsOpaque(state.patternStyles[aStyle]->mSurface->GetFormat());
249 } else if (!state.gradientStyles[aStyle]) {
250 // TODO: for gradient patterns we could check that all stops are opaque
251 // colors.
252 // it's a color pattern.
253 opaque = sRGBColor::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
254 color = true;
257 if (aIsColor) {
258 *aIsColor = color;
260 return opaque;
263 // This class is named 'GeneralCanvasPattern' instead of just
264 // 'GeneralPattern' to keep Windows PGO builds from confusing the
265 // GeneralPattern class in gfxContext.cpp with this one.
266 class CanvasGeneralPattern {
267 public:
268 using Style = CanvasRenderingContext2D::Style;
269 using ContextState = CanvasRenderingContext2D::ContextState;
271 Pattern& ForStyle(CanvasRenderingContext2D* aCtx, Style aStyle,
272 DrawTarget* aRT) {
273 // This should only be called once or the mPattern destructor will
274 // not be executed.
275 NS_ASSERTION(
276 !mPattern.GetPattern(),
277 "ForStyle() should only be called once on CanvasGeneralPattern!");
279 const ContextState& state = aCtx->CurrentState();
281 if (state.StyleIsColor(aStyle)) {
282 mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle]));
283 } else if (state.gradientStyles[aStyle] &&
284 state.gradientStyles[aStyle]->GetType() ==
285 CanvasGradient::Type::LINEAR) {
286 auto gradient = static_cast<CanvasLinearGradient*>(
287 state.gradientStyles[aStyle].get());
289 mPattern.InitLinearGradientPattern(
290 gradient->mBegin, gradient->mEnd,
291 gradient->GetGradientStopsForTarget(aRT));
292 } else if (state.gradientStyles[aStyle] &&
293 state.gradientStyles[aStyle]->GetType() ==
294 CanvasGradient::Type::RADIAL) {
295 auto gradient = static_cast<CanvasRadialGradient*>(
296 state.gradientStyles[aStyle].get());
298 mPattern.InitRadialGradientPattern(
299 gradient->mCenter1, gradient->mCenter2, gradient->mRadius1,
300 gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT));
301 } else if (state.gradientStyles[aStyle] &&
302 state.gradientStyles[aStyle]->GetType() ==
303 CanvasGradient::Type::CONIC) {
304 auto gradient =
305 static_cast<CanvasConicGradient*>(state.gradientStyles[aStyle].get());
307 mPattern.InitConicGradientPattern(
308 gradient->mCenter, gradient->mAngle, 0, 1,
309 gradient->GetGradientStopsForTarget(aRT));
310 } else if (state.patternStyles[aStyle]) {
311 aCtx->DoSecurityCheck(state.patternStyles[aStyle]->mPrincipal,
312 state.patternStyles[aStyle]->mForceWriteOnly,
313 state.patternStyles[aStyle]->mCORSUsed);
315 ExtendMode mode;
316 if (state.patternStyles[aStyle]->mRepeat ==
317 CanvasPattern::RepeatMode::NOREPEAT) {
318 mode = ExtendMode::CLAMP;
319 } else {
320 mode = ExtendMode::REPEAT;
323 SamplingFilter samplingFilter;
324 if (state.imageSmoothingEnabled) {
325 samplingFilter = SamplingFilter::GOOD;
326 } else {
327 samplingFilter = SamplingFilter::POINT;
330 mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
331 state.patternStyles[aStyle]->mTransform,
332 samplingFilter);
335 return *mPattern.GetPattern();
338 GeneralPattern mPattern;
341 /* This is an RAII based class that can be used as a drawtarget for
342 * operations that need to have a filter applied to their results.
343 * All coordinates passed to the constructor are in device space.
345 class AdjustedTargetForFilter {
346 public:
347 using ContextState = CanvasRenderingContext2D::ContextState;
349 AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx,
350 DrawTarget* aFinalTarget,
351 const gfx::IntPoint& aFilterSpaceToTargetOffset,
352 const gfx::IntRect& aPreFilterBounds,
353 const gfx::IntRect& aPostFilterBounds,
354 gfx::CompositionOp aCompositionOp)
355 : mFinalTarget(aFinalTarget),
356 mCtx(aCtx),
357 mPostFilterBounds(aPostFilterBounds),
358 mOffset(aFilterSpaceToTargetOffset),
359 mCompositionOp(aCompositionOp) {
360 nsIntRegion sourceGraphicNeededRegion;
361 nsIntRegion fillPaintNeededRegion;
362 nsIntRegion strokePaintNeededRegion;
364 FilterSupport::ComputeSourceNeededRegions(
365 aCtx->CurrentState().filter, mPostFilterBounds,
366 sourceGraphicNeededRegion, fillPaintNeededRegion,
367 strokePaintNeededRegion);
369 mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds();
370 mFillPaintRect = fillPaintNeededRegion.GetBounds();
371 mStrokePaintRect = strokePaintNeededRegion.GetBounds();
373 mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds);
375 if (mSourceGraphicRect.IsEmpty()) {
376 // The filter might not make any use of the source graphic. We need to
377 // create a DrawTarget that we can return from DT() anyway, so we'll
378 // just use a 1x1-sized one.
379 mSourceGraphicRect.SizeTo(1, 1);
382 if (!mFinalTarget->CanCreateSimilarDrawTarget(mSourceGraphicRect.Size(),
383 SurfaceFormat::B8G8R8A8)) {
384 mTarget = mFinalTarget;
385 mCtx = nullptr;
386 mFinalTarget = nullptr;
387 return;
390 mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(),
391 SurfaceFormat::B8G8R8A8);
393 if (mTarget) {
394 // See bug 1524554.
395 mTarget->ClearRect(gfx::Rect());
398 if (!mTarget || !mTarget->IsValid()) {
399 // XXX - Deal with the situation where our temp size is too big to
400 // fit in a texture (bug 1066622).
401 mTarget = mFinalTarget;
402 mCtx = nullptr;
403 mFinalTarget = nullptr;
404 return;
407 mTarget->SetTransform(mFinalTarget->GetTransform().PostTranslate(
408 -mSourceGraphicRect.TopLeft() + mOffset));
411 // Return a SourceSurface that contains the FillPaint or StrokePaint source.
412 already_AddRefed<SourceSurface> DoSourcePaint(
413 gfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle) {
414 if (aRect.IsEmpty()) {
415 return nullptr;
418 RefPtr<DrawTarget> dt = mFinalTarget->CreateSimilarDrawTarget(
419 aRect.Size(), SurfaceFormat::B8G8R8A8);
421 if (dt) {
422 // See bug 1524554.
423 dt->ClearRect(gfx::Rect());
426 if (!dt || !dt->IsValid()) {
427 aRect.SetEmpty();
428 return nullptr;
431 Matrix transform =
432 mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset);
434 dt->SetTransform(transform);
436 if (transform.Invert()) {
437 gfx::Rect dtBounds(0, 0, aRect.width, aRect.height);
438 gfx::Rect fillRect = transform.TransformBounds(dtBounds);
439 dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt));
441 return dt->Snapshot();
444 ~AdjustedTargetForFilter() {
445 if (!mCtx) {
446 return;
449 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
451 RefPtr<SourceSurface> fillPaint =
452 DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL);
453 RefPtr<SourceSurface> strokePaint = DoSourcePaint(
454 mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE);
456 AutoRestoreTransform autoRestoreTransform(mFinalTarget);
457 mFinalTarget->SetTransform(Matrix());
459 MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty());
460 gfx::FilterSupport::RenderFilterDescription(
461 mFinalTarget, mCtx->CurrentState().filter, gfx::Rect(mPostFilterBounds),
462 snapshot, mSourceGraphicRect, fillPaint, mFillPaintRect, strokePaint,
463 mStrokePaintRect, mCtx->CurrentState().filterAdditionalImages,
464 mPostFilterBounds.TopLeft() - mOffset,
465 DrawOptions(1.0f, mCompositionOp));
467 const gfx::FilterDescription& filter = mCtx->CurrentState().filter;
468 MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty());
469 if (filter.mPrimitives.LastElement().IsTainted()) {
470 if (mCtx->mCanvasElement) {
471 mCtx->mCanvasElement->SetWriteOnly();
472 } else if (mCtx->mOffscreenCanvas) {
473 mCtx->mOffscreenCanvas->SetWriteOnly();
478 DrawTarget* DT() { return mTarget; }
480 private:
481 RefPtr<DrawTarget> mTarget;
482 RefPtr<DrawTarget> mFinalTarget;
483 CanvasRenderingContext2D* mCtx;
484 gfx::IntRect mSourceGraphicRect;
485 gfx::IntRect mFillPaintRect;
486 gfx::IntRect mStrokePaintRect;
487 gfx::IntRect mPostFilterBounds;
488 gfx::IntPoint mOffset;
489 gfx::CompositionOp mCompositionOp;
492 /* This is an RAII based class that can be used as a drawtarget for
493 * operations that need to have a shadow applied to their results.
494 * All coordinates passed to the constructor are in device space.
496 class AdjustedTargetForShadow {
497 public:
498 using ContextState = CanvasRenderingContext2D::ContextState;
500 AdjustedTargetForShadow(CanvasRenderingContext2D* aCtx,
501 DrawTarget* aFinalTarget, const gfx::Rect& aBounds,
502 gfx::CompositionOp aCompositionOp)
503 : mFinalTarget(aFinalTarget), mCtx(aCtx), mCompositionOp(aCompositionOp) {
504 const ContextState& state = mCtx->CurrentState();
505 mSigma = state.ShadowBlurSigma();
507 // We actually include the bounds of the shadow blur, this makes it
508 // easier to execute the actual blur on hardware, and shouldn't affect
509 // the amount of pixels that need to be touched.
510 gfx::Rect bounds = aBounds;
511 int32_t blurRadius = state.ShadowBlurRadius();
512 bounds.Inflate(blurRadius);
513 bounds.RoundOut();
514 if (!bounds.ToIntRect(&mTempRect) ||
515 !mFinalTarget->CanCreateSimilarDrawTarget(mTempRect.Size(),
516 SurfaceFormat::B8G8R8A8)) {
517 mTarget = mFinalTarget;
518 mCtx = nullptr;
519 mFinalTarget = nullptr;
520 return;
523 mTarget = mFinalTarget->CreateShadowDrawTarget(
524 mTempRect.Size(), SurfaceFormat::B8G8R8A8, mSigma);
526 if (mTarget) {
527 // See bug 1524554.
528 mTarget->ClearRect(gfx::Rect());
531 if (!mTarget || !mTarget->IsValid()) {
532 // XXX - Deal with the situation where our temp size is too big to
533 // fit in a texture (bug 1066622).
534 mTarget = mFinalTarget;
535 mCtx = nullptr;
536 mFinalTarget = nullptr;
537 } else {
538 mTarget->SetTransform(
539 mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft()));
543 ~AdjustedTargetForShadow() {
544 if (!mCtx) {
545 return;
548 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
550 mFinalTarget->DrawSurfaceWithShadow(
551 snapshot, mTempRect.TopLeft(),
552 ShadowOptions(ToDeviceColor(mCtx->CurrentState().shadowColor),
553 mCtx->CurrentState().shadowOffset, mSigma),
554 mCompositionOp);
557 DrawTarget* DT() { return mTarget; }
559 gfx::IntPoint OffsetToFinalDT() { return mTempRect.TopLeft(); }
561 private:
562 RefPtr<DrawTarget> mTarget;
563 RefPtr<DrawTarget> mFinalTarget;
564 CanvasRenderingContext2D* mCtx;
565 Float mSigma;
566 gfx::IntRect mTempRect;
567 gfx::CompositionOp mCompositionOp;
571 * This is an RAII based class that can be used as a drawtarget for
572 * operations that need a shadow or a filter drawn. It will automatically
573 * provide a temporary target when needed, and if so blend it back with a
574 * shadow, filter, or both.
575 * If both a shadow and a filter are needed, the filter is applied first,
576 * and the shadow is applied to the filtered results.
578 * aBounds specifies the bounds of the drawing operation that will be
579 * drawn to the target, it is given in device space! If this is nullptr the
580 * drawing operation will be assumed to cover the whole canvas.
582 class AdjustedTarget {
583 public:
584 using ContextState = CanvasRenderingContext2D::ContextState;
586 explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
587 const gfx::Rect* aBounds = nullptr,
588 bool aAllowOptimization = false)
589 : mCtx(aCtx),
590 mOptimizeShadow(false),
591 mUsedOperation(aCtx->CurrentState().op) {
592 // All rects in this function are in the device space of ctx->mTarget.
594 // In order to keep our temporary surfaces as small as possible, we first
595 // calculate what their maximum required bounds would need to be if we
596 // were to fill the whole canvas. Everything outside those bounds we don't
597 // need to render.
598 gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight);
599 gfx::Rect maxSourceNeededBoundsForShadow =
600 MaxSourceNeededBoundsForShadow(r, aCtx);
601 gfx::Rect maxSourceNeededBoundsForFilter =
602 MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx);
603 if (!aCtx->IsTargetValid()) {
604 return;
607 gfx::Rect bounds = maxSourceNeededBoundsForFilter;
608 if (aBounds) {
609 bounds = bounds.Intersect(*aBounds);
611 gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
612 if (!aCtx->IsTargetValid() || !boundsAfterFilter.IsFinite()) {
613 return;
616 gfx::IntPoint offsetToFinalDT;
618 // First set up the shadow draw target, because the shadow goes outside.
619 // It applies to the post-filter results, if both a filter and a shadow
620 // are used.
621 const bool applyFilter = aCtx->NeedToApplyFilter();
622 if (aCtx->NeedToDrawShadow()) {
623 if (aAllowOptimization && !applyFilter) {
624 // If only drawing a shadow and no filter, then avoid buffering to an
625 // intermediate target while drawing the shadow directly to the final
626 // target. When doing so, we want to use the actual composition op
627 // instead of OP_OVER.
628 mTarget = aCtx->mTarget;
629 if (mTarget && mTarget->IsValid()) {
630 mOptimizeShadow = true;
631 return;
634 mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
635 aCtx, aCtx->mTarget, boundsAfterFilter, mUsedOperation);
636 mTarget = mShadowTarget->DT();
637 offsetToFinalDT = mShadowTarget->OffsetToFinalDT();
639 // If we also have a filter, the filter needs to be drawn with OP_OVER
640 // because shadow drawing already applies op on the result.
641 mUsedOperation = CompositionOp::OP_OVER;
644 // Now set up the filter draw target.
645 if (!aCtx->IsTargetValid()) {
646 return;
648 if (applyFilter) {
649 bounds.RoundOut();
651 if (!mTarget) {
652 mTarget = aCtx->mTarget;
654 gfx::IntRect intBounds;
655 if (!bounds.ToIntRect(&intBounds)) {
656 return;
658 mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
659 aCtx, mTarget, offsetToFinalDT, intBounds,
660 gfx::RoundedToInt(boundsAfterFilter), mUsedOperation);
661 mTarget = mFilterTarget->DT();
662 mUsedOperation = CompositionOp::OP_OVER;
664 if (!mTarget) {
665 mTarget = aCtx->mTarget;
669 ~AdjustedTarget() {
670 // The order in which the targets are finalized is important.
671 // Filters are inside, any shadow applies to the post-filter results.
672 mFilterTarget.reset();
673 mShadowTarget.reset();
676 operator DrawTarget*() { return mTarget; }
678 DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mTarget; }
680 CompositionOp UsedOperation() const { return mUsedOperation; }
682 ShadowOptions ShadowParams() const {
683 const ContextState& state = mCtx->CurrentState();
684 return ShadowOptions(ToDeviceColor(state.shadowColor), state.shadowOffset,
685 state.ShadowBlurSigma());
688 void Fill(const Path* aPath, const Pattern& aPattern,
689 const DrawOptions& aOptions) {
690 if (mOptimizeShadow) {
691 mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions);
693 mTarget->Fill(aPath, aPattern, aOptions);
696 void FillRect(const Rect& aRect, const Pattern& aPattern,
697 const DrawOptions& aOptions) {
698 if (mOptimizeShadow) {
699 RefPtr<Path> path = MakePathForRect(*mTarget, aRect);
700 mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions);
702 mTarget->FillRect(aRect, aPattern, aOptions);
705 void Stroke(const Path* aPath, const Pattern& aPattern,
706 const StrokeOptions& aStrokeOptions,
707 const DrawOptions& aOptions) {
708 if (mOptimizeShadow) {
709 mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions,
710 &aStrokeOptions);
712 mTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
715 void StrokeRect(const Rect& aRect, const Pattern& aPattern,
716 const StrokeOptions& aStrokeOptions,
717 const DrawOptions& aOptions) {
718 if (mOptimizeShadow) {
719 RefPtr<Path> path = MakePathForRect(*mTarget, aRect);
720 mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions,
721 &aStrokeOptions);
723 mTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
726 void StrokeLine(const Point& aStart, const Point& aEnd,
727 const Pattern& aPattern, const StrokeOptions& aStrokeOptions,
728 const DrawOptions& aOptions) {
729 if (mOptimizeShadow) {
730 RefPtr<PathBuilder> builder = mTarget->CreatePathBuilder();
731 builder->MoveTo(aStart);
732 builder->LineTo(aEnd);
733 RefPtr<Path> path = builder->Finish();
734 mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions,
735 &aStrokeOptions);
737 mTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
740 void DrawSurface(SourceSurface* aSurface, const Rect& aDest,
741 const Rect& aSource, const DrawSurfaceOptions& aSurfOptions,
742 const DrawOptions& aOptions) {
743 if (mOptimizeShadow) {
744 RefPtr<Path> path = MakePathForRect(*mTarget, aSource);
745 ShadowOptions shadowParams(ShadowParams());
746 SurfacePattern pattern(aSurface, ExtendMode::CLAMP, Matrix(),
747 shadowParams.BlurRadius() > 1
748 ? SamplingFilter::POINT
749 : aSurfOptions.mSamplingFilter);
750 Matrix matrix = Matrix::Scaling(aDest.width / aSource.width,
751 aDest.height / aSource.height);
752 matrix.PreTranslate(-aSource.x, -aSource.y);
753 matrix.PostTranslate(aDest.x, aDest.y);
754 AutoRestoreTransform autoRestoreTransform(mTarget);
755 mTarget->ConcatTransform(matrix);
756 mTarget->DrawShadow(path, pattern, shadowParams, aOptions);
758 mTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions);
761 private:
762 gfx::Rect MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds,
763 CanvasRenderingContext2D* aCtx) {
764 const bool applyFilter = aCtx->NeedToApplyFilter();
765 if (!aCtx->IsTargetValid()) {
766 return aDestBounds;
768 if (!applyFilter) {
769 return aDestBounds;
772 nsIntRegion sourceGraphicNeededRegion;
773 nsIntRegion fillPaintNeededRegion;
774 nsIntRegion strokePaintNeededRegion;
776 FilterSupport::ComputeSourceNeededRegions(
777 aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds),
778 sourceGraphicNeededRegion, fillPaintNeededRegion,
779 strokePaintNeededRegion);
781 return gfx::Rect(sourceGraphicNeededRegion.GetBounds());
784 gfx::Rect MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds,
785 CanvasRenderingContext2D* aCtx) {
786 if (!aCtx->NeedToDrawShadow()) {
787 return aDestBounds;
790 const ContextState& state = aCtx->CurrentState();
791 gfx::Rect sourceBounds = aDestBounds - state.shadowOffset;
792 sourceBounds.Inflate(state.ShadowBlurRadius());
794 // Union the shadow source with the original rect because we're going to
795 // draw both.
796 return sourceBounds.Union(aDestBounds);
799 gfx::Rect BoundsAfterFilter(const gfx::Rect& aBounds,
800 CanvasRenderingContext2D* aCtx) {
801 const bool applyFilter = aCtx->NeedToApplyFilter();
802 if (!aCtx->IsTargetValid()) {
803 return aBounds;
805 if (!applyFilter) {
806 return aBounds;
809 gfx::Rect bounds(aBounds);
810 bounds.RoundOut();
812 gfx::IntRect intBounds;
813 if (!bounds.ToIntRect(&intBounds)) {
814 return gfx::Rect();
817 nsIntRegion extents = gfx::FilterSupport::ComputePostFilterExtents(
818 aCtx->CurrentState().filter, intBounds);
819 return gfx::Rect(extents.GetBounds());
822 CanvasRenderingContext2D* mCtx;
823 bool mOptimizeShadow;
824 CompositionOp mUsedOperation;
825 RefPtr<DrawTarget> mTarget;
826 UniquePtr<AdjustedTargetForShadow> mShadowTarget;
827 UniquePtr<AdjustedTargetForFilter> mFilterTarget;
830 void CanvasPattern::SetTransform(const DOMMatrix2DInit& aInit,
831 ErrorResult& aError) {
832 RefPtr<DOMMatrixReadOnly> matrix =
833 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
834 if (aError.Failed()) {
835 return;
837 const auto* matrix2D = matrix->GetInternal2D();
838 if (!matrix2D->IsFinite()) {
839 return;
841 mTransform = Matrix(*matrix2D);
844 void CanvasGradient::AddColorStop(float aOffset, const nsACString& aColorstr,
845 ErrorResult& aRv) {
846 if (aOffset < 0.0 || aOffset > 1.0) {
847 return aRv.ThrowIndexSizeError("Offset out of 0-1.0 range");
850 if (!mContext) {
851 return aRv.ThrowSyntaxError("No canvas context");
854 auto color = mContext->ParseColor(
855 aColorstr, CanvasRenderingContext2D::ResolveCurrentColor::No);
856 if (!color) {
857 return aRv.ThrowSyntaxError("Invalid color");
860 GradientStop newStop;
862 newStop.offset = aOffset;
863 newStop.color = ToDeviceColor(*color);
865 mRawStops.AppendElement(newStop);
868 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
870 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
872 class CanvasShutdownObserver final : public nsIObserver {
873 public:
874 explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
875 : mCanvas(aCanvas) {}
877 void OnShutdown() {
878 if (!mCanvas) {
879 return;
882 mCanvas = nullptr;
883 nsContentUtils::UnregisterShutdownObserver(this);
886 NS_DECL_ISUPPORTS
887 NS_DECL_NSIOBSERVER
888 private:
889 ~CanvasShutdownObserver() = default;
891 CanvasRenderingContext2D* mCanvas;
894 NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
896 NS_IMETHODIMP
897 CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
898 const char16_t* aData) {
899 if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
900 mCanvas->OnShutdown();
901 OnShutdown();
904 return NS_OK;
907 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
908 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
910 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CanvasRenderingContext2D)
912 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
913 // Make sure we remove ourselves from the list of demotable contexts (raw
914 // pointers), since we're logically destructed at this point.
915 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
916 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOffscreenCanvas)
917 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
918 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
919 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
920 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
921 ImplCycleCollectionUnlink(
922 tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
923 ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
924 auto autoSVGFiltersObserver =
925 tmp->mStyleStack[i].autoSVGFiltersObserver.get();
926 if (autoSVGFiltersObserver) {
927 // XXXjwatt: I don't think this call achieves anything. See the comment
928 // that documents this function.
929 SVGObserverUtils::DetachFromCanvasContext(autoSVGFiltersObserver);
931 ImplCycleCollectionUnlink(tmp->mStyleStack[i].autoSVGFiltersObserver);
933 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
934 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
935 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
937 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
938 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
939 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOffscreenCanvas)
940 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
941 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
942 ImplCycleCollectionTraverse(
943 cb, tmp->mStyleStack[i].patternStyles[Style::STROKE],
944 "Stroke CanvasPattern");
945 ImplCycleCollectionTraverse(cb,
946 tmp->mStyleStack[i].patternStyles[Style::FILL],
947 "Fill CanvasPattern");
948 ImplCycleCollectionTraverse(
949 cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE],
950 "Stroke CanvasGradient");
951 ImplCycleCollectionTraverse(cb,
952 tmp->mStyleStack[i].gradientStyles[Style::FILL],
953 "Fill CanvasGradient");
954 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].autoSVGFiltersObserver,
955 "RAII SVG Filters Observer");
957 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
959 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D)
960 if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) {
961 dom::Element* canvasElement = tmp->mCanvasElement;
962 if (canvasElement) {
963 if (canvasElement->IsPurple()) {
964 canvasElement->RemovePurple();
966 dom::Element::MarkNodeChildren(canvasElement);
968 return true;
970 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
972 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D)
973 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
974 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
976 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D)
977 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
978 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
980 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D)
981 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
982 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
983 NS_INTERFACE_MAP_ENTRY(nsISupports)
984 NS_INTERFACE_MAP_END
986 CanvasRenderingContext2D::ContextState::ContextState() = default;
988 CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther)
989 : fontGroup(aOther.fontGroup),
990 fontLanguage(aOther.fontLanguage),
991 fontFont(aOther.fontFont),
992 gradientStyles(aOther.gradientStyles),
993 patternStyles(aOther.patternStyles),
994 colorStyles(aOther.colorStyles),
995 font(aOther.font),
996 textAlign(aOther.textAlign),
997 textBaseline(aOther.textBaseline),
998 textDirection(aOther.textDirection),
999 fontKerning(aOther.fontKerning),
1000 fontStretch(aOther.fontStretch),
1001 fontVariantCaps(aOther.fontVariantCaps),
1002 textRendering(aOther.textRendering),
1003 letterSpacing(aOther.letterSpacing),
1004 wordSpacing(aOther.wordSpacing),
1005 letterSpacingStr(aOther.letterSpacingStr),
1006 wordSpacingStr(aOther.wordSpacingStr),
1007 shadowColor(aOther.shadowColor),
1008 transform(aOther.transform),
1009 shadowOffset(aOther.shadowOffset),
1010 lineWidth(aOther.lineWidth),
1011 miterLimit(aOther.miterLimit),
1012 globalAlpha(aOther.globalAlpha),
1013 shadowBlur(aOther.shadowBlur),
1014 dash(aOther.dash.Clone()),
1015 dashOffset(aOther.dashOffset),
1016 op(aOther.op),
1017 fillRule(aOther.fillRule),
1018 lineCap(aOther.lineCap),
1019 lineJoin(aOther.lineJoin),
1020 filterString(aOther.filterString),
1021 filterChain(aOther.filterChain),
1022 autoSVGFiltersObserver(aOther.autoSVGFiltersObserver),
1023 filter(aOther.filter),
1024 filterAdditionalImages(aOther.filterAdditionalImages.Clone()),
1025 filterSourceGraphicTainted(aOther.filterSourceGraphicTainted),
1026 imageSmoothingEnabled(aOther.imageSmoothingEnabled),
1027 fontExplicitLanguage(aOther.fontExplicitLanguage) {}
1029 CanvasRenderingContext2D::ContextState::~ContextState() = default;
1031 void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle,
1032 nscolor aColor) {
1033 colorStyles[aWhichStyle] = aColor;
1034 gradientStyles[aWhichStyle] = nullptr;
1035 patternStyles[aWhichStyle] = nullptr;
1038 void CanvasRenderingContext2D::ContextState::SetPatternStyle(
1039 Style aWhichStyle, CanvasPattern* aPat) {
1040 gradientStyles[aWhichStyle] = nullptr;
1041 patternStyles[aWhichStyle] = aPat;
1044 void CanvasRenderingContext2D::ContextState::SetGradientStyle(
1045 Style aWhichStyle, CanvasGradient* aGrad) {
1046 gradientStyles[aWhichStyle] = aGrad;
1047 patternStyles[aWhichStyle] = nullptr;
1051 ** CanvasRenderingContext2D impl
1054 // Initialize our static variables.
1055 MOZ_THREAD_LOCAL(uintptr_t) CanvasRenderingContext2D::sNumLivingContexts;
1056 MOZ_THREAD_LOCAL(DrawTarget*) CanvasRenderingContext2D::sErrorTarget;
1058 // Helpers to map Canvas2D WebIDL enum values to gfx constants for rendering.
1059 static JoinStyle CanvasToGfx(CanvasLineJoin aJoin) {
1060 switch (aJoin) {
1061 case CanvasLineJoin::Round:
1062 return JoinStyle::ROUND;
1063 case CanvasLineJoin::Bevel:
1064 return JoinStyle::BEVEL;
1065 case CanvasLineJoin::Miter:
1066 return JoinStyle::MITER_OR_BEVEL;
1067 default:
1068 MOZ_CRASH("unknown lineJoin!");
1072 static CapStyle CanvasToGfx(CanvasLineCap aCap) {
1073 switch (aCap) {
1074 case CanvasLineCap::Butt:
1075 return CapStyle::BUTT;
1076 case CanvasLineCap::Round:
1077 return CapStyle::ROUND;
1078 case CanvasLineCap::Square:
1079 return CapStyle::SQUARE;
1080 default:
1081 MOZ_CRASH("unknown lineCap!");
1085 static uint8_t CanvasToGfx(CanvasFontKerning aKerning) {
1086 switch (aKerning) {
1087 case CanvasFontKerning::Auto:
1088 return NS_FONT_KERNING_AUTO;
1089 case CanvasFontKerning::Normal:
1090 return NS_FONT_KERNING_NORMAL;
1091 case CanvasFontKerning::None:
1092 return NS_FONT_KERNING_NONE;
1093 default:
1094 MOZ_CRASH("unknown kerning!");
1098 CanvasRenderingContext2D::CanvasRenderingContext2D(
1099 layers::LayersBackend aCompositorBackend)
1100 : // these are the default values from the Canvas spec
1101 mWidth(0),
1102 mHeight(0),
1103 mZero(false),
1104 mOpaqueAttrValue(false),
1105 mContextAttributesHasAlpha(true),
1106 mOpaque(false),
1107 mResetLayer(true),
1108 mIPC(false),
1109 mHasPendingStableStateCallback(false),
1110 mIsEntireFrameInvalid(false),
1111 mPredictManyRedrawCalls(false),
1112 mFrameCaptureState(FrameCaptureState::CLEAN,
1113 "CanvasRenderingContext2D::mFrameCaptureState"),
1114 mInvalidateCount(0),
1115 mWriteOnly(false) {
1116 sNumLivingContexts.infallibleInit();
1117 sErrorTarget.infallibleInit();
1118 sNumLivingContexts.set(sNumLivingContexts.get() + 1);
1121 CanvasRenderingContext2D::~CanvasRenderingContext2D() {
1122 RemovePostRefreshObserver();
1123 RemoveShutdownObserver();
1124 ResetBitmap();
1126 sNumLivingContexts.set(sNumLivingContexts.get() - 1);
1127 if (sNumLivingContexts.get() == 0 && sErrorTarget.get()) {
1128 RefPtr<DrawTarget> target = dont_AddRef(sErrorTarget.get());
1129 sErrorTarget.set(nullptr);
1133 void CanvasRenderingContext2D::Initialize() { AddShutdownObserver(); }
1135 JSObject* CanvasRenderingContext2D::WrapObject(
1136 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
1137 return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto);
1140 void CanvasRenderingContext2D::GetContextAttributes(
1141 CanvasRenderingContext2DSettings& aSettings) const {
1142 aSettings = CanvasRenderingContext2DSettings();
1144 aSettings.mAlpha = mContextAttributesHasAlpha;
1145 aSettings.mWillReadFrequently = mWillReadFrequently;
1147 // We don't support the 'desynchronized' and 'colorSpace' attributes, so
1148 // those just keep their default values.
1151 CanvasRenderingContext2D::ColorStyleCacheEntry
1152 CanvasRenderingContext2D::ParseColorSlow(const nsACString& aString) {
1153 ColorStyleCacheEntry result{nsCString(aString)};
1154 Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
1155 css::Loader* loader = document ? document->CSSLoader() : nullptr;
1157 PresShell* presShell = GetPresShell();
1158 ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
1159 bool wasCurrentColor = false;
1160 nscolor color;
1161 if (ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, &color,
1162 &wasCurrentColor, loader)) {
1163 result.mWasCurrentColor = wasCurrentColor;
1164 result.mColor.emplace(color);
1167 return result;
1170 Maybe<nscolor> CanvasRenderingContext2D::ParseColor(
1171 const nsACString& aString, ResolveCurrentColor aResolveCurrentColor) {
1172 auto entry = mColorStyleCache.Lookup(aString);
1173 if (!entry) {
1174 entry.Set(ParseColorSlow(aString));
1177 const auto& data = entry.Data();
1178 if (data.mWasCurrentColor && mCanvasElement &&
1179 aResolveCurrentColor == ResolveCurrentColor::Yes) {
1180 // If it was currentColor, get the value of the color property, flushing
1181 // style if necessary.
1182 RefPtr<const ComputedStyle> canvasStyle =
1183 nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
1184 if (canvasStyle) {
1185 return Some(canvasStyle->StyleText()->mColor.ToColor());
1188 return data.mColor;
1191 void CanvasRenderingContext2D::ResetBitmap(bool aFreeBuffer) {
1192 if (mCanvasElement) {
1193 mCanvasElement->InvalidateCanvas();
1196 // only do this for non-docshell created contexts,
1197 // since those are the ones that we created a surface for
1198 if (mTarget && IsTargetValid() && !mDocShell) {
1199 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
1202 bool forceReset = true;
1203 ReturnTarget(forceReset);
1204 mTarget = nullptr;
1205 if (aFreeBuffer) {
1206 mBufferProvider = nullptr;
1207 } else if (mBufferProvider) {
1208 // Try to keep the buffer around. However, we still need to clear the
1209 // contents as if it was recreated before next use.
1210 mBufferNeedsClear = true;
1213 // Since the target changes the backing texture will change, and this will
1214 // no longer be valid.
1215 mIsEntireFrameInvalid = false;
1216 mPredictManyRedrawCalls = false;
1217 mFrameCaptureState = FrameCaptureState::CLEAN;
1220 void CanvasRenderingContext2D::OnShutdown() {
1221 mShutdownObserver = nullptr;
1223 RefPtr<PersistentBufferProvider> provider = mBufferProvider;
1225 ResetBitmap();
1227 if (provider) {
1228 provider->OnShutdown();
1232 void CanvasRenderingContext2D::AddShutdownObserver() {
1233 MOZ_ASSERT(!mShutdownObserver);
1234 MOZ_ASSERT(NS_IsMainThread());
1236 mShutdownObserver = new CanvasShutdownObserver(this);
1237 nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
1240 void CanvasRenderingContext2D::RemoveShutdownObserver() {
1241 if (mShutdownObserver) {
1242 mShutdownObserver->OnShutdown();
1243 mShutdownObserver = nullptr;
1247 void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
1248 Style aWhichStyle) {
1249 MOZ_ASSERT(!aStr.IsVoid());
1251 Maybe<nscolor> color = ParseColor(aStr);
1252 if (!color) {
1253 return;
1256 CurrentState().SetColorStyle(aWhichStyle, *color);
1259 void CanvasRenderingContext2D::GetStyleAsUnion(
1260 OwningUTF8StringOrCanvasGradientOrCanvasPattern& aValue,
1261 Style aWhichStyle) {
1262 const ContextState& state = CurrentState();
1263 if (state.patternStyles[aWhichStyle]) {
1264 aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
1265 } else if (state.gradientStyles[aWhichStyle]) {
1266 aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
1267 } else {
1268 StyleColorToString(state.colorStyles[aWhichStyle],
1269 aValue.SetAsUTF8String());
1273 // static
1274 void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor,
1275 nsACString& aStr) {
1276 aStr.Truncate();
1277 // We can't reuse the normal CSS color stringification code,
1278 // because the spec calls for a different algorithm for canvas.
1279 if (NS_GET_A(aColor) == 255) {
1280 aStr.AppendPrintf("#%02x%02x%02x", NS_GET_R(aColor), NS_GET_G(aColor),
1281 NS_GET_B(aColor));
1282 } else {
1283 aStr.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor), NS_GET_G(aColor),
1284 NS_GET_B(aColor));
1285 aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
1286 aStr.Append(')');
1290 nsresult CanvasRenderingContext2D::Redraw() {
1291 mFrameCaptureState = FrameCaptureState::DIRTY;
1293 if (mIsEntireFrameInvalid) {
1294 return NS_OK;
1297 mIsEntireFrameInvalid = true;
1299 if (mCanvasElement) {
1300 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1301 mCanvasElement->InvalidateCanvasContent(nullptr);
1302 } else if (mOffscreenCanvas) {
1303 mOffscreenCanvas->QueueCommitToCompositor();
1304 } else {
1305 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1308 return NS_OK;
1311 void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
1312 mFrameCaptureState = FrameCaptureState::DIRTY;
1314 ++mInvalidateCount;
1316 if (mIsEntireFrameInvalid) {
1317 return;
1320 if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) {
1321 Redraw();
1322 return;
1325 if (mCanvasElement) {
1326 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
1327 mCanvasElement->InvalidateCanvasContent(&aR);
1328 } else if (mOffscreenCanvas) {
1329 mOffscreenCanvas->QueueCommitToCompositor();
1330 } else {
1331 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
1335 void CanvasRenderingContext2D::DidRefresh() {}
1337 void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) {
1338 mFrameCaptureState = FrameCaptureState::DIRTY;
1340 if (mIsEntireFrameInvalid) {
1341 ++mInvalidateCount;
1342 return;
1345 gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
1346 Redraw(newr);
1349 bool CanvasRenderingContext2D::CopyBufferProvider(
1350 PersistentBufferProvider& aOld, DrawTarget& aTarget, IntRect aCopyRect) {
1351 // Borrowing the snapshot must be done after ReturnTarget.
1352 RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot();
1354 if (!snapshot) {
1355 return false;
1358 aTarget.CopySurface(snapshot, aCopyRect, IntPoint());
1359 aOld.ReturnSnapshot(snapshot.forget());
1360 return true;
1363 void CanvasRenderingContext2D::Demote() {}
1365 void CanvasRenderingContext2D::ScheduleStableStateCallback() {
1366 if (mHasPendingStableStateCallback) {
1367 return;
1369 mHasPendingStableStateCallback = true;
1371 nsContentUtils::RunInStableState(
1372 NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", this,
1373 &CanvasRenderingContext2D::OnStableState));
1376 void CanvasRenderingContext2D::OnStableState() {
1377 if (!mHasPendingStableStateCallback) {
1378 return;
1381 ReturnTarget();
1383 mHasPendingStableStateCallback = false;
1386 void CanvasRenderingContext2D::RestoreClipsAndTransformToTarget() {
1387 // Restore clips and transform.
1388 mTarget->SetTransform(Matrix());
1390 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1391 // Cairo doesn't play well with huge clips. When given a very big clip it
1392 // will try to allocate big mask surface without taking the target
1393 // size into account which can cause OOM. See bug 1034593.
1394 // This limits the clip extents to the size of the canvas.
1395 // A fix in Cairo would probably be preferable, but requires somewhat
1396 // invasive changes.
1397 mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight));
1400 for (auto& style : mStyleStack) {
1401 for (auto& clipOrTransform : style.clipsAndTransforms) {
1402 if (clipOrTransform.IsClip()) {
1403 if (mClipsNeedConverting) {
1404 // We have possibly changed backends, so we need to convert the clips
1405 // in case they are no longer compatible with mTarget.
1406 RefPtr<PathBuilder> pathBuilder = mTarget->CreatePathBuilder();
1407 clipOrTransform.clip->StreamToSink(pathBuilder);
1408 clipOrTransform.clip = pathBuilder->Finish();
1410 mTarget->PushClip(clipOrTransform.clip);
1411 } else {
1412 mTarget->SetTransform(clipOrTransform.transform);
1417 mClipsNeedConverting = false;
1420 bool CanvasRenderingContext2D::BorrowTarget(const IntRect& aPersistedRect,
1421 bool aNeedsClear) {
1422 // We are attempting to request a DrawTarget from the current
1423 // PersistentBufferProvider. However, if the provider needs to be refreshed,
1424 // or if it is accelerated and the application has requested that we disallow
1425 // acceleration, then we skip trying to use this provider so that it will be
1426 // recreated by EnsureTarget later.
1427 if (!mBufferProvider || mBufferProvider->RequiresRefresh() ||
1428 (mBufferProvider->IsAccelerated() && GetEffectiveWillReadFrequently())) {
1429 return false;
1431 mTarget = mBufferProvider->BorrowDrawTarget(aPersistedRect);
1432 if (!mTarget || !mTarget->IsValid()) {
1433 if (mTarget) {
1434 mBufferProvider->ReturnDrawTarget(mTarget.forget());
1436 return false;
1438 if (mBufferNeedsClear) {
1439 if (mBufferProvider->PreservesDrawingState()) {
1440 // If the buffer provider preserves the clip and transform state, then
1441 // we must ensure it is cleared before reusing the target.
1442 if (!mTarget->RemoveAllClips()) {
1443 mBufferProvider->ReturnDrawTarget(mTarget.forget());
1444 return false;
1446 mTarget->SetTransform(Matrix());
1448 // If the canvas was reset, then we need to clear the target in case its
1449 // contents was somehow preserved. We only need to clear the target if
1450 // the operation doesn't fill the entire canvas.
1451 if (aNeedsClear) {
1452 mTarget->ClearRect(gfx::Rect(mTarget->GetRect()));
1455 if (!mBufferProvider->PreservesDrawingState() || mBufferNeedsClear) {
1456 RestoreClipsAndTransformToTarget();
1458 mBufferNeedsClear = false;
1459 return true;
1462 bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
1463 bool aWillClear) {
1464 if (AlreadyShutDown()) {
1465 gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
1466 SetErrorState();
1467 return false;
1470 if (mTarget) {
1471 return mTarget != sErrorTarget.get();
1474 // Check that the dimensions are sane
1475 if (mWidth > StaticPrefs::gfx_canvas_max_size() ||
1476 mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 ||
1477 mHeight < 0) {
1478 SetErrorState();
1479 return false;
1482 // If the next drawing command covers the entire canvas, we can skip copying
1483 // from the previous frame and/or clearing the canvas.
1484 gfx::Rect canvasRect(0, 0, mWidth, mHeight);
1485 bool canDiscardContent =
1486 aCoveredRect && CurrentState()
1487 .transform.TransformBounds(*aCoveredRect)
1488 .Contains(canvasRect);
1490 // If a clip is active we don't know for sure that the next drawing command
1491 // will really cover the entire canvas.
1492 for (const auto& style : mStyleStack) {
1493 if (!canDiscardContent) {
1494 break;
1496 for (const auto& clipOrTransform : style.clipsAndTransforms) {
1497 if (clipOrTransform.IsClip()) {
1498 canDiscardContent = false;
1499 break;
1504 ScheduleStableStateCallback();
1506 IntRect persistedRect = canDiscardContent || mBufferNeedsClear
1507 ? IntRect()
1508 : IntRect(0, 0, mWidth, mHeight);
1510 // Attempt to reuse the existing buffer provider.
1511 if (BorrowTarget(persistedRect, !canDiscardContent)) {
1512 return true;
1515 RefPtr<DrawTarget> newTarget;
1516 RefPtr<PersistentBufferProvider> newProvider;
1518 if (!TryAcceleratedTarget(newTarget, newProvider) &&
1519 !TrySharedTarget(newTarget, newProvider) &&
1520 !TryBasicTarget(newTarget, newProvider)) {
1521 gfxCriticalError(
1522 CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
1523 << "Failed borrow shared and basic targets.";
1525 SetErrorState();
1526 return false;
1529 MOZ_ASSERT(newTarget);
1530 MOZ_ASSERT(newProvider);
1532 bool needsClear =
1533 !canDiscardContent || (mBufferProvider && mBufferNeedsClear);
1534 if (newTarget->GetBackendType() == gfx::BackendType::SKIA &&
1535 (needsClear || !aWillClear)) {
1536 // Skia expects the unused X channel to contains 0xFF even for opaque
1537 // operations so we can't skip clearing in that case, even if we are going
1538 // to cover the entire canvas in the next drawing operation.
1539 newTarget->ClearRect(canvasRect);
1540 needsClear = false;
1543 // Try to copy data from the previous buffer provider if there is one.
1544 if (!canDiscardContent && mBufferProvider && !mBufferNeedsClear &&
1545 CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
1546 needsClear = false;
1549 if (needsClear) {
1550 newTarget->ClearRect(canvasRect);
1553 mTarget = std::move(newTarget);
1554 mBufferProvider = std::move(newProvider);
1555 mBufferNeedsClear = false;
1557 RegisterAllocation();
1558 AddZoneWaitingForGC();
1560 RestoreClipsAndTransformToTarget();
1562 // Force a full layer transaction since we didn't have a layer before
1563 // and now we might need one.
1564 if (mCanvasElement) {
1565 mCanvasElement->InvalidateCanvas();
1567 // EnsureTarget hasn't drawn anything. Preserve mFrameCaptureState.
1568 FrameCaptureState captureState = mFrameCaptureState;
1569 // Calling Redraw() tells our invalidation machinery that the entire
1570 // canvas is already invalid, which can speed up future drawing.
1571 Redraw();
1572 mFrameCaptureState = captureState;
1574 return true;
1577 void CanvasRenderingContext2D::SetInitialState() {
1578 // Set up the initial canvas defaults
1579 mPathBuilder = nullptr;
1580 mPath = nullptr;
1582 mStyleStack.Clear();
1583 ContextState* state = mStyleStack.AppendElement();
1584 state->globalAlpha = 1.0;
1586 state->colorStyles[Style::FILL] = NS_RGB(0, 0, 0);
1587 state->colorStyles[Style::STROKE] = NS_RGB(0, 0, 0);
1588 state->shadowColor = NS_RGBA(0, 0, 0, 0);
1591 void CanvasRenderingContext2D::SetErrorState() {
1592 EnsureErrorTarget();
1594 if (mTarget && mTarget != sErrorTarget.get()) {
1595 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
1598 mTarget = sErrorTarget.get();
1599 mBufferProvider = nullptr;
1601 // clear transforms, clips, etc.
1602 SetInitialState();
1605 void CanvasRenderingContext2D::RegisterAllocation() {
1606 // XXX - It would make more sense to track the allocation in
1607 // PeristentBufferProvider, rather than here.
1608 static bool registered = false;
1609 // FIXME: Disable the reporter for now, see bug 1241865
1610 if (!registered && false) {
1611 registered = true;
1612 RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
1616 void CanvasRenderingContext2D::AddZoneWaitingForGC() {
1617 JSObject* wrapper = GetWrapperPreserveColor();
1618 if (wrapper) {
1619 CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
1620 JS::GetObjectZone(wrapper));
1624 static WindowRenderer* WindowRendererFromCanvasElement(
1625 nsINode* aCanvasElement) {
1626 if (!aCanvasElement) {
1627 return nullptr;
1630 return nsContentUtils::WindowRendererForDocument(aCanvasElement->OwnerDoc());
1633 bool CanvasRenderingContext2D::TryAcceleratedTarget(
1634 RefPtr<gfx::DrawTarget>& aOutDT,
1635 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1636 if (!XRE_IsContentProcess()) {
1637 // Only allow accelerated contexts to be created in a content process to
1638 // ensure it is remoted appropriately and run on the correct parent or
1639 // GPU process threads.
1640 return false;
1642 if (mBufferProvider && mBufferProvider->IsAccelerated() &&
1643 mBufferProvider->RequiresRefresh()) {
1644 // If there is already a provider and we got here, then the provider needs
1645 // to be refreshed and we should avoid using acceleration in the future.
1646 mAllowAcceleration = false;
1648 // Don't try creating an accelerate DrawTarget if either acceleration failed
1649 // previously or if the application expects acceleration to be slow.
1650 if (!mAllowAcceleration || GetEffectiveWillReadFrequently()) {
1651 return false;
1653 aOutDT = DrawTargetWebgl::Create(GetSize(), GetSurfaceFormat());
1654 if (!aOutDT) {
1655 return false;
1658 aOutProvider = new PersistentBufferProviderAccelerated(aOutDT);
1659 return true;
1662 bool CanvasRenderingContext2D::TrySharedTarget(
1663 RefPtr<gfx::DrawTarget>& aOutDT,
1664 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1665 aOutDT = nullptr;
1666 aOutProvider = nullptr;
1668 if (!mCanvasElement) {
1669 return false;
1672 if (mBufferProvider && mBufferProvider->IsShared()) {
1673 // we are already using a shared buffer provider, we are allocating a new
1674 // one because the current one failed so let's just fall back to the basic
1675 // provider.
1676 mClipsNeedConverting = true;
1677 return false;
1680 WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
1682 if (!renderer) {
1683 return false;
1686 aOutProvider = renderer->CreatePersistentBufferProvider(
1687 GetSize(), GetSurfaceFormat(),
1688 !mAllowAcceleration || GetEffectiveWillReadFrequently());
1690 if (!aOutProvider) {
1691 return false;
1694 // We can pass an empty persisted rect since we just created the buffer
1695 // provider (nothing to restore).
1696 aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
1697 MOZ_ASSERT(aOutDT);
1699 return !!aOutDT;
1702 bool CanvasRenderingContext2D::TryBasicTarget(
1703 RefPtr<gfx::DrawTarget>& aOutDT,
1704 RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
1705 aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
1706 GetSize(), GetSurfaceFormat());
1707 if (!aOutDT) {
1708 return false;
1711 // See Bug 1524554 - this forces DT initialization.
1712 aOutDT->ClearRect(gfx::Rect());
1714 if (!aOutDT->IsValid()) {
1715 aOutDT = nullptr;
1716 return false;
1719 aOutProvider = new PersistentBufferProviderBasic(aOutDT);
1720 return true;
1723 PersistentBufferProvider* CanvasRenderingContext2D::GetBufferProvider() {
1724 if (mBufferProvider && mBufferNeedsClear) {
1725 // Force the buffer to clear before it is used.
1726 EnsureTarget();
1728 return mBufferProvider;
1731 Maybe<SurfaceDescriptor> CanvasRenderingContext2D::GetFrontBuffer(
1732 WebGLFramebufferJS*, const bool webvr) {
1733 if (auto* provider = GetBufferProvider()) {
1734 return provider->GetFrontBuffer();
1736 return Nothing();
1739 PresShell* CanvasRenderingContext2D::GetPresShell() {
1740 if (mCanvasElement) {
1741 return mCanvasElement->OwnerDoc()->GetPresShell();
1743 if (mDocShell) {
1744 return mDocShell->GetPresShell();
1746 return nullptr;
1749 NS_IMETHODIMP
1750 CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) {
1751 // Zero sized surfaces can cause problems.
1752 mZero = false;
1753 if (aHeight == 0) {
1754 aHeight = 1;
1755 mZero = true;
1757 if (aWidth == 0) {
1758 aWidth = 1;
1759 mZero = true;
1762 ClearTarget(aWidth, aHeight);
1764 return NS_OK;
1767 void CanvasRenderingContext2D::AddAssociatedMemory() {
1768 JSObject* wrapper = GetWrapperMaybeDead();
1769 if (wrapper) {
1770 JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
1771 JS::MemoryUse::DOMBinding);
1775 void CanvasRenderingContext2D::RemoveAssociatedMemory() {
1776 JSObject* wrapper = GetWrapperMaybeDead();
1777 if (wrapper) {
1778 JS::RemoveAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
1779 JS::MemoryUse::DOMBinding);
1783 void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) {
1784 // Only free the buffer provider if the size no longer matches.
1785 bool freeBuffer = aWidth != mWidth || aHeight != mHeight;
1786 ResetBitmap(freeBuffer);
1788 mResetLayer = true;
1790 SetInitialState();
1792 // Update dimensions only if new (strictly positive) values were passed.
1793 if (aWidth > 0 && aHeight > 0) {
1794 // Update the memory size associated with the wrapper object when we change
1795 // the dimensions. Note that we need to keep updating dying wrappers before
1796 // they are finalized so that the memory accounting balances out.
1797 RemoveAssociatedMemory();
1798 mWidth = aWidth;
1799 mHeight = aHeight;
1800 AddAssociatedMemory();
1803 if (mOffscreenCanvas) {
1804 OffscreenCanvasDisplayData data;
1805 data.mSize = {mWidth, mHeight};
1806 data.mIsOpaque = mOpaque;
1807 data.mIsAlphaPremult = true;
1808 data.mDoPaintCallbacks = true;
1809 mOffscreenCanvas->UpdateDisplayData(data);
1812 if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) {
1813 return;
1816 // For vertical writing-mode, unless text-orientation is sideways,
1817 // we'll modify the initial value of textBaseline to 'middle'.
1818 RefPtr<const ComputedStyle> canvasStyle =
1819 nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
1820 if (canvasStyle) {
1821 WritingMode wm(canvasStyle);
1822 if (wm.IsVertical() && !wm.IsSideways()) {
1823 CurrentState().textBaseline = CanvasTextBaseline::Middle;
1828 void CanvasRenderingContext2D::ReturnTarget(bool aForceReset) {
1829 if (mTarget && mBufferProvider && mTarget != sErrorTarget.get()) {
1830 CurrentState().transform = mTarget->GetTransform();
1831 if (aForceReset || !mBufferProvider->PreservesDrawingState()) {
1832 for (const auto& style : mStyleStack) {
1833 for (const auto& clipOrTransform : style.clipsAndTransforms) {
1834 if (clipOrTransform.IsClip()) {
1835 mTarget->PopClip();
1840 if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
1841 // With the cairo backend we pushed an extra clip rect which we have to
1842 // balance out here. See the comment in
1843 // RestoreClipsAndTransformToTarget.
1844 mTarget->PopClip();
1847 mTarget->SetTransform(Matrix());
1850 mBufferProvider->ReturnDrawTarget(mTarget.forget());
1854 NS_IMETHODIMP
1855 CanvasRenderingContext2D::InitializeWithDrawTarget(
1856 nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) {
1857 RemovePostRefreshObserver();
1858 mDocShell = aShell;
1859 AddPostRefreshObserverIfNecessary();
1861 IntSize size = aTarget->GetSize();
1862 SetDimensions(size.width, size.height);
1864 mTarget = aTarget;
1865 mBufferProvider = new PersistentBufferProviderBasic(aTarget);
1867 RestoreClipsAndTransformToTarget();
1869 return NS_OK;
1872 void CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(
1873 bool aOpaqueAttrValue) {
1874 if (aOpaqueAttrValue != mOpaqueAttrValue) {
1875 mOpaqueAttrValue = aOpaqueAttrValue;
1876 UpdateIsOpaque();
1880 void CanvasRenderingContext2D::UpdateIsOpaque() {
1881 mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue;
1882 ClearTarget();
1885 NS_IMETHODIMP
1886 CanvasRenderingContext2D::SetContextOptions(JSContext* aCx,
1887 JS::Handle<JS::Value> aOptions,
1888 ErrorResult& aRvForDictionaryInit) {
1889 if (aOptions.isNullOrUndefined()) {
1890 return NS_OK;
1893 // This shouldn't be called before drawing starts, so there should be no
1894 // drawtarget yet
1895 MOZ_ASSERT(!mTarget);
1897 CanvasRenderingContext2DSettings attributes;
1898 if (!attributes.Init(aCx, aOptions)) {
1899 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
1900 return NS_ERROR_UNEXPECTED;
1903 mWillReadFrequently = attributes.mWillReadFrequently;
1905 mContextAttributesHasAlpha = attributes.mAlpha;
1906 UpdateIsOpaque();
1908 return NS_OK;
1911 UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
1912 int32_t* out_format, gfx::IntSize* out_imageSize) {
1913 UniquePtr<uint8_t[]> ret;
1915 *out_format = 0;
1916 *out_imageSize = {};
1918 if (!GetBufferProvider() && !EnsureTarget()) {
1919 return nullptr;
1922 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
1923 if (snapshot) {
1924 RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
1925 if (data && data->GetSize() == GetSize()) {
1926 *out_format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1927 *out_imageSize = data->GetSize();
1928 ret = SurfaceToPackedBGRA(data);
1932 mBufferProvider->ReturnSnapshot(snapshot.forget());
1934 if (ret && ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
1935 nsRFPService::RandomizePixels(
1936 GetCookieJarSettings(), ret.get(),
1937 out_imageSize->width * out_imageSize->height * 4,
1938 SurfaceFormat::A8R8G8B8_UINT32);
1941 return ret;
1944 NS_IMETHODIMP
1945 CanvasRenderingContext2D::GetInputStream(const char* aMimeType,
1946 const nsAString& aEncoderOptions,
1947 nsIInputStream** aStream) {
1948 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1949 enccid += aMimeType;
1950 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1951 if (!encoder) {
1952 return NS_ERROR_FAILURE;
1955 int32_t format = 0;
1956 gfx::IntSize imageSize = {};
1957 UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format, &imageSize);
1958 if (!imageBuffer) {
1959 return NS_ERROR_FAILURE;
1962 return ImageEncoder::GetInputStream(imageSize.width, imageSize.height,
1963 imageBuffer.get(), format, encoder,
1964 aEncoderOptions, aStream);
1967 already_AddRefed<mozilla::gfx::SourceSurface>
1968 CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget* aTarget,
1969 gfxAlphaType* aOutAlphaType) {
1970 if (aOutAlphaType) {
1971 *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
1974 // For GetSurfaceSnapshot we always call EnsureTarget even if mBufferProvider
1975 // already exists, otherwise we get performance issues. See bug 1567054.
1976 if (!EnsureTarget()) {
1977 MOZ_ASSERT(
1978 mTarget == sErrorTarget.get(),
1979 "On EnsureTarget failure mTarget should be set to sErrorTarget.");
1980 // In rare circumstances we may have failed to create an error target.
1981 return mTarget ? mTarget->Snapshot() : nullptr;
1984 // The concept of BorrowSnapshot seems a bit broken here, but the original
1985 // code in GetSurfaceSnapshot just returned a snapshot from mTarget, which
1986 // amounts to breaking the concept implicitly.
1987 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot(aTarget);
1988 RefPtr<SourceSurface> retSurface = snapshot;
1989 mBufferProvider->ReturnSnapshot(snapshot.forget());
1990 return retSurface.forget();
1993 SurfaceFormat CanvasRenderingContext2D::GetSurfaceFormat() const {
1994 return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
1998 // state
2001 void CanvasRenderingContext2D::Save() {
2002 EnsureTarget();
2003 if (MOZ_UNLIKELY(!mTarget || mStyleStack.IsEmpty())) {
2004 SetErrorState();
2005 return;
2007 mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
2008 mStyleStack.SetCapacity(mStyleStack.Length() + 1);
2009 mStyleStack.AppendElement(CurrentState());
2011 if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) {
2012 // This is not fast, but is better than OOMing and shouldn't be hit by
2013 // reasonable code.
2014 mStyleStack.RemoveElementAt(0);
2018 void CanvasRenderingContext2D::Restore() {
2019 if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) {
2020 return;
2023 EnsureTarget();
2024 if (!IsTargetValid()) {
2025 return;
2028 for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) {
2029 if (clipOrTransform.IsClip()) {
2030 mTarget->PopClip();
2034 mStyleStack.RemoveLastElement();
2036 Matrix newMatrix = CurrentState().transform;
2037 Matrix adjustMatrix = mTarget->GetTransform();
2039 Matrix inverse = newMatrix;
2040 if (inverse.Invert()) {
2041 adjustMatrix = adjustMatrix * inverse;
2043 TransformCurrentPath(adjustMatrix);
2045 mTarget->SetTransform(newMatrix);
2049 // transformations
2052 void CanvasRenderingContext2D::Scale(double aX, double aY,
2053 ErrorResult& aError) {
2054 EnsureTarget();
2055 if (!IsTargetValid()) {
2056 aError.Throw(NS_ERROR_FAILURE);
2057 return;
2060 TransformCurrentPath(Matrix::Scaling(1 / aX, 1 / aY));
2061 Matrix newMatrix = mTarget->GetTransform();
2062 newMatrix.PreScale(aX, aY);
2064 SetTransformInternal(newMatrix);
2067 void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) {
2068 EnsureTarget();
2069 if (!IsTargetValid()) {
2070 aError.Throw(NS_ERROR_FAILURE);
2071 return;
2074 TransformCurrentPath(Matrix::Rotation(-aAngle));
2075 Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform();
2077 SetTransformInternal(newMatrix);
2080 void CanvasRenderingContext2D::Translate(double aX, double aY,
2081 ErrorResult& aError) {
2082 EnsureTarget();
2083 if (!IsTargetValid()) {
2084 aError.Throw(NS_ERROR_FAILURE);
2085 return;
2088 TransformCurrentPath(Matrix::Translation(-aX, -aY));
2089 Matrix newMatrix = mTarget->GetTransform();
2090 newMatrix.PreTranslate(aX, aY);
2092 SetTransformInternal(newMatrix);
2095 void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21,
2096 double aM22, double aDx, double aDy,
2097 ErrorResult& aError) {
2098 EnsureTarget();
2099 if (!IsTargetValid()) {
2100 aError.Throw(NS_ERROR_FAILURE);
2101 return;
2104 Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
2106 Matrix inverse = newMatrix;
2107 if (inverse.Invert()) {
2108 TransformCurrentPath(inverse);
2111 newMatrix *= mTarget->GetTransform();
2112 SetTransformInternal(newMatrix);
2115 already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform(
2116 ErrorResult& aError) {
2117 EnsureTarget();
2118 if (!IsTargetValid()) {
2119 aError.Throw(NS_ERROR_FAILURE);
2120 return nullptr;
2122 RefPtr<DOMMatrix> matrix =
2123 new DOMMatrix(GetParentObject(), mTarget->GetTransform());
2124 return matrix.forget();
2127 void CanvasRenderingContext2D::SetTransform(double aM11, double aM12,
2128 double aM21, double aM22,
2129 double aDx, double aDy,
2130 ErrorResult& aError) {
2131 EnsureTarget();
2132 if (!IsTargetValid()) {
2133 aError.Throw(NS_ERROR_FAILURE);
2134 return;
2137 Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy);
2139 Matrix adjustMatrix = mTarget->GetTransform();
2140 Matrix inverse = newMatrix;
2141 // Uses the inverted transform to undo the actual transform that
2142 // will be stored on the DrawTarget
2143 if (inverse.Invert()) {
2144 adjustMatrix = adjustMatrix * inverse;
2146 TransformCurrentPath(adjustMatrix);
2148 SetTransformInternal(newMatrix);
2151 void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit,
2152 ErrorResult& aError) {
2153 EnsureTarget();
2154 if (!IsTargetValid()) {
2155 aError.Throw(NS_ERROR_FAILURE);
2156 return;
2159 RefPtr<DOMMatrixReadOnly> matrix =
2160 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
2161 if (!aError.Failed()) {
2162 Matrix newMatrix = Matrix(*(matrix->GetInternal2D()));
2164 Matrix adjustMatrix = mTarget->GetTransform();
2165 Matrix inverse = newMatrix;
2166 // Uses the inverted transform to undo the actual transform that
2167 // will be stored on the DrawTarget
2168 if (inverse.Invert()) {
2169 adjustMatrix = adjustMatrix * inverse;
2171 TransformCurrentPath(adjustMatrix);
2173 SetTransformInternal(newMatrix);
2177 void CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform) {
2178 if (!aTransform.IsFinite()) {
2179 return;
2182 // Save the transform in the clip stack to be able to replay clips properly.
2183 auto& clipsAndTransforms = CurrentState().clipsAndTransforms;
2184 if (clipsAndTransforms.IsEmpty() ||
2185 clipsAndTransforms.LastElement().IsClip()) {
2186 clipsAndTransforms.AppendElement(ClipState(aTransform));
2187 } else {
2188 // If the last item is a transform we can replace it instead of appending
2189 // a new item.
2190 clipsAndTransforms.LastElement().transform = aTransform;
2192 mTarget->SetTransform(aTransform);
2195 void CanvasRenderingContext2D::ResetTransform(ErrorResult& aError) {
2196 SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError);
2200 // colors
2203 void CanvasRenderingContext2D::SetStyleFromUnion(
2204 const UTF8StringOrCanvasGradientOrCanvasPattern& aValue,
2205 Style aWhichStyle) {
2206 if (aValue.IsUTF8String()) {
2207 SetStyleFromString(aValue.GetAsUTF8String(), aWhichStyle);
2208 return;
2211 if (aValue.IsCanvasGradient()) {
2212 SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle);
2213 return;
2216 if (aValue.IsCanvasPattern()) {
2217 CanvasPattern& pattern = aValue.GetAsCanvasPattern();
2218 SetStyleFromPattern(pattern, aWhichStyle);
2219 if (pattern.mForceWriteOnly) {
2220 SetWriteOnly();
2222 return;
2225 MOZ_ASSERT_UNREACHABLE("Invalid union value");
2228 void CanvasRenderingContext2D::SetFillRule(const nsAString& aString) {
2229 FillRule rule;
2231 if (aString.EqualsLiteral("evenodd"))
2232 rule = FillRule::FILL_EVEN_ODD;
2233 else if (aString.EqualsLiteral("nonzero"))
2234 rule = FillRule::FILL_WINDING;
2235 else
2236 return;
2238 CurrentState().fillRule = rule;
2241 void CanvasRenderingContext2D::GetFillRule(nsAString& aString) {
2242 switch (CurrentState().fillRule) {
2243 case FillRule::FILL_WINDING:
2244 aString.AssignLiteral("nonzero");
2245 break;
2246 case FillRule::FILL_EVEN_ODD:
2247 aString.AssignLiteral("evenodd");
2248 break;
2252 // gradients and patterns
2254 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateLinearGradient(
2255 double aX0, double aY0, double aX1, double aY1) {
2256 RefPtr<CanvasGradient> grad =
2257 new CanvasLinearGradient(this, Point(aX0, aY0), Point(aX1, aY1));
2259 return grad.forget();
2262 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateRadialGradient(
2263 double aX0, double aY0, double aR0, double aX1, double aY1, double aR1,
2264 ErrorResult& aError) {
2265 if (aR0 < 0.0 || aR1 < 0.0) {
2266 aError.ThrowIndexSizeError("Negative radius");
2267 return nullptr;
2270 RefPtr<CanvasGradient> grad = new CanvasRadialGradient(
2271 this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1);
2273 return grad.forget();
2276 already_AddRefed<CanvasGradient> CanvasRenderingContext2D::CreateConicGradient(
2277 double aAngle, double aCx, double aCy) {
2278 double adjustedStartAngle = aAngle + M_PI / 2.0;
2279 return MakeAndAddRef<CanvasConicGradient>(this, adjustedStartAngle,
2280 Point(aCx, aCy));
2283 already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern(
2284 const CanvasImageSource& aSource, const nsAString& aRepeat,
2285 ErrorResult& aError) {
2286 CanvasPattern::RepeatMode repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
2288 if (aRepeat.IsEmpty() || aRepeat.EqualsLiteral("repeat")) {
2289 repeatMode = CanvasPattern::RepeatMode::REPEAT;
2290 } else if (aRepeat.EqualsLiteral("repeat-x")) {
2291 repeatMode = CanvasPattern::RepeatMode::REPEATX;
2292 } else if (aRepeat.EqualsLiteral("repeat-y")) {
2293 repeatMode = CanvasPattern::RepeatMode::REPEATY;
2294 } else if (aRepeat.EqualsLiteral("no-repeat")) {
2295 repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
2296 } else {
2297 aError.ThrowSyntaxError("Invalid pattern keyword");
2298 return nullptr;
2301 Element* element = nullptr;
2302 OffscreenCanvas* offscreenCanvas = nullptr;
2303 VideoFrame* videoFrame = nullptr;
2305 if (aSource.IsHTMLCanvasElement()) {
2306 HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement();
2307 element = canvas;
2309 nsIntSize size = canvas->GetSize();
2310 if (size.width == 0) {
2311 aError.ThrowInvalidStateError("Passed-in canvas has width 0");
2312 return nullptr;
2315 if (size.height == 0) {
2316 aError.ThrowInvalidStateError("Passed-in canvas has height 0");
2317 return nullptr;
2320 // Special case for Canvas, which could be an Azure canvas!
2321 nsICanvasRenderingContextInternal* srcCanvas = canvas->GetCurrentContext();
2322 if (srcCanvas) {
2323 // This might not be an Azure canvas!
2324 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
2325 if (!srcSurf) {
2326 aError.ThrowInvalidStateError(
2327 "CanvasRenderingContext2D.createPattern() failed to snapshot source"
2328 "canvas.");
2329 return nullptr;
2332 RefPtr<CanvasPattern> pat =
2333 new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(),
2334 canvas->IsWriteOnly(), false);
2336 return pat.forget();
2338 } else if (aSource.IsHTMLImageElement()) {
2339 HTMLImageElement* img = &aSource.GetAsHTMLImageElement();
2340 element = img;
2341 } else if (aSource.IsSVGImageElement()) {
2342 SVGImageElement* img = &aSource.GetAsSVGImageElement();
2343 element = img;
2344 } else if (aSource.IsHTMLVideoElement()) {
2345 auto& video = aSource.GetAsHTMLVideoElement();
2346 video.LogVisibility(
2347 mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
2348 element = &video;
2349 } else if (aSource.IsOffscreenCanvas()) {
2350 offscreenCanvas = &aSource.GetAsOffscreenCanvas();
2352 nsIntSize size = offscreenCanvas->GetWidthHeight();
2353 if (size.width == 0) {
2354 aError.ThrowInvalidStateError("Passed-in canvas has width 0");
2355 return nullptr;
2358 if (size.height == 0) {
2359 aError.ThrowInvalidStateError("Passed-in canvas has height 0");
2360 return nullptr;
2363 nsICanvasRenderingContextInternal* srcCanvas =
2364 offscreenCanvas->GetContext();
2365 if (srcCanvas) {
2366 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
2367 if (!srcSurf) {
2368 aError.ThrowInvalidStateError(
2369 "Passed-in canvas failed to create snapshot");
2370 return nullptr;
2373 RefPtr<CanvasPattern> pat = new CanvasPattern(
2374 this, srcSurf, repeatMode, srcCanvas->PrincipalOrNull(),
2375 offscreenCanvas->IsWriteOnly(), false);
2377 return pat.forget();
2379 } else if (aSource.IsVideoFrame()) {
2380 videoFrame = &aSource.GetAsVideoFrame();
2382 if (videoFrame->CodedWidth() == 0) {
2383 aError.ThrowInvalidStateError("Passed-in canvas has width 0");
2384 return nullptr;
2387 if (videoFrame->CodedHeight() == 0) {
2388 aError.ThrowInvalidStateError("Passed-in canvas has height 0");
2389 return nullptr;
2391 } else {
2392 // Special case for ImageBitmap
2393 ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
2394 EnsureTarget();
2395 if (!IsTargetValid()) {
2396 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2397 return nullptr;
2399 RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
2400 if (!srcSurf) {
2401 aError.ThrowInvalidStateError(
2402 "Passed-in ImageBitmap has been transferred");
2403 return nullptr;
2406 // An ImageBitmap never taints others so we set principalForSecurityCheck to
2407 // nullptr and set CORSUsed to true for passing the security check in
2408 // CanvasUtils::DoDrawImageSecurityCheck().
2409 RefPtr<CanvasPattern> pat = new CanvasPattern(
2410 this, srcSurf, repeatMode, nullptr, imgBitmap.IsWriteOnly(), true);
2412 return pat.forget();
2415 EnsureTarget();
2416 if (!IsTargetValid()) {
2417 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2418 return nullptr;
2421 // The canvas spec says that createPattern should use the first frame
2422 // of animated images
2423 auto flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
2424 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE;
2425 SurfaceFromElementResult res;
2426 if (offscreenCanvas) {
2427 res = nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, flags,
2428 mTarget);
2429 } else if (videoFrame) {
2430 res = nsLayoutUtils::SurfaceFromVideoFrame(videoFrame, flags, mTarget);
2431 } else {
2432 res = nsLayoutUtils::SurfaceFromElement(element, flags, mTarget);
2435 // Per spec, we should throw here for the HTMLImageElement and SVGImageElement
2436 // cases if the image request state is "broken". In terms of the infromation
2437 // in "res", the "broken" state corresponds to not having a size and not being
2438 // still-loading (so there is no size forthcoming).
2439 if (aSource.IsHTMLImageElement() || aSource.IsSVGImageElement()) {
2440 if (!res.mIsStillLoading && !res.mHasSize) {
2441 aError.ThrowInvalidStateError(
2442 "Passed-in image's current request's state is \"broken\"");
2443 return nullptr;
2446 if (res.mSize.width == 0 || res.mSize.height == 0) {
2447 return nullptr;
2450 // Is the "fully decodable" check already done in SurfaceFromElement? It's
2451 // not clear how to do it from here, exactly.
2454 RefPtr<SourceSurface> surface = res.GetSourceSurface();
2455 if (!surface) {
2456 return nullptr;
2459 RefPtr<CanvasPattern> pat =
2460 new CanvasPattern(this, surface, repeatMode, res.mPrincipal,
2461 res.mIsWriteOnly, res.mCORSUsed);
2462 return pat.forget();
2466 // shadows
2468 void CanvasRenderingContext2D::SetShadowColor(const nsACString& aShadowColor) {
2469 Maybe<nscolor> color = ParseColor(aShadowColor);
2470 if (!color) {
2471 return;
2474 CurrentState().shadowColor = *color;
2478 // filters
2481 static already_AddRefed<StyleLockedDeclarationBlock> CreateDeclarationForServo(
2482 nsCSSPropertyID aProperty, const nsACString& aPropertyValue,
2483 Document* aDocument) {
2484 ServoCSSParser::ParsingEnvironment env{aDocument->DefaultStyleAttrURLData(),
2485 aDocument->GetCompatibilityMode(),
2486 aDocument->CSSLoader()};
2487 RefPtr<StyleLockedDeclarationBlock> servoDeclarations =
2488 ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env,
2489 StyleParsingMode::DEFAULT);
2491 if (!servoDeclarations) {
2492 // We got a syntax error. The spec says this value must be ignored.
2493 return nullptr;
2496 // From canvas spec, force to set line-height property to 'normal' font
2497 // property.
2498 if (aProperty == eCSSProperty_font) {
2499 const nsCString normalString = "normal"_ns;
2500 Servo_DeclarationBlock_SetPropertyById(
2501 servoDeclarations, eCSSProperty_line_height, &normalString, false,
2502 env.mUrlExtraData, StyleParsingMode::DEFAULT, env.mCompatMode,
2503 env.mLoader, env.mRuleType, {});
2506 return servoDeclarations.forget();
2509 static already_AddRefed<StyleLockedDeclarationBlock>
2510 CreateFontDeclarationForServo(const nsACString& aFont, Document* aDocument) {
2511 return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument);
2514 static already_AddRefed<const ComputedStyle> GetFontStyleForServo(
2515 Element* aElement, const nsACString& aFont, PresShell* aPresShell,
2516 nsACString& aOutUsedFont, ErrorResult& aError) {
2517 RefPtr<StyleLockedDeclarationBlock> declarations =
2518 CreateFontDeclarationForServo(aFont, aPresShell->GetDocument());
2519 if (!declarations) {
2520 // We got a syntax error. The spec says this value must be ignored.
2521 return nullptr;
2524 // In addition to unparseable values, the spec says we need to reject
2525 // 'inherit' and 'initial'. The easiest way to check for this is to look
2526 // at font-size-adjust, which the font shorthand resets to 'none'.
2527 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations,
2528 eCSSProperty_font_size_adjust)) {
2529 return nullptr;
2532 ServoStyleSet* styleSet = aPresShell->StyleSet();
2534 // Have to get a parent ComputedStyle for inherit-like relative values (2em,
2535 // bolder, etc.)
2536 RefPtr<const ComputedStyle> parentStyle;
2537 if (aElement) {
2538 parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement);
2539 if (NS_WARN_IF(aPresShell->IsDestroying())) {
2540 // The flush might've killed the shell.
2541 aError.Throw(NS_ERROR_FAILURE);
2542 return nullptr;
2545 if (!parentStyle) {
2546 RefPtr<StyleLockedDeclarationBlock> declarations =
2547 CreateFontDeclarationForServo("10px sans-serif"_ns,
2548 aPresShell->GetDocument());
2549 MOZ_ASSERT(declarations);
2551 parentStyle =
2552 aPresShell->StyleSet()->ResolveForDeclarations(nullptr, declarations);
2555 MOZ_RELEASE_ASSERT(parentStyle, "Should have a valid parent style");
2557 MOZ_ASSERT(!aPresShell->IsDestroying(),
2558 "We should have returned an error above if the presshell is "
2559 "being destroyed.");
2561 RefPtr<const ComputedStyle> sc =
2562 styleSet->ResolveForDeclarations(parentStyle, declarations);
2564 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-font
2565 // The font-size component must be converted to CSS px for reserialization,
2566 // so we update the declarations with the value from the computed style.
2567 if (!sc->StyleFont()->mFont.family.is_system_font) {
2568 nsAutoCString computedFontSize;
2569 sc->GetComputedPropertyValue(eCSSProperty_font_size, computedFontSize);
2570 Servo_DeclarationBlock_SetPropertyById(
2571 declarations, eCSSProperty_font_size, &computedFontSize, false, nullptr,
2572 StyleParsingMode::DEFAULT, eCompatibility_FullStandards, nullptr,
2573 StyleCssRuleType::Style, {});
2576 // The font getter is required to be reserialized based on what we
2577 // parsed (including having line-height removed).
2578 // If we failed to reserialize, ignore this attempt to set the value.
2579 Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont);
2580 if (aOutUsedFont.IsEmpty()) {
2581 return nullptr;
2584 return sc.forget();
2587 static already_AddRefed<StyleLockedDeclarationBlock>
2588 CreateFilterDeclarationForServo(const nsACString& aFilter,
2589 Document* aDocument) {
2590 return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument);
2593 static already_AddRefed<const ComputedStyle> ResolveFilterStyleForServo(
2594 const nsACString& aFilterString, const ComputedStyle* aParentStyle,
2595 PresShell* aPresShell, ErrorResult& aError) {
2596 RefPtr<StyleLockedDeclarationBlock> declarations =
2597 CreateFilterDeclarationForServo(aFilterString, aPresShell->GetDocument());
2598 if (!declarations) {
2599 // Refuse to accept the filter, but do not throw an error.
2600 return nullptr;
2603 // In addition to unparseable values, the spec says we need to reject
2604 // 'inherit' and 'initial'.
2605 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations,
2606 eCSSProperty_filter)) {
2607 return nullptr;
2610 ServoStyleSet* styleSet = aPresShell->StyleSet();
2611 RefPtr<const ComputedStyle> computedValues =
2612 styleSet->ResolveForDeclarations(aParentStyle, declarations);
2614 return computedValues.forget();
2617 bool CanvasRenderingContext2D::ParseFilter(
2618 const nsACString& aString, StyleOwnedSlice<StyleFilter>& aFilterChain,
2619 ErrorResult& aError) {
2620 RefPtr<PresShell> presShell = GetPresShell();
2621 if (!presShell) {
2622 nsIGlobalObject* global = GetParentObject();
2623 FontFaceSet* fontFaceSet = global ? global->GetFonts() : nullptr;
2624 FontFaceSetImpl* fontFaceSetImpl =
2625 fontFaceSet ? fontFaceSet->GetImpl() : nullptr;
2626 RefPtr<URLExtraData> urlExtraData =
2627 fontFaceSetImpl ? fontFaceSetImpl->GetURLExtraData() : nullptr;
2629 if (NS_WARN_IF(!urlExtraData)) {
2630 // Provided we have a FontFaceSetImpl object, this should only happen on
2631 // worker threads, where we failed to initialize the worker before it was
2632 // shutdown.
2633 aError.ThrowInvalidStateError("Missing URLExtraData");
2634 return false;
2637 if (NS_WARN_IF(!Servo_ParseFilters(&aString, /* aIgnoreUrls */ true,
2638 urlExtraData, &aFilterChain))) {
2639 return false;
2642 return true;
2645 nsAutoCString usedFont; // unused
2647 RefPtr<const ComputedStyle> parentStyle = GetFontStyleForServo(
2648 mCanvasElement, GetFont(), presShell, usedFont, aError);
2649 if (!parentStyle) {
2650 return false;
2653 RefPtr<const ComputedStyle> style =
2654 ResolveFilterStyleForServo(aString, parentStyle, presShell, aError);
2655 if (!style) {
2656 return false;
2659 aFilterChain = style->StyleEffects()->mFilters;
2660 return true;
2663 void CanvasRenderingContext2D::SetFilter(const nsACString& aFilter,
2664 ErrorResult& aError) {
2665 StyleOwnedSlice<StyleFilter> filterChain;
2666 if (ParseFilter(aFilter, filterChain, aError)) {
2667 CurrentState().filterString = aFilter;
2668 CurrentState().filterChain = std::move(filterChain);
2669 if (mCanvasElement) {
2670 CurrentState().autoSVGFiltersObserver =
2671 SVGObserverUtils::ObserveFiltersForCanvasContext(
2672 this, mCanvasElement, CurrentState().filterChain.AsSpan());
2674 UpdateFilter();
2678 static already_AddRefed<const ComputedStyle> ResolveStyleForServo(
2679 nsCSSPropertyID aProperty, const nsACString& aString,
2680 const ComputedStyle* aParentStyle, PresShell* aPresShell,
2681 ErrorResult& aError) {
2682 RefPtr<StyleLockedDeclarationBlock> declarations =
2683 CreateDeclarationForServo(aProperty, aString, aPresShell->GetDocument());
2684 if (!declarations) {
2685 return nullptr;
2688 // In addition to unparseable values, reject 'inherit' and 'initial'.
2689 if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, aProperty)) {
2690 return nullptr;
2693 ServoStyleSet* styleSet = aPresShell->StyleSet();
2694 return styleSet->ResolveForDeclarations(aParentStyle, declarations);
2697 already_AddRefed<const ComputedStyle>
2698 CanvasRenderingContext2D::ResolveStyleForProperty(nsCSSPropertyID aProperty,
2699 const nsACString& aValue) {
2700 RefPtr<PresShell> presShell = GetPresShell();
2701 if (NS_WARN_IF(!presShell)) {
2702 return nullptr;
2705 nsAutoCString usedFont;
2706 IgnoredErrorResult err;
2707 RefPtr<const ComputedStyle> parentStyle =
2708 GetFontStyleForServo(mCanvasElement, GetFont(), presShell, usedFont, err);
2709 if (!parentStyle) {
2710 return nullptr;
2713 return ResolveStyleForServo(aProperty, aValue, parentStyle, presShell, err);
2716 void CanvasRenderingContext2D::GetLetterSpacing(nsACString& aLetterSpacing) {
2717 if (CurrentState().letterSpacingStr.IsEmpty()) {
2718 aLetterSpacing.AssignLiteral("0px");
2719 } else {
2720 aLetterSpacing = CurrentState().letterSpacingStr;
2724 void CanvasRenderingContext2D::SetLetterSpacing(
2725 const nsACString& aLetterSpacing) {
2726 ParseSpacing(aLetterSpacing, &CurrentState().letterSpacing,
2727 CurrentState().letterSpacingStr);
2730 void CanvasRenderingContext2D::GetWordSpacing(nsACString& aWordSpacing) {
2731 if (CurrentState().wordSpacingStr.IsEmpty()) {
2732 aWordSpacing.AssignLiteral("0px");
2733 } else {
2734 aWordSpacing = CurrentState().wordSpacingStr;
2738 void CanvasRenderingContext2D::SetWordSpacing(const nsACString& aWordSpacing) {
2739 ParseSpacing(aWordSpacing, &CurrentState().wordSpacing,
2740 CurrentState().wordSpacingStr);
2743 static GeckoFontMetrics GetFontMetricsFromCanvas(void* aContext) {
2744 auto* ctx = static_cast<CanvasRenderingContext2D*>(aContext);
2745 auto* fontGroup = ctx->GetCurrentFontStyle();
2746 if (!fontGroup) {
2747 // Shouldn't happen, but just in case... return plausible values for a
2748 // 10px font (canvas default size).
2749 return {Length::FromPixels(5.0),
2750 Length::FromPixels(5.0),
2751 Length::FromPixels(8.0),
2752 Length::FromPixels(10.0),
2753 Length::FromPixels(8.0),
2754 Length::FromPixels(10.0),
2755 0.0f,
2756 0.0f};
2758 auto metrics = fontGroup->GetMetricsForCSSUnits(nsFontMetrics::eHorizontal);
2759 return {Length::FromPixels(metrics.xHeight),
2760 Length::FromPixels(metrics.zeroWidth),
2761 Length::FromPixels(metrics.capHeight),
2762 Length::FromPixels(metrics.ideographicWidth),
2763 Length::FromPixels(metrics.maxAscent),
2764 Length::FromPixels(fontGroup->GetStyle()->size),
2765 0.0f,
2766 0.0f};
2769 void CanvasRenderingContext2D::ParseSpacing(const nsACString& aSpacing,
2770 float* aValue,
2771 nsACString& aNormalized) {
2772 // Normalize whitespace in the string before trying to parse it, as we want
2773 // to store it in normalized form, and this allows a simple check against the
2774 // 'normal' keyword, which is not accepted.
2775 nsAutoCString normalized(aSpacing);
2776 normalized.CompressWhitespace(true, true);
2777 ToLowerCase(normalized);
2778 if (normalized.EqualsLiteral("normal")) {
2779 return;
2781 float value;
2782 if (!Servo_ParseLengthWithoutStyleContext(&normalized, &value,
2783 GetFontMetricsFromCanvas, this)) {
2784 if (!GetPresShell()) {
2785 return;
2787 RefPtr<const ComputedStyle> style =
2788 ResolveStyleForProperty(eCSSProperty_letter_spacing, aSpacing);
2789 if (!style) {
2790 return;
2792 value = style->StyleText()->mLetterSpacing.ToCSSPixels();
2794 aNormalized = normalized;
2795 *aValue = value;
2798 class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize {
2799 public:
2800 CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont,
2801 nsAtom* aFontLanguage, bool aExplicitLanguage,
2802 nsPresContext* aPresContext)
2803 : mSize(aSize),
2804 mFont(aFont),
2805 mFontLanguage(aFontLanguage),
2806 mExplicitLanguage(aExplicitLanguage),
2807 mPresContext(aPresContext) {}
2809 virtual float GetEmLength() const override {
2810 return mFont.size.ToCSSPixels();
2813 virtual float GetExLength() const override {
2814 nsFontMetrics::Params params;
2815 params.language = mFontLanguage;
2816 params.explicitLanguage = mExplicitLanguage;
2817 params.textPerf = mPresContext->GetTextPerfMetrics();
2818 params.featureValueLookup = mPresContext->GetFontFeatureValuesLookup();
2819 RefPtr<nsFontMetrics> fontMetrics =
2820 mPresContext->GetMetricsFor(mFont, params);
2821 return NSAppUnitsToFloatPixels(fontMetrics->XHeight(),
2822 AppUnitsPerCSSPixel());
2825 virtual gfx::Size GetSize() const override { return Size(mSize); }
2827 private:
2828 gfx::IntSize mSize;
2829 const nsFont& mFont;
2830 nsAtom* mFontLanguage;
2831 bool mExplicitLanguage;
2832 nsPresContext* mPresContext;
2835 // The filter might reference an SVG filter that is declared inside this
2836 // document. Flush frames so that we'll have a SVGFilterFrame to work
2837 // with.
2838 static bool FiltersNeedFrameFlush(Span<const StyleFilter> aFilters) {
2839 for (const auto& filter : aFilters) {
2840 if (filter.IsUrl()) {
2841 return true;
2844 return false;
2847 void CanvasRenderingContext2D::UpdateFilter() {
2848 const bool writeOnly = IsWriteOnly() ||
2849 (mCanvasElement && mCanvasElement->IsWriteOnly()) ||
2850 (mOffscreenCanvas && mOffscreenCanvas->IsWriteOnly());
2852 RefPtr<PresShell> presShell = GetPresShell();
2853 if (!mOffscreenCanvas && (!presShell || presShell->IsDestroying())) {
2854 // Ensure we set an empty filter and update the state to
2855 // reflect the current "taint" status of the canvas
2856 CurrentState().filter = FilterDescription();
2857 CurrentState().filterSourceGraphicTainted = writeOnly;
2858 return;
2861 // The PresContext is only used with URL filters and we don't allow those to
2862 // be used on worker threads.
2863 nsPresContext* presContext = nullptr;
2864 if (presShell) {
2865 if (FiltersNeedFrameFlush(CurrentState().filterChain.AsSpan())) {
2866 presShell->FlushPendingNotifications(FlushType::Frames);
2869 if (MOZ_UNLIKELY(presShell->IsDestroying())) {
2870 return;
2873 presContext = presShell->GetPresContext();
2876 MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty());
2878 CurrentState().filter = FilterInstance::GetFilterDescription(
2879 mCanvasElement, CurrentState().filterChain.AsSpan(), writeOnly,
2880 CanvasUserSpaceMetrics(GetSize(), CurrentState().fontFont,
2881 CurrentState().fontLanguage,
2882 CurrentState().fontExplicitLanguage, presContext),
2883 gfxRect(0, 0, mWidth, mHeight), CurrentState().filterAdditionalImages);
2884 CurrentState().filterSourceGraphicTainted = writeOnly;
2888 // rects
2891 static bool ValidateRect(double& aX, double& aY, double& aWidth,
2892 double& aHeight, bool aIsZeroSizeValid) {
2893 if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) {
2894 return false;
2897 // bug 1018527
2898 // The values of canvas API input are in double precision, but Moz2D APIs are
2899 // using float precision. Bypass canvas API calls when the input is out of
2900 // float precision to avoid precision problem
2901 if (!std::isfinite((float)aX) || !std::isfinite((float)aY) ||
2902 !std::isfinite((float)aWidth) || !std::isfinite((float)aHeight)) {
2903 return false;
2906 // bug 1074733
2907 // The canvas spec does not forbid rects with negative w or h, so given
2908 // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate
2909 // the appropriate rect by flipping negative dimensions. This prevents
2910 // draw targets from receiving "empty" rects later on.
2911 if (aWidth < 0) {
2912 aWidth = -aWidth;
2913 aX -= aWidth;
2915 if (aHeight < 0) {
2916 aHeight = -aHeight;
2917 aY -= aHeight;
2919 return true;
2922 void CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW,
2923 double aH) {
2924 // Do not allow zeros - it's a no-op at that point per spec.
2925 if (!ValidateRect(aX, aY, aW, aH, false)) {
2926 return;
2929 gfx::Rect clearRect(aX, aY, aW, aH);
2931 EnsureTarget(&clearRect, true);
2932 if (!IsTargetValid()) {
2933 return;
2936 mTarget->ClearRect(clearRect);
2938 RedrawUser(gfxRect(aX, aY, aW, aH));
2941 void CanvasRenderingContext2D::FillRect(double aX, double aY, double aW,
2942 double aH) {
2943 if (!ValidateRect(aX, aY, aW, aH, true)) {
2944 return;
2947 const ContextState* state = &CurrentState();
2948 if (state->patternStyles[Style::FILL]) {
2949 auto& style = state->patternStyles[Style::FILL];
2950 CanvasPattern::RepeatMode repeat = style->mRepeat;
2951 // In the FillRect case repeat modes are easy to deal with.
2952 bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
2953 repeat == CanvasPattern::RepeatMode::REPEATY;
2954 bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
2955 repeat == CanvasPattern::RepeatMode::REPEATX;
2956 if ((limitx || limity) && style->mTransform.IsRectilinear()) {
2957 // For rectilinear transforms, we can just get the transformed pattern
2958 // bounds and intersect them with the fill rectangle bounds.
2959 // TODO: If the transform is not rectilinear, then we would need a fully
2960 // general clip path to represent the X and Y clip planes bounding the
2961 // pattern. For such cases, it would be more efficient to rely on Skia's
2962 // Decal tiling mode rather than trying to generate a path. Until then,
2963 // just punt to relying on the default Clamp mode.
2964 gfx::Rect patternBounds(style->mSurface->GetRect());
2965 patternBounds = style->mTransform.TransformBounds(patternBounds);
2966 if (style->mTransform.HasNonAxisAlignedTransform()) {
2967 // If there is an rotation (90 or 270 degrees), the X axis of the
2968 // pattern projects onto the Y axis of the geometry, and vice versa.
2969 std::swap(limitx, limity);
2971 // We always need to execute painting for non-over operators, even if
2972 // we end up with w/h = 0. The default Rect::Intersect can cause both
2973 // dimensions to become empty if either dimension individually fails
2974 // to overlap, which is unsuitable. Instead, we need to independently
2975 // limit the supplied rectangle on each dimension as required.
2976 if (limitx) {
2977 double x2 = aX + aW;
2978 aX = std::max(aX, double(patternBounds.x));
2979 aW = std::max(std::min(x2, double(patternBounds.XMost())) - aX, 0.0);
2981 if (limity) {
2982 double y2 = aY + aH;
2983 aY = std::max(aY, double(patternBounds.y));
2984 aH = std::max(std::min(y2, double(patternBounds.YMost())) - aY, 0.0);
2988 state = nullptr;
2990 bool isColor;
2991 bool discardContent = PatternIsOpaque(Style::FILL, &isColor) &&
2992 (CurrentState().op == CompositionOp::OP_OVER ||
2993 CurrentState().op == CompositionOp::OP_SOURCE);
2994 const gfx::Rect fillRect(aX, aY, aW, aH);
2995 EnsureTarget(discardContent ? &fillRect : nullptr, discardContent && isColor);
2996 if (!IsTargetValid()) {
2997 return;
3000 gfx::Rect bounds;
3001 const bool needBounds = NeedToCalculateBounds();
3002 if (!IsTargetValid()) {
3003 return;
3005 if (needBounds) {
3006 bounds = mTarget->GetTransform().TransformBounds(fillRect);
3009 AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled
3010 ? AntialiasMode::DEFAULT
3011 : AntialiasMode::NONE;
3013 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
3014 CompositionOp op = target.UsedOperation();
3015 if (!target) {
3016 return;
3018 target.FillRect(gfx::Rect(aX, aY, aW, aH),
3019 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
3020 DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
3022 RedrawUser(gfxRect(aX, aY, aW, aH));
3025 void CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW,
3026 double aH) {
3027 if (!aW && !aH) {
3028 return;
3031 if (!ValidateRect(aX, aY, aW, aH, true)) {
3032 return;
3035 EnsureTarget();
3036 if (!IsTargetValid()) {
3037 return;
3040 const bool needBounds = NeedToCalculateBounds();
3041 if (!IsTargetValid()) {
3042 return;
3045 gfx::Rect bounds;
3046 if (needBounds) {
3047 const ContextState& state = CurrentState();
3048 bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f,
3049 aW + state.lineWidth, aH + state.lineWidth);
3050 bounds = mTarget->GetTransform().TransformBounds(bounds);
3053 if (!IsTargetValid()) {
3054 return;
3057 if (!aH) {
3058 CapStyle cap = CapStyle::BUTT;
3059 if (CurrentState().lineJoin == CanvasLineJoin::Round) {
3060 cap = CapStyle::ROUND;
3062 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
3063 auto op = target.UsedOperation();
3064 if (!target) {
3065 return;
3068 const ContextState& state = CurrentState();
3069 target.StrokeLine(
3070 Point(aX, aY), Point(aX + aW, aY),
3071 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3072 StrokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), cap,
3073 state.miterLimit, state.dash.Length(),
3074 state.dash.Elements(), state.dashOffset),
3075 DrawOptions(state.globalAlpha, op));
3076 return;
3079 if (!aW) {
3080 CapStyle cap = CapStyle::BUTT;
3081 if (CurrentState().lineJoin == CanvasLineJoin::Round) {
3082 cap = CapStyle::ROUND;
3084 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
3085 auto op = target.UsedOperation();
3086 if (!target) {
3087 return;
3090 const ContextState& state = CurrentState();
3091 target.StrokeLine(
3092 Point(aX, aY), Point(aX, aY + aH),
3093 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3094 StrokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin), cap,
3095 state.miterLimit, state.dash.Length(),
3096 state.dash.Elements(), state.dashOffset),
3097 DrawOptions(state.globalAlpha, op));
3098 return;
3101 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
3102 auto op = target.UsedOperation();
3103 if (!target) {
3104 return;
3107 const ContextState& state = CurrentState();
3108 target.StrokeRect(
3109 gfx::Rect(aX, aY, aW, aH),
3110 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3111 StrokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin),
3112 CanvasToGfx(state.lineCap), state.miterLimit,
3113 state.dash.Length(), state.dash.Elements(),
3114 state.dashOffset),
3115 DrawOptions(state.globalAlpha, op));
3117 Redraw();
3121 // path bits
3124 void CanvasRenderingContext2D::BeginPath() {
3125 mPath = nullptr;
3126 mPathBuilder = nullptr;
3127 mPathPruned = false;
3130 void CanvasRenderingContext2D::FillImpl(const gfx::Path& aPath) {
3131 MOZ_ASSERT(IsTargetValid());
3132 if (aPath.IsEmpty()) {
3133 return;
3136 const bool needBounds = NeedToCalculateBounds();
3137 gfx::Rect bounds;
3138 if (needBounds) {
3139 bounds = aPath.GetBounds(mTarget->GetTransform());
3142 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
3143 if (!target) {
3144 return;
3147 auto op = target.UsedOperation();
3148 if (!IsTargetValid() || !target) {
3149 return;
3151 target.Fill(&aPath,
3152 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
3153 DrawOptions(CurrentState().globalAlpha, op));
3154 Redraw();
3157 void CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) {
3158 EnsureUserSpacePath(aWinding);
3159 if (!IsTargetValid()) {
3160 return;
3163 if (mPath) {
3164 FillImpl(*mPath);
3168 void CanvasRenderingContext2D::Fill(const CanvasPath& aPath,
3169 const CanvasWindingRule& aWinding) {
3170 EnsureTarget();
3171 if (!IsTargetValid()) {
3172 return;
3175 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
3176 if (gfxpath) {
3177 FillImpl(*gfxpath);
3181 void CanvasRenderingContext2D::StrokeImpl(const gfx::Path& aPath) {
3182 MOZ_ASSERT(IsTargetValid());
3183 if (aPath.IsEmpty()) {
3184 return;
3187 const ContextState* state = &CurrentState();
3188 StrokeOptions strokeOptions(state->lineWidth, CanvasToGfx(state->lineJoin),
3189 CanvasToGfx(state->lineCap), state->miterLimit,
3190 state->dash.Length(), state->dash.Elements(),
3191 state->dashOffset);
3192 state = nullptr;
3194 const bool needBounds = NeedToCalculateBounds();
3195 if (!IsTargetValid()) {
3196 return;
3198 gfx::Rect bounds;
3199 if (needBounds) {
3200 bounds = aPath.GetStrokedBounds(strokeOptions, mTarget->GetTransform());
3203 AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds, true);
3204 if (!target) {
3205 return;
3208 auto op = target.UsedOperation();
3209 if (!IsTargetValid() || !target) {
3210 return;
3212 target.Stroke(&aPath,
3213 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
3214 strokeOptions, DrawOptions(CurrentState().globalAlpha, op));
3215 Redraw();
3218 void CanvasRenderingContext2D::Stroke() {
3219 EnsureUserSpacePath();
3220 if (!IsTargetValid()) {
3221 return;
3224 if (mPath) {
3225 StrokeImpl(*mPath);
3229 void CanvasRenderingContext2D::Stroke(const CanvasPath& aPath) {
3230 EnsureTarget();
3231 if (!IsTargetValid()) {
3232 return;
3234 RefPtr<gfx::Path> gfxpath =
3235 aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
3236 if (gfxpath) {
3237 StrokeImpl(*gfxpath);
3241 void CanvasRenderingContext2D::DrawFocusIfNeeded(
3242 mozilla::dom::Element& aElement, ErrorResult& aRv) {
3243 EnsureUserSpacePath();
3244 if (!mPath) {
3245 return;
3248 if (DrawCustomFocusRing(aElement)) {
3249 AutoSaveRestore asr(this);
3251 // set state to conforming focus state
3252 ContextState* state = &CurrentState();
3253 state->globalAlpha = 1.0;
3254 state->shadowBlur = 0;
3255 state->shadowOffset.x = 0;
3256 state->shadowOffset.y = 0;
3257 state->op = mozilla::gfx::CompositionOp::OP_OVER;
3259 state->lineCap = CanvasLineCap::Butt;
3260 state->lineJoin = CanvasLineJoin::Miter;
3261 state->lineWidth = 1;
3262 state->dash.Clear();
3264 // color and style of the rings is the same as for image maps
3265 // set the background focus color
3266 state->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
3267 state = nullptr;
3269 // draw the focus ring
3270 Stroke();
3271 if (!mPath) {
3272 return;
3275 // set dashing for foreground
3276 nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
3277 for (uint32_t i = 0; i < 2; ++i) {
3278 if (!dash.AppendElement(1, fallible)) {
3279 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
3280 return;
3284 // set the foreground focus color
3285 CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0, 0, 0, 255));
3286 // draw the focus ring
3287 Stroke();
3288 if (!mPath) {
3289 return;
3294 bool CanvasRenderingContext2D::DrawCustomFocusRing(Element& aElement) {
3295 if (!aElement.State().HasState(ElementState::FOCUSRING)) {
3296 return false;
3299 HTMLCanvasElement* canvas = GetCanvas();
3300 if (!canvas || !aElement.IsInclusiveDescendantOf(canvas)) {
3301 return false;
3304 EnsureUserSpacePath();
3305 return true;
3308 void CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding) {
3309 EnsureUserSpacePath(aWinding);
3311 if (!mPath) {
3312 return;
3315 mTarget->PushClip(mPath);
3316 CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath));
3319 void CanvasRenderingContext2D::Clip(const CanvasPath& aPath,
3320 const CanvasWindingRule& aWinding) {
3321 EnsureTarget();
3322 if (!IsTargetValid()) {
3323 return;
3326 RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget);
3328 if (!gfxpath) {
3329 return;
3332 mTarget->PushClip(gfxpath);
3333 CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath));
3336 void CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2,
3337 double aY2, double aRadius,
3338 ErrorResult& aError) {
3339 if (aRadius < 0) {
3340 return aError.ThrowIndexSizeError("Negative radius");
3343 EnsureWritablePath();
3345 // Current point in user space!
3346 Point p0 = mPathBuilder->CurrentPoint();
3348 Point p1(aX1, aY1);
3349 Point p2(aX2, aY2);
3351 if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) {
3352 return;
3355 // Execute these calculations in double precision to avoid cumulative
3356 // rounding errors.
3357 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
3358 cy, angle0, angle1;
3359 bool anticlockwise;
3361 if (p0 == p1 || p1 == p2 || aRadius == 0) {
3362 LineTo(p1);
3363 return;
3366 // Check for colinearity
3367 dir = (p2.x.value - p1.x.value) * (p0.y.value - p1.y.value) +
3368 (p2.y.value - p1.y.value) * (p1.x.value - p0.x.value);
3369 if (dir == 0) {
3370 LineTo(p1);
3371 return;
3374 // XXX - Math for this code was already available from the non-azure code
3375 // and would be well tested. Perhaps converting to bezier directly might
3376 // be more efficient longer run.
3377 a2 = (p0.x - aX1) * (p0.x - aX1) + (p0.y - aY1) * (p0.y - aY1);
3378 b2 = (aX1 - aX2) * (aX1 - aX2) + (aY1 - aY2) * (aY1 - aY2);
3379 c2 = (p0.x - aX2) * (p0.x - aX2) + (p0.y - aY2) * (p0.y - aY2);
3380 cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2));
3382 sinx = sqrt(1 - cosx * cosx);
3383 d = aRadius / ((1 - cosx) / sinx);
3385 anx = (aX1 - p0.x) / sqrt(a2);
3386 any = (aY1 - p0.y) / sqrt(a2);
3387 bnx = (aX1 - aX2) / sqrt(b2);
3388 bny = (aY1 - aY2) / sqrt(b2);
3389 x3 = aX1 - anx * d;
3390 y3 = aY1 - any * d;
3391 x4 = aX1 - bnx * d;
3392 y4 = aY1 - bny * d;
3393 anticlockwise = (dir < 0);
3394 cx = x3 + any * aRadius * (anticlockwise ? 1 : -1);
3395 cy = y3 - anx * aRadius * (anticlockwise ? 1 : -1);
3396 angle0 = atan2((y3 - cy), (x3 - cx));
3397 angle1 = atan2((y4 - cy), (x4 - cx));
3399 LineTo(x3, y3);
3401 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
3404 void CanvasRenderingContext2D::Arc(double aX, double aY, double aR,
3405 double aStartAngle, double aEndAngle,
3406 bool aAnticlockwise, ErrorResult& aError) {
3407 if (aR < 0.0) {
3408 return aError.ThrowIndexSizeError("Negative radius");
3410 if (aStartAngle == aEndAngle) {
3411 LineTo(aX + aR * cos(aStartAngle), aY + aR * sin(aStartAngle));
3412 return;
3415 EnsureWritablePath();
3417 EnsureActivePath();
3419 mPathBuilder->Arc(Point(aX, aY), aR, aStartAngle, aEndAngle, aAnticlockwise);
3420 mPathPruned = false;
3423 void CanvasRenderingContext2D::Rect(double aX, double aY, double aW,
3424 double aH) {
3425 EnsureWritablePath();
3427 if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) ||
3428 !std::isfinite(aH)) {
3429 return;
3432 EnsureCapped();
3433 mPathBuilder->MoveTo(Point(aX, aY));
3434 if (aW == 0 && aH == 0) {
3435 return;
3437 mPathBuilder->LineTo(Point(aX + aW, aY));
3438 mPathBuilder->LineTo(Point(aX + aW, aY + aH));
3439 mPathBuilder->LineTo(Point(aX, aY + aH));
3440 mPathBuilder->Close();
3443 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
3444 static void RoundRectImpl(
3445 PathBuilder* aPathBuilder, const Maybe<Matrix>& aTransform, double aX,
3446 double aY, double aW, double aH,
3447 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
3448 aRadii,
3449 ErrorResult& aError) {
3450 // Step 1. If any of x, y, w, or h are infinite or NaN, then return.
3451 if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) ||
3452 !std::isfinite(aH)) {
3453 return;
3456 nsTArray<OwningUnrestrictedDoubleOrDOMPointInit> radii;
3457 // Step 2. If radii is an unrestricted double or DOMPointInit, then set radii
3458 // to « radii ».
3459 if (aRadii.IsUnrestrictedDouble()) {
3460 radii.AppendElement()->SetAsUnrestrictedDouble() =
3461 aRadii.GetAsUnrestrictedDouble();
3462 } else if (aRadii.IsDOMPointInit()) {
3463 radii.AppendElement()->SetAsDOMPointInit() = aRadii.GetAsDOMPointInit();
3464 } else {
3465 radii = aRadii.GetAsUnrestrictedDoubleOrDOMPointInitSequence();
3466 // Step 3. If radii is not a list of size one, two, three, or
3467 // four, then throw a RangeError.
3468 if (radii.Length() < 1 || radii.Length() > 4) {
3469 aError.ThrowRangeError("Can have between 1 and 4 radii");
3470 return;
3474 // Step 4. Let normalizedRadii be an empty list.
3475 AutoTArray<Size, 4> normalizedRadii;
3477 // Step 5. For each radius of radii:
3478 for (const auto& radius : radii) {
3479 // Step 5.1. If radius is a DOMPointInit:
3480 if (radius.IsDOMPointInit()) {
3481 const DOMPointInit& point = radius.GetAsDOMPointInit();
3482 // Step 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then
3483 // return.
3484 if (!std::isfinite(point.mX) || !std::isfinite(point.mY)) {
3485 return;
3488 // Step 5.1.2. If radius["x"] or radius["y"] is negative, then
3489 // throw a RangeError.
3490 if (point.mX < 0 || point.mY < 0) {
3491 aError.ThrowRangeError("Radius can not be negative");
3492 return;
3495 // Step 5.1.3. Otherwise, append radius to
3496 // normalizedRadii.
3497 normalizedRadii.AppendElement(
3498 Size(gfx::Float(point.mX), gfx::Float(point.mY)));
3499 continue;
3502 // Step 5.2. If radius is a unrestricted double:
3503 double r = radius.GetAsUnrestrictedDouble();
3504 // Step 5.2.1. If radius is infinite or NaN, then return.
3505 if (!std::isfinite(r)) {
3506 return;
3509 // Step 5.2.2. If radius is negative, then throw a RangeError.
3510 if (r < 0) {
3511 aError.ThrowRangeError("Radius can not be negative");
3512 return;
3515 // Step 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to
3516 // normalizedRadii.
3517 normalizedRadii.AppendElement(Size(gfx::Float(r), gfx::Float(r)));
3520 // Step 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null.
3521 Size upperLeft, upperRight, lowerRight, lowerLeft;
3523 if (normalizedRadii.Length() == 4) {
3524 // Step 7. If normalizedRadii's size is 4, then set upperLeft to
3525 // normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight
3526 // to normalizedRadii[2], and set lowerLeft to normalizedRadii[3].
3527 upperLeft = normalizedRadii[0];
3528 upperRight = normalizedRadii[1];
3529 lowerRight = normalizedRadii[2];
3530 lowerLeft = normalizedRadii[3];
3531 } else if (normalizedRadii.Length() == 3) {
3532 // Step 8. If normalizedRadii's size is 3, then set upperLeft to
3533 // normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1],
3534 // and set lowerRight to normalizedRadii[2].
3535 upperLeft = normalizedRadii[0];
3536 upperRight = normalizedRadii[1];
3537 lowerRight = normalizedRadii[2];
3538 lowerLeft = normalizedRadii[1];
3539 } else if (normalizedRadii.Length() == 2) {
3540 // Step 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight
3541 // to normalizedRadii[0] and set upperRight and lowerLeft to
3542 // normalizedRadii[1].
3543 upperLeft = normalizedRadii[0];
3544 upperRight = normalizedRadii[1];
3545 lowerRight = normalizedRadii[0];
3546 lowerLeft = normalizedRadii[1];
3547 } else {
3548 // Step 10. If normalizedRadii's size is 1, then set upperLeft, upperRight,
3549 // lowerRight, and lowerLeft to normalizedRadii[0].
3550 MOZ_ASSERT(normalizedRadii.Length() == 1);
3551 upperLeft = normalizedRadii[0];
3552 upperRight = normalizedRadii[0];
3553 lowerRight = normalizedRadii[0];
3554 lowerLeft = normalizedRadii[0];
3557 // This is not as specified but copied from Chrome.
3558 // XXX Maybe if we implemented Step 12 (the path algorithm) per
3559 // spec this wouldn't be needed?
3560 Float x(aX), y(aY), w(aW), h(aH);
3561 bool clockwise = true;
3562 if (w < 0) {
3563 // Horizontal flip
3564 clockwise = false;
3565 x += w;
3566 w = -w;
3567 std::swap(upperLeft, upperRight);
3568 std::swap(lowerLeft, lowerRight);
3571 if (h < 0) {
3572 // Vertical flip
3573 clockwise = !clockwise;
3574 y += h;
3575 h = -h;
3576 std::swap(upperLeft, lowerLeft);
3577 std::swap(upperRight, lowerRight);
3580 // Step 11. Corner curves must not overlap. Scale all radii to prevent this:
3581 // Step 11.1. Let top be upperLeft["x"] + upperRight["x"].
3582 Float top = upperLeft.width + upperRight.width;
3583 // Step 11.2. Let right be upperRight["y"] + lowerRight["y"].
3584 Float right = upperRight.height + lowerRight.height;
3585 // Step 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"].
3586 Float bottom = lowerRight.width + lowerLeft.width;
3587 // Step 11.4. Let left be upperLeft["y"] + lowerLeft["y"].
3588 Float left = upperLeft.height + lowerLeft.height;
3589 // Step 11.5. Let scale be the minimum value of the ratios w / top, h / right,
3590 // w / bottom, h / left.
3591 Float scale = std::min({w / top, h / right, w / bottom, h / left});
3592 // Step 11.6. If scale is less than 1, then set the x and y members of
3593 // upperLeft, upperRight, lowerLeft, and lowerRight to their current values
3594 // multiplied by scale.
3595 if (scale < 1.0f) {
3596 upperLeft = upperLeft * scale;
3597 upperRight = upperRight * scale;
3598 lowerLeft = lowerLeft * scale;
3599 lowerRight = lowerRight * scale;
3602 // Step 12. Create a new subpath:
3603 // Step 13. Mark the subpath as closed.
3604 // Note: Implemented by AppendRoundedRectToPath, which is shared with CSS
3605 // borders etc.
3606 gfx::Rect rect{x, y, w, h};
3607 RectCornerRadii cornerRadii(upperLeft, upperRight, lowerRight, lowerLeft);
3608 AppendRoundedRectToPath(aPathBuilder, rect, cornerRadii, clockwise,
3609 aTransform);
3611 // Step 14. Create a new subpath with the point (x, y) as the only point in
3612 // the subpath.
3613 // XXX We don't seem to be doing this for ::Rect either?
3616 void CanvasRenderingContext2D::RoundRect(
3617 double aX, double aY, double aW, double aH,
3618 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
3619 aRadii,
3620 ErrorResult& aError) {
3621 EnsureWritablePath();
3623 PathBuilder* builder = mPathBuilder;
3624 Maybe<Matrix> transform = Nothing();
3626 EnsureCapped();
3627 RoundRectImpl(builder, transform, aX, aY, aW, aH, aRadii, aError);
3630 void CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX,
3631 double aRadiusY, double aRotation,
3632 double aStartAngle, double aEndAngle,
3633 bool aAnticlockwise,
3634 ErrorResult& aError) {
3635 if (aRadiusX < 0.0 || aRadiusY < 0.0) {
3636 return aError.ThrowIndexSizeError("Negative radius");
3639 EnsureWritablePath();
3641 ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle,
3642 aEndAngle, aAnticlockwise, aRotation);
3643 mPathPruned = false;
3646 void CanvasRenderingContext2D::EnsureWritablePath() {
3647 EnsureTarget();
3648 // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
3649 // go ahead and create a path anyway since callers depend on that.
3651 FillRule fillRule = CurrentState().fillRule;
3653 if (mPathBuilder) {
3654 return;
3657 if (!mPath) {
3658 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3659 } else {
3660 mPathBuilder = mPath->CopyToBuilder(fillRule);
3664 void CanvasRenderingContext2D::EnsureUserSpacePath(
3665 const CanvasWindingRule& aWinding) {
3666 FillRule fillRule = CurrentState().fillRule;
3667 if (aWinding == CanvasWindingRule::Evenodd) {
3668 fillRule = FillRule::FILL_EVEN_ODD;
3671 EnsureTarget();
3672 if (!IsTargetValid()) {
3673 return;
3676 if (!mPath && !mPathBuilder) {
3677 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
3680 if (mPathBuilder) {
3681 EnsureCapped();
3682 mPath = mPathBuilder->Finish();
3683 mPathBuilder = nullptr;
3686 if (mPath && mPath->GetFillRule() != fillRule) {
3687 mPathBuilder = mPath->CopyToBuilder(fillRule);
3688 mPath = mPathBuilder->Finish();
3689 mPathBuilder = nullptr;
3692 NS_ASSERTION(mPath, "mPath should exist");
3695 void CanvasRenderingContext2D::TransformCurrentPath(const Matrix& aTransform) {
3696 EnsureTarget();
3697 if (!IsTargetValid()) {
3698 return;
3701 if (mPathBuilder) {
3702 RefPtr<Path> path = mPathBuilder->Finish();
3703 mPathBuilder = path->TransformedCopyToBuilder(aTransform);
3704 } else if (mPath) {
3705 mPathBuilder = mPath->TransformedCopyToBuilder(aTransform);
3706 mPath = nullptr;
3711 // text
3714 void CanvasRenderingContext2D::SetFont(const nsACString& aFont,
3715 ErrorResult& aError) {
3716 SetFontInternal(aFont, aError);
3717 if (aError.Failed()) {
3718 return;
3721 // Setting the font attribute magically resets fontVariantCaps and
3722 // fontStretch to normal.
3723 // (spec unclear, cf. https://github.com/whatwg/html/issues/8103)
3724 SetFontVariantCaps(CanvasFontVariantCaps::Normal);
3725 SetFontStretch(CanvasFontStretch::Normal);
3727 // If letterSpacing or wordSpacing is present, recompute to account for
3728 // changes to font-relative dimensions.
3729 UpdateSpacing();
3732 static float QuantizeFontSize(float aSize) {
3733 // Based on the Veltkamp-Dekker float-splitting algorithm, see e.g.
3734 // https://indico.cern.ch/event/313684/contributions/1687773/attachments/600513/826490/FPArith-Part2.pdf
3735 // A 32-bit float has 24 bits of precision (23 stored, plus an implicit 1 bit
3736 // at the start of the mantissa).
3737 constexpr int bitsToDrop = 17; // leaving 7 bits of precision
3738 constexpr int scale = 1 << bitsToDrop;
3739 float d = aSize * (scale + 1);
3740 float t = d - aSize;
3741 return d - t;
3744 bool CanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
3745 ErrorResult& aError) {
3746 RefPtr<PresShell> presShell = GetPresShell();
3747 if (!presShell) {
3748 return SetFontInternalDisconnected(aFont, aError);
3751 nsPresContext* c = presShell->GetPresContext();
3752 FontStyleCacheKey key{aFont, c->RestyleManager()->GetRestyleGeneration()};
3753 auto entry = mFontStyleCache.Lookup(key);
3754 if (!entry) {
3755 FontStyleData newData;
3756 newData.mKey = key;
3757 newData.mStyle = GetFontStyleForServo(mCanvasElement, aFont, presShell,
3758 newData.mUsedFont, aError);
3759 entry.Set(newData);
3762 const auto& data = entry.Data();
3763 if (!data.mStyle) {
3764 return false;
3767 const nsStyleFont* fontStyle = data.mStyle->StyleFont();
3769 // Purposely ignore the font size that respects the user's minimum
3770 // font preference (fontStyle->mFont.size) in favor of the computed
3771 // size (fontStyle->mSize). See
3772 // https://bugzilla.mozilla.org/show_bug.cgi?id=698652.
3773 // FIXME: Nobody initializes mAllowZoom for servo?
3774 // MOZ_ASSERT(!fontStyle->mAllowZoom,
3775 // "expected text zoom to be disabled on this nsStyleFont");
3776 nsFont resizedFont(fontStyle->mFont);
3777 // Create a font group working in units of CSS pixels instead of the usual
3778 // device pixels, to avoid being affected by page zoom. nsFontMetrics will
3779 // convert nsFont size in app units to device pixels for the font group, so
3780 // here we first apply to the size the equivalent of a conversion from device
3781 // pixels to CSS pixels, to adjust for the difference in expectations from
3782 // other nsFontMetrics clients.
3783 resizedFont.size =
3784 fontStyle->mSize.ScaledBy(1.0f / c->CSSToDevPixelScale().scale);
3786 // Quantize font size to avoid filling caches with thousands of fonts that
3787 // differ by imperceptibly-tiny size deltas.
3788 resizedFont.size = StyleCSSPixelLength::FromPixels(
3789 QuantizeFontSize(resizedFont.size.ToCSSPixels()));
3791 resizedFont.kerning = CanvasToGfx(CurrentState().fontKerning);
3793 // fontStretch handling: if fontStretch is not 'normal', apply it;
3794 // if it is normal, then use whatever the shorthand set.
3795 // XXX(jfkthame) The interaction between the shorthand and the separate attr
3796 // here is not clearly spec'd, and we may want to reconsider it (or revise
3797 // the available values); see https://github.com/whatwg/html/issues/8103.
3798 switch (CurrentState().fontStretch) {
3799 case CanvasFontStretch::Normal:
3800 // Leave whatever the shorthand set.
3801 break;
3802 case CanvasFontStretch::Ultra_condensed:
3803 resizedFont.stretch = StyleFontStretch::ULTRA_CONDENSED;
3804 break;
3805 case CanvasFontStretch::Extra_condensed:
3806 resizedFont.stretch = StyleFontStretch::EXTRA_CONDENSED;
3807 break;
3808 case CanvasFontStretch::Condensed:
3809 resizedFont.stretch = StyleFontStretch::CONDENSED;
3810 break;
3811 case CanvasFontStretch::Semi_condensed:
3812 resizedFont.stretch = StyleFontStretch::SEMI_CONDENSED;
3813 break;
3814 case CanvasFontStretch::Semi_expanded:
3815 resizedFont.stretch = StyleFontStretch::SEMI_EXPANDED;
3816 break;
3817 case CanvasFontStretch::Expanded:
3818 resizedFont.stretch = StyleFontStretch::EXPANDED;
3819 break;
3820 case CanvasFontStretch::Extra_expanded:
3821 resizedFont.stretch = StyleFontStretch::EXTRA_EXPANDED;
3822 break;
3823 case CanvasFontStretch::Ultra_expanded:
3824 resizedFont.stretch = StyleFontStretch::ULTRA_EXPANDED;
3825 break;
3826 default:
3827 MOZ_ASSERT_UNREACHABLE("unknown stretch value");
3828 break;
3831 // fontVariantCaps handling: if fontVariantCaps is not 'normal', apply it;
3832 // if it is, then use the smallCaps boolean from the shorthand.
3833 // XXX(jfkthame) The interaction between the shorthand and the separate attr
3834 // here is not clearly spec'd, and we may want to reconsider it (or revise
3835 // the available values); see https://github.com/whatwg/html/issues/8103.
3836 switch (CurrentState().fontVariantCaps) {
3837 case CanvasFontVariantCaps::Normal:
3838 // Leave whatever the shorthand set.
3839 break;
3840 case CanvasFontVariantCaps::Small_caps:
3841 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_SMALLCAPS;
3842 break;
3843 case CanvasFontVariantCaps::All_small_caps:
3844 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_ALLSMALL;
3845 break;
3846 case CanvasFontVariantCaps::Petite_caps:
3847 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_PETITECAPS;
3848 break;
3849 case CanvasFontVariantCaps::All_petite_caps:
3850 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_ALLPETITE;
3851 break;
3852 case CanvasFontVariantCaps::Unicase:
3853 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_UNICASE;
3854 break;
3855 case CanvasFontVariantCaps::Titling_caps:
3856 resizedFont.variantCaps = NS_FONT_VARIANT_CAPS_TITLING;
3857 break;
3858 default:
3859 MOZ_ASSERT_UNREACHABLE("unknown caps value");
3860 break;
3863 c->Document()->FlushUserFontSet();
3865 nsFontMetrics::Params params;
3866 params.language = fontStyle->mLanguage;
3867 params.explicitLanguage = fontStyle->mExplicitLanguage;
3868 params.userFontSet = c->GetUserFontSet();
3869 params.textPerf = c->GetTextPerfMetrics();
3870 RefPtr<nsFontMetrics> metrics = c->GetMetricsFor(resizedFont, params);
3872 gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup();
3873 CurrentState().fontGroup = newFontGroup;
3874 NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
3875 CurrentState().font = data.mUsedFont;
3876 CurrentState().fontFont = fontStyle->mFont;
3877 CurrentState().fontFont.size = fontStyle->mSize;
3878 CurrentState().fontLanguage = fontStyle->mLanguage;
3879 CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage;
3881 return true;
3884 static nsAutoCString FamilyListToString(
3885 const StyleFontFamilyList& aFamilyList) {
3886 return StringJoin(", "_ns, aFamilyList.list.AsSpan(),
3887 [](nsACString& dst, const StyleSingleFontFamily& name) {
3888 name.AppendToString(dst);
3892 static void SerializeFontForCanvas(const StyleFontFamilyList& aList,
3893 const gfxFontStyle& aStyle,
3894 nsACString& aUsedFont) {
3895 // Re-serialize the font shorthand as required by the canvas spec.
3896 aUsedFont.Truncate();
3898 if (!aStyle.style.IsNormal()) {
3899 aStyle.style.ToString(aUsedFont);
3900 aUsedFont.Append(" ");
3903 // font-weight is serialized as a number
3904 if (!aStyle.weight.IsNormal()) {
3905 aUsedFont.AppendFloat(aStyle.weight.ToFloat());
3906 aUsedFont.Append(" ");
3909 // font-stretch is serialized using CSS Fonts 3 keywords, not percentages.
3910 if (!aStyle.stretch.IsNormal() &&
3911 Servo_FontStretch_SerializeKeyword(&aStyle.stretch, &aUsedFont)) {
3912 aUsedFont.Append(" ");
3915 if (aStyle.variantCaps == NS_FONT_VARIANT_CAPS_SMALLCAPS) {
3916 aUsedFont.Append("small-caps ");
3919 // Serialize the computed (not specified) size, and the family name(s).
3920 aUsedFont.AppendFloat(aStyle.size);
3921 aUsedFont.Append("px ");
3922 aUsedFont.Append(FamilyListToString(aList));
3925 bool CanvasRenderingContext2D::SetFontInternalDisconnected(
3926 const nsACString& aFont, ErrorResult& aError) {
3927 FontFaceSet* fontFaceSet = nullptr;
3928 if (mCanvasElement) {
3929 fontFaceSet = mCanvasElement->OwnerDoc()->Fonts();
3930 } else {
3931 nsIGlobalObject* global = GetParentObject();
3932 fontFaceSet = global ? global->GetFonts() : nullptr;
3935 FontFaceSetImpl* fontFaceSetImpl =
3936 fontFaceSet ? fontFaceSet->GetImpl() : nullptr;
3937 RefPtr<URLExtraData> urlExtraData =
3938 fontFaceSetImpl ? fontFaceSetImpl->GetURLExtraData() : nullptr;
3940 if (NS_WARN_IF(!urlExtraData)) {
3941 // Provided we have a FontFaceSetImpl object, this should only happen on
3942 // worker threads, where we failed to initialize the worker before it was
3943 // shutdown.
3944 aError.ThrowInvalidStateError("Missing URLExtraData");
3945 return false;
3948 if (fontFaceSetImpl) {
3949 fontFaceSetImpl->FlushUserFontSet();
3952 // In the OffscreenCanvas case we don't have the context necessary to call
3953 // GetFontStyleForServo(), as we do in the main-thread canvas context, so
3954 // instead we borrow ParseFontShorthandForMatching to parse the attribute.
3955 StyleComputedFontStyleDescriptor style(
3956 StyleComputedFontStyleDescriptor::Normal());
3957 StyleFontFamilyList list;
3958 gfxFontStyle fontStyle;
3959 float size = 0.0f;
3960 bool smallCaps = false;
3961 if (!ServoCSSParser::ParseFontShorthandForMatching(
3962 aFont, urlExtraData, list, fontStyle.style, fontStyle.stretch,
3963 fontStyle.weight, &size, &smallCaps)) {
3964 return false;
3967 fontStyle.size = QuantizeFontSize(size);
3969 switch (CurrentState().fontStretch) {
3970 case CanvasFontStretch::Normal:
3971 // Leave whatever the shorthand set.
3972 break;
3973 case CanvasFontStretch::Ultra_condensed:
3974 fontStyle.stretch = StyleFontStretch::ULTRA_CONDENSED;
3975 break;
3976 case CanvasFontStretch::Extra_condensed:
3977 fontStyle.stretch = StyleFontStretch::EXTRA_CONDENSED;
3978 break;
3979 case CanvasFontStretch::Condensed:
3980 fontStyle.stretch = StyleFontStretch::CONDENSED;
3981 break;
3982 case CanvasFontStretch::Semi_condensed:
3983 fontStyle.stretch = StyleFontStretch::SEMI_CONDENSED;
3984 break;
3985 case CanvasFontStretch::Semi_expanded:
3986 fontStyle.stretch = StyleFontStretch::SEMI_EXPANDED;
3987 break;
3988 case CanvasFontStretch::Expanded:
3989 fontStyle.stretch = StyleFontStretch::EXPANDED;
3990 break;
3991 case CanvasFontStretch::Extra_expanded:
3992 fontStyle.stretch = StyleFontStretch::EXTRA_EXPANDED;
3993 break;
3994 case CanvasFontStretch::Ultra_expanded:
3995 fontStyle.stretch = StyleFontStretch::ULTRA_EXPANDED;
3996 break;
3997 default:
3998 MOZ_ASSERT_UNREACHABLE("unknown stretch value");
3999 break;
4002 // fontVariantCaps handling: if fontVariantCaps is not 'normal', apply it;
4003 // if it is, then use the smallCaps boolean from the shorthand.
4004 // XXX(jfkthame) The interaction between the shorthand and the separate attr
4005 // here is not clearly spec'd, and we may want to reconsider it (or revise
4006 // the available values); see https://github.com/whatwg/html/issues/8103.
4007 switch (CurrentState().fontVariantCaps) {
4008 case CanvasFontVariantCaps::Normal:
4009 fontStyle.variantCaps = smallCaps ? NS_FONT_VARIANT_CAPS_SMALLCAPS
4010 : NS_FONT_VARIANT_CAPS_NORMAL;
4011 break;
4012 case CanvasFontVariantCaps::Small_caps:
4013 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_SMALLCAPS;
4014 break;
4015 case CanvasFontVariantCaps::All_small_caps:
4016 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_ALLSMALL;
4017 break;
4018 case CanvasFontVariantCaps::Petite_caps:
4019 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_PETITECAPS;
4020 break;
4021 case CanvasFontVariantCaps::All_petite_caps:
4022 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_ALLPETITE;
4023 break;
4024 case CanvasFontVariantCaps::Unicase:
4025 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_UNICASE;
4026 break;
4027 case CanvasFontVariantCaps::Titling_caps:
4028 fontStyle.variantCaps = NS_FONT_VARIANT_CAPS_TITLING;
4029 break;
4030 default:
4031 MOZ_ASSERT_UNREACHABLE("unknown caps value");
4032 break;
4034 // If variantCaps is set, we need to disable a gfxFont fast-path.
4035 fontStyle.noFallbackVariantFeatures =
4036 (fontStyle.variantCaps == NS_FONT_VARIANT_CAPS_NORMAL);
4038 // Set the kerning feature, if required by the fontKerning attribute.
4039 gfxFontFeature setting{TRUETYPE_TAG('k', 'e', 'r', 'n'), 0};
4040 switch (CurrentState().fontKerning) {
4041 case CanvasFontKerning::None:
4042 setting.mValue = 0;
4043 fontStyle.featureSettings.AppendElement(setting);
4044 break;
4045 case CanvasFontKerning::Normal:
4046 setting.mValue = 1;
4047 fontStyle.featureSettings.AppendElement(setting);
4048 break;
4049 default:
4050 // auto case implies use user agent default
4051 break;
4054 // If we have a canvas element, get its lang (if known).
4055 RefPtr<nsAtom> language;
4056 bool explicitLanguage = false;
4057 if (mCanvasElement) {
4058 language = mCanvasElement->FragmentOrElement::GetLang();
4059 if (language) {
4060 explicitLanguage = true;
4061 } else {
4062 language = mCanvasElement->OwnerDoc()->GetLanguageForStyle();
4064 } else {
4065 // Pass the OS default language, to behave similarly to HTML or canvas-
4066 // element content with no language tag.
4067 language = nsLanguageAtomService::GetService()->GetLocaleLanguage();
4070 // TODO: Cache fontGroups in the Worker (use an nsFontCache?)
4071 gfxFontGroup* fontGroup =
4072 new gfxFontGroup(nullptr, // aPresContext
4073 list, // aFontFamilyList
4074 &fontStyle, // aStyle
4075 language, // aLanguage
4076 explicitLanguage, // aExplicitLanguage
4077 nullptr, // aTextPerf
4078 fontFaceSetImpl, // aUserFontSet
4079 1.0, // aDevToCssSize
4080 StyleFontVariantEmoji::Normal);
4081 CurrentState().fontGroup = fontGroup;
4082 SerializeFontForCanvas(list, fontStyle, CurrentState().font);
4083 CurrentState().fontFont = nsFont(StyleFontFamily{list, false, false},
4084 StyleCSSPixelLength::FromPixels(size));
4085 CurrentState().fontFont.variantCaps = fontStyle.variantCaps;
4086 CurrentState().fontLanguage = nullptr;
4087 CurrentState().fontExplicitLanguage = false;
4088 return true;
4091 void CanvasRenderingContext2D::UpdateSpacing() {
4092 auto state = CurrentState();
4093 if (!state.letterSpacingStr.IsEmpty()) {
4094 SetLetterSpacing(state.letterSpacingStr);
4096 if (!state.wordSpacingStr.IsEmpty()) {
4097 SetWordSpacing(state.wordSpacingStr);
4102 * Helper function that replaces the whitespace characters in a string
4103 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
4104 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
4105 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
4106 * We also replace characters with Bidi type Segment Separator or Block
4107 * Separator.
4108 * @param str The string whose whitespace characters to replace.
4110 static inline void TextReplaceWhitespaceCharacters(nsAutoString& aStr) {
4111 aStr.ReplaceChar(u"\x09\x0A\x0B\x0C\x0D\x1C\x1D\x1E\x1F\x85\x2029",
4112 char16_t(' '));
4115 void CanvasRenderingContext2D::FillText(const nsAString& aText, double aX,
4116 double aY,
4117 const Optional<double>& aMaxWidth,
4118 ErrorResult& aError) {
4119 DebugOnly<TextMetrics*> metrics = DrawOrMeasureText(
4120 aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, aError);
4121 MOZ_ASSERT(!metrics); // drawing operation never returns TextMetrics
4124 void CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX,
4125 double aY,
4126 const Optional<double>& aMaxWidth,
4127 ErrorResult& aError) {
4128 DebugOnly<TextMetrics*> metrics = DrawOrMeasureText(
4129 aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, aError);
4130 MOZ_ASSERT(!metrics); // drawing operation never returns TextMetrics
4133 TextMetrics* CanvasRenderingContext2D::MeasureText(const nsAString& aRawText,
4134 ErrorResult& aError) {
4135 Optional<double> maxWidth;
4136 return DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE,
4137 aError);
4141 * Used for nsBidiPresUtils::ProcessText
4143 struct MOZ_STACK_CLASS CanvasBidiProcessor final
4144 : public nsBidiPresUtils::BidiProcessor {
4145 using Style = CanvasRenderingContext2D::Style;
4147 CanvasBidiProcessor() : nsBidiPresUtils::BidiProcessor() {
4148 if (StaticPrefs::gfx_missing_fonts_notify()) {
4149 mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
4153 ~CanvasBidiProcessor() {
4154 // notify front-end code if we encountered missing glyphs in any script
4155 if (mMissingFonts) {
4156 mMissingFonts->Flush();
4160 class PropertyProvider : public gfxTextRun::PropertyProvider {
4161 public:
4162 explicit PropertyProvider(const CanvasBidiProcessor& aProcessor)
4163 : mProcessor(aProcessor) {}
4165 void GetSpacing(gfxTextRun::Range aRange,
4166 gfxFont::Spacing* aSpacing) const {
4167 for (auto i = aRange.start; i < aRange.end; ++i) {
4168 auto* charGlyphs = mProcessor.mTextRun->GetCharacterGlyphs();
4169 if (i == mProcessor.mTextRun->GetLength() - 1 ||
4170 (charGlyphs[i + 1].IsClusterStart() &&
4171 charGlyphs[i + 1].IsLigatureGroupStart())) {
4172 // Currently we add all the letterspacing to the right of the glyph,
4173 // which is similar to Chrome's behavior, though the LTR vs RTL
4174 // asymmetry seems unfortunate.
4175 if (mProcessor.mTextRun->IsRightToLeft()) {
4176 aSpacing->mAfter = 0;
4177 aSpacing->mBefore = mProcessor.mLetterSpacing;
4178 } else {
4179 aSpacing->mBefore = 0;
4180 aSpacing->mAfter = mProcessor.mLetterSpacing;
4182 } else {
4183 aSpacing->mBefore = 0;
4184 aSpacing->mAfter = 0;
4186 if (charGlyphs[i].CharIsSpace()) {
4187 if (mProcessor.mTextRun->IsRightToLeft()) {
4188 aSpacing->mBefore += mProcessor.mWordSpacing;
4189 } else {
4190 aSpacing->mAfter += mProcessor.mWordSpacing;
4193 aSpacing++;
4197 mozilla::StyleHyphens GetHyphensOption() const {
4198 return mozilla::StyleHyphens::None;
4201 // Methods only used when hyphenation is active, not relevant to canvas2d:
4202 void GetHyphenationBreaks(gfxTextRun::Range aRange,
4203 gfxTextRun::HyphenType* aBreakBefore) const {
4204 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
4206 gfxFloat GetHyphenWidth() const {
4207 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
4208 return 0.0;
4210 already_AddRefed<DrawTarget> GetDrawTarget() const {
4211 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
4212 return nullptr;
4214 uint32_t GetAppUnitsPerDevUnit() const {
4215 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
4216 return 60;
4218 gfx::ShapedTextFlags GetShapedTextFlags() const {
4219 MOZ_ASSERT_UNREACHABLE("no hyphenation in canvas2d text!");
4220 return gfx::ShapedTextFlags();
4223 private:
4224 const CanvasBidiProcessor& mProcessor;
4227 using ContextState = CanvasRenderingContext2D::ContextState;
4229 void SetText(const char16_t* aText, int32_t aLength,
4230 intl::BidiDirection aDirection) override {
4231 if (mIgnoreSetText) {
4232 // We've been told to ignore SetText because the processor is only ever
4233 // handling a single, fixed string.
4234 MOZ_ASSERT(mTextRun && mTextRun->GetLength() == uint32_t(aLength));
4235 return;
4237 mSetTextCount++;
4238 auto* pfl = gfxPlatformFontList::PlatformFontList();
4239 pfl->Lock();
4240 mFontgrp->CheckForUpdatedPlatformList();
4241 mFontgrp->UpdateUserFonts(); // ensure user font generation is current
4242 // adjust flags for current direction run
4243 gfx::ShapedTextFlags flags = mTextRunFlags;
4244 if (aDirection == intl::BidiDirection::RTL) {
4245 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
4246 } else {
4247 flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL;
4249 mTextRun = mFontgrp->MakeTextRun(
4250 aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags,
4251 nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts,
4252 mMissingFonts.get());
4253 pfl->Unlock();
4256 nscoord GetWidth() override {
4257 PropertyProvider provider(*this);
4258 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
4259 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
4260 : gfxFont::LOOSE_INK_EXTENTS,
4261 mDrawTarget, &provider);
4263 // this only measures the height; the total width is gotten from the
4264 // the return value of ProcessText.
4265 if (mDoMeasureBoundingBox) {
4266 textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
4267 mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
4270 return NSToCoordRound(textRunMetrics.mAdvanceWidth);
4273 already_AddRefed<gfxPattern> GetGradientFor(Style aStyle) {
4274 RefPtr<gfxPattern> pattern;
4275 CanvasGradient* gradient = mCtx->CurrentState().gradientStyles[aStyle];
4276 CanvasGradient::Type type = gradient->GetType();
4278 switch (type) {
4279 case CanvasGradient::Type::CONIC: {
4280 auto conic = static_cast<CanvasConicGradient*>(gradient);
4281 pattern = new gfxPattern(conic->mCenter.x, conic->mCenter.y,
4282 conic->mAngle, 0, 1);
4283 break;
4285 case CanvasGradient::Type::RADIAL: {
4286 auto radial = static_cast<CanvasRadialGradient*>(gradient);
4287 pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y,
4288 radial->mRadius1, radial->mCenter2.x,
4289 radial->mCenter2.y, radial->mRadius2);
4290 break;
4292 case CanvasGradient::Type::LINEAR: {
4293 auto linear = static_cast<CanvasLinearGradient*>(gradient);
4294 pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y,
4295 linear->mEnd.x, linear->mEnd.y);
4296 break;
4298 default:
4299 MOZ_ASSERT(false, "Should be linear, radial or conic gradient.");
4300 return nullptr;
4303 for (auto stop : gradient->mRawStops) {
4304 pattern->AddColorStop(stop.offset, stop.color);
4307 return pattern.forget();
4310 gfx::ExtendMode CvtCanvasRepeatToGfxRepeat(
4311 CanvasPattern::RepeatMode aRepeatMode) {
4312 switch (aRepeatMode) {
4313 case CanvasPattern::RepeatMode::REPEAT:
4314 return gfx::ExtendMode::REPEAT;
4315 case CanvasPattern::RepeatMode::REPEATX:
4316 return gfx::ExtendMode::REPEAT_X;
4317 case CanvasPattern::RepeatMode::REPEATY:
4318 return gfx::ExtendMode::REPEAT_Y;
4319 case CanvasPattern::RepeatMode::NOREPEAT:
4320 return gfx::ExtendMode::CLAMP;
4321 default:
4322 return gfx::ExtendMode::CLAMP;
4326 already_AddRefed<gfxPattern> GetPatternFor(Style aStyle) {
4327 const CanvasPattern* pat = mCtx->CurrentState().patternStyles[aStyle];
4328 RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, pat->mTransform);
4329 pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat));
4330 return pattern.forget();
4333 void DrawText(nscoord aXOffset) override {
4334 gfx::Point point = mPt;
4335 bool rtl = mTextRun->IsRightToLeft();
4336 bool verticalRun = mTextRun->IsVertical();
4337 RefPtr<gfxPattern> pattern;
4339 float& inlineCoord = verticalRun ? point.y.value : point.x.value;
4340 inlineCoord += aXOffset;
4342 PropertyProvider provider(*this);
4344 // offset is given in terms of left side of string
4345 if (rtl) {
4346 // Bug 581092 - don't use rounded pixel width to advance to
4347 // right-hand end of run, because this will cause different
4348 // glyph positioning for LTR vs RTL drawing of the same
4349 // glyph string on OS X and DWrite where textrun widths may
4350 // involve fractional pixels.
4351 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
4352 mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
4353 : gfxFont::LOOSE_INK_EXTENTS,
4354 mDrawTarget, &provider);
4355 inlineCoord += textRunMetrics.mAdvanceWidth;
4356 // old code was:
4357 // point.x += width * mAppUnitsPerDevPixel;
4358 // TODO: restore this if/when we move to fractional coords
4359 // throughout the text layout process
4362 mCtx->EnsureTarget();
4363 if (!mCtx->IsTargetValid()) {
4364 return;
4367 // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
4368 // appropriately.
4369 StrokeOptions strokeOpts;
4370 DrawOptions drawOpts;
4371 Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
4372 ? Style::FILL
4373 : Style::STROKE;
4374 const ContextState& state = mCtx->CurrentState();
4376 gfx::Rect bounds;
4377 if (mCtx->NeedToCalculateBounds()) {
4378 bounds = ToRect(mBoundingBox);
4379 bounds.MoveBy(mPt / mAppUnitsPerDevPixel);
4380 if (style == Style::STROKE) {
4381 bounds.Inflate(state.lineWidth / 2.0);
4383 bounds = mDrawTarget->GetTransform().TransformBounds(bounds);
4386 AdjustedTarget target(mCtx, bounds.IsEmpty() ? nullptr : &bounds, false);
4387 if (!target) {
4388 return;
4391 gfxContext thebes(target, /* aPreserveTransform */ true);
4392 gfxTextRun::DrawParams params(&thebes);
4394 params.allowGDI = false;
4396 if (state.StyleIsColor(style)) { // Color
4397 nscolor fontColor = state.colorStyles[style];
4398 if (style == Style::FILL) {
4399 params.context->SetColor(sRGBColor::FromABGR(fontColor));
4400 } else {
4401 params.textStrokeColor = fontColor;
4403 } else {
4404 if (state.gradientStyles[style]) { // Gradient
4405 pattern = GetGradientFor(style);
4406 } else if (state.patternStyles[style]) { // Pattern
4407 pattern = GetPatternFor(style);
4408 } else {
4409 MOZ_ASSERT(false, "Should never reach here.");
4410 return;
4412 MOZ_ASSERT(pattern, "No valid pattern.");
4414 if (style == Style::FILL) {
4415 params.context->SetPattern(pattern);
4416 } else {
4417 params.textStrokePattern = pattern;
4421 drawOpts.mAlpha = state.globalAlpha;
4422 drawOpts.mCompositionOp = target.UsedOperation();
4423 if (!mCtx->IsTargetValid()) {
4424 return;
4427 params.drawOpts = &drawOpts;
4428 params.provider = &provider;
4430 if (style == Style::STROKE) {
4431 strokeOpts.mLineWidth = state.lineWidth;
4432 strokeOpts.mLineJoin = CanvasToGfx(state.lineJoin);
4433 strokeOpts.mLineCap = CanvasToGfx(state.lineCap);
4434 strokeOpts.mMiterLimit = state.miterLimit;
4435 strokeOpts.mDashLength = state.dash.Length();
4436 strokeOpts.mDashPattern =
4437 (strokeOpts.mDashLength > 0) ? state.dash.Elements() : 0;
4438 strokeOpts.mDashOffset = state.dashOffset;
4440 params.drawMode = DrawMode::GLYPH_STROKE;
4441 params.strokeOpts = &strokeOpts;
4444 mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
4447 // current text run
4448 RefPtr<gfxTextRun> mTextRun;
4450 // pointer to a screen reference context used to measure text and such
4451 RefPtr<DrawTarget> mDrawTarget;
4453 // Pointer to the draw target we should fill our text to
4454 CanvasRenderingContext2D* mCtx = nullptr;
4456 // position of the left side of the string, alphabetic baseline
4457 gfx::Point mPt;
4459 // current font
4460 gfxFontGroup* mFontgrp = nullptr;
4462 // spacing adjustments to be applied
4463 gfx::Float mLetterSpacing = 0.0f;
4464 gfx::Float mWordSpacing = 0.0f;
4466 // to record any unsupported characters found in the text,
4467 // and notify front-end if it is interested
4468 UniquePtr<gfxMissingFontRecorder> mMissingFonts;
4470 // dev pixel conversion factor
4471 int32_t mAppUnitsPerDevPixel = 0;
4473 // operation (fill or stroke)
4474 CanvasRenderingContext2D::TextDrawOperation mOp =
4475 CanvasRenderingContext2D::TextDrawOperation::FILL;
4477 // union of bounding boxes of all runs, needed for shadows
4478 gfxRect mBoundingBox;
4480 // flags to use when creating textrun, based on CSS style
4481 gfx::ShapedTextFlags mTextRunFlags = gfx::ShapedTextFlags();
4483 // Count of how many times SetText has been called on this processor.
4484 uint32_t mSetTextCount = 0;
4486 // true iff the bounding box should be measured
4487 bool mDoMeasureBoundingBox = false;
4489 // true if future SetText calls should be ignored
4490 bool mIgnoreSetText = false;
4493 TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
4494 const nsAString& aRawText, float aX, float aY,
4495 const Optional<double>& aMaxWidth, TextDrawOperation aOp,
4496 ErrorResult& aError) {
4497 RefPtr<gfxFontGroup> currentFontStyle = GetCurrentFontStyle();
4498 if (NS_WARN_IF(!currentFontStyle)) {
4499 aError = NS_ERROR_FAILURE;
4500 return nullptr;
4503 RefPtr<PresShell> presShell = GetPresShell();
4504 RefPtr<Document> document = presShell ? presShell->GetDocument() : nullptr;
4506 // replace all the whitespace characters with U+0020 SPACE
4507 nsAutoString textToDraw(aRawText);
4508 TextReplaceWhitespaceCharacters(textToDraw);
4510 // According to spec, the API should return an empty array if maxWidth was
4511 // provided but is less than or equal to zero or equal to NaN.
4512 if (aMaxWidth.WasPassed() &&
4513 (aMaxWidth.Value() <= 0 || std::isnan(aMaxWidth.Value()))) {
4514 textToDraw.Truncate();
4517 RefPtr<const ComputedStyle> canvasStyle;
4518 if (mCanvasElement) {
4519 canvasStyle = nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
4522 // Get text direction, either from the property or inherited from context.
4523 const ContextState& state = CurrentState();
4524 bool isRTL;
4525 switch (state.textDirection) {
4526 case CanvasDirection::Ltr:
4527 isRTL = false;
4528 break;
4529 case CanvasDirection::Rtl:
4530 isRTL = true;
4531 break;
4532 case CanvasDirection::Inherit:
4533 if (canvasStyle) {
4534 isRTL =
4535 canvasStyle->StyleVisibility()->mDirection == StyleDirection::Rtl;
4536 } else if (document) {
4537 isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) ==
4538 IBMBIDI_TEXTDIRECTION_RTL;
4539 } else {
4540 isRTL = false;
4542 break;
4543 default:
4544 MOZ_CRASH("unknown direction!");
4547 // This is only needed to know if we can know the drawing bounding box easily.
4548 const bool doCalculateBounds = NeedToCalculateBounds();
4549 if (presShell && presShell->IsDestroying()) {
4550 aError = NS_ERROR_FAILURE;
4551 return nullptr;
4554 nsPresContext* presContext =
4555 presShell ? presShell->GetPresContext() : nullptr;
4557 if (presContext) {
4558 // ensure user font set is up to date
4559 presContext->Document()->FlushUserFontSet();
4560 currentFontStyle->SetUserFontSet(presContext->GetUserFontSet());
4563 if (currentFontStyle->GetStyle()->size == 0.0F) {
4564 aError = NS_OK;
4565 if (aOp == TextDrawOperation::MEASURE) {
4566 return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
4567 0.0, 0.0);
4569 return nullptr;
4572 if (!std::isfinite(aX) || !std::isfinite(aY)) {
4573 aError = NS_OK;
4574 // This may not be correct - what should TextMetrics contain in the case of
4575 // infinite width or height?
4576 if (aOp == TextDrawOperation::MEASURE) {
4577 return new TextMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
4578 0.0, 0.0);
4580 return nullptr;
4583 CanvasBidiProcessor processor;
4585 // If we don't have a ComputedStyle, we can't set up vertical-text flags
4586 // (for now, at least; perhaps we need new Canvas API to control this).
4587 processor.mTextRunFlags =
4588 canvasStyle ? nsLayoutUtils::GetTextRunFlagsForStyle(
4589 canvasStyle, presContext, canvasStyle->StyleFont(),
4590 canvasStyle->StyleText(), 0)
4591 : gfx::ShapedTextFlags();
4593 switch (state.textRendering) {
4594 case CanvasTextRendering::Auto:
4595 if (state.fontFont.size.ToCSSPixels() <
4596 StaticPrefs::browser_display_auto_quality_min_font_size()) {
4597 processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
4598 } else {
4599 processor.mTextRunFlags &= ~gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
4601 break;
4602 case CanvasTextRendering::OptimizeSpeed:
4603 processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
4604 break;
4605 case CanvasTextRendering::OptimizeLegibility:
4606 case CanvasTextRendering::GeometricPrecision:
4607 processor.mTextRunFlags &= ~gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
4608 break;
4609 default:
4610 MOZ_CRASH("unknown textRendering!");
4613 GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
4614 processor.mPt = gfx::Point(aX, aY);
4615 processor.mDrawTarget = gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
4617 // If we don't have a target then we don't have a transform. A target won't
4618 // be needed in the case where we're measuring the text size. This allows
4619 // to avoid creating a target if it's only being used to measure text sizes.
4620 if (mTarget) {
4621 processor.mDrawTarget->SetTransform(mTarget->GetTransform());
4623 processor.mCtx = this;
4624 processor.mOp = aOp;
4625 processor.mBoundingBox = gfxRect(0, 0, 0, 0);
4626 processor.mDoMeasureBoundingBox = doCalculateBounds ||
4627 !mIsEntireFrameInvalid ||
4628 aOp == TextDrawOperation::MEASURE;
4629 processor.mFontgrp = currentFontStyle;
4631 if (state.letterSpacing != 0.0 || state.wordSpacing != 0.0) {
4632 processor.mLetterSpacing =
4633 state.letterSpacing * processor.mAppUnitsPerDevPixel;
4634 processor.mWordSpacing = state.wordSpacing * processor.mAppUnitsPerDevPixel;
4635 processor.mTextRunFlags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
4636 if (state.letterSpacing != 0.0) {
4637 processor.mTextRunFlags |=
4638 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
4642 nscoord totalWidthCoord;
4644 processor.mFontgrp
4645 ->UpdateUserFonts(); // ensure user font generation is current
4646 RefPtr<gfxFont> font = processor.mFontgrp->GetFirstValidFont();
4647 const gfxFont::Metrics& fontMetrics =
4648 font->GetMetrics(nsFontMetrics::eHorizontal);
4650 // calls bidi algo twice since it needs the full text width and the
4651 // bounding boxes before rendering anything
4652 aError = nsBidiPresUtils::ProcessText(
4653 textToDraw.get(), textToDraw.Length(),
4654 isRTL ? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(),
4655 presContext, processor, nsBidiPresUtils::MODE_MEASURE, nullptr, 0,
4656 &totalWidthCoord, mBidiEngine);
4657 if (aError.Failed()) {
4658 return nullptr;
4661 // If ProcessText only called SetText once, we're dealing with a single run,
4662 // and so we don't need to repeat SetText and textRun construction at drawing
4663 // time below; we can just re-use the existing textRun.
4664 if (processor.mSetTextCount == 1) {
4665 processor.mIgnoreSetText = true;
4668 float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
4670 // offset pt.x based on text align
4671 gfxFloat anchorX;
4673 if (state.textAlign == CanvasTextAlign::Center) {
4674 anchorX = .5;
4675 } else if (state.textAlign == CanvasTextAlign::Left ||
4676 (!isRTL && state.textAlign == CanvasTextAlign::Start) ||
4677 (isRTL && state.textAlign == CanvasTextAlign::End)) {
4678 anchorX = 0;
4679 } else {
4680 anchorX = 1;
4683 float offsetX = anchorX * totalWidth;
4684 processor.mPt.x -= offsetX;
4686 gfx::ShapedTextFlags runOrientation =
4687 (processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
4688 nsFontMetrics::FontOrientation fontOrientation =
4689 (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
4690 runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT)
4691 ? nsFontMetrics::eVertical
4692 : nsFontMetrics::eHorizontal;
4694 // offset pt.y (or pt.x, for vertical text) based on text baseline
4695 gfxFloat baselineAnchor;
4697 switch (state.textBaseline) {
4698 case CanvasTextBaseline::Hanging:
4699 baselineAnchor = font->GetBaselines(fontOrientation).mHanging;
4700 break;
4701 case CanvasTextBaseline::Top:
4702 baselineAnchor = fontMetrics.emAscent;
4703 break;
4704 case CanvasTextBaseline::Middle:
4705 baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
4706 break;
4707 case CanvasTextBaseline::Alphabetic:
4708 baselineAnchor = font->GetBaselines(fontOrientation).mAlphabetic;
4709 break;
4710 case CanvasTextBaseline::Ideographic:
4711 baselineAnchor = font->GetBaselines(fontOrientation).mIdeographic;
4712 break;
4713 case CanvasTextBaseline::Bottom:
4714 baselineAnchor = -fontMetrics.emDescent;
4715 break;
4716 default:
4717 MOZ_CRASH("GFX: unexpected TextBaseline");
4720 // We can't query the textRun directly, as it may not have been created yet;
4721 // so instead we check the flags that will be used to initialize it.
4722 if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) {
4723 if (fontOrientation == nsFontMetrics::eVertical) {
4724 // Adjust to account for mTextRun being shaped using center baseline
4725 // rather than alphabetic.
4726 baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
4728 processor.mPt.x -= baselineAnchor;
4729 } else {
4730 processor.mPt.y += baselineAnchor;
4733 // if only measuring, don't need to do any more work
4734 if (aOp == TextDrawOperation::MEASURE) {
4735 aError = NS_OK;
4736 // Note that actualBoundingBoxLeft measures the distance in the leftward
4737 // direction, so its sign is reversed from our usual physical coordinates.
4738 double actualBoundingBoxLeft = offsetX - processor.mBoundingBox.X();
4739 double actualBoundingBoxRight = processor.mBoundingBox.XMost() - offsetX;
4740 double actualBoundingBoxAscent =
4741 -processor.mBoundingBox.Y() - baselineAnchor;
4742 double actualBoundingBoxDescent =
4743 processor.mBoundingBox.YMost() + baselineAnchor;
4744 auto baselines = font->GetBaselines(fontOrientation);
4745 return new TextMetrics(
4746 totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight,
4747 fontMetrics.maxAscent - baselineAnchor, // fontBBAscent
4748 fontMetrics.maxDescent + baselineAnchor, // fontBBDescent
4749 actualBoundingBoxAscent, actualBoundingBoxDescent,
4750 fontMetrics.emAscent - baselineAnchor, // emHeightAscent
4751 fontMetrics.emDescent + baselineAnchor, // emHeightDescent
4752 baselines.mHanging - baselineAnchor,
4753 baselines.mAlphabetic - baselineAnchor,
4754 baselines.mIdeographic - baselineAnchor);
4757 // If we did not actually calculate bounds, set up a simple bounding box
4758 // based on the text position and advance.
4759 if (!doCalculateBounds) {
4760 processor.mBoundingBox.width = totalWidth;
4761 processor.mBoundingBox.MoveBy(gfxPoint(processor.mPt.x, processor.mPt.y));
4764 processor.mPt.x *= processor.mAppUnitsPerDevPixel;
4765 processor.mPt.y *= processor.mAppUnitsPerDevPixel;
4767 EnsureTarget();
4768 if (!IsTargetValid()) {
4769 aError = NS_ERROR_FAILURE;
4770 return nullptr;
4773 Matrix oldTransform = mTarget->GetTransform();
4774 // if text is over aMaxWidth, then scale the text horizontally such that its
4775 // width is precisely aMaxWidth
4776 if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 &&
4777 totalWidth > aMaxWidth.Value()) {
4778 Matrix newTransform = oldTransform;
4780 // Translate so that the anchor point is at 0,0, then scale and then
4781 // translate back.
4782 newTransform.PreTranslate(aX, 0);
4783 newTransform.PreScale(aMaxWidth.Value() / totalWidth, 1);
4784 newTransform.PreTranslate(-aX, 0);
4785 /* we do this to avoid an ICE in the android compiler */
4786 Matrix androidCompilerBug = newTransform;
4787 mTarget->SetTransform(androidCompilerBug);
4790 // save the previous bounding box
4791 gfxRect boundingBox = processor.mBoundingBox;
4793 // don't ever need to measure the bounding box twice
4794 processor.mDoMeasureBoundingBox = false;
4796 aError = nsBidiPresUtils::ProcessText(
4797 textToDraw.get(), textToDraw.Length(),
4798 isRTL ? intl::BidiEmbeddingLevel::RTL() : intl::BidiEmbeddingLevel::LTR(),
4799 presContext, processor, nsBidiPresUtils::MODE_DRAW, nullptr, 0, nullptr,
4800 mBidiEngine);
4802 if (aError.Failed()) {
4803 return nullptr;
4806 mTarget->SetTransform(oldTransform);
4808 if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
4809 !doCalculateBounds) {
4810 RedrawUser(boundingBox);
4811 } else {
4812 Redraw();
4815 aError = NS_OK;
4816 return nullptr;
4819 gfxFontGroup* CanvasRenderingContext2D::GetCurrentFontStyle() {
4820 // Use lazy (re)initialization for the fontGroup since it's rather expensive.
4822 RefPtr<PresShell> presShell = GetPresShell();
4823 nsPresContext* presContext =
4824 presShell ? presShell->GetPresContext() : nullptr;
4826 // If we have a cached fontGroup, check that it is valid for the current
4827 // prescontext; if not, we need to discard and re-create it.
4828 RefPtr<gfxFontGroup>& fontGroup = CurrentState().fontGroup;
4829 if (fontGroup) {
4830 if (fontGroup->GetPresContext() != presContext) {
4831 fontGroup = nullptr;
4835 if (!fontGroup) {
4836 ErrorResult err;
4837 constexpr auto kDefaultFontStyle = "10px sans-serif"_ns;
4838 const float kDefaultFontSize = 10.0;
4839 // If the font has already been set, we're re-creating the fontGroup
4840 // and should re-use the existing font attribute; if not, we initialize
4841 // it to the canvas default.
4842 const nsCString& currentFont = CurrentState().font;
4843 bool fontUpdated = SetFontInternal(
4844 currentFont.IsEmpty() ? kDefaultFontStyle : currentFont, err);
4845 if (err.Failed() || !fontUpdated) {
4846 err.SuppressException();
4847 // XXX Should we get a default lang from the prescontext or something?
4848 nsAtom* language = nsGkAtoms::x_western;
4849 bool explicitLanguage = false;
4850 gfxFontStyle style;
4851 style.size = kDefaultFontSize;
4852 int32_t perDevPixel, perCSSPixel;
4853 GetAppUnitsValues(&perDevPixel, &perCSSPixel);
4854 gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel);
4855 const auto* sans =
4856 Servo_FontFamily_Generic(StyleGenericFontFamily::SansSerif);
4857 fontGroup = new gfxFontGroup(
4858 presContext, sans->families, &style, language, explicitLanguage,
4859 presContext ? presContext->GetTextPerfMetrics() : nullptr, nullptr,
4860 devToCssSize, StyleFontVariantEmoji::Normal);
4861 if (fontGroup) {
4862 CurrentState().font = kDefaultFontStyle;
4863 } else {
4864 NS_ERROR("Default canvas font is invalid");
4867 } else {
4868 // The fontgroup needs to check if its cached families/faces are valid.
4869 fontGroup->CheckForUpdatedPlatformList();
4872 return fontGroup;
4876 // line dash styles
4879 void CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments,
4880 ErrorResult& aRv) {
4881 nsTArray<mozilla::gfx::Float> dash;
4883 for (uint32_t x = 0; x < aSegments.Length(); x++) {
4884 if (aSegments[x] < 0.0) {
4885 // Pattern elements must be finite "numbers" >= 0, with "finite"
4886 // taken care of by WebIDL
4887 return;
4890 if (!dash.AppendElement(aSegments[x], fallible)) {
4891 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4892 return;
4895 if (aSegments.Length() %
4896 2) { // If the number of elements is odd, concatenate again
4897 for (uint32_t x = 0; x < aSegments.Length(); x++) {
4898 if (!dash.AppendElement(aSegments[x], fallible)) {
4899 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
4900 return;
4905 CurrentState().dash = std::move(dash);
4908 void CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
4909 const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
4910 aSegments.Clear();
4912 for (uint32_t x = 0; x < dash.Length(); x++) {
4913 aSegments.AppendElement(dash[x]);
4917 void CanvasRenderingContext2D::SetLineDashOffset(double aOffset) {
4918 CurrentState().dashOffset = aOffset;
4921 double CanvasRenderingContext2D::LineDashOffset() const {
4922 return CurrentState().dashOffset;
4925 bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX,
4926 double aY,
4927 const CanvasWindingRule& aWinding,
4928 nsIPrincipal& aSubjectPrincipal) {
4929 if (!FloatValidate(aX, aY)) {
4930 return false;
4933 // Check for site-specific permission and return false if no permission.
4934 if (mCanvasElement) {
4935 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
4936 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
4937 aSubjectPrincipal)) {
4938 return false;
4940 } else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting(
4941 RFPTarget::CanvasImageExtractionPrompt)) {
4942 return false;
4945 EnsureUserSpacePath(aWinding);
4946 if (!mPath) {
4947 return false;
4950 return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
4953 bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx,
4954 const CanvasPath& aPath, double aX,
4955 double aY,
4956 const CanvasWindingRule& aWinding,
4957 nsIPrincipal& aSubjectPrincipal) {
4958 if (!FloatValidate(aX, aY)) {
4959 return false;
4962 EnsureTarget();
4963 if (!IsTargetValid()) {
4964 return false;
4967 RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget);
4969 return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
4972 bool CanvasRenderingContext2D::IsPointInStroke(
4973 JSContext* aCx, double aX, double aY, nsIPrincipal& aSubjectPrincipal) {
4974 if (!FloatValidate(aX, aY)) {
4975 return false;
4978 // Check for site-specific permission and return false if no permission.
4979 if (mCanvasElement) {
4980 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
4981 if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
4982 aSubjectPrincipal)) {
4983 return false;
4985 } else if (mOffscreenCanvas && mOffscreenCanvas->ShouldResistFingerprinting(
4986 RFPTarget::CanvasImageExtractionPrompt)) {
4987 return false;
4990 EnsureUserSpacePath();
4991 if (!mPath) {
4992 return false;
4995 const ContextState& state = CurrentState();
4997 StrokeOptions strokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin),
4998 CanvasToGfx(state.lineCap), state.miterLimit,
4999 state.dash.Length(), state.dash.Elements(),
5000 state.dashOffset);
5002 return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY),
5003 mTarget->GetTransform());
5006 bool CanvasRenderingContext2D::IsPointInStroke(
5007 JSContext* aCx, const CanvasPath& aPath, double aX, double aY,
5008 nsIPrincipal& aSubjectPrincipal) {
5009 if (!FloatValidate(aX, aY)) {
5010 return false;
5013 EnsureTarget();
5014 if (!IsTargetValid()) {
5015 return false;
5018 RefPtr<gfx::Path> tempPath =
5019 aPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
5021 const ContextState& state = CurrentState();
5023 StrokeOptions strokeOptions(state.lineWidth, CanvasToGfx(state.lineJoin),
5024 CanvasToGfx(state.lineCap), state.miterLimit,
5025 state.dash.Length(), state.dash.Elements(),
5026 state.dashOffset);
5028 return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY),
5029 mTarget->GetTransform());
5032 // Returns a surface that contains only the part needed to draw aSourceRect.
5033 // On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
5034 // relative to the returned surface.
5035 static already_AddRefed<SourceSurface> ExtractSubrect(SourceSurface* aSurface,
5036 gfx::Rect* aSourceRect,
5037 DrawTarget* aTargetDT) {
5038 gfx::Rect roundedOutSourceRect = *aSourceRect;
5039 roundedOutSourceRect.RoundOut();
5040 gfx::IntRect roundedOutSourceRectInt;
5041 if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
5042 RefPtr<SourceSurface> surface(aSurface);
5043 return surface.forget();
5046 // Try to extract an optimized sub-surface.
5047 if (RefPtr<SourceSurface> surface =
5048 aSurface->ExtractSubrect(roundedOutSourceRectInt)) {
5049 *aSourceRect -= roundedOutSourceRect.TopLeft();
5050 return surface.forget();
5053 RefPtr<DrawTarget> subrectDT = aTargetDT->CreateSimilarDrawTarget(
5054 roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
5056 if (subrectDT) {
5057 // See bug 1524554.
5058 subrectDT->ClearRect(gfx::Rect());
5061 if (!subrectDT || !subrectDT->IsValid()) {
5062 RefPtr<SourceSurface> surface(aSurface);
5063 return surface.forget();
5066 *aSourceRect -= roundedOutSourceRect.TopLeft();
5068 subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
5069 return subrectDT->Snapshot();
5073 // image
5076 static void ClipImageDimension(double& aSourceCoord, double& aSourceSize,
5077 double& aClipOriginCoord, double& aClipSize,
5078 double& aDestCoord, double& aDestSize) {
5079 double scale = aDestSize / aSourceSize;
5080 double relativeCoord = aSourceCoord - aClipOriginCoord;
5081 if (relativeCoord < 0.0) {
5082 double destEnd = aDestCoord + aDestSize;
5083 aDestCoord -= relativeCoord * scale;
5084 aDestSize = destEnd - aDestCoord;
5085 aSourceSize += relativeCoord;
5086 aSourceCoord = aClipOriginCoord;
5088 double delta = aClipSize - (relativeCoord + aSourceSize);
5089 if (delta < 0.0) {
5090 aDestSize += delta * scale;
5091 aSourceSize = aClipSize - relativeCoord;
5095 // Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
5096 // to pull a SourceSurface from our cache. This allows us to avoid
5097 // reoptimizing surfaces if content and canvas backends are different.
5098 SurfaceFromElementResult CanvasRenderingContext2D::CachedSurfaceFromElement(
5099 Element* aElement) {
5100 SurfaceFromElementResult res;
5101 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
5102 if (!imageLoader) {
5103 return res;
5106 nsCOMPtr<imgIRequest> imgRequest;
5107 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
5108 getter_AddRefs(imgRequest));
5109 if (!imgRequest) {
5110 return res;
5113 uint32_t status = 0;
5114 if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
5115 !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
5116 return res;
5119 nsCOMPtr<nsIPrincipal> principal;
5120 if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
5121 !principal) {
5122 return res;
5125 if (NS_FAILED(imgRequest->GetHadCrossOriginRedirects(
5126 &res.mHadCrossOriginRedirects))) {
5127 return res;
5130 res.mSourceSurface = CanvasImageCache::LookupAllCanvas(aElement, mTarget);
5131 if (!res.mSourceSurface) {
5132 return res;
5135 res.mCORSUsed = nsLayoutUtils::ImageRequestUsesCORS(imgRequest);
5136 res.mSize = res.mIntrinsicSize = res.mSourceSurface->GetSize();
5137 res.mPrincipal = std::move(principal);
5138 res.mImageRequest = std::move(imgRequest);
5139 res.mIsWriteOnly = CheckWriteOnlySecurity(res.mCORSUsed, res.mPrincipal,
5140 res.mHadCrossOriginRedirects);
5142 return res;
5145 static void SwapScaleWidthHeightForRotation(gfx::Rect& aRect,
5146 VideoInfo::Rotation aDegrees) {
5147 if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
5148 aDegrees == VideoInfo::Rotation::kDegree_270) {
5149 std::swap(aRect.width, aRect.height);
5153 static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
5154 gfxFloat aRotatedHeight,
5155 VideoInfo::Rotation aDegrees) {
5156 Matrix shiftVideoCenterToOrigin;
5157 if (aDegrees == VideoInfo::Rotation::kDegree_90 ||
5158 aDegrees == VideoInfo::Rotation::kDegree_270) {
5159 shiftVideoCenterToOrigin =
5160 Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
5161 } else {
5162 shiftVideoCenterToOrigin =
5163 Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
5166 auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI;
5167 Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle));
5168 Matrix shiftLeftTopToOrigin =
5169 Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
5170 return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
5173 // drawImage(in HTMLImageElement image, in float dx, in float dy);
5174 // -- render image from 0,0 at dx,dy top-left coords
5175 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw,
5176 // in float dh);
5177 // -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh
5178 // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw,
5179 // in float sh, in float dx, in float dy, in float dw, in float dh);
5180 // -- render the region defined by (sx,sy,sw,wh) in image-local space into the
5181 // region (dx,dy,dw,dh) on the canvas
5183 // If only dx and dy are passed in then optional_argc should be 0. If only
5184 // dx, dy, dw and dh are passed in then optional_argc should be 2. The only
5185 // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
5186 // are all passed in.
5188 void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
5189 double aSx, double aSy, double aSw,
5190 double aSh, double aDx, double aDy,
5191 double aDw, double aDh,
5192 uint8_t aOptional_argc,
5193 ErrorResult& aError) {
5194 MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6);
5196 if (!ValidateRect(aDx, aDy, aDw, aDh, true)) {
5197 return;
5199 if (aOptional_argc == 6) {
5200 if (!ValidateRect(aSx, aSy, aSw, aSh, true)) {
5201 return;
5205 RefPtr<SourceSurface> srcSurf;
5206 gfx::IntSize imgSize;
5207 gfx::IntSize intrinsicImgSize;
5208 Maybe<IntRect> cropRect;
5209 Element* element = nullptr;
5210 OffscreenCanvas* offscreenCanvas = nullptr;
5211 VideoFrame* videoFrame = nullptr;
5213 EnsureTarget();
5214 if (!IsTargetValid()) {
5215 return;
5218 if (aImage.IsHTMLCanvasElement()) {
5219 HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement();
5220 element = canvas;
5221 nsIntSize size = canvas->GetSize();
5222 if (size.width == 0 || size.height == 0) {
5223 return aError.ThrowInvalidStateError("Passed-in canvas is empty");
5226 if (canvas->IsWriteOnly()) {
5227 SetWriteOnly();
5229 } else if (aImage.IsOffscreenCanvas()) {
5230 offscreenCanvas = &aImage.GetAsOffscreenCanvas();
5231 nsIntSize size = offscreenCanvas->GetWidthHeight();
5232 if (size.IsEmpty()) {
5233 return aError.ThrowInvalidStateError("Passed-in canvas is empty");
5236 srcSurf = offscreenCanvas->GetSurfaceSnapshot();
5237 if (srcSurf) {
5238 imgSize = intrinsicImgSize = srcSurf->GetSize();
5241 if (offscreenCanvas->IsWriteOnly()) {
5242 SetWriteOnly();
5244 } else if (aImage.IsImageBitmap()) {
5245 ImageBitmap& imageBitmap = aImage.GetAsImageBitmap();
5246 srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
5248 if (!srcSurf) {
5249 if (imageBitmap.IsClosed()) {
5250 aError.ThrowInvalidStateError("Passed-in ImageBitmap is closed");
5252 return;
5255 if (imageBitmap.IsWriteOnly()) {
5256 SetWriteOnly();
5259 imgSize = intrinsicImgSize =
5260 gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
5261 } else if (aImage.IsVideoFrame()) {
5262 videoFrame = &aImage.GetAsVideoFrame();
5263 } else {
5264 if (aImage.IsHTMLImageElement()) {
5265 HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
5266 element = img;
5267 } else if (aImage.IsSVGImageElement()) {
5268 SVGImageElement* img = &aImage.GetAsSVGImageElement();
5269 element = img;
5270 } else {
5271 HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
5272 video->LogVisibility(
5273 mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
5274 element = video;
5277 srcSurf =
5278 CanvasImageCache::LookupCanvas(element, mCanvasElement, mTarget,
5279 &imgSize, &intrinsicImgSize, &cropRect);
5282 DirectDrawInfo drawInfo;
5284 if (!srcSurf) {
5285 // The canvas spec says that drawImage should draw the first frame
5286 // of animated images. We also don't want to rasterize vector images.
5287 uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
5288 nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS |
5289 nsLayoutUtils::SFE_ALLOW_UNCROPPED_UNSCALED;
5291 SurfaceFromElementResult res;
5292 if (offscreenCanvas) {
5293 res = nsLayoutUtils::SurfaceFromOffscreenCanvas(offscreenCanvas, sfeFlags,
5294 mTarget);
5295 } else if (videoFrame) {
5296 res = nsLayoutUtils::SurfaceFromVideoFrame(videoFrame, sfeFlags, mTarget);
5297 } else {
5298 res = CanvasRenderingContext2D::CachedSurfaceFromElement(element);
5299 if (!res.mSourceSurface) {
5300 res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
5304 srcSurf = res.GetSourceSurface();
5305 if (!srcSurf && !res.mDrawInfo.mImgContainer) {
5306 // https://html.spec.whatwg.org/#check-the-usability-of-the-image-argument:
5308 // Only throw if the request is broken and the element is an
5309 // HTMLImageElement / SVGImageElement. Note that even for those the spec
5310 // says to silently do nothing in the following cases:
5311 // - The element is still loading.
5312 // - The image is bad, but it's not in the broken state (i.e., we could
5313 // decode the headers and get the size).
5314 if (!res.mIsStillLoading && !res.mHasSize &&
5315 (aImage.IsHTMLImageElement() || aImage.IsSVGImageElement())) {
5316 aError.ThrowInvalidStateError("Passed-in image is \"broken\"");
5317 } else if (videoFrame) {
5318 aError.ThrowInvalidStateError("Passed-in video frame is \"broken\"");
5320 return;
5323 imgSize = res.mSize;
5324 intrinsicImgSize = res.mIntrinsicSize;
5325 cropRect = res.mCropRect;
5326 DoSecurityCheck(res.mPrincipal, res.mIsWriteOnly, res.mCORSUsed);
5328 if (srcSurf) {
5329 if (res.mImageRequest) {
5330 CanvasImageCache::NotifyDrawImage(element, mCanvasElement, mTarget,
5331 srcSurf, imgSize, intrinsicImgSize,
5332 cropRect);
5334 } else {
5335 drawInfo = res.mDrawInfo;
5339 double clipOriginX, clipOriginY, clipWidth, clipHeight;
5340 if (cropRect) {
5341 clipOriginX = cropRect.ref().X();
5342 clipOriginY = cropRect.ref().Y();
5343 clipWidth = cropRect.ref().Width();
5344 clipHeight = cropRect.ref().Height();
5345 } else {
5346 clipOriginX = clipOriginY = 0.0;
5347 clipWidth = imgSize.width;
5348 clipHeight = imgSize.height;
5351 // Any provided coordinates are in the display space, or the same as the
5352 // intrinsic size. In order to get to the surface coordinate space, we may
5353 // need to adjust for scaling and/or cropping. If no source coordinates are
5354 // provided, then we can just directly use the actual surface size.
5355 if (aOptional_argc == 0) {
5356 aSx = clipOriginX;
5357 aSy = clipOriginY;
5358 aSw = clipWidth;
5359 aSh = clipHeight;
5360 aDw = (double)intrinsicImgSize.width;
5361 aDh = (double)intrinsicImgSize.height;
5362 } else if (aOptional_argc == 2) {
5363 aSx = clipOriginX;
5364 aSy = clipOriginY;
5365 aSw = clipWidth;
5366 aSh = clipHeight;
5367 } else if (cropRect || intrinsicImgSize != imgSize) {
5368 // We need to first scale between the cropped size and the intrinsic size,
5369 // and then adjust for the offset from the crop rect.
5370 double scaleXToCrop = clipWidth / intrinsicImgSize.width;
5371 double scaleYToCrop = clipHeight / intrinsicImgSize.height;
5372 aSx = aSx * scaleXToCrop + clipOriginX;
5373 aSy = aSy * scaleYToCrop + clipOriginY;
5374 aSw = aSw * scaleXToCrop;
5375 aSh = aSh * scaleYToCrop;
5378 if (aSw == 0.0 || aSh == 0.0) {
5379 return;
5382 ClipImageDimension(aSx, aSw, clipOriginX, clipWidth, aDx, aDw);
5383 ClipImageDimension(aSy, aSh, clipOriginY, clipHeight, aDy, aDh);
5385 if (aSw <= 0.0 || aSh <= 0.0 || aDw <= 0.0 || aDh <= 0.0) {
5386 // source and/or destination are fully clipped, so nothing is painted
5387 return;
5390 // Per spec, the smoothing setting applies only to scaling up a bitmap image.
5391 // When down-scaling the user agent is free to choose whether or not to smooth
5392 // the image. Nearest sampling when down-scaling is rarely desirable and
5393 // smoothing when down-scaling matches chromium's behavior.
5394 // If any dimension is up-scaled, we consider the image as being up-scaled.
5395 auto scale = mTarget->GetTransform().ScaleFactors();
5396 bool isDownScale =
5397 aDw * Abs(scale.xScale) < aSw && aDh * Abs(scale.yScale) < aSh;
5399 SamplingFilter samplingFilter;
5400 AntialiasMode antialiasMode;
5402 if (CurrentState().imageSmoothingEnabled || isDownScale) {
5403 samplingFilter = gfx::SamplingFilter::LINEAR;
5404 antialiasMode = AntialiasMode::DEFAULT;
5405 } else {
5406 samplingFilter = gfx::SamplingFilter::POINT;
5407 antialiasMode = AntialiasMode::NONE;
5410 const bool needBounds = NeedToCalculateBounds();
5411 if (!IsTargetValid()) {
5412 return;
5414 gfx::Rect bounds;
5415 if (needBounds) {
5416 bounds = gfx::Rect(aDx, aDy, aDw, aDh);
5417 bounds = mTarget->GetTransform().TransformBounds(bounds);
5420 if (!IsTargetValid()) {
5421 aError.Throw(NS_ERROR_FAILURE);
5422 return;
5425 if (srcSurf) {
5426 gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
5427 if (element == mCanvasElement) {
5428 // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
5429 // trigger a COW copy of the whole canvas into srcSurf. That's a huge
5430 // waste if sourceRect doesn't cover the whole canvas.
5431 // We avoid copying the whole canvas by manually copying just the part
5432 // that we need.
5433 srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
5436 AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds, true);
5437 if (!tempTarget) {
5438 gfxWarning() << "Invalid adjusted target in Canvas2D "
5439 << gfx::hexa((DrawTarget*)mTarget) << ", "
5440 << NeedToDrawShadow() << NeedToApplyFilter();
5441 return;
5444 auto op = tempTarget.UsedOperation();
5445 if (!IsTargetValid() || !tempTarget) {
5446 return;
5449 VideoInfo::Rotation rotationDeg = VideoInfo::Rotation::kDegree_0;
5450 if (HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(element)) {
5451 rotationDeg = video->RotationDegrees();
5454 gfx::Rect destRect(aDx, aDy, aDw, aDh);
5456 Matrix transform;
5457 if (rotationDeg != VideoInfo::Rotation::kDegree_0) {
5458 Matrix preTransform = ComputeRotationMatrix(aDw, aDh, rotationDeg);
5459 transform = preTransform * Matrix::Translation(aDx, aDy);
5461 SwapScaleWidthHeightForRotation(destRect, rotationDeg);
5462 // When rotation exists, aDx, aDy is handled by transform, Since aDest.x
5463 // aDest.y handling of DrawSurface() does not care about the rotation.
5464 destRect.x = 0;
5465 destRect.y = 0;
5467 Matrix currentTransform = tempTarget->GetTransform();
5468 transform *= currentTransform;
5470 tempTarget->SetTransform(transform);
5472 tempTarget.DrawSurface(
5473 srcSurf, destRect, sourceRect,
5474 DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED),
5475 DrawOptions(CurrentState().globalAlpha, op, antialiasMode));
5477 tempTarget->SetTransform(currentTransform);
5479 } else {
5480 DrawDirectlyToCanvas(drawInfo, &bounds, gfx::Rect(aDx, aDy, aDw, aDh),
5481 gfx::Rect(aSx, aSy, aSw, aSh), imgSize);
5484 RedrawUser(gfxRect(aDx, aDy, aDw, aDh));
5487 void CanvasRenderingContext2D::DrawDirectlyToCanvas(
5488 const DirectDrawInfo& aImage, gfx::Rect* aBounds, gfx::Rect aDest,
5489 gfx::Rect aSrc, gfx::IntSize aImgSize) {
5490 MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0,
5491 "Need positive source width and height");
5493 AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr : aBounds);
5494 if (!tempTarget || !tempTarget->IsValid()) {
5495 return;
5498 // Get any existing transforms on the context, including transformations used
5499 // for context shadow.
5500 Matrix matrix = tempTarget->GetTransform();
5501 gfxMatrix contextMatrix = ThebesMatrix(matrix);
5502 MatrixScalesDouble contextScale = contextMatrix.ScaleFactors();
5504 // Scale the dest rect to include the context scale.
5505 aDest.Scale((float)contextScale.xScale, (float)contextScale.yScale);
5507 // Scale the image size to the dest rect, and adjust the source rect to match.
5508 MatrixScalesDouble scale(aDest.width / aSrc.width,
5509 aDest.height / aSrc.height);
5510 IntSize scaledImageSize =
5511 IntSize::Ceil(static_cast<float>(scale.xScale * aImgSize.width),
5512 static_cast<float>(scale.yScale * aImgSize.height));
5513 aSrc.Scale(static_cast<float>(scale.xScale),
5514 static_cast<float>(scale.yScale));
5516 // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
5517 // the matrix even though this is a temp gfxContext.
5518 AutoRestoreTransform autoRestoreTransform(mTarget);
5520 gfxContext context(tempTarget);
5521 context.SetMatrixDouble(
5522 contextMatrix
5523 .PreScale(1.0 / contextScale.xScale, 1.0 / contextScale.yScale)
5524 .PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y));
5526 context.SetOp(tempTarget.UsedOperation());
5528 // FLAG_CLAMP is added for increased performance, since we never tile here.
5529 uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
5531 // XXX hmm is scaledImageSize really in CSS pixels?
5532 CSSIntSize sz(scaledImageSize.width, scaledImageSize.height);
5533 SVGImageContext svgContext(Some(sz));
5535 auto result = aImage.mImgContainer->Draw(
5536 &context, scaledImageSize,
5537 ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
5538 aImage.mWhichFrame, SamplingFilter::GOOD, svgContext, modifiedFlags,
5539 CurrentState().globalAlpha);
5541 if (result != ImgDrawResult::SUCCESS) {
5542 NS_WARNING("imgIContainer::Draw failed");
5546 void CanvasRenderingContext2D::SetGlobalCompositeOperation(
5547 const nsAString& aOp, ErrorResult& aError) {
5548 CompositionOp comp_op;
5550 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
5551 if (aOp.EqualsLiteral(cvsop)) comp_op = CompositionOp::OP_##op2d;
5553 CANVAS_OP_TO_GFX_OP("clear", CLEAR)
5554 else CANVAS_OP_TO_GFX_OP("copy", SOURCE)
5555 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
5556 else CANVAS_OP_TO_GFX_OP("source-in", IN)
5557 else CANVAS_OP_TO_GFX_OP("source-out", OUT)
5558 else CANVAS_OP_TO_GFX_OP("source-over", OVER)
5559 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
5560 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
5561 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
5562 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
5563 else CANVAS_OP_TO_GFX_OP("lighter", ADD)
5564 else CANVAS_OP_TO_GFX_OP("xor", XOR)
5565 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
5566 else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
5567 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
5568 else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
5569 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
5570 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
5571 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
5572 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
5573 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
5574 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
5575 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
5576 else CANVAS_OP_TO_GFX_OP("hue", HUE)
5577 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
5578 else CANVAS_OP_TO_GFX_OP("color", COLOR)
5579 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
5580 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
5581 else return;
5583 #undef CANVAS_OP_TO_GFX_OP
5584 CurrentState().op = comp_op;
5587 void CanvasRenderingContext2D::GetGlobalCompositeOperation(
5588 nsAString& aOp, ErrorResult& aError) {
5589 CompositionOp comp_op = CurrentState().op;
5591 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
5592 if (comp_op == CompositionOp::OP_##op2d) aOp.AssignLiteral(cvsop);
5594 CANVAS_OP_TO_GFX_OP("clear", CLEAR)
5595 else CANVAS_OP_TO_GFX_OP("copy", SOURCE)
5596 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
5597 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
5598 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
5599 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
5600 else CANVAS_OP_TO_GFX_OP("lighter", ADD)
5601 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
5602 else CANVAS_OP_TO_GFX_OP("source-in", IN)
5603 else CANVAS_OP_TO_GFX_OP("source-out", OUT)
5604 else CANVAS_OP_TO_GFX_OP("source-over", OVER)
5605 else CANVAS_OP_TO_GFX_OP("xor", XOR)
5606 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
5607 else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
5608 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
5609 else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
5610 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
5611 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
5612 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
5613 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
5614 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
5615 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
5616 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
5617 else CANVAS_OP_TO_GFX_OP("hue", HUE)
5618 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
5619 else CANVAS_OP_TO_GFX_OP("color", COLOR)
5620 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
5621 else {
5622 aError.Throw(NS_ERROR_FAILURE);
5625 #undef CANVAS_OP_TO_GFX_OP
5628 void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow,
5629 double aX, double aY, double aW,
5630 double aH, const nsACString& aBgColor,
5631 uint32_t aFlags,
5632 nsIPrincipal& aSubjectPrincipal,
5633 ErrorResult& aError) {
5634 if (int32_t(aW) == 0 || int32_t(aH) == 0) {
5635 return;
5638 // protect against too-large surfaces that will cause allocation
5639 // or overflow issues
5640 if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) {
5641 aError.Throw(NS_ERROR_FAILURE);
5642 return;
5645 Document* doc = aWindow.GetExtantDoc();
5646 if (doc && aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) {
5647 doc->WarnOnceAbout(
5648 DeprecatedOperations::eDrawWindowCanvasRenderingContext2D);
5651 // Flush layout updates
5652 if (!(aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) {
5653 nsContentUtils::FlushLayoutForTree(aWindow.GetOuterWindow());
5656 CompositionOp op = CurrentState().op;
5657 bool discardContent =
5658 GlobalAlpha() == 1.0f &&
5659 (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE);
5660 const gfx::Rect drawRect(aX, aY, aW, aH);
5661 EnsureTarget(discardContent ? &drawRect : nullptr);
5662 if (!IsTargetValid()) {
5663 return;
5666 RefPtr<nsPresContext> presContext;
5667 nsIDocShell* docshell = aWindow.GetDocShell();
5668 if (docshell) {
5669 presContext = docshell->GetPresContext();
5671 if (!presContext) {
5672 aError.Throw(NS_ERROR_FAILURE);
5673 return;
5676 Maybe<nscolor> backgroundColor = ParseColor(aBgColor);
5677 if (!backgroundColor) {
5678 aError.Throw(NS_ERROR_FAILURE);
5679 return;
5682 nsRect r(nsPresContext::CSSPixelsToAppUnits((float)aX),
5683 nsPresContext::CSSPixelsToAppUnits((float)aY),
5684 nsPresContext::CSSPixelsToAppUnits((float)aW),
5685 nsPresContext::CSSPixelsToAppUnits((float)aH));
5686 RenderDocumentFlags renderDocFlags =
5687 (RenderDocumentFlags::IgnoreViewportScrolling |
5688 RenderDocumentFlags::DocumentRelative);
5689 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET) {
5690 renderDocFlags |= RenderDocumentFlags::DrawCaret;
5692 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW) {
5693 renderDocFlags &= ~(RenderDocumentFlags::IgnoreViewportScrolling |
5694 RenderDocumentFlags::DocumentRelative);
5696 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS) {
5697 renderDocFlags |= RenderDocumentFlags::UseWidgetLayers;
5699 if (aFlags &
5700 CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES) {
5701 renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages;
5703 if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH) {
5704 renderDocFlags |= RenderDocumentFlags::DrawWindowNotFlushing;
5707 // gfxContext-over-Azure may modify the DrawTarget's transform, so
5708 // save and restore it
5709 Matrix matrix = mTarget->GetTransform();
5710 double sw = matrix._11 * aW;
5711 double sh = matrix._22 * aH;
5712 if (!sw || !sh) {
5713 return;
5716 Maybe<gfxContext> thebes;
5717 RefPtr<DrawTarget> drawDT;
5718 // Rendering directly is faster and can be done if mTarget supports Azure
5719 // and does not need alpha blending.
5720 // Since the pre-transaction callback calls ReturnTarget, we can't have a
5721 // gfxContext wrapped around it when using a shared buffer provider because
5722 // the DrawTarget's shared buffer may be unmapped in ReturnTarget.
5723 op = CompositionOp::OP_ADD;
5724 if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
5725 GlobalAlpha() == 1.0f) {
5726 op = CurrentState().op;
5727 if (!IsTargetValid()) {
5728 aError.Throw(NS_ERROR_FAILURE);
5729 return;
5732 if (op == CompositionOp::OP_OVER &&
5733 (!mBufferProvider || !mBufferProvider->IsShared())) {
5734 thebes.emplace(mTarget);
5735 thebes.ref().SetMatrix(matrix);
5736 } else {
5737 IntSize dtSize = IntSize::Ceil(sw, sh);
5738 if (!Factory::AllowedSurfaceSize(dtSize)) {
5739 // attempt to limit the DT to what will actually cover the target
5740 Size limitSize(mTarget->GetSize());
5741 limitSize.Scale(matrix._11, matrix._22);
5742 dtSize = Min(dtSize, IntSize::Ceil(limitSize));
5743 // if the DT is still too big, then error
5744 if (!Factory::AllowedSurfaceSize(dtSize)) {
5745 aError.Throw(NS_ERROR_FAILURE);
5746 return;
5749 drawDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
5750 dtSize, SurfaceFormat::B8G8R8A8);
5751 if (!drawDT || !drawDT->IsValid()) {
5752 aError.Throw(NS_ERROR_FAILURE);
5753 return;
5756 thebes.emplace(drawDT);
5757 thebes.ref().SetMatrix(Matrix::Scaling(matrix._11, matrix._22));
5759 MOZ_ASSERT(thebes.isSome());
5761 RefPtr<PresShell> presShell = presContext->PresShell();
5763 Unused << presShell->RenderDocument(r, renderDocFlags, *backgroundColor,
5764 &thebes.ref());
5765 // If this canvas was contained in the drawn window, the pre-transaction
5766 // callback may have returned its DT. If so, we must reacquire it here.
5767 EnsureTarget(discardContent ? &drawRect : nullptr);
5769 if (drawDT) {
5770 RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
5771 if (NS_WARN_IF(!snapshot)) {
5772 aError.Throw(NS_ERROR_FAILURE);
5773 return;
5776 op = CurrentState().op;
5777 if (!IsTargetValid()) {
5778 aError.Throw(NS_ERROR_FAILURE);
5779 return;
5781 gfx::Rect destRect(0, 0, aW, aH);
5782 gfx::Rect sourceRect(0, 0, sw, sh);
5783 mTarget->DrawSurface(snapshot, destRect, sourceRect,
5784 DrawSurfaceOptions(gfx::SamplingFilter::POINT),
5785 DrawOptions(GlobalAlpha(), op, AntialiasMode::NONE));
5786 } else {
5787 mTarget->SetTransform(matrix);
5790 // note that x and y are coordinates in the document that
5791 // we're drawing; x and y are drawn to 0,0 in current user
5792 // space.
5793 RedrawUser(gfxRect(0, 0, aW, aH));
5797 // device pixel getting/setting
5800 already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData(
5801 JSContext* aCx, int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
5802 nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) {
5803 if (!mCanvasElement && !mDocShell && !mOffscreenCanvas) {
5804 NS_ERROR("No canvas element and no docshell in GetImageData!!!");
5805 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
5806 return nullptr;
5809 // Check only if we have a canvas element; if we were created with a docshell,
5810 // then it's special internal use.
5811 if (IsWriteOnly() ||
5812 (mCanvasElement && !mCanvasElement->CallerCanRead(aSubjectPrincipal)) ||
5813 (mOffscreenCanvas &&
5814 !mOffscreenCanvas->CallerCanRead(aSubjectPrincipal))) {
5815 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
5816 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
5817 return nullptr;
5820 if (!aSw || !aSh) {
5821 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
5822 return nullptr;
5825 // Handle negative width and height by flipping the rectangle over in the
5826 // relevant direction.
5827 uint32_t w, h;
5828 if (aSw < 0) {
5829 w = -aSw;
5830 aSx -= w;
5831 } else {
5832 w = aSw;
5834 if (aSh < 0) {
5835 h = -aSh;
5836 aSy -= h;
5837 } else {
5838 h = aSh;
5841 if (w == 0) {
5842 w = 1;
5844 if (h == 0) {
5845 h = 1;
5848 JS::Rooted<JSObject*> array(aCx);
5849 aError = GetImageDataArray(aCx, aSx, aSy, w, h, aSubjectPrincipal,
5850 array.address());
5851 if (aError.Failed()) {
5852 return nullptr;
5854 MOZ_ASSERT(array);
5855 return MakeAndAddRef<ImageData>(w, h, *array);
5858 static IntRect ClipImageDataTransfer(IntRect& aSrc, const IntPoint& aDestOffset,
5859 const IntSize& aDestBounds) {
5860 IntRect dest = aSrc;
5861 dest.SafeMoveBy(aDestOffset);
5862 dest = IntRect(IntPoint(0, 0), aDestBounds).SafeIntersect(dest);
5864 aSrc = aSrc.SafeIntersect(dest - aDestOffset);
5865 return aSrc + aDestOffset;
5868 nsresult CanvasRenderingContext2D::GetImageDataArray(
5869 JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight,
5870 nsIPrincipal& aSubjectPrincipal, JSObject** aRetval) {
5871 MOZ_ASSERT(aWidth && aHeight);
5873 // Restrict the typed array length to INT32_MAX because that's all we support
5874 // in dom::TypedArray::ComputeState.
5875 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
5876 if (!len.isValid() || len.value() > INT32_MAX) {
5877 return NS_ERROR_DOM_INDEX_SIZE_ERR;
5880 CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth;
5881 CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight;
5883 if (!rightMost.isValid() || !bottomMost.isValid()) {
5884 return NS_ERROR_DOM_SYNTAX_ERR;
5887 JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value()));
5888 if (!darray) {
5889 return NS_ERROR_OUT_OF_MEMORY;
5892 if (mZero) {
5893 *aRetval = darray;
5894 return NS_OK;
5897 IntRect dstWriteRect(0, 0, aWidth, aHeight);
5898 IntRect srcReadRect = ClipImageDataTransfer(dstWriteRect, IntPoint(aX, aY),
5899 IntSize(mWidth, mHeight));
5900 if (srcReadRect.IsEmpty()) {
5901 *aRetval = darray;
5902 return NS_OK;
5905 if (!GetBufferProvider() && !EnsureTarget()) {
5906 return NS_ERROR_FAILURE;
5909 RefPtr<SourceSurface> snapshot = mBufferProvider->BorrowSnapshot();
5910 if (!snapshot) {
5911 return NS_ERROR_OUT_OF_MEMORY;
5914 RefPtr<DataSourceSurface> readback = snapshot->GetDataSurface();
5915 mBufferProvider->ReturnSnapshot(snapshot.forget());
5917 // Check for site-specific permission. This check is not needed if the
5918 // canvas was created with a docshell (that is only done for special
5919 // internal uses).
5920 bool usePlaceholder = false;
5921 if (mCanvasElement) {
5922 nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
5923 usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
5924 aSubjectPrincipal);
5925 } else if (mOffscreenCanvas) {
5926 usePlaceholder = mOffscreenCanvas->ShouldResistFingerprinting(
5927 RFPTarget::CanvasImageExtractionPrompt);
5930 // Clone the data source surface if canvas randomization is enabled. We need
5931 // to do this because we don't want to alter the actual image buffer.
5932 // Otherwise, we will provide inconsistent image data with multiple calls.
5934 // Note that we don't need to clone if we will use the place holder because
5935 // the place holder doesn't use actual image data.
5936 bool needRandomizePixels = false;
5937 if (!usePlaceholder &&
5938 ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
5939 needRandomizePixels = true;
5940 readback = CreateDataSourceSurfaceByCloning(readback);
5943 DataSourceSurface::MappedSurface rawData;
5944 if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
5945 return NS_ERROR_OUT_OF_MEMORY;
5948 do {
5949 uint8_t* randomData;
5950 if (usePlaceholder) {
5951 // Since we cannot call any GC-able functions (like requesting the RNG
5952 // service) after we call JS_GetUint8ClampedArrayData, we will
5953 // pre-generate the randomness required for GeneratePlaceholderCanvasData.
5954 randomData = TryToGenerateRandomDataForPlaceholderCanvasData();
5955 } else if (needRandomizePixels) {
5956 // Apply the random noises if canvan randomization is enabled. We don't
5957 // need to calculate random noises if we are going to use the place
5958 // holder.
5960 const IntSize size = readback->GetSize();
5961 nsRFPService::RandomizePixels(GetCookieJarSettings(), rawData.mData,
5962 size.height * size.width * 4,
5963 SurfaceFormat::A8R8G8B8_UINT32);
5966 JS::AutoCheckCannotGC nogc;
5967 bool isShared;
5968 uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
5969 MOZ_ASSERT(!isShared); // Should not happen, data was created above
5971 if (usePlaceholder) {
5972 FillPlaceholderCanvas(randomData, len.value(), data);
5973 break;
5976 uint32_t srcStride = rawData.mStride;
5977 uint8_t* src =
5978 rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
5980 uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
5982 if (mOpaque) {
5983 SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32, dst,
5984 aWidth * 4, SurfaceFormat::R8G8B8A8, dstWriteRect.Size());
5985 } else {
5986 UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32, dst,
5987 aWidth * 4, SurfaceFormat::R8G8B8A8,
5988 dstWriteRect.Size());
5990 } while (false);
5992 readback->Unmap();
5993 *aRetval = darray;
5994 return NS_OK;
5997 void CanvasRenderingContext2D::EnsureErrorTarget() {
5998 if (sErrorTarget.get()) {
5999 return;
6002 RefPtr<DrawTarget> errorTarget =
6003 gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
6004 IntSize(1, 1), SurfaceFormat::B8G8R8A8);
6005 MOZ_ASSERT(errorTarget, "Failed to allocate the error target!");
6007 sErrorTarget.set(errorTarget.forget().take());
6010 void CanvasRenderingContext2D::FillRuleChanged() {
6011 if (mPath) {
6012 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
6013 mPath = nullptr;
6017 void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx,
6018 int32_t aDy, ErrorResult& aRv) {
6019 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
6020 PutImageData_explicit(aDx, aDy, aImageData, false, 0, 0, 0, 0, aRv);
6023 void CanvasRenderingContext2D::PutImageData(ImageData& aImageData, int32_t aDx,
6024 int32_t aDy, int32_t aDirtyX,
6025 int32_t aDirtyY,
6026 int32_t aDirtyWidth,
6027 int32_t aDirtyHeight,
6028 ErrorResult& aRv) {
6029 PutImageData_explicit(aDx, aDy, aImageData, true, aDirtyX, aDirtyY,
6030 aDirtyWidth, aDirtyHeight, aRv);
6033 void CanvasRenderingContext2D::PutImageData_explicit(
6034 int32_t aX, int32_t aY, ImageData& aImageData, bool aHasDirtyRect,
6035 int32_t aDirtyX, int32_t aDirtyY, int32_t aDirtyWidth, int32_t aDirtyHeight,
6036 ErrorResult& aRv) {
6037 RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
6038 if (!arr.Init(aImageData.GetDataObject())) {
6039 return aRv.ThrowInvalidStateError(
6040 "Failed to extract Uint8ClampedArray from ImageData (security check "
6041 "failed?)");
6044 const uint32_t width = aImageData.Width();
6045 const uint32_t height = aImageData.Height();
6046 if (width == 0 || height == 0) {
6047 return aRv.ThrowInvalidStateError("Passed-in image is empty");
6050 IntRect dirtyRect;
6051 IntRect imageDataRect(0, 0, width, height);
6053 if (aHasDirtyRect) {
6054 // fix up negative dimensions
6055 if (aDirtyWidth < 0) {
6056 if (aDirtyWidth == INT_MIN) {
6057 return aRv.ThrowInvalidStateError("Dirty width is invalid");
6060 CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth;
6062 if (!checkedDirtyX.isValid()) {
6063 return aRv.ThrowInvalidStateError("Dirty width is invalid");
6066 aDirtyX = checkedDirtyX.value();
6067 aDirtyWidth = -aDirtyWidth;
6070 if (aDirtyHeight < 0) {
6071 if (aDirtyHeight == INT_MIN) {
6072 return aRv.ThrowInvalidStateError("Dirty height is invalid");
6075 CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight;
6077 if (!checkedDirtyY.isValid()) {
6078 return aRv.ThrowInvalidStateError("Dirty height is invalid");
6081 aDirtyY = checkedDirtyY.value();
6082 aDirtyHeight = -aDirtyHeight;
6085 // bound the dirty rect within the imageData rectangle
6086 dirtyRect = imageDataRect.Intersect(
6087 IntRect(aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight));
6089 if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) {
6090 return;
6092 } else {
6093 dirtyRect = imageDataRect;
6096 IntRect srcRect = dirtyRect;
6097 dirtyRect = ClipImageDataTransfer(srcRect, IntPoint(aX, aY),
6098 IntSize(mWidth, mHeight));
6099 if (dirtyRect.IsEmpty()) {
6100 return;
6103 arr.ComputeState();
6105 uint32_t dataLen = arr.Length();
6107 uint32_t len = width * height * 4;
6108 if (dataLen != len) {
6109 return aRv.ThrowInvalidStateError("Invalid width or height");
6112 // The canvas spec says that the current path, transformation matrix, shadow
6113 // attributes, global alpha, the clipping region, and global composition
6114 // operator must not affect the getImageData() and putImageData() methods.
6115 const gfx::Rect putRect(dirtyRect);
6116 EnsureTarget(&putRect);
6118 if (!IsTargetValid()) {
6119 return aRv.Throw(NS_ERROR_FAILURE);
6122 DataSourceSurface::MappedSurface map;
6123 RefPtr<DataSourceSurface> sourceSurface;
6124 uint8_t* lockedBits = nullptr;
6125 uint8_t* dstData;
6126 IntSize dstSize;
6127 int32_t dstStride;
6128 SurfaceFormat dstFormat;
6129 if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) {
6130 dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4;
6131 } else {
6132 sourceSurface = Factory::CreateDataSourceSurface(
6133 dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false);
6135 // In certain scenarios, requesting larger than 8k image fails. Bug 803568
6136 // covers the details of how to run into it, but the full detailed
6137 // investigation hasn't been done to determine the underlying cause. We
6138 // will just handle the failure to allocate the surface to avoid a crash.
6139 if (!sourceSurface) {
6140 return aRv.Throw(NS_ERROR_FAILURE);
6142 if (!sourceSurface->Map(DataSourceSurface::READ_WRITE, &map)) {
6143 return aRv.Throw(NS_ERROR_FAILURE);
6146 dstData = map.mData;
6147 if (!dstData) {
6148 return aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
6150 dstStride = map.mStride;
6151 dstFormat = sourceSurface->GetFormat();
6154 uint8_t* srcData = arr.Data() + srcRect.y * (width * 4) + srcRect.x * 4;
6156 PremultiplyData(
6157 srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData, dstStride,
6158 mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32,
6159 dirtyRect.Size());
6161 if (lockedBits) {
6162 mTarget->ReleaseBits(lockedBits);
6163 } else if (sourceSurface) {
6164 sourceSurface->Unmap();
6165 mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(),
6166 dirtyRect.TopLeft());
6169 Redraw(
6170 gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height));
6173 static already_AddRefed<ImageData> CreateImageData(
6174 JSContext* aCx, CanvasRenderingContext2D* aContext, uint32_t aW,
6175 uint32_t aH, ErrorResult& aError) {
6176 if (aW == 0) aW = 1;
6177 if (aH == 0) aH = 1;
6179 // Restrict the typed array length to INT32_MAX because that's all we support
6180 // in dom::TypedArray::ComputeState.
6181 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4;
6182 if (!len.isValid() || len.value() > INT32_MAX) {
6183 aError.ThrowIndexSizeError("Invalid width or height");
6184 return nullptr;
6187 // Create the fast typed array; it's initialized to 0 by default.
6188 JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value());
6189 if (!darray) {
6190 // TODO: Should use OOMReporter.
6191 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
6192 return nullptr;
6195 return do_AddRef(new ImageData(aW, aH, *darray));
6198 already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData(
6199 JSContext* aCx, int32_t aSw, int32_t aSh, ErrorResult& aError) {
6200 if (!aSw || !aSh) {
6201 aError.ThrowIndexSizeError("Invalid width or height");
6202 return nullptr;
6205 uint32_t w = Abs(aSw);
6206 uint32_t h = Abs(aSh);
6207 return dom::CreateImageData(aCx, this, w, h, aError);
6210 already_AddRefed<ImageData> CanvasRenderingContext2D::CreateImageData(
6211 JSContext* aCx, ImageData& aImagedata, ErrorResult& aError) {
6212 return dom::CreateImageData(aCx, this, aImagedata.Width(),
6213 aImagedata.Height(), aError);
6216 void CanvasRenderingContext2D::OnMemoryPressure() {
6217 if (mBufferProvider) {
6218 mBufferProvider->OnMemoryPressure();
6222 void CanvasRenderingContext2D::OnBeforePaintTransaction() {
6223 if (!mTarget) return;
6224 OnStableState();
6227 void CanvasRenderingContext2D::OnDidPaintTransaction() { MarkContextClean(); }
6229 bool CanvasRenderingContext2D::UpdateWebRenderCanvasData(
6230 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
6231 if (mOpaque) {
6232 // If we're opaque then make sure we have a surface so we paint black
6233 // instead of transparent.
6234 EnsureTarget();
6237 // Don't call EnsureTarget() ... if there isn't already a surface, then
6238 // we have nothing to paint and there is no need to create a surface just
6239 // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
6240 // layer manager which must NOT happen during a paint.
6241 if (!mBufferProvider && !IsTargetValid()) {
6242 // No DidTransactionCallback will be received, so mark the context clean
6243 // now so future invalidations will be dispatched.
6244 MarkContextClean();
6245 // Clear CanvasRenderer of WebRenderCanvasData
6246 aCanvasData->ClearCanvasRenderer();
6247 return false;
6250 auto renderer = aCanvasData->GetCanvasRenderer();
6252 if (!mResetLayer && renderer) {
6253 CanvasRendererData data;
6254 data.mContext = this;
6255 data.mSize = GetSize();
6257 if (renderer->IsDataValid(data)) {
6258 return true;
6262 renderer = aCanvasData->CreateCanvasRenderer();
6263 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
6264 // Clear CanvasRenderer of WebRenderCanvasData
6265 aCanvasData->ClearCanvasRenderer();
6266 return false;
6269 MOZ_ASSERT(renderer);
6270 mResetLayer = false;
6271 return true;
6274 bool CanvasRenderingContext2D::InitializeCanvasRenderer(
6275 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
6276 CanvasRendererData data;
6277 data.mContext = this;
6278 data.mSize = GetSize();
6279 data.mIsOpaque = mOpaque;
6280 data.mDoPaintCallbacks = true;
6282 if (!mBufferProvider) {
6283 // Force the creation of a buffer provider.
6284 EnsureTarget();
6285 ReturnTarget();
6286 if (!mBufferProvider) {
6287 MarkContextClean();
6288 return false;
6292 aRenderer->Initialize(data);
6293 aRenderer->SetDirty();
6294 return true;
6297 void CanvasRenderingContext2D::MarkContextClean() {
6298 if (mInvalidateCount > 0) {
6299 mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
6301 mIsEntireFrameInvalid = false;
6302 mInvalidateCount = 0;
6305 void CanvasRenderingContext2D::GetAppUnitsValues(int32_t* aPerDevPixel,
6306 int32_t* aPerCSSPixel) {
6307 // If we don't have a canvas element, we just return something generic.
6308 if (aPerDevPixel) {
6309 *aPerDevPixel = 60;
6311 if (aPerCSSPixel) {
6312 *aPerCSSPixel = 60;
6314 PresShell* presShell = GetPresShell();
6315 if (!presShell) {
6316 return;
6318 nsPresContext* presContext = presShell->GetPresContext();
6319 if (!presContext) {
6320 return;
6322 if (aPerDevPixel) {
6323 *aPerDevPixel = presContext->AppUnitsPerDevPixel();
6325 if (aPerCSSPixel) {
6326 *aPerCSSPixel = AppUnitsPerCSSPixel();
6330 void CanvasRenderingContext2D::SetWriteOnly() {
6331 mWriteOnly = true;
6332 if (mCanvasElement) {
6333 mCanvasElement->SetWriteOnly();
6334 } else if (mOffscreenCanvas) {
6335 mOffscreenCanvas->SetWriteOnly();
6339 bool CanvasRenderingContext2D::GetEffectiveWillReadFrequently() const {
6340 return StaticPrefs::gfx_canvas_willreadfrequently_enabled_AtStartup() &&
6341 mWillReadFrequently;
6344 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
6346 CanvasPath::CanvasPath(nsISupports* aParent) : mParent(aParent) {
6347 mPathBuilder =
6348 gfxPlatform::ThreadLocalScreenReferenceDrawTarget()->CreatePathBuilder();
6351 CanvasPath::CanvasPath(nsISupports* aParent,
6352 already_AddRefed<PathBuilder> aPathBuilder)
6353 : mParent(aParent), mPathBuilder(aPathBuilder) {
6354 if (!mPathBuilder) {
6355 mPathBuilder = gfxPlatform::ThreadLocalScreenReferenceDrawTarget()
6356 ->CreatePathBuilder();
6360 JSObject* CanvasPath::WrapObject(JSContext* aCx,
6361 JS::Handle<JSObject*> aGivenProto) {
6362 return Path2D_Binding::Wrap(aCx, this, aGivenProto);
6365 already_AddRefed<CanvasPath> CanvasPath::Constructor(
6366 const GlobalObject& aGlobal) {
6367 RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports());
6368 return path.forget();
6371 already_AddRefed<CanvasPath> CanvasPath::Constructor(
6372 const GlobalObject& aGlobal, CanvasPath& aCanvasPath) {
6373 RefPtr<gfx::DrawTarget> drawTarget =
6374 gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
6375 RefPtr<gfx::Path> tempPath =
6376 aCanvasPath.GetPath(CanvasWindingRule::Nonzero, drawTarget.get());
6378 RefPtr<CanvasPath> path =
6379 new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
6380 return path.forget();
6383 already_AddRefed<CanvasPath> CanvasPath::Constructor(
6384 const GlobalObject& aGlobal, const nsAString& aPathString) {
6385 RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString);
6386 if (!tempPath) {
6387 return Constructor(aGlobal);
6390 RefPtr<CanvasPath> path =
6391 new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
6392 return path.forget();
6395 void CanvasPath::ClosePath() {
6396 EnsurePathBuilder();
6398 mPathBuilder->Close();
6399 mPruned = false;
6402 inline void CanvasPath::EnsureCapped() const {
6403 // If there were zero-length segments emitted that were pruned, we need to
6404 // emit a LineTo to ensure that caps are generated for the segment.
6405 if (mPruned) {
6406 mPathBuilder->LineTo(mPathBuilder->CurrentPoint());
6407 mPruned = false;
6411 inline void CanvasPath::EnsureActive() const {
6412 // If the path is not active, then adding an op to the path may cause the path
6413 // to add the first point of the op as the initial point instead of the actual
6414 // current point.
6415 if (mPruned && !mPathBuilder->IsActive()) {
6416 mPathBuilder->MoveTo(mPathBuilder->CurrentPoint());
6417 mPruned = false;
6421 void CanvasPath::MoveTo(double aX, double aY) {
6422 EnsurePathBuilder();
6424 Point pos(ToFloat(aX), ToFloat(aY));
6425 if (!pos.IsFinite()) {
6426 return;
6429 EnsureCapped();
6430 mPathBuilder->MoveTo(pos);
6433 void CanvasPath::LineTo(double aX, double aY) {
6434 LineTo(Point(ToFloat(aX), ToFloat(aY)));
6437 void CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX,
6438 double aY) {
6439 EnsurePathBuilder();
6441 Point cp1(ToFloat(aCpx), ToFloat(aCpy));
6442 Point cp2(ToFloat(aX), ToFloat(aY));
6443 if (!cp1.IsFinite() || !cp2.IsFinite()) {
6444 return;
6446 if (cp1 == mPathBuilder->CurrentPoint() && cp1 == cp2) {
6447 mPruned = true;
6448 return;
6451 EnsureActive();
6453 mPathBuilder->QuadraticBezierTo(cp1, cp2);
6454 mPruned = false;
6457 void CanvasPath::BezierCurveTo(double aCp1x, double aCp1y, double aCp2x,
6458 double aCp2y, double aX, double aY) {
6459 BezierTo(gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)),
6460 gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)),
6461 gfx::Point(ToFloat(aX), ToFloat(aY)));
6464 void CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2,
6465 double aRadius, ErrorResult& aError) {
6466 if (aRadius < 0) {
6467 return aError.ThrowIndexSizeError("Negative radius");
6470 EnsurePathBuilder();
6472 // Current point in user space!
6473 Point p0 = mPathBuilder->CurrentPoint();
6474 Point p1(aX1, aY1);
6475 Point p2(aX2, aY2);
6477 if (!p1.IsFinite() || !p2.IsFinite() || !std::isfinite(aRadius)) {
6478 return;
6481 // Execute these calculations in double precision to avoid cumulative
6482 // rounding errors.
6483 double dir, a2, b2, c2, cosx, sinx, d, anx, any, bnx, bny, x3, y3, x4, y4, cx,
6484 cy, angle0, angle1;
6485 bool anticlockwise;
6487 if (p0 == p1 || p1 == p2 || aRadius == 0) {
6488 LineTo(p1);
6489 return;
6492 // Check for colinearity
6493 dir = (p2.x.value - p1.x.value) * (p0.y.value - p1.y.value) +
6494 (p2.y.value - p1.y.value) * (p1.x.value - p0.x.value);
6495 if (dir == 0) {
6496 LineTo(p1);
6497 return;
6500 // XXX - Math for this code was already available from the non-azure code
6501 // and would be well tested. Perhaps converting to bezier directly might
6502 // be more efficient longer run.
6503 a2 = (p0.x - aX1) * (p0.x - aX1) + (p0.y - aY1) * (p0.y - aY1);
6504 b2 = (aX1 - aX2) * (aX1 - aX2) + (aY1 - aY2) * (aY1 - aY2);
6505 c2 = (p0.x - aX2) * (p0.x - aX2) + (p0.y - aY2) * (p0.y - aY2);
6506 cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2));
6508 sinx = sqrt(1 - cosx * cosx);
6509 d = aRadius / ((1 - cosx) / sinx);
6511 anx = (aX1 - p0.x) / sqrt(a2);
6512 any = (aY1 - p0.y) / sqrt(a2);
6513 bnx = (aX1 - aX2) / sqrt(b2);
6514 bny = (aY1 - aY2) / sqrt(b2);
6515 x3 = aX1 - anx * d;
6516 y3 = aY1 - any * d;
6517 x4 = aX1 - bnx * d;
6518 y4 = aY1 - bny * d;
6519 anticlockwise = (dir < 0);
6520 cx = x3 + any * aRadius * (anticlockwise ? 1 : -1);
6521 cy = y3 - anx * aRadius * (anticlockwise ? 1 : -1);
6522 angle0 = atan2((y3 - cy), (x3 - cx));
6523 angle1 = atan2((y4 - cy), (x4 - cx));
6525 LineTo(x3, y3);
6527 Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError);
6530 void CanvasPath::Rect(double aX, double aY, double aW, double aH) {
6531 EnsurePathBuilder();
6533 if (!std::isfinite(aX) || !std::isfinite(aY) || !std::isfinite(aW) ||
6534 !std::isfinite(aH)) {
6535 return;
6538 MoveTo(aX, aY);
6539 if (aW == 0 && aH == 0) {
6540 return;
6542 LineTo(aX + aW, aY);
6543 LineTo(aX + aW, aY + aH);
6544 LineTo(aX, aY + aH);
6545 ClosePath();
6548 void CanvasPath::RoundRect(
6549 double aX, double aY, double aW, double aH,
6550 const UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence&
6551 aRadii,
6552 ErrorResult& aError) {
6553 EnsurePathBuilder();
6555 EnsureCapped();
6556 RoundRectImpl(mPathBuilder, Nothing(), aX, aY, aW, aH, aRadii, aError);
6559 void CanvasPath::Arc(double aX, double aY, double aRadius, double aStartAngle,
6560 double aEndAngle, bool aAnticlockwise,
6561 ErrorResult& aError) {
6562 if (aRadius < 0.0) {
6563 return aError.ThrowIndexSizeError("Negative radius");
6565 if (aStartAngle == aEndAngle) {
6566 LineTo(aX + aRadius * cos(aStartAngle), aY + aRadius * sin(aStartAngle));
6567 return;
6570 EnsurePathBuilder();
6572 EnsureActive();
6574 mPathBuilder->Arc(Point(aX, aY), aRadius, aStartAngle, aEndAngle,
6575 aAnticlockwise);
6576 mPruned = false;
6579 void CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
6580 double rotation, double startAngle, double endAngle,
6581 bool anticlockwise, ErrorResult& aError) {
6582 if (radiusX < 0.0 || radiusY < 0.0) {
6583 return aError.ThrowIndexSizeError("Negative radius");
6586 EnsurePathBuilder();
6588 ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle,
6589 anticlockwise, rotation);
6590 mPruned = false;
6593 void CanvasPath::LineTo(const gfx::Point& aPoint) {
6594 EnsurePathBuilder();
6596 if (!aPoint.IsFinite()) {
6597 return;
6599 if (aPoint == mPathBuilder->CurrentPoint()) {
6600 mPruned = true;
6601 return;
6604 EnsureActive();
6606 mPathBuilder->LineTo(aPoint);
6607 mPruned = false;
6610 void CanvasPath::BezierTo(const gfx::Point& aCP1, const gfx::Point& aCP2,
6611 const gfx::Point& aCP3) {
6612 EnsurePathBuilder();
6614 if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) {
6615 return;
6617 if (aCP1 == mPathBuilder->CurrentPoint() && aCP1 == aCP2 && aCP1 == aCP3) {
6618 mPruned = true;
6619 return;
6622 EnsureActive();
6624 mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
6625 mPruned = false;
6628 void CanvasPath::AddPath(CanvasPath& aCanvasPath, const DOMMatrix2DInit& aInit,
6629 ErrorResult& aError) {
6630 RefPtr<gfx::DrawTarget> drawTarget =
6631 gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
6632 RefPtr<gfx::Path> tempPath =
6633 aCanvasPath.GetPath(CanvasWindingRule::Nonzero, drawTarget.get());
6635 RefPtr<DOMMatrixReadOnly> matrix =
6636 DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
6637 if (aError.Failed()) {
6638 return;
6641 Matrix transform(*(matrix->GetInternal2D()));
6643 if (!transform.IsFinite()) {
6644 return;
6647 if (!transform.IsIdentity()) {
6648 RefPtr<PathBuilder> tempBuilder =
6649 tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING);
6650 tempPath = tempBuilder->Finish();
6653 EnsurePathBuilder(); // in case a path is added to itself
6654 EnsureCapped();
6655 tempPath->StreamToSink(mPathBuilder);
6658 already_AddRefed<gfx::Path> CanvasPath::GetPath(
6659 const CanvasWindingRule& aWinding, const DrawTarget* aTarget) const {
6660 FillRule fillRule = FillRule::FILL_WINDING;
6661 if (aWinding == CanvasWindingRule::Evenodd) {
6662 fillRule = FillRule::FILL_EVEN_ODD;
6665 if (mPath && (mPath->GetBackendType() == aTarget->GetBackendType()) &&
6666 (mPath->GetFillRule() == fillRule)) {
6667 RefPtr<gfx::Path> path(mPath);
6668 return path.forget();
6671 if (!mPath) {
6672 // if there is no path, there must be a pathbuilder
6673 MOZ_ASSERT(mPathBuilder);
6674 EnsureCapped();
6675 mPath = mPathBuilder->Finish();
6676 if (!mPath) {
6677 RefPtr<gfx::Path> path(mPath);
6678 return path.forget();
6681 mPathBuilder = nullptr;
6684 // retarget our backend if we're used with a different backend
6685 if (mPath->GetBackendType() != aTarget->GetBackendType()) {
6686 RefPtr<PathBuilder> tmpPathBuilder = aTarget->CreatePathBuilder(fillRule);
6687 mPath->StreamToSink(tmpPathBuilder);
6688 mPath = tmpPathBuilder->Finish();
6689 } else if (mPath->GetFillRule() != fillRule) {
6690 RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule);
6691 mPath = tmpPathBuilder->Finish();
6694 RefPtr<gfx::Path> path(mPath);
6695 return path.forget();
6698 void CanvasPath::EnsurePathBuilder() const {
6699 if (mPathBuilder) {
6700 return;
6703 // if there is not pathbuilder, there must be a path
6704 MOZ_ASSERT(mPath);
6705 mPathBuilder = mPath->CopyToBuilder();
6706 mPath = nullptr;
6709 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) {
6710 IntSize size = aContext->GetSize();
6712 // TODO: Bug 1552137: No memory will be allocated if either dimension is
6713 // greater than gfxPrefs::gfx_canvas_max_size(). We should check this here
6714 // too.
6716 CheckedInt<uint32_t> bytes =
6717 CheckedInt<uint32_t>(size.width) * size.height * 4;
6718 if (!bytes.isValid()) {
6719 return 0;
6722 return bytes.value();
6725 } // namespace mozilla::dom