tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SVGViewportElement.cpp (11700B)


      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/dom/SVGViewportElement.h"
      8 
      9 #include <stdint.h>
     10 
     11 #include <algorithm>
     12 
     13 #include "DOMSVGLength.h"
     14 #include "DOMSVGPoint.h"
     15 #include "mozilla/AlreadyAddRefed.h"
     16 #include "mozilla/ContentEvents.h"
     17 #include "mozilla/EventDispatcher.h"
     18 #include "mozilla/SMILTypes.h"
     19 #include "mozilla/SVGContentUtils.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/SVGLengthBinding.h"
     22 #include "mozilla/dom/SVGViewElement.h"
     23 #include "nsContentUtils.h"
     24 #include "nsError.h"
     25 #include "nsFrameSelection.h"
     26 #include "nsGkAtoms.h"
     27 #include "nsIFrame.h"
     28 #include "nsLayoutUtils.h"
     29 #include "nsStyleUtil.h"
     30 #include "prtime.h"
     31 
     32 using namespace mozilla::gfx;
     33 
     34 namespace mozilla::dom {
     35 
     36 SVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] = {
     37    {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
     38     SVGContentUtils::X},
     39    {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER,
     40     SVGContentUtils::Y},
     41    {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
     42     SVGContentUtils::X},
     43    {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
     44     SVGContentUtils::Y},
     45 };
     46 
     47 //----------------------------------------------------------------------
     48 // Implementation
     49 
     50 SVGViewportElement::SVGViewportElement(
     51    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     52    : SVGGraphicsElement(std::move(aNodeInfo)),
     53      mHasChildrenOnlyTransform(false) {}
     54 
     55 //----------------------------------------------------------------------
     56 
     57 already_AddRefed<SVGAnimatedRect> SVGViewportElement::ViewBox() {
     58  return mViewBox.ToSVGAnimatedRect(this);
     59 }
     60 
     61 already_AddRefed<DOMSVGAnimatedPreserveAspectRatio>
     62 SVGViewportElement::PreserveAspectRatio() {
     63  return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this);
     64 }
     65 
     66 //----------------------------------------------------------------------
     67 // nsIContent methods
     68 
     69 NS_IMETHODIMP_(bool)
     70 SVGViewportElement::IsAttributeMapped(const nsAtom* name) const {
     71  // We want to map the 'width' and 'height' attributes into style for
     72  // outer-<svg>, except when the attributes aren't set (since their default
     73  // values of '100%' can cause unexpected and undesirable behaviour for SVG
     74  // inline in HTML). We rely on SVGElement::UpdateContentStyleRule() to
     75  // prevent mapping of the default values into style (it only maps attributes
     76  // that are set). We also rely on a check in SVGElement::
     77  // UpdateContentStyleRule() to prevent us mapping the attributes when they're
     78  // given a <length> value that is not currently recognized by the SVG
     79  // specification.
     80 
     81  if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) {
     82    return true;
     83  }
     84 
     85  return SVGGraphicsElement::IsAttributeMapped(name);
     86 }
     87 
     88 //----------------------------------------------------------------------
     89 // SVGElement overrides
     90 
     91 // Helper for GetViewBoxTransform on root <svg> node
     92 // * aLength: internal value for our <svg> width or height attribute.
     93 // * aViewportLength: length of the corresponding dimension of the viewport.
     94 // * aSelf: the outermost <svg> node itself.
     95 // NOTE: aSelf is not an ancestor viewport element, so it can't be used to
     96 // resolve percentage lengths. (It can only be used to resolve
     97 // 'em'/'ex'-valued units).
     98 inline float ComputeSynthesizedViewBoxDimension(
     99    const SVGAnimatedLength& aLength, float aViewportLength,
    100    const SVGViewportElement* aSelf) {
    101  if (aLength.IsPercentage()) {
    102    return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
    103  }
    104 
    105  return aLength.GetAnimValueWithZoom(aSelf);
    106 }
    107 
    108 //----------------------------------------------------------------------
    109 // public helpers:
    110 
    111 void SVGViewportElement::UpdateHasChildrenOnlyTransform() {
    112  mHasChildrenOnlyTransform =
    113      HasViewBoxOrSyntheticViewBox() ||
    114      (IsRootSVGSVGElement() &&
    115       static_cast<SVGSVGElement*>(this)->IsScaledOrTranslated());
    116 }
    117 
    118 void SVGViewportElement::ChildrenOnlyTransformChanged(
    119    ChildrenOnlyTransformChangedFlags aFlags) {
    120  // Avoid wasteful calls:
    121  MOZ_ASSERT(!GetPrimaryFrame()->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
    122             "Non-display SVG frames don't maintain overflow rects");
    123 
    124  nsChangeHint changeHint;
    125 
    126  bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform;
    127 
    128  UpdateHasChildrenOnlyTransform();
    129 
    130  if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) {
    131    // Reconstruct the frame tree to handle stacking context changes:
    132    // XXXjwatt don't do this for root-<svg> or even outer-<svg>?
    133    changeHint = nsChangeHint_ReconstructFrame;
    134  } else {
    135    // We just assume the old and new transforms are different.
    136    changeHint = nsChangeHint(nsChangeHint_UpdateOverflow |
    137                              nsChangeHint_ChildrenOnlyTransform);
    138  }
    139 
    140  // If we're not reconstructing the frame tree, then we only call
    141  // PostRestyleEvent if we're not being called under reflow to avoid recursing
    142  // to death. See bug 767056 comments 10 and 12. Since our SVGOuterSVGFrame
    143  // is being reflowed we're going to invalidate and repaint its entire area
    144  // anyway (which will include our children).
    145  if ((changeHint & nsChangeHint_ReconstructFrame) ||
    146      !aFlags.contains(ChildrenOnlyTransformChangedFlag::DuringReflow)) {
    147    nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint);
    148  }
    149 }
    150 
    151 gfx::Matrix SVGViewportElement::GetViewBoxTransform() const {
    152  float viewportWidth, viewportHeight;
    153  if (IsInner()) {
    154    SVGElementMetrics metrics(this);
    155    viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValueWithZoom(metrics);
    156    viewportHeight =
    157        mLengthAttributes[ATTR_HEIGHT].GetAnimValueWithZoom(metrics);
    158  } else {
    159    viewportWidth = mViewportSize.width;
    160    viewportHeight = mViewportSize.height;
    161  }
    162 
    163  if (!std::isfinite(viewportWidth) || viewportWidth <= 0.0f ||
    164      !std::isfinite(viewportHeight) || viewportHeight <= 0.0f) {
    165    return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);  // singular
    166  }
    167 
    168  SVGViewBox viewBox = GetViewBoxWithSynthesis(viewportWidth, viewportHeight);
    169 
    170  if (!viewBox.IsValid()) {
    171    return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);  // singular
    172  }
    173 
    174  return SVGContentUtils::GetViewBoxTransform(
    175      viewportWidth, viewportHeight, viewBox.x, viewBox.y, viewBox.width,
    176      viewBox.height, GetPreserveAspectRatioWithOverride());
    177 }
    178 //----------------------------------------------------------------------
    179 // SVGViewportElement
    180 
    181 float SVGViewportElement::GetLength(uint8_t aCtxType) const {
    182  const auto& animatedViewBox = GetViewBoxInternal();
    183  float h = 0.0f, w = 0.0f;
    184  bool shouldComputeWidth =
    185           (aCtxType == SVGContentUtils::X || aCtxType == SVGContentUtils::XY),
    186       shouldComputeHeight =
    187           (aCtxType == SVGContentUtils::Y || aCtxType == SVGContentUtils::XY);
    188 
    189  if (animatedViewBox.HasRect()) {
    190    float zoom = UserSpaceMetrics::GetZoom(this);
    191    const auto& viewbox = animatedViewBox.GetAnimValue() * zoom;
    192    w = viewbox.width;
    193    h = viewbox.height;
    194  } else if (IsInner()) {
    195    // Resolving length for inner <svg> is exactly the same as other
    196    // ordinary element. We shouldn't use the SVGViewportElement overload
    197    // of GetAnimValue().
    198    SVGElementMetrics metrics(this);
    199    if (shouldComputeWidth) {
    200      w = mLengthAttributes[ATTR_WIDTH].GetAnimValueWithZoom(metrics);
    201    }
    202    if (shouldComputeHeight) {
    203      h = mLengthAttributes[ATTR_HEIGHT].GetAnimValueWithZoom(metrics);
    204    }
    205  } else if (ShouldSynthesizeViewBox()) {
    206    if (shouldComputeWidth) {
    207      w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
    208                                             mViewportSize.width, this);
    209    }
    210    if (shouldComputeHeight) {
    211      h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
    212                                             mViewportSize.height, this);
    213    }
    214  } else {
    215    w = mViewportSize.width;
    216    h = mViewportSize.height;
    217  }
    218 
    219  w = std::max(w, 0.0f);
    220  h = std::max(h, 0.0f);
    221 
    222  switch (aCtxType) {
    223    case SVGContentUtils::X:
    224      return w;
    225    case SVGContentUtils::Y:
    226      return h;
    227    case SVGContentUtils::XY:
    228      return float(SVGContentUtils::ComputeNormalizedHypotenuse(w, h));
    229  }
    230  return 0;
    231 }
    232 
    233 //----------------------------------------------------------------------
    234 // SVGElement methods
    235 
    236 /* virtual */
    237 gfxMatrix SVGViewportElement::ChildToUserSpaceTransform() const {
    238  auto viewBox = GetViewBoxTransform();
    239  if (IsInner()) {
    240    float x, y;
    241    const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y,
    242                                                                   nullptr);
    243    return ThebesMatrix(viewBox.PostTranslate(x, y));
    244  }
    245  if (IsRootSVGSVGElement()) {
    246    const auto* svg = static_cast<const SVGSVGElement*>(this);
    247    const SVGPoint& translate = svg->GetCurrentTranslate();
    248    float scale = svg->CurrentScale();
    249    return ThebesMatrix(viewBox.PostScale(scale, scale)
    250                            .PostTranslate(translate.GetX(), translate.GetY()));
    251  }
    252  // outer-<svg>, but inline in some other content:
    253  return ThebesMatrix(viewBox);
    254 }
    255 
    256 /* virtual */
    257 bool SVGViewportElement::HasValidDimensions() const {
    258  return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
    259          mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
    260         (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
    261          mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0);
    262 }
    263 
    264 SVGAnimatedViewBox* SVGViewportElement::GetAnimatedViewBox() {
    265  return &mViewBox;
    266 }
    267 
    268 SVGAnimatedPreserveAspectRatio*
    269 SVGViewportElement::GetAnimatedPreserveAspectRatio() {
    270  return &mPreserveAspectRatio;
    271 }
    272 
    273 bool SVGViewportElement::ShouldSynthesizeViewBox() const {
    274  MOZ_ASSERT(!HasViewBox(), "Should only be called if we lack a viewBox");
    275 
    276  // We synthesize a viewbox if we're the root node of an SVG image
    277  // document (and lack an explicit viewBox), as long as our width &
    278  // height attributes wouldn't yield an empty synthesized viewbox.
    279  return IsRootSVGSVGElement() && OwnerDoc()->IsBeingUsedAsImage() &&
    280         HasValidDimensions();
    281 }
    282 
    283 //----------------------------------------------------------------------
    284 // implementation helpers
    285 
    286 SVGViewBox SVGViewportElement::GetViewBoxWithSynthesis(
    287    float aViewportWidth, float aViewportHeight) const {
    288  const auto& animatedViewBox = GetViewBoxInternal();
    289  if (animatedViewBox.HasRect()) {
    290    float zoom = UserSpaceMetrics::GetZoom(this);
    291    return animatedViewBox.GetAnimValue() * zoom;
    292  }
    293 
    294  if (ShouldSynthesizeViewBox()) {
    295    // Special case -- fake a viewBox, using height & width attrs.
    296    // (Use |this| as context, since if we get here, we're outermost <svg>.)
    297    return SVGViewBox(
    298        0, 0,
    299        ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
    300                                           mViewportSize.width, this),
    301        ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
    302                                           mViewportSize.height, this));
    303  }
    304 
    305  // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
    306  // to having a viewBox that exactly matches our viewport size.
    307  return SVGViewBox(0, 0, aViewportWidth, aViewportHeight);
    308 }
    309 
    310 SVGElement::LengthAttributesInfo SVGViewportElement::GetLengthInfo() {
    311  return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
    312                              std::size(sLengthInfo));
    313 }
    314 
    315 }  // namespace mozilla::dom