tor-browser

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

SVGFragmentIdentifier.cpp (7529B)


      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 "SVGFragmentIdentifier.h"
      8 
      9 #include "SVGAnimatedTransformList.h"
     10 #include "mozilla/MediaFragmentURIParser.h"
     11 #include "mozilla/SVGOuterSVGFrame.h"
     12 #include "mozilla/dom/SVGSVGElement.h"
     13 #include "mozilla/dom/SVGViewElement.h"
     14 #include "nsCharSeparatedTokenizer.h"
     15 
     16 namespace mozilla {
     17 
     18 using namespace dom;
     19 
     20 static bool IsMatchingParameter(const nsAString& aString,
     21                                const nsAString& aParameterName) {
     22  // The first two tests ensure aString.Length() > aParameterName.Length()
     23  // so it's then safe to do the third test
     24  return StringBeginsWith(aString, aParameterName) && aString.Last() == ')' &&
     25         aString.CharAt(aParameterName.Length()) == '(';
     26 }
     27 
     28 // Handles setting/clearing the root's mSVGView pointer.
     29 class MOZ_RAII AutoSVGViewHandler {
     30 public:
     31  explicit AutoSVGViewHandler(SVGSVGElement* aRoot)
     32      : mRoot(aRoot), mValid(false) {
     33    mWasOverridden = mRoot->UseCurrentView();
     34    mRoot->mSVGView = nullptr;
     35    mRoot->mCurrentViewID = nullptr;
     36  }
     37 
     38  ~AutoSVGViewHandler() {
     39    if (!mWasOverridden && !mValid) {
     40      // we weren't overridden before and we aren't
     41      // overridden now so nothing has changed.
     42      return;
     43    }
     44    if (mValid) {
     45      mRoot->mSVGView = std::move(mSVGView);
     46    }
     47    mRoot->DidChangeSVGView();
     48    if (SVGOuterSVGFrame* osf = do_QueryFrame(mRoot->GetPrimaryFrame())) {
     49      osf->MaybeSendIntrinsicSizeAndRatioToEmbedder();
     50    }
     51  }
     52 
     53  void CreateSVGView() {
     54    MOZ_ASSERT(!mSVGView, "CreateSVGView should not be called multiple times");
     55    mSVGView = MakeUnique<SVGView>();
     56  }
     57 
     58  void SetViewBox(const gfx::Rect& aRect) {
     59    SVGViewBox viewBox(aRect.x, aRect.y, aRect.width, aRect.height);
     60    mSVGView->mViewBox.SetBaseValue(viewBox, mRoot, true);
     61    mValid = true;
     62  }
     63 
     64  bool ProcessAttr(const nsAString& aToken, const nsAString& aParams) {
     65    MOZ_ASSERT(mSVGView, "CreateSVGView should have been called");
     66 
     67    // SVGViewAttributes may occur in any order, but each type may only occur
     68    // at most one time in a correctly formed SVGViewSpec.
     69    // If we encounter any attribute more than once or get any syntax errors
     70    // we're going to return false and cancel any changes.
     71 
     72    if (IsMatchingParameter(aToken, u"viewBox"_ns)) {
     73      if (mSVGView->mViewBox.IsExplicitlySet() ||
     74          NS_FAILED(
     75              mSVGView->mViewBox.SetBaseValueString(aParams, mRoot, false))) {
     76        return false;
     77      }
     78    } else if (IsMatchingParameter(aToken, u"preserveAspectRatio"_ns)) {
     79      if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() ||
     80          NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString(
     81              aParams, mRoot, false))) {
     82        return false;
     83      }
     84    } else if (IsMatchingParameter(aToken, u"transform"_ns)) {
     85      if (mSVGView->mTransforms) {
     86        return false;
     87      }
     88      mSVGView->mTransforms = MakeUnique<SVGAnimatedTransformList>();
     89      if (NS_FAILED(
     90              mSVGView->mTransforms->SetBaseValueString(aParams, mRoot))) {
     91        return false;
     92      }
     93    } else if (IsMatchingParameter(aToken, u"zoomAndPan"_ns)) {
     94      if (mSVGView->mZoomAndPan.IsExplicitlySet()) {
     95        return false;
     96      }
     97      nsAtom* valAtom = NS_GetStaticAtom(aParams);
     98      if (!valAtom || !mSVGView->mZoomAndPan.SetBaseValueAtom(valAtom, mRoot)) {
     99        return false;
    100      }
    101    } else {
    102      return false;
    103    }
    104    return true;
    105  }
    106 
    107  void SetValid() { mValid = true; }
    108 
    109 private:
    110  SVGSVGElement* mRoot;
    111  UniquePtr<SVGView> mSVGView;
    112  bool mValid;
    113  bool mWasOverridden;
    114 };
    115 
    116 bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec,
    117                                               SVGSVGElement* aRoot) {
    118  AutoSVGViewHandler viewHandler(aRoot);
    119 
    120  if (!IsMatchingParameter(aViewSpec, u"svgView"_ns)) {
    121    return false;
    122  }
    123 
    124  // Each token is a SVGViewAttribute
    125  int32_t bracketPos = aViewSpec.FindChar('(');
    126  uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2;
    127  nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> tokenizer(
    128      Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';');
    129 
    130  if (!tokenizer.hasMoreTokens()) {
    131    return false;
    132  }
    133  viewHandler.CreateSVGView();
    134 
    135  do {
    136    nsAutoString token(tokenizer.nextToken());
    137 
    138    bracketPos = token.FindChar('(');
    139    if (bracketPos < 1 || token.Last() != ')') {
    140      // invalid SVGViewAttribute syntax
    141      return false;
    142    }
    143 
    144    const nsAString& params =
    145        Substring(token, bracketPos + 1, token.Length() - bracketPos - 2);
    146 
    147    if (!viewHandler.ProcessAttr(token, params)) {
    148      return false;
    149    }
    150 
    151  } while (tokenizer.hasMoreTokens());
    152 
    153  viewHandler.SetValid();
    154  return true;
    155 }
    156 
    157 static float PxLengthOrFallback(const LengthPercentage& aLenPct,
    158                                CSSIntCoord aFallback) {
    159  if (!aLenPct.IsLength()) {
    160    return aFallback;
    161  }
    162  return aLenPct.AsLength().ToCSSPixels();
    163 }
    164 
    165 bool SVGFragmentIdentifier::ProcessMediaFragment(
    166    const nsAString& aMediaFragment, SVGSVGElement* aRoot) {
    167  NS_ConvertUTF16toUTF8 mediaFragment(aMediaFragment);
    168  MediaFragmentURIParser parser(mediaFragment);
    169 
    170  bool foundMediaFragment = false;
    171 
    172  if (parser.HasStartTime()) {
    173    aRoot->SetCurrentTime(parser.GetStartTime());
    174    foundMediaFragment = true;
    175  }
    176  if (parser.HasEndTime()) {
    177    // pause animations at end time.
    178    aRoot->PauseAnimationsAt(parser.GetEndTime());
    179    foundMediaFragment = true;
    180  }
    181  if (parser.HasClip()) {
    182    gfx::Rect rect = IntRectToRect(parser.GetClip());
    183    if (parser.GetClipUnit() == eClipUnit_Percent) {
    184      float width = PxLengthOrFallback(aRoot->GetIntrinsicWidth(),
    185                                       kFallbackIntrinsicWidthInPixels);
    186      float height = PxLengthOrFallback(aRoot->GetIntrinsicHeight(),
    187                                        kFallbackIntrinsicHeightInPixels);
    188      rect.Scale(width / 100.0f, height / 100.0f);
    189    }
    190    AutoSVGViewHandler viewHandler(aRoot);
    191    viewHandler.CreateSVGView();
    192    viewHandler.SetViewBox(rect);
    193    foundMediaFragment = true;
    194  }
    195 
    196  return foundMediaFragment;
    197 }
    198 
    199 bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
    200    Document* aDocument, const nsAString& aAnchorName) {
    201  MOZ_ASSERT(aDocument->GetSVGRootElement(), "expecting an SVG root element");
    202 
    203  auto* rootElement = SVGSVGElement::FromNode(aDocument->GetRootElement());
    204 
    205  if (SVGViewElement::FromNodeOrNull(aDocument->GetElementById(aAnchorName))) {
    206    if (!rootElement->mCurrentViewID) {
    207      rootElement->mCurrentViewID = MakeUnique<nsString>();
    208    }
    209    *rootElement->mCurrentViewID = aAnchorName;
    210    rootElement->mSVGView = nullptr;
    211    rootElement->InvalidateTransformNotifyFrame();
    212    if (nsIFrame* f = rootElement->GetPrimaryFrame()) {
    213      if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) {
    214        osf->MaybeSendIntrinsicSizeAndRatioToEmbedder();
    215      }
    216    }
    217    // not an svgView()-style fragment identifier, return false so the caller
    218    // continues processing to match any :target pseudo elements
    219    return false;
    220  }
    221 
    222  if (ProcessSVGViewSpec(aAnchorName, rootElement)) {
    223    return true;
    224  }
    225  if (ProcessMediaFragment(aAnchorName, rootElement)) {
    226    return true;
    227  }
    228  return false;
    229 }
    230 
    231 }  // namespace mozilla