Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CanvasRenderingContext2D.h"
#include "mozilla/gfx/Helpers.h"
#include "nsXULElement.h"
#include "nsMathUtils.h"
#include "nsContentUtils.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/SVGImageContext.h"
#include "mozilla/SVGObserverUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/GeneratePlaceholderCanvasData.h"
#include "nsPresContext.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIFrame.h"
#include "nsError.h"
#include "nsCSSPseudoElements.h"
#include "nsComputedDOMStyle.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsColor.h"
#include "nsGfxCIID.h"
#include "nsIDocShell.h"
#include "nsPIDOMWindow.h"
#include "nsDisplayList.h"
#include "nsFocusManager.h"
#include "nsContentUtils.h"
#include "nsTArray.h"
#include "ImageEncoder.h"
#include "ImageRegion.h"
#include "gfxContext.h"
#include "gfxPlatform.h"
#include "gfxFont.h"
#include "gfxBlur.h"
#include "gfxTextRun.h"
#include "gfxUtils.h"
#include "nsFrameLoader.h"
#include "nsBidiPresUtils.h"
#include "Layers.h"
#include "LayerUserData.h"
#include "CanvasUtils.h"
#include "nsIMemoryReporter.h"
#include "nsStyleUtil.h"
#include "CanvasImageCache.h"
#include <algorithm>
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/Array.h" // JS::GetArrayLength
#include "js/Conversions.h"
#include "js/experimental/TypedData.h" // JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData
#include "js/HeapAPI.h"
#include "js/Warnings.h" // JS::WarnASCII
#include "mozilla/Alignment.h"
#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/CanvasGradient.h"
#include "mozilla/dom/CanvasPattern.h"
#include "mozilla/dom/DOMMatrix.h"
#include "mozilla/dom/ImageBitmap.h"
#include "mozilla/dom/ImageData.h"
#include "mozilla/dom/PBrowserParent.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/FilterInstance.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/PatternHelpers.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/layers/PersistentBufferProvider.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Preferences.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "nsCCUncollectableMarker.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
#include "mozilla/dom/CanvasPath.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/SVGImageElement.h"
#include "mozilla/dom/TextMetrics.h"
#include "mozilla/FloatingPoint.h"
#include "nsGlobalWindow.h"
#include "nsDeviceContext.h"
#include "nsFontMetrics.h"
#include "nsLayoutUtils.h"
#include "Units.h"
#include "CanvasUtils.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/ServoCSSParser.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/SVGContentUtils.h"
#include "mozilla/layers/CanvasClient.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/layers/WebRenderCanvasRenderer.h"
#undef free // apparently defined by some windows header, clashing with a
// free() method in SkTypes.h
#ifdef XP_WIN
# include "gfxWindowsPlatform.h"
#endif
// windows.h (included by chromium code) defines this, in its infinite wisdom
#undef DrawText
using namespace mozilla;
using namespace mozilla::CanvasUtils;
using namespace mozilla::css;
using namespace mozilla::gfx;
using namespace mozilla::image;
using namespace mozilla::ipc;
using namespace mozilla::layers;
namespace mozilla::dom {
// Cap sigma to avoid overly large temp surfaces.
const Float SIGMA_MAX = 100;
const size_t MAX_STYLE_STACK_SIZE = 1024;
/* Memory reporter stuff */
static int64_t gCanvasAzureMemoryUsed = 0;
// Adds Save() / Restore() calls to the scope.
class MOZ_RAII AutoSaveRestore {
public:
explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx) : mCtx(aCtx) {
mCtx->Save();
}
~AutoSaveRestore() { mCtx->Restore(); }
private:
RefPtr<CanvasRenderingContext2D> mCtx;
};
// This is KIND_OTHER because it's not always clear where in memory the pixels
// of a canvas are stored. Furthermore, this memory will be tracked by the
// underlying surface implementations. See bug 655638 for details.
class Canvas2dPixelsReporter final : public nsIMemoryReporter {
~Canvas2dPixelsReporter() = default;
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override {
MOZ_COLLECT_REPORT("canvas-2d-pixels", KIND_OTHER, UNITS_BYTES,
gCanvasAzureMemoryUsed,
"Memory used by 2D canvases. Each canvas requires "
"(width * height * 4) bytes.");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
class CanvasConicGradient : public CanvasGradient {
public:
CanvasConicGradient(CanvasRenderingContext2D* aContext, Float aAngle,
const Point& aCenter)
: CanvasGradient(aContext, Type::CONIC),
mAngle(aAngle),
mCenter(aCenter) {}
const Float mAngle;
const Point mCenter;
};
class CanvasRadialGradient : public CanvasGradient {
public:
CanvasRadialGradient(CanvasRenderingContext2D* aContext,
const Point& aBeginOrigin, Float aBeginRadius,
const Point& aEndOrigin, Float aEndRadius)
: CanvasGradient(aContext, Type::RADIAL),
mCenter1(aBeginOrigin),
mCenter2(aEndOrigin),
mRadius1(aBeginRadius),
mRadius2(aEndRadius) {}
Point mCenter1;
Point mCenter2;
Float mRadius1;
Float mRadius2;
};
class CanvasLinearGradient : public CanvasGradient {
public:
CanvasLinearGradient(CanvasRenderingContext2D* aContext, const Point& aBegin,
const Point& aEnd)
: CanvasGradient(aContext, Type::LINEAR), mBegin(aBegin), mEnd(aEnd) {}
protected:
friend struct CanvasBidiProcessor;
friend class CanvasGeneralPattern;
// Beginning of linear gradient.
Point mBegin;
// End of linear gradient.
Point mEnd;
};
bool CanvasRenderingContext2D::PatternIsOpaque(
CanvasRenderingContext2D::Style aStyle, bool* aIsColor) const {
const ContextState& state = CurrentState();
bool opaque = false;
bool color = false;
if (state.globalAlpha >= 1.0) {
if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) {
opaque = IsOpaque(state.patternStyles[aStyle]->mSurface->GetFormat());
} else if (!state.gradientStyles[aStyle]) {
// TODO: for gradient patterns we could check that all stops are opaque
// colors.
// it's a color pattern.
opaque = sRGBColor::FromABGR(state.colorStyles[aStyle]).a >= 1.0;
color = true;
}
}
if (aIsColor) {
*aIsColor = color;
}
return opaque;
}
// This class is named 'GeneralCanvasPattern' instead of just
// 'GeneralPattern' to keep Windows PGO builds from confusing the
// GeneralPattern class in gfxContext.cpp with this one.
class CanvasGeneralPattern {
public:
typedef CanvasRenderingContext2D::Style Style;
typedef CanvasRenderingContext2D::ContextState ContextState;
Pattern& ForStyle(CanvasRenderingContext2D* aCtx, Style aStyle,
DrawTarget* aRT) {
// This should only be called once or the mPattern destructor will
// not be executed.
NS_ASSERTION(
!mPattern.GetPattern(),
"ForStyle() should only be called once on CanvasGeneralPattern!");
const ContextState& state = aCtx->CurrentState();
if (state.StyleIsColor(aStyle)) {
mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle]));
} else if (state.gradientStyles[aStyle] &&
state.gradientStyles[aStyle]->GetType() ==
CanvasGradient::Type::LINEAR) {
auto gradient = static_cast<CanvasLinearGradient*>(
state.gradientStyles[aStyle].get());
mPattern.InitLinearGradientPattern(
gradient->mBegin, gradient->mEnd,
gradient->GetGradientStopsForTarget(aRT));
} else if (state.gradientStyles[aStyle] &&
state.gradientStyles[aStyle]->GetType() ==
CanvasGradient::Type::RADIAL) {
auto gradient = static_cast<CanvasRadialGradient*>(
state.gradientStyles[aStyle].get());
mPattern.InitRadialGradientPattern(
gradient->mCenter1, gradient->mCenter2, gradient->mRadius1,
gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT));
} else if (state.gradientStyles[aStyle] &&
state.gradientStyles[aStyle]->GetType() ==
CanvasGradient::Type::CONIC) {
auto gradient =
static_cast<CanvasConicGradient*>(state.gradientStyles[aStyle].get());
mPattern.InitConicGradientPattern(
gradient->mCenter, gradient->mAngle, 0, 1,
gradient->GetGradientStopsForTarget(aRT));
} else if (state.patternStyles[aStyle]) {
if (aCtx->mCanvasElement) {
CanvasUtils::DoDrawImageSecurityCheck(
aCtx->mCanvasElement, state.patternStyles[aStyle]->mPrincipal,
state.patternStyles[aStyle]->mForceWriteOnly,
state.patternStyles[aStyle]->mCORSUsed);
}
ExtendMode mode;
if (state.patternStyles[aStyle]->mRepeat ==
CanvasPattern::RepeatMode::NOREPEAT) {
mode = ExtendMode::CLAMP;
} else {
mode = ExtendMode::REPEAT;
}
SamplingFilter samplingFilter;
if (state.imageSmoothingEnabled) {
samplingFilter = SamplingFilter::GOOD;
} else {
samplingFilter = SamplingFilter::POINT;
}
mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode,
state.patternStyles[aStyle]->mTransform,
samplingFilter);
}
return *mPattern.GetPattern();
}
GeneralPattern mPattern;
};
/* This is an RAII based class that can be used as a drawtarget for
* operations that need to have a filter applied to their results.
* All coordinates passed to the constructor are in device space.
*/
class AdjustedTargetForFilter {
public:
typedef CanvasRenderingContext2D::ContextState ContextState;
AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx,
DrawTarget* aFinalTarget,
const gfx::IntPoint& aFilterSpaceToTargetOffset,
const gfx::IntRect& aPreFilterBounds,
const gfx::IntRect& aPostFilterBounds,
gfx::CompositionOp aCompositionOp)
: mFinalTarget(aFinalTarget),
mCtx(aCtx),
mPostFilterBounds(aPostFilterBounds),
mOffset(aFilterSpaceToTargetOffset),
mCompositionOp(aCompositionOp) {
nsIntRegion sourceGraphicNeededRegion;
nsIntRegion fillPaintNeededRegion;
nsIntRegion strokePaintNeededRegion;
FilterSupport::ComputeSourceNeededRegions(
aCtx->CurrentState().filter, mPostFilterBounds,
sourceGraphicNeededRegion, fillPaintNeededRegion,
strokePaintNeededRegion);
mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds();
mFillPaintRect = fillPaintNeededRegion.GetBounds();
mStrokePaintRect = strokePaintNeededRegion.GetBounds();
mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds);
if (mSourceGraphicRect.IsEmpty()) {
// The filter might not make any use of the source graphic. We need to
// create a DrawTarget that we can return from DT() anyway, so we'll
// just use a 1x1-sized one.
mSourceGraphicRect.SizeTo(1, 1);
}
if (!mFinalTarget->CanCreateSimilarDrawTarget(mSourceGraphicRect.Size(),
SurfaceFormat::B8G8R8A8)) {
mTarget = mFinalTarget;
mCtx = nullptr;
mFinalTarget = nullptr;
return;
}
mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(),
SurfaceFormat::B8G8R8A8);
if (mTarget) {
// See bug 1524554.
mTarget->ClearRect(gfx::Rect());
}
if (!mTarget || !mTarget->IsValid()) {
// XXX - Deal with the situation where our temp size is too big to
// fit in a texture (bug 1066622).
mTarget = mFinalTarget;
mCtx = nullptr;
mFinalTarget = nullptr;
return;
}
mTarget->SetTransform(mFinalTarget->GetTransform().PostTranslate(
-mSourceGraphicRect.TopLeft() + mOffset));
}
// Return a SourceSurface that contains the FillPaint or StrokePaint source.
already_AddRefed<SourceSurface> DoSourcePaint(
gfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle) {
if (aRect.IsEmpty()) {
return nullptr;
}
RefPtr<DrawTarget> dt = mFinalTarget->CreateSimilarDrawTarget(
aRect.Size(), SurfaceFormat::B8G8R8A8);
if (dt) {
// See bug 1524554.
dt->ClearRect(gfx::Rect());
}
if (!dt || !dt->IsValid()) {
aRect.SetEmpty();
return nullptr;
}
Matrix transform =
mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset);
dt->SetTransform(transform);
if (transform.Invert()) {
gfx::Rect dtBounds(0, 0, aRect.width, aRect.height);
gfx::Rect fillRect = transform.TransformBounds(dtBounds);
dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt));
}
return dt->Snapshot();
}
~AdjustedTargetForFilter() {
if (!mCtx) {
return;
}
RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
RefPtr<SourceSurface> fillPaint =
DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL);
RefPtr<SourceSurface> strokePaint = DoSourcePaint(
mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE);
AutoRestoreTransform autoRestoreTransform(mFinalTarget);
mFinalTarget->SetTransform(Matrix());
MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty());
gfx::FilterSupport::RenderFilterDescription(
mFinalTarget, mCtx->CurrentState().filter, gfx::Rect(mPostFilterBounds),
snapshot, mSourceGraphicRect, fillPaint, mFillPaintRect, strokePaint,
mStrokePaintRect, mCtx->CurrentState().filterAdditionalImages,
mPostFilterBounds.TopLeft() - mOffset,
DrawOptions(1.0f, mCompositionOp));
const gfx::FilterDescription& filter = mCtx->CurrentState().filter;
MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty());
if (filter.mPrimitives.LastElement().IsTainted() && mCtx->mCanvasElement) {
mCtx->mCanvasElement->SetWriteOnly();
}
}
DrawTarget* DT() { return mTarget; }
private:
RefPtr<DrawTarget> mTarget;
RefPtr<DrawTarget> mFinalTarget;
CanvasRenderingContext2D* mCtx;
gfx::IntRect mSourceGraphicRect;
gfx::IntRect mFillPaintRect;
gfx::IntRect mStrokePaintRect;
gfx::IntRect mPostFilterBounds;
gfx::IntPoint mOffset;
gfx::CompositionOp mCompositionOp;
};
/* This is an RAII based class that can be used as a drawtarget for
* operations that need to have a shadow applied to their results.
* All coordinates passed to the constructor are in device space.
*/
class AdjustedTargetForShadow {
public:
typedef CanvasRenderingContext2D::ContextState ContextState;
AdjustedTargetForShadow(CanvasRenderingContext2D* aCtx,
DrawTarget* aFinalTarget, const gfx::Rect& aBounds,
gfx::CompositionOp aCompositionOp)
: mFinalTarget(aFinalTarget), mCtx(aCtx), mCompositionOp(aCompositionOp) {
const ContextState& state = mCtx->CurrentState();
mSigma = state.ShadowBlurSigma();
// We actually include the bounds of the shadow blur, this makes it
// easier to execute the actual blur on hardware, and shouldn't affect
// the amount of pixels that need to be touched.
gfx::Rect bounds = aBounds;
int32_t blurRadius = state.ShadowBlurRadius();
bounds.Inflate(blurRadius);
bounds.RoundOut();
bounds.ToIntRect(&mTempRect);
if (!mFinalTarget->CanCreateSimilarDrawTarget(mTempRect.Size(),
SurfaceFormat::B8G8R8A8)) {
mTarget = mFinalTarget;
mCtx = nullptr;
mFinalTarget = nullptr;
return;
}
mTarget = mFinalTarget->CreateShadowDrawTarget(
mTempRect.Size(), SurfaceFormat::B8G8R8A8, mSigma);
if (mTarget) {
// See bug 1524554.
mTarget->ClearRect(gfx::Rect());
}
if (!mTarget || !mTarget->IsValid()) {
// XXX - Deal with the situation where our temp size is too big to
// fit in a texture (bug 1066622).
mTarget = mFinalTarget;
mCtx = nullptr;
mFinalTarget = nullptr;
} else {
mTarget->SetTransform(
mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft()));
}
}
~AdjustedTargetForShadow() {
if (!mCtx) {
return;
}
RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
mFinalTarget->DrawSurfaceWithShadow(
snapshot, mTempRect.TopLeft(),
ToDeviceColor(mCtx->CurrentState().shadowColor),
mCtx->CurrentState().shadowOffset, mSigma, mCompositionOp);
}
DrawTarget* DT() { return mTarget; }
gfx::IntPoint OffsetToFinalDT() { return mTempRect.TopLeft(); }
private:
RefPtr<DrawTarget> mTarget;
RefPtr<DrawTarget> mFinalTarget;
CanvasRenderingContext2D* mCtx;
Float mSigma;
gfx::IntRect mTempRect;
gfx::CompositionOp mCompositionOp;
};
/*
* This is an RAII based class that can be used as a drawtarget for
* operations that need a shadow or a filter drawn. It will automatically
* provide a temporary target when needed, and if so blend it back with a
* shadow, filter, or both.
* If both a shadow and a filter are needed, the filter is applied first,
* and the shadow is applied to the filtered results.
*
* aBounds specifies the bounds of the drawing operation that will be
* drawn to the target, it is given in device space! If this is nullptr the
* drawing operation will be assumed to cover the whole canvas.
*/
class AdjustedTarget {
public:
typedef CanvasRenderingContext2D::ContextState ContextState;
explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
const gfx::Rect* aBounds = nullptr) {
// All rects in this function are in the device space of ctx->mTarget.
// In order to keep our temporary surfaces as small as possible, we first
// calculate what their maximum required bounds would need to be if we
// were to fill the whole canvas. Everything outside those bounds we don't
// need to render.
gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight);
gfx::Rect maxSourceNeededBoundsForShadow =
MaxSourceNeededBoundsForShadow(r, aCtx);
gfx::Rect maxSourceNeededBoundsForFilter =
MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx);
if (!aCtx->IsTargetValid()) {
return;
}
gfx::Rect bounds = maxSourceNeededBoundsForFilter;
if (aBounds) {
bounds = bounds.Intersect(*aBounds);
}
gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx);
if (!aCtx->IsTargetValid()) {
return;
}
mozilla::gfx::CompositionOp op = aCtx->CurrentState().op;
gfx::IntPoint offsetToFinalDT;
// First set up the shadow draw target, because the shadow goes outside.
// It applies to the post-filter results, if both a filter and a shadow
// are used.
if (aCtx->NeedToDrawShadow()) {
mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
aCtx, aCtx->mTarget, boundsAfterFilter, op);
mTarget = mShadowTarget->DT();
offsetToFinalDT = mShadowTarget->OffsetToFinalDT();
// If we also have a filter, the filter needs to be drawn with OP_OVER
// because shadow drawing already applies op on the result.
op = gfx::CompositionOp::OP_OVER;
}
// Now set up the filter draw target.
const bool applyFilter = aCtx->NeedToApplyFilter();
if (!aCtx->IsTargetValid()) {
return;
}
if (applyFilter) {
bounds.RoundOut();
if (!mTarget) {
mTarget = aCtx->mTarget;
}
gfx::IntRect intBounds;
if (!bounds.ToIntRect(&intBounds)) {
return;
}
mFilterTarget = MakeUnique<AdjustedTargetForFilter>(
aCtx, mTarget, offsetToFinalDT, intBounds,
gfx::RoundedToInt(boundsAfterFilter), op);
mTarget = mFilterTarget->DT();
}
if (!mTarget) {
mTarget = aCtx->mTarget;
}
}
~AdjustedTarget() {
// The order in which the targets are finalized is important.
// Filters are inside, any shadow applies to the post-filter results.
mFilterTarget.reset();
mShadowTarget.reset();
}
operator DrawTarget*() { return mTarget; }
DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mTarget; }
private:
gfx::Rect MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds,
CanvasRenderingContext2D* aCtx) {
const bool applyFilter = aCtx->NeedToApplyFilter();
if (!aCtx->IsTargetValid()) {
return aDestBounds;
}
if (!applyFilter) {
return aDestBounds;
}
nsIntRegion sourceGraphicNeededRegion;
nsIntRegion fillPaintNeededRegion;
nsIntRegion strokePaintNeededRegion;
FilterSupport::ComputeSourceNeededRegions(
aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds),
sourceGraphicNeededRegion, fillPaintNeededRegion,
strokePaintNeededRegion);
return gfx::Rect(sourceGraphicNeededRegion.GetBounds());
}
gfx::Rect MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds,
CanvasRenderingContext2D* aCtx) {
if (!aCtx->NeedToDrawShadow()) {
return aDestBounds;
}
const ContextState& state = aCtx->CurrentState();
gfx::Rect sourceBounds = aDestBounds - state.shadowOffset;
sourceBounds.Inflate(state.ShadowBlurRadius());
// Union the shadow source with the original rect because we're going to
// draw both.
return sourceBounds.Union(aDestBounds);
}
gfx::Rect BoundsAfterFilter(const gfx::Rect& aBounds,
CanvasRenderingContext2D* aCtx) {
const bool applyFilter = aCtx->NeedToApplyFilter();
if (!aCtx->IsTargetValid()) {
return aBounds;
}
if (!applyFilter) {
return aBounds;
}
gfx::Rect bounds(aBounds);
bounds.RoundOut();
gfx::IntRect intBounds;
if (!bounds.ToIntRect(&intBounds)) {
return gfx::Rect();
}
nsIntRegion extents = gfx::FilterSupport::ComputePostFilterExtents(
aCtx->CurrentState().filter, intBounds);
return gfx::Rect(extents.GetBounds());
}
RefPtr<DrawTarget> mTarget;
UniquePtr<AdjustedTargetForShadow> mShadowTarget;
UniquePtr<AdjustedTargetForFilter> mFilterTarget;
};
void CanvasPattern::SetTransform(const DOMMatrix2DInit& aInit,
ErrorResult& aError) {
RefPtr<DOMMatrixReadOnly> matrix =
DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError);
if (aError.Failed()) {
return;
}
const auto* matrix2D = matrix->GetInternal2D();
if (!matrix2D->IsFinite()) {
return;
}
mTransform = Matrix(*matrix2D);
}
void CanvasGradient::AddColorStop(float aOffset, const nsACString& aColorstr,
ErrorResult& aRv) {
if (aOffset < 0.0 || aOffset > 1.0) {
return aRv.ThrowIndexSizeError("Offset out of 0-1.0 range");
}
PresShell* presShell = mContext ? mContext->GetPresShell() : nullptr;
ServoStyleSet* styleSet = presShell ? presShell->StyleSet() : nullptr;
nscolor color;
bool ok = ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), aColorstr,
&color);
if (!ok) {
return aRv.ThrowSyntaxError("Invalid color");
}
GradientStop newStop;
newStop.offset = aOffset;
newStop.color = ToDeviceColor(color);
mRawStops.AppendElement(newStop);
}
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
class CanvasShutdownObserver final : public nsIObserver {
public:
explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
: mCanvas(aCanvas) {}
void OnShutdown() {
if (!mCanvas) {
return;
}
mCanvas = nullptr;
nsContentUtils::UnregisterShutdownObserver(this);
}
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
private:
~CanvasShutdownObserver() = default;
CanvasRenderingContext2D* mCanvas;
};
NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
NS_IMETHODIMP
CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
mCanvas->OnShutdown();
OnShutdown();
}
return NS_OK;
}
NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
// Make sure we remove ourselves from the list of demotable contexts (raw
// pointers), since we're logically destructed at this point.
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
ImplCycleCollectionUnlink(
tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
auto autoSVGFiltersObserver =
tmp->mStyleStack[i].autoSVGFiltersObserver.get();
if (autoSVGFiltersObserver) {
// XXXjwatt: I don't think this call achieves anything. See the comment
// that documents this function.
SVGObserverUtils::DetachFromCanvasContext(autoSVGFiltersObserver);
}
ImplCycleCollectionUnlink(tmp->mStyleStack[i].autoSVGFiltersObserver);
}
for (size_t x = 0; x < tmp->mHitRegionsOptions.Length(); x++) {
RegionInfo& info = tmp->mHitRegionsOptions[x];
if (info.mElement) {
ImplCycleCollectionUnlink(info.mElement);
}
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
ImplCycleCollectionTraverse(
cb, tmp->mStyleStack[i].patternStyles[Style::STROKE],
"Stroke CanvasPattern");
ImplCycleCollectionTraverse(cb,
tmp->mStyleStack[i].patternStyles[Style::FILL],
"Fill CanvasPattern");
ImplCycleCollectionTraverse(
cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE],
"Stroke CanvasGradient");
ImplCycleCollectionTraverse(cb,
tmp->mStyleStack[i].gradientStyles[Style::FILL],
"Fill CanvasGradient");
ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].autoSVGFiltersObserver,
"RAII SVG Filters Observer");
}
for (size_t x = 0; x < tmp->mHitRegionsOptions.Length(); x++) {
RegionInfo& info = tmp->mHitRegionsOptions[x];
if (info.mElement) {
ImplCycleCollectionTraverse(cb, info.mElement,
"Hit region fallback element");
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D)
if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) {
dom::Element* canvasElement = tmp->mCanvasElement;
if (canvasElement) {
if (canvasElement->IsPurple()) {
canvasElement->RemovePurple();
}
dom::Element::MarkNodeChildren(canvasElement);
}
return true;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D)
return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D)
return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
CanvasRenderingContext2D::ContextState::ContextState() = default;
CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther)
: fontGroup(aOther.fontGroup),
fontLanguage(aOther.fontLanguage),
fontFont(aOther.fontFont),
gradientStyles(aOther.gradientStyles),
patternStyles(aOther.patternStyles),
colorStyles(aOther.colorStyles),
font(aOther.font),
textAlign(aOther.textAlign),
textBaseline(aOther.textBaseline),
shadowColor(aOther.shadowColor),
transform(aOther.transform),
shadowOffset(aOther.shadowOffset),
lineWidth(aOther.lineWidth),
miterLimit(aOther.miterLimit),
globalAlpha(aOther.globalAlpha),
shadowBlur(aOther.shadowBlur),
dash(aOther.dash.Clone()),
dashOffset(aOther.dashOffset),
op(aOther.op),
fillRule(aOther.fillRule),
lineCap(aOther.lineCap),
lineJoin(aOther.lineJoin),
filterString(aOther.filterString),
filterChain(aOther.filterChain),
autoSVGFiltersObserver(aOther.autoSVGFiltersObserver),
filter(aOther.filter),
filterAdditionalImages(aOther.filterAdditionalImages.Clone()),
filterSourceGraphicTainted(aOther.filterSourceGraphicTainted),
imageSmoothingEnabled(aOther.imageSmoothingEnabled),
fontExplicitLanguage(aOther.fontExplicitLanguage) {}
CanvasRenderingContext2D::ContextState::~ContextState() = default;
void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle,
nscolor aColor) {
colorStyles[aWhichStyle] = aColor;
gradientStyles[aWhichStyle] = nullptr;
patternStyles[aWhichStyle] = nullptr;
}
void CanvasRenderingContext2D::ContextState::SetPatternStyle(
Style aWhichStyle, CanvasPattern* aPat) {
gradientStyles[aWhichStyle] = nullptr;
patternStyles[aWhichStyle] = aPat;
}
void CanvasRenderingContext2D::ContextState::SetGradientStyle(
Style aWhichStyle, CanvasGradient* aGrad) {
gradientStyles[aWhichStyle] = aGrad;
patternStyles[aWhichStyle] = nullptr;
}
/**
** CanvasRenderingContext2D impl
**/
// Initialize our static variables.
uintptr_t CanvasRenderingContext2D::sNumLivingContexts = 0;
DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
CanvasRenderingContext2D::CanvasRenderingContext2D(
layers::LayersBackend aCompositorBackend)
: // these are the default values from the Canvas spec
mWidth(0),
mHeight(0),
mZero(false),
mOpaqueAttrValue(false),
mContextAttributesHasAlpha(true),
mOpaque(false),
mResetLayer(true),
mIPC(false),
mHasPendingStableStateCallback(false),
mIsEntireFrameInvalid(false),
mPredictManyRedrawCalls(false),
mIsCapturedFrameInvalid(false),
mPathTransformWillUpdate(false),
mInvalidateCount(0),
mWriteOnly(false) {
sNumLivingContexts++;
mShutdownObserver = new CanvasShutdownObserver(this);
nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
}
CanvasRenderingContext2D::~CanvasRenderingContext2D() {
RemovePostRefreshObserver();
RemoveShutdownObserver();
Reset();
sNumLivingContexts--;
if (!sNumLivingContexts) {
NS_IF_RELEASE(sErrorTarget);
}
}
JSObject* CanvasRenderingContext2D::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto);
}
bool CanvasRenderingContext2D::ParseColor(const nsACString& aString,
nscolor* aColor) {
Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
css::Loader* loader = document ? document->CSSLoader() : nullptr;
PresShell* presShell = GetPresShell();
ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
// First, try computing the color without handling currentcolor.
bool wasCurrentColor = false;
if (!ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, aColor,
&wasCurrentColor, loader)) {
return false;
}
if (wasCurrentColor && mCanvasElement) {
// Otherwise, get the value of the color property, flushing style
// if necessary.
RefPtr<ComputedStyle> canvasStyle =
nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr);
if (canvasStyle) {
*aColor = canvasStyle->StyleText()->mColor.ToColor();
}
// Beware that the presShell could be gone here.
}
return true;
}
nsresult CanvasRenderingContext2D::Reset() {
if (mCanvasElement) {
mCanvasElement->InvalidateCanvas();
}
// only do this for non-docshell created contexts,
// since those are the ones that we created a surface for
if (mTarget && IsTargetValid() && !mDocShell) {
gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
}
bool forceReset = true;
ReturnTarget(forceReset);
mTarget = nullptr;
mBufferProvider = nullptr;
// reset hit regions
mHitRegionsOptions.ClearAndRetainStorage();
// Since the target changes the backing texture will change, and this will
// no longer be valid.
mIsEntireFrameInvalid = false;
mPredictManyRedrawCalls = false;
mIsCapturedFrameInvalid = false;
return NS_OK;
}
void CanvasRenderingContext2D::OnShutdown() {
mShutdownObserver = nullptr;
RefPtr<PersistentBufferProvider> provider = mBufferProvider;
Reset();
if (provider) {
provider->OnShutdown();
}
}
void CanvasRenderingContext2D::RemoveShutdownObserver() {
if (mShutdownObserver) {
mShutdownObserver->OnShutdown();
mShutdownObserver = nullptr;
}
}
void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
Style aWhichStyle) {
MOZ_ASSERT(!aStr.IsVoid());
nscolor color;
if (!ParseColor(aStr, &color)) {
return;
}
CurrentState().SetColorStyle(aWhichStyle, color);
}
void CanvasRenderingContext2D::GetStyleAsUnion(
OwningUTF8StringOrCanvasGradientOrCanvasPattern& aValue,
Style aWhichStyle) {
const ContextState& state = CurrentState();
if (state.patternStyles[aWhichStyle]) {
aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle];
} else if (state.gradientStyles[aWhichStyle]) {
aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle];
} else {
StyleColorToString(state.colorStyles[aWhichStyle],
aValue.SetAsUTF8String());
}
}
// static
void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor,
nsACString& aStr) {
aStr.Truncate();
// We can't reuse the normal CSS color stringification code,
// because the spec calls for a different algorithm for canvas.
if (NS_GET_A(aColor) == 255) {
aStr.AppendPrintf("#%02x%02x%02x", NS_GET_R(aColor), NS_GET_G(aColor),
NS_GET_B(aColor));
} else {
aStr.AppendPrintf("rgba(%d, %d, %d, ", NS_GET_R(aColor), NS_GET_G(aColor),
NS_GET_B(aColor));
aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
aStr.Append(')');
}
}
nsresult CanvasRenderingContext2D::Redraw() {
mIsCapturedFrameInvalid = true;
if (mIsEntireFrameInvalid) {
return NS_OK;
}
mIsEntireFrameInvalid = true;
if (!mCanvasElement) {
NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
return NS_OK;
}
SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
mCanvasElement->InvalidateCanvasContent(nullptr);
return NS_OK;
}
void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
mIsCapturedFrameInvalid = true;
++mInvalidateCount;
if (mIsEntireFrameInvalid) {
return;
}
if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) {
Redraw();
return;
}
if (!mCanvasElement) {
NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
return;
}
SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
mCanvasElement->InvalidateCanvasContent(&aR);
}
void CanvasRenderingContext2D::DidRefresh() {}
void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) {
mIsCapturedFrameInvalid = true;
if (mIsEntireFrameInvalid) {
++mInvalidateCount;
return;
}
gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR));
Redraw(newr);
}
bool CanvasRenderingContext2D::CopyBufferProvider(
PersistentBufferProvider& aOld, DrawTarget& aTarget, IntRect aCopyRect) {
// Borrowing the snapshot must be done after ReturnTarget.
RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot();
if (!snapshot) {
return false;
}
aTarget.CopySurface(snapshot, aCopyRect, IntPoint());
aOld.ReturnSnapshot(snapshot.forget());
return true;
}
void CanvasRenderingContext2D::Demote() {}
void CanvasRenderingContext2D::ScheduleStableStateCallback() {
if (mHasPendingStableStateCallback) {
return;
}
mHasPendingStableStateCallback = true;
nsContentUtils::RunInStableState(
NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", this,
&CanvasRenderingContext2D::OnStableState));
}
void CanvasRenderingContext2D::OnStableState() {
if (!mHasPendingStableStateCallback) {
return;
}
ReturnTarget();
mHasPendingStableStateCallback = false;
}
void CanvasRenderingContext2D::RestoreClipsAndTransformToTarget() {
// Restore clips and transform.
mTarget->SetTransform(Matrix());
if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
// Cairo doesn't play well with huge clips. When given a very big clip it
// will try to allocate big mask surface without taking the target
// size into account which can cause OOM. See bug 1034593.
// This limits the clip extents to the size of the canvas.
// A fix in Cairo would probably be preferable, but requires somewhat
// invasive changes.
mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight));
}
for (auto& style : mStyleStack) {
for (auto clipOrTransform = style.clipsAndTransforms.begin();
clipOrTransform != style.clipsAndTransforms.end(); clipOrTransform++) {
if (clipOrTransform->IsClip()) {
if (mClipsNeedConverting) {
// We have possibly changed backends, so we need to convert the clips
// in case they are no longer compatible with mTarget.
RefPtr<PathBuilder> pathBuilder = mTarget->CreatePathBuilder();
clipOrTransform->clip->StreamToSink(pathBuilder);
clipOrTransform->clip = pathBuilder->Finish();
}
mTarget->PushClip(clipOrTransform->clip);
} else {
mTarget->SetTransform(clipOrTransform->transform);
}
}
}
mClipsNeedConverting = false;
}
bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
bool aWillClear) {
if (AlreadyShutDown()) {
gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
SetErrorState();
return false;
}
if (mTarget) {
return mTarget != sErrorTarget;
}
// Check that the dimensions are sane
if (mWidth > StaticPrefs::gfx_canvas_max_size() ||
mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 ||
mHeight < 0) {
SetErrorState();
return false;
}
// If the next drawing command covers the entire canvas, we can skip copying
// from the previous frame and/or clearing the canvas.
gfx::Rect canvasRect(0, 0, mWidth, mHeight);
bool canDiscardContent =
aCoveredRect && CurrentState()
.transform.TransformBounds(*aCoveredRect)
.Contains(canvasRect);
// If a clip is active we don't know for sure that the next drawing command
// will really cover the entire canvas.
for (const auto& style : mStyleStack) {
if (!canDiscardContent) {
break;
}
for (const auto& clipOrTransform : style.clipsAndTransforms) {
if (clipOrTransform.IsClip()) {
canDiscardContent = false;
break;
}
}
}
ScheduleStableStateCallback();
IntRect persistedRect =
canDiscardContent ? IntRect() : IntRect(0, 0, mWidth, mHeight);
if (mBufferProvider) {
mTarget = mBufferProvider->BorrowDrawTarget(persistedRect);
if (mTarget && !mBufferProvider->PreservesDrawingState()) {
RestoreClipsAndTransformToTarget();
}
if (mTarget && mTarget->IsValid()) {
return true;
}
}
RefPtr<DrawTarget> newTarget;
RefPtr<PersistentBufferProvider> newProvider;
if (!TrySharedTarget(newTarget, newProvider) &&
!TryBasicTarget(newTarget, newProvider)) {
gfxCriticalError(
CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
<< "Failed borrow shared and basic targets.";
SetErrorState();
return false;
}
MOZ_ASSERT(newTarget);
MOZ_ASSERT(newProvider);
bool needsClear = !canDiscardContent;
if (newTarget->GetBackendType() == gfx::BackendType::SKIA &&
(needsClear || !aWillClear)) {
// Skia expects the unused X channel to contains 0xFF even for opaque
// operations so we can't skip clearing in that case, even if we are going
// to cover the entire canvas in the next drawing operation.
newTarget->ClearRect(canvasRect);
needsClear = false;
}
// Try to copy data from the previous buffer provider if there is one.
if (!canDiscardContent && mBufferProvider &&
CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
needsClear = false;
}
if (needsClear) {
newTarget->ClearRect(canvasRect);
}
mTarget = std::move(newTarget);
mBufferProvider = std::move(newProvider);
RegisterAllocation();
RestoreClipsAndTransformToTarget();
// Force a full layer transaction since we didn't have a layer before
// and now we might need one.
if (mCanvasElement) {
mCanvasElement->InvalidateCanvas();
}
// EnsureTarget hasn't drawn anything. Preserve mIsCapturedFrameInvalid.
bool capturedFrameInvalid = mIsCapturedFrameInvalid;
// Calling Redraw() tells our invalidation machinery that the entire
// canvas is already invalid, which can speed up future drawing.
Redraw();
mIsCapturedFrameInvalid = capturedFrameInvalid;
return true;
}
void CanvasRenderingContext2D::SetInitialState() {
// Set up the initial canvas defaults
mPathBuilder = nullptr;
mPath = nullptr;
mDSPathBuilder = nullptr;
mPathTransformWillUpdate = false;
mStyleStack.Clear();
ContextState* state = mStyleStack.AppendElement();
state->globalAlpha = 1.0;
state->colorStyles[Style::FILL] = NS_RGB(0, 0, 0);
state->colorStyles[Style::STROKE] = NS_RGB(0, 0, 0);
state->shadowColor = NS_RGBA(0, 0, 0, 0);
}
void CanvasRenderingContext2D::SetErrorState() {
EnsureErrorTarget();
if (mTarget && mTarget != sErrorTarget) {
gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
}
mTarget = sErrorTarget;
mBufferProvider = nullptr;
// clear transforms, clips, etc.
SetInitialState();
}
void CanvasRenderingContext2D::RegisterAllocation() {
// XXX - It would make more sense to track the allocation in
// PeristentBufferProvider, rather than here.
static bool registered = false;
// FIXME: Disable the reporter for now, see bug 1241865
if (!registered && false) {
registered = true;
RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
}
JSObject* wrapper = GetWrapperPreserveColor();
if (wrapper) {
CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
JS::GetObjectZone(wrapper));
}
}
static already_AddRefed<LayerManager> LayerManagerFromCanvasElement(
nsINode* aCanvasElement) {
if (!aCanvasElement) {
return nullptr;
}
return nsContentUtils::PersistentLayerManagerForDocument(
aCanvasElement->OwnerDoc());
}
bool CanvasRenderingContext2D::TrySharedTarget(
RefPtr<gfx::DrawTarget>& aOutDT,
RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
aOutDT = nullptr;
aOutProvider = nullptr;
if (!mCanvasElement) {
return false;
}
if (mBufferProvider &&
(mBufferProvider->GetType() == LayersBackend::LAYERS_CLIENT ||
mBufferProvider->GetType() == LayersBackend::LAYERS_WR)) {
// we are already using a shared buffer provider, we are allocating a new
// one because the current one failed so let's just fall back to the basic
// provider.
mClipsNeedConverting = true;
return false;
}
RefPtr<LayerManager> layerManager =
LayerManagerFromCanvasElement(mCanvasElement);
if (!layerManager) {
return false;
}
aOutProvider = layerManager->CreatePersistentBufferProvider(
GetSize(), GetSurfaceFormat());
if (!aOutProvider) {
return false;
}
// We can pass an empty persisted rect since we just created the buffer
// provider (nothing to restore).
aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
MOZ_ASSERT(aOutDT);
return !!aOutDT;
}
bool CanvasRenderingContext2D::TryBasicTarget(
RefPtr<gfx::DrawTarget>& aOutDT,
RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
GetSize(), GetSurfaceFormat());
if (!aOutDT) {
return false;
}
// See Bug 1524554 - this forces DT initialization.
aOutDT->ClearRect(gfx::Rect());
if (!aOutDT->IsValid()) {
aOutDT = nullptr;