зеркало из https://github.com/mozilla/gecko-dev.git
327 строки
10 KiB
C++
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
|