Bug 1805294 [wpt PR 37463] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / dom / svg / SVGSVGElement.cpp
blobdc7cdd4f6077cf31809247e5c302ba129cf65105
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"
28 #include "nsIFrame.h"
29 #include "ISVGSVGFrame.h"
31 NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(SVG)
33 using namespace mozilla::gfx;
35 namespace mozilla::dom {
37 using namespace SVGPreserveAspectRatio_Binding;
38 using namespace SVGSVGElement_Binding;
40 SVGEnumMapping SVGSVGElement::sZoomAndPanMap[] = {
41 {nsGkAtoms::disable, SVG_ZOOMANDPAN_DISABLE},
42 {nsGkAtoms::magnify, SVG_ZOOMANDPAN_MAGNIFY},
43 {nullptr, 0}};
45 SVGElement::EnumInfo SVGSVGElement::sEnumInfo[1] = {
46 {nsGkAtoms::zoomAndPan, sZoomAndPanMap, SVG_ZOOMANDPAN_MAGNIFY}};
48 JSObject* SVGSVGElement::WrapNode(JSContext* aCx,
49 JS::Handle<JSObject*> aGivenProto) {
50 return SVGSVGElement_Binding::Wrap(aCx, this, aGivenProto);
53 //----------------------------------------------------------------------
54 // nsISupports methods
56 NS_IMPL_CYCLE_COLLECTION_CLASS(SVGSVGElement)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGSVGElement,
59 SVGSVGElementBase)
60 if (tmp->mTimedDocumentRoot) {
61 tmp->mTimedDocumentRoot->Unlink();
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGSVGElement,
65 SVGSVGElementBase)
66 if (tmp->mTimedDocumentRoot) {
67 tmp->mTimedDocumentRoot->Traverse(&cb);
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
71 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGSVGElement)
72 NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGSVGElement)
73 NS_INTERFACE_MAP_END_INHERITING(SVGSVGElementBase);
75 NS_IMPL_ADDREF_INHERITED(SVGSVGElement, SVGSVGElementBase)
76 NS_IMPL_RELEASE_INHERITED(SVGSVGElement, SVGSVGElementBase)
78 SVGView::SVGView() {
79 mZoomAndPan.Init(SVGSVGElement::ZOOMANDPAN, SVG_ZOOMANDPAN_MAGNIFY);
80 mViewBox.Init();
81 mPreserveAspectRatio.Init();
84 //----------------------------------------------------------------------
85 // Implementation
87 SVGSVGElement::SVGSVGElement(
88 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
89 FromParser aFromParser)
90 : SVGSVGElementBase(std::move(aNodeInfo)),
91 mCurrentTranslate(0.0f, 0.0f),
92 mCurrentScale(1.0f),
93 mStartAnimationOnBindToTree(aFromParser == NOT_FROM_PARSER ||
94 aFromParser == FROM_PARSER_FRAGMENT ||
95 aFromParser == FROM_PARSER_XSLT),
96 mImageNeedsTransformInvalidation(false) {}
98 //----------------------------------------------------------------------
99 // nsINode methods
101 NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(SVGSVGElement)
103 //----------------------------------------------------------------------
104 // nsIDOMSVGSVGElement methods:
106 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::X() {
107 return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
110 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Y() {
111 return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
114 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Width() {
115 return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
118 already_AddRefed<DOMSVGAnimatedLength> SVGSVGElement::Height() {
119 return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
122 bool SVGSVGElement::UseCurrentView() const {
123 return mSVGView || mCurrentViewID;
126 float SVGSVGElement::CurrentScale() const { return mCurrentScale; }
128 #define CURRENT_SCALE_MAX 16.0f
129 #define CURRENT_SCALE_MIN 0.0625f
131 void SVGSVGElement::SetCurrentScale(float aCurrentScale) {
132 // Prevent bizarre behaviour and maxing out of CPU and memory by clamping
133 aCurrentScale = clamped(aCurrentScale, CURRENT_SCALE_MIN, CURRENT_SCALE_MAX);
135 if (aCurrentScale == mCurrentScale) {
136 return;
138 mCurrentScale = aCurrentScale;
140 if (IsRootSVGSVGElement()) {
141 InvalidateTransformNotifyFrame();
145 already_AddRefed<DOMSVGPoint> SVGSVGElement::CurrentTranslate() {
146 return DOMSVGPoint::GetTranslateTearOff(&mCurrentTranslate, this);
149 uint32_t SVGSVGElement::SuspendRedraw(uint32_t max_wait_milliseconds) {
150 // suspendRedraw is a no-op in Mozilla, so it doesn't matter what
151 // we return
152 return 1;
155 void SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id) {
156 // no-op
159 void SVGSVGElement::UnsuspendRedrawAll() {
160 // no-op
163 void SVGSVGElement::ForceRedraw() {
164 // no-op
167 void SVGSVGElement::PauseAnimations() {
168 if (mTimedDocumentRoot) {
169 mTimedDocumentRoot->Pause(SMILTimeContainer::PAUSE_SCRIPT);
171 // else we're not the outermost <svg> or not bound to a tree, so silently fail
174 void SVGSVGElement::UnpauseAnimations() {
175 if (mTimedDocumentRoot) {
176 mTimedDocumentRoot->Resume(SMILTimeContainer::PAUSE_SCRIPT);
178 // else we're not the outermost <svg> or not bound to a tree, so silently fail
181 bool SVGSVGElement::AnimationsPaused() {
182 SMILTimeContainer* root = GetTimedDocumentRoot();
183 return root && root->IsPausedByType(SMILTimeContainer::PAUSE_SCRIPT);
186 float SVGSVGElement::GetCurrentTimeAsFloat() {
187 SMILTimeContainer* root = GetTimedDocumentRoot();
188 if (root) {
189 double fCurrentTimeMs = double(root->GetCurrentTimeAsSMILTime());
190 return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC);
192 return 0.f;
195 void SVGSVGElement::SetCurrentTime(float seconds) {
196 if (mTimedDocumentRoot) {
197 // Make sure the timegraph is up-to-date
198 FlushAnimations();
199 double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
200 // Round to nearest whole number before converting, to avoid precision
201 // errors
202 SMILTime lMilliseconds = SVGUtils::ClampToInt64(NS_round(fMilliseconds));
203 mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
204 AnimationNeedsResample();
205 // Trigger synchronous sample now, to:
206 // - Make sure we get an up-to-date paint after this method
207 // - re-enable event firing (it got disabled during seeking, and it
208 // doesn't get re-enabled until the first sample after the seek -- so
209 // let's make that happen now.)
210 FlushAnimations();
212 // else we're not the outermost <svg> or not bound to a tree, so silently fail
215 void SVGSVGElement::DeselectAll() {
216 nsIFrame* frame = GetPrimaryFrame();
217 if (frame) {
218 RefPtr<nsFrameSelection> frameSelection = frame->GetFrameSelection();
219 frameSelection->ClearNormalSelection();
223 already_AddRefed<DOMSVGNumber> SVGSVGElement::CreateSVGNumber() {
224 return do_AddRef(new DOMSVGNumber(this));
227 already_AddRefed<DOMSVGLength> SVGSVGElement::CreateSVGLength() {
228 return do_AddRef(new DOMSVGLength());
231 already_AddRefed<DOMSVGAngle> SVGSVGElement::CreateSVGAngle() {
232 return do_AddRef(new DOMSVGAngle(this));
235 already_AddRefed<DOMSVGPoint> SVGSVGElement::CreateSVGPoint() {
236 return do_AddRef(new DOMSVGPoint(Point(0, 0)));
239 already_AddRefed<SVGMatrix> SVGSVGElement::CreateSVGMatrix() {
240 return do_AddRef(new SVGMatrix());
243 already_AddRefed<SVGRect> SVGSVGElement::CreateSVGRect() {
244 return do_AddRef(new SVGRect(this));
247 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransform() {
248 return do_AddRef(new DOMSVGTransform());
251 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransformFromMatrix(
252 const DOMMatrix2DInit& matrix, ErrorResult& rv) {
253 return do_AddRef(new DOMSVGTransform(matrix, rv));
256 void SVGSVGElement::DidChangeTranslate() {
257 if (Document* doc = GetUncomposedDoc()) {
258 RefPtr<PresShell> presShell = doc->GetPresShell();
259 // now dispatch the appropriate event if we are the root element
260 if (presShell && IsRootSVGSVGElement()) {
261 nsEventStatus status = nsEventStatus_eIgnore;
262 WidgetEvent svgScrollEvent(true, eSVGScroll);
263 presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
264 InvalidateTransformNotifyFrame();
269 //----------------------------------------------------------------------
270 // SVGZoomAndPanValues
271 uint16_t SVGSVGElement::ZoomAndPan() const {
272 return mEnumAttributes[ZOOMANDPAN].GetAnimValue();
275 void SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv) {
276 if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE ||
277 aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) {
278 ErrorResult nestedRv;
279 mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this, nestedRv);
280 MOZ_ASSERT(!nestedRv.Failed(),
281 "We already validated our aZoomAndPan value!");
282 return;
285 rv.ThrowRangeError<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR>();
288 //----------------------------------------------------------------------
289 SMILTimeContainer* SVGSVGElement::GetTimedDocumentRoot() {
290 if (mTimedDocumentRoot) {
291 return mTimedDocumentRoot.get();
294 // We must not be the outermost <svg> element, try to find it
295 SVGSVGElement* outerSVGElement = SVGContentUtils::GetOuterSVGElement(this);
297 if (outerSVGElement) {
298 return outerSVGElement->GetTimedDocumentRoot();
300 // invalid structure
301 return nullptr;
303 //----------------------------------------------------------------------
304 // SVGElement
305 nsresult SVGSVGElement::BindToTree(BindContext& aContext, nsINode& aParent) {
306 SMILAnimationController* smilController = nullptr;
308 if (Document* doc = aContext.GetComposedDoc()) {
309 if ((smilController = doc->GetAnimationController())) {
310 // SMIL is enabled in this document
311 if (WillBeOutermostSVG(aParent)) {
312 // We'll be the outermost <svg> element. We'll need a time container.
313 if (!mTimedDocumentRoot) {
314 mTimedDocumentRoot = MakeUnique<SMILTimeContainer>();
316 } else {
317 // We're a child of some other <svg> element, so we don't need our own
318 // time container. However, we need to make sure that we'll get a
319 // kick-start if we get promoted to be outermost later on.
320 mTimedDocumentRoot = nullptr;
321 mStartAnimationOnBindToTree = true;
326 nsresult rv = SVGGraphicsElement::BindToTree(aContext, aParent);
327 NS_ENSURE_SUCCESS(rv, rv);
329 if (mTimedDocumentRoot && smilController) {
330 rv = mTimedDocumentRoot->SetParent(smilController);
331 if (mStartAnimationOnBindToTree) {
332 mTimedDocumentRoot->Begin();
333 mStartAnimationOnBindToTree = false;
337 return rv;
340 void SVGSVGElement::UnbindFromTree(bool aNullParent) {
341 if (mTimedDocumentRoot) {
342 mTimedDocumentRoot->SetParent(nullptr);
345 SVGGraphicsElement::UnbindFromTree(aNullParent);
348 SVGAnimatedTransformList* SVGSVGElement::GetAnimatedTransformList(
349 uint32_t aFlags) {
350 if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) {
351 return mSVGView->mTransforms.get();
353 return SVGGraphicsElement::GetAnimatedTransformList(aFlags);
356 void SVGSVGElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
357 if (aVisitor.mEvent->mMessage == eSVGLoad) {
358 if (mTimedDocumentRoot) {
359 mTimedDocumentRoot->Begin();
360 // Set 'resample needed' flag, so that if any script calls a DOM method
361 // that requires up-to-date animations before our first sample callback,
362 // we'll force a synchronous sample.
363 AnimationNeedsResample();
366 SVGSVGElementBase::GetEventTargetParent(aVisitor);
369 bool SVGSVGElement::IsEventAttributeNameInternal(nsAtom* aName) {
370 /* The events in EventNameType_SVGSVG are for events that are only
371 applicable to outermost 'svg' elements. We don't check if we're an outer
372 'svg' element in case we're not inserted into the document yet, but since
373 the target of the events in question will always be the outermost 'svg'
374 element, this shouldn't cause any real problems.
376 return nsContentUtils::IsEventAttributeName(
377 aName, (EventNameType_SVGGraphic | EventNameType_SVGSVG));
380 //----------------------------------------------------------------------
381 // public helpers:
383 int32_t SVGSVGElement::GetIntrinsicWidth() {
384 if (mLengthAttributes[ATTR_WIDTH].IsPercentage()) {
385 return -1;
387 // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
388 // that uses the passed argument as the context, but that's fine since we
389 // know the length isn't a percentage so the context won't be used (and we
390 // need to pass the element to be able to resolve em/ex units).
391 float width = mLengthAttributes[ATTR_WIDTH].GetAnimValue(this);
392 return SVGUtils::ClampToInt(width);
395 int32_t SVGSVGElement::GetIntrinsicHeight() {
396 if (mLengthAttributes[ATTR_HEIGHT].IsPercentage()) {
397 return -1;
399 // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
400 // that uses the passed argument as the context, but that's fine since we
401 // know the length isn't a percentage so the context won't be used (and we
402 // need to pass the element to be able to resolve em/ex units).
403 float height = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(this);
404 return SVGUtils::ClampToInt(height);
407 void SVGSVGElement::FlushImageTransformInvalidation() {
408 MOZ_ASSERT(!GetParent(), "Should only be called on root node");
409 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
410 "Should only be called on image documents");
412 if (mImageNeedsTransformInvalidation) {
413 InvalidateTransformNotifyFrame();
414 mImageNeedsTransformInvalidation = false;
418 //----------------------------------------------------------------------
419 // implementation helpers
421 bool SVGSVGElement::WillBeOutermostSVG(nsINode& aParent) const {
422 nsINode* parent = &aParent;
423 while (parent && parent->IsSVGElement()) {
424 if (parent->IsSVGElement(nsGkAtoms::foreignObject)) {
425 // SVG in a foreignObject must have its own <svg> (SVGOuterSVGFrame).
426 return false;
428 if (parent->IsSVGElement(nsGkAtoms::svg)) {
429 return false;
431 parent = parent->GetParentOrShadowHostNode();
434 return true;
437 void SVGSVGElement::InvalidateTransformNotifyFrame() {
438 ISVGSVGFrame* svgframe = do_QueryFrame(GetPrimaryFrame());
439 // might fail this check if we've failed conditional processing
440 if (svgframe) {
441 svgframe->NotifyViewportOrTransformChanged(
442 ISVGDisplayableFrame::TRANSFORM_CHANGED);
446 SVGElement::EnumAttributesInfo SVGSVGElement::GetEnumInfo() {
447 return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo));
450 void SVGSVGElement::SetImageOverridePreserveAspectRatio(
451 const SVGPreserveAspectRatio& aPAR) {
452 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
453 "should only override preserveAspectRatio in images");
455 bool hasViewBox = HasViewBox();
456 if (!hasViewBox && ShouldSynthesizeViewBox()) {
457 // My non-<svg:image> clients will have been painting me with a synthesized
458 // viewBox, but my <svg:image> client that's about to paint me now does NOT
459 // want that. Need to tell ourselves to flush our transform.
460 mImageNeedsTransformInvalidation = true;
463 if (!hasViewBox) {
464 return; // preserveAspectRatio irrelevant (only matters if we have viewBox)
467 if (SetPreserveAspectRatioProperty(aPAR)) {
468 mImageNeedsTransformInvalidation = true;
472 void SVGSVGElement::ClearImageOverridePreserveAspectRatio() {
473 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
474 "should only override image preserveAspectRatio in images");
476 if (!HasViewBox() && ShouldSynthesizeViewBox()) {
477 // My non-<svg:image> clients will want to paint me with a synthesized
478 // viewBox, but my <svg:image> client that just painted me did NOT
479 // use that. Need to tell ourselves to flush our transform.
480 mImageNeedsTransformInvalidation = true;
483 if (ClearPreserveAspectRatioProperty()) {
484 mImageNeedsTransformInvalidation = true;
488 bool SVGSVGElement::SetPreserveAspectRatioProperty(
489 const SVGPreserveAspectRatio& aPAR) {
490 SVGPreserveAspectRatio* pAROverridePtr = new SVGPreserveAspectRatio(aPAR);
491 nsresult rv =
492 SetProperty(nsGkAtoms::overridePreserveAspectRatio, pAROverridePtr,
493 nsINode::DeleteProperty<SVGPreserveAspectRatio>, true);
494 MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN,
495 "Setting override value when it's already set...?");
497 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
498 // property-insertion failed (e.g. OOM in property-table code)
499 delete pAROverridePtr;
500 return false;
502 return true;
505 const SVGPreserveAspectRatio* SVGSVGElement::GetPreserveAspectRatioProperty()
506 const {
507 void* valPtr = GetProperty(nsGkAtoms::overridePreserveAspectRatio);
508 if (valPtr) {
509 return static_cast<SVGPreserveAspectRatio*>(valPtr);
511 return nullptr;
514 bool SVGSVGElement::ClearPreserveAspectRatioProperty() {
515 void* valPtr = TakeProperty(nsGkAtoms::overridePreserveAspectRatio);
516 bool didHaveProperty = !!valPtr;
517 delete static_cast<SVGPreserveAspectRatio*>(valPtr);
518 return didHaveProperty;
521 SVGPreserveAspectRatio SVGSVGElement::GetPreserveAspectRatioWithOverride()
522 const {
523 Document* doc = GetUncomposedDoc();
524 if (doc && doc->IsBeingUsedAsImage()) {
525 const SVGPreserveAspectRatio* pAROverridePtr =
526 GetPreserveAspectRatioProperty();
527 if (pAROverridePtr) {
528 return *pAROverridePtr;
532 SVGViewElement* viewElement = GetCurrentViewElement();
534 // This check is equivalent to "!HasViewBox() &&
535 // ShouldSynthesizeViewBox()". We're just holding onto the viewElement that
536 // HasViewBox() would look up, so that we don't have to look it up again
537 // later.
538 if (!((viewElement && viewElement->mViewBox.HasRect()) ||
539 (mSVGView && mSVGView->mViewBox.HasRect()) || mViewBox.HasRect()) &&
540 ShouldSynthesizeViewBox()) {
541 // If we're synthesizing a viewBox, use preserveAspectRatio="none";
542 return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE,
543 SVG_MEETORSLICE_SLICE);
546 if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) {
547 return viewElement->mPreserveAspectRatio.GetAnimValue();
549 if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) {
550 return mSVGView->mPreserveAspectRatio.GetAnimValue();
552 return mPreserveAspectRatio.GetAnimValue();
555 SVGViewElement* SVGSVGElement::GetCurrentViewElement() const {
556 if (mCurrentViewID) {
557 // XXXsmaug It is unclear how this should work in case we're in Shadow DOM.
558 Document* doc = GetUncomposedDoc();
559 if (doc) {
560 Element* element = doc->GetElementById(*mCurrentViewID);
561 if (element && element->IsSVGElement(nsGkAtoms::view)) {
562 return static_cast<SVGViewElement*>(element);
566 return nullptr;
569 const SVGAnimatedViewBox& SVGSVGElement::GetViewBoxInternal() const {
570 SVGViewElement* viewElement = GetCurrentViewElement();
572 if (viewElement && viewElement->mViewBox.HasRect()) {
573 return viewElement->mViewBox;
575 if (mSVGView && mSVGView->mViewBox.HasRect()) {
576 return mSVGView->mViewBox;
579 return mViewBox;
582 SVGAnimatedTransformList* SVGSVGElement::GetTransformInternal() const {
583 return (mSVGView && mSVGView->mTransforms) ? mSVGView->mTransforms.get()
584 : mTransforms.get();
587 } // namespace mozilla::dom