/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "DOMSVGTransform.h" #include "mozAutoDocUpdate.h" #include "mozilla/dom/DOMMatrix.h" #include "mozilla/dom/DOMMatrixBinding.h" #include "mozilla/dom/SVGMatrix.h" #include "mozilla/dom/SVGTransformBinding.h" #include "mozilla/DebugOnly.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "nsError.h" #include "SVGAnimatedTransformList.h" #include "SVGAttrTearoffTable.h" namespace { const double kRadPerDegree = 2.0 * M_PI / 360.0; } // namespace namespace mozilla::dom { using namespace SVGTransform_Binding; static SVGAttrTearoffTable& SVGMatrixTearoffTable() { static SVGAttrTearoffTable sSVGMatrixTearoffTable; return sSVGMatrixTearoffTable; } //---------------------------------------------------------------------- // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to // clear our list's weak ref to us to be safe. (The other option would be to // not unlink and rely on the breaking of the other edges in the cycle, as // NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGTransform) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGTransform) // We may not belong to a list, so we must null check tmp->mList. if (tmp->mList) { tmp->mList->mItems[tmp->mListIndex] = nullptr; } NS_IMPL_CYCLE_COLLECTION_UNLINK(mList) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGTransform) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList) SVGMatrix* matrix = SVGMatrixTearoffTable().GetTearoff(tmp); CycleCollectionNoteChild(cb, matrix, "matrix"); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGTransform) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END JSObject* DOMSVGTransform::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return SVGTransform_Binding::Wrap(aCx, this, aGivenProto); } //---------------------------------------------------------------------- // Ctors: DOMSVGTransform::DOMSVGTransform(DOMSVGTransformList* aList, uint32_t aListIndex, bool aIsAnimValItem) : mList(aList), mListIndex(aListIndex), mIsAnimValItem(aIsAnimValItem), mTransform(nullptr) { // These shifts are in sync with the members in the header. MOZ_ASSERT(aList && aListIndex <= MaxListIndex(), "bad arg"); MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!"); } DOMSVGTransform::DOMSVGTransform() : mList(nullptr), mListIndex(0), mIsAnimValItem(false), mTransform(new SVGTransform()) // Default ctor for objects not in a // list initialises to matrix type with // identity matrix {} DOMSVGTransform::DOMSVGTransform(const gfxMatrix& aMatrix) : mList(nullptr), mListIndex(0), mIsAnimValItem(false), mTransform(new SVGTransform(aMatrix)) {} DOMSVGTransform::DOMSVGTransform(const DOMMatrix2DInit& aMatrix, ErrorResult& rv) : mList(nullptr), mListIndex(0), mIsAnimValItem(false), mTransform(new SVGTransform()) { SetMatrix(aMatrix, rv); } DOMSVGTransform::DOMSVGTransform(const SVGTransform& aTransform) : mList(nullptr), mListIndex(0), mIsAnimValItem(false), mTransform(new SVGTransform(aTransform)) {} DOMSVGTransform::~DOMSVGTransform() { SVGMatrix* matrix = SVGMatrixTearoffTable().GetTearoff(this); if (matrix) { SVGMatrixTearoffTable().RemoveTearoff(this); NS_RELEASE(matrix); } // Our mList's weak ref to us must be nulled out when we die. If GC has // unlinked us using the cycle collector code, then that has already // happened, and mList is null. if (mList) { mList->mItems[mListIndex] = nullptr; } } uint16_t DOMSVGTransform::Type() const { return Transform().Type(); } SVGMatrix* DOMSVGTransform::GetMatrix() { SVGMatrix* wrapper = SVGMatrixTearoffTable().GetTearoff(this); if (!wrapper) { NS_ADDREF(wrapper = new SVGMatrix(*this)); SVGMatrixTearoffTable().AddTearoff(this, wrapper); } return wrapper; } float DOMSVGTransform::Angle() const { return Transform().Angle(); } void DOMSVGTransform::SetMatrix(const DOMMatrix2DInit& aMatrix, ErrorResult& aRv) { if (mIsAnimValItem) { aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } RefPtr matrix = DOMMatrixReadOnly::FromMatrix(GetParentObject(), aMatrix, aRv); if (aRv.Failed()) { return; } const gfxMatrix* matrix2D = matrix->GetInternal2D(); if (!matrix2D->IsFinite()) { aRv.ThrowTypeError("Matrix setter"); return; } SetMatrix(*matrix2D); } void DOMSVGTransform::SetTranslate(float tx, float ty, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } if (Transform().Type() == SVG_TRANSFORM_TRANSLATE && Matrixgfx()._31 == tx && Matrixgfx()._32 == ty) { return; } AutoChangeTransformListNotifier notifier(this); Transform().SetTranslate(tx, ty); } void DOMSVGTransform::SetScale(float sx, float sy, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } if (Transform().Type() == SVG_TRANSFORM_SCALE && Matrixgfx()._11 == sx && Matrixgfx()._22 == sy) { return; } AutoChangeTransformListNotifier notifier(this); Transform().SetScale(sx, sy); } void DOMSVGTransform::SetRotate(float angle, float cx, float cy, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } if (Transform().Type() == SVG_TRANSFORM_ROTATE) { float currentCx, currentCy; Transform().GetRotationOrigin(currentCx, currentCy); if (Transform().Angle() == angle && currentCx == cx && currentCy == cy) { return; } } AutoChangeTransformListNotifier notifier(this); Transform().SetRotate(angle, cx, cy); } void DOMSVGTransform::SetSkewX(float angle, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } if (Transform().Type() == SVG_TRANSFORM_SKEWX && Transform().Angle() == angle) { return; } if (!IsFinite(tan(angle * kRadPerDegree))) { rv.ThrowRangeError(); return; } AutoChangeTransformListNotifier notifier(this); DebugOnly result = Transform().SetSkewX(angle); MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewX unexpectedly failed"); } void DOMSVGTransform::SetSkewY(float angle, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } if (Transform().Type() == SVG_TRANSFORM_SKEWY && Transform().Angle() == angle) { return; } if (!IsFinite(tan(angle * kRadPerDegree))) { rv.ThrowRangeError(); return; } AutoChangeTransformListNotifier notifier(this); DebugOnly result = Transform().SetSkewY(angle); MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewY unexpectedly failed"); } //---------------------------------------------------------------------- // List management methods: void DOMSVGTransform::InsertingIntoList(DOMSVGTransformList* aList, uint32_t aListIndex, bool aIsAnimValItem) { MOZ_ASSERT(!HasOwner(), "Inserting item that is already in a list"); mList = aList; mListIndex = aListIndex; mIsAnimValItem = aIsAnimValItem; mTransform = nullptr; MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGLength!"); } void DOMSVGTransform::RemovingFromList() { MOZ_ASSERT(!mTransform, "Item in list also has another non-list value associated with it"); mTransform = MakeUnique(InternalItem()); mList = nullptr; mIsAnimValItem = false; } SVGTransform& DOMSVGTransform::InternalItem() { SVGAnimatedTransformList* alist = Element()->GetAnimatedTransformList(); return mIsAnimValItem && alist->mAnimVal ? (*alist->mAnimVal)[mListIndex] : alist->mBaseVal[mListIndex]; } const SVGTransform& DOMSVGTransform::InternalItem() const { return const_cast(this)->InternalItem(); } #ifdef DEBUG bool DOMSVGTransform::IndexIsValid() { SVGAnimatedTransformList* alist = Element()->GetAnimatedTransformList(); return (mIsAnimValItem && mListIndex < alist->GetAnimValue().Length()) || (!mIsAnimValItem && mListIndex < alist->GetBaseValue().Length()); } #endif // DEBUG //---------------------------------------------------------------------- // Interface for SVGMatrix's use void DOMSVGTransform::SetMatrix(const gfxMatrix& aMatrix) { MOZ_ASSERT(!mIsAnimValItem, "Attempting to modify read-only transform"); if (Transform().Type() == SVG_TRANSFORM_MATRIX && SVGTransform::MatricesEqual(Matrixgfx(), aMatrix)) { return; } AutoChangeTransformListNotifier notifier(this); Transform().SetMatrix(aMatrix); } } // namespace mozilla::dom