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