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