Bumping gaia.json for 1 gaia revision(s) a=gaia-bump
[gecko.git] / dom / canvas / CanvasRenderingContext2D.cpp
blobb4dfc69c7d1f43659039d71cff49462d08832d06
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 "nsIServiceManager.h"
12 #include "nsMathUtils.h"
13 #include "SVGImageContext.h"
15 #include "nsContentUtils.h"
17 #include "nsIDocument.h"
18 #include "mozilla/dom/HTMLCanvasElement.h"
19 #include "nsSVGEffects.h"
20 #include "nsPresContext.h"
21 #include "nsIPresShell.h"
23 #include "nsIInterfaceRequestorUtils.h"
24 #include "nsIFrame.h"
25 #include "nsError.h"
27 #include "nsCSSParser.h"
28 #include "mozilla/css/StyleRule.h"
29 #include "mozilla/css/Declaration.h"
30 #include "nsComputedDOMStyle.h"
31 #include "nsStyleSet.h"
33 #include "nsPrintfCString.h"
35 #include "nsReadableUtils.h"
37 #include "nsColor.h"
38 #include "nsGfxCIID.h"
39 #include "nsIDocShell.h"
40 #include "nsIDOMWindow.h"
41 #include "nsPIDOMWindow.h"
42 #include "nsDisplayList.h"
43 #include "nsFocusManager.h"
45 #include "nsTArray.h"
47 #include "ImageEncoder.h"
48 #include "ImageRegion.h"
50 #include "gfxContext.h"
51 #include "gfxASurface.h"
52 #include "gfxImageSurface.h"
53 #include "gfxPlatform.h"
54 #include "gfxFont.h"
55 #include "gfxBlur.h"
56 #include "gfxUtils.h"
58 #include "nsFrameLoader.h"
59 #include "nsBidi.h"
60 #include "nsBidiPresUtils.h"
61 #include "Layers.h"
62 #include "CanvasUtils.h"
63 #include "nsIMemoryReporter.h"
64 #include "nsStyleUtil.h"
65 #include "CanvasImageCache.h"
67 #include <algorithm>
69 #include "jsapi.h"
70 #include "jsfriendapi.h"
72 #include "mozilla/Alignment.h"
73 #include "mozilla/Assertions.h"
74 #include "mozilla/CheckedInt.h"
75 #include "mozilla/DebugOnly.h"
76 #include "mozilla/dom/ContentParent.h"
77 #include "mozilla/dom/ImageData.h"
78 #include "mozilla/dom/PBrowserParent.h"
79 #include "mozilla/dom/ToJSValue.h"
80 #include "mozilla/dom/TypedArray.h"
81 #include "mozilla/Endian.h"
82 #include "mozilla/gfx/2D.h"
83 #include "mozilla/gfx/PathHelpers.h"
84 #include "mozilla/gfx/DataSurfaceHelpers.h"
85 #include "mozilla/ipc/DocumentRendererParent.h"
86 #include "mozilla/ipc/PDocumentRendererParent.h"
87 #include "mozilla/MathAlgorithms.h"
88 #include "mozilla/Preferences.h"
89 #include "mozilla/Telemetry.h"
90 #include "mozilla/unused.h"
91 #include "nsCCUncollectableMarker.h"
92 #include "nsWrapperCacheInlines.h"
93 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
94 #include "mozilla/dom/HTMLImageElement.h"
95 #include "mozilla/dom/HTMLVideoElement.h"
96 #include "mozilla/dom/SVGMatrix.h"
97 #include "mozilla/dom/TextMetrics.h"
98 #include "mozilla/dom/UnionTypes.h"
99 #include "mozilla/dom/SVGMatrix.h"
100 #include "nsGlobalWindow.h"
101 #include "GLContext.h"
102 #include "GLContextProvider.h"
103 #include "SVGContentUtils.h"
104 #include "SVGImageContext.h"
105 #include "nsIScreenManager.h"
107 #undef free // apparently defined by some windows header, clashing with a free()
108 // method in SkTypes.h
109 #ifdef USE_SKIA
110 #include "SkiaGLGlue.h"
111 #include "SurfaceStream.h"
112 #include "SurfaceTypes.h"
113 #endif
115 using mozilla::gl::GLContext;
116 using mozilla::gl::SkiaGLGlue;
117 using mozilla::gl::GLContextProvider;
119 #ifdef XP_WIN
120 #include "gfxWindowsPlatform.h"
121 #endif
123 #ifdef MOZ_WIDGET_GONK
124 #include "mozilla/layers/ShadowLayers.h"
125 #endif
127 // windows.h (included by chromium code) defines this, in its infinite wisdom
128 #undef DrawText
130 using namespace mozilla;
131 using namespace mozilla::CanvasUtils;
132 using namespace mozilla::css;
133 using namespace mozilla::gfx;
134 using namespace mozilla::image;
135 using namespace mozilla::ipc;
136 using namespace mozilla::layers;
138 namespace mgfx = mozilla::gfx;
140 namespace mozilla {
141 namespace dom {
143 // Cap sigma to avoid overly large temp surfaces.
144 const Float SIGMA_MAX = 100;
146 /* Memory reporter stuff */
147 static int64_t gCanvasAzureMemoryUsed = 0;
149 // This is KIND_OTHER because it's not always clear where in memory the pixels
150 // of a canvas are stored. Furthermore, this memory will be tracked by the
151 // underlying surface implementations. See bug 655638 for details.
152 class Canvas2dPixelsReporter MOZ_FINAL : public nsIMemoryReporter
154 ~Canvas2dPixelsReporter() {}
155 public:
156 NS_DECL_ISUPPORTS
158 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
159 nsISupports* aData, bool aAnonymize)
161 return MOZ_COLLECT_REPORT(
162 "canvas-2d-pixels", KIND_OTHER, UNITS_BYTES,
163 gCanvasAzureMemoryUsed,
164 "Memory used by 2D canvases. Each canvas requires "
165 "(width * height * 4) bytes.");
169 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
171 class CanvasRadialGradient : public CanvasGradient
173 public:
174 CanvasRadialGradient(CanvasRenderingContext2D* aContext,
175 const Point &aBeginOrigin, Float aBeginRadius,
176 const Point &aEndOrigin, Float aEndRadius)
177 : CanvasGradient(aContext, Type::RADIAL)
178 , mCenter1(aBeginOrigin)
179 , mCenter2(aEndOrigin)
180 , mRadius1(aBeginRadius)
181 , mRadius2(aEndRadius)
185 Point mCenter1;
186 Point mCenter2;
187 Float mRadius1;
188 Float mRadius2;
191 class CanvasLinearGradient : public CanvasGradient
193 public:
194 CanvasLinearGradient(CanvasRenderingContext2D* aContext,
195 const Point &aBegin, const Point &aEnd)
196 : CanvasGradient(aContext, Type::LINEAR)
197 , mBegin(aBegin)
198 , mEnd(aEnd)
202 protected:
203 friend class CanvasGeneralPattern;
205 // Beginning of linear gradient.
206 Point mBegin;
207 // End of linear gradient.
208 Point mEnd;
211 // This class is named 'GeneralCanvasPattern' instead of just
212 // 'GeneralPattern' to keep Windows PGO builds from confusing the
213 // GeneralPattern class in gfxContext.cpp with this one.
215 class CanvasGeneralPattern
217 public:
218 typedef CanvasRenderingContext2D::Style Style;
219 typedef CanvasRenderingContext2D::ContextState ContextState;
221 CanvasGeneralPattern() : mPattern(nullptr) {}
222 ~CanvasGeneralPattern()
224 if (mPattern) {
225 mPattern->~Pattern();
229 Pattern& ForStyle(CanvasRenderingContext2D *aCtx,
230 Style aStyle,
231 DrawTarget *aRT)
233 // This should only be called once or the mPattern destructor will
234 // not be executed.
235 NS_ASSERTION(!mPattern, "ForStyle() should only be called once on CanvasGeneralPattern!");
237 const ContextState &state = aCtx->CurrentState();
239 if (state.StyleIsColor(aStyle)) {
240 mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle]));
241 } else if (state.gradientStyles[aStyle] &&
242 state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) {
243 CanvasLinearGradient *gradient =
244 static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get());
246 mPattern = new (mLinearGradientPattern.addr())
247 LinearGradientPattern(gradient->mBegin, gradient->mEnd,
248 gradient->GetGradientStopsForTarget(aRT));
249 } else if (state.gradientStyles[aStyle] &&
250 state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) {
251 CanvasRadialGradient *gradient =
252 static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get());
254 mPattern = new (mRadialGradientPattern.addr())
255 RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1,
256 gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT));
257 } else if (state.patternStyles[aStyle]) {
258 if (aCtx->mCanvasElement) {
259 CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement,
260 state.patternStyles[aStyle]->mPrincipal,
261 state.patternStyles[aStyle]->mForceWriteOnly,
262 state.patternStyles[aStyle]->mCORSUsed);
265 ExtendMode mode;
266 if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) {
267 mode = ExtendMode::CLAMP;
268 } else {
269 mode = ExtendMode::REPEAT;
271 mPattern = new (mSurfacePattern.addr())
272 SurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
273 state.patternStyles[aStyle]->mTransform);
276 return *mPattern;
279 union {
280 AlignedStorage2<ColorPattern> mColorPattern;
281 AlignedStorage2<LinearGradientPattern> mLinearGradientPattern;
282 AlignedStorage2<RadialGradientPattern> mRadialGradientPattern;
283 AlignedStorage2<SurfacePattern> mSurfacePattern;
285 Pattern *mPattern;
288 /* This is an RAII based class that can be used as a drawtarget for
289 * operations that need a shadow drawn. It will automatically provide a
290 * temporary target when needed, and if so blend it back with a shadow.
292 * aBounds specifies the bounds of the drawing operation that will be
293 * drawn to the target, it is given in device space! This function will
294 * change aBounds to incorporate shadow bounds. If this is nullptr the drawing
295 * operation will be assumed to cover an infinite rect.
297 class AdjustedTarget
299 public:
300 typedef CanvasRenderingContext2D::ContextState ContextState;
302 AdjustedTarget(CanvasRenderingContext2D *ctx,
303 mgfx::Rect *aBounds = nullptr)
304 : mCtx(nullptr)
306 if (!ctx->NeedToDrawShadow()) {
307 mTarget = ctx->mTarget;
308 return;
310 mCtx = ctx;
312 const ContextState &state = mCtx->CurrentState();
314 mSigma = state.shadowBlur / 2.0f;
316 if (mSigma > SIGMA_MAX) {
317 mSigma = SIGMA_MAX;
320 Matrix transform = mCtx->mTarget->GetTransform();
322 mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight);
324 static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5;
325 int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5);
327 // We need to enlarge and possibly offset our temporary surface
328 // so that things outside of the canvas may cast shadows.
329 mTempRect.Inflate(Margin(blurRadius + std::max<Float>(state.shadowOffset.y, 0),
330 blurRadius + std::max<Float>(-state.shadowOffset.x, 0),
331 blurRadius + std::max<Float>(-state.shadowOffset.y, 0),
332 blurRadius + std::max<Float>(state.shadowOffset.x, 0)));
334 if (aBounds) {
335 // We actually include the bounds of the shadow blur, this makes it
336 // easier to execute the actual blur on hardware, and shouldn't affect
337 // the amount of pixels that need to be touched.
338 aBounds->Inflate(Margin(blurRadius, blurRadius,
339 blurRadius, blurRadius));
340 mTempRect = mTempRect.Intersect(*aBounds);
343 mTempRect.ScaleRoundOut(1.0f);
345 transform._31 -= mTempRect.x;
346 transform._32 -= mTempRect.y;
348 mTarget =
349 mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)),
350 SurfaceFormat::B8G8R8A8, mSigma);
352 if (!mTarget) {
353 // XXX - Deal with the situation where our temp size is too big to
354 // fit in a texture.
355 mTarget = ctx->mTarget;
356 mCtx = nullptr;
357 } else {
358 mTarget->SetTransform(transform);
362 ~AdjustedTarget()
364 if (!mCtx) {
365 return;
368 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
370 mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(),
371 Color::FromABGR(mCtx->CurrentState().shadowColor),
372 mCtx->CurrentState().shadowOffset, mSigma,
373 mCtx->CurrentState().op);
376 operator DrawTarget*()
378 return mTarget;
381 DrawTarget* operator->()
383 return mTarget;
386 private:
387 RefPtr<DrawTarget> mTarget;
388 CanvasRenderingContext2D *mCtx;
389 Float mSigma;
390 mgfx::Rect mTempRect;
393 void
394 CanvasPattern::SetTransform(SVGMatrix& aMatrix)
396 mTransform = ToMatrix(aMatrix.GetMatrix());
399 void
400 CanvasGradient::AddColorStop(float offset, const nsAString& colorstr, ErrorResult& rv)
402 if (offset < 0.0 || offset > 1.0) {
403 rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
404 return;
407 nsCSSValue value;
408 nsCSSParser parser;
409 if (!parser.ParseColorString(colorstr, nullptr, 0, value)) {
410 rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
411 return;
414 nscolor color;
415 if (!nsRuleNode::ComputeColor(value, nullptr, nullptr, color)) {
416 rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
417 return;
420 mStops = nullptr;
422 GradientStop newStop;
424 newStop.offset = offset;
425 newStop.color = Color::FromABGR(color);
427 mRawStops.AppendElement(newStop);
430 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef)
431 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release)
433 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
435 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef)
436 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
438 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
440 class CanvasRenderingContext2DUserData : public LayerUserData {
441 public:
442 CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext)
443 : mContext(aContext)
445 aContext->mUserDatas.AppendElement(this);
447 ~CanvasRenderingContext2DUserData()
449 if (mContext) {
450 mContext->mUserDatas.RemoveElement(this);
454 static void PreTransactionCallback(void* aData)
456 CanvasRenderingContext2DUserData* self =
457 static_cast<CanvasRenderingContext2DUserData*>(aData);
458 CanvasRenderingContext2D* context = self->mContext;
459 if (!context || !context->mStream || !context->mTarget)
460 return;
462 // Since SkiaGL default to store drawing command until flush
463 // We will have to flush it before present.
464 context->mTarget->Flush();
467 static void DidTransactionCallback(void* aData)
469 CanvasRenderingContext2DUserData* self =
470 static_cast<CanvasRenderingContext2DUserData*>(aData);
471 if (self->mContext) {
472 self->mContext->MarkContextClean();
475 bool IsForContext(CanvasRenderingContext2D *aContext)
477 return mContext == aContext;
479 void Forget()
481 mContext = nullptr;
484 private:
485 CanvasRenderingContext2D *mContext;
488 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
489 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
491 NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
493 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
494 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
495 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
496 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
497 ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
498 ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
499 ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
501 for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
502 RegionInfo& info = tmp->mHitRegionsOptions[x];
503 if (info.mElement) {
504 ImplCycleCollectionUnlink(info.mElement);
507 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
508 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
512 for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
513 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern");
514 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern");
515 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient");
516 ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient");
518 for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
519 RegionInfo& info = tmp->mHitRegionsOptions[x];
520 if (info.mElement) {
521 ImplCycleCollectionTraverse(cb, info.mElement, "Hit region fallback element");
524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
527 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D)
529 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D)
530 if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) {
531 dom::Element* canvasElement = tmp->mCanvasElement;
532 if (canvasElement) {
533 if (canvasElement->IsPurple()) {
534 canvasElement->RemovePurple();
536 dom::Element::MarkNodeChildren(canvasElement);
538 return true;
540 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
542 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D)
543 return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
544 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
546 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D)
547 return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
548 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
550 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D)
551 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
552 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
553 NS_INTERFACE_MAP_ENTRY(nsISupports)
554 NS_INTERFACE_MAP_END
557 ** CanvasRenderingContext2D impl
561 // Initialize our static variables.
562 uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0;
563 DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
567 CanvasRenderingContext2D::CanvasRenderingContext2D()
568 : mForceSoftware(false)
569 // these are the default values from the Canvas spec
570 , mWidth(0), mHeight(0)
571 , mZero(false), mOpaque(false)
572 , mResetLayer(true)
573 , mIPC(false)
574 , mStream(nullptr)
575 , mIsEntireFrameInvalid(false)
576 , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
577 , mInvalidateCount(0)
579 sNumLivingContexts++;
580 SetIsDOMBinding();
583 CanvasRenderingContext2D::~CanvasRenderingContext2D()
585 Reset();
586 // Drop references from all CanvasRenderingContext2DUserData to this context
587 for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
588 mUserDatas[i]->Forget();
590 sNumLivingContexts--;
591 if (!sNumLivingContexts) {
592 NS_IF_RELEASE(sErrorTarget);
595 RemoveDemotableContext(this);
598 JSObject*
599 CanvasRenderingContext2D::WrapObject(JSContext *cx)
601 return CanvasRenderingContext2DBinding::Wrap(cx, this);
604 bool
605 CanvasRenderingContext2D::ParseColor(const nsAString& aString,
606 nscolor* aColor)
608 nsIDocument* document = mCanvasElement
609 ? mCanvasElement->OwnerDoc()
610 : nullptr;
612 // Pass the CSS Loader object to the parser, to allow parser error
613 // reports to include the outer window ID.
614 nsCSSParser parser(document ? document->CSSLoader() : nullptr);
615 nsCSSValue value;
616 if (!parser.ParseColorString(aString, nullptr, 0, value)) {
617 return false;
620 if (value.IsNumericColorUnit()) {
621 // if we already have a color we can just use it directly
622 *aColor = value.GetColorValue();
623 } else {
624 // otherwise resolve it
625 nsIPresShell* presShell = GetPresShell();
626 nsRefPtr<nsStyleContext> parentContext;
627 if (mCanvasElement && mCanvasElement->IsInDoc()) {
628 // Inherit from the canvas element.
629 parentContext = nsComputedDOMStyle::GetStyleContextForElement(
630 mCanvasElement, nullptr, presShell);
633 unused << nsRuleNode::ComputeColor(
634 value, presShell ? presShell->GetPresContext() : nullptr, parentContext,
635 *aColor);
637 return true;
640 nsresult
641 CanvasRenderingContext2D::Reset()
643 if (mCanvasElement) {
644 mCanvasElement->InvalidateCanvas();
647 // only do this for non-docshell created contexts,
648 // since those are the ones that we created a surface for
649 if (mTarget && IsTargetValid() && !mDocShell) {
650 gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
653 mTarget = nullptr;
654 mStream = nullptr;
656 // reset hit regions
657 mHitRegionsOptions.ClearAndRetainStorage();
659 // Since the target changes the backing texture will change, and this will
660 // no longer be valid.
661 mIsEntireFrameInvalid = false;
662 mPredictManyRedrawCalls = false;
664 return NS_OK;
667 void
668 CanvasRenderingContext2D::SetStyleFromString(const nsAString& str,
669 Style whichStyle)
671 MOZ_ASSERT(!str.IsVoid());
673 nscolor color;
674 if (!ParseColor(str, &color)) {
675 return;
678 CurrentState().SetColorStyle(whichStyle, color);
681 void
682 CanvasRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue,
683 Style aWhichStyle)
685 const ContextState &state = CurrentState();
686 if (state.patternStyles[aWhichStyle]) {
687 aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
688 } else if (state.gradientStyles[aWhichStyle]) {
689 aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
690 } else {
691 StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString());
695 // static
696 void
697 CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr)
699 // We can't reuse the normal CSS color stringification code,
700 // because the spec calls for a different algorithm for canvas.
701 if (NS_GET_A(aColor) == 255) {
702 CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x",
703 NS_GET_R(aColor),
704 NS_GET_G(aColor),
705 NS_GET_B(aColor)),
706 aStr);
707 } else {
708 CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ",
709 NS_GET_R(aColor),
710 NS_GET_G(aColor),
711 NS_GET_B(aColor)),
712 aStr);
713 aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
714 aStr.Append(')');
718 nsresult
719 CanvasRenderingContext2D::Redraw()
721 if (mIsEntireFrameInvalid) {
722 return NS_OK;
725 mIsEntireFrameInvalid = true;
727 if (!mCanvasElement) {
728 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
729 return NS_OK;
732 nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
734 mCanvasElement->InvalidateCanvasContent(nullptr);
736 return NS_OK;
739 void
740 CanvasRenderingContext2D::Redraw(const mgfx::Rect &r)
742 ++mInvalidateCount;
744 if (mIsEntireFrameInvalid) {
745 return;
748 if (mPredictManyRedrawCalls ||
749 mInvalidateCount > kCanvasMaxInvalidateCount) {
750 Redraw();
751 return;
754 if (!mCanvasElement) {
755 NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
756 return;
759 nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
761 mCanvasElement->InvalidateCanvasContent(&r);
764 void
765 CanvasRenderingContext2D::RedrawUser(const gfxRect& r)
767 if (mIsEntireFrameInvalid) {
768 ++mInvalidateCount;
769 return;
772 mgfx::Rect newr =
773 mTarget->GetTransform().TransformBounds(ToRect(r));
774 Redraw(newr);
777 void CanvasRenderingContext2D::Demote()
779 if (!IsTargetValid() || mForceSoftware || !mStream)
780 return;
782 RemoveDemotableContext(this);
784 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
785 RefPtr<DrawTarget> oldTarget = mTarget;
786 mTarget = nullptr;
787 mStream = nullptr;
788 mResetLayer = true;
789 mForceSoftware = true;
791 // Recreate target, now demoted to software only
792 EnsureTarget();
793 if (!IsTargetValid())
794 return;
796 // Restore the content from the old DrawTarget
797 mgfx::Rect r(0, 0, mWidth, mHeight);
798 mTarget->DrawSurface(snapshot, r, r);
800 // Restore the clips and transform
801 for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) {
802 mTarget->PushClip(CurrentState().clipsPushed[i]);
805 mTarget->SetTransform(oldTarget->GetTransform());
808 std::vector<CanvasRenderingContext2D*>&
809 CanvasRenderingContext2D::DemotableContexts()
811 static std::vector<CanvasRenderingContext2D*> contexts;
812 return contexts;
815 void
816 CanvasRenderingContext2D::DemoteOldestContextIfNecessary()
818 const size_t kMaxContexts = 64;
820 std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts();
821 if (contexts.size() < kMaxContexts)
822 return;
824 CanvasRenderingContext2D* oldest = contexts.front();
825 oldest->Demote();
828 void
829 CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* context)
831 std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context);
832 if (iter != DemotableContexts().end())
833 return;
835 DemotableContexts().push_back(context);
838 void
839 CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* context)
841 std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context);
842 if (iter != DemotableContexts().end())
843 DemotableContexts().erase(iter);
846 bool
847 CanvasRenderingContext2D::CheckSizeForSkiaGL(IntSize size) {
848 MOZ_ASSERT(NS_IsMainThread());
850 int minsize = Preferences::GetInt("gfx.canvas.min-size-for-skia-gl", 128);
851 if (size.width < minsize || size.height < minsize) {
852 return false;
855 // Maximum pref allows 3 different options:
856 // 0 means unlimited size
857 // > 0 means use value as an absolute threshold
858 // < 0 means use the number of screen pixels as a threshold
859 int maxsize = Preferences::GetInt("gfx.canvas.max-size-for-skia-gl", 0);
861 // unlimited max size
862 if (!maxsize) {
863 return true;
866 // absolute max size threshold
867 if (maxsize > 0) {
868 return size.width <= maxsize && size.height <= maxsize;
871 // Cache the number of pixels on the primary screen
872 static int32_t gScreenPixels = -1;
873 if (gScreenPixels < 0) {
874 // Default to historical mobile screen size of 980x480, like FishIEtank.
875 // In addition, allow skia use up to this size even if the screen is smaller.
876 // A lot content expects this size to work well.
877 // See Bug 999841
878 if (gfxPlatform::GetPlatform()->HasEnoughTotalSystemMemoryForSkiaGL()) {
879 gScreenPixels = 980 * 480;
882 nsCOMPtr<nsIScreenManager> screenManager =
883 do_GetService("@mozilla.org/gfx/screenmanager;1");
884 if (screenManager) {
885 nsCOMPtr<nsIScreen> primaryScreen;
886 screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
887 if (primaryScreen) {
888 int32_t x, y, width, height;
889 primaryScreen->GetRect(&x, &y, &width, &height);
891 gScreenPixels = std::max(gScreenPixels, width * height);
896 // Just always use a scale of 1.0. It can be changed if a lot of contents need it.
897 static double gDefaultScale = 1.0;
899 double scale = gDefaultScale > 0 ? gDefaultScale : 1.0;
900 int32_t threshold = ceil(scale * scale * gScreenPixels);
902 // screen size acts as max threshold
903 return threshold < 0 || (size.width * size.height) <= threshold;
906 void
907 CanvasRenderingContext2D::EnsureTarget()
909 if (mTarget) {
910 return;
913 // Check that the dimensions are sane
914 IntSize size(mWidth, mHeight);
915 if (size.width <= 0xFFFF && size.height <= 0xFFFF &&
916 size.width >= 0 && size.height >= 0) {
917 SurfaceFormat format = GetSurfaceFormat();
918 nsIDocument* ownerDoc = nullptr;
919 if (mCanvasElement) {
920 ownerDoc = mCanvasElement->OwnerDoc();
923 nsRefPtr<LayerManager> layerManager = nullptr;
925 if (ownerDoc) {
926 layerManager =
927 nsContentUtils::PersistentLayerManagerForDocument(ownerDoc);
930 if (layerManager) {
931 if (gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas() &&
932 !mForceSoftware &&
933 CheckSizeForSkiaGL(size)) {
934 DemoteOldestContextIfNecessary();
936 SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
938 #if USE_SKIA
939 if (glue && glue->GetGrContext() && glue->GetGLContext()) {
940 mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
941 if (mTarget) {
942 mStream = gl::SurfaceStream::CreateForType(gl::SurfaceStreamType::TripleBuffer,
943 glue->GetGLContext());
944 AddDemotableContext(this);
945 } else {
946 printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n");
949 #endif
950 if (!mTarget) {
951 mTarget = layerManager->CreateDrawTarget(size, format);
953 } else
954 mTarget = layerManager->CreateDrawTarget(size, format);
955 } else {
956 mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
960 if (mTarget) {
961 static bool registered = false;
962 if (!registered) {
963 registered = true;
964 RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
967 gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
968 JSContext* context = nsContentUtils::GetCurrentJSContext();
969 if (context) {
970 JS_updateMallocCounter(context, mWidth * mHeight * 4);
973 mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
974 if (mTarget->GetBackendType() == mgfx::BackendType::CAIRO) {
975 // Cairo doesn't play well with huge clips. When given a very big clip it
976 // will try to allocate big mask surface without taking the target
977 // size into account which can cause OOM. See bug 1034593.
978 // This limits the clip extents to the size of the canvas.
979 // A fix in Cairo would probably be preferable, but requires somewhat
980 // invasive changes.
981 mTarget->PushClipRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
983 // Force a full layer transaction since we didn't have a layer before
984 // and now we might need one.
985 if (mCanvasElement) {
986 mCanvasElement->InvalidateCanvas();
988 // Calling Redraw() tells our invalidation machinery that the entire
989 // canvas is already invalid, which can speed up future drawing.
990 Redraw();
991 } else {
992 EnsureErrorTarget();
993 mTarget = sErrorTarget;
997 #ifdef DEBUG
998 int32_t
999 CanvasRenderingContext2D::GetWidth() const
1001 return mWidth;
1004 int32_t
1005 CanvasRenderingContext2D::GetHeight() const
1007 return mHeight;
1009 #endif
1011 NS_IMETHODIMP
1012 CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height)
1014 ClearTarget();
1016 // Zero sized surfaces can cause problems.
1017 mZero = false;
1018 if (height == 0) {
1019 height = 1;
1020 mZero = true;
1022 if (width == 0) {
1023 width = 1;
1024 mZero = true;
1026 mWidth = width;
1027 mHeight = height;
1029 return NS_OK;
1032 void
1033 CanvasRenderingContext2D::ClearTarget()
1035 Reset();
1037 mResetLayer = true;
1039 // set up the initial canvas defaults
1040 mStyleStack.Clear();
1041 mPathBuilder = nullptr;
1042 mPath = nullptr;
1043 mDSPathBuilder = nullptr;
1045 ContextState *state = mStyleStack.AppendElement();
1046 state->globalAlpha = 1.0;
1048 state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
1049 state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
1050 state->shadowColor = NS_RGBA(0,0,0,0);
1053 NS_IMETHODIMP
1054 CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell,
1055 gfxASurface *surface,
1056 int32_t width,
1057 int32_t height)
1059 mDocShell = shell;
1061 SetDimensions(width, height);
1062 mTarget = gfxPlatform::GetPlatform()->
1063 CreateDrawTargetForSurface(surface, IntSize(width, height));
1065 if (!mTarget) {
1066 EnsureErrorTarget();
1067 mTarget = sErrorTarget;
1070 if (mTarget->GetBackendType() == mgfx::BackendType::CAIRO) {
1071 // Cf comment in EnsureTarget
1072 mTarget->PushClipRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
1075 return NS_OK;
1078 NS_IMETHODIMP
1079 CanvasRenderingContext2D::SetIsOpaque(bool isOpaque)
1081 if (isOpaque != mOpaque) {
1082 mOpaque = isOpaque;
1083 ClearTarget();
1086 return NS_OK;
1089 NS_IMETHODIMP
1090 CanvasRenderingContext2D::SetIsIPC(bool isIPC)
1092 if (isIPC != mIPC) {
1093 mIPC = isIPC;
1094 ClearTarget();
1097 return NS_OK;
1100 NS_IMETHODIMP
1101 CanvasRenderingContext2D::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions)
1103 if (aOptions.isNullOrUndefined()) {
1104 return NS_OK;
1107 ContextAttributes2D attributes;
1108 NS_ENSURE_TRUE(attributes.Init(aCx, aOptions), NS_ERROR_UNEXPECTED);
1110 if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) {
1111 // Use software when there is going to be a lot of readback
1112 mForceSoftware = attributes.mWillReadFrequently;
1115 if (!attributes.mAlpha) {
1116 SetIsOpaque(true);
1119 return NS_OK;
1122 void
1123 CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer,
1124 int32_t* aFormat)
1126 *aImageBuffer = nullptr;
1127 *aFormat = 0;
1129 EnsureTarget();
1130 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
1131 if (!snapshot) {
1132 return;
1135 RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
1136 if (!data || data->GetSize() != IntSize(mWidth, mHeight)) {
1137 return;
1140 *aImageBuffer = SurfaceToPackedBGRA(data);
1141 *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1144 nsString CanvasRenderingContext2D::GetHitRegion(const mozilla::gfx::Point& aPoint)
1146 for (size_t x = 0 ; x < mHitRegionsOptions.Length(); x++) {
1147 RegionInfo& info = mHitRegionsOptions[x];
1148 if (info.mPath->ContainsPoint(aPoint, Matrix())) {
1149 return info.mId;
1152 return nsString();
1155 NS_IMETHODIMP
1156 CanvasRenderingContext2D::GetInputStream(const char *aMimeType,
1157 const char16_t *aEncoderOptions,
1158 nsIInputStream **aStream)
1160 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1161 enccid += aMimeType;
1162 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1163 if (!encoder) {
1164 return NS_ERROR_FAILURE;
1167 nsAutoArrayPtr<uint8_t> imageBuffer;
1168 int32_t format = 0;
1169 GetImageBuffer(getter_Transfers(imageBuffer), &format);
1170 if (!imageBuffer) {
1171 return NS_ERROR_FAILURE;
1174 return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
1175 encoder, aEncoderOptions, aStream);
1178 SurfaceFormat
1179 CanvasRenderingContext2D::GetSurfaceFormat() const
1181 return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
1185 // state
1188 void
1189 CanvasRenderingContext2D::Save()
1191 EnsureTarget();
1192 mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform();
1193 mStyleStack.SetCapacity(mStyleStack.Length() + 1);
1194 mStyleStack.AppendElement(CurrentState());
1197 void
1198 CanvasRenderingContext2D::Restore()
1200 if (mStyleStack.Length() - 1 == 0)
1201 return;
1203 TransformWillUpdate();
1205 for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) {
1206 mTarget->PopClip();
1209 mStyleStack.RemoveElementAt(mStyleStack.Length() - 1);
1211 mTarget->SetTransform(CurrentState().transform);
1215 // transformations
1218 void
1219 CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error)
1221 TransformWillUpdate();
1222 if (!IsTargetValid()) {
1223 error.Throw(NS_ERROR_FAILURE);
1224 return;
1227 Matrix newMatrix = mTarget->GetTransform();
1228 mTarget->SetTransform(newMatrix.Scale(x, y));
1231 void
1232 CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error)
1234 TransformWillUpdate();
1235 if (!IsTargetValid()) {
1236 error.Throw(NS_ERROR_FAILURE);
1237 return;
1240 Matrix rotation = Matrix::Rotation(angle);
1241 mTarget->SetTransform(rotation * mTarget->GetTransform());
1244 void
1245 CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error)
1247 TransformWillUpdate();
1248 if (!IsTargetValid()) {
1249 error.Throw(NS_ERROR_FAILURE);
1250 return;
1253 Matrix newMatrix = mTarget->GetTransform();
1254 mTarget->SetTransform(newMatrix.Translate(x, y));
1257 void
1258 CanvasRenderingContext2D::Transform(double m11, double m12, double m21,
1259 double m22, double dx, double dy,
1260 ErrorResult& error)
1262 TransformWillUpdate();
1263 if (!IsTargetValid()) {
1264 error.Throw(NS_ERROR_FAILURE);
1265 return;
1268 Matrix matrix(m11, m12, m21, m22, dx, dy);
1269 mTarget->SetTransform(matrix * mTarget->GetTransform());
1272 void
1273 CanvasRenderingContext2D::SetTransform(double m11, double m12,
1274 double m21, double m22,
1275 double dx, double dy,
1276 ErrorResult& error)
1278 TransformWillUpdate();
1279 if (!IsTargetValid()) {
1280 error.Throw(NS_ERROR_FAILURE);
1281 return;
1284 Matrix matrix(m11, m12, m21, m22, dx, dy);
1285 mTarget->SetTransform(matrix);
1288 static void
1289 MatrixToJSObject(JSContext* cx, const Matrix& matrix,
1290 JS::MutableHandle<JSObject*> result, ErrorResult& error)
1292 double elts[6] = { matrix._11, matrix._12,
1293 matrix._21, matrix._22,
1294 matrix._31, matrix._32 };
1296 // XXX Should we enter GetWrapper()'s compartment?
1297 JS::Rooted<JS::Value> val(cx);
1298 if (!ToJSValue(cx, elts, &val)) {
1299 error.Throw(NS_ERROR_OUT_OF_MEMORY);
1300 } else {
1301 result.set(&val.toObject());
1305 static bool
1306 ObjectToMatrix(JSContext* cx, JS::Handle<JSObject*> obj, Matrix& matrix,
1307 ErrorResult& error)
1309 uint32_t length;
1310 if (!JS_GetArrayLength(cx, obj, &length) || length != 6) {
1311 // Not an array-like thing or wrong size
1312 error.Throw(NS_ERROR_INVALID_ARG);
1313 return false;
1316 Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22,
1317 &matrix._31, &matrix._32 };
1318 for (uint32_t i = 0; i < 6; ++i) {
1319 JS::Rooted<JS::Value> elt(cx);
1320 double d;
1321 if (!JS_GetElement(cx, obj, i, &elt)) {
1322 error.Throw(NS_ERROR_FAILURE);
1323 return false;
1325 if (!CoerceDouble(elt, &d)) {
1326 error.Throw(NS_ERROR_INVALID_ARG);
1327 return false;
1329 if (!FloatValidate(d)) {
1330 // This is weird, but it's the behavior of SetTransform()
1331 return false;
1333 *elts[i] = Float(d);
1335 return true;
1338 void
1339 CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx,
1340 JS::Handle<JSObject*> currentTransform,
1341 ErrorResult& error)
1343 EnsureTarget();
1344 if (!IsTargetValid()) {
1345 error.Throw(NS_ERROR_FAILURE);
1346 return;
1349 Matrix newCTM;
1350 if (ObjectToMatrix(cx, currentTransform, newCTM, error)) {
1351 mTarget->SetTransform(newCTM);
1355 void
1356 CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx,
1357 JS::MutableHandle<JSObject*> result,
1358 ErrorResult& error) const
1360 MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(),
1361 result, error);
1364 void
1365 CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx,
1366 JS::Handle<JSObject*> currentTransform,
1367 ErrorResult& error)
1369 EnsureTarget();
1370 if (!IsTargetValid()) {
1371 error.Throw(NS_ERROR_FAILURE);
1372 return;
1375 Matrix newCTMInverse;
1376 if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) {
1377 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
1378 if (newCTMInverse.Invert()) {
1379 mTarget->SetTransform(newCTMInverse);
1384 void
1385 CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx,
1386 JS::MutableHandle<JSObject*> result,
1387 ErrorResult& error) const
1389 if (!mTarget) {
1390 MatrixToJSObject(cx, Matrix(), result, error);
1391 return;
1394 Matrix ctm = mTarget->GetTransform();
1396 if (!ctm.Invert()) {
1397 double NaN = JS_GetNaNValue(cx).toDouble();
1398 ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN);
1401 MatrixToJSObject(cx, ctm, result, error);
1405 // colors
1408 void
1409 CanvasRenderingContext2D::SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& value,
1410 Style whichStyle)
1412 if (value.IsString()) {
1413 SetStyleFromString(value.GetAsString(), whichStyle);
1414 return;
1417 if (value.IsCanvasGradient()) {
1418 SetStyleFromGradient(value.GetAsCanvasGradient(), whichStyle);
1419 return;
1422 if (value.IsCanvasPattern()) {
1423 SetStyleFromPattern(value.GetAsCanvasPattern(), whichStyle);
1424 return;
1427 MOZ_ASSERT_UNREACHABLE("Invalid union value");
1430 void
1431 CanvasRenderingContext2D::SetFillRule(const nsAString& aString)
1433 FillRule rule;
1435 if (aString.EqualsLiteral("evenodd"))
1436 rule = FillRule::FILL_EVEN_ODD;
1437 else if (aString.EqualsLiteral("nonzero"))
1438 rule = FillRule::FILL_WINDING;
1439 else
1440 return;
1442 CurrentState().fillRule = rule;
1445 void
1446 CanvasRenderingContext2D::GetFillRule(nsAString& aString)
1448 switch (CurrentState().fillRule) {
1449 case FillRule::FILL_WINDING:
1450 aString.AssignLiteral("nonzero"); break;
1451 case FillRule::FILL_EVEN_ODD:
1452 aString.AssignLiteral("evenodd"); break;
1456 // gradients and patterns
1458 already_AddRefed<CanvasGradient>
1459 CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1)
1461 nsRefPtr<CanvasGradient> grad =
1462 new CanvasLinearGradient(this, Point(x0, y0), Point(x1, y1));
1464 return grad.forget();
1467 already_AddRefed<CanvasGradient>
1468 CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0,
1469 double x1, double y1, double r1,
1470 ErrorResult& aError)
1472 if (r0 < 0.0 || r1 < 0.0) {
1473 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
1474 return nullptr;
1477 nsRefPtr<CanvasGradient> grad =
1478 new CanvasRadialGradient(this, Point(x0, y0), r0, Point(x1, y1), r1);
1480 return grad.forget();
1483 already_AddRefed<CanvasPattern>
1484 CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element,
1485 const nsAString& repeat,
1486 ErrorResult& error)
1488 CanvasPattern::RepeatMode repeatMode =
1489 CanvasPattern::RepeatMode::NOREPEAT;
1491 if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) {
1492 repeatMode = CanvasPattern::RepeatMode::REPEAT;
1493 } else if (repeat.EqualsLiteral("repeat-x")) {
1494 repeatMode = CanvasPattern::RepeatMode::REPEATX;
1495 } else if (repeat.EqualsLiteral("repeat-y")) {
1496 repeatMode = CanvasPattern::RepeatMode::REPEATY;
1497 } else if (repeat.EqualsLiteral("no-repeat")) {
1498 repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
1499 } else {
1500 error.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1501 return nullptr;
1504 Element* htmlElement;
1505 if (element.IsHTMLCanvasElement()) {
1506 HTMLCanvasElement* canvas = &element.GetAsHTMLCanvasElement();
1507 htmlElement = canvas;
1509 nsIntSize size = canvas->GetSize();
1510 if (size.width == 0 || size.height == 0) {
1511 error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1512 return nullptr;
1515 // Special case for Canvas, which could be an Azure canvas!
1516 nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0);
1517 if (srcCanvas) {
1518 // This might not be an Azure canvas!
1519 RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
1521 nsRefPtr<CanvasPattern> pat =
1522 new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false);
1524 return pat.forget();
1526 } else if (element.IsHTMLImageElement()) {
1527 HTMLImageElement* img = &element.GetAsHTMLImageElement();
1528 if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) {
1529 error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1530 return nullptr;
1533 htmlElement = img;
1534 } else {
1535 htmlElement = &element.GetAsHTMLVideoElement();
1538 EnsureTarget();
1540 // The canvas spec says that createPattern should use the first frame
1541 // of animated images
1542 nsLayoutUtils::SurfaceFromElementResult res =
1543 nsLayoutUtils::SurfaceFromElement(htmlElement,
1544 nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
1546 if (!res.mSourceSurface) {
1547 error.Throw(NS_ERROR_NOT_AVAILABLE);
1548 return nullptr;
1551 nsRefPtr<CanvasPattern> pat =
1552 new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal,
1553 res.mIsWriteOnly, res.mCORSUsed);
1555 return pat.forget();
1559 // shadows
1561 void
1562 CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor)
1564 nscolor color;
1565 if (!ParseColor(shadowColor, &color)) {
1566 return;
1569 CurrentState().shadowColor = color;
1573 // rects
1576 void
1577 CanvasRenderingContext2D::ClearRect(double x, double y, double w,
1578 double h)
1580 if (!mTarget) {
1581 return;
1584 mTarget->ClearRect(mgfx::Rect(x, y, w, h));
1586 RedrawUser(gfxRect(x, y, w, h));
1589 void
1590 CanvasRenderingContext2D::FillRect(double x, double y, double w,
1591 double h)
1593 const ContextState &state = CurrentState();
1595 if (state.patternStyles[Style::FILL]) {
1596 CanvasPattern::RepeatMode repeat =
1597 state.patternStyles[Style::FILL]->mRepeat;
1598 // In the FillRect case repeat modes are easy to deal with.
1599 bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATY;
1600 bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATX;
1602 IntSize patternSize =
1603 state.patternStyles[Style::FILL]->mSurface->GetSize();
1605 // We always need to execute painting for non-over operators, even if
1606 // we end up with w/h = 0.
1607 if (limitx) {
1608 if (x < 0) {
1609 w += x;
1610 if (w < 0) {
1611 w = 0;
1614 x = 0;
1616 if (x + w > patternSize.width) {
1617 w = patternSize.width - x;
1618 if (w < 0) {
1619 w = 0;
1623 if (limity) {
1624 if (y < 0) {
1625 h += y;
1626 if (h < 0) {
1627 h = 0;
1630 y = 0;
1632 if (y + h > patternSize.height) {
1633 h = patternSize.height - y;
1634 if (h < 0) {
1635 h = 0;
1641 mgfx::Rect bounds;
1643 EnsureTarget();
1644 if (NeedToDrawShadow()) {
1645 bounds = mgfx::Rect(x, y, w, h);
1646 bounds = mTarget->GetTransform().TransformBounds(bounds);
1649 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1650 FillRect(mgfx::Rect(x, y, w, h),
1651 CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
1652 DrawOptions(state.globalAlpha, UsedOperation()));
1654 RedrawUser(gfxRect(x, y, w, h));
1657 void
1658 CanvasRenderingContext2D::StrokeRect(double x, double y, double w,
1659 double h)
1661 const ContextState &state = CurrentState();
1663 mgfx::Rect bounds;
1665 if (!w && !h) {
1666 return;
1669 EnsureTarget();
1670 if (!IsTargetValid()) {
1671 return;
1674 if (NeedToDrawShadow()) {
1675 bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f,
1676 w + state.lineWidth, h + state.lineWidth);
1677 bounds = mTarget->GetTransform().TransformBounds(bounds);
1680 if (!h) {
1681 CapStyle cap = CapStyle::BUTT;
1682 if (state.lineJoin == JoinStyle::ROUND) {
1683 cap = CapStyle::ROUND;
1685 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1686 StrokeLine(Point(x, y), Point(x + w, y),
1687 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
1688 StrokeOptions(state.lineWidth, state.lineJoin,
1689 cap, state.miterLimit,
1690 state.dash.Length(),
1691 state.dash.Elements(),
1692 state.dashOffset),
1693 DrawOptions(state.globalAlpha, UsedOperation()));
1694 return;
1697 if (!w) {
1698 CapStyle cap = CapStyle::BUTT;
1699 if (state.lineJoin == JoinStyle::ROUND) {
1700 cap = CapStyle::ROUND;
1702 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1703 StrokeLine(Point(x, y), Point(x, y + h),
1704 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
1705 StrokeOptions(state.lineWidth, state.lineJoin,
1706 cap, state.miterLimit,
1707 state.dash.Length(),
1708 state.dash.Elements(),
1709 state.dashOffset),
1710 DrawOptions(state.globalAlpha, UsedOperation()));
1711 return;
1714 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1715 StrokeRect(mgfx::Rect(x, y, w, h),
1716 CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
1717 StrokeOptions(state.lineWidth, state.lineJoin,
1718 state.lineCap, state.miterLimit,
1719 state.dash.Length(),
1720 state.dash.Elements(),
1721 state.dashOffset),
1722 DrawOptions(state.globalAlpha, UsedOperation()));
1724 Redraw();
1728 // path bits
1731 void
1732 CanvasRenderingContext2D::BeginPath()
1734 mPath = nullptr;
1735 mPathBuilder = nullptr;
1736 mDSPathBuilder = nullptr;
1737 mPathTransformWillUpdate = false;
1740 void
1741 CanvasRenderingContext2D::Fill(const CanvasWindingRule& winding)
1743 EnsureUserSpacePath(winding);
1745 if (!mPath) {
1746 return;
1749 mgfx::Rect bounds;
1751 if (NeedToDrawShadow()) {
1752 bounds = mPath->GetBounds(mTarget->GetTransform());
1755 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1756 Fill(mPath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
1757 DrawOptions(CurrentState().globalAlpha, UsedOperation()));
1759 Redraw();
1762 void CanvasRenderingContext2D::Fill(const CanvasPath& path, const CanvasWindingRule& winding)
1764 EnsureTarget();
1766 RefPtr<gfx::Path> gfxpath = path.GetPath(winding, mTarget);
1768 if (!gfxpath) {
1769 return;
1772 mgfx::Rect bounds;
1774 if (NeedToDrawShadow()) {
1775 bounds = gfxpath->GetBounds(mTarget->GetTransform());
1778 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1779 Fill(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget),
1780 DrawOptions(CurrentState().globalAlpha, UsedOperation()));
1782 Redraw();
1785 void
1786 CanvasRenderingContext2D::Stroke()
1788 EnsureUserSpacePath();
1790 if (!mPath) {
1791 return;
1794 const ContextState &state = CurrentState();
1796 StrokeOptions strokeOptions(state.lineWidth, state.lineJoin,
1797 state.lineCap, state.miterLimit,
1798 state.dash.Length(), state.dash.Elements(),
1799 state.dashOffset);
1801 mgfx::Rect bounds;
1802 if (NeedToDrawShadow()) {
1803 bounds =
1804 mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
1807 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1808 Stroke(mPath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
1809 strokeOptions, DrawOptions(state.globalAlpha, UsedOperation()));
1811 Redraw();
1814 void
1815 CanvasRenderingContext2D::Stroke(const CanvasPath& path)
1817 EnsureTarget();
1819 RefPtr<gfx::Path> gfxpath = path.GetPath(CanvasWindingRule::Nonzero, mTarget);
1821 if (!gfxpath) {
1822 return;
1825 const ContextState &state = CurrentState();
1827 StrokeOptions strokeOptions(state.lineWidth, state.lineJoin,
1828 state.lineCap, state.miterLimit,
1829 state.dash.Length(), state.dash.Elements(),
1830 state.dashOffset);
1832 mgfx::Rect bounds;
1833 if (NeedToDrawShadow()) {
1834 bounds =
1835 gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform());
1838 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
1839 Stroke(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget),
1840 strokeOptions, DrawOptions(state.globalAlpha, UsedOperation()));
1842 Redraw();
1845 void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement)
1847 EnsureUserSpacePath();
1849 if (!mPath) {
1850 return;
1853 if(DrawCustomFocusRing(aElement)) {
1854 Save();
1856 // set state to conforming focus state
1857 ContextState& state = CurrentState();
1858 state.globalAlpha = 1.0;
1859 state.shadowBlur = 0;
1860 state.shadowOffset.x = 0;
1861 state.shadowOffset.y = 0;
1862 state.op = mozilla::gfx::CompositionOp::OP_OVER;
1864 state.lineCap = CapStyle::BUTT;
1865 state.lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL;
1866 state.lineWidth = 1;
1867 CurrentState().dash.Clear();
1869 // color and style of the rings is the same as for image maps
1870 // set the background focus color
1871 CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255));
1872 // draw the focus ring
1873 Stroke();
1875 // set dashing for foreground
1876 FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
1877 dash.AppendElement(1);
1878 dash.AppendElement(1);
1880 // set the foreground focus color
1881 CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255));
1882 // draw the focus ring
1883 Stroke();
1885 Restore();
1889 bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement)
1891 EnsureUserSpacePath();
1893 HTMLCanvasElement* canvas = GetCanvas();
1895 if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) {
1896 return false;
1899 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1900 if (fm) {
1901 // check that the element i focused
1902 nsCOMPtr<nsIDOMElement> focusedElement;
1903 fm->GetFocusedElement(getter_AddRefs(focusedElement));
1904 if (SameCOMIdentity(aElement.AsDOMNode(), focusedElement)) {
1905 nsPIDOMWindow *window = aElement.OwnerDoc()->GetWindow();
1906 if (window) {
1907 return window->ShouldShowFocusRing();
1912 return false;
1915 void
1916 CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding)
1918 EnsureUserSpacePath(winding);
1920 if (!mPath) {
1921 return;
1924 mTarget->PushClip(mPath);
1925 CurrentState().clipsPushed.push_back(mPath);
1928 void
1929 CanvasRenderingContext2D::Clip(const CanvasPath& path, const CanvasWindingRule& winding)
1931 EnsureTarget();
1933 RefPtr<gfx::Path> gfxpath = path.GetPath(winding, mTarget);
1935 if (!gfxpath) {
1936 return;
1939 mTarget->PushClip(gfxpath);
1940 CurrentState().clipsPushed.push_back(gfxpath);
1943 void
1944 CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2,
1945 double y2, double radius,
1946 ErrorResult& error)
1948 if (radius < 0) {
1949 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
1950 return;
1953 EnsureWritablePath();
1955 // Current point in user space!
1956 Point p0;
1957 if (mPathBuilder) {
1958 p0 = mPathBuilder->CurrentPoint();
1959 } else {
1960 Matrix invTransform = mTarget->GetTransform();
1961 if (!invTransform.Invert()) {
1962 return;
1965 p0 = invTransform * mDSPathBuilder->CurrentPoint();
1968 Point p1(x1, y1);
1969 Point p2(x2, y2);
1971 // Execute these calculations in double precision to avoid cumulative
1972 // rounding errors.
1973 double dir, a2, b2, c2, cosx, sinx, d, anx, any,
1974 bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1;
1975 bool anticlockwise;
1977 if (p0 == p1 || p1 == p2 || radius == 0) {
1978 LineTo(p1.x, p1.y);
1979 return;
1982 // Check for colinearity
1983 dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
1984 if (dir == 0) {
1985 LineTo(p1.x, p1.y);
1986 return;
1990 // XXX - Math for this code was already available from the non-azure code
1991 // and would be well tested. Perhaps converting to bezier directly might
1992 // be more efficient longer run.
1993 a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1);
1994 b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
1995 c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2);
1996 cosx = (a2+b2-c2)/(2*sqrt(a2*b2));
1998 sinx = sqrt(1 - cosx*cosx);
1999 d = radius / ((1 - cosx) / sinx);
2001 anx = (x1-p0.x) / sqrt(a2);
2002 any = (y1-p0.y) / sqrt(a2);
2003 bnx = (x1-x2) / sqrt(b2);
2004 bny = (y1-y2) / sqrt(b2);
2005 x3 = x1 - anx*d;
2006 y3 = y1 - any*d;
2007 x4 = x1 - bnx*d;
2008 y4 = y1 - bny*d;
2009 anticlockwise = (dir < 0);
2010 cx = x3 + any*radius*(anticlockwise ? 1 : -1);
2011 cy = y3 - anx*radius*(anticlockwise ? 1 : -1);
2012 angle0 = atan2((y3-cy), (x3-cx));
2013 angle1 = atan2((y4-cy), (x4-cx));
2016 LineTo(x3, y3);
2018 Arc(cx, cy, radius, angle0, angle1, anticlockwise, error);
2021 void
2022 CanvasRenderingContext2D::Arc(double x, double y, double r,
2023 double startAngle, double endAngle,
2024 bool anticlockwise, ErrorResult& error)
2026 if (r < 0.0) {
2027 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2028 return;
2031 EnsureWritablePath();
2033 ArcToBezier(this, Point(x, y), Size(r, r), startAngle, endAngle, anticlockwise);
2036 void
2037 CanvasRenderingContext2D::Rect(double x, double y, double w, double h)
2039 EnsureWritablePath();
2041 if (mPathBuilder) {
2042 mPathBuilder->MoveTo(Point(x, y));
2043 mPathBuilder->LineTo(Point(x + w, y));
2044 mPathBuilder->LineTo(Point(x + w, y + h));
2045 mPathBuilder->LineTo(Point(x, y + h));
2046 mPathBuilder->Close();
2047 } else {
2048 mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y));
2049 mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y));
2050 mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h));
2051 mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h));
2052 mDSPathBuilder->Close();
2056 void
2057 CanvasRenderingContext2D::EnsureWritablePath()
2059 if (mDSPathBuilder) {
2060 return;
2063 FillRule fillRule = CurrentState().fillRule;
2065 if (mPathBuilder) {
2066 if (mPathTransformWillUpdate) {
2067 mPath = mPathBuilder->Finish();
2068 mDSPathBuilder =
2069 mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
2070 mPath = nullptr;
2071 mPathBuilder = nullptr;
2072 mPathTransformWillUpdate = false;
2074 return;
2077 EnsureTarget();
2078 if (!mPath) {
2079 NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null");
2080 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
2081 } else if (!mPathTransformWillUpdate) {
2082 mPathBuilder = mPath->CopyToBuilder(fillRule);
2083 } else {
2084 mDSPathBuilder =
2085 mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
2086 mPathTransformWillUpdate = false;
2087 mPath = nullptr;
2091 void
2092 CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& winding)
2094 FillRule fillRule = CurrentState().fillRule;
2095 if(winding == CanvasWindingRule::Evenodd)
2096 fillRule = FillRule::FILL_EVEN_ODD;
2098 if (!mPath && !mPathBuilder && !mDSPathBuilder) {
2099 EnsureTarget();
2100 mPathBuilder = mTarget->CreatePathBuilder(fillRule);
2103 if (mPathBuilder) {
2104 mPath = mPathBuilder->Finish();
2105 mPathBuilder = nullptr;
2108 if (mPath &&
2109 mPathTransformWillUpdate) {
2110 mDSPathBuilder =
2111 mPath->TransformedCopyToBuilder(mPathToDS, fillRule);
2112 mPath = nullptr;
2113 mPathTransformWillUpdate = false;
2116 if (mDSPathBuilder) {
2117 RefPtr<Path> dsPath;
2118 dsPath = mDSPathBuilder->Finish();
2119 mDSPathBuilder = nullptr;
2121 Matrix inverse = mTarget->GetTransform();
2122 if (!inverse.Invert()) {
2123 NS_WARNING("Could not invert transform");
2124 return;
2127 mPathBuilder =
2128 dsPath->TransformedCopyToBuilder(inverse, fillRule);
2129 mPath = mPathBuilder->Finish();
2130 mPathBuilder = nullptr;
2133 if (mPath && mPath->GetFillRule() != fillRule) {
2134 mPathBuilder = mPath->CopyToBuilder(fillRule);
2135 mPath = mPathBuilder->Finish();
2136 mPathBuilder = nullptr;
2139 NS_ASSERTION(mPath, "mPath should exist");
2142 void
2143 CanvasRenderingContext2D::TransformWillUpdate()
2145 EnsureTarget();
2147 // Store the matrix that would transform the current path to device
2148 // space.
2149 if (mPath || mPathBuilder) {
2150 if (!mPathTransformWillUpdate) {
2151 // If the transform has already been updated, but a device space builder
2152 // has not been created yet mPathToDS contains the right transform to
2153 // transform the current mPath into device space.
2154 // We should leave it alone.
2155 mPathToDS = mTarget->GetTransform();
2157 mPathTransformWillUpdate = true;
2162 // text
2166 * Helper function for SetFont that creates a style rule for the given font.
2167 * @param aFont The CSS font string
2168 * @param aNode The canvas element
2169 * @param aResult Pointer in which to place the new style rule.
2170 * @remark Assumes all pointer arguments are non-null.
2172 static nsresult
2173 CreateFontStyleRule(const nsAString& aFont,
2174 nsINode* aNode,
2175 StyleRule** aResult)
2177 nsRefPtr<StyleRule> rule;
2178 bool changed;
2180 nsIPrincipal* principal = aNode->NodePrincipal();
2181 nsIDocument* document = aNode->OwnerDoc();
2183 nsIURI* docURL = document->GetDocumentURI();
2184 nsIURI* baseURL = document->GetDocBaseURI();
2186 // Pass the CSS Loader object to the parser, to allow parser error reports
2187 // to include the outer window ID.
2188 nsCSSParser parser(document->CSSLoader());
2190 nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL,
2191 principal, getter_AddRefs(rule));
2192 if (NS_FAILED(rv)) {
2193 return rv;
2196 rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL,
2197 principal, rule->GetDeclaration(), &changed,
2198 false);
2199 if (NS_FAILED(rv))
2200 return rv;
2202 rv = parser.ParseProperty(eCSSProperty_line_height,
2203 NS_LITERAL_STRING("normal"), docURL, baseURL,
2204 principal, rule->GetDeclaration(), &changed,
2205 false);
2206 if (NS_FAILED(rv)) {
2207 return rv;
2210 rule->RuleMatched();
2212 rule.forget(aResult);
2213 return NS_OK;
2216 void
2217 CanvasRenderingContext2D::SetFont(const nsAString& font,
2218 ErrorResult& error)
2221 * If font is defined with relative units (e.g. ems) and the parent
2222 * style context changes in between calls, setting the font to the
2223 * same value as previous could result in a different computed value,
2224 * so we cannot have the optimization where we check if the new font
2225 * string is equal to the old one.
2228 if (!mCanvasElement && !mDocShell) {
2229 NS_WARNING("Canvas element must be non-null or a docshell must be provided");
2230 error.Throw(NS_ERROR_FAILURE);
2231 return;
2234 nsIPresShell* presShell = GetPresShell();
2235 if (!presShell) {
2236 error.Throw(NS_ERROR_FAILURE);
2237 return;
2239 nsIDocument* document = presShell->GetDocument();
2241 nsRefPtr<css::StyleRule> rule;
2242 error = CreateFontStyleRule(font, document, getter_AddRefs(rule));
2244 if (error.Failed()) {
2245 return;
2248 css::Declaration *declaration = rule->GetDeclaration();
2249 // The easiest way to see whether we got a syntax error or whether
2250 // we got 'inherit' or 'initial' is to look at font-size-adjust,
2251 // which the shorthand resets to either 'none' or
2252 // '-moz-system-font'.
2253 // We know the declaration is not !important, so we can use
2254 // GetNormalBlock().
2255 const nsCSSValue *fsaVal =
2256 declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust);
2257 if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None &&
2258 fsaVal->GetUnit() != eCSSUnit_System_Font)) {
2259 // We got an all-property value or a syntax error. The spec says
2260 // this value must be ignored.
2261 return;
2264 nsTArray< nsCOMPtr<nsIStyleRule> > rules;
2265 rules.AppendElement(rule);
2267 nsStyleSet* styleSet = presShell->StyleSet();
2269 // have to get a parent style context for inherit-like relative
2270 // values (2em, bolder, etc.)
2271 nsRefPtr<nsStyleContext> parentContext;
2273 if (mCanvasElement && mCanvasElement->IsInDoc()) {
2274 // inherit from the canvas element
2275 parentContext = nsComputedDOMStyle::GetStyleContextForElement(
2276 mCanvasElement,
2277 nullptr,
2278 presShell);
2279 } else {
2280 // otherwise inherit from default (10px sans-serif)
2281 nsRefPtr<css::StyleRule> parentRule;
2282 error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"),
2283 document,
2284 getter_AddRefs(parentRule));
2286 if (error.Failed()) {
2287 return;
2290 nsTArray< nsCOMPtr<nsIStyleRule> > parentRules;
2291 parentRules.AppendElement(parentRule);
2292 parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules);
2295 if (!parentContext) {
2296 error.Throw(NS_ERROR_FAILURE);
2297 return;
2300 // add a rule to prevent text zoom from affecting the style
2301 rules.AppendElement(new nsDisableTextZoomStyleRule);
2303 nsRefPtr<nsStyleContext> sc =
2304 styleSet->ResolveStyleForRules(parentContext, rules);
2305 if (!sc) {
2306 error.Throw(NS_ERROR_FAILURE);
2307 return;
2310 const nsStyleFont* fontStyle = sc->StyleFont();
2312 NS_ASSERTION(fontStyle, "Could not obtain font style");
2314 nsIAtom* language = sc->StyleFont()->mLanguage;
2315 if (!language) {
2316 language = presShell->GetPresContext()->GetLanguageFromCharset();
2319 // use CSS pixels instead of dev pixels to avoid being affected by page zoom
2320 const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel();
2322 bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview ||
2323 presShell->GetPresContext()->Type() == nsPresContext::eContext_Print);
2325 // Purposely ignore the font size that respects the user's minimum
2326 // font preference (fontStyle->mFont.size) in favor of the computed
2327 // size (fontStyle->mSize). See
2328 // https://bugzilla.mozilla.org/show_bug.cgi?id=698652.
2329 MOZ_ASSERT(!fontStyle->mAllowZoom,
2330 "expected text zoom to be disabled on this nsStyleFont");
2331 gfxFontStyle style(fontStyle->mFont.style,
2332 fontStyle->mFont.weight,
2333 fontStyle->mFont.stretch,
2334 NSAppUnitsToFloatPixels(fontStyle->mSize, float(aupcp)),
2335 language,
2336 fontStyle->mFont.sizeAdjust,
2337 fontStyle->mFont.systemFont,
2338 printerFont,
2339 fontStyle->mFont.synthesis & NS_FONT_SYNTHESIS_WEIGHT,
2340 fontStyle->mFont.synthesis & NS_FONT_SYNTHESIS_STYLE,
2341 fontStyle->mFont.languageOverride);
2343 fontStyle->mFont.AddFontFeaturesToStyle(&style);
2345 nsPresContext *c = presShell->GetPresContext();
2346 CurrentState().fontGroup =
2347 gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.fontlist,
2348 &style,
2349 c->GetUserFontSet());
2350 NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
2351 CurrentState().fontGroup->SetTextPerfMetrics(c->GetTextPerfMetrics());
2353 // The font getter is required to be reserialized based on what we
2354 // parsed (including having line-height removed). (Older drafts of
2355 // the spec required font sizes be converted to pixels, but that no
2356 // longer seems to be required.)
2357 declaration->GetValue(eCSSProperty_font, CurrentState().font);
2360 void
2361 CanvasRenderingContext2D::SetTextAlign(const nsAString& ta)
2363 if (ta.EqualsLiteral("start"))
2364 CurrentState().textAlign = TextAlign::START;
2365 else if (ta.EqualsLiteral("end"))
2366 CurrentState().textAlign = TextAlign::END;
2367 else if (ta.EqualsLiteral("left"))
2368 CurrentState().textAlign = TextAlign::LEFT;
2369 else if (ta.EqualsLiteral("right"))
2370 CurrentState().textAlign = TextAlign::RIGHT;
2371 else if (ta.EqualsLiteral("center"))
2372 CurrentState().textAlign = TextAlign::CENTER;
2375 void
2376 CanvasRenderingContext2D::GetTextAlign(nsAString& ta)
2378 switch (CurrentState().textAlign)
2380 case TextAlign::START:
2381 ta.AssignLiteral("start");
2382 break;
2383 case TextAlign::END:
2384 ta.AssignLiteral("end");
2385 break;
2386 case TextAlign::LEFT:
2387 ta.AssignLiteral("left");
2388 break;
2389 case TextAlign::RIGHT:
2390 ta.AssignLiteral("right");
2391 break;
2392 case TextAlign::CENTER:
2393 ta.AssignLiteral("center");
2394 break;
2398 void
2399 CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb)
2401 if (tb.EqualsLiteral("top"))
2402 CurrentState().textBaseline = TextBaseline::TOP;
2403 else if (tb.EqualsLiteral("hanging"))
2404 CurrentState().textBaseline = TextBaseline::HANGING;
2405 else if (tb.EqualsLiteral("middle"))
2406 CurrentState().textBaseline = TextBaseline::MIDDLE;
2407 else if (tb.EqualsLiteral("alphabetic"))
2408 CurrentState().textBaseline = TextBaseline::ALPHABETIC;
2409 else if (tb.EqualsLiteral("ideographic"))
2410 CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC;
2411 else if (tb.EqualsLiteral("bottom"))
2412 CurrentState().textBaseline = TextBaseline::BOTTOM;
2415 void
2416 CanvasRenderingContext2D::GetTextBaseline(nsAString& tb)
2418 switch (CurrentState().textBaseline)
2420 case TextBaseline::TOP:
2421 tb.AssignLiteral("top");
2422 break;
2423 case TextBaseline::HANGING:
2424 tb.AssignLiteral("hanging");
2425 break;
2426 case TextBaseline::MIDDLE:
2427 tb.AssignLiteral("middle");
2428 break;
2429 case TextBaseline::ALPHABETIC:
2430 tb.AssignLiteral("alphabetic");
2431 break;
2432 case TextBaseline::IDEOGRAPHIC:
2433 tb.AssignLiteral("ideographic");
2434 break;
2435 case TextBaseline::BOTTOM:
2436 tb.AssignLiteral("bottom");
2437 break;
2442 * Helper function that replaces the whitespace characters in a string
2443 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
2444 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
2445 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
2446 * @param str The string whose whitespace characters to replace.
2448 static inline void
2449 TextReplaceWhitespaceCharacters(nsAutoString& str)
2451 str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' '));
2454 void
2455 CanvasRenderingContext2D::FillText(const nsAString& text, double x,
2456 double y,
2457 const Optional<double>& maxWidth,
2458 ErrorResult& error)
2460 error = DrawOrMeasureText(text, x, y, maxWidth, TextDrawOperation::FILL, nullptr);
2463 void
2464 CanvasRenderingContext2D::StrokeText(const nsAString& text, double x,
2465 double y,
2466 const Optional<double>& maxWidth,
2467 ErrorResult& error)
2469 error = DrawOrMeasureText(text, x, y, maxWidth, TextDrawOperation::STROKE, nullptr);
2472 TextMetrics*
2473 CanvasRenderingContext2D::MeasureText(const nsAString& rawText,
2474 ErrorResult& error)
2476 float width;
2477 Optional<double> maxWidth;
2478 error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, &width);
2479 if (error.Failed()) {
2480 return nullptr;
2483 return new TextMetrics(width);
2486 void
2487 CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& options, ErrorResult& error)
2489 // check if the path is valid
2490 EnsureUserSpacePath(CanvasWindingRule::Nonzero);
2491 if(!mPath) {
2492 error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2493 return;
2496 // get the bounds of the current path. They are relative to the canvas
2497 mgfx::Rect bounds(mPath->GetBounds(mTarget->GetTransform()));
2498 if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) {
2499 // The specified region has no pixels.
2500 error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2501 return;
2504 // remove old hit region first
2505 RemoveHitRegion(options.mId);
2507 if (options.mControl) {
2508 // also remove regions with this control
2509 for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
2510 RegionInfo& info = mHitRegionsOptions[x];
2511 if (info.mElement == options.mControl) {
2512 mHitRegionsOptions.RemoveElementAt(x);
2513 break;
2516 #ifdef ACCESSIBILITY
2517 options.mControl->SetProperty(nsGkAtoms::hitregion, new bool(true),
2518 nsINode::DeleteProperty<bool>);
2519 #endif
2522 // finally, add the region to the list
2523 RegionInfo info;
2524 info.mId = options.mId;
2525 info.mElement = options.mControl;
2526 RefPtr<PathBuilder> pathBuilder = mPath->TransformedCopyToBuilder(mTarget->GetTransform());
2527 info.mPath = pathBuilder->Finish();
2529 mHitRegionsOptions.InsertElementAt(0, info);
2532 void
2533 CanvasRenderingContext2D::RemoveHitRegion(const nsAString& id)
2535 if (id.Length() == 0) {
2536 return;
2539 for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) {
2540 RegionInfo& info = mHitRegionsOptions[x];
2541 if (info.mId == id) {
2542 mHitRegionsOptions.RemoveElementAt(x);
2544 return;
2549 bool
2550 CanvasRenderingContext2D::GetHitRegionRect(Element* aElement, nsRect& aRect)
2552 for (unsigned int x = 0; x < mHitRegionsOptions.Length(); x++) {
2553 RegionInfo& info = mHitRegionsOptions[x];
2554 if (info.mElement == aElement) {
2555 mgfx::Rect bounds(info.mPath->GetBounds());
2556 gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height);
2557 aRect = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel());
2559 return true;
2563 return false;
2567 * Used for nsBidiPresUtils::ProcessText
2569 struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
2571 typedef CanvasRenderingContext2D::ContextState ContextState;
2573 virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction)
2575 mFontgrp->UpdateFontList(); // ensure user font generation is current
2576 mTextRun = mFontgrp->MakeTextRun(text,
2577 length,
2578 mThebes,
2579 mAppUnitsPerDevPixel,
2580 direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
2583 virtual nscoord GetWidth()
2585 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
2586 mTextRun->GetLength(),
2587 mDoMeasureBoundingBox ?
2588 gfxFont::TIGHT_INK_EXTENTS :
2589 gfxFont::LOOSE_INK_EXTENTS,
2590 mThebes,
2591 nullptr);
2593 // this only measures the height; the total width is gotten from the
2594 // the return value of ProcessText.
2595 if (mDoMeasureBoundingBox) {
2596 textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
2597 mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
2600 return NSToCoordRound(textRunMetrics.mAdvanceWidth);
2603 virtual void DrawText(nscoord xOffset, nscoord width)
2605 gfxPoint point = mPt;
2606 point.x += xOffset;
2608 // offset is given in terms of left side of string
2609 if (mTextRun->IsRightToLeft()) {
2610 // Bug 581092 - don't use rounded pixel width to advance to
2611 // right-hand end of run, because this will cause different
2612 // glyph positioning for LTR vs RTL drawing of the same
2613 // glyph string on OS X and DWrite where textrun widths may
2614 // involve fractional pixels.
2615 gfxTextRun::Metrics textRunMetrics =
2616 mTextRun->MeasureText(0,
2617 mTextRun->GetLength(),
2618 mDoMeasureBoundingBox ?
2619 gfxFont::TIGHT_INK_EXTENTS :
2620 gfxFont::LOOSE_INK_EXTENTS,
2621 mThebes,
2622 nullptr);
2623 point.x += textRunMetrics.mAdvanceWidth;
2624 // old code was:
2625 // point.x += width * mAppUnitsPerDevPixel;
2626 // TODO: restore this if/when we move to fractional coords
2627 // throughout the text layout process
2630 uint32_t numRuns;
2631 const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns);
2632 const int32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel;
2633 const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
2634 Point baselineOrigin =
2635 Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit);
2637 float advanceSum = 0;
2639 mCtx->EnsureTarget();
2640 for (uint32_t c = 0; c < numRuns; c++) {
2641 gfxFont *font = runs[c].mFont;
2642 uint32_t endRun = 0;
2643 if (c + 1 < numRuns) {
2644 endRun = runs[c + 1].mCharacterOffset;
2645 } else {
2646 endRun = mTextRun->GetLength();
2649 const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs();
2651 RefPtr<ScaledFont> scaledFont =
2652 gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font);
2654 if (!scaledFont) {
2655 // This can occur when something switched DirectWrite off.
2656 return;
2659 RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions();
2661 GlyphBuffer buffer;
2663 std::vector<Glyph> glyphBuf;
2665 for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) {
2666 Glyph newGlyph;
2667 if (glyphs[i].IsSimpleGlyph()) {
2668 newGlyph.mIndex = glyphs[i].GetSimpleGlyph();
2669 if (mTextRun->IsRightToLeft()) {
2670 newGlyph.mPosition.x = baselineOrigin.x - advanceSum -
2671 glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
2672 } else {
2673 newGlyph.mPosition.x = baselineOrigin.x + advanceSum;
2675 newGlyph.mPosition.y = baselineOrigin.y;
2676 advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
2677 glyphBuf.push_back(newGlyph);
2678 continue;
2681 if (!glyphs[i].GetGlyphCount()) {
2682 continue;
2685 gfxTextRun::DetailedGlyph *detailedGlyphs =
2686 mTextRun->GetDetailedGlyphs(i);
2688 if (glyphs[i].IsMissing()) {
2689 newGlyph.mIndex = 0;
2690 if (mTextRun->IsRightToLeft()) {
2691 newGlyph.mPosition.x = baselineOrigin.x - advanceSum -
2692 detailedGlyphs[0].mAdvance * devUnitsPerAppUnit;
2693 } else {
2694 newGlyph.mPosition.x = baselineOrigin.x + advanceSum;
2696 newGlyph.mPosition.y = baselineOrigin.y;
2697 advanceSum += detailedGlyphs[0].mAdvance * devUnitsPerAppUnit;
2698 glyphBuf.push_back(newGlyph);
2699 continue;
2702 for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) {
2703 newGlyph.mIndex = detailedGlyphs[c].mGlyphID;
2704 if (mTextRun->IsRightToLeft()) {
2705 newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit -
2706 advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit;
2707 } else {
2708 newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum;
2710 newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit;
2711 glyphBuf.push_back(newGlyph);
2712 advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit;
2716 if (!glyphBuf.size()) {
2717 // This may happen for glyph runs for a 0 size font.
2718 continue;
2721 buffer.mGlyphs = &glyphBuf.front();
2722 buffer.mNumGlyphs = glyphBuf.size();
2724 Rect bounds = mCtx->mTarget->GetTransform().
2725 TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y,
2726 mBoundingBox.width, mBoundingBox.height));
2727 if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) {
2728 AdjustedTarget(mCtx, &bounds)->
2729 FillGlyphs(scaledFont, buffer,
2730 CanvasGeneralPattern().
2731 ForStyle(mCtx, CanvasRenderingContext2D::Style::FILL, mCtx->mTarget),
2732 DrawOptions(mState->globalAlpha, mCtx->UsedOperation()),
2733 renderingOptions);
2734 } else if (mOp == CanvasRenderingContext2D::TextDrawOperation::STROKE) {
2735 // stroke glyphs one at a time to avoid poor CoreGraphics performance
2736 // when stroking a path with a very large number of points
2737 buffer.mGlyphs = &glyphBuf.front();
2738 buffer.mNumGlyphs = 1;
2739 const ContextState& state = *mState;
2740 AdjustedTarget target(mCtx, &bounds);
2741 const StrokeOptions strokeOpts(state.lineWidth, state.lineJoin,
2742 state.lineCap, state.miterLimit,
2743 state.dash.Length(),
2744 state.dash.Elements(),
2745 state.dashOffset);
2746 CanvasGeneralPattern cgp;
2747 const Pattern& patForStyle
2748 (cgp.ForStyle(mCtx, CanvasRenderingContext2D::Style::STROKE, mCtx->mTarget));
2749 const DrawOptions drawOpts(state.globalAlpha, mCtx->UsedOperation());
2751 for (unsigned i = glyphBuf.size(); i > 0; --i) {
2752 RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget);
2753 target->Stroke(path, patForStyle, strokeOpts, drawOpts);
2754 buffer.mGlyphs++;
2760 // current text run
2761 nsAutoPtr<gfxTextRun> mTextRun;
2763 // pointer to a screen reference context used to measure text and such
2764 nsRefPtr<gfxContext> mThebes;
2766 // Pointer to the draw target we should fill our text to
2767 CanvasRenderingContext2D *mCtx;
2769 // position of the left side of the string, alphabetic baseline
2770 gfxPoint mPt;
2772 // current font
2773 gfxFontGroup* mFontgrp;
2775 // dev pixel conversion factor
2776 int32_t mAppUnitsPerDevPixel;
2778 // operation (fill or stroke)
2779 CanvasRenderingContext2D::TextDrawOperation mOp;
2781 // context state
2782 ContextState *mState;
2784 // union of bounding boxes of all runs, needed for shadows
2785 gfxRect mBoundingBox;
2787 // true iff the bounding box should be measured
2788 bool mDoMeasureBoundingBox;
2791 nsresult
2792 CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
2793 float aX,
2794 float aY,
2795 const Optional<double>& aMaxWidth,
2796 TextDrawOperation aOp,
2797 float* aWidth)
2799 nsresult rv;
2801 // spec isn't clear on what should happen if aMaxWidth <= 0, so
2802 // treat it as an invalid argument
2803 // technically, 0 should be an invalid value as well, but 0 is the default
2804 // arg, and there is no way to tell if the default was used
2805 if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0)
2806 return NS_ERROR_INVALID_ARG;
2808 if (!mCanvasElement && !mDocShell) {
2809 NS_WARNING("Canvas element must be non-null or a docshell must be provided");
2810 return NS_ERROR_FAILURE;
2813 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
2814 if (!presShell)
2815 return NS_ERROR_FAILURE;
2817 nsIDocument* document = presShell->GetDocument();
2819 // replace all the whitespace characters with U+0020 SPACE
2820 nsAutoString textToDraw(aRawText);
2821 TextReplaceWhitespaceCharacters(textToDraw);
2823 // for now, default to ltr if not in doc
2824 bool isRTL = false;
2826 if (mCanvasElement && mCanvasElement->IsInDoc()) {
2827 // try to find the closest context
2828 nsRefPtr<nsStyleContext> canvasStyle =
2829 nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
2830 nullptr,
2831 presShell);
2832 if (!canvasStyle) {
2833 return NS_ERROR_FAILURE;
2836 isRTL = canvasStyle->StyleVisibility()->mDirection ==
2837 NS_STYLE_DIRECTION_RTL;
2838 } else {
2839 isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL;
2842 gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
2843 NS_ASSERTION(currentFontStyle, "font group is null");
2845 // ensure user font set is up to date
2846 currentFontStyle->
2847 SetUserFontSet(presShell->GetPresContext()->GetUserFontSet());
2849 if (currentFontStyle->GetStyle()->size == 0.0F) {
2850 if (aWidth) {
2851 *aWidth = 0;
2853 return NS_OK;
2856 const ContextState &state = CurrentState();
2858 // This is only needed to know if we can know the drawing bounding box easily.
2859 bool doDrawShadow = NeedToDrawShadow();
2861 CanvasBidiProcessor processor;
2863 GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
2864 processor.mPt = gfxPoint(aX, aY);
2865 processor.mThebes =
2866 new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
2868 // If we don't have a target then we don't have a transform. A target won't
2869 // be needed in the case where we're measuring the text size. This allows
2870 // to avoid creating a target if it's only being used to measure text sizes.
2871 if (mTarget) {
2872 Matrix matrix = mTarget->GetTransform();
2873 processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32));
2875 processor.mCtx = this;
2876 processor.mOp = aOp;
2877 processor.mBoundingBox = gfxRect(0, 0, 0, 0);
2878 processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid;
2879 processor.mState = &CurrentState();
2880 processor.mFontgrp = currentFontStyle;
2882 nscoord totalWidthCoord;
2884 // calls bidi algo twice since it needs the full text width and the
2885 // bounding boxes before rendering anything
2886 nsBidi bidiEngine;
2887 rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
2888 textToDraw.Length(),
2889 isRTL ? NSBIDI_RTL : NSBIDI_LTR,
2890 presShell->GetPresContext(),
2891 processor,
2892 nsBidiPresUtils::MODE_MEASURE,
2893 nullptr,
2895 &totalWidthCoord,
2896 &bidiEngine);
2897 if (NS_FAILED(rv)) {
2898 return rv;
2901 float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
2902 if (aWidth) {
2903 *aWidth = totalWidth;
2906 // if only measuring, don't need to do any more work
2907 if (aOp==TextDrawOperation::MEASURE) {
2908 return NS_OK;
2911 // offset pt.x based on text align
2912 gfxFloat anchorX;
2914 if (state.textAlign == TextAlign::CENTER) {
2915 anchorX = .5;
2916 } else if (state.textAlign == TextAlign::LEFT ||
2917 (!isRTL && state.textAlign == TextAlign::START) ||
2918 (isRTL && state.textAlign == TextAlign::END)) {
2919 anchorX = 0;
2920 } else {
2921 anchorX = 1;
2924 processor.mPt.x -= anchorX * totalWidth;
2926 // offset pt.y based on text baseline
2927 processor.mFontgrp->UpdateFontList(); // ensure user font generation is current
2928 NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts");
2929 const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics();
2931 gfxFloat anchorY;
2933 switch (state.textBaseline)
2935 case TextBaseline::HANGING:
2936 // fall through; best we can do with the information available
2937 case TextBaseline::TOP:
2938 anchorY = fontMetrics.emAscent;
2939 break;
2940 case TextBaseline::MIDDLE:
2941 anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
2942 break;
2943 case TextBaseline::IDEOGRAPHIC:
2944 // fall through; best we can do with the information available
2945 case TextBaseline::ALPHABETIC:
2946 anchorY = 0;
2947 break;
2948 case TextBaseline::BOTTOM:
2949 anchorY = -fontMetrics.emDescent;
2950 break;
2951 default:
2952 MOZ_CRASH("unexpected TextBaseline");
2955 processor.mPt.y += anchorY;
2957 // correct bounding box to get it to be the correct size/position
2958 processor.mBoundingBox.width = totalWidth;
2959 processor.mBoundingBox.MoveBy(processor.mPt);
2961 processor.mPt.x *= processor.mAppUnitsPerDevPixel;
2962 processor.mPt.y *= processor.mAppUnitsPerDevPixel;
2964 EnsureTarget();
2965 Matrix oldTransform = mTarget->GetTransform();
2966 // if text is over aMaxWidth, then scale the text horizontally such that its
2967 // width is precisely aMaxWidth
2968 if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 &&
2969 totalWidth > aMaxWidth.Value()) {
2970 Matrix newTransform = oldTransform;
2972 // Translate so that the anchor point is at 0,0, then scale and then
2973 // translate back.
2974 newTransform.Translate(aX, 0);
2975 newTransform.Scale(aMaxWidth.Value() / totalWidth, 1);
2976 newTransform.Translate(-aX, 0);
2977 /* we do this to avoid an ICE in the android compiler */
2978 Matrix androidCompilerBug = newTransform;
2979 mTarget->SetTransform(androidCompilerBug);
2982 // save the previous bounding box
2983 gfxRect boundingBox = processor.mBoundingBox;
2985 // don't ever need to measure the bounding box twice
2986 processor.mDoMeasureBoundingBox = false;
2988 rv = nsBidiPresUtils::ProcessText(textToDraw.get(),
2989 textToDraw.Length(),
2990 isRTL ? NSBIDI_RTL : NSBIDI_LTR,
2991 presShell->GetPresContext(),
2992 processor,
2993 nsBidiPresUtils::MODE_DRAW,
2994 nullptr,
2996 nullptr,
2997 &bidiEngine);
3000 mTarget->SetTransform(oldTransform);
3002 if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
3003 !doDrawShadow) {
3004 RedrawUser(boundingBox);
3005 return NS_OK;
3008 Redraw();
3009 return NS_OK;
3012 gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle()
3014 // use lazy initilization for the font group since it's rather expensive
3015 if (!CurrentState().fontGroup) {
3016 ErrorResult err;
3017 NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif");
3018 static float kDefaultFontSize = 10.0;
3019 SetFont(kDefaultFontStyle, err);
3020 if (err.Failed()) {
3021 gfxFontStyle style;
3022 style.size = kDefaultFontSize;
3023 CurrentState().fontGroup =
3024 gfxPlatform::GetPlatform()->CreateFontGroup(FontFamilyList(eFamily_sans_serif),
3025 &style,
3026 nullptr);
3027 if (CurrentState().fontGroup) {
3028 CurrentState().font = kDefaultFontStyle;
3030 nsIPresShell* presShell = GetPresShell();
3031 if (presShell) {
3032 CurrentState().fontGroup->SetTextPerfMetrics(
3033 presShell->GetPresContext()->GetTextPerfMetrics());
3035 } else {
3036 NS_ERROR("Default canvas font is invalid");
3042 return CurrentState().fontGroup;
3046 // line caps/joins
3049 void
3050 CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle)
3052 CapStyle cap;
3054 if (capstyle.EqualsLiteral("butt")) {
3055 cap = CapStyle::BUTT;
3056 } else if (capstyle.EqualsLiteral("round")) {
3057 cap = CapStyle::ROUND;
3058 } else if (capstyle.EqualsLiteral("square")) {
3059 cap = CapStyle::SQUARE;
3060 } else {
3061 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3062 return;
3065 CurrentState().lineCap = cap;
3068 void
3069 CanvasRenderingContext2D::GetLineCap(nsAString& capstyle)
3071 switch (CurrentState().lineCap) {
3072 case CapStyle::BUTT:
3073 capstyle.AssignLiteral("butt");
3074 break;
3075 case CapStyle::ROUND:
3076 capstyle.AssignLiteral("round");
3077 break;
3078 case CapStyle::SQUARE:
3079 capstyle.AssignLiteral("square");
3080 break;
3084 void
3085 CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle)
3087 JoinStyle j;
3089 if (joinstyle.EqualsLiteral("round")) {
3090 j = JoinStyle::ROUND;
3091 } else if (joinstyle.EqualsLiteral("bevel")) {
3092 j = JoinStyle::BEVEL;
3093 } else if (joinstyle.EqualsLiteral("miter")) {
3094 j = JoinStyle::MITER_OR_BEVEL;
3095 } else {
3096 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3097 return;
3100 CurrentState().lineJoin = j;
3103 void
3104 CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error)
3106 switch (CurrentState().lineJoin) {
3107 case JoinStyle::ROUND:
3108 joinstyle.AssignLiteral("round");
3109 break;
3110 case JoinStyle::BEVEL:
3111 joinstyle.AssignLiteral("bevel");
3112 break;
3113 case JoinStyle::MITER_OR_BEVEL:
3114 joinstyle.AssignLiteral("miter");
3115 break;
3116 default:
3117 error.Throw(NS_ERROR_FAILURE);
3121 void
3122 CanvasRenderingContext2D::SetMozDash(JSContext* cx,
3123 const JS::Value& mozDash,
3124 ErrorResult& error)
3126 FallibleTArray<Float> dash;
3127 error = JSValToDashArray(cx, mozDash, dash);
3128 if (!error.Failed()) {
3129 ContextState& state = CurrentState();
3130 state.dash = dash;
3131 if (state.dash.IsEmpty()) {
3132 state.dashOffset = 0;
3137 void
3138 CanvasRenderingContext2D::GetMozDash(JSContext* cx,
3139 JS::MutableHandle<JS::Value> retval,
3140 ErrorResult& error)
3142 DashArrayToJSVal(CurrentState().dash, cx, retval, error);
3145 void
3146 CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset)
3148 ContextState& state = CurrentState();
3149 if (!state.dash.IsEmpty()) {
3150 state.dashOffset = mozDashOffset;
3154 void
3155 CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments)
3157 FallibleTArray<mozilla::gfx::Float> dash;
3159 for (uint32_t x = 0; x < aSegments.Length(); x++) {
3160 if (aSegments[x] < 0.0) {
3161 // Pattern elements must be finite "numbers" >= 0, with "finite"
3162 // taken care of by WebIDL
3163 return;
3165 dash.AppendElement(aSegments[x]);
3167 if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again
3168 for (uint32_t x = 0; x < aSegments.Length(); x++) {
3169 dash.AppendElement(aSegments[x]);
3173 CurrentState().dash = dash;
3176 void
3177 CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const {
3178 const FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
3179 aSegments.Clear();
3181 for (uint32_t x = 0; x < dash.Length(); x++) {
3182 aSegments.AppendElement(dash[x]);
3186 void
3187 CanvasRenderingContext2D::SetLineDashOffset(double mOffset) {
3188 CurrentState().dashOffset = mOffset;
3191 double
3192 CanvasRenderingContext2D::LineDashOffset() const {
3193 return CurrentState().dashOffset;
3196 bool
3197 CanvasRenderingContext2D::IsPointInPath(double x, double y, const CanvasWindingRule& winding)
3199 if (!FloatValidate(x,y)) {
3200 return false;
3203 EnsureUserSpacePath(winding);
3204 if (!mPath) {
3205 return false;
3208 if (mPathTransformWillUpdate) {
3209 return mPath->ContainsPoint(Point(x, y), mPathToDS);
3212 return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform());
3215 bool CanvasRenderingContext2D::IsPointInPath(const CanvasPath& mPath, double x, double y, const CanvasWindingRule& mWinding)
3217 if (!FloatValidate(x,y)) {
3218 return false;
3221 EnsureTarget();
3222 RefPtr<gfx::Path> tempPath = mPath.GetPath(mWinding, mTarget);
3224 return tempPath->ContainsPoint(Point(x, y), mTarget->GetTransform());
3227 bool
3228 CanvasRenderingContext2D::IsPointInStroke(double x, double y)
3230 if (!FloatValidate(x,y)) {
3231 return false;
3234 EnsureUserSpacePath();
3235 if (!mPath) {
3236 return false;
3239 const ContextState &state = CurrentState();
3241 StrokeOptions strokeOptions(state.lineWidth,
3242 state.lineJoin,
3243 state.lineCap,
3244 state.miterLimit,
3245 state.dash.Length(),
3246 state.dash.Elements(),
3247 state.dashOffset);
3249 if (mPathTransformWillUpdate) {
3250 return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS);
3252 return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform());
3255 bool CanvasRenderingContext2D::IsPointInStroke(const CanvasPath& mPath, double x, double y)
3257 if (!FloatValidate(x,y)) {
3258 return false;
3261 EnsureTarget();
3262 RefPtr<gfx::Path> tempPath = mPath.GetPath(CanvasWindingRule::Nonzero, mTarget);
3264 const ContextState &state = CurrentState();
3266 StrokeOptions strokeOptions(state.lineWidth,
3267 state.lineJoin,
3268 state.lineCap,
3269 state.miterLimit,
3270 state.dash.Length(),
3271 state.dash.Elements(),
3272 state.dashOffset);
3274 return tempPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform());
3277 // Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
3278 // to pull a SourceSurface from our cache. This allows us to avoid
3279 // reoptimizing surfaces if content and canvas backends are different.
3280 nsLayoutUtils::SurfaceFromElementResult
3281 CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement)
3283 nsLayoutUtils::SurfaceFromElementResult res;
3285 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
3286 if (!imageLoader) {
3287 return res;
3290 nsCOMPtr<imgIRequest> imgRequest;
3291 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
3292 getter_AddRefs(imgRequest));
3293 if (!imgRequest) {
3294 return res;
3297 uint32_t status;
3298 if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
3299 !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
3300 return res;
3303 nsCOMPtr<nsIPrincipal> principal;
3304 if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
3305 !principal) {
3306 return res;
3309 res.mSourceSurface = CanvasImageCache::SimpleLookup(aElement);
3310 if (!res.mSourceSurface) {
3311 return res;
3314 int32_t corsmode = imgIRequest::CORS_NONE;
3315 if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
3316 res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
3319 res.mSize = ThebesIntSize(res.mSourceSurface->GetSize());
3320 res.mPrincipal = principal.forget();
3321 res.mIsWriteOnly = false;
3322 res.mImageRequest = imgRequest.forget();
3324 return res;
3328 // image
3331 // drawImage(in HTMLImageElement image, in float dx, in float dy);
3332 // -- render image from 0,0 at dx,dy top-left coords
3333 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh);
3334 // -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh
3335 // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
3336 // -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
3338 // If only dx and dy are passed in then optional_argc should be 0. If only
3339 // dx, dy, dw and dh are passed in then optional_argc should be 2. The only
3340 // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
3341 // are all passed in.
3343 void
3344 CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image,
3345 double sx, double sy, double sw,
3346 double sh, double dx, double dy,
3347 double dw, double dh,
3348 uint8_t optional_argc,
3349 ErrorResult& error)
3351 MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6);
3353 RefPtr<SourceSurface> srcSurf;
3354 gfxIntSize imgSize;
3356 Element* element;
3358 EnsureTarget();
3359 if (image.IsHTMLCanvasElement()) {
3360 HTMLCanvasElement* canvas = &image.GetAsHTMLCanvasElement();
3361 element = canvas;
3362 nsIntSize size = canvas->GetSize();
3363 if (size.width == 0 || size.height == 0) {
3364 error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3365 return;
3367 } else {
3368 if (image.IsHTMLImageElement()) {
3369 HTMLImageElement* img = &image.GetAsHTMLImageElement();
3370 element = img;
3371 } else {
3372 HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
3373 element = video;
3376 srcSurf =
3377 CanvasImageCache::Lookup(element, mCanvasElement, &imgSize);
3380 nsLayoutUtils::DirectDrawInfo drawInfo;
3382 if (!srcSurf) {
3383 // The canvas spec says that drawImage should draw the first frame
3384 // of animated images. We also don't want to rasterize vector images.
3385 uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME |
3386 nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
3387 // The cache lookup can miss even if the image is already in the cache
3388 // if the image is coming from a different element or cached for a
3389 // different canvas. This covers the case when we miss due to caching
3390 // for a different canvas, but CanvasImageCache should be fixed if we
3391 // see misses due to different elements drawing the same image.
3392 nsLayoutUtils::SurfaceFromElementResult res =
3393 CachedSurfaceFromElement(element);
3394 if (!res.mSourceSurface)
3395 res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
3397 if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
3398 // Spec says to silently do nothing if the element is still loading.
3399 if (!res.mIsStillLoading) {
3400 error.Throw(NS_ERROR_NOT_AVAILABLE);
3402 return;
3405 imgSize = res.mSize;
3407 // Scale sw/sh based on aspect ratio
3408 if (image.IsHTMLVideoElement()) {
3409 HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
3410 int32_t displayWidth = video->VideoWidth();
3411 int32_t displayHeight = video->VideoHeight();
3412 sw *= (double)imgSize.width / (double)displayWidth;
3413 sh *= (double)imgSize.height / (double)displayHeight;
3416 if (mCanvasElement) {
3417 CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
3418 res.mPrincipal, res.mIsWriteOnly,
3419 res.mCORSUsed);
3422 if (res.mSourceSurface) {
3423 if (res.mImageRequest) {
3424 CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest,
3425 res.mSourceSurface, imgSize);
3428 srcSurf = res.mSourceSurface;
3429 } else {
3430 drawInfo = res.mDrawInfo;
3434 if (optional_argc == 0) {
3435 sx = sy = 0.0;
3436 dw = sw = (double) imgSize.width;
3437 dh = sh = (double) imgSize.height;
3438 } else if (optional_argc == 2) {
3439 sx = sy = 0.0;
3440 sw = (double) imgSize.width;
3441 sh = (double) imgSize.height;
3444 if (sw == 0.0 || sh == 0.0) {
3445 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3446 return;
3449 if (dw == 0.0 || dh == 0.0) {
3450 // not really failure, but nothing to do --
3451 // and noone likes a divide-by-zero
3452 return;
3455 if (sx < 0.0 || sy < 0.0 ||
3456 sw < 0.0 || sw > (double) imgSize.width ||
3457 sh < 0.0 || sh > (double) imgSize.height ||
3458 dw < 0.0 || dh < 0.0) {
3459 // XXX - Unresolved spec issues here, for now return error.
3460 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3461 return;
3464 Filter filter;
3466 if (CurrentState().imageSmoothingEnabled)
3467 filter = mgfx::Filter::LINEAR;
3468 else
3469 filter = mgfx::Filter::POINT;
3471 mgfx::Rect bounds;
3473 if (NeedToDrawShadow()) {
3474 bounds = mgfx::Rect(dx, dy, dw, dh);
3475 bounds = mTarget->GetTransform().TransformBounds(bounds);
3478 if (srcSurf) {
3479 AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
3480 DrawSurface(srcSurf,
3481 mgfx::Rect(dx, dy, dw, dh),
3482 mgfx::Rect(sx, sy, sw, sh),
3483 DrawSurfaceOptions(filter),
3484 DrawOptions(CurrentState().globalAlpha, UsedOperation()));
3485 } else {
3486 DrawDirectlyToCanvas(drawInfo, &bounds,
3487 mgfx::Rect(dx, dy, dw, dh),
3488 mgfx::Rect(sx, sy, sw, sh),
3489 imgSize);
3492 RedrawUser(gfxRect(dx, dy, dw, dh));
3495 void
3496 CanvasRenderingContext2D::DrawDirectlyToCanvas(
3497 const nsLayoutUtils::DirectDrawInfo& image,
3498 mgfx::Rect* bounds,
3499 mgfx::Rect dest,
3500 mgfx::Rect src,
3501 gfxIntSize imgSize)
3503 MOZ_ASSERT(src.width > 0 && src.height > 0,
3504 "Need positive source width and height");
3506 gfxMatrix contextMatrix;
3507 AdjustedTarget tempTarget(this, bounds->IsEmpty() ? nullptr: bounds);
3509 // Get any existing transforms on the context, including transformations used
3510 // for context shadow.
3511 if (tempTarget) {
3512 Matrix matrix = tempTarget->GetTransform();
3513 contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21,
3514 matrix._22, matrix._31, matrix._32);
3516 gfxSize contextScale(contextMatrix.ScaleFactors(true));
3518 // Scale the dest rect to include the context scale.
3519 dest.Scale(contextScale.width, contextScale.height);
3521 // Scale the image size to the dest rect, and adjust the source rect to match.
3522 gfxSize scale(dest.width / src.width, dest.height / src.height);
3523 nsIntSize scaledImageSize(std::ceil(imgSize.width * scale.width),
3524 std::ceil(imgSize.height * scale.height));
3525 src.Scale(scale.width, scale.height);
3527 // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore
3528 // the matrix even though this is a temp gfxContext.
3529 AutoSaveTransform autoSR(mTarget);
3531 nsRefPtr<gfxContext> context = new gfxContext(tempTarget);
3532 context->SetMatrix(contextMatrix);
3533 context->Scale(1.0 / contextScale.width, 1.0 / contextScale.height);
3534 context->Translate(gfxPoint(dest.x - src.x, dest.y - src.y));
3536 // FLAG_CLAMP is added for increased performance, since we never tile here.
3537 uint32_t modifiedFlags = image.mDrawingFlags | imgIContainer::FLAG_CLAMP;
3539 SVGImageContext svgContext(scaledImageSize, Nothing(), CurrentState().globalAlpha);
3541 nsresult rv = image.mImgContainer->
3542 Draw(context, scaledImageSize,
3543 ImageRegion::Create(gfxRect(src.x, src.y, src.width, src.height)),
3544 image.mWhichFrame, GraphicsFilter::FILTER_GOOD,
3545 Some(svgContext), modifiedFlags);
3547 NS_ENSURE_SUCCESS_VOID(rv);
3550 static bool
3551 IsStandardCompositeOp(CompositionOp op)
3553 return (op == CompositionOp::OP_SOURCE ||
3554 op == CompositionOp::OP_ATOP ||
3555 op == CompositionOp::OP_IN ||
3556 op == CompositionOp::OP_OUT ||
3557 op == CompositionOp::OP_OVER ||
3558 op == CompositionOp::OP_DEST_IN ||
3559 op == CompositionOp::OP_DEST_OUT ||
3560 op == CompositionOp::OP_DEST_OVER ||
3561 op == CompositionOp::OP_DEST_ATOP ||
3562 op == CompositionOp::OP_ADD ||
3563 op == CompositionOp::OP_XOR);
3566 void
3567 CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op,
3568 ErrorResult& error)
3570 CompositionOp comp_op;
3572 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
3573 if (op.EqualsLiteral(cvsop)) \
3574 comp_op = CompositionOp::OP_##op2d;
3576 CANVAS_OP_TO_GFX_OP("copy", SOURCE)
3577 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
3578 else CANVAS_OP_TO_GFX_OP("source-in", IN)
3579 else CANVAS_OP_TO_GFX_OP("source-out", OUT)
3580 else CANVAS_OP_TO_GFX_OP("source-over", OVER)
3581 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
3582 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
3583 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
3584 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
3585 else CANVAS_OP_TO_GFX_OP("lighter", ADD)
3586 else CANVAS_OP_TO_GFX_OP("xor", XOR)
3587 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
3588 else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
3589 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
3590 else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
3591 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
3592 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
3593 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
3594 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
3595 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
3596 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
3597 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
3598 else CANVAS_OP_TO_GFX_OP("hue", HUE)
3599 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
3600 else CANVAS_OP_TO_GFX_OP("color", COLOR)
3601 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
3602 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3603 else return;
3605 if (!IsStandardCompositeOp(comp_op)) {
3606 Demote();
3609 #undef CANVAS_OP_TO_GFX_OP
3610 CurrentState().op = comp_op;
3613 void
3614 CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op,
3615 ErrorResult& error)
3617 CompositionOp comp_op = CurrentState().op;
3619 #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \
3620 if (comp_op == CompositionOp::OP_##op2d) \
3621 op.AssignLiteral(cvsop);
3623 CANVAS_OP_TO_GFX_OP("copy", SOURCE)
3624 else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP)
3625 else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN)
3626 else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT)
3627 else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER)
3628 else CANVAS_OP_TO_GFX_OP("lighter", ADD)
3629 else CANVAS_OP_TO_GFX_OP("source-atop", ATOP)
3630 else CANVAS_OP_TO_GFX_OP("source-in", IN)
3631 else CANVAS_OP_TO_GFX_OP("source-out", OUT)
3632 else CANVAS_OP_TO_GFX_OP("source-over", OVER)
3633 else CANVAS_OP_TO_GFX_OP("xor", XOR)
3634 else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY)
3635 else CANVAS_OP_TO_GFX_OP("screen", SCREEN)
3636 else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY)
3637 else CANVAS_OP_TO_GFX_OP("darken", DARKEN)
3638 else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN)
3639 else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE)
3640 else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN)
3641 else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT)
3642 else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT)
3643 else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE)
3644 else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION)
3645 else CANVAS_OP_TO_GFX_OP("hue", HUE)
3646 else CANVAS_OP_TO_GFX_OP("saturation", SATURATION)
3647 else CANVAS_OP_TO_GFX_OP("color", COLOR)
3648 else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY)
3649 else {
3650 error.Throw(NS_ERROR_FAILURE);
3653 if (!IsStandardCompositeOp(comp_op)) {
3654 Demote();
3657 #undef CANVAS_OP_TO_GFX_OP
3660 void
3661 CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& window, double x,
3662 double y, double w, double h,
3663 const nsAString& bgColor,
3664 uint32_t flags, ErrorResult& error)
3666 // protect against too-large surfaces that will cause allocation
3667 // or overflow issues
3668 if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)),
3669 0xffff)) {
3670 error.Throw(NS_ERROR_FAILURE);
3671 return;
3674 EnsureTarget();
3675 // We can't allow web apps to call this until we fix at least the
3676 // following potential security issues:
3677 // -- rendering cross-domain IFRAMEs and then extracting the results
3678 // -- rendering the user's theme and then extracting the results
3679 // -- rendering native anonymous content (e.g., file input paths;
3680 // scrollbars should be allowed)
3681 if (!nsContentUtils::IsCallerChrome()) {
3682 // not permitted to use DrawWindow
3683 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3684 error.Throw(NS_ERROR_DOM_SECURITY_ERR);
3685 return;
3688 // Flush layout updates
3689 if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) {
3690 nsContentUtils::FlushLayoutForTree(&window);
3693 nsRefPtr<nsPresContext> presContext;
3694 nsIDocShell* docshell = window.GetDocShell();
3695 if (docshell) {
3696 docshell->GetPresContext(getter_AddRefs(presContext));
3698 if (!presContext) {
3699 error.Throw(NS_ERROR_FAILURE);
3700 return;
3703 nscolor backgroundColor;
3704 if (!ParseColor(bgColor, &backgroundColor)) {
3705 error.Throw(NS_ERROR_FAILURE);
3706 return;
3709 nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x),
3710 nsPresContext::CSSPixelsToAppUnits((float)y),
3711 nsPresContext::CSSPixelsToAppUnits((float)w),
3712 nsPresContext::CSSPixelsToAppUnits((float)h));
3713 uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
3714 nsIPresShell::RENDER_DOCUMENT_RELATIVE);
3715 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) {
3716 renderDocFlags |= nsIPresShell::RENDER_CARET;
3718 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) {
3719 renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
3720 nsIPresShell::RENDER_DOCUMENT_RELATIVE);
3722 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) {
3723 renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS;
3725 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) {
3726 renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES;
3728 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) {
3729 renderDocFlags |= nsIPresShell::RENDER_DRAWWINDOW_NOT_FLUSHING;
3732 // gfxContext-over-Azure may modify the DrawTarget's transform, so
3733 // save and restore it
3734 Matrix matrix = mTarget->GetTransform();
3735 double sw = matrix._11 * w;
3736 double sh = matrix._22 * h;
3737 if (!sw || !sh) {
3738 return;
3740 nsRefPtr<gfxContext> thebes;
3741 RefPtr<DrawTarget> drawDT;
3742 // Rendering directly is faster and can be done if mTarget supports Azure
3743 // and does not need alpha blending.
3744 if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
3745 GlobalAlpha() == 1.0f)
3747 thebes = new gfxContext(mTarget);
3748 thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
3749 matrix._22, matrix._31, matrix._32));
3750 } else {
3751 drawDT =
3752 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)),
3753 SurfaceFormat::B8G8R8A8);
3754 if (!drawDT) {
3755 error.Throw(NS_ERROR_FAILURE);
3756 return;
3759 thebes = new gfxContext(drawDT);
3760 thebes->Scale(matrix._11, matrix._22);
3763 nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
3764 unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes);
3765 if (drawDT) {
3766 RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
3767 RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
3769 RefPtr<SourceSurface> source =
3770 mTarget->CreateSourceSurfaceFromData(data->GetData(),
3771 data->GetSize(),
3772 data->Stride(),
3773 data->GetFormat());
3775 if (!source) {
3776 error.Throw(NS_ERROR_FAILURE);
3777 return;
3780 mgfx::Rect destRect(0, 0, w, h);
3781 mgfx::Rect sourceRect(0, 0, sw, sh);
3782 mTarget->DrawSurface(source, destRect, sourceRect,
3783 DrawSurfaceOptions(mgfx::Filter::POINT),
3784 DrawOptions(GlobalAlpha(), CompositionOp::OP_OVER,
3785 AntialiasMode::NONE));
3786 mTarget->Flush();
3787 } else {
3788 mTarget->SetTransform(matrix);
3791 // note that x and y are coordinates in the document that
3792 // we're drawing; x and y are drawn to 0,0 in current user
3793 // space.
3794 RedrawUser(gfxRect(0, 0, w, h));
3797 void
3798 CanvasRenderingContext2D::AsyncDrawXULElement(nsXULElement& elem,
3799 double x, double y,
3800 double w, double h,
3801 const nsAString& bgColor,
3802 uint32_t flags,
3803 ErrorResult& error)
3805 // We can't allow web apps to call this until we fix at least the
3806 // following potential security issues:
3807 // -- rendering cross-domain IFRAMEs and then extracting the results
3808 // -- rendering the user's theme and then extracting the results
3809 // -- rendering native anonymous content (e.g., file input paths;
3810 // scrollbars should be allowed)
3811 if (!nsContentUtils::IsCallerChrome()) {
3812 // not permitted to use DrawWindow
3813 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3814 error.Throw(NS_ERROR_DOM_SECURITY_ERR);
3815 return;
3818 #if 0
3819 nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(&elem);
3820 if (!loaderOwner) {
3821 error.Throw(NS_ERROR_FAILURE);
3822 return;
3825 nsRefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader();
3826 if (!frameloader) {
3827 error.Throw(NS_ERROR_FAILURE);
3828 return;
3831 PBrowserParent *child = frameloader->GetRemoteBrowser();
3832 if (!child) {
3833 nsIDocShell* docShell = frameLoader->GetExistingDocShell();
3834 if (!docShell) {
3835 error.Throw(NS_ERROR_FAILURE);
3836 return;
3839 nsCOMPtr<nsIDOMWindow> window = docShell->GetWindow();
3840 if (!window) {
3841 error.Throw(NS_ERROR_FAILURE);
3842 return;
3845 return DrawWindow(window, x, y, w, h, bgColor, flags);
3848 // protect against too-large surfaces that will cause allocation
3849 // or overflow issues
3850 if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) {
3851 error.Throw(NS_ERROR_FAILURE);
3852 return;
3855 bool flush =
3856 (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0;
3858 uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
3859 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) {
3860 renderDocFlags |= nsIPresShell::RENDER_CARET;
3862 if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) {
3863 renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
3866 nsRect rect(nsPresContext::CSSPixelsToAppUnits(x),
3867 nsPresContext::CSSPixelsToAppUnits(y),
3868 nsPresContext::CSSPixelsToAppUnits(w),
3869 nsPresContext::CSSPixelsToAppUnits(h));
3870 if (mIPC) {
3871 PDocumentRendererParent *pdocrender =
3872 child->SendPDocumentRendererConstructor(rect,
3873 mThebes->CurrentMatrix(),
3874 nsString(aBGColor),
3875 renderDocFlags, flush,
3876 nsIntSize(mWidth, mHeight));
3877 if (!pdocrender)
3878 return NS_ERROR_FAILURE;
3880 DocumentRendererParent *docrender =
3881 static_cast<DocumentRendererParent *>(pdocrender);
3883 docrender->SetCanvasContext(this, mThebes);
3885 #endif
3889 // device pixel getting/setting
3892 already_AddRefed<ImageData>
3893 CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
3894 double aSy, double aSw,
3895 double aSh, ErrorResult& error)
3897 EnsureTarget();
3898 if (!IsTargetValid()) {
3899 error.Throw(NS_ERROR_FAILURE);
3900 return nullptr;
3903 if (!mCanvasElement && !mDocShell) {
3904 NS_ERROR("No canvas element and no docshell in GetImageData!!!");
3905 error.Throw(NS_ERROR_DOM_SECURITY_ERR);
3906 return nullptr;
3909 // Check only if we have a canvas element; if we were created with a docshell,
3910 // then it's special internal use.
3911 if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
3912 !nsContentUtils::IsCallerChrome())
3914 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3915 error.Throw(NS_ERROR_DOM_SECURITY_ERR);
3916 return nullptr;
3919 if (!NS_finite(aSx) || !NS_finite(aSy) ||
3920 !NS_finite(aSw) || !NS_finite(aSh)) {
3921 error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
3922 return nullptr;
3925 if (!aSw || !aSh) {
3926 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3927 return nullptr;
3930 int32_t x = JS_DoubleToInt32(aSx);
3931 int32_t y = JS_DoubleToInt32(aSy);
3932 int32_t wi = JS_DoubleToInt32(aSw);
3933 int32_t hi = JS_DoubleToInt32(aSh);
3935 // Handle negative width and height by flipping the rectangle over in the
3936 // relevant direction.
3937 uint32_t w, h;
3938 if (aSw < 0) {
3939 w = -wi;
3940 x -= w;
3941 } else {
3942 w = wi;
3944 if (aSh < 0) {
3945 h = -hi;
3946 y -= h;
3947 } else {
3948 h = hi;
3951 if (w == 0) {
3952 w = 1;
3954 if (h == 0) {
3955 h = 1;
3958 JS::Rooted<JSObject*> array(aCx);
3959 error = GetImageDataArray(aCx, x, y, w, h, array.address());
3960 if (error.Failed()) {
3961 return nullptr;
3963 MOZ_ASSERT(array);
3965 nsRefPtr<ImageData> imageData = new ImageData(w, h, *array);
3966 return imageData.forget();
3969 nsresult
3970 CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
3971 int32_t aX,
3972 int32_t aY,
3973 uint32_t aWidth,
3974 uint32_t aHeight,
3975 JSObject** aRetval)
3977 MOZ_ASSERT(aWidth && aHeight);
3979 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
3980 if (!len.isValid()) {
3981 return NS_ERROR_DOM_INDEX_SIZE_ERR;
3984 CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth;
3985 CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight;
3987 if (!rightMost.isValid() || !bottomMost.isValid()) {
3988 return NS_ERROR_DOM_SYNTAX_ERR;
3991 IntRect srcRect(0, 0, mWidth, mHeight);
3992 IntRect destRect(aX, aY, aWidth, aHeight);
3993 IntRect srcReadRect = srcRect.Intersect(destRect);
3994 RefPtr<DataSourceSurface> readback;
3995 if (!srcReadRect.IsEmpty() && !mZero) {
3996 RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
3997 if (snapshot) {
3998 readback = snapshot->GetDataSurface();
4000 if (!readback || !readback->GetData()) {
4001 return NS_ERROR_OUT_OF_MEMORY;
4005 JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value()));
4006 if (!darray) {
4007 return NS_ERROR_OUT_OF_MEMORY;
4010 if (mZero) {
4011 *aRetval = darray;
4012 return NS_OK;
4015 uint8_t* data = JS_GetUint8ClampedArrayData(darray);
4017 IntRect dstWriteRect = srcReadRect;
4018 dstWriteRect.MoveBy(-aX, -aY);
4020 uint8_t* src = data;
4021 uint32_t srcStride = aWidth * 4;
4022 if (readback) {
4023 srcStride = readback->Stride();
4024 src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4;
4027 // NOTE! dst is the same as src, and this relies on reading
4028 // from src and advancing that ptr before writing to dst.
4029 // NOTE! I'm not sure that it is, I think this comment might have been
4030 // inherited from Thebes canvas and is no longer true
4031 uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
4033 if (mOpaque) {
4034 for (int32_t j = 0; j < dstWriteRect.height; ++j) {
4035 for (int32_t i = 0; i < dstWriteRect.width; ++i) {
4036 // XXX Is there some useful swizzle MMX we can use here?
4037 #if MOZ_LITTLE_ENDIAN
4038 uint8_t b = *src++;
4039 uint8_t g = *src++;
4040 uint8_t r = *src++;
4041 src++;
4042 #else
4043 src++;
4044 uint8_t r = *src++;
4045 uint8_t g = *src++;
4046 uint8_t b = *src++;
4047 #endif
4048 *dst++ = r;
4049 *dst++ = g;
4050 *dst++ = b;
4051 *dst++ = 255;
4053 src += srcStride - (dstWriteRect.width * 4);
4054 dst += (aWidth * 4) - (dstWriteRect.width * 4);
4056 } else
4057 for (int32_t j = 0; j < dstWriteRect.height; ++j) {
4058 for (int32_t i = 0; i < dstWriteRect.width; ++i) {
4059 // XXX Is there some useful swizzle MMX we can use here?
4060 #if MOZ_LITTLE_ENDIAN
4061 uint8_t b = *src++;
4062 uint8_t g = *src++;
4063 uint8_t r = *src++;
4064 uint8_t a = *src++;
4065 #else
4066 uint8_t a = *src++;
4067 uint8_t r = *src++;
4068 uint8_t g = *src++;
4069 uint8_t b = *src++;
4070 #endif
4071 // Convert to non-premultiplied color
4072 *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + r];
4073 *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + g];
4074 *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + b];
4075 *dst++ = a;
4077 src += srcStride - (dstWriteRect.width * 4);
4078 dst += (aWidth * 4) - (dstWriteRect.width * 4);
4081 *aRetval = darray;
4082 return NS_OK;
4085 void
4086 CanvasRenderingContext2D::EnsureErrorTarget()
4088 if (sErrorTarget) {
4089 return;
4092 RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8);
4093 MOZ_ASSERT(errorTarget, "Failed to allocate the error target!");
4095 sErrorTarget = errorTarget;
4096 NS_ADDREF(sErrorTarget);
4099 void
4100 CanvasRenderingContext2D::FillRuleChanged()
4102 if (mPath) {
4103 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
4104 mPath = nullptr;
4108 void
4109 CanvasRenderingContext2D::PutImageData(ImageData& imageData, double dx,
4110 double dy, ErrorResult& error)
4112 dom::Uint8ClampedArray arr;
4113 DebugOnly<bool> inited = arr.Init(imageData.GetDataObject());
4114 MOZ_ASSERT(inited);
4116 error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy),
4117 imageData.Width(), imageData.Height(),
4118 &arr, false, 0, 0, 0, 0);
4121 void
4122 CanvasRenderingContext2D::PutImageData(ImageData& imageData, double dx,
4123 double dy, double dirtyX,
4124 double dirtyY, double dirtyWidth,
4125 double dirtyHeight,
4126 ErrorResult& error)
4128 dom::Uint8ClampedArray arr;
4129 DebugOnly<bool> inited = arr.Init(imageData.GetDataObject());
4130 MOZ_ASSERT(inited);
4132 error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy),
4133 imageData.Width(), imageData.Height(),
4134 &arr, true,
4135 JS_DoubleToInt32(dirtyX),
4136 JS_DoubleToInt32(dirtyY),
4137 JS_DoubleToInt32(dirtyWidth),
4138 JS_DoubleToInt32(dirtyHeight));
4141 // void putImageData (in ImageData d, in float x, in float y);
4142 // void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight);
4144 nsresult
4145 CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
4146 dom::Uint8ClampedArray* aArray,
4147 bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
4148 int32_t dirtyWidth, int32_t dirtyHeight)
4150 if (w == 0 || h == 0) {
4151 return NS_ERROR_DOM_INVALID_STATE_ERR;
4154 IntRect dirtyRect;
4155 IntRect imageDataRect(0, 0, w, h);
4157 if (hasDirtyRect) {
4158 // fix up negative dimensions
4159 if (dirtyWidth < 0) {
4160 NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR);
4162 CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth;
4164 if (!checkedDirtyX.isValid())
4165 return NS_ERROR_DOM_INDEX_SIZE_ERR;
4167 dirtyX = checkedDirtyX.value();
4168 dirtyWidth = -dirtyWidth;
4171 if (dirtyHeight < 0) {
4172 NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR);
4174 CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight;
4176 if (!checkedDirtyY.isValid())
4177 return NS_ERROR_DOM_INDEX_SIZE_ERR;
4179 dirtyY = checkedDirtyY.value();
4180 dirtyHeight = -dirtyHeight;
4183 // bound the dirty rect within the imageData rectangle
4184 dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight));
4186 if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0)
4187 return NS_OK;
4188 } else {
4189 dirtyRect = imageDataRect;
4192 dirtyRect.MoveBy(IntPoint(x, y));
4193 dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect);
4195 if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) {
4196 return NS_OK;
4199 aArray->ComputeLengthAndData();
4201 uint32_t dataLen = aArray->Length();
4203 uint32_t len = w * h * 4;
4204 if (dataLen != len) {
4205 return NS_ERROR_DOM_INVALID_STATE_ERR;
4208 nsRefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(gfxIntSize(w, h),
4209 gfxImageFormat::ARGB32,
4210 false);
4211 if (!imgsurf || imgsurf->CairoStatus()) {
4212 return NS_ERROR_FAILURE;
4215 uint8_t *src = aArray->Data();
4216 uint8_t *dst = imgsurf->Data();
4218 for (uint32_t j = 0; j < h; j++) {
4219 for (uint32_t i = 0; i < w; i++) {
4220 uint8_t r = *src++;
4221 uint8_t g = *src++;
4222 uint8_t b = *src++;
4223 uint8_t a = *src++;
4225 // Convert to premultiplied color (losslessly if the input came from getImageData)
4226 #if MOZ_LITTLE_ENDIAN
4227 *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b];
4228 *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g];
4229 *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r];
4230 *dst++ = a;
4231 #else
4232 *dst++ = a;
4233 *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r];
4234 *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g];
4235 *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b];
4236 #endif
4240 EnsureTarget();
4241 if (!IsTargetValid()) {
4242 return NS_ERROR_FAILURE;
4245 RefPtr<SourceSurface> sourceSurface =
4246 mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), SurfaceFormat::B8G8R8A8);
4248 // In certain scenarios, requesting larger than 8k image fails. Bug 803568
4249 // covers the details of how to run into it, but the full detailed
4250 // investigation hasn't been done to determine the underlying cause. We
4251 // will just handle the failure to allocate the surface to avoid a crash.
4252 if (!sourceSurface) {
4253 return NS_ERROR_FAILURE;
4256 mTarget->CopySurface(sourceSurface,
4257 IntRect(dirtyRect.x - x, dirtyRect.y - y,
4258 dirtyRect.width, dirtyRect.height),
4259 IntPoint(dirtyRect.x, dirtyRect.y));
4261 Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height));
4263 return NS_OK;
4266 static already_AddRefed<ImageData>
4267 CreateImageData(JSContext* cx, CanvasRenderingContext2D* context,
4268 uint32_t w, uint32_t h, ErrorResult& error)
4270 if (w == 0)
4271 w = 1;
4272 if (h == 0)
4273 h = 1;
4275 CheckedInt<uint32_t> len = CheckedInt<uint32_t>(w) * h * 4;
4276 if (!len.isValid()) {
4277 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
4278 return nullptr;
4281 // Create the fast typed array; it's initialized to 0 by default.
4282 JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value());
4283 if (!darray) {
4284 error.Throw(NS_ERROR_OUT_OF_MEMORY);
4285 return nullptr;
4288 nsRefPtr<mozilla::dom::ImageData> imageData =
4289 new mozilla::dom::ImageData(w, h, *darray);
4290 return imageData.forget();
4293 already_AddRefed<ImageData>
4294 CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw,
4295 double sh, ErrorResult& error)
4297 if (!sw || !sh) {
4298 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
4299 return nullptr;
4302 int32_t wi = JS_DoubleToInt32(sw);
4303 int32_t hi = JS_DoubleToInt32(sh);
4305 uint32_t w = Abs(wi);
4306 uint32_t h = Abs(hi);
4307 return mozilla::dom::CreateImageData(cx, this, w, h, error);
4310 already_AddRefed<ImageData>
4311 CanvasRenderingContext2D::CreateImageData(JSContext* cx,
4312 ImageData& imagedata,
4313 ErrorResult& error)
4315 return mozilla::dom::CreateImageData(cx, this, imagedata.Width(),
4316 imagedata.Height(), error);
4319 static uint8_t g2DContextLayerUserData;
4321 already_AddRefed<CanvasLayer>
4322 CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
4323 CanvasLayer *aOldLayer,
4324 LayerManager *aManager)
4326 if (mOpaque) {
4327 // If we're opaque then make sure we have a surface so we paint black
4328 // instead of transparent.
4329 EnsureTarget();
4332 // Don't call EnsureTarget() ... if there isn't already a surface, then
4333 // we have nothing to paint and there is no need to create a surface just
4334 // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
4335 // layer manager which must NOT happen during a paint.
4336 if (!mTarget || !IsTargetValid()) {
4337 // No DidTransactionCallback will be received, so mark the context clean
4338 // now so future invalidations will be dispatched.
4339 MarkContextClean();
4340 return nullptr;
4343 mTarget->Flush();
4345 if (!mResetLayer && aOldLayer) {
4346 CanvasRenderingContext2DUserData* userData =
4347 static_cast<CanvasRenderingContext2DUserData*>(
4348 aOldLayer->GetUserData(&g2DContextLayerUserData));
4350 CanvasLayer::Data data;
4351 if (mStream) {
4352 #ifdef USE_SKIA
4353 SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
4355 if (glue) {
4356 data.mGLContext = glue->GetGLContext();
4357 data.mStream = mStream.get();
4359 #endif
4360 } else {
4361 data.mDrawTarget = mTarget;
4364 if (userData && userData->IsForContext(this) && aOldLayer->IsDataValid(data)) {
4365 nsRefPtr<CanvasLayer> ret = aOldLayer;
4366 return ret.forget();
4370 nsRefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer();
4371 if (!canvasLayer) {
4372 NS_WARNING("CreateCanvasLayer returned null!");
4373 // No DidTransactionCallback will be received, so mark the context clean
4374 // now so future invalidations will be dispatched.
4375 MarkContextClean();
4376 return nullptr;
4378 CanvasRenderingContext2DUserData *userData = nullptr;
4379 // Make the layer tell us whenever a transaction finishes (including
4380 // the current transaction), so we can clear our invalidation state and
4381 // start invalidating again. We need to do this for all layers since
4382 // callers of DrawWindow may be expecting to receive normal invalidation
4383 // notifications after this paint.
4385 // The layer will be destroyed when we tear down the presentation
4386 // (at the latest), at which time this userData will be destroyed,
4387 // releasing the reference to the element.
4388 // The userData will receive DidTransactionCallbacks, which flush the
4389 // the invalidation state to indicate that the canvas is up to date.
4390 userData = new CanvasRenderingContext2DUserData(this);
4391 canvasLayer->SetDidTransactionCallback(
4392 CanvasRenderingContext2DUserData::DidTransactionCallback, userData);
4393 canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
4395 CanvasLayer::Data data;
4396 if (mStream) {
4397 SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
4399 if (glue) {
4400 canvasLayer->SetPreTransactionCallback(
4401 CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
4402 #if USE_SKIA
4403 data.mGLContext = glue->GetGLContext();
4404 #endif
4405 data.mStream = mStream.get();
4406 data.mTexID = (uint32_t)((uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE));
4408 } else {
4409 data.mDrawTarget = mTarget;
4412 data.mSize = nsIntSize(mWidth, mHeight);
4413 data.mHasAlpha = !mOpaque;
4415 canvasLayer->Initialize(data);
4416 uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
4417 canvasLayer->SetContentFlags(flags);
4418 canvasLayer->Updated();
4420 mResetLayer = false;
4422 return canvasLayer.forget();
4425 void
4426 CanvasRenderingContext2D::MarkContextClean()
4428 if (mInvalidateCount > 0) {
4429 mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
4431 mIsEntireFrameInvalid = false;
4432 mInvalidateCount = 0;
4436 bool
4437 CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager)
4439 return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight));
4442 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
4443 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
4445 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
4447 CanvasPath::CanvasPath(nsISupports* aParent)
4448 : mParent(aParent)
4450 SetIsDOMBinding();
4452 mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder();
4455 CanvasPath::CanvasPath(nsISupports* aParent, TemporaryRef<PathBuilder> aPathBuilder)
4456 : mParent(aParent), mPathBuilder(aPathBuilder)
4458 SetIsDOMBinding();
4460 if (!mPathBuilder) {
4461 mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder();
4465 JSObject*
4466 CanvasPath::WrapObject(JSContext* aCx)
4468 return Path2DBinding::Wrap(aCx, this);
4471 already_AddRefed<CanvasPath>
4472 CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
4474 nsRefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports());
4475 return path.forget();
4478 already_AddRefed<CanvasPath>
4479 CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv)
4481 RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero,
4482 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
4484 nsRefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
4485 return path.forget();
4488 already_AddRefed<CanvasPath>
4489 CanvasPath::Constructor(const GlobalObject& aGlobal, const nsAString& aPathString, ErrorResult& aRv)
4491 RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString);
4492 if (!tempPath) {
4493 return Constructor(aGlobal, aRv);
4496 nsRefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder());
4497 return path.forget();
4500 void
4501 CanvasPath::ClosePath()
4503 EnsurePathBuilder();
4505 mPathBuilder->Close();
4508 void
4509 CanvasPath::MoveTo(double x, double y)
4511 EnsurePathBuilder();
4513 mPathBuilder->MoveTo(Point(ToFloat(x), ToFloat(y)));
4516 void
4517 CanvasPath::LineTo(double x, double y)
4519 EnsurePathBuilder();
4521 mPathBuilder->LineTo(Point(ToFloat(x), ToFloat(y)));
4524 void
4525 CanvasPath::QuadraticCurveTo(double cpx, double cpy, double x, double y)
4527 EnsurePathBuilder();
4529 mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(cpx), ToFloat(cpy)),
4530 gfx::Point(ToFloat(x), ToFloat(y)));
4533 void
4534 CanvasPath::BezierCurveTo(double cp1x, double cp1y,
4535 double cp2x, double cp2y,
4536 double x, double y)
4538 BezierTo(gfx::Point(ToFloat(cp1x), ToFloat(cp1y)),
4539 gfx::Point(ToFloat(cp2x), ToFloat(cp2y)),
4540 gfx::Point(ToFloat(x), ToFloat(y)));
4543 void
4544 CanvasPath::ArcTo(double x1, double y1, double x2, double y2, double radius,
4545 ErrorResult& error)
4547 if (radius < 0) {
4548 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
4549 return;
4552 EnsurePathBuilder();
4554 // Current point in user space!
4555 Point p0 = mPathBuilder->CurrentPoint();
4556 Point p1(x1, y1);
4557 Point p2(x2, y2);
4559 // Execute these calculations in double precision to avoid cumulative
4560 // rounding errors.
4561 double dir, a2, b2, c2, cosx, sinx, d, anx, any,
4562 bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1;
4563 bool anticlockwise;
4565 if (p0 == p1 || p1 == p2 || radius == 0) {
4566 LineTo(p1.x, p1.y);
4567 return;
4570 // Check for colinearity
4571 dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x);
4572 if (dir == 0) {
4573 LineTo(p1.x, p1.y);
4574 return;
4578 // XXX - Math for this code was already available from the non-azure code
4579 // and would be well tested. Perhaps converting to bezier directly might
4580 // be more efficient longer run.
4581 a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1);
4582 b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
4583 c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2);
4584 cosx = (a2+b2-c2)/(2*sqrt(a2*b2));
4586 sinx = sqrt(1 - cosx*cosx);
4587 d = radius / ((1 - cosx) / sinx);
4589 anx = (x1-p0.x) / sqrt(a2);
4590 any = (y1-p0.y) / sqrt(a2);
4591 bnx = (x1-x2) / sqrt(b2);
4592 bny = (y1-y2) / sqrt(b2);
4593 x3 = x1 - anx*d;
4594 y3 = y1 - any*d;
4595 x4 = x1 - bnx*d;
4596 y4 = y1 - bny*d;
4597 anticlockwise = (dir < 0);
4598 cx = x3 + any*radius*(anticlockwise ? 1 : -1);
4599 cy = y3 - anx*radius*(anticlockwise ? 1 : -1);
4600 angle0 = atan2((y3-cy), (x3-cx));
4601 angle1 = atan2((y4-cy), (x4-cx));
4604 LineTo(x3, y3);
4606 Arc(cx, cy, radius, angle0, angle1, anticlockwise, error);
4609 void
4610 CanvasPath::Rect(double x, double y, double w, double h)
4612 MoveTo(x, y);
4613 LineTo(x + w, y);
4614 LineTo(x + w, y + h);
4615 LineTo(x, y + h);
4616 ClosePath();
4619 void
4620 CanvasPath::Arc(double x, double y, double radius,
4621 double startAngle, double endAngle, bool anticlockwise,
4622 ErrorResult& error)
4624 if (radius < 0.0) {
4625 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
4626 return;
4629 EnsurePathBuilder();
4631 ArcToBezier(this, Point(x, y), Size(radius, radius), startAngle, endAngle, anticlockwise);
4634 void
4635 CanvasPath::LineTo(const gfx::Point& aPoint)
4637 EnsurePathBuilder();
4639 mPathBuilder->LineTo(aPoint);
4642 void
4643 CanvasPath::BezierTo(const gfx::Point& aCP1,
4644 const gfx::Point& aCP2,
4645 const gfx::Point& aCP3)
4647 EnsurePathBuilder();
4649 mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
4652 void
4653 CanvasPath::AddPath(CanvasPath& aCanvasPath, const Optional<NonNull<SVGMatrix>>& aMatrix)
4655 RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero,
4656 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
4658 if (aMatrix.WasPassed()) {
4659 const SVGMatrix& m = aMatrix.Value();
4660 Matrix transform(m.A(), m.B(), m.C(), m.D(), m.E(), m.F());
4662 if (!transform.IsIdentity()) {
4663 RefPtr<PathBuilder> tempBuilder = tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING);
4664 tempPath = tempBuilder->Finish();
4668 tempPath->StreamToSink(mPathBuilder);
4671 TemporaryRef<gfx::Path>
4672 CanvasPath::GetPath(const CanvasWindingRule& winding, const DrawTarget* aTarget) const
4674 FillRule fillRule = FillRule::FILL_WINDING;
4675 if (winding == CanvasWindingRule::Evenodd) {
4676 fillRule = FillRule::FILL_EVEN_ODD;
4679 if (mPath &&
4680 (mPath->GetBackendType() == aTarget->GetBackendType()) &&
4681 (mPath->GetFillRule() == fillRule)) {
4682 return mPath;
4685 if (!mPath) {
4686 // if there is no path, there must be a pathbuilder
4687 MOZ_ASSERT(mPathBuilder);
4688 mPath = mPathBuilder->Finish();
4689 if (!mPath)
4690 return mPath;
4692 mPathBuilder = nullptr;
4695 // retarget our backend if we're used with a different backend
4696 if (mPath->GetBackendType() != aTarget->GetBackendType()) {
4697 RefPtr<PathBuilder> tmpPathBuilder = aTarget->CreatePathBuilder(fillRule);
4698 mPath->StreamToSink(tmpPathBuilder);
4699 mPath = tmpPathBuilder->Finish();
4700 } else if (mPath->GetFillRule() != fillRule) {
4701 RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule);
4702 mPath = tmpPathBuilder->Finish();
4705 return mPath;
4708 void
4709 CanvasPath::EnsurePathBuilder() const
4711 if (mPathBuilder) {
4712 return;
4715 // if there is not pathbuilder, there must be a path
4716 MOZ_ASSERT(mPath);
4717 mPathBuilder = mPath->CopyToBuilder();
4718 mPath = nullptr;