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 ¶ms =
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