Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / svg / SVGSVGElement.cpp
blobdfb6dd82114059424c1c25da978df15844d60677
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 =
134 std::clamp(aCurrentScale, CURRENT_SCALE_MIN, CURRENT_SCALE_MAX);
136 if (aCurrentScale == mCurrentScale) {
137 return;
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
152 // we return
153 return 1;
156 void SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id) {
157 // no-op
160 void SVGSVGElement::UnsuspendRedrawAll() {
161 // no-op
164 void SVGSVGElement::ForceRedraw() {
165 // no-op
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();
189 if (root) {
190 double fCurrentTimeMs = double(root->GetCurrentTimeAsSMILTime());
191 return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC);
193 return 0.f;
196 void SVGSVGElement::SetCurrentTime(float seconds) {
197 if (!mTimedDocumentRoot) {
198 // we're not the outermost <svg> or not bound to a tree, so silently fail
199 return;
201 // Make sure the timegraph is up-to-date
202 if (auto* currentDoc = GetComposedDoc()) {
203 currentDoc->FlushPendingNotifications(FlushType::Style);
205 if (!mTimedDocumentRoot) {
206 return;
208 FlushAnimations();
209 double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
210 // Round to nearest whole number before converting, to avoid precision
211 // errors
212 SMILTime lMilliseconds = SVGUtils::ClampToInt64(NS_round(fMilliseconds));
213 mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
214 AnimationNeedsResample();
215 // Trigger synchronous sample now, to:
216 // - Make sure we get an up-to-date paint after this method
217 // - re-enable event firing (it got disabled during seeking, and it
218 // doesn't get re-enabled until the first sample after the seek -- so
219 // let's make that happen now.)
220 FlushAnimations();
223 void SVGSVGElement::DeselectAll() {
224 nsIFrame* frame = GetPrimaryFrame();
225 if (frame) {
226 RefPtr<nsFrameSelection> frameSelection = frame->GetFrameSelection();
227 frameSelection->ClearNormalSelection();
231 already_AddRefed<DOMSVGNumber> SVGSVGElement::CreateSVGNumber() {
232 return do_AddRef(new DOMSVGNumber(this));
235 already_AddRefed<DOMSVGLength> SVGSVGElement::CreateSVGLength() {
236 return do_AddRef(new DOMSVGLength());
239 already_AddRefed<DOMSVGAngle> SVGSVGElement::CreateSVGAngle() {
240 return do_AddRef(new DOMSVGAngle(this));
243 already_AddRefed<DOMSVGPoint> SVGSVGElement::CreateSVGPoint() {
244 return do_AddRef(new DOMSVGPoint(Point(0, 0)));
247 already_AddRefed<SVGMatrix> SVGSVGElement::CreateSVGMatrix() {
248 return do_AddRef(new SVGMatrix());
251 already_AddRefed<SVGRect> SVGSVGElement::CreateSVGRect() {
252 return do_AddRef(new SVGRect(this));
255 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransform() {
256 return do_AddRef(new DOMSVGTransform());
259 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransformFromMatrix(
260 const DOMMatrix2DInit& matrix, ErrorResult& rv) {
261 return do_AddRef(new DOMSVGTransform(matrix, rv));
264 void SVGSVGElement::DidChangeTranslate() {
265 if (Document* doc = GetUncomposedDoc()) {
266 RefPtr<PresShell> presShell = doc->GetPresShell();
267 // now dispatch the appropriate event if we are the root element
268 if (presShell && IsRootSVGSVGElement()) {
269 nsEventStatus status = nsEventStatus_eIgnore;
270 WidgetEvent svgScrollEvent(true, eSVGScroll);
271 presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
272 InvalidateTransformNotifyFrame();
277 //----------------------------------------------------------------------
278 // SVGZoomAndPanValues
279 uint16_t SVGSVGElement::ZoomAndPan() const {
280 return mEnumAttributes[ZOOMANDPAN].GetAnimValue();
283 void SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv) {
284 if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE ||
285 aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) {
286 ErrorResult nestedRv;
287 mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this, nestedRv);
288 MOZ_ASSERT(!nestedRv.Failed(),
289 "We already validated our aZoomAndPan value!");
290 return;
293 rv.ThrowRangeError<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR>();
296 //----------------------------------------------------------------------
297 SMILTimeContainer* SVGSVGElement::GetTimedDocumentRoot() {
298 if (mTimedDocumentRoot) {
299 return mTimedDocumentRoot.get();
302 // We must not be the outermost <svg> element, try to find it
303 SVGSVGElement* outerSVGElement = SVGContentUtils::GetOuterSVGElement(this);
305 if (outerSVGElement) {
306 return outerSVGElement->GetTimedDocumentRoot();
308 // invalid structure
309 return nullptr;
311 //----------------------------------------------------------------------
312 // SVGElement
313 nsresult SVGSVGElement::BindToTree(BindContext& aContext, nsINode& aParent) {
314 SMILAnimationController* smilController = nullptr;
316 if (Document* doc = aContext.GetComposedDoc()) {
317 if ((smilController = doc->GetAnimationController())) {
318 // SMIL is enabled in this document
319 if (WillBeOutermostSVG(aParent)) {
320 // We'll be the outermost <svg> element. We'll need a time container.
321 if (!mTimedDocumentRoot) {
322 mTimedDocumentRoot = MakeUnique<SMILTimeContainer>();
324 } else {
325 // We're a child of some other <svg> element, so we don't need our own
326 // time container. However, we need to make sure that we'll get a
327 // kick-start if we get promoted to be outermost later on.
328 mTimedDocumentRoot = nullptr;
329 mStartAnimationOnBindToTree = true;
334 nsresult rv = SVGGraphicsElement::BindToTree(aContext, aParent);
335 NS_ENSURE_SUCCESS(rv, rv);
337 if (mTimedDocumentRoot && smilController) {
338 rv = mTimedDocumentRoot->SetParent(smilController);
339 if (mStartAnimationOnBindToTree) {
340 mTimedDocumentRoot->Begin();
341 mStartAnimationOnBindToTree = false;
345 return rv;
348 void SVGSVGElement::UnbindFromTree(UnbindContext& aContext) {
349 if (mTimedDocumentRoot) {
350 mTimedDocumentRoot->SetParent(nullptr);
353 SVGGraphicsElement::UnbindFromTree(aContext);
356 SVGAnimatedTransformList* SVGSVGElement::GetAnimatedTransformList(
357 uint32_t aFlags) {
358 if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) {
359 return mSVGView->mTransforms.get();
361 return SVGGraphicsElement::GetAnimatedTransformList(aFlags);
364 void SVGSVGElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
365 if (aVisitor.mEvent->mMessage == eSVGLoad) {
366 if (mTimedDocumentRoot) {
367 mTimedDocumentRoot->Begin();
368 // Set 'resample needed' flag, so that if any script calls a DOM method
369 // that requires up-to-date animations before our first sample callback,
370 // we'll force a synchronous sample.
371 AnimationNeedsResample();
374 SVGSVGElementBase::GetEventTargetParent(aVisitor);
377 bool SVGSVGElement::IsEventAttributeNameInternal(nsAtom* aName) {
378 /* The events in EventNameType_SVGSVG are for events that are only
379 applicable to outermost 'svg' elements. We don't check if we're an outer
380 'svg' element in case we're not inserted into the document yet, but since
381 the target of the events in question will always be the outermost 'svg'
382 element, this shouldn't cause any real problems.
384 return nsContentUtils::IsEventAttributeName(
385 aName, (EventNameType_SVGGraphic | EventNameType_SVGSVG));
388 LengthPercentage SVGSVGElement::GetIntrinsicWidthOrHeight(int aAttr) {
389 MOZ_ASSERT(aAttr == ATTR_WIDTH || aAttr == ATTR_HEIGHT);
391 if (mLengthAttributes[aAttr].IsPercentage()) {
392 float rawSize = mLengthAttributes[aAttr].GetAnimValInSpecifiedUnits();
393 return LengthPercentage::FromPercentage(rawSize);
396 // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue
397 // that uses the passed argument as the context, but that's fine since we
398 // know the length isn't a percentage so the context won't be used (and we
399 // need to pass the element to be able to resolve em/ex units).
400 float rawSize = mLengthAttributes[aAttr].GetAnimValueWithZoom(this);
401 return LengthPercentage::FromPixels(rawSize);
404 //----------------------------------------------------------------------
405 // public helpers:
407 LengthPercentage SVGSVGElement::GetIntrinsicWidth() {
408 return GetIntrinsicWidthOrHeight(ATTR_WIDTH);
411 LengthPercentage SVGSVGElement::GetIntrinsicHeight() {
412 return GetIntrinsicWidthOrHeight(ATTR_HEIGHT);
415 void SVGSVGElement::FlushImageTransformInvalidation() {
416 MOZ_ASSERT(!GetParent(), "Should only be called on root node");
417 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
418 "Should only be called on image documents");
420 if (mImageNeedsTransformInvalidation) {
421 InvalidateTransformNotifyFrame();
422 mImageNeedsTransformInvalidation = false;
426 //----------------------------------------------------------------------
427 // implementation helpers
429 bool SVGSVGElement::WillBeOutermostSVG(nsINode& aParent) const {
430 nsINode* parent = &aParent;
431 while (parent && parent->IsSVGElement()) {
432 if (parent->IsSVGElement(nsGkAtoms::foreignObject)) {
433 // SVG in a foreignObject must have its own <svg> (SVGOuterSVGFrame).
434 return false;
436 if (parent->IsSVGElement(nsGkAtoms::svg)) {
437 return false;
439 parent = parent->GetParentOrShadowHostNode();
442 return true;
445 void SVGSVGElement::DidChangeSVGView() {
446 InvalidateTransformNotifyFrame();
447 // We map the SVGView transform as the transform css property, so need to
448 // schedule attribute mapping.
449 if (!IsPendingMappedAttributeEvaluation() &&
450 mAttrs.MarkAsPendingPresAttributeEvaluation()) {
451 OwnerDoc()->ScheduleForPresAttrEvaluation(this);
455 void SVGSVGElement::InvalidateTransformNotifyFrame() {
456 // might fail this check if we've failed conditional processing
457 if (ISVGSVGFrame* svgframe = do_QueryFrame(GetPrimaryFrame())) {
458 svgframe->NotifyViewportOrTransformChanged(
459 ISVGDisplayableFrame::TRANSFORM_CHANGED);
463 SVGElement::EnumAttributesInfo SVGSVGElement::GetEnumInfo() {
464 return EnumAttributesInfo(mEnumAttributes, sEnumInfo, std::size(sEnumInfo));
467 void SVGSVGElement::SetImageOverridePreserveAspectRatio(
468 const SVGPreserveAspectRatio& aPAR) {
469 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
470 "should only override preserveAspectRatio in images");
472 bool hasViewBox = HasViewBox();
473 if (!hasViewBox && ShouldSynthesizeViewBox()) {
474 // My non-<svg:image> clients will have been painting me with a synthesized
475 // viewBox, but my <svg:image> client that's about to paint me now does NOT
476 // want that. Need to tell ourselves to flush our transform.
477 mImageNeedsTransformInvalidation = true;
480 if (!hasViewBox) {
481 return; // preserveAspectRatio irrelevant (only matters if we have viewBox)
484 if (SetPreserveAspectRatioProperty(aPAR)) {
485 mImageNeedsTransformInvalidation = true;
489 void SVGSVGElement::ClearImageOverridePreserveAspectRatio() {
490 MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
491 "should only override image preserveAspectRatio in images");
493 if (!HasViewBox() && ShouldSynthesizeViewBox()) {
494 // My non-<svg:image> clients will want to paint me with a synthesized
495 // viewBox, but my <svg:image> client that just painted me did NOT
496 // use that. Need to tell ourselves to flush our transform.
497 mImageNeedsTransformInvalidation = true;
500 if (ClearPreserveAspectRatioProperty()) {
501 mImageNeedsTransformInvalidation = true;
505 bool SVGSVGElement::SetPreserveAspectRatioProperty(
506 const SVGPreserveAspectRatio& aPAR) {
507 SVGPreserveAspectRatio* pAROverridePtr = new SVGPreserveAspectRatio(aPAR);
508 nsresult rv =
509 SetProperty(nsGkAtoms::overridePreserveAspectRatio, pAROverridePtr,
510 nsINode::DeleteProperty<SVGPreserveAspectRatio>, true);
511 MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN,
512 "Setting override value when it's already set...?");
514 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
515 // property-insertion failed (e.g. OOM in property-table code)
516 delete pAROverridePtr;
517 return false;
519 return true;
522 const SVGPreserveAspectRatio* SVGSVGElement::GetPreserveAspectRatioProperty()
523 const {
524 void* valPtr = GetProperty(nsGkAtoms::overridePreserveAspectRatio);
525 if (valPtr) {
526 return static_cast<SVGPreserveAspectRatio*>(valPtr);
528 return nullptr;
531 bool SVGSVGElement::ClearPreserveAspectRatioProperty() {
532 void* valPtr = TakeProperty(nsGkAtoms::overridePreserveAspectRatio);
533 bool didHaveProperty = !!valPtr;
534 delete static_cast<SVGPreserveAspectRatio*>(valPtr);
535 return didHaveProperty;
538 SVGPreserveAspectRatio SVGSVGElement::GetPreserveAspectRatioWithOverride()
539 const {
540 Document* doc = GetUncomposedDoc();
541 if (doc && doc->IsBeingUsedAsImage()) {
542 const SVGPreserveAspectRatio* pAROverridePtr =
543 GetPreserveAspectRatioProperty();
544 if (pAROverridePtr) {
545 return *pAROverridePtr;
549 SVGViewElement* viewElement = GetCurrentViewElement();
551 // This check is equivalent to "!HasViewBox() &&
552 // ShouldSynthesizeViewBox()". We're just holding onto the viewElement that
553 // HasViewBox() would look up, so that we don't have to look it up again
554 // later.
555 if (!((viewElement && viewElement->mViewBox.HasRect()) ||
556 (mSVGView && mSVGView->mViewBox.HasRect()) || mViewBox.HasRect()) &&
557 ShouldSynthesizeViewBox()) {
558 // If we're synthesizing a viewBox, use preserveAspectRatio="none";
559 return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE,
560 SVG_MEETORSLICE_SLICE);
563 if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) {
564 return viewElement->mPreserveAspectRatio.GetAnimValue();
566 if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) {
567 return mSVGView->mPreserveAspectRatio.GetAnimValue();
569 return mPreserveAspectRatio.GetAnimValue();
572 SVGViewElement* SVGSVGElement::GetCurrentViewElement() const {
573 if (mCurrentViewID) {
574 // XXXsmaug It is unclear how this should work in case we're in Shadow DOM.
575 Document* doc = GetUncomposedDoc();
576 if (doc) {
577 Element* element = doc->GetElementById(*mCurrentViewID);
578 return SVGViewElement::FromNodeOrNull(element);
581 return nullptr;
584 const SVGAnimatedViewBox& SVGSVGElement::GetViewBoxInternal() const {
585 SVGViewElement* viewElement = GetCurrentViewElement();
587 if (viewElement && viewElement->mViewBox.HasRect()) {
588 return viewElement->mViewBox;
590 if (mSVGView && mSVGView->mViewBox.HasRect()) {
591 return mSVGView->mViewBox;
594 return mViewBox;
597 } // namespace mozilla::dom