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 "mozilla/dom/SVGSVGElement.h"
10 #include "mozilla/dom/SVGViewElement.h"
11 #include "mozilla/SVGOuterSVGFrame.h"
12 #include "nsCharSeparatedTokenizer.h"
13 #include "SVGAnimatedTransformList.h"
14 
15 namespace mozilla {
16 
17 using namespace dom;
18 
IsMatchingParameter(const nsAString & aString,const nsAString & aParameterName)19 static bool IsMatchingParameter(const nsAString& aString,
20                                 const nsAString& aParameterName) {
21   // The first two tests ensure aString.Length() > aParameterName.Length()
22   // so it's then safe to do the third test
23   return StringBeginsWith(aString, aParameterName) && aString.Last() == ')' &&
24          aString.CharAt(aParameterName.Length()) == '(';
25 }
26 
GetViewElement(Document * aDocument,const nsAString & aId)27 static SVGViewElement* GetViewElement(Document* aDocument,
28                                       const nsAString& aId) {
29   Element* element = aDocument->GetElementById(aId);
30   return (element && element->IsSVGElement(nsGkAtoms::view))
31              ? static_cast<SVGViewElement*>(element)
32              : nullptr;
33 }
34 
35 // Handles setting/clearing the root's mSVGView pointer.
36 class MOZ_RAII AutoSVGViewHandler {
37  public:
AutoSVGViewHandler(SVGSVGElement * aRoot)38   explicit AutoSVGViewHandler(SVGSVGElement* aRoot)
39       : mRoot(aRoot), mValid(false) {
40     mWasOverridden = mRoot->UseCurrentView();
41     mRoot->mSVGView = nullptr;
42     mRoot->mCurrentViewID = nullptr;
43   }
44 
~AutoSVGViewHandler()45   ~AutoSVGViewHandler() {
46     if (!mWasOverridden && !mValid) {
47       // we weren't overridden before and we aren't
48       // overridden now so nothing has changed.
49       return;
50     }
51     if (mValid) {
52       mRoot->mSVGView = std::move(mSVGView);
53     }
54     mRoot->InvalidateTransformNotifyFrame();
55     if (nsIFrame* f = mRoot->GetPrimaryFrame()) {
56       if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) {
57         osf->MaybeSendIntrinsicSizeAndRatioToEmbedder();
58       }
59     }
60   }
61 
CreateSVGView()62   void CreateSVGView() {
63     MOZ_ASSERT(!mSVGView, "CreateSVGView should not be called multiple times");
64     mSVGView = MakeUnique<SVGView>();
65   }
66 
ProcessAttr(const nsAString & aToken,const nsAString & aParams)67   bool ProcessAttr(const nsAString& aToken, const nsAString& aParams) {
68     MOZ_ASSERT(mSVGView, "CreateSVGView should have been called");
69 
70     // SVGViewAttributes may occur in any order, but each type may only occur
71     // at most one time in a correctly formed SVGViewSpec.
72     // If we encounter any attribute more than once or get any syntax errors
73     // we're going to return false and cancel any changes.
74 
75     if (IsMatchingParameter(aToken, u"viewBox"_ns)) {
76       if (mSVGView->mViewBox.IsExplicitlySet() ||
77           NS_FAILED(
78               mSVGView->mViewBox.SetBaseValueString(aParams, mRoot, false))) {
79         return false;
80       }
81     } else if (IsMatchingParameter(aToken, u"preserveAspectRatio"_ns)) {
82       if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() ||
83           NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString(
84               aParams, mRoot, false))) {
85         return false;
86       }
87     } else if (IsMatchingParameter(aToken, u"transform"_ns)) {
88       if (mSVGView->mTransforms) {
89         return false;
90       }
91       mSVGView->mTransforms = MakeUnique<SVGAnimatedTransformList>();
92       if (NS_FAILED(
93               mSVGView->mTransforms->SetBaseValueString(aParams, mRoot))) {
94         return false;
95       }
96     } else if (IsMatchingParameter(aToken, u"zoomAndPan"_ns)) {
97       if (mSVGView->mZoomAndPan.IsExplicitlySet()) {
98         return false;
99       }
100       nsAtom* valAtom = NS_GetStaticAtom(aParams);
101       if (!valAtom || !mSVGView->mZoomAndPan.SetBaseValueAtom(valAtom, mRoot)) {
102         return false;
103       }
104     } else {
105       return false;
106     }
107     return true;
108   }
109 
SetValid()110   void SetValid() { mValid = true; }
111 
112  private:
113   SVGSVGElement* mRoot;
114   UniquePtr<SVGView> mSVGView;
115   bool mValid;
116   bool mWasOverridden;
117 };
118 
ProcessSVGViewSpec(const nsAString & aViewSpec,SVGSVGElement * aRoot)119 bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec,
120                                                SVGSVGElement* aRoot) {
121   AutoSVGViewHandler viewHandler(aRoot);
122 
123   if (!IsMatchingParameter(aViewSpec, u"svgView"_ns)) {
124     return false;
125   }
126 
127   // Each token is a SVGViewAttribute
128   int32_t bracketPos = aViewSpec.FindChar('(');
129   uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2;
130   nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> tokenizer(
131       Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';');
132 
133   if (!tokenizer.hasMoreTokens()) {
134     return false;
135   }
136   viewHandler.CreateSVGView();
137 
138   do {
139     nsAutoString token(tokenizer.nextToken());
140 
141     bracketPos = token.FindChar('(');
142     if (bracketPos < 1 || token.Last() != ')') {
143       // invalid SVGViewAttribute syntax
144       return false;
145     }
146 
147     const nsAString& params =
148         Substring(token, bracketPos + 1, token.Length() - bracketPos - 2);
149 
150     if (!viewHandler.ProcessAttr(token, params)) {
151       return false;
152     }
153 
154   } while (tokenizer.hasMoreTokens());
155 
156   viewHandler.SetValid();
157   return true;
158 }
159 
ProcessFragmentIdentifier(Document * aDocument,const nsAString & aAnchorName)160 bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
161     Document* aDocument, const nsAString& aAnchorName) {
162   MOZ_ASSERT(aDocument->GetRootElement()->IsSVGElement(nsGkAtoms::svg),
163              "expecting an SVG root element");
164 
165   SVGSVGElement* rootElement =
166       static_cast<SVGSVGElement*>(aDocument->GetRootElement());
167 
168   const SVGViewElement* viewElement = GetViewElement(aDocument, aAnchorName);
169 
170   if (viewElement) {
171     if (!rootElement->mCurrentViewID) {
172       rootElement->mCurrentViewID = MakeUnique<nsString>();
173     }
174     *rootElement->mCurrentViewID = aAnchorName;
175     rootElement->mSVGView = nullptr;
176     rootElement->InvalidateTransformNotifyFrame();
177     if (nsIFrame* f = rootElement->GetPrimaryFrame()) {
178       if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) {
179         osf->MaybeSendIntrinsicSizeAndRatioToEmbedder();
180       }
181     }
182     // not an svgView()-style fragment identifier, return false so the caller
183     // continues processing to match any :target pseudo elements
184     return false;
185   }
186 
187   return ProcessSVGViewSpec(aAnchorName, rootElement);
188 }
189 
190 }  // namespace mozilla
191