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 "mozilla/dom/SVGSVGElement.h"
8 
9 #include "mozilla/ContentEvents.h"
10 #include "mozilla/dom/BindContext.h"
11 #include "mozilla/dom/SVGSVGElementBinding.h"
12 #include "mozilla/dom/SVGMatrix.h"
13 #include "mozilla/dom/SVGRect.h"
14 #include "mozilla/dom/SVGViewElement.h"
15 #include "mozilla/EventDispatcher.h"
16 #include "mozilla/PresShell.h"
17 #include "mozilla/SMILAnimationController.h"
18 #include "mozilla/SMILTimeContainer.h"
19 
20 #include "DOMSVGAngle.h"
21 #include "DOMSVGLength.h"
22 #include "DOMSVGNumber.h"
23 #include "DOMSVGPoint.h"
24 #include "nsFrameSelection.h"
25 #include "nsIFrame.h"
26 #include "nsISVGSVGFrame.h"
27 #include "nsSVGDisplayableFrame.h"
28 #include "nsSVGUtils.h"
29 
30 NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(SVG)
31 
32 using namespace mozilla::gfx;
33 
34 namespace mozilla {
35 namespace dom {
36 
37 using namespace SVGPreserveAspectRatio_Binding;
38 using namespace SVGSVGElement_Binding;
39 
40 SVGEnumMapping SVGSVGElement::sZoomAndPanMap[] = {
41     {nsGkAtoms::disable, SVG_ZOOMANDPAN_DISABLE},
42     {nsGkAtoms::magnify, SVG_ZOOMANDPAN_MAGNIFY},
43     {nullptr, 0}};
44 
45 SVGElement::EnumInfo SVGSVGElement::sEnumInfo[1] = {
46     {nsGkAtoms::zoomAndPan, sZoomAndPanMap, SVG_ZOOMANDPAN_MAGNIFY}};
47 
NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMSVGTranslatePoint,nsISVGPoint,mElement)48 NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMSVGTranslatePoint, nsISVGPoint, mElement)
49 
50 NS_IMPL_ADDREF_INHERITED(DOMSVGTranslatePoint, nsISVGPoint)
51 NS_IMPL_RELEASE_INHERITED(DOMSVGTranslatePoint, nsISVGPoint)
52 
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGTranslatePoint)
54   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
55   // We have to qualify nsISVGPoint because NS_GET_IID looks for a class in the
56   // global namespace
57   NS_INTERFACE_MAP_ENTRY(nsISVGPoint)
58   NS_INTERFACE_MAP_ENTRY(nsISupports)
59 NS_INTERFACE_MAP_END
60 
61 DOMSVGPoint* DOMSVGTranslatePoint::Copy() {
62   return new DOMSVGPoint(mPt.GetX(), mPt.GetY());
63 }
64 
GetParentObject()65 nsISupports* DOMSVGTranslatePoint::GetParentObject() {
66   return ToSupports(mElement);
67 }
68 
SetX(float aValue,ErrorResult & rv)69 void DOMSVGTranslatePoint::SetX(float aValue, ErrorResult& rv) {
70   mElement->SetCurrentTranslate(aValue, mPt.GetY());
71 }
72 
SetY(float aValue,ErrorResult & rv)73 void DOMSVGTranslatePoint::SetY(float aValue, ErrorResult& rv) {
74   mElement->SetCurrentTranslate(mPt.GetX(), aValue);
75 }
76 
MatrixTransform(SVGMatrix & matrix)77 already_AddRefed<nsISVGPoint> DOMSVGTranslatePoint::MatrixTransform(
78     SVGMatrix& matrix) {
79   float a = matrix.A(), b = matrix.B(), c = matrix.C();
80   float d = matrix.D(), e = matrix.E(), f = matrix.F();
81   float x = mPt.GetX();
82   float y = mPt.GetY();
83 
84   nsCOMPtr<nsISVGPoint> point =
85       new DOMSVGPoint(a * x + c * y + e, b * x + d * y + f);
86   return point.forget();
87 }
88 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)89 JSObject* SVGSVGElement::WrapNode(JSContext* aCx,
90                                   JS::Handle<JSObject*> aGivenProto) {
91   return SVGSVGElement_Binding::Wrap(aCx, this, aGivenProto);
92 }
93 
94 //----------------------------------------------------------------------
95 // nsISupports methods
96 
97 NS_IMPL_CYCLE_COLLECTION_CLASS(SVGSVGElement)
98 
99 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGSVGElement,
100                                                 SVGSVGElementBase)
101   if (tmp->mTimedDocumentRoot) {
102     tmp->mTimedDocumentRoot->Unlink();
103   }
104 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGSVGElement,
106                                                   SVGSVGElementBase)
107   if (tmp->mTimedDocumentRoot) {
108     tmp->mTimedDocumentRoot->Traverse(&cb);
109   }
110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
111 
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(SVGSVGElement,SVGSVGElementBase)112 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(SVGSVGElement, SVGSVGElementBase)
113 
114 SVGView::SVGView() {
115   mZoomAndPan.Init(SVGSVGElement::ZOOMANDPAN, SVG_ZOOMANDPAN_MAGNIFY);
116   mViewBox.Init();
117   mPreserveAspectRatio.Init();
118 }
119 
120 //----------------------------------------------------------------------
121 // Implementation
122 
SVGSVGElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,FromParser aFromParser)123 SVGSVGElement::SVGSVGElement(
124     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
125     FromParser aFromParser)
126     : SVGSVGElementBase(std::move(aNodeInfo)),
127       mCurrentTranslate(0.0f, 0.0f),
128       mCurrentScale(1.0f),
129       mPreviousTranslate(0.0f, 0.0f),
130       mPreviousScale(1.0f),
131       mStartAnimationOnBindToTree(aFromParser == NOT_FROM_PARSER ||
132                                   aFromParser == FROM_PARSER_FRAGMENT ||
133                                   aFromParser == FROM_PARSER_XSLT),
134       mImageNeedsTransformInvalidation(false) {}
135 
136 //----------------------------------------------------------------------
137 // nsINode methods
138 
NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(SVGSVGElement)139 NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(SVGSVGElement)
140 
141 //----------------------------------------------------------------------
142 // nsIDOMSVGSVGElement methods:
143 
144 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::X() {
145   return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
146 }
147 
Y()148 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Y() {
149   return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
150 }
151 
Width()152 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Width() {
153   return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
154 }
155 
Height()156 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Height() {
157   return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
158 }
159 
UseCurrentView()160 bool SVGSVGElement::UseCurrentView() { return mSVGView || mCurrentViewID; }
161 
CurrentScale()162 float SVGSVGElement::CurrentScale() { return mCurrentScale; }
163 
164 #define CURRENT_SCALE_MAX 16.0f
165 #define CURRENT_SCALE_MIN 0.0625f
166 
SetCurrentScale(float aCurrentScale)167 void SVGSVGElement::SetCurrentScale(float aCurrentScale) {
168   SetCurrentScaleTranslate(aCurrentScale, mCurrentTranslate.GetX(),
169                            mCurrentTranslate.GetY());
170 }
171 
CurrentTranslate()172 already_AddRefed<nsISVGPoint> SVGSVGElement::CurrentTranslate() {
173   nsCOMPtr<nsISVGPoint> point =
174       new DOMSVGTranslatePoint(&mCurrentTranslate, this);
175   return point.forget();
176 }
177 
SuspendRedraw(uint32_t max_wait_milliseconds)178 uint32_t SVGSVGElement::SuspendRedraw(uint32_t max_wait_milliseconds) {
179   // suspendRedraw is a no-op in Mozilla, so it doesn't matter what
180   // we return
181   return 1;
182 }
183 
UnsuspendRedraw(uint32_t suspend_handle_id)184 void SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id) {
185   // no-op
186 }
187 
UnsuspendRedrawAll()188 void SVGSVGElement::UnsuspendRedrawAll() {
189   // no-op
190 }
191 
ForceRedraw()192 void SVGSVGElement::ForceRedraw() {
193   // no-op
194 }
195 
PauseAnimations()196 void SVGSVGElement::PauseAnimations() {
197   if (mTimedDocumentRoot) {
198     mTimedDocumentRoot->Pause(SMILTimeContainer::PAUSE_SCRIPT);
199   }
200   // else we're not the outermost <svg> or not bound to a tree, so silently fail
201 }
202 
UnpauseAnimations()203 void SVGSVGElement::UnpauseAnimations() {
204   if (mTimedDocumentRoot) {
205     mTimedDocumentRoot->Resume(SMILTimeContainer::PAUSE_SCRIPT);
206   }
207   // else we're not the outermost <svg> or not bound to a tree, so silently fail
208 }
209 
AnimationsPaused()210 bool SVGSVGElement::AnimationsPaused() {
211   SMILTimeContainer* root = GetTimedDocumentRoot();
212   return root && root->IsPausedByType(SMILTimeContainer::PAUSE_SCRIPT);
213 }
214 
GetCurrentTimeAsFloat()215 float SVGSVGElement::GetCurrentTimeAsFloat() {
216   SMILTimeContainer* root = GetTimedDocumentRoot();
217   if (root) {
218     double fCurrentTimeMs = double(root->GetCurrentTimeAsSMILTime());
219     return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC);
220   }
221   return 0.f;
222 }
223 
SetCurrentTime(float seconds)224 void SVGSVGElement::SetCurrentTime(float seconds) {
225   if (mTimedDocumentRoot) {
226     // Make sure the timegraph is up-to-date
227     FlushAnimations();
228     double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
229     // Round to nearest whole number before converting, to avoid precision
230     // errors
231     SMILTime lMilliseconds = int64_t(NS_round(fMilliseconds));
232     mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
233     AnimationNeedsResample();
234     // Trigger synchronous sample now, to:
235     //  - Make sure we get an up-to-date paint after this method
236     //  - re-enable event firing (it got disabled during seeking, and it
237     //  doesn't get re-enabled until the first sample after the seek -- so
238     //  let's make that happen now.)
239     FlushAnimations();
240   }
241   // else we're not the outermost <svg> or not bound to a tree, so silently fail
242 }
243 
DeselectAll()244 void SVGSVGElement::DeselectAll() {
245   nsIFrame* frame = GetPrimaryFrame();
246   if (frame) {
247     RefPtr<nsFrameSelection> frameSelection = frame->GetFrameSelection();
248     frameSelection->ClearNormalSelection();
249   }
250 }
251 
CreateSVGNumber()252 already_AddRefed<DOMSVGNumber> SVGSVGElement::CreateSVGNumber() {
253   return do_AddRef(new DOMSVGNumber(this));
254 }
255 
CreateSVGLength()256 already_AddRefed<DOMSVGLength> SVGSVGElement::CreateSVGLength() {
257   return do_AddRef(new DOMSVGLength());
258 }
259 
CreateSVGAngle()260 already_AddRefed<DOMSVGAngle> SVGSVGElement::CreateSVGAngle() {
261   return do_AddRef(new DOMSVGAngle(this));
262 }
263 
CreateSVGPoint()264 already_AddRefed<nsISVGPoint> SVGSVGElement::CreateSVGPoint() {
265   return do_AddRef(new DOMSVGPoint(0, 0));
266 }
267 
CreateSVGMatrix()268 already_AddRefed<SVGMatrix> SVGSVGElement::CreateSVGMatrix() {
269   return do_AddRef(new SVGMatrix());
270 }
271 
CreateSVGRect()272 already_AddRefed<SVGRect> SVGSVGElement::CreateSVGRect() {
273   return do_AddRef(new SVGRect(this));
274 }
275 
CreateSVGTransform()276 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransform() {
277   return do_AddRef(new DOMSVGTransform());
278 }
279 
CreateSVGTransformFromMatrix(SVGMatrix & matrix)280 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransformFromMatrix(
281     SVGMatrix& matrix) {
282   return do_AddRef(new DOMSVGTransform(matrix.GetMatrix()));
283 }
284 
285 //----------------------------------------------------------------------
286 // helper method for implementing SetCurrentScale/Translate
287 
SetCurrentScaleTranslate(float s,float x,float y)288 void SVGSVGElement::SetCurrentScaleTranslate(float s, float x, float y) {
289   if (s == mCurrentScale && x == mCurrentTranslate.GetX() &&
290       y == mCurrentTranslate.GetY()) {
291     return;
292   }
293 
294   // Prevent bizarre behaviour and maxing out of CPU and memory by clamping
295   if (s < CURRENT_SCALE_MIN)
296     s = CURRENT_SCALE_MIN;
297   else if (s > CURRENT_SCALE_MAX)
298     s = CURRENT_SCALE_MAX;
299 
300   // IMPORTANT: If either mCurrentTranslate *or* mCurrentScale is changed then
301   // mPreviousTranslate_x, mPreviousTranslate_y *and* mPreviousScale must all
302   // be updated otherwise SVGZoomEvents will end up with invalid data. I.e. an
303   // SVGZoomEvent's properties previousScale and previousTranslate must contain
304   // the state of currentScale and currentTranslate immediately before the
305   // change that caused the event's dispatch, which is *not* necessarily the
306   // same thing as the values of currentScale and currentTranslate prior to
307   // their own last change.
308   //
309   // XXX This comment is out-of-date due to removal of SVGZoomEvent.  Can we
310   // remove some of this code?
311   mPreviousScale = mCurrentScale;
312   mPreviousTranslate = mCurrentTranslate;
313 
314   mCurrentScale = s;
315   mCurrentTranslate = SVGPoint(x, y);
316 
317   // now dispatch the appropriate event if we are the root element
318   Document* doc = GetUncomposedDoc();
319   if (doc) {
320     RefPtr<PresShell> presShell = doc->GetPresShell();
321     if (presShell && IsRoot()) {
322       nsEventStatus status = nsEventStatus_eIgnore;
323       if (mPreviousScale == mCurrentScale) {
324         WidgetEvent svgScrollEvent(true, eSVGScroll);
325         presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
326       }
327       InvalidateTransformNotifyFrame();
328     }
329   }
330 }
331 
SetCurrentTranslate(float x,float y)332 void SVGSVGElement::SetCurrentTranslate(float x, float y) {
333   SetCurrentScaleTranslate(mCurrentScale, x, y);
334 }
335 
336 //----------------------------------------------------------------------
337 // SVGZoomAndPanValues
ZoomAndPan()338 uint16_t SVGSVGElement::ZoomAndPan() {
339   return mEnumAttributes[ZOOMANDPAN].GetAnimValue();
340 }
341 
SetZoomAndPan(uint16_t aZoomAndPan,ErrorResult & rv)342 void SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv) {
343   if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE ||
344       aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) {
345     ErrorResult nestedRv;
346     mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this, nestedRv);
347     MOZ_ASSERT(!nestedRv.Failed(),
348                "We already validated our aZoomAndPan value!");
349     return;
350   }
351 
352   rv.ThrowRangeError<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR>();
353 }
354 
355 //----------------------------------------------------------------------
GetTimedDocumentRoot()356 SMILTimeContainer* SVGSVGElement::GetTimedDocumentRoot() {
357   if (mTimedDocumentRoot) {
358     return mTimedDocumentRoot.get();
359   }
360 
361   // We must not be the outermost <svg> element, try to find it
362   SVGSVGElement* outerSVGElement = SVGContentUtils::GetOuterSVGElement(this);
363 
364   if (outerSVGElement) {
365     return outerSVGElement->GetTimedDocumentRoot();
366   }
367   // invalid structure
368   return nullptr;
369 }
370 //----------------------------------------------------------------------
371 // SVGElement
BindToTree(BindContext & aContext,nsINode & aParent)372 nsresult SVGSVGElement::BindToTree(BindContext& aContext, nsINode& aParent) {
373   SMILAnimationController* smilController = nullptr;
374 
375   // FIXME(emilio, bug 1555948): Should probably use composed doc.
376   if (Document* doc = aContext.GetUncomposedDoc()) {
377     if ((smilController = doc->GetAnimationController())) {
378       // SMIL is enabled in this document
379       if (WillBeOutermostSVG(aParent)) {
380         // We'll be the outermost <svg> element.  We'll need a time container.
381         if (!mTimedDocumentRoot) {
382           mTimedDocumentRoot = MakeUnique<SMILTimeContainer>();
383         }
384       } else {
385         // We're a child of some other <svg> element, so we don't need our own
386         // time container. However, we need to make sure that we'll get a
387         // kick-start if we get promoted to be outermost later on.
388         mTimedDocumentRoot = nullptr;
389         mStartAnimationOnBindToTree = true;
390       }
391     }
392   }
393 
394   nsresult rv = SVGGraphicsElement::BindToTree(aContext, aParent);
395   NS_ENSURE_SUCCESS(rv, rv);
396 
397   if (mTimedDocumentRoot && smilController) {
398     rv = mTimedDocumentRoot->SetParent(smilController);
399     if (mStartAnimationOnBindToTree) {
400       mTimedDocumentRoot->Begin();
401       mStartAnimationOnBindToTree = false;
402     }
403   }
404 
405   return rv;
406 }
407 
UnbindFromTree(bool aNullParent)408 void SVGSVGElement::UnbindFromTree(bool aNullParent) {
409   if (mTimedDocumentRoot) {
410     mTimedDocumentRoot->SetParent(nullptr);
411   }
412 
413   SVGGraphicsElement::UnbindFromTree(aNullParent);
414 }
415 
GetAnimatedTransformList(uint32_t aFlags)416 SVGAnimatedTransformList* SVGSVGElement::GetAnimatedTransformList(
417     uint32_t aFlags) {
418   if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) {
419     return mSVGView->mTransforms.get();
420   }
421   return SVGGraphicsElement::GetAnimatedTransformList(aFlags);
422 }
423 
GetEventTargetParent(EventChainPreVisitor & aVisitor)424 void SVGSVGElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
425   if (aVisitor.mEvent->mMessage == eSVGLoad) {
426     if (mTimedDocumentRoot) {
427       mTimedDocumentRoot->Begin();
428       // Set 'resample needed' flag, so that if any script calls a DOM method
429       // that requires up-to-date animations before our first sample callback,
430       // we'll force a synchronous sample.
431       AnimationNeedsResample();
432     }
433   }
434   SVGSVGElementBase::GetEventTargetParent(aVisitor);
435 }
436 
IsEventAttributeNameInternal(nsAtom * aName)437 bool SVGSVGElement::IsEventAttributeNameInternal(nsAtom* aName) {
438   /* The events in EventNameType_SVGSVG are for events that are only
439      applicable to outermost 'svg' elements. We don't check if we're an outer
440      'svg' element in case we're not inserted into the document yet, but since
441      the target of the events in question will always be the outermost 'svg'
442      element, this shouldn't cause any real problems.
443   */
444   return nsContentUtils::IsEventAttributeName(
445       aName, (EventNameType_SVGGraphic | EventNameType_SVGSVG));
446 }
447 
448 //----------------------------------------------------------------------
449 // public helpers:
450 
GetIntrinsicWidth()451 int32_t SVGSVGElement::GetIntrinsicWidth() {
452   if (mLengthAttributes[ATTR_WIDTH].IsPercentage()) {
453     return -1;
454   }
455   // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
456   // that uses the passed argument as the context, but that's fine since we
457   // know the length isn't a percentage so the context won't be used (and we
458   // need to pass the element to be able to resolve em/ex units).
459   float width = mLengthAttributes[ATTR_WIDTH].GetAnimValue(this);
460   return nsSVGUtils::ClampToInt(width);
461 }
462 
GetIntrinsicHeight()463 int32_t SVGSVGElement::GetIntrinsicHeight() {
464   if (mLengthAttributes[ATTR_HEIGHT].IsPercentage()) {
465     return -1;
466   }
467   // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
468   // that uses the passed argument as the context, but that's fine since we
469   // know the length isn't a percentage so the context won't be used (and we
470   // need to pass the element to be able to resolve em/ex units).
471   float height = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(this);
472   return nsSVGUtils::ClampToInt(height);
473 }
474 
FlushImageTransformInvalidation()475 void SVGSVGElement::FlushImageTransformInvalidation() {
476   MOZ_ASSERT(!GetParent(), "Should only be called on root node");
477   MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
478              "Should only be called on image documents");
479 
480   if (mImageNeedsTransformInvalidation) {
481     InvalidateTransformNotifyFrame();
482     mImageNeedsTransformInvalidation = false;
483   }
484 }
485 
486 //----------------------------------------------------------------------
487 // implementation helpers
488 
WillBeOutermostSVG(nsINode & aParent) const489 bool SVGSVGElement::WillBeOutermostSVG(nsINode& aParent) const {
490   nsINode* parent = &aParent;
491   while (parent && parent->IsSVGElement()) {
492     if (parent->IsSVGElement(nsGkAtoms::foreignObject)) {
493       // SVG in a foreignObject must have its own <svg> (nsSVGOuterSVGFrame).
494       return false;
495     }
496     if (parent->IsSVGElement(nsGkAtoms::svg)) {
497       return false;
498     }
499     parent = parent->GetParentOrShadowHostNode();
500   }
501 
502   return true;
503 }
504 
InvalidateTransformNotifyFrame()505 void SVGSVGElement::InvalidateTransformNotifyFrame() {
506   nsISVGSVGFrame* svgframe = do_QueryFrame(GetPrimaryFrame());
507   // might fail this check if we've failed conditional processing
508   if (svgframe) {
509     svgframe->NotifyViewportOrTransformChanged(
510         nsSVGDisplayableFrame::TRANSFORM_CHANGED);
511   }
512 }
513 
GetEnumInfo()514 SVGElement::EnumAttributesInfo SVGSVGElement::GetEnumInfo() {
515   return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo));
516 }
517 
SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio & aPAR)518 void SVGSVGElement::SetImageOverridePreserveAspectRatio(
519     const SVGPreserveAspectRatio& aPAR) {
520   MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
521              "should only override preserveAspectRatio in images");
522 
523   bool hasViewBox = HasViewBox();
524   if (!hasViewBox && ShouldSynthesizeViewBox()) {
525     // My non-<svg:image> clients will have been painting me with a synthesized
526     // viewBox, but my <svg:image> client that's about to paint me now does NOT
527     // want that.  Need to tell ourselves to flush our transform.
528     mImageNeedsTransformInvalidation = true;
529   }
530 
531   if (!hasViewBox) {
532     return;  // preserveAspectRatio irrelevant (only matters if we have viewBox)
533   }
534 
535   if (SetPreserveAspectRatioProperty(aPAR)) {
536     mImageNeedsTransformInvalidation = true;
537   }
538 }
539 
ClearImageOverridePreserveAspectRatio()540 void SVGSVGElement::ClearImageOverridePreserveAspectRatio() {
541   MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
542              "should only override image preserveAspectRatio in images");
543 
544   if (!HasViewBox() && ShouldSynthesizeViewBox()) {
545     // My non-<svg:image> clients will want to paint me with a synthesized
546     // viewBox, but my <svg:image> client that just painted me did NOT
547     // use that.  Need to tell ourselves to flush our transform.
548     mImageNeedsTransformInvalidation = true;
549   }
550 
551   if (ClearPreserveAspectRatioProperty()) {
552     mImageNeedsTransformInvalidation = true;
553   }
554 }
555 
SetPreserveAspectRatioProperty(const SVGPreserveAspectRatio & aPAR)556 bool SVGSVGElement::SetPreserveAspectRatioProperty(
557     const SVGPreserveAspectRatio& aPAR) {
558   SVGPreserveAspectRatio* pAROverridePtr = new SVGPreserveAspectRatio(aPAR);
559   nsresult rv =
560       SetProperty(nsGkAtoms::overridePreserveAspectRatio, pAROverridePtr,
561                   nsINode::DeleteProperty<SVGPreserveAspectRatio>, true);
562   MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN,
563              "Setting override value when it's already set...?");
564 
565   if (MOZ_UNLIKELY(NS_FAILED(rv))) {
566     // property-insertion failed (e.g. OOM in property-table code)
567     delete pAROverridePtr;
568     return false;
569   }
570   return true;
571 }
572 
GetPreserveAspectRatioProperty() const573 const SVGPreserveAspectRatio* SVGSVGElement::GetPreserveAspectRatioProperty()
574     const {
575   void* valPtr = GetProperty(nsGkAtoms::overridePreserveAspectRatio);
576   if (valPtr) {
577     return static_cast<SVGPreserveAspectRatio*>(valPtr);
578   }
579   return nullptr;
580 }
581 
ClearPreserveAspectRatioProperty()582 bool SVGSVGElement::ClearPreserveAspectRatioProperty() {
583   void* valPtr = TakeProperty(nsGkAtoms::overridePreserveAspectRatio);
584   bool didHaveProperty = !!valPtr;
585   delete static_cast<SVGPreserveAspectRatio*>(valPtr);
586   return didHaveProperty;
587 }
588 
GetPreserveAspectRatioWithOverride() const589 SVGPreserveAspectRatio SVGSVGElement::GetPreserveAspectRatioWithOverride()
590     const {
591   Document* doc = GetUncomposedDoc();
592   if (doc && doc->IsBeingUsedAsImage()) {
593     const SVGPreserveAspectRatio* pAROverridePtr =
594         GetPreserveAspectRatioProperty();
595     if (pAROverridePtr) {
596       return *pAROverridePtr;
597     }
598   }
599 
600   SVGViewElement* viewElement = GetCurrentViewElement();
601 
602   // This check is equivalent to "!HasViewBox() &&
603   // ShouldSynthesizeViewBox()". We're just holding onto the viewElement that
604   // HasViewBox() would look up, so that we don't have to look it up again
605   // later.
606   if (!((viewElement && viewElement->mViewBox.HasRect()) ||
607         (mSVGView && mSVGView->mViewBox.HasRect()) || mViewBox.HasRect()) &&
608       ShouldSynthesizeViewBox()) {
609     // If we're synthesizing a viewBox, use preserveAspectRatio="none";
610     return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE,
611                                   SVG_MEETORSLICE_SLICE);
612   }
613 
614   if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) {
615     return viewElement->mPreserveAspectRatio.GetAnimValue();
616   }
617   if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) {
618     return mSVGView->mPreserveAspectRatio.GetAnimValue();
619   }
620   return mPreserveAspectRatio.GetAnimValue();
621 }
622 
GetCurrentViewElement() const623 SVGViewElement* SVGSVGElement::GetCurrentViewElement() const {
624   if (mCurrentViewID) {
625     // XXXsmaug It is unclear how this should work in case we're in Shadow DOM.
626     Document* doc = GetUncomposedDoc();
627     if (doc) {
628       Element* element = doc->GetElementById(*mCurrentViewID);
629       if (element && element->IsSVGElement(nsGkAtoms::view)) {
630         return static_cast<SVGViewElement*>(element);
631       }
632     }
633   }
634   return nullptr;
635 }
636 
GetViewBoxInternal() const637 const SVGAnimatedViewBox& SVGSVGElement::GetViewBoxInternal() const {
638   SVGViewElement* viewElement = GetCurrentViewElement();
639 
640   if (viewElement && viewElement->mViewBox.HasRect()) {
641     return viewElement->mViewBox;
642   }
643   if (mSVGView && mSVGView->mViewBox.HasRect()) {
644     return mSVGView->mViewBox;
645   }
646 
647   return mViewBox;
648 }
649 
GetTransformInternal() const650 SVGAnimatedTransformList* SVGSVGElement::GetTransformInternal() const {
651   return (mSVGView && mSVGView->mTransforms) ? mSVGView->mTransforms.get()
652                                              : mTransforms.get();
653 }
654 
655 }  // namespace dom
656 }  // namespace mozilla
657