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