summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGSVGElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGSVGElement.cpp')
-rw-r--r--dom/svg/SVGSVGElement.cpp1192
1 files changed, 1192 insertions, 0 deletions
diff --git a/dom/svg/SVGSVGElement.cpp b/dom/svg/SVGSVGElement.cpp
new file mode 100644
index 000000000..90c3c3fff
--- /dev/null
+++ b/dom/svg/SVGSVGElement.cpp
@@ -0,0 +1,1192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdint.h>
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Likely.h"
+
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsLayoutStylesheetCache.h"
+#include "DOMSVGNumber.h"
+#include "DOMSVGLength.h"
+#include "nsSVGAngle.h"
+#include "nsCOMPtr.h"
+#include "nsIPresShell.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/SVGMatrix.h"
+#include "DOMSVGPoint.h"
+#include "nsIFrame.h"
+#include "nsFrameSelection.h"
+#include "nsISVGSVGFrame.h" //XXX
+#include "mozilla/dom/SVGRect.h"
+#include "nsError.h"
+#include "nsISVGChildFrame.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/SVGSVGElementBinding.h"
+#include "nsSVGUtils.h"
+#include "mozilla/dom/SVGViewElement.h"
+#include "nsStyleUtil.h"
+#include "SVGContentUtils.h"
+
+#include "nsSMILTimeContainer.h"
+#include "nsSMILAnimationController.h"
+#include "nsSMILTypes.h"
+#include "SVGAngle.h"
+#include <algorithm>
+#include "prtime.h"
+
+NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT_CHECK_PARSER(SVG)
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace dom {
+
+class SVGAnimatedLength;
+
+JSObject*
+SVGSVGElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return SVGSVGElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMSVGTranslatePoint, nsISVGPoint,
+ mElement)
+
+NS_IMPL_ADDREF_INHERITED(DOMSVGTranslatePoint, nsISVGPoint)
+NS_IMPL_RELEASE_INHERITED(DOMSVGTranslatePoint, nsISVGPoint)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGTranslatePoint)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ // We have to qualify nsISVGPoint because NS_GET_IID looks for a class in the
+ // global namespace
+ NS_INTERFACE_MAP_ENTRY(mozilla::nsISVGPoint)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+SVGSVGElement::~SVGSVGElement()
+{
+}
+
+DOMSVGPoint*
+DOMSVGTranslatePoint::Copy()
+{
+ return new DOMSVGPoint(mPt.GetX(), mPt.GetY());
+}
+
+nsISupports*
+DOMSVGTranslatePoint::GetParentObject()
+{
+ return static_cast<nsIDOMSVGElement*>(mElement);
+}
+
+void
+DOMSVGTranslatePoint::SetX(float aValue, ErrorResult& rv)
+{
+ mElement->SetCurrentTranslate(aValue, mPt.GetY());
+}
+
+void
+DOMSVGTranslatePoint::SetY(float aValue, ErrorResult& rv)
+{
+ mElement->SetCurrentTranslate(mPt.GetX(), aValue);
+}
+
+already_AddRefed<nsISVGPoint>
+DOMSVGTranslatePoint::MatrixTransform(SVGMatrix& matrix)
+{
+ float a = matrix.A(), b = matrix.B(), c = matrix.C();
+ float d = matrix.D(), e = matrix.E(), f = matrix.F();
+ float x = mPt.GetX();
+ float y = mPt.GetY();
+
+ nsCOMPtr<nsISVGPoint> point = new DOMSVGPoint(a*x + c*y + e, b*x + d*y + f);
+ return point.forget();
+}
+
+SVGView::SVGView()
+{
+ mZoomAndPan.Init(SVGSVGElement::ZOOMANDPAN,
+ SVG_ZOOMANDPAN_MAGNIFY);
+ mViewBox.Init();
+ mPreserveAspectRatio.Init();
+}
+
+nsSVGElement::LengthInfo SVGSVGElement::sLengthInfo[4] =
+{
+ { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
+ { &nsGkAtoms::y, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
+ { &nsGkAtoms::width, 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE, SVGContentUtils::X },
+ { &nsGkAtoms::height, 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE, SVGContentUtils::Y },
+};
+
+nsSVGEnumMapping SVGSVGElement::sZoomAndPanMap[] = {
+ {&nsGkAtoms::disable, SVG_ZOOMANDPAN_DISABLE},
+ {&nsGkAtoms::magnify, SVG_ZOOMANDPAN_MAGNIFY},
+ {nullptr, 0}
+};
+
+nsSVGElement::EnumInfo SVGSVGElement::sEnumInfo[1] =
+{
+ { &nsGkAtoms::zoomAndPan,
+ sZoomAndPanMap,
+ SVG_ZOOMANDPAN_MAGNIFY
+ }
+};
+
+//----------------------------------------------------------------------
+// nsISupports methods
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SVGSVGElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGSVGElement,
+ SVGSVGElementBase)
+ if (tmp->mTimedDocumentRoot) {
+ tmp->mTimedDocumentRoot->Unlink();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGSVGElement,
+ SVGSVGElementBase)
+ if (tmp->mTimedDocumentRoot) {
+ tmp->mTimedDocumentRoot->Traverse(&cb);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(SVGSVGElement,SVGSVGElementBase)
+NS_IMPL_RELEASE_INHERITED(SVGSVGElement,SVGSVGElementBase)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(SVGSVGElement)
+ NS_INTERFACE_TABLE_INHERITED(SVGSVGElement, nsIDOMNode, nsIDOMElement,
+ nsIDOMSVGElement)
+NS_INTERFACE_TABLE_TAIL_INHERITING(SVGSVGElementBase)
+
+//----------------------------------------------------------------------
+// Implementation
+
+SVGSVGElement::SVGSVGElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : SVGSVGElementBase(aNodeInfo),
+ mViewportWidth(0),
+ mViewportHeight(0),
+ mCurrentTranslate(0.0f, 0.0f),
+ mCurrentScale(1.0f),
+ mPreviousTranslate(0.0f, 0.0f),
+ mPreviousScale(1.0f),
+ mStartAnimationOnBindToTree(aFromParser == NOT_FROM_PARSER ||
+ aFromParser == FROM_PARSER_FRAGMENT ||
+ aFromParser == FROM_PARSER_XSLT),
+ mImageNeedsTransformInvalidation(false),
+ mIsPaintingSVGImageElement(false),
+ mHasChildrenOnlyTransform(false)
+{
+}
+
+//----------------------------------------------------------------------
+// nsIDOMNode methods
+
+// From NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSVGElement)
+nsresult
+SVGSVGElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ *aResult = nullptr;
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ SVGSVGElement *it = new SVGSVGElement(ni, NOT_FROM_PARSER);
+
+ nsCOMPtr<nsINode> kungFuDeathGrip = it;
+ nsresult rv1 = it->Init();
+ nsresult rv2 = const_cast<SVGSVGElement*>(this)->CopyInnerTo(it);
+ if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
+ kungFuDeathGrip.swap(*aResult);
+ }
+
+ return NS_FAILED(rv1) ? rv1 : rv2;
+}
+
+
+//----------------------------------------------------------------------
+// nsIDOMSVGSVGElement methods:
+
+already_AddRefed<SVGAnimatedLength>
+SVGSVGElement::X()
+{
+ return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
+}
+
+already_AddRefed<SVGAnimatedLength>
+SVGSVGElement::Y()
+{
+ return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
+}
+
+already_AddRefed<SVGAnimatedLength>
+SVGSVGElement::Width()
+{
+ return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
+}
+
+already_AddRefed<SVGAnimatedLength>
+SVGSVGElement::Height()
+{
+ return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
+}
+
+float
+SVGSVGElement::PixelUnitToMillimeterX()
+{
+ return MM_PER_INCH_FLOAT / 96;
+}
+
+float
+SVGSVGElement::PixelUnitToMillimeterY()
+{
+ return PixelUnitToMillimeterX();
+}
+
+float
+SVGSVGElement::ScreenPixelToMillimeterX()
+{
+ return MM_PER_INCH_FLOAT / 96;
+}
+
+float
+SVGSVGElement::ScreenPixelToMillimeterY()
+{
+ return ScreenPixelToMillimeterX();
+}
+
+bool
+SVGSVGElement::UseCurrentView()
+{
+ return mSVGView || mCurrentViewID;
+}
+
+float
+SVGSVGElement::CurrentScale()
+{
+ return mCurrentScale;
+}
+
+#define CURRENT_SCALE_MAX 16.0f
+#define CURRENT_SCALE_MIN 0.0625f
+
+void
+SVGSVGElement::SetCurrentScale(float aCurrentScale)
+{
+ SetCurrentScaleTranslate(aCurrentScale,
+ mCurrentTranslate.GetX(), mCurrentTranslate.GetY());
+}
+
+already_AddRefed<nsISVGPoint>
+SVGSVGElement::CurrentTranslate()
+{
+ nsCOMPtr<nsISVGPoint> point = new DOMSVGTranslatePoint(&mCurrentTranslate, this);
+ return point.forget();
+}
+
+uint32_t
+SVGSVGElement::SuspendRedraw(uint32_t max_wait_milliseconds)
+{
+ // suspendRedraw is a no-op in Mozilla, so it doesn't matter what
+ // we return
+ return 1;
+}
+
+void
+SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id)
+{
+ // no-op
+}
+
+void
+SVGSVGElement::UnsuspendRedrawAll()
+{
+ // no-op
+}
+
+void
+SVGSVGElement::ForceRedraw()
+{
+ // no-op
+}
+
+void
+SVGSVGElement::PauseAnimations()
+{
+ if (mTimedDocumentRoot) {
+ mTimedDocumentRoot->Pause(nsSMILTimeContainer::PAUSE_SCRIPT);
+ }
+ // else we're not the outermost <svg> or not bound to a tree, so silently fail
+}
+
+void
+SVGSVGElement::UnpauseAnimations()
+{
+ if (mTimedDocumentRoot) {
+ mTimedDocumentRoot->Resume(nsSMILTimeContainer::PAUSE_SCRIPT);
+ }
+ // else we're not the outermost <svg> or not bound to a tree, so silently fail
+}
+
+bool
+SVGSVGElement::AnimationsPaused()
+{
+ nsSMILTimeContainer* root = GetTimedDocumentRoot();
+ return root && root->IsPausedByType(nsSMILTimeContainer::PAUSE_SCRIPT);
+}
+
+float
+SVGSVGElement::GetCurrentTime()
+{
+ nsSMILTimeContainer* root = GetTimedDocumentRoot();
+ if (root) {
+ double fCurrentTimeMs = double(root->GetCurrentTime());
+ return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC);
+ } else {
+ return 0.f;
+ }
+}
+
+void
+SVGSVGElement::SetCurrentTime(float seconds)
+{
+ if (mTimedDocumentRoot) {
+ // Make sure the timegraph is up-to-date
+ FlushAnimations();
+ double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
+ // Round to nearest whole number before converting, to avoid precision
+ // errors
+ nsSMILTime lMilliseconds = int64_t(NS_round(fMilliseconds));
+ mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
+ AnimationNeedsResample();
+ // Trigger synchronous sample now, to:
+ // - Make sure we get an up-to-date paint after this method
+ // - re-enable event firing (it got disabled during seeking, and it
+ // doesn't get re-enabled until the first sample after the seek -- so
+ // let's make that happen now.)
+ FlushAnimations();
+ }
+ // else we're not the outermost <svg> or not bound to a tree, so silently fail
+}
+
+void
+SVGSVGElement::DeselectAll()
+{
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ RefPtr<nsFrameSelection> frameSelection = frame->GetFrameSelection();
+ frameSelection->ClearNormalSelection();
+ }
+}
+
+already_AddRefed<DOMSVGNumber>
+SVGSVGElement::CreateSVGNumber()
+{
+ RefPtr<DOMSVGNumber> number = new DOMSVGNumber(ToSupports(this));
+ return number.forget();
+}
+
+already_AddRefed<DOMSVGLength>
+SVGSVGElement::CreateSVGLength()
+{
+ nsCOMPtr<DOMSVGLength> length = new DOMSVGLength();
+ return length.forget();
+}
+
+already_AddRefed<SVGAngle>
+SVGSVGElement::CreateSVGAngle()
+{
+ nsSVGAngle* angle = new nsSVGAngle();
+ angle->Init();
+ RefPtr<SVGAngle> svgangle = new SVGAngle(angle, this, SVGAngle::CreatedValue);
+ return svgangle.forget();
+}
+
+already_AddRefed<nsISVGPoint>
+SVGSVGElement::CreateSVGPoint()
+{
+ nsCOMPtr<nsISVGPoint> point = new DOMSVGPoint(0, 0);
+ return point.forget();
+}
+
+already_AddRefed<SVGMatrix>
+SVGSVGElement::CreateSVGMatrix()
+{
+ RefPtr<SVGMatrix> matrix = new SVGMatrix();
+ return matrix.forget();
+}
+
+already_AddRefed<SVGIRect>
+SVGSVGElement::CreateSVGRect()
+{
+ return NS_NewSVGRect(this);
+}
+
+already_AddRefed<SVGTransform>
+SVGSVGElement::CreateSVGTransform()
+{
+ RefPtr<SVGTransform> transform = new SVGTransform();
+ return transform.forget();
+}
+
+already_AddRefed<SVGTransform>
+SVGSVGElement::CreateSVGTransformFromMatrix(SVGMatrix& matrix)
+{
+ RefPtr<SVGTransform> transform = new SVGTransform(matrix.GetMatrix());
+ return transform.forget();
+}
+
+//----------------------------------------------------------------------
+
+already_AddRefed<SVGAnimatedRect>
+SVGSVGElement::ViewBox()
+{
+ return mViewBox.ToSVGAnimatedRect(this);
+}
+
+already_AddRefed<DOMSVGAnimatedPreserveAspectRatio>
+SVGSVGElement::PreserveAspectRatio()
+{
+ return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this);
+}
+
+uint16_t
+SVGSVGElement::ZoomAndPan()
+{
+ return mEnumAttributes[ZOOMANDPAN].GetAnimValue();
+}
+
+void
+SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv)
+{
+ if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE ||
+ aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) {
+ mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this);
+ return;
+ }
+
+ rv.ThrowRangeError<MSG_INVALID_ZOOMANDPAN_VALUE_ERROR>();
+}
+
+//----------------------------------------------------------------------
+// helper methods for implementing SVGZoomEvent:
+
+void
+SVGSVGElement::SetCurrentScaleTranslate(float s, float x, float y)
+{
+ if (s == mCurrentScale &&
+ x == mCurrentTranslate.GetX() && y == mCurrentTranslate.GetY()) {
+ return;
+ }
+
+ // Prevent bizarre behaviour and maxing out of CPU and memory by clamping
+ if (s < CURRENT_SCALE_MIN)
+ s = CURRENT_SCALE_MIN;
+ else if (s > CURRENT_SCALE_MAX)
+ s = CURRENT_SCALE_MAX;
+
+ // IMPORTANT: If either mCurrentTranslate *or* mCurrentScale is changed then
+ // mPreviousTranslate_x, mPreviousTranslate_y *and* mPreviousScale must all
+ // be updated otherwise SVGZoomEvents will end up with invalid data. I.e. an
+ // SVGZoomEvent's properties previousScale and previousTranslate must contain
+ // the state of currentScale and currentTranslate immediately before the
+ // change that caused the event's dispatch, which is *not* necessarily the
+ // same thing as the values of currentScale and currentTranslate prior to
+ // their own last change.
+ mPreviousScale = mCurrentScale;
+ mPreviousTranslate = mCurrentTranslate;
+
+ mCurrentScale = s;
+ mCurrentTranslate = SVGPoint(x, y);
+
+ // now dispatch the appropriate event if we are the root element
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ if (presShell && IsRoot()) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (mPreviousScale != mCurrentScale) {
+ InternalSVGZoomEvent svgZoomEvent(true, eSVGZoom);
+ presShell->HandleDOMEventWithTarget(this, &svgZoomEvent, &status);
+ } else {
+ WidgetEvent svgScrollEvent(true, eSVGScroll);
+ presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
+ }
+ InvalidateTransformNotifyFrame();
+ }
+ }
+}
+
+void
+SVGSVGElement::SetCurrentTranslate(float x, float y)
+{
+ SetCurrentScaleTranslate(mCurrentScale, x, y);
+}
+
+nsSMILTimeContainer*
+SVGSVGElement::GetTimedDocumentRoot()
+{
+ if (mTimedDocumentRoot) {
+ return mTimedDocumentRoot;
+ }
+
+ // We must not be the outermost <svg> element, try to find it
+ SVGSVGElement *outerSVGElement =
+ SVGContentUtils::GetOuterSVGElement(this);
+
+ if (outerSVGElement) {
+ return outerSVGElement->GetTimedDocumentRoot();
+ }
+ // invalid structure
+ return nullptr;
+}
+
+//----------------------------------------------------------------------
+// nsIContent methods
+
+NS_IMETHODIMP_(bool)
+SVGSVGElement::IsAttributeMapped(const nsIAtom* name) const
+{
+ // We want to map the 'width' and 'height' attributes into style for
+ // outer-<svg>, except when the attributes aren't set (since their default
+ // values of '100%' can cause unexpected and undesirable behaviour for SVG
+ // inline in HTML). We rely on nsSVGElement::UpdateContentStyleRule() to
+ // prevent mapping of the default values into style (it only maps attributes
+ // that are set). We also rely on a check in nsSVGElement::
+ // UpdateContentStyleRule() to prevent us mapping the attributes when they're
+ // given a <length> value that is not currently recognized by the SVG
+ // specification.
+
+ if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) {
+ return true;
+ }
+
+ static const MappedAttributeEntry* const map[] = {
+ sColorMap,
+ sFEFloodMap,
+ sFillStrokeMap,
+ sFiltersMap,
+ sFontSpecificationMap,
+ sGradientStopMap,
+ sGraphicsMap,
+ sLightingEffectsMap,
+ sMarkersMap,
+ sTextContentElementsMap,
+ sViewportsMap
+ };
+
+ return FindAttributeDependence(name, map) ||
+ SVGSVGElementBase::IsAttributeMapped(name);
+}
+
+//----------------------------------------------------------------------
+// nsIContent methods:
+
+nsresult
+SVGSVGElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->mMessage == eSVGLoad) {
+ if (mTimedDocumentRoot) {
+ mTimedDocumentRoot->Begin();
+ // Set 'resample needed' flag, so that if any script calls a DOM method
+ // that requires up-to-date animations before our first sample callback,
+ // we'll force a synchronous sample.
+ AnimationNeedsResample();
+ }
+ }
+ return SVGSVGElementBase::PreHandleEvent(aVisitor);
+}
+
+bool
+SVGSVGElement::IsEventAttributeName(nsIAtom* aName)
+{
+ /* The events in EventNameType_SVGSVG are for events that are only
+ applicable to outermost 'svg' elements. We don't check if we're an outer
+ 'svg' element in case we're not inserted into the document yet, but since
+ the target of the events in question will always be the outermost 'svg'
+ element, this shouldn't cause any real problems.
+ */
+ return nsContentUtils::IsEventAttributeName(aName,
+ (EventNameType_SVGGraphic | EventNameType_SVGSVG));
+}
+
+//----------------------------------------------------------------------
+// nsSVGElement overrides
+
+// Helper for GetViewBoxTransform on root <svg> node
+// * aLength: internal value for our <svg> width or height attribute.
+// * aViewportLength: length of the corresponding dimension of the viewport.
+// * aSelf: the outermost <svg> node itself.
+// NOTE: aSelf is not an ancestor viewport element, so it can't be used to
+// resolve percentage lengths. (It can only be used to resolve
+// 'em'/'ex'-valued units).
+inline float
+ComputeSynthesizedViewBoxDimension(const nsSVGLength2& aLength,
+ float aViewportLength,
+ const SVGSVGElement* aSelf)
+{
+ if (aLength.IsPercentage()) {
+ return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
+ }
+
+ return aLength.GetAnimValue(const_cast<SVGSVGElement*>(aSelf));
+}
+
+//----------------------------------------------------------------------
+// public helpers:
+
+gfx::Matrix
+SVGSVGElement::GetViewBoxTransform() const
+{
+ float viewportWidth, viewportHeight;
+ if (IsInner()) {
+ SVGSVGElement *ctx = GetCtx();
+ viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
+ viewportHeight = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
+ } else {
+ viewportWidth = mViewportWidth;
+ viewportHeight = mViewportHeight;
+ }
+
+ if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) {
+ return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ nsSVGViewBoxRect viewBox =
+ GetViewBoxWithSynthesis(viewportWidth, viewportHeight);
+
+ if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
+ return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ return SVGContentUtils::GetViewBoxTransform(viewportWidth, viewportHeight,
+ viewBox.x, viewBox.y,
+ viewBox.width, viewBox.height,
+ GetPreserveAspectRatioWithOverride());
+}
+
+void
+SVGSVGElement::UpdateHasChildrenOnlyTransform()
+{
+ bool hasChildrenOnlyTransform =
+ HasViewBoxOrSyntheticViewBox() ||
+ (IsRoot() && (mCurrentTranslate != SVGPoint(0.0f, 0.0f) ||
+ mCurrentScale != 1.0f));
+ mHasChildrenOnlyTransform = hasChildrenOnlyTransform;
+}
+
+void
+SVGSVGElement::ChildrenOnlyTransformChanged(uint32_t aFlags)
+{
+ // Avoid wasteful calls:
+ MOZ_ASSERT(!(GetPrimaryFrame()->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "Non-display SVG frames don't maintain overflow rects");
+
+ nsChangeHint changeHint;
+
+ bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform;
+
+ UpdateHasChildrenOnlyTransform();
+
+ if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) {
+ // Reconstruct the frame tree to handle stacking context changes:
+ // XXXjwatt don't do this for root-<svg> or even outer-<svg>?
+ changeHint = nsChangeHint_ReconstructFrame;
+ } else {
+ // We just assume the old and new transforms are different.
+ changeHint = nsChangeHint(nsChangeHint_UpdateOverflow |
+ nsChangeHint_ChildrenOnlyTransform);
+ }
+
+ // If we're not reconstructing the frame tree, then we only call
+ // PostRestyleEvent if we're not being called under reflow to avoid recursing
+ // to death. See bug 767056 comments 10 and 12. Since our nsSVGOuterSVGFrame
+ // is being reflowed we're going to invalidate and repaint its entire area
+ // anyway (which will include our children).
+ if ((changeHint & nsChangeHint_ReconstructFrame) ||
+ !(aFlags & eDuringReflow)) {
+ nsLayoutUtils::PostRestyleEvent(this, nsRestyleHint(0), changeHint);
+ }
+}
+
+nsresult
+SVGSVGElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsSMILAnimationController* smilController = nullptr;
+
+ if (aDocument) {
+ smilController = aDocument->GetAnimationController();
+ if (smilController) {
+ // SMIL is enabled in this document
+ if (WillBeOutermostSVG(aParent, aBindingParent)) {
+ // We'll be the outermost <svg> element. We'll need a time container.
+ if (!mTimedDocumentRoot) {
+ mTimedDocumentRoot = new nsSMILTimeContainer();
+ }
+ } else {
+ // We're a child of some other <svg> element, so we don't need our own
+ // time container. However, we need to make sure that we'll get a
+ // kick-start if we get promoted to be outermost later on.
+ mTimedDocumentRoot = nullptr;
+ mStartAnimationOnBindToTree = true;
+ }
+ }
+ }
+
+ nsresult rv = SVGSVGElementBase::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ // Setup the style sheet during binding, not element construction,
+ // because we could move the root SVG element from the document
+ // that created it to another document.
+ auto cache = nsLayoutStylesheetCache::For(doc->GetStyleBackendType());
+ doc->EnsureOnDemandBuiltInUASheet(cache->SVGSheet());
+ }
+
+ if (mTimedDocumentRoot && smilController) {
+ rv = mTimedDocumentRoot->SetParent(smilController);
+ if (mStartAnimationOnBindToTree) {
+ mTimedDocumentRoot->Begin();
+ mStartAnimationOnBindToTree = false;
+ }
+ }
+
+ return rv;
+}
+
+void
+SVGSVGElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (mTimedDocumentRoot) {
+ mTimedDocumentRoot->SetParent(nullptr);
+ }
+
+ SVGSVGElementBase::UnbindFromTree(aDeep, aNullParent);
+}
+
+//----------------------------------------------------------------------
+// implementation helpers
+
+bool
+SVGSVGElement::WillBeOutermostSVG(nsIContent* aParent,
+ nsIContent* aBindingParent) const
+{
+ nsIContent* parent = aBindingParent ? aBindingParent : aParent;
+
+ while (parent && parent->IsSVGElement()) {
+ if (parent->IsSVGElement(nsGkAtoms::foreignObject)) {
+ // SVG in a foreignObject must have its own <svg> (nsSVGOuterSVGFrame).
+ return false;
+ }
+ if (parent->IsSVGElement(nsGkAtoms::svg)) {
+ return false;
+ }
+ parent = parent->GetParent();
+ }
+
+ return true;
+}
+
+void
+SVGSVGElement::InvalidateTransformNotifyFrame()
+{
+ nsISVGSVGFrame* svgframe = do_QueryFrame(GetPrimaryFrame());
+ // might fail this check if we've failed conditional processing
+ if (svgframe) {
+ svgframe->NotifyViewportOrTransformChanged(
+ nsISVGChildFrame::TRANSFORM_CHANGED);
+ }
+}
+
+bool
+SVGSVGElement::HasPreserveAspectRatio()
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::preserveAspectRatio) ||
+ mPreserveAspectRatio.IsAnimated();
+}
+
+SVGViewElement*
+SVGSVGElement::GetCurrentViewElement() const
+{
+ if (mCurrentViewID) {
+ //XXXsmaug It is unclear how this should work in case we're in Shadow DOM.
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ Element *element = doc->GetElementById(*mCurrentViewID);
+ if (element && element->IsSVGElement(nsGkAtoms::view)) {
+ return static_cast<SVGViewElement*>(element);
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsSVGViewBoxRect
+SVGSVGElement::GetViewBoxWithSynthesis(
+ float aViewportWidth, float aViewportHeight) const
+{
+ // The logic here should match HasViewBoxRect().
+ SVGViewElement* viewElement = GetCurrentViewElement();
+ if (viewElement && viewElement->mViewBox.HasRect()) {
+ return viewElement->mViewBox.GetAnimValue();
+ }
+ if (mSVGView && mSVGView->mViewBox.HasRect()) {
+ return mSVGView->mViewBox.GetAnimValue();
+ }
+ if (mViewBox.HasRect()) {
+ return mViewBox.GetAnimValue();
+ }
+
+ if (ShouldSynthesizeViewBox()) {
+ // Special case -- fake a viewBox, using height & width attrs.
+ // (Use |this| as context, since if we get here, we're outermost <svg>.)
+ return nsSVGViewBoxRect(0, 0,
+ ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
+ mViewportWidth, this),
+ ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
+ mViewportHeight, this));
+
+ }
+
+ // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
+ // to having a viewBox that exactly matches our viewport size.
+ return nsSVGViewBoxRect(0, 0, aViewportWidth, aViewportHeight);
+}
+
+SVGPreserveAspectRatio
+SVGSVGElement::GetPreserveAspectRatioWithOverride() const
+{
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc && doc->IsBeingUsedAsImage()) {
+ const SVGPreserveAspectRatio *pAROverridePtr = GetPreserveAspectRatioProperty();
+ if (pAROverridePtr) {
+ return *pAROverridePtr;
+ }
+ }
+
+ SVGViewElement* viewElement = GetCurrentViewElement();
+
+ // This check is equivalent to "!HasViewBoxRect() && ShouldSynthesizeViewBox()".
+ // We're just holding onto the viewElement that HasViewBoxRect() would look up,
+ // so that we don't have to look it up again later.
+ if (!((viewElement && viewElement->mViewBox.HasRect()) ||
+ (mSVGView && mSVGView->mViewBox.HasRect()) ||
+ mViewBox.HasRect()) &&
+ ShouldSynthesizeViewBox()) {
+ // If we're synthesizing a viewBox, use preserveAspectRatio="none";
+ return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE, SVG_MEETORSLICE_SLICE);
+ }
+
+ if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) {
+ return viewElement->mPreserveAspectRatio.GetAnimValue();
+ }
+ if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) {
+ return mSVGView->mPreserveAspectRatio.GetAnimValue();
+ }
+ return mPreserveAspectRatio.GetAnimValue();
+}
+
+//----------------------------------------------------------------------
+// SVGSVGElement
+
+float
+SVGSVGElement::GetLength(uint8_t aCtxType)
+{
+ float h, w;
+
+ SVGViewElement* viewElement = GetCurrentViewElement();
+ const nsSVGViewBoxRect* viewbox = nullptr;
+
+ // The logic here should match HasViewBoxRect().
+ if (viewElement && viewElement->mViewBox.HasRect()) {
+ viewbox = &viewElement->mViewBox.GetAnimValue();
+ } else if (mSVGView && mSVGView->mViewBox.HasRect()) {
+ viewbox = &mSVGView->mViewBox.GetAnimValue();
+ } else if (mViewBox.HasRect()) {
+ viewbox = &mViewBox.GetAnimValue();
+ }
+
+ if (viewbox) {
+ w = viewbox->width;
+ h = viewbox->height;
+ } else if (IsInner()) {
+ SVGSVGElement *ctx = GetCtx();
+ w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
+ h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
+ } else if (ShouldSynthesizeViewBox()) {
+ w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
+ mViewportWidth, this);
+ h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
+ mViewportHeight, this);
+ } else {
+ w = mViewportWidth;
+ h = mViewportHeight;
+ }
+
+ w = std::max(w, 0.0f);
+ h = std::max(h, 0.0f);
+
+ switch (aCtxType) {
+ case SVGContentUtils::X:
+ return w;
+ case SVGContentUtils::Y:
+ return h;
+ case SVGContentUtils::XY:
+ return float(SVGContentUtils::ComputeNormalizedHypotenuse(w, h));
+ }
+ return 0;
+}
+
+//----------------------------------------------------------------------
+// nsSVGElement methods
+
+/* virtual */ gfxMatrix
+SVGSVGElement::PrependLocalTransformsTo(
+ const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const
+{
+ // 'transform' attribute (or an override from a fragment identifier):
+ gfxMatrix fromUserSpace =
+ SVGContentUtils::PrependLocalTransformsTo(
+ aMatrix, aWhich, mAnimateMotionTransform,
+ mSVGView && mSVGView->mTransforms ? mSVGView->mTransforms : mTransforms);
+
+ if (aWhich == eUserSpaceToParent) {
+ return fromUserSpace;
+ }
+
+ if (IsInner()) {
+ float x, y;
+ const_cast<SVGSVGElement*>(this)->GetAnimatedLengthValues(&x, &y, nullptr);
+ if (aWhich == eAllTransforms) {
+ // the common case
+ return ThebesMatrix(GetViewBoxTransform()) * gfxMatrix::Translation(x, y) * fromUserSpace;
+ }
+ MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
+ return ThebesMatrix(GetViewBoxTransform()) * gfxMatrix::Translation(x, y) * aMatrix;
+ }
+
+ if (IsRoot()) {
+ gfxMatrix zoomPanTM;
+ zoomPanTM.Translate(gfxPoint(mCurrentTranslate.GetX(), mCurrentTranslate.GetY()));
+ zoomPanTM.Scale(mCurrentScale, mCurrentScale);
+ return ThebesMatrix(GetViewBoxTransform()) * zoomPanTM * fromUserSpace;
+ }
+
+ // outer-<svg>, but inline in some other content:
+ return ThebesMatrix(GetViewBoxTransform()) * fromUserSpace;
+}
+
+nsSVGAnimatedTransformList*
+SVGSVGElement::GetAnimatedTransformList(uint32_t aFlags)
+{
+ if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) {
+ return mSVGView->mTransforms;
+ }
+ return SVGSVGElementBase::GetAnimatedTransformList(aFlags);
+}
+
+/* virtual */ bool
+SVGSVGElement::HasValidDimensions() const
+{
+ return !IsInner() ||
+ ((!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
+ mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
+ (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
+ mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0));
+}
+
+nsSVGElement::LengthAttributesInfo
+SVGSVGElement::GetLengthInfo()
+{
+ return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
+ ArrayLength(sLengthInfo));
+}
+
+nsSVGElement::EnumAttributesInfo
+SVGSVGElement::GetEnumInfo()
+{
+ return EnumAttributesInfo(mEnumAttributes, sEnumInfo,
+ ArrayLength(sEnumInfo));
+}
+
+nsSVGViewBox*
+SVGSVGElement::GetViewBox()
+{
+ return &mViewBox;
+}
+
+SVGAnimatedPreserveAspectRatio *
+SVGSVGElement::GetPreserveAspectRatio()
+{
+ return &mPreserveAspectRatio;
+}
+
+bool
+SVGSVGElement::HasViewBoxRect() const
+{
+ SVGViewElement* viewElement = GetCurrentViewElement();
+ if ((viewElement && viewElement->mViewBox.HasRect()) ||
+ (mSVGView && mSVGView->mViewBox.HasRect())) {
+ return true;
+ }
+ return mViewBox.HasRect();
+}
+
+bool
+SVGSVGElement::ShouldSynthesizeViewBox() const
+{
+ MOZ_ASSERT(!HasViewBoxRect(),
+ "Should only be called if we lack a viewBox");
+
+ nsIDocument* doc = GetUncomposedDoc();
+ return doc &&
+ doc->IsBeingUsedAsImage() &&
+ !mIsPaintingSVGImageElement &&
+ !GetParent();
+}
+
+bool
+SVGSVGElement::SetPreserveAspectRatioProperty(const SVGPreserveAspectRatio& aPAR)
+{
+ SVGPreserveAspectRatio* pAROverridePtr = new SVGPreserveAspectRatio(aPAR);
+ nsresult rv = SetProperty(nsGkAtoms::overridePreserveAspectRatio,
+ pAROverridePtr,
+ nsINode::DeleteProperty<SVGPreserveAspectRatio>,
+ true);
+ MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN,
+ "Setting override value when it's already set...?");
+
+ if (MOZ_UNLIKELY(NS_FAILED(rv))) {
+ // property-insertion failed (e.g. OOM in property-table code)
+ delete pAROverridePtr;
+ return false;
+ }
+ return true;
+}
+
+const SVGPreserveAspectRatio*
+SVGSVGElement::GetPreserveAspectRatioProperty() const
+{
+ void* valPtr = GetProperty(nsGkAtoms::overridePreserveAspectRatio);
+ if (valPtr) {
+ return static_cast<SVGPreserveAspectRatio*>(valPtr);
+ }
+ return nullptr;
+}
+
+bool
+SVGSVGElement::ClearPreserveAspectRatioProperty()
+{
+ void* valPtr = UnsetProperty(nsGkAtoms::overridePreserveAspectRatio);
+ delete static_cast<SVGPreserveAspectRatio*>(valPtr);
+ return valPtr;
+}
+
+void
+SVGSVGElement::SetIsPaintingForSVGImageElement(bool aIsPaintingSVGImageElement)
+{
+ mIsPaintingSVGImageElement = aIsPaintingSVGImageElement;
+}
+
+void
+SVGSVGElement::
+ SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR)
+{
+#ifdef DEBUG
+ MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
+ "should only override preserveAspectRatio in images");
+#endif
+
+ bool hasViewBoxRect = HasViewBoxRect();
+ if (!hasViewBoxRect && ShouldSynthesizeViewBox()) {
+ // My non-<svg:image> clients will have been painting me with a synthesized
+ // viewBox, but my <svg:image> client that's about to paint me now does NOT
+ // want that. Need to tell ourselves to flush our transform.
+ mImageNeedsTransformInvalidation = true;
+ }
+
+ if (!hasViewBoxRect) {
+ return; // preserveAspectRatio irrelevant (only matters if we have viewBox)
+ }
+
+ if (SetPreserveAspectRatioProperty(aPAR)) {
+ mImageNeedsTransformInvalidation = true;
+ }
+}
+
+void
+SVGSVGElement::ClearImageOverridePreserveAspectRatio()
+{
+#ifdef DEBUG
+ MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
+ "should only override image preserveAspectRatio in images");
+#endif
+
+ if (!HasViewBoxRect() && ShouldSynthesizeViewBox()) {
+ // My non-<svg:image> clients will want to paint me with a synthesized
+ // viewBox, but my <svg:image> client that just painted me did NOT
+ // use that. Need to tell ourselves to flush our transform.
+ mImageNeedsTransformInvalidation = true;
+ }
+
+ if (ClearPreserveAspectRatioProperty()) {
+ mImageNeedsTransformInvalidation = true;
+ }
+}
+
+void
+SVGSVGElement::FlushImageTransformInvalidation()
+{
+ MOZ_ASSERT(!GetParent(), "Should only be called on root node");
+ MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(),
+ "Should only be called on image documents");
+
+ if (mImageNeedsTransformInvalidation) {
+ InvalidateTransformNotifyFrame();
+ mImageNeedsTransformInvalidation = false;
+ }
+}
+
+int32_t
+SVGSVGElement::GetIntrinsicWidth()
+{
+ if (mLengthAttributes[ATTR_WIDTH].IsPercentage()) {
+ return -1;
+ }
+ // Passing |this| as a SVGSVGElement* invokes the variant of GetAnimValue
+ // that uses the passed argument as the context, but that's fine since we
+ // know the length isn't a percentage so the context won't be used (and we
+ // need to pass the element to be able to resolve em/ex units).
+ float width = mLengthAttributes[ATTR_WIDTH].GetAnimValue(this);
+ return nsSVGUtils::ClampToInt(width);
+}
+
+int32_t
+SVGSVGElement::GetIntrinsicHeight()
+{
+ if (mLengthAttributes[ATTR_HEIGHT].IsPercentage()) {
+ return -1;
+ }
+ // Passing |this| as a SVGSVGElement* invokes the variant of GetAnimValue
+ // that uses the passed argument as the context, but that's fine since we
+ // know the length isn't a percentage so the context won't be used (and we
+ // need to pass the element to be able to resolve em/ex units).
+ float height = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(this);
+ return nsSVGUtils::ClampToInt(height);
+}
+
+} // namespace dom
+} // namespace mozilla