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 "mozilla/ISVGDisplayableFrame.h"
17 #include "mozilla/SVGContentUtils.h"
18 #include "mozilla/SVGTextFrame.h"
19 #include "mozilla/SVGUtils.h"
20 #include "nsContentUtils.h"
21 #include "nsIFrame.h"
22 #include "nsLayoutUtils.h"
23 
24 using namespace mozilla::gfx;
25 
26 namespace mozilla {
27 namespace dom {
28 
29 already_AddRefed<DOMSVGAnimatedTransformList>
Transform()30 SVGTransformableElement::Transform() {
31   // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList
32   // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so:
33   return DOMSVGAnimatedTransformList::GetDOMWrapper(
34       GetAnimatedTransformList(DO_ALLOCATE), this);
35 }
36 
37 //----------------------------------------------------------------------
38 // nsIContent methods
39 
NS_IMETHODIMP_(bool)40 NS_IMETHODIMP_(bool)
41 SVGTransformableElement::IsAttributeMapped(const nsAtom* name) const {
42   static const MappedAttributeEntry* const map[] = {sColorMap, sFillStrokeMap,
43                                                     sGraphicsMap};
44 
45   return FindAttributeDependence(name, map) ||
46          SVGElement::IsAttributeMapped(name);
47 }
48 
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const49 nsChangeHint SVGTransformableElement::GetAttributeChangeHint(
50     const nsAtom* aAttribute, int32_t aModType) const {
51   nsChangeHint retval =
52       SVGElement::GetAttributeChangeHint(aAttribute, aModType);
53   if (aAttribute == nsGkAtoms::transform ||
54       aAttribute == nsGkAtoms::mozAnimateMotionDummyAttr) {
55     nsIFrame* frame =
56         const_cast<SVGTransformableElement*>(this)->GetPrimaryFrame();
57     retval |= nsChangeHint_InvalidateRenderingObservers;
58     if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
59       return retval;
60     }
61 
62     bool isAdditionOrRemoval = false;
63     if (aModType == MutationEvent_Binding::ADDITION ||
64         aModType == MutationEvent_Binding::REMOVAL) {
65       isAdditionOrRemoval = true;
66     } else {
67       MOZ_ASSERT(aModType == MutationEvent_Binding::MODIFICATION,
68                  "Unknown modification type.");
69       if (!mTransforms || !mTransforms->HasTransform()) {
70         // New value is empty, treat as removal.
71         // FIXME: Should we just rely on CreatedOrRemovedOnLastChange?
72         isAdditionOrRemoval = true;
73       } else if (mTransforms->CreatedOrRemovedOnLastChange()) {
74         // Old value was empty, treat as addition.
75         isAdditionOrRemoval = true;
76       }
77     }
78 
79     if (isAdditionOrRemoval) {
80       retval |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
81     } else {
82       // We just assume the old and new transforms are different.
83       retval |= nsChangeHint_UpdatePostTransformOverflow |
84                 nsChangeHint_UpdateTransformLayer;
85     }
86   }
87   return retval;
88 }
89 
IsEventAttributeNameInternal(nsAtom * aName)90 bool SVGTransformableElement::IsEventAttributeNameInternal(nsAtom* aName) {
91   return nsContentUtils::IsEventAttributeName(aName, EventNameType_SVGGraphic);
92 }
93 
94 //----------------------------------------------------------------------
95 // SVGElement overrides
96 
PrependLocalTransformsTo(const gfxMatrix & aMatrix,SVGTransformTypes aWhich) const97 gfxMatrix SVGTransformableElement::PrependLocalTransformsTo(
98     const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const {
99   if (aWhich == eChildToUserSpace) {
100     // We don't have any eUserSpaceToParent transforms. (Sub-classes that do
101     // must override this function and handle that themselves.)
102     return aMatrix;
103   }
104   return GetUserToParentTransform(mAnimateMotionTransform.get(),
105                                   mTransforms.get()) *
106          aMatrix;
107 }
108 
GetAnimateMotionTransform() const109 const gfx::Matrix* SVGTransformableElement::GetAnimateMotionTransform() const {
110   return mAnimateMotionTransform.get();
111 }
112 
SetAnimateMotionTransform(const gfx::Matrix * aMatrix)113 void SVGTransformableElement::SetAnimateMotionTransform(
114     const gfx::Matrix* aMatrix) {
115   if ((!aMatrix && !mAnimateMotionTransform) ||
116       (aMatrix && mAnimateMotionTransform &&
117        aMatrix->FuzzyEquals(*mAnimateMotionTransform))) {
118     return;
119   }
120   bool transformSet = mTransforms && mTransforms->IsExplicitlySet();
121   bool prevSet = mAnimateMotionTransform || transformSet;
122   mAnimateMotionTransform =
123       aMatrix ? MakeUnique<gfx::Matrix>(*aMatrix) : nullptr;
124   bool nowSet = mAnimateMotionTransform || transformSet;
125   int32_t modType;
126   if (prevSet && !nowSet) {
127     modType = MutationEvent_Binding::REMOVAL;
128   } else if (!prevSet && nowSet) {
129     modType = MutationEvent_Binding::ADDITION;
130   } else {
131     modType = MutationEvent_Binding::MODIFICATION;
132   }
133   DidAnimateTransformList(modType);
134   nsIFrame* frame = GetPrimaryFrame();
135   if (frame) {
136     // If the result of this transform and any other transforms on this frame
137     // is the identity matrix, then DoApplyRenderingChangeToTree won't handle
138     // our nsChangeHint_UpdateTransformLayer hint since aFrame->IsTransformed()
139     // will return false. That's fine, but we still need to schedule a repaint,
140     // and that won't otherwise happen. Since it's cheap to call SchedulePaint,
141     // we don't bother to check IsTransformed().
142     frame->SchedulePaint();
143   }
144 }
145 
GetAnimatedTransformList(uint32_t aFlags)146 SVGAnimatedTransformList* SVGTransformableElement::GetAnimatedTransformList(
147     uint32_t aFlags) {
148   if (!mTransforms && (aFlags & DO_ALLOCATE)) {
149     mTransforms = MakeUnique<SVGAnimatedTransformList>();
150   }
151   return mTransforms.get();
152 }
153 
GetNearestViewportElement()154 SVGElement* SVGTransformableElement::GetNearestViewportElement() {
155   return SVGContentUtils::GetNearestViewportElement(this);
156 }
157 
GetFarthestViewportElement()158 SVGElement* SVGTransformableElement::GetFarthestViewportElement() {
159   return SVGContentUtils::GetOuterSVGElement(this);
160 }
161 
ZeroBBox(SVGTransformableElement & aOwner)162 static already_AddRefed<SVGRect> ZeroBBox(SVGTransformableElement& aOwner) {
163   return MakeAndAddRef<SVGRect>(&aOwner, Rect{0, 0, 0, 0});
164 }
165 
GetBBox(const SVGBoundingBoxOptions & aOptions)166 already_AddRefed<SVGRect> SVGTransformableElement::GetBBox(
167     const SVGBoundingBoxOptions& aOptions) {
168   nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
169 
170   if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
171     return ZeroBBox(*this);
172   }
173   ISVGDisplayableFrame* svgframe = do_QueryFrame(frame);
174 
175   if (!svgframe) {
176     if (!SVGUtils::IsInSVGTextSubtree(frame)) {
177       return ZeroBBox(*this);
178     }
179 
180     // For <tspan>, <textPath>, the frame is an nsInlineFrame or
181     // nsBlockFrame, |svgframe| will be a nullptr.
182     // We implement their getBBox directly here instead of in
183     // SVGUtils::GetBBox, because SVGUtils::GetBBox is more
184     // or less used for other purpose elsewhere. e.g. gradient
185     // code assumes GetBBox of <tspan> returns the bbox of the
186     // outer <text>.
187     // TODO: cleanup this sort of usecase of SVGUtils::GetBBox,
188     // then move this code SVGUtils::GetBBox.
189     SVGTextFrame* text =
190         static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
191             frame->GetParent(), LayoutFrameType::SVGText));
192 
193     if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
194       return ZeroBBox(*this);
195     }
196 
197     gfxRect rec = text->TransformFrameRectFromTextChild(
198         frame->GetRectRelativeToSelf(), frame);
199 
200     // Should also add the |x|, |y| of the SVGTextFrame itself, since
201     // the result obtained by TransformFrameRectFromTextChild doesn't
202     // include them.
203     rec.x += float(text->GetPosition().x) / AppUnitsPerCSSPixel();
204     rec.y += float(text->GetPosition().y) / AppUnitsPerCSSPixel();
205 
206     return do_AddRef(new SVGRect(this, ToRect(rec)));
207   }
208 
209   if (!NS_SVGNewGetBBoxEnabled()) {
210     return do_AddRef(new SVGRect(
211         this, ToRect(SVGUtils::GetBBox(
212                   frame, SVGUtils::eBBoxIncludeFillGeometry |
213                              SVGUtils::eUseUserSpaceOfUseElement))));
214   }
215   uint32_t flags = 0;
216   if (aOptions.mFill) {
217     flags |= SVGUtils::eBBoxIncludeFill;
218   }
219   if (aOptions.mStroke) {
220     flags |= SVGUtils::eBBoxIncludeStroke;
221   }
222   if (aOptions.mMarkers) {
223     flags |= SVGUtils::eBBoxIncludeMarkers;
224   }
225   if (aOptions.mClipped) {
226     flags |= SVGUtils::eBBoxIncludeClipped;
227   }
228   if (flags == 0) {
229     return do_AddRef(new SVGRect(this, gfx::Rect()));
230   }
231   if (flags == SVGUtils::eBBoxIncludeMarkers ||
232       flags == SVGUtils::eBBoxIncludeClipped) {
233     flags |= SVGUtils::eBBoxIncludeFill;
234   }
235   flags |= SVGUtils::eUseUserSpaceOfUseElement;
236   return do_AddRef(new SVGRect(this, ToRect(SVGUtils::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