SVGFragmentIdentifier.cpp (7529B)
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 "SVGAnimatedTransformList.h" 10 #include "mozilla/MediaFragmentURIParser.h" 11 #include "mozilla/SVGOuterSVGFrame.h" 12 #include "mozilla/dom/SVGSVGElement.h" 13 #include "mozilla/dom/SVGViewElement.h" 14 #include "nsCharSeparatedTokenizer.h" 15 16 namespace mozilla { 17 18 using namespace dom; 19 20 static bool IsMatchingParameter(const nsAString& aString, 21 const nsAString& aParameterName) { 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) && aString.Last() == ')' && 25 aString.CharAt(aParameterName.Length()) == '('; 26 } 27 28 // Handles setting/clearing the root's mSVGView pointer. 29 class MOZ_RAII AutoSVGViewHandler { 30 public: 31 explicit AutoSVGViewHandler(SVGSVGElement* aRoot) 32 : mRoot(aRoot), mValid(false) { 33 mWasOverridden = mRoot->UseCurrentView(); 34 mRoot->mSVGView = nullptr; 35 mRoot->mCurrentViewID = nullptr; 36 } 37 38 ~AutoSVGViewHandler() { 39 if (!mWasOverridden && !mValid) { 40 // we weren't overridden before and we aren't 41 // overridden now so nothing has changed. 42 return; 43 } 44 if (mValid) { 45 mRoot->mSVGView = std::move(mSVGView); 46 } 47 mRoot->DidChangeSVGView(); 48 if (SVGOuterSVGFrame* osf = do_QueryFrame(mRoot->GetPrimaryFrame())) { 49 osf->MaybeSendIntrinsicSizeAndRatioToEmbedder(); 50 } 51 } 52 53 void CreateSVGView() { 54 MOZ_ASSERT(!mSVGView, "CreateSVGView should not be called multiple times"); 55 mSVGView = MakeUnique<SVGView>(); 56 } 57 58 void SetViewBox(const gfx::Rect& aRect) { 59 SVGViewBox viewBox(aRect.x, aRect.y, aRect.width, aRect.height); 60 mSVGView->mViewBox.SetBaseValue(viewBox, mRoot, true); 61 mValid = true; 62 } 63 64 bool ProcessAttr(const nsAString& aToken, const nsAString& aParams) { 65 MOZ_ASSERT(mSVGView, "CreateSVGView should have been called"); 66 67 // SVGViewAttributes may occur in any order, but each type may only occur 68 // at most one time in a correctly formed SVGViewSpec. 69 // If we encounter any attribute more than once or get any syntax errors 70 // we're going to return false and cancel any changes. 71 72 if (IsMatchingParameter(aToken, u"viewBox"_ns)) { 73 if (mSVGView->mViewBox.IsExplicitlySet() || 74 NS_FAILED( 75 mSVGView->mViewBox.SetBaseValueString(aParams, mRoot, false))) { 76 return false; 77 } 78 } else if (IsMatchingParameter(aToken, u"preserveAspectRatio"_ns)) { 79 if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() || 80 NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString( 81 aParams, mRoot, false))) { 82 return false; 83 } 84 } else if (IsMatchingParameter(aToken, u"transform"_ns)) { 85 if (mSVGView->mTransforms) { 86 return false; 87 } 88 mSVGView->mTransforms = MakeUnique<SVGAnimatedTransformList>(); 89 if (NS_FAILED( 90 mSVGView->mTransforms->SetBaseValueString(aParams, mRoot))) { 91 return false; 92 } 93 } else if (IsMatchingParameter(aToken, u"zoomAndPan"_ns)) { 94 if (mSVGView->mZoomAndPan.IsExplicitlySet()) { 95 return false; 96 } 97 nsAtom* valAtom = NS_GetStaticAtom(aParams); 98 if (!valAtom || !mSVGView->mZoomAndPan.SetBaseValueAtom(valAtom, mRoot)) { 99 return false; 100 } 101 } else { 102 return false; 103 } 104 return true; 105 } 106 107 void SetValid() { mValid = true; } 108 109 private: 110 SVGSVGElement* mRoot; 111 UniquePtr<SVGView> mSVGView; 112 bool mValid; 113 bool mWasOverridden; 114 }; 115 116 bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec, 117 SVGSVGElement* aRoot) { 118 AutoSVGViewHandler viewHandler(aRoot); 119 120 if (!IsMatchingParameter(aViewSpec, u"svgView"_ns)) { 121 return false; 122 } 123 124 // Each token is a SVGViewAttribute 125 int32_t bracketPos = aViewSpec.FindChar('('); 126 uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2; 127 nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> tokenizer( 128 Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';'); 129 130 if (!tokenizer.hasMoreTokens()) { 131 return false; 132 } 133 viewHandler.CreateSVGView(); 134 135 do { 136 nsAutoString token(tokenizer.nextToken()); 137 138 bracketPos = token.FindChar('('); 139 if (bracketPos < 1 || token.Last() != ')') { 140 // invalid SVGViewAttribute syntax 141 return false; 142 } 143 144 const nsAString& params = 145 Substring(token, bracketPos + 1, token.Length() - bracketPos - 2); 146 147 if (!viewHandler.ProcessAttr(token, params)) { 148 return false; 149 } 150 151 } while (tokenizer.hasMoreTokens()); 152 153 viewHandler.SetValid(); 154 return true; 155 } 156 157 static float PxLengthOrFallback(const LengthPercentage& aLenPct, 158 CSSIntCoord aFallback) { 159 if (!aLenPct.IsLength()) { 160 return aFallback; 161 } 162 return aLenPct.AsLength().ToCSSPixels(); 163 } 164 165 bool SVGFragmentIdentifier::ProcessMediaFragment( 166 const nsAString& aMediaFragment, SVGSVGElement* aRoot) { 167 NS_ConvertUTF16toUTF8 mediaFragment(aMediaFragment); 168 MediaFragmentURIParser parser(mediaFragment); 169 170 bool foundMediaFragment = false; 171 172 if (parser.HasStartTime()) { 173 aRoot->SetCurrentTime(parser.GetStartTime()); 174 foundMediaFragment = true; 175 } 176 if (parser.HasEndTime()) { 177 // pause animations at end time. 178 aRoot->PauseAnimationsAt(parser.GetEndTime()); 179 foundMediaFragment = true; 180 } 181 if (parser.HasClip()) { 182 gfx::Rect rect = IntRectToRect(parser.GetClip()); 183 if (parser.GetClipUnit() == eClipUnit_Percent) { 184 float width = PxLengthOrFallback(aRoot->GetIntrinsicWidth(), 185 kFallbackIntrinsicWidthInPixels); 186 float height = PxLengthOrFallback(aRoot->GetIntrinsicHeight(), 187 kFallbackIntrinsicHeightInPixels); 188 rect.Scale(width / 100.0f, height / 100.0f); 189 } 190 AutoSVGViewHandler viewHandler(aRoot); 191 viewHandler.CreateSVGView(); 192 viewHandler.SetViewBox(rect); 193 foundMediaFragment = true; 194 } 195 196 return foundMediaFragment; 197 } 198 199 bool SVGFragmentIdentifier::ProcessFragmentIdentifier( 200 Document* aDocument, const nsAString& aAnchorName) { 201 MOZ_ASSERT(aDocument->GetSVGRootElement(), "expecting an SVG root element"); 202 203 auto* rootElement = SVGSVGElement::FromNode(aDocument->GetRootElement()); 204 205 if (SVGViewElement::FromNodeOrNull(aDocument->GetElementById(aAnchorName))) { 206 if (!rootElement->mCurrentViewID) { 207 rootElement->mCurrentViewID = MakeUnique<nsString>(); 208 } 209 *rootElement->mCurrentViewID = aAnchorName; 210 rootElement->mSVGView = nullptr; 211 rootElement->InvalidateTransformNotifyFrame(); 212 if (nsIFrame* f = rootElement->GetPrimaryFrame()) { 213 if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) { 214 osf->MaybeSendIntrinsicSizeAndRatioToEmbedder(); 215 } 216 } 217 // not an svgView()-style fragment identifier, return false so the caller 218 // continues processing to match any :target pseudo elements 219 return false; 220 } 221 222 if (ProcessSVGViewSpec(aAnchorName, rootElement)) { 223 return true; 224 } 225 if (ProcessMediaFragment(aAnchorName, rootElement)) { 226 return true; 227 } 228 return false; 229 } 230 231 } // namespace mozilla