/* -*- 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 "DOMSVGPoint.h" #include "DOMSVGPointList.h" #include "gfx2DGlue.h" #include "nsError.h" #include "mozilla/dom/DOMMatrix.h" #include "mozilla/dom/SVGPointBinding.h" // See the architecture comment in DOMSVGPointList.h. using namespace mozilla::gfx; namespace mozilla::dom { //---------------------------------------------------------------------- // Helper class: AutoChangePointNotifier // class MOZ_RAII AutoChangePointNotifier { public: explicit AutoChangePointNotifier(DOMSVGPoint* aValue) : mValue(aValue) { MOZ_ASSERT(mValue, "Expecting non-null value"); } ~AutoChangePointNotifier() { if (mValue->IsTranslatePoint()) { mValue->DidChangeTranslate(); } } private: DOMSVGPoint* const mValue; }; static SVGAttrTearoffTable sSVGTranslateTearOffTable; // 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(DOMSVGPoint) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPoint) tmp->CleanupWeakRefs(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPoint) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPoint) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END JSObject* DOMSVGPoint::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return SVGPoint_Binding::Wrap(aCx, this, aGivenProto); } float DOMSVGPoint::X() { if (mIsAnimValItem && IsInList()) { Element()->FlushAnimations(); // May make IsInList() == false } return InternalItem().mX; } void DOMSVGPoint::SetX(float aX, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } auto& val = InternalItem(); if (val.mX == aX) { return; } AutoChangePointListNotifier listNotifier(this); AutoChangePointNotifier translateNotifier(this); val.mX = aX; } float DOMSVGPoint::Y() { if (mIsAnimValItem && IsInList()) { Element()->FlushAnimations(); // May make IsInList() == false } return InternalItem().mY; } void DOMSVGPoint::SetY(float aY, ErrorResult& rv) { if (mIsAnimValItem) { rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } auto& val = InternalItem(); if (val.mY == aY) { return; } AutoChangePointListNotifier listNotifier(this); AutoChangePointNotifier translateNotifier(this); val.mY = aY; } already_AddRefed DOMSVGPoint::MatrixTransform( const DOMMatrix2DInit& aMatrix, ErrorResult& aRv) { RefPtr matrix = DOMMatrixReadOnly::FromMatrix(GetParentObject(), aMatrix, aRv); if (aRv.Failed()) { return nullptr; } const auto* matrix2D = matrix->GetInternal2D(); if (!matrix2D->IsFinite()) { aRv.ThrowTypeError("MatrixTransform matrix"); return nullptr; } auto pt = matrix2D->TransformPoint(InternalItem()); return do_AddRef(new DOMSVGPoint(ToPoint(pt))); } void DOMSVGPoint::InsertingIntoList(DOMSVGPointList* aList, uint32_t aListIndex, bool aIsAnimValItem) { MOZ_RELEASE_ASSERT(!IsInList(), "Inserting item that is already in a list"); MOZ_RELEASE_ASSERT(!mIsTranslatePoint, "Inserting item that is a currentTranslate"); delete mVal; mVal = nullptr; mOwner = aList; mListIndex = aListIndex; mIsAnimValItem = aIsAnimValItem; MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGPoint!"); } void DOMSVGPoint::RemovingFromList() { MOZ_ASSERT( IsInList(), "We should start in a list if we're going to be removed from one."); mVal = new SVGPoint(InternalItem()); mOwner = nullptr; mIsAnimValItem = false; } SVGPoint& DOMSVGPoint::InternalItem() { if (nsCOMPtr pointList = do_QueryInterface(mOwner)) { return pointList->InternalList().mItems[mListIndex]; } return *mVal; } already_AddRefed DOMSVGPoint::GetTranslateTearOff( SVGPoint* aVal, SVGSVGElement* aSVGSVGElement) { RefPtr domPoint = sSVGTranslateTearOffTable.GetTearoff(aVal); if (!domPoint) { domPoint = new DOMSVGPoint(aVal, aSVGSVGElement); sSVGTranslateTearOffTable.AddTearoff(aVal, domPoint); } return domPoint.forget(); } bool DOMSVGPoint::AttrIsAnimating() const { nsCOMPtr pointList = do_QueryInterface(mOwner); return pointList && pointList->AttrIsAnimating(); } void DOMSVGPoint::DidChangeTranslate() { nsCOMPtr svg = do_QueryInterface(mOwner); MOZ_ASSERT(svg); nsContentUtils::AddScriptRunner( NewRunnableMethod("dom::SVGSVGElement::DidChangeTranslate", svg, &SVGSVGElement::DidChangeTranslate)); } SVGElement* DOMSVGPoint::Element() { if (nsCOMPtr pointList = do_QueryInterface(mOwner)) { return pointList->Element(); } nsCOMPtr svg = do_QueryInterface(mOwner); return svg; } void DOMSVGPoint::CleanupWeakRefs() { // Our mList's weak ref to us must be nulled out when we die (or when we're // cycle collected), so we that don't leave behind a pointer to // free / soon-to-be-free memory. if (nsCOMPtr pointList = do_QueryInterface(mOwner)) { MOZ_ASSERT(pointList->mItems[mListIndex] == this, "Clearing out the wrong list index...?"); pointList->mItems[mListIndex] = nullptr; } if (mVal) { if (mIsTranslatePoint) { // Similarly, we must update the tearoff table to remove its (non-owning) // pointer to mVal. sSVGTranslateTearOffTable.RemoveTearoff(mVal); } else { // In this case we own mVal delete mVal; } mVal = nullptr; } } #ifdef DEBUG bool DOMSVGPoint::IndexIsValid() { nsCOMPtr pointList = do_QueryInterface(mOwner); return mListIndex < pointList->InternalList().Length(); } #endif } // namespace mozilla::dom