summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGViewportElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGViewportElement.cpp')
-rw-r--r--dom/svg/SVGViewportElement.cpp344
1 files changed, 344 insertions, 0 deletions
diff --git a/dom/svg/SVGViewportElement.cpp b/dom/svg/SVGViewportElement.cpp
new file mode 100644
index 0000000000..a98c7fb443
--- /dev/null
+++ b/dom/svg/SVGViewportElement.cpp
@@ -0,0 +1,344 @@
+/* -*- 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 "mozilla/dom/SVGViewportElement.h"
+
+#include <stdint.h>
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Likely.h"
+#include "mozilla/SMILTypes.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SVGLengthBinding.h"
+#include "mozilla/dom/SVGViewElement.h"
+
+#include "DOMSVGLength.h"
+#include "DOMSVGPoint.h"
+#include "nsContentUtils.h"
+#include "nsFrameSelection.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleUtil.h"
+
+#include <algorithm>
+#include "prtime.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla::dom {
+
+SVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] = {
+ {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
+ SVGContentUtils::X},
+ {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
+ SVGContentUtils::Y},
+ {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
+ SVGContentUtils::X},
+ {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
+ SVGContentUtils::Y},
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+SVGViewportElement::SVGViewportElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : SVGGraphicsElement(std::move(aNodeInfo)),
+ mHasChildrenOnlyTransform(false) {}
+
+//----------------------------------------------------------------------
+
+already_AddRefed<SVGAnimatedRect> SVGViewportElement::ViewBox() {
+ return mViewBox.ToSVGAnimatedRect(this);
+}
+
+already_AddRefed<DOMSVGAnimatedPreserveAspectRatio>
+SVGViewportElement::PreserveAspectRatio() {
+ return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this);
+}
+
+//----------------------------------------------------------------------
+// nsIContent methods
+
+NS_IMETHODIMP_(bool)
+SVGViewportElement::IsAttributeMapped(const nsAtom* 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 SVGElement::UpdateContentStyleRule() to
+ // prevent mapping of the default values into style (it only maps attributes
+ // that are set). We also rely on a check in SVGElement::
+ // 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;
+ }
+
+ return SVGGraphicsElement::IsAttributeMapped(name);
+}
+
+//----------------------------------------------------------------------
+// SVGElement 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 SVGAnimatedLength& aLength, float aViewportLength,
+ const SVGViewportElement* aSelf) {
+ if (aLength.IsPercentage()) {
+ return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
+ }
+
+ return aLength.GetAnimValue(aSelf);
+}
+
+//----------------------------------------------------------------------
+// public helpers:
+
+void SVGViewportElement::UpdateHasChildrenOnlyTransform() {
+ bool hasChildrenOnlyTransform =
+ HasViewBoxOrSyntheticViewBox() ||
+ (IsRootSVGSVGElement() &&
+ static_cast<SVGSVGElement*>(this)->IsScaledOrTranslated());
+ mHasChildrenOnlyTransform = hasChildrenOnlyTransform;
+}
+
+void SVGViewportElement::ChildrenOnlyTransformChanged(uint32_t aFlags) {
+ // Avoid wasteful calls:
+ MOZ_ASSERT(!GetPrimaryFrame()->HasAnyStateBits(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 SVGOuterSVGFrame
+ // 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, RestyleHint{0}, changeHint);
+ }
+}
+
+gfx::Matrix SVGViewportElement::GetViewBoxTransform() const {
+ float viewportWidth, viewportHeight;
+ if (IsInner()) {
+ SVGElementMetrics metrics(this);
+ viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValue(metrics);
+ viewportHeight = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(metrics);
+ } else {
+ viewportWidth = mViewportSize.width;
+ viewportHeight = mViewportSize.height;
+ }
+
+ if (!std::isfinite(viewportWidth) || viewportWidth <= 0.0f ||
+ !std::isfinite(viewportHeight) || viewportHeight <= 0.0f) {
+ return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ SVGViewBox viewBox = GetViewBoxWithSynthesis(viewportWidth, viewportHeight);
+
+ if (!std::isfinite(viewBox.width) || viewBox.width <= 0.0f ||
+ !std::isfinite(viewBox.height) || 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());
+}
+//----------------------------------------------------------------------
+// SVGViewportElement
+
+float SVGViewportElement::GetLength(uint8_t aCtxType) const {
+ const SVGViewBox* viewbox = GetViewBoxInternal().HasRect()
+ ? &GetViewBoxInternal().GetAnimValue()
+ : nullptr;
+
+ float h = 0.0f, w = 0.0f;
+ bool shouldComputeWidth =
+ (aCtxType == SVGContentUtils::X || aCtxType == SVGContentUtils::XY),
+ shouldComputeHeight =
+ (aCtxType == SVGContentUtils::Y || aCtxType == SVGContentUtils::XY);
+
+ if (viewbox) {
+ w = viewbox->width;
+ h = viewbox->height;
+ } else if (IsInner()) {
+ // Resolving length for inner <svg> is exactly the same as other
+ // ordinary element. We shouldn't use the SVGViewportElement overload
+ // of GetAnimValue().
+ SVGElementMetrics metrics(this);
+ if (shouldComputeWidth) {
+ w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(metrics);
+ }
+ if (shouldComputeHeight) {
+ h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(metrics);
+ }
+ } else if (ShouldSynthesizeViewBox()) {
+ if (shouldComputeWidth) {
+ w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
+ mViewportSize.width, this);
+ }
+ if (shouldComputeHeight) {
+ h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
+ mViewportSize.height, this);
+ }
+ } else {
+ w = mViewportSize.width;
+ h = mViewportSize.height;
+ }
+
+ 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;
+}
+
+//----------------------------------------------------------------------
+// SVGElement methods
+
+/* virtual */
+gfxMatrix SVGViewportElement::PrependLocalTransformsTo(
+ const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const {
+ // 'transform' attribute (or an override from a fragment identifier):
+ gfxMatrix userToParent;
+
+ if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) {
+ userToParent = GetUserToParentTransform(mAnimateMotionTransform.get(),
+ GetTransformInternal());
+ if (aWhich == eUserSpaceToParent) {
+ return userToParent * aMatrix;
+ }
+ }
+
+ gfxMatrix childToUser;
+
+ if (IsInner()) {
+ float x, y;
+ const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y,
+ nullptr);
+ childToUser = ThebesMatrix(GetViewBoxTransform().PostTranslate(x, y));
+ } else if (IsRootSVGSVGElement()) {
+ const SVGSVGElement* svg = static_cast<const SVGSVGElement*>(this);
+ const SVGPoint& translate = svg->GetCurrentTranslate();
+ float scale = svg->CurrentScale();
+ childToUser =
+ ThebesMatrix(GetViewBoxTransform()
+ .PostScale(scale, scale)
+ .PostTranslate(translate.GetX(), translate.GetY()));
+ } else {
+ // outer-<svg>, but inline in some other content:
+ childToUser = ThebesMatrix(GetViewBoxTransform());
+ }
+
+ if (aWhich == eAllTransforms) {
+ return childToUser * userToParent * aMatrix;
+ }
+
+ MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
+
+ // The following may look broken because pre-multiplying our eChildToUserSpace
+ // transform with another matrix without including our eUserSpaceToParent
+ // transform between the two wouldn't make sense. We don't expect that to
+ // ever happen though. We get here either when the identity matrix has been
+ // passed because our caller just wants our eChildToUserSpace transform, or
+ // when our eUserSpaceToParent transform has already been multiplied into the
+ // matrix that our caller passes (such as when we're called from PaintSVG).
+ return childToUser * aMatrix;
+}
+
+/* virtual */
+bool SVGViewportElement::HasValidDimensions() const {
+ return !IsInner() ||
+ ((!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
+ mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
+ (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
+ mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0));
+}
+
+SVGAnimatedViewBox* SVGViewportElement::GetAnimatedViewBox() {
+ return &mViewBox;
+}
+
+SVGAnimatedPreserveAspectRatio*
+SVGViewportElement::GetAnimatedPreserveAspectRatio() {
+ return &mPreserveAspectRatio;
+}
+
+bool SVGViewportElement::ShouldSynthesizeViewBox() const {
+ MOZ_ASSERT(!HasViewBox(), "Should only be called if we lack a viewBox");
+
+ return IsRootSVGSVGElement() && OwnerDoc()->IsBeingUsedAsImage();
+}
+
+//----------------------------------------------------------------------
+// implementation helpers
+
+SVGViewBox SVGViewportElement::GetViewBoxWithSynthesis(
+ float aViewportWidth, float aViewportHeight) const {
+ if (GetViewBoxInternal().HasRect()) {
+ return GetViewBoxInternal().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 SVGViewBox(
+ 0, 0,
+ ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
+ mViewportSize.width, this),
+ ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
+ mViewportSize.height, this));
+ }
+
+ // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
+ // to having a viewBox that exactly matches our viewport size.
+ return SVGViewBox(0, 0, aViewportWidth, aViewportHeight);
+}
+
+SVGElement::LengthAttributesInfo SVGViewportElement::GetLengthInfo() {
+ return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
+ ArrayLength(sLengthInfo));
+}
+
+} // namespace mozilla::dom