/* -*- 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 "nsCSSValue.h"
#include "nsXULElement.h"
#include "nsMathUtils.h"
#include "nsContentUtils.h"
#include "mozilla/intl/BidiEmbeddingLevel.h"
#include "mozilla/GeckoBindings.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/FontFaceSetImpl.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/GeneratePlaceholderCanvasData.h"
#include "mozilla/dom/VideoFrame.h"
#include "mozilla/gfx/CanvasShutdownManager.h"
#include "nsPresContext.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIFrame.h"
#include "nsError.h"
#include "nsCSSPseudoElements.h"
#include "nsComputedDOMStyle.h"
#include "nsPrintfCString.h"
#include "nsRFPService.h"
#include "nsReadableUtils.h"
#include "nsColor.h"
#include "nsGfxCIID.h"
#include "nsIDocShell.h"
#include "nsPIDOMWindow.h"
#include "nsDisplayList.h"
#include "nsFocusManager.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 "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/PropertyAndElement.h" // JS_GetElement
#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/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/ImageBridgeChild.h"
#include "mozilla/layers/PersistentBufferProvider.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Preferences.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/StaticPrefs_browser.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 "mozilla/Logging.h"
#include "nsGlobalWindowInner.h"
#include "nsDeviceContext.h"
#include "nsFontMetrics.h"
#include "nsLayoutUtils.h"
#include "Units.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"
#include "WindowRenderer.h"
#include "GeckoBindings.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 Atomic<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:
using Style = CanvasRenderingContext2D::Style;
using ContextState = CanvasRenderingContext2D::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]) {
aCtx->DoSecurityCheck(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:
using ContextState = CanvasRenderingContext2D::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()) {
if (mCtx->mCanvasElement) {
mCtx->mCanvasElement->SetWriteOnly();
}
else if (mCtx->mOffscreenCanvas) {
mCtx->mOffscreenCanvas->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:
using ContextState = CanvasRenderingContext2D::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();
if (!bounds.ToIntRect(&mTempRect) ||
!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(),
ShadowOptions(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:
using ContextState = CanvasRenderingContext2D::ContextState;
explicit AdjustedTarget(CanvasRenderingContext2D* aCtx,
const gfx::Rect* aBounds = nullptr,
bool aAllowOptimization =
false)
: mCtx(aCtx),
mOptimizeShadow(
false),
mUsedOperation(aCtx->CurrentState().op) {
// 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() || !boundsAfterFilter.IsFinite()) {
return;
}
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.
const bool applyFilter = aCtx->NeedToApplyFilter();
if (aCtx->NeedToDrawShadow()) {
if (aAllowOptimization && !applyFilter) {
// If only drawing a shadow and no filter, then avoid buffering to an
// intermediate target while drawing the shadow directly to the final
// target. When doing so, we want to use the actual composition op
// instead of OP_OVER.
mTarget = aCtx->mTarget;
if (mTarget && mTarget->IsValid()) {
mOptimizeShadow =
true;
return;
}
}
mShadowTarget = MakeUnique<AdjustedTargetForShadow>(
aCtx, aCtx->mTarget, boundsAfterFilter, mUsedOperation);
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.
mUsedOperation = CompositionOp::OP_OVER;
}
// Now set up the filter draw target.
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), mUsedOperation);
mTarget = mFilterTarget->DT();
mUsedOperation = CompositionOp::OP_OVER;
}
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; }
CompositionOp UsedOperation()
const {
return mUsedOperation; }
bool UseOptimizeShadow()
const {
return mOptimizeShadow; }
ShadowOptions ShadowParams()
const {
const ContextState& state = mCtx->CurrentState();
return ShadowOptions(ToDeviceColor(state.shadowColor), state.shadowOffset,
state.ShadowBlurSigma());
}
void Fill(
const Path* aPath,
const Pattern& aPattern,
const DrawOptions& aOptions) {
if (mOptimizeShadow) {
mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions);
}
mTarget->Fill(aPath, aPattern, aOptions);
}
void FillRect(
const Rect& aRect,
const Pattern& aPattern,
const DrawOptions& aOptions) {
if (mOptimizeShadow) {
RefPtr<Path> path = MakePathForRect(*mTarget, aRect);
mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions);
}
mTarget->FillRect(aRect, aPattern, aOptions);
}
void Stroke(
const Path* aPath,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) {
if (mOptimizeShadow) {
mTarget->DrawShadow(aPath, aPattern, ShadowParams(), aOptions,
&aStrokeOptions);
}
mTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
}
void StrokeRect(
const Rect& aRect,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) {
if (mOptimizeShadow) {
RefPtr<Path> path = MakePathForRect(*mTarget, aRect);
mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions,
&aStrokeOptions);
}
mTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
}
void StrokeLine(
const Point& aStart,
const Point& aEnd,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) {
if (mOptimizeShadow) {
RefPtr<PathBuilder> builder = mTarget->CreatePathBuilder();
builder->MoveTo(aStart);
builder->LineTo(aEnd);
RefPtr<Path> path = builder->Finish();
mTarget->DrawShadow(path, aPattern, ShadowParams(), aOptions,
&aStrokeOptions);
}
mTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
}
void DrawSurface(SourceSurface* aSurface,
const Rect& aDest,
const Rect& aSource,
const DrawSurfaceOptions& aSurfOptions,
const DrawOptions& aOptions) {
if (mOptimizeShadow) {
RefPtr<Path> path = MakePathForRect(*mTarget, aSource);
ShadowOptions shadowParams(ShadowParams());
SurfacePattern pattern(aSurface, ExtendMode::CLAMP, Matrix(),
shadowParams.BlurRadius() > 1
? SamplingFilter::POINT
: aSurfOptions.mSamplingFilter);
Matrix matrix = Matrix::Scaling(aDest.width / aSource.width,
aDest.height / aSource.height);
matrix.PreTranslate(-aSource.x, -aSource.y);
matrix.PostTranslate(aDest.x, aDest.y);
AutoRestoreTransform autoRestoreTransform(mTarget);
mTarget->ConcatTransform(matrix);
mTarget->DrawShadow(path, pattern, shadowParams, aOptions);
}
mTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions);
}
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());
}
CanvasRenderingContext2D* mCtx;
bool mOptimizeShadow;
CompositionOp mUsedOperation;
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");
}
if (!mContext) {
return aRv.ThrowSyntaxError(
"No canvas context");
}
auto color = mContext->ParseColor(
aColorstr, CanvasRenderingContext2D::ResolveCurrentColor::No);
if (!color) {
return aRv.ThrowSyntaxError(
"Invalid color");
}
GradientStop newStop;
newStop.offset = aOffset;
newStop.color = ToDeviceColor(*color);
mRawStops.AppendElement(newStop);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_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(mOffscreenCanvas)
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]);
if (
auto* autoSVGFiltersObserver =
tmp->mStyleStack[i].autoSVGFiltersObserver.get()) {
/*
* XXXjwatt: I don't think this is doing anything useful. All we do under
* this function is clear a raw C-style (i.e. not strong) pointer. That's
* clearly not helping in breaking any cycles. The fact that we MOZ_CRASH
* in OnRenderingChange if that pointer is null indicates that this isn't
* even doing anything useful in terms of preventing further invalidation
* from any observed filters.
*/
autoSVGFiltersObserver->Detach();
}
ImplCycleCollectionUnlink(tmp->mStyleStack[i].autoSVGFiltersObserver);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOffscreenCanvas)
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");
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
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& aOthe
r)
: 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),
textDirection(aOther.textDirection),
fontKerning(aOther.fontKerning),
fontStretch(aOther.fontStretch),
fontVariantCaps(aOther.fontVariantCaps),
textRendering(aOther.textRendering),
letterSpacing(aOther.letterSpacing),
wordSpacing(aOther.wordSpacing),
fontLineHeight(aOther.fontLineHeight),
letterSpacingStr(aOther.letterSpacingStr),
wordSpacingStr(aOther.wordSpacingStr),
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.
MOZ_THREAD_LOCAL(uintptr_t) CanvasRenderingContext2D::sNumLivingContexts;
MOZ_THREAD_LOCAL(DrawTarget*) CanvasRenderingContext2D::sErrorTarget;
// Helpers to map Canvas2D WebIDL enum values to gfx constants for rendering.
static JoinStyle CanvasToGfx(CanvasLineJoin aJoin) {
switch (aJoin) {
case CanvasLineJoin::Round:
return JoinStyle::ROUND;
case CanvasLineJoin::Bevel:
return JoinStyle::BEVEL;
case CanvasLineJoin::Miter:
return JoinStyle::MITER_OR_BEVEL;
default:
MOZ_CRASH("unknown lineJoin!");
}
}
static CapStyle CanvasToGfx(CanvasLineCap aCap) {
switch (aCap) {
case CanvasLineCap::Butt:
return CapStyle::BUTT;
case CanvasLineCap::Round:
return CapStyle::ROUND;
case CanvasLineCap::Square:
return CapStyle::SQUARE;
default:
MOZ_CRASH("unknown lineCap!");
}
}
static uint8_t CanvasToGfx(CanvasFontKerning aKerning) {
switch (aKerning) {
case CanvasFontKerning::Auto:
return NS_FONT_KERNING_AUTO;
case CanvasFontKerning::Normal:
return NS_FONT_KERNING_NORMAL;
case CanvasFontKerning::None:
return NS_FONT_KERNING_NONE;
default:
MOZ_CRASH("unknown kerning!");
}
}
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),
mFrameCaptureState(FrameCaptureState::CLEAN,
"CanvasRenderingContext2D::mFrameCaptureState"),
mInvalidateCount(0),
mWriteOnly(false) {
sNumLivingContexts.infallibleInit();
sErrorTarget.infallibleInit();
sNumLivingContexts.set(sNumLivingContexts.get() + 1);
}
CanvasRenderingContext2D::~CanvasRenderingContext2D() {
CanvasImageCache::NotifyCanvasDestroyed(this);
RemovePostRefreshObserver();
RemoveShutdownObserver();
ResetBitmap();
sNumLivingContexts.set(sNumLivingContexts.get() - 1);
if (sNumLivingContexts.get() == 0 && sErrorTarget.get()) {
RefPtr<DrawTarget> target = dont_AddRef(sErrorTarget.get());
sErrorTarget.set(nullptr);
}
}
nsresult CanvasRenderingContext2D::Initialize() {
if (NS_WARN_IF(!AddShutdownObserver())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
JSObject* CanvasRenderingContext2D::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto);
}
void CanvasRenderingContext2D::GetContextAttributes(
CanvasRenderingContext2DSettings& aSettings) const {
aSettings = CanvasRenderingContext2DSettings();
aSettings.mAlpha = mContextAttributesHasAlpha;
aSettings.mWillReadFrequently = mWillReadFrequently;
aSettings.mForceSoftwareRendering = mForceSoftwareRendering;
// We don't support the 'desynchronized' and 'colorSpace' attributes, so
// those just keep their default values.
}
void CanvasRenderingContext2D::GetDebugInfo(
bool aEnsureTarget, CanvasRenderingContext2DDebugInfo& aDebugInfo,
ErrorResult& aError) {
if (aEnsureTarget && !EnsureTarget(aError)) {
return;
}
if (!mBufferProvider) {
aError.ThrowInvalidStateError("No buffer provider available");
return;
}
if (!mTarget) {
aError.ThrowInvalidStateError("No target available");
return;
}
aDebugInfo.mIsAccelerated = mBufferProvider->IsAccelerated();
aDebugInfo.mIsShared = mBufferProvider->IsShared();
aDebugInfo.mBackendType = static_cast<int8_t>(mTarget->GetBackendType());
aDebugInfo.mDrawTargetType = static_cast<int8_t>(mTarget->GetType());
}
CanvasRenderingContext2D::ColorStyleCacheEntry
CanvasRenderingContext2D::ParseColorSlow(const nsACString& aString) {
ColorStyleCacheEntry result{nsCString(aString)};
Document* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr;
css::Loader* loader = document ? document->CSSLoader() : nullptr;
PresShell* presShell = GetPresShell();
ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr;
bool wasCurrentColor = false;
nscolor color;
if (ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, &color,
&wasCurrentColor, loader)) {
result.mWasCurrentColor = wasCurrentColor;
result.mColor.emplace(color);
}
return result;
}
Maybe<nscolor> CanvasRenderingContext2D::ParseColor(
const nsACString& aString, ResolveCurrentColor aResolveCurrentColor) {
auto entry = mColorStyleCache.Lookup(aString);
if (!entry) {
entry.Set(ParseColorSlow(aString));
}
const auto& data = entry.Data();
if (data.mWasCurrentColor && mCanvasElement &&
aResolveCurrentColor == ResolveCurrentColor::Yes) {
// If it was currentColor, get the value of the color property, flushing
// style if necessary.
RefPtr<const ComputedStyle> canvasStyle =
nsComputedDOMStyle::GetComputedStyle(mCanvasElement);
if (canvasStyle) {
return Some(canvasStyle->StyleText()->mColor.ToColor());
}
}
return data.mColor;
}
void CanvasRenderingContext2D::ResetBitmap(bool aFreeBuffer) {
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;
if (aFreeBuffer) {
mBufferProvider = nullptr;
} else if (mBufferProvider) {
// Try to keep the buffer around. However, we still need to clear the
// contents as if it was recreated before next use.
mBufferNeedsClear = true;
}
// Since the target changes the backing texture will change, and this will
// no longer be valid.
mIsEntireFrameInvalid = false;
mPredictManyRedrawCalls = false;
mFrameCaptureState = FrameCaptureState::CLEAN;
}
void CanvasRenderingContext2D::OnShutdown() {
RefPtr<PersistentBufferProvider> provider = mBufferProvider;
ResetBitmap();
if (provider) {
provider->OnShutdown();
}
if (mOffscreenCanvas) {
mOffscreenCanvas->Destroy();
}
mHasShutdown = true;
}
bool CanvasRenderingContext2D::AddShutdownObserver() {
auto* const canvasManager = CanvasShutdownManager::Get();
if (NS_WARN_IF(!canvasManager)) {
mHasShutdown = true;
return false;
}
canvasManager->AddShutdownObserver(this);
return true;
}
void CanvasRenderingContext2D::RemoveShutdownObserver() {
auto* const canvasManager = CanvasShutdownManager::MaybeGet();
if (!canvasManager) {
return;
}
canvasManager->RemoveShutdownObserver(this);
}
void CanvasRenderingContext2D::OnRemoteCanvasLost() {
// We only lose context / data if we are using remote canvas, which is only
// for accelerated targets.
if (!mBufferProvider || !mBufferProvider->IsAccelerated() || mIsContextLost) {
return;
}
// 2. Set context's context lost to true.
mIsContextLost = mAllowContextRestore = true;
// 3. Reset the rendering context to its default state given context.
ClearTarget();
// We dispatch because it isn't safe to call into the script event handlers,
// and we don't want to mutate our state in CanvasShutdownManager.
NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction(
"CanvasRenderingContext2D::OnRemoteCanvasLost", [self = RefPtr{this}] {
// 4. Let shouldRestore be the result of firing an event named
// contextlost at canvas, with the cancelable attribute initialized to
// true.
self->mAllowContextRestore = self->DispatchEvent(
u"contextlost"_ns, CanBubble::eNo, Cancelable::eYes);
}));
}
void CanvasRenderingContext2D::OnRemoteCanvasRestored() {
// We never lost our context if it was not a remote canvas, nor can we restore
// if we have already shutdown.
if (mHasShutdown || !mIsContextLost || !mAllowContextRestore) {
return;
}
// We dispatch because it isn't safe to call into the script event handlers,
// and we don't want to mutate our state in CanvasShutdownManager.
NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction(
"CanvasRenderingContext2D::OnRemoteCanvasRestored",
[self = RefPtr{this}] {
// 5. If shouldRestore is false, then abort these steps.
if (!self->mHasShutdown && self->mIsContextLost &&
self->mAllowContextRestore) {
// 7. Set context's context lost to false.
self->mIsContextLost = false;
// 6. Attempt to restore context by creating a backing storage using
// context's attributes and associating them with context. If this
// fails, then abort these steps.
if (!self->EnsureTarget()) {
self->mIsContextLost = true;
return;
}
// 8. Fire an event named contextrestored at canvas.
self->DispatchEvent(u"contextrestored"_ns, CanBubble::eNo,
Cancelable::eNo);
}
}));
}
void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
Style aWhichStyle) {
MOZ_ASSERT(!aStr.IsVoid());
Maybe<nscolor> color = ParseColor(aStr);
if (!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() {
mFrameCaptureState = FrameCaptureState::DIRTY;
if (mIsEntireFrameInvalid) {
return NS_OK;
}
mIsEntireFrameInvalid = true;
if (mCanvasElement) {
SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
mCanvasElement->InvalidateCanvasContent(nullptr);
} else if (mOffscreenCanvas) {
mOffscreenCanvas->QueueCommitToCompositor();
} else {
NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
}
return NS_OK;
}
void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
mFrameCaptureState = FrameCaptureState::DIRTY;
++mInvalidateCount;
if (mIsEntireFrameInvalid) {
return;
}
if (mPredictManyRedrawCalls || mInvalidateCount > kCanvasMaxInvalidateCount) {
Redraw();
return;
}
if (mCanvasElement) {
SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
mCanvasElement->InvalidateCanvasContent(&aR);
} else if (mOffscreenCanvas) {
mOffscreenCanvas->QueueCommitToCompositor();
} else {
NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
}
}
void CanvasRenderingContext2D::DidRefresh() {}
void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) {
mFrameCaptureState = FrameCaptureState::DIRTY;
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) {
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::BorrowTarget(const IntRect& aPersistedRect,
bool aNeedsClear) {
// We are attempting to request a DrawTarget from the current
// PersistentBufferProvider. However, if the provider needs to be refreshed,
// or if it is accelerated and the application has requested that we disallow
// acceleration, then we skip trying to use this provider so that it will be
// recreated by EnsureTarget later.
if (!mBufferProvider || mBufferProvider->RequiresRefresh() ||
(mBufferProvider->IsAccelerated() && UseSoftwareRendering())) {
return false;
}
mTarget = mBufferProvider->BorrowDrawTarget(aPersistedRect);
if (!mTarget || !mTarget->IsValid()) {
if (mTarget) {
mBufferProvider->ReturnDrawTarget(mTarget.forget());
}
return false;
}
if (mBufferNeedsClear) {
if (mBufferProvider->PreservesDrawingState()) {
// If the buffer provider preserves the clip and transform state, then
// we must ensure it is cleared before reusing the target.
if (!mTarget->RemoveAllClips()) {
mBufferProvider->ReturnDrawTarget(mTarget.forget());
return false;
}
mTarget->SetTransform(Matrix());
}
// If the canvas was reset, then we need to clear the target in case its
// contents was somehow preserved. We only need to clear the target if
// the operation doesn't fill the entire canvas.
if (aNeedsClear) {
mTarget->ClearRect(gfx::Rect(mTarget->GetRect()));
}
}
if (!mBufferProvider->PreservesDrawingState() || mBufferNeedsClear) {
RestoreClipsAndTransformToTarget();
}
mBufferNeedsClear = false;
return true;
}
bool CanvasRenderingContext2D::EnsureTarget(ErrorResult& aError,
const gfx::Rect* aCoveredRect,
bool aWillClear,
bool aSkipTransform) {
if (AlreadyShutDown()) {
gfxCriticalNoteOnce << "Attempt to render into a Canvas2d after shutdown.";
SetErrorState();
aError.ThrowInvalidStateError(
"Cannot use canvas after shutdown initiated.");
return false;
}
// The spec doesn't say what to do in this case, but Chrome silently fails
// without throwing an error. We should at least throw if the canvas is
// permanently disabled.
if (NS_WARN_IF(mIsContextLost)) {
if (!mAllowContextRestore) {
aError.ThrowInvalidStateError(
"Cannot use canvas as context is lost forever.");
}
return false;
}
if (mTarget) {
if (mTarget == sErrorTarget.get()) {
aError.ThrowInvalidStateError("Canvas is already in error state.");
return false;
}
return true;
}
// Check that the dimensions are sane
if (mWidth > StaticPrefs::gfx_canvas_max_size() ||
mHeight > StaticPrefs::gfx_canvas_max_size()) {
SetErrorState();
aError.ThrowInvalidStateError("Canvas exceeds max size.");
return false;
}
if (mWidth < 0 || mHeight < 0) {
SetErrorState();
aError.ThrowInvalidStateError("Canvas has invalid size.");
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 &&
(aSkipTransform ? *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 || mBufferNeedsClear
? IntRect()
: IntRect(0, 0, mWidth, mHeight);
// Attempt to reuse the existing buffer provider.
if (BorrowTarget(persistedRect, !canDiscardContent)) {
return true;
}
RefPtr<DrawTarget> newTarget;
RefPtr<PersistentBufferProvider> newProvider;
if (!TryAcceleratedTarget(newTarget, newProvider) &&
!TrySharedTarget(newTarget, newProvider) &&
!TryBasicTarget(newTarget, newProvider, aError)) {
gfxCriticalError(
CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())))
<< "Failed borrow shared and basic targets.";
SetErrorState();
return false;
}
MOZ_ASSERT(newTarget);
MOZ_ASSERT(newProvider);
bool needsClear =
!canDiscardContent || (mBufferProvider && mBufferNeedsClear);
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 && !mBufferNeedsClear &&
CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) {
needsClear = false;
}
if (needsClear) {
newTarget->ClearRect(canvasRect);
}
// Ensure any Path state is compatible with the type of DrawTarget used. This
// may require making a copy with the correct type if they (rarely) mismatch.
if (mPathBuilder &&
mPathBuilder->GetBackendType() != newTarget->GetBackendType()) {
RefPtr<Path> path = mPathBuilder->Finish();
mPathBuilder = newTarget->CreatePathBuilder(path->GetFillRule());
path->StreamToSink(mPathBuilder);
}
if (mPath && mPath->GetBackendType() != newTarget->GetBackendType()) {
RefPtr<PathBuilder> builder =
newTarget->CreatePathBuilder(mPath->GetFillRule());
mPath->StreamToSink(builder);
mPath = builder->Finish();
}
mTarget = std::move(newTarget);
mBufferProvider = std::move(newProvider);
mBufferNeedsClear = false;
RegisterAllocation();
AddZoneWaitingForGC();
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 mFrameCaptureState.
FrameCaptureState captureState = mFrameCaptureState;
// Calling Redraw() tells our invalidation machinery that the entire
// canvas is already invalid, which can speed up future drawing.
Redraw();
mFrameCaptureState = captureState;
return true;
}
void CanvasRenderingContext2D::SetInitialState() {
// Set up the initial canvas defaults
mPathBuilder = nullptr;
mPath = nullptr;
mPathPruned = false;
mPathTransform = Matrix();
mPathTransformDirty = 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.get()) {
gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
}
mTarget = sErrorTarget.get();
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());
}
}
void CanvasRenderingContext2D::AddZoneWaitingForGC() {
JSObject* wrapper = GetWrapperPreserveColor();
if (wrapper) {
CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(
JS::GetObjectZone(wrapper));
}
}
static WindowRenderer* WindowRendererFromCanvasElement(
nsINode* aCanvasElement) {
if (!aCanvasElement) {
return nullptr;
}
return nsContentUtils::WindowRendererForDocument(aCanvasElement->OwnerDoc());
}
bool CanvasRenderingContext2D::TryAcceleratedTarget(
RefPtr<gfx::DrawTarget>& aOutDT,
RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
if (!XRE_IsContentProcess()) {
// Only allow accelerated contexts to be created in a content process to
// ensure it is remoted appropriately and run on the correct parent or
// GPU process threads.
return false;
}
if (mBufferProvider && mBufferProvider->IsAccelerated() &&
mBufferProvider->RequiresRefresh()) {
// If there is already a provider and we got here, then the provider needs
// to be refreshed and we should avoid using acceleration in the future.
mAllowAcceleration = false;
}
// Don't try creating an accelerate DrawTarget if either acceleration failed
// previously or if the application expects acceleration to be slow.
if (!mAllowAcceleration || UseSoftwareRendering()) {
return false;
}
if (mCanvasElement) {
MOZ_ASSERT(NS_IsMainThread());
WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
if (NS_WARN_IF(!renderer)) {
return false;
}
aOutProvider = PersistentBufferProviderAccelerated::Create(
GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor());
} else if (mOffscreenCanvas &&
StaticPrefs::gfx_canvas_remote_allow_offscreen()) {
RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton();
if (NS_WARN_IF(!imageBridge)) {
return false;
}
aOutProvider = PersistentBufferProviderAccelerated::Create(
GetSize(), GetSurfaceFormat(), imageBridge);
}
if (!aOutProvider) {
return false;
}
aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
MOZ_ASSERT(aOutDT);
return !!aOutDT;
}
bool CanvasRenderingContext2D::TrySharedTarget(
RefPtr<gfx::DrawTarget>& aOutDT,
RefPtr<layers::PersistentBufferProvider>& aOutProvider) {
aOutDT = nullptr;
aOutProvider = nullptr;
if (mBufferProvider && mBufferProvider->IsShared()) {
// 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;
}
if (mCanvasElement) {
MOZ_ASSERT(NS_IsMainThread());
WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement);
if (NS_WARN_IF(!renderer)) {
return false;
}
aOutProvider = renderer->CreatePersistentBufferProvider(
GetSize(), GetSurfaceFormat(),
!mAllowAcceleration || UseSoftwareRendering());
} else if (mOffscreenCanvas) {
if (!StaticPrefs::gfx_offscreencanvas_shared_provider()) {
return false;
}
RefPtr<layers::ImageBridgeChild> imageBridge =
layers::ImageBridgeChild::GetSingleton();
if (NS_WARN_IF(!imageBridge)) {
return false;
}
aOutProvider = PersistentBufferProviderShared::Create(
GetSize(), GetSurfaceFormat(), imageBridge,
!mAllowAcceleration || UseSoftwareRendering(),
mOffscreenCanvas->GetWindowID());
}
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,
ErrorResult& aError) {
aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
GetSize(), GetSurfaceFormat(), UseSoftwareRendering());
if (!aOutDT) {
aError.ThrowInvalidStateError("Canvas could not create basic draw target.");
return false;
}
// See Bug 1524554 - this forces DT initialization.
aOutDT->ClearRect(gfx::Rect());
if (!aOutDT->IsValid()) {
aOutDT = nullptr;
aError.ThrowInvalidStateError("Canvas could not init basic draw target.");
return false;
}
aOutProvider = new PersistentBufferProviderBasic(aOutDT);
return true;
}
PersistentBufferProvider* CanvasRenderingContext2D::GetBufferProvider() {
if (mBufferProvider && mBufferNeedsClear) {
// Force the buffer to clear before it is used.
EnsureTarget();
}
return mBufferProvider;
}
Maybe<SurfaceDescriptor> CanvasRenderingContext2D::GetFrontBuffer(
WebGLFramebufferJS*, const bool webvr) {
if (auto* provider = GetBufferProvider()) {
return provider->GetFrontBuffer();
}
return Nothing();
}
already_AddRefed<layers::FwdTransactionTracker>
CanvasRenderingContext2D::UseCompositableForwarder(
layers::CompositableForwarder* aForwarder) {
if (mBufferProvider) {
return mBufferProvider->UseCompositableForwarder(aForwarder);
}
return nullptr;
}
PresShell* CanvasRenderingContext2D::GetPresShell() {
if (mCanvasElement) {
return mCanvasElement->OwnerDoc()->GetPresShell();
}
if (mDocShell) {
return mDocShell->GetPresShell();
}
return nullptr;
}
NS_IMETHODIMP
CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) {
// Zero sized surfaces can cause problems.
mZero = false;
if (aHeight == 0) {
aHeight = 1;
mZero = true;
}
if (aWidth == 0) {
aWidth = 1;
mZero = true;
}
ClearTarget(aWidth, aHeight);
return NS_OK;
}
void CanvasRenderingContext2D::AddAssociatedMemory() {
JSObject* wrapper = GetWrapperMaybeDead();
if (wrapper) {
JS::AddAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
JS::MemoryUse::DOMBinding);
}
}
void CanvasRenderingContext2D::RemoveAssociatedMemory() {
JSObject* wrapper = GetWrapperMaybeDead();
if (wrapper) {
JS::RemoveAssociatedMemory(wrapper, BindingJSObjectMallocBytes(this),
JS::MemoryUse::DOMBinding);
}
}
void CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) {
// Only free the buffer provider if the size no longer matches.
bool freeBuffer = aWidth != mWidth || aHeight != mHeight;
ResetBitmap(freeBuffer);
mResetLayer = true;
SetInitialState();
// Update dimensions only if new (strictly positive) values were passed.
if (aWidth > 0 && aHeight > 0) {
// Update the memory size associated with the wrapper object when we change
// the dimensions. Note that we need to keep updating dying wrappers before
--> --------------------
--> maximum size reached
--> --------------------