diff --git a/content/canvas/src/CanvasRenderingContext2D.cpp b/content/canvas/src/CanvasRenderingContext2D.cpp index 2ce29c01f3c..b950bf3b064 100644 --- a/content/canvas/src/CanvasRenderingContext2D.cpp +++ b/content/canvas/src/CanvasRenderingContext2D.cpp @@ -1,3926 +1,3926 @@ -/* -*- 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 "base/basictypes.h" -#include "CanvasRenderingContext2D.h" - -#include "nsIDOMXULElement.h" - -#include "prenv.h" - -#include "nsIServiceManager.h" -#include "nsMathUtils.h" - -#include "nsContentUtils.h" - -#include "nsIDocument.h" -#include "nsHTMLCanvasElement.h" -#include "nsSVGEffects.h" -#include "nsPresContext.h" -#include "nsIPresShell.h" -#include "nsIVariant.h" - -#include "nsIInterfaceRequestorUtils.h" -#include "nsIFrame.h" -#include "nsError.h" -#include "nsIScriptError.h" - -#include "nsCSSParser.h" -#include "mozilla/css/StyleRule.h" -#include "mozilla/css/Declaration.h" -#include "nsComputedDOMStyle.h" -#include "nsStyleSet.h" - -#include "nsPrintfCString.h" - -#include "nsReadableUtils.h" - -#include "nsColor.h" -#include "nsGfxCIID.h" -#include "nsIScriptSecurityManager.h" -#include "nsIDocShell.h" -#include "nsIDOMWindow.h" -#include "nsPIDOMWindow.h" -#include "nsIDocShellTreeItem.h" -#include "nsIDocShellTreeNode.h" -#include "nsIXPConnect.h" -#include "nsDisplayList.h" - -#include "nsTArray.h" - -#include "imgIEncoder.h" - -#include "gfxContext.h" -#include "gfxASurface.h" -#include "gfxImageSurface.h" -#include "gfxPlatform.h" -#include "gfxFont.h" -#include "gfxBlur.h" -#include "gfxUtils.h" -#include "gfxFontMissingGlyphs.h" - -#include "nsFrameManager.h" -#include "nsFrameLoader.h" -#include "nsBidi.h" -#include "nsBidiPresUtils.h" -#include "Layers.h" -#include "CanvasUtils.h" -#include "nsIMemoryReporter.h" -#include "nsStyleUtil.h" -#include "CanvasImageCache.h" - -#include - -#include "jsapi.h" -#include "jsfriendapi.h" - -#include "mozilla/Assertions.h" -#include "mozilla/CheckedInt.h" -#include "mozilla/dom/ContentParent.h" -#include "mozilla/dom/ImageData.h" -#include "mozilla/dom/PBrowserParent.h" -#include "mozilla/dom/TypedArray.h" -#include "mozilla/gfx/2D.h" -#include "mozilla/gfx/PathHelpers.h" -#include "mozilla/ipc/DocumentRendererParent.h" -#include "mozilla/ipc/PDocumentRendererParent.h" -#include "mozilla/Preferences.h" -#include "mozilla/Telemetry.h" -#include "mozilla/unused.h" -#include "nsCCUncollectableMarker.h" -#include "nsWrapperCacheInlines.h" -#include "nsJSUtils.h" -#include "XPCQuickStubs.h" -#include "mozilla/dom/BindingUtils.h" -#include "nsHTMLImageElement.h" -#include "nsHTMLVideoElement.h" -#include "mozilla/dom/CanvasRenderingContext2DBinding.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::ipc; -using namespace mozilla::layers; - -namespace mgfx = mozilla::gfx; - -#define NS_TEXTMETRICSAZURE_PRIVATE_IID \ - {0x9793f9e7, 0x9dc1, 0x4e9c, {0x81, 0xc8, 0xfc, 0xa7, 0x14, 0xf4, 0x30, 0x79}} - -namespace mozilla { -namespace dom { - -static float kDefaultFontSize = 10.0; -static NS_NAMED_LITERAL_STRING(kDefaultFontName, "sans-serif"); -static NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); - -// Cap sigma to avoid overly large temp surfaces. -const Float SIGMA_MAX = 100; - -/* Memory reporter stuff */ -static nsIMemoryReporter *gCanvasAzureMemoryReporter = nullptr; -static int64_t gCanvasAzureMemoryUsed = 0; - -static int64_t GetCanvasAzureMemoryUsed() { - return gCanvasAzureMemoryUsed; -} - -// 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. -NS_MEMORY_REPORTER_IMPLEMENT(CanvasAzureMemory, - "canvas-2d-pixel-bytes", - KIND_OTHER, - UNITS_BYTES, - GetCanvasAzureMemoryUsed, - "Memory used by 2D canvases. Each canvas requires (width * height * 4) " - "bytes.") - -class CanvasRadialGradient : public CanvasGradient -{ -public: - CanvasRadialGradient(const Point &aBeginOrigin, Float aBeginRadius, - const Point &aEndOrigin, Float aEndRadius) - : CanvasGradient(RADIAL) - , mCenter1(aBeginOrigin) - , mCenter2(aEndOrigin) - , mRadius1(aBeginRadius) - , mRadius2(aEndRadius) - { - } - - Point mCenter1; - Point mCenter2; - Float mRadius1; - Float mRadius2; -}; - -class CanvasLinearGradient : public CanvasGradient -{ -public: - CanvasLinearGradient(const Point &aBegin, const Point &aEnd) - : CanvasGradient(LINEAR) - , mBegin(aBegin) - , mEnd(aEnd) - { - } - -protected: - friend class CanvasGeneralPattern; - - // Beginning of linear gradient. - Point mBegin; - // End of linear gradient. - Point mEnd; -}; - -// 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; - - CanvasGeneralPattern() : mPattern(nullptr) {} - ~CanvasGeneralPattern() - { - if (mPattern) { - mPattern->~Pattern(); - } - } - - 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, "ForStyle() should only be called once on CanvasGeneralPattern!"); - - const ContextState &state = aCtx->CurrentState(); - - if (state.StyleIsColor(aStyle)) { - mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle])); - } else if (state.gradientStyles[aStyle] && - state.gradientStyles[aStyle]->GetType() == CanvasGradient::LINEAR) { - CanvasLinearGradient *gradient = - static_cast(state.gradientStyles[aStyle].get()); - - mPattern = new (mLinearGradientPattern.addr()) - LinearGradientPattern(gradient->mBegin, gradient->mEnd, - gradient->GetGradientStopsForTarget(aRT)); - } else if (state.gradientStyles[aStyle] && - state.gradientStyles[aStyle]->GetType() == CanvasGradient::RADIAL) { - CanvasRadialGradient *gradient = - static_cast(state.gradientStyles[aStyle].get()); - - mPattern = new (mRadialGradientPattern.addr()) - RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, - gradient->mRadius2, 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::NOREPEAT) { - mode = EXTEND_CLAMP; - } else { - mode = EXTEND_REPEAT; - } - mPattern = new (mSurfacePattern.addr()) - SurfacePattern(state.patternStyles[aStyle]->mSurface, mode); - } - - return *mPattern; - } - - union { - AlignedStorage2 mColorPattern; - AlignedStorage2 mLinearGradientPattern; - AlignedStorage2 mRadialGradientPattern; - AlignedStorage2 mSurfacePattern; - }; - Pattern *mPattern; -}; - -/* This is an RAII based class that can be used as a drawtarget for - * operations that need a shadow drawn. It will automatically provide a - * temporary target when needed, and if so blend it back with a shadow. - * - * aBounds specifies the bounds of the drawing operation that will be - * drawn to the target, it is given in device space! This function will - * change aBounds to incorporate shadow bounds. If this is NULL the drawing - * operation will be assumed to cover an infinite rect. - */ -class AdjustedTarget -{ -public: - typedef CanvasRenderingContext2D::ContextState ContextState; - - AdjustedTarget(CanvasRenderingContext2D *ctx, - mgfx::Rect *aBounds = nullptr) - : mCtx(nullptr) - { - if (!ctx->NeedToDrawShadow()) { - mTarget = ctx->mTarget; - return; - } - mCtx = ctx; - - const ContextState &state = mCtx->CurrentState(); - - mSigma = state.shadowBlur / 2.0f; - - if (mSigma > SIGMA_MAX) { - mSigma = SIGMA_MAX; - } - - Matrix transform = mCtx->mTarget->GetTransform(); - - mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight); - - static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; - int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5); - - // We need to enlarge and possibly offset our temporary surface - // so that things outside of the canvas may cast shadows. - mTempRect.Inflate(Margin(blurRadius + NS_MAX(state.shadowOffset.x, 0), - blurRadius + NS_MAX(state.shadowOffset.y, 0), - blurRadius + NS_MAX(-state.shadowOffset.x, 0), - blurRadius + NS_MAX(-state.shadowOffset.y, 0))); - - if (aBounds) { - // 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. - aBounds->Inflate(Margin(blurRadius, blurRadius, - blurRadius, blurRadius)); - mTempRect = mTempRect.Intersect(*aBounds); - } - - mTempRect.ScaleRoundOut(1.0f); - - transform._31 -= mTempRect.x; - transform._32 -= mTempRect.y; - - mTarget = - mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)), - FORMAT_B8G8R8A8, mSigma); - - if (!mTarget) { - // XXX - Deal with the situation where our temp size is too big to - // fit in a texture. - mTarget = ctx->mTarget; - mCtx = nullptr; - } else { - mTarget->SetTransform(transform); - } - } - - ~AdjustedTarget() - { - if (!mCtx) { - return; - } - - RefPtr snapshot = mTarget->Snapshot(); - - mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), - Color::FromABGR(mCtx->CurrentState().shadowColor), - mCtx->CurrentState().shadowOffset, mSigma, - mCtx->CurrentState().op); - } - - DrawTarget* operator->() - { - return mTarget; - } - -private: - RefPtr mTarget; - CanvasRenderingContext2D *mCtx; - Float mSigma; - mgfx::Rect mTempRect; -}; - -NS_IMETHODIMP -CanvasGradient::AddColorStop(float offset, const nsAString& colorstr) -{ - if (!FloatValidate(offset) || offset < 0.0 || offset > 1.0) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - nsCSSValue value; - nsCSSParser parser; - if (!parser.ParseColorString(colorstr, nullptr, 0, value)) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - nscolor color; - if (!nsRuleNode::ComputeColor(value, nullptr, nullptr, color)) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - mStops = nullptr; - - GradientStop newStop; - - newStop.offset = offset; - newStop.color = Color::FromABGR(color); - - mRawStops.AppendElement(newStop); - - return NS_OK; -} - -NS_DEFINE_STATIC_IID_ACCESSOR(CanvasGradient, NS_CANVASGRADIENTAZURE_PRIVATE_IID) - -NS_IMPL_ADDREF(CanvasGradient) -NS_IMPL_RELEASE(CanvasGradient) - -NS_INTERFACE_MAP_BEGIN(CanvasGradient) - NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasGradient) - NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasGradient) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasGradient) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -NS_DEFINE_STATIC_IID_ACCESSOR(CanvasPattern, NS_CANVASPATTERNAZURE_PRIVATE_IID) - -NS_IMPL_ADDREF(CanvasPattern) -NS_IMPL_RELEASE(CanvasPattern) - -NS_INTERFACE_MAP_BEGIN(CanvasPattern) - NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasPattern) - NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasPattern) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasPattern) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -/** - ** TextMetrics - **/ -class TextMetrics : public nsIDOMTextMetrics -{ -public: - TextMetrics(float w) : width(w) { } - - virtual ~TextMetrics() { } - - NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXTMETRICSAZURE_PRIVATE_IID) - - NS_IMETHOD GetWidth(float* w) - { - *w = width; - return NS_OK; - } - - NS_DECL_ISUPPORTS - -private: - float width; -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(TextMetrics, NS_TEXTMETRICSAZURE_PRIVATE_IID) - -NS_IMPL_ADDREF(TextMetrics) -NS_IMPL_RELEASE(TextMetrics) - -NS_INTERFACE_MAP_BEGIN(TextMetrics) - NS_INTERFACE_MAP_ENTRY(mozilla::dom::TextMetrics) - NS_INTERFACE_MAP_ENTRY(nsIDOMTextMetrics) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TextMetrics) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -class CanvasRenderingContext2DUserData : public LayerUserData { -public: - CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext) - : mContext(aContext) - { - aContext->mUserDatas.AppendElement(this); - } - ~CanvasRenderingContext2DUserData() - { - if (mContext) { - mContext->mUserDatas.RemoveElement(this); - } - } - static void DidTransactionCallback(void* aData) - { - CanvasRenderingContext2DUserData* self = - static_cast(aData); - if (self->mContext) { - self->mContext->MarkContextClean(); - } - } - bool IsForContext(CanvasRenderingContext2D *aContext) - { - return mContext == aContext; - } - void Forget() - { - mContext = nullptr; - } - -private: - CanvasRenderingContext2D *mContext; -}; - -NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) -NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasRenderingContext2D, mCanvasElement) - -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) - if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { - 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->IsBlack(); -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END - -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) - return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); -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 impl - **/ - - -// Initialize our static variables. -uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; -uint8_t (*CanvasRenderingContext2D::sUnpremultiplyTable)[256] = nullptr; -uint8_t (*CanvasRenderingContext2D::sPremultiplyTable)[256] = nullptr; -DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; - - - -CanvasRenderingContext2D::CanvasRenderingContext2D() - : mZero(false), mOpaque(false), mResetLayer(true) - , mIPC(false) - , mIsEntireFrameInvalid(false) - , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) - , mInvalidateCount(0) -{ - sNumLivingContexts++; - SetIsDOMBinding(); -} - -CanvasRenderingContext2D::~CanvasRenderingContext2D() -{ - Reset(); - // Drop references from all CanvasRenderingContext2DUserData to this context - for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { - mUserDatas[i]->Forget(); - } - sNumLivingContexts--; - if (!sNumLivingContexts) { - delete[] sUnpremultiplyTable; - delete[] sPremultiplyTable; - sUnpremultiplyTable = nullptr; - sPremultiplyTable = nullptr; - NS_IF_RELEASE(sErrorTarget); - } -} - -JSObject* -CanvasRenderingContext2D::WrapObject(JSContext *cx, JSObject *scope, - bool *triedToWrap) -{ - return CanvasRenderingContext2DBinding::Wrap(cx, scope, this, triedToWrap); -} - -bool -CanvasRenderingContext2D::ParseColor(const nsAString& aString, - nscolor* aColor) -{ - nsIDocument* document = mCanvasElement - ? mCanvasElement->OwnerDoc() - : nullptr; - - // Pass the CSS Loader object to the parser, to allow parser error - // reports to include the outer window ID. - nsCSSParser parser(document ? document->CSSLoader() : nullptr); - nsCSSValue value; - if (!parser.ParseColorString(aString, nullptr, 0, value)) { - return false; - } - - if (value.GetUnit() == nsCSSUnit::eCSSUnit_Color) { - // if we already have a color we can just use it directly - *aColor = value.GetColorValue(); - } else { - // otherwise resolve it - nsIPresShell* presShell = GetPresShell(); - nsRefPtr parentContext; - if (mCanvasElement && mCanvasElement->IsInDoc()) { - // Inherit from the canvas element. - parentContext = nsComputedDOMStyle::GetStyleContextForElement( - mCanvasElement, nullptr, presShell); - } - - unused << nsRuleNode::ComputeColor( - value, presShell ? presShell->GetPresContext() : nullptr, parentContext, - *aColor); - } - 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; - } - - mTarget = nullptr; - - // Since the target changes the backing texture will change, and this will - // no longer be valid. - mThebesSurface = nullptr; - mIsEntireFrameInvalid = false; - mPredictManyRedrawCalls = false; - - return NS_OK; -} - -static void -WarnAboutUnexpectedStyle(nsHTMLCanvasElement* canvasElement) -{ - nsContentUtils::ReportToConsole( - nsIScriptError::warningFlag, - "Canvas", - canvasElement ? canvasElement->OwnerDoc() : nullptr, - nsContentUtils::eDOM_PROPERTIES, - "UnexpectedCanvasVariantStyle"); -} - -void -CanvasRenderingContext2D::SetStyleFromString(const nsAString& str, - Style whichStyle) -{ - MOZ_ASSERT(!str.IsVoid()); - - nscolor color; - if (!ParseColor(str, &color)) { - return; - } - - CurrentState().SetColorStyle(whichStyle, color); -} - -nsISupports* -CanvasRenderingContext2D::GetStyleAsStringOrInterface(nsAString& aStr, - CanvasMultiGetterType& aType, - Style aWhichStyle) -{ - const ContextState &state = CurrentState(); - nsISupports* supports; - if (state.patternStyles[aWhichStyle]) { - aStr.SetIsVoid(true); - supports = state.patternStyles[aWhichStyle]; - aType = CMG_STYLE_PATTERN; - } else if (state.gradientStyles[aWhichStyle]) { - aStr.SetIsVoid(true); - supports = state.gradientStyles[aWhichStyle]; - aType = CMG_STYLE_GRADIENT; - } else { - StyleColorToString(state.colorStyles[aWhichStyle], aStr); - supports = nullptr; - aType = CMG_STYLE_STRING; - } - return supports; -} - -// static -void -CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) -{ - // 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) { - CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", - NS_GET_R(aColor), - NS_GET_G(aColor), - NS_GET_B(aColor)), - aStr); - } else { - CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", - NS_GET_R(aColor), - NS_GET_G(aColor), - NS_GET_B(aColor)), - aStr); - aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); - aStr.Append(')'); - } -} - -nsresult -CanvasRenderingContext2D::Redraw() -{ - if (mIsEntireFrameInvalid) { - return NS_OK; - } - - mIsEntireFrameInvalid = true; - - if (!mCanvasElement) { - NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); - return NS_OK; - } - - if (!mThebesSurface) - mThebesSurface = - gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); - mThebesSurface->MarkDirty(); - - nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); - - mCanvasElement->InvalidateCanvasContent(nullptr); - - return NS_OK; -} - -void -CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) -{ - ++mInvalidateCount; - - if (mIsEntireFrameInvalid) { - return; - } - - if (mPredictManyRedrawCalls || - mInvalidateCount > kCanvasMaxInvalidateCount) { - Redraw(); - return; - } - - if (!mCanvasElement) { - NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); - return; - } - - if (!mThebesSurface) - mThebesSurface = - gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); - mThebesSurface->MarkDirty(); - - nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); - - mCanvasElement->InvalidateCanvasContent(&r); -} - -void -CanvasRenderingContext2D::RedrawUser(const gfxRect& r) -{ - if (mIsEntireFrameInvalid) { - ++mInvalidateCount; - return; - } - - mgfx::Rect newr = - mTarget->GetTransform().TransformBounds(ToRect(r)); - Redraw(newr); -} - -void -CanvasRenderingContext2D::EnsureTarget() -{ - if (mTarget) { - return; - } - - // Check that the dimensions are sane - IntSize size(mWidth, mHeight); - if (size.width <= 0xFFFF && size.height <= 0xFFFF && - size.width >= 0 && size.height >= 0) { - SurfaceFormat format = GetSurfaceFormat(); - nsIDocument* ownerDoc = nullptr; - if (mCanvasElement) { - ownerDoc = mCanvasElement->OwnerDoc(); - } - - nsRefPtr layerManager = nullptr; - - if (ownerDoc) { - layerManager = - nsContentUtils::PersistentLayerManagerForDocument(ownerDoc); - } - - if (layerManager) { - mTarget = layerManager->CreateDrawTarget(size, format); - } else { - mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format); - } - } - - if (mTarget) { - if (gCanvasAzureMemoryReporter == nullptr) { - gCanvasAzureMemoryReporter = new NS_MEMORY_REPORTER_NAME(CanvasAzureMemory); - NS_RegisterMemoryReporter(gCanvasAzureMemoryReporter); - } - - gCanvasAzureMemoryUsed += mWidth * mHeight * 4; - JSContext* context = nsContentUtils::GetCurrentJSContext(); - if (context) { - JS_updateMallocCounter(context, mWidth * mHeight * 4); - } - - mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight))); - // Force a full layer transaction since we didn't have a layer before - // and now we might need one. - if (mCanvasElement) { - mCanvasElement->InvalidateCanvas(); - } - // Calling Redraw() tells our invalidation machinery that the entire - // canvas is already invalid, which can speed up future drawing. - Redraw(); - } else { - EnsureErrorTarget(); - mTarget = sErrorTarget; - } -} - -NS_IMETHODIMP -CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height) -{ - ClearTarget(); - - // Zero sized surfaces cause issues, so just go with 1x1. - if (height == 0 || width == 0) { - mZero = true; - mWidth = 1; - mHeight = 1; - } else { - mZero = false; - mWidth = width; - mHeight = height; - } - - return NS_OK; -} - -void -CanvasRenderingContext2D::ClearTarget() -{ - Reset(); - - mResetLayer = true; - - // set up the initial canvas defaults - mStyleStack.Clear(); - mPathBuilder = nullptr; - mPath = nullptr; - mDSPathBuilder = nullptr; - - 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); -} - -NS_IMETHODIMP -CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell, - gfxASurface *surface, - int32_t width, - int32_t height) -{ - mDocShell = shell; - mThebesSurface = surface; - - SetDimensions(width, height); - mTarget = gfxPlatform::GetPlatform()-> - CreateDrawTargetForSurface(surface, IntSize(width, height)); - if (!mTarget) { - EnsureErrorTarget(); - mTarget = sErrorTarget; - } - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::SetIsOpaque(bool isOpaque) -{ - if (isOpaque != mOpaque) { - mOpaque = isOpaque; - ClearTarget(); - } - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::SetIsIPC(bool isIPC) -{ - if (isIPC != mIPC) { - mIPC = isIPC; - ClearTarget(); - } - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, uint32_t aFlags) -{ - nsresult rv = NS_OK; - - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } - - nsRefPtr surface; - - if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { - return NS_ERROR_FAILURE; - } - - nsRefPtr pat = new gfxPattern(surface); - - pat->SetFilter(aFilter); - pat->SetExtend(gfxPattern::EXTEND_PAD); - - gfxContext::GraphicsOperator op = ctx->CurrentOperator(); - if (mOpaque) - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - - // XXX I don't want to use PixelSnapped here, but layout doesn't guarantee - // pixel alignment for this stuff! - ctx->NewPath(); - ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat); - ctx->Fill(); - - if (mOpaque) - ctx->SetOperator(op); - - if (!(aFlags & RenderFlagPremultAlpha)) { - nsRefPtr curSurface = ctx->CurrentSurface(); - nsRefPtr gis = curSurface->GetAsImageSurface(); - NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!"); - - gfxUtils::UnpremultiplyImageSurface(gis); - } - - return rv; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::GetInputStream(const char *aMimeType, - const PRUnichar *aEncoderOptions, - nsIInputStream **aStream) -{ - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } - - nsRefPtr surface; - - if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { - return NS_ERROR_FAILURE; - } - - nsresult rv; - const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type="; - nsAutoArrayPtr conid(new (std::nothrow) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]); - - if (!conid) { - return NS_ERROR_OUT_OF_MEMORY; - } - - strcpy(conid, encoderPrefix); - strcat(conid, aMimeType); - - nsCOMPtr encoder = do_CreateInstance(conid); - if (!encoder) { - return NS_ERROR_FAILURE; - } - - nsAutoArrayPtr imageBuffer(new (std::nothrow) uint8_t[mWidth * mHeight * 4]); - if (!imageBuffer) { - return NS_ERROR_OUT_OF_MEMORY; - } - - nsRefPtr imgsurf = - new gfxImageSurface(imageBuffer.get(), - gfxIntSize(mWidth, mHeight), - mWidth * 4, - gfxASurface::ImageFormatARGB32); - - if (!imgsurf || imgsurf->CairoStatus()) { - return NS_ERROR_FAILURE; - } - - nsRefPtr ctx = new gfxContext(imgsurf); - - if (!ctx || ctx->HasError()) { - return NS_ERROR_FAILURE; - } - - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(surface, gfxPoint(0, 0)); - ctx->Paint(); - - rv = encoder->InitFromData(imageBuffer.get(), - mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4, - imgIEncoder::INPUT_FORMAT_HOSTARGB, - nsDependentString(aEncoderOptions)); - NS_ENSURE_SUCCESS(rv, rv); - - return CallQueryInterface(encoder, aStream); -} - -SurfaceFormat -CanvasRenderingContext2D::GetSurfaceFormat() const -{ - return mOpaque ? FORMAT_B8G8R8X8 : FORMAT_B8G8R8A8; -} - -// -// state -// - -void -CanvasRenderingContext2D::Save() -{ - EnsureTarget(); - mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); - mStyleStack.SetCapacity(mStyleStack.Length() + 1); - mStyleStack.AppendElement(CurrentState()); -} - -void -CanvasRenderingContext2D::Restore() -{ - if (mStyleStack.Length() - 1 == 0) - return; - - TransformWillUpdate(); - - for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { - mTarget->PopClip(); - } - - mStyleStack.RemoveElementAt(mStyleStack.Length() - 1); - - mTarget->SetTransform(CurrentState().transform); -} - -// -// transformations -// - -void -CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error) -{ - if (!FloatValidate(x,y)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newMatrix = mTarget->GetTransform(); - mTarget->SetTransform(newMatrix.Scale(x, y)); -} - -void -CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error) -{ - if (!FloatValidate(angle)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - - Matrix rotation = Matrix::Rotation(angle); - mTarget->SetTransform(rotation * mTarget->GetTransform()); -} - -void -CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error) -{ - if (!FloatValidate(x,y)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newMatrix = mTarget->GetTransform(); - mTarget->SetTransform(newMatrix.Translate(x, y)); -} - -void -CanvasRenderingContext2D::Transform(double m11, double m12, double m21, - double m22, double dx, double dy, - ErrorResult& error) -{ - if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix matrix(m11, m12, m21, m22, dx, dy); - mTarget->SetTransform(matrix * mTarget->GetTransform()); -} - -void -CanvasRenderingContext2D::SetTransform(double m11, double m12, - double m21, double m22, - double dx, double dy, - ErrorResult& error) -{ - if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix matrix(m11, m12, m21, m22, dx, dy); - mTarget->SetTransform(matrix); -} - -JSObject* -MatrixToJSObject(JSContext* cx, const Matrix& matrix, ErrorResult& error) -{ - jsval elts[] = { - DOUBLE_TO_JSVAL(matrix._11), DOUBLE_TO_JSVAL(matrix._12), - DOUBLE_TO_JSVAL(matrix._21), DOUBLE_TO_JSVAL(matrix._22), - DOUBLE_TO_JSVAL(matrix._31), DOUBLE_TO_JSVAL(matrix._32) - }; - - // XXX Should we enter GetWrapper()'s compartment? - JSObject* obj = JS_NewArrayObject(cx, 6, elts); - if (!obj) { - error.Throw(NS_ERROR_OUT_OF_MEMORY); - } - return obj; -} - -bool -ObjectToMatrix(JSContext* cx, JSObject& obj, Matrix& matrix, ErrorResult& error) -{ - uint32_t length; - if (!JS_GetArrayLength(cx, &obj, &length) || length != 6) { - // Not an array-like thing or wrong size - error.Throw(NS_ERROR_INVALID_ARG); - return false; - } - - Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22, - &matrix._31, &matrix._32 }; - for (uint32_t i = 0; i < 6; ++i) { - jsval elt; - double d; - if (!JS_GetElement(cx, &obj, i, &elt)) { - error.Throw(NS_ERROR_FAILURE); - return false; - } - if (!CoerceDouble(elt, &d)) { - error.Throw(NS_ERROR_INVALID_ARG); - return false; - } - if (!FloatValidate(d)) { - // This is weird, but it's the behavior of SetTransform() - return false; - } - *elts[i] = Float(d); - } - return true; -} - -void -CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, - JSObject& currentTransform, - ErrorResult& error) -{ - EnsureTarget(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newCTM; - if (ObjectToMatrix(cx, currentTransform, newCTM, error)) { - mTarget->SetTransform(newCTM); - } -} - -JSObject* -CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, - ErrorResult& error) const -{ - return MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), error); -} - -void -CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, - JSObject& currentTransform, - ErrorResult& error) -{ - EnsureTarget(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newCTMInverse; - if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - if (newCTMInverse.Invert()) { - mTarget->SetTransform(newCTMInverse); - } - } -} - -JSObject* -CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, - ErrorResult& error) const -{ - if (!mTarget) { - return MatrixToJSObject(cx, Matrix(), error); - } - - Matrix ctm = mTarget->GetTransform(); - - if (!ctm.Invert()) { - double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); - ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); - } - - return MatrixToJSObject(cx, ctm, error); -} - -// -// colors -// - -void -CanvasRenderingContext2D::SetStyleFromJSValue(JSContext* cx, - JS::Value& value, - Style whichStyle) -{ - if (value.isString()) { - nsDependentJSString strokeStyle; - if (strokeStyle.init(cx, value.toString())) { - SetStyleFromString(strokeStyle, whichStyle); - } - return; - } - - if (value.isObject()) { - nsCOMPtr holder; - - CanvasGradient* gradient; - nsresult rv = xpc_qsUnwrapArg(cx, value, &gradient, - static_cast(getter_AddRefs(holder)), - &value); - if (NS_SUCCEEDED(rv)) { - SetStyleFromGradient(gradient, whichStyle); - return; - } - - CanvasPattern* pattern; - rv = xpc_qsUnwrapArg(cx, value, &pattern, - static_cast(getter_AddRefs(holder)), - &value); - if (NS_SUCCEEDED(rv)) { - SetStyleFromPattern(pattern, whichStyle); - return; - } - } - - WarnAboutUnexpectedStyle(mCanvasElement); -} - -static JS::Value -WrapStyle(JSContext* cx, JSObject* obj, - CanvasRenderingContext2D::CanvasMultiGetterType type, - nsAString& str, nsISupports* supports, ErrorResult& error) -{ - JS::Value v; - bool ok; - switch (type) { - case CanvasRenderingContext2D::CMG_STYLE_STRING: - { - ok = xpc::StringToJsval(cx, str, &v); - break; - } - case CanvasRenderingContext2D::CMG_STYLE_PATTERN: - case CanvasRenderingContext2D::CMG_STYLE_GRADIENT: - { - ok = dom::WrapObject(cx, obj, supports, &v); - break; - } - default: - MOZ_NOT_REACHED("unexpected CanvasMultiGetterType"); - } - if (!ok) { - error.Throw(NS_ERROR_FAILURE); - } - return v; -} - - -JS::Value -CanvasRenderingContext2D::GetStrokeStyle(JSContext* cx, - ErrorResult& error) -{ - nsString str; - CanvasMultiGetterType t; - nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_STROKE); - return WrapStyle(cx, GetWrapper(), t, str, supports, error); -} - -JS::Value -CanvasRenderingContext2D::GetFillStyle(JSContext* cx, - ErrorResult& error) -{ - nsString str; - CanvasMultiGetterType t; - nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_FILL); - return WrapStyle(cx, GetWrapper(), t, str, supports, error); -} - -void -CanvasRenderingContext2D::SetFillRule(const nsAString& aString) -{ - FillRule rule; - - if (aString.EqualsLiteral("evenodd")) - rule = FILL_EVEN_ODD; - else if (aString.EqualsLiteral("nonzero")) - rule = FILL_WINDING; - else - return; - - CurrentState().fillRule = rule; -} - -void -CanvasRenderingContext2D::GetFillRule(nsAString& aString) -{ - switch (CurrentState().fillRule) { - case FILL_WINDING: - aString.AssignLiteral("nonzero"); break; - case FILL_EVEN_ODD: - aString.AssignLiteral("evenodd"); break; - } -} -// -// gradients and patterns -// -already_AddRefed -CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1, - ErrorResult& aError) -{ - if (!FloatValidate(x0,y0,x1,y1)) { - aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return nullptr; - } - - nsRefPtr grad = - new CanvasLinearGradient(Point(x0, y0), Point(x1, y1)); - - return grad.forget(); -} - -already_AddRefed -CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, - double x1, double y1, double r1, - ErrorResult& aError) -{ - if (!FloatValidate(x0,y0,r0,x1,y1,r1)) { - aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return nullptr; - } - - if (r0 < 0.0 || r1 < 0.0) { - aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return nullptr; - } - - nsRefPtr grad = - new CanvasRadialGradient(Point(x0, y0), r0, Point(x1, y1), r1); - - return grad.forget(); -} - -already_AddRefed -CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, - const nsAString& repeat, - ErrorResult& error) -{ - CanvasPattern::RepeatMode repeatMode = - CanvasPattern::NOREPEAT; - - if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) { - repeatMode = CanvasPattern::REPEAT; - } else if (repeat.EqualsLiteral("repeat-x")) { - repeatMode = CanvasPattern::REPEATX; - } else if (repeat.EqualsLiteral("repeat-y")) { - repeatMode = CanvasPattern::REPEATY; - } else if (repeat.EqualsLiteral("no-repeat")) { - repeatMode = CanvasPattern::NOREPEAT; - } else { - error.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return NULL; - } - - Element* htmlElement; - if (element.IsHTMLCanvasElement()) { - nsHTMLCanvasElement* canvas = element.GetAsHTMLCanvasElement(); - htmlElement = canvas; - - nsIntSize size = canvas->GetSize(); - if (size.width == 0 || size.height == 0) { - error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return NULL; - } - - // Special case for Canvas, which could be an Azure canvas! - nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); - if (srcCanvas) { - // This might not be an Azure canvas! - RefPtr srcSurf = srcCanvas->GetSurfaceSnapshot(); - - nsRefPtr pat = - new CanvasPattern(srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false); - - return pat.forget(); - } - } else if (element.IsHTMLImageElement()) { - htmlElement = element.GetAsHTMLImageElement(); - } else { - htmlElement = element.GetAsHTMLVideoElement(); - } - - // The canvas spec says that createPattern should use the first frame - // of animated images - nsLayoutUtils::SurfaceFromElementResult res = - nsLayoutUtils::SurfaceFromElement(htmlElement, - nsLayoutUtils::SFE_WANT_FIRST_FRAME | nsLayoutUtils::SFE_WANT_NEW_SURFACE); - - if (!res.mSurface) { - error.Throw(NS_ERROR_NOT_AVAILABLE); - return NULL; - } - - // Ignore nullptr cairo surfaces! See bug 666312. - if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) { - return NULL; - } - - EnsureTarget(); - RefPtr srcSurf = - gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); - - nsRefPtr pat = - new CanvasPattern(srcSurf, repeatMode, res.mPrincipal, - res.mIsWriteOnly, res.mCORSUsed); - - return pat.forget(); -} - -// -// shadows -// -void -CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor) -{ - nscolor color; - if (!ParseColor(shadowColor, &color)) { - return; - } - - CurrentState().shadowColor = color; -} - -// -// rects -// - -void -CanvasRenderingContext2D::ClearRect(double x, double y, double w, - double h) -{ - if (!FloatValidate(x,y,w,h) || !mTarget) { - return; - } - - mTarget->ClearRect(mgfx::Rect(x, y, w, h)); - - RedrawUser(gfxRect(x, y, w, h)); -} - -void -CanvasRenderingContext2D::FillRect(double x, double y, double w, - double h) -{ - if (!FloatValidate(x,y,w,h)) { - return; - } - - const ContextState &state = CurrentState(); - - if (state.patternStyles[STYLE_FILL]) { - CanvasPattern::RepeatMode repeat = - state.patternStyles[STYLE_FILL]->mRepeat; - // In the FillRect case repeat modes are easy to deal with. - bool limitx = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATY; - bool limity = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATX; - - IntSize patternSize = - state.patternStyles[STYLE_FILL]->mSurface->GetSize(); - - // We always need to execute painting for non-over operators, even if - // we end up with w/h = 0. - if (limitx) { - if (x < 0) { - w += x; - if (w < 0) { - w = 0; - } - - x = 0; - } - if (x + w > patternSize.width) { - w = patternSize.width - x; - if (w < 0) { - w = 0; - } - } - } - if (limity) { - if (y < 0) { - h += y; - if (h < 0) { - h = 0; - } - - y = 0; - } - if (y + h > patternSize.height) { - h = patternSize.height - y; - if (h < 0) { - h = 0; - } - } - } - } - - mgfx::Rect bounds; - - EnsureTarget(); - if (NeedToDrawShadow()) { - bounds = mgfx::Rect(x, y, w, h); - bounds = mTarget->GetTransform().TransformBounds(bounds); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - FillRect(mgfx::Rect(x, y, w, h), - CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), - DrawOptions(state.globalAlpha, UsedOperation())); - - RedrawUser(gfxRect(x, y, w, h)); -} - -void -CanvasRenderingContext2D::StrokeRect(double x, double y, double w, - double h) -{ - if (!FloatValidate(x,y,w,h)) { - return; - } - - const ContextState &state = CurrentState(); - - mgfx::Rect bounds; - - if (!w && !h) { - return; - } - - EnsureTarget(); - if (!IsTargetValid()) { - return; - } - - if (NeedToDrawShadow()) { - bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f, - w + state.lineWidth, h + state.lineWidth); - bounds = mTarget->GetTransform().TransformBounds(bounds); - } - - if (!h) { - CapStyle cap = CAP_BUTT; - if (state.lineJoin == JOIN_ROUND) { - cap = CAP_ROUND; - } - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - StrokeLine(Point(x, y), Point(x + w, y), - CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - cap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, UsedOperation())); - return; - } - - if (!w) { - CapStyle cap = CAP_BUTT; - if (state.lineJoin == JOIN_ROUND) { - cap = CAP_ROUND; - } - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - StrokeLine(Point(x, y), Point(x, y + h), - CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - cap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, UsedOperation())); - return; - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - StrokeRect(mgfx::Rect(x, y, w, h), - CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - state.lineCap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, UsedOperation())); - - Redraw(); -} - -// -// path bits -// - -void -CanvasRenderingContext2D::BeginPath() -{ - mPath = nullptr; - mPathBuilder = nullptr; - mDSPathBuilder = nullptr; - mPathTransformWillUpdate = false; -} - -void -CanvasRenderingContext2D::Fill() -{ - EnsureUserSpacePath(); - - if (!mPath) { - return; - } - - mgfx::Rect bounds; - - if (NeedToDrawShadow()) { - bounds = mPath->GetBounds(mTarget->GetTransform()); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - Fill(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), - DrawOptions(CurrentState().globalAlpha, UsedOperation())); - - Redraw(); -} - -void -CanvasRenderingContext2D::Stroke() -{ - EnsureUserSpacePath(); - - if (!mPath) { - return; - } - - const ContextState &state = CurrentState(); - - StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, - state.lineCap, state.miterLimit, - state.dash.Length(), state.dash.Elements(), - state.dashOffset); - - mgfx::Rect bounds; - if (NeedToDrawShadow()) { - bounds = - mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - Stroke(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); - - Redraw(); -} - -void -CanvasRenderingContext2D::Clip() -{ - EnsureUserSpacePath(); - - if (!mPath) { - return; - } - - mTarget->PushClip(mPath); - CurrentState().clipsPushed.push_back(mPath); -} - -void -CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, - double y2, double radius, - ErrorResult& error) -{ - if (!FloatValidate(x1, y1, x2, y2, radius)) { - return; - } - - if (radius < 0) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - EnsureWritablePath(); - - // Current point in user space! - Point p0; - if (mPathBuilder) { - p0 = mPathBuilder->CurrentPoint(); - } else { - Matrix invTransform = mTarget->GetTransform(); - if (!invTransform.Invert()) { - return; - } - - p0 = invTransform * mDSPathBuilder->CurrentPoint(); - } - - Point p1(x1, y1); - Point p2(x2, y2); - - // Execute these calculations in double precision to avoid cumulative - // rounding errors. - double dir, a2, b2, c2, cosx, sinx, d, anx, any, - bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; - bool anticlockwise; - - if (p0 == p1 || p1 == p2 || radius == 0) { - LineTo(p1.x, p1.y); - return; - } - - // Check for colinearity - dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); - if (dir == 0) { - LineTo(p1.x, p1.y); - return; - } - - - // XXX - Math for this code was already available from the non-azure code - // and would be well tested. Perhaps converting to bezier directly might - // be more efficient longer run. - a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); - b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); - c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); - cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); - - sinx = sqrt(1 - cosx*cosx); - d = radius / ((1 - cosx) / sinx); - - anx = (x1-p0.x) / sqrt(a2); - any = (y1-p0.y) / sqrt(a2); - bnx = (x1-x2) / sqrt(b2); - bny = (y1-y2) / sqrt(b2); - x3 = x1 - anx*d; - y3 = y1 - any*d; - x4 = x1 - bnx*d; - y4 = y1 - bny*d; - anticlockwise = (dir < 0); - cx = x3 + any*radius*(anticlockwise ? 1 : -1); - cy = y3 - anx*radius*(anticlockwise ? 1 : -1); - angle0 = atan2((y3-cy), (x3-cx)); - angle1 = atan2((y4-cy), (x4-cx)); - - - LineTo(x3, y3); - - Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); -} - -void -CanvasRenderingContext2D::Arc(double x, double y, double r, - double startAngle, double endAngle, - bool anticlockwise, ErrorResult& error) -{ - if (!FloatValidate(x, y, r, startAngle, endAngle)) { - return; - } - - if (r < 0.0) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - EnsureWritablePath(); - - ArcToBezier(this, Point(x, y), r, startAngle, endAngle, anticlockwise); -} - -void -CanvasRenderingContext2D::Rect(double x, double y, double w, double h) -{ - if (!FloatValidate(x, y, w, h)) { - return; - } - - EnsureWritablePath(); - - if (mPathBuilder) { - mPathBuilder->MoveTo(Point(x, y)); - mPathBuilder->LineTo(Point(x + w, y)); - mPathBuilder->LineTo(Point(x + w, y + h)); - mPathBuilder->LineTo(Point(x, y + h)); - mPathBuilder->Close(); - } else { - mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y)); - mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y)); - mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h)); - mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h)); - mDSPathBuilder->Close(); - } -} - -void -CanvasRenderingContext2D::EnsureWritablePath() -{ - if (mDSPathBuilder) { - return; - } - - FillRule fillRule = CurrentState().fillRule; - - if (mPathBuilder) { - if (mPathTransformWillUpdate) { - mPath = mPathBuilder->Finish(); - mDSPathBuilder = - mPath->TransformedCopyToBuilder(mPathToDS, fillRule); - mPath = nullptr; - mPathBuilder = nullptr; - mPathTransformWillUpdate = false; - } - return; - } - - EnsureTarget(); - if (!mPath) { - NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); - mPathBuilder = mTarget->CreatePathBuilder(fillRule); - } else if (!mPathTransformWillUpdate) { - mPathBuilder = mPath->CopyToBuilder(fillRule); - } else { - mDSPathBuilder = - mPath->TransformedCopyToBuilder(mPathToDS, fillRule); - mPathTransformWillUpdate = false; - } -} - -void -CanvasRenderingContext2D::EnsureUserSpacePath(bool aCommitTransform /* = true */) -{ - FillRule fillRule = CurrentState().fillRule; - - if (!mPath && !mPathBuilder && !mDSPathBuilder) { - EnsureTarget(); - mPathBuilder = mTarget->CreatePathBuilder(fillRule); - } - - if (mPathBuilder) { - mPath = mPathBuilder->Finish(); - mPathBuilder = nullptr; - } - - if (aCommitTransform && - mPath && - mPathTransformWillUpdate) { - mDSPathBuilder = - mPath->TransformedCopyToBuilder(mPathToDS, fillRule); - mPath = nullptr; - mPathTransformWillUpdate = false; - } - - if (mDSPathBuilder) { - RefPtr dsPath; - dsPath = mDSPathBuilder->Finish(); - mDSPathBuilder = nullptr; - - Matrix inverse = mTarget->GetTransform(); - if (!inverse.Invert()) { - NS_WARNING("Could not invert transform"); - return; - } - - mPathBuilder = - dsPath->TransformedCopyToBuilder(inverse, fillRule); - mPath = mPathBuilder->Finish(); - mPathBuilder = nullptr; - } - - if (mPath && mPath->GetFillRule() != fillRule) { - mPathBuilder = mPath->CopyToBuilder(fillRule); - mPath = mPathBuilder->Finish(); - } - - NS_ASSERTION(mPath, "mPath should exist"); -} - -void -CanvasRenderingContext2D::TransformWillUpdate() -{ - EnsureTarget(); - - // Store the matrix that would transform the current path to device - // space. - if (mPath || mPathBuilder) { - if (!mPathTransformWillUpdate) { - // If the transform has already been updated, but a device space builder - // has not been created yet mPathToDS contains the right transform to - // transform the current mPath into device space. - // We should leave it alone. - mPathToDS = mTarget->GetTransform(); - } - mPathTransformWillUpdate = true; - } -} - -// -// text -// - -/** - * Helper function for SetFont that creates a style rule for the given font. - * @param aFont The CSS font string - * @param aNode The canvas element - * @param aResult Pointer in which to place the new style rule. - * @remark Assumes all pointer arguments are non-null. - */ -static nsresult -CreateFontStyleRule(const nsAString& aFont, - nsINode* aNode, - StyleRule** aResult) -{ - nsRefPtr rule; - bool changed; - - nsIPrincipal* principal = aNode->NodePrincipal(); - nsIDocument* document = aNode->OwnerDoc(); - - nsIURI* docURL = document->GetDocumentURI(); - nsIURI* baseURL = document->GetDocBaseURI(); - - // Pass the CSS Loader object to the parser, to allow parser error reports - // to include the outer window ID. - nsCSSParser parser(document->CSSLoader()); - - nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, - principal, getter_AddRefs(rule)); - if (NS_FAILED(rv)) { - return rv; - } - - rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL, - principal, rule->GetDeclaration(), &changed, - false); - if (NS_FAILED(rv)) - return rv; - - rv = parser.ParseProperty(eCSSProperty_line_height, - NS_LITERAL_STRING("normal"), docURL, baseURL, - principal, rule->GetDeclaration(), &changed, - false); - if (NS_FAILED(rv)) { - return rv; - } - - rule->RuleMatched(); - - rule.forget(aResult); - return NS_OK; -} - -void -CanvasRenderingContext2D::SetFont(const nsAString& font, - ErrorResult& error) -{ - /* - * If font is defined with relative units (e.g. ems) and the parent - * style context changes in between calls, setting the font to the - * same value as previous could result in a different computed value, - * so we cannot have the optimization where we check if the new font - * string is equal to the old one. - */ - - if (!mCanvasElement && !mDocShell) { - NS_WARNING("Canvas element must be non-null or a docshell must be provided"); - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsIPresShell* presShell = GetPresShell(); - if (!presShell) { - error.Throw(NS_ERROR_FAILURE); - return; - } - nsIDocument* document = presShell->GetDocument(); - - nsRefPtr rule; - error = CreateFontStyleRule(font, document, getter_AddRefs(rule)); - - if (error.Failed()) { - return; - } - - css::Declaration *declaration = rule->GetDeclaration(); - // The easiest way to see whether we got a syntax error or whether - // we got 'inherit' or 'initial' is to look at font-size-adjust, - // which the shorthand resets to either 'none' or - // '-moz-system-font'. - // We know the declaration is not !important, so we can use - // GetNormalBlock(). - const nsCSSValue *fsaVal = - declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust); - if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None && - fsaVal->GetUnit() != eCSSUnit_System_Font)) { - // We got an all-property value or a syntax error. The spec says - // this value must be ignored. - return; - } - - nsTArray< nsCOMPtr > rules; - rules.AppendElement(rule); - - nsStyleSet* styleSet = presShell->StyleSet(); - - // have to get a parent style context for inherit-like relative - // values (2em, bolder, etc.) - nsRefPtr parentContext; - - if (mCanvasElement && mCanvasElement->IsInDoc()) { - // inherit from the canvas element - parentContext = nsComputedDOMStyle::GetStyleContextForElement( - mCanvasElement, - nullptr, - presShell); - } else { - // otherwise inherit from default (10px sans-serif) - nsRefPtr parentRule; - error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"), - document, - getter_AddRefs(parentRule)); - - if (error.Failed()) { - return; - } - - nsTArray< nsCOMPtr > parentRules; - parentRules.AppendElement(parentRule); - parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules); - } - - if (!parentContext) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsRefPtr sc = - styleSet->ResolveStyleForRules(parentContext, rules); - if (!sc) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - const nsStyleFont* fontStyle = sc->GetStyleFont(); - - NS_ASSERTION(fontStyle, "Could not obtain font style"); - - nsIAtom* language = sc->GetStyleFont()->mLanguage; - if (!language) { - language = presShell->GetPresContext()->GetLanguageFromCharset(); - } - - // use CSS pixels instead of dev pixels to avoid being affected by page zoom - const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel(); - // un-zoom the font size to avoid being affected by text-only zoom - // - // Purposely ignore the font size that respects the user's minimum - // font preference (fontStyle->mFont.size) in favor of the computed - // size (fontStyle->mSize). See - // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. - const nscoord fontSize = nsStyleFont::UnZoomText(parentContext->PresContext(), fontStyle->mSize); - - bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview || - presShell->GetPresContext()->Type() == nsPresContext::eContext_Print); - - gfxFontStyle style(fontStyle->mFont.style, - fontStyle->mFont.weight, - fontStyle->mFont.stretch, - NSAppUnitsToFloatPixels(fontSize, float(aupcp)), - language, - fontStyle->mFont.sizeAdjust, - fontStyle->mFont.systemFont, - printerFont, - fontStyle->mFont.languageOverride); - - fontStyle->mFont.AddFontFeaturesToStyle(&style); - - CurrentState().fontGroup = - gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, - &style, - presShell->GetPresContext()->GetUserFontSet()); - NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); - - // The font getter is required to be reserialized based on what we - // parsed (including having line-height removed). (Older drafts of - // the spec required font sizes be converted to pixels, but that no - // longer seems to be required.) - declaration->GetValue(eCSSProperty_font, CurrentState().font); -} - -void -CanvasRenderingContext2D::SetTextAlign(const nsAString& ta) -{ - if (ta.EqualsLiteral("start")) - CurrentState().textAlign = TEXT_ALIGN_START; - else if (ta.EqualsLiteral("end")) - CurrentState().textAlign = TEXT_ALIGN_END; - else if (ta.EqualsLiteral("left")) - CurrentState().textAlign = TEXT_ALIGN_LEFT; - else if (ta.EqualsLiteral("right")) - CurrentState().textAlign = TEXT_ALIGN_RIGHT; - else if (ta.EqualsLiteral("center")) - CurrentState().textAlign = TEXT_ALIGN_CENTER; -} - -void -CanvasRenderingContext2D::GetTextAlign(nsAString& ta) -{ - switch (CurrentState().textAlign) - { - case TEXT_ALIGN_START: - ta.AssignLiteral("start"); - break; - case TEXT_ALIGN_END: - ta.AssignLiteral("end"); - break; - case TEXT_ALIGN_LEFT: - ta.AssignLiteral("left"); - break; - case TEXT_ALIGN_RIGHT: - ta.AssignLiteral("right"); - break; - case TEXT_ALIGN_CENTER: - ta.AssignLiteral("center"); - break; - } -} - -void -CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb) -{ - if (tb.EqualsLiteral("top")) - CurrentState().textBaseline = TEXT_BASELINE_TOP; - else if (tb.EqualsLiteral("hanging")) - CurrentState().textBaseline = TEXT_BASELINE_HANGING; - else if (tb.EqualsLiteral("middle")) - CurrentState().textBaseline = TEXT_BASELINE_MIDDLE; - else if (tb.EqualsLiteral("alphabetic")) - CurrentState().textBaseline = TEXT_BASELINE_ALPHABETIC; - else if (tb.EqualsLiteral("ideographic")) - CurrentState().textBaseline = TEXT_BASELINE_IDEOGRAPHIC; - else if (tb.EqualsLiteral("bottom")) - CurrentState().textBaseline = TEXT_BASELINE_BOTTOM; -} - -void -CanvasRenderingContext2D::GetTextBaseline(nsAString& tb) -{ - switch (CurrentState().textBaseline) - { - case TEXT_BASELINE_TOP: - tb.AssignLiteral("top"); - break; - case TEXT_BASELINE_HANGING: - tb.AssignLiteral("hanging"); - break; - case TEXT_BASELINE_MIDDLE: - tb.AssignLiteral("middle"); - break; - case TEXT_BASELINE_ALPHABETIC: - tb.AssignLiteral("alphabetic"); - break; - case TEXT_BASELINE_IDEOGRAPHIC: - tb.AssignLiteral("ideographic"); - break; - case TEXT_BASELINE_BOTTOM: - tb.AssignLiteral("bottom"); - break; - } -} - -/* - * Helper function that replaces the whitespace characters in a string - * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, - * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE - * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). - * @param str The string whose whitespace characters to replace. - */ -static inline void -TextReplaceWhitespaceCharacters(nsAutoString& str) -{ - str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", PRUnichar(' ')); -} - -void -CanvasRenderingContext2D::FillText(const nsAString& text, double x, - double y, - const Optional& maxWidth, - ErrorResult& error) -{ - error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL, nullptr); -} - -void -CanvasRenderingContext2D::StrokeText(const nsAString& text, double x, - double y, - const Optional& maxWidth, - ErrorResult& error) -{ - error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE, nullptr); -} - -already_AddRefed -CanvasRenderingContext2D::MeasureText(const nsAString& rawText, - ErrorResult& error) -{ - float width; - Optional maxWidth; - error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TEXT_DRAW_OPERATION_MEASURE, &width); - if (error.Failed()) { - return NULL; - } - - nsRefPtr textMetrics = new TextMetrics(width); - - return textMetrics.forget(); -} - -/** - * Used for nsBidiPresUtils::ProcessText - */ -struct NS_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor -{ - typedef CanvasRenderingContext2D::ContextState ContextState; - - virtual void SetText(const PRUnichar* text, int32_t length, nsBidiDirection direction) - { - mFontgrp->UpdateFontList(); // ensure user font generation is current - mTextRun = mFontgrp->MakeTextRun(text, - length, - mThebes, - mAppUnitsPerDevPixel, - direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); - } - - virtual nscoord GetWidth() - { - gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, - mTextRun->GetLength(), - mDoMeasureBoundingBox ? - gfxFont::TIGHT_INK_EXTENTS : - gfxFont::LOOSE_INK_EXTENTS, - mThebes, - nullptr); - - // this only measures the height; the total width is gotten from the - // the return value of ProcessText. - if (mDoMeasureBoundingBox) { - textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); - mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); - } - - return NSToCoordRound(textRunMetrics.mAdvanceWidth); - } - - virtual void DrawText(nscoord xOffset, nscoord width) - { - gfxPoint point = mPt; - point.x += xOffset; - - // offset is given in terms of left side of string - if (mTextRun->IsRightToLeft()) { - // Bug 581092 - don't use rounded pixel width to advance to - // right-hand end of run, because this will cause different - // glyph positioning for LTR vs RTL drawing of the same - // glyph string on OS X and DWrite where textrun widths may - // involve fractional pixels. - gfxTextRun::Metrics textRunMetrics = - mTextRun->MeasureText(0, - mTextRun->GetLength(), - mDoMeasureBoundingBox ? - gfxFont::TIGHT_INK_EXTENTS : - gfxFont::LOOSE_INK_EXTENTS, - mThebes, - nullptr); - point.x += textRunMetrics.mAdvanceWidth; - // old code was: - // point.x += width * mAppUnitsPerDevPixel; - // TODO: restore this if/when we move to fractional coords - // throughout the text layout process - } - - uint32_t numRuns; - const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); - const uint32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel; - const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); - Point baselineOrigin = - Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); - - float advanceSum = 0; - - mCtx->EnsureTarget(); - for (uint32_t c = 0; c < numRuns; c++) { - gfxFont *font = runs[c].mFont; - uint32_t endRun = 0; - if (c + 1 < numRuns) { - endRun = runs[c + 1].mCharacterOffset; - } else { - endRun = mTextRun->GetLength(); - } - - const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); - - RefPtr scaledFont = - gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); - - if (!scaledFont) { - // This can occur when something switched DirectWrite off. - return; - } - - GlyphBuffer buffer; - - std::vector glyphBuf; - - for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { - Glyph newGlyph; - if (glyphs[i].IsSimpleGlyph()) { - newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x - advanceSum - - glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; - } else { - newGlyph.mPosition.x = baselineOrigin.x + advanceSum; - } - newGlyph.mPosition.y = baselineOrigin.y; - advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; - glyphBuf.push_back(newGlyph); - continue; - } - - if (!glyphs[i].GetGlyphCount()) { - continue; - } - - gfxTextRun::DetailedGlyph *detailedGlyphs = - mTextRun->GetDetailedGlyphs(i); - - if (glyphs[i].IsMissing()) { - float xpos; - float advance = detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; - if (mTextRun->IsRightToLeft()) { - xpos = baselineOrigin.x - advanceSum - advance; - } else { - xpos = baselineOrigin.x + advanceSum; - } - advanceSum += advance; - - // default-ignorable characters will have zero advance width. - // we don't draw a hexbox for them, just leave them invisible - if (advance > 0) { - // for now, we use gfxFontMissingGlyphs to draw the hexbox; - // some day we should replace this with a direct Azure version - - // get the DrawTarget's transform, so we can apply it to the - // thebes context for gfxFontMissingGlyphs - Matrix matrix = mCtx->mTarget->GetTransform(); - nsRefPtr thebes; - if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { - // XXX See bug 808288 comment 5 - Bas says: - // This is a little tricky, potentially this could go wrong if - // we fell back to a Cairo context because of for example - // extremely large Canvas size. Cairo content is technically - // -not- supported, but SupportsAzureContent would return true - // as the browser uses D2D content. - // I'm thinking Cairo content will be good enough to do - // DrawMissingGlyph though. - thebes = new gfxContext(mCtx->mTarget); - } else { - nsRefPtr drawSurf; - mCtx->GetThebesSurface(getter_AddRefs(drawSurf)); - thebes = new gfxContext(drawSurf); - } - thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, - matrix._22, matrix._31, matrix._32)); - - gfxFloat height = font->GetMetrics().maxAscent; - gfxRect glyphRect(xpos, baselineOrigin.y - height, - advance, height); - gfxFontMissingGlyphs::DrawMissingGlyph(thebes, glyphRect, - detailedGlyphs[0].mGlyphID); - - mCtx->mTarget->SetTransform(matrix); - } - continue; - } - - for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { - newGlyph.mIndex = detailedGlyphs[c].mGlyphID; - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - - advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; - } else { - newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; - } - newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; - glyphBuf.push_back(newGlyph); - advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; - } - } - - if (!glyphBuf.size()) { - // This may happen for glyph runs for a 0 size font. - continue; - } - - buffer.mGlyphs = &glyphBuf.front(); - buffer.mNumGlyphs = glyphBuf.size(); - - Rect bounds = mCtx->mTarget->GetTransform(). - TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y, - mBoundingBox.width, mBoundingBox.height)); - if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL) { - AdjustedTarget(mCtx, &bounds)-> - FillGlyphs(scaledFont, buffer, - CanvasGeneralPattern(). - ForStyle(mCtx, CanvasRenderingContext2D::STYLE_FILL, mCtx->mTarget), - DrawOptions(mState->globalAlpha, mCtx->UsedOperation())); - } else if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE) { - RefPtr path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget); - - const ContextState& state = *mState; - AdjustedTarget(mCtx, &bounds)-> - Stroke(path, CanvasGeneralPattern(). - ForStyle(mCtx, CanvasRenderingContext2D::STYLE_STROKE, mCtx->mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - state.lineCap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, mCtx->UsedOperation())); - - } - } - } - - // current text run - nsAutoPtr mTextRun; - - // pointer to a screen reference context used to measure text and such - nsRefPtr mThebes; - - // Pointer to the draw target we should fill our text to - CanvasRenderingContext2D *mCtx; - - // position of the left side of the string, alphabetic baseline - gfxPoint mPt; - - // current font - gfxFontGroup* mFontgrp; - - // dev pixel conversion factor - uint32_t mAppUnitsPerDevPixel; - - // operation (fill or stroke) - CanvasRenderingContext2D::TextDrawOperation mOp; - - // context state - ContextState *mState; - - // union of bounding boxes of all runs, needed for shadows - gfxRect mBoundingBox; - - // true iff the bounding box should be measured - bool mDoMeasureBoundingBox; -}; - -nsresult -CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, - float aX, - float aY, - const Optional& aMaxWidth, - TextDrawOperation aOp, - float* aWidth) -{ - nsresult rv; - - if (!FloatValidate(aX, aY) || - (aMaxWidth.WasPassed() && !FloatValidate(aMaxWidth.Value()))) - return NS_ERROR_DOM_SYNTAX_ERR; - - // spec isn't clear on what should happen if aMaxWidth <= 0, so - // treat it as an invalid argument - // technically, 0 should be an invalid value as well, but 0 is the default - // arg, and there is no way to tell if the default was used - if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0) - return NS_ERROR_INVALID_ARG; - - if (!mCanvasElement && !mDocShell) { - NS_WARNING("Canvas element must be non-null or a docshell must be provided"); - return NS_ERROR_FAILURE; - } - - nsCOMPtr presShell = GetPresShell(); - if (!presShell) - return NS_ERROR_FAILURE; - - nsIDocument* document = presShell->GetDocument(); - - // replace all the whitespace characters with U+0020 SPACE - nsAutoString textToDraw(aRawText); - TextReplaceWhitespaceCharacters(textToDraw); - - // for now, default to ltr if not in doc - bool isRTL = false; - - if (mCanvasElement && mCanvasElement->IsInDoc()) { - // try to find the closest context - nsRefPtr canvasStyle = - nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, - nullptr, - presShell); - if (!canvasStyle) { - return NS_ERROR_FAILURE; - } - - isRTL = canvasStyle->GetStyleVisibility()->mDirection == - NS_STYLE_DIRECTION_RTL; - } else { - isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; - } - - gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); - NS_ASSERTION(currentFontStyle, "font group is null"); - - if (currentFontStyle->GetStyle()->size == 0.0F) { - if (aWidth) { - *aWidth = 0; - } - return NS_OK; - } - - const ContextState &state = CurrentState(); - - // This is only needed to know if we can know the drawing bounding box easily. - bool doDrawShadow = aOp == TEXT_DRAW_OPERATION_FILL && NeedToDrawShadow(); - - CanvasBidiProcessor processor; - - GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); - processor.mPt = gfxPoint(aX, aY); - processor.mThebes = - new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); - - // If we don't have a target then we don't have a transform. A target won't - // be needed in the case where we're measuring the text size. This allows - // to avoid creating a target if it's only being used to measure text sizes. - if (mTarget) { - Matrix matrix = mTarget->GetTransform(); - processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); - } - processor.mCtx = this; - processor.mOp = aOp; - processor.mBoundingBox = gfxRect(0, 0, 0, 0); - processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid; - processor.mState = &CurrentState(); - processor.mFontgrp = currentFontStyle; - - nscoord totalWidthCoord; - - // calls bidi algo twice since it needs the full text width and the - // bounding boxes before rendering anything - nsBidi bidiEngine; - rv = nsBidiPresUtils::ProcessText(textToDraw.get(), - textToDraw.Length(), - isRTL ? NSBIDI_RTL : NSBIDI_LTR, - presShell->GetPresContext(), - processor, - nsBidiPresUtils::MODE_MEASURE, - nullptr, - 0, - &totalWidthCoord, - &bidiEngine); - if (NS_FAILED(rv)) { - return rv; - } - - float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; - if (aWidth) { - *aWidth = totalWidth; - } - - // if only measuring, don't need to do any more work - if (aOp==TEXT_DRAW_OPERATION_MEASURE) { - return NS_OK; - } - - // offset pt.x based on text align - gfxFloat anchorX; - - if (state.textAlign == TEXT_ALIGN_CENTER) { - anchorX = .5; - } else if (state.textAlign == TEXT_ALIGN_LEFT || - (!isRTL && state.textAlign == TEXT_ALIGN_START) || - (isRTL && state.textAlign == TEXT_ALIGN_END)) { - anchorX = 0; - } else { - anchorX = 1; - } - - processor.mPt.x -= anchorX * totalWidth; - - // offset pt.y based on text baseline - processor.mFontgrp->UpdateFontList(); // ensure user font generation is current - NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts"); - const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics(); - - gfxFloat anchorY; - - switch (state.textBaseline) - { - case TEXT_BASELINE_HANGING: - // fall through; best we can do with the information available - case TEXT_BASELINE_TOP: - anchorY = fontMetrics.emAscent; - break; - case TEXT_BASELINE_MIDDLE: - anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; - break; - case TEXT_BASELINE_IDEOGRAPHIC: - // fall through; best we can do with the information available - case TEXT_BASELINE_ALPHABETIC: - anchorY = 0; - break; - case TEXT_BASELINE_BOTTOM: - anchorY = -fontMetrics.emDescent; - break; - default: - MOZ_NOT_REACHED("unexpected TextBaseline"); - } - - processor.mPt.y += anchorY; - - // correct bounding box to get it to be the correct size/position - processor.mBoundingBox.width = totalWidth; - processor.mBoundingBox.MoveBy(processor.mPt); - - processor.mPt.x *= processor.mAppUnitsPerDevPixel; - processor.mPt.y *= processor.mAppUnitsPerDevPixel; - - EnsureTarget(); - Matrix oldTransform = mTarget->GetTransform(); - // if text is over aMaxWidth, then scale the text horizontally such that its - // width is precisely aMaxWidth - if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && - totalWidth > aMaxWidth.Value()) { - Matrix newTransform = oldTransform; - - // Translate so that the anchor point is at 0,0, then scale and then - // translate back. - newTransform.Translate(aX, 0); - newTransform.Scale(aMaxWidth.Value() / totalWidth, 1); - newTransform.Translate(-aX, 0); - /* we do this to avoid an ICE in the android compiler */ - Matrix androidCompilerBug = newTransform; - mTarget->SetTransform(androidCompilerBug); - } - - // save the previous bounding box - gfxRect boundingBox = processor.mBoundingBox; - - // don't ever need to measure the bounding box twice - processor.mDoMeasureBoundingBox = false; - - rv = nsBidiPresUtils::ProcessText(textToDraw.get(), - textToDraw.Length(), - isRTL ? NSBIDI_RTL : NSBIDI_LTR, - presShell->GetPresContext(), - processor, - nsBidiPresUtils::MODE_DRAW, - nullptr, - 0, - nullptr, - &bidiEngine); - - - mTarget->SetTransform(oldTransform); - - if (aOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL && - !doDrawShadow) { - RedrawUser(boundingBox); - return NS_OK; - } - - Redraw(); - return NS_OK; -} - -gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle() -{ - // use lazy initilization for the font group since it's rather expensive - if (!CurrentState().fontGroup) { - ErrorResult err; - SetFont(kDefaultFontStyle, err); - if (err.Failed()) { - gfxFontStyle style; - style.size = kDefaultFontSize; - CurrentState().fontGroup = - gfxPlatform::GetPlatform()->CreateFontGroup(kDefaultFontName, - &style, - nullptr); - if (CurrentState().fontGroup) { - CurrentState().font = kDefaultFontStyle; - } else { - NS_ERROR("Default canvas font is invalid"); - } - } - - } - - return CurrentState().fontGroup; -} - -// -// line caps/joins -// - -void -CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle) -{ - CapStyle cap; - - if (capstyle.EqualsLiteral("butt")) { - cap = CAP_BUTT; - } else if (capstyle.EqualsLiteral("round")) { - cap = CAP_ROUND; - } else if (capstyle.EqualsLiteral("square")) { - cap = CAP_SQUARE; - } else { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return; - } - - CurrentState().lineCap = cap; -} - -void -CanvasRenderingContext2D::GetLineCap(nsAString& capstyle) -{ - switch (CurrentState().lineCap) { - case CAP_BUTT: - capstyle.AssignLiteral("butt"); - break; - case CAP_ROUND: - capstyle.AssignLiteral("round"); - break; - case CAP_SQUARE: - capstyle.AssignLiteral("square"); - break; - } -} - -void -CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle) -{ - JoinStyle j; - - if (joinstyle.EqualsLiteral("round")) { - j = JOIN_ROUND; - } else if (joinstyle.EqualsLiteral("bevel")) { - j = JOIN_BEVEL; - } else if (joinstyle.EqualsLiteral("miter")) { - j = JOIN_MITER_OR_BEVEL; - } else { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return; - } - - CurrentState().lineJoin = j; -} - -void -CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error) -{ - switch (CurrentState().lineJoin) { - case JOIN_ROUND: - joinstyle.AssignLiteral("round"); - break; - case JOIN_BEVEL: - joinstyle.AssignLiteral("bevel"); - break; - case JOIN_MITER_OR_BEVEL: - joinstyle.AssignLiteral("miter"); - break; - default: - error.Throw(NS_ERROR_FAILURE); - } -} - -void -CanvasRenderingContext2D::SetMozDash(JSContext* cx, - const JS::Value& mozDash, - ErrorResult& error) -{ - FallibleTArray dash; - error = JSValToDashArray(cx, mozDash, dash); - if (!error.Failed()) { - ContextState& state = CurrentState(); - state.dash = dash; - if (state.dash.IsEmpty()) { - state.dashOffset = 0; - } - } -} - -JS::Value -CanvasRenderingContext2D::GetMozDash(JSContext* cx, ErrorResult& error) -{ - JS::Value mozDash; - error = DashArrayToJSVal(CurrentState().dash, cx, &mozDash); - return mozDash; -} - -void -CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) -{ - if (!FloatValidate(mozDashOffset)) { - return; - } - - ContextState& state = CurrentState(); - if (!state.dash.IsEmpty()) { - state.dashOffset = mozDashOffset; - } -} - -bool -CanvasRenderingContext2D::IsPointInPath(double x, double y) -{ - if (!FloatValidate(x,y)) { - return false; - } - - EnsureUserSpacePath(false); - if (!mPath) { - return false; - } - if (mPathTransformWillUpdate) { - return mPath->ContainsPoint(Point(x, y), mPathToDS); - } - return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); -} - -bool -CanvasRenderingContext2D::MozIsPointInStroke(double x, double y) -{ - if (!FloatValidate(x,y)) { - return false; - } - - EnsureUserSpacePath(false); - if (!mPath) { - return false; - } - - const ContextState &state = CurrentState(); - - StrokeOptions strokeOptions(state.lineWidth, - state.lineJoin, - state.lineCap, - state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset); - - if (mPathTransformWillUpdate) { - return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS); - } - return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); -} - -// -// image -// - -// drawImage(in HTMLImageElement image, in float dx, in float dy); -// -- render image from 0,0 at dx,dy top-left coords -// drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh); -// -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh -// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); -// -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas - -// If only dx and dy are passed in then optional_argc should be 0. If only -// dx, dy, dw and dh are passed in then optional_argc should be 2. The only -// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh -// are all passed in. - -void -CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, - double sx, double sy, double sw, - double sh, double dx, double dy, - double dw, double dh, - uint8_t optional_argc, - ErrorResult& error) -{ - MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6); - - RefPtr srcSurf; - gfxIntSize imgSize; - - Element* element; - - EnsureTarget(); - if (image.IsHTMLCanvasElement()) { - nsHTMLCanvasElement* canvas = image.GetAsHTMLCanvasElement(); - element = canvas; - nsIntSize size = canvas->GetSize(); - if (size.width == 0 || size.height == 0) { - error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return; - } - - // Special case for Canvas, which could be an Azure canvas! - nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); - if (srcCanvas == this) { - // Self-copy. - srcSurf = mTarget->Snapshot(); - imgSize = gfxIntSize(mWidth, mHeight); - } else if (srcCanvas) { - // This might not be an Azure canvas! - srcSurf = srcCanvas->GetSurfaceSnapshot(); - - if (srcSurf) { - if (mCanvasElement) { - // Do security check here. - CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, - element->NodePrincipal(), - canvas->IsWriteOnly(), - false); - } - imgSize = gfxIntSize(srcSurf->GetSize().width, srcSurf->GetSize().height); - } - } - } else { - if (image.IsHTMLImageElement()) { - nsHTMLImageElement* img = image.GetAsHTMLImageElement(); - element = img; - } else { - nsHTMLVideoElement* video = image.GetAsHTMLVideoElement(); - element = video; - } - - gfxASurface* imgsurf = - CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); - if (imgsurf) { - srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, imgsurf); - } - } - - if (!srcSurf) { - // The canvas spec says that drawImage should draw the first frame - // of animated images - uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME; - nsLayoutUtils::SurfaceFromElementResult res = - nsLayoutUtils::SurfaceFromElement(element, sfeFlags); - - if (!res.mSurface) { - // Spec says to silently do nothing if the element is still loading. - if (!res.mIsStillLoading) { - error.Throw(NS_ERROR_NOT_AVAILABLE); - } - return; - } - - // Ignore cairo surfaces that are bad! See bug 666312. - if (res.mSurface->CairoStatus()) { - return; - } - - imgSize = res.mSize; - - if (mCanvasElement) { - CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, - res.mPrincipal, res.mIsWriteOnly, - res.mCORSUsed); - } - - if (res.mImageRequest) { - CanvasImageCache::NotifyDrawImage(element, mCanvasElement, - res.mImageRequest, res.mSurface, imgSize); - } - - srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); - } - - if (optional_argc == 0) { - sx = sy = 0.0; - dw = sw = (double) imgSize.width; - dh = sh = (double) imgSize.height; - } else if (optional_argc == 2) { - sx = sy = 0.0; - sw = (double) imgSize.width; - sh = (double) imgSize.height; - } - - if (sw == 0.0 || sh == 0.0) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - if (dw == 0.0 || dh == 0.0) { - // not really failure, but nothing to do -- - // and noone likes a divide-by-zero - return; - } - - if (sx < 0.0 || sy < 0.0 || - sw < 0.0 || sw > (double) imgSize.width || - sh < 0.0 || sh > (double) imgSize.height || - dw < 0.0 || dh < 0.0) { - // XXX - Unresolved spec issues here, for now return error. - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - Filter filter; - - if (CurrentState().imageSmoothingEnabled) - filter = mgfx::FILTER_LINEAR; - else - filter = mgfx::FILTER_POINT; - - mgfx::Rect bounds; - - if (NeedToDrawShadow()) { - bounds = mgfx::Rect(dx, dy, dw, dh); - bounds = mTarget->GetTransform().TransformBounds(bounds); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - DrawSurface(srcSurf, - mgfx::Rect(dx, dy, dw, dh), - mgfx::Rect(sx, sy, sw, sh), - DrawSurfaceOptions(filter), - DrawOptions(CurrentState().globalAlpha, UsedOperation())); - - RedrawUser(gfxRect(dx, dy, dw, dh)); -} - -void -CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op, - ErrorResult& error) -{ - CompositionOp comp_op; - -#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ - if (op.EqualsLiteral(cvsop)) \ - comp_op = OP_##op2d; - - CANVAS_OP_TO_GFX_OP("copy", SOURCE) - else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) - else CANVAS_OP_TO_GFX_OP("source-in", IN) - else CANVAS_OP_TO_GFX_OP("source-out", OUT) - else CANVAS_OP_TO_GFX_OP("source-over", OVER) - else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) - else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) - else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) - else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) - else CANVAS_OP_TO_GFX_OP("lighter", ADD) - else CANVAS_OP_TO_GFX_OP("xor", XOR) - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - else return; - -#undef CANVAS_OP_TO_GFX_OP - CurrentState().op = comp_op; -} - -void -CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op, - ErrorResult& error) -{ - CompositionOp comp_op = CurrentState().op; - -#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ - if (comp_op == OP_##op2d) \ - op.AssignLiteral(cvsop); - - CANVAS_OP_TO_GFX_OP("copy", SOURCE) - else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) - else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) - else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) - else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) - else CANVAS_OP_TO_GFX_OP("lighter", ADD) - else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) - else CANVAS_OP_TO_GFX_OP("source-in", IN) - else CANVAS_OP_TO_GFX_OP("source-out", OUT) - else CANVAS_OP_TO_GFX_OP("source-over", OVER) - else CANVAS_OP_TO_GFX_OP("xor", XOR) - else { - error.Throw(NS_ERROR_FAILURE); - } - -#undef CANVAS_OP_TO_GFX_OP -} - -void -CanvasRenderingContext2D::DrawWindow(nsIDOMWindow* window, double x, - double y, double w, double h, - const nsAString& bgColor, - uint32_t flags, ErrorResult& error) -{ - // protect against too-large surfaces that will cause allocation - // or overflow issues - if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)), - 0xffff)) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - EnsureTarget(); - // We can't allow web apps to call this until we fix at least the - // following potential security issues: - // -- rendering cross-domain IFRAMEs and then extracting the results - // -- rendering the user's theme and then extracting the results - // -- rendering native anonymous content (e.g., file input paths; - // scrollbars should be allowed) - if (!nsContentUtils::IsCallerChrome()) { - // not permitted to use DrawWindow - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return; - } - - // Flush layout updates - if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) { - nsContentUtils::FlushLayoutForTree(window); - } - - nsRefPtr presContext; - nsCOMPtr win = do_QueryInterface(window); - if (win) { - nsIDocShell* docshell = win->GetDocShell(); - if (docshell) { - docshell->GetPresContext(getter_AddRefs(presContext)); - } - } - if (!presContext) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nscolor backgroundColor; - if (!ParseColor(bgColor, &backgroundColor)) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x), - nsPresContext::CSSPixelsToAppUnits((float)y), - nsPresContext::CSSPixelsToAppUnits((float)w), - nsPresContext::CSSPixelsToAppUnits((float)h)); - uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | - nsIPresShell::RENDER_DOCUMENT_RELATIVE); - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { - renderDocFlags |= nsIPresShell::RENDER_CARET; - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { - renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | - nsIPresShell::RENDER_DOCUMENT_RELATIVE); - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) { - renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) { - renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; - } - - // gfxContext-over-Azure may modify the DrawTarget's transform, so - // save and restore it - Matrix matrix = mTarget->GetTransform(); - nsRefPtr thebes; - if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { - thebes = new gfxContext(mTarget); - } else { - nsRefPtr drawSurf; - GetThebesSurface(getter_AddRefs(drawSurf)); - thebes = new gfxContext(drawSurf); - } - thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, - matrix._22, matrix._31, matrix._32)); - nsCOMPtr shell = presContext->PresShell(); - unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); - mTarget->SetTransform(matrix); - - // note that x and y are coordinates in the document that - // we're drawing; x and y are drawn to 0,0 in current user - // space. - RedrawUser(gfxRect(0, 0, w, h)); -} - -void -CanvasRenderingContext2D::AsyncDrawXULElement(nsIDOMXULElement* elem, - double x, double y, - double w, double h, - const nsAString& bgColor, - uint32_t flags, - ErrorResult& error) -{ - // We can't allow web apps to call this until we fix at least the - // following potential security issues: - // -- rendering cross-domain IFRAMEs and then extracting the results - // -- rendering the user's theme and then extracting the results - // -- rendering native anonymous content (e.g., file input paths; - // scrollbars should be allowed) - if (!nsContentUtils::IsCallerChrome()) { - // not permitted to use DrawWindow - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return; - } - -#if 0 - nsCOMPtr loaderOwner = do_QueryInterface(elem); - if (!loaderOwner) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsRefPtr frameloader = loaderOwner->GetFrameLoader(); - if (!frameloader) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - PBrowserParent *child = frameloader->GetRemoteBrowser(); - if (!child) { - nsCOMPtr window = - do_GetInterface(frameloader->GetExistingDocShell()); - if (!window) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - return DrawWindow(window, x, y, w, h, bgColor, flags); - } - - // protect against too-large surfaces that will cause allocation - // or overflow issues - if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - bool flush = - (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0; - - uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { - renderDocFlags |= nsIPresShell::RENDER_CARET; - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { - renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; - } - - nsRect rect(nsPresContext::CSSPixelsToAppUnits(x), - nsPresContext::CSSPixelsToAppUnits(y), - nsPresContext::CSSPixelsToAppUnits(w), - nsPresContext::CSSPixelsToAppUnits(h)); - if (mIPC) { - PDocumentRendererParent *pdocrender = - child->SendPDocumentRendererConstructor(rect, - mThebes->CurrentMatrix(), - nsString(aBGColor), - renderDocFlags, flush, - nsIntSize(mWidth, mHeight)); - if (!pdocrender) - return NS_ERROR_FAILURE; - - DocumentRendererParent *docrender = - static_cast(pdocrender); - - docrender->SetCanvasContext(this, mThebes); - } -#endif -} - -// -// device pixel getting/setting -// - -void -CanvasRenderingContext2D::EnsureUnpremultiplyTable() { - if (sUnpremultiplyTable) - return; - - // Infallably alloc the unpremultiply table. - sUnpremultiplyTable = new uint8_t[256][256]; - - // It's important that the array be indexed first by alpha and then by rgb - // value. When we unpremultiply a pixel, we're guaranteed to do three - // lookups with the same alpha; indexing by alpha first makes it likely that - // those three lookups will be close to one another in memory, thus - // increasing the chance of a cache hit. - - // a == 0 case - for (uint32_t c = 0; c <= 255; c++) { - sUnpremultiplyTable[0][c] = c; - } - - for (int a = 1; a <= 255; a++) { - for (int c = 0; c <= 255; c++) { - sUnpremultiplyTable[a][c] = (uint8_t)((c * 255) / a); - } - } -} - - -already_AddRefed -CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, - double aSy, double aSw, - double aSh, ErrorResult& error) -{ - EnsureTarget(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return NULL; - } - - if (!mCanvasElement && !mDocShell) { - NS_ERROR("No canvas element and no docshell in GetImageData!!!"); - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return NULL; - } - - // Check only if we have a canvas element; if we were created with a docshell, - // then it's special internal use. - if (mCanvasElement && mCanvasElement->IsWriteOnly() && - !nsContentUtils::IsCallerChrome()) - { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return NULL; - } - - if (!NS_finite(aSx) || !NS_finite(aSy) || - !NS_finite(aSw) || !NS_finite(aSh)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return NULL; - } - - if (!aSw || !aSh) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return NULL; - } - - int32_t x = JS_DoubleToInt32(aSx); - int32_t y = JS_DoubleToInt32(aSy); - int32_t wi = JS_DoubleToInt32(aSw); - int32_t hi = JS_DoubleToInt32(aSh); - - // Handle negative width and height by flipping the rectangle over in the - // relevant direction. - uint32_t w, h; - if (aSw < 0) { - w = -wi; - x -= w; - } else { - w = wi; - } - if (aSh < 0) { - h = -hi; - y -= h; - } else { - h = hi; - } - - if (w == 0) { - w = 1; - } - if (h == 0) { - h = 1; - } - - JSObject* array; - error = GetImageDataArray(aCx, x, y, w, h, &array); - if (error.Failed()) { - return NULL; - } - MOZ_ASSERT(array); - - nsRefPtr imageData = new ImageData(w, h, *array); - return imageData.forget(); -} - -nsresult -CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, - int32_t aX, - int32_t aY, - uint32_t aWidth, - uint32_t aHeight, - JSObject** aRetval) -{ - MOZ_ASSERT(aWidth && aHeight); - - CheckedInt len = CheckedInt(aWidth) * aHeight * 4; - if (!len.isValid()) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - CheckedInt rightMost = CheckedInt(aX) + aWidth; - CheckedInt bottomMost = CheckedInt(aY) + aHeight; - - if (!rightMost.isValid() || !bottomMost.isValid()) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - JSObject* darray = JS_NewUint8ClampedArray(aCx, len.value()); - if (!darray) { - return NS_ERROR_OUT_OF_MEMORY; - } - - if (mZero) { - *aRetval = darray; - return NS_OK; - } - - uint8_t* data = JS_GetUint8ClampedArrayData(darray); - - IntRect srcRect(0, 0, mWidth, mHeight); - IntRect destRect(aX, aY, aWidth, aHeight); - - IntRect srcReadRect = srcRect.Intersect(destRect); - IntRect dstWriteRect = srcReadRect; - dstWriteRect.MoveBy(-aX, -aY); - - uint8_t* src = data; - uint32_t srcStride = aWidth * 4; - - RefPtr readback; - if (!srcReadRect.IsEmpty()) { - RefPtr snapshot = mTarget->Snapshot(); - if (snapshot) { - readback = snapshot->GetDataSurface(); - - srcStride = readback->Stride(); - src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4; - } - } - - // make sure sUnpremultiplyTable has been created - EnsureUnpremultiplyTable(); - - // NOTE! dst is the same as src, and this relies on reading - // from src and advancing that ptr before writing to dst. - // NOTE! I'm not sure that it is, I think this comment might have been - // inherited from Thebes canvas and is no longer true - uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; - - for (int32_t j = 0; j < dstWriteRect.height; ++j) { - for (int32_t i = 0; i < dstWriteRect.width; ++i) { - // XXX Is there some useful swizzle MMX we can use here? -#ifdef IS_LITTLE_ENDIAN - uint8_t b = *src++; - uint8_t g = *src++; - uint8_t r = *src++; - uint8_t a = *src++; -#else - uint8_t a = *src++; - uint8_t r = *src++; - uint8_t g = *src++; - uint8_t b = *src++; -#endif - // Convert to non-premultiplied color - *dst++ = sUnpremultiplyTable[a][r]; - *dst++ = sUnpremultiplyTable[a][g]; - *dst++ = sUnpremultiplyTable[a][b]; - *dst++ = a; - } - src += srcStride - (dstWriteRect.width * 4); - dst += (aWidth * 4) - (dstWriteRect.width * 4); - } - - *aRetval = darray; - return NS_OK; -} - -void -CanvasRenderingContext2D::EnsurePremultiplyTable() { - if (sPremultiplyTable) - return; - - // Infallably alloc the premultiply table. - sPremultiplyTable = new uint8_t[256][256]; - - // Like the unpremultiply table, it's important that we index the premultiply - // table with the alpha value as the first index to ensure good cache - // performance. - - for (int a = 0; a <= 255; a++) { - for (int c = 0; c <= 255; c++) { - sPremultiplyTable[a][c] = (a * c + 254) / 255; - } - } -} - -void -CanvasRenderingContext2D::EnsureErrorTarget() -{ - if (sErrorTarget) { - return; - } - - RefPtr errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(IntSize(1, 1), FORMAT_B8G8R8A8); - NS_ABORT_IF_FALSE(errorTarget, "Failed to allocate the error target!"); - - sErrorTarget = errorTarget; - NS_ADDREF(sErrorTarget); -} - -void -CanvasRenderingContext2D::FillRuleChanged() -{ - if (mPath) { - mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); - mPath = nullptr; - } -} - -void -CanvasRenderingContext2D::PutImageData(JSContext* cx, - ImageData& imageData, double dx, - double dy, ErrorResult& error) -{ - if (!FloatValidate(dx, dy)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return; - } - - dom::Uint8ClampedArray arr(imageData.GetDataObject()); - - error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), - imageData.Width(), imageData.Height(), - arr.Data(), arr.Length(), false, 0, 0, 0, 0); -} - -void -CanvasRenderingContext2D::PutImageData(JSContext* cx, - ImageData& imageData, double dx, - double dy, double dirtyX, - double dirtyY, double dirtyWidth, - double dirtyHeight, - ErrorResult& error) -{ - if (!FloatValidate(dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return; - } - - dom::Uint8ClampedArray arr(imageData.GetDataObject()); - - error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), - imageData.Width(), imageData.Height(), - arr.Data(), arr.Length(), true, - JS_DoubleToInt32(dirtyX), - JS_DoubleToInt32(dirtyY), - JS_DoubleToInt32(dirtyWidth), - JS_DoubleToInt32(dirtyHeight)); -} - -// void putImageData (in ImageData d, in float x, in float y); -// void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight); - -nsresult -CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h, - unsigned char *aData, uint32_t aDataLen, - bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY, - int32_t dirtyWidth, int32_t dirtyHeight) -{ - if (w == 0 || h == 0) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - IntRect dirtyRect; - IntRect imageDataRect(0, 0, w, h); - - if (hasDirtyRect) { - // fix up negative dimensions - if (dirtyWidth < 0) { - NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); - - CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth; - - if (!checkedDirtyX.isValid()) - return NS_ERROR_DOM_INDEX_SIZE_ERR; - - dirtyX = checkedDirtyX.value(); - dirtyWidth = -dirtyWidth; - } - - if (dirtyHeight < 0) { - NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); - - CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight; - - if (!checkedDirtyY.isValid()) - return NS_ERROR_DOM_INDEX_SIZE_ERR; - - dirtyY = checkedDirtyY.value(); - dirtyHeight = -dirtyHeight; - } - - // bound the dirty rect within the imageData rectangle - dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); - - if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) - return NS_OK; - } else { - dirtyRect = imageDataRect; - } - - dirtyRect.MoveBy(IntPoint(x, y)); - dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); - - if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { - return NS_OK; - } - - uint32_t len = w * h * 4; - if (aDataLen != len) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - nsRefPtr imgsurf = new gfxImageSurface(gfxIntSize(w, h), - gfxASurface::ImageFormatARGB32, - false); - if (!imgsurf || imgsurf->CairoStatus()) { - return NS_ERROR_FAILURE; - } - - // ensure premultiply table has been created - EnsurePremultiplyTable(); - - uint8_t *src = aData; - uint8_t *dst = imgsurf->Data(); - - for (uint32_t j = 0; j < h; j++) { - for (uint32_t i = 0; i < w; i++) { - uint8_t r = *src++; - uint8_t g = *src++; - uint8_t b = *src++; - uint8_t a = *src++; - - // Convert to premultiplied color (losslessly if the input came from getImageData) -#ifdef IS_LITTLE_ENDIAN - *dst++ = sPremultiplyTable[a][b]; - *dst++ = sPremultiplyTable[a][g]; - *dst++ = sPremultiplyTable[a][r]; - *dst++ = a; -#else - *dst++ = a; - *dst++ = sPremultiplyTable[a][r]; - *dst++ = sPremultiplyTable[a][g]; - *dst++ = sPremultiplyTable[a][b]; -#endif - } - } - - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } - - RefPtr sourceSurface = - mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), FORMAT_B8G8R8A8); - - - mTarget->CopySurface(sourceSurface, - IntRect(dirtyRect.x - x, dirtyRect.y - y, - dirtyRect.width, dirtyRect.height), - IntPoint(dirtyRect.x, dirtyRect.y)); - - Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface) -{ - EnsureTarget(); - if (!mThebesSurface) { - mThebesSurface = - gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); - - if (!mThebesSurface) { - return NS_ERROR_FAILURE; - } - } else { - // Normally GetThebesSurfaceForDrawTarget will handle the flush, when - // we're returning a cached ThebesSurface we need to flush here. - mTarget->Flush(); - } - - *surface = mThebesSurface; - NS_ADDREF(*surface); - - return NS_OK; -} - -static already_AddRefed -CreateImageData(JSContext* cx, CanvasRenderingContext2D* context, - uint32_t w, uint32_t h, ErrorResult& error) -{ - if (w == 0) - w = 1; - if (h == 0) - h = 1; - - CheckedInt len = CheckedInt(w) * h * 4; - if (!len.isValid()) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return NULL; - } - - // Create the fast typed array; it's initialized to 0 by default. - JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value()); - if (!darray) { - error.Throw(NS_ERROR_OUT_OF_MEMORY); - return NULL; - } - - nsRefPtr imageData = - new mozilla::dom::ImageData(w, h, *darray); - return imageData.forget(); -} - -already_AddRefed -CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw, - double sh, ErrorResult& error) -{ - if (!FloatValidate(sw, sh)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return NULL; - } - - if (!sw || !sh) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return NULL; - } - - int32_t wi = JS_DoubleToInt32(sw); - int32_t hi = JS_DoubleToInt32(sh); - - uint32_t w = NS_ABS(wi); - uint32_t h = NS_ABS(hi); - return mozilla::dom::CreateImageData(cx, this, w, h, error); -} - -already_AddRefed -CanvasRenderingContext2D::CreateImageData(JSContext* cx, - ImageData& imagedata, - ErrorResult& error) -{ - return mozilla::dom::CreateImageData(cx, this, imagedata.Width(), - imagedata.Height(), error); -} - -static uint8_t g2DContextLayerUserData; - -already_AddRefed -CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, - CanvasLayer *aOldLayer, - LayerManager *aManager) -{ - // Don't call EnsureTarget() ... if there isn't already a surface, then - // we have nothing to paint and there is no need to create a surface just - // to paint nothing. Also, EnsureTarget() can cause creation of a persistent - // layer manager which must NOT happen during a paint. - if (!mTarget || !IsTargetValid()) { - // No DidTransactionCallback will be received, so mark the context clean - // now so future invalidations will be dispatched. - MarkContextClean(); - return nullptr; - } - - mTarget->Flush(); - - if (!mResetLayer && aOldLayer) { - CanvasRenderingContext2DUserData* userData = - static_cast( - aOldLayer->GetUserData(&g2DContextLayerUserData)); - if (userData && userData->IsForContext(this)) { - NS_ADDREF(aOldLayer); - return aOldLayer; - } - } - - nsRefPtr canvasLayer = aManager->CreateCanvasLayer(); - if (!canvasLayer) { - NS_WARNING("CreateCanvasLayer returned null!"); - // No DidTransactionCallback will be received, so mark the context clean - // now so future invalidations will be dispatched. - MarkContextClean(); - return nullptr; - } - CanvasRenderingContext2DUserData *userData = nullptr; - // Make the layer tell us whenever a transaction finishes (including - // the current transaction), so we can clear our invalidation state and - // start invalidating again. We need to do this for all layers since - // callers of DrawWindow may be expecting to receive normal invalidation - // notifications after this paint. - - // The layer will be destroyed when we tear down the presentation - // (at the latest), at which time this userData will be destroyed, - // releasing the reference to the element. - // The userData will receive DidTransactionCallbacks, which flush the - // the invalidation state to indicate that the canvas is up to date. - userData = new CanvasRenderingContext2DUserData(this); - canvasLayer->SetDidTransactionCallback( - CanvasRenderingContext2DUserData::DidTransactionCallback, userData); - canvasLayer->SetUserData(&g2DContextLayerUserData, userData); - - CanvasLayer::Data data; - - data.mDrawTarget = mTarget; - data.mSize = nsIntSize(mWidth, mHeight); - - canvasLayer->Initialize(data); - uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; - canvasLayer->SetContentFlags(flags); - canvasLayer->Updated(); - - mResetLayer = false; - - return canvasLayer.forget(); -} - -void -CanvasRenderingContext2D::MarkContextClean() -{ - if (mInvalidateCount > 0) { - mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; - } - mIsEntireFrameInvalid = false; - mInvalidateCount = 0; -} - - -bool -CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) -{ - return !aManager->CanUseCanvasLayerForSize(gfxIntSize(mWidth, mHeight)); -} - -} -} - -DOMCI_DATA(TextMetrics, mozilla::dom::TextMetrics) -DOMCI_DATA(CanvasGradient, mozilla::dom::CanvasGradient) -DOMCI_DATA(CanvasPattern, mozilla::dom::CanvasPattern) -DOMCI_DATA(CanvasRenderingContext2D, mozilla::dom::CanvasRenderingContext2D) - +/* -*- 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 "base/basictypes.h" +#include "CanvasRenderingContext2D.h" + +#include "nsIDOMXULElement.h" + +#include "prenv.h" + +#include "nsIServiceManager.h" +#include "nsMathUtils.h" + +#include "nsContentUtils.h" + +#include "nsIDocument.h" +#include "nsHTMLCanvasElement.h" +#include "nsSVGEffects.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsIVariant.h" + +#include "nsIInterfaceRequestorUtils.h" +#include "nsIFrame.h" +#include "nsError.h" +#include "nsIScriptError.h" + +#include "nsCSSParser.h" +#include "mozilla/css/StyleRule.h" +#include "mozilla/css/Declaration.h" +#include "nsComputedDOMStyle.h" +#include "nsStyleSet.h" + +#include "nsPrintfCString.h" + +#include "nsReadableUtils.h" + +#include "nsColor.h" +#include "nsGfxCIID.h" +#include "nsIScriptSecurityManager.h" +#include "nsIDocShell.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeNode.h" +#include "nsIXPConnect.h" +#include "nsDisplayList.h" + +#include "nsTArray.h" + +#include "imgIEncoder.h" + +#include "gfxContext.h" +#include "gfxASurface.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxFont.h" +#include "gfxBlur.h" +#include "gfxUtils.h" +#include "gfxFontMissingGlyphs.h" + +#include "nsFrameManager.h" +#include "nsFrameLoader.h" +#include "nsBidi.h" +#include "nsBidiPresUtils.h" +#include "Layers.h" +#include "CanvasUtils.h" +#include "nsIMemoryReporter.h" +#include "nsStyleUtil.h" +#include "CanvasImageCache.h" + +#include + +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ipc/DocumentRendererParent.h" +#include "mozilla/ipc/PDocumentRendererParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/unused.h" +#include "nsCCUncollectableMarker.h" +#include "nsWrapperCacheInlines.h" +#include "nsJSUtils.h" +#include "XPCQuickStubs.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsHTMLImageElement.h" +#include "nsHTMLVideoElement.h" +#include "mozilla/dom/CanvasRenderingContext2DBinding.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::ipc; +using namespace mozilla::layers; + +namespace mgfx = mozilla::gfx; + +#define NS_TEXTMETRICSAZURE_PRIVATE_IID \ + {0x9793f9e7, 0x9dc1, 0x4e9c, {0x81, 0xc8, 0xfc, 0xa7, 0x14, 0xf4, 0x30, 0x79}} + +namespace mozilla { +namespace dom { + +static float kDefaultFontSize = 10.0; +static NS_NAMED_LITERAL_STRING(kDefaultFontName, "sans-serif"); +static NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); + +// Cap sigma to avoid overly large temp surfaces. +const Float SIGMA_MAX = 100; + +/* Memory reporter stuff */ +static nsIMemoryReporter *gCanvasAzureMemoryReporter = nullptr; +static int64_t gCanvasAzureMemoryUsed = 0; + +static int64_t GetCanvasAzureMemoryUsed() { + return gCanvasAzureMemoryUsed; +} + +// 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. +NS_MEMORY_REPORTER_IMPLEMENT(CanvasAzureMemory, + "canvas-2d-pixel-bytes", + KIND_OTHER, + UNITS_BYTES, + GetCanvasAzureMemoryUsed, + "Memory used by 2D canvases. Each canvas requires (width * height * 4) " + "bytes.") + +class CanvasRadialGradient : public CanvasGradient +{ +public: + CanvasRadialGradient(const Point &aBeginOrigin, Float aBeginRadius, + const Point &aEndOrigin, Float aEndRadius) + : CanvasGradient(RADIAL) + , mCenter1(aBeginOrigin) + , mCenter2(aEndOrigin) + , mRadius1(aBeginRadius) + , mRadius2(aEndRadius) + { + } + + Point mCenter1; + Point mCenter2; + Float mRadius1; + Float mRadius2; +}; + +class CanvasLinearGradient : public CanvasGradient +{ +public: + CanvasLinearGradient(const Point &aBegin, const Point &aEnd) + : CanvasGradient(LINEAR) + , mBegin(aBegin) + , mEnd(aEnd) + { + } + +protected: + friend class CanvasGeneralPattern; + + // Beginning of linear gradient. + Point mBegin; + // End of linear gradient. + Point mEnd; +}; + +// 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; + + CanvasGeneralPattern() : mPattern(nullptr) {} + ~CanvasGeneralPattern() + { + if (mPattern) { + mPattern->~Pattern(); + } + } + + 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, "ForStyle() should only be called once on CanvasGeneralPattern!"); + + const ContextState &state = aCtx->CurrentState(); + + if (state.StyleIsColor(aStyle)) { + mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle])); + } else if (state.gradientStyles[aStyle] && + state.gradientStyles[aStyle]->GetType() == CanvasGradient::LINEAR) { + CanvasLinearGradient *gradient = + static_cast(state.gradientStyles[aStyle].get()); + + mPattern = new (mLinearGradientPattern.addr()) + LinearGradientPattern(gradient->mBegin, gradient->mEnd, + gradient->GetGradientStopsForTarget(aRT)); + } else if (state.gradientStyles[aStyle] && + state.gradientStyles[aStyle]->GetType() == CanvasGradient::RADIAL) { + CanvasRadialGradient *gradient = + static_cast(state.gradientStyles[aStyle].get()); + + mPattern = new (mRadialGradientPattern.addr()) + RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, + gradient->mRadius2, 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::NOREPEAT) { + mode = EXTEND_CLAMP; + } else { + mode = EXTEND_REPEAT; + } + mPattern = new (mSurfacePattern.addr()) + SurfacePattern(state.patternStyles[aStyle]->mSurface, mode); + } + + return *mPattern; + } + + union { + AlignedStorage2 mColorPattern; + AlignedStorage2 mLinearGradientPattern; + AlignedStorage2 mRadialGradientPattern; + AlignedStorage2 mSurfacePattern; + }; + Pattern *mPattern; +}; + +/* This is an RAII based class that can be used as a drawtarget for + * operations that need a shadow drawn. It will automatically provide a + * temporary target when needed, and if so blend it back with a shadow. + * + * aBounds specifies the bounds of the drawing operation that will be + * drawn to the target, it is given in device space! This function will + * change aBounds to incorporate shadow bounds. If this is NULL the drawing + * operation will be assumed to cover an infinite rect. + */ +class AdjustedTarget +{ +public: + typedef CanvasRenderingContext2D::ContextState ContextState; + + AdjustedTarget(CanvasRenderingContext2D *ctx, + mgfx::Rect *aBounds = nullptr) + : mCtx(nullptr) + { + if (!ctx->NeedToDrawShadow()) { + mTarget = ctx->mTarget; + return; + } + mCtx = ctx; + + const ContextState &state = mCtx->CurrentState(); + + mSigma = state.shadowBlur / 2.0f; + + if (mSigma > SIGMA_MAX) { + mSigma = SIGMA_MAX; + } + + Matrix transform = mCtx->mTarget->GetTransform(); + + mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight); + + static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; + int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5); + + // We need to enlarge and possibly offset our temporary surface + // so that things outside of the canvas may cast shadows. + mTempRect.Inflate(Margin(blurRadius + NS_MAX(state.shadowOffset.x, 0), + blurRadius + NS_MAX(state.shadowOffset.y, 0), + blurRadius + NS_MAX(-state.shadowOffset.x, 0), + blurRadius + NS_MAX(-state.shadowOffset.y, 0))); + + if (aBounds) { + // 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. + aBounds->Inflate(Margin(blurRadius, blurRadius, + blurRadius, blurRadius)); + mTempRect = mTempRect.Intersect(*aBounds); + } + + mTempRect.ScaleRoundOut(1.0f); + + transform._31 -= mTempRect.x; + transform._32 -= mTempRect.y; + + mTarget = + mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)), + FORMAT_B8G8R8A8, mSigma); + + if (!mTarget) { + // XXX - Deal with the situation where our temp size is too big to + // fit in a texture. + mTarget = ctx->mTarget; + mCtx = nullptr; + } else { + mTarget->SetTransform(transform); + } + } + + ~AdjustedTarget() + { + if (!mCtx) { + return; + } + + RefPtr snapshot = mTarget->Snapshot(); + + mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), + Color::FromABGR(mCtx->CurrentState().shadowColor), + mCtx->CurrentState().shadowOffset, mSigma, + mCtx->CurrentState().op); + } + + DrawTarget* operator->() + { + return mTarget; + } + +private: + RefPtr mTarget; + CanvasRenderingContext2D *mCtx; + Float mSigma; + mgfx::Rect mTempRect; +}; + +NS_IMETHODIMP +CanvasGradient::AddColorStop(float offset, const nsAString& colorstr) +{ + if (!FloatValidate(offset) || offset < 0.0 || offset > 1.0) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + nsCSSValue value; + nsCSSParser parser; + if (!parser.ParseColorString(colorstr, nullptr, 0, value)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + nscolor color; + if (!nsRuleNode::ComputeColor(value, nullptr, nullptr, color)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + mStops = nullptr; + + GradientStop newStop; + + newStop.offset = offset; + newStop.color = Color::FromABGR(color); + + mRawStops.AppendElement(newStop); + + return NS_OK; +} + +NS_DEFINE_STATIC_IID_ACCESSOR(CanvasGradient, NS_CANVASGRADIENTAZURE_PRIVATE_IID) + +NS_IMPL_ADDREF(CanvasGradient) +NS_IMPL_RELEASE(CanvasGradient) + +NS_INTERFACE_MAP_BEGIN(CanvasGradient) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasGradient) + NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasGradient) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasGradient) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_DEFINE_STATIC_IID_ACCESSOR(CanvasPattern, NS_CANVASPATTERNAZURE_PRIVATE_IID) + +NS_IMPL_ADDREF(CanvasPattern) +NS_IMPL_RELEASE(CanvasPattern) + +NS_INTERFACE_MAP_BEGIN(CanvasPattern) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasPattern) + NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasPattern) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasPattern) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/** + ** TextMetrics + **/ +class TextMetrics : public nsIDOMTextMetrics +{ +public: + TextMetrics(float w) : width(w) { } + + virtual ~TextMetrics() { } + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXTMETRICSAZURE_PRIVATE_IID) + + NS_IMETHOD GetWidth(float* w) + { + *w = width; + return NS_OK; + } + + NS_DECL_ISUPPORTS + +private: + float width; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(TextMetrics, NS_TEXTMETRICSAZURE_PRIVATE_IID) + +NS_IMPL_ADDREF(TextMetrics) +NS_IMPL_RELEASE(TextMetrics) + +NS_INTERFACE_MAP_BEGIN(TextMetrics) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::TextMetrics) + NS_INTERFACE_MAP_ENTRY(nsIDOMTextMetrics) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TextMetrics) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +class CanvasRenderingContext2DUserData : public LayerUserData { +public: + CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext) + : mContext(aContext) + { + aContext->mUserDatas.AppendElement(this); + } + ~CanvasRenderingContext2DUserData() + { + if (mContext) { + mContext->mUserDatas.RemoveElement(this); + } + } + static void DidTransactionCallback(void* aData) + { + CanvasRenderingContext2DUserData* self = + static_cast(aData); + if (self->mContext) { + self->mContext->MarkContextClean(); + } + } + bool IsForContext(CanvasRenderingContext2D *aContext) + { + return mContext == aContext; + } + void Forget() + { + mContext = nullptr; + } + +private: + CanvasRenderingContext2D *mContext; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) +NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasRenderingContext2D, mCanvasElement) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) + if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { + 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->IsBlack(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) + return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); +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 impl + **/ + + +// Initialize our static variables. +uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; +uint8_t (*CanvasRenderingContext2D::sUnpremultiplyTable)[256] = nullptr; +uint8_t (*CanvasRenderingContext2D::sPremultiplyTable)[256] = nullptr; +DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; + + + +CanvasRenderingContext2D::CanvasRenderingContext2D() + : mZero(false), mOpaque(false), mResetLayer(true) + , mIPC(false) + , mIsEntireFrameInvalid(false) + , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) + , mInvalidateCount(0) +{ + sNumLivingContexts++; + SetIsDOMBinding(); +} + +CanvasRenderingContext2D::~CanvasRenderingContext2D() +{ + Reset(); + // Drop references from all CanvasRenderingContext2DUserData to this context + for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { + mUserDatas[i]->Forget(); + } + sNumLivingContexts--; + if (!sNumLivingContexts) { + delete[] sUnpremultiplyTable; + delete[] sPremultiplyTable; + sUnpremultiplyTable = nullptr; + sPremultiplyTable = nullptr; + NS_IF_RELEASE(sErrorTarget); + } +} + +JSObject* +CanvasRenderingContext2D::WrapObject(JSContext *cx, JSObject *scope, + bool *triedToWrap) +{ + return CanvasRenderingContext2DBinding::Wrap(cx, scope, this, triedToWrap); +} + +bool +CanvasRenderingContext2D::ParseColor(const nsAString& aString, + nscolor* aColor) +{ + nsIDocument* document = mCanvasElement + ? mCanvasElement->OwnerDoc() + : nullptr; + + // Pass the CSS Loader object to the parser, to allow parser error + // reports to include the outer window ID. + nsCSSParser parser(document ? document->CSSLoader() : nullptr); + nsCSSValue value; + if (!parser.ParseColorString(aString, nullptr, 0, value)) { + return false; + } + + if (value.GetUnit() == nsCSSUnit::eCSSUnit_Color) { + // if we already have a color we can just use it directly + *aColor = value.GetColorValue(); + } else { + // otherwise resolve it + nsIPresShell* presShell = GetPresShell(); + nsRefPtr parentContext; + if (mCanvasElement && mCanvasElement->IsInDoc()) { + // Inherit from the canvas element. + parentContext = nsComputedDOMStyle::GetStyleContextForElement( + mCanvasElement, nullptr, presShell); + } + + unused << nsRuleNode::ComputeColor( + value, presShell ? presShell->GetPresContext() : nullptr, parentContext, + *aColor); + } + 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; + } + + mTarget = nullptr; + + // Since the target changes the backing texture will change, and this will + // no longer be valid. + mThebesSurface = nullptr; + mIsEntireFrameInvalid = false; + mPredictManyRedrawCalls = false; + + return NS_OK; +} + +static void +WarnAboutUnexpectedStyle(nsHTMLCanvasElement* canvasElement) +{ + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, + "Canvas", + canvasElement ? canvasElement->OwnerDoc() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "UnexpectedCanvasVariantStyle"); +} + +void +CanvasRenderingContext2D::SetStyleFromString(const nsAString& str, + Style whichStyle) +{ + MOZ_ASSERT(!str.IsVoid()); + + nscolor color; + if (!ParseColor(str, &color)) { + return; + } + + CurrentState().SetColorStyle(whichStyle, color); +} + +nsISupports* +CanvasRenderingContext2D::GetStyleAsStringOrInterface(nsAString& aStr, + CanvasMultiGetterType& aType, + Style aWhichStyle) +{ + const ContextState &state = CurrentState(); + nsISupports* supports; + if (state.patternStyles[aWhichStyle]) { + aStr.SetIsVoid(true); + supports = state.patternStyles[aWhichStyle]; + aType = CMG_STYLE_PATTERN; + } else if (state.gradientStyles[aWhichStyle]) { + aStr.SetIsVoid(true); + supports = state.gradientStyles[aWhichStyle]; + aType = CMG_STYLE_GRADIENT; + } else { + StyleColorToString(state.colorStyles[aWhichStyle], aStr); + supports = nullptr; + aType = CMG_STYLE_STRING; + } + return supports; +} + +// static +void +CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) +{ + // 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) { + CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", + NS_GET_R(aColor), + NS_GET_G(aColor), + NS_GET_B(aColor)), + aStr); + } else { + CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", + NS_GET_R(aColor), + NS_GET_G(aColor), + NS_GET_B(aColor)), + aStr); + aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); + aStr.Append(')'); + } +} + +nsresult +CanvasRenderingContext2D::Redraw() +{ + if (mIsEntireFrameInvalid) { + return NS_OK; + } + + mIsEntireFrameInvalid = true; + + if (!mCanvasElement) { + NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); + return NS_OK; + } + + if (!mThebesSurface) + mThebesSurface = + gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); + mThebesSurface->MarkDirty(); + + nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); + + mCanvasElement->InvalidateCanvasContent(nullptr); + + return NS_OK; +} + +void +CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) +{ + ++mInvalidateCount; + + if (mIsEntireFrameInvalid) { + return; + } + + if (mPredictManyRedrawCalls || + mInvalidateCount > kCanvasMaxInvalidateCount) { + Redraw(); + return; + } + + if (!mCanvasElement) { + NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); + return; + } + + if (!mThebesSurface) + mThebesSurface = + gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); + mThebesSurface->MarkDirty(); + + nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); + + mCanvasElement->InvalidateCanvasContent(&r); +} + +void +CanvasRenderingContext2D::RedrawUser(const gfxRect& r) +{ + if (mIsEntireFrameInvalid) { + ++mInvalidateCount; + return; + } + + mgfx::Rect newr = + mTarget->GetTransform().TransformBounds(ToRect(r)); + Redraw(newr); +} + +void +CanvasRenderingContext2D::EnsureTarget() +{ + if (mTarget) { + return; + } + + // Check that the dimensions are sane + IntSize size(mWidth, mHeight); + if (size.width <= 0xFFFF && size.height <= 0xFFFF && + size.width >= 0 && size.height >= 0) { + SurfaceFormat format = GetSurfaceFormat(); + nsIDocument* ownerDoc = nullptr; + if (mCanvasElement) { + ownerDoc = mCanvasElement->OwnerDoc(); + } + + nsRefPtr layerManager = nullptr; + + if (ownerDoc) { + layerManager = + nsContentUtils::PersistentLayerManagerForDocument(ownerDoc); + } + + if (layerManager) { + mTarget = layerManager->CreateDrawTarget(size, format); + } else { + mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format); + } + } + + if (mTarget) { + if (gCanvasAzureMemoryReporter == nullptr) { + gCanvasAzureMemoryReporter = new NS_MEMORY_REPORTER_NAME(CanvasAzureMemory); + NS_RegisterMemoryReporter(gCanvasAzureMemoryReporter); + } + + gCanvasAzureMemoryUsed += mWidth * mHeight * 4; + JSContext* context = nsContentUtils::GetCurrentJSContext(); + if (context) { + JS_updateMallocCounter(context, mWidth * mHeight * 4); + } + + mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight))); + // Force a full layer transaction since we didn't have a layer before + // and now we might need one. + if (mCanvasElement) { + mCanvasElement->InvalidateCanvas(); + } + // Calling Redraw() tells our invalidation machinery that the entire + // canvas is already invalid, which can speed up future drawing. + Redraw(); + } else { + EnsureErrorTarget(); + mTarget = sErrorTarget; + } +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height) +{ + ClearTarget(); + + // Zero sized surfaces cause issues, so just go with 1x1. + if (height == 0 || width == 0) { + mZero = true; + mWidth = 1; + mHeight = 1; + } else { + mZero = false; + mWidth = width; + mHeight = height; + } + + return NS_OK; +} + +void +CanvasRenderingContext2D::ClearTarget() +{ + Reset(); + + mResetLayer = true; + + // set up the initial canvas defaults + mStyleStack.Clear(); + mPathBuilder = nullptr; + mPath = nullptr; + mDSPathBuilder = nullptr; + + 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); +} + +NS_IMETHODIMP +CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell, + gfxASurface *surface, + int32_t width, + int32_t height) +{ + mDocShell = shell; + mThebesSurface = surface; + + SetDimensions(width, height); + mTarget = gfxPlatform::GetPlatform()-> + CreateDrawTargetForSurface(surface, IntSize(width, height)); + if (!mTarget) { + EnsureErrorTarget(); + mTarget = sErrorTarget; + } + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetIsOpaque(bool isOpaque) +{ + if (isOpaque != mOpaque) { + mOpaque = isOpaque; + ClearTarget(); + } + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetIsIPC(bool isIPC) +{ + if (isIPC != mIPC) { + mIPC = isIPC; + ClearTarget(); + } + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, uint32_t aFlags) +{ + nsresult rv = NS_OK; + + EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } + + nsRefPtr surface; + + if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { + return NS_ERROR_FAILURE; + } + + nsRefPtr pat = new gfxPattern(surface); + + pat->SetFilter(aFilter); + pat->SetExtend(gfxPattern::EXTEND_PAD); + + gfxContext::GraphicsOperator op = ctx->CurrentOperator(); + if (mOpaque) + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + + // XXX I don't want to use PixelSnapped here, but layout doesn't guarantee + // pixel alignment for this stuff! + ctx->NewPath(); + ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat); + ctx->Fill(); + + if (mOpaque) + ctx->SetOperator(op); + + if (!(aFlags & RenderFlagPremultAlpha)) { + nsRefPtr curSurface = ctx->CurrentSurface(); + nsRefPtr gis = curSurface->GetAsImageSurface(); + NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!"); + + gfxUtils::UnpremultiplyImageSurface(gis); + } + + return rv; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::GetInputStream(const char *aMimeType, + const PRUnichar *aEncoderOptions, + nsIInputStream **aStream) +{ + EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } + + nsRefPtr surface; + + if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type="; + nsAutoArrayPtr conid(new (std::nothrow) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]); + + if (!conid) { + return NS_ERROR_OUT_OF_MEMORY; + } + + strcpy(conid, encoderPrefix); + strcat(conid, aMimeType); + + nsCOMPtr encoder = do_CreateInstance(conid); + if (!encoder) { + return NS_ERROR_FAILURE; + } + + nsAutoArrayPtr imageBuffer(new (std::nothrow) uint8_t[mWidth * mHeight * 4]); + if (!imageBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsRefPtr imgsurf = + new gfxImageSurface(imageBuffer.get(), + gfxIntSize(mWidth, mHeight), + mWidth * 4, + gfxASurface::ImageFormatARGB32); + + if (!imgsurf || imgsurf->CairoStatus()) { + return NS_ERROR_FAILURE; + } + + nsRefPtr ctx = new gfxContext(imgsurf); + + if (!ctx || ctx->HasError()) { + return NS_ERROR_FAILURE; + } + + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + ctx->SetSource(surface, gfxPoint(0, 0)); + ctx->Paint(); + + rv = encoder->InitFromData(imageBuffer.get(), + mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4, + imgIEncoder::INPUT_FORMAT_HOSTARGB, + nsDependentString(aEncoderOptions)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(encoder, aStream); +} + +SurfaceFormat +CanvasRenderingContext2D::GetSurfaceFormat() const +{ + return mOpaque ? FORMAT_B8G8R8X8 : FORMAT_B8G8R8A8; +} + +// +// state +// + +void +CanvasRenderingContext2D::Save() +{ + EnsureTarget(); + mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); + mStyleStack.SetCapacity(mStyleStack.Length() + 1); + mStyleStack.AppendElement(CurrentState()); +} + +void +CanvasRenderingContext2D::Restore() +{ + if (mStyleStack.Length() - 1 == 0) + return; + + TransformWillUpdate(); + + for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { + mTarget->PopClip(); + } + + mStyleStack.RemoveElementAt(mStyleStack.Length() - 1); + + mTarget->SetTransform(CurrentState().transform); +} + +// +// transformations +// + +void +CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error) +{ + if (!FloatValidate(x,y)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newMatrix = mTarget->GetTransform(); + mTarget->SetTransform(newMatrix.Scale(x, y)); +} + +void +CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error) +{ + if (!FloatValidate(angle)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + + Matrix rotation = Matrix::Rotation(angle); + mTarget->SetTransform(rotation * mTarget->GetTransform()); +} + +void +CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error) +{ + if (!FloatValidate(x,y)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newMatrix = mTarget->GetTransform(); + mTarget->SetTransform(newMatrix.Translate(x, y)); +} + +void +CanvasRenderingContext2D::Transform(double m11, double m12, double m21, + double m22, double dx, double dy, + ErrorResult& error) +{ + if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix matrix(m11, m12, m21, m22, dx, dy); + mTarget->SetTransform(matrix * mTarget->GetTransform()); +} + +void +CanvasRenderingContext2D::SetTransform(double m11, double m12, + double m21, double m22, + double dx, double dy, + ErrorResult& error) +{ + if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix matrix(m11, m12, m21, m22, dx, dy); + mTarget->SetTransform(matrix); +} + +JSObject* +MatrixToJSObject(JSContext* cx, const Matrix& matrix, ErrorResult& error) +{ + jsval elts[] = { + DOUBLE_TO_JSVAL(matrix._11), DOUBLE_TO_JSVAL(matrix._12), + DOUBLE_TO_JSVAL(matrix._21), DOUBLE_TO_JSVAL(matrix._22), + DOUBLE_TO_JSVAL(matrix._31), DOUBLE_TO_JSVAL(matrix._32) + }; + + // XXX Should we enter GetWrapper()'s compartment? + JSObject* obj = JS_NewArrayObject(cx, 6, elts); + if (!obj) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + } + return obj; +} + +bool +ObjectToMatrix(JSContext* cx, JSObject& obj, Matrix& matrix, ErrorResult& error) +{ + uint32_t length; + if (!JS_GetArrayLength(cx, &obj, &length) || length != 6) { + // Not an array-like thing or wrong size + error.Throw(NS_ERROR_INVALID_ARG); + return false; + } + + Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22, + &matrix._31, &matrix._32 }; + for (uint32_t i = 0; i < 6; ++i) { + jsval elt; + double d; + if (!JS_GetElement(cx, &obj, i, &elt)) { + error.Throw(NS_ERROR_FAILURE); + return false; + } + if (!CoerceDouble(elt, &d)) { + error.Throw(NS_ERROR_INVALID_ARG); + return false; + } + if (!FloatValidate(d)) { + // This is weird, but it's the behavior of SetTransform() + return false; + } + *elts[i] = Float(d); + } + return true; +} + +void +CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, + JSObject& currentTransform, + ErrorResult& error) +{ + EnsureTarget(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newCTM; + if (ObjectToMatrix(cx, currentTransform, newCTM, error)) { + mTarget->SetTransform(newCTM); + } +} + +JSObject* +CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, + ErrorResult& error) const +{ + return MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), error); +} + +void +CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, + JSObject& currentTransform, + ErrorResult& error) +{ + EnsureTarget(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newCTMInverse; + if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + if (newCTMInverse.Invert()) { + mTarget->SetTransform(newCTMInverse); + } + } +} + +JSObject* +CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, + ErrorResult& error) const +{ + if (!mTarget) { + return MatrixToJSObject(cx, Matrix(), error); + } + + Matrix ctm = mTarget->GetTransform(); + + if (!ctm.Invert()) { + double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); + ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); + } + + return MatrixToJSObject(cx, ctm, error); +} + +// +// colors +// + +void +CanvasRenderingContext2D::SetStyleFromJSValue(JSContext* cx, + JS::Value& value, + Style whichStyle) +{ + if (value.isString()) { + nsDependentJSString strokeStyle; + if (strokeStyle.init(cx, value.toString())) { + SetStyleFromString(strokeStyle, whichStyle); + } + return; + } + + if (value.isObject()) { + nsCOMPtr holder; + + CanvasGradient* gradient; + nsresult rv = xpc_qsUnwrapArg(cx, value, &gradient, + static_cast(getter_AddRefs(holder)), + &value); + if (NS_SUCCEEDED(rv)) { + SetStyleFromGradient(gradient, whichStyle); + return; + } + + CanvasPattern* pattern; + rv = xpc_qsUnwrapArg(cx, value, &pattern, + static_cast(getter_AddRefs(holder)), + &value); + if (NS_SUCCEEDED(rv)) { + SetStyleFromPattern(pattern, whichStyle); + return; + } + } + + WarnAboutUnexpectedStyle(mCanvasElement); +} + +static JS::Value +WrapStyle(JSContext* cx, JSObject* obj, + CanvasRenderingContext2D::CanvasMultiGetterType type, + nsAString& str, nsISupports* supports, ErrorResult& error) +{ + JS::Value v; + bool ok; + switch (type) { + case CanvasRenderingContext2D::CMG_STYLE_STRING: + { + ok = xpc::StringToJsval(cx, str, &v); + break; + } + case CanvasRenderingContext2D::CMG_STYLE_PATTERN: + case CanvasRenderingContext2D::CMG_STYLE_GRADIENT: + { + ok = dom::WrapObject(cx, obj, supports, &v); + break; + } + default: + MOZ_NOT_REACHED("unexpected CanvasMultiGetterType"); + } + if (!ok) { + error.Throw(NS_ERROR_FAILURE); + } + return v; +} + + +JS::Value +CanvasRenderingContext2D::GetStrokeStyle(JSContext* cx, + ErrorResult& error) +{ + nsString str; + CanvasMultiGetterType t; + nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_STROKE); + return WrapStyle(cx, GetWrapper(), t, str, supports, error); +} + +JS::Value +CanvasRenderingContext2D::GetFillStyle(JSContext* cx, + ErrorResult& error) +{ + nsString str; + CanvasMultiGetterType t; + nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_FILL); + return WrapStyle(cx, GetWrapper(), t, str, supports, error); +} + +void +CanvasRenderingContext2D::SetFillRule(const nsAString& aString) +{ + FillRule rule; + + if (aString.EqualsLiteral("evenodd")) + rule = FILL_EVEN_ODD; + else if (aString.EqualsLiteral("nonzero")) + rule = FILL_WINDING; + else + return; + + CurrentState().fillRule = rule; +} + +void +CanvasRenderingContext2D::GetFillRule(nsAString& aString) +{ + switch (CurrentState().fillRule) { + case FILL_WINDING: + aString.AssignLiteral("nonzero"); break; + case FILL_EVEN_ODD: + aString.AssignLiteral("evenodd"); break; + } +} +// +// gradients and patterns +// +already_AddRefed +CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1, + ErrorResult& aError) +{ + if (!FloatValidate(x0,y0,x1,y1)) { + aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsRefPtr grad = + new CanvasLinearGradient(Point(x0, y0), Point(x1, y1)); + + return grad.forget(); +} + +already_AddRefed +CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, + double x1, double y1, double r1, + ErrorResult& aError) +{ + if (!FloatValidate(x0,y0,r0,x1,y1,r1)) { + aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + if (r0 < 0.0 || r1 < 0.0) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + nsRefPtr grad = + new CanvasRadialGradient(Point(x0, y0), r0, Point(x1, y1), r1); + + return grad.forget(); +} + +already_AddRefed +CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, + const nsAString& repeat, + ErrorResult& error) +{ + CanvasPattern::RepeatMode repeatMode = + CanvasPattern::NOREPEAT; + + if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) { + repeatMode = CanvasPattern::REPEAT; + } else if (repeat.EqualsLiteral("repeat-x")) { + repeatMode = CanvasPattern::REPEATX; + } else if (repeat.EqualsLiteral("repeat-y")) { + repeatMode = CanvasPattern::REPEATY; + } else if (repeat.EqualsLiteral("no-repeat")) { + repeatMode = CanvasPattern::NOREPEAT; + } else { + error.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return NULL; + } + + Element* htmlElement; + if (element.IsHTMLCanvasElement()) { + nsHTMLCanvasElement* canvas = element.GetAsHTMLCanvasElement(); + htmlElement = canvas; + + nsIntSize size = canvas->GetSize(); + if (size.width == 0 || size.height == 0) { + error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return NULL; + } + + // Special case for Canvas, which could be an Azure canvas! + nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); + if (srcCanvas) { + // This might not be an Azure canvas! + RefPtr srcSurf = srcCanvas->GetSurfaceSnapshot(); + + nsRefPtr pat = + new CanvasPattern(srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false); + + return pat.forget(); + } + } else if (element.IsHTMLImageElement()) { + htmlElement = element.GetAsHTMLImageElement(); + } else { + htmlElement = element.GetAsHTMLVideoElement(); + } + + // The canvas spec says that createPattern should use the first frame + // of animated images + nsLayoutUtils::SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromElement(htmlElement, + nsLayoutUtils::SFE_WANT_FIRST_FRAME | nsLayoutUtils::SFE_WANT_NEW_SURFACE); + + if (!res.mSurface) { + error.Throw(NS_ERROR_NOT_AVAILABLE); + return NULL; + } + + // Ignore nullptr cairo surfaces! See bug 666312. + if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) { + return NULL; + } + + EnsureTarget(); + RefPtr srcSurf = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); + + nsRefPtr pat = + new CanvasPattern(srcSurf, repeatMode, res.mPrincipal, + res.mIsWriteOnly, res.mCORSUsed); + + return pat.forget(); +} + +// +// shadows +// +void +CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor) +{ + nscolor color; + if (!ParseColor(shadowColor, &color)) { + return; + } + + CurrentState().shadowColor = color; +} + +// +// rects +// + +void +CanvasRenderingContext2D::ClearRect(double x, double y, double w, + double h) +{ + if (!FloatValidate(x,y,w,h) || !mTarget) { + return; + } + + mTarget->ClearRect(mgfx::Rect(x, y, w, h)); + + RedrawUser(gfxRect(x, y, w, h)); +} + +void +CanvasRenderingContext2D::FillRect(double x, double y, double w, + double h) +{ + if (!FloatValidate(x,y,w,h)) { + return; + } + + const ContextState &state = CurrentState(); + + if (state.patternStyles[STYLE_FILL]) { + CanvasPattern::RepeatMode repeat = + state.patternStyles[STYLE_FILL]->mRepeat; + // In the FillRect case repeat modes are easy to deal with. + bool limitx = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATY; + bool limity = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATX; + + IntSize patternSize = + state.patternStyles[STYLE_FILL]->mSurface->GetSize(); + + // We always need to execute painting for non-over operators, even if + // we end up with w/h = 0. + if (limitx) { + if (x < 0) { + w += x; + if (w < 0) { + w = 0; + } + + x = 0; + } + if (x + w > patternSize.width) { + w = patternSize.width - x; + if (w < 0) { + w = 0; + } + } + } + if (limity) { + if (y < 0) { + h += y; + if (h < 0) { + h = 0; + } + + y = 0; + } + if (y + h > patternSize.height) { + h = patternSize.height - y; + if (h < 0) { + h = 0; + } + } + } + } + + mgfx::Rect bounds; + + EnsureTarget(); + if (NeedToDrawShadow()) { + bounds = mgfx::Rect(x, y, w, h); + bounds = mTarget->GetTransform().TransformBounds(bounds); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + FillRect(mgfx::Rect(x, y, w, h), + CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), + DrawOptions(state.globalAlpha, UsedOperation())); + + RedrawUser(gfxRect(x, y, w, h)); +} + +void +CanvasRenderingContext2D::StrokeRect(double x, double y, double w, + double h) +{ + if (!FloatValidate(x,y,w,h)) { + return; + } + + const ContextState &state = CurrentState(); + + mgfx::Rect bounds; + + if (!w && !h) { + return; + } + + EnsureTarget(); + if (!IsTargetValid()) { + return; + } + + if (NeedToDrawShadow()) { + bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f, + w + state.lineWidth, h + state.lineWidth); + bounds = mTarget->GetTransform().TransformBounds(bounds); + } + + if (!h) { + CapStyle cap = CAP_BUTT; + if (state.lineJoin == JOIN_ROUND) { + cap = CAP_ROUND; + } + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + StrokeLine(Point(x, y), Point(x + w, y), + CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + cap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, UsedOperation())); + return; + } + + if (!w) { + CapStyle cap = CAP_BUTT; + if (state.lineJoin == JOIN_ROUND) { + cap = CAP_ROUND; + } + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + StrokeLine(Point(x, y), Point(x, y + h), + CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + cap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, UsedOperation())); + return; + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + StrokeRect(mgfx::Rect(x, y, w, h), + CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, UsedOperation())); + + Redraw(); +} + +// +// path bits +// + +void +CanvasRenderingContext2D::BeginPath() +{ + mPath = nullptr; + mPathBuilder = nullptr; + mDSPathBuilder = nullptr; + mPathTransformWillUpdate = false; +} + +void +CanvasRenderingContext2D::Fill() +{ + EnsureUserSpacePath(); + + if (!mPath) { + return; + } + + mgfx::Rect bounds; + + if (NeedToDrawShadow()) { + bounds = mPath->GetBounds(mTarget->GetTransform()); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + Fill(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), + DrawOptions(CurrentState().globalAlpha, UsedOperation())); + + Redraw(); +} + +void +CanvasRenderingContext2D::Stroke() +{ + EnsureUserSpacePath(); + + if (!mPath) { + return; + } + + const ContextState &state = CurrentState(); + + StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), state.dash.Elements(), + state.dashOffset); + + mgfx::Rect bounds; + if (NeedToDrawShadow()) { + bounds = + mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + Stroke(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); + + Redraw(); +} + +void +CanvasRenderingContext2D::Clip() +{ + EnsureUserSpacePath(); + + if (!mPath) { + return; + } + + mTarget->PushClip(mPath); + CurrentState().clipsPushed.push_back(mPath); +} + +void +CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, + double y2, double radius, + ErrorResult& error) +{ + if (!FloatValidate(x1, y1, x2, y2, radius)) { + return; + } + + if (radius < 0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + EnsureWritablePath(); + + // Current point in user space! + Point p0; + if (mPathBuilder) { + p0 = mPathBuilder->CurrentPoint(); + } else { + Matrix invTransform = mTarget->GetTransform(); + if (!invTransform.Invert()) { + return; + } + + p0 = invTransform * mDSPathBuilder->CurrentPoint(); + } + + Point p1(x1, y1); + Point p2(x2, y2); + + // Execute these calculations in double precision to avoid cumulative + // rounding errors. + double dir, a2, b2, c2, cosx, sinx, d, anx, any, + bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; + bool anticlockwise; + + if (p0 == p1 || p1 == p2 || radius == 0) { + LineTo(p1.x, p1.y); + return; + } + + // Check for colinearity + dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); + if (dir == 0) { + LineTo(p1.x, p1.y); + return; + } + + + // XXX - Math for this code was already available from the non-azure code + // and would be well tested. Perhaps converting to bezier directly might + // be more efficient longer run. + a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); + b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); + c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); + cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); + + sinx = sqrt(1 - cosx*cosx); + d = radius / ((1 - cosx) / sinx); + + anx = (x1-p0.x) / sqrt(a2); + any = (y1-p0.y) / sqrt(a2); + bnx = (x1-x2) / sqrt(b2); + bny = (y1-y2) / sqrt(b2); + x3 = x1 - anx*d; + y3 = y1 - any*d; + x4 = x1 - bnx*d; + y4 = y1 - bny*d; + anticlockwise = (dir < 0); + cx = x3 + any*radius*(anticlockwise ? 1 : -1); + cy = y3 - anx*radius*(anticlockwise ? 1 : -1); + angle0 = atan2((y3-cy), (x3-cx)); + angle1 = atan2((y4-cy), (x4-cx)); + + + LineTo(x3, y3); + + Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); +} + +void +CanvasRenderingContext2D::Arc(double x, double y, double r, + double startAngle, double endAngle, + bool anticlockwise, ErrorResult& error) +{ + if (!FloatValidate(x, y, r, startAngle, endAngle)) { + return; + } + + if (r < 0.0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + EnsureWritablePath(); + + ArcToBezier(this, Point(x, y), r, startAngle, endAngle, anticlockwise); +} + +void +CanvasRenderingContext2D::Rect(double x, double y, double w, double h) +{ + if (!FloatValidate(x, y, w, h)) { + return; + } + + EnsureWritablePath(); + + if (mPathBuilder) { + mPathBuilder->MoveTo(Point(x, y)); + mPathBuilder->LineTo(Point(x + w, y)); + mPathBuilder->LineTo(Point(x + w, y + h)); + mPathBuilder->LineTo(Point(x, y + h)); + mPathBuilder->Close(); + } else { + mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y)); + mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y)); + mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h)); + mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h)); + mDSPathBuilder->Close(); + } +} + +void +CanvasRenderingContext2D::EnsureWritablePath() +{ + if (mDSPathBuilder) { + return; + } + + FillRule fillRule = CurrentState().fillRule; + + if (mPathBuilder) { + if (mPathTransformWillUpdate) { + mPath = mPathBuilder->Finish(); + mDSPathBuilder = + mPath->TransformedCopyToBuilder(mPathToDS, fillRule); + mPath = nullptr; + mPathBuilder = nullptr; + mPathTransformWillUpdate = false; + } + return; + } + + EnsureTarget(); + if (!mPath) { + NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); + mPathBuilder = mTarget->CreatePathBuilder(fillRule); + } else if (!mPathTransformWillUpdate) { + mPathBuilder = mPath->CopyToBuilder(fillRule); + } else { + mDSPathBuilder = + mPath->TransformedCopyToBuilder(mPathToDS, fillRule); + mPathTransformWillUpdate = false; + } +} + +void +CanvasRenderingContext2D::EnsureUserSpacePath(bool aCommitTransform /* = true */) +{ + FillRule fillRule = CurrentState().fillRule; + + if (!mPath && !mPathBuilder && !mDSPathBuilder) { + EnsureTarget(); + mPathBuilder = mTarget->CreatePathBuilder(fillRule); + } + + if (mPathBuilder) { + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (aCommitTransform && + mPath && + mPathTransformWillUpdate) { + mDSPathBuilder = + mPath->TransformedCopyToBuilder(mPathToDS, fillRule); + mPath = nullptr; + mPathTransformWillUpdate = false; + } + + if (mDSPathBuilder) { + RefPtr dsPath; + dsPath = mDSPathBuilder->Finish(); + mDSPathBuilder = nullptr; + + Matrix inverse = mTarget->GetTransform(); + if (!inverse.Invert()) { + NS_WARNING("Could not invert transform"); + return; + } + + mPathBuilder = + dsPath->TransformedCopyToBuilder(inverse, fillRule); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (mPath && mPath->GetFillRule() != fillRule) { + mPathBuilder = mPath->CopyToBuilder(fillRule); + mPath = mPathBuilder->Finish(); + } + + NS_ASSERTION(mPath, "mPath should exist"); +} + +void +CanvasRenderingContext2D::TransformWillUpdate() +{ + EnsureTarget(); + + // Store the matrix that would transform the current path to device + // space. + if (mPath || mPathBuilder) { + if (!mPathTransformWillUpdate) { + // If the transform has already been updated, but a device space builder + // has not been created yet mPathToDS contains the right transform to + // transform the current mPath into device space. + // We should leave it alone. + mPathToDS = mTarget->GetTransform(); + } + mPathTransformWillUpdate = true; + } +} + +// +// text +// + +/** + * Helper function for SetFont that creates a style rule for the given font. + * @param aFont The CSS font string + * @param aNode The canvas element + * @param aResult Pointer in which to place the new style rule. + * @remark Assumes all pointer arguments are non-null. + */ +static nsresult +CreateFontStyleRule(const nsAString& aFont, + nsINode* aNode, + StyleRule** aResult) +{ + nsRefPtr rule; + bool changed; + + nsIPrincipal* principal = aNode->NodePrincipal(); + nsIDocument* document = aNode->OwnerDoc(); + + nsIURI* docURL = document->GetDocumentURI(); + nsIURI* baseURL = document->GetDocBaseURI(); + + // Pass the CSS Loader object to the parser, to allow parser error reports + // to include the outer window ID. + nsCSSParser parser(document->CSSLoader()); + + nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, + principal, getter_AddRefs(rule)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL, + principal, rule->GetDeclaration(), &changed, + false); + if (NS_FAILED(rv)) + return rv; + + rv = parser.ParseProperty(eCSSProperty_line_height, + NS_LITERAL_STRING("normal"), docURL, baseURL, + principal, rule->GetDeclaration(), &changed, + false); + if (NS_FAILED(rv)) { + return rv; + } + + rule->RuleMatched(); + + rule.forget(aResult); + return NS_OK; +} + +void +CanvasRenderingContext2D::SetFont(const nsAString& font, + ErrorResult& error) +{ + /* + * If font is defined with relative units (e.g. ems) and the parent + * style context changes in between calls, setting the font to the + * same value as previous could result in a different computed value, + * so we cannot have the optimization where we check if the new font + * string is equal to the old one. + */ + + if (!mCanvasElement && !mDocShell) { + NS_WARNING("Canvas element must be non-null or a docshell must be provided"); + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsIPresShell* presShell = GetPresShell(); + if (!presShell) { + error.Throw(NS_ERROR_FAILURE); + return; + } + nsIDocument* document = presShell->GetDocument(); + + nsRefPtr rule; + error = CreateFontStyleRule(font, document, getter_AddRefs(rule)); + + if (error.Failed()) { + return; + } + + css::Declaration *declaration = rule->GetDeclaration(); + // The easiest way to see whether we got a syntax error or whether + // we got 'inherit' or 'initial' is to look at font-size-adjust, + // which the shorthand resets to either 'none' or + // '-moz-system-font'. + // We know the declaration is not !important, so we can use + // GetNormalBlock(). + const nsCSSValue *fsaVal = + declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust); + if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None && + fsaVal->GetUnit() != eCSSUnit_System_Font)) { + // We got an all-property value or a syntax error. The spec says + // this value must be ignored. + return; + } + + nsTArray< nsCOMPtr > rules; + rules.AppendElement(rule); + + nsStyleSet* styleSet = presShell->StyleSet(); + + // have to get a parent style context for inherit-like relative + // values (2em, bolder, etc.) + nsRefPtr parentContext; + + if (mCanvasElement && mCanvasElement->IsInDoc()) { + // inherit from the canvas element + parentContext = nsComputedDOMStyle::GetStyleContextForElement( + mCanvasElement, + nullptr, + presShell); + } else { + // otherwise inherit from default (10px sans-serif) + nsRefPtr parentRule; + error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"), + document, + getter_AddRefs(parentRule)); + + if (error.Failed()) { + return; + } + + nsTArray< nsCOMPtr > parentRules; + parentRules.AppendElement(parentRule); + parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules); + } + + if (!parentContext) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsRefPtr sc = + styleSet->ResolveStyleForRules(parentContext, rules); + if (!sc) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + const nsStyleFont* fontStyle = sc->GetStyleFont(); + + NS_ASSERTION(fontStyle, "Could not obtain font style"); + + nsIAtom* language = sc->GetStyleFont()->mLanguage; + if (!language) { + language = presShell->GetPresContext()->GetLanguageFromCharset(); + } + + // use CSS pixels instead of dev pixels to avoid being affected by page zoom + const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel(); + // un-zoom the font size to avoid being affected by text-only zoom + // + // Purposely ignore the font size that respects the user's minimum + // font preference (fontStyle->mFont.size) in favor of the computed + // size (fontStyle->mSize). See + // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. + const nscoord fontSize = nsStyleFont::UnZoomText(parentContext->PresContext(), fontStyle->mSize); + + bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview || + presShell->GetPresContext()->Type() == nsPresContext::eContext_Print); + + gfxFontStyle style(fontStyle->mFont.style, + fontStyle->mFont.weight, + fontStyle->mFont.stretch, + NSAppUnitsToFloatPixels(fontSize, float(aupcp)), + language, + fontStyle->mFont.sizeAdjust, + fontStyle->mFont.systemFont, + printerFont, + fontStyle->mFont.languageOverride); + + fontStyle->mFont.AddFontFeaturesToStyle(&style); + + CurrentState().fontGroup = + gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, + &style, + presShell->GetPresContext()->GetUserFontSet()); + NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); + + // The font getter is required to be reserialized based on what we + // parsed (including having line-height removed). (Older drafts of + // the spec required font sizes be converted to pixels, but that no + // longer seems to be required.) + declaration->GetValue(eCSSProperty_font, CurrentState().font); +} + +void +CanvasRenderingContext2D::SetTextAlign(const nsAString& ta) +{ + if (ta.EqualsLiteral("start")) + CurrentState().textAlign = TEXT_ALIGN_START; + else if (ta.EqualsLiteral("end")) + CurrentState().textAlign = TEXT_ALIGN_END; + else if (ta.EqualsLiteral("left")) + CurrentState().textAlign = TEXT_ALIGN_LEFT; + else if (ta.EqualsLiteral("right")) + CurrentState().textAlign = TEXT_ALIGN_RIGHT; + else if (ta.EqualsLiteral("center")) + CurrentState().textAlign = TEXT_ALIGN_CENTER; +} + +void +CanvasRenderingContext2D::GetTextAlign(nsAString& ta) +{ + switch (CurrentState().textAlign) + { + case TEXT_ALIGN_START: + ta.AssignLiteral("start"); + break; + case TEXT_ALIGN_END: + ta.AssignLiteral("end"); + break; + case TEXT_ALIGN_LEFT: + ta.AssignLiteral("left"); + break; + case TEXT_ALIGN_RIGHT: + ta.AssignLiteral("right"); + break; + case TEXT_ALIGN_CENTER: + ta.AssignLiteral("center"); + break; + } +} + +void +CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb) +{ + if (tb.EqualsLiteral("top")) + CurrentState().textBaseline = TEXT_BASELINE_TOP; + else if (tb.EqualsLiteral("hanging")) + CurrentState().textBaseline = TEXT_BASELINE_HANGING; + else if (tb.EqualsLiteral("middle")) + CurrentState().textBaseline = TEXT_BASELINE_MIDDLE; + else if (tb.EqualsLiteral("alphabetic")) + CurrentState().textBaseline = TEXT_BASELINE_ALPHABETIC; + else if (tb.EqualsLiteral("ideographic")) + CurrentState().textBaseline = TEXT_BASELINE_IDEOGRAPHIC; + else if (tb.EqualsLiteral("bottom")) + CurrentState().textBaseline = TEXT_BASELINE_BOTTOM; +} + +void +CanvasRenderingContext2D::GetTextBaseline(nsAString& tb) +{ + switch (CurrentState().textBaseline) + { + case TEXT_BASELINE_TOP: + tb.AssignLiteral("top"); + break; + case TEXT_BASELINE_HANGING: + tb.AssignLiteral("hanging"); + break; + case TEXT_BASELINE_MIDDLE: + tb.AssignLiteral("middle"); + break; + case TEXT_BASELINE_ALPHABETIC: + tb.AssignLiteral("alphabetic"); + break; + case TEXT_BASELINE_IDEOGRAPHIC: + tb.AssignLiteral("ideographic"); + break; + case TEXT_BASELINE_BOTTOM: + tb.AssignLiteral("bottom"); + break; + } +} + +/* + * Helper function that replaces the whitespace characters in a string + * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, + * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE + * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). + * @param str The string whose whitespace characters to replace. + */ +static inline void +TextReplaceWhitespaceCharacters(nsAutoString& str) +{ + str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", PRUnichar(' ')); +} + +void +CanvasRenderingContext2D::FillText(const nsAString& text, double x, + double y, + const Optional& maxWidth, + ErrorResult& error) +{ + error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL, nullptr); +} + +void +CanvasRenderingContext2D::StrokeText(const nsAString& text, double x, + double y, + const Optional& maxWidth, + ErrorResult& error) +{ + error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE, nullptr); +} + +already_AddRefed +CanvasRenderingContext2D::MeasureText(const nsAString& rawText, + ErrorResult& error) +{ + float width; + Optional maxWidth; + error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TEXT_DRAW_OPERATION_MEASURE, &width); + if (error.Failed()) { + return NULL; + } + + nsRefPtr textMetrics = new TextMetrics(width); + + return textMetrics.forget(); +} + +/** + * Used for nsBidiPresUtils::ProcessText + */ +struct NS_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor +{ + typedef CanvasRenderingContext2D::ContextState ContextState; + + virtual void SetText(const PRUnichar* text, int32_t length, nsBidiDirection direction) + { + mFontgrp->UpdateFontList(); // ensure user font generation is current + mTextRun = mFontgrp->MakeTextRun(text, + length, + mThebes, + mAppUnitsPerDevPixel, + direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); + } + + virtual nscoord GetWidth() + { + gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, + mTextRun->GetLength(), + mDoMeasureBoundingBox ? + gfxFont::TIGHT_INK_EXTENTS : + gfxFont::LOOSE_INK_EXTENTS, + mThebes, + nullptr); + + // this only measures the height; the total width is gotten from the + // the return value of ProcessText. + if (mDoMeasureBoundingBox) { + textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); + mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); + } + + return NSToCoordRound(textRunMetrics.mAdvanceWidth); + } + + virtual void DrawText(nscoord xOffset, nscoord width) + { + gfxPoint point = mPt; + point.x += xOffset; + + // offset is given in terms of left side of string + if (mTextRun->IsRightToLeft()) { + // Bug 581092 - don't use rounded pixel width to advance to + // right-hand end of run, because this will cause different + // glyph positioning for LTR vs RTL drawing of the same + // glyph string on OS X and DWrite where textrun widths may + // involve fractional pixels. + gfxTextRun::Metrics textRunMetrics = + mTextRun->MeasureText(0, + mTextRun->GetLength(), + mDoMeasureBoundingBox ? + gfxFont::TIGHT_INK_EXTENTS : + gfxFont::LOOSE_INK_EXTENTS, + mThebes, + nullptr); + point.x += textRunMetrics.mAdvanceWidth; + // old code was: + // point.x += width * mAppUnitsPerDevPixel; + // TODO: restore this if/when we move to fractional coords + // throughout the text layout process + } + + uint32_t numRuns; + const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); + const uint32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel; + const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); + Point baselineOrigin = + Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); + + float advanceSum = 0; + + mCtx->EnsureTarget(); + for (uint32_t c = 0; c < numRuns; c++) { + gfxFont *font = runs[c].mFont; + uint32_t endRun = 0; + if (c + 1 < numRuns) { + endRun = runs[c + 1].mCharacterOffset; + } else { + endRun = mTextRun->GetLength(); + } + + const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); + + RefPtr scaledFont = + gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); + + if (!scaledFont) { + // This can occur when something switched DirectWrite off. + return; + } + + GlyphBuffer buffer; + + std::vector glyphBuf; + + for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { + Glyph newGlyph; + if (glyphs[i].IsSimpleGlyph()) { + newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); + if (mTextRun->IsRightToLeft()) { + newGlyph.mPosition.x = baselineOrigin.x - advanceSum - + glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; + } else { + newGlyph.mPosition.x = baselineOrigin.x + advanceSum; + } + newGlyph.mPosition.y = baselineOrigin.y; + advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; + glyphBuf.push_back(newGlyph); + continue; + } + + if (!glyphs[i].GetGlyphCount()) { + continue; + } + + gfxTextRun::DetailedGlyph *detailedGlyphs = + mTextRun->GetDetailedGlyphs(i); + + if (glyphs[i].IsMissing()) { + float xpos; + float advance = detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; + if (mTextRun->IsRightToLeft()) { + xpos = baselineOrigin.x - advanceSum - advance; + } else { + xpos = baselineOrigin.x + advanceSum; + } + advanceSum += advance; + + // default-ignorable characters will have zero advance width. + // we don't draw a hexbox for them, just leave them invisible + if (advance > 0) { + // for now, we use gfxFontMissingGlyphs to draw the hexbox; + // some day we should replace this with a direct Azure version + + // get the DrawTarget's transform, so we can apply it to the + // thebes context for gfxFontMissingGlyphs + Matrix matrix = mCtx->mTarget->GetTransform(); + nsRefPtr thebes; + if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { + // XXX See bug 808288 comment 5 - Bas says: + // This is a little tricky, potentially this could go wrong if + // we fell back to a Cairo context because of for example + // extremely large Canvas size. Cairo content is technically + // -not- supported, but SupportsAzureContent would return true + // as the browser uses D2D content. + // I'm thinking Cairo content will be good enough to do + // DrawMissingGlyph though. + thebes = new gfxContext(mCtx->mTarget); + } else { + nsRefPtr drawSurf; + mCtx->GetThebesSurface(getter_AddRefs(drawSurf)); + thebes = new gfxContext(drawSurf); + } + thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, + matrix._22, matrix._31, matrix._32)); + + gfxFloat height = font->GetMetrics().maxAscent; + gfxRect glyphRect(xpos, baselineOrigin.y - height, + advance, height); + gfxFontMissingGlyphs::DrawMissingGlyph(thebes, glyphRect, + detailedGlyphs[0].mGlyphID); + + mCtx->mTarget->SetTransform(matrix); + } + continue; + } + + for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { + newGlyph.mIndex = detailedGlyphs[c].mGlyphID; + if (mTextRun->IsRightToLeft()) { + newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - + advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; + } else { + newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; + } + newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; + glyphBuf.push_back(newGlyph); + advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; + } + } + + if (!glyphBuf.size()) { + // This may happen for glyph runs for a 0 size font. + continue; + } + + buffer.mGlyphs = &glyphBuf.front(); + buffer.mNumGlyphs = glyphBuf.size(); + + Rect bounds = mCtx->mTarget->GetTransform(). + TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y, + mBoundingBox.width, mBoundingBox.height)); + if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL) { + AdjustedTarget(mCtx, &bounds)-> + FillGlyphs(scaledFont, buffer, + CanvasGeneralPattern(). + ForStyle(mCtx, CanvasRenderingContext2D::STYLE_FILL, mCtx->mTarget), + DrawOptions(mState->globalAlpha, mCtx->UsedOperation())); + } else if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE) { + RefPtr path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget); + + const ContextState& state = *mState; + AdjustedTarget(mCtx, &bounds)-> + Stroke(path, CanvasGeneralPattern(). + ForStyle(mCtx, CanvasRenderingContext2D::STYLE_STROKE, mCtx->mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, mCtx->UsedOperation())); + + } + } + } + + // current text run + nsAutoPtr mTextRun; + + // pointer to a screen reference context used to measure text and such + nsRefPtr mThebes; + + // Pointer to the draw target we should fill our text to + CanvasRenderingContext2D *mCtx; + + // position of the left side of the string, alphabetic baseline + gfxPoint mPt; + + // current font + gfxFontGroup* mFontgrp; + + // dev pixel conversion factor + uint32_t mAppUnitsPerDevPixel; + + // operation (fill or stroke) + CanvasRenderingContext2D::TextDrawOperation mOp; + + // context state + ContextState *mState; + + // union of bounding boxes of all runs, needed for shadows + gfxRect mBoundingBox; + + // true iff the bounding box should be measured + bool mDoMeasureBoundingBox; +}; + +nsresult +CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, + float aX, + float aY, + const Optional& aMaxWidth, + TextDrawOperation aOp, + float* aWidth) +{ + nsresult rv; + + if (!FloatValidate(aX, aY) || + (aMaxWidth.WasPassed() && !FloatValidate(aMaxWidth.Value()))) + return NS_ERROR_DOM_SYNTAX_ERR; + + // spec isn't clear on what should happen if aMaxWidth <= 0, so + // treat it as an invalid argument + // technically, 0 should be an invalid value as well, but 0 is the default + // arg, and there is no way to tell if the default was used + if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0) + return NS_ERROR_INVALID_ARG; + + if (!mCanvasElement && !mDocShell) { + NS_WARNING("Canvas element must be non-null or a docshell must be provided"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr presShell = GetPresShell(); + if (!presShell) + return NS_ERROR_FAILURE; + + nsIDocument* document = presShell->GetDocument(); + + // replace all the whitespace characters with U+0020 SPACE + nsAutoString textToDraw(aRawText); + TextReplaceWhitespaceCharacters(textToDraw); + + // for now, default to ltr if not in doc + bool isRTL = false; + + if (mCanvasElement && mCanvasElement->IsInDoc()) { + // try to find the closest context + nsRefPtr canvasStyle = + nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, + nullptr, + presShell); + if (!canvasStyle) { + return NS_ERROR_FAILURE; + } + + isRTL = canvasStyle->GetStyleVisibility()->mDirection == + NS_STYLE_DIRECTION_RTL; + } else { + isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; + } + + gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); + NS_ASSERTION(currentFontStyle, "font group is null"); + + if (currentFontStyle->GetStyle()->size == 0.0F) { + if (aWidth) { + *aWidth = 0; + } + return NS_OK; + } + + const ContextState &state = CurrentState(); + + // This is only needed to know if we can know the drawing bounding box easily. + bool doDrawShadow = aOp == TEXT_DRAW_OPERATION_FILL && NeedToDrawShadow(); + + CanvasBidiProcessor processor; + + GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); + processor.mPt = gfxPoint(aX, aY); + processor.mThebes = + new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); + + // If we don't have a target then we don't have a transform. A target won't + // be needed in the case where we're measuring the text size. This allows + // to avoid creating a target if it's only being used to measure text sizes. + if (mTarget) { + Matrix matrix = mTarget->GetTransform(); + processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); + } + processor.mCtx = this; + processor.mOp = aOp; + processor.mBoundingBox = gfxRect(0, 0, 0, 0); + processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid; + processor.mState = &CurrentState(); + processor.mFontgrp = currentFontStyle; + + nscoord totalWidthCoord; + + // calls bidi algo twice since it needs the full text width and the + // bounding boxes before rendering anything + nsBidi bidiEngine; + rv = nsBidiPresUtils::ProcessText(textToDraw.get(), + textToDraw.Length(), + isRTL ? NSBIDI_RTL : NSBIDI_LTR, + presShell->GetPresContext(), + processor, + nsBidiPresUtils::MODE_MEASURE, + nullptr, + 0, + &totalWidthCoord, + &bidiEngine); + if (NS_FAILED(rv)) { + return rv; + } + + float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; + if (aWidth) { + *aWidth = totalWidth; + } + + // if only measuring, don't need to do any more work + if (aOp==TEXT_DRAW_OPERATION_MEASURE) { + return NS_OK; + } + + // offset pt.x based on text align + gfxFloat anchorX; + + if (state.textAlign == TEXT_ALIGN_CENTER) { + anchorX = .5; + } else if (state.textAlign == TEXT_ALIGN_LEFT || + (!isRTL && state.textAlign == TEXT_ALIGN_START) || + (isRTL && state.textAlign == TEXT_ALIGN_END)) { + anchorX = 0; + } else { + anchorX = 1; + } + + processor.mPt.x -= anchorX * totalWidth; + + // offset pt.y based on text baseline + processor.mFontgrp->UpdateFontList(); // ensure user font generation is current + NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts"); + const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics(); + + gfxFloat anchorY; + + switch (state.textBaseline) + { + case TEXT_BASELINE_HANGING: + // fall through; best we can do with the information available + case TEXT_BASELINE_TOP: + anchorY = fontMetrics.emAscent; + break; + case TEXT_BASELINE_MIDDLE: + anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; + break; + case TEXT_BASELINE_IDEOGRAPHIC: + // fall through; best we can do with the information available + case TEXT_BASELINE_ALPHABETIC: + anchorY = 0; + break; + case TEXT_BASELINE_BOTTOM: + anchorY = -fontMetrics.emDescent; + break; + default: + MOZ_NOT_REACHED("unexpected TextBaseline"); + } + + processor.mPt.y += anchorY; + + // correct bounding box to get it to be the correct size/position + processor.mBoundingBox.width = totalWidth; + processor.mBoundingBox.MoveBy(processor.mPt); + + processor.mPt.x *= processor.mAppUnitsPerDevPixel; + processor.mPt.y *= processor.mAppUnitsPerDevPixel; + + EnsureTarget(); + Matrix oldTransform = mTarget->GetTransform(); + // if text is over aMaxWidth, then scale the text horizontally such that its + // width is precisely aMaxWidth + if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && + totalWidth > aMaxWidth.Value()) { + Matrix newTransform = oldTransform; + + // Translate so that the anchor point is at 0,0, then scale and then + // translate back. + newTransform.Translate(aX, 0); + newTransform.Scale(aMaxWidth.Value() / totalWidth, 1); + newTransform.Translate(-aX, 0); + /* we do this to avoid an ICE in the android compiler */ + Matrix androidCompilerBug = newTransform; + mTarget->SetTransform(androidCompilerBug); + } + + // save the previous bounding box + gfxRect boundingBox = processor.mBoundingBox; + + // don't ever need to measure the bounding box twice + processor.mDoMeasureBoundingBox = false; + + rv = nsBidiPresUtils::ProcessText(textToDraw.get(), + textToDraw.Length(), + isRTL ? NSBIDI_RTL : NSBIDI_LTR, + presShell->GetPresContext(), + processor, + nsBidiPresUtils::MODE_DRAW, + nullptr, + 0, + nullptr, + &bidiEngine); + + + mTarget->SetTransform(oldTransform); + + if (aOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL && + !doDrawShadow) { + RedrawUser(boundingBox); + return NS_OK; + } + + Redraw(); + return NS_OK; +} + +gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle() +{ + // use lazy initilization for the font group since it's rather expensive + if (!CurrentState().fontGroup) { + ErrorResult err; + SetFont(kDefaultFontStyle, err); + if (err.Failed()) { + gfxFontStyle style; + style.size = kDefaultFontSize; + CurrentState().fontGroup = + gfxPlatform::GetPlatform()->CreateFontGroup(kDefaultFontName, + &style, + nullptr); + if (CurrentState().fontGroup) { + CurrentState().font = kDefaultFontStyle; + } else { + NS_ERROR("Default canvas font is invalid"); + } + } + + } + + return CurrentState().fontGroup; +} + +// +// line caps/joins +// + +void +CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle) +{ + CapStyle cap; + + if (capstyle.EqualsLiteral("butt")) { + cap = CAP_BUTT; + } else if (capstyle.EqualsLiteral("round")) { + cap = CAP_ROUND; + } else if (capstyle.EqualsLiteral("square")) { + cap = CAP_SQUARE; + } else { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + return; + } + + CurrentState().lineCap = cap; +} + +void +CanvasRenderingContext2D::GetLineCap(nsAString& capstyle) +{ + switch (CurrentState().lineCap) { + case CAP_BUTT: + capstyle.AssignLiteral("butt"); + break; + case CAP_ROUND: + capstyle.AssignLiteral("round"); + break; + case CAP_SQUARE: + capstyle.AssignLiteral("square"); + break; + } +} + +void +CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle) +{ + JoinStyle j; + + if (joinstyle.EqualsLiteral("round")) { + j = JOIN_ROUND; + } else if (joinstyle.EqualsLiteral("bevel")) { + j = JOIN_BEVEL; + } else if (joinstyle.EqualsLiteral("miter")) { + j = JOIN_MITER_OR_BEVEL; + } else { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + return; + } + + CurrentState().lineJoin = j; +} + +void +CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error) +{ + switch (CurrentState().lineJoin) { + case JOIN_ROUND: + joinstyle.AssignLiteral("round"); + break; + case JOIN_BEVEL: + joinstyle.AssignLiteral("bevel"); + break; + case JOIN_MITER_OR_BEVEL: + joinstyle.AssignLiteral("miter"); + break; + default: + error.Throw(NS_ERROR_FAILURE); + } +} + +void +CanvasRenderingContext2D::SetMozDash(JSContext* cx, + const JS::Value& mozDash, + ErrorResult& error) +{ + FallibleTArray dash; + error = JSValToDashArray(cx, mozDash, dash); + if (!error.Failed()) { + ContextState& state = CurrentState(); + state.dash = dash; + if (state.dash.IsEmpty()) { + state.dashOffset = 0; + } + } +} + +JS::Value +CanvasRenderingContext2D::GetMozDash(JSContext* cx, ErrorResult& error) +{ + JS::Value mozDash; + error = DashArrayToJSVal(CurrentState().dash, cx, &mozDash); + return mozDash; +} + +void +CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) +{ + if (!FloatValidate(mozDashOffset)) { + return; + } + + ContextState& state = CurrentState(); + if (!state.dash.IsEmpty()) { + state.dashOffset = mozDashOffset; + } +} + +bool +CanvasRenderingContext2D::IsPointInPath(double x, double y) +{ + if (!FloatValidate(x,y)) { + return false; + } + + EnsureUserSpacePath(false); + if (!mPath) { + return false; + } + if (mPathTransformWillUpdate) { + return mPath->ContainsPoint(Point(x, y), mPathToDS); + } + return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); +} + +bool +CanvasRenderingContext2D::MozIsPointInStroke(double x, double y) +{ + if (!FloatValidate(x,y)) { + return false; + } + + EnsureUserSpacePath(false); + if (!mPath) { + return false; + } + + const ContextState &state = CurrentState(); + + StrokeOptions strokeOptions(state.lineWidth, + state.lineJoin, + state.lineCap, + state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset); + + if (mPathTransformWillUpdate) { + return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS); + } + return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); +} + +// +// image +// + +// drawImage(in HTMLImageElement image, in float dx, in float dy); +// -- render image from 0,0 at dx,dy top-left coords +// drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh); +// -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh +// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); +// -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas + +// If only dx and dy are passed in then optional_argc should be 0. If only +// dx, dy, dw and dh are passed in then optional_argc should be 2. The only +// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh +// are all passed in. + +void +CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, + double sx, double sy, double sw, + double sh, double dx, double dy, + double dw, double dh, + uint8_t optional_argc, + ErrorResult& error) +{ + MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6); + + RefPtr srcSurf; + gfxIntSize imgSize; + + Element* element; + + EnsureTarget(); + if (image.IsHTMLCanvasElement()) { + nsHTMLCanvasElement* canvas = image.GetAsHTMLCanvasElement(); + element = canvas; + nsIntSize size = canvas->GetSize(); + if (size.width == 0 || size.height == 0) { + error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + // Special case for Canvas, which could be an Azure canvas! + nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); + if (srcCanvas == this) { + // Self-copy. + srcSurf = mTarget->Snapshot(); + imgSize = gfxIntSize(mWidth, mHeight); + } else if (srcCanvas) { + // This might not be an Azure canvas! + srcSurf = srcCanvas->GetSurfaceSnapshot(); + + if (srcSurf) { + if (mCanvasElement) { + // Do security check here. + CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, + element->NodePrincipal(), + canvas->IsWriteOnly(), + false); + } + imgSize = gfxIntSize(srcSurf->GetSize().width, srcSurf->GetSize().height); + } + } + } else { + if (image.IsHTMLImageElement()) { + nsHTMLImageElement* img = image.GetAsHTMLImageElement(); + element = img; + } else { + nsHTMLVideoElement* video = image.GetAsHTMLVideoElement(); + element = video; + } + + gfxASurface* imgsurf = + CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); + if (imgsurf) { + srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, imgsurf); + } + } + + if (!srcSurf) { + // The canvas spec says that drawImage should draw the first frame + // of animated images + uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME; + nsLayoutUtils::SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromElement(element, sfeFlags); + + if (!res.mSurface) { + // Spec says to silently do nothing if the element is still loading. + if (!res.mIsStillLoading) { + error.Throw(NS_ERROR_NOT_AVAILABLE); + } + return; + } + + // Ignore cairo surfaces that are bad! See bug 666312. + if (res.mSurface->CairoStatus()) { + return; + } + + imgSize = res.mSize; + + if (mCanvasElement) { + CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, + res.mPrincipal, res.mIsWriteOnly, + res.mCORSUsed); + } + + if (res.mImageRequest) { + CanvasImageCache::NotifyDrawImage(element, mCanvasElement, + res.mImageRequest, res.mSurface, imgSize); + } + + srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); + } + + if (optional_argc == 0) { + sx = sy = 0.0; + dw = sw = (double) imgSize.width; + dh = sh = (double) imgSize.height; + } else if (optional_argc == 2) { + sx = sy = 0.0; + sw = (double) imgSize.width; + sh = (double) imgSize.height; + } + + if (sw == 0.0 || sh == 0.0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + if (dw == 0.0 || dh == 0.0) { + // not really failure, but nothing to do -- + // and noone likes a divide-by-zero + return; + } + + if (sx < 0.0 || sy < 0.0 || + sw < 0.0 || sw > (double) imgSize.width || + sh < 0.0 || sh > (double) imgSize.height || + dw < 0.0 || dh < 0.0) { + // XXX - Unresolved spec issues here, for now return error. + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + Filter filter; + + if (CurrentState().imageSmoothingEnabled) + filter = mgfx::FILTER_LINEAR; + else + filter = mgfx::FILTER_POINT; + + mgfx::Rect bounds; + + if (NeedToDrawShadow()) { + bounds = mgfx::Rect(dx, dy, dw, dh); + bounds = mTarget->GetTransform().TransformBounds(bounds); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + DrawSurface(srcSurf, + mgfx::Rect(dx, dy, dw, dh), + mgfx::Rect(sx, sy, sw, sh), + DrawSurfaceOptions(filter), + DrawOptions(CurrentState().globalAlpha, UsedOperation())); + + RedrawUser(gfxRect(dx, dy, dw, dh)); +} + +void +CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op, + ErrorResult& error) +{ + CompositionOp comp_op; + +#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ + if (op.EqualsLiteral(cvsop)) \ + comp_op = OP_##op2d; + + CANVAS_OP_TO_GFX_OP("copy", SOURCE) + else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) + else CANVAS_OP_TO_GFX_OP("source-in", IN) + else CANVAS_OP_TO_GFX_OP("source-out", OUT) + else CANVAS_OP_TO_GFX_OP("source-over", OVER) + else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) + else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) + else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) + else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) + else CANVAS_OP_TO_GFX_OP("lighter", ADD) + else CANVAS_OP_TO_GFX_OP("xor", XOR) + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + else return; + +#undef CANVAS_OP_TO_GFX_OP + CurrentState().op = comp_op; +} + +void +CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op, + ErrorResult& error) +{ + CompositionOp comp_op = CurrentState().op; + +#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ + if (comp_op == OP_##op2d) \ + op.AssignLiteral(cvsop); + + CANVAS_OP_TO_GFX_OP("copy", SOURCE) + else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) + else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) + else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) + else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) + else CANVAS_OP_TO_GFX_OP("lighter", ADD) + else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) + else CANVAS_OP_TO_GFX_OP("source-in", IN) + else CANVAS_OP_TO_GFX_OP("source-out", OUT) + else CANVAS_OP_TO_GFX_OP("source-over", OVER) + else CANVAS_OP_TO_GFX_OP("xor", XOR) + else { + error.Throw(NS_ERROR_FAILURE); + } + +#undef CANVAS_OP_TO_GFX_OP +} + +void +CanvasRenderingContext2D::DrawWindow(nsIDOMWindow* window, double x, + double y, double w, double h, + const nsAString& bgColor, + uint32_t flags, ErrorResult& error) +{ + // protect against too-large surfaces that will cause allocation + // or overflow issues + if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)), + 0xffff)) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + EnsureTarget(); + // We can't allow web apps to call this until we fix at least the + // following potential security issues: + // -- rendering cross-domain IFRAMEs and then extracting the results + // -- rendering the user's theme and then extracting the results + // -- rendering native anonymous content (e.g., file input paths; + // scrollbars should be allowed) + if (!nsContentUtils::IsCallerChrome()) { + // not permitted to use DrawWindow + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + // Flush layout updates + if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) { + nsContentUtils::FlushLayoutForTree(window); + } + + nsRefPtr presContext; + nsCOMPtr win = do_QueryInterface(window); + if (win) { + nsIDocShell* docshell = win->GetDocShell(); + if (docshell) { + docshell->GetPresContext(getter_AddRefs(presContext)); + } + } + if (!presContext) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nscolor backgroundColor; + if (!ParseColor(bgColor, &backgroundColor)) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x), + nsPresContext::CSSPixelsToAppUnits((float)y), + nsPresContext::CSSPixelsToAppUnits((float)w), + nsPresContext::CSSPixelsToAppUnits((float)h)); + uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | + nsIPresShell::RENDER_DOCUMENT_RELATIVE); + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { + renderDocFlags |= nsIPresShell::RENDER_CARET; + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { + renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | + nsIPresShell::RENDER_DOCUMENT_RELATIVE); + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) { + renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) { + renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; + } + + // gfxContext-over-Azure may modify the DrawTarget's transform, so + // save and restore it + Matrix matrix = mTarget->GetTransform(); + nsRefPtr thebes; + if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { + thebes = new gfxContext(mTarget); + } else { + nsRefPtr drawSurf; + GetThebesSurface(getter_AddRefs(drawSurf)); + thebes = new gfxContext(drawSurf); + } + thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, + matrix._22, matrix._31, matrix._32)); + nsCOMPtr shell = presContext->PresShell(); + unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); + mTarget->SetTransform(matrix); + + // note that x and y are coordinates in the document that + // we're drawing; x and y are drawn to 0,0 in current user + // space. + RedrawUser(gfxRect(0, 0, w, h)); +} + +void +CanvasRenderingContext2D::AsyncDrawXULElement(nsIDOMXULElement* elem, + double x, double y, + double w, double h, + const nsAString& bgColor, + uint32_t flags, + ErrorResult& error) +{ + // We can't allow web apps to call this until we fix at least the + // following potential security issues: + // -- rendering cross-domain IFRAMEs and then extracting the results + // -- rendering the user's theme and then extracting the results + // -- rendering native anonymous content (e.g., file input paths; + // scrollbars should be allowed) + if (!nsContentUtils::IsCallerChrome()) { + // not permitted to use DrawWindow + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + +#if 0 + nsCOMPtr loaderOwner = do_QueryInterface(elem); + if (!loaderOwner) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsRefPtr frameloader = loaderOwner->GetFrameLoader(); + if (!frameloader) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + PBrowserParent *child = frameloader->GetRemoteBrowser(); + if (!child) { + nsCOMPtr window = + do_GetInterface(frameloader->GetExistingDocShell()); + if (!window) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + return DrawWindow(window, x, y, w, h, bgColor, flags); + } + + // protect against too-large surfaces that will cause allocation + // or overflow issues + if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + bool flush = + (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0; + + uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { + renderDocFlags |= nsIPresShell::RENDER_CARET; + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { + renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; + } + + nsRect rect(nsPresContext::CSSPixelsToAppUnits(x), + nsPresContext::CSSPixelsToAppUnits(y), + nsPresContext::CSSPixelsToAppUnits(w), + nsPresContext::CSSPixelsToAppUnits(h)); + if (mIPC) { + PDocumentRendererParent *pdocrender = + child->SendPDocumentRendererConstructor(rect, + mThebes->CurrentMatrix(), + nsString(aBGColor), + renderDocFlags, flush, + nsIntSize(mWidth, mHeight)); + if (!pdocrender) + return NS_ERROR_FAILURE; + + DocumentRendererParent *docrender = + static_cast(pdocrender); + + docrender->SetCanvasContext(this, mThebes); + } +#endif +} + +// +// device pixel getting/setting +// + +void +CanvasRenderingContext2D::EnsureUnpremultiplyTable() { + if (sUnpremultiplyTable) + return; + + // Infallably alloc the unpremultiply table. + sUnpremultiplyTable = new uint8_t[256][256]; + + // It's important that the array be indexed first by alpha and then by rgb + // value. When we unpremultiply a pixel, we're guaranteed to do three + // lookups with the same alpha; indexing by alpha first makes it likely that + // those three lookups will be close to one another in memory, thus + // increasing the chance of a cache hit. + + // a == 0 case + for (uint32_t c = 0; c <= 255; c++) { + sUnpremultiplyTable[0][c] = c; + } + + for (int a = 1; a <= 255; a++) { + for (int c = 0; c <= 255; c++) { + sUnpremultiplyTable[a][c] = (uint8_t)((c * 255) / a); + } + } +} + + +already_AddRefed +CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, + double aSy, double aSw, + double aSh, ErrorResult& error) +{ + EnsureTarget(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return NULL; + } + + if (!mCanvasElement && !mDocShell) { + NS_ERROR("No canvas element and no docshell in GetImageData!!!"); + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return NULL; + } + + // Check only if we have a canvas element; if we were created with a docshell, + // then it's special internal use. + if (mCanvasElement && mCanvasElement->IsWriteOnly() && + !nsContentUtils::IsCallerChrome()) + { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return NULL; + } + + if (!NS_finite(aSx) || !NS_finite(aSy) || + !NS_finite(aSw) || !NS_finite(aSh)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return NULL; + } + + if (!aSw || !aSh) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return NULL; + } + + int32_t x = JS_DoubleToInt32(aSx); + int32_t y = JS_DoubleToInt32(aSy); + int32_t wi = JS_DoubleToInt32(aSw); + int32_t hi = JS_DoubleToInt32(aSh); + + // Handle negative width and height by flipping the rectangle over in the + // relevant direction. + uint32_t w, h; + if (aSw < 0) { + w = -wi; + x -= w; + } else { + w = wi; + } + if (aSh < 0) { + h = -hi; + y -= h; + } else { + h = hi; + } + + if (w == 0) { + w = 1; + } + if (h == 0) { + h = 1; + } + + JSObject* array; + error = GetImageDataArray(aCx, x, y, w, h, &array); + if (error.Failed()) { + return NULL; + } + MOZ_ASSERT(array); + + nsRefPtr imageData = new ImageData(w, h, *array); + return imageData.forget(); +} + +nsresult +CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, + int32_t aX, + int32_t aY, + uint32_t aWidth, + uint32_t aHeight, + JSObject** aRetval) +{ + MOZ_ASSERT(aWidth && aHeight); + + CheckedInt len = CheckedInt(aWidth) * aHeight * 4; + if (!len.isValid()) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + CheckedInt rightMost = CheckedInt(aX) + aWidth; + CheckedInt bottomMost = CheckedInt(aY) + aHeight; + + if (!rightMost.isValid() || !bottomMost.isValid()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + JSObject* darray = JS_NewUint8ClampedArray(aCx, len.value()); + if (!darray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mZero) { + *aRetval = darray; + return NS_OK; + } + + uint8_t* data = JS_GetUint8ClampedArrayData(darray); + + IntRect srcRect(0, 0, mWidth, mHeight); + IntRect destRect(aX, aY, aWidth, aHeight); + + IntRect srcReadRect = srcRect.Intersect(destRect); + IntRect dstWriteRect = srcReadRect; + dstWriteRect.MoveBy(-aX, -aY); + + uint8_t* src = data; + uint32_t srcStride = aWidth * 4; + + RefPtr readback; + if (!srcReadRect.IsEmpty()) { + RefPtr snapshot = mTarget->Snapshot(); + if (snapshot) { + readback = snapshot->GetDataSurface(); + + srcStride = readback->Stride(); + src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4; + } + } + + // make sure sUnpremultiplyTable has been created + EnsureUnpremultiplyTable(); + + // NOTE! dst is the same as src, and this relies on reading + // from src and advancing that ptr before writing to dst. + // NOTE! I'm not sure that it is, I think this comment might have been + // inherited from Thebes canvas and is no longer true + uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; + + for (int32_t j = 0; j < dstWriteRect.height; ++j) { + for (int32_t i = 0; i < dstWriteRect.width; ++i) { + // XXX Is there some useful swizzle MMX we can use here? +#ifdef IS_LITTLE_ENDIAN + uint8_t b = *src++; + uint8_t g = *src++; + uint8_t r = *src++; + uint8_t a = *src++; +#else + uint8_t a = *src++; + uint8_t r = *src++; + uint8_t g = *src++; + uint8_t b = *src++; +#endif + // Convert to non-premultiplied color + *dst++ = sUnpremultiplyTable[a][r]; + *dst++ = sUnpremultiplyTable[a][g]; + *dst++ = sUnpremultiplyTable[a][b]; + *dst++ = a; + } + src += srcStride - (dstWriteRect.width * 4); + dst += (aWidth * 4) - (dstWriteRect.width * 4); + } + + *aRetval = darray; + return NS_OK; +} + +void +CanvasRenderingContext2D::EnsurePremultiplyTable() { + if (sPremultiplyTable) + return; + + // Infallably alloc the premultiply table. + sPremultiplyTable = new uint8_t[256][256]; + + // Like the unpremultiply table, it's important that we index the premultiply + // table with the alpha value as the first index to ensure good cache + // performance. + + for (int a = 0; a <= 255; a++) { + for (int c = 0; c <= 255; c++) { + sPremultiplyTable[a][c] = (a * c + 254) / 255; + } + } +} + +void +CanvasRenderingContext2D::EnsureErrorTarget() +{ + if (sErrorTarget) { + return; + } + + RefPtr errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(IntSize(1, 1), FORMAT_B8G8R8A8); + NS_ABORT_IF_FALSE(errorTarget, "Failed to allocate the error target!"); + + sErrorTarget = errorTarget; + NS_ADDREF(sErrorTarget); +} + +void +CanvasRenderingContext2D::FillRuleChanged() +{ + if (mPath) { + mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); + mPath = nullptr; + } +} + +void +CanvasRenderingContext2D::PutImageData(JSContext* cx, + ImageData& imageData, double dx, + double dy, ErrorResult& error) +{ + if (!FloatValidate(dx, dy)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + dom::Uint8ClampedArray arr(imageData.GetDataObject()); + + error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), + imageData.Width(), imageData.Height(), + arr.Data(), arr.Length(), false, 0, 0, 0, 0); +} + +void +CanvasRenderingContext2D::PutImageData(JSContext* cx, + ImageData& imageData, double dx, + double dy, double dirtyX, + double dirtyY, double dirtyWidth, + double dirtyHeight, + ErrorResult& error) +{ + if (!FloatValidate(dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + dom::Uint8ClampedArray arr(imageData.GetDataObject()); + + error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), + imageData.Width(), imageData.Height(), + arr.Data(), arr.Length(), true, + JS_DoubleToInt32(dirtyX), + JS_DoubleToInt32(dirtyY), + JS_DoubleToInt32(dirtyWidth), + JS_DoubleToInt32(dirtyHeight)); +} + +// void putImageData (in ImageData d, in float x, in float y); +// void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight); + +nsresult +CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h, + unsigned char *aData, uint32_t aDataLen, + bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY, + int32_t dirtyWidth, int32_t dirtyHeight) +{ + if (w == 0 || h == 0) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + IntRect dirtyRect; + IntRect imageDataRect(0, 0, w, h); + + if (hasDirtyRect) { + // fix up negative dimensions + if (dirtyWidth < 0) { + NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); + + CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth; + + if (!checkedDirtyX.isValid()) + return NS_ERROR_DOM_INDEX_SIZE_ERR; + + dirtyX = checkedDirtyX.value(); + dirtyWidth = -dirtyWidth; + } + + if (dirtyHeight < 0) { + NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); + + CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight; + + if (!checkedDirtyY.isValid()) + return NS_ERROR_DOM_INDEX_SIZE_ERR; + + dirtyY = checkedDirtyY.value(); + dirtyHeight = -dirtyHeight; + } + + // bound the dirty rect within the imageData rectangle + dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); + + if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) + return NS_OK; + } else { + dirtyRect = imageDataRect; + } + + dirtyRect.MoveBy(IntPoint(x, y)); + dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); + + if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { + return NS_OK; + } + + uint32_t len = w * h * 4; + if (aDataLen != len) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + nsRefPtr imgsurf = new gfxImageSurface(gfxIntSize(w, h), + gfxASurface::ImageFormatARGB32, + false); + if (!imgsurf || imgsurf->CairoStatus()) { + return NS_ERROR_FAILURE; + } + + // ensure premultiply table has been created + EnsurePremultiplyTable(); + + uint8_t *src = aData; + uint8_t *dst = imgsurf->Data(); + + for (uint32_t j = 0; j < h; j++) { + for (uint32_t i = 0; i < w; i++) { + uint8_t r = *src++; + uint8_t g = *src++; + uint8_t b = *src++; + uint8_t a = *src++; + + // Convert to premultiplied color (losslessly if the input came from getImageData) +#ifdef IS_LITTLE_ENDIAN + *dst++ = sPremultiplyTable[a][b]; + *dst++ = sPremultiplyTable[a][g]; + *dst++ = sPremultiplyTable[a][r]; + *dst++ = a; +#else + *dst++ = a; + *dst++ = sPremultiplyTable[a][r]; + *dst++ = sPremultiplyTable[a][g]; + *dst++ = sPremultiplyTable[a][b]; +#endif + } + } + + EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } + + RefPtr sourceSurface = + mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), FORMAT_B8G8R8A8); + + + mTarget->CopySurface(sourceSurface, + IntRect(dirtyRect.x - x, dirtyRect.y - y, + dirtyRect.width, dirtyRect.height), + IntPoint(dirtyRect.x, dirtyRect.y)); + + Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface) +{ + EnsureTarget(); + if (!mThebesSurface) { + mThebesSurface = + gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); + + if (!mThebesSurface) { + return NS_ERROR_FAILURE; + } + } else { + // Normally GetThebesSurfaceForDrawTarget will handle the flush, when + // we're returning a cached ThebesSurface we need to flush here. + mTarget->Flush(); + } + + *surface = mThebesSurface; + NS_ADDREF(*surface); + + return NS_OK; +} + +static already_AddRefed +CreateImageData(JSContext* cx, CanvasRenderingContext2D* context, + uint32_t w, uint32_t h, ErrorResult& error) +{ + if (w == 0) + w = 1; + if (h == 0) + h = 1; + + CheckedInt len = CheckedInt(w) * h * 4; + if (!len.isValid()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return NULL; + } + + // Create the fast typed array; it's initialized to 0 by default. + JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value()); + if (!darray) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return NULL; + } + + nsRefPtr imageData = + new mozilla::dom::ImageData(w, h, *darray); + return imageData.forget(); +} + +already_AddRefed +CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw, + double sh, ErrorResult& error) +{ + if (!FloatValidate(sw, sh)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return NULL; + } + + if (!sw || !sh) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return NULL; + } + + int32_t wi = JS_DoubleToInt32(sw); + int32_t hi = JS_DoubleToInt32(sh); + + uint32_t w = NS_ABS(wi); + uint32_t h = NS_ABS(hi); + return mozilla::dom::CreateImageData(cx, this, w, h, error); +} + +already_AddRefed +CanvasRenderingContext2D::CreateImageData(JSContext* cx, + ImageData& imagedata, + ErrorResult& error) +{ + return mozilla::dom::CreateImageData(cx, this, imagedata.Width(), + imagedata.Height(), error); +} + +static uint8_t g2DContextLayerUserData; + +already_AddRefed +CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, + CanvasLayer *aOldLayer, + LayerManager *aManager) +{ + // Don't call EnsureTarget() ... if there isn't already a surface, then + // we have nothing to paint and there is no need to create a surface just + // to paint nothing. Also, EnsureTarget() can cause creation of a persistent + // layer manager which must NOT happen during a paint. + if (!mTarget || !IsTargetValid()) { + // No DidTransactionCallback will be received, so mark the context clean + // now so future invalidations will be dispatched. + MarkContextClean(); + return nullptr; + } + + mTarget->Flush(); + + if (!mResetLayer && aOldLayer) { + CanvasRenderingContext2DUserData* userData = + static_cast( + aOldLayer->GetUserData(&g2DContextLayerUserData)); + if (userData && userData->IsForContext(this)) { + NS_ADDREF(aOldLayer); + return aOldLayer; + } + } + + nsRefPtr canvasLayer = aManager->CreateCanvasLayer(); + if (!canvasLayer) { + NS_WARNING("CreateCanvasLayer returned null!"); + // No DidTransactionCallback will be received, so mark the context clean + // now so future invalidations will be dispatched. + MarkContextClean(); + return nullptr; + } + CanvasRenderingContext2DUserData *userData = nullptr; + // Make the layer tell us whenever a transaction finishes (including + // the current transaction), so we can clear our invalidation state and + // start invalidating again. We need to do this for all layers since + // callers of DrawWindow may be expecting to receive normal invalidation + // notifications after this paint. + + // The layer will be destroyed when we tear down the presentation + // (at the latest), at which time this userData will be destroyed, + // releasing the reference to the element. + // The userData will receive DidTransactionCallbacks, which flush the + // the invalidation state to indicate that the canvas is up to date. + userData = new CanvasRenderingContext2DUserData(this); + canvasLayer->SetDidTransactionCallback( + CanvasRenderingContext2DUserData::DidTransactionCallback, userData); + canvasLayer->SetUserData(&g2DContextLayerUserData, userData); + + CanvasLayer::Data data; + + data.mDrawTarget = mTarget; + data.mSize = nsIntSize(mWidth, mHeight); + + canvasLayer->Initialize(data); + uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; + canvasLayer->SetContentFlags(flags); + canvasLayer->Updated(); + + mResetLayer = false; + + return canvasLayer.forget(); +} + +void +CanvasRenderingContext2D::MarkContextClean() +{ + if (mInvalidateCount > 0) { + mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; + } + mIsEntireFrameInvalid = false; + mInvalidateCount = 0; +} + + +bool +CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) +{ + return !aManager->CanUseCanvasLayerForSize(gfxIntSize(mWidth, mHeight)); +} + +} +} + +DOMCI_DATA(TextMetrics, mozilla::dom::TextMetrics) +DOMCI_DATA(CanvasGradient, mozilla::dom::CanvasGradient) +DOMCI_DATA(CanvasPattern, mozilla::dom::CanvasPattern) +DOMCI_DATA(CanvasRenderingContext2D, mozilla::dom::CanvasRenderingContext2D) +