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