gecko-dev/dom/svg/DOMSVGTransform.cpp

327 строки
10 KiB
C++

/* -*- 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/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 {
namespace dom {
using namespace SVGTransform_Binding;
static SVGAttrTearoffTable<DOMSVGTransform, SVGMatrix>&
SVGMatrixTearoffTable() {
static SVGAttrTearoffTable<DOMSVGTransform, SVGMatrix> 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
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMSVGTransform, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMSVGTransform, Release)
JSObject* DOMSVGTransform::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return SVGTransform_Binding::Wrap(aCx, this, aGivenProto);
}
//----------------------------------------------------------------------
// Helper class: AutoChangeTransformNotifier
// Stack-based helper class to pair calls to WillChangeTransformList
// and DidChangeTransformList.
class MOZ_RAII AutoChangeTransformNotifier {
public:
explicit AutoChangeTransformNotifier(
DOMSVGTransform* aTransform MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mTransform(aTransform) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
MOZ_ASSERT(mTransform, "Expecting non-null transform");
if (mTransform->HasOwner()) {
mUpdateBatch.emplace(mTransform->Element()->GetComposedDoc(), true);
mEmptyOrOldValue =
mTransform->Element()->WillChangeTransformList(mUpdateBatch.ref());
}
}
~AutoChangeTransformNotifier() {
if (mTransform->HasOwner()) {
mTransform->Element()->DidChangeTransformList(mEmptyOrOldValue,
mUpdateBatch.ref());
// Null check mTransform->mList, since DidChangeTransformList can run
// script, potentially removing mTransform from its list.
if (mTransform->mList && mTransform->mList->IsAnimating()) {
mTransform->Element()->AnimationNeedsResample();
}
}
}
private:
Maybe<mozAutoDocUpdate> mUpdateBatch;
DOMSVGTransform* const mTransform;
nsAttrValue mEmptyOrOldValue;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
//----------------------------------------------------------------------
// 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 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(SVGMatrix& aMatrix, ErrorResult& rv) {
if (mIsAnimValItem) {
rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
return;
}
SetMatrix(aMatrix.GetMatrix());
}
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;
}
AutoChangeTransformNotifier 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;
}
AutoChangeTransformNotifier 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;
}
}
AutoChangeTransformNotifier 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<MSG_INVALID_TRANSFORM_ANGLE_ERROR>();
return;
}
AutoChangeTransformNotifier notifier(this);
DebugOnly<nsresult> 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<MSG_INVALID_TRANSFORM_ANGLE_ERROR>();
return;
}
AutoChangeTransformNotifier notifier(this);
DebugOnly<nsresult> 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<SVGTransform>(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<DOMSVGTransform*>(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;
}
AutoChangeTransformNotifier notifier(this);
Transform().SetMatrix(aMatrix);
}
} // namespace dom
} // namespace mozilla