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/. */
7 #include "SVGFragmentIdentifier.h"
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"
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()) == '(';
27 // Handles setting/clearing the root's mSVGView pointer.
28 class MOZ_RAII AutoSVGViewHandler
{
30 explicit AutoSVGViewHandler(SVGSVGElement
* aRoot
)
31 : mRoot(aRoot
), mValid(false) {
32 mWasOverridden
= mRoot
->UseCurrentView();
33 mRoot
->mSVGView
= nullptr;
34 mRoot
->mCurrentViewID
= nullptr;
37 ~AutoSVGViewHandler() {
38 if (!mWasOverridden
&& !mValid
) {
39 // we weren't overridden before and we aren't
40 // overridden now so nothing has changed.
44 mRoot
->mSVGView
= std::move(mSVGView
);
46 mRoot
->InvalidateTransformNotifyFrame();
47 if (nsIFrame
* f
= mRoot
->GetPrimaryFrame()) {
48 if (SVGOuterSVGFrame
* osf
= do_QueryFrame(f
)) {
49 osf
->MaybeSendIntrinsicSizeAndRatioToEmbedder();
54 void CreateSVGView() {
55 MOZ_ASSERT(!mSVGView
, "CreateSVGView should not be called multiple times");
56 mSVGView
= MakeUnique
<SVGView
>();
59 bool ProcessAttr(const nsAString
& aToken
, const nsAString
& aParams
) {
60 MOZ_ASSERT(mSVGView
, "CreateSVGView should have been called");
62 // SVGViewAttributes may occur in any order, but each type may only occur
63 // at most one time in a correctly formed SVGViewSpec.
64 // If we encounter any attribute more than once or get any syntax errors
65 // we're going to return false and cancel any changes.
67 if (IsMatchingParameter(aToken
, u
"viewBox"_ns
)) {
68 if (mSVGView
->mViewBox
.IsExplicitlySet() ||
70 mSVGView
->mViewBox
.SetBaseValueString(aParams
, mRoot
, false))) {
73 } else if (IsMatchingParameter(aToken
, u
"preserveAspectRatio"_ns
)) {
74 if (mSVGView
->mPreserveAspectRatio
.IsExplicitlySet() ||
75 NS_FAILED(mSVGView
->mPreserveAspectRatio
.SetBaseValueString(
76 aParams
, mRoot
, false))) {
79 } else if (IsMatchingParameter(aToken
, u
"transform"_ns
)) {
80 if (mSVGView
->mTransforms
) {
83 mSVGView
->mTransforms
= MakeUnique
<SVGAnimatedTransformList
>();
85 mSVGView
->mTransforms
->SetBaseValueString(aParams
, mRoot
))) {
88 } else if (IsMatchingParameter(aToken
, u
"zoomAndPan"_ns
)) {
89 if (mSVGView
->mZoomAndPan
.IsExplicitlySet()) {
92 nsAtom
* valAtom
= NS_GetStaticAtom(aParams
);
93 if (!valAtom
|| !mSVGView
->mZoomAndPan
.SetBaseValueAtom(valAtom
, mRoot
)) {
102 void SetValid() { mValid
= true; }
105 SVGSVGElement
* mRoot
;
106 UniquePtr
<SVGView
> mSVGView
;
111 bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString
& aViewSpec
,
112 SVGSVGElement
* aRoot
) {
113 AutoSVGViewHandler
viewHandler(aRoot
);
115 if (!IsMatchingParameter(aViewSpec
, u
"svgView"_ns
)) {
119 // Each token is a SVGViewAttribute
120 int32_t bracketPos
= aViewSpec
.FindChar('(');
121 uint32_t lengthOfViewSpec
= aViewSpec
.Length() - bracketPos
- 2;
122 nsCharSeparatedTokenizerTemplate
<NS_TokenizerIgnoreNothing
> tokenizer(
123 Substring(aViewSpec
, bracketPos
+ 1, lengthOfViewSpec
), ';');
125 if (!tokenizer
.hasMoreTokens()) {
128 viewHandler
.CreateSVGView();
131 nsAutoString
token(tokenizer
.nextToken());
133 bracketPos
= token
.FindChar('(');
134 if (bracketPos
< 1 || token
.Last() != ')') {
135 // invalid SVGViewAttribute syntax
139 const nsAString
& params
=
140 Substring(token
, bracketPos
+ 1, token
.Length() - bracketPos
- 2);
142 if (!viewHandler
.ProcessAttr(token
, params
)) {
146 } while (tokenizer
.hasMoreTokens());
148 viewHandler
.SetValid();
152 bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
153 Document
* aDocument
, const nsAString
& aAnchorName
) {
154 MOZ_ASSERT(aDocument
->GetRootElement()->IsSVGElement(nsGkAtoms::svg
),
155 "expecting an SVG root element");
157 auto* rootElement
= SVGSVGElement::FromNode(aDocument
->GetRootElement());
159 const auto* viewElement
=
160 SVGViewElement::FromNodeOrNull(aDocument
->GetElementById(aAnchorName
));
163 if (!rootElement
->mCurrentViewID
) {
164 rootElement
->mCurrentViewID
= MakeUnique
<nsString
>();
166 *rootElement
->mCurrentViewID
= aAnchorName
;
167 rootElement
->mSVGView
= nullptr;
168 rootElement
->InvalidateTransformNotifyFrame();
169 if (nsIFrame
* f
= rootElement
->GetPrimaryFrame()) {
170 if (SVGOuterSVGFrame
* osf
= do_QueryFrame(f
)) {
171 osf
->MaybeSendIntrinsicSizeAndRatioToEmbedder();
174 // not an svgView()-style fragment identifier, return false so the caller
175 // continues processing to match any :target pseudo elements
179 return ProcessSVGViewSpec(aAnchorName
, rootElement
);
182 } // namespace mozilla