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/. */
7 #include "mozilla/dom/SVGSVGElement.h"
9 #include "mozilla/ContentEvents.h"
10 #include "mozilla/dom/BindContext.h"
11 #include "mozilla/dom/DOMMatrix.h"
12 #include "mozilla/dom/SVGSVGElementBinding.h"
13 #include "mozilla/dom/SVGMatrix.h"
14 #include "mozilla/dom/SVGRect.h"
15 #include "mozilla/dom/SVGViewElement.h"
16 #include "mozilla/EventDispatcher.h"
17 #include "mozilla/ISVGDisplayableFrame.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/SMILAnimationController.h"
20 #include "mozilla/SMILTimeContainer.h"
21 #include "mozilla/SVGUtils.h"
23 #include "DOMSVGAngle.h"
24 #include "DOMSVGLength.h"
25 #include "DOMSVGNumber.h"
26 #include "DOMSVGPoint.h"
27 #include "nsFrameSelection.h"
29 #include "ISVGSVGFrame.h"
31 NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(SVG
)
33 using namespace mozilla::gfx
;
38 using namespace SVGPreserveAspectRatio_Binding
;
39 using namespace SVGSVGElement_Binding
;
41 SVGEnumMapping
SVGSVGElement::sZoomAndPanMap
[] = {
42 {nsGkAtoms::disable
, SVG_ZOOMANDPAN_DISABLE
},
43 {nsGkAtoms::magnify
, SVG_ZOOMANDPAN_MAGNIFY
},
46 SVGElement::EnumInfo
SVGSVGElement::sEnumInfo
[1] = {
47 {nsGkAtoms::zoomAndPan
, sZoomAndPanMap
, SVG_ZOOMANDPAN_MAGNIFY
}};
49 JSObject
* SVGSVGElement::WrapNode(JSContext
* aCx
,
50 JS::Handle
<JSObject
*> aGivenProto
) {
51 return SVGSVGElement_Binding::Wrap(aCx
, this, aGivenProto
);
54 //----------------------------------------------------------------------
55 // nsISupports methods
57 NS_IMPL_CYCLE_COLLECTION_CLASS(SVGSVGElement
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGSVGElement
,
61 if (tmp
->mTimedDocumentRoot
) {
62 tmp
->mTimedDocumentRoot
->Unlink();
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
65 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGSVGElement
,
67 if (tmp
->mTimedDocumentRoot
) {
68 tmp
->mTimedDocumentRoot
->Traverse(&cb
);
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
72 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGSVGElement
)
73 NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGSVGElement
)
74 NS_INTERFACE_MAP_END_INHERITING(SVGSVGElementBase
);
76 NS_IMPL_ADDREF_INHERITED(SVGSVGElement
, SVGSVGElementBase
)
77 NS_IMPL_RELEASE_INHERITED(SVGSVGElement
, SVGSVGElementBase
)
80 mZoomAndPan
.Init(SVGSVGElement::ZOOMANDPAN
, SVG_ZOOMANDPAN_MAGNIFY
);
82 mPreserveAspectRatio
.Init();
85 //----------------------------------------------------------------------
88 SVGSVGElement::SVGSVGElement(
89 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
90 FromParser aFromParser
)
91 : SVGSVGElementBase(std::move(aNodeInfo
)),
92 mCurrentTranslate(0.0f
, 0.0f
),
94 mStartAnimationOnBindToTree(aFromParser
== NOT_FROM_PARSER
||
95 aFromParser
== FROM_PARSER_FRAGMENT
||
96 aFromParser
== FROM_PARSER_XSLT
),
97 mImageNeedsTransformInvalidation(false) {}
99 //----------------------------------------------------------------------
102 NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(SVGSVGElement
)
104 //----------------------------------------------------------------------
105 // nsIDOMSVGSVGElement methods:
107 already_AddRefed
<DOMSVGAnimatedLength
> SVGSVGElement::X() {
108 return mLengthAttributes
[ATTR_X
].ToDOMAnimatedLength(this);
111 already_AddRefed
<DOMSVGAnimatedLength
> SVGSVGElement::Y() {
112 return mLengthAttributes
[ATTR_Y
].ToDOMAnimatedLength(this);
115 already_AddRefed
<DOMSVGAnimatedLength
> SVGSVGElement::Width() {
116 return mLengthAttributes
[ATTR_WIDTH
].ToDOMAnimatedLength(this);
119 already_AddRefed
<DOMSVGAnimatedLength
> SVGSVGElement::Height() {
120 return mLengthAttributes
[ATTR_HEIGHT
].ToDOMAnimatedLength(this);
123 bool SVGSVGElement::UseCurrentView() const {
124 return mSVGView
|| mCurrentViewID
;
127 float SVGSVGElement::CurrentScale() const { return mCurrentScale
; }
129 #define CURRENT_SCALE_MAX 16.0f
130 #define CURRENT_SCALE_MIN 0.0625f
132 void SVGSVGElement::SetCurrentScale(float aCurrentScale
) {
133 // Prevent bizarre behaviour and maxing out of CPU and memory by clamping
134 aCurrentScale
= clamped(aCurrentScale
, CURRENT_SCALE_MIN
, CURRENT_SCALE_MAX
);
136 if (aCurrentScale
== mCurrentScale
) {
139 mCurrentScale
= aCurrentScale
;
141 if (IsRootSVGSVGElement()) {
142 InvalidateTransformNotifyFrame();
146 already_AddRefed
<DOMSVGPoint
> SVGSVGElement::CurrentTranslate() {
147 return DOMSVGPoint::GetTranslateTearOff(&mCurrentTranslate
, this);
150 uint32_t SVGSVGElement::SuspendRedraw(uint32_t max_wait_milliseconds
) {
151 // suspendRedraw is a no-op in Mozilla, so it doesn't matter what
156 void SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id
) {
160 void SVGSVGElement::UnsuspendRedrawAll() {
164 void SVGSVGElement::ForceRedraw() {
168 void SVGSVGElement::PauseAnimations() {
169 if (mTimedDocumentRoot
) {
170 mTimedDocumentRoot
->Pause(SMILTimeContainer::PAUSE_SCRIPT
);
172 // else we're not the outermost <svg> or not bound to a tree, so silently fail
175 void SVGSVGElement::UnpauseAnimations() {
176 if (mTimedDocumentRoot
) {
177 mTimedDocumentRoot
->Resume(SMILTimeContainer::PAUSE_SCRIPT
);
179 // else we're not the outermost <svg> or not bound to a tree, so silently fail
182 bool SVGSVGElement::AnimationsPaused() {
183 SMILTimeContainer
* root
= GetTimedDocumentRoot();
184 return root
&& root
->IsPausedByType(SMILTimeContainer::PAUSE_SCRIPT
);
187 float SVGSVGElement::GetCurrentTimeAsFloat() {
188 SMILTimeContainer
* root
= GetTimedDocumentRoot();
190 double fCurrentTimeMs
= double(root
->GetCurrentTimeAsSMILTime());
191 return (float)(fCurrentTimeMs
/ PR_MSEC_PER_SEC
);
196 void SVGSVGElement::SetCurrentTime(float seconds
) {
197 if (mTimedDocumentRoot
) {
198 // Make sure the timegraph is up-to-date
200 double fMilliseconds
= double(seconds
) * PR_MSEC_PER_SEC
;
201 // Round to nearest whole number before converting, to avoid precision
203 SMILTime lMilliseconds
= int64_t(NS_round(fMilliseconds
));
204 mTimedDocumentRoot
->SetCurrentTime(lMilliseconds
);
205 AnimationNeedsResample();
206 // Trigger synchronous sample now, to:
207 // - Make sure we get an up-to-date paint after this method
208 // - re-enable event firing (it got disabled during seeking, and it
209 // doesn't get re-enabled until the first sample after the seek -- so
210 // let's make that happen now.)
213 // else we're not the outermost <svg> or not bound to a tree, so silently fail
216 void SVGSVGElement::DeselectAll() {
217 nsIFrame
* frame
= GetPrimaryFrame();
219 RefPtr
<nsFrameSelection
> frameSelection
= frame
->GetFrameSelection();
220 frameSelection
->ClearNormalSelection();
224 already_AddRefed
<DOMSVGNumber
> SVGSVGElement::CreateSVGNumber() {
225 return do_AddRef(new DOMSVGNumber(this));
228 already_AddRefed
<DOMSVGLength
> SVGSVGElement::CreateSVGLength() {
229 return do_AddRef(new DOMSVGLength());
232 already_AddRefed
<DOMSVGAngle
> SVGSVGElement::CreateSVGAngle() {
233 return do_AddRef(new DOMSVGAngle(this));
236 already_AddRefed
<DOMSVGPoint
> SVGSVGElement::CreateSVGPoint() {
237 return do_AddRef(new DOMSVGPoint(Point(0, 0)));
240 already_AddRefed
<SVGMatrix
> SVGSVGElement::CreateSVGMatrix() {
241 return do_AddRef(new SVGMatrix());
244 already_AddRefed
<SVGRect
> SVGSVGElement::CreateSVGRect() {
245 return do_AddRef(new SVGRect(this));
248 already_AddRefed
<DOMSVGTransform
> SVGSVGElement::CreateSVGTransform() {
249 return do_AddRef(new DOMSVGTransform());
252 already_AddRefed
<DOMSVGTransform
> SVGSVGElement::CreateSVGTransformFromMatrix(
253 const DOMMatrix2DInit
& matrix
, ErrorResult
& rv
) {
254 return do_AddRef(new DOMSVGTransform(matrix
, rv
));
257 void SVGSVGElement::DidChangeTranslate() {
258 if (Document
* doc
= GetUncomposedDoc()) {
259 RefPtr
<PresShell
> presShell
= doc
->GetPresShell();
260 // now dispatch the appropriate event if we are the root element
261 if (presShell
&& IsRootSVGSVGElement()) {
262 nsEventStatus status
= nsEventStatus_eIgnore
;
263 WidgetEvent
svgScrollEvent(true, eSVGScroll
);
264 presShell
->HandleDOMEventWithTarget(this, &svgScrollEvent
, &status
);
265 InvalidateTransformNotifyFrame();
270 //----------------------------------------------------------------------
271 // SVGZoomAndPanValues
272 uint16_t SVGSVGElement::ZoomAndPan() const {
273 return mEnumAttributes
[ZOOMANDPAN
].GetAnimValue();
276 void SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan
, ErrorResult
& rv
) {
277 if (aZoomAndPan
== SVG_ZOOMANDPAN_DISABLE
||
278 aZoomAndPan
== SVG_ZOOMANDPAN_MAGNIFY
) {
279 ErrorResult nestedRv
;
280 mEnumAttributes
[ZOOMANDPAN
].SetBaseValue(aZoomAndPan
, this, nestedRv
);
281 MOZ_ASSERT(!nestedRv
.Failed(),
282 "We already validated our aZoomAndPan value!");
286 rv
.ThrowRangeError
<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR
>();
289 //----------------------------------------------------------------------
290 SMILTimeContainer
* SVGSVGElement::GetTimedDocumentRoot() {
291 if (mTimedDocumentRoot
) {
292 return mTimedDocumentRoot
.get();
295 // We must not be the outermost <svg> element, try to find it
296 SVGSVGElement
* outerSVGElement
= SVGContentUtils::GetOuterSVGElement(this);
298 if (outerSVGElement
) {
299 return outerSVGElement
->GetTimedDocumentRoot();
304 //----------------------------------------------------------------------
306 nsresult
SVGSVGElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
307 SMILAnimationController
* smilController
= nullptr;
309 if (Document
* doc
= aContext
.GetComposedDoc()) {
310 if ((smilController
= doc
->GetAnimationController())) {
311 // SMIL is enabled in this document
312 if (WillBeOutermostSVG(aParent
)) {
313 // We'll be the outermost <svg> element. We'll need a time container.
314 if (!mTimedDocumentRoot
) {
315 mTimedDocumentRoot
= MakeUnique
<SMILTimeContainer
>();
318 // We're a child of some other <svg> element, so we don't need our own
319 // time container. However, we need to make sure that we'll get a
320 // kick-start if we get promoted to be outermost later on.
321 mTimedDocumentRoot
= nullptr;
322 mStartAnimationOnBindToTree
= true;
327 nsresult rv
= SVGGraphicsElement::BindToTree(aContext
, aParent
);
328 NS_ENSURE_SUCCESS(rv
, rv
);
330 if (mTimedDocumentRoot
&& smilController
) {
331 rv
= mTimedDocumentRoot
->SetParent(smilController
);
332 if (mStartAnimationOnBindToTree
) {
333 mTimedDocumentRoot
->Begin();
334 mStartAnimationOnBindToTree
= false;
341 void SVGSVGElement::UnbindFromTree(bool aNullParent
) {
342 if (mTimedDocumentRoot
) {
343 mTimedDocumentRoot
->SetParent(nullptr);
346 SVGGraphicsElement::UnbindFromTree(aNullParent
);
349 SVGAnimatedTransformList
* SVGSVGElement::GetAnimatedTransformList(
351 if (!(aFlags
& DO_ALLOCATE
) && mSVGView
&& mSVGView
->mTransforms
) {
352 return mSVGView
->mTransforms
.get();
354 return SVGGraphicsElement::GetAnimatedTransformList(aFlags
);
357 void SVGSVGElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
358 if (aVisitor
.mEvent
->mMessage
== eSVGLoad
) {
359 if (mTimedDocumentRoot
) {
360 mTimedDocumentRoot
->Begin();
361 // Set 'resample needed' flag, so that if any script calls a DOM method
362 // that requires up-to-date animations before our first sample callback,
363 // we'll force a synchronous sample.
364 AnimationNeedsResample();
367 SVGSVGElementBase::GetEventTargetParent(aVisitor
);
370 bool SVGSVGElement::IsEventAttributeNameInternal(nsAtom
* aName
) {
371 /* The events in EventNameType_SVGSVG are for events that are only
372 applicable to outermost 'svg' elements. We don't check if we're an outer
373 'svg' element in case we're not inserted into the document yet, but since
374 the target of the events in question will always be the outermost 'svg'
375 element, this shouldn't cause any real problems.
377 return nsContentUtils::IsEventAttributeName(
378 aName
, (EventNameType_SVGGraphic
| EventNameType_SVGSVG
));
381 //----------------------------------------------------------------------
384 int32_t SVGSVGElement::GetIntrinsicWidth() {
385 if (mLengthAttributes
[ATTR_WIDTH
].IsPercentage()) {
388 // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
389 // that uses the passed argument as the context, but that's fine since we
390 // know the length isn't a percentage so the context won't be used (and we
391 // need to pass the element to be able to resolve em/ex units).
392 float width
= mLengthAttributes
[ATTR_WIDTH
].GetAnimValue(this);
393 return SVGUtils::ClampToInt(width
);
396 int32_t SVGSVGElement::GetIntrinsicHeight() {
397 if (mLengthAttributes
[ATTR_HEIGHT
].IsPercentage()) {
400 // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
401 // that uses the passed argument as the context, but that's fine since we
402 // know the length isn't a percentage so the context won't be used (and we
403 // need to pass the element to be able to resolve em/ex units).
404 float height
= mLengthAttributes
[ATTR_HEIGHT
].GetAnimValue(this);
405 return SVGUtils::ClampToInt(height
);
408 void SVGSVGElement::FlushImageTransformInvalidation() {
409 MOZ_ASSERT(!GetParent(), "Should only be called on root node");
410 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
411 "Should only be called on image documents");
413 if (mImageNeedsTransformInvalidation
) {
414 InvalidateTransformNotifyFrame();
415 mImageNeedsTransformInvalidation
= false;
419 //----------------------------------------------------------------------
420 // implementation helpers
422 bool SVGSVGElement::WillBeOutermostSVG(nsINode
& aParent
) const {
423 nsINode
* parent
= &aParent
;
424 while (parent
&& parent
->IsSVGElement()) {
425 if (parent
->IsSVGElement(nsGkAtoms::foreignObject
)) {
426 // SVG in a foreignObject must have its own <svg> (SVGOuterSVGFrame).
429 if (parent
->IsSVGElement(nsGkAtoms::svg
)) {
432 parent
= parent
->GetParentOrShadowHostNode();
438 void SVGSVGElement::InvalidateTransformNotifyFrame() {
439 ISVGSVGFrame
* svgframe
= do_QueryFrame(GetPrimaryFrame());
440 // might fail this check if we've failed conditional processing
442 svgframe
->NotifyViewportOrTransformChanged(
443 ISVGDisplayableFrame::TRANSFORM_CHANGED
);
447 SVGElement::EnumAttributesInfo
SVGSVGElement::GetEnumInfo() {
448 return EnumAttributesInfo(mEnumAttributes
, sEnumInfo
, ArrayLength(sEnumInfo
));
451 void SVGSVGElement::SetImageOverridePreserveAspectRatio(
452 const SVGPreserveAspectRatio
& aPAR
) {
453 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
454 "should only override preserveAspectRatio in images");
456 bool hasViewBox
= HasViewBox();
457 if (!hasViewBox
&& ShouldSynthesizeViewBox()) {
458 // My non-<svg:image> clients will have been painting me with a synthesized
459 // viewBox, but my <svg:image> client that's about to paint me now does NOT
460 // want that. Need to tell ourselves to flush our transform.
461 mImageNeedsTransformInvalidation
= true;
465 return; // preserveAspectRatio irrelevant (only matters if we have viewBox)
468 if (SetPreserveAspectRatioProperty(aPAR
)) {
469 mImageNeedsTransformInvalidation
= true;
473 void SVGSVGElement::ClearImageOverridePreserveAspectRatio() {
474 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
475 "should only override image preserveAspectRatio in images");
477 if (!HasViewBox() && ShouldSynthesizeViewBox()) {
478 // My non-<svg:image> clients will want to paint me with a synthesized
479 // viewBox, but my <svg:image> client that just painted me did NOT
480 // use that. Need to tell ourselves to flush our transform.
481 mImageNeedsTransformInvalidation
= true;
484 if (ClearPreserveAspectRatioProperty()) {
485 mImageNeedsTransformInvalidation
= true;
489 bool SVGSVGElement::SetPreserveAspectRatioProperty(
490 const SVGPreserveAspectRatio
& aPAR
) {
491 SVGPreserveAspectRatio
* pAROverridePtr
= new SVGPreserveAspectRatio(aPAR
);
493 SetProperty(nsGkAtoms::overridePreserveAspectRatio
, pAROverridePtr
,
494 nsINode::DeleteProperty
<SVGPreserveAspectRatio
>, true);
495 MOZ_ASSERT(rv
!= NS_PROPTABLE_PROP_OVERWRITTEN
,
496 "Setting override value when it's already set...?");
498 if (MOZ_UNLIKELY(NS_FAILED(rv
))) {
499 // property-insertion failed (e.g. OOM in property-table code)
500 delete pAROverridePtr
;
506 const SVGPreserveAspectRatio
* SVGSVGElement::GetPreserveAspectRatioProperty()
508 void* valPtr
= GetProperty(nsGkAtoms::overridePreserveAspectRatio
);
510 return static_cast<SVGPreserveAspectRatio
*>(valPtr
);
515 bool SVGSVGElement::ClearPreserveAspectRatioProperty() {
516 void* valPtr
= TakeProperty(nsGkAtoms::overridePreserveAspectRatio
);
517 bool didHaveProperty
= !!valPtr
;
518 delete static_cast<SVGPreserveAspectRatio
*>(valPtr
);
519 return didHaveProperty
;
522 SVGPreserveAspectRatio
SVGSVGElement::GetPreserveAspectRatioWithOverride()
524 Document
* doc
= GetUncomposedDoc();
525 if (doc
&& doc
->IsBeingUsedAsImage()) {
526 const SVGPreserveAspectRatio
* pAROverridePtr
=
527 GetPreserveAspectRatioProperty();
528 if (pAROverridePtr
) {
529 return *pAROverridePtr
;
533 SVGViewElement
* viewElement
= GetCurrentViewElement();
535 // This check is equivalent to "!HasViewBox() &&
536 // ShouldSynthesizeViewBox()". We're just holding onto the viewElement that
537 // HasViewBox() would look up, so that we don't have to look it up again
539 if (!((viewElement
&& viewElement
->mViewBox
.HasRect()) ||
540 (mSVGView
&& mSVGView
->mViewBox
.HasRect()) || mViewBox
.HasRect()) &&
541 ShouldSynthesizeViewBox()) {
542 // If we're synthesizing a viewBox, use preserveAspectRatio="none";
543 return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE
,
544 SVG_MEETORSLICE_SLICE
);
547 if (viewElement
&& viewElement
->mPreserveAspectRatio
.IsExplicitlySet()) {
548 return viewElement
->mPreserveAspectRatio
.GetAnimValue();
550 if (mSVGView
&& mSVGView
->mPreserveAspectRatio
.IsExplicitlySet()) {
551 return mSVGView
->mPreserveAspectRatio
.GetAnimValue();
553 return mPreserveAspectRatio
.GetAnimValue();
556 SVGViewElement
* SVGSVGElement::GetCurrentViewElement() const {
557 if (mCurrentViewID
) {
558 // XXXsmaug It is unclear how this should work in case we're in Shadow DOM.
559 Document
* doc
= GetUncomposedDoc();
561 Element
* element
= doc
->GetElementById(*mCurrentViewID
);
562 if (element
&& element
->IsSVGElement(nsGkAtoms::view
)) {
563 return static_cast<SVGViewElement
*>(element
);
570 const SVGAnimatedViewBox
& SVGSVGElement::GetViewBoxInternal() const {
571 SVGViewElement
* viewElement
= GetCurrentViewElement();
573 if (viewElement
&& viewElement
->mViewBox
.HasRect()) {
574 return viewElement
->mViewBox
;
576 if (mSVGView
&& mSVGView
->mViewBox
.HasRect()) {
577 return mSVGView
->mViewBox
;
583 SVGAnimatedTransformList
* SVGSVGElement::GetTransformInternal() const {
584 return (mSVGView
&& mSVGView
->mTransforms
) ? mSVGView
->mTransforms
.get()
589 } // namespace mozilla