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