1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "SVGTransformableElement.h"
8 
9 #include "DOMSVGAnimatedTransformList.h"
10 #include "gfx2DGlue.h"
11 #include "mozilla/dom/MutationEventBinding.h"
12 #include "mozilla/dom/SVGGraphicsElementBinding.h"
13 #include "mozilla/dom/SVGMatrix.h"
14 #include "mozilla/dom/SVGRect.h"
15 #include "mozilla/dom/SVGSVGElement.h"
16 #include "nsContentUtils.h"
17 #include "nsIFrame.h"
18 #include "SVGTextFrame.h"
19 #include "SVGContentUtils.h"
20 #include "nsSVGDisplayableFrame.h"
21 #include "nsSVGUtils.h"
22 
23 using namespace mozilla::gfx;
24 
25 namespace mozilla {
26 namespace dom {
27 
28 already_AddRefed<DOMSVGAnimatedTransformList>
Transform()29 SVGTransformableElement::Transform() {
30   // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList
31   // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so:
32   return DOMSVGAnimatedTransformList::GetDOMWrapper(
33       GetAnimatedTransformList(DO_ALLOCATE), this);
34 }
35 
36 //----------------------------------------------------------------------
37 // nsIContent methods
38 
NS_IMETHODIMP_(bool)39 NS_IMETHODIMP_(bool)
40 SVGTransformableElement::IsAttributeMapped(const nsAtom* name) const {
41   static const MappedAttributeEntry* const map[] = {sColorMap, sFillStrokeMap,
42                                                     sGraphicsMap};
43 
44   return FindAttributeDependence(name, map) ||
45          SVGElement::IsAttributeMapped(name);
46 }
47 
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const48 nsChangeHint SVGTransformableElement::GetAttributeChangeHint(
49     const nsAtom* aAttribute, int32_t aModType) const {
50   nsChangeHint retval =
51       SVGElement::GetAttributeChangeHint(aAttribute, aModType);
52   if (aAttribute == nsGkAtoms::transform ||
53       aAttribute == nsGkAtoms::mozAnimateMotionDummyAttr) {
54     nsIFrame* frame =
55         const_cast<SVGTransformableElement*>(this)->GetPrimaryFrame();
56     retval |= nsChangeHint_InvalidateRenderingObservers;
57     if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
58       return retval;
59     }
60 
61     bool isAdditionOrRemoval = false;
62     if (aModType == MutationEvent_Binding::ADDITION ||
63         aModType == MutationEvent_Binding::REMOVAL) {
64       isAdditionOrRemoval = true;
65     } else {
66       MOZ_ASSERT(aModType == MutationEvent_Binding::MODIFICATION,
67                  "Unknown modification type.");
68       if (!mTransforms || !mTransforms->HasTransform()) {
69         // New value is empty, treat as removal.
70         // FIXME: Should we just rely on CreatedOrRemovedOnLastChange?
71         isAdditionOrRemoval = true;
72       } else if (mTransforms->CreatedOrRemovedOnLastChange()) {
73         // Old value was empty, treat as addition.
74         isAdditionOrRemoval = true;
75       }
76     }
77 
78     if (isAdditionOrRemoval) {
79       retval |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
80     } else {
81       // We just assume the old and new transforms are different.
82       retval |= nsChangeHint_UpdatePostTransformOverflow |
83                 nsChangeHint_UpdateTransformLayer;
84     }
85   }
86   return retval;
87 }
88 
IsEventAttributeNameInternal(nsAtom * aName)89 bool SVGTransformableElement::IsEventAttributeNameInternal(nsAtom* aName) {
90   return nsContentUtils::IsEventAttributeName(aName, EventNameType_SVGGraphic);
91 }
92 
93 //----------------------------------------------------------------------
94 // SVGElement overrides
95 
PrependLocalTransformsTo(const gfxMatrix & aMatrix,SVGTransformTypes aWhich) const96 gfxMatrix SVGTransformableElement::PrependLocalTransformsTo(
97     const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const {
98   if (aWhich == eChildToUserSpace) {
99     // We don't have any eUserSpaceToParent transforms. (Sub-classes that do
100     // must override this function and handle that themselves.)
101     return aMatrix;
102   }
103   return GetUserToParentTransform(mAnimateMotionTransform.get(),
104                                   mTransforms.get()) *
105          aMatrix;
106 }
107 
GetAnimateMotionTransform() const108 const gfx::Matrix* SVGTransformableElement::GetAnimateMotionTransform() const {
109   return mAnimateMotionTransform.get();
110 }
111 
SetAnimateMotionTransform(const gfx::Matrix * aMatrix)112 void SVGTransformableElement::SetAnimateMotionTransform(
113     const gfx::Matrix* aMatrix) {
114   if ((!aMatrix && !mAnimateMotionTransform) ||
115       (aMatrix && mAnimateMotionTransform &&
116        aMatrix->FuzzyEquals(*mAnimateMotionTransform))) {
117     return;
118   }
119   bool transformSet = mTransforms && mTransforms->IsExplicitlySet();
120   bool prevSet = mAnimateMotionTransform || transformSet;
121   mAnimateMotionTransform =
122       aMatrix ? MakeUnique<gfx::Matrix>(*aMatrix) : nullptr;
123   bool nowSet = mAnimateMotionTransform || transformSet;
124   int32_t modType;
125   if (prevSet && !nowSet) {
126     modType = MutationEvent_Binding::REMOVAL;
127   } else if (!prevSet && nowSet) {
128     modType = MutationEvent_Binding::ADDITION;
129   } else {
130     modType = MutationEvent_Binding::MODIFICATION;
131   }
132   DidAnimateTransformList(modType);
133   nsIFrame* frame = GetPrimaryFrame();
134   if (frame) {
135     // If the result of this transform and any other transforms on this frame
136     // is the identity matrix, then DoApplyRenderingChangeToTree won't handle
137     // our nsChangeHint_UpdateTransformLayer hint since aFrame->IsTransformed()
138     // will return false. That's fine, but we still need to schedule a repaint,
139     // and that won't otherwise happen. Since it's cheap to call SchedulePaint,
140     // we don't bother to check IsTransformed().
141     frame->SchedulePaint();
142   }
143 }
144 
GetAnimatedTransformList(uint32_t aFlags)145 SVGAnimatedTransformList* SVGTransformableElement::GetAnimatedTransformList(
146     uint32_t aFlags) {
147   if (!mTransforms && (aFlags & DO_ALLOCATE)) {
148     mTransforms = MakeUnique<SVGAnimatedTransformList>();
149   }
150   return mTransforms.get();
151 }
152 
GetNearestViewportElement()153 SVGElement* SVGTransformableElement::GetNearestViewportElement() {
154   return SVGContentUtils::GetNearestViewportElement(this);
155 }
156 
GetFarthestViewportElement()157 SVGElement* SVGTransformableElement::GetFarthestViewportElement() {
158   return SVGContentUtils::GetOuterSVGElement(this);
159 }
160 
ZeroBBox(SVGTransformableElement & aOwner)161 static already_AddRefed<SVGRect> ZeroBBox(SVGTransformableElement& aOwner) {
162   return MakeAndAddRef<SVGRect>(&aOwner, Rect{0, 0, 0, 0});
163 }
164 
GetBBox(const SVGBoundingBoxOptions & aOptions)165 already_AddRefed<SVGRect> SVGTransformableElement::GetBBox(
166     const SVGBoundingBoxOptions& aOptions) {
167   nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
168 
169   if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
170     return ZeroBBox(*this);
171   }
172   nsSVGDisplayableFrame* svgframe = do_QueryFrame(frame);
173 
174   if (!svgframe) {
175     if (!nsSVGUtils::IsInSVGTextSubtree(frame)) {
176       return ZeroBBox(*this);
177     }
178 
179     // For <tspan>, <textPath>, the frame is an nsInlineFrame or
180     // nsBlockFrame, |svgframe| will be a nullptr.
181     // We implement their getBBox directly here instead of in
182     // nsSVGUtils::GetBBox, because nsSVGUtils::GetBBox is more
183     // or less used for other purpose elsewhere. e.g. gradient
184     // code assumes GetBBox of <tspan> returns the bbox of the
185     // outer <text>.
186     // TODO: cleanup this sort of usecase of nsSVGUtils::GetBBox,
187     // then move this code nsSVGUtils::GetBBox.
188     SVGTextFrame* text =
189         static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
190             frame->GetParent(), LayoutFrameType::SVGText));
191 
192     if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
193       return ZeroBBox(*this);
194     }
195 
196     gfxRect rec = text->TransformFrameRectFromTextChild(
197         frame->GetRectRelativeToSelf(), frame);
198 
199     // Should also add the |x|, |y| of the SVGTextFrame itself, since
200     // the result obtained by TransformFrameRectFromTextChild doesn't
201     // include them.
202     rec.x += float(text->GetPosition().x) / AppUnitsPerCSSPixel();
203     rec.y += float(text->GetPosition().y) / AppUnitsPerCSSPixel();
204 
205     return do_AddRef(new SVGRect(this, ToRect(rec)));
206   }
207 
208   if (!NS_SVGNewGetBBoxEnabled()) {
209     return do_AddRef(new SVGRect(
210         this, ToRect(nsSVGUtils::GetBBox(
211                   frame, nsSVGUtils::eBBoxIncludeFillGeometry |
212                              nsSVGUtils::eUseUserSpaceOfUseElement))));
213   }
214   uint32_t flags = 0;
215   if (aOptions.mFill) {
216     flags |= nsSVGUtils::eBBoxIncludeFill;
217   }
218   if (aOptions.mStroke) {
219     flags |= nsSVGUtils::eBBoxIncludeStroke;
220   }
221   if (aOptions.mMarkers) {
222     flags |= nsSVGUtils::eBBoxIncludeMarkers;
223   }
224   if (aOptions.mClipped) {
225     flags |= nsSVGUtils::eBBoxIncludeClipped;
226   }
227   if (flags == 0) {
228     return do_AddRef(new SVGRect(this, gfx::Rect()));
229   }
230   if (flags == nsSVGUtils::eBBoxIncludeMarkers ||
231       flags == nsSVGUtils::eBBoxIncludeClipped) {
232     flags |= nsSVGUtils::eBBoxIncludeFill;
233   }
234   flags |= nsSVGUtils::eUseUserSpaceOfUseElement;
235   return do_AddRef(
236       new SVGRect(this, ToRect(nsSVGUtils::GetBBox(frame, flags))));
237 }
238 
GetCTM()239 already_AddRefed<SVGMatrix> SVGTransformableElement::GetCTM() {
240   Document* currentDoc = GetComposedDoc();
241   if (currentDoc) {
242     // Flush all pending notifications so that our frames are up to date
243     currentDoc->FlushPendingNotifications(FlushType::Layout);
244   }
245   gfx::Matrix m = SVGContentUtils::GetCTM(this, false);
246   RefPtr<SVGMatrix> mat =
247       m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
248   return mat.forget();
249 }
250 
GetScreenCTM()251 already_AddRefed<SVGMatrix> SVGTransformableElement::GetScreenCTM() {
252   Document* currentDoc = GetComposedDoc();
253   if (currentDoc) {
254     // Flush all pending notifications so that our frames are up to date
255     currentDoc->FlushPendingNotifications(FlushType::Layout);
256   }
257   gfx::Matrix m = SVGContentUtils::GetCTM(this, true);
258   RefPtr<SVGMatrix> mat =
259       m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
260   return mat.forget();
261 }
262 
GetTransformToElement(SVGGraphicsElement & aElement,ErrorResult & rv)263 already_AddRefed<SVGMatrix> SVGTransformableElement::GetTransformToElement(
264     SVGGraphicsElement& aElement, ErrorResult& rv) {
265   // the easiest way to do this (if likely to increase rounding error):
266   RefPtr<SVGMatrix> ourScreenCTM = GetScreenCTM();
267   RefPtr<SVGMatrix> targetScreenCTM = aElement.GetScreenCTM();
268   if (!ourScreenCTM || !targetScreenCTM) {
269     rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
270     return nullptr;
271   }
272   RefPtr<SVGMatrix> tmp = targetScreenCTM->Inverse(rv);
273   if (rv.Failed()) return nullptr;
274 
275   RefPtr<SVGMatrix> mat = tmp->Multiply(*ourScreenCTM);
276   return mat.forget();
277 }
278 
279 /* static */
GetUserToParentTransform(const gfx::Matrix * aAnimateMotionTransform,const SVGAnimatedTransformList * aTransforms)280 gfxMatrix SVGTransformableElement::GetUserToParentTransform(
281     const gfx::Matrix* aAnimateMotionTransform,
282     const SVGAnimatedTransformList* aTransforms) {
283   gfxMatrix result;
284 
285   if (aAnimateMotionTransform) {
286     result.PreMultiply(ThebesMatrix(*aAnimateMotionTransform));
287   }
288 
289   if (aTransforms) {
290     result.PreMultiply(aTransforms->GetAnimValue().GetConsolidationMatrix());
291   }
292 
293   return result;
294 }
295 
296 }  // namespace dom
297 }  // namespace mozilla
298